diff --git a/src/main/java/spoon/support/compiler/jdt/ParentExiter.java b/src/main/java/spoon/support/compiler/jdt/ParentExiter.java index 34d4767ab26..217b9bdbcb0 100644 --- a/src/main/java/spoon/support/compiler/jdt/ParentExiter.java +++ b/src/main/java/spoon/support/compiler/jdt/ParentExiter.java @@ -520,7 +520,9 @@ public void visitCtCatchVariable(CtCatchVariable e) { @Override public void visitCtClass(CtClass ctClass) { if (child instanceof CtConstructor) { - ctClass.addConstructor((CtConstructor) child); + CtConstructor constructor = (CtConstructor) child; + ctClass.addConstructor(constructor); + fixJdtEnumConstructorSuperCall(ctClass, constructor); } if (child instanceof CtAnonymousExecutable) { ctClass.addAnonymousExecutable((CtAnonymousExecutable) child); @@ -528,6 +530,31 @@ public void visitCtClass(CtClass ctClass) { super.visitCtClass(ctClass); } + private void fixJdtEnumConstructorSuperCall(CtClass ctClass, CtConstructor constructor) { + // For some reason JDT inserts a `super()` call in implicit enum constructors. + // Explicit super calls are forbidden as java.lang.Enum subclasses are permitted by the JLS to delegate + // to the Enum constructor in whatever way they like. + // The constructor is implicit so this isn't *technically* illegal, but it doesn't really make much sense + // as explicit constructors can never contain such a call. Additionally, the Enum class from the standard + // library has a "String, int" constructor, rendering the parameterless supercall semantically invalid. + // We just remove the call to make it a bit more consistent. + // See https://github.com/INRIA/spoon/issues/4758 for more details. + if (!child.isImplicit() || !ctClass.isEnum() || !constructor.getParameters().isEmpty()) { + return; + } + if (constructor.getBody().getStatements().isEmpty()) { + return; + } + if (!(constructor.getBody().getStatement(0) instanceof CtInvocation)) { + return; + } + + CtInvocation superCall = constructor.getBody().getStatement(0); + if (superCall.getExecutable().getSimpleName().equals("")) { + constructor.getBody().removeStatement(superCall); + } + } + @Override public void visitCtTypeParameter(CtTypeParameter typeParameter) { if (childJDT instanceof TypeReference && child instanceof CtTypeAccess) { diff --git a/src/test/java/spoon/test/enums/EnumsTest.java b/src/test/java/spoon/test/enums/EnumsTest.java index b3c3a2dfc08..98e59b209f6 100644 --- a/src/test/java/spoon/test/enums/EnumsTest.java +++ b/src/test/java/spoon/test/enums/EnumsTest.java @@ -18,6 +18,7 @@ import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -28,8 +29,10 @@ import spoon.reflect.CtModel; import spoon.reflect.code.CtBlock; import spoon.reflect.code.CtExpression; +import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtNewClass; import spoon.reflect.code.CtStatement; +import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtEnum; import spoon.reflect.declaration.CtEnumValue; import spoon.reflect.declaration.CtField; @@ -37,8 +40,10 @@ import spoon.reflect.declaration.CtType; import spoon.reflect.declaration.ModifierKind; import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtExecutableReference; import spoon.reflect.visitor.filter.TypeFilter; import spoon.support.reflect.CtExtendedModifier; +import spoon.test.GitHubIssue; import spoon.test.SpoonTestHelpers; import spoon.test.annotation.AnnotationTest; import spoon.test.enums.testclasses.Burritos; @@ -58,6 +63,7 @@ import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; @@ -280,6 +286,25 @@ void testLocalEnumExists() { )); } + @GitHubIssue(issueNumber = 4758, fixed = true) + @DisplayName("Implicit enum constructors do not contain a super call") + void testImplicitEnumConstructorSuperCall() { + CtEnum myEnum = (CtEnum) Launcher.parseClass("enum Foo { CONSTANT; }"); + CtConstructor constructor = myEnum.getConstructors().iterator().next(); + + assertThat(constructor.isImplicit(), is(true)); + + for (CtStatement statement : constructor.getBody().getStatements()) { + if (!(statement instanceof CtInvocation)) { + continue; + } + CtExecutableReference executable = ((CtInvocation) statement).getExecutable(); + if (!executable.getDeclaringType().getQualifiedName().equals("java.lang.Enum")) { + continue; + } + assertThat(executable.getSimpleName(), not(is(""))); + } + } static class NestedEnumTypeProvider implements ArgumentsProvider { private final CtType ctClass;