diff --git a/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java b/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java index bb1395a6bf..753f49431b 100644 --- a/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java +++ b/nullaway/src/main/java/com/uber/nullaway/ErrorMessage.java @@ -53,7 +53,6 @@ public enum MessageTypes { WRONG_OVERRIDE_POSTCONDITION, WRONG_OVERRIDE_PRECONDITION, TYPE_PARAMETER_CANNOT_BE_NULLABLE, - ASSIGN_GENERIC_NULLABLE, } public String getMessage() { diff --git a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java index 4fdfa4b9d1..f7b0f3ac6e 100644 --- a/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java +++ b/nullaway/src/main/java/com/uber/nullaway/GenericsChecks.java @@ -1,25 +1,13 @@ package com.uber.nullaway; -import static com.uber.nullaway.NullabilityUtil.castToNonNull; - -import com.google.common.base.Preconditions; import com.google.errorprone.VisitorState; -import com.google.errorprone.suppliers.Supplier; -import com.google.errorprone.suppliers.Suppliers; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.AnnotatedTypeTree; import com.sun.source.tree.AnnotationTree; -import com.sun.source.tree.AssignmentTree; -import com.sun.source.tree.NewClassTree; import com.sun.source.tree.ParameterizedTypeTree; import com.sun.source.tree.Tree; -import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Attribute; import com.sun.tools.javac.code.Type; -import com.sun.tools.javac.code.TypeMetadata; -import com.sun.tools.javac.code.Types; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,18 +15,8 @@ /** Methods for performing checks related to generic types and nullability. */ public final class GenericsChecks { - private static final String NULLABLE_NAME = "org.jspecify.annotations.Nullable"; - - private static final Supplier NULLABLE_TYPE_SUPPLIER = - Suppliers.typeFromString(NULLABLE_NAME); - private VisitorState state; - private Config config; - private NullAway analysis; - - public GenericsChecks(VisitorState state, Config config, NullAway analysis) { - this.state = state; - this.config = config; - this.analysis = analysis; + private GenericsChecks() { + // just utility methods } /** @@ -92,14 +70,14 @@ public static void checkInstantiationForParameterizedTypedTree( // if base type argument does not have @Nullable annotation then the instantiation is // invalid if (!hasNullableAnnotation) { - reportInvalidInstantiationError( + invalidInstantiationError( nullableTypeArguments.get(i), baseType, typeVariable, state, analysis); } } } } - private static void reportInvalidInstantiationError( + private static void invalidInstantiationError( Tree tree, Type baseType, Type baseTypeVariable, VisitorState state, NullAway analysis) { ErrorBuilder errorBuilder = analysis.getErrorBuilder(); ErrorMessage errorMessage = @@ -112,194 +90,4 @@ private static void reportInvalidInstantiationError( errorBuilder.createErrorDescription( errorMessage, analysis.buildDescription(tree), state, null)); } - - private static void reportInvalidAssignmentInstantiationError( - Tree tree, Type lhsType, Type rhsType, VisitorState state, NullAway analysis) { - ErrorBuilder errorBuilder = analysis.getErrorBuilder(); - ErrorMessage errorMessage = - new ErrorMessage( - ErrorMessage.MessageTypes.ASSIGN_GENERIC_NULLABLE, - String.format( - "Cannot assign from type " - + rhsType - + " to type " - + lhsType - + " due to mismatched nullability of type parameters")); - state.reportMatch( - errorBuilder.createErrorDescription( - errorMessage, analysis.buildDescription(tree), state, null)); - } - - /** - * For a tree representing an assignment, ensures that from the perspective of type parameter - * nullability, the type of the right-hand side is assignable to (a subtype of) the type of the - * left-hand side. This check ensures that for every parameterized type nested in each of the - * types, the type parameters have identical nullability. - * - * @param tree the tree to check, which must be either an {@link AssignmentTree} or a {@link - * VariableTree} - */ - public void checkTypeParameterNullnessForAssignability(Tree tree) { - if (!config.isJSpecifyMode()) { - return; - } - Tree lhsTree; - Tree rhsTree; - if (tree instanceof VariableTree) { - VariableTree varTree = (VariableTree) tree; - lhsTree = varTree.getType(); - rhsTree = varTree.getInitializer(); - } else { - AssignmentTree assignmentTree = (AssignmentTree) tree; - lhsTree = assignmentTree.getVariable(); - rhsTree = assignmentTree.getExpression(); - } - // rhsTree can be null for a VariableTree. Also, we don't need to do a check - // if rhsTree is the null literal - if (rhsTree == null || rhsTree.getKind().equals(Tree.Kind.NULL_LITERAL)) { - return; - } - Type lhsType = ASTHelpers.getType(lhsTree); - Type rhsType = ASTHelpers.getType(rhsTree); - // For NewClassTrees with annotated type parameters, javac does not preserve the annotations in - // its computed type for the expression. As a workaround, we construct a replacement Type - // object with the appropriate annotations. - if (rhsTree instanceof NewClassTree - && ((NewClassTree) rhsTree).getIdentifier() instanceof ParameterizedTypeTree) { - ParameterizedTypeTree paramTypedTree = - (ParameterizedTypeTree) ((NewClassTree) rhsTree).getIdentifier(); - if (paramTypedTree.getTypeArguments().isEmpty()) { - // no explicit type parameters - return; - } - rhsType = typeWithPreservedAnnotations(paramTypedTree); - } - if (lhsType instanceof Type.ClassType && rhsType instanceof Type.ClassType) { - compareNullabilityAnnotations((Type.ClassType) lhsType, (Type.ClassType) rhsType, tree); - } - } - - /** - * Compare two types from an assignment for identical type parameter nullability, recursively - * checking nested generic types. See the JSpecify - * specification and the JLS - * subtyping rules for class and interface types. - * - * @param lhsType type for the lhs of the assignment - * @param rhsType type for the rhs of the assignment - * @param tree tree representing the assignment - */ - private void compareNullabilityAnnotations( - Type.ClassType lhsType, Type.ClassType rhsType, Tree tree) { - Types types = state.getTypes(); - // The base type of rhsType may be a subtype of lhsType's base type. In such cases, we must - // compare lhsType against the supertype of rhsType with a matching base type. - rhsType = (Type.ClassType) types.asSuper(rhsType, lhsType.tsym); - // This is impossible, considering the fact that standard Java subtyping succeeds before running - // NullAway - if (rhsType == null) { - throw new RuntimeException("Did not find supertype of " + rhsType + " matching " + lhsType); - } - List lhsTypeArguments = lhsType.getTypeArguments(); - List rhsTypeArguments = rhsType.getTypeArguments(); - // This is impossible, considering the fact that standard Java subtyping succeeds before running - // NullAway - if (lhsTypeArguments.size() != rhsTypeArguments.size()) { - throw new RuntimeException( - "Number of types arguments in " + rhsType + " does not match " + lhsType); - } - for (int i = 0; i < lhsTypeArguments.size(); i++) { - Type lhsTypeArgument = lhsTypeArguments.get(i); - Type rhsTypeArgument = rhsTypeArguments.get(i); - boolean isLHSNullableAnnotated = false; - List lhsAnnotations = lhsTypeArgument.getAnnotationMirrors(); - // To ensure that we are checking only jspecify nullable annotations - for (Attribute.TypeCompound annotation : lhsAnnotations) { - if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { - isLHSNullableAnnotated = true; - break; - } - } - boolean isRHSNullableAnnotated = false; - List rhsAnnotations = rhsTypeArgument.getAnnotationMirrors(); - // To ensure that we are checking only jspecify nullable annotations - for (Attribute.TypeCompound annotation : rhsAnnotations) { - if (annotation.getAnnotationType().toString().equals(NULLABLE_NAME)) { - isRHSNullableAnnotated = true; - break; - } - } - if (isLHSNullableAnnotated != isRHSNullableAnnotated) { - reportInvalidAssignmentInstantiationError(tree, lhsType, rhsType, state, analysis); - return; - } - // nested generics - if (lhsTypeArgument.getTypeArguments().length() > 0) { - compareNullabilityAnnotations( - (Type.ClassType) lhsTypeArgument, (Type.ClassType) rhsTypeArgument, tree); - } - } - } - - /** - * For the Parameterized typed trees, ASTHelpers.getType(tree) does not return a Type with - * preserved annotations. This method takes a Parameterized typed tree as an input and returns the - * Type of the tree with the annotations. - * - * @param tree A parameterized typed tree for which we need class type with preserved annotations. - * @return A Type with preserved annotations. - */ - private Type.ClassType typeWithPreservedAnnotations(ParameterizedTypeTree tree) { - Type.ClassType type = (Type.ClassType) ASTHelpers.getType(tree); - Preconditions.checkNotNull(type); - Type nullableType = NULLABLE_TYPE_SUPPLIER.get(state); - List typeArguments = tree.getTypeArguments(); - List newTypeArgs = new ArrayList<>(); - boolean hasNullableAnnotation = false; - for (int i = 0; i < typeArguments.size(); i++) { - AnnotatedTypeTree annotatedType = null; - Tree curTypeArg = typeArguments.get(i); - // If the type argument has an annotation, it will either be an AnnotatedTypeTree, or a - // ParameterizedTypeTree in the case of a nested generic type - if (curTypeArg instanceof AnnotatedTypeTree) { - annotatedType = (AnnotatedTypeTree) curTypeArg; - } else if (curTypeArg instanceof ParameterizedTypeTree - && ((ParameterizedTypeTree) curTypeArg).getType() instanceof AnnotatedTypeTree) { - annotatedType = (AnnotatedTypeTree) ((ParameterizedTypeTree) curTypeArg).getType(); - } - List annotations = - annotatedType != null ? annotatedType.getAnnotations() : Collections.emptyList(); - for (AnnotationTree annotation : annotations) { - if (ASTHelpers.isSameType( - nullableType, ASTHelpers.getType(annotation.getAnnotationType()), state)) { - hasNullableAnnotation = true; - break; - } - } - // construct a TypeMetadata object containing a nullability annotation if needed - com.sun.tools.javac.util.List nullableAnnotationCompound = - hasNullableAnnotation - ? com.sun.tools.javac.util.List.from( - Collections.singletonList( - new Attribute.TypeCompound( - nullableType, com.sun.tools.javac.util.List.nil(), null))) - : com.sun.tools.javac.util.List.nil(); - TypeMetadata typeMetadata = - new TypeMetadata(new TypeMetadata.Annotations(nullableAnnotationCompound)); - Type currentTypeArgType = castToNonNull(ASTHelpers.getType(curTypeArg)); - if (currentTypeArgType.getTypeArguments().size() > 0) { - // nested generic type; recursively preserve its nullability type argument annotations - currentTypeArgType = typeWithPreservedAnnotations((ParameterizedTypeTree) curTypeArg); - } - Type.ClassType newTypeArgType = - (Type.ClassType) currentTypeArgType.cloneWithMetadata(typeMetadata); - newTypeArgs.add(newTypeArgType); - } - Type.ClassType finalType = - new Type.ClassType( - type.getEnclosingType(), com.sun.tools.javac.util.List.from(newTypeArgs), type.tsym); - return finalType; - } } diff --git a/nullaway/src/main/java/com/uber/nullaway/NullAway.java b/nullaway/src/main/java/com/uber/nullaway/NullAway.java index 7fe7fdbdb1..ce5ad948b9 100644 --- a/nullaway/src/main/java/com/uber/nullaway/NullAway.java +++ b/nullaway/src/main/java/com/uber/nullaway/NullAway.java @@ -466,11 +466,6 @@ public Description matchAssignment(AssignmentTree tree, VisitorState state) { if (lhsType != null && lhsType.isPrimitive()) { doUnboxingCheck(state, tree.getExpression()); } - // generics check - if (lhsType != null && lhsType.getTypeArguments().length() > 0) { - new GenericsChecks(state, config, this).checkTypeParameterNullnessForAssignability(tree); - } - Symbol assigned = ASTHelpers.getSymbol(tree.getVariable()); if (assigned == null || assigned.getKind() != ElementKind.FIELD) { // not a field of nullable type @@ -1338,10 +1333,6 @@ public Description matchVariable(VariableTree tree, VisitorState state) { return Description.NO_MATCH; } VarSymbol symbol = ASTHelpers.getSymbol(tree); - if (tree.getInitializer() != null) { - new GenericsChecks(state, config, this).checkTypeParameterNullnessForAssignability(tree); - } - if (symbol.type.isPrimitive() && tree.getInitializer() != null) { doUnboxingCheck(state, tree.getInitializer()); } diff --git a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java index 18f2e69b84..3a0396792d 100644 --- a/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java +++ b/nullaway/src/test/java/com/uber/nullaway/NullAwayJSpecifyGenericsTests.java @@ -14,14 +14,14 @@ public void basicTypeParamInstantiation() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", - " static class NullableTypeParam {}", - " // BUG: Diagnostic contains: Generic type parameter", - " static void testBadNonNull(NonNullTypeParam<@Nullable String> t1) {", + " static class NonNullTypeParam {}", + " static class NullableTypeParam {}", " // BUG: Diagnostic contains: Generic type parameter", - " NonNullTypeParam<@Nullable String> t2 = null;", - " NullableTypeParam<@Nullable String> t3 = null;", - " }", + " static void testBadNonNull(NonNullTypeParam<@Nullable String> t1) {", + " // BUG: Diagnostic contains: Generic type parameter", + " NonNullTypeParam<@Nullable String> t2 = null;", + " NullableTypeParam<@Nullable String> t3 = null;", + " }", "}") .doTest(); } @@ -34,25 +34,24 @@ public void constructorTypeParamInstantiation() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", - " static class NullableTypeParam {}", - " static void testOkNonNull(NonNullTypeParam t) {", - " NonNullTypeParam t2 = new NonNullTypeParam();", - " }", - " static void testBadNonNull(NonNullTypeParam t) {", - " // BUG: Diagnostic contains: Generic type parameter", - " NonNullTypeParam t2 = new NonNullTypeParam<@Nullable String>();", - " // BUG: Diagnostic contains: Generic type parameter", - " testBadNonNull(new NonNullTypeParam<@Nullable String>());", - " testBadNonNull(", - " new NonNullTypeParam<", - " // BUG: Diagnostic contains: Generic type parameter", - " @Nullable String>());", - " }", - " static void testOkNullable(NullableTypeParam t1, NullableTypeParam<@Nullable String> t2) {", - " NullableTypeParam t3 = new NullableTypeParam();", - " NullableTypeParam<@Nullable String> t4 = new NullableTypeParam<@Nullable String>();", - " }", + " static class NonNullTypeParam {}", + " static class NullableTypeParam {}", + " static void testOkNonNull(NonNullTypeParam t) {", + " NonNullTypeParam t2 = new NonNullTypeParam();", + " }", + " static void testBadNonNull(NonNullTypeParam t) {", + " // BUG: Diagnostic contains: Generic type parameter", + " NonNullTypeParam t2 = new NonNullTypeParam<@Nullable String>();", + " // BUG: Diagnostic contains: Generic type parameter", + " testBadNonNull(new NonNullTypeParam<@Nullable String>());", + " testBadNonNull(new NonNullTypeParam<", + " // BUG: Diagnostic contains: Generic type parameter", + " @Nullable String>());", + " }", + " static void testOkNullable(NullableTypeParam t1, NullableTypeParam<@Nullable String> t2) {", + " NullableTypeParam t3 = new NullableTypeParam();", + " NullableTypeParam<@Nullable String> t4 = new NullableTypeParam<@Nullable String>();", + " }", "}") .doTest(); } @@ -65,20 +64,14 @@ public void multipleTypeParametersInstantiation() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class MixedTypeParam {}", - " static class PartiallyInvalidSubclass", - " // BUG: Diagnostic contains: Generic type parameter", - " extends MixedTypeParam<@Nullable String, String, String, @Nullable String> {}", - " static class ValidSubclass1", - " extends MixedTypeParam {}", - " static class PartiallyInvalidSubclass2", - " extends MixedTypeParam<", - " String,", - " String,", - " String,", - " // BUG: Diagnostic contains: Generic type parameter", - " @Nullable String> {}", - " static class ValidSubclass2 extends MixedTypeParam {}", + " static class MixedTypeParam {}", + " // BUG: Diagnostic contains: Generic type parameter", + " static class PartiallyInvalidSubclass extends MixedTypeParam<@Nullable String, String, String, @Nullable String> {}", + " static class ValidSubclass1 extends MixedTypeParam {}", + " static class PartiallyInvalidSubclass2 extends MixedTypeParam {}", + " static class ValidSubclass2 extends MixedTypeParam {}", "}") .doTest(); } @@ -91,13 +84,13 @@ public void subClassTypeParamInstantiation() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", - " static class NullableTypeParam {}", - " static class SuperClassForValidSubclass {", - " static class ValidSubclass extends NullableTypeParam<@Nullable String> {}", - " // BUG: Diagnostic contains: Generic type parameter", - " static class InvalidSubclass extends NonNullTypeParam<@Nullable String> {}", - " }", + " static class NonNullTypeParam {}", + " static class NullableTypeParam {}", + " static class SuperClassForValidSubclass {", + " static class ValidSubclass extends NullableTypeParam<@Nullable String> {}", + " // BUG: Diagnostic contains: Generic type parameter", + " static class InvalidSubclass extends NonNullTypeParam<@Nullable String> {}", + " }", "}") .doTest(); } @@ -110,12 +103,11 @@ public void interfaceImplementationTypeParamInstantiation() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static interface NonNullTypeParamInterface {}", - " static interface NullableTypeParamInterface {}", - " static class InvalidInterfaceImplementation", - " // BUG: Diagnostic contains: Generic type parameter", - " implements NonNullTypeParamInterface<@Nullable String> {}", - " static class ValidInterfaceImplementation implements NullableTypeParamInterface {}", + " static interface NonNullTypeParamInterface{}", + " static interface NullableTypeParamInterface{}", + " // BUG: Diagnostic contains: Generic type parameter", + " static class InvalidInterfaceImplementation implements NonNullTypeParamInterface<@Nullable String> {}", + " static class ValidInterfaceImplementation implements NullableTypeParamInterface {}", "}") .doTest(); } @@ -128,17 +120,17 @@ public void nestedTypeParams() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", - " static class NullableTypeParam {}", - " // BUG: Diagnostic contains: Generic type parameter", - " static void testBadNonNull(NullableTypeParam> t) {", - " // BUG: Diagnostic contains: Generic type parameter", - " NullableTypeParam>> t2 = null;", + " static class NonNullTypeParam {}", + " static class NullableTypeParam {}", " // BUG: Diagnostic contains: Generic type parameter", - " t2 = new NullableTypeParam>>();", - " // this is fine", - " NullableTypeParam>> t3 = null;", - " }", + " static void testBadNonNull(NullableTypeParam> t) {", + " // BUG: Diagnostic contains: Generic type parameter", + " NullableTypeParam>> t2 = null;", + " // BUG: Diagnostic contains: Generic type parameter", + " t2 = new NullableTypeParam>>();", + " // this is fine", + " NullableTypeParam>> t3 = null;", + " }", "}") .doTest(); } @@ -151,15 +143,15 @@ public void returnTypeParamInstantiation() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", - " static class NullableTypeParam {}", - " // BUG: Diagnostic contains: Generic type parameter", - " static NonNullTypeParam<@Nullable String> testBadNonNull() {", - " return new NonNullTypeParam();", - " }", - " static NullableTypeParam<@Nullable String> testOKNull() {", - " return new NullableTypeParam<@Nullable String>();", - " }", + " static class NonNullTypeParam {}", + " static class NullableTypeParam {}", + " // BUG: Diagnostic contains: Generic type parameter", + " static NonNullTypeParam<@Nullable String> testBadNonNull() {", + " return new NonNullTypeParam();", + " }", + " static NullableTypeParam<@Nullable String> testOKNull() {", + " return new NullableTypeParam<@Nullable String>();", + " }", "}") .doTest(); } @@ -173,18 +165,18 @@ public void testOKNewClassInstantiationForOtherAnnotations() { "import lombok.NonNull;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", - " static class DifferentAnnotTypeParam1 {}", - " static class DifferentAnnotTypeParam2<@NonNull E> {}", - " static void testOKOtherAnnotation(NonNullTypeParam t) {", - " // should not show error for annotation other than @Nullable", - " testOKOtherAnnotation(new NonNullTypeParam<@NonNull String>());", - " DifferentAnnotTypeParam1 t1 = new DifferentAnnotTypeParam1();", - " // BUG: Diagnostic contains: Generic type parameter", - " DifferentAnnotTypeParam2 t2 = new DifferentAnnotTypeParam2<@Nullable String>();", - " // BUG: Diagnostic contains: Generic type parameter", - " DifferentAnnotTypeParam1 t3 = new DifferentAnnotTypeParam1<@Nullable String>();", - " }", + " static class NonNullTypeParam {}", + " static class DifferentAnnotTypeParam1 {}", + " static class DifferentAnnotTypeParam2<@NonNull E> {}", + " static void testOKOtherAnnotation(NonNullTypeParam t) {", + " // should not show error for annotation other than @Nullable", + " testOKOtherAnnotation(new NonNullTypeParam<@NonNull String>());", + " DifferentAnnotTypeParam1 t1 = new DifferentAnnotTypeParam1();", + " // BUG: Diagnostic contains: Generic type parameter", + " DifferentAnnotTypeParam2 t2 = new DifferentAnnotTypeParam2<@Nullable String>();", + " // BUG: Diagnostic contains: Generic type parameter", + " DifferentAnnotTypeParam1 t3 = new DifferentAnnotTypeParam1<@Nullable String>();", + " }", "}") .doTest(); } @@ -197,7 +189,7 @@ public void downcastInstantiation() { "package com.uber;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", + " static class NonNullTypeParam { }", " static void instOf(Object o) {", " // BUG: Diagnostic contains: Generic type parameter", " Object p = (NonNullTypeParam<@Nullable String>) o;", @@ -215,7 +207,7 @@ public void instantiationInUnannotatedCode() { "package com.other;", "import org.jspecify.annotations.Nullable;", "class Test {", - " static class NonNullTypeParam {}", + " static class NonNullTypeParam { }", " static void instOf(Object o) {", " Object p = (NonNullTypeParam<@Nullable String>) o;", " }", @@ -223,271 +215,6 @@ public void instantiationInUnannotatedCode() { .doTest(); } - @Test - public void genericsChecksForAssignments() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " static class NullableTypeParam {}", - " static void testPositive(NullableTypeParam<@Nullable String> t1) {", - " // BUG: Diagnostic contains: Cannot assign from type", - " NullableTypeParam t2 = t1;", - " }", - " static void testNegative(NullableTypeParam<@Nullable String> t1) {", - " NullableTypeParam<@Nullable String> t2 = t1;", - " }", - "}") - .doTest(); - } - - @Test - public void nestedChecksForAssignmentsMultipleArguments() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " static class SampleClass {}", - " static class SampleClassMultipleArguments {}", - " static void testPositive() {", - " // BUG: Diagnostic contains: Cannot assign from type", - " SampleClassMultipleArguments>, String> t1 =", - " new SampleClassMultipleArguments>, String>();", - " }", - " static void testNegative() {", - " SampleClassMultipleArguments>, String> t1 =", - " new SampleClassMultipleArguments>, String>();", - " }", - "}") - .doTest(); - } - - @Test - public void superTypeAssignmentChecksSingleInterface() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " interface Fn

{}", - " class FnImpl implements Fn<@Nullable String, @Nullable String> {}", - " void testPositive() {", - " // BUG: Diagnostic contains: Cannot assign from type", - " Fn<@Nullable String, String> f = new FnImpl();", - " }", - " void testNegative() {", - " Fn<@Nullable String, @Nullable String> f = new FnImpl();", - " }", - "}") - .doTest(); - } - - @Test - public void superTypeAssignmentChecksMultipleInterface() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " interface Fn1 {}", - " interface Fn2

{}", - " class FnImpl implements Fn1<@Nullable String, @Nullable String>, Fn2 {}", - " void testPositive() {", - " // BUG: Diagnostic contains: Cannot assign from type", - " Fn2<@Nullable String> f = new FnImpl();", - " }", - " void testNegative() {", - " Fn2 f = new FnImpl();", - " }", - "}") - .doTest(); - } - - @Test - public void superTypeAssignmentChecksMultipleLevelInheritance() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " class SuperClassC {}", - " class SuperClassB

extends SuperClassC

{}", - " class SubClassA

extends SuperClassB

{}", - " class FnImpl1 extends SubClassA {}", - " class FnImpl2 extends SubClassA<@Nullable String> {}", - " void testPositive() {", - " SuperClassC<@Nullable String> f;", - " // BUG: Diagnostic contains: Cannot assign from type", - " f = new FnImpl1();", - " }", - " void testNegative() {", - " SuperClassC<@Nullable String> f;", - " // No error", - " f = new FnImpl2();", - " }", - "}") - .doTest(); - } - - @Test - public void subtypeWithParameters() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " class D

{}", - " class B

extends D

{}", - " void testPositive(B<@Nullable String> b) {", - " // BUG: Diagnostic contains: Cannot assign from type", - " D f1 = new B<@Nullable String>();", - " // BUG: Diagnostic contains: Cannot assign from type", - " D f2 = b;", - " }", - " void testNegative(B b) {", - " D f1 = new B();", - " D f2 = b;", - " }", - "}") - .doTest(); - } - - @Test - public void fancierSubtypeWithParameters() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " class Super {}", - " class Sub extends Super {}", - " void testNegative() {", - " // valid assignment", - " Super<@Nullable String, String> s = new Sub();", - " }", - " void testPositive() {", - " // BUG: Diagnostic contains: Cannot assign from type", - " Super<@Nullable String, String> s2 = new Sub<@Nullable String, String>();", - " }", - "}") - .doTest(); - } - - @Test - public void nestedVariableDeclarationChecks() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " class D

{}", - " class B

extends D

{}", - " class C

{}", - " class A, P extends @Nullable Object> {}", - " void testPositive() {", - " // BUG: Diagnostic contains: Cannot assign from type", - " D> f1 = new B>();", - " // BUG: Diagnostic contains: Cannot assign from type", - " A, String> f2 = new A, @Nullable String>();", - " // BUG: Diagnostic contains: Cannot assign from type", - " D> f3 = new B<@Nullable C>();", - " }", - " void testNegative() {", - " D> f1 = new B>();", - " A, String> f2 = new A, String>();", - " D<@Nullable C> f3 = new B<@Nullable C>();", - " }", - "}") - .doTest(); - } - - @Test - public void testForMethodReferenceInAnAssignment() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " interface A {", - " String function(T1 o);", - " }", - " static String foo(Object o) {", - " return o.toString();", - " }", - " static void testPositive() {", - " // TODO: we should report an error here, since Test::foo cannot take", - " // a @Nullable parameter. we don't catch this yet", - " A<@Nullable Object> p = Test::foo;", - " }", - " static void testNegative() {", - " A p = Test::foo;", - " }", - "}") - .doTest(); - } - - @Test - public void testForLambdasInAnAssignment() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " interface A {", - " String function(T1 o);", - " }", - " static void testPositive() {", - " // TODO: we should report an error here, since the lambda cannot take", - " // a @Nullable parameter. we don't catch this yet", - " A<@Nullable Object> p = o -> o.toString();", - " }", - " static void testNegative() {", - " A p = o -> o.toString();", - " }", - "}") - .doTest(); - } - - @Test - public void testForDiamondInAnAssignment() { - makeHelper() - .addSourceLines( - "Test.java", - "package com.uber;", - "import org.jspecify.annotations.Nullable;", - "class Test {", - " interface A {", - " String function(T1 o);", - " }", - " static class B implements A {", - " public String function(T1 o) {", - " return o.toString();", - " }", - " }", - " static void testPositive() {", - " // TODO: we should report an error here, since B's type parameter", - " // cannot be @Nullable; we do not catch this yet", - " A<@Nullable Object> p = new B<>();", - " }", - " static void testNegative() {", - " A p = new B<>();", - " }", - "}") - .doTest(); - } - private CompilationTestHelper makeHelper() { return makeTestHelperWithArgs( Arrays.asList(