diff --git a/src/main/java/org/codehaus/groovy/transform/StaticTypesTransformation.java b/src/main/java/org/codehaus/groovy/transform/StaticTypesTransformation.java index 53450dc94c7..839583a6a54 100644 --- a/src/main/java/org/codehaus/groovy/transform/StaticTypesTransformation.java +++ b/src/main/java/org/codehaus/groovy/transform/StaticTypesTransformation.java @@ -22,22 +22,30 @@ import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; +import org.codehaus.groovy.ast.ClassCodeExpressionTransformer; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.PropertyNode; +import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.ListExpression; +import org.codehaus.groovy.ast.expr.PropertyExpression; +import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.control.CompilationUnit; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.syntax.SyntaxException; import org.codehaus.groovy.transform.stc.GroovyTypeCheckingExtensionSupport; import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor; +import org.codehaus.groovy.transform.stc.StaticTypesMarker; import java.util.Collections; import java.util.Map; +import static org.codehaus.groovy.ast.tools.GeneralUtils.classX; +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX; + /** * Handles the implementation of the {@link groovy.transform.TypeChecked} transformation. */ @@ -73,6 +81,45 @@ public void visit(ASTNode[] nodes, SourceUnit source) { } if (visitor != null) { visitor.performSecondPass(); + resolveEnumConstantsInSwitchCases(node, source); + } + } + + /** + * GROOVY-8444, GROOVY-11614, GROOVY-12190: replaces "CONST" expressions resolved by the + * type checker in enum switch case labels with "EnumType.CONST" expressions; + * without static compilation they would otherwise be dynamic property lookups + * on the enclosing class, failing at runtime. + */ + private static void resolveEnumConstantsInSwitchCases(final AnnotatedNode node, final SourceUnit source) { + ClassCodeExpressionTransformer transformer = new ClassCodeExpressionTransformer() { + @Override + protected SourceUnit getSourceUnit() { + return source; + } + + @Override + public Expression transform(final Expression expression) { + if (expression instanceof ClosureExpression) { // switch expressions are desugared to closures + expression.visit(this); + return expression; + } + if (expression instanceof VariableExpression) { + ClassNode enumType = expression.getNodeMetaData(StaticTypesMarker.SWITCH_CONDITION_EXPRESSION_TYPE); + if (enumType != null) { + PropertyExpression pe = propX(classX(enumType), expression.getText()); + setSourcePosition(pe, expression); + return pe; + } + return expression; + } + return super.transform(expression); + } + }; + if (node instanceof ClassNode classNode) { + transformer.visitClass(classNode); + } else if (node instanceof MethodNode methodNode) { + transformer.visitMethod(methodNode); } } diff --git a/src/test/groovy/bugs/Groovy8444.groovy b/src/test/groovy/bugs/Groovy8444.groovy index dc3b59effe7..8fd72e6a986 100644 --- a/src/test/groovy/bugs/Groovy8444.groovy +++ b/src/test/groovy/bugs/Groovy8444.groovy @@ -433,4 +433,91 @@ final class Groovy8444 { assert 2.2 == meth(SomeEnum.B, OtherEnum.D) ''' } + + // GROOVY-12190 + @Test + void testAccessingEnumConstantInSwitchCaseWithTypeChecked() { + assertScript '''\ + enum SomeEnum { + A, B + } + @groovy.transform.TypeChecked + def meth(SomeEnum e) { + switch (e) { + case A: return 1 + case B: return 2 + } + } + assert 1 == meth(SomeEnum.A) + assert 2 == meth(SomeEnum.B) + ''' + } + + // GROOVY-12190 + @Test + void testAccessingEnumConstantInSwitchExprCaseWithTypeChecked() { + assertScript '''\ + enum SomeEnum { + A, B + } + @groovy.transform.TypeChecked + def meth(SomeEnum e) { + switch (e) { + case A -> 1 + case B -> 2 + } + } + assert 1 == meth(SomeEnum.A) + assert 2 == meth(SomeEnum.B) + ''' + } + + // GROOVY-12190 + @Test + void testAccessingEnumConstantInNestedSwitchExprCaseWithTypeChecked() { + assertScript '''\ + enum SomeEnum { + A, B + } + @groovy.transform.TypeChecked + def meth(SomeEnum e) { + switch (e) { + case A -> + switch(e) { + case A -> 1.1 + case B -> 1.2 + } + case B -> + switch(e) { + case A -> 2.1 + case B -> 2.2 + } + } + } + assert 1.1 == meth(SomeEnum.A) + assert 2.2 == meth(SomeEnum.B) + ''' + } + + // GROOVY-12190 + @Test + void testAccessingEnumConstantInSwitchCaseInClosureWithTypeChecked() { + assertScript '''\ + enum SomeEnum { + A, B + } + @groovy.transform.TypeChecked + def meth(SomeEnum e) { + def c = { + switch (e) { + case A: return 1 + case B: return 2 + } + } + c() + } + assert 1 == meth(SomeEnum.A) + assert 2 == meth(SomeEnum.B) + ''' + } }