diff --git a/checker/tests/nullness/TypeVarPrimitivesNullness.java b/checker/tests/nullness/TypeVarPrimitivesNullness.java index b7c10ecacae6..bba4d842f6ec 100644 --- a/checker/tests/nullness/TypeVarPrimitivesNullness.java +++ b/checker/tests/nullness/TypeVarPrimitivesNullness.java @@ -30,4 +30,38 @@ public class TypeVarPrimitivesNullness { // :: error: [unboxing.of.nullable] long l = tLong; } + + void add(T t) { + // :: error: [unboxing.of.nullable] + int result = t + 1; + // Make sure CFG construction was sufficient to capture that t must be non-null at this point, + // since the unboxing must have succeeded + t.toString(); + } + + void assign(T t) { + // :: error: [unboxing.of.nullable] + int result = t; + t.toString(); + } + + void takeLong(long l) {} + + void invoke(T t) { + // :: error: [unboxing.of.nullable] + takeLong(t); + t.toString(); + } + + void equality(T t) { + // :: error: [unboxing.of.nullable] + boolean result = t == 1; + t.toString(); + } + + void bitwise(T t) { + // :: error: [unboxing.of.nullable] + long result = t & 1L; + t.toString(); + } } diff --git a/checker/tests/nullness/Unboxing.java b/checker/tests/nullness/Unboxing.java index baa143724613..0deb75743295 100644 --- a/checker/tests/nullness/Unboxing.java +++ b/checker/tests/nullness/Unboxing.java @@ -37,7 +37,7 @@ int bar(@Nullable Integer in) { int barT(T in) { // :: error: [unboxing.of.nullable] int q = in; - // :: error: [unboxing.of.nullable] + // no error, since in has been unboxed return in; } } diff --git a/checker/tests/nullness/java21/TypeVarSwitchSelector.java b/checker/tests/nullness/java21/TypeVarSwitchSelector.java new file mode 100644 index 000000000000..470f97801ba9 --- /dev/null +++ b/checker/tests/nullness/java21/TypeVarSwitchSelector.java @@ -0,0 +1,21 @@ +import org.checkerframework.checker.nullness.qual.Nullable; + +// @below-java21-jdk-skip-test + +// None of the WPI formats supports the new Java 21 languages features, so skip inference until they +// do. +// @infer-jaifs-skip-test +// @infer-ajava-skip-test +// @infer-stubs-skip-test +public class TypeVarSwitchSelector { + + // A switch over a selector whose type is a type variable is a pattern switch, so the selector is + // not unboxed and the `case null` branch is reachable. + String patternSwitch(T t) { + return switch (t) { + // :: error: [dereference.of.nullable] + case null -> t.toString(); + case Integer i -> i.toString(); + }; + } +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index 4867358b5fbe..b5b53e7149ba 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -881,14 +881,15 @@ protected Node box(Node node) { } /** - * If the input node is a boxed type, unbox it, otherwise leave it alone. + * If the input node is a boxed type or a type variable bounded by a boxed type, unbox it; + * otherwise leave it alone. * * @param node in input node * @return a Node representing the unboxed version of the input, which may simply be the input * node */ protected Node unbox(Node node) { - if (TypesUtils.isBoxedPrimitive(node.getType())) { + if (TypesUtils.isUnboxable(node.getType(), types)) { MemberSelectTree primValueSelect = treeBuilder.buildPrimValueMethodAccess(node.getTree()); handleArtificialTree(primValueSelect); @@ -914,12 +915,21 @@ protected Node unbox(Node node) { } } + /** + * Returns information about the tree's type for deciding which operand conversions to apply. + * Records whether unboxing conversion applies and whether the type is numeric or boolean after + * unboxing. The recorded type is the unboxed type if unboxing applies, or the original type + * otherwise. + * + * @param tree the tree whose type to inspect + * @return information about the tree's type and its unboxing conversion + */ private TreeInfo getTreeInfo(Tree tree) { TypeMirror type = TreeUtils.typeOf(tree); - boolean boxed = TypesUtils.isBoxedPrimitive(type); - TypeMirror unboxedType = boxed ? types.unboxedType(type) : type; + boolean unboxable = TypesUtils.isUnboxable(type, types); + TypeMirror unboxedType = TypesUtils.unboxedTypeOrSelf(type, types); - boolean bool = TypesUtils.isBooleanType(type); + boolean bool = TypesUtils.isBooleanType(unboxedType); boolean numeric = TypesUtils.isNumeric(unboxedType); return new TreeInfo() { @@ -929,8 +939,8 @@ public boolean isNumeric() { } @Override - public boolean isBoxed() { - return boxed; + public boolean isUnboxable() { + return unboxable; } @Override @@ -1000,42 +1010,28 @@ protected Node unaryNumericPromotion(Node node) { } /** - * Returns true if the argument type is a numeric primitive or a boxed numeric primitive and false - * otherwise. + * Returns true if the argument type is a numeric primitive, or if unboxing conversion converts it + * to a numeric primitive. + * + * @param type a type + * @return true if the argument type is a numeric primitive or can be unboxed to one */ protected boolean isNumericOrBoxed(TypeMirror type) { - if (TypesUtils.isBoxedPrimitive(type)) { - type = types.unboxedType(type); - } - return TypesUtils.isNumeric(type); + return TypesUtils.isNumeric(TypesUtils.unboxedTypeOrSelf(type, types)); } /** * Compute the type to which two numeric types must be promoted before performing a binary numeric - * operation on them. The input types must both be numeric and the output type is primitive. + * operation on them. The input types must both be convertible to numeric primitive types, and the + * output type is primitive. * * @param left the type of the left operand * @param right the type of the right operand * @return a TypeMirror representing the binary numeric promoted type */ protected TypeMirror binaryPromotedType(TypeMirror left, TypeMirror right) { - if (!left.getKind().isPrimitive()) { - if (TypesUtils.isCapturedTypeVariable(left)) { - // This doesn't seem legal according to the JLS, but javac accepts it. - left = types.unboxedType(TypesUtils.upperBound(left)); - } else { - left = types.unboxedType(left); - } - } - - if (!right.getKind().isPrimitive()) { - if (TypesUtils.isCapturedTypeVariable(right)) { - // This doesn't seem legal according to the JLS, but javac accepts it. - right = types.unboxedType(TypesUtils.upperBound(right)); - } else { - right = types.unboxedType(right); - } - } + left = TypesUtils.unboxedTypeOrSelf(left, types); + right = TypesUtils.unboxedTypeOrSelf(right, types); TypeKind promotedTypeKind = TypeKindUtils.widenedNumericType(left, right); return types.getPrimitiveType(promotedTypeKind); @@ -1113,8 +1109,9 @@ protected Node narrow(Node node, TypeMirror destType) { * @return a Node with the value narrowed and boxed to the destType, which may be the input node */ protected Node narrowAndBox(Node node, TypeMirror destType) { - if (TypesUtils.isBoxedPrimitive(destType)) { - return box(narrow(node, types.unboxedType(destType))); + TypeMirror boxedDestType = TypesUtils.boxedPrimitiveForUnboxing(destType, types); + if (boxedDestType != null) { + return box(narrow(node, types.unboxedType(boxedDestType))); } else { return narrow(node, destType); } @@ -1130,13 +1127,7 @@ protected Node narrowAndBox(Node node, TypeMirror destType) { protected boolean conversionRequiresNarrowing(TypeMirror varType, Node node) { // Narrowing is restricted to cases where the left-hand side is byte, char, short or Byte, // Char, Short and the right-hand side is a constant. - TypeMirror unboxedVarType = - TypesUtils.isBoxedPrimitive(varType) ? types.unboxedType(varType) : varType; - TypeKind unboxedVarKind = unboxedVarType.getKind(); - boolean isLeftNarrowableTo = - unboxedVarKind == TypeKind.BYTE - || unboxedVarKind == TypeKind.SHORT - || unboxedVarKind == TypeKind.CHAR; + boolean isLeftNarrowableTo = TypesUtils.canBeNarrowingPrimitiveConversion(varType, types); boolean isRightConstant = node instanceof ValueLiteralNode; return isLeftNarrowableTo && isRightConstant; } @@ -1165,7 +1156,7 @@ protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllow boolean isRightNumeric = TypesUtils.isNumeric(nodeType); boolean isRightPrimitive = TypesUtils.isPrimitive(nodeType); - boolean isRightBoxed = TypesUtils.isBoxedPrimitive(nodeType); + boolean isRightUnboxable = TypesUtils.isUnboxable(nodeType, types); boolean isRightReference = nodeType instanceof ReferenceType; boolean isLeftNumeric = TypesUtils.isNumeric(varType); boolean isLeftPrimitive = TypesUtils.isPrimitive(varType); @@ -1184,7 +1175,7 @@ protected Node commonConvert(Node node, TypeMirror varType, boolean contextAllow } else { node = box(node); } - } else if (isRightBoxed && isLeftPrimitive) { + } else if (isRightUnboxable && isLeftPrimitive) { node = unbox(node); nodeType = node.getType(); @@ -1401,13 +1392,12 @@ protected Node conditionalExprPromotion(Node node, TypeMirror destType) { // If the operand is byte or Byte and the whole expression is // short, then convert to short. - boolean isBoxedPrimitive = TypesUtils.isBoxedPrimitive(nodeType); - TypeMirror unboxedNodeType = isBoxedPrimitive ? types.unboxedType(nodeType) : nodeType; - TypeMirror unboxedDestType = - TypesUtils.isBoxedPrimitive(destType) ? types.unboxedType(destType) : destType; + boolean isNodeUnboxable = TypesUtils.isUnboxable(nodeType, types); + TypeMirror unboxedNodeType = TypesUtils.unboxedTypeOrSelf(nodeType, types); + TypeMirror unboxedDestType = TypesUtils.unboxedTypeOrSelf(destType, types); if (TypesUtils.isNumeric(unboxedNodeType) && TypesUtils.isNumeric(unboxedDestType)) { if (unboxedNodeType.getKind() == TypeKind.BYTE && destType.getKind() == TypeKind.SHORT) { - if (isBoxedPrimitive) { + if (isNodeUnboxable) { node = unbox(node); } return widen(node, destType); @@ -1417,7 +1407,7 @@ protected Node conditionalExprPromotion(Node node, TypeMirror destType) { // is the unboxed version of it, then apply unboxing. TypeKind destKind = destType.getKind(); if (destKind == TypeKind.BYTE || destKind == TypeKind.CHAR || destKind == TypeKind.SHORT) { - if (isBoxedPrimitive) { + if (isNodeUnboxable) { return unbox(node); } else if (nodeType.getKind() == TypeKind.INT) { return narrow(node, destType); @@ -2224,7 +2214,12 @@ private void buildSelector() { selectorVarUseNode.setInSource(false); extendWithNode(selectorVarUseNode); - Node selectorExprNode = unbox(scan(selectorExprTree, null)); + Node selectorExprNode = scan(selectorExprTree, null); + // A switch whose selector has a type-variable type, such as `T extends Integer`, cannot have + // constant labels, so it is a pattern switch and its selector is not unboxed. + if (TypesUtils.isBoxedPrimitive(selectorExprType)) { + selectorExprNode = unbox(selectorExprNode); + } AssignmentTree assign = treeBuilder.buildAssignment(selectorVarUseTree, selectorExprTree); handleArtificialTree(assign); @@ -3693,7 +3688,8 @@ public Node visitCompoundAssignment(CompoundAssignmentTree tree, Void p) { TypeMirror promotedType = binaryPromotedType(leftType, rightType); targetRHS = binaryNumericPromotion(targetLHS, promotedType); value = binaryNumericPromotion(value, promotedType); - } else if (TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType)) { + } else if (TypesUtils.isBooleanType(TypesUtils.unboxedTypeOrSelf(leftType, types)) + && TypesUtils.isBooleanType(TypesUtils.unboxedTypeOrSelf(rightType, types))) { targetRHS = unbox(targetLHS); value = unbox(value); } else { @@ -3977,17 +3973,8 @@ public Node visitBinary(BinaryTree tree, Void p) { case GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL -> { // see JLS 15.20.1 - TypeMirror leftType = TreeUtils.typeOf(leftTree); - if (TypesUtils.isBoxedPrimitive(leftType)) { - leftType = types.unboxedType(leftType); - } - - TypeMirror rightType = TreeUtils.typeOf(rightTree); - if (TypesUtils.isBoxedPrimitive(rightType)) { - rightType = types.unboxedType(rightType); - } - - TypeMirror promotedType = binaryPromotedType(leftType, rightType); + TypeMirror promotedType = + binaryPromotedType(TreeUtils.typeOf(leftTree), TreeUtils.typeOf(rightTree)); Node left = binaryNumericPromotion(scan(leftTree, p), promotedType); Node right = binaryNumericPromotion(scan(rightTree, p), promotedType); @@ -4012,7 +3999,7 @@ public Node visitBinary(BinaryTree tree, Void p) { if (leftInfo.isNumeric() && rightInfo.isNumeric() - && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + && !(leftInfo.isUnboxable() && rightInfo.isUnboxable())) { // JLS 15.21.1 numerical equality TypeMirror promotedType = binaryPromotedType(leftInfo.unboxedType(), rightInfo.unboxedType()); @@ -4020,10 +4007,10 @@ public Node visitBinary(BinaryTree tree, Void p) { right = binaryNumericPromotion(right, promotedType); } else if (leftInfo.isBoolean() && rightInfo.isBoolean() - && !(leftInfo.isBoxed() && rightInfo.isBoxed())) { + && !(leftInfo.isUnboxable() && rightInfo.isUnboxable())) { // JLS 15.21.2 boolean equality - left = unboxAsNeeded(left, leftInfo.isBoxed()); - right = unboxAsNeeded(right, rightInfo.isBoxed()); + left = unboxAsNeeded(left, leftInfo.isUnboxable()); + right = unboxAsNeeded(right, rightInfo.isUnboxable()); } if (kind == Tree.Kind.EQUAL_TO) { @@ -4039,7 +4026,8 @@ public Node visitBinary(BinaryTree tree, Void p) { TypeMirror leftType = TreeUtils.typeOf(leftTree); TypeMirror rightType = TreeUtils.typeOf(rightTree); boolean isBooleanOp = - TypesUtils.isBooleanType(leftType) && TypesUtils.isBooleanType(rightType); + TypesUtils.isBooleanType(TypesUtils.unboxedTypeOrSelf(leftType, types)) + && TypesUtils.isBooleanType(TypesUtils.unboxedTypeOrSelf(rightType, types)); Node left; Node right; diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java index 32953cdb80fc..6146156c4632 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/TreeInfo.java @@ -5,11 +5,12 @@ /** A tuple with 4 named elements. */ /*package-private*/ interface TreeInfo { /** - * Returns true if this is boxed. + * Returns true if unboxing conversion applies to this: it is a boxed primitive, or a type + * variable or intersection type whose erasure is a boxed primitive. * - * @return true if this is boxed + * @return true if unboxing conversion applies to this */ - boolean isBoxed(); + boolean isUnboxable(); /** * Returns true if this is numeric. diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java index d1e8bd63f92a..fbd44647122f 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueCheckerUtils.java @@ -47,6 +47,9 @@ public static List getValuesCastedToType( /** * Returns a list of the values of an annotation, and then cast the values to a given type. * + *

A type variable or intersection type that can be unboxed is converted using its boxed + * primitive bound. + * * @param anno the annotation that contains values * @param castTo the unannotated type that is casted to * @param isUnsigned true if the type being casted to is unsigned @@ -58,6 +61,11 @@ public static List getValuesCastedToType( TypeMirror castTo, boolean isUnsigned, ValueAnnotatedTypeFactory atypeFactory) { + // A type variable bounded by a primitive wrapper has that wrapper's runtime representation. + TypeMirror boxedType = TypesUtils.boxedPrimitiveForUnboxing(castTo, atypeFactory.types); + if (boxedType != null) { + castTo = boxedType; + } Class castType = TypesUtils.getClassFromType(castTo); List values; switch (AnnotationUtils.annotationName(anno)) { diff --git a/framework/tests/all-systems/TypeVarPrimitives.java b/framework/tests/all-systems/TypeVarPrimitives.java index 1ad25d008ba8..a621b2bc8a23 100644 --- a/framework/tests/all-systems/TypeVarPrimitives.java +++ b/framework/tests/all-systems/TypeVarPrimitives.java @@ -9,4 +9,50 @@ void method(T tLong) { void methodIntersection(T tLong) { long l = tLong; } + + // Regression tests for https://github.com/typetools/checker-framework/issues/8328. + int add(T t) { + return t + 1; + } + + long subtract(T t) { + return 1 - t; + } + + int assign(T t) { + int result = t; + return result; + } + + boolean equality(T t) { + return t == 1; + } + + int conditional(boolean b, T t) { + return b ? t : 0; + } + + long bitwise(T t) { + return t & 1L; + } + + boolean lessThan(T t) { + return t < 1; + } + + boolean booleanOps(T b) { + return (b & true) || b == false; + } + + boolean compoundBoolean(T t) { + boolean x = true; + x &= t; + x |= t; + x ^= t; + return x; + } + + long captured(java.util.List l) { + return l.get(0) * 2L; + } } diff --git a/framework/tests/value/TypeVarUnboxing.java b/framework/tests/value/TypeVarUnboxing.java new file mode 100644 index 000000000000..e739b2d13b72 --- /dev/null +++ b/framework/tests/value/TypeVarUnboxing.java @@ -0,0 +1,60 @@ +import org.checkerframework.common.value.qual.BoolVal; +import org.checkerframework.common.value.qual.DoubleVal; +import org.checkerframework.common.value.qual.IntVal; + +/** Regression test for evaluating unboxing of a type variable with a known value. */ +public class TypeVarUnboxing { + void increment(@IntVal(1) T t) { + // :: error: [unary.increment] + t++; + } + + void byteValues(@IntVal(1) T input) { + @IntVal({1, 2, 3}) T t = input; + @IntVal(1) byte before = t++; + @IntVal(2) byte after = t; + @IntVal(2) byte beforeDecrement = t--; + @IntVal(1) byte afterDecrement = t; + // :: error: [assignment] + @IntVal(2) byte wrong = t; + } + + void shortValue(@IntVal(1) T t) { + @IntVal(2) int result = t + 1; + } + + void characterValue(@IntVal(65) T t) { + @IntVal(65) char result = t; + // :: error: [assignment] + @IntVal(66) char wrong = t; + } + + void integerValue(@IntVal(1) T t) { + @IntVal(2) int result = t + 1; + } + + void longValue(@IntVal(1) T t) { + @IntVal(2) long result = t + 1; + } + + void floatValue(@DoubleVal(1.5) T t) { + @DoubleVal(2.5) float result = t + 1; + } + + void doubleValue(@DoubleVal(1.5) T t) { + @DoubleVal(2.5) double result = t + 1; + } + + void booleanValue(@BoolVal(true) T t) { + @BoolVal(true) boolean result = t; + @BoolVal(false) boolean negated = !t; + } + + void chainedBounds(@IntVal(1) U t) { + @IntVal(1) byte result = t; + } + + void intersectionBound(@IntVal(1) T t) { + @IntVal(1) byte result = t; + } +} diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index 403c9aab08a5..653ab10d350e 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -421,6 +421,54 @@ public static boolean isBoxedPrimitive(TypeMirror type) { return isDeclaredOfName(type, fqBoxedTypes); } + /** + * Returns the boxed primitive type that unboxing conversion (JLS 5.1.8) unboxes when applied to + * the given type, or null if unboxing conversion does not apply to the given type. The result is + * the given type itself if it is a boxed primitive, or the erasure of the given type if it is a + * type variable or intersection type whose erasure is a boxed primitive. + * + *

Javac's unboxing API accepts wrapper classes, such as {@code Integer}, but rejects type + * variables. For example, erasing {@code T} when {@code T extends Integer} produces {@code + * Integer}, which the API can unbox to {@code int}. Erasure handles both ordinary and captured + * type variables. + * + * @param type a type + * @param types the type utilities + * @return the boxed primitive type that unboxing conversion unboxes, or null + */ + public static @Nullable TypeMirror boxedPrimitiveForUnboxing(TypeMirror type, Types types) { + TypeKind kind = type.getKind(); + if (kind == TypeKind.TYPEVAR || kind == TypeKind.INTERSECTION) { + type = types.erasure(type); + } + return isBoxedPrimitive(type) ? type : null; + } + + /** + * Returns true if unboxing conversion (JLS 5.1.8) applies to the given type: it is a boxed + * primitive, or a type variable or intersection type whose erasure is a boxed primitive. + * + * @param type a type + * @param types the type utilities + * @return true if unboxing conversion applies to the given type + */ + public static boolean isUnboxable(TypeMirror type, Types types) { + return boxedPrimitiveForUnboxing(type, types) != null; + } + + /** + * If unboxing conversion (JLS 5.1.8) applies to the given type, returns the resulting primitive + * type; otherwise returns the given type. + * + * @param type a type + * @param types the type utilities + * @return the unboxed version of the given type, or the given type itself + */ + public static TypeMirror unboxedTypeOrSelf(TypeMirror type, Types types) { + TypeMirror boxed = boxedPrimitiveForUnboxing(type, types); + return boxed == null ? type : types.unboxedType(boxed); + } + /** * Returns true if this is an immutable type in the JDK. * @@ -1695,8 +1743,7 @@ public static TypeMirror createWildcard( */ public static boolean canBeNarrowingPrimitiveConversion(TypeMirror type, Types types) { // See CFGBuilder.CFGTranslationPhaseOne#conversionRequiresNarrowing() - TypeMirror unboxedType = isBoxedPrimitive(type) ? types.unboxedType(type) : type; - TypeKind unboxedKind = unboxedType.getKind(); + TypeKind unboxedKind = unboxedTypeOrSelf(type, types).getKind(); return unboxedKind == TypeKind.BYTE || unboxedKind == TypeKind.SHORT || unboxedKind == TypeKind.CHAR; diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java index dfd740257389..e5c76bd9070a 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/trees/TreeBuilder.java @@ -493,14 +493,17 @@ public static Symbol.MethodSymbol getValueOfMethod( * Builds an AST Tree to access the *Value() method of a boxed type such as Short or Float, where * * is the corresponding primitive type (i.e. shortValue or floatValue). * - * @param expr an expression whose type is a boxed type + * @param expr an expression whose type is a boxed type or a type variable bounded by a boxed type * @return a MemberSelectTree that accesses the *Value() method of the expression */ public MemberSelectTree buildPrimValueMethodAccess(Tree expr) { - TypeMirror boxedType = TreeUtils.typeOf(expr); + TypeMirror exprType = TreeUtils.typeOf(expr); + TypeMirror boxedType = TypesUtils.boxedPrimitiveForUnboxing(exprType, modelTypes); + if (boxedType == null) { + throw new BugInCF("Cannot unbox %s of type %s", expr, exprType); + } TypeElement boxedElement = (TypeElement) ((DeclaredType) boxedType).asElement(); - assert TypesUtils.isBoxedPrimitive(boxedType); TypeMirror unboxedType = modelTypes.unboxedType(boxedType); // Find the *Value() method of the boxed type