Skip to content

Commit

Permalink
fix: Remove super calls from implicit enum constructors (#4769)
Browse files Browse the repository at this point in the history
  • Loading branch information
I-Al-Istannen committed Jul 7, 2022
1 parent 839b7da commit 3381d4a
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 1 deletion.
29 changes: 28 additions & 1 deletion src/main/java/spoon/support/compiler/jdt/ParentExiter.java
Expand Up @@ -520,14 +520,41 @@ public <T> void visitCtCatchVariable(CtCatchVariable<T> e) {
@Override
public <T> void visitCtClass(CtClass<T> ctClass) {
if (child instanceof CtConstructor) {
ctClass.addConstructor((CtConstructor<T>) child);
CtConstructor<T> constructor = (CtConstructor<T>) child;
ctClass.addConstructor(constructor);
fixJdtEnumConstructorSuperCall(ctClass, constructor);
}
if (child instanceof CtAnonymousExecutable) {
ctClass.addAnonymousExecutable((CtAnonymousExecutable) child);
}
super.visitCtClass(ctClass);
}

private <T> void fixJdtEnumConstructorSuperCall(CtClass<T> ctClass, CtConstructor<T> 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("<init>")) {
constructor.getBody().removeStatement(superCall);
}
}

@Override
public void visitCtTypeParameter(CtTypeParameter typeParameter) {
if (childJDT instanceof TypeReference && child instanceof CtTypeAccess) {
Expand Down
25 changes: 25 additions & 0 deletions src/test/java/spoon/test/enums/EnumsTest.java
Expand Up @@ -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;
Expand All @@ -28,17 +29,21 @@
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;
import spoon.reflect.declaration.CtMethod;
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;
Expand All @@ -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;
Expand Down Expand Up @@ -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("<init>")));
}
}

static class NestedEnumTypeProvider implements ArgumentsProvider {
private final CtType<?> ctClass;
Expand Down

0 comments on commit 3381d4a

Please sign in to comment.