Skip to content

Commit 649d7bb

Browse files
committed
fix(gsp): resolve g.taglib STC calls when the GSP page inherits getProperty (GROOVY-12041)
GroovyPageTypeCheckingExtension recorded the taglib-namespace receiver (e.g. `g`) in unresolvedVariable / unresolvedProperty and matched it back by AST node identity in methodNotFound. On Groovy 5, when the compiled GSP page inherits getProperty(String) from its base class, those callbacks no longer fire for the namespace, so the receiver is never recorded and `g.message(...)` is rejected by @CompileStatic with "Cannot find matching method java.lang.Object#message(...)". Per GROOVY-12041, match the call receiver by name against the allowed taglib namespaces in methodNotFound, independently of whether the earlier callback fired. This is resilient to the getProperty(String) short-circuit and does not rely on node identity. Re-enables the previously @IgnoreIf-skipped g.* prefix cases in GspCompileStaticSpec on Groovy 5. Standalone reproducer (matrix across 4.0.31 / 5.0.6 / 5.0.7-SNAPSHOT / 6.0.0-SNAPSHOT): https://github.com/jamesfredley/groovy5-stc-extension-node-identity-bug Verified: grails-gsp-core test on 5.0.7-SNAPSHOT - 21 passed, 0 failed, 2 skipped (the 2 skips are an unrelated undeclared-variable concern, left as-is). Assisted-by: claude-code:claude-4.8-opus
1 parent 68fe246 commit 649d7bb

2 files changed

Lines changed: 23 additions & 7 deletions

File tree

grails-gsp/core/src/main/groovy/org/grails/gsp/compiler/GroovyPageTypeCheckingExtension.groovy

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,29 @@ class GroovyPageTypeCheckingExtension extends GroovyTypeCheckingExtensionSupport
6969
}
7070

7171
methodNotFound { receiver, name, argList, argTypes, call ->
72-
if (isThisTheReceiver(call) || (call.objectExpression != null && currentScope.dynamicProperties.contains(call.objectExpression))) {
72+
if (isThisTheReceiver(call)) {
73+
return makeDynamic(call)
74+
}
75+
def objectExpression = call.objectExpression
76+
if (objectExpression == null) {
77+
return null
78+
}
79+
if (currentScope.dynamicProperties.contains(objectExpression)) {
80+
return makeDynamic(call)
81+
}
82+
// Groovy 5+ (GROOVY-12041): when the compiled GSP page inherits
83+
// getProperty(String) from its base class, the unresolvedVariable /
84+
// unresolvedProperty callback no longer fires for a taglib namespace
85+
// such as 'g', so the receiver is never recorded in dynamicProperties
86+
// and the node-identity check above cannot match. Fall back to
87+
// matching the receiver by name against the allowed taglib namespaces.
88+
String namespaceName = null
89+
if (objectExpression instanceof VariableExpression) {
90+
namespaceName = ((VariableExpression) objectExpression).name
91+
} else if (objectExpression instanceof PropertyExpression && isThisTheReceiver(objectExpression)) {
92+
namespaceName = ((PropertyExpression) objectExpression).propertyAsString
93+
}
94+
if (namespaceName != null && currentScope.allowedTagLibs.contains(namespaceName)) {
7395
return makeDynamic(call)
7496
}
7597
}

grails-gsp/core/src/test/groovy/org/grails/gsp/GspCompileStaticSpec.groovy

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,6 @@ class GspCompileStaticSpec extends Specification {
9393
compileStatic << [true, false]
9494
}
9595

96-
// Note: In Groovy 5, the g.message() syntax with g. prefix fails static type checking
97-
// because the type checking extension doesn't properly resolve the 'g' taglib property.
98-
// Tests with gDotPrefix: true are skipped on Groovy 5+.
99-
@IgnoreIf({ instance.isGroovy5OrLater() && data.gDotPrefix })
10096
def "should support message tag invocation"() {
10197
given:
10298
def template = '<%@ compileStatic="true"%>${' + (gDotPrefix ? 'g.' : '') + '''message(code:'World')}'''
@@ -108,7 +104,6 @@ class GspCompileStaticSpec extends Specification {
108104
gDotPrefix << [false, true]
109105
}
110106

111-
@IgnoreIf({ instance.isGroovy5OrLater() && data.gDotPrefix })
112107
def "should support message tag invocation inline"() {
113108
given:
114109
def template = """<%@ compileStatic="true"%><%
@@ -124,7 +119,6 @@ out.print(${gDotPrefix ? 'g.' : ''}message(code:'World'))
124119
gDotPrefix << [false, true]
125120
}
126121

127-
@IgnoreIf({ instance.isGroovy5OrLater() && data.gDotPrefix })
128122
def "should support message tag invocation inline in a closure"() {
129123
given:
130124
def template = """<%@ compileStatic="true"%><%

0 commit comments

Comments
 (0)