diff --git a/java/java-psi-api/src/com/intellij/psi/PsiBreakStatement.java b/java/java-psi-api/src/com/intellij/psi/PsiBreakStatement.java index 879df13b113b..e9874660a1e8 100644 --- a/java/java-psi-api/src/com/intellij/psi/PsiBreakStatement.java +++ b/java/java-psi-api/src/com/intellij/psi/PsiBreakStatement.java @@ -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; + } +} \ No newline at end of file diff --git a/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java b/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java index 477575be4f77..ca5b218c8e2e 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/parser/StatementParser.java @@ -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; diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java index 00e5fc392aca..634fa4d266c3 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/PsiImplUtil.java @@ -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 * diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBreakStatementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBreakStatementImpl.java index e2c710b76a3d..7941c110d748 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBreakStatementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiBreakStatementImpl.java @@ -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.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; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiContinueStatementImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiContinueStatementImpl.java index f60fbd60a644..013cef2f4e2b 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiContinueStatementImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiContinueStatementImpl.java @@ -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; } diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java index d53a2b94c536..fde2bbef7bd0 100644 --- a/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java +++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/tree/java/PsiReferenceExpressionImpl.java @@ -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 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 { 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 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 resolveAllQualifiers(@NotNull PsiReferenceExpressionImpl expression, @NotNull final PsiFile containingFile) { + private static List 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); } diff --git a/java/java-tests/testData/psi/parser-partial/statements/BreakExpr.txt b/java/java-tests/testData/psi/parser-partial/statements/BreakExpr.txt new file mode 100644 index 000000000000..277b08fb638f --- /dev/null +++ b/java/java-tests/testData/psi/parser-partial/statements/BreakExpr.txt @@ -0,0 +1,13 @@ +PsiJavaFile:BreakExpr.java + PsiBreakStatement + PsiKeyword:break('break') + PsiWhiteSpace(' ') + PsiMethodCallExpression:boo() + PsiReferenceExpression:boo + PsiReferenceParameterList + + PsiIdentifier:boo('boo') + PsiExpressionList + PsiJavaToken:LPARENTH('(') + PsiJavaToken:RPARENTH(')') + PsiJavaToken:SEMICOLON(';') \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-partial/statements/BreakIncomplete.txt b/java/java-tests/testData/psi/parser-partial/statements/BreakIncomplete.txt new file mode 100644 index 000000000000..dd3d12c215d4 --- /dev/null +++ b/java/java-tests/testData/psi/parser-partial/statements/BreakIncomplete.txt @@ -0,0 +1,5 @@ +PsiJavaFile:BreakIncomplete.java + PsiBreakStatement + PsiKeyword:break('break') + PsiErrorElement:';' expected + \ No newline at end of file diff --git a/java/java-tests/testData/psi/parser-partial/statements/BreakNormal1.txt b/java/java-tests/testData/psi/parser-partial/statements/BreakNormal1.txt index 2d59018b5898..98eb78bcc7b7 100644 --- a/java/java-tests/testData/psi/parser-partial/statements/BreakNormal1.txt +++ b/java/java-tests/testData/psi/parser-partial/statements/BreakNormal1.txt @@ -2,5 +2,8 @@ PsiJavaFile:BreakNormal1.java PsiBreakStatement PsiKeyword:break('break') PsiWhiteSpace(' ') - PsiIdentifier:LABEL('LABEL') + PsiReferenceExpression:LABEL + PsiReferenceParameterList + + PsiIdentifier:LABEL('LABEL') PsiJavaToken:SEMICOLON(';') \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/parser/partial/StatementParserTest.java b/java/java-tests/testSrc/com/intellij/java/parser/partial/StatementParserTest.java index 8856c078bd02..6a1a8fcafdde 100644 --- a/java/java-tests/testSrc/com/intellij/java/parser/partial/StatementParserTest.java +++ b/java/java-tests/testSrc/com/intellij/java/parser/partial/StatementParserTest.java @@ -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;"); }