< 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 >