major
#29806
WrapperGenerator: every top-level class of a module with a singleton extends StructuredElement (regression of #26716)
Affects tl-element 8.0.0-alpha10, com.top_logic.element.model.generate.InterfaceGenerator.
Problem
InterfaceGenerator.writeImports() / getExtends() decide by TLModelGenerator.isStructure(module), i.e. by whether the module carries a TLSingletons annotation, that a generated interface without generalizations extends com.top_logic.element.structured.StructuredElement instead of Wrapper:
protected boolean isStructure(TLModule module) {
return module.getAnnotation(TLSingletons.class) != null;
}
Adding one singleton (a root object holding shared master data) to a module whose classes all derive from tl.model:TLObject therefore turned all 32 generated wrappers of that module into structured-element wrappers (AttributedStructuredElementWrapper as implementation base, StructuredElement methods on every interface) although none of the model classes derives from tl.element:StructuredElement.
#26716 (fixed in 7.5.0) states exactly the expected behavior: "Eine generierte Fach-Klasse soll nur dann von tl:StructuredElement ableiten, wenn es ihr Modell-Pendant auch tut - nicht alle Klassen in einem Modul, das zufälligerweise ein Singleton definiert." The module-level decision is back (or was never removed from the interface generator).
Expected
A generated interface extends StructuredElement only when its model class (transitively) specializes tl.element:StructuredElement; a singleton in the module has no influence on the other classes.
Workaround
Put the singleton into a separate module without generated bindings (here: module ehome.mask holding the root, the classes stay in ehome.operational).
Seen in the E-Home application (haumacher/ehome, commit 86f15bf).
Analysis
The defect is in ImplementationTemplateGenerator.superTypeSpec(), which chooses the super class of a generated XImpl: it walks the primary generalization, recurses through every abstract generalization without looking at its implementation-binding, and when the chain ends it falls back to the module decision isStructure(module). Every application class has the implicit abstract generalization tl.model:TLObject, so the chain of every plain class ends there and in a module with singletons each XImpl extends AttributedStructuredElementWrapper. The reverse defect exists as well: a class deriving from tl.element:StructuredElement in a module without singletons gets AttributedWrapper.
The same module decision in InterfaceGenerator (interface without generalizations extends StructuredElement) is unreachable for application types but equally wrong. The decision in FactoryGenerator (factory extends StructuredElementWrapperFactory and gets singleton lookups when the module declares singletons) is correct and stays.
Solution
The generator mirrors the rule the runtime uses to find the implementation class of an object (DynamicBinding): the implementation class of a type is its implementation-binding or, by naming convention, its generated XImpl; a type without one inherits the implementation class of its primary generalization.
- Which types get an XImpl is unchanged: concrete types, and abstract types with an implementation-binding (their class implements dynamically defined subtypes).
- XImpl extends the implementation class of the primary generalization when that generalization has one (its implementation-binding class, or its XImpl if the type is concrete or bound); otherwise the walk continues with the primary generalization of that type. The last fallback is AttributedWrapper. With the bindings tl.model:TLObject → AttributedWrapper and tl.element:StructuredElement → AttributedStructuredElementWrapper a wrapper gets the structured base exactly when its model type specializes tl.element:StructuredElement.
- A generated interface without generalizations extends Wrapper; the singleton annotation of the module has no influence on types.
- TLModelGenerator.isStructure(TLModule) becomes hasSingletons(TLModule) and is used by the factory generator only.
- TestWrapperGenerator generates and compiles a model with a plain class in a module with singletons (base AttributedWrapper), a structured class in a module without singletons (base AttributedStructuredElementWrapper), an interface without binding (no XImpl) and a bound interface with a subclass (XImpl chain) and asserts the generated super types. The generated wrappers of the engine modules with a generate-java build step (tl-element, tl-bpe, tl-demo) and the test models of tl-element are regenerated.
Migration
Regenerate the Java binding of the application modules. After regeneration a class that does not specialize tl.element:StructuredElement gets AttributedWrapper as implementation base; code that treated such objects as StructuredElement (casts, getParent(), getChildren()) no longer compiles and must be corrected, or the model class has to declare the tl.element:StructuredElement generalization it relied on. The XImpl of a class whose primary generalization is a concrete or bound type extends that type's implementation class instead of the engine base class; hand-written code is not affected by this.