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 @@ -77,6 +77,8 @@
import build.codemodel.jdk.expression.Symbol;
import build.codemodel.jdk.expression.Ternary;
import build.codemodel.jdk.expression.UnknownExpression;
import build.codemodel.jdk.statement.CatchClause;
import build.codemodel.jdk.statement.EnhancedFor;
import build.codemodel.jdk.statement.ExpressionStatement;
import build.codemodel.jdk.statement.LocalVariableDeclaration;
import build.codemodel.objectoriented.descriptor.ConstructorDescriptor;
Expand Down Expand Up @@ -148,6 +150,30 @@ public class JdkExpressionConverter
*/
private final Map<Element, LocalVariableDeclaration> localVariableDeclarations = new HashMap<>();

/**
* Maps a resolved javac {@link Element} for an enhanced-for loop variable to the
* {@link EnhancedFor} that declared it, populated as {@link JdkStatementConverter} converts
* each enhanced-for loop so that later identifier usages within the same body conversion can
* be linked back to their declaring loop.
*/
private final Map<Element, EnhancedFor> enhancedForDeclarations = new HashMap<>();

/**
* Maps a resolved javac {@link Element} for a catch-clause exception parameter to the
* {@link CatchClause} that declared it, populated as {@link JdkStatementConverter} converts
* each catch clause so that later identifier usages within the same body conversion can be
* linked back to their declaring clause.
*/
private final Map<Element, CatchClause> catchParameterDeclarations = new HashMap<>();

/**
* Maps a resolved javac {@link Element} for an {@code instanceof}/switch pattern binding
* variable to the {@link InstanceOf} that declared it, populated as each pattern test is
* converted so that later identifier usages within the same body conversion can be linked
* back to their declaring test.
*/
private final Map<Element, InstanceOf> patternBindingDeclarations = new HashMap<>();

/**
* Creates a {@link JdkExpressionConverter}.
*
Expand Down Expand Up @@ -289,6 +315,50 @@ void registerLocalVariableDeclaration(final VariableTree tree, final LocalVariab
}
}

/**
* Records that the given enhanced-for {@link VariableTree} declares {@code loop}'s loop
* variable, so that later {@link Symbol.EnhancedForVariable} resolution can link an identifier
* usage back to it.
*
* @param tree the {@link VariableTree} declaring the enhanced-for loop variable
* @param loop the {@link EnhancedFor} converted from the enclosing loop
*/
void registerEnhancedForVariable(final VariableTree tree, final EnhancedFor loop) {
elementOf(tree).ifPresent(element -> enhancedForDeclarations.put(element, loop));
}

/**
* Records that the given catch parameter {@link VariableTree} declares {@code catchClause}'s
* exception parameter, so that later {@link Symbol.CatchParameter} resolution can link an
* identifier usage back to it.
*
* @param tree the {@link VariableTree} declaring the catch parameter
* @param catchClause the {@link CatchClause} converted from the enclosing catch clause
*/
void registerCatchParameter(final VariableTree tree, final CatchClause catchClause) {
elementOf(tree).ifPresent(element -> catchParameterDeclarations.put(element, catchClause));
}

/**
* Records that the given pattern-binding {@link VariableTree} declares {@code instanceOf}'s
* binding variable, so that later {@link Symbol.PatternBinding} resolution can link an
* identifier usage back to it.
*
* @param tree the {@link VariableTree} declaring the pattern-binding variable
* @param instanceOf the {@link InstanceOf} pattern test that declared this binding
*/
void registerPatternBinding(final VariableTree tree, final InstanceOf instanceOf) {
elementOf(tree).ifPresent(element -> patternBindingDeclarations.put(element, instanceOf));
}

private Optional<Element> elementOf(final VariableTree tree) {
if (trees == null || compilationUnit == null) {
return Optional.empty();
}
final var path = trees.getPath(compilationUnit, tree);
return path == null ? Optional.empty() : Optional.ofNullable(trees.getElement(path));
}

private Optional<Symbol> resolveSymbol(final IdentifierTree t) {
if (trees == null || compilationUnit == null) {
return Optional.empty();
Expand All @@ -315,8 +385,13 @@ private Optional<Symbol> resolveSymbol(final IdentifierTree t) {
return Optional.empty();
}
return switch (element.getKind()) {
case LOCAL_VARIABLE -> Optional.<Symbol>of(new Symbol.LocalVariable(
typeUsage, Optional.ofNullable(localVariableDeclarations.get(element))));
case LOCAL_VARIABLE -> Optional.<Symbol>of(enhancedForDeclarations.containsKey(element)
? new Symbol.EnhancedForVariable(typeUsage, Optional.of(enhancedForDeclarations.get(element)))
: new Symbol.LocalVariable(typeUsage, Optional.ofNullable(localVariableDeclarations.get(element))));
case EXCEPTION_PARAMETER -> Optional.<Symbol>of(new Symbol.CatchParameter(
typeUsage, Optional.ofNullable(catchParameterDeclarations.get(element))));
case BINDING_VARIABLE -> Optional.<Symbol>of(new Symbol.PatternBinding(
typeUsage, Optional.ofNullable(patternBindingDeclarations.get(element))));
case PARAMETER -> resolveParameter(element).map(Symbol.class::cast);
case FIELD, ENUM_CONSTANT -> resolveField(element, typeUsage).map(Symbol.class::cast);
case CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, RECORD ->
Expand Down Expand Up @@ -675,10 +750,14 @@ public Expression visitInstanceOf(final InstanceOfTree t, final Void v) {
}
final var type = resolveTypeUsage(typeTree);
addSourceLocation(typeTree).ifPresent(type::addTrait);
return InstanceOf.of(
final var instanceOf = InstanceOf.of(
convert(t.getExpression()),
type,
bindingVariable);
if (t.getPattern() instanceof BindingPatternTree bp) {
registerPatternBinding(bp.getVariable(), instanceOf);
}
return instanceOf;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,14 @@ public Statement visitEnhancedForLoop(final EnhancedForLoopTree t, final Void v)
&& t.getVariable().getModifiers().getFlags().contains(Modifier.FINAL);
final TypeUsage type = exprConverter.resolveTypeUsage(t.getVariable().getType());
exprConverter.addSourceLocation(t.getVariable().getType()).ifPresent(type::addTrait);
final var enhancedFor = EnhancedFor.of(codeModel,
final var enhancedFor = EnhancedFor.ofPending(codeModel,
isFinal,
type,
t.getVariable().getName().toString(),
exprConverter.convert(t.getExpression()),
convert(t.getStatement()));
exprConverter.convert(t.getExpression()));
exprConverter.addSourceLocation(t.getVariable()).ifPresent(enhancedFor::addTrait);
exprConverter.registerEnhancedForVariable(t.getVariable(), enhancedFor);
enhancedFor.completeBody(convert(t.getStatement()));
return enhancedFor;
}

Expand Down Expand Up @@ -249,11 +250,12 @@ private CatchClause convertCatch(final CatchTree c) {
exprConverter.addSourceLocation(typeTree).ifPresent(type::addTrait);
types = List.of(type);
}
final var catchClause = CatchClause.of(codeModel,
final var catchClause = CatchClause.ofPending(codeModel,
types,
c.getParameter().getName().toString(),
convertBlock(c.getBlock()));
c.getParameter().getName().toString());
exprConverter.addSourceLocation(c.getParameter()).ifPresent(catchClause::addTrait);
exprConverter.registerCatchParameter(c.getParameter(), catchClause);
catchClause.completeBody(convertBlock(c.getBlock()));
return catchClause;
}

Expand Down Expand Up @@ -324,9 +326,7 @@ SwitchCase convertCase(final CaseTree c, final Expression selector) {

/**
* Converts a type-pattern or deconstruction-pattern case label to an {@link InstanceOf} label
* testing the switch {@code selector}. A binding variable's {@code Symbol} resolution is not
* wired up here — pattern-bound variables don't yet have a declaration node to resolve back to
* (see the TODO on {@code Symbol} resolution gaps).
* testing the switch {@code selector}.
*/
private Optional<Expression> convertPatternLabel(final PatternTree pattern,
final Expression selector) {
Expand All @@ -335,6 +335,7 @@ private Optional<Expression> convertPatternLabel(final PatternTree pattern,
exprConverter.addSourceLocation(binding.getVariable().getType()).ifPresent(type::addTrait);
final var instanceOf = InstanceOf.of(selector, type, Optional.of(binding.getVariable().getName().toString()));
exprConverter.addSourceLocation(binding.getVariable()).ifPresent(instanceOf::addTrait);
exprConverter.registerPatternBinding(binding.getVariable(), instanceOf);
return Optional.of(instanceOf);
}
if (pattern instanceof DeconstructionPatternTree deconstruction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import build.codemodel.foundation.descriptor.Trait;
import build.codemodel.foundation.naming.TypeName;
import build.codemodel.foundation.usage.TypeUsage;
import build.codemodel.jdk.statement.CatchClause;
import build.codemodel.jdk.statement.EnhancedFor;
import build.codemodel.jdk.statement.LocalVariableDeclaration;
import build.codemodel.objectoriented.descriptor.FieldDescriptor;

Expand All @@ -46,7 +48,8 @@
*/
@Singular
public sealed interface Symbol extends Trait, Composite
permits Symbol.LocalVariable, Symbol.Parameter, Symbol.Field,
permits Symbol.LocalVariable, Symbol.EnhancedForVariable, Symbol.CatchParameter,
Symbol.PatternBinding, Symbol.Parameter, Symbol.Field,
Symbol.TypeReference, Symbol.ThisReference, Symbol.SuperReference {

/**
Expand Down Expand Up @@ -84,6 +87,42 @@ public LocalVariable(final TypeUsage declaredType) {
}
}

/**
* An enhanced-for loop variable reference, e.g. {@code x} in
* {@code for (int x : xs) { return x; } }.
*
* @param declaredType the declared type of the loop variable
* @param declaration the {@link Optional} {@link EnhancedFor} statement that declared this
* variable, when it could be resolved back to the declaring loop within
* the same body conversion
*/
record EnhancedForVariable(TypeUsage declaredType, Optional<EnhancedFor> declaration) implements Symbol {
}

/**
* A catch-clause exception parameter reference, e.g. {@code e} in
* {@code catch (IOException e) { throw e; } }.
*
* @param declaredType the declared type of the exception parameter
* @param declaration the {@link Optional} {@link CatchClause} that declared this parameter,
* when it could be resolved back to the declaring clause within the same
* body conversion
*/
record CatchParameter(TypeUsage declaredType, Optional<CatchClause> declaration) implements Symbol {
}

/**
* An {@code instanceof} or switch pattern binding-variable reference, e.g. {@code s} in
* {@code if (o instanceof String s) { return s; } }.
*
* @param declaredType the declared type of the binding variable
* @param declaration the {@link Optional} {@link InstanceOf} pattern test that declared this
* binding, when it could be resolved back to the declaring test within the
* same body conversion
*/
record PatternBinding(TypeUsage declaredType, Optional<InstanceOf> declaration) implements Symbol {
}

/**
* A method or constructor parameter reference.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,15 @@ public final class CatchClause

/**
* The catch body block.
*
* <p>Not {@code final}: {@link #ofPending} constructs a {@link CatchClause} before its body is
* converted, so that the exception parameter's {@code Element} can be registered for
* {@code Symbol} resolution before body conversion visits identifiers referencing it — the
* body transitively references this very node, so it cannot be supplied as a constructor
* argument. {@link #completeBody} sets it exactly once; from any external caller's perspective
* the node is immutable once returned.
*/
private final Block body;
private Block body;

private CatchClause(final CodeModel codeModel,
final List<TypeUsage> exceptionTypes,
Expand All @@ -71,7 +78,7 @@ private CatchClause(final CodeModel codeModel,
super(codeModel);
this.exceptionTypes = Objects.requireNonNull(exceptionTypes, "exceptionTypes must not be null");
this.paramName = Objects.requireNonNull(paramName, "paramName must not be null");
this.body = Objects.requireNonNull(body, "body must not be null");
this.body = body;
}

@Unmarshal
Expand Down Expand Up @@ -154,7 +161,39 @@ public static CatchClause of(final CodeModel codeModel,
final List<TypeUsage> exceptionTypes,
final String paramName,
final Block body) {
return new CatchClause(codeModel, exceptionTypes, paramName, body);
return new CatchClause(codeModel, exceptionTypes, paramName,
Objects.requireNonNull(body, "body must not be null"));
}

/**
* Creates a {@link CatchClause} without its body, for callers that need the node's identity
* (e.g. to register the exception parameter's declaring construct for {@code Symbol}
* resolution) before the body can be converted. {@link #completeBody} must be called exactly
* once before any other code observes this node.
*
* @param codeModel the {@link CodeModel}
* @param exceptionTypes the resolved {@link TypeUsage}s of the caught exception(s)
* @param paramName the name of the exception parameter
* @return a new bodyless {@link CatchClause}, to be finished with {@link #completeBody}
*/
public static CatchClause ofPending(final CodeModel codeModel,
final List<TypeUsage> exceptionTypes,
final String paramName) {
return new CatchClause(codeModel, exceptionTypes, paramName, null);
}

/**
* Sets the body of a {@link CatchClause} created via {@link #ofPending}. May be called exactly
* once.
*
* @param body the catch body {@link Block}
* @throws IllegalStateException if the body was already set
*/
public void completeBody(final Block body) {
if (this.body != null) {
throw new IllegalStateException("CatchClause body is already set");
}
this.body = Objects.requireNonNull(body, "body must not be null");
}

static {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,15 @@ public final class EnhancedFor

/**
* The loop body statement.
*
* <p>Not {@code final}: {@link #ofPending} constructs an {@link EnhancedFor} before its body
* is converted, so that the loop variable's {@code Element} can be registered for
* {@code Symbol} resolution before body conversion visits identifiers referencing it — the
* body transitively references this very node, so it cannot be supplied as a constructor
* argument. {@link #completeBody} sets it exactly once; from any external caller's perspective
* the node is immutable once returned.
*/
private final Statement body;
private Statement body;

private EnhancedFor(final CodeModel codeModel,
final boolean isFinal,
Expand All @@ -84,7 +91,7 @@ private EnhancedFor(final CodeModel codeModel,
this.type = Objects.requireNonNull(type, "type must not be null");
this.variable = Objects.requireNonNull(variable, "variable must not be null");
this.iterable = Objects.requireNonNull(iterable, "iterable must not be null");
this.body = Objects.requireNonNull(body, "body must not be null");
this.body = body;
}

@Unmarshal
Expand Down Expand Up @@ -198,7 +205,43 @@ public static EnhancedFor of(final CodeModel codeModel,
final String variable,
final Expression iterable,
final Statement body) {
return new EnhancedFor(codeModel, isFinal, type, variable, iterable, body);
return new EnhancedFor(codeModel, isFinal, type, variable, iterable,
Objects.requireNonNull(body, "body must not be null"));
}

/**
* Creates an {@link EnhancedFor} without its body, for callers that need the node's identity
* (e.g. to register the loop variable's declaring construct for {@code Symbol} resolution)
* before the body can be converted. {@link #completeBody} must be called exactly once before
* any other code observes this node.
*
* @param codeModel the {@link CodeModel}
* @param isFinal whether the loop variable is {@code final}
* @param type the resolved {@link TypeUsage} of the loop variable
* @param variable the name of the loop variable
* @param iterable the iterable {@link Expression}
* @return a new bodyless {@link EnhancedFor}, to be finished with {@link #completeBody}
*/
public static EnhancedFor ofPending(final CodeModel codeModel,
final boolean isFinal,
final TypeUsage type,
final String variable,
final Expression iterable) {
return new EnhancedFor(codeModel, isFinal, type, variable, iterable, null);
}

/**
* Sets the loop body of an {@link EnhancedFor} created via {@link #ofPending}. May be called
* exactly once.
*
* @param body the loop body {@link Statement}
* @throws IllegalStateException if the body was already set
*/
public void completeBody(final Statement body) {
if (this.body != null) {
throw new IllegalStateException("EnhancedFor body is already set");
}
this.body = Objects.requireNonNull(body, "body must not be null");
}

static {
Expand Down
Loading