ammer

Type cycles

In linking subdefinitions, it is recommended to link any library to all of its subdefinitions using the @:ammer.sub metadata. Why is this necessary?

Consider the following types:

// in file Foobar.hx
@:ammer.sub((_ : FoobarStruct))
@:ammer.sub((_ : FoobarSub))
class Foobar extends ammer.def.Library<"foobar"> {
  public static function some_function():FoobarStruct;
}

// in file FoobarStruct.hx
class FoobarStruct extends ammer.def.Struct<"foobar_t", Foobar> {
  // ...
}

// in file FoobarSub.hx
class FoobarSub extends ammer.def.Sublibrary<Foobar> {
  public static function another_function():Void;
}

Between Foobar and FoobarSub, there is a two-way link:

If the @:ammer.sub((_ : FoobarSub)) annotation was omitted, then the following client code could cause a compilation failure:

Foobar.some_function();
FoobarSub.another_function();

This is because Haxe performs typing on demand: the Foobar module (in the file Foobar.hx) is not discovered and typed until the first time it is needed. This can happen if a type (and its static method) declared in that module is used, as in the first line.

However, Foobar is not a regular Haxe type: it is an ammer-annotated library definition. When an ammer library is typed, the following steps take place (simplified):

Without the @:ammer.sub((_ : FoobarSub)) annotation on Foobar, the first line of the client only causes the Haxe compiler to discover Foobar and FoobarStruct. When typing the second line (the call to FoobarSub.another_function), FoobarSub is discovered, but it cannot be added to Foobar anymore: the library was finalised and the glue code was already generated.

The safe recommendation is therefore to always use @:ammer.sub, even when other fields would cause the Haxe compiler to discover the subdefinitions.

« Previous: Advanced topics Next: ammer-core »