Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infer generics for assignments #1131

Open
wants to merge 76 commits into
base: master
Choose a base branch
from
Open

Conversation

haewiful
Copy link
Contributor

  • Infer Nullability for assignments of instances with type parameters. ex) Foo<@Nullable Object> f = Foo.make(null);

  • void genericInferenceOnAssignments() in GenericMethodTests.java

@msridhar
Copy link
Collaborator

@haewiful thanks for this! It looks like CI fails on the :nullaway:buildWithNullAway task:

https://github.com/uber/NullAway/actions/runs/12978381939/job/36192901792?pr=1131#step:5:606

I suggest looking at the code it is crashing on and trying to write a unit test that causes the same failure, which will make it easier to debug.

Copy link

codecov bot commented Feb 3, 2025

Codecov Report

Attention: Patch coverage is 87.12871% with 13 lines in your changes missing coverage. Please review.

Project coverage is 88.13%. Comparing base (baf8f77) to head (3053e4d).
Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...a/com/uber/nullaway/generics/InferTypeVisitor.java 81.57% 2 Missing and 5 partials ⚠️
...ava/com/uber/nullaway/generics/GenericsChecks.java 88.88% 1 Missing and 5 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1131      +/-   ##
============================================
- Coverage     88.16%   88.13%   -0.04%     
- Complexity     2281     2307      +26     
============================================
  Files            87       88       +1     
  Lines          7461     7557      +96     
  Branches       1491     1513      +22     
============================================
+ Hits           6578     6660      +82     
- Misses          445      448       +3     
- Partials        438      449      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Tree rhsTree;
if (tree instanceof VariableTree) {
VariableTree varTree = (VariableTree) tree;
rhsTree = varTree.getInitializer();

if (rhsTree instanceof MethodInvocationTree) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this logic is not in the right place, as it won't work for regular assignments; it only runs for variable declarations. I added a failing test to illustrate.

Copy link
Collaborator

@msridhar msridhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting there! Few more comments and another failing test

import org.jspecify.annotations.Nullable;

/** Visitor that uses two types to infer the type of type variables. */
public class InferTypeVisitor
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that returning Maps from each visit method can work, but it's a bit convoluted and also inefficient (as many short-lived maps may be allocated). Instead, I suggest making the genericNullness map an instance field of InferTypeVisitor, initialized to an empty map. The visit methods could mutate this map. And then, at the end, the caller could call a method like getGenericNullessMap() to get the result. With this approach, this class could extend Types.DefaultTypeVisitor<Void, Type>, and all the visit methods would just return null.

// 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 rhsType = getTreeType(rhsTree, config);

if (lhsType != null && rhsType != null) {
if (rhsTree instanceof MethodInvocationTree) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we move the code under the if at line 440 to also be under this if? We'd still need to store genericNullness inside inferredTypes but then we could just use it immediately here to reconstruct the inferred type.

Comment on lines 468 to 474
List<Type> keyTypeList =
genericNullness.keySet().stream()
.map(typeVar -> (Type) typeVar)
.collect(Collectors.toList());
com.sun.tools.javac.util.List<Type> from = com.sun.tools.javac.util.List.from(keyTypeList);
com.sun.tools.javac.util.List<Type> to =
com.sun.tools.javac.util.List.from(genericNullness.values());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this code, it's unclear that the from and to lists will end up in the appropriate order, where the right keys are lined up with the right values. (It might work with LinkedHashMaps but the code doesn't use those or enforce that right now.) Instead, I suggest looping over the entries of genericNullness, and building the lists one key-value pair at a time. You can build up the right list type using a ListBuffer and calling append on it, like here:

" return new Foo<>(u);",
" }",
" static void test(Foo<@Nullable Object> f1, Foo<Object> f2) {",
" // no error expected",
Copy link
Collaborator

@msridhar msridhar Feb 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test currently does not pass. I would expect no error for the first call to create, but an error for the second call to create (since the second argument is Foo<Object>). Do you agree? If so we need to fix this.

Comment on lines +1089 to +1091
new GenericsChecks()
.getGenericReturnNullnessAtInvocation(
ASTHelpers.getSymbol(tree), tree, state, config);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm, this seems suspect. If you create a new GenericsChecks object here, then the inferred types will immediately disappear. Do we need to instead somehow pass in the GenericsChecks object from the NullAway instance into this class?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants