7.2.x (grails-scaffolding) - Parameterize the @Scaffold controller superclass so scaffolded controller members aren't type-erased#15729
Conversation
…ed controller members aren't type-erased
…keep raw form otherwise (both injectors)
|
Added a small robustness guard (both injectors, since the merged #15717 service-injector code shares the shape): the superclass is only parameterized when the base declares exactly one type parameter. A base with zero or multiple type parameters — e.g. |
✅ All tests passed ✅🏷️ Commit: a32edba Learn more about TestLens at testlens.app. |
jdaugherty
left a comment
There was a problem hiding this comment.
In 8.x, I 100% agree with this change. In 7.2.x, I'm not sure. @matrei would you consider this a breaking change?
|
@jdaugherty it matches #15717 which is already in 7.2.x |
|
I'm good to merge then. |
|
@jdaugherty thanks |
Summary
Controller-side counterpart of #15717.
ScaffoldingControllerInjectorset the injected superclass viaGrailsASTUtils.nonGeneric(superClassNode, domainClass), so a scaffolded controller's superclass was written raw and every inherited generic member erased to itsGormEntityupper bound under static compilation:fails with:
The common workaround in real apps is sprinkling
@CompileDynamicover everyqueryForResource/createResource/listAllResourcesoverride, giving up static compilation exactly where project-scoping security checks live.Design
Same pattern #15717 applied to
ScaffoldingServiceInjector: take agetPlainNodeReference()copy of the resolved superclass, set its generics to the domain type, mark the class node withsetUsingGenerics(true)— injection runs at CANONICALIZATION (after generics resolution), so the generic superclass signature is only emitted when the class node itself reportsusesGenerics; otherwise it is written raw — thensetSuperClass(parameterizedSuper). Applies to all three forms:@Scaffold(Widget),@Scaffold(RestfulController<Widget>), and custom bases@Scaffold(ApiController<Widget>)(the declared base is preserved, not collapsed toRestfulController).Tests
New
ScaffoldingControllerInjectorSpecmirroringScaffoldingServiceInjectorSpec:@Scaffold(Widget)→genericSuperclassisRestfulController<Widget>, not raw@Scaffold(RestfulController<Widget>)→ parameterizedCustomBase<Widget>@CompileStaticscaffolded controller overridingqueryForResource/createResource/listAllResourcesand narrowing thesuper.*results to the domain type compiles without castsAll four tests fail against the previous injector and pass with the fix;
:grails-scaffolding:testpasses in full.