fernando88to opened a new pull request, #32:
URL: https://github.com/apache/grails-intellij-plugin/pull/32
## Problem
In a Grails 3+ project, a named-argument call to `render` inside a
controller is flagged
as an error even though the code compiles and runs:
```groovy
@GrailsCompileStatic
class ErrorController {
def index() {
try {
doSomething()
} catch (Exception e) {
log.error(e.message)
render(view: errorPage) // <- "Method call is ambiguous"
}
}
}
```
The usual workaround is to force a positional argument, which resolves
cleanly but is
noise in the source:
```groovy
render([view: errorPage] as Map) // no error
```
The same problem applies to other named-argument controller calls, e.g.
`redirect(controller: 'foo', action: 'index')`.
## Root cause
`ControllerMembersProvider` decided which members to contribute by probing
for a single
Grails 1.x/2.x class:
```java
PsiClass apiClass =
facade.findClass("org.codehaus.groovy.grails.plugins.web.api.ControllersApi",
resolveScope);
if (apiClass != null) {
// Grails >= 1.4: enhance from the *Api classes
}
else {
// assumed "Grails < 1.4": inject CLASS_SOURCE
DynamicMemberUtils.process(executeProcessor, psiClass, ref,
CLASS_SOURCE);
}
```
That class no longer exists in Grails 3+, so every modern project fell
into the legacy
branch and got `CLASS_SOURCE` injected — including:
```java
" private void render(Map params, Closure cl = null){...}"
```
Meanwhile Grails 3+ controllers already carry `grails.artefact.Controller`
(`ResponseRenderer`, `ResponseRedirector`, `RequestForwarder`,
`DataBinder`,
`WebAttributes`, `ServletAttributes`), which
`Grails3TraitInjectorContributor` adds as a
supertype — so `render(Map)` is a real code member of the class.
The call therefore had two applicable candidates and neither is more
specific. Resolution
probe on `render(view: page)` before the fix:
```
candidates=2
com.bar.CccController#render(java.util.Map) [GrTraitMethod]
applicable=true
<none>#render(java.util.Map, groovy.lang.Closure)
[GrDynamicMethodWithCache] applicable=true
highlight[ERROR]: Method call is ambiguous
```
And the same probe on `render([view: page] as Map)`:
```
candidates=1
com.bar.CccController#render(java.util.Map) [GrTraitMethod]
(no error)
```
which is exactly why the `as Map` workaround appeared to help — the
injected dynamic
method simply stops being a candidate for a positional argument.
## Fix
Do not contribute the legacy members when the trait-based controller model
is present:
```java
else if (facade.findClass(CONTROLLER_TRAIT_CLASS, resolveScope) == null) {
// Grails version < 1.4: no API classes and no controller trait, so
nothing declares these members
if (!DynamicMemberUtils.process(executeProcessor, psiClass, ref,
CLASS_SOURCE)) return;
}
```
Nothing is lost on Grails 3+. Verified against `grails-plugin-controllers`
/
`grails-web-common` 6.2.3 with `javap`: every member `CLASS_SOURCE`
provided is declared
by the trait set — `render`, `redirect`, `chain`, `forward`, `bindData`,
`withForm`,
`withFormat`, `hasErrors`, `errors`, `modelAndView`, `templateUri`,
`viewUri`, plus
`params`, `request`, `response`, `session`, `flash`, `servletContext`,
`grailsApplication`
and `webRequest`. The legacy versions were in fact worse: the injected
`getParams()`
returns `org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap`, a
type that does
not exist in Grails 3+.
Behaviour for Grails < 1.4 and for 1.4–2.x is unchanged: the first branch
still probes
`ControllersApi`, and the legacy injection still runs when neither the API
classes nor the
controller trait are on the classpath.
## Tests
New `GrailsControllerTraitMembersTest` (light fixture,
`grails-app/controllers/` source
root) declares the traits with `@grails.artefact.Enhances("Controller")`,
which is the hook
the plugin itself uses to inject them, so the real resolution path is
exercised. Each case
asserts that the call resolves to exactly one method — the trait's — and
that no
`ambiguous` highlight is produced:
- `render(view: errorPage)` — the reported symptom
- `render(text: 'hello')` — shows it is not specific to `view:`, and
covers a gap in
`GrailsControllerAmbiguousMethodInspectionTest`, which exercises that
same snippet only
on the Grails 1.4 fixture
- `render([view: errorPage] as Map)` — keeps the positional form clean
- `redirect(controller: 'ccc', action: 'index')` — same defect with
identical arity
With the fix reverted, the test fails and names the duplicate:
```
render() must resolve to the single trait method, got
com.bar.CccController#render(Map args); <none>#render(Map params,
Closure cl = null)
```
## Out of scope
Inner-closure support for `withFormat { html { } json { } }` is still
keyed to
`lightMethodKey="ControllerMembersProvider_controller_method"`
(`grails-method-descriptors.xml`), which only exists on the Grails 1.x/2.x
paths. On
Grails 3+ that would need a `methodDescriptor` for
`grails.artefact.Controller#withFormat`.
This is a pre-existing gap — with the call ambiguous it could not resolve
at all — and is
left for a separate change.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]