Skip to content

Commit

Permalink
* Alternate approach for checking effective finality of locals referred
Browse files Browse the repository at this point in the history
from guard expression

* Fixes #2077
  • Loading branch information
srikanth-sankaran committed Mar 5, 2024
1 parent 6c652c6 commit fd09117
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import org.eclipse.jdt.internal.compiler.flow.FlowContext;
import org.eclipse.jdt.internal.compiler.flow.FlowInfo;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
Expand Down Expand Up @@ -103,8 +102,12 @@ public TypeBinding resolveType(BlockScope scope) {
if (this.resolvedType != null || this.primaryPattern == null)
return this.resolvedType;
this.resolvedType = this.primaryPattern.resolveType(scope);

this.condition.resolveTypeExpectingWithBindings(this.primaryPattern.bindingsWhenTrue(), scope, TypeBinding.BOOLEAN);
try {
scope.resolvingGuardExpression = true; // as guards cannot nest in the same scope, no save & restore called for
this.condition.resolveTypeExpectingWithBindings(this.primaryPattern.bindingsWhenTrue(), scope, TypeBinding.BOOLEAN);
} finally {
scope.resolvingGuardExpression = false;
}
Constant cst = this.condition.optimizedBooleanConstant();
if (cst.typeID() == TypeIds.T_boolean && cst.booleanValue() == false) {
scope.problemReporter().falseLiteralInGuard(this.condition);
Expand All @@ -113,27 +116,6 @@ public TypeBinding resolveType(BlockScope scope) {
if (!isEffectivelyUnguarded())
this.primaryPattern.setIsEffectivelyGuarded();

this.condition.traverse(new ASTVisitor() {
@Override
public boolean visit(
SingleNameReference ref,
BlockScope skope) {
LocalVariableBinding local = ref.localVariableBinding();
if (local != null) {
ref.bits |= ASTNode.IsUsedInPatternGuard;
}
return false;
}
@Override
public boolean visit(
QualifiedNameReference ref,
BlockScope skope) {
if ((ref.bits & ASTNode.RestrictiveFlagMASK) == Binding.LOCAL) {
ref.bits |= ASTNode.IsUsedInPatternGuard;
}
return false;
}
}, scope);
return this.resolvedType = this.primaryPattern.resolvedType;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public class BlockScope extends Scope {
// annotation support
public boolean insideTypeAnnotation = false;
public Statement blockStatement;
public boolean resolvingGuardExpression = false;

private boolean reparentLocals = false;

Expand Down Expand Up @@ -1431,6 +1432,12 @@ public void reportClashingDeclarations(LocalVariableBinding [] left, LocalVariab
}
}
}

@Override
public boolean resolvingGuardExpression() {
return this.resolvingGuardExpression;
}

public void include(LocalVariableBinding[] bindings) {
// `this` is assumed to be populated with bindings.
if (bindings != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public char[] readableName() {
public final CompilationUnitScope compilationUnitScope;
private Map<String, Supplier<ReferenceBinding>> commonTypeBindings = null;


private static class NullDefaultRange {
final int start, end;
int value;
Expand Down Expand Up @@ -2035,6 +2036,10 @@ public LocalVariableBinding findVariable(char[] variable, InvocationSite invocat
return null;
}

public boolean resolvingGuardExpression() {
return false;
}

/* API
*
* Answer the binding that corresponds to the argument name.
Expand Down Expand Up @@ -2075,6 +2080,7 @@ public Binding getBinding(char[] name, int mask, InvocationSite invocationSite,
int depth = 0;
int foundDepth = 0;
boolean shouldTrackOuterLocals = false;
boolean resolvingGuardExpression = false;
ReferenceBinding foundActualReceiverType = null;
done : while (true) { // done when a COMPILATION_UNIT_SCOPE is found
switch (scope.kind) {
Expand All @@ -2086,6 +2092,8 @@ public Binding getBinding(char[] name, int mask, InvocationSite invocationSite,

//$FALL-THROUGH$ could duplicate the code below to save a cast - questionable optimization
case BLOCK_SCOPE :
if (!resolvingGuardExpression)
resolvingGuardExpression = scope.resolvingGuardExpression();
LocalVariableBinding variableBinding = scope.findVariable(name, invocationSite);
// looks in this scope only
if (variableBinding != null) {
Expand All @@ -2106,6 +2114,8 @@ public Binding getBinding(char[] name, int mask, InvocationSite invocationSite,
variableDeclaration.bits |= ASTNode.ShadowsOuterLocal;
}
}
if (resolvingGuardExpression && invocationSite instanceof NameReference nameReference)
nameReference.bits |= ASTNode.IsUsedInPatternGuard;
return variableBinding;
}
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7707,4 +7707,117 @@ public static void main(String argv[]) {
},
"133");
}
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2077
// [Patterns] Incorrect complaint about non-final variable reference from guard expression
public void testIssue2077() throws Exception {
runConformTest(
new String[] {
"X.java",
"""
public class X {
public static void main(String [] args) {
new X().foo("Hello ", 1);
}
public void foo(Object obj, int x) {
int y = 10;
y = 20;
y = 30;
switch (obj) {
case String s when switch (x) {
case 1 -> { int y1 = 10; y1 = 30; yield y1!=20; }
default -> { yield false; }
}
-> {
System.out.println(s + "OK");
if (y == 0)
System.out.println(s + "OK");
}
default -> {}
}
}
}
"""
},
"Hello OK");
}
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2077
// [Patterns] Incorrect complaint about non-final variable reference from guard expression
public void testIssue2077_2() throws Exception {
runNegativeTest(
new String[] {
"X.java",
"""
public class X {
public static void main(String [] args) {
new X().foo("Hello ", 1);
}
public void foo(Object obj, int x) {
int y = 10;
y = 20;
y = 30;
switch (obj) {
case String s when switch (x) {
case 1 -> { int y1 = 10; y1 = 30; yield 30 != y1; }
default -> { yield false; }
} && y != 0
-> {
System.out.println(s + "OK");
if (y == 0)
System.out.println(s + "OK");
}
default -> {}
}
}
}
"""
},
"----------\n" +
"1. ERROR in X.java (at line 13)\r\n" +
" } && y != 0\r\n" +
" ^\n" +
"Local variable y referenced from a guard must be final or effectively final\n" +
"----------\n");
}
// https://github.com/eclipse-jdt/eclipse.jdt.core/issues/2077
// [Patterns] Incorrect complaint about non-final variable reference from guard expression
public void testIssue2077_3() throws Exception {
runNegativeTest(
new String[] {
"X.java",
"""
public class X {
public static void main(String [] args) {
new X().foo("Hello ", 1);
}
public void foo(Object obj, int x) {
int y = 10;
y = 20;
y = 30;
switch (obj) {
case String s when switch (x) {
case 1 -> { int y1 = 10; y1 = 30; yield y != y1; }
default -> { yield false; }
} && y != 0
-> {
System.out.println(s + "OK");
if (y == 0)
System.out.println(s + "OK");
}
default -> {}
}
}
}
"""
},
"----------\n" +
"1. ERROR in X.java (at line 11)\r\n" +
" case 1 -> { int y1 = 10; y1 = 30; yield y != y1; }\r\n" +
" ^\n" +
"Local variable y referenced from a guard must be final or effectively final\n" +
"----------\n");
// We throw AbortMethod after first error, so second error doesn't surface
}
}

0 comments on commit fd09117

Please sign in to comment.