in errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/ShadowUsageCheck.java [140:387]
private void matchedShadowOf(MethodInvocationTree shadowOfCall, VisitorState state) {
ExpressionTree shadowOfArg = shadowOfCall.getArguments().get(0);
Type shadowOfArgType = getExpressionType(shadowOfArg);
Tree parent = state.getPath().getParentPath().getLeaf();
CompilationUnitTree compilationUnit = state.getPath().getCompilationUnit();
// pointless (ShadowX) shadowOf(x)? drop it.
if (parent.getKind() == Kind.TYPE_CAST) {
parent = removeCastIfUnnecessary((JCTypeCast) parent, state);
}
switch (parent.getKind()) {
case VARIABLE: // ShadowType shadowType = shadowOf(type);
{
// shadow is being assigned to a variable; don't do that!
JCVariableDecl variableDecl = (JCVariableDecl) parent;
String oldVarName = variableDecl.getName().toString();
// since it's being declared here, no danger of a collision on this var name...
knownLocalVars.remove(oldVarName);
String newVarName = pickNewName(shadowOfArg, oldVarName, this);
varRemapping.put(getSymbol(variableDecl), newVarName);
// ... but be careful not to collide with it later.
knownLocalVars.add(newVarName);
// replace shadow variable declaration with shadowed type and name
if (!newVarName.equals(shadowOfArg.toString())) {
Type shadowedType = getUpperBound(shadowOfArgType, state);
String shadowedTypeName =
SuggestedFixes.prettyType(state, possibleFixes.fixBuilder, shadowedType);
String newAssignment =
shadowedTypeName + " " + newVarName + " = " + shadowOfArg + ";";
// avoid overlapping replacements:
if (shadowOfArg instanceof JCMethodInvocation) {
JCExpression jcExpression = ((JCMethodInvocation) shadowOfArg).meth;
if (jcExpression instanceof JCFieldAccess) {
possibleFixes.removeFixFor(((JCFieldAccess) jcExpression).selected);
}
}
possibleFixes.fixByReplacing(parent, newAssignment);
} else {
possibleFixes.fixByDeleting(parent);
}
// replace shadow variable reference with `nonShadowInstance` or
// `shadowOf(nonShadowInstance)` as appropriate.
new TreePathScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree identifierTreeX, Void aVoid) {
JCIdent identifierTree = (JCIdent) identifierTreeX;
Symbol symbol = getSymbol(identifierTree);
if (variableDecl.sym.equals(symbol) && !isLeftSideOfAssignment(identifierTree)) {
TreePath idPath = TreePath.getPath(compilationUnit, identifierTree);
Tree parent = idPath.getParentPath().getLeaf();
boolean callDirectlyOnFramework = shouldCallDirectlyOnFramework(idPath);
JCTree replaceNode;
if (parent instanceof JCFieldAccess && !callDirectlyOnFramework) {
JCFieldAccess fieldAccess = (JCFieldAccess) parent;
JCMethodInvocation newShadowOfCall =
createSyntheticShadowAccess(shadowOfCall, newVarName, symbol, state);
replaceFieldSelected(fieldAccess, newShadowOfCall, state);
replaceNode = newShadowOfCall;
} else {
identifierTree.name = state.getName(newVarName);
identifierTree.sym.name = state.getName(newVarName);
replaceNode = identifierTree;
}
String replaceWith =
callDirectlyOnFramework
? newVarName
: shadowOfCall.getMethodSelect() + "(" + newVarName + ")";
possibleFixes.put(
replaceNode, possibleFixes.new ReplacementFix(identifierTree, replaceWith));
}
return super.visitIdentifier(identifierTree, aVoid);
}
private boolean isLeftSideOfAssignment(IdentifierTree identifierTree) {
Tree parent = getCurrentPath().getParentPath().getLeaf();
if (parent instanceof AssignmentTree) {
return identifierTree.equals(((AssignmentTree) parent).getVariable());
}
return false;
}
}.scan(compilationUnit, null);
}
break;
case ASSIGNMENT: // this.shadowType = shadowOf(type);
{
// shadow is being assigned to a field or variable; don't do that!
JCAssign assignment = (JCAssign) parent;
Symbol fieldSymbol = getSymbol(assignment.lhs);
String oldFieldName = assignment.lhs.toString();
String remappedName = varRemapping.get(fieldSymbol);
// since it's being declared here, no danger of a collision on this var name...
knownFields.remove(oldFieldName);
String newFieldName =
remappedName == null ? pickNewName(shadowOfArg, oldFieldName, this) : remappedName;
varRemapping.put(fieldSymbol, newFieldName);
// ... but be careful not to collide with it later.
knownLocalVars.add(newFieldName);
// local variable declaration should have been handled above in the VARIABLE case;
// just strip shadowOf() and assign it to the de-shadowed variable.
if (fieldSymbol.getKind() == ElementKind.LOCAL_VARIABLE) {
if (newFieldName.equals(shadowOfArg.toString())) {
// assigning to self, don't bother
TreePath assignmentPath = TreePath.getPath(compilationUnit, assignment);
Tree assignmentParent = assignmentPath.getParentPath().getLeaf();
if (assignmentParent instanceof ExpressionStatementTree) {
possibleFixes.fixByDeleting(assignmentParent);
}
} else {
possibleFixes.fixByReplacing(
assignment, newFieldName + " = " + shadowOfArg.toString());
}
break;
}
Symbol shadowOfArgSym = getSymbol(shadowOfArg);
ElementKind shadowOfArgDomicile =
shadowOfArgSym == null
? ElementKind.OTHER // it's probably an expression, not a var...
: shadowOfArgSym.getKind();
boolean namesAreSame = newFieldName.equals(shadowOfArg.toString());
boolean useExistingField =
shadowOfArgDomicile == ElementKind.FIELD
&& namesAreSame
&& !isMethodParam(ASTHelpers.getSymbol(shadowOfArg), state.getPath());
if (useExistingField) {
fixVar(fieldSymbol, state, possibleFixes).delete();
ExpressionStatementTree enclosingNode =
ASTHelpers.findEnclosingNode(
TreePath.getPath(compilationUnit, assignment), ExpressionStatementTree.class);
if (enclosingNode != null) {
possibleFixes.fixByDeleting(enclosingNode);
}
} else {
Type shadowedType = getUpperBound(shadowOfArgType, state);
String shadowedTypeName =
SuggestedFixes.prettyType(state, possibleFixes.fixBuilder, shadowedType);
fixVar(fieldSymbol, state, possibleFixes)
.setName(newFieldName)
.setTypeName(shadowedTypeName)
.setRenameUses(false)
.modify();
String thisStr = "";
if (assignment.lhs.toString().startsWith("this.")
|| (shadowOfArgDomicile == ElementKind.LOCAL_VARIABLE && namesAreSame)) {
thisStr = "this.";
}
possibleFixes.fixByReplacing(
assignment, thisStr + newFieldName + " = " + shadowOfArg);
}
TreePath containingBlock = findParentOfKind(state, Kind.BLOCK);
if (containingBlock != null) {
// replace shadow field reference with `nonShadowInstance` or
// `shadowOf(nonShadowInstance)` as appropriate.
new TreePathScanner<Void, Void>() {
@Override
public Void visitMemberSelect(MemberSelectTree memberSelectTree, Void aVoid) {
maybeReplaceFieldRef(memberSelectTree.getExpression());
return super.visitMemberSelect(memberSelectTree, aVoid);
}
@Override
public Void visitIdentifier(IdentifierTree identifierTree, Void aVoid) {
maybeReplaceFieldRef(identifierTree);
return super.visitIdentifier(identifierTree, aVoid);
}
private void maybeReplaceFieldRef(ExpressionTree subject) {
Symbol symbol = getSymbol(subject);
if (symbol != null && symbol.getKind() == ElementKind.FIELD) {
TreePath subjectPath = TreePath.getPath(compilationUnit, subject);
if (symbol.equals(fieldSymbol) && isPartOfMethodInvocation(subjectPath)) {
String fieldRef =
subject.toString().startsWith("this.")
? "this." + newFieldName
: newFieldName;
JCTree replaceNode = (JCTree) subject;
Tree container = subjectPath.getParentPath().getLeaf();
if (container instanceof JCFieldAccess) {
JCFieldAccess fieldAccess = (JCFieldAccess) container;
JCMethodInvocation newShadowOfCall =
createSyntheticShadowAccess(shadowOfCall, newFieldName, symbol, state);
replaceFieldSelected(fieldAccess, newShadowOfCall, state);
replaceNode = newShadowOfCall;
}
String replaceWith =
shouldCallDirectlyOnFramework(subjectPath)
? fieldRef
: shadowOfCall.getMethodSelect() + "(" + fieldRef + ")";
possibleFixes.put(
replaceNode, possibleFixes.new ReplacementFix(subject, replaceWith));
}
}
}
}.scan(compilationUnit, null);
}
}
break;
case MEMBER_SELECT: // shadowOf(type).method();
{
if (shouldCallDirectlyOnFramework(state.getPath())) {
if (!isInSyntheticShadowAccess(state)) {
possibleFixes.fixByReplacing(shadowOfCall, shadowOfArg.toString());
}
}
}
break;
case TYPE_CAST:
System.out.println("WARN: not sure what to do with " + parent.getKind() + ": " + parent);
break;
default:
throw new RuntimeException(
"not sure what to do with " + parent.getKind() + ": " + parent);
}
}