8306112: Implementation of JEP 445: Unnamed Classes and Instance Main Methods (Preview)

8308613: javax.lang.model updates for JEP 445 (preview)
8308913: Update core reflection for JEP 445 (preview)

Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org>
Co-authored-by: Joe Darcy <darcy@openjdk.org>
Co-authored-by: Jan Lahoda <jlahoda@openjdk.org>
Co-authored-by: Jim Laskey <jlaskey@openjdk.org>
Co-authored-by: Adam Sotona <asotona@openjdk.org>
Reviewed-by: mcimadamore, vromero, darcy
This commit is contained in:
Jim Laskey 2023-06-05 18:45:39 +00:00
parent e970ddbc60
commit 98b53c06cf
40 changed files with 1654 additions and 176 deletions

View File

@ -98,6 +98,7 @@ define SetupInterimModule
EXCLUDES := sun javax/tools/snippet-files, \
EXCLUDE_FILES := $(TOPDIR)/src/$1/share/classes/module-info.java \
$(TOPDIR)/src/$1/share/classes/javax/tools/ToolProvider.java \
$(TOPDIR)/src/$1/share/classes/com/sun/tools/javac/launcher/Main.java \
Standard.java, \
EXTRA_FILES := $(BUILDTOOLS_OUTPUTDIR)/gensrc/$1.interim/module-info.java \
$($1.interim_EXTRA_FILES), \

View File

@ -70,8 +70,10 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import jdk.internal.javac.PreviewFeature;
import jdk.internal.loader.BootLoader;
import jdk.internal.loader.BuiltinClassLoader;
import jdk.internal.misc.PreviewFeatures;
import jdk.internal.misc.Unsafe;
import jdk.internal.module.Resources;
import jdk.internal.reflect.CallerSensitive;
@ -81,6 +83,7 @@ import jdk.internal.reflect.Reflection;
import jdk.internal.reflect.ReflectionFactory;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import sun.invoke.util.Wrapper;
import sun.reflect.generics.factory.CoreReflectionFactory;
import sun.reflect.generics.factory.GenericsFactory;
@ -157,7 +160,8 @@ import sun.reflect.misc.ReflectUtil;
* other members are the classes and interfaces whose declarations are
* enclosed within the top-level class declaration.
*
* <p> A class or interface created by the invocation of
* <h2><a id=hiddenClasses>Hidden Classes</a></h2>
* A class or interface created by the invocation of
* {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...)
* Lookup::defineHiddenClass} is a {@linkplain Class#isHidden() <em>hidden</em>}
* class or interface.
@ -185,6 +189,31 @@ import sun.reflect.misc.ReflectUtil;
* a class or interface is hidden has no bearing on the characteristics
* exposed by the methods of class {@code Class}.
*
* <h2><a id=unnamedClasses>Unnamed Classes</a></h2>
*
* A {@code class} file representing an {@linkplain #isUnnamedClass unnamed class}
* is generated by a Java compiler from a source file for an unnamed class.
* The {@code Class} object representing an unnamed class is top-level,
* {@linkplain #isSynthetic synthetic}, and {@code final}. While an
* unnamed class does <em>not</em> have a name in its Java source
* form, several of the name-related methods of {@code java.lang.Class}
* do return non-null and non-empty results for the {@code Class}
* object representing an unnamed class.
*
* Conventionally, a Java compiler, starting from a source file for an
* unnamed class, say {@code HelloWorld.java}, creates a
* similarly-named {@code class} file, {@code HelloWorld.class}, where
* the class stored in that {@code class} file is named {@code
* "HelloWorld"}, matching the base names of the source and {@code
* class} files.
*
* For the {@code Class} object of an unnamed class {@code
* HelloWorld}, the methods to get the {@linkplain #getName name} and
* {@linkplain #getTypeName type name} return results
* equal to {@code "HelloWorld"}. The {@linkplain #getSimpleName
* simple name} of such an unnamed class is the empty string and the
* {@linkplain #getCanonicalName canonical name} is {@code null}.
*
* @param <T> the type of the class modeled by this {@code Class}
* object. For example, the type of {@code String.class} is {@code
* Class<String>}. Use {@code Class<?>} if the class being modeled is
@ -1717,7 +1746,7 @@ public final class Class<T> implements java.io.Serializable,
/**
* Returns the simple name of the underlying class as given in the
* source code. An empty string is returned if the underlying class is
* {@linkplain #isAnonymousClass() anonymous}.
* {@linkplain #isAnonymousClass() anonymous} or {@linkplain #isUnnamedClass() unnamed}.
* A {@linkplain #isSynthetic() synthetic class}, one not present
* in source code, can have a non-empty name including special
* characters, such as "{@code $}".
@ -1730,6 +1759,9 @@ public final class Class<T> implements java.io.Serializable,
* @since 1.5
*/
public String getSimpleName() {
if (isUnnamedClass()) {
return "";
}
ReflectionData<T> rd = reflectionData();
String simpleName = rd.simpleName;
if (simpleName == null) {
@ -1779,6 +1811,7 @@ public final class Class<T> implements java.io.Serializable,
* <ul>
* <li>a {@linkplain #isLocalClass() local class}
* <li>a {@linkplain #isAnonymousClass() anonymous class}
* <li>an {@linkplain #isUnnamedClass() unnamed class}
* <li>a {@linkplain #isHidden() hidden class}
* <li>an array whose component type does not have a canonical name</li>
* </ul>
@ -1798,6 +1831,9 @@ public final class Class<T> implements java.io.Serializable,
* @since 1.5
*/
public String getCanonicalName() {
if (isUnnamedClass()) {
return null;
}
ReflectionData<T> rd = reflectionData();
String canonicalName = rd.canonicalName;
if (canonicalName == null) {
@ -1832,12 +1868,33 @@ public final class Class<T> implements java.io.Serializable,
}
}
/**
* {@return {@code true} if and only if the underlying class
* is an unnamed class}
*
* @apiNote
* An unnamed class is not an {@linkplain #isAnonymousClass anonymous class}.
*
* @since 21
*
* @jls 7.3 Compilation Units
*/
@PreviewFeature(feature=PreviewFeature.Feature.UNNAMED_CLASSES,
reflective=true)
public boolean isUnnamedClass() {
return PreviewFeatures.isEnabled() && isSynthetic()
&& isTopLevelClass()
&& Modifier.isFinal(getModifiers());
}
/**
* Returns {@code true} if and only if the underlying class
* is an anonymous class.
*
* @apiNote
* An anonymous class is not a {@linkplain #isHidden() hidden class}.
* An anonymous class is not an {@linkplain #isUnnamedClass() unnamed class}.
*
* @return {@code true} if and only if this class is an anonymous class.
* @since 1.5

View File

@ -72,6 +72,8 @@ public @interface PreviewFeature {
STRING_TEMPLATES,
@JEP(number=443, title="Unnamed Patterns and Variables")
UNNAMED,
@JEP(number=445, title="Unnamed Classes and Instance Main Methods")
UNNAMED_CLASSES,
/**
* A key for testing.
*/

View File

@ -0,0 +1,169 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.misc;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class MainMethodFinder {
private static boolean correctArgs(Method method) {
int argc = method.getParameterCount();
return argc == 0 || argc == 1 && method.getParameterTypes()[0] == String[].class;
}
/**
* Gather all the "main" methods in the class hierarchy.
*
* @param refc the main class or super class
* @param mains accumulated main methods
* @param isMainClass the class is the main class and not a super class
*/
private static void gatherMains(Class<?> refc, List<Method> mains, boolean isMainClass) {
if (refc != null && refc != Object.class) {
for (Method method : refc.getDeclaredMethods()) {
int mods = method.getModifiers();
// Must be named "main", public|protected|package-private, not synthetic (bridge) and either
// no arguments or one string array argument. Only statics in the Main class are acceptable.
if ("main".equals(method.getName()) &&
!method.isSynthetic() &&
!Modifier.isPrivate(mods) &&
correctArgs(method) &&
(isMainClass || !Modifier.isStatic(mods)))
{
mains.add(method);
}
}
gatherMains(refc.getSuperclass(), mains, false);
}
}
/**
* Comparator for two methods.
* Priority order is;
* sub-class < super-class.
* static < non-static,
* string arg < no arg and
*
* @param a first method
* @param b second method
*
* @return -1, 0 or 1 to represent higher priority. equals priority or lesser priority.
*/
private static int compareMethods(Method a, Method b) {
Class<?> aClass = a.getDeclaringClass();
Class<?> bClass = b.getDeclaringClass();
if (aClass != bClass) {
if (bClass.isAssignableFrom(aClass)) {
return -1;
} else {
return 1;
}
}
int aMods = a.getModifiers();
int bMods = b.getModifiers();
boolean aIsStatic = Modifier.isStatic(aMods);
boolean bIsStatic = Modifier.isStatic(bMods);
if (aIsStatic && !bIsStatic) {
return -1;
} else if (!aIsStatic && bIsStatic) {
return 1;
}
int aCount = a.getParameterCount();
int bCount = b.getParameterCount();
if (bCount < aCount) {
return -1;
} else if (aCount < bCount) {
return 1;
}
return 0;
}
/**
* Return the traditional main method or null if not found.
*
* @param mainClass main class
*
* @return main method or null
*/
private static Method getTraditionalMain(Class<?> mainClass) {
try {
Method traditionalMain = mainClass.getMethod("main", String[].class);
int mods = traditionalMain.getModifiers();
if (Modifier.isStatic(mods) && Modifier.isPublic(mods) && traditionalMain.getReturnType() == void.class) {
return traditionalMain;
}
} catch (NoSuchMethodException ex) {
// not found
}
return null;
}
/**
* {@return priority main method if none found}
*
* @param mainClass main class
*
* @throws NoSuchMethodException when not preview and no method found
*/
public static Method findMainMethod(Class<?> mainClass) throws NoSuchMethodException {
boolean isTraditionMain = !PreviewFeatures.isEnabled();
if (isTraditionMain) {
return mainClass.getMethod("main", String[].class);
}
List<Method> mains = new ArrayList<>();
gatherMains(mainClass, mains, true);
if (mains.isEmpty()) {
throw new NoSuchMethodException("No main method found");
}
if (1 < mains.size()) {
mains.sort(MainMethodFinder::compareMethods);
}
Method mainMethod = mains.get(0);
Method traditionalMain = getTraditionalMain(mainClass);
if (traditionalMain != null && !traditionalMain.equals(mainMethod)) {
System.err.println("WARNING: \"" + mains.get(0) + "\" chosen over \"" + traditionalMain + "\"");
}
return mains.get(0);
}
}

View File

@ -48,6 +48,7 @@ import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
@ -78,6 +79,8 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.util.OperatingSystem;
import jdk.internal.misc.MainMethodFinder;
import jdk.internal.misc.PreviewFeatures;
import jdk.internal.misc.VM;
import jdk.internal.module.ModuleBootstrap;
import jdk.internal.module.Modules;
@ -843,11 +846,32 @@ public final class LauncherHelper {
return false;
}
/*
* main type flags
*/
private static final int MAIN_WITHOUT_ARGS = 1;
private static final int MAIN_NONSTATIC = 2;
private static int mainType = 0;
/*
* Return type so that launcher invokes the correct main
*/
public static int getMainType() {
return mainType;
}
private static void setMainType(Method mainMethod) {
int mods = mainMethod.getModifiers();
boolean isStatic = Modifier.isStatic(mods);
boolean noArgs = mainMethod.getParameterCount() == 0;
mainType = (isStatic ? 0 : MAIN_NONSTATIC) | (noArgs ? MAIN_WITHOUT_ARGS : 0);
}
// Check the existence and signature of main and abort if incorrect
static void validateMainClass(Class<?> mainClass) {
Method mainMethod = null;
try {
mainMethod = mainClass.getMethod("main", String[].class);
mainMethod = MainMethodFinder.findMainMethod(mainClass);
} catch (NoSuchMethodException nsme) {
// invalid main or not FX application, abort with an error
abort(null, "java.launcher.cls.error4", mainClass.getName(),
@ -863,16 +887,42 @@ public final class LauncherHelper {
}
}
setMainType(mainMethod);
/*
* getMethod (above) will choose the correct method, based
* findMainMethod (above) will choose the correct method, based
* on its name and parameter type, however, we still have to
* ensure that the method is static and returns a void.
* ensure that the method is static (non-preview) and returns a void.
*/
int mod = mainMethod.getModifiers();
if (!Modifier.isStatic(mod)) {
int mods = mainMethod.getModifiers();
boolean isStatic = Modifier.isStatic(mods);
boolean isPublic = Modifier.isPublic(mods);
boolean noArgs = mainMethod.getParameterCount() == 0;
if (!PreviewFeatures.isEnabled()) {
if (!isStatic || !isPublic || noArgs) {
abort(null, "java.launcher.cls.error2", "static",
mainMethod.getDeclaringClass().getName());
}
}
if (!isStatic) {
if (mainClass.isMemberClass() && !Modifier.isStatic(mainClass.getModifiers())) {
abort(null, "java.launcher.cls.error9",
mainMethod.getDeclaringClass().getName());
}
try {
Constructor<?> constructor = mainClass.getDeclaredConstructor();
if (Modifier.isPrivate(constructor.getModifiers())) {
abort(null, "java.launcher.cls.error8",
mainMethod.getDeclaringClass().getName());
}
} catch (Throwable ex) {
abort(null, "java.launcher.cls.error8",
mainMethod.getDeclaringClass().getName());
}
}
if (mainMethod.getReturnType() != java.lang.Void.TYPE) {
abort(null, "java.launcher.cls.error3",
mainMethod.getDeclaringClass().getName());

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@ -236,6 +236,13 @@ java.launcher.cls.error6=\
java.launcher.cls.error7=\
Error: Unable to initialize main class {0}\n\
Caused by: {1}: {2}
java.launcher.cls.error8=\
Error: no non-private zero argument constructor found in class {0}\n\
remove private from existing constructor or define as:\n\
\ public {0}()
java.launcher.cls.error9=\
Error: non-static inner class {0} constructor can not be invoked \n\
make inner class static or move inner class out to separate source file
java.launcher.jar.error1=\
Error: An unexpected error occurred while trying to open file {0}
java.launcher.jar.error2=manifest not found in {0}

View File

@ -401,8 +401,10 @@ JavaMain(void* _args)
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
jobjectArray mainArgs;
jmethodID mainID;
jmethodID constructor;
jobject mainObject;
int ret = 0;
jlong start = 0, end = 0;
@ -539,12 +541,55 @@ JavaMain(void* _args)
* is not required. The main method is invoked here so that extraneous java
* stacks are not in the application stack trace.
*/
#define MAIN_WITHOUT_ARGS 1
#define MAIN_NONSTATIC 2
jclass helperClass = GetLauncherHelperClass(env);
jmethodID getMainType = (*env)->GetStaticMethodID(env, helperClass,
"getMainType",
"()I");
CHECK_EXCEPTION_NULL_LEAVE(getMainType);
int mainType = (*env)->CallStaticIntMethod(env, helperClass, getMainType);
CHECK_EXCEPTION_LEAVE(mainType);
switch (mainType) {
case 0: {
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* Invoke main method. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
break;
}
case MAIN_WITHOUT_ARGS: {
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"()V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallStaticVoidMethod(env, mainClass, mainID);
break;
}
case MAIN_NONSTATIC: {
constructor = (*env)->GetMethodID(env, mainClass, "<init>", "()V");
CHECK_EXCEPTION_NULL_LEAVE(constructor);
mainObject = (*env)->NewObject(env, mainClass, constructor);
CHECK_EXCEPTION_NULL_LEAVE(mainObject);
mainID = (*env)->GetMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallVoidMethod(env, mainObject, mainID, mainArgs);
break;
}
case MAIN_NONSTATIC | MAIN_WITHOUT_ARGS: {
constructor = (*env)->GetMethodID(env, mainClass, "<init>", "()V");
CHECK_EXCEPTION_NULL_LEAVE(constructor);
mainObject = (*env)->NewObject(env, mainClass, constructor);
CHECK_EXCEPTION_NULL_LEAVE(mainObject);
mainID = (*env)->GetMethodID(env, mainClass, "main",
"()V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
(*env)->CallVoidMethod(env, mainObject, mainID);
break;
}
}
/*
* The launcher's exit code (in the absence of calls to

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -28,6 +28,7 @@ package javax.annotation.processing;
import javax.tools.JavaFileManager;
import javax.tools.*;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import java.io.IOException;
@ -176,6 +177,13 @@ 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
@ -255,6 +263,13 @@ public interface Filer {
* <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

View File

@ -25,6 +25,8 @@
package javax.lang.model.element;
import jdk.internal.javac.PreviewFeature;
import java.util.List;
import javax.lang.model.type.*;
import javax.lang.model.util.*;
@ -147,7 +149,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 and anonymous classes, which do not have canonical
* For local, anonymous, and {@linkplain #isUnnamed() unnamed} classes, which do not have canonical
* names, an {@linkplain Name##empty_name empty name} is
* returned.
*
@ -163,6 +165,7 @@ 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();
@ -172,6 +175,10 @@ 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
*
@ -179,6 +186,22 @@ 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

View File

@ -444,6 +444,9 @@ public class ClassFinder {
if (c.members_field == null) {
try {
c.complete();
if ((c.flags_field & UNNAMED_CLASS) != 0) {
syms.removeClass(ps.modle, flatname);
}
} catch (CompletionFailure ex) {
if (absent) {
syms.removeClass(ps.modle, flatname);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -123,6 +123,10 @@ public class Flags {
*/
public static final int HASINIT = 1<<18;
/** Class is a unnamed top level class.
*/
public static final int UNNAMED_CLASS = 1<<19;
/** Flag is set for compiler-generated anonymous method symbols
* that `own' an initializer block.
*/
@ -490,6 +494,7 @@ public class Flags {
ANNOTATION(Flags.ANNOTATION),
DEPRECATED(Flags.DEPRECATED),
HASINIT(Flags.HASINIT),
UNNAMED_CLASS(Flags.UNNAMED_CLASS),
BLOCK(Flags.BLOCK),
FROM_SOURCE(Flags.FROM_SOURCE),
ENUM(Flags.ENUM),

View File

@ -210,6 +210,7 @@ public class Preview {
public boolean isPreview(Feature feature) {
return switch (feature) {
case STRING_TEMPLATES -> true;
case UNNAMED_CLASSES -> true;
case UNNAMED_VARIABLES -> true;
//Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing).
//When real preview features will be added, this method can be implemented to return 'true'

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -238,6 +238,7 @@ public enum Source {
UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK21, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL),
RECORD_PATTERNS(JDK21, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL),
STRING_TEMPLATES(JDK21, Fragments.FeatureStringTemplates, DiagKind.PLURAL),
UNNAMED_CLASSES(JDK21, Fragments.FeatureUnnamedClasses, DiagKind.PLURAL),
WARN_ON_ILLEGAL_UTF8(MIN, JDK21),
UNNAMED_VARIABLES(JDK21, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL),
;

View File

@ -1256,6 +1256,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
/** A class for class symbols
*/
@SuppressWarnings("preview") // isUnnamed()
public static class ClassSymbol extends TypeSymbol implements TypeElement {
/** a scope for all class members; variables, methods and inner classes
@ -1367,9 +1368,14 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
return fullname.toString();
}
@DefinedBy(Api.LANGUAGE_MODEL)
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Name getQualifiedName() {
return fullname;
return isUnnamed() ? fullname.subName(0, 0) /* empty name */ : fullname;
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Name getSimpleName() {
return name;
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
@ -1545,7 +1551,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
@DefinedBy(Api.LANGUAGE_MODEL)
public NestingKind getNestingKind() {
apiComplete();
if (owner.kind == PCK)
if (owner.kind == PCK) // Handles unnamed classes as well
return NestingKind.TOP_LEVEL;
else if (name.isEmpty())
return NestingKind.ANONYMOUS;
@ -1636,6 +1642,11 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem
public List<Type> getPermittedSubclasses() {
return permitted.map(s -> s.type);
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public boolean isUnnamed() {
return (flags_field & Flags.UNNAMED_CLASS) != 0 ;
}
}

View File

@ -5511,6 +5511,10 @@ public class Attr extends JCTree.Visitor {
chk.checkClassOverrideEqualsAndHashIfNeeded(env.tree.pos(), c);
chk.checkFunctionalInterface((JCClassDecl) env.tree, c);
chk.checkLeaksNotAccessible(env, (JCClassDecl) env.tree);
if ((c.flags_field & Flags.UNNAMED_CLASS) != 0) {
chk.checkHasMain(env.tree.pos(), c);
}
} finally {
env.info.returnResult = prevReturnRes;
log.useSource(prev);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -2250,6 +2250,36 @@ public class Check {
}
}
public void checkHasMain(DiagnosticPosition pos, ClassSymbol c) {
boolean found = false;
for (Symbol sym : c.members().getSymbolsByName(names.main)) {
if (sym.kind == MTH && (sym.flags() & PRIVATE) == 0) {
MethodSymbol meth = (MethodSymbol)sym;
if (!types.isSameType(meth.getReturnType(), syms.voidType)) {
continue;
}
if (meth.params.isEmpty()) {
found = true;
break;
}
if (meth.params.size() != 1) {
continue;
}
if (!types.isSameType(meth.params.head.type, types.makeArrayType(syms.stringType))) {
continue;
}
found = true;
break;
}
}
if (!found) {
log.error(pos, Errors.UnnamedClassDoesNotHaveMainMethod);
}
}
public void checkModuleName (JCModuleDecl tree) {
Name moduleName = tree.sym.name;
Assert.checkNonNull(moduleName);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -308,8 +308,6 @@ public class Enter extends JCTree.Visitor {
@Override
public void visitTopLevel(JCCompilationUnit tree) {
// Assert.checkNonNull(tree.modle, tree.sourcefile.toString());
JavaFileObject prev = log.useSource(tree.sourcefile);
boolean addEnv = false;
boolean isPkgInfo = tree.sourcefile.isNameCompatible("package-info",
@ -441,6 +439,9 @@ public class Enter extends JCTree.Visitor {
log.error(tree.pos(),
Errors.ClassPublicShouldBeInFile(topElement, tree.name));
}
if ((tree.mods.flags & UNNAMED_CLASS) != 0) {
syms.removeClass(env.toplevel.modle, tree.name);
}
} else {
if (!tree.name.isEmpty() &&
!chk.checkUniqueClassName(tree.pos(), tree.name, enclScope)) {

View File

@ -53,7 +53,6 @@ import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType;
import com.sun.tools.javac.util.JCDiagnostic.Warning;
import java.util.Arrays;
import java.util.Collection;
@ -65,7 +64,6 @@ import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
@ -2432,7 +2430,6 @@ public class Resolve {
if (kind.contains(KindSelector.TYP)) {
sym = findType(env, name);
if (sym.exists()) return sym;
else bestSoFar = bestOf(bestSoFar, sym);
}

View File

@ -2730,6 +2730,11 @@ public class ClassReader {
signatureBuffer = new byte[ns];
}
readClass(c);
if (previewClassFile) {
if ((c.flags_field & SYNTHETIC) != 0 && c.isSubClass(syms.objectType.tsym, types)) {
c.flags_field |= UNNAMED_CLASS;
}
}
}
public void readClassFile(ClassSymbol c) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -38,6 +38,7 @@ import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
@ -84,6 +85,8 @@ import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.resources.LauncherProperties.Errors;
import com.sun.tools.javac.util.JCDiagnostic.Error;
import jdk.internal.misc.MainMethodFinder;
import jdk.internal.misc.PreviewFeatures;
import jdk.internal.misc.VM;
import static javax.tools.JavaFileObject.Kind.SOURCE;
@ -201,8 +204,8 @@ public class Main {
Context context = new Context(file.toAbsolutePath());
String mainClassName = compile(file, getJavacOpts(runtimeArgs), context);
String[] appArgs = Arrays.copyOfRange(args, 1, args.length);
execute(mainClassName, appArgs, context);
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
execute(mainClassName, mainArgs, context);
}
/**
@ -403,7 +406,9 @@ public class Main {
if (l.mainClass == null) {
throw new Fault(Errors.NoClass);
}
String mainClassName = l.mainClass.getQualifiedName().toString();
TypeElement mainClass = l.mainClass;
String mainClassName = (mainClass.isUnnamed() ? mainClass.getSimpleName()
: mainClass.getQualifiedName()).toString();
return mainClassName;
}
@ -412,31 +417,72 @@ public class Main {
* will load recently compiled classes from memory.
*
* @param mainClassName the class to be executed
* @param appArgs the arguments for the {@code main} method
* @param mainArgs the arguments for the {@code main} method
* @param context the context for the class to be executed
* @throws Fault if there is a problem finding or invoking the {@code main} method
* @throws InvocationTargetException if the {@code main} method throws an exception
*/
private void execute(String mainClassName, String[] appArgs, Context context)
private void execute(String mainClassName, String[] mainArgs, Context context)
throws Fault, InvocationTargetException {
System.setProperty("jdk.launcher.sourcefile", context.file.toString());
ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
Class<?> appClass;
try {
Class<?> appClass = Class.forName(mainClassName, true, cl);
Method main = appClass.getDeclaredMethod("main", String[].class);
int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
throw new Fault(Errors.MainNotPublicStatic);
}
if (!main.getReturnType().equals(void.class)) {
throw new Fault(Errors.MainNotVoid);
}
main.setAccessible(true);
main.invoke(0, (Object) appArgs);
appClass = Class.forName(mainClassName, true, cl);
} catch (ClassNotFoundException e) {
throw new Fault(Errors.CantFindClass(mainClassName));
}
Method mainMethod;
try {
mainMethod = MainMethodFinder.findMainMethod(appClass);
} catch (NoSuchMethodException e) {
throw new Fault(Errors.CantFindMainMethod(mainClassName));
}
int mods = mainMethod.getModifiers();
boolean isStatic = Modifier.isStatic(mods);
boolean isPublic = Modifier.isPublic(mods);
boolean noArgs = mainMethod.getParameterCount() == 0;
if (!PreviewFeatures.isEnabled() && (!isStatic || !isPublic)) {
throw new Fault(Errors.MainNotPublicStatic);
}
if (!mainMethod.getReturnType().equals(void.class)) {
throw new Fault(Errors.MainNotVoid);
}
Object instance = null;
if (!isStatic) {
Constructor<?> constructor;
try {
constructor = appClass.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new Fault(Errors.CantFindConstructor(mainClassName));
}
try {
constructor.setAccessible(true);
instance = constructor.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new Fault(Errors.CantAccessConstructor(mainClassName));
}
}
try {
// Similar to sun.launcher.LauncherHelper#executeMainClass
// but duplicated here to prevent additional launcher frames
mainMethod.setAccessible(true);
Object receiver = isStatic ? appClass : instance;
if (noArgs) {
mainMethod.invoke(receiver);
} else {
mainMethod.invoke(receiver, (Object)mainArgs);
}
} catch (IllegalAccessException e) {
throw new Fault(Errors.CantAccessMainMethod(mainClassName));
} catch (InvocationTargetException e) {

View File

@ -30,12 +30,15 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.lang.model.SourceVersion;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import com.sun.source.tree.ModuleTree.ModuleKind;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Source.Feature;
import com.sun.tools.javac.file.PathFileObject;
import com.sun.tools.javac.parser.Tokens.*;
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
import com.sun.tools.javac.resources.CompilerProperties.Errors;
@ -182,10 +185,33 @@ public class JavacParser implements Parser {
this.allowStringFolding = fac.options.getBoolean("allowStringFolding", true);
this.keepDocComments = keepDocComments;
this.parseModuleInfo = parseModuleInfo;
docComments = newDocCommentTable(keepDocComments, fac);
this.docComments = newDocCommentTable(keepDocComments, fac);
this.keepLineMap = keepLineMap;
this.errorTree = F.Erroneous();
endPosTable = newEndPosTable(keepEndPositions);
this.endPosTable = newEndPosTable(keepEndPositions);
this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source);
this.allowRecords = Feature.RECORDS.allowedInSource(source);
this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source);
}
/** Construct a parser from an existing parser, with minimal overhead.
*/
@SuppressWarnings("this-escape")
protected JavacParser(JavacParser parser,
Lexer S) {
this.S = S;
this.token = parser.token;
this.F = parser.F;
this.log = parser.log;
this.names = parser.names;
this.source = parser.source;
this.preview = parser.preview;
this.allowStringFolding = parser.allowStringFolding;
this.keepDocComments = false;
this.parseModuleInfo = false;
this.docComments = null;
this.errorTree = F.Erroneous();
this.endPosTable = newEndPosTable(false);
this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source);
this.allowRecords = Feature.RECORDS.allowedInSource(source);
this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source);
@ -2794,10 +2820,7 @@ public class JavacParser implements Parser {
case FINAL: {
dc = token.comment(CommentStyle.JAVADOC);
JCModifiers mods = modifiersOpt();
if (token.kind == INTERFACE ||
token.kind == CLASS ||
token.kind == ENUM ||
isRecordStart()) {
if (isDeclaration()) {
return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc));
} else {
JCExpression t = parseType(true);
@ -3891,6 +3914,7 @@ public class JavacParser implements Parser {
}
boolean firstTypeDecl = true; // have we see a class, enum, or interface declaration yet?
boolean isUnnamedClass = false;
while (token.kind != EOF) {
if (token.pos <= endPosTable.errorEndPos) {
// error recovery
@ -3949,16 +3973,43 @@ public class JavacParser implements Parser {
reportSyntaxError(token.pos, Errors.ExpectedModule);
}
}
defs.appendList(semiList.toList());
boolean isTopLevelMethodOrField = false;
// Do to a significant number of existing negative tests
// this code speculatively tests to see if a top level method
// or field can parse. If the method or field can parse then
// it is parsed. Otherwise, parsing continues as though
// unnamed classes did not exist and error reporting
// is the same as in the past.
if (Feature.UNNAMED_CLASSES.allowedInSource(source) && !isDeclaration()) {
final JCModifiers finalMods = mods;
JavacParser speculative = new VirtualParser(this);
List<JCTree> speculativeResult =
speculative.topLevelMethodOrFieldDeclaration(finalMods);
if (speculativeResult.head.hasTag(METHODDEF) ||
speculativeResult.head.hasTag(VARDEF)) {
isTopLevelMethodOrField = true;
}
}
if (isTopLevelMethodOrField) {
defs.appendList(topLevelMethodOrFieldDeclaration(mods));
isUnnamedClass = true;
} else {
JCTree def = typeDeclaration(mods, docComment);
if (def instanceof JCExpressionStatement statement)
def = statement.expr;
defs.append(def);
}
mods = null;
firstTypeDecl = false;
}
}
JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(defs.toList());
List<JCTree> topLevelDefs = isUnnamedClass ? constructUnnamedClass(defs.toList()) : defs.toList();
JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(topLevelDefs);
if (!consumedToplevelDoc)
attach(toplevel, firstToken.comment(CommentStyle.JAVADOC));
if (defs.isEmpty())
@ -3972,6 +4023,43 @@ public class JavacParser implements Parser {
return toplevel;
}
// Restructure top level to be an unnamed class.
private List<JCTree> constructUnnamedClass(List<JCTree> origDefs) {
checkSourceLevel(Feature.UNNAMED_CLASSES);
ListBuffer<JCTree> topDefs = new ListBuffer<>();
ListBuffer<JCTree> defs = new ListBuffer<>();
for (JCTree def : origDefs) {
if (def.hasTag(Tag.PACKAGEDEF)) {
log.error(def.pos(), Errors.UnnamedClassShouldNotHavePackageDeclaration);
} else if (def.hasTag(Tag.IMPORT)) {
topDefs.append(def);
} else if (!def.hasTag(Tag.SKIP)) {
defs.append(def);
}
}
int primaryPos = defs.first().pos;
String simplename = PathFileObject.getSimpleName(log.currentSourceFile());
if (simplename.endsWith(".java")) {
simplename = simplename.substring(0, simplename.length() - ".java".length());
}
if (!SourceVersion.isIdentifier(simplename) || SourceVersion.isKeyword(simplename)) {
log.error(primaryPos, Errors.BadFileName(simplename));
}
Name name = names.fromString(simplename);
JCModifiers anonMods = F.at(primaryPos)
.Modifiers(Flags.FINAL|Flags.SYNTHETIC|Flags.UNNAMED_CLASS, List.nil());
JCClassDecl anon = F.at(primaryPos).ClassDef(
anonMods, name, List.nil(), null, List.nil(), List.nil(),
defs.toList());
topDefs.append(anon);
return topDefs.toList();
}
JCModuleDecl moduleDecl(JCModifiers mods, ModuleKind kind, Comment dc) {
int pos = token.pos;
checkSourceLevel(Feature.MODULES);
@ -4132,17 +4220,17 @@ public class JavacParser implements Parser {
} else {
errs = List.of(mods);
}
final JCErroneous erroneousTree;
JCDiagnostic.Error error;
if (parseModuleInfo) {
erroneousTree = syntaxError(pos, errs, Errors.ExpectedModuleOrOpen);
error = Errors.ExpectedModuleOrOpen;
} else if (allowRecords) {
error = Errors.Expected4(CLASS, INTERFACE, ENUM, "record");
} else {
if (allowRecords) {
erroneousTree = syntaxError(pos, errs, Errors.Expected4(CLASS, INTERFACE, ENUM, "record"));
} else {
erroneousTree = syntaxError(pos, errs, Errors.Expected3(CLASS, INTERFACE, ENUM));
error = Errors.Expected3(CLASS, INTERFACE, ENUM);
}
}
return toP(F.Exec(erroneousTree));
return toP(F.Exec(syntaxError(pos, errs, error)));
}
}
@ -4363,7 +4451,7 @@ public class JavacParser implements Parser {
hasStructuralErrors = true;
}
wasError = false;
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(enumName,
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(null, enumName,
false, false));
if (token.pos <= endPosTable.errorEndPos) {
// error recovery
@ -4469,7 +4557,7 @@ public class JavacParser implements Parser {
}
ListBuffer<JCTree> defs = new ListBuffer<>();
while (token.kind != RBRACE && token.kind != EOF) {
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(className, isInterface, isRecord));
defs.appendList(classOrInterfaceOrRecordBodyDeclaration(null, className, isInterface, isRecord));
if (token.pos <= endPosTable.errorEndPos) {
// error recovery
skip(false, true, true, false);
@ -4508,18 +4596,17 @@ public class JavacParser implements Parser {
* )
*
*/
protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(Name className, boolean isInterface, boolean isRecord) {
protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(JCModifiers mods, Name className,
boolean isInterface,
boolean isRecord) {
if (token.kind == SEMI) {
nextToken();
return List.nil();
} else {
Comment dc = token.comment(CommentStyle.JAVADOC);
int pos = token.pos;
JCModifiers mods = modifiersOpt();
if (token.kind == CLASS ||
allowRecords && isRecordStart() ||
token.kind == INTERFACE ||
token.kind == ENUM) {
mods = modifiersOpt(mods);
if (isDeclaration()) {
return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc));
} else if (token.kind == LBRACE &&
(mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 &&
@ -4531,6 +4618,15 @@ public class JavacParser implements Parser {
}
return List.of(block(pos, mods.flags));
} else {
return constructorOrMethodOrFieldDeclaration(mods, className, isInterface, isRecord, dc);
}
}
}
private List<JCTree> constructorOrMethodOrFieldDeclaration(JCModifiers mods, Name className,
boolean isInterface,
boolean isRecord, Comment dc) {
int pos;
pos = token.pos;
List<JCTypeParameter> typarams = typeParametersOpt();
// if there are type parameters but no modifiers, save the start
@ -4551,6 +4647,7 @@ public class JavacParser implements Parser {
pos = token.pos;
JCExpression type;
boolean isVoid = token.kind == VOID;
if (isVoid) {
type = to(F.at(pos).TypeIdent(TypeTag.VOID));
nextToken();
@ -4558,19 +4655,27 @@ public class JavacParser implements Parser {
// method returns types are un-annotated types
type = unannotatedType(false);
}
// Constructor
if ((token.kind == LPAREN && !isInterface ||
isRecord && token.kind == LBRACE) && type.hasTag(IDENT)) {
if (isInterface || tk.name() != className)
if (isInterface || tk.name() != className) {
log.error(DiagnosticFlag.SYNTAX, pos, Errors.InvalidMethDeclRetTypeReq);
else if (annosAfterParams.nonEmpty())
} else if (annosAfterParams.nonEmpty()) {
illegal(annosAfterParams.head.pos);
}
if (isRecord && token.kind == LBRACE) {
mods.flags |= Flags.COMPACT_RECORD_CONSTRUCTOR;
}
return List.of(methodDeclaratorRest(
pos, mods, null, names.init, typarams,
isInterface, true, isRecord, dc));
} else if (isRecord && type.hasTag(IDENT) && token.kind == THROWS) {
}
// Record constructor
if (isRecord && type.hasTag(IDENT) && token.kind == THROWS) {
// trying to define a compact constructor with a throws clause
log.error(DiagnosticFlag.SYNTAX, token.pos,
Errors.InvalidCanonicalConstructorInRecord(
@ -4581,14 +4686,20 @@ public class JavacParser implements Parser {
return List.of(methodDeclaratorRest(
pos, mods, null, names.init, typarams,
isInterface, true, isRecord, dc));
} else {
}
pos = token.pos;
Name name = ident();
// Method
if (token.kind == LPAREN) {
return List.of(methodDeclaratorRest(
pos, mods, type, name, typarams,
isInterface, isVoid, false, dc));
} else if (!isVoid && typarams.isEmpty()) {
}
// Field
if (!isVoid && typarams.isEmpty()) {
if (!isRecord || (isRecord && (mods.flags & Flags.STATIC) != 0)) {
List<JCTree> defs =
variableDeclaratorsRest(pos, mods, type, name, isInterface, dc,
@ -4596,16 +4707,19 @@ public class JavacParser implements Parser {
accept(SEMI);
storeEnd(defs.last(), S.prevToken().endPos);
return defs;
} else {
}
int errPos = pos;
variableDeclaratorsRest(pos, mods, type, name, isInterface, dc,
new ListBuffer<JCTree>(), false).toList();
accept(SEMI);
return List.of(syntaxError(errPos, null, Errors.RecordCannotDeclareInstanceFields));
}
} else {
pos = token.pos;
List<JCTree> err;
// Error recovery
if (isVoid || typarams.nonEmpty()) {
JCMethodDecl m =
toP(F.at(pos).MethodDef(mods, name, type, typarams,
@ -4615,11 +4729,72 @@ public class JavacParser implements Parser {
} else {
err = List.nil();
}
return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN)));
}
private List<JCTree> topLevelMethodOrFieldDeclaration(JCModifiers mods) throws AssertionError {
int topPos = token.pos;
int pos = token.pos;
Comment dc = token.comment(CommentStyle.JAVADOC);
List<JCTypeParameter> typarams = typeParametersOpt();
// if there are type parameters but no modifiers, save the start
// position of the method in the modifiers.
if (typarams.nonEmpty() && mods.pos == Position.NOPOS) {
mods.pos = pos;
storeEnd(mods, pos);
}
List<JCAnnotation> annosAfterParams = annotationsOpt(Tag.ANNOTATION);
if (annosAfterParams.nonEmpty()) {
mods.annotations = mods.annotations.appendList(annosAfterParams);
if (mods.pos == Position.NOPOS)
mods.pos = mods.annotations.head.pos;
}
pos = token.pos;
JCExpression type;
boolean isVoid = token.kind == VOID;
if (isVoid) {
type = to(F.at(pos).TypeIdent(TypeTag.VOID));
nextToken();
} else {
type = unannotatedType(false);
}
if (token.kind == IDENTIFIER) {
pos = token.pos;
Name name = ident();
// Method
if (token.kind == LPAREN) {
return List.of(methodDeclaratorRest(pos, mods, type, name, typarams,
false, isVoid, false, dc));
}
// Field
if (!isVoid && typarams.isEmpty() && (token.kind == EQ || token.kind == SEMI)) {
List<JCTree> defs =
variableDeclaratorsRest(pos, mods, type, name, false, dc,
new ListBuffer<JCTree>(), false).toList();
accept(SEMI);
storeEnd(defs.last(), S.prevToken().endPos);
return defs;
}
}
return List.of(F.Erroneous());
}
protected boolean isDeclaration() {
return token.kind == CLASS ||
token.kind == INTERFACE ||
token.kind == ENUM ||
isRecordStart() && allowRecords;
}
protected boolean isRecordStart() {

View File

@ -42,7 +42,7 @@ import static com.sun.tools.javac.parser.Tokens.*;
*/
public class Scanner implements Lexer {
private final Tokens tokens;
protected Tokens tokens;
/** The token, set by nextToken().
*/

View File

@ -0,0 +1,189 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.parser;
import com.sun.tools.javac.parser.Tokens.Token;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCErroneous;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.JCDiagnostic.Error;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Position.LineMap;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* The virtual parser allows for speculative parsing while not commiting to
* consuming tokens unless the speculation is successful.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class VirtualParser extends JavacParser {
private boolean hasErrors;
public VirtualParser(JavacParser parser) {
super(parser, new VirtualScanner(parser.S));
}
@Override
protected JCErroneous syntaxError(int pos, Error errorKey) {
hasErrors = true;
return F.Erroneous();
}
@Override
protected JCErroneous syntaxError(int pos, List<JCTree> errs, Error errorKey) {
hasErrors = true;
return F.Erroneous();
}
@Override
protected void reportSyntaxError(int pos, Error errorKey) {
hasErrors = true;
}
@Override
protected void reportSyntaxError(DiagnosticPosition diagPos, Error errorKey) {
hasErrors = true;
}
public boolean hasErrors() {
return hasErrors;
}
/**
* Scanner that does token lookahead and throws AssertionErrors if an error
* occurs.
*/
public static class VirtualScanner implements Lexer {
/** Parent scanner.
*/
Lexer S;
/** Token offset from where parent scanner branched.
*/
int offset = 0;
/** The token, set by nextToken().
*/
private Token token;
/** The previous token, set by nextToken().
*/
private Token prevToken;
public VirtualScanner(Lexer s) {
while (s instanceof VirtualScanner virtualScanner) {
s = virtualScanner.S;
offset += virtualScanner.offset;
}
S = s;
token = s.token();
prevToken = S.prevToken();
}
@Override
public void nextToken() {
prevToken = token;
offset++;
token = token();
}
@Override
public Token token() {
return token(0);
}
@Override
public Token token(int lookahead) {
return S.token(offset + lookahead);
}
@Override
public Token prevToken() {
return prevToken;
}
@Override
public void setPrevToken(Token prevToken) {
this.prevToken = prevToken;
}
@Override
public Token split() {
Token[] splitTokens = token.split(((Scanner)S).tokens);
prevToken = splitTokens[0];
token = splitTokens[1];
return token;
}
@Override
public int errPos() {
throw new AssertionError();
}
@Override
public void errPos(int pos) {
throw new AssertionError();
}
@Override
public LineMap getLineMap() {
return S.getLineMap();
}
public void commit() {
for (int i = 0 ; i < offset ; i++) {
S.nextToken(); // advance underlying lexer until position matches
}
}
}
/**
* Attempts a parse action and returns true if successful or false if
* a parse error is thrown.
*
* @param parser parent parser
* @param parserAction function that takes a parser and invokes a method on that parser
*
* @return true if successful
*/
public static boolean tryParse(JavacParser parser, Consumer<JavacParser> parserAction) {
VirtualParser virtualParser = new VirtualParser(parser);
try {
parserAction.accept(virtualParser);
return true;
} catch (AssertionError ex) {
return false;
}
}
}

View File

@ -118,6 +118,7 @@ public class PrintingProcessor extends AbstractProcessor {
}
@Override @DefinedBy(Api.LANGUAGE_MODEL)
@SuppressWarnings("preview") // isUnnamed
public PrintingElementVisitor visitExecutable(ExecutableElement e, Boolean p) {
ElementKind kind = e.getKind();
@ -125,18 +126,26 @@ public class PrintingProcessor extends AbstractProcessor {
kind != INSTANCE_INIT) {
Element enclosing = e.getEnclosingElement();
// Don't print out the constructor of an anonymous class
// Don't print out the constructor of an anonymous or unnamed class
if (kind == CONSTRUCTOR &&
enclosing != null &&
NestingKind.ANONYMOUS ==
(NestingKind.ANONYMOUS ==
// Use an anonymous class to determine anonymity!
(new SimpleElementVisitor14<NestingKind, Void>() {
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public NestingKind visitType(TypeElement e, Void p) {
return e.getNestingKind();
}
}).visit(enclosing))
}).visit(enclosing)
|| // Don't print the constructor of an unnamed class
(new SimpleElementVisitor14<Boolean, Void>(false) {
@Override @DefinedBy(Api.LANGUAGE_MODEL)
public Boolean visitType(TypeElement e, Void p) {
return e.isUnnamed();
}
}).visit(enclosing)) ) {
return this;
}
defaultAction(e, true);
printFormalTypeParameters(e, true);
@ -169,6 +178,7 @@ public class PrintingProcessor extends AbstractProcessor {
@Override @DefinedBy(Api.LANGUAGE_MODEL)
@SuppressWarnings("preview") // isUnnamed
public PrintingElementVisitor visitType(TypeElement e, Boolean p) {
ElementKind kind = e.getKind();
NestingKind nestingKind = e.getNestingKind();
@ -202,6 +212,14 @@ public class PrintingProcessor extends AbstractProcessor {
printParameters(constructors.get(0));
}
writer.print(")");
} else if (e.isUnnamed()) {
writer.println("// Unnamed class in file whose name starts with " + e.getSimpleName());
for(Element element : e.getEnclosedElements()) {
this.visit(element);
}
return this;
} else {
if (nestingKind == TOP_LEVEL) {
PackageElement pkg = elementUtils.getPackageOf(e);

View File

@ -1,5 +1,5 @@
#
# Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
@ -512,6 +512,16 @@ compiler.err.invalid.repeatable.annotation.not.applicable.in.context=\
compiler.err.duplicate.class=\
duplicate class: {0}
# 0: string
compiler.err.bad.file.name=\
bad file name: {0}
compiler.err.unnamed.class.should.not.have.package.declaration=\
unnamed class should not have package declaration
compiler.err.unnamed.class.does.not.have.main.method=\
unnamed class does not have main method in the form of void main() or void main(String[] args)
# 0: name, 1: name
compiler.err.same.binary.name=\
classes: {0} and {1} have the same binary name
@ -3158,6 +3168,9 @@ compiler.misc.feature.string.templates=\
compiler.misc.feature.unconditional.patterns.in.instanceof=\
unconditional patterns in instanceof
compiler.misc.feature.unnamed.classes=\
unnamed classes
compiler.warn.underscore.as.identifier=\
as of release 9, ''_'' is a keyword, and may not be used as an identifier

View File

@ -119,6 +119,14 @@ launcher.err.cant.find.main.method=\
launcher.err.cant.access.main.method=\
can''t access main method in class: {0}
# 0: string
launcher.err.cant.find.constructor=\
can''t find no argument constructor in class: {0}
# 0: string
launcher.err.cant.access.constructor=\
can''t access no argument constructor in class: {0}
# 0: path, 1: object
launcher.err.cant.read.file=\
error reading source file {0}: {1}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -132,6 +132,8 @@ public class TreeMaker implements JCTree.Factory {
|| node instanceof JCModuleDecl
|| node instanceof JCSkip
|| node instanceof JCErroneous
|| node instanceof JCMethodDecl
|| node instanceof JCVariableDecl
|| (node instanceof JCExpressionStatement expressionStatement
&& expressionStatement.expr instanceof JCErroneous),
() -> node.getClass().getSimpleName());
@ -1159,7 +1161,7 @@ public class TreeMaker implements JCTree.Factory {
!it.hasNext();
}
}
return false;
return sym.kind == TYP && (sym.flags_field & Flags.UNNAMED_CLASS) != 0;
}
/** The name of synthetic parameter number `i'.

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -356,8 +356,7 @@ public class Convert {
if (start == 0 && end == name.length()) {
return name;
}
return name.subName(
name.lastIndexOf((byte)'.') + 1, name.getByteLength());
return name.subName(start, end);
}
/** Return the last part of a qualified name from its string representation

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -193,6 +193,7 @@ public class Names {
public final Name module_info;
public final Name package_info;
public final Name requireNonNull;
public final Name main;
// lambda-related
public final Name lambda;
@ -388,6 +389,7 @@ public class Names {
module_info = fromString("module-info");
package_info = fromString("package-info");
requireNonNull = fromString("requireNonNull");
main = fromString("main");
//lambda-related
lambda = fromString("lambda$");

View File

@ -0,0 +1,190 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.nio.file.Files;
import java.nio.file.Path;
/**
* @test
* @summary test execution priority of main methods
* @run main InstanceMainTest
*/
public class InstanceMainTest extends TestHelper {
private static final String[] SOURCES = new String[] {
// static dominating with args
"""
class MainClass {
static void main() {
throw new AssertionError();
}
static void main(String[] args) {
}
}
""",
// static dominating instance
"""
class MainClass {
void main(String[] args) {
throw new AssertionError();
}
static void main() {
}
}
""",
// instance dominating with args
"""
class MainClass {
void main() {
throw new AssertionError();
}
void main(String[] args) {
}
}
""",
// instance no args
"""
class MainClass {
void main() {
}
}
""",
// unnamed class static dominating with args
"""
static void main() {
throw new AssertionError();
}
static void main(String[] args) {
}
""",
// unnamed class static dominating instance
"""
void main(String[] args) {
throw new AssertionError();
}
static void main() {
}
""",
// unnamed class instance dominating with args
"""
void main() {
throw new AssertionError();
}
void main(String[] args) {
}
""",
// unnamed class instance main no args
"""
void main() {
}
""",
// instance main dominating super static
"""
class MainClass extends SuperClass {
void main() {
}
}
class SuperClass {
void main(String[] args) {
throw new AssertionError();
}
}
""",
// super instance main with args dominating
"""
public class MainClass extends Super {
}
class Super {
public void main(String... args) {
}
public void main() {
throw new AssertionError();
}
}
""",
// ignore super instance main
"""
public class MainClass extends Super {
public static void main(String... args) {
}
}
class Super {
public static void main(String... args) {
throw new AssertionError();
}
}
""",
// enum main
"""
enum MainClass {
A;
public static void main() {
}
}
""",
// record main
"""
record MainClass() {
static void main() {
System.out.println("Done!");
}
}
""",
// interface main
"""
interface MainClass {
static void main() {
System.out.println("Done!");
}
}
"""
};
public static void main(String... args) throws Exception {
for (String source : SOURCES) {
Files.writeString(Path.of("MainClass.java"), source);
var version = System.getProperty("java.specification.version");
var tr = doExec(javaCmd, "--enable-preview", "--source", version, "MainClass.java");
if (!tr.isOK()) {
System.err.println(source);
System.err.println(tr);
throw new AssertionError();
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.misc.feature.unnamed.classes
// key: compiler.warn.preview.feature.use.plural
// options: -source ${jdk.version} --enable-preview -Xlint:preview
public static void main(String... args) {
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.err.bad.file.name
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: -source ${jdk.version} --enable-preview
public static void main(String... args) {
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.err.unnamed.class.should.not.have.package.declaration
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: -source ${jdk.version} --enable-preview
package unnamed.classes;
public static void main(String... args) {
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// key: compiler.err.unnamed.class.does.not.have.main.method
// key: compiler.note.preview.filename
// key: compiler.note.preview.recompile
// options: -source ${jdk.version} --enable-preview
public void ordinaryMethod() {
}

View File

@ -553,7 +553,7 @@ public class SourceLauncherTest extends TestRunner {
tb.writeJavaFiles(base,
"class NotPublic { static void main(String... args) { } }");
testError(base.resolve("NotPublic.java"), "",
"error: 'main' method is not declared 'public static'");
"error: can't find main(String[]) method in class: NotPublic");
}
@Test

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* A class with no-name.
*/
void main() {
printMessage();
}
private static String horseName = null;
private static void printMessage() {
if (horseName == null) {
System.out.print("A horse has no name.");
} else
System.out.print("A horse's name is " + horseName);
}

View File

@ -0,0 +1,200 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8306112
* @summary Test basic processing of unnamed classes.
* @library /tools/javac/lib
* @modules java.compiler
* jdk.compiler
* @build JavacTestingAbstractProcessor TestUnnamedClass
* @compile -processor TestUnnamedClass -proc:only --enable-preview --release ${jdk.version} Anonymous.java
*/
import java.lang.annotation.*;
import java.io.Writer;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.lang.model.util.Elements;
import static javax.lang.model.util.ElementFilter.*;
import javax.tools.JavaFileObject;
/*
* Ideally, this processor would test both the compile-time
* representation of an unnamed class starting from a source file as
* well as the representation starting from a class file. Currently,
* only the source file based view will be tested.
*
* For future work to test the class file based view, an additional jtreg directive like the following could
* be used:
*
* @compile/process -processor TestUnnamedClass -proc:only Anonymous Nameless
*/
@SuppressWarnings("preview")
public class TestUnnamedClass extends JavacTestingAbstractProcessor {
private static int round = 0;
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (round == 0) { // Check file from comamnd line
checkRoots(roundEnv);
generateUnnamed();
}
if (!roundEnv.processingOver()) { // Test generated file(s)
checkRoots(roundEnv);
}
round++;
return true;
}
private void checkRoots(RoundEnvironment roundEnv) {
int checks = 0;
for (TypeElement type : typesIn(roundEnv.getRootElements())) {
System.out.println("Checking " + type.getQualifiedName());
checks++;
checkUnnamedClassProperties(type);
}
if (checks == 0) {
messager.printError("No checking done of any candidate unnamed classes.");
}
}
private void generateUnnamed() {
try {
String unnamedSource = """
void main() {
System.out.println("Nameless, but not voiceless.");
}
""";
JavaFileObject outputFile = processingEnv.getFiler().createSourceFile("Nameless");
try(Writer w = outputFile.openWriter()) {
w.append(unnamedSource);
}
} catch (java.io.IOException ioe) {
throw new RuntimeException(ioe);
}
}
/*
* From JEP 445 JLS changes:
*
* "An unnamed class compilation unit implicitly declares a class that satisfies the following
* properties:
* It is always a top level class.
* It is always an unnamed class (it has no canonical or fully qualified name (6.7)).
* It is never abstract (8.1.1.1).
* It is always final (8.1.1.2).
* It is always a member of an unnamed package (7.4.2) and has package access.
* Its direct superclass type is always Object (8.1.4).
* It never has any direct superinterface types (8.1.5).
*
* The body of the class contains every ClassMemberDeclaration
* from the unnamed class compilation unit. It is not possible for
* an unnamed class compilation unit to declare an instance
* initializer, static initializer, or constructor.
*
* It has an implicitly declared default constructor (8.8.9).
* All members of this class, including any implicitly declared
* members, are subject to the usual rules for member declarations
* in a class.
*
* It is a compile-time error if this class does not declare a candidate main method (12.1.4).
*/
void checkUnnamedClassProperties(TypeElement unnamedClass) {
if (unnamedClass.getNestingKind() != NestingKind.TOP_LEVEL) {
messager.printError("Unnamed class is not top-level.", unnamedClass);
}
if (!unnamedClass.isUnnamed()) {
messager.printError("Unnamed class is _not_ indicated as such.", unnamedClass);
}
if (unnamedClass.getSimpleName().isEmpty()) {
messager.printError("Unnamed class does have an empty simple name.", unnamedClass);
}
if (!unnamedClass.getQualifiedName().isEmpty()) {
messager.printError("Unnamed class does _not_ have an empty qualified name.", unnamedClass);
}
if (unnamedClass.getModifiers().contains(Modifier.ABSTRACT)) {
messager.printError("Unnamed class is abstract.", unnamedClass);
}
if (!unnamedClass.getModifiers().contains(Modifier.FINAL)) {
messager.printError("Unnamed class is _not_ final.", unnamedClass);
}
if (!elements.getPackageOf(unnamedClass).isUnnamed()) {
messager.printError("Unnamed class is _not_ in an unnamed package.", unnamedClass);
}
if (unnamedClass.getModifiers().contains(Modifier.PUBLIC) ||
unnamedClass.getModifiers().contains(Modifier.PRIVATE) ||
unnamedClass.getModifiers().contains(Modifier.PROTECTED)) {
messager.printError("Unnamed class does _not_ have package access.", unnamedClass);
}
if ( !types.isSameType(unnamedClass.getSuperclass(),
elements.getTypeElement("java.lang.Object").asType())) {
messager.printError("Unnamed class does _not_ have java.lang.Object as a superclass.", unnamedClass);
}
if (!unnamedClass.getInterfaces().isEmpty()) {
messager.printError("Unnamed class has superinterfaces.", unnamedClass);
}
List<ExecutableElement> ctors = constructorsIn(unnamedClass.getEnclosedElements());
if (ctors.size() != 1 ) {
messager.printError("Did not find exactly one constructor", unnamedClass);
}
ExecutableElement ctor = ctors.getFirst();
if (elements.getOrigin(ctor) != Elements.Origin.MANDATED) {
messager.printError("Constructor was not marked as mandated", ctor);
}
List<ExecutableElement> methods = methodsIn(unnamedClass.getEnclosedElements());
// Just look for a method named "main"; don't check the other details.
boolean mainFound = false;
Name mainName = elements.getName("main");
for (var method : methods) {
if (method.getSimpleName().equals(mainName)) {
mainFound = true;
break;
}
}
if (!mainFound) {
messager.printError("No main mehtod found", unnamedClass);
}
}
}

View File

@ -0,0 +1,8 @@
/**
* @test /nodynamiccopyright/
* @compile/fail/ref=UnnamedClassRecovery.out -XDrawDiagnostics --enable-preview --source ${jdk.version} UnnamedClassRecovery.java
*/
public void main() {
//the following is intentionally missing a semicolon:
System.err.println("Hello!")
}

View File

@ -0,0 +1,4 @@
UnnamedClassRecovery.java:7:33: compiler.err.expected: ';'
- compiler.note.preview.filename: UnnamedClassRecovery.java, DEFAULT
- compiler.note.preview.recompile
1 error

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary enums: ensure unnamed class is visible to java.lang.Enum<unnamed_class>
* @enablePreview
* @compile NestedEnum.java
*/
enum Foo {A, B}
void main() {
System.out.println(Foo.A);
}