RFR: 8302344: Compiler Implementation for Unnamed patterns and variables (Preview) [v4]
Jan Lahoda
jlahoda at openjdk.org
Fri May 5 09:29:44 UTC 2023
On Mon, 1 May 2023 22:29:21 GMT, Aggelos Biboudis <abimpoudis at openjdk.org> wrote:
>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java line 1777:
>>
>>> 1775: log.error(guard.pos(), Errors.GuardHasConstantExpressionFalse);
>>> 1776: }
>>> 1777: } else if (guard != null) {
>>
>> I guess this is caused by something like:
>>
>> case _ when ...
>>
>> If now a guard can appear regardless of how many labels there are, should be refactor this code so that if `guard != null` it is always attributed? And then we do other stuff in case `labels.tail.isEmpty()` ? Note that the code for attributing the guard is slightly different in the two cases in this patch: the original code creates a nested scope - while the new code doesn't.
>
> Actually this is for the case where we have `case A(_), B(C _) when ... `. In that case we are sure that the only scope we can use is the switch environment (since no bindings are introduced). Hm, I don't see another way of refactoring this without depending on `labels.tail.isEmpty()`
I was looking into this again, and I am afraid this is not quite correct. One of the issues is that this attrib will place any binding variables from the guard into the current `matchBindings`, and when the guard is attributed again, the variables in the `matchBindings` will collide. For example:
String unnamedGuardAddsBindings(Object o1, Object o2) {
return switch (o1) {
case String _, Object _ when o2 instanceof String s: yield s;
case Object _: yield "any";
};
}
My current proposal would be to (try to):
- attribute the guard when handling the first pattern (so that we can detect whether or not it is constant)
- but, do not merge the bindings from the guard immediately, but only after all the patterns are handled
It could look like this:
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
index e9c3ca27f1c..bade6b530eb 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java
@@ -1688,7 +1688,7 @@ public class Attr extends JCTree.Visitor {
wasError = true;
}
MatchBindings currentBindings = null;
- boolean wasUnconditionalPattern = hasUnconditionalPattern;
+ MatchBindings guardBindings = null;
for (List<JCCaseLabel> labels = c.labels; labels.nonEmpty(); labels = labels.tail) {
JCCaseLabel label = labels.head;
if (label instanceof JCConstantCaseLabel constLabel) {
@@ -1761,7 +1761,7 @@ public class Attr extends JCTree.Visitor {
checkCastablePattern(pat.pos(), seltype, primaryType);
Type patternType = types.erasure(primaryType);
JCExpression guard = c.guard;
- if (labels.tail.isEmpty() && guard != null) {
+ if (labels == c.labels && guard != null) {
MatchBindings afterPattern = matchBindings;
Env<AttrContext> bodyEnv = bindingEnv(switchEnv, matchBindings.bindingsWhenTrue);
try {
@@ -1769,14 +1769,13 @@ public class Attr extends JCTree.Visitor {
} finally {
bodyEnv.info.scope.leave();
}
- matchBindings = matchBindingsComputer.caseGuard(c, afterPattern, matchBindings);
+
+ guardBindings = matchBindings;
+ matchBindings = afterPattern;
if (TreeInfo.isBooleanWithValue(guard, 0)) {
log.error(guard.pos(), Errors.GuardHasConstantExpressionFalse);
}
- } else if (guard != null) {
- // if the label has multiple patterns and a guard (with binding from the switch environment)
- attribExpr(guard, switchEnv, syms.booleanType);
}
boolean unguarded = TreeInfo.unguardedCase(c) && !pat.hasTag(RECORDPATTERN);
boolean unconditional =
@@ -1799,6 +1798,10 @@ public class Attr extends JCTree.Visitor {
currentBindings = matchBindingsComputer.switchCase(label, currentBindings, matchBindings);
}
+ if (guardBindings != null) {
+ currentBindings = matchBindingsComputer.caseGuard(c, currentBindings, guardBindings);
+ }
+
Env<AttrContext> caseEnv =
bindingEnv(switchEnv, c, currentBindings.bindingsWhenTrue);
try {
(I didn't run tests on this, though.)
>> src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java line 746:
>>
>>> 744:
>>> 745: sealed interface PatternDescription {
>>> 746: public static PatternDescription from(Types types, Type selectorType, JCPattern pattern, Symtab syms) {
>>
>> Optional comment: the fact that this factory needs so much compiler state makes me think that perhaps a better place for it would be as an instance method of Flow, rather than a static method in PatternDescription.
>
> @lahodaj WDYT? Looks like it can be easily refactored and it is used only in one place (apart from the recursive call). I can refactor it in this PR or u can do it in #13074.
I somewhat liked the "factory method" aspect to this, but I admit the parameter list is getting longer. So, up to you, I think.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/13528#discussion_r1185881273
PR Review Comment: https://git.openjdk.org/jdk/pull/13528#discussion_r1185817955
More information about the compiler-dev
mailing list