enhancement
major
minor
major
minor
major
#30037
Name the base type of polymorphic configuration properties as wildcard bound (PolymorphicConfiguration<? extends X>) and check the rule in the build
Follow-up to #29767. The sweep was started as #29433, which was closed as a duplicate of #29429; #29429 corrected five declarations only, so the rest of the sweep has had no open ticket since.
Rule
A configuration property referencing a polymorphic configuration names its base type as a wildcard bound:
{{{#!java PolymorphicConfiguration<? extends Renderer> getRenderer(); // correct List<PolymorphicConfiguration<? extends Renderer>> getRenderers(); // correct PolymorphicConfiguration<Renderer> getRenderer(); // wrong }}}
Without the wildcard, the configuration of a concrete implementation (MyRenderer.Config extends PolymorphicConfiguration<MyRenderer>) is not assignable to the property in Java: setRenderer(myRendererConfig), TypedConfiguration.newConfigItem(...) results or a list of such configurations need casts, and the declared type claims less than the property accepts at runtime. The declaration is also invisible to a search for the properties of a given base type (PolymorphicConfiguration<? extends ViewAction>). The same holds for setters (void setRenderer(PolymorphicConfiguration<? extends Renderer> value)), for NamedPolymorphicConfiguration, and for the element type of List, Collection and Map properties.
Not to change:
- A configuration declaring its own implementation: interface Config extends PolymorphicConfiguration<MyImpl>.
- A parameterization by a type variable: PolymorphicConfiguration<T> getImpl() in NamedInstanceConfig<T>, <T> T getInstance(PolymorphicConfiguration<T>) in InstantiationContext.
- Local variables, casts and loop variables - they follow the property type and only need adjusting where the change of the property makes them fail to compile.
Adding the wildcard may require a type witness where the result is instantiated in a stream: configs.stream().<ViewAction> map(context::getInstance). TypedConfiguration.getInstanceList(...) / getInstanceMap(...) already accept wildcard lists.
State before the change
On master (7f10b639dd, 2026-10-29) a grep for (Named)?PolymorphicConfiguration<[A-Z] in property declarations found 258 declarations to fix in 22 modules (list attached as polymorphic-configuration-wildcards.txt); the conformance check below reported 32 in com.top_logic.basic, 127 in com.top_logic and 85 in the other modules.
Lösung
- Conformance check test.com.top_logic.basic.TestPolymorphicConfigurationWildcards: added to the TestAll of every module by BasicTestCollector, switchable by the GlobalConfig flag test-polymorphic-configuration-wildcards (default true). It walks the ConfigurationItem interfaces of the TypeIndex and checks those whose class files lie in the tested module directory - main and test classes. For each method declared by such an interface (getters: return type, setters: parameter type; static and default methods are skipped) it fails for every PolymorphicConfiguration<X> / NamedPolymorphicConfiguration<X> with a concrete type X` (neither wildcard nor type variable), also as type argument of `List / Collection / Map and as array component. The check works on the reflective generic types, so declarations spread over several lines are found too; all violations are reported in one failure message (Interface#method: type). The check logic is unit-tested by TestPolymorphicConfigurationWildcardsCheck.
- Sweep: all 244 violating property declarations in main and test code of all modules (including the configuration framework's own tests and TestDeFactoMonomorphicProperties) now use the wildcard; local variables, loop variables and helper parameters receiving a changed getter's result were adjusted where compilation required it, without new casts.
Migration
The getters and setters of configuration properties referencing a polymorphic configuration now declare PolymorphicConfiguration<? extends X> (or List<PolymorphicConfiguration<? extends X>>, ...) instead of PolymorphicConfiguration<X>. Application code assigning the result of such an engine getter to a variable of type PolymorphicConfiguration<X> no longer compiles; declare the variable as PolymorphicConfiguration<? extends X>. A sub-interface redeclaring such a getter should use the wildcard type as well. Where the result is instantiated in a stream, a type witness may be needed: configs.stream().<X> map(context::getInstance). Application configuration interfaces must follow the same rule: the conformance check runs in the application's TestAll and reports violations (switch it off with test-polymorphic-configuration-wildcards="false" in the global test configuration only temporarily). XML configurations and data are unchanged.