Skip to content

Commit c8d5933

Browse files
committed
Use the new TypeInformationPresenter to output more type information (#134)
1 parent 570d80e commit c8d5933

File tree

6 files changed

+128
-10
lines changed

6 files changed

+128
-10
lines changed

settings.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ includeBuild("../checker-framework")
1515
dependencyResolutionManagement {
1616
versionCatalogs {
1717
libs {
18-
version("checkerFramework", "3.42.0-eisop2-SNAPSHOT")
18+
version("checkerFramework", "3.42.0-eisop3-SNAPSHOT")
1919

2020
library("checkerFramework-checker", "io.github.eisop", "checker").versionRef("checkerFramework")
2121
library("checkerFramework-checker-qual", "io.github.eisop", "checker-qual").versionRef("checkerFramework")
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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 com.sun.source.tree.AssignmentTree;
18+
import com.sun.source.tree.ClassTree;
19+
import com.sun.source.tree.ExpressionTree;
20+
import com.sun.source.tree.MethodInvocationTree;
21+
import com.sun.source.tree.Tree;
22+
import java.util.List;
23+
import javax.lang.model.element.ExecutableElement;
24+
import org.checkerframework.framework.type.AnnotatedTypeFactory;
25+
import org.checkerframework.framework.type.AnnotatedTypeFormatter;
26+
import org.checkerframework.framework.type.AnnotatedTypeMirror;
27+
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
28+
import org.checkerframework.framework.util.visualize.AbstractTypeInformationPresenter;
29+
import org.checkerframework.framework.util.visualize.TypeOccurrenceKind;
30+
import org.checkerframework.javacutil.TreeUtils;
31+
32+
/**
33+
* Output "sinkType" and "sourceType" diagnostic warning messages so the conformance tests can look
34+
* for (a subset of) them.
35+
*/
36+
public final class ConformanceTypeInformationPresenter extends AbstractTypeInformationPresenter {
37+
38+
/**
39+
* Constructs a presenter for the given factory.
40+
*
41+
* @param atypeFactory the AnnotatedTypeFactory for the current analysis
42+
*/
43+
public ConformanceTypeInformationPresenter(AnnotatedTypeFactory atypeFactory) {
44+
super(atypeFactory);
45+
}
46+
47+
@Override
48+
protected AnnotatedTypeFormatter createTypeFormatter() {
49+
// Use the same type formatter as normal error messages. Look into whether a different format
50+
// would be better here.
51+
return atypeFactory.getAnnotatedTypeFormatter();
52+
}
53+
54+
@Override
55+
protected TypeInformationReporter createTypeInformationReporter(ClassTree tree) {
56+
return new ConformanceTypeInformationReporter(tree);
57+
}
58+
59+
class ConformanceTypeInformationReporter extends TypeInformationReporter {
60+
ConformanceTypeInformationReporter(ClassTree tree) {
61+
super(tree);
62+
}
63+
64+
@Override
65+
protected void reportTreeType(
66+
Tree tree, AnnotatedTypeMirror type, TypeOccurrenceKind occurrenceKind) {
67+
switch (tree.getKind()) {
68+
case ASSIGNMENT:
69+
AssignmentTree asgn = (AssignmentTree) tree;
70+
AnnotatedTypeMirror varType =
71+
genFactory != null
72+
? genFactory.getAnnotatedTypeLhs(asgn.getVariable())
73+
: atypeFactory.getAnnotatedType(asgn.getVariable());
74+
checker.reportWarning(
75+
asgn.getVariable(),
76+
"sinkType",
77+
typeFormatter.format(varType),
78+
asgn.getVariable().toString());
79+
break;
80+
case RETURN:
81+
checker.reportWarning(tree, "sinkType", typeFormatter.format(type), "return");
82+
break;
83+
case METHOD_INVOCATION:
84+
ExecutableElement calledElem = TreeUtils.elementFromUse((MethodInvocationTree) tree);
85+
String methodName = calledElem.getSimpleName().toString();
86+
AnnotatedExecutableType calledType = (AnnotatedExecutableType) type;
87+
List<? extends AnnotatedTypeMirror> params = calledType.getParameterTypes();
88+
MethodInvocationTree mit = (MethodInvocationTree) tree;
89+
List<? extends ExpressionTree> args = mit.getArguments();
90+
assert params.size() == args.size();
91+
92+
for (int i = 0; i < params.size(); ++i) {
93+
String paramName = calledElem.getParameters().get(i).getSimpleName().toString();
94+
String paramLocation = String.format("%s#%s", methodName, paramName);
95+
checker.reportWarning(
96+
tree, "sinkType", typeFormatter.format(params.get(i)), paramLocation);
97+
}
98+
break;
99+
default:
100+
// Nothing special for other trees.
101+
}
102+
103+
if (TreeUtils.isExpressionTree(tree)) {
104+
checker.reportWarning(tree, "sourceType", typeFormatter.format(type), tree.toString());
105+
}
106+
}
107+
}
108+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ final class NullSpecAnnotatedTypeFactory
122122

123123
private final AnnotatedDeclaredType javaUtilCollection;
124124

125+
private final ConformanceTypeInformationPresenter conformanceInformationPresenter;
126+
125127
final AnnotatedDeclaredType javaLangClass;
126128
final AnnotatedDeclaredType javaLangThreadLocal;
127129
final AnnotatedDeclaredType javaUtilMap;
@@ -305,6 +307,9 @@ private NullSpecAnnotatedTypeFactory(
305307
withMostConvenientWorld = this;
306308
}
307309

310+
conformanceInformationPresenter =
311+
checker.hasOption("showTypes") ? new ConformanceTypeInformationPresenter(this) : null;
312+
308313
if (!givenOtherWorld) {
309314
/*
310315
* Now the withLeastConvenientWorld and withMostConvenientWorld fields of both `this` and
@@ -1707,6 +1712,10 @@ public void postProcessClassTree(ClassTree tree) {
17071712
* type.invalid.conflicting.annos error, which I have described more in
17081713
* https://github.com/jspecify/jspecify-reference-checker/commit/d16a0231487e239bc94145177de464b5f77c8b19
17091714
*/
1715+
1716+
if (conformanceInformationPresenter != null) {
1717+
conformanceInformationPresenter.process(tree, getPath(tree));
1718+
}
17101719
}
17111720

17121721
@Override

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@
3939
* <li>"strict": Whether the checker should be a sound, strict type system. Does not imply that
4040
* implementation code is checked.
4141
* <li>"checkImpl": Whether implementation code should be checked.
42+
* <li>"showTypes": Whether to output type information for the conformance test suite.
4243
* </ol>
4344
*/
44-
@SupportedOptions({"strict", "checkImpl"})
45+
@SupportedOptions({"strict", "checkImpl", "showTypes"})
4546
public final class NullSpecChecker extends BaseTypeChecker {
4647
/*
4748
* A non-final field is ugly, but we can't create our Util instance in the constructor because the

src/test/java/tests/NullSpecTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private boolean corresponds(TestDiagnostic missing, DetailMessage unexpected) {
206206
}
207207
case "jspecify_conflicting_annotations":
208208
switch (unexpected.messageKey) {
209-
case "conflicting.annos":
209+
case "type.invalid.conflicting.annos":
210210
return true;
211211
default:
212212
return false;

tests/ConformanceTest-report.txt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
# 84 pass; 30 fail; 114 total; 73.7% score
2-
FAIL: Basic.java:28:test:expression-type:Object?:nullable
3-
FAIL: Basic.java:28:test:sink-type:Object!:return
1+
# 90 pass; 24 fail; 114 total; 78.9% score
2+
PASS: Basic.java:28:test:expression-type:Object?:nullable
3+
PASS: Basic.java:28:test:sink-type:Object!:return
44
PASS: Basic.java:28:test:cannot-convert:Object? to Object!
5-
FAIL: Basic.java:34:test:expression-type:Object!:nonNull
6-
FAIL: Basic.java:34:test:sink-type:Object?:return
7-
FAIL: Basic.java:41:test:sink-type:Object?:nullableObject
8-
FAIL: Basic.java:43:test:sink-type:String!:testSinkType#nonNullString
5+
PASS: Basic.java:34:test:expression-type:Object!:nonNull
6+
PASS: Basic.java:34:test:sink-type:Object?:return
7+
PASS: Basic.java:41:test:sink-type:Object?:nullableObject
8+
PASS: Basic.java:43:test:sink-type:String!:testSinkType#nonNullString
99
FAIL: Basic.java:49:test:expression-type:List!<capture of ? extends String?>:nullableStrings
1010
PASS: Basic.java: no unexpected facts
1111
PASS: Irrelevant.java:28:test:irrelevant-annotation:Nullable

0 commit comments

Comments
 (0)