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
@NullMarked (and its negation) includes module and package targets #34
Comments
Note that if we solve #35 then we can at least keep it down to just ONE annotation people put on every class, like @MyPreferredDefaults. |
To start enumerating those:
|
It is also very uncomfortable that a package-info present at compile time may be different or missing when a dependent is compiled, and perceived nullabilities of symbols can change. We want class files to be more self-contained than that. |
There's also the refactoring issue: if you move a class to a package with a different package-level default then you have to remember to add a class-level default to preserve the semantics. |
Very good one. It's bad enough when moving a class causes or removes warnings in that class itself, but when it changes the meaning of things in that class as seen by other classes, that's pretty bad. |
(Credit for highlighting the refactoring issue should go to Neil Fuller.) |
I will try to pull together a full summary of all the pros/cons for package-level defaults. |
I'd like to propose allowing A possible variant here is to let people just use one of the defaulting annotations on packages, but require the same or a different defaulting annotation to be placed on very top-level class in scope. I like the economy of avoiding an extra annotation but there could be confusion when the rule about top-level classes isn't followed/enforced. |
@kevin1e100 thanks, an interesting approach! It indeed solves the issue of a source file being self-contained. Will we require all checkers to check this syntactic rule? Then we still must specify what the scope is exactly, and package-splitting issue will remain. |
I think our job is to enforce it :) Adding more annotations is not a total show-stopper, but where we can make it clear for the users what's expected of them through making checkers complain, there's no risk of misunderstanding |
Another question that's connected to enforcing: what do we do if the checker encounters a class that violates the "want default for every class rule"?
I think this should be in sync with how we treat illegally annotated entities. Do we have an issue for that? |
I haven't seen an issue around what to do when encounterting illegal
annotations, so let's make one.
|
As discussed today, I think it is important to support package-level defaults since it is the only realistic way to set defaults (like the very popular non-null one) with a scope which is wide enough to avoid cluttering the codebase with class level defaults. Ease of use is important even at the prototype stage, and lack of such feature would strongly impact adoption IMO. Defining that only at module level is not an option given the very low adoption. But it is likely doable to avoid issues by introducing some constraints related to modules which are now widely adopted best practices even by libraries that does not use modules yet. In practice, most of the JVM ecosystem tries to be a good citizen and avoid providing classes with the same package in 2 different JARs. This seems to be a pattern much more widely seen than the module support itself, so I tend to think we could provide that as a rule to follow in the specification. For the issues related to tests, I think this should not be a showstopper since the scope of the specification is limited to public API. While tests have of course a public API, it is not part of the application or library public API so could maybe be excluded from the scope of the annotations. |
One concern about package defaults that I thought I remembered seeing was from someone working on incremental compilation -- presumably an IDE, perhaps even IntelliJ? The concern was over recognizing that files in a package should be recompiled when its package-info is changed. I haven't managed to dig up the reference to this, though. [ edit: maybe #38 (comment) ] |
If we were to decide against support for package-level defaults, presumably that decision would apply not just to nullness but to all annotations. That might lead to a world in which libraries like Guava end up starting every class like this: @DefaultNotNull
@MustUseResult Not to mention common but not universal annotations, which could include: @Immutable
@GwtCompatible
@PublishParameterNames I wonder if this leads us down the road of wanting... @GuavaDefaultAnnotations // defined to imply our preferred set :( [edit: Oops, I see that that's #35, mentioned above.] |
Is using compiler flags to set defaults even less attractive than using |
Oh, compiler flags are probably a non-starter because of IDEs. (They of course also make individual source files not self-contained, though that's true of a package-level default, too.) |
From scanning this issue I see three problems mentioned:
Did I miss any problems? For the last item I'd like to read some more background / motivation. What tool would need to analyze a class file without the context of its containing artifact? What's the use case? |
Minor correction: I believe the desire was to have self-contained source files. There was also a concern around refactorings, i.e., moving source files out of or into a package (or module). This is arguably related to the self-contained source file concern, but refactorings could possibly also be made aware. Finally there's also the concern of forgetting to include a package-info.java file in a Jar. This can for instance somewhat easily happen in Google's build system, Bazel.
I think the base requirements document discusses this a bit. The main concern here, I believe, are humans reading source files and being able to understand them without looking elsewhere. |
Thanks, this concern I understand, but I believe Java is already far beyond the point where all relevant information is explicit in each source file. Most prominently: any use of type inference removes essential type information from the source code. This is where IDEs help: by providing the elided information on demand. I think it safe to assume that users have gotten used to receiving type information via text hovers, code lenses etc. rather from the source code. By the same token, implicit nullness annotations are integrated into the same existing IDE functionality. In fact, a text hover to show the effective signature of a method will be easier to consult, than even scrolling to the top of a source file to find the nullness regime of this file. |
Even moving a method from one class to another may require adjustment of nullness annotations. So, yes, this is an issue to be handled by refactoring implementations. |
I assume you meant to say |
Allow me to put some words in Kevin's mouth while he's busy at JVMLS and can't defend himself :) I think his concern isn't so much with Bazel per se, which will always include a
This ends up producing a |
Thanks, @cpovirk. That's exactly the information about Bazel that I was missing.
You may count me as a member of that band. Put differently, the build issue is a special variant of the split-package problem, nothing that would normally happen, when ppl avoid split packages is the first place, right? In #30 I argued why I prefer to ship on optimal nullness strategy over helping people to maintain their split packages for many more years to come. Let me add that split packages not only prevent those artifacts themselves from being "modularized", it also renders such libraries unusable in any modularized application, because those would need to consume the library as a set of automatic modules. But artifacts with split packages are illegal also as auto modules. |
Right. Split packages are the problem (as they are elsewhere). |
Protocol from 2019-08-01 mentions:
Solution use by Eclipse: don't put any |
(cross-reference to #50) |
@stephan-herrmann I had never thought of that. That may indeed be a solution. I guess it's either that or use "multi-release jars", which for all I know might not work as well as promised. |
My understanding was that a missing I tried this with the following two files: A @pkg.AnnoModule
module Test {
} and a package pkg;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
// @Target({ElementType.MODULE})
public @interface AnnoModule {} If I run
If I add the explicit Reading through https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/annotation/Target.java#L34:
I think that comment was not correctly updated when the To me the current behavior of javac seems desirable, because it treats a missing However, the javadoc needs to be updated one way or the other. |
IDEA-273004 reports that IntelliJ has some difficulty with
|
I'm told that it works OK with NetBeans. If anyone has Eclipse already installed, here's how to get a jar to test with:
That produces a [edit: Or nowadays, just download https://repo1.maven.org/maven2/org/jspecify/jspecify/0.2.0/jspecify-0.2.0.jar.] |
Switching to a separate concern about large-scope defaults: Something reminded me that a Java package or module may contain both Java and Kotlin code. That means that a
|
To spell out something that I'm now realizing people have been referring to all along: While it's true that one obstacle to putting [edit: The following is overstated: See the correction in my next post.] (And as noted above, some people have constructed builds that make it difficult to adopt modules. Often that's because of split packages, but it can also arise with tools that merge multiple jars into one -- and probably in other cases that I haven't thought about, since I'm only generally familiar with all this. Such builds are already taking some risks: A module-level I wouldn't call that sufficient reason to not support module-level annotations, but it's something for library authors to keep in mind when deciding at what scope to annotate. [edit: An attempted GitHub search for annotations with |
Sorry, that was overstated: It's easier for users to "use modules" than I thought: They don't need to write a |
(Module-specific topics to #222 now.) |
Propose: we will include MODULE and PACKAGE in the targets, and
Please view this issue on github and add a comment or even just an emoji reaction! to express your feedback. |
Looks like we have consensus. |
I made an accidental discovery: I could use $ tail -n +1 *.java
==> Anno.java <==
import static java.lang.annotation.ElementType.MODULE;
import java.lang.annotation.Target;
@Target(MODULE)
@interface Anno {}
==> Foo.java <==
class Foo {
@Anno
void go() {}
}
$ javac Foo.java Anno.java
# no errors?!
$ javac -version
javac 11.0.13 I do get the expected error with a newer javac:
I didn't immediately find a bug about this upstream. [edit: But it looks like it was fixed in JDK 16.] Anyway, that's fine because now we allow The not-as-fine part is that this same bug also allows I don't think that rises to the level that should make us actually avoid |
The current, working decision, as of 2022-03-20, is that users can use The argument for changing this decision is that the pitfalls are interesting, including the fact that package-level annotations apply to split packages, including tests, and the fact that module-level annotations apply only when the artifact is installed in the module-path by clients, and not in the classpath. However, because the pitfalls are not JSpecify-specific, we should allow the obvious application at these levels. This must be decided before version 1.0 of the jar. I propose the current decision be finalized for 1.0. If you agree, please add a thumbs-up emoji (👍) to this comment. If you disagree, please add a thumbs-down emoji (👎) to this comment and briefly explain your disagreement. Please only add a thumbs-down if you feel you can make a strong case why this decision will be materially worse for users or tool providers than an alternative. Results: Seven 👍; no other votemoji. |
I agree (and I have given my thumbs up), but I feel obligated call attention to one more pitfall that I don't think came up on this particular thread: Some users expect for annotations on a package to apply to subpackages, or at least they wonder if that might be the behavior. I expect us to finalize our working decision that annotations do not apply to subpackages (#8). Regardless of what we decide, some users will have different expectations than that. I think that's unfortunate but not a reason to pull back from supporting package-level annotations at all. Please still see the proposal and vote above! |
Decision: |
Filing this so we can punt on it. [edit: haha no]
Trying to accommodate package- or module-level defaults is fraught with issues, so for now, we are going to require an annotation like
@DefaultNotNull
to appear on every class.We know people aren't going to like this. So this issue is about continuing to try to figure something out eventually.
The text was updated successfully, but these errors were encountered: