Skip to content

Commit 3a033f0

Browse files
committed
GROOVY-5728, GROOVY-6747, GROOVY-7686
1 parent 3c14090 commit 3a033f0

File tree

17 files changed

+1458
-29
lines changed

17 files changed

+1458
-29
lines changed

base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/EnumerationTests.java

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2020 the original author or authors.
2+
* Copyright 2009-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -701,7 +701,7 @@ public void testEnum13() {
701701
runConformTest(sources);
702702
}
703703

704-
@Test // https://issues.apache.org/jira/browse/GROOVY-4219
704+
@Test
705705
public void testEnum4219() {
706706
//@formatter:off
707707
String[] sources = {
@@ -746,7 +746,7 @@ public void testEnum4219() {
746746
runConformTest(sources);
747747
}
748748

749-
@Test(timeout = 1500) // https://issues.apache.org/jira/browse/GROOVY-4438
749+
@Test(timeout = 1500)
750750
public void testEnum4438() {
751751
//@formatter:off
752752
String[] sources = {
@@ -765,7 +765,32 @@ public void testEnum4438() {
765765
runNegativeTest(sources, "");
766766
}
767767

768-
@Test(timeout = 1500) // https://issues.apache.org/jira/browse/GROOVY-8507
768+
@Test
769+
public void testEnum6747() {
770+
//@formatter:off
771+
String[] sources = {
772+
"Script.groovy",
773+
"enum Codes {\n" +
774+
" YES('Y') {\n" +
775+
" @Override String getCode() { /*string*/ }\n" +
776+
" },\n" +
777+
" NO('N') {\n" +
778+
" @Override String getCode() { /*string*/ }\n" +
779+
" }\n" +
780+
" abstract String getCode()\n" +
781+
" private final String string\n" +
782+
" private Codes(String string) {\n" +
783+
" this.string = string\n" +
784+
" }\n" +
785+
"}\n" +
786+
"print Codes.YES.code\n",
787+
};
788+
//@formatter:on
789+
790+
runConformTest(sources, "null"); // TODO: 'Y'
791+
}
792+
793+
@Test(timeout = 1500)
769794
public void testEnum8507() {
770795
//@formatter:off
771796
String[] sources = {

base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/InnerClassTests.java

+51-1
Original file line numberDiff line numberDiff line change
@@ -1481,8 +1481,58 @@ public void testAnonymousInnerClass28() {
14811481
runConformTest(sources, "class D$1;class D$2");
14821482
}
14831483

1484-
@Test // https://issues.apache.org/jira/browse/GROOVY-8104
1484+
@Test // https://issues.apache.org/jira/browse/GROOVY-5728
14851485
public void testAnonymousInnerClass29() {
1486+
//@formatter:off
1487+
String[] sources = {
1488+
"Script.groovy",
1489+
"abstract class A {\n" +
1490+
" private A() { }\n" +
1491+
" abstract answer()\n" +
1492+
" static A create() {\n" +
1493+
" return new A() {\n" + // IllegalAccessError when A$1 calls private constructor
1494+
" def answer() { 42 }\n" +
1495+
" }\n" +
1496+
" }\n" +
1497+
"}\n" +
1498+
"print A.create().answer()\n",
1499+
};
1500+
//@formatter:on
1501+
1502+
runConformTest(sources, "42");
1503+
}
1504+
1505+
@Test // https://issues.apache.org/jira/browse/GROOVY-7686
1506+
public void testAnonymousInnerClass30() {
1507+
//@formatter:off
1508+
String[] sources = {
1509+
"Script.groovy",
1510+
"abstract class A {\n" +
1511+
" A() {\n" +
1512+
" m()\n" +
1513+
" }\n" +
1514+
" abstract void m()\n" +
1515+
"}\n" +
1516+
"void test() {\n" +
1517+
" def v = false\n" +
1518+
" def a = new A() {\n" +
1519+
" // run by super ctor\n" +
1520+
" @Override void m() {\n" +
1521+
" assert v != null\n" +
1522+
" }\n" +
1523+
" }\n" +
1524+
" v = true\n" +
1525+
" a.m()\n" +
1526+
"}\n" +
1527+
"test()\n",
1528+
};
1529+
//@formatter:on
1530+
1531+
runConformTest(sources, "");
1532+
}
1533+
1534+
@Test // https://issues.apache.org/jira/browse/GROOVY-8104
1535+
public void testAnonymousInnerClass31() {
14861536
//@formatter:off
14871537
String[] sources = {
14881538
"Script.groovy",

base/org.codehaus.groovy25/.checkstyle

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
<file-match-pattern match-pattern="groovy/ast/tools/GenericsUtils.java" include-pattern="false" />
4040
<file-match-pattern match-pattern="groovy/classgen/AnnotationVisitor.java" include-pattern="false" />
4141
<file-match-pattern match-pattern="groovy/classgen/AsmClassGenerator.java" include-pattern="false" />
42-
<file-match-pattern match-pattern="groovy/classgen/EnumVisitor.java" include-pattern="false" />
42+
<file-match-pattern match-pattern="groovy/classgen/Enum(Completion)?Visitor.java" include-pattern="false" />
4343
<file-match-pattern match-pattern="groovy/classgen/ExtendedVerifier.java" include-pattern="false" />
44-
<file-match-pattern match-pattern="groovy/classgen/InnerClassVisitor(Helper)?.java" include-pattern="false" />
44+
<file-match-pattern match-pattern="groovy/classgen/InnerClass(CompletionVisitor|Visitor(Helper)?).java" include-pattern="false" />
4545
<file-match-pattern match-pattern="groovy/classgen/ReturnAdder.java" include-pattern="false" />
4646
<file-match-pattern match-pattern="groovy/classgen/Verifier.java" include-pattern="false" />
4747
<file-match-pattern match-pattern="groovy/classgen/asm/BinaryExpressionHelper.java" include-pattern="false" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.codehaus.groovy.classgen;
20+
21+
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
22+
import org.codehaus.groovy.ast.ClassHelper;
23+
import org.codehaus.groovy.ast.ClassNode;
24+
import org.codehaus.groovy.ast.CodeVisitorSupport;
25+
import org.codehaus.groovy.ast.ConstructorNode;
26+
import org.codehaus.groovy.ast.EnumConstantClassNode;
27+
import org.codehaus.groovy.ast.InnerClassNode;
28+
import org.codehaus.groovy.ast.Parameter;
29+
import org.codehaus.groovy.ast.expr.ArgumentListExpression;
30+
import org.codehaus.groovy.ast.expr.CastExpression;
31+
import org.codehaus.groovy.ast.expr.ConstantExpression;
32+
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
33+
import org.codehaus.groovy.ast.expr.Expression;
34+
import org.codehaus.groovy.ast.expr.TupleExpression;
35+
import org.codehaus.groovy.ast.expr.VariableExpression;
36+
import org.codehaus.groovy.ast.stmt.BlockStatement;
37+
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
38+
import org.codehaus.groovy.ast.stmt.Statement;
39+
import org.codehaus.groovy.control.CompilationUnit;
40+
import org.codehaus.groovy.control.SourceUnit;
41+
import org.codehaus.groovy.transform.TupleConstructorASTTransformation;
42+
43+
import java.util.ArrayList;
44+
import java.util.List;
45+
46+
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedConstructor;
47+
import static groovyjarjarasm.asm.Opcodes.ACC_PRIVATE;
48+
import static groovyjarjarasm.asm.Opcodes.ACC_SYNTHETIC;
49+
50+
/**
51+
* Enums have a parent constructor with two arguments from java.lang.Enum.
52+
* This visitor adds those two arguments into manually created constructors
53+
* and performs the necessary super call.
54+
*/
55+
public class EnumCompletionVisitor extends ClassCodeVisitorSupport {
56+
private final SourceUnit sourceUnit;
57+
58+
public EnumCompletionVisitor(CompilationUnit cu, SourceUnit su) {
59+
sourceUnit = su;
60+
}
61+
62+
public void visitClass(ClassNode node) {
63+
if (!node.isEnum()) return;
64+
completeEnum(node);
65+
}
66+
67+
protected SourceUnit getSourceUnit() {
68+
return sourceUnit;
69+
}
70+
71+
private void completeEnum(ClassNode enumClass) {
72+
boolean isAic = isAnonymousInnerClass(enumClass);
73+
/* GRECLIPSE edit -- GROOVY-6747
74+
if (enumClass.getDeclaredConstructors().isEmpty()) {
75+
*/
76+
if (nonSyntheticConstructors(enumClass).isEmpty()) {
77+
// GRECLIPSE end
78+
addImplicitConstructors(enumClass, isAic);
79+
}
80+
81+
/* GRECLIPSE edit -- GROOVY-6747
82+
for (ConstructorNode ctor : enumClass.getDeclaredConstructors()) {
83+
*/
84+
for (ConstructorNode ctor : nonSyntheticConstructors(enumClass)) {
85+
// GRECLIPSE end
86+
transformConstructor(ctor, isAic);
87+
}
88+
}
89+
90+
/**
91+
* Add map and no-arg constructor or mirror those of the superclass (i.e. base enum).
92+
*/
93+
private static void addImplicitConstructors(ClassNode enumClass, boolean aic) {
94+
/* GRECLIPSE edit -- GROOVY-6747
95+
if (aic) {
96+
ClassNode sn = enumClass.getSuperClass();
97+
List<ConstructorNode> sctors = new ArrayList<ConstructorNode>(sn.getDeclaredConstructors());
98+
if (sctors.isEmpty()) {
99+
addMapConstructors(enumClass);
100+
} else {
101+
for (ConstructorNode constructorNode : sctors) {
102+
ConstructorNode init = new ConstructorNode(ACC_PUBLIC, constructorNode.getParameters(), ClassNode.EMPTY_ARRAY, new BlockStatement());
103+
enumClass.addConstructor(init);
104+
}
105+
}
106+
} else {
107+
addMapConstructors(enumClass);
108+
}
109+
*/
110+
if (aic) {
111+
List<ConstructorNode> superCtors = nonSyntheticConstructors(enumClass.getSuperClass());
112+
if (!superCtors.isEmpty()) {
113+
for (ConstructorNode ctor : superCtors) {
114+
addGeneratedConstructor(enumClass, ACC_PRIVATE, ctor.getParameters(), ClassNode.EMPTY_ARRAY, new BlockStatement());
115+
}
116+
return;
117+
}
118+
}
119+
addMapConstructors(enumClass);
120+
// GRECLIPSE end
121+
}
122+
123+
/**
124+
* If constructor does not define a call to super, then transform constructor
125+
* to get String,int parameters at beginning and add call super(String,int).
126+
*/
127+
private void transformConstructor(ConstructorNode ctor, boolean isAic) {
128+
boolean chainedThisConstructorCall = false;
129+
ConstructorCallExpression cce = null;
130+
if (ctor.firstStatementIsSpecialConstructorCall()) {
131+
Statement code = ctor.getFirstStatement();
132+
cce = (ConstructorCallExpression) ((ExpressionStatement) code).getExpression();
133+
if (cce.isSuperCall()) return;
134+
// must be call to this(...)
135+
chainedThisConstructorCall = true;
136+
}
137+
// we need to add parameters
138+
Parameter[] oldP = ctor.getParameters();
139+
Parameter[] newP = new Parameter[oldP.length + 2];
140+
String stringParameterName = getUniqueVariableName("__str", ctor.getCode());
141+
newP[0] = new Parameter(ClassHelper.STRING_TYPE, stringParameterName);
142+
String intParameterName = getUniqueVariableName("__int", ctor.getCode());
143+
newP[1] = new Parameter(ClassHelper.int_TYPE, intParameterName);
144+
System.arraycopy(oldP, 0, newP, 2, oldP.length);
145+
ctor.setParameters(newP);
146+
VariableExpression stringVariable = new VariableExpression(newP[0]);
147+
VariableExpression intVariable = new VariableExpression(newP[1]);
148+
if (chainedThisConstructorCall) {
149+
TupleExpression args = (TupleExpression) cce.getArguments();
150+
List<Expression> argsExprs = args.getExpressions();
151+
argsExprs.add(0, stringVariable);
152+
argsExprs.add(1, intVariable);
153+
} else {
154+
// add a super call
155+
List<Expression> args = new ArrayList<Expression>();
156+
args.add(stringVariable);
157+
args.add(intVariable);
158+
if (isAic) {
159+
for (Parameter parameter : oldP) {
160+
args.add(new VariableExpression(parameter.getName()));
161+
}
162+
// GRECLIPSE add -- GROOVY-6747
163+
ClassNode enumClass = ctor.getDeclaringClass().getSuperClass();
164+
makeBridgeConstructor(enumClass, newP); // bridge enum's private constructor
165+
args.add(new CastExpression(enumClass.getPlainNodeReference(), ConstantExpression.NULL));
166+
// GRECLIPSE end
167+
}
168+
cce = new ConstructorCallExpression(ClassNode.SUPER, new ArgumentListExpression(args));
169+
BlockStatement code = new BlockStatement();
170+
code.addStatement(new ExpressionStatement(cce));
171+
Statement oldCode = ctor.getCode();
172+
if (oldCode != null) code.addStatement(oldCode);
173+
ctor.setCode(code);
174+
}
175+
}
176+
177+
private static void addMapConstructors(ClassNode enumClass) {
178+
TupleConstructorASTTransformation.addSpecialMapConstructors(ACC_PRIVATE, enumClass, "One of the enum constants for enum " + enumClass.getName() +
179+
" was initialized with null. Please use a non-null value or define your own constructor.", true);
180+
}
181+
182+
// GRECLIPSE add
183+
/**
184+
* Ensures the enum type {@code e} has an accessible constructor for its AIC
185+
* constant class to call. This constructor delegates to the enum's private
186+
* constructor.
187+
*/
188+
private static void makeBridgeConstructor(final ClassNode e, final Parameter[] p) {
189+
Parameter[] newP = new Parameter[p.length + 1];
190+
for (int i = 0; i < p.length; i += 1) {
191+
newP[i] = new Parameter(p[i].getType(), "p" + i);
192+
}
193+
newP[p.length] = new Parameter(e.getPlainNodeReference(), "$anonymous");
194+
195+
if (e.getDeclaredConstructor(newP) == null) {
196+
ArgumentListExpression args = new ArgumentListExpression();
197+
for (int i = 0; i < p.length; i += 1) args.addExpression(new VariableExpression(newP[i]));
198+
Statement thisCtorCall = new ExpressionStatement(new ConstructorCallExpression(ClassNode.THIS, args));
199+
addGeneratedConstructor(e, ACC_SYNTHETIC, newP, ClassNode.EMPTY_ARRAY, thisCtorCall).setSynthetic(true);
200+
}
201+
}
202+
203+
private static List<ConstructorNode> nonSyntheticConstructors(final ClassNode cn) {
204+
return cn.getDeclaredConstructors().stream().filter(c -> !c.isSynthetic()).collect(java.util.stream.Collectors.toList());
205+
}
206+
// GRECLIPSE end
207+
208+
private String getUniqueVariableName(final String name, Statement code) {
209+
if (code == null) return name;
210+
final Object[] found = new Object[1];
211+
CodeVisitorSupport cv = new CodeVisitorSupport() {
212+
public void visitVariableExpression(VariableExpression expression) {
213+
if (expression.getName().equals(name)) found[0] = Boolean.TRUE;
214+
}
215+
};
216+
code.visit(cv);
217+
if (found[0] != null) return getUniqueVariableName("_" + name, code);
218+
return name;
219+
}
220+
221+
private static boolean isAnonymousInnerClass(ClassNode enumClass) {
222+
if (!(enumClass instanceof EnumConstantClassNode)) return false;
223+
InnerClassNode ic = (InnerClassNode) enumClass;
224+
return ic.getVariableScope() == null;
225+
}
226+
}

0 commit comments

Comments
 (0)