Skip to content
Draft
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 @@ -8,14 +8,20 @@
package spoon.reflect.visitor.filter;

import spoon.reflect.code.CaseKind;
import spoon.reflect.code.CtBinaryOperator;
import spoon.reflect.code.CtBodyHolder;
import spoon.reflect.code.CtCase;
import spoon.reflect.code.CtCatch;
import spoon.reflect.code.CtCatchVariable;
import spoon.reflect.code.CtFor;
import spoon.reflect.code.CtIf;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.code.CtSwitch;
import spoon.reflect.code.CtTypePattern;
import spoon.reflect.code.CtWhile;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtField;
Expand Down Expand Up @@ -137,7 +143,16 @@ public void apply(CtElement input, CtConsumer<Object> outputConsumer) {
}
}
}
} else if (parent instanceof CtBodyHolder || parent instanceof CtStatementList) {
} else if (parent instanceof CtIf ifElement) {
var cond = ifElement.getCondition();
searchTypePattern(outputConsumer, cond);
} else if (parent instanceof CtWhile whileElement) {
var expr = whileElement.getLoopingExpression();
searchTypePattern(outputConsumer, expr);
} else if (parent instanceof CtFor forElement) {
var expr = forElement.getExpression();
searchTypePattern(outputConsumer, expr);
} else if (parent instanceof CtBodyHolder || parent instanceof CtStatementList || parent instanceof CtExpression<?>) {
//visit all previous CtVariable siblings of scopeElement element in parent BodyHolder or Statement list
siblingsQuery.setInput(scopeElement).forEach(outputConsumer);
if (query.isTerminated()) {
Expand All @@ -156,6 +171,13 @@ public void apply(CtElement input, CtConsumer<Object> outputConsumer) {
return;
}
}
} else if (parent instanceof CtBinaryOperator<?> op) {
//search for type pattern in binary operator
var left = op.getLeftHandOperand();
searchTypePattern(outputConsumer, left);
if (query.isTerminated()) {
return;
}
}
}
if (parent instanceof CtModifiable) {
Expand All @@ -165,6 +187,16 @@ public void apply(CtElement input, CtConsumer<Object> outputConsumer) {
}
}

private void searchTypePattern(CtConsumer<Object> outputConsumer, CtExpression<?> cond) {
cond.filterChildren(new TypeFilter<>(CtTypePattern.class))
.forEach(typePattern -> {
var var = ((CtTypePattern) typePattern).getVariable();
if (var != null && (variableName == null || variableName.equals(var.getSimpleName()))) {
outputConsumer.accept(var);
}
});
}

/**
* @param var
* @param output
Expand Down
208 changes: 208 additions & 0 deletions src/test/java/spoon/test/reference/InstanceOfReferenceTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package spoon.test.reference;

import org.junit.jupiter.api.Test;
import spoon.reflect.CtModel;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtTypePattern;
import spoon.reflect.reference.CtLocalVariableReference;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.testing.utils.GitHubIssue;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static spoon.test.SpoonTestHelpers.createModelFromString;

/**
* Tests that references to pattern variables declared using the <code>instanceof</code> operator can be resolved.
* Pattern matching for instanceof was introduced in Java 16, cf. <a href=https://openjdk.java.net/jeps/394>JEP 394</a>.
* Variables declared in pattern matches have <a href="https://openjdk.org/projects/amber/design-notes/patterns/pattern-match-semantics">flow scope semantics</a>.
*/
public class InstanceOfReferenceTest {
@Test
public void testVariableDeclaredInIf() {
String code = """
class X {
String typePattern(Object obj) {
boolean someCondition = true;
if (someCondition && obj instanceof String s) {
return s;
}
return "";
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(1);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testVariableDeclaredInWhileLoop() {
String code = """
class X {
public void processShapes(List<Object> shapes) {
var iter = 0;
while (iter < shapes.size() && shapes.get(iter) instanceof String shape) {
iter++;
System.out.println(shape);
}
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(3);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testVariableDeclaredInForLoop() {
String code = """
class X {
public void processShapes(List<Object> shapes) {
for (var iter = 0; iter < shapes.size() && shapes.get(iter) instanceof String shape; iter++) {
System.out.println(shape);
}
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(3);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testDeclaredVariableUsedInSameCondition() {
String code = """
class X {
public void processShapes(Object obj) {
if (obj instanceof String s && s.length() > 5) {
// NOP
}
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(0);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testDeclaredVariableUsedInSameCondition2() {
String code = """
class X {
public void hasRightSize(Shape s) throws MyException {
return s instanceof Circle c && c.getRadius() > 10;
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(0);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testFlowScope() {
String code = """
class X {
public void onlyForStrings(Object o) throws MyException {
if (!(o instanceof String s))
throw new MyException();
// s is in scope
System.out.println(s);
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(0);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testFlowScope2() {
String code = """
class X {
String s = "abc";

public void method2(Object o) {
if (!(o instanceof String s)) {
System.out.println("not a string");
} else {
System.out.println(s); // The local variable is in scope here!
}
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(0);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testFlowScope3() {
String code = """
class X {
String typePattern(Object obj) {
if (obj instanceof String s) {
System.out.println("It's a string");
} else {
throw new RuntimeException("It's not a string");
}
return s; // We can still access s here!
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(0);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}

@Test
public void testCorrectScoping() {
String code = """
class Example2 {
Point p;

void test2(Object o) {
if (o instanceof Point p) {
// p refers to the pattern variable
System.out.println(p);
} else {
// p refers to the field
System.out.println(p);
}
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
var refs = model.getElements(new TypeFilter<>(CtLocalVariableReference.class));
assertEquals(1, refs.size());
var decl = refs.get(0).getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package spoon.test.reference;

import org.junit.jupiter.api.Test;
import spoon.reflect.CtModel;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtTypePattern;
import spoon.reflect.reference.CtLocalVariableReference;
import spoon.reflect.visitor.filter.TypeFilter;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static spoon.test.SpoonTestHelpers.createModelFromString;

public class PatternMatchingReferenceTest {
@Test
public void testCasePatternReference() {
String code = """
class X {
public void processShape(Shape shape) {
switch (shape) {
case Circle c -> {
System.out.println("This is a circle with radius: " + c.getRadius());
}
}
}
}
""";
CtModel model = createModelFromString(code, 21);
CtLocalVariable<?> variable = model.getElements(new TypeFilter<>(CtTypePattern.class)).get(0).getVariable();
CtLocalVariableReference<?> ref = model.getElements(new TypeFilter<>(CtLocalVariableReference.class)).get(0);
var decl = ref.getDeclaration();
assertNotNull(decl);
assertEquals(variable, decl);
}
}
Loading