Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,11 @@ public Expression visitLambdaExpression(final LambdaExpressionTree t, final Void
body = Block.empty(codeModel);
}
final var params = t.getParameters().stream()
.map(p -> new LambdaParameter(resolveLambdaParameterType(p), p.getName().toString()))
.map(p -> {
final var param = new LambdaParameter(codeModel, resolveLambdaParameterType(p), p.getName().toString());
addSourceLocation(p).ifPresent(param::addTrait);
return param;
})
.toList();
return Lambda.of(codeModel, params, body);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,11 @@
import build.codemodel.expression.AbstractExpression;
import build.codemodel.foundation.CodeModel;
import build.codemodel.foundation.descriptor.Trait;
import build.codemodel.foundation.usage.TypeUsage;
import build.codemodel.imperative.Statement;

import java.lang.invoke.MethodHandles;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -74,29 +71,22 @@ private Lambda(final CodeModel codeModel,
public Lambda(@Bound final CodeModel codeModel,
final Marshaller marshaller,
final Stream<Marshalled<Trait>> traits,
final Stream<Marshalled<TypeUsage>> paramTypes,
final Stream<String> paramNames,
final Stream<Marshalled<LambdaParameter>> parameters,
final Marshalled<Statement> body) {
super(codeModel, marshaller, traits);
final var typeList = paramTypes == null
? List.<Optional<TypeUsage>>of()
: paramTypes.map(mt -> Optional.<TypeUsage>ofNullable(marshaller.unmarshal(mt))).toList();
final var nameList = paramNames == null ? List.<String>of() : paramNames.toList();
this.parameters = IntStream.range(0, Math.min(typeList.size(), nameList.size()))
.mapToObj(i -> new LambdaParameter(typeList.get(i), nameList.get(i)))
.toList();
this.parameters = parameters == null
? List.of()
: parameters.map(marshaller::unmarshal).toList();
this.body = marshaller.unmarshal(body);
}

@Marshal
public void destructor(final Marshaller marshaller,
final Out<Stream<Marshalled<Trait>>> traits,
final Out<Stream<Marshalled<TypeUsage>>> paramTypes,
final Out<Stream<String>> paramNames,
final Out<Stream<Marshalled<LambdaParameter>>> parameters,
final Out<Marshalled<Statement>> body) {
super.destructor(marshaller, traits);
paramTypes.set(this.parameters.stream().map(p -> marshaller.marshal(p.type().orElse(null))));
paramNames.set(this.parameters.stream().map(LambdaParameter::name));
parameters.set(this.parameters.stream().map(marshaller::marshal));
body.set(marshaller.marshal(this.body));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,24 @@
* #L%
*/

import build.base.foundation.iterator.Iterators;
import build.base.marshalling.Bound;
import build.base.marshalling.Marshal;
import build.base.marshalling.Marshalled;
import build.base.marshalling.Marshaller;
import build.base.marshalling.Marshalling;
import build.base.marshalling.Out;
import build.base.marshalling.Unmarshal;
import build.base.mereology.Composite;
import build.codemodel.foundation.CodeModel;
import build.codemodel.foundation.descriptor.AbstractTraitable;
import build.codemodel.foundation.descriptor.Trait;
import build.codemodel.foundation.descriptor.Traitable;
import build.codemodel.foundation.usage.TypeUsage;

import java.util.Iterator;
import java.lang.invoke.MethodHandles;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

/**
* A parameter of a lambda expression, with its resolved {@link TypeUsage} and parameter name.
Expand All @@ -36,17 +48,125 @@
* is not — distinguishing "no annotation and inference unavailable" from "annotation present
* but unresolvable".
*
* @param type an {@link Optional} resolved {@link TypeUsage} of the parameter
* @param name the parameter name
* @author reed.vonredwitz
* @since Apr-2026
*/
public record LambdaParameter(Optional<TypeUsage> type, String name) implements Composite {
public final class LambdaParameter
extends AbstractTraitable
implements Traitable {

/**
* The {@link Optional} resolved {@link TypeUsage} of the parameter.
*/
private final Optional<TypeUsage> type;

/**
* The parameter name.
*/
private final String name;

/**
* Constructs a {@link LambdaParameter}.
*
* @param codeModel the {@link CodeModel}
* @param type the {@link Optional} resolved {@link TypeUsage} of the parameter
* @param name the parameter name
*/
public LambdaParameter(final CodeModel codeModel,
final Optional<TypeUsage> type,
final String name) {

super(codeModel);

this.type = type == null ? Optional.empty() : type;
this.name = Objects.requireNonNull(name, "The name must not be null");
}

/**
* {@link Unmarshal} a {@link LambdaParameter}.
*
* @param codeModel the {@link CodeModel}
* @param marshaller the {@link Marshaller} for unmarshalling the {@link Marshalled} {@link Trait}s
* @param traits the {@link Marshalled} {@link Trait}s
* @param type the {@link Optional} {@link Marshalled} {@link TypeUsage} of the parameter
* @param name the parameter name
*/
@Unmarshal
public LambdaParameter(@Bound final CodeModel codeModel,
final Marshaller marshaller,
final Stream<Marshalled<Trait>> traits,
final Optional<Marshalled<TypeUsage>> type,
final String name) {

super(codeModel, marshaller, traits);

this.type = type == null ? Optional.empty() : type.map(marshaller::unmarshal);
this.name = name;
}

/**
* {@link Marshal} a {@link LambdaParameter}.
*
* @param marshaller the {@link Marshaller}
* @param traits the {@link Marshalled} {@link Trait}s
* @param type the {@link Optional} {@link Marshalled} {@link TypeUsage} of the parameter
* @param name the parameter name
*/
@Marshal
public void destructor(final Marshaller marshaller,
final Out<Stream<Marshalled<Trait>>> traits,
final Out<Optional<Marshalled<TypeUsage>>> type,
final Out<String> name) {

super.destructor(marshaller, traits);

type.set(this.type.map(marshaller::marshal));
name.set(this.name);
}

/**
* Obtains the {@link Optional} resolved {@link TypeUsage} of the parameter.
*
* @return the {@link Optional} {@link TypeUsage}
*/
public Optional<TypeUsage> type() {
return this.type;
}

/**
* Obtains the parameter name.
*
* @return the parameter name
*/
public String name() {
return this.name;
}

@Override
public <T> Iterator<T> iterator(final Class<T> kind) {
return type.filter(kind::isInstance)
.map(t -> Iterators.of(kind.cast(t)))
.orElseGet(Iterators::empty);
public Stream<? extends Composite> compositeChildren() {
return this.type.stream();
}

@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
return object instanceof LambdaParameter other
&& Objects.equals(this.type, other.type)
&& Objects.equals(this.name, other.name)
&& super.equals(other);
}

@Override
public int hashCode() {
return Objects.hash(this.type, this.name, super.hashCode());
}

static {
Marshalling.register(LambdaParameter.class, MethodHandles.lookup());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
* #L%
*/

import build.codemodel.expression.Expression;
import build.codemodel.imperative.Return;
import build.codemodel.jdk.descriptor.MethodBodyDescriptor;
import build.codemodel.jdk.descriptor.SourceLocation;
import build.codemodel.jdk.expression.Lambda;
import build.codemodel.jdk.statement.EnhancedFor;
import build.codemodel.jdk.statement.LocalVariableDeclaration;
import build.codemodel.objectoriented.descriptor.ConstructorDescriptor;
Expand Down Expand Up @@ -215,4 +218,36 @@ public void run(List<String> values) {

assertThat(loop.getTrait(SourceLocation.FilePosition.class)).isPresent();
}

@Test
void lambdaParameterShouldCarryLocationTrait() {
final var source = JavaFileObjects.forSourceString(
"com.example.Foo", """
package com.example;
import java.util.Comparator;
public class Foo {
public Comparator<String> comparator() {
return (String a, String b) -> a.compareTo(b);
}
}
""");

final var codeModel = JdkInitializerTests.runInternal(
new JdkInitializer(List.of(), List.of(), List.of(source)));

final var typeName = codeModel.getNameProvider().getTypeName(Optional.empty(), "com.example.Foo");
final var method = codeModel.getTypeDescriptor(typeName).orElseThrow()
.traits(MethodDescriptor.class).findFirst().orElseThrow();
final var body = method.getTrait(MethodBodyDescriptor.class).orElseThrow().body();
final var lambda = (Lambda) body.statements()
.map(s -> s instanceof Return r ? r.expression().orElse(null) : (Expression) null)
.filter(e -> e instanceof Lambda)
.findFirst().orElseThrow();

final var param = lambda.parameters().findFirst().orElseThrow();
assertThat(param.getTrait(SourceLocation.FilePosition.class)).isPresent();
final var location = param.getTrait(SourceLocation.FilePosition.class).orElseThrow();
assertThat(location.startPosition()).isGreaterThanOrEqualTo(0);
assertThat(location.endPosition()).isGreaterThan(location.startPosition());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ void switchExpressionPartsContainsSelectorAndCases() {

@Test
void lambdaPartsContainsParametersAndBody() {
final var param = new LambdaParameter(Optional.of(stringType()), "s");
final var param = new LambdaParameter(codeModel, Optional.of(stringType()), "s");
final var body = Block.of(Assert.of(BooleanLiteral.of(codeModel, true), Optional.empty()));
final var expr = Lambda.of(codeModel, List.of(param), body);
assertThat(expr.parts().toList()).containsExactlyInAnyOrder(param, body);
Expand All @@ -262,13 +262,13 @@ void lambdaPartsContainsParametersAndBody() {
@Test
void lambdaParameterWithType_partsContainsType() {
final var type = stringType();
final var param = new LambdaParameter(Optional.of(type), "s");
final var param = new LambdaParameter(codeModel, Optional.of(type), "s");
assertThat(param.parts().toList()).containsExactly(type);
}

@Test
void lambdaParameterWithoutType_partsIsEmpty() {
final var param = new LambdaParameter(Optional.empty(), "s");
final var param = new LambdaParameter(codeModel, Optional.empty(), "s");
assertThat(param.parts().toList()).isEmpty();
}

Expand Down