Skip to content

Commit 76b33c9

Browse files
authored
Use the new TypeUseLocation default locations (#165)
1 parent 11a2980 commit 76b33c9

File tree

11 files changed

+251
-35
lines changed

11 files changed

+251
-35
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
run: ./gradlew build conformanceTests demoTest --include-build ../jspecify
3131
env:
3232
SHALLOW: 1
33+
JSPECIFY_CONFORMANCE_TEST_MODE: details
3334
- name: Check out jspecify/samples-google-prototype-eisop
3435
if: always()
3536
run: |

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ if (!cfQualJar.toFile().exists()) {
235235

236236
spotless {
237237
java {
238+
target '**/*.java'
238239
googleJavaFormat()
239240
formatAnnotations()
240241
}

src/main/java/com/google/jspecify/nullness/NullSpecAnnotatedTypeFactory.java

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ final class NullSpecAnnotatedTypeFactory
115115
private final AnnotationMirror minusNull;
116116
private final AnnotationMirror unionNull;
117117
private final AnnotationMirror nullnessOperatorUnspecified;
118+
private final AnnotationMirror parametricNull;
118119

119120
private final boolean isLeastConvenientWorld;
120121
private final NullSpecAnnotatedTypeFactory withLeastConvenientWorld;
@@ -128,6 +129,11 @@ final class NullSpecAnnotatedTypeFactory
128129
final AnnotatedDeclaredType javaLangThreadLocal;
129130
final AnnotatedDeclaredType javaUtilMap;
130131

132+
// Ensure that all locations that appear in the `defaultLocations` also appear somewhere in the
133+
// `nullMarkedLocations`.
134+
// As defaults are added and removed to the same environment, we need to ensure that all values
135+
// are correctly changed.
136+
131137
private static final TypeUseLocation[] defaultLocationsMinusNull =
132138
new TypeUseLocation[] {
133139
TypeUseLocation.CONSTRUCTOR_RESULT,
@@ -138,14 +144,38 @@ final class NullSpecAnnotatedTypeFactory
138144

139145
private static final TypeUseLocation[] defaultLocationsUnionNull =
140146
new TypeUseLocation[] {
141-
TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE,
147+
TypeUseLocation.IMPLICIT_WILDCARD_UPPER_BOUND_SUPER,
148+
TypeUseLocation.LOCAL_VARIABLE,
149+
TypeUseLocation.RESOURCE_VARIABLE,
142150
};
143151

144152
private static final TypeUseLocation[] defaultLocationsUnspecified =
145153
new TypeUseLocation[] {
146-
// TypeUseLocation.UNBOUNDED_WILDCARD_UPPER_BOUND, TODO
154+
TypeUseLocation.IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER,
155+
TypeUseLocation.TYPE_VARIABLE_USE,
156+
TypeUseLocation.OTHERWISE
157+
};
158+
159+
private static final TypeUseLocation[] nullMarkedLocationsMinusNull =
160+
new TypeUseLocation[] {
161+
TypeUseLocation.CONSTRUCTOR_RESULT,
162+
TypeUseLocation.EXCEPTION_PARAMETER,
163+
TypeUseLocation.IMPLICIT_LOWER_BOUND,
164+
TypeUseLocation.RECEIVER,
147165
TypeUseLocation.OTHERWISE
148166
};
167+
private static final TypeUseLocation[] nullMarkedLocationsUnionNull =
168+
new TypeUseLocation[] {
169+
TypeUseLocation.LOCAL_VARIABLE,
170+
TypeUseLocation.RESOURCE_VARIABLE,
171+
TypeUseLocation.IMPLICIT_WILDCARD_UPPER_BOUND_NO_SUPER,
172+
TypeUseLocation.IMPLICIT_WILDCARD_UPPER_BOUND_SUPER,
173+
};
174+
175+
private static final TypeUseLocation[] nullMarkedLocationsParametric =
176+
new TypeUseLocation[] {TypeUseLocation.TYPE_VARIABLE_USE};
177+
178+
private static final TypeUseLocation[] nullMarkedLocationsUnspecified = new TypeUseLocation[] {};
149179

150180
/** Constructor that takes all configuration from the provided {@code checker}. */
151181
NullSpecAnnotatedTypeFactory(BaseTypeChecker checker, Util util) {
@@ -170,6 +200,7 @@ private NullSpecAnnotatedTypeFactory(
170200
minusNull = util.minusNull;
171201
unionNull = util.unionNull;
172202
nullnessOperatorUnspecified = util.nullnessOperatorUnspecified;
203+
parametricNull = util.parametricNull;
173204

174205
addAliasedTypeAnnotation(
175206
"org.jspecify.annotations.NullnessUnspecified", nullnessOperatorUnspecified);
@@ -187,30 +218,36 @@ private NullSpecAnnotatedTypeFactory(
187218
AnnotationMirror nullMarkedDefaultQualMinusNull =
188219
new AnnotationBuilder(processingEnv, DefaultQualifier.class)
189220
.setValue("value", MinusNull.class)
190-
.setValue(
191-
"locations",
192-
new TypeUseLocation[] {
193-
TypeUseLocation.EXCEPTION_PARAMETER, TypeUseLocation.OTHERWISE
194-
})
221+
.setValue("locations", nullMarkedLocationsMinusNull)
195222
.setValue("applyToSubpackages", false)
196223
.build();
197224
AnnotationMirror nullMarkedDefaultQualUnionNull =
198225
new AnnotationBuilder(processingEnv, DefaultQualifier.class)
199226
.setValue("value", Nullable.class)
200-
.setValue(
201-
"locations",
202-
new TypeUseLocation[] {
203-
TypeUseLocation.LOCAL_VARIABLE, TypeUseLocation.RESOURCE_VARIABLE,
204-
// TypeUseLocation.UNBOUNDED_WILDCARD_UPPER_BOUND TODO
205-
})
227+
.setValue("locations", nullMarkedLocationsUnionNull)
228+
.setValue("applyToSubpackages", false)
229+
.build();
230+
AnnotationMirror nullMarkedDefaultQualParametric =
231+
new AnnotationBuilder(processingEnv, DefaultQualifier.class)
232+
.setValue("value", ParametricNull.class)
233+
.setValue("locations", nullMarkedLocationsParametric)
234+
.setValue("applyToSubpackages", false)
235+
.build();
236+
AnnotationMirror nullMarkedDefaultQualUnspecified =
237+
new AnnotationBuilder(processingEnv, DefaultQualifier.class)
238+
.setValue("value", NullnessUnspecified.class)
239+
.setValue("locations", nullMarkedLocationsUnspecified)
206240
.setValue("applyToSubpackages", false)
207241
.build();
208242
AnnotationMirror nullMarkedDefaultQual =
209243
new AnnotationBuilder(processingEnv, DefaultQualifier.List.class)
210244
.setValue(
211245
"value",
212246
new AnnotationMirror[] {
213-
nullMarkedDefaultQualMinusNull, nullMarkedDefaultQualUnionNull
247+
nullMarkedDefaultQualMinusNull,
248+
nullMarkedDefaultQualUnionNull,
249+
nullMarkedDefaultQualParametric,
250+
nullMarkedDefaultQualUnspecified
214251
})
215252
.build();
216253

@@ -359,7 +396,8 @@ protected void addUncheckedStandardDefaults(QualifierDefaults defs) {
359396

360397
@Override
361398
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
362-
return new LinkedHashSet<>(asList(Nullable.class, NullnessUnspecified.class, MinusNull.class));
399+
return new LinkedHashSet<>(
400+
asList(Nullable.class, NullnessUnspecified.class, MinusNull.class, ParametricNull.class));
363401
}
364402

365403
@Override
@@ -438,10 +476,16 @@ protected Map<DefaultQualifierKind, Set<DefaultQualifierKind>> createDirectSuper
438476
nameToQualifierKind.get(Nullable.class.getCanonicalName());
439477
DefaultQualifierKind nullnessOperatorUnspecified =
440478
nameToQualifierKind.get(NullnessUnspecified.class.getCanonicalName());
479+
DefaultQualifierKind parametricNullKind =
480+
nameToQualifierKind.get(ParametricNull.class.getCanonicalName());
441481

442482
Map<DefaultQualifierKind, Set<DefaultQualifierKind>> supers = new HashMap<>();
443-
supers.put(minusNullKind, singleton(nullnessOperatorUnspecified));
483+
LinkedHashSet<DefaultQualifierKind> superOfMinusNull = new LinkedHashSet<>();
484+
superOfMinusNull.add(nullnessOperatorUnspecified);
485+
superOfMinusNull.add(parametricNullKind);
486+
supers.put(minusNullKind, superOfMinusNull);
444487
supers.put(nullnessOperatorUnspecified, singleton(unionNullKind));
488+
supers.put(parametricNullKind, singleton(unionNullKind));
445489
supers.put(unionNullKind, emptySet());
446490
return supers;
447491
/*
@@ -457,6 +501,16 @@ protected Map<DefaultQualifierKind, Set<DefaultQualifierKind>> createDirectSuper
457501
}
458502
};
459503
}
504+
505+
@Override
506+
public AnnotationMirror getParametricQualifier(AnnotationMirror qualifier) {
507+
return parametricNull;
508+
}
509+
510+
@Override
511+
public boolean isParametricQualifier(AnnotationMirror qualifier) {
512+
return areSame(parametricNull, qualifier);
513+
}
460514
}
461515

462516
@Override
@@ -706,6 +760,9 @@ private List<? extends AnnotatedTypeMirror> getUpperBounds(AnnotatedTypeMirror t
706760
*
707761
* My only worry is that I always worry about making calls to getAnnotatedType, as discussed in
708762
* various comments in this file (e.g., in NullSpecTreeAnnotator.visitMethodInvocation).
763+
*
764+
* This is likely caused by https://github.com/eisop/checker-framework/issues/737.
765+
* Revisit this once that issue is fixed.
709766
*/
710767
if (type instanceof AnnotatedTypeVariable
711768
&& !isCapturedTypeVariable(type.getUnderlyingType())) {
@@ -861,8 +918,8 @@ protected AnnotatedTypeMirror substituteTypeVariable(
861918
substitute.replaceAnnotation(minusNull);
862919
} else if (argument.hasAnnotation(unionNull) || use.hasAnnotation(unionNull)) {
863920
substitute.replaceAnnotation(unionNull);
864-
} else if (argument.hasAnnotation(nullnessOperatorUnspecified)
865-
|| use.hasAnnotation(nullnessOperatorUnspecified)) {
921+
} else if (argument.hasEffectiveAnnotation(nullnessOperatorUnspecified)
922+
|| use.hasEffectiveAnnotation(nullnessOperatorUnspecified)) {
866923
substitute.replaceAnnotation(nullnessOperatorUnspecified);
867924
}
868925

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2024 The JSpecify Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.jspecify.nullness;
16+
17+
import static java.lang.annotation.ElementType.TYPE_USE;
18+
19+
import java.lang.annotation.Target;
20+
import org.checkerframework.framework.qual.InvisibleQualifier;
21+
import org.checkerframework.framework.qual.ParametricTypeVariableUseQualifier;
22+
23+
/** Internal implementation detail; not usable in user code. */
24+
@Target(TYPE_USE)
25+
@InvisibleQualifier
26+
@ParametricTypeVariableUseQualifier(Nullable.class)
27+
@interface ParametricNull {}

src/main/java/com/google/jspecify/nullness/Util.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ final class Util {
5151
final AnnotationMirror minusNull;
5252
final AnnotationMirror unionNull;
5353
final AnnotationMirror nullnessOperatorUnspecified;
54+
final AnnotationMirror parametricNull;
5455

5556
final TypeElement javaUtilCollectionElement;
5657
final ExecutableElement collectionToArrayNoArgElement;
@@ -116,6 +117,7 @@ final class Util {
116117
minusNull = AnnotationBuilder.fromClass(e, MinusNull.class);
117118
unionNull = AnnotationBuilder.fromClass(e, Nullable.class);
118119
nullnessOperatorUnspecified = AnnotationBuilder.fromClass(e, NullnessUnspecified.class);
120+
parametricNull = AnnotationBuilder.fromClass(e, ParametricNull.class);
119121
/*
120122
* Note that all the above annotations must be on the *classpath*, not just the *processorpath*.
121123
* That's because, even if we change fromClass to fromName, AnnotationBuilder ultimately calls

src/test/java/tests/ConformanceTest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,13 @@ static final class DetailMessageReportedFact extends ReportedFact {
161161
"assignment.type.incompatible",
162162
"atomicreference.must.include.null",
163163
"cast.unsafe",
164-
"lambda.param",
165-
"methodref.receiver.bound",
166-
"methodref.receiver",
167-
"methodref.return",
168-
"override.param",
169-
"override.return",
164+
"lambda.param.type.incompatible",
165+
"methodref.receiver.bound.invalid",
166+
"methodref.receiver.invalid",
167+
"methodref.return.invalid",
168+
"override.param.invalid",
169+
"override.receiver.invalid",
170+
"override.return.invalid",
170171
"return.type.incompatible",
171172
"threadlocal.must.include.null",
172173
"type.argument.type.incompatible");

tests/ConformanceTestOnSamples-report.txt

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 67 pass; 506 fail; 573 total; 11.7% score
1+
# 73 pass; 500 fail; 573 total; 12.7% score
22
FAIL: AnnotatedInnerOfNonParameterized.java: no unexpected facts
33
FAIL: AnnotatedInnerOfParameterized.java: no unexpected facts
44
FAIL: AnnotatedReceiver.java: no unexpected facts
@@ -14,15 +14,15 @@ FAIL: ArraySubtype.java: no unexpected facts
1414
PASS: AssignmentAsExpression.java: no unexpected facts
1515
PASS: AugmentedInferenceAgreesWithBaseInference.java:35:jspecify_nullness_mismatch
1616
PASS: AugmentedInferenceAgreesWithBaseInference.java: no unexpected facts
17-
FAIL: BoundedTypeVariableReturn.java:27:jspecify_nullness_mismatch
18-
FAIL: BoundedTypeVariableReturn.java:32:jspecify_nullness_mismatch
17+
PASS: BoundedTypeVariableReturn.java:27:jspecify_nullness_mismatch
18+
PASS: BoundedTypeVariableReturn.java:32:jspecify_nullness_mismatch
1919
PASS: BoundedTypeVariableReturn.java: no unexpected facts
2020
FAIL: CaptureAsInferredTypeArgument.java:51:jspecify_nullness_mismatch
2121
FAIL: CaptureAsInferredTypeArgument.java:53:jspecify_nullness_mismatch
2222
FAIL: CaptureAsInferredTypeArgument.java:61:jspecify_nullness_mismatch
2323
FAIL: CaptureAsInferredTypeArgument.java:63:jspecify_nullness_mismatch
2424
FAIL: CaptureAsInferredTypeArgument.java: no unexpected facts
25-
FAIL: CaptureConversionForSubtyping.java: no unexpected facts
25+
PASS: CaptureConversionForSubtyping.java: no unexpected facts
2626
FAIL: CaptureConvertedToObject.java:71:jspecify_nullness_mismatch
2727
FAIL: CaptureConvertedToObject.java: no unexpected facts
2828
FAIL: CaptureConvertedToObjectUnionNull.java: no unexpected facts
@@ -70,11 +70,11 @@ FAIL: CaptureConvertedUnspecToOther.java: no unexpected facts
7070
FAIL: CaptureConvertedUnspecToOtherUnionNull.java: no unexpected facts
7171
FAIL: CaptureConvertedUnspecToOtherUnspec.java: no unexpected facts
7272
PASS: CastOfCaptureOfNotNullMarkedUnboundedWildcardForObjectBoundedTypeParameter.java: no unexpected facts
73-
PASS: CastOfCaptureOfUnboundedWildcardForNotNullMarkedObjectBoundedTypeParameter.java: no unexpected facts
73+
FAIL: CastOfCaptureOfUnboundedWildcardForNotNullMarkedObjectBoundedTypeParameter.java: no unexpected facts
7474
PASS: CastOfCaptureOfUnboundedWildcardForObjectBoundedTypeParameter.java: no unexpected facts
7575
FAIL: CastToPrimitive.java:33:jspecify_nullness_mismatch
7676
FAIL: CastToPrimitive.java: no unexpected facts
77-
FAIL: CastWildcardToTypeVariable.java:23:jspecify_nullness_mismatch
77+
PASS: CastWildcardToTypeVariable.java:23:jspecify_nullness_mismatch
7878
PASS: CastWildcardToTypeVariable.java: no unexpected facts
7979
PASS: Catch.java: no unexpected facts
8080
PASS: ClassLiteral.java: no unexpected facts
@@ -116,10 +116,10 @@ FAIL: ContainmentSuperVsExtends.java:24:jspecify_nullness_mismatch
116116
FAIL: ContainmentSuperVsExtends.java: no unexpected facts
117117
FAIL: ContainmentSuperVsExtendsSameType.java:23:jspecify_nullness_mismatch
118118
FAIL: ContainmentSuperVsExtendsSameType.java: no unexpected facts
119-
FAIL: ContravariantReturns.java:30:jspecify_nullness_mismatch
120-
FAIL: ContravariantReturns.java:34:jspecify_nullness_mismatch
121-
FAIL: ContravariantReturns.java:38:jspecify_nullness_mismatch
122-
FAIL: ContravariantReturns.java: no unexpected facts
119+
PASS: ContravariantReturns.java:30:jspecify_nullness_mismatch
120+
PASS: ContravariantReturns.java:34:jspecify_nullness_mismatch
121+
PASS: ContravariantReturns.java:38:jspecify_nullness_mismatch
122+
PASS: ContravariantReturns.java: no unexpected facts
123123
PASS: CovariantReturns.java: no unexpected facts
124124
FAIL: DereferenceClass.java:33:jspecify_nullness_mismatch
125125
FAIL: DereferenceClass.java: no unexpected facts
@@ -303,7 +303,7 @@ FAIL: NullLiteralToTypeVariableUnionNull.java: no unexpected facts
303303
FAIL: NullLiteralToTypeVariableUnspec.java: no unexpected facts
304304
FAIL: NullMarkedDirectUseOfNotNullMarkedBoundedTypeVariable.java: no unexpected facts
305305
PASS: NullUnmarkedUndoesNullMarked.java: no unexpected facts
306-
PASS: NullUnmarkedUndoesNullMarkedForWildcards.java: no unexpected facts
306+
FAIL: NullUnmarkedUndoesNullMarkedForWildcards.java: no unexpected facts
307307
PASS: NullnessDoesNotAffectOverloadSelection.java:23:jspecify_nullness_mismatch
308308
PASS: NullnessDoesNotAffectOverloadSelection.java: no unexpected facts
309309
PASS: ObjectAsSuperOfTypeVariable.java:35:jspecify_nullness_mismatch

tests/regression/Issue159.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright 2024 The JSpecify Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Test case for Issue 159:
16+
// https://github.com/jspecify/jspecify-reference-checker/issues/159
17+
18+
import java.util.ArrayList;
19+
import org.jspecify.annotations.NullMarked;
20+
21+
@NullMarked
22+
class Issue159<E> extends ArrayList<E> {
23+
<F> Issue159<F> foo() {
24+
return new Issue159<F>();
25+
}
26+
}

tests/regression/Issue163.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2024 The JSpecify Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Test case for Issue 163:
16+
// https://github.com/jspecify/jspecify-reference-checker/issues/163
17+
18+
import org.jspecify.annotations.NullMarked;
19+
20+
@NullMarked
21+
class Issue163NullForUnspecVoid {
22+
void x(Issue163Value val, Issue163Visitor<Void> vis) {
23+
val.accept(vis, null);
24+
}
25+
}
26+
27+
interface Issue163Value {
28+
<P> void accept(Issue163Visitor<P> visitor, P param);
29+
}
30+
31+
interface Issue163Visitor<P> {}

0 commit comments

Comments
 (0)