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 @@ -42,6 +42,7 @@
import build.codemodel.expression.StringLiteral;
import build.codemodel.expression.Subtraction;
import build.codemodel.foundation.CodeModel;
import build.codemodel.foundation.descriptor.CallableDescriptor;
import build.codemodel.foundation.usage.NamedTypeUsage;
import build.codemodel.foundation.usage.TypeUsage;
import build.codemodel.foundation.usage.UnknownTypeUsage;
Expand Down Expand Up @@ -76,6 +77,7 @@
import build.codemodel.jdk.expression.Ternary;
import build.codemodel.jdk.expression.UnknownExpression;
import build.codemodel.jdk.statement.ExpressionStatement;
import build.codemodel.objectoriented.descriptor.ConstructorDescriptor;
import build.codemodel.objectoriented.descriptor.FieldDescriptor;
import build.codemodel.objectoriented.descriptor.MethodDescriptor;
import com.sun.source.tree.ArrayAccessTree;
Expand Down Expand Up @@ -110,6 +112,7 @@
import java.util.Optional;
import java.util.function.Function;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
Expand Down Expand Up @@ -275,7 +278,7 @@ private Optional<Symbol> resolveSymbol(final IdentifierTree t) {
}
return switch (element.getKind()) {
case LOCAL_VARIABLE -> Optional.<Symbol>of(new Symbol.LocalVariable(typeUsage));
case PARAMETER -> Optional.<Symbol>of(new Symbol.Parameter(typeUsage));
case PARAMETER -> resolveParameter(element).map(Symbol.class::cast);
case FIELD, ENUM_CONSTANT -> resolveField(element).map(Symbol.class::cast);
case CLASS, INTERFACE, ENUM, ANNOTATION_TYPE, RECORD ->
Optional.<Symbol>of(new Symbol.TypeReference(typeUsage));
Expand All @@ -300,6 +303,44 @@ private Optional<Symbol.Field> resolveField(final Element element) {
.map(Symbol.Field::new);
}

private Optional<Symbol.Parameter> resolveParameter(final Element element) {
if (!(element.getEnclosingElement() instanceof ExecutableElement executableElement)) {
return Optional.empty();
}
// A lambda expression's parameters are owned by the enclosing method/constructor rather than
// a symbol of their own, so they won't appear here — leave them without a Symbol trait.
final var index = executableElement.getParameters().indexOf(element);
if (index < 0) {
return Optional.empty();
}
if (!(executableElement.getEnclosingElement() instanceof TypeElement typeElement)) {
return Optional.empty();
}
final var fqn = typeElement.getQualifiedName().toString();
final var typeName = codeModel.getNameProvider().getTypeName(Optional.empty(), fqn);
final var typeDescriptor = codeModel.getTypeDescriptor(typeName).orElse(null);
if (typeDescriptor == null) {
return Optional.empty();
}
final var paramTypes = executableElement.getParameters().stream()
.map(p -> typeResolver.apply(p.asType()))
.toList();
final var arity = paramTypes.size();
final Optional<? extends CallableDescriptor> callable = executableElement.getKind() == ElementKind.CONSTRUCTOR
? typeDescriptor.traits(ConstructorDescriptor.class)
.filter(cd -> cd.getFormalParameterCount() == arity)
.filter(cd -> parametersMatch(cd, paramTypes))
.findFirst()
: typeDescriptor.traits(MethodDescriptor.class)
.filter(md -> md.methodName().name().toString().equals(executableElement.getSimpleName().toString()))
.filter(md -> md.getFormalParameterCount() == arity)
.filter(md -> parametersMatch(md, paramTypes))
.findFirst();
return callable
.map(c -> c.getFormalParameter(index))
.map(Symbol.Parameter::new);
}

private Optional<ResolvedMethod> resolveMethod(final MethodInvocationTree t) {
if (trees == null || compilationUnit == null || typeResolver == null) {
return Optional.empty();
Expand All @@ -310,36 +351,56 @@ private Optional<ResolvedMethod> resolveMethod(final MethodInvocationTree t) {
return Optional.empty();
}
final var element = trees.getElement(selectPath);
if (!(element instanceof ExecutableElement executableElement)) {
return Optional.empty();
}
if (!(executableElement.getEnclosingElement() instanceof TypeElement typeElement)) {
return Optional.empty();
}
final var fqn = typeElement.getQualifiedName().toString();
final var typeName = codeModel.getNameProvider().getTypeName(Optional.empty(), fqn);
final var typeDescriptor = codeModel.getTypeDescriptor(typeName).orElse(null);
if (typeDescriptor == null) {
return resolveMethod(element);
} catch (final Exception e) {
return Optional.empty();
}
}

private Optional<ResolvedMethod> resolveMethod(final MemberReferenceTree t) {
if (trees == null || compilationUnit == null || typeResolver == null) {
return Optional.empty();
}
try {
final var path = TreePath.getPath(compilationUnit, t);
if (path == null) {
return Optional.empty();
}
final var simpleName = executableElement.getSimpleName().toString();
final var arity = executableElement.getParameters().size();
final var paramTypes = executableElement.getParameters().stream()
.map(p -> typeResolver.apply(p.asType()))
.toList();
return typeDescriptor.traits(MethodDescriptor.class)
.filter(md -> md.methodName().name().toString().equals(simpleName))
.filter(md -> md.getFormalParameterCount() == arity)
.filter(md -> parametersMatch(md, paramTypes))
.findFirst()
.map(ResolvedMethod::new);
final var element = trees.getElement(path);
return resolveMethod(element);
} catch (final Exception e) {
return Optional.empty();
}
}

private boolean parametersMatch(final MethodDescriptor md, final List<TypeUsage> paramTypes) {
final var formals = md.formalParameters().toList();
private Optional<ResolvedMethod> resolveMethod(final Element element) {
if (!(element instanceof ExecutableElement executableElement)) {
return Optional.empty();
}
if (!(executableElement.getEnclosingElement() instanceof TypeElement typeElement)) {
return Optional.empty();
}
final var fqn = typeElement.getQualifiedName().toString();
final var typeName = codeModel.getNameProvider().getTypeName(Optional.empty(), fqn);
final var typeDescriptor = codeModel.getTypeDescriptor(typeName).orElse(null);
if (typeDescriptor == null) {
return Optional.empty();
}
final var simpleName = executableElement.getSimpleName().toString();
final var arity = executableElement.getParameters().size();
final var paramTypes = executableElement.getParameters().stream()
.map(p -> typeResolver.apply(p.asType()))
.toList();
return typeDescriptor.traits(MethodDescriptor.class)
.filter(md -> md.methodName().name().toString().equals(simpleName))
.filter(md -> md.getFormalParameterCount() == arity)
.filter(md -> parametersMatch(md, paramTypes))
.findFirst()
.map(ResolvedMethod::new);
}

private boolean parametersMatch(final CallableDescriptor cd, final List<TypeUsage> paramTypes) {
final var formals = cd.formalParameters().toList();
for (int i = 0; i < formals.size(); i++) {
if (!typeUsageNamesMatch(formals.get(i).type(), paramTypes.get(i))) {
return false;
Expand Down Expand Up @@ -601,10 +662,13 @@ public Expression visitSwitchExpression(final SwitchExpressionTree t, final Void
@Override
public Expression visitMemberReference(final MemberReferenceTree t, final Void v) {
final var qualifierExpr = t.getQualifierExpression();
return MethodReference.of(
final var reference = MethodReference.of(
convert(qualifierExpr),
t.getName().toString(),
resolveReceiverType(qualifierExpr));
resolveMethod(t).ifPresent(reference::addTrait);
addSourceLocation(t).ifPresent(reference::addTrait);
return reference;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,12 @@ private CatchClause convertCatch(final CatchTree c) {
} else {
types = List.of(exprConverter.resolveTypeUsage(typeTree));
}
return CatchClause.of(codeModel,
final var catchClause = CatchClause.of(codeModel,
types,
c.getParameter().getName().toString(),
convertBlock(c.getBlock()));
exprConverter.addSourceLocation(c.getParameter()).ifPresent(catchClause::addTrait);
return catchClause;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import build.base.foundation.iterator.Iterators;
import build.base.mereology.Composite;
import build.codemodel.foundation.descriptor.FormalParameterDescriptor;
import build.codemodel.foundation.descriptor.Singular;
import build.codemodel.foundation.descriptor.Trait;
import build.codemodel.foundation.usage.TypeUsage;
Expand Down Expand Up @@ -69,9 +70,13 @@ record LocalVariable(TypeUsage declaredType) implements Symbol {
/**
* A method or constructor parameter reference.
*
* @param declaredType the declared type of the parameter
* @param descriptor the resolved {@link FormalParameterDescriptor} declaring this parameter
*/
record Parameter(TypeUsage declaredType) implements Symbol {
record Parameter(FormalParameterDescriptor descriptor) implements Symbol {
@Override
public TypeUsage declaredType() {
return descriptor.type();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import build.codemodel.jdk.expression.Lambda;
import build.codemodel.jdk.statement.EnhancedFor;
import build.codemodel.jdk.statement.LocalVariableDeclaration;
import build.codemodel.jdk.statement.Try;
import build.codemodel.objectoriented.descriptor.ConstructorDescriptor;
import build.codemodel.objectoriented.descriptor.FieldDescriptor;
import build.codemodel.objectoriented.descriptor.MethodDescriptor;
Expand Down Expand Up @@ -250,4 +251,35 @@ public Comparator<String> comparator() {
assertThat(location.startPosition()).isGreaterThanOrEqualTo(0);
assertThat(location.endPosition()).isGreaterThan(location.startPosition());
}

@Test
void catchClauseExceptionParameterShouldCarryLocationTrait() {
final var source = JavaFileObjects.forSourceString(
"com.example.Foo", """
package com.example;
public class Foo {
public void run() {
try {
System.out.println();
} catch (RuntimeException e) {
}
}
}
""");

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 tryStatement = (Try) body.statements().findFirst().orElseThrow();
final var catchClause = tryStatement.catches().findFirst().orElseThrow();

assertThat(catchClause.getTrait(SourceLocation.FilePosition.class)).isPresent();
final var location = catchClause.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 @@ -2,7 +2,9 @@

import build.codemodel.foundation.usage.NamedTypeUsage;
import build.codemodel.jdk.descriptor.MethodBodyDescriptor;
import build.codemodel.jdk.descriptor.SourceLocation;
import build.codemodel.jdk.expression.MethodReference;
import build.codemodel.jdk.expression.ResolvedMethod;
import build.codemodel.objectoriented.descriptor.MethodDescriptor;
import build.base.compile.testing.JavaFileObjects;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -91,4 +93,47 @@ public void run() {
assertThat(((NamedTypeUsage) ref.qualifierType().get()).typeName().name().toString())
.isEqualTo("Integer");
}

@Test
void shouldResolveMethodAndSourceLocationForSameTypeMethodReference() {
final var source = JavaFileObjects.forSourceString(
"build.codemodel.jdk.example.Caller", """
package build.codemodel.jdk.example;
import java.util.function.Supplier;
public class Caller {
public String greet() {
return "hello";
}
public void run() {
Supplier<String> s = this::greet;
}
}
""");

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

final var typeName = codeModel.getNameProvider()
.getTypeName(Optional.empty(), "build.codemodel.jdk.example.Caller");
final var descriptor = codeModel.getTypeDescriptor(typeName).orElseThrow();

final var run = descriptor.traits(MethodDescriptor.class)
.filter(m -> m.methodName().name().toString().equals("run"))
.findFirst().orElseThrow();

final var body = run.getTrait(MethodBodyDescriptor.class).orElseThrow().body();
final var decl = (build.codemodel.jdk.statement.LocalVariableDeclaration)
body.statements().findFirst().orElseThrow();
final var ref = (MethodReference) decl.initializer().orElseThrow();

assertThat(ref.methodName()).isEqualTo("greet");
final var resolved = ref.getTrait(ResolvedMethod.class).orElseThrow();
assertThat(resolved.descriptor().methodName().name().toString()).isEqualTo("greet");
assertThat(resolved.descriptor().typeDescriptor().typeName()).isEqualTo(typeName);

assertThat(ref.getTrait(SourceLocation.FilePosition.class)).isPresent();
final var location = ref.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 @@ -3,8 +3,11 @@
import build.codemodel.foundation.usage.NamedTypeUsage;
import build.codemodel.imperative.Return;
import build.codemodel.jdk.descriptor.MethodBodyDescriptor;
import build.codemodel.jdk.expression.CompoundAssignment;
import build.codemodel.jdk.expression.Identifier;
import build.codemodel.jdk.expression.Symbol;
import build.codemodel.jdk.statement.ExpressionStatement;
import build.codemodel.objectoriented.descriptor.ConstructorDescriptor;
import build.codemodel.objectoriented.descriptor.MethodDescriptor;
import build.base.compile.testing.JavaFileObjects;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -86,7 +89,41 @@ public String bar(String input) {
final var identifier = (Identifier) ret.expression().orElseThrow();
final var symbol = identifier.getTrait(Symbol.class).orElseThrow();
assertThat(symbol).isInstanceOf(Symbol.Parameter.class);
assertThat(((Symbol.Parameter) symbol).declaredType().toString()).contains("String");
final var parameter = (Symbol.Parameter) symbol;
assertThat(parameter.declaredType().toString()).contains("String");
assertThat(parameter.descriptor().name().orElseThrow().toString()).isEqualTo("input");
}

@Test
void shouldResolveConstructorParameter() {
final var source = JavaFileObjects.forSourceString("com.example.Foo", """
package com.example;
public class Foo {
private String value;
public Foo(String value) {
this.value = value;
}
}
""");
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 descriptor = codeModel.getTypeDescriptor(typeName).orElseThrow();
final var constructor = descriptor.traits(ConstructorDescriptor.class).findFirst().orElseThrow();
final var body = constructor.getTrait(MethodBodyDescriptor.class).orElseThrow().body();

final var assignment = body.statements()
.filter(s -> s instanceof ExpressionStatement)
.map(s -> (CompoundAssignment) ((ExpressionStatement) s).expression())
.findFirst().orElseThrow();

final var identifier = (Identifier) assignment.value();
final var symbol = identifier.getTrait(Symbol.class).orElseThrow();
assertThat(symbol).isInstanceOf(Symbol.Parameter.class);
final var parameter = (Symbol.Parameter) symbol;
assertThat(parameter.declaredType().toString()).contains("String");
assertThat(parameter.descriptor().name().orElseThrow().toString()).isEqualTo("value");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import build.codemodel.expression.BooleanLiteral;
import build.codemodel.expression.Expression;
import build.codemodel.expression.NumericLiteral;
import build.codemodel.foundation.descriptor.FormalParameterDescriptor;
import build.codemodel.foundation.naming.IrreducibleName;
import build.codemodel.foundation.naming.NonCachingNameProvider;
import build.codemodel.foundation.usage.TypeUsage;
Expand Down Expand Up @@ -285,7 +286,8 @@ void symbolLocalVariable_partsContainsDeclaredType() {
@Test
void symbolParameter_partsContainsDeclaredType() {
final var type = stringType();
assertThat(new Symbol.Parameter(type).parts().toList()).containsExactly(type);
final var descriptor = FormalParameterDescriptor.of(codeModel, Optional.of(IrreducibleName.of("value")), type);
assertThat(new Symbol.Parameter(descriptor).parts().toList()).containsExactly(type);
}

@Test
Expand Down