Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.eclipse.jdt.internal.compiler.flow.LabelFlowContext;
import org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;

public class LabeledStatement extends Statement {

Expand Down Expand Up @@ -154,6 +155,16 @@ public void traverse(
visitor.endVisit(this, blockScope);
}

@Override
public LocalVariableBinding[] bindingsWhenComplete() {
// If control can reach past this labeled statement via a break to our own
// label, the pattern variable(s) may not be matched at that exit point,
// so nothing may be treated as definitely matched on completion.
if (this.statement.breaksOut(this.label))
return NO_VARIABLES;
return this.statement.bindingsWhenComplete();
}

@Override
public boolean doesNotCompleteNormally() {
if (this.statement.breaksOut(this.label))
Expand All @@ -165,4 +176,4 @@ public boolean doesNotCompleteNormally() {
public boolean completesByContinue() {
return this.statement instanceof ContinueStatement; // NOT this.statement.continuesAtOuterLabel
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,9 @@ public boolean visit(Block block, BlockScope scope) {

@Override
public boolean visit(BreakStatement breakStatement, BlockScope skope) {
if (breakStatement.label != null && !CharOperation.equals(label, breakStatement.label)) {
return false;
}
if (label == null || CharOperation.equals(label, breakStatement.label))
this.breaksOut = true;
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*******************************************************************************
/*******************************************************************************
* Copyright (c) 2020, 2025 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
Expand Down Expand Up @@ -4871,4 +4871,158 @@ public static void main(String [] args) {
+ "false\n"
+ "true");
}
// A pattern variable declared in a while-condition must remain in scope after the
// loop when the only exit from the loop body is a 'break' to an *enclosing* label
// (here 'break X' leaves the block X, not the while), so normal loop completion is
// the only way to reach 'a2 = s;' and 's' is definitely matched there.
public void testLabeledBreakOutOfEnclosingBlock() {
runConformTest(
new String[] {
"X.java",
"""
public class X {
public static void foo(Object obj) {
Object obj1 = obj;
Object obj2 = null;
X: {
while (!(obj1 instanceof String s)) {
break X;
}
obj2 = s;
}
System.out.println(obj2);
}
public static void main(String[] argv) {
foo("a");
}
}
""",
},
"a");
}
// A pattern variable from a labeled while-condition must be in scope after the loop
// when the body cannot break out at all (it always throws), so the loop can only be
// left by normal completion where the pattern is matched.
public void testLabeledWhileNoBreak() {
runConformTest(
new String[] {
"X.java",
"""
public class X {
public static void foo(Object obj) {
Object obj1 = obj;
Object obj2 = null;
X: while (!(obj1 instanceof String s)) {
throw new RuntimeException("no match");
}
obj2 = s;
System.out.println(obj2);
}
public static void main(String[] argv) {
foo("a");
}
}
""",
},
"a");
}
// When 'break X' targets the while loop's own label, the loop may be left with the
// pattern *unmatched*, so the pattern variable must NOT be in scope afterwards and a
// subsequent 'String s' is a legal fresh declaration (must not clash).
public void testLabeledWhileBreakThenRedeclare() {
runConformTest(
new String[] {
"X.java",
"""
public class X {
public static void foo(Object obj) {
Object obj1 = obj;
X: while (!(obj1 instanceof String s)) {
break X;
}
String s = "foo";
System.out.println(s);
}
public static void main(String[] argv) {
foo("a");
}
}
""",
},
"foo");
}
// When 'break X' targets the while loop's own label, the loop can complete with the
// pattern unmatched, so the pattern variable must not be resolvable after the loop.
public void testLabeledWhileBreakExitsLoop() {
Map<String, String> options = getCompilerOptions(true);
runNegativeTest(
new String[] {
"X.java",
"""
public class X {
public static void foo(Object obj) {
Object obj1 = obj;
Object obj2 = null;
X: while (!(obj1 instanceof String s)) {
break X;
}
obj2 = s;
System.out.println(obj2);
}
public static void main(String[] argv) {
foo("a");
}
}
""",
},
"----------\n" +
"1. ERROR in X.java (at line 8)\n" +
" obj2 = s;\n" +
" ^\n" +
"s cannot be resolved to a variable\n" +
"----------\n",
"",
null,
true,
options);
}
// An inner 'break' that targets the while loop (not the enclosing label X) can leave
// the loop with the pattern unmatched, so the pattern variable must not be resolvable
// after the loop even though control also reaches there via 'break X'.
public void testInnerBreakExitsWhile() {
Map<String, String> options = getCompilerOptions(true);
runNegativeTest(
new String[] {
"X.java",
"""
public class X {
public static void foo(Object obj) {
Object obj1 = obj;
Object obj2 = null;
X: {
while (!(obj1 instanceof String s)) {
if (obj1 == null) break;
break X;
}
obj2 = s;
}
System.out.println(obj2);
}
public static void main(String[] argv) {
foo("a");
}
}
""",
},
"----------\n" +
"1. ERROR in X.java (at line 10)\n" +
" obj2 = s;\n" +
" ^\n" +
"s cannot be resolved to a variable\n" +
"----------\n",
"",
null,
true,
options);
}
}
Loading