diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 43dcf25c263..f95ffd0e402 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -69,9 +69,7 @@ public @interface PreviewFeature { FOREIGN, @JEP(number=459, title="String Templates", status="Second Preview") STRING_TEMPLATES, - @JEP(number=445, title="Unnamed Classes and Instance Main Methods", status="Deprecated") - UNNAMED_CLASSES, - @JEP(number=463, title="Implicitly Declared Classes and Instance Main Methods", status="Preview") + @JEP(number=477, title="Implicitly Declared Classes and Instance Main Methods", status="Third Preview") IMPLICIT_CLASSES, @JEP(number=464, title="Scoped Values", status="Second Preview") SCOPED_VALUES, diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java index 89de932ab72..17173e480f1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java @@ -230,6 +230,7 @@ public class Symtab { public final Type valueBasedInternalType; public final Type classDescType; public final Type enumDescType; + public final Type ioType; // For serialization lint checking public final Type objectStreamFieldType; @@ -616,6 +617,7 @@ public class Symtab { valueBasedInternalType = enterSyntheticAnnotation("jdk.internal.ValueBased+Annotation"); classDescType = enterClass("java.lang.constant.ClassDesc"); enumDescType = enterClass("java.lang.Enum$EnumDesc"); + ioType = enterClass("java.io.IO"); // For serialization lint checking objectStreamFieldType = enterClass("java.io.ObjectStreamField"); objectInputStreamType = enterClass("java.io.ObjectInputStream"); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java index f36f425283a..72943e09f0f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java @@ -323,7 +323,7 @@ public class TypeEnter implements Completer { sym.owner.complete(); } - private void importJavaLang(JCCompilationUnit tree, Env env, ImportFilter typeImportFilter) { + private void implicitImports(JCCompilationUnit tree, Env env) { // Import-on-demand java.lang. PackageSymbol javaLang = syms.enterPackage(syms.java_base, names.java_lang); if (javaLang.members().isEmpty() && !javaLang.exists()) { @@ -332,6 +332,28 @@ public class TypeEnter implements Completer { } importAll(make.at(tree.pos()).Import(make.Select(make.QualIdent(javaLang.owner), javaLang), false), javaLang, env); + + List defs = tree.getTypeDecls(); + boolean isImplicitClass = !defs.isEmpty() && + defs.head instanceof JCClassDecl cls && + (cls.mods.flags & IMPLICIT_CLASS) != 0; + if (isImplicitClass) { + doModuleImport(make.ModuleImport(make.QualIdent(syms.java_base))); + if (peekTypeExists(syms.ioType.tsym)) { + doImport(make.Import(make.Select(make.QualIdent(syms.ioType.tsym), + names.asterisk), true)); + } + } + } + + private boolean peekTypeExists(TypeSymbol type) { + try { + type.complete(); + return !type.type.isErroneous(); + } catch (CompletionFailure cf) { + //does not exist + return false; + } } private void resolveImports(JCCompilationUnit tree, Env env) { @@ -356,7 +378,7 @@ public class TypeEnter implements Completer { (origin, sym) -> sym.kind == TYP && chk.importAccessible(sym, packge); - importJavaLang(tree, env, typeImportFilter); + implicitImports(tree, env); JCModuleDecl decl = tree.getModuleDecl(); diff --git a/test/langtools/tools/javac/ImplicitClass/ImplicitImports.java b/test/langtools/tools/javac/ImplicitClass/ImplicitImports.java new file mode 100644 index 00000000000..1362864d8fe --- /dev/null +++ b/test/langtools/tools/javac/ImplicitClass/ImplicitImports.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2024, 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 8325324 + * @summary Verify behavior w.r.t. implicit imports + * @library /tools/lib + * @modules jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * jdk.compiler/com.sun.tools.javac.util + * @build toolbox.ToolBox toolbox.JavacTask + * @run main ImplicitImports +*/ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Objects; + +import toolbox.TestRunner; +import toolbox.JavacTask; +import toolbox.JavaTask; +import toolbox.Task; +import toolbox.Task.OutputKind; +import toolbox.ToolBox; + +public class ImplicitImports extends TestRunner { + + private static final String SOURCE_VERSION = System.getProperty("java.specification.version"); + private ToolBox tb; + + public static void main(String... args) throws Exception { + new ImplicitImports().runTests(); + } + + ImplicitImports() { + super(System.err); + tb = new ToolBox(); + } + + public void runTests() throws Exception { + runTests(m -> new Object[] { Paths.get(m.getName()) }); + } + + @Test + public void testImplicitJavaBaseImport(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + public static void main(String... args) { + List l = new ArrayList<>(); + System.out.println(l.getClass().getName()); + } + """); + + Files.createDirectories(classes); + + {//with --release: + new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + var out = new JavaTask(tb) + .classpath(classes.toString()) + .className("Test") + .vmOptions("--enable-preview") + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + var expectedOut = List.of("java.util.ArrayList"); + + if (!Objects.equals(expectedOut, out)) { + throw new AssertionError("Incorrect Output, expected: " + expectedOut + + ", actual: " + out); + + } + } + + {//with --source: + new JavacTask(tb) + .options("--enable-preview", "--source", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + var out = new JavaTask(tb) + .classpath(classes.toString()) + .className("Test") + .vmOptions("--enable-preview") + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + var expectedOut = List.of("java.util.ArrayList"); + + if (!Objects.equals(expectedOut, out)) { + throw new AssertionError("Incorrect Output, expected: " + expectedOut + + ", actual: " + out); + + } + } + } + + @Test + public void testImplicitSimpleIOImport(Path base) throws Exception { + Path current = base.resolve("."); + + Path patchClasses = prepareIOPatch(current); + + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + public static void main(String... args) { + println("Hello, World!"); + } + """); + + Files.createDirectories(classes); + + new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION, + "--patch-module", "java.base=" + patchClasses) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + var out = new JavaTask(tb) + .classpath(classes.toString()) + .className("Test") + .vmOptions("--enable-preview", + "--patch-module", "java.base=" + patchClasses) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + var expectedOut = List.of("Hello, World!"); + + if (!Objects.equals(expectedOut, out)) { + throw new AssertionError("Incorrect Output, expected: " + expectedOut + + ", actual: " + out); + + } + } + + @Test + public void testNoImplicitImportsForOrdinaryClasses(Path base) throws Exception { + Path current = base.resolve("."); + + Path patchClasses = prepareIOPatch(current); + + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + public class Test { + public static void main(String... args) { + List l = new ArrayList<>(); + println("Hello, World!"); + } + } + """); + + Files.createDirectories(classes); + + var log = new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION, + "--patch-module", "java.base=" + patchClasses, + "-XDrawDiagnostics") + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + + var expectedLog = List.of( + "Test.java:3:9: compiler.err.cant.resolve.location: kindname.class, List, , , (compiler.misc.location: kindname.class, Test, null)", + "Test.java:3:30: compiler.err.cant.resolve.location: kindname.class, ArrayList, , , (compiler.misc.location: kindname.class, Test, null)", + "Test.java:4:9: compiler.err.cant.resolve.location.args: kindname.method, println, , java.lang.String, (compiler.misc.location: kindname.class, Test, null)", + "3 errors" + ); + + if (!Objects.equals(expectedLog, log)) { + throw new AssertionError("Incorrect Output, expected: " + expectedLog + + ", actual: " + log); + + } + } + + private Path prepareIOPatch(Path base) throws IOException { + Path patchSrc = base.resolve("patch-src"); + Path patchClasses = base.resolve("patch-classes"); + tb.writeJavaFiles(patchSrc, + """ + package java.io; + public class IO { + public static void println(Object o) { + System.out.println(o); + } + } + """); + + Files.createDirectories(patchClasses); + + new JavacTask(tb) + .options("--patch-module", "java.base=" + patchSrc) + .outdir(patchClasses) + .files(tb.findJavaFiles(patchSrc)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + return patchClasses; + } + + @Test + public void testWithExplicitImport(Path base) throws Exception { + Path current = base.resolve("."); + Path src = current.resolve("src"); + Path classes = current.resolve("classes"); + tb.writeFile(src.resolve("Test.java"), + """ + import java.lang.*; + public static void main(String... args) { + List l = new ArrayList<>(); + System.out.println(l.getClass().getName()); + } + """); + + Files.createDirectories(classes); + + new JavacTask(tb) + .options("--enable-preview", "--release", SOURCE_VERSION) + .outdir(classes) + .files(tb.findJavaFiles(src)) + .run(Task.Expect.SUCCESS) + .writeAll(); + + var out = new JavaTask(tb) + .classpath(classes.toString()) + .className("Test") + .vmOptions("--enable-preview") + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + var expectedOut = List.of("java.util.ArrayList"); + + if (!Objects.equals(expectedOut, out)) { + throw new AssertionError("Incorrect Output, expected: " + expectedOut + + ", actual: " + out); + + } + } +} diff --git a/test/langtools/tools/javac/processing/model/TestSymtabItems.java b/test/langtools/tools/javac/processing/model/TestSymtabItems.java index ce20063b9b9..ba9bb20230b 100644 --- a/test/langtools/tools/javac/processing/model/TestSymtabItems.java +++ b/test/langtools/tools/javac/processing/model/TestSymtabItems.java @@ -85,6 +85,10 @@ public class TestSymtabItems { if (f.getName().toLowerCase().contains("methodhandle")) continue; + // Temporarily ignore java.io.IO: + if (f.getName().equals("ioType")) + continue; + //both noModule and unnamedModule claim the unnamed package, ignore noModule for now: if (f.getName().equals("noModule")) continue;