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

Improve Map.merge annotations. #14

Merged
merged 2 commits into from
Jun 11, 2023
Merged

Improve Map.merge annotations. #14

merged 2 commits into from
Jun 11, 2023

Conversation

cpovirk
Copy link
Collaborator

@cpovirk cpovirk commented Jun 8, 2023

  1. Add @Nullable to the method return type and to the "return-type" type
    argument of remappingFunction.

    We had been trying to get by without, requiring users to suppress where
    needed. But our philosophy has shifted more toward "If you want to be
    able to put null in or get null out, then it needs to be @Nullable."

    This change brings us closer to upstream, which uses @PolyNull.
  2. Add @NonNull to value parameter and to the "input" types argument of
    remappingFunction.

…rn type.

We had been trying to get by without, requiring users to suppress where
needed. But our philosophy has shifted more toward "If you want to be
able to put null in or get null out, then it needs to be `@Nullable`."

This change brings us closer to upstream, which uses `@PolyNull`.
Copy link
Collaborator

@wmdietl wmdietl left a comment

Choose a reason for hiding this comment

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

Adding the @Nullable looks good. It would be interesting to learn how many new warnings you get from the @Nullable on the return type. That would be another case for adding @PolyNull.
I do have a question about the value parameter.

src/java.base/share/classes/java/util/Map.java Outdated Show resolved Hide resolved
@wmdietl
Copy link
Collaborator

wmdietl commented Jun 9, 2023

In the PR title, should on the method and function return type be on the parameter and return type or something else?

@cpovirk
Copy link
Collaborator Author

cpovirk commented Jun 9, 2023

It would be interesting to learn how many new warnings you get from the @Nullable on the return type. That would be another case for adding @PolyNull.

Realistically, the answer is likely to be "zero," just because we don't use these stubs that widely yet.

Well, OK, it's not going to be "zero" because I'm going to have to update some implementations of these methods in com.google.common to match. Well, OK, those failures are technically going to come from the @Nullable inside the parameter type, not the one on the return type :-P

@cpovirk
Copy link
Collaborator Author

cpovirk commented Jun 9, 2023

In the PR title, should on the method and function return type be on the parameter and return type or something else?

"function return type" was meant to refer to the final type argument of remappingFunction.

I'll see what I can do about that as I rework the description as a whole to cover the newly broadened scope from the @NonNull annotations discussed here and in eisop/jdk#41.

@cpovirk cpovirk changed the title Annotate Map.merge with @Nullable on the method and function return type. Improve Map.merge annotations. Jun 9, 2023
Copy link
Collaborator

@wmdietl wmdietl left a comment

Choose a reason for hiding this comment

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

Thanks, looks good!

@wmdietl wmdietl merged commit 788ec9d into main Jun 11, 2023
1 check passed
@wmdietl wmdietl deleted the mapmerge branch June 11, 2023 17:38
@cpovirk
Copy link
Collaborator Author

cpovirk commented Jun 12, 2023

It would be interesting to learn how many new warnings you get from the @Nullable on the return type. That would be another case for adding @PolyNull.

If I make the change for our Checker Framework users, I do see errors. It's not many, simply because there merge is mildly rare and Checker Framework usage is still a minority in our depot. But I skimmed over almost every call that we have to merge in Checker-Framework-checked code (at least in files that have a suppression somewhere), and it's fair to say that essentially every merge call wants to return a non-nullable value :)

I still feel OK with this PR, at least for now :) I could imagine taking a different path by avoiding @Nullable in computeIfAbsent, which is more commonly used and which is "even more obviously" intended primarily for cases in which you want to insert a key. But I don't know really know what I think. I think the merge thing was prompted by some Kotlin work, so maybe we'll see if the Kotlin work touches on computeIfAbsent, too.

cpovirk added a commit that referenced this pull request Jun 12, 2023
It's redundant there, since the value type is already non-nullable.

I overlooked this in #14 and noticed
it only later in eisop/jdk#45.
@cpovirk
Copy link
Collaborator Author

cpovirk commented Jul 19, 2023

We might someday care that Kotlin avoids @Nullable on computeIfAbsent, even as it uses it on compute, computeIfPresent, and merge:

https://github.com/JetBrains/kotlin/blob/1391c983a29c52996ca85b747383edfbfb24997c/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/typeEnhancement/predefinedEnhancementInfo.kt#L109-L110

@cpovirk
Copy link
Collaborator Author

cpovirk commented Jul 26, 2023

...and a week later, we're seeing actual problems from the difference between our signatures and Kotlin's :)

cpovirk added a commit that referenced this pull request Aug 15, 2023
…work and Kotlin do.

In contrast, for _`computeIfAbsent`_, I've kept the signature _without_
`@Nullable` on the function result type and method return type. This is
far more convenient for most callers, and it just might not cause
problems for anyone: Callers of `computeIfAbsent` might _always_ provide
a function that elects to insert a value (as expressed by returning a
non-null value).

I think this gives us the signatures that we'll want for the long term
for `Map`, though of course we'd revisit if we chose to include
`@PolyNull`.

Previously:

- #14 (comment)
- #19 (comment)

Note that this PR excludes `java.util.Properties` because there are a
number of other changes that I'm going to make to it (and then
upstream).
cpovirk added a commit that referenced this pull request Aug 15, 2023
…work and Kotlin do.

In contrast, for _`computeIfAbsent`_, I've kept the signature _without_
`@Nullable` on the function result type and method return type. This is
far more convenient for most callers, and it just might not cause
problems for anyone: Callers of `computeIfAbsent` might _always_ provide
a function that elects to insert a value (as expressed by returning a
non-null value).

I think this gives us the signatures that we'll want for the long term
for `Map`, though of course we'd revisit if we chose to include
`@PolyNull`.

Previously:

- #14 (comment)
- #19 (comment)

Note that this PR excludes `java.util.Properties` because there are a
number of other changes that I'm going to make to it (and then
upstream).
@cpovirk
Copy link
Collaborator Author

cpovirk commented Aug 17, 2023

(The Kotlin problems we're seeing appear to run deeper than a mismatch between our signatures and Kotlin's, but the mismatch does appear to be part of the picture. I'm hoping to report a Kotlin bug and then link to it from here.)

Backing up to computeIfAbsent, which I've mostly been ignoring here: I just encountered one user who does use it to sometimes compute a null value. (It's possible that putIfAbsent would be OK there, but the computation function is long. I can't tell at a glance whether it's slow, but it's in graphics code, so there may be good reason to avoid even small overhead.) I'm probably going to migrate that user to compute after #32 lands.

I still feel OK about annotating computeIfAbsent to forbid nullable results, but we now have an existence proof that that could break someone's compile. (I'm not sure it could realistically break anyone at runtime, which is the more important thing, but I haven't thought it through.)

@cpovirk
Copy link
Collaborator Author

cpovirk commented Aug 18, 2023

(I'm not sure it could realistically break anyone at runtime, which is the more important thing, but I haven't thought it through.)

I think I can envision a way:

val result = concurrentMap.computeIfAbsent(key, ::unannotatedJavaFunctionThatMightReturnNull)

I don't think that's likely, but it's possible (I think... I haven't tested).

@cpovirk
Copy link
Collaborator Author

cpovirk commented Sep 12, 2023

(I just learned of another case (this time in Java) in which someone is using computeIfAbsent with a function that might return null. At least, the person I talked to seemed to be suggesting that null was possible, and the function was calling a method with a nullable return type. I didn't ask explicitly, nor experiment with the code, but it seemed pretty clear that null was a possibility.)

@cpovirk
Copy link
Collaborator Author

cpovirk commented May 2, 2024

...and a week later, we're seeing actual problems from the difference between our signatures and Kotlin's :)

To partially tie up this loose end: I've confirmed that the problem we were seeing will go away once we can get Kotlin to recognize our usages of @NonNull on Map.computeIfPresent and Map.merge (without which our signatures for those methods appear to differ from Kotlin's).

If I'd been thinking, I would have tested what would happen if I were to change our signature for Map.computeIfAbsent to differ from Kotlin's. However, I also got the impression that Kotlin's K2 compiler may have looser checks in this area in general (at least so far), so that may be mostly academic.

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.

None yet

2 participants