[java] value breaks: parser/PSI (IDEA-196643)

This commit is contained in:
Roman Shevchenko
2018-11-20 18:56:41 +01:00
parent 43c66d1b29
commit ce161e00f3
10 changed files with 159 additions and 109 deletions
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi;
import org.jetbrains.annotations.Nullable;
@@ -22,19 +8,28 @@ import org.jetbrains.annotations.Nullable;
*/
public interface PsiBreakStatement extends PsiStatement {
/**
* Returns the identifier representing the label specified on the statement.
* Returns the label identifier iff it is present and the statement is not inside a switch expression, {@code null} otherwise.
*
* @return the identifier for the label, or null if the statement has no label.
* @see #getExpression()
*/
@Nullable
PsiIdentifier getLabelIdentifier();
@Nullable PsiIdentifier getLabelIdentifier();
/**
* Returns the statement instance ({@link PsiForStatement}, {@link PsiSwitchStatement} etc.) representing
* the statement out of which {@code break} transfers control.
*
* @return the statement instance, or null if the statement is not valid in the context where it is located.
* Returns the label/value expression, or {@code null} if the statement is empty.
*/
@Nullable
PsiStatement findExitedStatement();
}
@Nullable PsiExpression getExpression();
/**
* Returns the statement ({@link PsiLoopStatement} or {@link PsiSwitchStatement}) or {@link PsiSwitchExpression switch expression}
* representing the element out of which {@code break} transfers control.
*/
@Nullable PsiElement findExitedElement();
/** @deprecated doesn't support "switch" expressions; use {@link #findExitedElement()} instead */
@Deprecated
@SuppressWarnings("DeprecatedIsStillUsed")
default PsiStatement findExitedStatement() {
PsiElement enclosingElement = findExitedElement();
return enclosingElement instanceof PsiStatement ? (PsiStatement)enclosingElement : null;
}
}
@@ -461,10 +461,12 @@ public class StatementParser {
}
@NotNull
private static PsiBuilder.Marker parseBreakStatement(PsiBuilder builder) {
private PsiBuilder.Marker parseBreakStatement(PsiBuilder builder) {
PsiBuilder.Marker statement = builder.mark();
builder.advanceLexer();
expect(builder, JavaTokenType.IDENTIFIER);
if (builder.getTokenType() != JavaTokenType.SEMICOLON) {
myParser.getExpressionParser().parse(builder);
}
semicolon(builder);
done(statement, JavaElementType.BREAK_STATEMENT);
return statement;
@@ -38,6 +38,7 @@ import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PairFunction;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -632,6 +633,39 @@ public class PsiImplUtil {
}
}
@Contract("null -> false")
public static boolean isPlainReference(@Nullable PsiExpression expression) {
return expression instanceof PsiReferenceExpression && ((PsiReferenceExpression)expression).getQualifierExpression() == null;
}
@Nullable
public static PsiLoopStatement findEnclosingLoop(@NotNull PsiElement start) {
for (PsiElement e = start; !isCodeBoundary(e); e = e.getParent()) {
if (e instanceof PsiLoopStatement) return (PsiLoopStatement)e;
}
return null;
}
@Nullable
public static PsiElement findEnclosingSwitchOrLoop(@NotNull PsiElement start) {
for (PsiElement e = start; !isCodeBoundary(e); e = e.getParent()) {
if (e instanceof PsiSwitchBlock || e instanceof PsiLoopStatement) return e;
}
return null;
}
@Nullable
public static PsiLabeledStatement findEnclosingLabeledStatement(@NotNull PsiElement start, @NotNull String label) {
for (PsiElement e = start; !isCodeBoundary(e); e = e.getParent()) {
if (e instanceof PsiLabeledStatement && label.equals(((PsiLabeledStatement)e).getName())) return (PsiLabeledStatement)e;
}
return null;
}
private static boolean isCodeBoundary(@Nullable PsiElement e) {
return e == null || e instanceof PsiMethod || e instanceof PsiClassInitializer || e instanceof PsiLambdaExpression;
}
/**
* Returns enclosing label statement for given label expression
*
@@ -4,74 +4,63 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.PsiLabelReference;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
public class PsiBreakStatementImpl extends CompositePsiElement implements PsiBreakStatement, Constants {
public class PsiBreakStatementImpl extends CompositePsiElement implements PsiBreakStatement {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiBreakStatementImpl");
public PsiBreakStatementImpl() {
super(BREAK_STATEMENT);
super(JavaElementType.BREAK_STATEMENT);
}
@Override
public PsiIdentifier getLabelIdentifier() {
return (PsiIdentifier)findChildByRoleAsPsiElement(ChildRole.LABEL);
PsiIdentifier labelId = labelId();
return labelId != null && !(findExitedElement() instanceof PsiSwitchExpression) ? labelId : null;
}
@Override
public PsiStatement findExitedStatement() {
PsiIdentifier label = getLabelIdentifier();
public PsiExpression getExpression() {
return (PsiExpression)findPsiChildByType(ElementType.EXPRESSION_BIT_SET);
}
@Override
public PsiElement findExitedElement() {
PsiIdentifier label = labelId();
if (label == null) {
for (ASTNode parent = getTreeParent(); parent != null; parent = parent.getTreeParent()) {
IElementType i = parent.getElementType();
if (i == FOR_STATEMENT || i == WHILE_STATEMENT || i == DO_WHILE_STATEMENT || i == SWITCH_STATEMENT || i == FOREACH_STATEMENT) {
return (PsiStatement)SourceTreeToPsiMap.treeElementToPsi(parent);
}
else if (i == METHOD || i == CLASS_INITIALIZER) {
return null; // do not pass through anonymous/local class
}
}
return PsiImplUtil.findEnclosingSwitchOrLoop(this);
}
else {
String labelName = label.getText();
for (CompositeElement parent = getTreeParent(); parent != null; parent = parent.getTreeParent()) {
IElementType i = parent.getElementType();
if (i == LABELED_STATEMENT) {
ASTNode statementLabel = parent.findChildByRole(ChildRole.LABEL_NAME);
if (statementLabel != null && statementLabel.getText().equals(labelName)) {
return SourceTreeToPsiMap.<PsiLabeledStatement>treeToPsiNotNull(parent).getStatement();
}
}
if (i == METHOD || i == CLASS_INITIALIZER || i == LAMBDA_EXPRESSION) {
return null; // do not pass through anonymous/local class
}
}
PsiLabeledStatement labeled = PsiImplUtil.findEnclosingLabeledStatement(this, label.getText());
if (labeled != null) {
return labeled.getStatement();
}
PsiElement enclosing = PsiImplUtil.findEnclosingSwitchOrLoop(this);
if (enclosing instanceof PsiSwitchExpression) {
return enclosing;
}
return null;
}
@Override
public PsiReference getReference() {
PsiIdentifier label = getLabelIdentifier();
return label != null ? new PsiLabelReference(this, label) : null;
private PsiIdentifier labelId() {
PsiExpression expr = getExpression();
return PsiImplUtil.isPlainReference(expr) ? PsiTreeUtil.getChildOfType(expr, PsiIdentifier.class) : null;
}
@Override
public ASTNode findChildByRole(int role) {
LOG.assertTrue(ChildRole.isUnique(role));
switch (role) {
case ChildRole.BREAK_KEYWORD: return findChildByType(BREAK_KEYWORD);
case ChildRole.LABEL: return findChildByType(IDENTIFIER);
case ChildRole.CLOSING_SEMICOLON: return TreeUtil.findChildBackward(this, SEMICOLON);
case ChildRole.BREAK_KEYWORD: return findChildByType(JavaTokenType.BREAK_KEYWORD);
case ChildRole.LABEL: return findChildByType(ElementType.EXPRESSION_BIT_SET);
case ChildRole.CLOSING_SEMICOLON: return TreeUtil.findChildBackward(this, JavaTokenType.SEMICOLON);
default: return null;
}
}
@@ -80,9 +69,9 @@ public class PsiBreakStatementImpl extends CompositePsiElement implements PsiBre
public int getChildRole(@NotNull ASTNode child) {
LOG.assertTrue(child.getTreeParent() == this);
IElementType i = child.getElementType();
if (i == BREAK_KEYWORD) return ChildRole.BREAK_KEYWORD;
if (i == IDENTIFIER) return ChildRole.LABEL;
if (i == SEMICOLON) return ChildRole.CLOSING_SEMICOLON;
if (i == JavaTokenType.BREAK_KEYWORD) return ChildRole.BREAK_KEYWORD;
if (ElementType.EXPRESSION_BIT_SET.contains(i)) return ChildRole.LABEL;
if (i == JavaTokenType.SEMICOLON) return ChildRole.CLOSING_SEMICOLON;
return ChildRoleBase.NONE;
}
@@ -4,52 +4,40 @@ package com.intellij.psi.impl.source.tree.java;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.PsiLabelReference;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.Constants;
import com.intellij.psi.impl.source.tree.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.impl.source.tree.ChildRole;
import com.intellij.psi.impl.source.tree.CompositePsiElement;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NotNull;
public class PsiContinueStatementImpl extends CompositePsiElement implements PsiContinueStatement, Constants {
public class PsiContinueStatementImpl extends CompositePsiElement implements PsiContinueStatement {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.java.PsiContinueStatementImpl");
public PsiContinueStatementImpl() {
super(CONTINUE_STATEMENT);
super(JavaElementType.CONTINUE_STATEMENT);
}
@Override
public PsiIdentifier getLabelIdentifier() {
return (PsiIdentifier)findChildByRoleAsPsiElement(ChildRole.LABEL);
return (PsiIdentifier)findPsiChildByType(JavaTokenType.IDENTIFIER);
}
@Override
public PsiStatement findContinuedStatement() {
PsiIdentifier label = getLabelIdentifier();
if (label == null){
for(ASTNode parent = getTreeParent(); parent != null; parent = parent.getTreeParent()){
IElementType i = parent.getElementType();
if (i == FOR_STATEMENT || i == FOREACH_STATEMENT || i == WHILE_STATEMENT || i == DO_WHILE_STATEMENT) {
return (PsiStatement)SourceTreeToPsiMap.treeElementToPsi(parent);
}
if (i == METHOD || i == CLASS_INITIALIZER) {
return null;
}
}
if (label == null) {
return PsiImplUtil.findEnclosingLoop(this);
}
else{
String labelName = label.getText();
for(CompositeElement parent = getTreeParent(); parent != null; parent = parent.getTreeParent()){
if (parent.getElementType() == LABELED_STATEMENT){
TreeElement statementLabel = (TreeElement)parent.findChildByRole(ChildRole.LABEL_NAME);
if (statementLabel.textMatches(labelName)){
return ((PsiLabeledStatement)SourceTreeToPsiMap.treeElementToPsi(parent)).getStatement();
}
}
if (parent.getElementType() == METHOD || parent.getElementType() == CLASS_INITIALIZER || parent.getElementType() == LAMBDA_EXPRESSION) return null; // do not pass through anonymous/local class
}
PsiLabeledStatement labeled = PsiImplUtil.findEnclosingLabeledStatement(this, label.getText());
if (labeled != null) {
return labeled.getStatement();
}
return null;
}
@@ -1,6 +1,4 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.impl.source.tree.java;
import com.google.common.annotations.VisibleForTesting;
@@ -33,6 +31,7 @@ import com.intellij.psi.scope.processor.MethodResolverProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.tree.ChildRoleBase;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
@@ -108,7 +107,7 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
final String qualifiedName = qualifierClass.getQualifiedName();
final List<PsiJavaCodeReferenceElement> refs = getImportsFromClass(importList, qualifiedName);
JavaCodeStyleSettingsFacade javaCodeStyleSettingsFacade = JavaCodeStyleSettingsFacade.getInstance(qualifierClass.getProject());
if (!javaCodeStyleSettingsFacade.isToImportInDemand(qualifiedName) && refs.size() + 1 < javaCodeStyleSettingsFacade.getNamesCountToUseImportOnDemand() ||
if (!javaCodeStyleSettingsFacade.isToImportInDemand(qualifiedName) && refs.size() + 1 < javaCodeStyleSettingsFacade.getNamesCountToUseImportOnDemand() ||
JavaCodeStyleManager.getInstance(qualifierClass.getProject()).hasConflictingOnDemandImport((PsiJavaFile)importList.getContainingFile(), qualifierClass, staticName)) {
importList.add(JavaPsiFacade.getElementFactory(qualifierClass.getProject()).createImportStaticStatement(qualifierClass, staticName));
} else {
@@ -178,6 +177,7 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
public static final class OurGenericsResolver implements ResolveCache.PolyVariantContextResolver<PsiJavaReference> {
public static final OurGenericsResolver INSTANCE = new OurGenericsResolver();
private static final TokenSet EXACT_REFS = TokenSet.create(JavaElementType.REFERENCE_EXPRESSION, JavaElementType.BREAK_STATEMENT);
@NotNull
@Override
@@ -189,7 +189,7 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
List<ResolveResult[]> qualifiers = resolveAllQualifiers(expression, containingFile);
JavaResolveResult[] result = expression.resolve(parentType, containingFile);
if (result.length == 0 && incompleteCode && parentType != JavaElementType.REFERENCE_EXPRESSION) {
if (result.length == 0 && incompleteCode && !EXACT_REFS.contains(parentType)) {
result = expression.resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
}
@@ -201,7 +201,7 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
}
@NotNull
private static List<ResolveResult[]> resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull final PsiFile containingFile) {
private static List<ResolveResult[]> resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull PsiFile containingFile) {
// to avoid SOE, resolve all qualifiers starting from the innermost
PsiElement qualifier = expression.getQualifier();
if (qualifier == null) return Collections.emptyList();
@@ -297,6 +297,25 @@ public class PsiReferenceExpressionImpl extends ExpressionPsiElement implements
return resolve(JavaElementType.REFERENCE_EXPRESSION, containingFile);
}
if (parentType == JavaElementType.BREAK_STATEMENT && getQualifierExpression() == null) {
PsiElement breakStatement = getParent();
JavaResolveResult[] labels = JavaResolveResult.EMPTY_ARRAY;
PsiLabeledStatement labeled = PsiImplUtil.findEnclosingLabeledStatement(breakStatement, getText());
if (labeled != null) {
labels = new JavaResolveResult[]{new CandidateInfo(labeled, PsiSubstitutor.EMPTY)};
}
PsiElement context = PsiImplUtil.findEnclosingSwitchOrLoop(breakStatement);
if (!(context instanceof PsiSwitchExpression)) {
return labels;
}
else if (labels.length > 0) {
JavaResolveResult[] vars = resolveToVariable(containingFile);
return ArrayUtil.mergeArrays(labels, vars);
}
}
return resolveToVariable(containingFile);
}
@@ -0,0 +1,13 @@
PsiJavaFile:BreakExpr.java
PsiBreakStatement
PsiKeyword:break('break')
PsiWhiteSpace(' ')
PsiMethodCallExpression:boo()
PsiReferenceExpression:boo
PsiReferenceParameterList
<empty list>
PsiIdentifier:boo('boo')
PsiExpressionList
PsiJavaToken:LPARENTH('(')
PsiJavaToken:RPARENTH(')')
PsiJavaToken:SEMICOLON(';')
@@ -0,0 +1,5 @@
PsiJavaFile:BreakIncomplete.java
PsiBreakStatement
PsiKeyword:break('break')
PsiErrorElement:';' expected
<empty list>
@@ -2,5 +2,8 @@ PsiJavaFile:BreakNormal1.java
PsiBreakStatement
PsiKeyword:break('break')
PsiWhiteSpace(' ')
PsiIdentifier:LABEL('LABEL')
PsiReferenceExpression:LABEL
PsiReferenceParameterList
<empty list>
PsiIdentifier:LABEL('LABEL')
PsiJavaToken:SEMICOLON(';')
@@ -25,6 +25,8 @@ public class StatementParserTest extends JavaParsingTestCase {
public void testBreakNormal0() { doParserTest("break;"); }
public void testBreakNormal1() { doParserTest("break LABEL;"); }
public void testBreakIncomplete() { doParserTest("break"); }
public void testBreakExpr() { doParserTest("break boo();"); }
public void testContinueNormal0() { doParserTest("continue;"); }
public void testContinueNormal1() { doParserTest("continue LABEL;"); }