Sunday, August 7, 2011

Compiler bug, an example why Tangent is non-trivial

Especially in the bootstrapping stage, programming languages aren't fun to work with. Tangent takes that usual un-fun and makes it a pile more un-fun because of features other developers had the good sense to avoid.

Take an exception generated by the runtime today:

Unable to invoke: No method exists on the type "enum? -> 'a'" of the signature "enum? -> 'a'".

Which is... awesome. At least there's something approximating a decent error message, even if it's still unacceptably bad. And since I managed to get this thing actually compiling (horrifically, see last post) debugging it is a pain. Actually it's not that bad. The runtime is in nice, un-code generated C# so I could debug it, but that's the thing; Tangent has enough runtime code (in this case the dynamic dispatch) that other languages don't have that I spend a bit of time writing and later debugging happy error messages like this.

So I manage to cut down the test program so there's only one enum and only one value. Still doesn't work so it's not just 2 different enums not comparing right due to my lack of name capture. Step into code to see that the types have the same name, the same goose names (see 2nd post for goose high level info), the same reduction rules... everything that should determine type equivalence... but they're not the same type.

Which is odd. There's only one type with this name in the table... Then I see that one is the literal type (a kind) and the other is a type value. A kind is not a type, so that's why the mismatch happens. Fair enough, I screwed up the level when doing code generation. So I hunt through the compiled bits to find that there really is only one type that matches this, and it's the kind (as it should be).

So I eventually found it out. The issue is with gooses and how I track type literals. Gooses (as talked about earlier) are flags to say 'you must inherit this class to be a subtype of this class'. How does that work? By keeping references to types that are inherited. A goose type has a reference to itself (to differentiate itself from a type with the same members, but is supposed to be ducktyped). This works awesome. I use it (and I suspect users of the language) will use empty goose types to annotate types. Like... for indicating that a type instance is to be treated as a literal T or Kind of T instead of the value T.

See where this is headed? Yeah, for something to be considered a literal T it needs the Type reference in its goose list indicating that it is. The reference it needs is just a static instance on Type, so matching is easy and straightforward. Except that when I was doing code generation, I added the goose representation for it to be created new. Had the same fields, same properties, same data... but wasn't the same reference as the static Type.TypeLiteralGoose. So it wasn't being treated as a Type Literal by the compiled runtime, and caused type matching errors.

Easy enough to fix; reference the static field rather than create a new instance with the same bits. Finding it is the hard part.

Granted, there were more bugs under that one, but that's another story for another time.