< 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,37 ****
--- 24,40 ----
*/
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,1457 ****
attribStat(tree.body, env.dup(tree));
result = null;
}
public void visitSwitch(JCSwitch tree) {
! Type seltype = attribExpr(tree.selector, env);
Env<AttrContext> switchEnv =
! env.dup(tree, 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);
// 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) {
JCCase c = l.head;
! if (c.pat != null) {
! if (enumSwitch) {
! Symbol sym = enumConstant(c.pat, seltype);
if (sym == null) {
! log.error(c.pat.pos(), Errors.EnumLabelMustBeUnqualifiedEnum);
} else if (!labels.add(sym)) {
log.error(c.pos(), Errors.DuplicateCaseLabel);
}
} else {
! Type pattype = attribExpr(c.pat, switchEnv, seltype);
if (!pattype.hasTag(ERROR)) {
if (pattype.constValue() == null) {
! log.error(c.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);
} finally {
caseEnv.info.scope.leave();
addVars(c.stats, switchEnv.info.scope);
}
}
!
! result = null;
! }
! finally {
switchEnv.info.scope.leave();
}
}
// where
/** Add any variables defined in stats to the switch scope. */
--- 1396,1539 ----
attribStat(tree.body, env.dup(tree));
result = null;
}
public void visitSwitch(JCSwitch tree) {
! 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(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(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?
! @SuppressWarnings("removal")
! CaseKind caseKind = null;
! boolean wasError = false;
! for (List<JCCase> l = cases; l.nonEmpty(); l = l.tail) {
JCCase c = l.head;
! 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(pat.pos(), Errors.EnumLabelMustBeUnqualifiedEnum);
} else if (!labels.add(sym)) {
log.error(c.pos(), Errors.DuplicateCaseLabel);
}
} else {
! Type pattype = attribExpr(pat, switchEnv, seltype);
if (!pattype.hasTag(ERROR)) {
if (pattype.constValue() == null) {
! 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 {
! attribCase.accept(c, caseEnv);
} finally {
caseEnv.info.scope.leave();
addVars(c.stats, switchEnv.info.scope);
}
}
! } finally {
switchEnv.info.scope.leave();
}
}
// where
/** Add any variables defined in stats to the switch scope. */
*** 1607,1617 ****
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();
if (condtype.constValue() != null &&
truetype.constValue() != null &&
falsetype.constValue() != null &&
!owntype.hasTag(NONE)) {
//constant folding
--- 1689,1701 ----
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(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,1758 ****
* @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) {
// If same type, that is the result
! if (types.isSameType(thentype, elsetype))
! return thentype.baseType();
! Type thenUnboxed = (thentype.isPrimitive())
! ? thentype : types.unboxedType(thentype);
! Type elseUnboxed = (elsetype.isPrimitive())
! ? elsetype : types.unboxedType(elsetype);
// 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 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 (TypeTag tag : primitiveTags) {
Type candidate = syms.typeOfTag[tag.ordinal()];
! if (types.isSubtype(thenUnboxed, candidate) &&
! types.isSubtype(elseUnboxed, 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();
! if (thentype.hasTag(VOID) || elsetype.hasTag(VOID)) {
! log.error(pos,
! Errors.NeitherConditionalSubtype(thentype,
! elsetype));
! return thentype.baseType();
}
// 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());
}
final static TypeTag[] primitiveTags = new TypeTag[]{
BYTE,
CHAR,
--- 1772,1841 ----
* @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(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 (condTypes.tail.stream().allMatch(t -> types.isSameType(first, t)))
! return first.baseType();
! 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 (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.
! 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 (unboxedTypes.stream().allMatch(t -> types.isSubtype(t, candidate))) {
return candidate;
}
}
}
// Those were all the cases that could result in a primitive
! condTypes = condTypes.stream()
! .map(t -> t.isPrimitive() ? types.boxedClass(t).type : t)
! .collect(List.collector());
! 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(condTypes.stream().map(t -> t.baseType()).collect(List.collector()));
}
final static TypeTag[] primitiveTags = new TypeTag[]{
BYTE,
CHAR,
*** 1780,1790 ****
attribExpr(tree.expr, localEnv);
result = null;
}
public void visitBreak(JCBreak tree) {
! tree.target = findJumpTarget(tree.pos(), tree.getTag(), tree.label, env);
result = null;
}
public void visitContinue(JCContinue tree) {
tree.target = findJumpTarget(tree.pos(), tree.getTag(), tree.label, env);
--- 1863,1934 ----
attribExpr(tree.expr, localEnv);
result = null;
}
public void visitBreak(JCBreak tree) {
! 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,1817 ****
--- 1950,1985 ----
*/
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,1869 ****
// 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));
// Found labelled statement target, now go inwards
// to next non-labelled tree.
! return TreeInfo.referencedStatement(labelled);
} else {
! return labelled;
}
}
break;
case DOLOOP:
case WHILELOOP:
case FORLOOP:
case FOREACHLOOP:
! if (label == null) return env1.tree;
break;
case SWITCH:
! if (label == null && tag == BREAK) return env1.tree;
break;
case LAMBDA:
case METHODDEF:
case CLASSDEF:
break LOOP;
default:
}
env1 = env1.next;
}
if (label != null)
! log.error(pos, Errors.UndefLabel(label));
else if (tag == CONTINUE)
! log.error(pos, Errors.ContOutsideLoop);
else
! log.error(pos, Errors.BreakOutsideSwitchLoop);
! return null;
}
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 {
// 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)) {
--- 1987,2050 ----
// 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)) {
! pendingError = Errors.NotLoopLabel(label);
! }
// Found labelled statement target, now go inwards
// to next non-labelled tree.
! return Pair.of(TreeInfo.referencedStatement(labelled), pendingError);
} else {
! return Pair.of(labelled, pendingError);
}
}
break;
case DOLOOP:
case WHILELOOP:
case FORLOOP:
case FOREACHLOOP:
! if (label == null) return Pair.of(env1.tree, pendingError);
break;
case SWITCH:
! 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)
! return Pair.of(null, Errors.UndefLabel(label));
else if (tag == CONTINUE)
! return Pair.of(null, Errors.ContOutsideLoop);
else
! 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,2951 ****
--- 3123,3133 ----
}
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 >