Skip to content

Commit b3920ef

Browse files
Add support for lambda expressions (#269)
Add support for Java lambda expressions
1 parent 354d090 commit b3920ef

File tree

33 files changed

+1780
-33
lines changed

33 files changed

+1780
-33
lines changed

sourcegen-bytecode-writer/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ dependencies {
1616

1717
compileOnly(mn.micronaut.core.processor)
1818

19+
testImplementation(projects.sourcegenAnnotations)
20+
testImplementation(projects.sourcegenGenerator)
21+
testImplementation(mn.micronaut.inject.java.test)
1922
testImplementation(mn.micronaut.core.processor)
2023
testImplementation(mnTest.junit.jupiter.api)
2124
testImplementation(libs.intellij.java.decompiler)

sourcegen-bytecode-writer/src/main/java/io/micronaut/sourcegen/bytecode/ByteCodeWriter.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,13 +433,17 @@ private void writeProperty(ClassVisitor classWriter, ObjectDef objectDef, Proper
433433
}
434434

435435
/**
436-
* Write an interface.
436+
* Write a method.
437437
*
438438
* @param classVisitor The class visitor
439439
* @param objectDef The object definition
440440
* @param methodDef The method definition
441441
*/
442-
public void writeMethod(ClassVisitor classVisitor, @Nullable ObjectDef objectDef, MethodDef methodDef) {
442+
private void writeMethod(ClassVisitor classVisitor, @Nullable ObjectDef objectDef, MethodDef methodDef) {
443+
writeMethod(classVisitor, objectDef, methodDef, false);
444+
}
445+
446+
private void writeMethod(ClassVisitor classVisitor, @Nullable ObjectDef objectDef, MethodDef methodDef, boolean isLambda) {
443447
String name = methodDef.getName();
444448
String methodDescriptor = TypeUtils.getMethodDescriptor(objectDef, methodDef);
445449
int modifiersFlag = getModifiersFlag(methodDef.getModifiers());
@@ -468,7 +472,7 @@ public void writeMethod(ClassVisitor classVisitor, @Nullable ObjectDef objectDef
468472
generatorAdapter.visitAnnotableParameterCount(methodDef.getParameters().size(), true);
469473
}
470474

471-
MethodContext context = new MethodContext(objectDef, methodDef);
475+
MethodContext context = new MethodContext(objectDef, methodDef, isLambda);
472476
Label startMethod = null;
473477

474478
int parameterIndex = 0;
@@ -531,6 +535,10 @@ public void writeMethod(ClassVisitor classVisitor, @Nullable ObjectDef objectDef
531535
generatorAdapter.visitMaxs(20, 20);
532536
}
533537
generatorAdapter.visitEnd();
538+
539+
for (MethodDef lambdaDef: context.lambdaMethods()) {
540+
writeMethod(classVisitor, objectDef, lambdaDef, true);
541+
}
534542
}
535543

536544
private List<StatementDef> adjustConstructorStatements(ObjectDef objectDef, List<StatementDef> statements) {

sourcegen-bytecode-writer/src/main/java/io/micronaut/sourcegen/bytecode/MethodContext.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import org.objectweb.asm.Label;
2323
import org.objectweb.asm.Type;
2424

25+
import java.util.ArrayList;
2526
import java.util.LinkedHashMap;
27+
import java.util.List;
2628
import java.util.Map;
2729

2830
/**
@@ -31,16 +33,20 @@
3133
* @param objectDef The current object definition
3234
* @param methodDef The current method definition.
3335
* @param locals The locals
36+
* @param lambdaMethods Lambda methods created by this method
37+
* @param isLambda Whether this method is a lambda
3438
* @since 1.5
3539
*/
3640
@Internal
3741
public record MethodContext(@Nullable ObjectDef objectDef,
3842
MethodDef methodDef,
39-
Map<String, LocalData> locals) {
43+
Map<String, LocalData> locals,
44+
List<MethodDef> lambdaMethods,
45+
boolean isLambda) {
4046

4147
public MethodContext(@Nullable ObjectDef objectDef,
42-
MethodDef methodDef) {
43-
this(objectDef, methodDef, new LinkedHashMap<>());
48+
MethodDef methodDef, boolean isLambda) {
49+
this(objectDef, methodDef, new LinkedHashMap<>(), new ArrayList<>(), isLambda);
4450
}
4551

4652
/**

sourcegen-bytecode-writer/src/main/java/io/micronaut/sourcegen/bytecode/expression/AbstractStatementAwareExpressionWriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
* @author Denis Stepanov
2626
* @since 1.5
2727
*/
28-
public abstract sealed class AbstractStatementAwareExpressionWriter implements ExpressionWriter permits InvokeInstanceMethodExpressionWriter, InvokeStaticMethodExpressionWriter, NewInstanceExpressionWriter {
28+
public abstract sealed class AbstractStatementAwareExpressionWriter implements ExpressionWriter permits InvokeInstanceMethodExpressionWriter, InvokeStaticMethodExpressionWriter, NewInstanceExpressionWriter, LambdaExpressionWriter {
2929

3030
protected boolean statement;
3131

sourcegen-bytecode-writer/src/main/java/io/micronaut/sourcegen/bytecode/expression/ExpressionWriter.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.micronaut.core.reflect.ReflectionUtils;
1919
import io.micronaut.sourcegen.bytecode.MethodContext;
2020
import io.micronaut.sourcegen.model.ExpressionDef;
21+
import io.micronaut.sourcegen.model.ExpressionDef.Lambda;
2122
import io.micronaut.sourcegen.model.TypeDef;
2223
import io.micronaut.sourcegen.model.VariableDef;
2324
import org.objectweb.asm.commons.GeneratorAdapter;
@@ -94,6 +95,9 @@ static ExpressionWriter of(ExpressionDef expressionDef) {
9495
if (expressionDef instanceof ExpressionDef.InvokeHashCodeMethod invokeHashCodeMethod) {
9596
return new InvokeHashCodeMethodExpressionWriter(invokeHashCodeMethod);
9697
}
98+
if (expressionDef instanceof Lambda lambda) {
99+
return new LambdaExpressionWriter(lambda);
100+
}
97101
throw new UnsupportedOperationException("Unrecognized expression: " + expressionDef);
98102
}
99103

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Copyright 2017-2024 original authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.micronaut.sourcegen.bytecode.expression;
17+
18+
import io.micronaut.sourcegen.bytecode.MethodContext;
19+
import io.micronaut.sourcegen.bytecode.TypeUtils;
20+
import io.micronaut.sourcegen.model.ExpressionDef;
21+
import io.micronaut.sourcegen.model.ExpressionDef.Lambda;
22+
import io.micronaut.sourcegen.model.MethodDef;
23+
import io.micronaut.sourcegen.model.ParameterDef;
24+
import io.micronaut.sourcegen.model.StatementDef;
25+
import io.micronaut.sourcegen.model.VariableDef;
26+
import org.objectweb.asm.Handle;
27+
import org.objectweb.asm.Opcodes;
28+
import org.objectweb.asm.Type;
29+
import org.objectweb.asm.commons.GeneratorAdapter;
30+
31+
import javax.lang.model.element.Modifier;
32+
import java.util.ArrayList;
33+
import java.util.LinkedHashSet;
34+
import java.util.List;
35+
import java.util.Set;
36+
37+
final class LambdaExpressionWriter extends AbstractStatementAwareExpressionWriter {
38+
39+
public static final String EXCEPTION_VAR_NAME = "exception";
40+
public static final String THIS_VAR_NAME = "this";
41+
public static final String SUPER_VAR_NAME = "super";
42+
43+
private static final String METAFACTORY_OWNER = "java/lang/invoke/LambdaMetafactory";
44+
private static final String METAFACTORY_METHOD = "metafactory";
45+
private static final String METAFACTORY_DESCRIPTOR =
46+
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;" +
47+
"Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;" +
48+
"Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
49+
50+
private final Lambda lambda;
51+
52+
public LambdaExpressionWriter(Lambda lambda) {
53+
this.lambda = lambda;
54+
}
55+
56+
@Override
57+
public void write(GeneratorAdapter generatorAdapter, MethodContext context) {
58+
List<VariableDef> capturedVariables = captureVariables(lambda.implementation());
59+
MethodDef implementationMethodDef = createLambdaMethodDef(context, lambda, capturedVariables);
60+
context.lambdaMethods().add(implementationMethodDef);
61+
62+
// The captured variables are the parameters to the called bootstrap method
63+
for (VariableDef variable : capturedVariables) {
64+
new VariableExpressionWriter(variable).write(generatorAdapter, context);
65+
}
66+
67+
String descriptor = TypeUtils.getType(context.objectDef().getName()).getDescriptor();
68+
if (descriptor.endsWith(";")) {
69+
descriptor = descriptor.substring(0, descriptor.length() - 1);
70+
}
71+
if (descriptor.startsWith("L")) {
72+
descriptor = descriptor.substring(1);
73+
}
74+
Handle lambdaMethodHandle = new Handle(
75+
Opcodes.H_INVOKESTATIC,
76+
descriptor,
77+
implementationMethodDef.getName(),
78+
TypeUtils.getMethodDescriptor(context.objectDef(), implementationMethodDef),
79+
false
80+
);
81+
Handle bootstrapMethodHandle = new Handle(
82+
Opcodes.H_INVOKESTATIC,
83+
METAFACTORY_OWNER,
84+
METAFACTORY_METHOD,
85+
METAFACTORY_DESCRIPTOR,
86+
false
87+
);
88+
89+
generatorAdapter.visitInvokeDynamicInsn(
90+
lambda.implementation().getName(),
91+
createDynamicInvocationDescriptor(capturedVariables, context),
92+
bootstrapMethodHandle,
93+
Type.getType(TypeUtils.getMethodDescriptor(context.objectDef(), lambda.target())),
94+
lambdaMethodHandle,
95+
Type.getType(TypeUtils.getMethodDescriptor(context.objectDef(), lambda.implementation()))
96+
);
97+
popValueIfNeeded(generatorAdapter, lambda.type());
98+
}
99+
100+
private String createDynamicInvocationDescriptor(List<VariableDef> capturedVariables, MethodContext context) {
101+
StringBuilder dynamicDescriptor = new StringBuilder("(");
102+
for (VariableDef variable : capturedVariables) {
103+
dynamicDescriptor.append(TypeUtils.getType(variable.type(), context.objectDef()));
104+
}
105+
dynamicDescriptor.append(")");
106+
dynamicDescriptor.append(TypeUtils.getType(lambda.type()).getDescriptor());
107+
return dynamicDescriptor.toString();
108+
}
109+
110+
private MethodDef createLambdaMethodDef(MethodContext context, Lambda lambda, List<VariableDef> capturedVariables) {
111+
MethodDef original = lambda.implementation();
112+
List<ParameterDef> parameters = new ArrayList<>();
113+
114+
// The captured variables are parameters
115+
for (VariableDef variable : capturedVariables) {
116+
if (variable instanceof VariableDef.Local local) {
117+
parameters.add(ParameterDef.builder(local.name(), local.type()).build());
118+
} else if (variable instanceof VariableDef.MethodParameter parameter) {
119+
parameters.add(ParameterDef.builder(parameter.name(), parameter.type()).build());
120+
} else if (variable instanceof VariableDef.Field field) {
121+
parameters.add(ParameterDef.builder(field.name(), field.type()).build());
122+
} else if (variable instanceof VariableDef.This thisVar) {
123+
parameters.add(ParameterDef.builder(THIS_VAR_NAME, thisVar.type()).build());
124+
} else if (variable instanceof VariableDef.Super superVar) {
125+
parameters.add(ParameterDef.builder(SUPER_VAR_NAME, superVar.type()).build());
126+
} else if (variable instanceof VariableDef.ExceptionVar exception) {
127+
parameters.add(ParameterDef.builder(EXCEPTION_VAR_NAME, exception.type()).build());
128+
}
129+
}
130+
131+
parameters.addAll(original.getParameters());
132+
return MethodDef.builder("lambda$" + context.methodDef().getName() + "$" +
133+
context.lambdaMethods().size())
134+
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
135+
.addParameters(parameters)
136+
.returns(original.getReturnType())
137+
.addStatements(original.getStatements())
138+
.build();
139+
}
140+
141+
private List<VariableDef> captureVariables(MethodDef method) {
142+
Set<String> variables = new LinkedHashSet<>(
143+
method.getParameters().stream().map(v -> v.getName()).toList()
144+
);
145+
List<VariableDef> capturedVariables = new ArrayList<>();
146+
for (StatementDef statement : method.getStatements()) {
147+
captureVariables(statement, variables, capturedVariables);
148+
}
149+
return capturedVariables;
150+
}
151+
152+
private void captureVariables(StatementDef statement, Set<String> variables, List<VariableDef> capturedVariables) {
153+
statement.nestedExpressionsStream()
154+
.forEach(expressionDef -> captureVariables(expressionDef, variables, capturedVariables));
155+
}
156+
157+
private void captureVariables(ExpressionDef expression, Set<String> variables, List<VariableDef> capturedVariables) {
158+
if (expression instanceof VariableDef variable) {
159+
if (variable instanceof VariableDef.Local local) {
160+
if (!variables.contains(local.name())) {
161+
capturedVariables.add(local);
162+
variables.add(local.name());
163+
}
164+
} else if (variable instanceof VariableDef.MethodParameter parameter) {
165+
if (!variables.contains(parameter.name())) {
166+
capturedVariables.add(parameter);
167+
variables.add(parameter.name());
168+
}
169+
} else if (variable instanceof VariableDef.Field field) {
170+
captureVariables(field.instance(), variables, capturedVariables);
171+
} else if (variable instanceof VariableDef.This) {
172+
if (!variables.contains(THIS_VAR_NAME)) {
173+
capturedVariables.add(variable);
174+
variables.add(THIS_VAR_NAME);
175+
}
176+
} else if (variable instanceof VariableDef.Super) {
177+
if (!variables.contains(SUPER_VAR_NAME)) {
178+
capturedVariables.add(variable);
179+
variables.add(SUPER_VAR_NAME);
180+
}
181+
} else if (variable instanceof VariableDef.ExceptionVar) {
182+
if (!variables.contains(EXCEPTION_VAR_NAME)) {
183+
capturedVariables.add(variable);
184+
variables.add(EXCEPTION_VAR_NAME);
185+
}
186+
}
187+
} else {
188+
expression.nestedExpressionsStream()
189+
.forEach(expressionDef -> captureVariables(expressionDef, variables, capturedVariables));
190+
}
191+
}
192+
193+
}

0 commit comments

Comments
 (0)