JDK-8319134 : Implement JEP 463: Changes to annotation processing related to dropping unnamed classes
  • Type: CSR
  • Component: core-libs
  • Sub-Component: javax.lang.model
  • Priority: P3
  • Status: Provisional
  • Resolution: Unresolved
  • Fix Versions: 22
  • Submitted: 2023-10-30
  • Updated: 2023-11-14
Related Reports
CSR :  
Relates :  
Description
Summary
-------

Remove  annotation processing APIs for unnamed classes, remove documentation related to unnamed classes from javadoc.

Problem
-------

In the second preview of this feature, support for unnamed classes has been dropped and replaced with implicit classes. The distinction between unnamed classes and implicit classes is that the implicit class syntax is purely syntactic sugar and the classes generated by the compiler are exactly like regular classes with no indication of their origin.

Solution
--------

Remove annotation processing APIs that rely on the detectability of unnamed classes and remove documentation related to unnamed classes.

Specification
-------------

`javax.lang.model.element.TypeElement::isUnnamed `is removed. This was the method was also used to detect an unnamed class. `javax.lang.model.element.TypeElement::getSimpleName` and `javax.lang.model.element.TypeElement::getQualifiedName()` have documentation removed related to unnamed classes.

`javax.annotation.processing::Filer::createSourceFile` and `javax.annotation.processing::Filer::createClassFile` have documentation removed related to unnamed classes.

```
    diff --git a/src/java.compiler/share/classes/javax/annotation/processing/Filer.java b/src/java.compiler/share/classes/javax/annotation/processing/Filer.java
    index 9ebcf2c5908..0bce817ee8e 100644
    --- a/src/java.compiler/share/classes/javax/annotation/processing/Filer.java
    +++ b/src/java.compiler/share/classes/javax/annotation/processing/Filer.java
    @@ -177,13 +177,6 @@ public interface Filer {
          * <p>Creating a source file in or for an <em>unnamed</em> package in a <em>named</em>
          * module is <em>not</em> supported.
          *
    -     * <p>If the environment is configured to support {@linkplain
    -     * TypeElement#isUnnamed unnamed classes}, the name argument is
    -     * used to provide the leading component of the name used for the
    -     * output file. For example {@code filer.createSourceFile("Foo")}
    -     * to create an unnamed class hosted in {@code Foo.java}. All
    -     * unnamed classes must be in an unnamed package.
    -     *
          * @apiNote To use a particular {@linkplain
          * java.nio.charset.Charset charset} to encode the contents of the
          * file, an {@code OutputStreamWriter} with the chosen charset can
    @@ -263,13 +256,6 @@ JavaFileObject createSourceFile(CharSequence name,
          * <p>Creating a class file in or for an <em>unnamed</em> package in a <em>named</em>
          * module is <em>not</em> supported.
          *
    -     * <p>If the environment is configured to support {@linkplain
    -     * TypeElement#isUnnamed unnamed classes}, the name argument is
    -     * used to provide the leading component of the name used for the
    -     * output file. For example {@code filer.createClassFile("Foo")} to
    -     * create an unnamed class hosted in {@code Foo.class}. All unnamed
    -     * classes must be in an unnamed package.
    -     *
          * @apiNote To avoid subsequent errors, the contents of the class
          * file should be compatible with the {@linkplain
          * ProcessingEnvironment#getSourceVersion source version} being
    diff --git a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java
    index d430e33e591..a8c3b91f8b5 100644
    --- a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java
    +++ b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java
    @@ -155,7 +155,7 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
         /**
          * Returns the fully qualified name of this class or interface
          * element.  More precisely, it returns the <i>canonical</i> name.
    -     * For local, anonymous, and {@linkplain #isUnnamed() unnamed} classes, which do not have canonical
    +     * For local, and anonymous classes, which do not have canonical
          * names, an {@linkplain Name##empty_name empty name} is
          * returned.
          *
    @@ -171,7 +171,6 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
          *
          * @see Elements#getBinaryName
          * @jls 6.7 Fully Qualified Names and Canonical Names
    -     * @jls 7.3 Compilation Units
          */
         Name getQualifiedName();
 
    @@ -181,10 +180,6 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
          * For an anonymous class, an {@linkplain Name##empty_name empty
          * name} is returned.
          *
    -     * For an {@linkplain #isUnnamed() unnamed} class, a name matching
    -     * the base name of the hosting file, minus any extension, is
    -     * returned.
    -     *
          * @return the simple name of this class or interface,
          * an empty name for an anonymous class
          *
    @@ -192,22 +187,6 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable
         @Override
         Name getSimpleName();
 
    -    /**
    -     * {@return {@code true} if this is an unnamed class and {@code
    -     * false} otherwise}
    -     *
    -     * @implSpec
    -     * The default implementation of this method returns {@code false}.
    -     *
    -     * @jls 7.3 Compilation Units
    -     * @since 21
    -     */
    -    @PreviewFeature(feature=PreviewFeature.Feature.UNNAMED_CLASSES,
    -                    reflective=true)
    -    default boolean isUnnamed() {
    -        return false;
    -    }
    -
         /**
          * Returns the direct superclass of this class or interface element.
          * If this class or interface element represents an interface or the class
```

PDFs of changed API javadocs enclosed.

Comments
My summary of what I think the JEP 445 -> 463 update for the annotation processing API should be is: * Some explicit mention of how to create implicit classes should be maintained in the Filer * TypeElement.isUnnamed() should be renamed to TypeElement.isImplicit() In a Pascal-ian sense of "this note is longer than usual because I lack the time to make it short," I will provide a rationale for this guidance. IMO, the primary issue with the framing used in JEP 445 was not in defining a new category, new flavor of compilation unit (the unnamed class), but in using nomenclature for that concept that runs into the fraught twisty maze of all-different class names in the Java platform. Just in the Java Language Specification we have fully-qualified names, which are similar to, but distinct from, canonical names and we have anonymous classes, which don't have a name, but don't have a name in a different way than unnamed classes don't have a name. The situation is worse for core reflection where there are binary names, simple names, type names, and so on. Given my previous comment during the JEP 445 CSR review: > For future iterations of this work, I encourage consideration of a different term for the "unnamed classes", perhaps "implicit classes", but something other than "unnamed." > > The classes very much have names in more than a technical sense, like anonymous classes, as the name of the resulting class is commonly used to launch the program, etc. > > An "implicit class" also has an easier story with reflective APIs like core reflection. java.lang.Class already has getName(), getSimpleName(), getTypeName(), getCanonicalName(), which includes discussion of "binary names" vs "type names as represented in class files." It is awkward to explain that an unnamed class has each of these kinds of names and it is the same name you would get following the usual conventions, Foo.java compiling down to Foo.class, etc. > > Ron Pressler and Jim Laskey, please look over the various name methods in java.lang.Class to see how they should be updated to cover unnamed classes. It might be more concise to add a paragraph at the top of spec of java.lang.Class describing the expected naming behavior for unnamed classes. https://bugs.openjdk.org/browse/JDK-8306113?focusedId=14584195&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14584195 Relaying on off-list comment from May 2023 about JEP 445, I also thought it would be helpful to have a reliable predicate for determining isUnnamedClass/isImplicitClass in the reflective APIs. I'm happy to see the general direction of JEP 463. However, I think the current iteration drifts too far away from not surfacing the existence of implicit classes in the reflective APIs, especially for javax.lang.model. Before continuing with a more detailed rationale for the particular API changes I'm recommending, a digressions on design _affordances_. The classic examples of an anti-afforance is the so-called Norman door, named after Donald Norman, author of "The Design Of Everyday Things." tl;dr if you have a door that has on one side: * a flat plate * a "C" shaped handle * "PUSH" in small text and on the other side * a flat plate * a "C" shaped handle * "PULL" in small text the door is unnecessarily hard to use because the push vs pull distinction is so hard to notice that many users can make a mistake when trying to pass through the door. A strong argument can be made that in a case like the Norman door, the users who make a mistake trying to transit through the door aren't the problem, the design of the door is. (One way to improve such a design would be to remove the handle on the PUSH side -- you can only push a plate and always pull a handle.) Changing contexts, the US interstate highway system has shield-shape signs labeling roads "Route $N". While arguably some of these signs are redundant, when driving in unfamiliar areas, I've found them reassuring that I was indeed where I thought I should be. In other situations, the sign conventions help clarify confusing situations. For example, there is a stretch of highway running along Berkeley, CA that is simultaneously Route 80 East and Route 580 _West_. There is a broader context from which is labeling is sensible, but it is certainly confusing the first time one sees it and the signs help provide some guidance on how to get where you want to go. [*] Circling back to the annotation processing API, I view it as a useful affordance to users of the API to have an explicit statement concerning how to create a source file or a class file for an implicit class even such such a statement may not be strictly needed. The convention for package-info file is also the "obvious" idiom, but I believe there is value in listing it explicitly, which also reminds the reader that such entities can be created. Another benefit of an explicit statement is making it easier for other implementations to match the reference implementation's behavior and for conformance tests to have a clear mapping to a testable assertion. The presentation of the semantics of the annotation processing API has taken conscious steps to make finding relevant information users of API easier and more explicit including: * JDK-8224687: "Add clarifying overrides of Element.asType to more specific subinterfaces" in JDK 13 * JDK-8282411: "Add useful predicates to ElementKind" in JDK 19 * JDK-8289249: "Add methods to Elements for record constructors" in JDK 20 * JDK-8300857: '"State return value for Types.asElement(NoType) explicitly" in JDK 21 * JDK-8315137: "Add explicit override RecordComponentElement.asType()" in JDK 22 The annotation processing facility is a general-purpose meta-programming API that does not needed annotation to operate. As such, the higher fidelity the API can represent to the original sources, the easier the API is to use for a wider variety of tasks. The printing processor provide a useful sanity check on the modeling of new features and their support in the API as well as a task-neutral way to exercise the API on a variety of inputs. While the "just a normal class" output of the printing processing for implicit class, due to the lack of a isImplicitClass or equivalent predicate, would meet a minimum-viable-product level of functionality, we strive for a higher standard in the annotation processing API. For example, when available, repeating annotations are printed in a form that elides the container annotation (JDK-8005295: Use mandated information for printing of repeating annotations) even though repeating annotations and just a desugaring. I think the existing degree of functionality for printing unnamed classes in JDK 21 should be carried over to implicit classes in JDK 22. I'm happy to have a meeting if there are further concerns and if that is judged to be a more expeditious way to reach consensus on this CSR than continuing the discussion in comments. [*] Route 80 is an east-west road that runs across the country; Route 580 is a spur highway within California that predominantly runs east-west. The section of road in question is mostly north-south, with its east-west projection matching the labeling of Route 80 and opposite to Route 580 for several miles.
14-11-2023

[~darcy]: First, the `Filer` API supports the creation by annotation processors of new source and class files. Its `create{Source,Class}File` methods take a fully-qualified class or interface name and map it to a host-system-dependent file name. That mapping is already predictable, though intentionally not specified. What change are you asking for here? If one of these methods is used to write out an implicit class, should it somehow behave differently? Second, what value would there be in having `javax.lang.model` explicitly model implicit classes and instance main methods? The entire point of the revised approach in this second preview is to remove distinctions between implicit classes and normal top-level classes. Third, [~jlaskey] shows above that the (unspecified) `-Xprint` processor produces reasonable output for an implicit class. What more do you want?
08-11-2023

But that was then and this is now. And, then was wrong, that is why the feature was renamed to "Implicitly Declared Classes". It is not a new kind of class, it is merely a new way to declare regular classes. End of story. To make it anything else is inflating substance.
08-11-2023

The printing processor, by design, only attempts to print down to declaration boundary and not emit the interior of methods, etc. As a concrete example, for JDK 21 given the source void main() { System.out.println("Hello world"); } the printing processor emits: jdk-21/bin/javac --enable-preview --release 21 -Xprint HelloWorld.java // Unnamed class in file whose name starts with HelloWorld void main(); Note: HelloWorld.java uses preview features of Java SE 21. Note: Recompile with -Xlint:preview for details.
08-11-2023

That's questionable. Print processor already has holes. ``` class Test { void test() { class Local { } } } ``` ``` $ javac -Xprint Test.java class Test { Test(); void test(); } ``` As it is, the print processor produces a relatively similar facsimile: ``` void main() { System.out.println("Done!"); } ``` ``` $ javac --enable-preview -source 22 -Xprint Main.java final class Main { Main(); void main(); } ```
08-11-2023

PS Operationally, a reasonable test is if the printing processor can make a good facsimile of the original source's declaration structure.
07-11-2023

[~jlaskey], for the Filer API specifically, if the user wants to write out an implicit class, it is reasonable for them to have a predictable mapping from the name of the class to the resulting source or class file, especially since the mapping in a general sense is "implementation defined." There are various compiler desugarings that are nevertheless modeled in javax.lang.model, both for semantic purposes and so that an approximation to the original source can be reconstructed. For example, repeated annotations and other constructs where the mandated/synthetic information can be set.
07-11-2023

Moving to Provisional, not Approved. Given the implementation choices allowed the in the JLS, I think the javax.lang.model and javax.annotation.processing updates still need to acknowledged implicit classes and speak on how they are created and modeled, etc.
07-11-2023

Please offer some explicit recommendations. The analogy as I see it is the same as other syntactic sugaring. For example: int i = 3, j = 0; switch (i) { case 1: j = 2; break; case 2: j = 3; break; case 3: j = 4; break; } switch (i) { case 1 -> j = 2; case 2 -> j = 3; case 3 -> j = 4; } If you look at the AST of this code, the distinction goes away. int i = 3; int j = 0; switch (i) { case 1: j = 2; break; case 2: j = 3; break; case 3: j = 4; break; } switch (i) { case 1 -> { j = 2; break; } case 2 -> { j = 3; break; } case 3 -> { j = 4; break; } Same is true of implicit classes final class Main { public static void main() { } } and public static void main() { } Both have the same an AST: final class Main { Main() { super(); } public static void main() { } } There is no distinction between the two.
07-11-2023

Moving back to Provisional.
31-10-2023

Moving to Provisional, not Approved. Please also include a diff of the changes in the next round of review. Other than leaving a stray reference to "@jls 7.3 Compilation Units" in getQualifiedName(), this looks to be a clean excision of the prior API changes.
30-10-2023