Skip to content

Commit

Permalink
fix: expression type cast source positions (#2113)
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky authored and monperrus committed Jun 28, 2018
1 parent 661b01a commit 45c7017
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 7 deletions.
11 changes: 9 additions & 2 deletions src/main/java/spoon/support/compiler/jdt/ContextBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,12 @@ public class ContextBuilder {

Deque<String> annotationValueName = new ArrayDeque<>();

List<CtTypeReference<?>> casts = new ArrayList<>(CASTS_CONTAINER_DEFAULT_CAPACITY);
public static class CastInfo {
int nrOfBrackets;
CtTypeReference<?> typeRef;
}

List<CastInfo> casts = new ArrayList<>(CASTS_CONTAINER_DEFAULT_CAPACITY);

CompilationUnitDeclaration compilationunitdeclaration;

Expand All @@ -72,6 +77,8 @@ public class ContextBuilder {

boolean isBuildLambda = false;

boolean isBuildTypeCast = false;

boolean ignoreComputeImports = false;

/**
Expand Down Expand Up @@ -99,7 +106,7 @@ void enter(CtElement e, ASTNode node) {

if (current instanceof CtExpression) {
while (!casts.isEmpty()) {
((CtExpression<?>) current).addTypeCast(casts.remove(0));
((CtExpression<?>) current).addTypeCast(casts.remove(0).typeRef);
}
}
if (current instanceof CtStatement && !this.label.isEmpty()) {
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/spoon/support/compiler/jdt/JDTTreeBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
import spoon.reflect.reference.CtReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.reference.CtUnboundVariableReference;
import spoon.support.compiler.jdt.ContextBuilder.CastInfo;
import spoon.support.reflect.CtExtendedModifier;

import java.util.HashSet;
Expand Down Expand Up @@ -935,7 +936,11 @@ public boolean visit(BreakStatement breakStatement, BlockScope scope) {

@Override
public boolean visit(CastExpression castExpression, BlockScope scope) {
context.casts.add(this.references.buildTypeReference(castExpression.type, scope));
CastInfo ci = new CastInfo();
//the 8 bits from 21 to 28 represents number of enclosing brackets
ci.nrOfBrackets = ((castExpression.bits >>> 21) & 0xF);
ci.typeRef = this.references.buildTypeReference(castExpression.type, scope, true);
context.casts.add(ci);
castExpression.expression.traverse(this, scope);
return false;
}
Expand Down
65 changes: 64 additions & 1 deletion src/main/java/spoon/support/compiler/jdt/PositionBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import spoon.SpoonException;
import spoon.reflect.code.CtCatchVariable;
import spoon.reflect.code.CtExpression;
import spoon.reflect.code.CtStatementList;
import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.cu.SourcePosition;
Expand All @@ -42,9 +43,12 @@
import spoon.reflect.declaration.CtModifiable;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.factory.CoreFactory;
import spoon.reflect.reference.CtTypeReference;
import spoon.support.compiler.jdt.ContextBuilder.CastInfo;
import spoon.support.reflect.CtExtendedModifier;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import static spoon.support.compiler.jdt.JDTTreeBuilderQuery.getModifiers;
Expand Down Expand Up @@ -73,7 +77,6 @@ SourcePosition buildPositionCtElement(CtElement e, ASTNode node) {
int[] lineSeparatorPositions = cr.lineSeparatorPositions;
char[] contents = cr.compilationUnit.getContents();


int sourceStart = node.sourceStart;
int sourceEnd = node.sourceEnd;
if ((node instanceof Annotation)) {
Expand All @@ -90,6 +93,55 @@ SourcePosition buildPositionCtElement(CtElement e, ASTNode node) {
if (statementEnd > 0) {
sourceEnd = statementEnd;
}

if (this.jdtTreeBuilder.getContextBuilder().isBuildTypeCast && e instanceof CtTypeReference) {
//the type cast reference must be enclosed with brackets
int declarationSourceStart = sourceStart;
int declarationSourceEnd = sourceEnd;
declarationSourceStart = findPrevNonWhitespace(contents, 0, declarationSourceStart - 1);
if (contents[declarationSourceStart] != '(') {
throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at start of cast expression on offset: " + declarationSourceStart);
}
declarationSourceEnd = findNextNonWhitespace(contents, contents.length, declarationSourceEnd + 1);
if (contents[declarationSourceEnd] != ')') {
throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at end of cast expression on offset: " + declarationSourceEnd);
}
return cf.createCompoundSourcePosition(cu,
sourceStart, sourceEnd,
declarationSourceStart, declarationSourceEnd,
lineSeparatorPositions);
}

List<CastInfo> casts = this.jdtTreeBuilder.getContextBuilder().casts;

if (casts.size() > 0 && e instanceof CtExpression) {
int declarationSourceStart = sourceStart;
int declarationSourceEnd = sourceEnd;
SourcePosition pos = casts.get(0).typeRef.getPosition();
if (pos.isValidPosition()) {
declarationSourceStart = pos.getSourceStart();
int nrOfBrackets = getNrOfFirstCastExpressionBrackets();
while (nrOfBrackets > 0) {
declarationSourceStart = findPrevNonWhitespace(contents, 0, declarationSourceStart - 1);
if (contents[declarationSourceStart] != '(') {
throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at start of expression on offset: " + declarationSourceStart);
}
nrOfBrackets--;
}
nrOfBrackets = getNrOfCastExpressionBrackets();
while (nrOfBrackets > 0) {
declarationSourceEnd = findNextNonWhitespace(contents, contents.length, declarationSourceEnd + 1);
if (contents[declarationSourceEnd] != ')') {
throw new SpoonException("Unexpected character \'" + contents[declarationSourceStart] + "\' at end of expression on offset: " + declarationSourceEnd);
}
nrOfBrackets--;
}
}
return cf.createCompoundSourcePosition(cu,
sourceStart, sourceEnd,
declarationSourceStart, declarationSourceEnd,
lineSeparatorPositions);
}
}

if (node instanceof TypeParameter) {
Expand Down Expand Up @@ -285,6 +337,17 @@ SourcePosition buildPositionCtElement(CtElement e, ASTNode node) {
return cf.createSourcePosition(cu, sourceStart, sourceEnd, lineSeparatorPositions);
}

private int getNrOfFirstCastExpressionBrackets() {
return this.jdtTreeBuilder.getContextBuilder().casts.get(0).nrOfBrackets;
}

private int getNrOfCastExpressionBrackets() {
int nr = 0;
for (CastInfo castInfo : this.jdtTreeBuilder.getContextBuilder().casts) {
nr += castInfo.nrOfBrackets;
}
return nr;
}

private void setModifiersPosition(CtModifiable e, int start, int end) {
CoreFactory cf = this.jdtTreeBuilder.getFactory().Core();
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/spoon/support/compiler/jdt/ReferenceBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,14 @@ private CtTypeReference<?> getBoundedTypeReference(TypeBinding binding) {
* @return a type reference.
*/
<T> CtTypeReference<T> buildTypeReference(TypeReference type, Scope scope) {
return buildTypeReference(type, scope, false);
}
<T> CtTypeReference<T> buildTypeReference(TypeReference type, Scope scope, boolean isTypeCast) {
if (type == null) {
return null;
}
CtTypeReference<T> typeReference = this.<T>getTypeReference(type.resolvedType, type);
return buildTypeReferenceInternal(typeReference, type, scope);
return buildTypeReferenceInternal(typeReference, type, scope, isTypeCast);
}

/**
Expand Down Expand Up @@ -173,11 +176,11 @@ private CtTypeParameterReference buildTypeParameterReference(TypeReference type,
if (type == null) {
return null;
}
return (CtTypeParameterReference) this.buildTypeReferenceInternal(this.getTypeParameterReference(type.resolvedType, type), type, scope);
return (CtTypeParameterReference) this.buildTypeReferenceInternal(this.getTypeParameterReference(type.resolvedType, type), type, scope, false);
}


private <T> CtTypeReference<T> buildTypeReferenceInternal(CtTypeReference<T> typeReference, TypeReference type, Scope scope) {
private <T> CtTypeReference<T> buildTypeReferenceInternal(CtTypeReference<T> typeReference, TypeReference type, Scope scope, boolean isTypeCast) {
if (type == null) {
return null;
}
Expand All @@ -187,7 +190,9 @@ private <T> CtTypeReference<T> buildTypeReferenceInternal(CtTypeReference<T> typ
if (currentReference == null) {
break;
}
this.jdtTreeBuilder.getContextBuilder().isBuildTypeCast = isTypeCast;
this.jdtTreeBuilder.getContextBuilder().enter(currentReference, type);
this.jdtTreeBuilder.getContextBuilder().isBuildTypeCast = false;
if (type.annotations != null && type.annotations.length - 1 <= position && type.annotations[position] != null && type.annotations[position].length > 0) {
for (Annotation annotation : type.annotations[position]) {
if (scope instanceof ClassScope) {
Expand Down
48 changes: 48 additions & 0 deletions src/test/java/spoon/test/position/PositionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import spoon.reflect.code.*;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.cu.position.BodyHolderSourcePosition;
import spoon.reflect.cu.position.CompoundSourcePosition;
import spoon.reflect.cu.position.DeclarationSourcePosition;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtConstructor;
Expand Down Expand Up @@ -768,4 +769,51 @@ public void testArrayArgParameter() throws Exception {
assertEquals("String arg[]", contentAtPosition(classContent, param.getType().getPosition()));
}
}

@Test
public void testExpressions() throws Exception {
//contract: the expression including type casts has correct position which includes all brackets too
final CtType<?> foo = ModelUtils.buildClass(Expressions.class);
String classContent = getClassContent(foo);
List<CtInvocation<?>> statements = (List) foo.getMethodsByName("method").get(0).getBody().getStatements();

int idx = 0;
assertEquals("\"x\"", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));
assertEquals("(\"x\")", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));
assertEquals("(String)null", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));
assertEquals("( String) ( (Serializable)(( (null ))))", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));
assertEquals("(((String) null))", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));
assertEquals("( /*c2*/\n" +
" (\n" +
" /*c3*/ String\n" +
" /*c4*/) //c5\n" +
" null /*c6*/\n" +
" //c7\n" +
" )", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));
assertEquals("(List<?>) null", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));
assertEquals("(List<List<Map<String,Integer>>>) null", contentAtPosition(classContent, statements.get(idx++).getArguments().get(0).getPosition()));

//contract: check the position of expression without type casts
{
CtExpression<?> expr = statements.get(1).getArguments().get(0);
assertEquals("(\"x\")", contentAtPosition(classContent, expr.getPosition()));
//if there is no expression, then it uses primitive SourcePosition
assertFalse(expr.getPosition() instanceof CompoundSourcePosition);
}

//contract: check the position of children of the most complex expression
{
CtExpression<?> expr = statements.get(3).getArguments().get(0);
assertEquals("( String) ( (Serializable)(( (null ))))", contentAtPosition(classContent, expr.getPosition()));
//if there is type cast in expression, then it uses CompoundSourcePosition
assertTrue(expr.getPosition() instanceof CompoundSourcePosition);

//contract: check the position of type casts
assertEquals("( String)", contentAtPosition(classContent, expr.getTypeCasts().get(0).getPosition()));
assertEquals("(Serializable)", contentAtPosition(classContent, expr.getTypeCasts().get(1).getPosition()));
//contract: check the position of expression "name"
CompoundSourcePosition compoundSourcePosition = (CompoundSourcePosition) expr.getPosition();
assertEquals("(( (null )))", contentAtPosition(classContent, compoundSourcePosition.getNameStart(), compoundSourcePosition.getNameEnd()));
}
}
}
24 changes: 24 additions & 0 deletions src/test/java/spoon/test/position/testclasses/Expressions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package spoon.test.position.testclasses;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

public class Expressions {
void method() {
System.out.print("x");
System.out.print(("x"));
System.out.print((String)null);
System.out.print(( String) ( (Serializable)(( (null )))));
System.out.print((((String) null)));
System.out.print(/*c1*/ ( /*c2*/
(
/*c3*/ String
/*c4*/) //c5
null /*c6*/
//c7
) /*c8*/ );
System.out.print((List<?>) null);
System.out.print((List<List<Map<String,Integer>>>) null);
};
}

0 comments on commit 45c7017

Please sign in to comment.