wren-lang / wren

The Wren Programming Language. Wren is a small, fast, class-based concurrent scripting language.
http://wren.io
MIT License
6.9k stars 552 forks source link

No subclassing of builtins? #70

Closed tamc closed 9 years ago

tamc commented 9 years ago

This:

class SubList is List {}

var ok = new List
ok.add("All works ok")
IO.print(ok)

var problem = new SubList
problem.add("Doesn't work ok")
IO.print(problem)

Gives this:

[All works ok]
[1]    72518 segmentation fault

As of 84ead3bb18135657fb276ae09459e3be4717764d on OSX Yosemite

Is there a problem with subclassing built in classes?

munificent commented 9 years ago

Yes, in general subclassing the built-ins does work since they expect their internal bit representation to be very specific and get horribly confused when you invoke one of the inherited built-in methods on the derived type.

I haven't implemented this yet, but my tentative plan is to just seal the built-in types and make it a runtime error to subclass them. Most of the functionality you'd want to inherit is in the (as-yet-undocumented!) Sequence class, which you can subclass, so subclassing List doesn't buy you much.

Thank you for filing this bug, though. It's absolutely a bug that it silently lets you try to do this and then vomits all over itself.

verpeteren commented 9 years ago

I thought that this issue would be an easy task to start contributing with wren, but my analytical powers seem to be fading as I cannot quite find out the superclass name/type.

Questions:

1) What built-ins should may NOT be subclassed?

[?] all where  type > OBJ_CLASS
[ ] other: ....

2) Where should the afrore mentioned runtime-error should be triggered:

[?] wren_vm.c/runInterpreter inside CASE_CODE(IS).
[ ] wren_value.c/wrenNewXXX
[ ] wren_value.c/initObj
[ ] wren_value.c/wrenBindSuperclass
[ ] other: ....

3) What is the best way to find out the type of the requested superclass? It seems that everwhere I point my debugger to, the type is OBJ_CLASS.

I feel so stupid, that I cannot fullfill this task that seemed to be so trivial. But now that I started to dig in, I want to know more. Any pointers or tips are greatly appreciated.

munificent commented 9 years ago

1) What built-ins should may NOT be subclassed?

Basically, the ones that are backed by a C object whose type is not OBJ_INSTANCE. That's:

  OBJ_CLASS,
  OBJ_CLOSURE,
  OBJ_FIBER,
  OBJ_FN,
  OBJ_LIST,
  OBJ_MAP,
  OBJ_RANGE,
  OBJ_STRING

2) Where should the afrore mentioned runtime-error should be triggered:

Probably in CASE_CODE(CLASS):, right after:

// TODO: Handle the superclass not being a class object!

:)

3) What is the best way to find out the type of the requested superclass? It seems that everwhere I point my debugger to, the type is OBJ_CLASS.

The superclass is a class. Even the built-in types still have actual class objects for their classes, which is where their methods are defined. The problem with inheriting from a built-in type isn't that they don't have a real superclass—they do.

It's that the methods defined in that superclass assume the instance is a specific kind of C object. They will fail horribly if the receiver is an OBJ_INSTANCE instead.

So what you'll need to check is, "Is the superclass the class of one of the forbidden built-in types?" To do that, it's probably just:

if (superclass == vm->classClass ||
    superclass == vm->fnClass ||
    ...)
{
  // Runtime error...
}

You may run into some weird corner case behavior when bootstrapping the core library on this, but hopefully not.

I feel so stupid, that I cannot fullfill this task that seemed to be so trivial.

Lots of things seem trivial before you know a lot of details. This definitely isn't a trivial task. You're pretty deep in the bowels of a C implementation of a programming language's metaobject protocol. It's tricky enough that I procrastinated doing it, after all. :)