From 30e79255c4228d977cfd0e5e9bb329a7d970f996 Mon Sep 17 00:00:00 2001 From: Paul King Date: Fri, 28 Aug 2026 06:38:11 +1000 Subject: [PATCH] GROOVY-12305: STC: record narrowed type of instanceof-guarded property expression for static compilation --- .../stc/StaticTypeCheckingVisitor.java | 8 ++++++- .../stc/FieldsAndPropertiesSTCTest.groovy | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index ac48070d093..f74ee46f309 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -861,6 +861,12 @@ public void visitPropertyExpression(final PropertyExpression expression) { boolean readOnly = !typeCheckingContext.isTargetOfEnclosingAssignment(expression); if (existsProperty(expression, readOnly) || extension.handleUnresolvedProperty(expression)) { + if (readOnly) { // GROOVY-12305: record narrowed type of instanceof-guarded receiver for later use + ClassNode temporaryType = getInferredTypeFromTempInfo(expression, expression.getNodeMetaData(INFERRED_TYPE)); + if (temporaryType != null && !isObjectType(temporaryType)) { + expression.putNodeMetaData(INFERRED_TYPE, temporaryType); + } + } return; // resolved or excused } recordMissingProperty(expression); @@ -7428,7 +7434,7 @@ protected List getTemporaryTypesForExpression(final Expression expres } private ClassNode getInferredTypeFromTempInfo(final Expression expression, final ClassNode expressionType) { - if (expression instanceof VariableExpression && !isPrimitiveType(expressionType)) { + if ((expression instanceof VariableExpression || expression instanceof PropertyExpression) && !isPrimitiveType(expressionType)) { List tempTypes = getTemporaryTypesForExpression(expression); if (!tempTypes.isEmpty()) { ClassNode superclass; diff --git a/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy b/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy index 26dcbf13e61..c9c97db48e0 100644 --- a/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy +++ b/src/test/groovy/groovy/transform/stc/FieldsAndPropertiesSTCTest.groovy @@ -1321,6 +1321,30 @@ class FieldsAndPropertiesSTCTest extends StaticTypeCheckingTestCase { ''' } + // GROOVY-12305 + @Test + void testMapPropertyAccess19() { + assertScript ''' + def test(Map foo) { + foo.a instanceof Map ? foo.a.b : foo.a + } + assert test(a: [b: 'c']) == 'c' + assert test(a: 'x') == 'x' + ''' + + assertScript ''' + def test(Map foo) { + if (foo.a instanceof Map) { + foo.a.b + } else { + foo.a + } + } + assert test(a: [b: 'c']) == 'c' + assert test(a: 'x') == 'x' + ''' + } + @Test void testTypeCheckerDoesNotThinkPropertyIsReadOnly() { assertScript '''