Skip to content

Commit d2ee082

Browse files
committed
fix(jdk-codemodel): resolve implicit lambda parameter types
1 parent 2aa95bf commit d2ee082

6 files changed

Lines changed: 88 additions & 89 deletions

File tree

jdk-codemodel/src/main/java/build/codemodel/jdk/JdkExpressionConverter.java

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -244,44 +244,37 @@ public Expression visitMethodInvocation(final MethodInvocationTree t, final Void
244244
}
245245

246246
TypeUsage resolveTypeUsage(final Tree typeTree) {
247-
if (typeTree == null || trees == null || compilationUnit == null || typeResolver == null) {
248-
return UnknownTypeUsage.create(codeModel);
249-
}
250-
try {
251-
final var path = TreePath.getPath(compilationUnit, typeTree);
252-
if (path == null) {
253-
return UnknownTypeUsage.create(codeModel);
254-
}
255-
final var mirror = trees.getTypeMirror(path);
256-
if (mirror == null
257-
|| mirror.getKind() == TypeKind.ERROR
258-
|| mirror.getKind() == TypeKind.NONE
259-
|| mirror.getKind() == TypeKind.OTHER) {
260-
return UnknownTypeUsage.create(codeModel);
261-
}
262-
return typeResolver.apply(mirror);
263-
} catch (final Exception e) {
264-
return UnknownTypeUsage.create(codeModel);
265-
}
247+
return resolveTypeMirror(typeTree)
248+
.map(typeResolver)
249+
.orElseGet(() -> UnknownTypeUsage.create(codeModel));
250+
}
251+
252+
private Optional<TypeUsage> resolveLambdaParameterType(final com.sun.source.tree.VariableTree p) {
253+
final var tree = p.getType() != null ? p.getType() : p;
254+
return resolveTypeMirror(tree).map(typeResolver);
266255
}
267256

268257
private Optional<TypeUsage> resolveReceiverType(final ExpressionTree receiverExpr) {
269-
if (trees == null || compilationUnit == null || typeResolver == null) {
258+
return resolveTypeMirror(receiverExpr).map(typeResolver);
259+
}
260+
261+
private Optional<TypeMirror> resolveTypeMirror(final Tree tree) {
262+
if (tree == null || trees == null || compilationUnit == null || typeResolver == null) {
270263
return Optional.empty();
271264
}
272265
try {
273-
final var path = TreePath.getPath(compilationUnit, receiverExpr);
266+
final var path = TreePath.getPath(compilationUnit, tree);
274267
if (path == null) {
275268
return Optional.empty();
276269
}
277-
final var typeMirror = trees.getTypeMirror(path);
278-
if (typeMirror == null
279-
|| typeMirror.getKind() == TypeKind.ERROR
280-
|| typeMirror.getKind() == TypeKind.NONE
281-
|| typeMirror.getKind() == TypeKind.OTHER) {
270+
final var mirror = trees.getTypeMirror(path);
271+
if (mirror == null
272+
|| mirror.getKind() == TypeKind.ERROR
273+
|| mirror.getKind() == TypeKind.NONE
274+
|| mirror.getKind() == TypeKind.OTHER) {
282275
return Optional.empty();
283276
}
284-
return Optional.of(typeResolver.apply(typeMirror));
277+
return Optional.of(mirror);
285278
} catch (final Exception e) {
286279
return Optional.empty();
287280
}
@@ -447,7 +440,7 @@ public Expression visitLambdaExpression(final LambdaExpressionTree t, final Void
447440
body = Block.empty(codeModel);
448441
}
449442
final var params = t.getParameters().stream()
450-
.map(p -> new LambdaParameter(resolveTypeUsage(p.getType()), p.getName().toString()))
443+
.map(p -> new LambdaParameter(resolveLambdaParameterType(p), p.getName().toString()))
451444
.toList();
452445
return Lambda.of(codeModel, params, body);
453446
}

jdk-codemodel/src/main/java/build/codemodel/jdk/JdkInitializer.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,6 @@ public void initialize(final CodeModel codeModel) {
208208
public Void visitClass(final ClassTree classTree, final Void unused) {
209209
exprConverter.setTypeContext(trees, cut,
210210
mirror -> resolveTypeUsage(mirror, null));
211-
stmtConverter.setTypeContext(trees, cut,
212-
mirror -> resolveTypeUsage(mirror, null));
213211
final TreePath classPath = trees.getPath(cut, classTree);
214212
final var typeElement = (TypeElement) trees.getElement(classPath);
215213
if (typeElement != null

jdk-codemodel/src/main/java/build/codemodel/jdk/JdkStatementConverter.java

Lines changed: 5 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import build.codemodel.expression.Expression;
2424
import build.codemodel.foundation.CodeModel;
2525
import build.codemodel.foundation.usage.TypeUsage;
26-
import build.codemodel.foundation.usage.UnknownTypeUsage;
2726
import build.codemodel.imperative.Block;
2827
import build.codemodel.imperative.If;
2928
import build.codemodel.imperative.Return;
@@ -49,7 +48,6 @@
4948
import com.sun.source.tree.BreakTree;
5049
import com.sun.source.tree.CaseTree;
5150
import com.sun.source.tree.CatchTree;
52-
import com.sun.source.tree.CompilationUnitTree;
5351
import com.sun.source.tree.ContinueTree;
5452
import com.sun.source.tree.DoWhileLoopTree;
5553
import com.sun.source.tree.EmptyStatementTree;
@@ -70,15 +68,10 @@
7068
import com.sun.source.tree.VariableTree;
7169
import com.sun.source.tree.WhileLoopTree;
7270
import com.sun.source.util.SimpleTreeVisitor;
73-
import com.sun.source.util.TreePath;
74-
import com.sun.source.util.Trees;
7571

7672
import java.util.List;
7773
import java.util.Optional;
78-
import java.util.function.Function;
7974
import javax.lang.model.element.Modifier;
80-
import javax.lang.model.type.TypeKind;
81-
import javax.lang.model.type.TypeMirror;
8275

8376
/**
8477
* Converts {@link StatementTree} nodes from the javac tree API to model {@link Statement} nodes.
@@ -91,14 +84,11 @@ public class JdkStatementConverter
9184

9285
private final CodeModel codeModel;
9386
private final JdkExpressionConverter exprConverter;
94-
private Trees trees;
95-
private CompilationUnitTree compilationUnit;
96-
private Function<TypeMirror, TypeUsage> typeResolver;
9787

9888
/**
9989
* Creates a {@link JdkStatementConverter}.
10090
*
101-
* @param codeModel the {@link CodeModel} used to construct statement nodes
91+
* @param codeModel the {@link CodeModel} used to construct statement nodes
10292
* @param exprConverter the {@link JdkExpressionConverter} used to convert sub-expressions
10393
*/
10494
public JdkStatementConverter(final CodeModel codeModel,
@@ -107,21 +97,6 @@ public JdkStatementConverter(final CodeModel codeModel,
10797
this.exprConverter = exprConverter;
10898
}
10999

110-
/**
111-
* Provides the type-resolution context required to resolve declared variable and parameter types.
112-
*
113-
* @param trees the javac {@link Trees} utility
114-
* @param compilationUnit the {@link CompilationUnitTree} being processed
115-
* @param typeResolver a function mapping a {@link TypeMirror} to a {@link TypeUsage}
116-
*/
117-
public void setTypeContext(final Trees trees,
118-
final CompilationUnitTree compilationUnit,
119-
final Function<TypeMirror, TypeUsage> typeResolver) {
120-
this.trees = trees;
121-
this.compilationUnit = compilationUnit;
122-
this.typeResolver = typeResolver;
123-
}
124-
125100
/**
126101
* Converts the given {@link StatementTree} to a model {@link Statement}.
127102
* Returns an empty {@link Block} for a {@code null} tree.
@@ -189,7 +164,7 @@ public Statement visitExpressionStatement(final ExpressionStatementTree t, final
189164
public Statement visitVariable(final VariableTree t, final Void v) {
190165
final boolean isFinal = t.getModifiers() != null
191166
&& t.getModifiers().getFlags().contains(Modifier.FINAL);
192-
final TypeUsage type = resolveTypeUsage(t.getType());
167+
final TypeUsage type = exprConverter.resolveTypeUsage(t.getType());
193168
return LocalVariableDeclaration.of(codeModel,
194169
isFinal,
195170
type,
@@ -216,7 +191,7 @@ public Statement visitForLoop(final ForLoopTree t, final Void v) {
216191
public Statement visitEnhancedForLoop(final EnhancedForLoopTree t, final Void v) {
217192
final boolean isFinal = t.getVariable().getModifiers() != null
218193
&& t.getVariable().getModifiers().getFlags().contains(Modifier.FINAL);
219-
final TypeUsage type = resolveTypeUsage(t.getVariable().getType());
194+
final TypeUsage type = exprConverter.resolveTypeUsage(t.getVariable().getType());
220195
return EnhancedFor.of(codeModel,
221196
isFinal,
222197
type,
@@ -245,10 +220,10 @@ private CatchClause convertCatch(final CatchTree c) {
245220
final List<TypeUsage> types;
246221
if (typeTree instanceof UnionTypeTree unionType) {
247222
types = unionType.getTypeAlternatives().stream()
248-
.map(this::resolveTypeUsage)
223+
.map(exprConverter::resolveTypeUsage)
249224
.toList();
250225
} else {
251-
types = List.of(resolveTypeUsage(typeTree));
226+
types = List.of(exprConverter.resolveTypeUsage(typeTree));
252227
}
253228
return CatchClause.of(codeModel,
254229
types,
@@ -320,25 +295,4 @@ protected Statement defaultAction(final Tree node, final Void v) {
320295
return Block.empty(codeModel);
321296
}
322297

323-
private TypeUsage resolveTypeUsage(final Tree typeTree) {
324-
if (typeTree == null || trees == null || compilationUnit == null || typeResolver == null) {
325-
return UnknownTypeUsage.create(codeModel);
326-
}
327-
try {
328-
final var path = TreePath.getPath(compilationUnit, typeTree);
329-
if (path == null) {
330-
return UnknownTypeUsage.create(codeModel);
331-
}
332-
final var mirror = trees.getTypeMirror(path);
333-
if (mirror == null
334-
|| mirror.getKind() == TypeKind.ERROR
335-
|| mirror.getKind() == TypeKind.NONE
336-
|| mirror.getKind() == TypeKind.OTHER) {
337-
return UnknownTypeUsage.create(codeModel);
338-
}
339-
return typeResolver.apply(mirror);
340-
} catch (final Exception e) {
341-
return UnknownTypeUsage.create(codeModel);
342-
}
343-
}
344298
}

jdk-codemodel/src/main/java/build/codemodel/jdk/expression/Lambda.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.lang.invoke.MethodHandles;
3737
import java.util.List;
3838
import java.util.Objects;
39+
import java.util.Optional;
3940
import java.util.stream.IntStream;
4041
import java.util.stream.Stream;
4142

@@ -76,7 +77,9 @@ public Lambda(@Bound final CodeModel codeModel,
7677
final Stream<String> paramNames,
7778
final Marshalled<Statement> body) {
7879
super(codeModel, marshaller, traits);
79-
final var typeList = paramTypes == null ? List.<TypeUsage>of() : paramTypes.map(marshaller::unmarshal).toList();
80+
final var typeList = paramTypes == null
81+
? List.<Optional<TypeUsage>>of()
82+
: paramTypes.map(mt -> Optional.<TypeUsage>ofNullable(marshaller.unmarshal(mt))).toList();
8083
final var nameList = paramNames == null ? List.<String>of() : paramNames.toList();
8184
this.parameters = IntStream.range(0, Math.min(typeList.size(), nameList.size()))
8285
.mapToObj(i -> new LambdaParameter(typeList.get(i), nameList.get(i)))
@@ -91,7 +94,7 @@ public void destructor(final Marshaller marshaller,
9194
final Out<Stream<String>> paramNames,
9295
final Out<Marshalled<Statement>> body) {
9396
super.destructor(marshaller, traits);
94-
paramTypes.set(this.parameters.stream().map(p -> marshaller.marshal(p.type())));
97+
paramTypes.set(this.parameters.stream().map(p -> marshaller.marshal(p.type().orElse(null))));
9598
paramNames.set(this.parameters.stream().map(LambdaParameter::name));
9699
body.set(marshaller.marshal(this.body));
97100
}

jdk-codemodel/src/main/java/build/codemodel/jdk/expression/LambdaParameter.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,21 @@
2222

2323
import build.codemodel.foundation.usage.TypeUsage;
2424

25+
import java.util.Optional;
26+
2527
/**
2628
* A parameter of a lambda expression, with its resolved {@link TypeUsage} and parameter name.
2729
*
28-
* @param type the resolved {@link TypeUsage} of the parameter
30+
* <p>For explicitly-annotated parameters the type is always present (resolving to
31+
* {@link build.codemodel.foundation.usage.UnknownTypeUsage} on failure). For implicitly-typed
32+
* parameters the type is present when javac's inferred type is reachable, and empty when it
33+
* is not — distinguishing "no annotation and inference unavailable" from "annotation present
34+
* but unresolvable".
35+
*
36+
* @param type an {@link Optional} resolved {@link TypeUsage} of the parameter
2937
* @param name the parameter name
3038
* @author reed.vonredwitz
3139
* @since Apr-2026
3240
*/
33-
public record LambdaParameter(TypeUsage type, String name) {
41+
public record LambdaParameter(Optional<TypeUsage> type, String name) {
3442
}

jdk-codemodel/src/test/java/build/codemodel/jdk/BodyCaptureTests.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,54 @@ public Comparator<String> comparator() {
134134
final var params = lambda.parameters().toList();
135135
assertThat(params).hasSize(2);
136136
assertThat(params.get(0).name()).isEqualTo("a");
137-
assertThat(params.get(0).type()).isInstanceOf(NamedTypeUsage.class);
138-
assertThat(((NamedTypeUsage) params.get(0).type()).typeName().name().toString()).isEqualTo("String");
137+
assertThat(params.get(0).type()).isPresent();
138+
assertThat(params.get(0).type().orElseThrow()).isInstanceOf(NamedTypeUsage.class);
139+
assertThat(((NamedTypeUsage) params.get(0).type().orElseThrow()).typeName().name().toString()).isEqualTo("String");
139140
assertThat(params.get(1).name()).isEqualTo("b");
140-
assertThat(params.get(1).type()).isInstanceOf(NamedTypeUsage.class);
141-
assertThat(((NamedTypeUsage) params.get(1).type()).typeName().name().toString()).isEqualTo("String");
141+
assertThat(params.get(1).type()).isPresent();
142+
assertThat(params.get(1).type().orElseThrow()).isInstanceOf(NamedTypeUsage.class);
143+
assertThat(((NamedTypeUsage) params.get(1).type().orElseThrow()).typeName().name().toString()).isEqualTo("String");
144+
}
145+
146+
@Test
147+
void shouldResolveImplicitLambdaParameterTypes() {
148+
final var source = JavaFileObjects.forSourceString(
149+
"build.codemodel.jdk.example.ImplicitSorter", """
150+
package build.codemodel.jdk.example;
151+
import java.util.Comparator;
152+
public class ImplicitSorter {
153+
public Comparator<String> comparator() {
154+
return (a, b) -> a.compareTo(b);
155+
}
156+
}
157+
""");
158+
159+
final var codeModel = JdkInitializerTests.runInternal(
160+
new JdkInitializer(List.of(), List.of(), List.of(source)));
161+
162+
final var typeName = codeModel.getNameProvider()
163+
.getTypeName(Optional.empty(), "build.codemodel.jdk.example.ImplicitSorter");
164+
final var method = codeModel.getTypeDescriptor(typeName).orElseThrow()
165+
.traits(MethodDescriptor.class)
166+
.filter(m -> m.methodName().name().toString().equals("comparator"))
167+
.findFirst().orElseThrow();
168+
final var body = method.getTrait(MethodBodyDescriptor.class).orElseThrow().body();
169+
final var lambda = body.statements()
170+
.map(s -> s instanceof Return r ? r.expression().orElse(null) : null)
171+
.filter(e -> e instanceof Lambda)
172+
.map(e -> (Lambda) e)
173+
.findFirst().orElseThrow();
174+
175+
final var params = lambda.parameters().toList();
176+
assertThat(params).hasSize(2);
177+
assertThat(params.get(0).name()).isEqualTo("a");
178+
assertThat(params.get(0).type()).isPresent();
179+
assertThat(params.get(0).type().orElseThrow()).isInstanceOf(NamedTypeUsage.class);
180+
assertThat(((NamedTypeUsage) params.get(0).type().orElseThrow()).typeName().name().toString()).isEqualTo("String");
181+
assertThat(params.get(1).name()).isEqualTo("b");
182+
assertThat(params.get(1).type()).isPresent();
183+
assertThat(params.get(1).type().orElseThrow()).isInstanceOf(NamedTypeUsage.class);
184+
assertThat(((NamedTypeUsage) params.get(1).type().orElseThrow()).typeName().name().toString()).isEqualTo("String");
142185
}
143186

144187
@Test

0 commit comments

Comments
 (0)