Skip to content

Commit 99135d2

Browse files
committed
8359145: Implement JEP 530: Primitive Types in Patterns, instanceof, and switch (Fourth Preview)
Reviewed-by: jlahoda
1 parent 02ff38f commit 99135d2

22 files changed

+776
-59
lines changed

src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ private static String typeSwitchClassName(Class<?> targetClass) {
777777
return name + "$$TypeSwitch";
778778
}
779779

780-
// this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives
780+
// this method should be in sync with com.sun.tools.javac.code.Types.isUnconditionallyExactTypeBased
781781
private static boolean unconditionalExactnessCheck(Class<?> selectorType, Class<?> targetType) {
782782
Wrapper selectorWrapper = Wrapper.forBasicType(selectorType);
783783
Wrapper targetWrapper = Wrapper.forBasicType(targetType);

src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeTag.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
package com.sun.tools.javac.code;
2727

2828
import com.sun.source.tree.Tree.Kind;
29-
29+
import java.lang.runtime.ExactConversionsSupport;
3030
import javax.lang.model.type.TypeKind;
3131

3232
import static com.sun.tools.javac.code.TypeTag.NumericClasses.*;
@@ -186,6 +186,10 @@ public boolean isInSuperClassesOf(TypeTag tag) {
186186
return (this.numericClass & tag.superClasses) != 0;
187187
}
188188

189+
public boolean isNumeric() {
190+
return this.numericClass != 0;
191+
}
192+
189193
/** Returns the number of type tags.
190194
*/
191195
public static int getTypeTagCount() {
@@ -247,11 +251,11 @@ public boolean checkRange(int value) {
247251
case BOOLEAN:
248252
return 0 <= value && value <= 1;
249253
case BYTE:
250-
return Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE;
254+
return ExactConversionsSupport.isIntToByteExact(value);
251255
case CHAR:
252-
return Character.MIN_VALUE <= value && value <= Character.MAX_VALUE;
256+
return ExactConversionsSupport.isIntToCharExact(value);
253257
case SHORT:
254-
return Short.MIN_VALUE <= value && value <= Short.MAX_VALUE;
258+
return ExactConversionsSupport.isIntToShortExact(value);
255259
case INT:
256260
return true;
257261
default:

src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
package com.sun.tools.javac.code;
2727

2828
import java.lang.ref.SoftReference;
29+
import java.lang.runtime.ExactConversionsSupport;
2930
import java.util.HashSet;
3031
import java.util.HashMap;
3132
import java.util.Locale;
@@ -5086,46 +5087,128 @@ public Type visitCapturedType(CapturedType t, S s) {
50865087
}
50875088
// </editor-fold>
50885089

5089-
// <editor-fold defaultstate="collapsed" desc="Unconditionality">
5090-
/** Check unconditionality between any combination of reference or primitive types.
5090+
// <editor-fold defaultstate="collapsed" desc="Unconditional Exactness">
5091+
/** Check type-based unconditional exactness between any combination of
5092+
* reference or primitive types according to JLS 5.7.2.
50915093
*
5092-
* Rules:
5093-
* an identity conversion
5094-
* a widening reference conversion
5095-
* a widening primitive conversion (delegates to `checkUnconditionallyExactPrimitives`)
5096-
* a boxing conversion
5097-
* a boxing conversion followed by a widening reference conversion
5094+
* The following are unconditionally exact regardless of the input
5095+
* expression:
5096+
*
5097+
* - an identity conversion
5098+
* - a widening reference conversion
5099+
* - an exact widening primitive conversion
5100+
* - a boxing conversion
5101+
* - a boxing conversion followed by a widening reference conversion
50985102
*
50995103
* @param source Source primitive or reference type
51005104
* @param target Target primitive or reference type
51015105
*/
5102-
public boolean isUnconditionallyExact(Type source, Type target) {
5106+
public boolean isUnconditionallyExactTypeBased(Type source, Type target) {
51035107
if (isSameType(source, target)) {
51045108
return true;
51055109
}
51065110

5107-
return target.isPrimitive()
5108-
? isUnconditionallyExactPrimitives(source, target)
5109-
: isSubtype(boxedTypeOrType(erasure(source)), target);
5111+
if (target.isPrimitive()) {
5112+
if (source.isPrimitive() &&
5113+
((source.getTag().isStrictSubRangeOf(target.getTag())) &&
5114+
!((source.hasTag(BYTE) && target.hasTag(CHAR)) ||
5115+
(source.hasTag(INT) && target.hasTag(FLOAT)) ||
5116+
(source.hasTag(LONG) && (target.hasTag(DOUBLE) || target.hasTag(FLOAT)))))) return true;
5117+
else {
5118+
return false;
5119+
}
5120+
} else {
5121+
return isSubtype(boxedTypeOrType(erasure(source)), target);
5122+
}
51105123
}
51115124

5112-
/** Check unconditionality between primitive types.
5125+
/** Check value-based unconditional exactness between any combination of
5126+
* reference or primitive types for the value of a constant expression
5127+
* according to JLS 5.7.2.
5128+
*
5129+
* The following can be unconditionally exact if the source primitive is a
5130+
* constant expression and the conversions is exact for that constant
5131+
* expression:
51135132
*
5114-
* - widening from one integral type to another,
5115-
* - widening from one floating point type to another,
5116-
* - widening from byte, short, or char to a floating point type,
5117-
* - widening from int to double.
5133+
* - a narrowing primitive conversion
5134+
* - a widening and narrowing primitive conversion
5135+
* - a widening primitive conversion that is not exact
5136+
*
5137+
* @param source Source primitive or reference type, should be a numeric value
5138+
* @param target Target primitive or reference type
5139+
*/
5140+
public boolean isUnconditionallyExactValueBased(Type source, Type target) {
5141+
if (!(source.constValue() instanceof Number value) || !target.getTag().isNumeric()) return false;
5142+
5143+
switch (source.getTag()) {
5144+
case BYTE:
5145+
switch (target.getTag()) {
5146+
case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue());
5147+
}
5148+
break;
5149+
case CHAR:
5150+
switch (target.getTag()) {
5151+
case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue());
5152+
case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue());
5153+
}
5154+
break;
5155+
case SHORT:
5156+
switch (target.getTag()) {
5157+
case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue());
5158+
case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue());
5159+
}
5160+
break;
5161+
case INT:
5162+
switch (target.getTag()) {
5163+
case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue());
5164+
case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue());
5165+
case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue());
5166+
case FLOAT: return ExactConversionsSupport.isIntToFloatExact(value.intValue());
5167+
}
5168+
break;
5169+
case FLOAT:
5170+
switch (target.getTag()) {
5171+
case BYTE: return ExactConversionsSupport.isFloatToByteExact(value.floatValue());
5172+
case CHAR: return ExactConversionsSupport.isFloatToCharExact(value.floatValue());
5173+
case SHORT: return ExactConversionsSupport.isFloatToShortExact(value.floatValue());
5174+
case INT: return ExactConversionsSupport.isFloatToIntExact(value.floatValue());
5175+
case LONG: return ExactConversionsSupport.isFloatToLongExact(value.floatValue());
5176+
}
5177+
break;
5178+
case LONG:
5179+
switch (target.getTag()) {
5180+
case BYTE: return ExactConversionsSupport.isLongToByteExact(value.longValue());
5181+
case CHAR: return ExactConversionsSupport.isLongToCharExact(value.longValue());
5182+
case SHORT: return ExactConversionsSupport.isLongToShortExact(value.longValue());
5183+
case INT: return ExactConversionsSupport.isLongToIntExact(value.longValue());
5184+
case FLOAT: return ExactConversionsSupport.isLongToFloatExact(value.longValue());
5185+
case DOUBLE: return ExactConversionsSupport.isLongToDoubleExact(value.longValue());
5186+
}
5187+
break;
5188+
case DOUBLE:
5189+
switch (target.getTag()) {
5190+
case BYTE: return ExactConversionsSupport.isDoubleToByteExact(value.doubleValue());
5191+
case CHAR: return ExactConversionsSupport.isDoubleToCharExact(value.doubleValue());
5192+
case SHORT: return ExactConversionsSupport.isDoubleToShortExact(value.doubleValue());
5193+
case INT: return ExactConversionsSupport.isDoubleToIntExact(value.doubleValue());
5194+
case FLOAT: return ExactConversionsSupport.isDoubleToFloatExact(value.doubleValue());
5195+
case LONG: return ExactConversionsSupport.isDoubleToLongExact(value.doubleValue());
5196+
}
5197+
break;
5198+
}
5199+
return true;
5200+
}
5201+
5202+
/** Check both type or value-based unconditional exactness between any
5203+
* combination of reference or primitive types for the value of a constant
5204+
* expression according to JLS 5.7.2.
51185205
*
5119-
* @param selectorType Type of selector
5120-
* @param targetType Target type
5206+
* @param source Source primitive or reference type, should be a numeric value
5207+
* @param target Target primitive or reference type
51215208
*/
5122-
public boolean isUnconditionallyExactPrimitives(Type selectorType, Type targetType) {
5123-
return isSameType(selectorType, targetType) ||
5124-
(selectorType.isPrimitive() && targetType.isPrimitive()) &&
5125-
((selectorType.getTag().isStrictSubRangeOf(targetType.getTag())) &&
5126-
!((selectorType.hasTag(BYTE) && targetType.hasTag(CHAR)) ||
5127-
(selectorType.hasTag(INT) && targetType.hasTag(FLOAT)) ||
5128-
(selectorType.hasTag(LONG) && (targetType.hasTag(DOUBLE) || targetType.hasTag(FLOAT)))));
5209+
public boolean isUnconditionallyExactCombined(Type currentType, Type testType) {
5210+
return isUnconditionallyExactTypeBased(currentType, testType) ||
5211+
(currentType.constValue() instanceof Number && isUnconditionallyExactValueBased(currentType, testType));
51295212
}
51305213
// </editor-fold>
51315214

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1861,7 +1861,7 @@ private void handleSwitch(JCTree switchTree,
18611861
boolean unconditional =
18621862
unguarded &&
18631863
!patternType.isErroneous() &&
1864-
types.isUnconditionallyExact(seltype, patternType);
1864+
types.isUnconditionallyExactTypeBased(seltype, patternType);
18651865
if (unconditional) {
18661866
if (hasUnconditionalPattern) {
18671867
log.error(pat.pos(), Errors.DuplicateUnconditionalPattern);

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.util.*;
2929
import java.util.function.BiConsumer;
3030
import java.util.function.BiPredicate;
31-
import java.util.function.Function;
3231
import java.util.function.Predicate;
3332
import java.util.function.Supplier;
3433
import java.util.function.ToIntBiFunction;
@@ -167,6 +166,7 @@ protected Check(Context context) {
167166
allowModules = Feature.MODULES.allowedInSource(source);
168167
allowRecords = Feature.RECORDS.allowedInSource(source);
169168
allowSealed = Feature.SEALED_CLASSES.allowedInSource(source);
169+
allowPrimitivePatterns = preview.isEnabled() && Feature.PRIMITIVE_PATTERNS.allowedInSource(source);
170170
}
171171

172172
/** Character for synthetic names
@@ -190,6 +190,10 @@ protected Check(Context context) {
190190
*/
191191
private final boolean allowSealed;
192192

193+
/** Are primitive patterns allowed
194+
*/
195+
private final boolean allowPrimitivePatterns;
196+
193197
/** Whether to force suppression of deprecation and preview warnings.
194198
* This happens when attributing import statements for JDK 9+.
195199
* @see Feature#DEPRECATION_ON_IMPORT
@@ -4764,21 +4768,26 @@ void checkSwitchCaseLabelDominated(JCCaseLabel unconditionalCaseLabel, List<JCCa
47644768
JCCase testCase = caseAndLabel.fst;
47654769
JCCaseLabel testCaseLabel = caseAndLabel.snd;
47664770
Type testType = labelType(testCaseLabel);
4771+
4772+
// an unconditional pattern cannot be followed by any other label
4773+
if (allowPrimitivePatterns && unconditionalCaseLabel == testCaseLabel && unconditionalCaseLabel != label) {
4774+
log.error(label.pos(), Errors.PatternDominated);
4775+
continue;
4776+
}
4777+
47674778
boolean dominated = false;
4768-
if (types.isUnconditionallyExact(currentType, testType) &&
4769-
!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) {
4770-
//the current label is potentially dominated by the existing (test) label, check:
4771-
if (label instanceof JCConstantCaseLabel) {
4772-
dominated |= !(testCaseLabel instanceof JCConstantCaseLabel) &&
4779+
if (!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) {
4780+
// the current label is potentially dominated by the existing (test) label, check:
4781+
if (types.isUnconditionallyExactCombined(currentType, testType) &&
4782+
label instanceof JCConstantCaseLabel) {
4783+
dominated = !(testCaseLabel instanceof JCConstantCaseLabel) &&
47734784
TreeInfo.unguardedCase(testCase);
47744785
} else if (label instanceof JCPatternCaseLabel patternCL &&
47754786
testCaseLabel instanceof JCPatternCaseLabel testPatternCaseLabel &&
47764787
(testCase.equals(c) || TreeInfo.unguardedCase(testCase))) {
4777-
dominated = patternDominated(testPatternCaseLabel.pat,
4778-
patternCL.pat);
4788+
dominated = patternDominated(testPatternCaseLabel.pat, patternCL.pat);
47794789
}
47804790
}
4781-
47824791
if (dominated) {
47834792
log.error(label.pos(), Errors.PatternDominated);
47844793
}
@@ -4798,7 +4807,7 @@ private Type labelType(JCCaseLabel label) {
47984807
private boolean patternDominated(JCPattern existingPattern, JCPattern currentPattern) {
47994808
Type existingPatternType = types.erasure(existingPattern.type);
48004809
Type currentPatternType = types.erasure(currentPattern.type);
4801-
if (!types.isUnconditionallyExact(currentPatternType, existingPatternType)) {
4810+
if (!types.isUnconditionallyExactTypeBased(currentPatternType, existingPatternType)) {
48024811
return false;
48034812
}
48044813
if (currentPattern instanceof JCBindingPattern ||

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,8 +568,8 @@ private boolean isBpCovered(Type componentType, PatternDescription newNested) {
568568
Type pattype = types.erasure(bp.type);
569569

570570
return seltype.isPrimitive() ?
571-
types.isUnconditionallyExact(seltype, pattype) :
572-
(bp.type.isPrimitive() && types.isUnconditionallyExact(types.unboxedType(seltype), bp.type)) || types.isSubtype(seltype, pattype);
571+
types.isUnconditionallyExactTypeBased(seltype, pattype) :
572+
(bp.type.isPrimitive() && types.isUnconditionallyExactTypeBased(types.unboxedType(seltype), bp.type)) || types.isSubtype(seltype, pattype);
573573
}
574574
return false;
575575
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant;
3737
import com.sun.tools.javac.main.Option.PkgInfo;
3838
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
39-
import com.sun.tools.javac.resources.CompilerProperties.Notes;
4039
import com.sun.tools.javac.tree.*;
4140
import com.sun.tools.javac.util.*;
4241
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
@@ -2835,7 +2834,7 @@ public void visitTypeTest(JCInstanceOf tree) {
28352834
JCExpression exactnessCheck;
28362835
JCExpression instanceOfExpr = translate(tree.expr);
28372836

2838-
if (types.isUnconditionallyExact(tree.expr.type, tree.pattern.type)) {
2837+
if (types.isUnconditionallyExactTypeBased(tree.expr.type, tree.pattern.type)) {
28392838
// instanceOfExpr; true
28402839
prefixStatement = make.Exec(instanceOfExpr);
28412840
exactnessCheck = make.Literal(BOOLEAN, 1).setType(syms.booleanType.constType(1));
@@ -2844,7 +2843,7 @@ public void visitTypeTest(JCInstanceOf tree) {
28442843
prefixStatement = null;
28452844
exactnessCheck = getExactnessCheck(tree, instanceOfExpr);
28462845
} else if (tree.expr.type.isReference()) {
2847-
if (types.isUnconditionallyExact(types.unboxedType(tree.expr.type), tree.pattern.type)) {
2846+
if (types.isUnconditionallyExactTypeBased(types.unboxedType(tree.expr.type), tree.pattern.type)) {
28482847
// instanceOfExpr != null
28492848
prefixStatement = null;
28502849
exactnessCheck = makeBinary(NE, instanceOfExpr, makeNull());

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ public void resolve(VarSymbol commonBinding,
921921
JCBindingPattern binding = (JCBindingPattern) instanceofCheck.pattern;
922922
hasUnconditional =
923923
(!types.erasure(binding.type).isPrimitive() ? instanceofCheck.allowNull :
924-
types.isUnconditionallyExact(commonNestedExpression.type, types.erasure(binding.type))) &&
924+
types.isUnconditionallyExactTypeBased(commonNestedExpression.type, types.erasure(binding.type))) &&
925925
accList.tail.isEmpty();
926926
List<JCCaseLabel> newLabel;
927927

test/langtools/tools/javac/patterns/Domination.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626
* @bug 8262891 8290709
2727
* @summary Check the pattern domination error are reported correctly.
2828
* @compile/fail/ref=Domination.out -XDrawDiagnostics Domination.java
29+
* @compile/fail/ref=DominationWithPP.out --enable-preview --source ${jdk.version} -XDrawDiagnostics Domination.java
2930
*/
30-
3131
public class Domination {
3232
int testDominatesError1(Object o) {
3333
switch (o) {
@@ -218,4 +218,14 @@ int testNotDominates2(Integer x) {
218218
case null : return -1;
219219
}
220220
}
221+
222+
int testCasePatternDominatedbyPreceedingUnconditionalCasePattern () {
223+
interface A {}
224+
interface B {}
225+
A aa = new A() {};
226+
switch (aa) {
227+
case A a : return 1;
228+
case B b : return -1;
229+
}
230+
}
221231
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Domination.java:35:18: compiler.err.pattern.dominated
2+
Domination.java:43:18: compiler.err.pattern.dominated
3+
Domination.java:51:18: compiler.err.pattern.dominated
4+
Domination.java:67:18: compiler.err.pattern.dominated
5+
Domination.java:88:18: compiler.err.pattern.dominated
6+
Domination.java:113:18: compiler.err.pattern.dominated
7+
Domination.java:144:18: compiler.err.pattern.dominated
8+
Domination.java:153:18: compiler.err.pattern.dominated
9+
Domination.java:184:18: compiler.err.pattern.dominated
10+
Domination.java:193:18: compiler.err.pattern.dominated
11+
Domination.java:202:18: compiler.err.pattern.dominated
12+
Domination.java:211:18: compiler.err.pattern.dominated
13+
Domination.java:228:18: compiler.err.pattern.dominated
14+
13 errors

0 commit comments

Comments
 (0)