Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ private static String typeSwitchClassName(Class<?> targetClass) {
return name + "$$TypeSwitch";
}

// this method should be in sync with com.sun.tools.javac.code.Types.checkUnconditionallyExactPrimitives
// this method should be in sync with com.sun.tools.javac.code.Types.isUnconditionallyExactPrimitives
private static boolean unconditionalExactnessCheck(Class<?> selectorType, Class<?> targetType) {
Wrapper selectorWrapper = Wrapper.forBasicType(selectorType);
Wrapper targetWrapper = Wrapper.forBasicType(targetType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
package com.sun.tools.javac.code;

import com.sun.source.tree.Tree.Kind;

import java.lang.runtime.ExactConversionsSupport;
import javax.lang.model.type.TypeKind;

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

public boolean isNumeric() {
return this.numericClass != 0;
}

/** Returns the number of type tags.
*/
public static int getTypeTagCount() {
Expand Down Expand Up @@ -247,11 +251,11 @@ public boolean checkRange(int value) {
case BOOLEAN:
return 0 <= value && value <= 1;
case BYTE:
return Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE;
return ExactConversionsSupport.isIntToByteExact(value);
case CHAR:
return Character.MIN_VALUE <= value && value <= Character.MAX_VALUE;
return ExactConversionsSupport.isIntToCharExact(value);
case SHORT:
return Short.MIN_VALUE <= value && value <= Short.MAX_VALUE;
return ExactConversionsSupport.isIntToShortExact(value);
case INT:
return true;
default:
Expand Down
118 changes: 91 additions & 27 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package com.sun.tools.javac.code;

import java.lang.ref.SoftReference;
import java.lang.runtime.ExactConversionsSupport;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Locale;
Expand Down Expand Up @@ -5054,14 +5055,25 @@ public Type visitCapturedType(CapturedType t, S s) {
// </editor-fold>

// <editor-fold defaultstate="collapsed" desc="Unconditionality">
/** Check unconditionality between any combination of reference or primitive types.
/** Check unconditionality between any combination of reference or primitive
* types.
*
* Rules:
* an identity conversion
* a widening reference conversion
* a widening primitive conversion (delegates to `checkUnconditionallyExactPrimitives`)
* a boxing conversion
* a boxing conversion followed by a widening reference conversion
* The following are unconditionally exact regardless of the input
* expression:
*
* - an identity conversion
* - a widening reference conversion
* - an exact widening primitive conversion
* - a boxing conversion
* - a boxing conversion followed by a widening reference conversion
*
* Additionally, the following can be unconditionally exact if the source
* primitive is a constant expression and the conversions is exact for that
* constant expression:
*
* - a narrowing primitive conversion
* - a widening and narrowing primitive conversion
* - a widening primitive conversion that is not exact
*
* @param source Source primitive or reference type
* @param target Target primitive or reference type
Expand All @@ -5071,28 +5083,80 @@ public boolean isUnconditionallyExact(Type source, Type target) {
return true;
}

return target.isPrimitive()
? isUnconditionallyExactPrimitives(source, target)
: isSubtype(boxedTypeOrType(erasure(source)), target);
if (target.isPrimitive()) {
if (source.isPrimitive() &&
((source.getTag().isStrictSubRangeOf(target.getTag())) &&
!((source.hasTag(BYTE) && target.hasTag(CHAR)) ||
(source.hasTag(INT) && target.hasTag(FLOAT)) ||
(source.hasTag(LONG) && (target.hasTag(DOUBLE) || target.hasTag(FLOAT)))))) return true;
else {
return false;
}
} else {
return isSubtype(boxedTypeOrType(erasure(source)), target);
}
}
// where
public boolean isUnconditionallyExactConstantPrimitives(Type source, Type target) {
if (!(source.constValue() instanceof Number value) || !target.getTag().isNumeric()) return false;

/** Check unconditionality between primitive types.
*
* - widening from one integral type to another,
* - widening from one floating point type to another,
* - widening from byte, short, or char to a floating point type,
* - widening from int to double.
*
* @param selectorType Type of selector
* @param targetType Target type
*/
public boolean isUnconditionallyExactPrimitives(Type selectorType, Type targetType) {
return isSameType(selectorType, targetType) ||
(selectorType.isPrimitive() && targetType.isPrimitive()) &&
((selectorType.getTag().isStrictSubRangeOf(targetType.getTag())) &&
!((selectorType.hasTag(BYTE) && targetType.hasTag(CHAR)) ||
(selectorType.hasTag(INT) && targetType.hasTag(FLOAT)) ||
(selectorType.hasTag(LONG) && (targetType.hasTag(DOUBLE) || targetType.hasTag(FLOAT)))));
switch (source.getTag()) {
case BYTE:
switch (target.getTag()) {
case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue());
}
break;
case CHAR:
switch (target.getTag()) {
case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue());
case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue());
}
break;
case SHORT:
switch (target.getTag()) {
case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue());
case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue());
}
break;
case INT:
switch (target.getTag()) {
case BYTE: return ExactConversionsSupport.isIntToByteExact(value.intValue());
case CHAR: return ExactConversionsSupport.isIntToCharExact(value.intValue());
case SHORT: return ExactConversionsSupport.isIntToShortExact(value.intValue());
case FLOAT: return ExactConversionsSupport.isIntToFloatExact(value.intValue());
}
break;
case FLOAT:
switch (target.getTag()) {
case BYTE: return ExactConversionsSupport.isFloatToByteExact(value.floatValue());
case CHAR: return ExactConversionsSupport.isFloatToCharExact(value.floatValue());
case SHORT: return ExactConversionsSupport.isFloatToShortExact(value.floatValue());
case INT: return ExactConversionsSupport.isFloatToIntExact(value.floatValue());
case LONG: return ExactConversionsSupport.isFloatToLongExact(value.floatValue());
}
break;
case LONG:
switch (target.getTag()) {
case BYTE: return ExactConversionsSupport.isLongToByteExact(value.longValue());
case CHAR: return ExactConversionsSupport.isLongToCharExact(value.longValue());
case SHORT: return ExactConversionsSupport.isLongToShortExact(value.longValue());
case INT: return ExactConversionsSupport.isLongToIntExact(value.longValue());
case FLOAT: return ExactConversionsSupport.isLongToFloatExact(value.longValue());
case DOUBLE: return ExactConversionsSupport.isLongToDoubleExact(value.longValue());
}
break;
case DOUBLE:
switch (target.getTag()) {
case BYTE: return ExactConversionsSupport.isDoubleToByteExact(value.doubleValue());
case CHAR: return ExactConversionsSupport.isDoubleToCharExact(value.doubleValue());
case SHORT: return ExactConversionsSupport.isDoubleToShortExact(value.doubleValue());
case INT: return ExactConversionsSupport.isDoubleToIntExact(value.doubleValue());
case FLOAT: return ExactConversionsSupport.isDoubleToFloatExact(value.doubleValue());
case LONG: return ExactConversionsSupport.isDoubleToLongExact(value.doubleValue());
}
break;
}
return true;
}
// </editor-fold>

Expand Down
27 changes: 21 additions & 6 deletions src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ protected Check(Context context) {
allowModules = Feature.MODULES.allowedInSource(source);
allowRecords = Feature.RECORDS.allowedInSource(source);
allowSealed = Feature.SEALED_CLASSES.allowedInSource(source);
allowPrimitivePatterns = preview.isEnabled() && Feature.PRIMITIVE_PATTERNS.allowedInSource(source);
}

/** Character for synthetic names
Expand All @@ -190,6 +191,10 @@ protected Check(Context context) {
*/
private final boolean allowSealed;

/** Are primitive patterns allowed
*/
private final boolean allowPrimitivePatterns;

/** Whether to force suppression of deprecation and preview warnings.
* This happens when attributing import statements for JDK 9+.
* @see Feature#DEPRECATION_ON_IMPORT
Expand Down Expand Up @@ -4734,20 +4739,30 @@ void checkSwitchCaseLabelDominated(JCCaseLabel unconditionalCaseLabel, List<JCCa
JCCaseLabel testCaseLabel = caseAndLabel.snd;
Type testType = labelType(testCaseLabel);
boolean dominated = false;
if (types.isUnconditionallyExact(currentType, testType) &&
!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) {

if (unconditionalCaseLabel == testCaseLabel) unconditionalFound = true;
boolean dominatedCandidate = false;
if (!currentType.hasTag(ERROR) && !testType.hasTag(ERROR)) {
if (types.isUnconditionallyExact(currentType, testType)) {
dominatedCandidate = true;
} else if (currentType.constValue() instanceof Number) {
dominatedCandidate = types.isUnconditionallyExactConstantPrimitives(currentType, testType);
}
//the current label is potentially dominated by the existing (test) label, check:
if (label instanceof JCConstantCaseLabel) {
dominated |= !(testCaseLabel instanceof JCConstantCaseLabel) &&
dominated = dominatedCandidate &&
!(testCaseLabel instanceof JCConstantCaseLabel) &&
TreeInfo.unguardedCase(testCase);
} else if (label instanceof JCPatternCaseLabel patternCL &&
testCaseLabel instanceof JCPatternCaseLabel testPatternCaseLabel &&
(testCase.equals(c) || TreeInfo.unguardedCase(testCase))) {
dominated = patternDominated(testPatternCaseLabel.pat,
patternCL.pat);
dominated = dominatedCandidate &&
patternDominated(testPatternCaseLabel.pat, patternCL.pat);
}
}

if (allowPrimitivePatterns && unconditionalFound && unconditionalCaseLabel != label) {
dominated = true;
}
if (dominated) {
log.error(label.pos(), Errors.PatternDominated);
}
Expand Down
12 changes: 11 additions & 1 deletion test/langtools/tools/javac/patterns/Domination.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
* @bug 8262891 8290709
* @summary Check the pattern domination error are reported correctly.
* @compile/fail/ref=Domination.out -XDrawDiagnostics Domination.java
* @compile/fail/ref=DominationWithPP.out --enable-preview --source ${jdk.version} -XDrawDiagnostics Domination.java
*/

public class Domination {
int testDominatesError1(Object o) {
switch (o) {
Expand Down Expand Up @@ -218,4 +218,14 @@ int testNotDominates2(Integer x) {
case null : return -1;
}
}

int testCasePatternDominatedbyPreceedingUnconditionalCasePattern () {
interface A {}
interface B {}
A aa = new A() {};
switch (aa) {
case A a : return 1;
case B b : return -1;
}
}
}
14 changes: 14 additions & 0 deletions test/langtools/tools/javac/patterns/DominationWithPP.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Domination.java:35:18: compiler.err.pattern.dominated
Domination.java:43:18: compiler.err.pattern.dominated
Domination.java:51:18: compiler.err.pattern.dominated
Domination.java:67:18: compiler.err.pattern.dominated
Domination.java:88:18: compiler.err.pattern.dominated
Domination.java:113:18: compiler.err.pattern.dominated
Domination.java:144:18: compiler.err.pattern.dominated
Domination.java:153:18: compiler.err.pattern.dominated
Domination.java:184:18: compiler.err.pattern.dominated
Domination.java:193:18: compiler.err.pattern.dominated
Domination.java:202:18: compiler.err.pattern.dominated
Domination.java:211:18: compiler.err.pattern.dominated
Domination.java:228:18: compiler.err.pattern.dominated
13 errors
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* @test /nodynamiccopyright/
* @summary Retain exhaustiveness properties of switches with a constant selector
* @enablePreview
* @compile/fail/ref=PrimitivePatternsSwitchConstants.out -XDrawDiagnostics -XDshould-stop.at=FLOW PrimitivePatternsSwitchConstants.java
*/
public class PrimitivePatternsSwitchConstants {
void testConstExpressions() {
switch (42) { // error: not exhaustive
case byte _ :
}

switch (42l) { // error: not exhaustive
case byte _ :
}

switch (123456) { // error: not exhaustive
case byte _ :
}

switch (16_777_216) { // error: not exhaustive
case float _ :
}

switch (16_777_217) { // error: not exhaustive
case float _ :
}

switch (42d) { // error: not exhaustive
case float _ :
}

switch (1) { // OK
case long _ :
}

final int i = 42;
switch (i) { // OK
case long _ :
}

switch (1) { // error: non-exhaustive
case Long _ : // error: widening primitive conversion and boxing is not supported
}

switch (42) {
case byte bb -> {}
case int ii -> {} // OK
};

switch (42) {
case 42 -> {}
case int ii -> {} // OK
};

switch (42) {
case (byte) 42 -> {}
case int ii -> {} // OK
};

switch (42) {
case 42 -> {}
default -> {} // OK
};

switch (42) {
default -> {} // OK
case 42 -> {}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
PrimitivePatternsSwitchConstants.java:43:18: compiler.err.prob.found.req: (compiler.misc.inconvertible.types: int, java.lang.Long)
PrimitivePatternsSwitchConstants.java:9:9: compiler.err.not.exhaustive.statement
PrimitivePatternsSwitchConstants.java:13:9: compiler.err.not.exhaustive.statement
PrimitivePatternsSwitchConstants.java:17:9: compiler.err.not.exhaustive.statement
PrimitivePatternsSwitchConstants.java:21:9: compiler.err.not.exhaustive.statement
PrimitivePatternsSwitchConstants.java:25:9: compiler.err.not.exhaustive.statement
PrimitivePatternsSwitchConstants.java:29:9: compiler.err.not.exhaustive.statement
PrimitivePatternsSwitchConstants.java:42:9: compiler.err.not.exhaustive.statement
- compiler.note.preview.filename: PrimitivePatternsSwitchConstants.java, DEFAULT
- compiler.note.preview.recompile
8 errors
Loading