blob: 6aeda7a672d952a713121e575fe098646194f1ee [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2021 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.codeassist.complete;
import java.util.Stack;
import org.eclipse.jdt.internal.compiler.*;
import org.eclipse.jdt.internal.compiler.ast.*;
import org.eclipse.jdt.internal.compiler.lookup.*;
/**
* Detect the presence of a node in expression
*/
public class CompletionNodeDetector extends ASTVisitor {
static class FindAny extends GenericAstVisitor {
private ASTNode searchFor;
boolean found;
public FindAny(ASTNode searchFor) {
this.searchFor = searchFor;
}
@Override
protected boolean visitNode(ASTNode node) {
if (node == this.searchFor) {
this.found = true;
}
return !this.found;
}
}
public static boolean findAny(CompilationUnitDeclaration unit, ASTNode searchFor) {
FindAny visitor = new FindAny(searchFor);
unit.traverse(visitor, (CompilationUnitScope)null, false);
return visitor.found;
}
@SuppressWarnings("serial")
static class StopTraversal extends RuntimeException { /* no details */}
private ASTNode searchedNode;
private ASTNode parent;
private Expression outerExpression;
private Stack<ASTNode> interestingEnclosings = new Stack<>();
private ASTNode enclosingNode;
private boolean result;
private ASTNode blockedNode;
private boolean containsPotentialPolyExpression = false;
public CompletionNodeDetector(ASTNode searchedNode, ASTNode visitedAst){
this.searchedNode = searchedNode;
this.result = false;
if(searchedNode != null && visitedAst != null) {
try {
if (visitedAst instanceof AbstractMethodDeclaration) {
((AbstractMethodDeclaration) visitedAst).traverse(this, (ClassScope) null);
} else if (visitedAst instanceof CompilationUnitDeclaration) {
((CompilationUnitDeclaration) visitedAst).traverse(this, (CompilationUnitScope) null);
} else {
visitedAst.traverse(this, null);
}
} catch (StopTraversal st) {
// nothing
}
}
}
public boolean containsCompletionNode() {
return this.result;
}
public ASTNode getCompletionNodeParent() {
return this.parent;
}
public Expression getCompletionNodeOuterExpression() {
if (this.outerExpression != null)
return this.outerExpression;
else if (this.parent instanceof Expression)
return (Expression) this.parent;
return null;
}
public ASTNode getCompletionEnclosingNode() {
return this.enclosingNode;
}
@Override
public void endVisit(AllocationExpression allocationExpression, BlockScope scope) {
endVisit(allocationExpression);
}
@Override
public void endVisit(AND_AND_Expression and_and_Expression, BlockScope scope) {
this.interestingEnclosings.pop();
endVisit(and_and_Expression);
}
@Override
public void endVisit(ArrayAllocationExpression arrayAllocationExpression, BlockScope scope) {
endVisit(arrayAllocationExpression);
}
@Override
public void endVisit(ArrayInitializer arrayInitializer, BlockScope scope) {
endVisit(arrayInitializer);
}
@Override
public void endVisit(ArrayQualifiedTypeReference arrayQualifiedTypeReference, BlockScope scope) {
endVisit(arrayQualifiedTypeReference);
}
@Override
public void endVisit(ArrayQualifiedTypeReference arrayQualifiedTypeReference, ClassScope scope) {
endVisit(arrayQualifiedTypeReference);
}
@Override
public void endVisit(ArrayReference arrayReference, BlockScope scope) {
endVisit(arrayReference);
}
@Override
public void endVisit(ArrayTypeReference arrayTypeReference, BlockScope scope) {
endVisit(arrayTypeReference);
}
@Override
public void endVisit(ArrayTypeReference arrayTypeReference, ClassScope scope) {
endVisit(arrayTypeReference);
}
@Override
public void endVisit(Assignment assignment, BlockScope scope) {
endVisit(assignment);
}
@Override
public void endVisit(BinaryExpression binaryExpression, BlockScope scope) {
endVisit(binaryExpression);
}
@Override
public void endVisit(CastExpression castExpression, BlockScope scope) {
endVisit(castExpression);
}
@Override
public void endVisit(CompoundAssignment compoundAssignment, BlockScope scope) {
endVisit(compoundAssignment);
}
@Override
public void endVisit(ConditionalExpression conditionalExpression, BlockScope scope) {
endVisit(conditionalExpression);
}
@Override
public void endVisit(EqualExpression equalExpression, BlockScope scope) {
endVisit(equalExpression);
}
@Override
public void endVisit(ExplicitConstructorCall explicitConstructor, BlockScope scope) {
endVisit(explicitConstructor);
}
@Override
public void endVisit(FieldReference fieldReference, BlockScope scope) {
endVisit(fieldReference);
}
@Override
public void endVisit(IfStatement ifStatement, BlockScope scope) {
this.interestingEnclosings.pop();
}
@Override
public void endVisit(InstanceOfExpression instanceOfExpression, BlockScope scope) {
endVisit(instanceOfExpression);
}
@Override
public void endVisit(LocalDeclaration localDeclaration, BlockScope scope) {
endVisit(localDeclaration);
}
@Override
public void endVisit(MessageSend messageSend, BlockScope scope) {
endVisit(messageSend);
}
@Override
public void endVisit(OR_OR_Expression or_or_Expression, BlockScope scope) {
endVisit(or_or_Expression);
}
@Override
public void endVisit(ParameterizedQualifiedTypeReference parameterizedQualifiedTypeReference, BlockScope scope) {
endVisit(parameterizedQualifiedTypeReference);
}
@Override
public void endVisit(ParameterizedQualifiedTypeReference parameterizedQualifiedTypeReference, ClassScope scope) {
endVisit(parameterizedQualifiedTypeReference);
}
@Override
public void endVisit(ParameterizedSingleTypeReference parameterizedSingleTypeReference, BlockScope scope) {
endVisit(parameterizedSingleTypeReference);
}
@Override
public void endVisit(ParameterizedSingleTypeReference parameterizedSingleTypeReference, ClassScope scope) {
endVisit(parameterizedSingleTypeReference);
}
@Override
public void endVisit(PostfixExpression postfixExpression, BlockScope scope) {
endVisit(postfixExpression);
}
@Override
public void endVisit(PrefixExpression prefixExpression, BlockScope scope) {
endVisit(prefixExpression);
}
@Override
public void endVisit(QualifiedAllocationExpression qualifiedAllocationExpression, BlockScope scope) {
endVisit(qualifiedAllocationExpression);
}
@Override
public void endVisit(QualifiedNameReference qualifiedNameReference, BlockScope scope) {
endVisit(qualifiedNameReference);
}
@Override
public void endVisit(QualifiedSuperReference qualifiedSuperReference, BlockScope scope) {
endVisit(qualifiedSuperReference);
}
@Override
public void endVisit(QualifiedThisReference qualifiedThisReference, BlockScope scope) {
endVisit(qualifiedThisReference);
}
@Override
public void endVisit(QualifiedTypeReference qualifiedTypeReference, BlockScope scope) {
endVisit(qualifiedTypeReference);
}
@Override
public void endVisit(QualifiedTypeReference qualifiedTypeReference, ClassScope scope) {
endVisit(qualifiedTypeReference);
}
@Override
public void endVisit(ReferenceExpression referenceExpression, BlockScope blockScope) {
endVisit(referenceExpression);
}
@Override
public void endVisit(ReturnStatement returnStatement, BlockScope scope) {
endVisit(returnStatement);
}
@Override
public void endVisit(SingleNameReference singleNameReference, BlockScope scope) {
endVisit(singleNameReference);
}
@Override
public void endVisit(SingleTypeReference singleTypeReference, BlockScope scope) {
endVisit(singleTypeReference);
}
@Override
public void endVisit(SingleTypeReference singleTypeReference, ClassScope scope) {
endVisit(singleTypeReference);
}
@Override
public void endVisit(SuperReference superReference, BlockScope scope) {
endVisit(superReference);
}
@Override
public void endVisit(SwitchStatement switchStatement, BlockScope scope) {
endVisit(switchStatement);
}
@Override
public void endVisit(ThisReference thisReference, BlockScope scope) {
endVisit(thisReference);
}
@Override
public void endVisit(UnaryExpression unaryExpression, BlockScope scope) {
endVisit(unaryExpression);
}
@Override
public void endVisit(MemberValuePair pair, BlockScope scope) {
endVisit(pair);
}
public void endVisit(MemberValuePair pair, CompilationUnitScope scope) {
endVisit(pair);
}
@Override
public void endVisit(LambdaExpression lambda, BlockScope scope) {
endVisit(lambda);
}
@Override
public void endVisit(MethodDeclaration methodDeclaration, ClassScope scope) {
if (this.result)
throw new StopTraversal(); // don't associate with out-of-scope outer expression
}
@Override
public void endVisit(ConstructorDeclaration constructorDeclaration, ClassScope scope) {
if (this.result)
throw new StopTraversal(); // don't associate with out-of-scope outer expression
}
@Override
public boolean visit(AllocationExpression allocationExpression, BlockScope scope) {
return this.visit(allocationExpression);
}
@Override
public boolean visit(AND_AND_Expression and_and_Expression, BlockScope scope) {
this.interestingEnclosings.add(and_and_Expression);
return this.visit(and_and_Expression);
}
@Override
public boolean visit(ArrayAllocationExpression arrayAllocationExpression, BlockScope scope) {
return this.visit(arrayAllocationExpression);
}
@Override
public boolean visit(ArrayInitializer arrayInitializer, BlockScope scope) {
return this.visit(arrayInitializer);
}
@Override
public boolean visit(ArrayQualifiedTypeReference arrayQualifiedTypeReference, BlockScope scope) {
return this.visit(arrayQualifiedTypeReference);
}
@Override
public boolean visit(ArrayQualifiedTypeReference arrayQualifiedTypeReference, ClassScope scope) {
return this.visit(arrayQualifiedTypeReference);
}
@Override
public boolean visit(ArrayReference arrayReference, BlockScope scope) {
return this.visit(arrayReference);
}
@Override
public boolean visit(ArrayTypeReference arrayTypeReference, BlockScope scope) {
return this.visit(arrayTypeReference);
}
@Override
public boolean visit(ArrayTypeReference arrayTypeReference, ClassScope scope) {
return this.visit(arrayTypeReference);
}
@Override
public boolean visit(Assignment assignment, BlockScope scope) {
return this.visit(assignment);
}
@Override
public boolean visit(BinaryExpression binaryExpression, BlockScope scope) {
return this.visit(binaryExpression);
}
@Override
public boolean visit(CastExpression castExpression, BlockScope scope) {
return this.visit(castExpression);
}
@Override
public boolean visit(CompoundAssignment compoundAssignment, BlockScope scope) {
return this.visit(compoundAssignment);
}
@Override
public boolean visit(ConditionalExpression conditionalExpression, BlockScope scope) {
return this.visit(conditionalExpression);
}
@Override
public boolean visit(EqualExpression equalExpression, BlockScope scope) {
return this.visit(equalExpression);
}
@Override
public boolean visit(ExplicitConstructorCall explicitConstructor, BlockScope scope) {
return this.visit(explicitConstructor);
}
@Override
public boolean visit(FieldReference fieldReference, BlockScope scope) {
return this.visit(fieldReference);
}
@Override
public boolean visit(IfStatement ifStatement, BlockScope scope) {
this.interestingEnclosings.push(ifStatement);
return true;
}
@Override
public boolean visit(InstanceOfExpression instanceOfExpression, BlockScope scope) {
return this.visit(instanceOfExpression);
}
@Override
public boolean visit(LocalDeclaration localDeclaration, BlockScope scope) {
return this.visit(localDeclaration);
}
@Override
public boolean visit(MessageSend messageSend, BlockScope scope) {
return this.visit(messageSend);
}
@Override
public boolean visit(OR_OR_Expression or_or_Expression, BlockScope scope) {
return this.visit(or_or_Expression);
}
@Override
public boolean visit(ParameterizedQualifiedTypeReference parameterizedQualifiedTypeReference, BlockScope scope) {
return this.visit(parameterizedQualifiedTypeReference);
}
@Override
public boolean visit(ParameterizedQualifiedTypeReference parameterizedQualifiedTypeReference, ClassScope scope) {
return this.visit(parameterizedQualifiedTypeReference);
}
@Override
public boolean visit(ParameterizedSingleTypeReference parameterizedSingleTypeReference, BlockScope scope) {
return this.visit(parameterizedSingleTypeReference);
}
@Override
public boolean visit(ParameterizedSingleTypeReference parameterizedSingleTypeReference, ClassScope scope) {
return this.visit(parameterizedSingleTypeReference);
}
@Override
public boolean visit(PostfixExpression postfixExpression, BlockScope scope) {
return this.visit(postfixExpression);
}
@Override
public boolean visit(PrefixExpression prefixExpression, BlockScope scope) {
return this.visit(prefixExpression);
}
@Override
public boolean visit(QualifiedAllocationExpression qualifiedAllocationExpression, BlockScope scope) {
return this.visit(qualifiedAllocationExpression);
}
@Override
public boolean visit(QualifiedNameReference qualifiedNameReference, BlockScope scope) {
return this.visit(qualifiedNameReference);
}
@Override
public boolean visit(QualifiedSuperReference qualifiedSuperReference, BlockScope scope) {
return this.visit(qualifiedSuperReference);
}
@Override
public boolean visit(QualifiedThisReference qualifiedThisReference, BlockScope scope) {
return this.visit(qualifiedThisReference);
}
@Override
public boolean visit(QualifiedTypeReference qualifiedTypeReference, BlockScope scope) {
return this.visit(qualifiedTypeReference);
}
@Override
public boolean visit(QualifiedTypeReference qualifiedTypeReference, ClassScope scope) {
return this.visit(qualifiedTypeReference);
}
@Override
public boolean visit(ReferenceExpression referenceExpression, BlockScope blockScope) {
return this.visit(referenceExpression);
}
@Override
public boolean visit(ReturnStatement returnStatement, BlockScope scope) {
return this.visit(returnStatement);
}
@Override
public boolean visit(SingleNameReference singleNameReference, BlockScope scope) {
return this.visit(singleNameReference);
}
@Override
public boolean visit(SingleTypeReference singleTypeReference, BlockScope scope) {
return this.visit(singleTypeReference);
}
@Override
public boolean visit(SingleTypeReference singleTypeReference, ClassScope scope) {
return this.visit(singleTypeReference);
}
@Override
public boolean visit(StringLiteral stringLiteral, BlockScope scope) {
return this.visit(stringLiteral);
}
@Override
public boolean visit(SuperReference superReference, BlockScope scope) {
return this.visit(superReference);
}
@Override
public boolean visit(SwitchStatement switchStatement, BlockScope blockScope) {
return this.visit(switchStatement);
}
@Override
public boolean visit(ThisReference thisReference, BlockScope scope) {
return this.visit(thisReference);
}
@Override
public boolean visit(UnaryExpression unaryExpression, BlockScope scope) {
return this.visit(unaryExpression);
}
@Override
public boolean visit(MemberValuePair pair, BlockScope scope) {
return this.visit(pair);
}
public boolean visit(MemberValuePair pair, CompilationUnitScope scope) {
return this.visit(pair);
}
private void endVisit(ASTNode astNode) {
if (this.blockedNode == astNode) {
return; // result was found even before this node was entered => sibling of the searchNode => skip
}
if(this.result) {
if ((this.parent == null
|| (this.parent instanceof ArrayInitializer && astNode instanceof Statement)) // heuristically get more context
&& astNode != this.searchedNode) {
if(!(astNode instanceof AllocationExpression && ((AllocationExpression) astNode).type == this.searchedNode)
&& !(astNode instanceof ConditionalExpression && ((ConditionalExpression) astNode).valueIfTrue == this.searchedNode)
&& !(astNode instanceof ConditionalExpression && ((ConditionalExpression) astNode).valueIfFalse == this.searchedNode)) {
this.parent = astNode;
}
}
checkUpdateOuter(astNode);
}
}
protected void checkUpdateOuter(ASTNode astNode) {
if (this.containsPotentialPolyExpression && astNode instanceof Expression) {
// resolving a contained poly expression can only benefit from any additional expression context
this.outerExpression = (Expression) astNode;
} else {
this.containsPotentialPolyExpression |= isPotentiallyPolyExpression(astNode);
// resetting containsPotentialPolyExpression could become necessary when we search the outerExpression within
// a node larger than expressions, but currently CompletionParser.attachOrphanCompletionNode() is the only client
// interested in outerExpression and only passes an expression for visiting.
}
if (!this.interestingEnclosings.isEmpty()) {
// prepare enclosingNode for use in CompletionEngine.findFieldsAndMethodsFromCastedReceiver(..)
ASTNode enclosing = this.interestingEnclosings.peek();
ASTNode rightOfEnclosing = null;
if (enclosing instanceof AND_AND_Expression) {
rightOfEnclosing = ((AND_AND_Expression) enclosing).right;
} else if (enclosing instanceof IfStatement) {
rightOfEnclosing = ((IfStatement) enclosing).thenStatement;
}
if (rightOfEnclosing == astNode || rightOfEnclosing == this.enclosingNode) {
this.enclosingNode = enclosing;
return;
}
}
// the following corresponds to stuff in CompletionParser.buildMoreContext* that would set CompletionParser.enclosingNode:
if (astNode instanceof ReturnStatement || astNode instanceof AbstractVariableDeclaration) {
this.enclosingNode = astNode;
throw new StopTraversal();
}
}
private boolean isPotentiallyPolyExpression(ASTNode node) {
// these expressions may need more enclosing context for resolution:
return node instanceof Invocation || node instanceof FunctionalExpression || node instanceof ConditionalExpression;
}
private boolean visit(ASTNode astNode) {
if (this.result) {
this.blockedNode = astNode;
}
if(astNode == this.searchedNode) {
this.result = true;
}
return !this.result;
}
}