Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review2: fix: expression type cast source positions #2113

Merged
merged 4 commits into from
Jun 28, 2018
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
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my information, because I'm curious ;) It's something you obtained by reverse engineering or there's a documentation somewhere with that info?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a documentation of these bits in source code of compiler ... and I needed some luck to found it ;-)

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);
};
}