bcc icon indicating copy to clipboard operation
bcc copied to clipboard

Compilation of generics

Open HurryStarfish opened this issue 6 years ago • 0 comments

In their current form, generics in BlitzMax-NG are basically just templates. Code inside generic types gets parsed, but doesn't seem to go through semantic analysis until the generic type is actually "applied" by assigning concrete types to its type parameters.

This causes some major problems:

  • There is very little guarantee about the correctness of generic code. Even basic errors like undeclared identifiers, incorrect types etc. are not detected unless the type is actually used somewhere. As long as the code is syntactically correct, it is possible to compile utter semantic nonsense without any complaints from the compiler:
Type TTest<T>
	Method M:M()
		If BRL.StandardIO Then asfd + [] / Null * 1.2.3[0] = "asdf"()
	End Method
End Type
  • A mistake in generic code inside a library/module will cause compile errors for the person who uses the module instead of the one who wrote it. This can be subtle and only happen with certain combinations of type parameters, like the following class which produces a compile error only when it's used with the type argument Int (even then, it shouldn't do that, but that's a topic for another day):
Type TTest2<T>
	Method M(x:Int) End Method
	Method M(x:T) End Method
End Type

To prevent this, bcc should put the original code, in its generic form, through a pass of semantic analysis first. During this, name resolution, type checking etc. should be performed, just like on non-generic code. It would be convenient for this to have generic types, as well as type parameters (including Where-constraints on them) represented as TType objects internally, just like other types are. (TODO: How exactly would generic functions and methods be represented?) Once generic code has passed this stage, it should be guaranteed to compile without errors for every possible combination of type arguments (conforming to the given constraints) that might be applied later. So just like with non-generic code, once it compiles, a module or file can be imported and used without having to worry about it suddenly causing new compile errors at a later time.

C code generation stays largely unaffected by this; for the most part it should (and can) still only be done once concrete types have been constructed. Different type arguments being applied will still lead to different C code being generated. Some additional info should be generated for generics though. In particular, it should be possible for reflection to retrieve a representation of generic types in their "un-applied" form (as they were declared in code). This is necessary to preserve certain information between types: for example, given a class TFoo<Int> implementing an interface IBar<Int> at runtime, reflection should be able to tell whether the original declaration was Type TFoo<T> Implements IBar<T> or TFoo<T> Implements IBar<Int>, which wouldn't be possible otherwise. This kind of information might be useful for debugging too.

HurryStarfish avatar Jan 24 '19 15:01 HurryStarfish