Skip to content

Commit 8213bd0

Browse files
committed
GROOVY-7932: generate bridge methods for private constructors during static compilation (closes groovy#449)
1 parent fe8914e commit 8213bd0

File tree

3 files changed

+107
-15
lines changed

3 files changed

+107
-15
lines changed

src/main/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,21 +126,34 @@ public void writeInvokeConstructor(final ConstructorCallExpression call) {
126126
cn = new ConstructorNode(mn.getModifiers(), mn.getParameters(), mn.getExceptions(), mn.getCode());
127127
cn.setDeclaringClass(mn.getDeclaringClass());
128128
}
129+
TupleExpression args = makeArgumentList(call.getArguments());
129130
if (cn.isPrivate()) {
130131
ClassNode classNode = controller.getClassNode();
131132
ClassNode declaringClass = cn.getDeclaringClass();
132133
if (declaringClass != classNode) {
133-
controller.getSourceUnit().addError(new SyntaxException("Cannot call private constructor for " + declaringClass.toString(false) +
134+
MethodNode bridge = null;
135+
if (call.getNodeMetaData(StaticTypesMarker.PV_METHODS_ACCESS) != null) {
136+
Map<MethodNode, MethodNode> bridgeMethods = declaringClass.getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_BRIDGE_METHODS);
137+
bridge = bridgeMethods != null ? bridgeMethods.get(cn) : null;
138+
}
139+
if (bridge != null && bridge instanceof ConstructorNode) {
140+
ArgumentListExpression newArgs = new ArgumentListExpression(new ConstantExpression(null));
141+
for (Expression arg: args) {
142+
newArgs.addExpression(arg);
143+
}
144+
cn = (ConstructorNode) bridge;
145+
args = newArgs;
146+
} else {
147+
controller.getSourceUnit().addError(new SyntaxException("Cannot call private constructor for " + declaringClass.toString(false) +
134148
" from class " + classNode.toString(false), call.getLineNumber(), call.getColumnNumber(), mn.getLastLineNumber(), call.getLastColumnNumber()));
149+
}
135150
}
136151
}
137152

138153
String ownerDescriptor = prepareConstructorCall(cn);
139-
TupleExpression args = makeArgumentList(call.getArguments());
140154
int before = controller.getOperandStack().getStackLength();
141155
loadArguments(args.getExpressions(), cn.getParameters());
142156
finnishConstructorCall(cn, ownerDescriptor, controller.getOperandStack().getStackLength() - before);
143-
144157
}
145158

146159
@Override

src/main/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
import static org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys.*;
4747
import static org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
4848
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
49+
import static org.objectweb.asm.Opcodes.ACC_STATIC;
50+
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
4951

5052
/**
5153
* This visitor is responsible for amending the AST with static compilation metadata or transform the AST so that
@@ -248,6 +250,7 @@ private static void addPrivateBridgeMethods(final ClassNode node) {
248250
Set<ASTNode> accessedMethods = (Set<ASTNode>) node.getNodeMetaData(StaticTypesMarker.PV_METHODS_ACCESS);
249251
if (accessedMethods==null) return;
250252
List<MethodNode> methods = new ArrayList<MethodNode>(node.getAllDeclaredMethods());
253+
methods.addAll(node.getDeclaredConstructors());
251254
Map<MethodNode, MethodNode> privateBridgeMethods = (Map<MethodNode, MethodNode>) node.getNodeMetaData(PRIVATE_BRIDGE_METHODS);
252255
if (privateBridgeMethods!=null) {
253256
// private bridge methods already added
@@ -273,7 +276,6 @@ private static void addPrivateBridgeMethods(final ClassNode node) {
273276
orig.getName()
274277
);
275278
}
276-
newParams[0] = new Parameter(node.getPlainNodeReference(), "$that");
277279
Expression arguments;
278280
if (method.getParameters()==null || method.getParameters().length==0) {
279281
arguments = ArgumentListExpression.EMPTY_ARGUMENTS;
@@ -284,17 +286,36 @@ private static void addPrivateBridgeMethods(final ClassNode node) {
284286
}
285287
arguments = new ArgumentListExpression(args);
286288
}
287-
Expression receiver = method.isStatic()?new ClassExpression(node):new VariableExpression(newParams[0]);
288-
MethodCallExpression mce = new MethodCallExpression(receiver, method.getName(), arguments);
289-
mce.setMethodTarget(method);
290-
291-
ExpressionStatement returnStatement = new ExpressionStatement(mce);
292-
MethodNode bridge = node.addMethod(
293-
"access$"+i, access,
294-
correctToGenericsSpecRecurse(genericsSpec, method.getReturnType(), methodSpecificGenerics),
295-
newParams,
296-
method.getExceptions(),
297-
returnStatement);
289+
290+
MethodNode bridge;
291+
if (method instanceof ConstructorNode) {
292+
// create constructor with a nested class as the first parameter, creating one if necessary
293+
ClassNode thatType = null;
294+
Iterator<InnerClassNode> innerClasses = node.getInnerClasses();
295+
if (innerClasses.hasNext()) {
296+
thatType = innerClasses.next();
297+
} else {
298+
thatType = new InnerClassNode(node.redirect(), node.getName() + "$1", ACC_STATIC | ACC_SYNTHETIC, ClassHelper.OBJECT_TYPE);
299+
node.getModule().addClass(thatType);
300+
}
301+
newParams[0] = new Parameter(thatType.getPlainNodeReference(), "$that");
302+
Expression cce = new ConstructorCallExpression(ClassNode.THIS, arguments);
303+
Statement body = new ExpressionStatement(cce);
304+
bridge = node.addConstructor(ACC_SYNTHETIC, newParams, ClassNode.EMPTY_ARRAY, body);
305+
} else {
306+
newParams[0] = new Parameter(node.getPlainNodeReference(), "$that");
307+
Expression receiver = method.isStatic()?new ClassExpression(node):new VariableExpression(newParams[0]);
308+
MethodCallExpression mce = new MethodCallExpression(receiver, method.getName(), arguments);
309+
mce.setMethodTarget(method);
310+
311+
ExpressionStatement returnStatement = new ExpressionStatement(mce);
312+
bridge = node.addMethod(
313+
"access$"+i, access,
314+
correctToGenericsSpecRecurse(genericsSpec, method.getReturnType(), methodSpecificGenerics),
315+
newParams,
316+
method.getExceptions(),
317+
returnStatement);
318+
}
298319
GenericsType[] origGenericsTypes = method.getGenericsTypes();
299320
if (origGenericsTypes !=null) {
300321
bridge.setGenericsTypes(applyGenericsContextToPlaceHolders(genericsSpec,origGenericsTypes));

src/test/org/codehaus/groovy/classgen/asm/sc/StaticCompileConstructorsTest.groovy

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,63 @@ class StaticCompileConstructorsTest extends ConstructorsSTCTest implements Stati
6060
}
6161
""", "Cannot statically compile constructor implicitly including non static elements from object initializers, properties or fields")
6262
}
63+
64+
void testPrivateConstructorFromClosure() {
65+
try {
66+
assertScript '''
67+
class Foo {
68+
String s
69+
private Foo(String s) { this.s = s }
70+
static Foo makeFoo(String s) {
71+
def cl = { new Foo(s) }
72+
cl()
73+
}
74+
}
75+
assert Foo.makeFoo('pls').s == 'pls'
76+
'''
77+
} finally {
78+
//println astTrees
79+
}
80+
}
81+
82+
void testPrivateConstructorFromNestedClass() {
83+
try {
84+
assertScript '''
85+
class Foo {
86+
String s
87+
private Foo(String s) { this.s = s }
88+
static class Bar {
89+
static Foo makeFoo(String s) { new Foo(s) }
90+
}
91+
92+
}
93+
assert Foo.Bar.makeFoo('pls').s == 'pls'
94+
'''
95+
} finally {
96+
//println astTrees
97+
}
98+
}
99+
100+
void testPrivateConstructorFromAIC() {
101+
try {
102+
assertScript '''
103+
class Foo {
104+
String s
105+
private Foo(String s) { this.s = s }
106+
static Foo makeFoo(String s) {
107+
return new Object() {
108+
Foo makeFoo(String x) {
109+
new Foo(x)
110+
}
111+
}.makeFoo(s)
112+
}
113+
}
114+
assert Foo.makeFoo('pls').s == 'pls'
115+
'''
116+
} finally {
117+
//println astTrees
118+
}
119+
}
120+
63121
}
64122

0 commit comments

Comments
 (0)