Skip to content

Commit c074350

Browse files
committed
fix: Remove super calls from implicit enum constructors
1 parent 808c06d commit c074350

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

src/main/java/spoon/support/compiler/jdt/ParentExiter.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,14 +520,41 @@ public <T> void visitCtCatchVariable(CtCatchVariable<T> e) {
520520
@Override
521521
public <T> void visitCtClass(CtClass<T> ctClass) {
522522
if (child instanceof CtConstructor) {
523-
ctClass.addConstructor((CtConstructor<T>) child);
523+
CtConstructor<T> constructor = (CtConstructor<T>) child;
524+
ctClass.addConstructor(constructor);
525+
fixJdtEnumConstructorSuperCall(ctClass, constructor);
524526
}
525527
if (child instanceof CtAnonymousExecutable) {
526528
ctClass.addAnonymousExecutable((CtAnonymousExecutable) child);
527529
}
528530
super.visitCtClass(ctClass);
529531
}
530532

533+
private <T> void fixJdtEnumConstructorSuperCall(CtClass<T> ctClass, CtConstructor<T> constructor) {
534+
// For some reason JDT inserts a `super()` call in implicit enum constructors.
535+
// Explicit super calls are forbidden as java.lang.Enum subclasses are permitted by the JLS to delegate
536+
// to the Enum constructor in whatever way they like.
537+
// The constructor is implicit so this isn't *technically* illegal, but it doesn't really make much sense
538+
// as explicit constructors can never contain such a call. Additionally, the Enum class from the standard
539+
// library has a "String, int" constructor, rendering the parameterless supercall semantically invalid.
540+
// We just remove the call to make it a bit more consistent.
541+
// See https://github.com/INRIA/spoon/issues/4758 for more details.
542+
if (!child.isImplicit() || !ctClass.isEnum() || !constructor.getParameters().isEmpty()) {
543+
return;
544+
}
545+
if (constructor.getBody().getStatements().isEmpty()) {
546+
return;
547+
}
548+
if (!(constructor.getBody().getStatement(0) instanceof CtInvocation)) {
549+
return;
550+
}
551+
552+
CtInvocation<?> superCall = constructor.getBody().getStatement(0);
553+
if (superCall.getExecutable().getSimpleName().equals("<init>")) {
554+
constructor.getBody().getStatements().remove(0);
555+
}
556+
}
557+
531558
@Override
532559
public void visitCtTypeParameter(CtTypeParameter typeParameter) {
533560
if (childJDT instanceof TypeReference && child instanceof CtTypeAccess) {

src/test/java/spoon/test/enums/EnumsTest.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.apache.commons.lang3.StringUtils;
2020
import org.junit.jupiter.api.Assertions;
21+
import org.junit.jupiter.api.DisplayName;
2122
import org.junit.jupiter.api.Test;
2223
import org.junit.jupiter.api.extension.ExtensionContext;
2324
import org.junit.jupiter.params.ParameterizedTest;
@@ -28,17 +29,21 @@
2829
import spoon.reflect.CtModel;
2930
import spoon.reflect.code.CtBlock;
3031
import spoon.reflect.code.CtExpression;
32+
import spoon.reflect.code.CtInvocation;
3133
import spoon.reflect.code.CtNewClass;
3234
import spoon.reflect.code.CtStatement;
35+
import spoon.reflect.declaration.CtConstructor;
3336
import spoon.reflect.declaration.CtEnum;
3437
import spoon.reflect.declaration.CtEnumValue;
3538
import spoon.reflect.declaration.CtField;
3639
import spoon.reflect.declaration.CtMethod;
3740
import spoon.reflect.declaration.CtType;
3841
import spoon.reflect.declaration.ModifierKind;
3942
import spoon.reflect.factory.Factory;
43+
import spoon.reflect.reference.CtExecutableReference;
4044
import spoon.reflect.visitor.filter.TypeFilter;
4145
import spoon.support.reflect.CtExtendedModifier;
46+
import spoon.test.GitHubIssue;
4247
import spoon.test.SpoonTestHelpers;
4348
import spoon.test.annotation.AnnotationTest;
4449
import spoon.test.enums.testclasses.Burritos;
@@ -58,6 +63,7 @@
5863

5964
import static org.hamcrest.CoreMatchers.hasItem;
6065
import static org.hamcrest.CoreMatchers.is;
66+
import static org.hamcrest.CoreMatchers.not;
6167
import static org.hamcrest.MatcherAssert.assertThat;
6268
import static org.junit.jupiter.api.Assertions.assertFalse;
6369
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -264,6 +270,25 @@ void testLocalEnumExists() {
264270
));
265271
}
266272

273+
@GitHubIssue(issueNumber = 4758, fixed = true)
274+
@DisplayName("Implicit enum constructors do not contain a super call")
275+
void testImplicitEnumConstructorSuperCall() {
276+
CtEnum<?> myEnum = (CtEnum<?>) Launcher.parseClass("enum Foo { CONSTANT; }");
277+
CtConstructor<?> constructor = myEnum.getConstructors().iterator().next();
278+
279+
assertThat(constructor.isImplicit(), is(true));
280+
281+
for (CtStatement statement : constructor.getBody().getStatements()) {
282+
if (!(statement instanceof CtInvocation)) {
283+
continue;
284+
}
285+
CtExecutableReference<?> executable = ((CtInvocation<?>) statement).getExecutable();
286+
if (!executable.getDeclaringType().getQualifiedName().equals("java.lang.Enum")) {
287+
continue;
288+
}
289+
assertThat(executable.getSimpleName(), not(is("<init>")));
290+
}
291+
}
267292

268293
static class NestedEnumTypeProvider implements ArgumentsProvider {
269294
private final CtType<?> ctClass;

0 commit comments

Comments
 (0)