< prev index next >

src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java

Print this page
rev 51258 : imported patch switch.diff

@@ -24,14 +24,17 @@
  */
 
 package com.sun.tools.javac.comp;
 
 import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
 
 import javax.lang.model.element.ElementKind;
 import javax.tools.JavaFileObject;
 
+import com.sun.source.tree.CaseTree.CaseKind;
 import com.sun.source.tree.IdentifierTree;
 import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
 import com.sun.source.tree.MemberSelectTree;
 import com.sun.source.tree.TreeVisitor;
 import com.sun.source.util.SimpleTreeVisitor;

@@ -1393,65 +1396,144 @@
         attribStat(tree.body, env.dup(tree));
         result = null;
     }
 
     public void visitSwitch(JCSwitch tree) {
-        Type seltype = attribExpr(tree.selector, env);
+        handleSwitch(tree, tree.selector, tree.cases, (c, caseEnv) -> {
+            attribStats(c.stats, caseEnv);
+        });
+        result = null;
+    }
+
+    public void visitSwitchExpression(JCSwitchExpression tree) {
+        tree.polyKind = (pt().hasTag(NONE) && pt() != Type.recoveryType && pt() != Infer.anyPoly) ?
+                PolyKind.STANDALONE : PolyKind.POLY;
+
+        if (tree.polyKind == PolyKind.POLY && resultInfo.pt.hasTag(VOID)) {
+            //this means we are returning a poly conditional from void-compatible lambda expression
+            resultInfo.checkContext.report(tree, diags.fragment(Fragments.SwitchExpressionTargetCantBeVoid));
+            result = tree.type = types.createErrorType(resultInfo.pt);
+            return;
+        }
+
+        ResultInfo condInfo = tree.polyKind == PolyKind.STANDALONE ?
+                unknownExprInfo :
+                resultInfo.dup(switchExpressionContext(resultInfo.checkContext));
+
+        ListBuffer<DiagnosticPosition> caseTypePositions = new ListBuffer<>();
+        ListBuffer<Type> caseTypes = new ListBuffer<>();
+
+        handleSwitch(tree, tree.selector, tree.cases, (c, caseEnv) -> {
+            caseEnv.info.breakResult = condInfo;
+            attribStats(c.stats, caseEnv);
+            new TreeScanner() {
+                @Override
+                public void visitBreak(JCBreak brk) {
+                    if (brk.target == tree) {
+                        caseTypePositions.append(brk.value != null ? brk.value.pos() : brk.pos());
+                        caseTypes.append(brk.value != null ? brk.value.type : syms.errType);
+                    }
+                    super.visitBreak(brk);
+                }
+
+                @Override public void visitClassDef(JCClassDecl tree) {}
+                @Override public void visitLambda(JCLambda tree) {}
+            }.scan(c.stats);
+        });
+
+        if (tree.cases.isEmpty()) {
+            log.error(tree.pos(),
+                      Errors.SwitchExpressionEmpty);
+        }
+
+        Type owntype = (tree.polyKind == PolyKind.STANDALONE) ? condType(caseTypePositions.toList(), caseTypes.toList()) : pt();
+
+        result = tree.type = check(tree, owntype, KindSelector.VAL, resultInfo);
+    }
+    //where:
+        CheckContext switchExpressionContext(CheckContext checkContext) {
+            return new Check.NestedCheckContext(checkContext) {
+                //this will use enclosing check context to check compatibility of
+                //subexpression against target type; if we are in a method check context,
+                //depending on whether boxing is allowed, we could have incompatibilities
+                @Override
+                public void report(DiagnosticPosition pos, JCDiagnostic details) {
+                    enclosingContext.report(pos, diags.fragment(Fragments.IncompatibleTypeInSwitchExpression(details)));
+                }
+            };
+        }
+
+    private void handleSwitch(JCTree switchTree,
+                              JCExpression selector,
+                              List<JCCase> cases,
+                              BiConsumer<JCCase, Env<AttrContext>> attribCase) {
+        Type seltype = attribExpr(selector, env);
 
         Env<AttrContext> switchEnv =
-            env.dup(tree, env.info.dup(env.info.scope.dup()));
+            env.dup(switchTree, env.info.dup(env.info.scope.dup()));
 
         try {
-
             boolean enumSwitch = (seltype.tsym.flags() & Flags.ENUM) != 0;
             boolean stringSwitch = types.isSameType(seltype, syms.stringType);
             if (!enumSwitch && !stringSwitch)
-                seltype = chk.checkType(tree.selector.pos(), seltype, syms.intType);
+                seltype = chk.checkType(selector.pos(), seltype, syms.intType);
 
             // Attribute all cases and
             // check that there are no duplicate case labels or default clauses.
             Set<Object> labels = new HashSet<>(); // The set of case labels.
             boolean hasDefault = false;      // Is there a default label?
-            for (List<JCCase> l = tree.cases; l.nonEmpty(); l = l.tail) {
+            @SuppressWarnings("removal")
+            CaseKind caseKind = null;
+            boolean wasError = false;
+            for (List<JCCase> l = cases; l.nonEmpty(); l = l.tail) {
                 JCCase c = l.head;
-                if (c.pat != null) {
-                    if (enumSwitch) {
-                        Symbol sym = enumConstant(c.pat, seltype);
+                if (caseKind == null) {
+                    caseKind = c.caseKind;
+                } else if (caseKind != c.caseKind && !wasError) {
+                    log.error(c.pos(),
+                              Errors.SwitchMixingCaseTypes);
+                    wasError = true;
+                }
+                if (c.getExpressions().nonEmpty()) {
+                    for (JCExpression pat : c.getExpressions()) {
+                        if (TreeInfo.isNull(pat)) {
+                            log.error(pat.pos(),
+                                      Errors.SwitchNullNotAllowed);
+                        } else if (enumSwitch) {
+                            Symbol sym = enumConstant(pat, seltype);
                         if (sym == null) {
-                            log.error(c.pat.pos(), Errors.EnumLabelMustBeUnqualifiedEnum);
+                                log.error(pat.pos(), Errors.EnumLabelMustBeUnqualifiedEnum);
                         } else if (!labels.add(sym)) {
                             log.error(c.pos(), Errors.DuplicateCaseLabel);
                         }
                     } else {
-                        Type pattype = attribExpr(c.pat, switchEnv, seltype);
+                            Type pattype = attribExpr(pat, switchEnv, seltype);
                         if (!pattype.hasTag(ERROR)) {
                             if (pattype.constValue() == null) {
-                                log.error(c.pat.pos(),
+                                    log.error(pat.pos(),
                                           (stringSwitch ? Errors.StringConstReq : Errors.ConstExprReq));
                             } else if (!labels.add(pattype.constValue())) {
                                 log.error(c.pos(), Errors.DuplicateCaseLabel);
                             }
                         }
                     }
+                    }
                 } else if (hasDefault) {
                     log.error(c.pos(), Errors.DuplicateDefaultLabel);
                 } else {
                     hasDefault = true;
                 }
                 Env<AttrContext> caseEnv =
                     switchEnv.dup(c, env.info.dup(switchEnv.info.scope.dup()));
                 try {
-                    attribStats(c.stats, caseEnv);
+                    attribCase.accept(c, caseEnv);
                 } finally {
                     caseEnv.info.scope.leave();
                     addVars(c.stats, switchEnv.info.scope);
                 }
             }
-
-            result = null;
-        }
-        finally {
+        } finally {
             switchEnv.info.scope.leave();
         }
     }
     // where
         /** Add any variables defined in stats to the switch scope. */

@@ -1607,11 +1689,13 @@
                 resultInfo.dup(conditionalContext(resultInfo.checkContext));
 
         Type truetype = attribTree(tree.truepart, env, condInfo);
         Type falsetype = attribTree(tree.falsepart, env, condInfo);
 
-        Type owntype = (tree.polyKind == PolyKind.STANDALONE) ? condType(tree, truetype, falsetype) : pt();
+        Type owntype = (tree.polyKind == PolyKind.STANDALONE) ?
+                condType(List.of(tree.truepart.pos(), tree.falsepart.pos()),
+                         List.of(truetype, falsetype)) : pt();
         if (condtype.constValue() != null &&
                 truetype.constValue() != null &&
                 falsetype.constValue() != null &&
                 !owntype.hasTag(NONE)) {
             //constant folding

@@ -1688,71 +1772,70 @@
          *  @param pos      The source position to be used for error
          *                  diagnostics.
          *  @param thentype The type of the expression's then-part.
          *  @param elsetype The type of the expression's else-part.
          */
-        Type condType(DiagnosticPosition pos,
-                               Type thentype, Type elsetype) {
+        Type condType(List<DiagnosticPosition> positions, List<Type> condTypes) {
+            if (condTypes.isEmpty()) {
+                return syms.objectType; //TODO: how to handle?
+            }
+            if (condTypes.size() == 1) {
+                return condTypes.head;
+            }
+            Type first = condTypes.head;
             // If same type, that is the result
-            if (types.isSameType(thentype, elsetype))
-                return thentype.baseType();
+            if (condTypes.tail.stream().allMatch(t -> types.isSameType(first, t)))
+                return first.baseType();
 
-            Type thenUnboxed = (thentype.isPrimitive())
-                ? thentype : types.unboxedType(thentype);
-            Type elseUnboxed = (elsetype.isPrimitive())
-                ? elsetype : types.unboxedType(elsetype);
+            List<Type> unboxedTypes = condTypes.stream()
+                                               .map(t -> t.isPrimitive() ? t : types.unboxedType(t))
+                                               .collect(List.collector());
 
             // Otherwise, if both arms can be converted to a numeric
             // type, return the least numeric type that fits both arms
             // (i.e. return larger of the two, or return int if one
             // arm is short, the other is char).
-            if (thenUnboxed.isPrimitive() && elseUnboxed.isPrimitive()) {
+            if (unboxedTypes.stream().allMatch(t -> t.isPrimitive())) {
                 // If one arm has an integer subrange type (i.e., byte,
                 // short, or char), and the other is an integer constant
                 // that fits into the subrange, return the subrange type.
-                if (thenUnboxed.getTag().isStrictSubRangeOf(INT) &&
-                    elseUnboxed.hasTag(INT) &&
-                    types.isAssignable(elseUnboxed, thenUnboxed)) {
-                    return thenUnboxed.baseType();
-                }
-                if (elseUnboxed.getTag().isStrictSubRangeOf(INT) &&
-                    thenUnboxed.hasTag(INT) &&
-                    types.isAssignable(thenUnboxed, elseUnboxed)) {
-                    return elseUnboxed.baseType();
+                for (Type type : unboxedTypes) {
+                    if (!type.getTag().isStrictSubRangeOf(INT)) {
+                        continue;
+                    }
+                    if (unboxedTypes.stream().filter(t -> t != type).allMatch(t -> t.hasTag(INT) && types.isAssignable(t, type)))
+                        return type.baseType();
                 }
 
                 for (TypeTag tag : primitiveTags) {
                     Type candidate = syms.typeOfTag[tag.ordinal()];
-                    if (types.isSubtype(thenUnboxed, candidate) &&
-                        types.isSubtype(elseUnboxed, candidate)) {
+                    if (unboxedTypes.stream().allMatch(t -> types.isSubtype(t, candidate))) {
                         return candidate;
                     }
                 }
             }
 
             // Those were all the cases that could result in a primitive
-            if (thentype.isPrimitive())
-                thentype = types.boxedClass(thentype).type;
-            if (elsetype.isPrimitive())
-                elsetype = types.boxedClass(elsetype).type;
-
-            if (types.isSubtype(thentype, elsetype))
-                return elsetype.baseType();
-            if (types.isSubtype(elsetype, thentype))
-                return thentype.baseType();
+            condTypes = condTypes.stream()
+                                 .map(t -> t.isPrimitive() ? types.boxedClass(t).type : t)
+                                 .collect(List.collector());
 
-            if (thentype.hasTag(VOID) || elsetype.hasTag(VOID)) {
-                log.error(pos,
-                          Errors.NeitherConditionalSubtype(thentype,
-                                                           elsetype));
-                return thentype.baseType();
+            for (Type type : condTypes) {
+                if (condTypes.stream().filter(t -> t != type).allMatch(t -> types.isAssignable(t, type)))
+                    return type.baseType();
             }
 
+            Iterator<DiagnosticPosition> posIt = positions.iterator();
+
+            condTypes = condTypes.stream()
+                                 .map(t -> chk.checkNonVoid(posIt.next(), t))
+                                 .collect(List.collector());
+
             // both are known to be reference types.  The result is
             // lub(thentype,elsetype). This cannot fail, as it will
             // always be possible to infer "Object" if nothing better.
-            return types.lub(thentype.baseType(), elsetype.baseType());
+            return types.lub(condTypes.stream().map(t -> t.baseType()).collect(List.collector()));
         }
 
     final static TypeTag[] primitiveTags = new TypeTag[]{
         BYTE,
         CHAR,

@@ -1780,11 +1863,72 @@
         attribExpr(tree.expr, localEnv);
         result = null;
     }
 
     public void visitBreak(JCBreak tree) {
-        tree.target = findJumpTarget(tree.pos(), tree.getTag(), tree.label, env);
+        if (env.info.breakResult != null) {
+            if (tree.value == null) {
+                tree.target = findJumpTarget(tree.pos(), tree.getTag(), null, env);
+                if (tree.target.hasTag(SWITCH_EXPRESSION)) {
+                    log.error(tree.pos(), Errors.BreakMissingValue);
+                }
+            } else {
+                if (env.info.breakResult.pt.hasTag(VOID)) {
+                    //can happen?
+                    env.info.breakResult.checkContext.report(tree.value.pos(),
+                              diags.fragment(Fragments.UnexpectedRetVal));
+                }
+                boolean attribute = true;
+                if (tree.value.hasTag(IDENT)) {
+                    //disambiguate break <LABEL> and break <ident-as-an-expression>:
+                    Name label = ((JCIdent) tree.value).name;
+                    Pair<JCTree, Error> jumpTarget = findJumpTargetNoError(tree.getTag(), label, env);
+
+                    if (jumpTarget.fst != null) {
+                        JCTree speculative = deferredAttr.attribSpeculative(tree.value, env, unknownExprInfo);
+                        if (!speculative.type.hasTag(ERROR)) {
+                            log.error(tree.pos(), Errors.BreakAmbiguousTarget(label));
+                            if (jumpTarget.snd == null) {
+                                tree.target = jumpTarget.fst;
+                                attribute = false;
+                            } else {
+                                //nothing
+                            }
+                        } else {
+                            if (jumpTarget.snd != null) {
+                                log.error(tree.pos(), jumpTarget.snd);
+                            }
+                            tree.target = jumpTarget.fst;
+                            attribute = false;
+                        }
+                    }
+                }
+                if (attribute) {
+                    attribTree(tree.value, env, env.info.breakResult);
+                    JCTree immediateTarget = findJumpTarget(tree.pos(), tree.getTag(), null, env);
+                    if (immediateTarget.getTag() != SWITCH_EXPRESSION) {
+                        log.error(tree.pos(), Errors.BreakExprNotImmediate(immediateTarget.getTag()));
+                        Env<AttrContext> env1 = env;
+                        while (env1 != null && env1.tree.getTag() != SWITCH_EXPRESSION) {
+                            env1 = env1.next;
+                        }
+                        Assert.checkNonNull(env1);
+                        tree.target = env1.tree;
+                    } else {
+                        tree.target = immediateTarget;
+                    }
+                }
+            }
+        } else {
+            if (tree.value == null || tree.value.hasTag(IDENT)) {
+                Name label = tree.value != null ? ((JCIdent) tree.value).name : null;
+                tree.target = findJumpTarget(tree.pos(), tree.getTag(), label, env);
+            } else {
+                log.error(tree.pos(), Errors.BreakComplexValueNoSwitchExpression);
+                attribTree(tree.value, env, unknownExprInfo);
+            }
+        }
         result = null;
     }
 
     public void visitContinue(JCContinue tree) {
         tree.target = findJumpTarget(tree.pos(), tree.getTag(), tree.label, env);

@@ -1806,12 +1950,36 @@
          */
         private JCTree findJumpTarget(DiagnosticPosition pos,
                                     JCTree.Tag tag,
                                     Name label,
                                     Env<AttrContext> env) {
+            Pair<JCTree, Error> jumpTarget = findJumpTargetNoError(tag, label, env);
+
+            if (jumpTarget.snd != null) {
+                log.error(pos, jumpTarget.snd);
+            }
+
+            return jumpTarget.fst;
+        }
+        /** Return the target of a break or continue statement, if it exists,
+         *  report an error if not.
+         *  Note: The target of a labelled break or continue is the
+         *  (non-labelled) statement tree referred to by the label,
+         *  not the tree representing the labelled statement itself.
+         *
+         *  @param tag     The tag of the jump statement. This is either
+         *                 Tree.BREAK or Tree.CONTINUE.
+         *  @param label   The label of the jump statement, or null if no
+         *                 label is given.
+         *  @param env     The environment current at the jump statement.
+         */
+        private Pair<JCTree, JCDiagnostic.Error> findJumpTargetNoError(JCTree.Tag tag,
+                                                                       Name label,
+                                                                       Env<AttrContext> env) {
             // Search environments outwards from the point of jump.
             Env<AttrContext> env1 = env;
+            JCDiagnostic.Error pendingError = null;
             LOOP:
             while (env1 != null) {
                 switch (env1.tree.getTag()) {
                     case LABELLED:
                         JCLabeledStatement labelled = (JCLabeledStatement)env1.tree;

@@ -1819,51 +1987,64 @@
                             // If jump is a continue, check that target is a loop.
                             if (tag == CONTINUE) {
                                 if (!labelled.body.hasTag(DOLOOP) &&
                                         !labelled.body.hasTag(WHILELOOP) &&
                                         !labelled.body.hasTag(FORLOOP) &&
-                                        !labelled.body.hasTag(FOREACHLOOP))
-                                    log.error(pos, Errors.NotLoopLabel(label));
+                                        !labelled.body.hasTag(FOREACHLOOP)) {
+                                    pendingError = Errors.NotLoopLabel(label);
+                                }
                                 // Found labelled statement target, now go inwards
                                 // to next non-labelled tree.
-                                return TreeInfo.referencedStatement(labelled);
+                                return Pair.of(TreeInfo.referencedStatement(labelled), pendingError);
                             } else {
-                                return labelled;
+                                return Pair.of(labelled, pendingError);
                             }
                         }
                         break;
                     case DOLOOP:
                     case WHILELOOP:
                     case FORLOOP:
                     case FOREACHLOOP:
-                        if (label == null) return env1.tree;
+                        if (label == null) return Pair.of(env1.tree, pendingError);
                         break;
                     case SWITCH:
-                        if (label == null && tag == BREAK) return env1.tree;
+                        if (label == null && tag == BREAK) return Pair.of(env1.tree, null);
+                        break;
+                    case SWITCH_EXPRESSION:
+                        if (tag == BREAK) {
+                            if (label == null) {
+                                return Pair.of(env1.tree, null);
+                            } else {
+                                pendingError = Errors.BreakOutsideSwitchExpression;
+                            }
+                        } else {
+                            pendingError = Errors.ContinueOutsideSwitchExpression;
+                        }
                         break;
                     case LAMBDA:
                     case METHODDEF:
                     case CLASSDEF:
                         break LOOP;
                     default:
                 }
                 env1 = env1.next;
             }
             if (label != null)
-                log.error(pos, Errors.UndefLabel(label));
+                return Pair.of(null, Errors.UndefLabel(label));
             else if (tag == CONTINUE)
-                log.error(pos, Errors.ContOutsideLoop);
+                return Pair.of(null, Errors.ContOutsideLoop);
             else
-                log.error(pos, Errors.BreakOutsideSwitchLoop);
-            return null;
+                return Pair.of(null, Errors.BreakOutsideSwitchLoop);
         }
 
     public void visitReturn(JCReturn tree) {
         // Check that there is an enclosing method which is
         // nested within than the enclosing class.
         if (env.info.returnResult == null) {
             log.error(tree.pos(), Errors.RetOutsideMeth);
+        } else if (env.info.breakResult != null) {
+            log.error(tree.pos(), Errors.ReturnOutsideSwitchExpression);
         } else {
             // Attribute return expression, if it exists, and check that
             // it conforms to result type of enclosing method.
             if (tree.expr != null) {
                 if (env.info.returnResult.pt.hasTag(VOID)) {

@@ -2942,10 +3123,11 @@
                 }
                 lambdaEnv = env.dup(that, env.info.dup(env.info.scope.dupUnshared(newScopeOwner)));
             } else {
                 lambdaEnv = env.dup(that, env.info.dup(env.info.scope.dup()));
             }
+            lambdaEnv.info.breakResult = null;
             return lambdaEnv;
         }
 
     @Override
     public void visitReference(final JCMemberReference that) {
< prev index next >