From 4aee3bd9df97e2b96a444751ea26a3a9091223f5 Mon Sep 17 00:00:00 2001 From: "Ahmad K. Bawaneh" Date: Wed, 17 Jul 2024 21:44:55 +0300 Subject: [PATCH 01/12] Enable embedding sources inside the sourcemaps json as sourcesContent (#9936) This PR updates the rebased version of sourcemaps jar and other dependencies to support embedding the source files content into the source maps sourcesContent field. In doing so, it also removes the protobuf dependency from being needed at runtime on the server for deobfuscating stack traces - only legacy dev mode requires it now. Fixes #9752 Fixes #9778 Fixes #9790 --- dev/build.xml | 16 +-- .../linker/impl/StandardLinkerContext.java | 6 ++ .../gwt/core/linker/SymbolMapsLinker.java | 97 +++++++++++++++++-- .../src/com/google/gwt/dev/CompilePerms.java | 6 +- dev/core/src/com/google/gwt/dev/Compiler.java | 5 +- dev/core/src/com/google/gwt/dev/Link.java | 5 +- .../src/com/google/gwt/dev/Precompile.java | 2 +- .../com/google/gwt/dev/PrecompileOnePerm.java | 5 +- .../src/com/google/gwt/dev/cfg/ModuleDef.java | 19 ++++ .../gwt/dev/jjs/JavaToJavaScriptCompiler.java | 1 + eclipse/dev/.classpath | 2 +- eclipse/dev/codeserver/.classpath | 2 +- eclipse/user/.classpath | 2 +- requestfactory/build.xml | 3 + servlet/build.xml | 12 +-- .../gwt/core/CompilerParameters.gwt.xml | 6 ++ .../core/server/StackTraceDeobfuscator.java | 6 +- 17 files changed, 163 insertions(+), 32 deletions(-) diff --git a/dev/build.xml b/dev/build.xml index 1b5a2ac9ffc..0b181dd75a9 100755 --- a/dev/build.xml +++ b/dev/build.xml @@ -68,11 +68,12 @@ - + + - + @@ -129,7 +130,8 @@ src="${gwt.tools.lib}/eclipse/org.eclipse.jdt.core_3.32.0.v20221108-1853.jar"/> - + + @@ -140,7 +142,7 @@ - + @@ -227,9 +229,11 @@ + location="${gwt.tools.lib}/guava/guava-33.0/guava-33.0.0-jre-rebased.jar"/> + - + selectionProperties; + private final ModuleDef module; public StandardLinkerContext(TreeLogger logger, ModuleDef module, ResourceOracle publicResourceOracle, JsOutputOption outputOption) @@ -143,6 +144,7 @@ public StandardLinkerContext(TreeLogger logger, ModuleDef module, this.moduleLastModified = module.lastModified(); this.publicResourceOracle = publicResourceOracle; this.outputOption = outputOption; + this.module = module; // Sort the linkers into the order they should actually run. linkerClasses = new ArrayList>(); @@ -537,6 +539,10 @@ public void produceOutput(TreeLogger logger, ArtifactSet artifacts, } } + public ModuleDef getModule() { + return module; + } + /** * (Re)instantiate all linkers. */ diff --git a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java index edffd14cc7f..b56a65d7f4a 100644 --- a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java +++ b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java @@ -15,6 +15,9 @@ */ package com.google.gwt.core.linker; +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + import com.google.gwt.core.ext.LinkerContext; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; @@ -31,17 +34,27 @@ import com.google.gwt.core.ext.linker.SoftPermutation; import com.google.gwt.core.ext.linker.SymbolData; import com.google.gwt.core.ext.linker.SyntheticArtifact; +import com.google.gwt.core.ext.linker.impl.StandardLinkerContext; +import com.google.gwt.dev.cfg.ResourceLoader; +import com.google.gwt.dev.cfg.ResourceLoaders; import com.google.gwt.dev.util.Util; import com.google.gwt.dev.util.collect.HashMap; import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; +import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapConsumerV3; import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGeneratorV3; -import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapGeneratorV3.ExtensionMergeAction; +import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapParseException; +import com.google.gwt.thirdparty.guava.common.collect.Maps; +import com.google.gwt.util.tools.Utility; import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -162,7 +175,8 @@ public static class SourceMapArtifact extends SyntheticArtifact { private final String sourceRoot; public SourceMapArtifact(int permutationId, int fragment, byte[] js, String sourceRoot) { - super(SymbolMapsLinker.class, permutationId + '/' + sourceMapFilenameForFragment(fragment), js); + super(SymbolMapsLinker.class, permutationId + '/' + + sourceMapFilenameForFragment(fragment), js); this.permutationId = permutationId; this.fragment = fragment; this.js = js; @@ -277,6 +291,7 @@ public ArtifactSet link(TreeLogger logger, LinkerContext context, Event writeSourceMapsEvent = SpeedTracerLogger.start(CompilerEventType.WRITE_SOURCE_MAPS); + StandardLinkerContext stdContext = (StandardLinkerContext) context; for (SourceMapArtifact se : artifacts.find(SourceMapArtifact.class)) { // filename is permutation_id/sourceMap.json String sourceMapString = Util.readStreamAsString(se.getContents(logger)); @@ -312,14 +327,16 @@ public ArtifactSet link(TreeLogger logger, LinkerContext context, totalPrefixLines += op.getNumLines(); } } + // TODO(cromwellian): apply insert and remove edits - sourceMapGenerator.mergeMapSection(totalPrefixLines, 0, sourceMapString, - new ExtensionMergeAction() { - @Override - public Object merge(String extKey, Object oldVal, Object newVal) { - return newVal; - } - }); + if (stdContext.getModule().shouldEmbedSourceMapContents()) { + embedSourcesInSourceMaps(logger, stdContext, artifacts, sourceMapGenerator, + totalPrefixLines, sourceMapString, partialPath); + } else { + sourceMapGenerator.mergeMapSection(totalPrefixLines, 0, sourceMapString, + (extKey, oldVal, newVal) -> newVal); + } + StringWriter stringWriter = new StringWriter(); sourceMapGenerator.appendTo(stringWriter, "sourceMap"); emArt = emitSourceMapString(logger, stringWriter.toString(), partialPath); @@ -335,6 +352,68 @@ public Object merge(String extKey, Object oldVal, Object newVal) { return artifacts; } + private static void embedSourcesInSourceMaps(TreeLogger logger, StandardLinkerContext context, + ArtifactSet artifacts, + SourceMapGeneratorV3 sourceMapGenerator, + int totalPrefixLines, String sourceMapString, + String partialPath) + throws SourceMapParseException { + sourceMapGenerator.setStartingPosition(totalPrefixLines, 0); + SourceMapConsumerV3 section = new SourceMapConsumerV3(); + section.parse(sourceMapString); + section.visitMappings(sourceMapGenerator::addMapping); + + for (Entry entry : section.getExtensions().entrySet()) { + String extensionKey = entry.getKey(); + sourceMapGenerator.addExtension(extensionKey, entry.getValue()); + } + + ResourceLoader resourceLoader = ResourceLoaders.fromContextClassLoader(); + + Map generatedSources = Maps.newHashMap(); + artifacts.find(EmittedArtifact.class) + .forEach(emittedArtifact -> { + if (Visibility.Source == emittedArtifact.getVisibility()) { + generatedSources.put(emittedArtifact.getPartialPath(), emittedArtifact); + } + }); + + for (String sourceFileName : section.getOriginalSources()) { + String content; + InputStream cis = null; + try { + cis = loadSource(logger, sourceFileName, generatedSources, + resourceLoader); + if (isNull(cis)) { + cis = context.getModule().findSourceFile(sourceFileName).openContents(); + } + content = Util.readStreamAsString(cis); + sourceMapGenerator.addSourcesContent(sourceFileName, content); + } catch (UnableToCompleteException | URISyntaxException | IOException e) { + logger.log(TreeLogger.Type.WARN, "Can't write source map " + + partialPath, e); + } finally { + Utility.close(cis); + } + } + } + + private static InputStream loadSource(TreeLogger logger, String sourceFileName, + Map generatedSources, + ResourceLoader resourceLoader) + throws UnableToCompleteException, URISyntaxException, IOException { + if (generatedSources.containsKey(sourceFileName)) { + return generatedSources.get(sourceFileName).getContents(logger); + } else { + // ask the resourceOracle for the file contents and add it + URL resource = resourceLoader.getResource(sourceFileName); + if (nonNull(resource)) { + return resource.openStream(); + } + } + return null; + } + /** * Override to change the manner in which the symbol map is emitted. */ diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java index 3b8a1b042f8..2a65b87b77b 100644 --- a/dev/core/src/com/google/gwt/dev/CompilePerms.java +++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java @@ -380,8 +380,12 @@ private boolean precompileAndCompile(TreeLogger logger, String moduleName, if (precompilation == null) { return false; } + + boolean embedSourcesContent = compilerContext.getModule() + .shouldEmbedSourceMapContents(); + // TODO: move to precompile() after params are refactored - if (!options.shouldSaveSource()) { + if (!options.shouldSaveSource() && !embedSourcesContent) { precompilation.removeSourceArtifacts(logger); } diff --git a/dev/core/src/com/google/gwt/dev/Compiler.java b/dev/core/src/com/google/gwt/dev/Compiler.java index 6e122046bde..31a22f8c197 100644 --- a/dev/core/src/com/google/gwt/dev/Compiler.java +++ b/dev/core/src/com/google/gwt/dev/Compiler.java @@ -193,8 +193,11 @@ public static boolean compile( if (precompilation == null) { return false; } + + boolean embedSourcesContent = compilerContext.getModule() + .shouldEmbedSourceMapContents(); // TODO: move to precompile() after params are refactored - if (!options.shouldSaveSource()) { + if (!options.shouldSaveSource() && !embedSourcesContent) { precompilation.removeSourceArtifacts(branch); } diff --git a/dev/core/src/com/google/gwt/dev/Link.java b/dev/core/src/com/google/gwt/dev/Link.java index d7d00625010..a0e02c09db0 100644 --- a/dev/core/src/com/google/gwt/dev/Link.java +++ b/dev/core/src/com/google/gwt/dev/Link.java @@ -415,7 +415,10 @@ private static void doProduceOutput(TreeLogger logger, ArtifactSet artifacts, linkerContext.produceOutput(logger, artifacts, Visibility.Private, extraFileSet); - if (saveSources) { + boolean embedSourcesContent = linkerContext.getModule() + .shouldEmbedSourceMapContents(); + + if (saveSources && !embedSourcesContent) { // Assume that all source code is available in the compiler's classpath. // (This will have to be adjusted to work with Super Dev Mode.) ResourceLoader loader = ResourceLoaders.fromContextClassLoader(); diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java index a81bdc80a0c..8386a61f231 100644 --- a/dev/core/src/com/google/gwt/dev/Precompile.java +++ b/dev/core/src/com/google/gwt/dev/Precompile.java @@ -443,7 +443,7 @@ public boolean run(TreeLogger logger) throws UnableToCompleteException { return false; } // TODO: move to precompile() after params are refactored - if (!options.shouldSaveSource()) { + if (!options.shouldSaveSource() && !module.shouldEmbedSourceMapContents()) { precompilation.removeSourceArtifacts(logger); } Util.writeObjectAsFile(logger, precompilationFile, precompilation); diff --git a/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java b/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java index dd679c6948d..9a123d6a5c0 100644 --- a/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java +++ b/dev/core/src/com/google/gwt/dev/PrecompileOnePerm.java @@ -233,8 +233,11 @@ private boolean precompilePermutation(TreeLogger logger, return false; } + boolean embedSourcesContent = compilerContext.getModule() + .shouldEmbedSourceMapContents(); + // TODO: precompile should do this after we get the parameter passing refactored. - if (!options.shouldSaveSource()) { + if (!options.shouldSaveSource() && embedSourcesContent) { precompilation.removeSourceArtifacts(logger); } diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java index 9b91b3bb41a..ff68743b463 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java +++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java @@ -67,6 +67,9 @@ * XML for unit tests. */ public class ModuleDef implements DepsInfoProvider { + + public static final String EMBED_SOURCE_MAPS = "compiler.embedSourceMaps"; + private static final ResourceFilter NON_JAVA_RESOURCES = new ResourceFilter() { @Override public boolean allows(String path) { @@ -628,6 +631,22 @@ public synchronized void setNameOverride(String nameOverride) { this.nameOverride = nameOverride; } + /** + * Checks if embedding sources content inside sourceMaps json is enabled or not. + * @return the boolean value true/false of compiler.embedSourceMaps + * configuration property + */ + public boolean shouldEmbedSourceMapContents() { + return getProperties() + .getConfigurationProperties() + .stream() + .filter(configurationProperty -> EMBED_SOURCE_MAPS.equals( + configurationProperty.getName())) + .findFirst() + .map(configurationProperty -> Boolean.valueOf(configurationProperty.getValue())) + .orElse(false); + } + void addBindingPropertyDefinedValue(BindingProperty bindingProperty, String token) { bindingProperty.addDefinedValue(bindingProperty.getRootCondition(), token); } diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index a51cd7f9597..5895e377d82 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java @@ -1201,6 +1201,7 @@ private String buildEntryMethodHolder(StandardGeneratorContext context, } EntryMethodHolderGenerator entryMethodHolderGenerator = new EntryMethodHolderGenerator(); + context.setCurrentGenerator(EntryMethodHolderGenerator.class); String entryMethodHolderTypeName = entryMethodHolderGenerator.generate(logger, context, module.getCanonicalName()); context.finish(logger); diff --git a/eclipse/dev/.classpath b/eclipse/dev/.classpath index ac771cbca58..a84d801f71e 100644 --- a/eclipse/dev/.classpath +++ b/eclipse/dev/.classpath @@ -21,7 +21,7 @@ - + diff --git a/eclipse/dev/codeserver/.classpath b/eclipse/dev/codeserver/.classpath index 88a6968b93b..bc64bf5c128 100644 --- a/eclipse/dev/codeserver/.classpath +++ b/eclipse/dev/codeserver/.classpath @@ -3,7 +3,7 @@ - + diff --git a/eclipse/user/.classpath b/eclipse/user/.classpath index 75f936204d0..1fa63d0ffcc 100644 --- a/eclipse/user/.classpath +++ b/eclipse/user/.classpath @@ -44,7 +44,7 @@ - + diff --git a/requestfactory/build.xml b/requestfactory/build.xml index c835c86da4e..ba82e804e35 100755 --- a/requestfactory/build.xml +++ b/requestfactory/build.xml @@ -41,6 +41,9 @@ + + + diff --git a/servlet/build.xml b/servlet/build.xml index 47d554892f1..9f23dfcdf5f 100644 --- a/servlet/build.xml +++ b/servlet/build.xml @@ -24,11 +24,11 @@ - - + + + - @@ -70,11 +70,11 @@ - - + + + - diff --git a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml index df4aca4b873..fdeb60705fc 100644 --- a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml +++ b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml @@ -96,6 +96,12 @@ + + + + diff --git a/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java b/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java index 776e4cfd964..f6f37ef59f4 100644 --- a/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java +++ b/user/src/com/google/gwt/core/server/StackTraceDeobfuscator.java @@ -15,9 +15,9 @@ */ package com.google.gwt.core.server; +import com.google.gwt.thirdparty.debugging.sourcemap.OriginalMapping; import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapConsumerFactory; import com.google.gwt.thirdparty.debugging.sourcemap.SourceMapping; -import com.google.gwt.thirdparty.debugging.sourcemap.proto.Mapping; import java.io.BufferedReader; import java.io.File; @@ -299,13 +299,13 @@ public final StackTraceElement resymbolize(StackTraceElement ste, String strongN if (sourceMapCapable && fragmentId != -1 && column != -1) { SourceMapping sourceMapping = loadSourceMap(strongName, fragmentId); if (sourceMapping != null && ste.getLineNumber() > -1) { - Mapping.OriginalMapping mappingForLine = sourceMapping + OriginalMapping mappingForLine = sourceMapping .getMappingForLine(jsLineNumber, column); if (mappingForLine != null) { if (declaringClass == null || declaringClass.equals(ste.getClassName())) { declaringClass = mappingForLine.getOriginalFile(); - methodName = mappingForLine.getIdentifier(); + methodName = mappingForLine.getIdentifier().orElse(null); } fileName = mappingForLine.getOriginalFile(); lineNumber = mappingForLine.getLineNumber(); From 5e87768cb9917ae5ccb69053e62ca071c1caffce Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Mon, 22 Jul 2024 18:52:44 -0500 Subject: [PATCH 02/12] String tests should be useful in optimized output (#9986) Resolves TODOs in the old tests, so that the DeadCodeElimination pass doesn't rewrite the test to do nothing, but actually confirm that the String class does what is expected. Related #9975 --- .../gwt/emultest/java/lang/StringTest.java | 39 ++++++------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/user/test/com/google/gwt/emultest/java/lang/StringTest.java b/user/test/com/google/gwt/emultest/java/lang/StringTest.java index 4b08a45eeea..8b75056edae 100644 --- a/user/test/com/google/gwt/emultest/java/lang/StringTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/StringTest.java @@ -24,13 +24,6 @@ import java.util.Arrays; import java.util.Locale; -/** - * TODO: COMPILER OPTIMIZATIONS HAVE MADE THIS TEST NOT ACTUALLY TEST ANYTHING! - * NEED A VERSION THAT DOESN'T USE STATICALLY DETERMINABLE STRINGS! - * - * See individual method TODOs for ones that still need work -- the ones without - * comments are already protected against optimization. - */ public class StringTest extends GWTTestCase { @Override @@ -862,9 +855,6 @@ public void testToString() { assertSame("s same as s.toString()", s, s.toString()); } - /* - * TODO: needs rewriting to avoid compiler optimizations. - */ public void testTrim() { trimRightAssertEquals("abc", " \t abc \n "); trimRightAssertEquals("abc", "abc"); @@ -911,40 +901,33 @@ public void testUpperCase() { assertEquals("", hideFromCompiler("").toUpperCase(Locale.getDefault())); } - /* - * TODO: needs rewriting to avoid compiler optimizations. - */ public void testValueOf() { - assertTrue(String.valueOf(C.FLOAT_VALUE).startsWith(C.FLOAT_STRING)); - assertEquals(C.INT_STRING, String.valueOf(C.INT_VALUE)); - assertEquals(C.LONG_STRING, String.valueOf(C.LONG_VALUE)); - assertTrue(String.valueOf(C.DOUBLE_VALUE).startsWith(C.DOUBLE_STRING)); - assertEquals(C.CHAR_STRING, String.valueOf(C.CHAR_VALUE)); - assertEquals(C.CHAR_ARRAY_STRING, String.valueOf(C.CHAR_ARRAY_VALUE)); + assertTrue(String.valueOf(hideFromCompiler(C.FLOAT_VALUE)).startsWith(C.FLOAT_STRING)); + assertEquals(C.INT_STRING, String.valueOf(hideFromCompiler(C.INT_VALUE))); + assertEquals(C.LONG_STRING, String.valueOf(hideFromCompiler(C.LONG_VALUE))); + assertTrue(String.valueOf(C.DOUBLE_VALUE).startsWith(hideFromCompiler(C.DOUBLE_STRING))); + assertEquals(C.CHAR_STRING, String.valueOf(hideFromCompiler(C.CHAR_VALUE))); + assertEquals(C.CHAR_ARRAY_STRING, String.valueOf(hideFromCompiler(C.CHAR_ARRAY_VALUE))); assertEquals( - C.CHAR_ARRAY_STRING_SUB, String.valueOf(C.CHAR_ARRAY_VALUE, 1, + C.CHAR_ARRAY_STRING_SUB, String.valueOf(hideFromCompiler(C.CHAR_ARRAY_VALUE), 1, 4)); - assertEquals(C.FALSE_STRING, String.valueOf(C.FALSE_VALUE)); - assertEquals(C.TRUE_STRING, String.valueOf(C.TRUE_VALUE)); + assertEquals(C.FALSE_STRING, String.valueOf(hideFromCompiler(C.FALSE_VALUE))); + assertEquals(C.TRUE_STRING, String.valueOf(hideFromCompiler(C.TRUE_VALUE))); assertEquals(C.getLargeCharArrayString(), String.valueOf(C.getLargeCharArrayValue())); } /** * Helper method for testTrim to avoid compiler optimizations. - * - * TODO: insufficient, compiler now inlines. */ public void trimRightAssertEquals(String left, String right) { - assertEquals(left, right.trim()); + assertEquals(left, hideFromCompiler(right).trim()); } /** * Helper method for testTrim to avoid compiler optimizations. - * - * TODO: insufficient, compiler now inlines. */ public void trimRightAssertSame(String left, String right) { - assertSame(left, right.trim()); + assertSame(left, hideFromCompiler(right).trim()); } private void compareList(String category, String[] desired, String[] got) { From 8618e23164d1a9e4f8ae60b33c8ca33e44cfe38c Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Wed, 24 Jul 2024 09:28:49 -0500 Subject: [PATCH 03/12] Include the module name when linking to recent Javadoc (#9983) Fixes #9895 --- .../google/doctool/custom/JavaEmulSummaryDoclet.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/build_tools/doctool/src/com/google/doctool/custom/JavaEmulSummaryDoclet.java b/build_tools/doctool/src/com/google/doctool/custom/JavaEmulSummaryDoclet.java index 1b8a5ce21be..ba0d8eb0f27 100644 --- a/build_tools/doctool/src/com/google/doctool/custom/JavaEmulSummaryDoclet.java +++ b/build_tools/doctool/src/com/google/doctool/custom/JavaEmulSummaryDoclet.java @@ -42,6 +42,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -79,13 +80,18 @@ public boolean run(DocletEnvironment env) { pw.println("\n"); getSpecifiedPackages(env).forEach(pack -> { + Optional matchingModuleName = ModuleLayer.boot().modules().stream() + .filter(m -> m.getPackages().contains(pack.getQualifiedName().toString())) + .findFirst(); + pw.format("

Package %s

\n", pack.getQualifiedName().toString().replace('.', '_'), pack.getQualifiedName().toString()); pw.println("
"); - String packURL = JAVADOC_URL + pack.getQualifiedName().toString() - .replace(".", "/") + "/"; + String packURL = JAVADOC_URL + + matchingModuleName.map(m -> m.getName() + "/").orElse("") + + pack.getQualifiedName().toString().replace(".", "/") + "/"; Iterator classesIterator = pack.getEnclosedElements() .stream() From e9fecc58ebe471134604ad367e18df5302137da7 Mon Sep 17 00:00:00 2001 From: Zbynek Konecny Date: Wed, 24 Jul 2024 16:29:38 +0200 Subject: [PATCH 04/12] String method emulation for Java 11, align Character.isWhitespace with Java 11 (#9975) Adds emulation for java.lang.String methods added in Java 11, partially completing #9831: * isBlank * lines * repeat * strip * stripLeading * stripTrailing Fixes #9973 Partial #9831 --- .../google/gwt/emul/java/lang/Character.java | 2 +- .../com/google/gwt/emul/java/lang/String.java | 92 +++++++++++++++ .../google/gwt/emultest/EmulJava11Suite.java | 2 + .../gwt/emultest/java/lang/CharacterTest.java | 6 +- .../gwt/emultest/java/lang/StringTest.java | 1 + .../gwt/emultest/java11/lang/StringTest.java | 110 ++++++++++++++++++ 6 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 user/test/com/google/gwt/emultest/java11/lang/StringTest.java diff --git a/user/super/com/google/gwt/emul/java/lang/Character.java b/user/super/com/google/gwt/emul/java/lang/Character.java index 4b01e4d43d3..d675b1719e6 100644 --- a/user/super/com/google/gwt/emul/java/lang/Character.java +++ b/user/super/com/google/gwt/emul/java/lang/Character.java @@ -322,7 +322,7 @@ private static boolean isWhitespace(String ch) { // the Java definition includes separators. whitespaceRegex = new NativeRegExp( - "[\\u1680\\u180E\\u2000-\\u2006\\u2008-\\u200A\\u2028\\u2029\\u205F\\u3000\\uFEFF]" + "[\\u1680\\u2000-\\u2006\\u2008-\\u200A\\u2028\\u2029\\u205F\\u3000]" + "|[\\t-\\r ]" + "|[\\x1C-\\x1F]"); } diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java index 7b4872bb8ad..693f53bdc7f 100644 --- a/user/super/com/google/gwt/emul/java/lang/String.java +++ b/user/super/com/google/gwt/emul/java/lang/String.java @@ -16,6 +16,7 @@ package java.lang; +import static javaemul.internal.InternalPreconditions.checkArgument; import static javaemul.internal.InternalPreconditions.checkCriticalStringBounds; import static javaemul.internal.InternalPreconditions.checkNotNull; import static javaemul.internal.InternalPreconditions.checkStringBounds; @@ -27,7 +28,12 @@ import java.nio.charset.UnsupportedCharsetException; import java.util.Comparator; import java.util.Locale; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.StringJoiner; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import javaemul.internal.ArrayHelper; import javaemul.internal.Coercions; import javaemul.internal.EmulatedCharset; @@ -755,6 +761,91 @@ public String trim() { return start > 0 || end < length ? substring(start, end) : this; } + public String strip() { + int length = length(); + int start = getLeadingWhitespaceLength(); + if (start == length) { + return ""; + } + return substring(start, length - getTrailingWhitespaceLength()); + } + + public String stripLeading() { + return substring(getLeadingWhitespaceLength()); + } + + public String stripTrailing() { + return substring(0, length() - getTrailingWhitespaceLength()); + } + + public boolean isBlank() { + return length() == getLeadingWhitespaceLength(); + } + + public Stream lines() { + return StreamSupport.stream(new LinesSpliterator(), false); + } + + public String repeat(int count) { + checkArgument(count >= 0, "count is negative: " + count); + return asNativeString().repeat(count); + } + + private int getLeadingWhitespaceLength() { + int length = length(); + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(charAt(i))) { + return i; + } + } + return length; + } + + private int getTrailingWhitespaceLength() { + int length = length(); + for (int i = length - 1; i >= 0; i--) { + if (!Character.isWhitespace(charAt(i))) { + return length - 1 - i; + } + } + return length; + } + + private class LinesSpliterator extends Spliterators.AbstractSpliterator { + private int nextIndex = 0; + private int rPosition = -1; + private int nPosition = -1; + + private LinesSpliterator() { + super(Long.MAX_VALUE, Spliterator.IMMUTABLE | Spliterator.ORDERED); + } + + @Override + public boolean tryAdvance(Consumer action) { + if (isEmpty()) { + return false; + } + if (rPosition < nextIndex) { + rPosition = cappedIndexOf('\r'); + } + if (nPosition < nextIndex) { + nPosition = cappedIndexOf('\n'); + } + int lineEnd = Math.min(nPosition, rPosition); + action.accept(substring(nextIndex, lineEnd)); + nextIndex = lineEnd + 1; + if (nPosition == rPosition + 1) { + nextIndex++; + } + return nextIndex < length(); + } + + private int cappedIndexOf(char c) { + int index = indexOf(c, nextIndex); + return index == -1 ? length() : index; + } + } + @JsType(isNative = true, name = "String", namespace = "") private static class NativeString { public static native String fromCharCode(char x); @@ -771,6 +862,7 @@ private static class NativeString { public native String toLocaleUpperCase(); public native String toLowerCase(); public native String toUpperCase(); + public native String repeat(int count); } // CHECKSTYLE_OFF: Utility Methods for unboxed String. diff --git a/user/test/com/google/gwt/emultest/EmulJava11Suite.java b/user/test/com/google/gwt/emultest/EmulJava11Suite.java index 01b44205b85..9c757edfc3a 100644 --- a/user/test/com/google/gwt/emultest/EmulJava11Suite.java +++ b/user/test/com/google/gwt/emultest/EmulJava11Suite.java @@ -15,6 +15,7 @@ */ package com.google.gwt.emultest; +import com.google.gwt.emultest.java11.lang.StringTest; import com.google.gwt.emultest.java11.util.OptionalDoubleTest; import com.google.gwt.emultest.java11.util.OptionalIntTest; import com.google.gwt.emultest.java11.util.OptionalLongTest; @@ -32,6 +33,7 @@ OptionalLongTest.class, OptionalTest.class, PredicateTest.class, + StringTest.class, }) public class EmulJava11Suite { } diff --git a/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java b/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java index 41c6f247ec4..a2ccc9e6ade 100644 --- a/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java @@ -360,7 +360,8 @@ public void testIsWhitepace() { char[] nonBreakingSpaceSeparators = { '\u00A0', // NO-BREAK SPACE. '\u2007', // FIGURE SPACE. - '\u202F' // NARROW NO-BREAK SPACE. + '\u202F', // NARROW NO-BREAK SPACE. + '\uFFEF' // ZERO WIDTH NO-BREAK SPACE. }; char[] specialCases = { @@ -379,7 +380,8 @@ public void testIsWhitepace() { 'a', // LATIN SMALL LETTER A. 'B', // LATIN CAPITAL LETTER B. '_', // LOW LINE. - '\u2500' // BOX DRAWINGS LIGHT HORIZONTAL. + '\u2500', // BOX DRAWINGS LIGHT HORIZONTAL. + '\u180E', // MONGOLIAN VOWEL SEPARATOR, was considered whitespace in Java 8. }; int[] supplementaryCounterExamples = { diff --git a/user/test/com/google/gwt/emultest/java/lang/StringTest.java b/user/test/com/google/gwt/emultest/java/lang/StringTest.java index 8b75056edae..ea12fee74c1 100644 --- a/user/test/com/google/gwt/emultest/java/lang/StringTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/StringTest.java @@ -885,6 +885,7 @@ public void testTrim() { // JavaScript would trim \u2029 and other unicode whitespace type characters; but Java wont trimRightAssertEquals("\u2029abc\u00a0","\u2029abc\u00a0"); + trimRightAssertEquals("\uffefx\u180e", "\uffefx\u180e "); } public void testUpperCase() { diff --git a/user/test/com/google/gwt/emultest/java11/lang/StringTest.java b/user/test/com/google/gwt/emultest/java11/lang/StringTest.java new file mode 100644 index 00000000000..92650aca510 --- /dev/null +++ b/user/test/com/google/gwt/emultest/java11/lang/StringTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.emultest.java11.lang; + +import com.google.gwt.emultest.java.util.EmulTestBase; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * Tests for java.lang.String Java 11 API emulation. + */ +public class StringTest extends EmulTestBase { + public void testIsBlank() { + assertTrue(hideFromCompiler("").isBlank()); + assertTrue(hideFromCompiler(" ").isBlank()); + assertFalse(hideFromCompiler("x ").isBlank()); + assertTrue(hideFromCompiler("\u001c").isBlank()); + assertFalse(hideFromCompiler("\u00a0").isBlank()); + } + + public void testStrip() { + stripRightAsssertEquals("", ""); + stripRightAsssertEquals("", " "); + stripRightAsssertEquals("x", " x "); + stripRightAsssertEquals("x", "\u001cx\u001c"); + stripRightAsssertEquals("\u00a0x\u00a0", "\u00a0x\u00a0 "); + stripRightAsssertEquals("\uffefx\u180e", "\uffefx\u180e "); + } + + public void testStripLeading() { + stripRightLeadingAsssertEquals("", ""); + stripRightLeadingAsssertEquals("", " "); + stripRightLeadingAsssertEquals("x ", " x "); + stripRightLeadingAsssertEquals("x\u001c", "\u001cx\u001c"); + stripRightLeadingAsssertEquals("\u00a0x\u00a0", "\u00a0x\u00a0"); + } + + public void testStripTrailing() { + stripRightTrailingAsssertEquals("", ""); + stripRightTrailingAsssertEquals("", " "); + stripRightTrailingAsssertEquals(" x", " x "); + stripRightTrailingAsssertEquals("\u001cx", "\u001cx\u001c"); + stripRightTrailingAsssertEquals("\u00a0x\u00a0", "\u00a0x\u00a0 "); + } + + private void stripRightAsssertEquals(String expected, String arg) { + assertEquals(expected, hideFromCompiler(arg).strip()); + } + + private void stripRightLeadingAsssertEquals(String expected, String arg) { + assertEquals(expected, hideFromCompiler(arg).stripLeading()); + } + + private void stripRightTrailingAsssertEquals(String expected, String arg) { + assertEquals(expected, hideFromCompiler(arg).stripTrailing()); + } + + public void testRepeat() { + assertEquals("", hideFromCompiler("foo").repeat(0)); + assertEquals("foo", hideFromCompiler("foo").repeat(1)); + assertEquals("foofoofoo", hideFromCompiler("foo").repeat(3)); + try { + String noFoo = hideFromCompiler("foo").repeat(-1); + throw new Error("Should fail with negative arg"); + } catch (IllegalArgumentException ex) { + assertEquals("count is negative: -1", ex.getMessage()); + } + } + + public void testLines() { + assertEquals(Arrays.asList("a", "b", "c", "d"), + "a\rb\nc\r\nd".lines().collect(Collectors.toList())); + assertEquals(Arrays.asList("a"), + "a\n".lines().collect(Collectors.toList())); + assertEquals(Arrays.asList("a"), + "a\r\n".lines().collect(Collectors.toList())); + assertEquals(Arrays.asList(), + "".lines().collect(Collectors.toList())); + assertEquals(Arrays.asList(""), + "\n".lines().collect(Collectors.toList())); + assertEquals(Arrays.asList(""), + "\r\n".lines().collect(Collectors.toList())); + assertEquals(Arrays.asList("", ""), + "\n\r\n".lines().collect(Collectors.toList())); + assertEquals(Arrays.asList("", "", "c"), + "\n\r\nc".lines().collect(Collectors.toList())); + } + + private T hideFromCompiler(T value) { + if (Math.random() < -1) { + // Can never happen, but fools the compiler enough not to optimize this call. + fail(); + } + return value; + } +} From a78f58f4379bcb17cc032d2a9834ef7e9bb3735b Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Wed, 24 Jul 2024 11:54:49 -0500 Subject: [PATCH 05/12] Update CI for Java 22, improved PR checks (#9979) Updates nightly builds and PR checks to verify they run cleanly with Java 22, and ensures that all tests compile for PR checks. Also tries to fix reviewdog annotations by adding a newly required parameter. --- .github/workflows/full-check.yml | 2 +- .github/workflows/quick-check.yml | 5 ++- .../javac/GeneratedClassnameFinderTest.java | 44 +++++++++++-------- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.github/workflows/full-check.yml b/.github/workflows/full-check.yml index 0ce36b8411f..c1a277efd9f 100644 --- a/.github/workflows/full-check.yml +++ b/.github/workflows/full-check.yml @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java-version: [ '11', '17', '21' ] + java-version: [ '11', '17', '21', '22' ] steps: - name: Checkout GWT itself into one directory uses: actions/checkout@v4 diff --git a/.github/workflows/quick-check.yml b/.github/workflows/quick-check.yml index 5dfdfd55cc1..09977bd4d68 100644 --- a/.github/workflows/quick-check.yml +++ b/.github/workflows/quick-check.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java-version: ['11', '17', '21'] + java-version: ['11', '17', '21', '22'] steps: - name: Checkout GWT itself into one directory uses: actions/checkout@v4 @@ -42,12 +42,13 @@ jobs: TZ=America/Los_Angeles \ ANT_OPTS=-Dfile.encoding=UTF8 \ ANT_OPTS=-Xmx2g - ant clean dist doc checkstyle apicheck + ant clean compile.tests dist doc checkstyle apicheck - name: Create pull request comments/annotations for checkstyle from the java 21 build, even on failure if: ${{ always() && github.event_name == 'pull_request' && matrix.java-version == '21' }} env: REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REVIEWDOG_SKIP_DOGHOUSE: true run: | set -eux shopt -s globstar diff --git a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java index b878acf3ecf..65926826dc2 100644 --- a/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java +++ b/dev/core/test/com/google/gwt/dev/javac/GeneratedClassnameFinderTest.java @@ -107,35 +107,41 @@ public void testEnum() { public void testJavacWeirdness() { List classNames = new JavacWeirdnessTester().getGeneratedClasses(); - if (classNames.size() == 3) { - // javac7 - JavacWeirdnessTester$1 doesn't verify, so it's excluded - assertTrue(classNames.get(0) + " should not contain Foo", - classNames.get(0).indexOf("Foo") == -1); - assertTrue(classNames.get(1) + " should contain Foo", - classNames.get(1).indexOf("Foo") != -1); - assertTrue(classNames.get(2) + " should contain Foo", - classNames.get(2).indexOf("Foo") != -1); - } else if (classNames.size() == 4) { + if (classNames.size() == 4) { // javac8: // JavacWeirdnessTester$1 // JavacWeirdnessTester$2 // JavacWeirdnessTester$2Foo // JavacWeirdnessTester$3Foo - assertTrue(classNames.get(0) + " should not contain Foo", - classNames.get(0).indexOf("Foo") == -1); - assertTrue(classNames.get(1) + " should not contain Foo", - classNames.get(1).indexOf("Foo") == -1); - assertTrue(classNames.get(2) + " should contain Foo", - classNames.get(2).indexOf("Foo") != -1); - assertTrue(classNames.get(3) + " should contain Foo", - classNames.get(3).indexOf("Foo") != -1); + assertFalse(classNames.get(0) + " should not contain Foo", + classNames.get(0).contains("Foo")); + assertFalse(classNames.get(1) + " should not contain Foo", + classNames.get(1).contains("Foo")); + assertTrue(classNames.get(2) + " should contain Foo", classNames.get(2).contains("Foo")); + assertTrue(classNames.get(3) + " should contain Foo", classNames.get(3).contains("Foo")); + } else if (classNames.size() == 5) { + // javac22: + // JavacWeirdnessTester$1 + // JavacWeirdnessTester$2 + // JavacWeirdnessTester$1Foo + // JavacWeirdnessTester$2Foo + // JavacWeirdnessTester$3Foo + assertFalse(classNames.get(0) + " should not contain Foo", + classNames.get(0).contains("Foo")); + assertFalse(classNames.get(1) + " should not contain Foo", + classNames.get(1).contains("Foo")); + assertTrue(classNames.get(2) + " should contain Foo", classNames.get(2).contains("Foo")); + assertTrue(classNames.get(3) + " should contain Foo", classNames.get(2).contains("Foo")); + assertTrue(classNames.get(4) + " should contain Foo", classNames.get(3).contains("Foo")); } else { - fail(); + fail("Expected 4 or 5 classes, found " + classNames); } } public void testNamedLocal() { - assertEquals(2, new NamedLocalTester().getGeneratedClasses().size()); + List generatedClasses = new NamedLocalTester().getGeneratedClasses(); + assertTrue(generatedClasses.toString(), + generatedClasses.size() == 2 || generatedClasses.size() == 3); } public void testNested() { From 1ef74dfaf430bfebc993439925592aa2f8e5ddac Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Thu, 25 Jul 2024 19:01:16 -0500 Subject: [PATCH 06/12] Implement java.lang.Math.nextAfter, nextUp, nextDown (#9978) Fixes #9977 --- .../com/google/gwt/emul/java/lang/Math.java | 77 +++++- .../gwt/emultest/java/lang/MathTest.java | 240 +++++++++++++++++- 2 files changed, 307 insertions(+), 10 deletions(-) diff --git a/user/super/com/google/gwt/emul/java/lang/Math.java b/user/super/com/google/gwt/emul/java/lang/Math.java index 5bf6829ebab..4d9d5da6742 100644 --- a/user/super/com/google/gwt/emul/java/lang/Math.java +++ b/user/super/com/google/gwt/emul/java/lang/Math.java @@ -32,14 +32,6 @@ public final class Math { // public static int getExponent (double d) // public static int getExponent (float f) // public static double IEEEremainder(double f1, double f2) - // public static double nextAfter(double start, double direction) - // public static float nextAfter(float start, float direction) - // public static double nextUp(double start) { - // return nextAfter(start, 1.0d); - // } - // public static float nextUp(float start) { - // return nextAfter(start,1.0f); - // } public static final double E = 2.7182818284590452354; public static final double PI = 3.14159265358979323846; @@ -344,6 +336,75 @@ public static double toRadians(double x) { return x * PI_OVER_180; } + public static double nextAfter(double start, double direction) { + // Simple case described by Javadoc: + if (start == direction) { + return direction; + } + + // NaN special case, if either is NaN, return NaN. + if (Double.isNaN(start) || Double.isNaN(direction)) { + return Double.NaN; + } + + // The javadoc 'special cases' for infinities and min_value are handled already by manipulating + // the bits of the start value below. However, that approach used below doesn't work around + // zeros - we have two zero values to deal with (positive and negative) with very different bit + // representations (zero and Long.MIN_VALUE respectively). + if (start == 0) { + return direction > start ? Double.MIN_VALUE : -Double.MIN_VALUE; + } + + // Convert to int bits and increment or decrement - the fact that two positive ieee754 float + // values can be compared as ints (or two negative values, with the comparison inverted) means + // that this trick works as naturally as A + 1 > A. NaNs and zeros were already handled above. + long bits = Double.doubleToLongBits(start); + bits += (direction > start) == (bits >= 0) ? 1 : -1; + return Double.longBitsToDouble(bits); + } + + public static float nextAfter(float start, double direction) { + // Simple case described by Javadoc: + if (start == direction) { + return (float) direction; + } + + // NaN special case, if either is NaN, return NaN. + if (Float.isNaN(start) || Double.isNaN(direction)) { + return Float.NaN; + } + // The javadoc 'special cases' for INFINITYs, MIN_VALUE, and MAX_VALUE are handled already by + // manipulating the bits of the start value below. However, that approach used below doesn't + // work around zeros - we have two zero values to deal with (positive and negative) with very + // different bit representations (zero and Integer.MIN_VALUE respectively). + if (start == 0) { + return direction > start ? Float.MIN_VALUE : -Float.MIN_VALUE; + } + + // Convert to int bits and increment or decrement - the fact that two positive ieee754 float + // values can be compared as ints (or two negative values, with the comparison inverted) means + // that this trick works as naturally as A + 1 > A. NaNs and zeros were already handled above. + int bits = Float.floatToIntBits(start); + bits += (direction > start) == (bits >= 0) ? 1 : -1; + return Float.intBitsToFloat(bits); + } + + public static double nextUp(double start) { + return nextAfter(start, Double.POSITIVE_INFINITY); + } + + public static float nextUp(float start) { + return nextAfter(start, Double.POSITIVE_INFINITY); + } + + public static double nextDown(double start) { + return nextAfter(start, Double.NEGATIVE_INFINITY); + } + + public static float nextDown(float start) { + return nextAfter(start, Double.NEGATIVE_INFINITY); + } + private static boolean isSafeIntegerRange(double value) { return Integer.MIN_VALUE <= value && value <= Integer.MAX_VALUE; } diff --git a/user/test/com/google/gwt/emultest/java/lang/MathTest.java b/user/test/com/google/gwt/emultest/java/lang/MathTest.java index a163ce67353..f195c4e147f 100644 --- a/user/test/com/google/gwt/emultest/java/lang/MathTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/MathTest.java @@ -25,12 +25,13 @@ public class MathTest extends GWTTestCase { private static void assertNegativeZero(double x) { - assertTrue(isNegativeZero(x)); + assertEquals(0.0, x); + assertTrue(String.valueOf(x), isNegativeZero(x)); } private static void assertPositiveZero(double x) { assertEquals(0.0, x); - assertFalse(isNegativeZero(x)); + assertFalse(String.valueOf(x), isNegativeZero(x)); } private static void assertNaN(double x) { @@ -633,4 +634,239 @@ public void testScalb() { assertEquals(4294967296.0f, Math.scalb(1f, 32)); assertEquals(2.3283064e-10f, Math.scalb(1f, -32), 1e-7f); } + + public void testNextAfterFloat() { + // Test the five "special cases" described by the Javadoc, with both Float and Double + // "direction" values. + assertNaN(Math.nextAfter(Float.NaN, Float.NaN)); + assertNaN(Math.nextAfter(Float.NaN, Double.NaN)); + assertNaN(Math.nextAfter(Float.NaN, 0)); + assertNaN(Math.nextAfter(0, Float.NaN)); + assertNaN(Math.nextAfter(0, Double.NaN)); + + assertNegativeZero(Math.nextAfter(0.0f, -0.0f)); + assertNegativeZero(Math.nextAfter(0.0f, -0.0d)); + assertNegativeZero(Math.nextAfter(-0.0f, -0.0f)); + assertNegativeZero(Math.nextAfter(-0.0f, -0.0d)); + assertPositiveZero(Math.nextAfter(0.0f, 0.0f)); + assertPositiveZero(Math.nextAfter(0.0f, 0.0d)); + assertPositiveZero(Math.nextAfter(-0.0f, 0.0f)); + assertPositiveZero(Math.nextAfter(-0.0f, 0.0d)); + + assertNegativeZero(Math.nextAfter(-Float.MIN_VALUE, 1)); + assertPositiveZero(Math.nextAfter(Float.MIN_VALUE, -1)); + + assertEquals(Float.MAX_VALUE, Math.nextAfter(Float.POSITIVE_INFINITY, -1)); + assertEquals(Float.MAX_VALUE, + Math.nextAfter(Float.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)); + assertEquals(Float.MAX_VALUE, + Math.nextAfter(Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY)); + assertEquals(-Float.MAX_VALUE, + Math.nextAfter(Float.NEGATIVE_INFINITY, 1)); + assertEquals(-Float.MAX_VALUE, + Math.nextAfter(Float.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); + assertEquals(-Float.MAX_VALUE, + Math.nextAfter(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY)); + + assertEquals(Float.POSITIVE_INFINITY, + Math.nextAfter(Float.MAX_VALUE, Float.POSITIVE_INFINITY)); + assertEquals(Float.POSITIVE_INFINITY, + Math.nextAfter(Float.MAX_VALUE, Double.POSITIVE_INFINITY)); + assertEquals(Float.NEGATIVE_INFINITY, + Math.nextAfter(-Float.MAX_VALUE, Float.NEGATIVE_INFINITY)); + assertEquals(Float.NEGATIVE_INFINITY, + Math.nextAfter(-Float.MAX_VALUE, Double.NEGATIVE_INFINITY)); + + // General rules: if values compare as equal, return "direction" (exceptions covered above) + assertEquals(Float.POSITIVE_INFINITY, + Math.nextAfter(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)); + assertEquals(Float.POSITIVE_INFINITY, + Math.nextAfter(Float.POSITIVE_INFINITY, Double.POSITIVE_INFINITY)); + assertEquals(Float.NEGATIVE_INFINITY, + Math.nextAfter(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)); + assertEquals(Float.NEGATIVE_INFINITY, + Math.nextAfter(Float.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)); + assertEquals(Float.MAX_VALUE, Math.nextAfter(Float.MAX_VALUE, Float.MAX_VALUE)); + + // Return number adjacent to "start" in the relative direction of "direction". Using hex to + // easily see bit patterns in the sample data. + assertEquals(0x1.fffffcp127f, Math.nextAfter(Float.MAX_VALUE, 0)); + assertEquals(0x1.fffffcp127f, + Math.nextAfter(Float.MAX_VALUE, Float.NEGATIVE_INFINITY)); + assertEquals(-0x1.fffffcp127f, Math.nextAfter(-Float.MAX_VALUE, 0)); + assertEquals(-0x1.fffffcp127f, + Math.nextAfter(-Float.MAX_VALUE, Float.POSITIVE_INFINITY)); + assertEquals(0x1.fffffep124f, Math.nextAfter(0x1.0p125f, 0)); + assertEquals(0x1.0p125f, + Math.nextAfter(0x1.fffffep124f, Float.POSITIVE_INFINITY)); + + // Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative + assertEquals(Float.MIN_VALUE, Math.nextAfter(0.0f, 1)); + assertEquals(Float.MIN_VALUE, Math.nextAfter(-0.0f, 1)); + assertEquals(-Float.MIN_VALUE, Math.nextAfter(0.0f, -1)); + assertEquals(-Float.MIN_VALUE, Math.nextAfter(-0.0f, -1)); + + // Test near 1, where exponent sign flips positive/negative + assertEquals(0x1.000002p0f, Math.nextAfter(1.0f, 2)); + assertEquals(0x1.fffffep-1f, Math.nextAfter(1.0f, 0)); + assertEquals(1.0f, Math.nextAfter(0x1.fffffep-1f, 2)); + assertEquals(1.0f, Math.nextAfter(0x1.000002p0f, 0)); + + // Repeat near -1 + assertEquals(-0x1.000002p0f, Math.nextAfter(-1.0f, -2)); + assertEquals(-0x1.fffffep-1f, Math.nextAfter(-1.0f, 0)); + assertEquals(-1.0f, Math.nextAfter(-0x1.fffffep-1f, -2)); + assertEquals(-1.0f, Math.nextAfter(-0x1.000002p0f, 0)); + } + + public void testNextUpFloat() { + // Special cases from javadoc + assertNaN(Math.nextUp(Float.NaN)); + assertEquals(Float.POSITIVE_INFINITY, Math.nextUp(Float.POSITIVE_INFINITY)); + assertEquals(Float.MIN_VALUE, Math.nextUp(0.0f)); + assertEquals(Float.MIN_VALUE, Math.nextUp(-0.0f)); + + assertEquals(Float.POSITIVE_INFINITY, Math.nextUp(Float.MAX_VALUE)); + assertEquals(-Float.MAX_VALUE, Math.nextUp(Float.NEGATIVE_INFINITY)); + + assertNegativeZero(Math.nextUp(-Float.MIN_VALUE)); + + assertEquals(0x1.0p2f, Math.nextUp(0x1.fffffep1f)); + assertEquals(0x1.000002p2f, Math.nextUp(0x1.0p2f)); + } + + public void testNextDownFloat() { + // Special cases from javadoc + assertNaN(Math.nextDown(Float.NaN)); + assertEquals(Float.NEGATIVE_INFINITY, Math.nextDown(Float.NEGATIVE_INFINITY)); + assertEquals(-Float.MIN_VALUE, Math.nextDown(0.0f)); + assertEquals(-Float.MIN_VALUE, Math.nextDown(-0.0f)); + + assertEquals(Float.NEGATIVE_INFINITY, Math.nextDown(-Float.MAX_VALUE)); + assertEquals(Float.MAX_VALUE, Math.nextDown(Float.POSITIVE_INFINITY)); + + assertPositiveZero(Math.nextDown(Float.MIN_VALUE)); + + assertEquals(0x1.fffffep1f, Math.nextDown(0x1.0p2f)); + assertEquals(0x1.fffffcp1f, Math.nextDown(0x1.fffffep1f)); + } + + public void testNextAfterDouble() { + // Test the five "special cases" described by the Javadoc + assertNaN(Math.nextAfter(Double.NaN, Double.NaN)); + assertNaN(Math.nextAfter(Double.NaN, 0)); + assertNaN(Math.nextAfter(0d, Double.NaN)); + + assertNegativeZero(Math.nextAfter(0.0d, -0.0d)); + assertNegativeZero(Math.nextAfter(-0.0d, -0.0d)); + assertPositiveZero(Math.nextAfter(0.0d, 0.0d)); + assertPositiveZero(Math.nextAfter(-0.0d, 0.0d)); + + assertNegativeZero(Math.nextAfter(-Double.MIN_VALUE, 1)); + assertPositiveZero(Math.nextAfter(Double.MIN_VALUE, -1)); + + assertEquals(Double.MAX_VALUE, Math.nextAfter(Double.POSITIVE_INFINITY, -1)); + assertEquals(Double.MAX_VALUE, + Math.nextAfter(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY)); + assertEquals(-Double.MAX_VALUE, Math.nextAfter(Double.NEGATIVE_INFINITY, 1)); + assertEquals(-Double.MAX_VALUE, + Math.nextAfter(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY)); + + assertEquals(Double.POSITIVE_INFINITY, + Math.nextAfter(Double.MAX_VALUE, Double.POSITIVE_INFINITY)); + assertEquals(Double.NEGATIVE_INFINITY, + Math.nextAfter(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY)); + + // General rules: if values compare as equal, return "direction" (exceptions covered above) + assertEquals(Double.POSITIVE_INFINITY, + Math.nextAfter(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY)); + assertEquals(Double.NEGATIVE_INFINITY, + Math.nextAfter(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY)); + assertEquals(Double.MAX_VALUE, Math.nextAfter(Double.MAX_VALUE, Double.MAX_VALUE)); + + // Return number adjacent to "start" in the relative direction of "direction". Using hex to + // easily see bit patterns in the sample data. + assertEquals(0x1.ffffffffffffep1023, Math.nextAfter(Double.MAX_VALUE, 0)); + assertEquals(0x1.ffffffffffffep1023, + Math.nextAfter(Double.MAX_VALUE, Double.NEGATIVE_INFINITY)); + assertEquals(-0x1.ffffffffffffep1023, Math.nextAfter(-Double.MAX_VALUE, 0)); + assertEquals(-0x1.ffffffffffffep1023, + Math.nextAfter(-Double.MAX_VALUE, Double.POSITIVE_INFINITY)); + assertEquals(0x1.fffffffffffffp124, Math.nextAfter(0x1.0p125, 0)); + assertEquals(0x1.0p125, Math.nextAfter(0x1.fffffffffffffp124, Double.POSITIVE_INFINITY)); + + // Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative + assertEquals(Double.MIN_VALUE, Math.nextAfter(0.0d, 1)); + assertEquals(Double.MIN_VALUE, Math.nextAfter(-0.0d, 1)); + assertEquals(-Double.MIN_VALUE, Math.nextAfter(0.0d, -1)); + assertEquals(-Double.MIN_VALUE, Math.nextAfter(-0.0d, -1)); + + // Test near 1, where exponent sign flips positive/negative + assertEquals(0x1.0000000000001p0d, Math.nextAfter(1.0d, 2)); + assertEquals(0x1.fffffffffffffp-1d, Math.nextAfter(1.0d, 0)); + assertEquals(1.0d, Math.nextAfter(0x1.fffffffffffffp-1d, 2)); + assertEquals(1.0d, Math.nextAfter(0x1.0000000000001p0d, 0)); + + // Repeat near -1 + assertEquals(-0x1.0000000000001p0d, Math.nextAfter(-1.0d, -2)); + assertEquals(-0x1.fffffffffffffp-1d, Math.nextAfter(-1.0d, 0)); + assertEquals(-1.0d, Math.nextAfter(-0x1.fffffffffffffp-1d, -2)); + assertEquals(-1.0d, Math.nextAfter(-0x1.0000000000001p0d, 0)); + } + + public void testNextUpDouble() { + // Special cases from javadoc + assertNaN(Math.nextUp(Double.NaN)); + assertEquals(Double.POSITIVE_INFINITY, Math.nextUp(Double.POSITIVE_INFINITY)); + assertEquals(Double.MIN_VALUE, Math.nextUp(0.0)); + assertEquals(Double.MIN_VALUE, Math.nextUp(-0.0)); + + assertEquals(Double.POSITIVE_INFINITY, Math.nextUp(Double.MAX_VALUE)); + assertEquals(-Double.MAX_VALUE, Math.nextUp(Double.NEGATIVE_INFINITY)); + + assertNegativeZero(Math.nextUp(-Double.MIN_VALUE)); + + assertEquals(0x1.0p2d, Math.nextUp(0x1.fffffffffffffp1d)); + assertEquals(0x1.0000000000001p2d, Math.nextUp(0x1.0p2d)); + + // Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative + assertEquals(Double.MIN_VALUE, Math.nextUp(0.0d)); + assertEquals(Double.MIN_VALUE, Math.nextUp(-0.0d)); + + // Test near 1, where exponent sign flips positive/negative + assertEquals(0x1.0000000000001p0d, Math.nextUp(1.0d)); + assertEquals(1.0d, Math.nextUp(0x1.fffffffffffffp-1d)); + + // Repeat near -1 + assertEquals(-0x1.fffffffffffffp-1d, Math.nextUp(-1.0d)); + assertEquals(-1.0d, Math.nextUp(-0x1.0000000000001p0d)); + } + + public void testNextDownDouble() { + // Special cases from javadoc + assertNaN(Math.nextDown(Double.NaN)); + assertEquals(Double.NEGATIVE_INFINITY, Math.nextDown(Double.NEGATIVE_INFINITY)); + assertEquals(-Double.MIN_VALUE, Math.nextDown(0.0d)); + assertEquals(-Double.MIN_VALUE, Math.nextDown(-0.0d)); + + assertEquals(Double.NEGATIVE_INFINITY, Math.nextDown(-Double.MAX_VALUE)); + assertEquals(Double.MAX_VALUE, Math.nextDown(Double.POSITIVE_INFINITY)); + + assertPositiveZero(Math.nextDown(Double.MIN_VALUE)); + + assertEquals(0x1.fffffffffffffp1d, Math.nextDown(0x1.0p2d)); + assertEquals(0x1.ffffffffffffep1d, Math.nextDown(0x1.fffffffffffffp1d)); + + // Test near zero (minvalue -> zero is tested above), mantissa sign flips positive/negative + assertEquals(-Double.MIN_VALUE, Math.nextDown(0.0d)); + assertEquals(-Double.MIN_VALUE, Math.nextDown(-0.0d)); + + // Test near 1, where exponent sign flips positive/negative + assertEquals(0x1.fffffffffffffp-1d, Math.nextDown(1.0d)); + assertEquals(1.0d, Math.nextDown(0x1.0000000000001p0d)); + + // Repeat near -1 + assertEquals(-0x1.0000000000001p0d, Math.nextDown(-1.0d)); + assertEquals(-1.0d, Math.nextDown(-0x1.fffffffffffffp-1d)); + } } From 22ff07e00d2d0d9b224397cbfc2cb7e2e248bf7d Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 26 Jul 2024 15:11:26 -0500 Subject: [PATCH 07/12] Restore BigDecimal tests that use float/double bits (#9988) --- .../java/math/BigDecimalConvertTest.java | 60 +++++++++---------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/user/test/com/google/gwt/emultest/java/math/BigDecimalConvertTest.java b/user/test/com/google/gwt/emultest/java/math/BigDecimalConvertTest.java index 5f5dd965102..9592da17bca 100644 --- a/user/test/com/google/gwt/emultest/java/math/BigDecimalConvertTest.java +++ b/user/test/com/google/gwt/emultest/java/math/BigDecimalConvertTest.java @@ -62,14 +62,13 @@ public void testByteValue() { /** * Double value of a small negative BigDecimal. */ - // TODO(jat): add back after Double.doubleToLongBits is implemented -// public void testDoubleValueMinusZero() { -// String a = "-123809648392384754573567356745735.63567890295784902768787678287E-400"; -// BigDecimal aNumber = new BigDecimal(a); -// long minusZero = -9223372036854775808L; -// double result = aNumber.doubleValue(); -// assertTrue("incorrect value", Double.doubleToLongBits(result) == minusZero); -// } + public void testDoubleValueMinusZero() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + long minusZero = -9223372036854775808L; + double result = aNumber.doubleValue(); + assertTrue("incorrect value", Double.doubleToLongBits(result) == minusZero); + } /** * Double value of a negative BigDecimal. @@ -94,14 +93,13 @@ public void testDoubleValueNegInfinity() { /** * Double value of a small positive BigDecimal. */ - // TODO(jat): add back after Double.doubleToLongBits is implemented -// public void testDoubleValuePlusZero() { -// String a = "123809648392384754573567356745735.63567890295784902768787678287E-400"; -// BigDecimal aNumber = new BigDecimal(a); -// long zero = 0; -// double result = aNumber.doubleValue(); -// assertTrue("incorrect value", Double.doubleToLongBits(result) == zero); -// } + public void testDoubleValuePlusZero() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + long zero = 0; + double result = aNumber.doubleValue(); + assertTrue("incorrect value", Double.doubleToLongBits(result) == zero); + } /** * Double value of a positive BigDecimal. @@ -126,14 +124,13 @@ public void testDoubleValuePosInfinity() { /** * Float value of a small negative BigDecimal. */ - // TODO(jat): add back after Float.floatToIntBits is implemented -// public void testFloatValueMinusZero() { -// String a = "-123809648392384754573567356745735.63567890295784902768787678287E-400"; -// BigDecimal aNumber = new BigDecimal(a); -// int minusZero = -2147483648; -// float result = aNumber.floatValue(); -// assertTrue("incorrect value", Float.floatToIntBits(result) == minusZero); -// } + public void testFloatValueMinusZero() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + int minusZero = -2147483648; + float result = aNumber.floatValue(); + assertTrue("incorrect value", Float.floatToIntBits(result) == minusZero); + } /** * Float value of a negative BigDecimal. @@ -159,14 +156,13 @@ public void testFloatValueNegInfinity() { /** * Float value of a small positive BigDecimal. */ - // TODO(jat): add back after Float.floatToIntBits is implemented -// public void testFloatValuePlusZero() { -// String a = "123809648392384754573567356745735.63567890295784902768787678287E-400"; -// BigDecimal aNumber = new BigDecimal(a); -// int zero = 0; -// float result = aNumber.floatValue(); -// assertTrue("incorrect value", Float.floatToIntBits(result) == zero); -// } + public void testFloatValuePlusZero() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + int zero = 0; + float result = aNumber.floatValue(); + assertTrue("incorrect value", Float.floatToIntBits(result) == zero); + } /** * Float value of a positive BigDecimal. From 594a326017add7b319df1d94a411c87603d2402f Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Mon, 29 Jul 2024 19:50:40 -0500 Subject: [PATCH 08/12] Update API reference check to validate that GWT 2.11 APIs are preserved (#9982) --- build.xml | 4 +- .../api-checker/config/gwt211_212userapi.conf | 152 ++++++++++++++++++ 2 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 tools/api-checker/config/gwt211_212userapi.conf diff --git a/build.xml b/build.xml index c10eea6f2c5..cea3af60e29 100755 --- a/build.xml +++ b/build.xml @@ -31,9 +31,9 @@ + location="tools/api-checker/config/gwt211_212userapi.conf"/> + value="${gwt.tools}/api-checker-reference/2.11.0/gwt-dev-modified.jar:${gwt.tools}/api-checker-reference/2.11.0/gwt-user-modified.jar"/> diff --git a/tools/api-checker/config/gwt211_212userapi.conf b/tools/api-checker/config/gwt211_212userapi.conf new file mode 100644 index 00000000000..1279e029d85 --- /dev/null +++ b/tools/api-checker/config/gwt211_212userapi.conf @@ -0,0 +1,152 @@ +#existing API + +# dirRoot_old is missing because refJars are being supplied +name_old gwt211userApi +#sourceFiles is specified as colon-separated list of files +sourceFiles_old com/google/gwt\ +:com/google/web\ +:javax/validation\ + +#excludedFiles is specified as colon-separated ant patterns +# The entries for javax and org exclude the validation stuff. +# Bug: http://code.google.com/p/google-web-toolkit/issues/detail?id=5566 +excludedFiles_old **/linker/**\ +:**/rebind/**\ +:**/server/**\ +:**/tools/**\ +:**/vm/**\ +:com/google/gwt/core/client/impl/JavaScriptExceptionBase.java\ +:com/google/gwt/core/client/impl/WeakMapping.java\ +:com/google/gwt/core/shared/impl/StringCase.java\ +:com/google/gwt/core/shared/impl/ThrowableTypeResolver.java\ +:com/google/gwt/core/ext/**\ +:com/google/gwt/dev/*.java\ +:com/google/gwt/dev/asm/**\ +:com/google/gwt/dev/cfg/**\ +:com/google/gwt/dev/codeserver/**\ +:com/google/gwt/dev/common/**\ +:com/google/gwt/dev/generator/**\ +:com/google/gwt/dev/javac/**\ +:com/google/gwt/dev/jdt/**\ +:com/google/gwt/dev/jjs/*.java\ +:com/google/gwt/dev/jjs/ast/**\ +:com/google/gwt/dev/jjs/impl/**\ +:com/google/gwt/dev/js/**\ +:com/google/gwt/dev/json/**\ +:com/google/gwt/dev/resource/**\ +:com/google/gwt/dev/shell/**\ +:com/google/gwt/dev/ui/**\ +:com/google/gwt/dev/url/**\ +:com/google/gwt/dev/util/**\ +:com/google/gwt/i18n/**/impl/cldr/**\ +:com/google/gwt/junit/*.java\ +:com/google/gwt/junit/client/GWTTestCase.java\ +:com/google/gwt/junit/client/impl/GWTRunner.java\ +:com/google/gwt/junit/client/impl/GWTTestAccessor.java\ +:com/google/gwt/junit/remote/**\ +:com/google/gwt/regexp/shared/**\ +:com/google/gwt/resources/css/**\ +:com/google/gwt/resources/gss/**\ +:com/google/gwt/resources/converter/**\ +:com/google/gwt/resources/ext/**\ +:com/google/gwt/resources/rg/**\ +:com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java\ +:com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java\ +:com/google/gwt/safehtml/shared/SafeUriHostedModeUtils.java\ +:com/google/gwt/soyc/**\ +:com/google/gwt/typedarrays/super/com/google/gwt/typedarrays/shared/TypedArraysFactory.java\ +:com/google/gwt/user/client/rpc/core/**\ +:com/google/gwt/user/client/rpc/impl/**\ +:com/google/gwt/uibinder/attributeparsers/**\ +:com/google/gwt/uibinder/client/impl/**\ +:com/google/gwt/uibinder/elementparsers/**\ +:com/google/gwt/uibinder/testing/**\ +:com/google/gwt/util/**\ +:com/google/gwt/validation/**\ +:com/google/web/bindery/autobean/shared/ValueCodexHelper.java\ +:com/google/web/bindery/autobean/**/impl/**\ +:com/google/web/bindery/requestfactory/apt/**\ +:com/google/web/bindery/requestfactory/gwt/client/RequestBatcher.java\ +:com/google/web/bindery/requestfactory/gwt/client/impl/**\ +:com/google/web/bindery/requestfactory/server/impl/**\ +:com/google/web/bindery/requestfactory/shared/impl/**\ +:com/google/web/bindery/requestfactory/vm/**\ +:javax/**\ +:org/**\ + +############################################## +#new Api + +dirRoot_new ./ +name_new gwt28userApi +#sourceFiles is specified as colon-separated list of files +sourceFiles_new dev/core/super\ +:user/src\ +:user/super\ + +#excludedFiles is specified as colon-separated ant patterns +# The entries for javax and org exclude the validation stuff. +# Bug: http://code.google.com/p/google-web-toolkit/issues/detail?id=5566 +excludedFiles_new **/linker/**\ +:**/rebind/**\ +:**/server/**\ +:**/tools/**\ +:**/vm/**\ +:user/src/com/google/gwt/core/client/impl/JavaScriptExceptionBase.java\ +:user/src/com/google/gwt/core/client/impl/WeakMapping.java\ +:user/src/com/google/gwt/core/shared/impl/ThrowableTypeResolver.java\ +:user/src/com/google/gwt/i18n/**/impl/cldr/**\ +:user/src/com/google/gwt/junit/*.java\ +:user/src/com/google/gwt/junit/client/GWTTestCase.java\ +:user/src/com/google/gwt/junit/client/impl/GWTRunner.java\ +:user/src/com/google/gwt/junit/client/impl/GWTTestAccessor.java\ +:user/src/com/google/gwt/regexp/shared/**\ +:user/src/com/google/gwt/resources/css/**\ +:user/src/com/google/gwt/resources/gss/**\ +:user/src/com/google/gwt/resources/converter/**\ +:user/src/com/google/gwt/resources/ext/**\ +:user/src/com/google/gwt/resources/rg/**\ +:user/src/com/google/gwt/safecss/shared/SafeStylesHostedModeUtils.java\ +:user/src/com/google/gwt/safehtml/shared/SafeHtmlHostedModeUtils.java\ +:user/src/com/google/gwt/safehtml/shared/SafeUriHostedModeUtils.java\ +:user/src/com/google/gwt/user/client/rpc/core/**\ +:user/src/com/google/gwt/user/client/rpc/impl/**\ +:user/src/com/google/gwt/uibinder/attributeparsers/**\ +:user/src/com/google/gwt/uibinder/client/impl/**\ +:user/src/com/google/gwt/uibinder/elementparsers/**\ +:user/src/com/google/gwt/uibinder/testing/**\ +:user/src/com/google/gwt/util/**\ +:user/src/com/google/gwt/validation/**\ +:user/src/com/google/web/bindery/autobean/shared/ValueCodexHelper.java\ +:user/src/com/google/web/bindery/autobean/**/impl/**\ +:user/src/com/google/web/bindery/requestfactory/apt/**\ +:user/src/com/google/web/bindery/requestfactory/gwt/client/RequestBatcher.java\ +:user/src/com/google/web/bindery/requestfactory/gwt/client/impl/**\ +:user/src/com/google/web/bindery/requestfactory/server/impl/**\ +:user/src/com/google/web/bindery/requestfactory/shared/impl/**\ +:user/src/com/google/web/bindery/requestfactory/vm/**\ +:user/src/javax/**\ +:user/src/org/**\ +:user/super/com/google/gwt/typedarrays/super/com/google/gwt/typedarrays/shared/TypedArraysFactory.java\ + +############################################## +#excluded packages colon separated list +excludedPackages com.google.gwt.core.client.impl\ +:com.google.gwt.core.shared.impl\ +:com.google.gwt.core.client.js.impl\ +:com.google.gwt.editor.client.impl\ +:com.google.gwt.i18n.client.impl\ +:com.google.gwt.junit.client.impl\ +:com.google.gwt.lang\ +:com.google.gwt.logging.impl\ +:com.google.gwt.resources.client.impl\ +:com.google.gwt.rpc.client.impl\ +:com.google.gwt.user.client.impl\ +:com.google.gwt.user.client.ui.impl\ +:com.google.gwt.xml.client.impl\ +:javaemul.internal\ + +############################################## +#Api whitelist +# when adding to the white-list, include comments as to why the addition is +# being made. \ No newline at end of file From 7f42724eaa0aca048a7e10ce1decabf303241a51 Mon Sep 17 00:00:00 2001 From: Dmitrii Tikhomirov Date: Mon, 29 Jul 2024 18:55:48 -0700 Subject: [PATCH 09/12] Added missing IOException to Reader.read(...) methods (#9933) This is technically a breaking change for GWT's API, but brings it in line with the actual JRE. Fixes #9915 --- tools/api-checker/config/gwt211_212userapi.conf | 6 +++++- user/super/com/google/gwt/emul/java/io/Reader.java | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/api-checker/config/gwt211_212userapi.conf b/tools/api-checker/config/gwt211_212userapi.conf index 1279e029d85..ecd503d03df 100644 --- a/tools/api-checker/config/gwt211_212userapi.conf +++ b/tools/api-checker/config/gwt211_212userapi.conf @@ -149,4 +149,8 @@ excludedPackages com.google.gwt.core.client.impl\ ############################################## #Api whitelist # when adding to the white-list, include comments as to why the addition is -# being made. \ No newline at end of file +# being made. + +# java.io.Reader missing IOException throws for read methods +java.io.Reader::read([C) OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE new method has more exceptions: [Ljava/io/IOException;] +java.io.Reader::read([CII) OVERRIDABLE_METHOD_EXCEPTION_TYPE_CHANGE new method has more exceptions: [Ljava/io/IOException;] diff --git a/user/super/com/google/gwt/emul/java/io/Reader.java b/user/super/com/google/gwt/emul/java/io/Reader.java index 54137991e65..63216ad3a92 100644 --- a/user/super/com/google/gwt/emul/java/io/Reader.java +++ b/user/super/com/google/gwt/emul/java/io/Reader.java @@ -59,7 +59,7 @@ public int read() throws IOException { /** * Attempts to fill {@code buf} with characters up to the size of the array. */ - public int read(char[] buf) { + public int read(char[] buf) throws IOException { return read(buf, 0, buf.length); } @@ -67,7 +67,7 @@ public int read(char[] buf) { * Attempts to fill {@code buf} with up to {@code len} characters. Characters * will be stored in {@code buf} starting at index {@code off}. */ - public abstract int read(char[] buf, int off, int len); + public abstract int read(char[] buf, int off, int len) throws IOException; /** * Returns whether the stream is ready for reading characters. From cb6532d7994d285f2c1dbdc2fbc91e0994d8e734 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 9 Aug 2024 09:14:00 -0500 Subject: [PATCH 10/12] Add support for Java 14 Records (#9934) Implements records as Java objects, so they can be transpiled to JS. Unlike in Java bytecode, hashCode/toString/equals have a complete implementation, rather than using method references and constant bootstrap to generate code at runtime. Includes proposed jsinterop changes to support records without generating invalid code or compile/runtime errors. Fixes #9894 --- .../javac/testing/impl/JavaResourceBase.java | 34 ++- .../gwt/dev/jjs/JavaToJavaScriptCompiler.java | 3 + .../com/google/gwt/dev/jjs/ast/HasJsInfo.java | 3 + .../com/google/gwt/dev/jjs/ast/JProgram.java | 3 +- .../google/gwt/dev/jjs/ast/JRecordType.java | 28 ++ .../gwt/dev/jjs/ast/RuntimeConstants.java | 4 + .../gwt/dev/jjs/impl/GwtAstBuilder.java | 88 +++++-- .../impl/ImplementClassLiteralsAsFields.java | 2 + .../jjs/impl/ImplementRecordComponents.java | 249 ++++++++++++++++++ .../gwt/dev/jjs/JavaAstConstructor.java | 1 + .../jjs/impl/JChangeTrackingVisitorTest.java | 37 ++- .../gwt/dev/jjs/impl/Java17AstTest.java | 15 +- .../com/google/gwt/emul/java/lang/Record.java | 31 +++ .../emul/java/lang/runtime/ObjectMethods.java | 23 ++ .../google/gwt/dev/jjs/test/Java17Test.java | 169 +++++++++++- .../gwt/dev/jjs/test/TopLevelRecord.java | 21 ++ .../google/gwt/dev/jjs/test/Java17Test.java | 10 +- 17 files changed, 669 insertions(+), 52 deletions(-) create mode 100644 dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java create mode 100644 dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java create mode 100644 user/super/com/google/gwt/emul/java/lang/Record.java create mode 100644 user/super/com/google/gwt/emul/java/lang/runtime/ObjectMethods.java create mode 100644 user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/TopLevelRecord.java diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java index aae771dda59..fdec1b1e4dd 100644 --- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java +++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java @@ -275,6 +275,31 @@ public class JavaResourceBase { " public Class getClass() { return ___clazz; }", "}"); + // This class must exist for JDT to be able to compile record types. + public static final MockJavaResource OBJECTMETHODS = + createMockJavaResource("java.lang.runtime.ObjectMethods", + "package java.lang.runtime;", + "public class ObjectMethods {}"); + + // We only need Objects.hash() for Records - the real JRE would synthesize these methods on the + // fly using ObjectMethods, but we need to generate the code up front. This implementation is + // wrong, but the important thing is only that it exists for these tests. + public static final MockJavaResource OBJECTS = + createMockJavaResource("java.util.Objects", + "package java.util;", + "public class Objects {", + " public static int hash(Object... values) { return values.hashCode(); }", + "}"); + + public static final MockJavaResource RECORD = + createMockJavaResource("java.lang.Record", + "package java.lang;", + "public abstract class Record {", + " protected Record(){}", + " public abstract int hashCode();", + " public abstract boolean equals(Object other);", + " public abstract String toString();", + "}"); public static final MockJavaResource RUNTIME_EXCEPTION = createMockJavaResource("java.lang.RuntimeException", "package java.lang;", @@ -442,10 +467,11 @@ public static MockJavaResource[] getStandardResources() { AUTOCLOSEABLE, ANNOTATION, ARRAY_LIST, BYTE, BOOLEAN, CHARACTER, CHAR_SEQUENCE, CLASS, CLASS_NOT_FOUND_EXCEPTION, CLONEABLE, COLLECTION, COMPARABLE, DOUBLE, ENUM, EXCEPTION, ERROR, FUNCTIONALINTERFACE, FLOAT, INTEGER, IS_SERIALIZABLE, JAVASCRIPTEXCEPTION, - JAVASCRIPTOBJECT, LIST, LONG, MAP, NO_CLASS_DEF_FOUND_ERROR, NUMBER, OBJECT, - RUNTIME_EXCEPTION, SERIALIZABLE, SHORT, STRING, STRING_BUILDER, SUPPRESS_WARNINGS, SYSTEM, - THROWABLE, SPECIALIZE_METHOD, DO_NOT_AUTOBOX, JSTYPE, JSCONSTRUCTOR, JSPACKAGE, JSPROPERTY, - JSMETHOD, JSIGNORE, JSFUNCTION, JSOVERLAY, JSOPTIONAL}; + JAVASCRIPTOBJECT, LIST, LONG, MAP, NO_CLASS_DEF_FOUND_ERROR, NUMBER, OBJECT, OBJECTMETHODS, + OBJECTS, RECORD, RUNTIME_EXCEPTION, SERIALIZABLE, SHORT, STRING, STRING_BUILDER, + SUPPRESS_WARNINGS, SYSTEM, THROWABLE, SPECIALIZE_METHOD, DO_NOT_AUTOBOX, JSTYPE, + JSCONSTRUCTOR, JSPACKAGE, JSPROPERTY, JSMETHOD, JSIGNORE, JSFUNCTION, JSOVERLAY, + JSOPTIONAL}; } /** diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index 5895e377d82..0e6dc75490e 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java @@ -84,6 +84,7 @@ import com.google.gwt.dev.jjs.impl.ImplementCastsAndTypeChecks; import com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields; import com.google.gwt.dev.jjs.impl.ImplementJsVarargs; +import com.google.gwt.dev.jjs.impl.ImplementRecordComponents; import com.google.gwt.dev.jjs.impl.JavaAstVerifier; import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; import com.google.gwt.dev.jjs.impl.JjsUtils; @@ -1156,6 +1157,8 @@ private UnifiedAst precompile(PrecompilationContext precompilationContext) // Replace calls to native overrides of object methods. ReplaceCallsToNativeJavaLangObjectOverrides.exec(jprogram); + ImplementRecordComponents.exec(jprogram); + FixAssignmentsToUnboxOrCast.exec(jprogram); if (options.isEnableAssertions()) { AssertionNormalizer.exec(jprogram); diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java index 05a6f9e8dda..ec24041b3f5 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/HasJsInfo.java @@ -60,6 +60,9 @@ public String computeName(JMember member) { @Override public String computeName(JMember member) { String methodName = member.getName(); + if (member.getEnclosingType() instanceof JRecordType) { + return methodName; + } if (startsWithCamelCase(methodName, "get")) { return Introspector.decapitalize(methodName.substring(3)); } diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java index 2811ac4bdd6..6764157ab48 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java @@ -1267,7 +1267,8 @@ private static Set buildInitialTypeNamesToIndex() { JAVASCRIPTOBJECT, CLASS_LITERAL_HOLDER, "com.google.gwt.core.client.RunAsyncCallback", "com.google.gwt.core.client.impl.AsyncFragmentLoader", "com.google.gwt.core.client.impl.Impl", - "com.google.gwt.core.client.prefetch.RunAsyncCode")); + "com.google.gwt.core.client.prefetch.RunAsyncCode", + "java.util.Objects")); typeNamesToIndex.addAll(CODEGEN_TYPES_SET); return typeNamesToIndex; } diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java new file mode 100644 index 00000000000..974fece4309 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JRecordType.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.ast; + +import com.google.gwt.dev.jjs.SourceInfo; + +/** + * Java record type reference expression. At this time, there is no AST node for components, + * instead using fields, since records can only have fields declared for components. + */ +public class JRecordType extends JClassType { + public JRecordType(SourceInfo info, String name) { + super(info, name, false, true); + } +} diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java b/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java index 15f5b8e2487..5dc68da4322 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/RuntimeConstants.java @@ -43,6 +43,8 @@ public class RuntimeConstants { public static final String CLASS_CREATE_FOR_PRIMITIVE = "Class.createForPrimitive"; public static final String CLASS_CREATE_FOR_INTERFACE = "Class.createForInterface"; + public static final String CLASS_GET_SIMPLE_NAME = "Class.getSimpleName"; + public static final String COLLAPSED_PROPERTY_HOLDER_GET_PERMUTATION_ID = "CollapsedPropertyHolder.getPermutationId"; @@ -97,4 +99,6 @@ public class RuntimeConstants { public static final String RUNTIME_UNIQUE_ID = "Runtime.uniqueId"; public static final String UTIL_MAKE_ENUM_NAME = "Util.makeEnumName"; + + public static final String OBJECTS_HASH = "Objects.hash"; } diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index e74a5df28f7..711b855a048 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java @@ -82,6 +82,7 @@ import com.google.gwt.dev.jjs.ast.JPrefixOperation; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JRecordType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JStatement; @@ -222,6 +223,7 @@ import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.MethodVerifier; import org.eclipse.jdt.internal.compiler.lookup.NestedTypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.RecordComponentBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; @@ -2836,12 +2838,6 @@ protected void pushBinaryOp(BinaryExpression x, JBinaryOperator op) { } protected boolean visit(TypeDeclaration x) { - if (x.isRecord()) { - InternalCompilerException exception = - new InternalCompilerException("Records not yet supported"); - exception.addNode(new JClassType(makeSourceInfo(x), intern(x.name), false, false)); - throw exception; - } JDeclaredType type = (JDeclaredType) typeMap.get(x.binding); assert !type.isExternal(); classStack.push(curClass); @@ -4182,8 +4178,14 @@ private void createField(FieldDeclaration x) { getFieldDisposition(binding), AccessModifier.fromFieldBinding(binding)); } enclosingType.addField(field); - JsInteropUtil.maybeSetJsInteropProperties(field, shouldExport(field), x.annotations); - processSuppressedWarnings(field, x.annotations); + if (x.isARecordComponent) { + // Skip setting jsinterop properties on record component fields + RecordComponentBinding component = ((SourceTypeBinding) binding.declaringClass).getRecordComponent(x.name); + processSuppressedWarnings(field, component.sourceRecordComponent().annotations); + } else { + JsInteropUtil.maybeSetJsInteropProperties(field, shouldExport(field), x.annotations); + processSuppressedWarnings(field, x.annotations); + } typeMap.setField(binding, field); } @@ -4192,7 +4194,7 @@ private void createMembers(TypeDeclaration x) { JDeclaredType type = (JDeclaredType) typeMap.get(binding); SourceInfo info = type.getSourceInfo(); try { - /** + /* * We emulate static initializers and instance initializers as methods. As * in other cases, this gives us: simpler AST, easier to optimize, more * like output JavaScript. Clinit is always in slot 0, init (if it exists) @@ -4247,6 +4249,35 @@ private void createMembers(TypeDeclaration x) { } } + if (x.isRecord()) { + // build implicit record component accessor methods, JDT doesn't declare them + for (JField field : type.getFields()) { + // Create a method binding that corresponds to the method we are creating, jdt won't + // offer us one unless it was defined in source. + char[] fieldName = field.getName().toCharArray(); + MethodBinding recordComponentAccessor = binding.getExactMethod( + fieldName, new TypeBinding[0], curCud.scope); + + // Get the record component, and pass on any annotations meant for the method + JMethod componentMethod = typeMap.get(recordComponentAccessor); + RecordComponentBinding component = binding.getRecordComponent(fieldName); + processAnnotations(component.sourceRecordComponent().annotations, componentMethod); + } + + // At this time, we need to be sure a binding exists, either because the record declared + // its own, or we make one specifically for it. + MethodBinding toStringBinding = binding.getExactMethod( + TO_STRING_METHOD_NAME.toCharArray(), Binding.NO_TYPES, curCud.scope); + typeMap.get(toStringBinding); + TypeBinding[] equalsArgs = {x.scope.getJavaLangObject()}; + MethodBinding equalsBinding = binding.getExactMethod( + EQUALS_METHOD_NAME.toCharArray(), equalsArgs, curCud.scope); + typeMap.get(equalsBinding); + MethodBinding hashcodeBinding = binding.getExactMethod( + HASHCODE_METHOD_NAME.toCharArray(), Binding.NO_TYPES, curCud.scope); + typeMap.get(hashcodeBinding); + } + if (x.memberTypes != null) { for (TypeDeclaration memberType : x.memberTypes) { createMembers(memberType); @@ -4353,17 +4384,16 @@ private void createMethod(AbstractMethodDeclaration x) { } enclosingType.addMethod(method); - processAnnotations(x, method); + processAnnotations(x.annotations, method); typeMap.setMethod(b, method); } - private void processAnnotations(AbstractMethodDeclaration x, - JMethod method) { - maybeAddMethodSpecialization(x, method); - maybeSetInliningMode(x, method); - maybeSetHasNoSideEffects(x, method); - JsInteropUtil.maybeSetJsInteropProperties(method, shouldExport(method), x.annotations); - processSuppressedWarnings(method, x.annotations); + private void processAnnotations(Annotation[] annotations, JMethod method) { + maybeAddMethodSpecialization(annotations, method); + maybeSetInliningMode(annotations, method); + maybeSetHasNoSideEffects(annotations, method); + JsInteropUtil.maybeSetJsInteropProperties(method, shouldExport(method), annotations); + processSuppressedWarnings(method, annotations); } private void processAnnotations(JParameter parameter, Annotation... annotations) { @@ -4371,7 +4401,7 @@ private void processAnnotations(JParameter parameter, Annotation... annotations) processSuppressedWarnings(parameter, annotations); } - private void processSuppressedWarnings(CanHaveSuppressedWarnings x, Annotation... annotations) { + private static void processSuppressedWarnings(CanHaveSuppressedWarnings x, Annotation... annotations) { x.setSuppressedWarnings(JdtUtil.getSuppressedWarnings(annotations)); } @@ -4383,26 +4413,26 @@ private static boolean isUncheckedGenericMethodCall(MessageSend messageSend) { return false; } - private static void maybeSetInliningMode(AbstractMethodDeclaration x, JMethod method) { + private static void maybeSetInliningMode(Annotation[] annotations, JMethod method) { if (JdtUtil.getAnnotationByName( - x.annotations, "javaemul.internal.annotations.DoNotInline") != null) { + annotations, "javaemul.internal.annotations.DoNotInline") != null) { method.setInliningMode(InliningMode.DO_NOT_INLINE); } else if (JdtUtil.getAnnotationByName( - x.annotations, "javaemul.internal.annotations.ForceInline") != null) { + annotations, "javaemul.internal.annotations.ForceInline") != null) { method.setInliningMode(InliningMode.FORCE_INLINE); } } - private static void maybeSetHasNoSideEffects(AbstractMethodDeclaration x, JMethod method) { + private static void maybeSetHasNoSideEffects(Annotation[] annotations, JMethod method) { if (JdtUtil.getAnnotationByName( - x.annotations, "javaemul.internal.annotations.HasNoSideEffects") != null) { + annotations, "javaemul.internal.annotations.HasNoSideEffects") != null) { method.setHasSideEffects(false); } } - private void maybeAddMethodSpecialization(AbstractMethodDeclaration x, JMethod method) { + private void maybeAddMethodSpecialization(Annotation[] annotations, JMethod method) { AnnotationBinding specializeAnnotation = JdtUtil.getAnnotationByName( - x.annotations, "javaemul.internal.annotations.SpecializeMethod"); + annotations, "javaemul.internal.annotations.SpecializeMethod"); if (specializeAnnotation == null) { return; } @@ -4481,8 +4511,12 @@ private void createTypes(TypeDeclaration x) { JDeclaredType type; if (binding.isClass()) { - type = new JClassType( - info, name, binding.isAbstract(), binding.isFinal() || binding.isAnonymousType()); + if (binding.isRecord()) { + type = new JRecordType(info, name); + } else { + type = new JClassType( + info, name, binding.isAbstract(), binding.isFinal() || binding.isAnonymousType()); + } } else if (binding.isInterface() || binding.isAnnotationType()) { type = new JInterfaceType(info, name); } else if (binding.isEnum()) { diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java index 3006bda578b..598218a9ce1 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java @@ -37,6 +37,7 @@ import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JRecordType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JRuntimeTypeReference; import com.google.gwt.dev.jjs.ast.JType; @@ -87,6 +88,7 @@ public class ImplementClassLiteralsAsFields { literalFactoryMethodByTypeClass = new ImmutableMap.Builder() .put(JEnumType.class, ClassLiteralFactoryMethod.CREATE_FOR_ENUM) .put(JClassType.class, ClassLiteralFactoryMethod.CREATE_FOR_CLASS) + .put(JRecordType.class, ClassLiteralFactoryMethod.CREATE_FOR_CLASS) .put(JInterfaceType.class, ClassLiteralFactoryMethod.CREATE_FOR_INTERFACE) .put(JPrimitiveType.class, ClassLiteralFactoryMethod.CREATE_FOR_PRIMITIVE) .build(); diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java new file mode 100644 index 00000000000..c0eee4e2435 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementRecordComponents.java @@ -0,0 +1,249 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.impl; + +import com.google.gwt.dev.jjs.SourceInfo; +import com.google.gwt.dev.jjs.ast.JBinaryOperation; +import com.google.gwt.dev.jjs.ast.JBinaryOperator; +import com.google.gwt.dev.jjs.ast.JBooleanLiteral; +import com.google.gwt.dev.jjs.ast.JClassLiteral; +import com.google.gwt.dev.jjs.ast.JClassType; +import com.google.gwt.dev.jjs.ast.JDeclaredType; +import com.google.gwt.dev.jjs.ast.JExpression; +import com.google.gwt.dev.jjs.ast.JField; +import com.google.gwt.dev.jjs.ast.JFieldRef; +import com.google.gwt.dev.jjs.ast.JIfStatement; +import com.google.gwt.dev.jjs.ast.JIntLiteral; +import com.google.gwt.dev.jjs.ast.JLocal; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JMethodBody; +import com.google.gwt.dev.jjs.ast.JMethodCall; +import com.google.gwt.dev.jjs.ast.JNewArray; +import com.google.gwt.dev.jjs.ast.JNullLiteral; +import com.google.gwt.dev.jjs.ast.JParameter; +import com.google.gwt.dev.jjs.ast.JPrimitiveType; +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JRecordType; +import com.google.gwt.dev.jjs.ast.JStringLiteral; +import com.google.gwt.dev.jjs.ast.JThisRef; +import com.google.gwt.dev.jjs.ast.JUnsafeTypeCoercion; +import com.google.gwt.dev.jjs.ast.RuntimeConstants; +import com.google.gwt.thirdparty.guava.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Implements the methods required for a Java Record type, based on its components/fields. + */ +public class ImplementRecordComponents { + + public static void exec(JProgram program) { + new ImplementRecordComponents(program).execImpl(); + } + + private final JProgram program; + private final AutoboxUtils autoboxUtils; + private final JMethod getClassMethod; + private final JMethod getSimpleNameMethod; + private final JClassType javaLangString; + + private ImplementRecordComponents(JProgram program) { + this.program = program; + this.autoboxUtils = new AutoboxUtils(program); + + getClassMethod = program.getIndexedMethod(RuntimeConstants.OBJECT_GET_CLASS); + getSimpleNameMethod = program.getIndexedMethod(RuntimeConstants.CLASS_GET_SIMPLE_NAME); + javaLangString = program.getTypeJavaLangString(); + } + + private void execImpl() { + for (JDeclaredType type : program.getDeclaredTypes()) { + if (type instanceof JRecordType) { + implementRecordComponents((JRecordType) type); + } + } + } + + private void implementRecordComponents(JRecordType type) { + // This is a record type, and any methods that were declared but not referenced now need + // to be defined. These include the field-named method accessors, equals/hashCode and + // toString. If not defined, we'll synthesize them based on the record components. + SourceInfo info = type.getSourceInfo(); + for (JMethod method : type.getMethods()) { + if (method.getBody() != null) { + // If there is a body, that means the record has its own declaration of this method, and + // we should not re-declare it. + continue; + } + + if (method.getName().equals(GwtAstBuilder.TO_STRING_METHOD_NAME) + && method.getParams().isEmpty()) { + implementToString(type, method, info); + } else if (method.getName().equals(GwtAstBuilder.EQUALS_METHOD_NAME) + && method.getParams().size() == 1 + && method.getParams().get(0).getType().equals(program.getTypeJavaLangObject())) { + implementEquals(type, method, info); + } else if (method.getName().equals(GwtAstBuilder.HASHCODE_METHOD_NAME) + && method.getParams().isEmpty()) { + implementHashCode(type, method, info); + } else if (method.getParams().isEmpty()) { + // Check if it has the same name+type as a component/field + Optional matchingField = type.getFields().stream() + .filter(f -> f.getName().equals(method.getName())) + .filter(f -> f.getType().equals(method.getType())) + .findFirst(); + matchingField.ifPresent(f -> implementComponentAccessor(type, method, f)); + } + } + } + + private static void implementComponentAccessor(JRecordType type, JMethod method, JField field) { + // We can pick a more specific source for this than the others, use the "field" itself. + SourceInfo info = field.getSourceInfo(); + + // Create a simple accessor method and bind it, so it can be used anywhere outside this type. + JFieldRef fieldReference = new JFieldRef(info, new JThisRef(info, type), + field, type); + JMethodBody body = new JMethodBody(info); + body.getBlock().addStmt(fieldReference.makeReturnStatement()); + method.setBody(body); + } + + private void implementHashCode(JRecordType type, JMethod method, SourceInfo info) { + final JExpression hashcodeStatement; + if (type.getFields().isEmpty()) { + // No fields, just emit hashcode=0 + hashcodeStatement = new JIntLiteral(info, 0); + } else { + List exprs = Lists.newArrayListWithCapacity(type.getFields().size()); + for (JField field : type.getFields()) { + JFieldRef jFieldRef = new JFieldRef(info, new JThisRef(info, type), field, type); + if (jFieldRef.getType().isPrimitiveType()) { + exprs.add(autoboxUtils.box(jFieldRef, (JPrimitiveType) jFieldRef.getType())); + } else { + exprs.add(jFieldRef); + } + } + JNewArray varargsWrapper = JNewArray.createArrayWithInitializers(info, + program.getTypeJavaLangObjectArray(), exprs); + JMethod hash = program.getIndexedMethod(RuntimeConstants.OBJECTS_HASH); + hashcodeStatement = new JMethodCall(info, null, hash, varargsWrapper); + } + JMethodBody body = new JMethodBody(info); + body.getBlock().addStmt(hashcodeStatement.makeReturnStatement()); + + method.setBody(body); + } + + private void implementEquals(JRecordType type, JMethod method, SourceInfo info) { + JMethodBody body = new JMethodBody(info); + JParameter otherParam = method.getParams().get(0); + + // Equals is built from first == check between this and other, as a fast path for the same + // object and also to ensure that other isn't null. Then MyRecord.class == other.getClass(), + // and now we know they're the same type and can cast safely to access fields for the rest. + JBinaryOperation eq = + new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.EQ, + new JThisRef(info, type), + otherParam.createRef(info)); + body.getBlock().addStmt(new JIfStatement(info, eq, + JBooleanLiteral.TRUE.makeReturnStatement(), null)); + + JBinaryOperation sameTypeCheck = + new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.NEQ, + new JClassLiteral(info, type), + new JMethodCall(info, otherParam.createRef(info), getClassMethod)); + body.getBlock().addStmt(new JIfStatement(info, sameTypeCheck, + JBooleanLiteral.FALSE.makeReturnStatement(), null)); + + // Create a local to assign to and compare each component + JLocal typedOther = JProgram.createLocal(info, "other", type, true, body); + // We can use an unsafe cast since we know the check will succeed + JUnsafeTypeCoercion uncheckedCast = + new JUnsafeTypeCoercion(info, type, otherParam.createRef(info)); + JBinaryOperation uncheckedAssign = new JBinaryOperation(info, type, JBinaryOperator.ASG, + typedOther.createRef(info), uncheckedCast); + body.getBlock().addStmt(uncheckedAssign.makeStatement()); + + JExpression componentCheck = JBooleanLiteral.TRUE; + JMethod objectEquals = program.getIndexedMethod(RuntimeConstants.OBJECT_EQUALS); + for (JField field : type.getFields()) { + if (!field.isStatic()) { + JFieldRef myField = new JFieldRef(info, new JThisRef(info, type), field, type); + JFieldRef otherField = new JFieldRef(info, typedOther.createRef(info), field, type); + final JBinaryOperation equals; + if (field.getType().isPrimitiveType()) { + equals = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, + JBinaryOperator.EQ, + myField, + otherField); + } else { + // we would like to use Objects.equals here to be more consise, but we would need + // to look up the right impl based on the field - just as simple to insert a null check + // and get it a little closer to all being inlined away + equals = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.AND, + new JBinaryOperation(info, JPrimitiveType.BOOLEAN, JBinaryOperator.NEQ, + myField, JNullLiteral.INSTANCE), + new JMethodCall(info, myField, objectEquals, otherField)); + } + if (componentCheck != JBooleanLiteral.TRUE) { + componentCheck = new JBinaryOperation(info, JPrimitiveType.BOOLEAN, + JBinaryOperator.AND, + componentCheck, + equals); + } else { + componentCheck = equals; + } + } + } + + body.getBlock().addStmt(componentCheck.makeReturnStatement()); + method.setBody(body); + } + + private void implementToString(JRecordType type, JMethod method, SourceInfo info) { + List args = new ArrayList<>(); + + // Concatenate type with []s and component values, assigning to toStrExpr as we append + // more concat operations. Using getClass().getSimpleName() rather than a string literal + // allows the toString implementation to emit the obfuscated class name. + JMethodCall getClass = new JMethodCall(info, new JThisRef(info, type), getClassMethod); + JExpression toStrExpr = new JMethodCall(info, getClass, getSimpleNameMethod); + + args.add(new JStringLiteral(info, "[", javaLangString)); + List fields = type.getFields(); + for (int i = 0; i < fields.size(); i++) { + if (i != 0) { + args.add(new JStringLiteral(info, ", ", javaLangString)); + } + JField field = fields.get(i); + args.add(new JStringLiteral(info, field.getName() + "=", javaLangString)); + args.add(new JFieldRef(info, new JThisRef(info, type), field, type)); + } + args.add(new JStringLiteral(info, "]", javaLangString)); + for (JExpression arg : args) { + toStrExpr = new JBinaryOperation(info, javaLangString, JBinaryOperator.CONCAT, + toStrExpr, + arg); + } + + JMethodBody body = new JMethodBody(info); + body.getBlock().addStmt(toStrExpr.makeReturnStatement()); + method.setBody(body); + } +} diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java index d6a66d7484d..32ce51b7cf3 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java +++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java @@ -155,6 +155,7 @@ public CharSequence getContent() { " static boolean isClassMetadataEnabled() { return true; }", " public boolean desiredAssertionStatus() { return true; }", " public String getName() { return null; }", + " public String getSimpleName() { return null; }", "}" ); } diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java index 1374bd97bfd..fc90562c55c 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JChangeTrackingVisitorTest.java @@ -22,6 +22,7 @@ import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JParameter; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JVariable; @@ -31,6 +32,7 @@ import com.google.gwt.thirdparty.guava.common.collect.Sets; import java.util.Arrays; +import java.util.List; import java.util.Set; /** @@ -46,7 +48,10 @@ public AddParamWhenEnterMethodVisitor(OptimizerContext optimizerCtx) { @Override public boolean enter(JMethod x, Context ctx) { - x.createParameter(SourceOrigin.UNKNOWN, "_newParam_enter", JPrimitiveType.INT); + // Don't add args at the end of varargs methods + if (isNotVarargs(x)) { + x.createParameter(SourceOrigin.UNKNOWN, "_newParam_enter", JPrimitiveType.INT); + } return true; } } @@ -58,10 +63,18 @@ public AddParamWhenExitMethodVisitor(OptimizerContext optimizerCtx) { @Override public void exit(JMethod x, Context ctx) { - x.createParameter(SourceOrigin.UNKNOWN, "_newParam_exit", JPrimitiveType.INT); + // Don't add args at the end of varargs methods + if (isNotVarargs(x)) { + x.createParameter(SourceOrigin.UNKNOWN, "_newParam_exit", JPrimitiveType.INT); + } } } + private static boolean isNotVarargs(JMethod x) { + List params = x.getParams(); + return params.isEmpty() || !params.get(params.size() - 1).isVarargs(); + } + private static final class AddParamsWhenEnterAndExitMethodVisitor extends JChangeTrackingVisitor { public AddParamsWhenEnterAndExitMethodVisitor(OptimizerContext optimizerCtx) { super(optimizerCtx); @@ -69,13 +82,19 @@ public AddParamsWhenEnterAndExitMethodVisitor(OptimizerContext optimizerCtx) { @Override public boolean enter(JMethod x, Context ctx) { - x.createParameter(SourceOrigin.UNKNOWN, "_newParam_enter", JPrimitiveType.INT); + // Don't add args at the end of varargs methods + if (isNotVarargs(x)) { + x.createParameter(SourceOrigin.UNKNOWN, "_newParam_enter", JPrimitiveType.INT); + } return true; } @Override public void exit(JMethod x, Context ctx) { - x.createParameter(SourceOrigin.UNKNOWN, "_newParam_exit", JPrimitiveType.INT); + // Don't add args at the end of varargs methods + if (isNotVarargs(x)) { + x.createParameter(SourceOrigin.UNKNOWN, "_newParam_exit", JPrimitiveType.INT); + } } } @@ -87,7 +106,10 @@ public AddParamWhenEnterNonConstructorMethodVisitor(OptimizerContext optimizerCt @Override public boolean enter(JMethod x, Context ctx) { - x.createParameter(SourceOrigin.UNKNOWN, "_newParam_enter", JPrimitiveType.INT); + // Don't add args at the end of varargs methods + if (isNotVarargs(x)) { + x.createParameter(SourceOrigin.UNKNOWN, "_newParam_enter", JPrimitiveType.INT); + } return true; } @@ -105,7 +127,10 @@ public AddParamWhenExitNonConstructorMethodVisitor(OptimizerContext optimizerCtx @Override public void exit(JMethod x, Context ctx) { - x.createParameter(SourceOrigin.UNKNOWN, "_newParam_exit", JPrimitiveType.INT); + // Don't add args at the end of varargs methods + if (isNotVarargs(x)) { + x.createParameter(SourceOrigin.UNKNOWN, "_newParam_exit", JPrimitiveType.INT); + } } @Override diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/Java17AstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/Java17AstTest.java index 032587ff349..dda567e4440 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/Java17AstTest.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/Java17AstTest.java @@ -135,18 +135,9 @@ public void testSealedClassesNotPermitted() { } } - public void testRecordsNotSupported() { - try { - addSnippetClassDecl("public record Point(int x, int y) {}"); - compileSnippet("void", "Point rectangle = new Point(0, 0);"); - fail("Compile should have failed but succeeded."); - } catch (Exception e) { - if (!(e.getCause() instanceof UnableToCompleteException) - && !(e instanceof UnableToCompleteException)) { - e.printStackTrace(); - fail(); - } - } + public void testRecordSyntax() throws UnableToCompleteException { + addSnippetClassDecl("public record Point(int x, int y) {}"); + compileSnippet("void", "Point rectangle = new Point(0, 0);"); } public void testSwitchExpressionsNotSupported() { diff --git a/user/super/com/google/gwt/emul/java/lang/Record.java b/user/super/com/google/gwt/emul/java/lang/Record.java new file mode 100644 index 00000000000..bc9ee724f6a --- /dev/null +++ b/user/super/com/google/gwt/emul/java/lang/Record.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package java.lang; + +/** + * Superclass of all Java records. + */ +public abstract class Record { + + protected Record() { + } + + public abstract int hashCode(); + + public abstract boolean equals(Object other); + + public abstract String toString(); +} diff --git a/user/super/com/google/gwt/emul/java/lang/runtime/ObjectMethods.java b/user/super/com/google/gwt/emul/java/lang/runtime/ObjectMethods.java new file mode 100644 index 00000000000..5a42f844476 --- /dev/null +++ b/user/super/com/google/gwt/emul/java/lang/runtime/ObjectMethods.java @@ -0,0 +1,23 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package java.lang.runtime; + +/** + * This class must exist for Record to compile, but has no emulated methods. + */ +public class ObjectMethods { + +} diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java index b45530f9512..a15c35acf64 100644 --- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java +++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2024 GWT Project Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -21,6 +21,8 @@ import java.util.Arrays; import java.util.List; +import jsinterop.annotations.*; + /** * Tests Java 17 features. It is super sourced so that gwt can be compiles under Java 11. * @@ -114,6 +116,171 @@ private void checkIfCompiled(Shape square, Shape circle) { assertTrue(circle instanceof Circle); } + /** + * Sample nested record. Has no components. Implements a single interface + */ + record InnerRecord() implements Comparable { + /** + * Simple accessor-looking method to ensure it doesn't participate in hashcode/tostring. + */ + public String value() { + return "Hello"; + } + + @Override + public int compareTo(InnerRecord other) { + return 0; + } + } + + /** + * Record type that takes a record as a component + */ + record RecordWithReferenceType(TopLevelRecord refType){} + + public void testRecordClasses() { + /** + * Sample local record. + */ + record LocalRecord() { + @Override + public String toString() { + return "Example"; + } + } + + assertTrue(new InnerRecord() instanceof Record); + assertTrue(new InnerRecord() instanceof Comparable); + + assertFalse(new InnerRecord().toString().contains("Hello")); + assertTrue(new InnerRecord().toString().startsWith("InnerRecord")); + assertEquals(0, new InnerRecord().hashCode()); + assertEquals(new InnerRecord(), new InnerRecord()); + + assertEquals("Example", new LocalRecord().toString()); + + TopLevelRecord withValues = new TopLevelRecord("Banana", 7); + assertTrue(withValues.toString().contains("7")); + assertTrue(withValues.toString().contains("Banana")); + assertEquals("Banana", withValues.name()); + assertEquals(7, withValues.count()); + // Under the current implementation this next line would fail - this is not inconsistent with the spec, + // but it is different than what the JVM does. +// assertEquals(0, new TopLevelRecord("", 0).hashCode()); + assertFalse(0 == new TopLevelRecord("", 7).hashCode()); + assertFalse(0 == new TopLevelRecord("Pear", 0).hashCode()); + + assertFalse(new InnerRecord().equals(new LocalRecord())); + + RecordWithReferenceType sameA = new RecordWithReferenceType(new TopLevelRecord("a", 1)); + RecordWithReferenceType sameB = new RecordWithReferenceType(new TopLevelRecord("a", 1)); + RecordWithReferenceType different = new RecordWithReferenceType(new TopLevelRecord("a", 2)); + // check that an instance is equal to itself + assertEquals(sameA, sameA); + assertEquals(sameA.hashCode(), sameA.hashCode()); + //check that an instance is equal to a different record instance with same values + assertEquals(sameA, sameB); + assertEquals(sameA.hashCode(), sameB.hashCode()); + + assertFalse(sameA.equals(different)); + assertFalse(sameA.hashCode() == different.hashCode()); + } + + /** + * Simple record with one property accessor, one default method accessor + */ + @JsType(namespace = "java17") + public record JsRecord1(@JsProperty String name, int value) { } + + /** + * Simple native type to verify JsRecord1. + */ + @JsType(name = "JsRecord1", namespace = "java17", isNative = true) + public static class JsObject1 { + public String name; + public native int value(); + public JsObject1(String name, int value) { } + } + + /** + * Record with explicit method accessor + */ + @JsType(namespace = "java17") + public record JsRecord2(@JsMethod String name, int value) { } + + /** + * Simple native type to verify JsRecord2. + */ + @JsType(name = "JsRecord2", namespace = "java17", isNative = true) + public static class JsObject2 { + public JsObject2(String name, int value) { } + + public native String name(); + public native int value(); + } + + /** + * Record with exported properties and methods. + */ + public record JsRecord3(String red, JsRecord1 green, JsRecord2 blue) { + @JsProperty + public String getFlavor() { + return "grape"; + } + @JsMethod + public int countBeans() { + return 7; + } + } + + /** + * Represented as an interface since there is no constructor to call or use to type check. + */ + @JsType(isNative = true) + public interface JsObject3 { + @JsProperty + String getFlavor(); + int countBeans(); + } + + public void testJsTypeRecords() { + // Test with default accessor (method) and a property accessor + JsRecord1 r1 = new JsRecord1("foo", 7); + assertEquals("foo", r1.name()); + assertEquals(7, r1.value()); + assertEquals(new JsRecord1("foo", 7), r1); + + // Create an instance from JS, verify it is the same + JsObject1 o1 = new JsObject1("foo", 7); + assertEquals("foo", o1.name); + assertEquals(7, o1.value()); + assertEquals(o1.toString(), r1.toString()); + assertEquals(o1, r1); + + // Repeat the test with methods explicitly configured for accessors + JsRecord2 r2 = new JsRecord2("foo", 7); + assertEquals("foo", r2.name()); + assertEquals(7, r2.value()); + assertEquals(new JsRecord2("foo", 7), r2); + + // Create an instance from JS, verify it is the same + JsObject2 o2 = new JsObject2("foo", 7); + assertEquals("foo", o2.name()); + assertEquals(7, o2.value()); + assertEquals(o2.toString(), r2.toString()); + assertEquals(o2, r2); + + // Test an object with exposed properties and methods + JsRecord3 r3 = new JsRecord3("fork", r1, r2); + assertEquals("grape", r3.getFlavor()); + assertEquals(7, r3.countBeans()); + + // Cast the instance to JS, verify it is the same + JsObject3 o3 = (JsObject3) (Object) r3; + assertEquals("grape", r3.getFlavor()); + assertEquals(7, r3.countBeans()); + } + public void testInstanceOfPatternMatching() { Shape shape1 = new Circle(); if (shape1 instanceof Circle circle) { diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/TopLevelRecord.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/TopLevelRecord.java new file mode 100644 index 00000000000..51f830f4d8d --- /dev/null +++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/TopLevelRecord.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 GWT Project Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.jjs.test; + +/** + * Sample top-level record in its own compilation unit. + */ +public record TopLevelRecord(String name, int count) {} diff --git a/user/test/com/google/gwt/dev/jjs/test/Java17Test.java b/user/test/com/google/gwt/dev/jjs/test/Java17Test.java index 6377acfc43a..f14ad1f227b 100644 --- a/user/test/com/google/gwt/dev/jjs/test/Java17Test.java +++ b/user/test/com/google/gwt/dev/jjs/test/Java17Test.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Google Inc. + * Copyright 2024 GWT Project Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of @@ -50,6 +50,14 @@ public void testSealedClassesPermitted() { assertFalse(isGwtSourceLevel17()); } + public void testRecordClasses() { + assertFalse(isGwtSourceLevel17()); + } + + public void testJsTypeRecords() { + assertFalse(isGwtSourceLevel17()); + } + public void testInstanceOfPatternMatching() { assertFalse(isGwtSourceLevel17()); } From 2d0d95607bd2e9e88c94af6a1d83f028af529632 Mon Sep 17 00:00:00 2001 From: Colin Alworth Date: Fri, 9 Aug 2024 10:58:23 -0500 Subject: [PATCH 11/12] Fix native JsMethods with varargs called from Java varargs methods (#9957) Native jsinterop varargs calls of the form `instance.method(argsArray)` sometimes implemented using `instance.method.apply(instance, argsArray)`, where instance represents some expression. JsUtils.createApplyInvocation assumes that ImplementJsVarargs has already created a local variable to ensure that no side effects can take place when cloning the "instance", but ImplementJsVarargs doesn't actually check if a side effect was able to happen. This fix only adds an additional check, creating a local variable in the case where the instance expression has side effects, and clarifies that a native JsProperty might have sideeffects (as it could call a JS property accessor). An additional fix is made to JsSafeCloner, to let it successfully clone a qualified reference (e.g. `$wnd.console`) rather than produce an invalid JS AST. This ended up not being strictly required for the fix, but appears to be more correct and will at least generate code rather than a NullPointerException in a later location. Fixes #9932 --- .../gwt/dev/jjs/impl/GwtAstBuilder.java | 7 +- .../gwt/dev/jjs/impl/ImplementJsVarargs.java | 6 +- .../com/google/gwt/dev/js/JsSafeCloner.java | 10 +- .../gwt/core/interop/JsTypeVarargsTest.java | 109 +++++++++++++ .../google/gwt/dev/jjs/test/VarargsTest.java | 150 ++++++++++++++++++ 5 files changed, 273 insertions(+), 9 deletions(-) diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index 711b855a048..99aa00d4ec0 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java @@ -3315,13 +3315,13 @@ private List popCallArguments(SourceInfo info, Expression[] argumen List args = pop(arguments); for (int i = 0; i < args.size(); i++) { // Account for varargs parameter. - int parameterIndex = Math.min(i, methodBinding.parameters.length - 1); + int parameterIndex = Math.min(i, methodBinding.original().parameters.length - 1); args.set(i, maybeBoxOrUnbox( args.get(i), arguments[i].implicitConversion, isDoNotAutoBoxParameter(methodBinding, parameterIndex))); } - if (!methodBinding.isVarargs()) { + if (!methodBinding.original().isVarargs()) { return args; } @@ -3331,7 +3331,8 @@ private List popCallArguments(SourceInfo info, Expression[] argumen args = Lists.newArrayListWithCapacity(1); } - TypeBinding[] params = methodBinding.parameters; + TypeBinding[] params = methodBinding.isVarargs() ? methodBinding.parameters : + methodBinding.original().parameters; int varArg = params.length - 1; // See if there's a single varArg which is already an array. diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java index 9c86dc00cda..14794e867bb 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementJsVarargs.java @@ -296,7 +296,7 @@ public boolean visit(JMethod x, Context ctx) { varargsParameter = Iterables.getLast(x.getParams()); varargsIndex = x.getParams().size() - 1; - // JsVarargs parameter can be assumend not null in the implementing method + // JsVarargs parameter can be assumed not null in the implementing method varargsParameter.setType(varargsParameter.getType().strengthenToNonNull()); argumentsCopyVariable = null; @@ -462,8 +462,8 @@ public void endVisit(JMethodCall x, Context ctx) { // Passed as an array to varargs method will result in an apply call, in which case hoist the // qualifier to make sure it is only evaluated once. JExpression instance = x.getInstance(); - if (x.getTarget().needsDynamicDispatch() && !x.isStaticDispatchOnly() - && instance != null && !(instance instanceof JVariableRef)) { + if (x.getTarget().needsDynamicDispatch() && !x.isStaticDispatchOnly() && instance != null + && !(instance instanceof JVariableRef && !instance.hasSideEffects())) { // Move the potentially sideffecting qualifier to a temporary variable so that // the code generation for calls that need .apply don't need to hande the case. SourceInfo sourceInfo = x.getSourceInfo(); diff --git a/dev/core/src/com/google/gwt/dev/js/JsSafeCloner.java b/dev/core/src/com/google/gwt/dev/js/JsSafeCloner.java index 39b7452e912..009c5269f56 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsSafeCloner.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSafeCloner.java @@ -143,13 +143,17 @@ public void endVisit(JsNameOf x, JsContext ctx) { */ @Override public void endVisit(JsNameRef x, JsContext ctx) { - if (x.getQualifier() == null && x.getIdent() == "arguments") { + if (x.getQualifier() == null && "arguments".equals(x.getIdent())) { // References to the arguments object can not be hoisted. successful = false; stack.push(null); } - JsNameRef toReturn = new JsNameRef(x.getSourceInfo(), x.getName()); - + final JsNameRef toReturn; + if (x.getName() == null) { + toReturn = new JsNameRef(x.getSourceInfo(), x.getIdent(), x.getQualifier()); + } else { + toReturn = new JsNameRef(x.getSourceInfo(), x.getName()); + } if (x.getQualifier() != null) { toReturn.setQualifier(stack.pop()); } diff --git a/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java b/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java index 36d5172fd45..c94021619bc 100644 --- a/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java +++ b/user/test/com/google/gwt/core/interop/JsTypeVarargsTest.java @@ -20,10 +20,12 @@ import com.google.gwt.core.client.ScriptInjector; import com.google.gwt.junit.client.GWTTestCase; +import java.util.Objects; import javaemul.internal.annotations.DoNotInline; import jsinterop.annotations.JsFunction; import jsinterop.annotations.JsMethod; import jsinterop.annotations.JsPackage; +import jsinterop.annotations.JsProperty; import jsinterop.annotations.JsType; /** @@ -321,4 +323,111 @@ public static int varargJsMethodUninstantiatedVararg( public native void testVarargsCall_uninstantiatedVararg() /*-{ @GWTTestCase::assertEquals(II)(0, $global.varargJsMethodUninstantiatedVararg()); }-*/; + + // https://github.com/gwtproject/gwt/issues/9932 + public void testVarargsFromJavaToJsinterop() { + assertEquals(3, nonNativeMethod("A", "B", "C")); + } + + // Java declaration of globally available instance method that takes varargs + @JsType(namespace = JsPackage.GLOBAL) + public static class VarArgsQualifiedInstanceMethod { + @JsProperty(namespace = JsPackage.GLOBAL) + public static VarArgsQualifiedInstanceMethod INSTANCE = new VarArgsQualifiedInstanceMethod(); + public int getLength(Object... values) { + return values.length; + } + } + + // Declaring this type lets us use jsinterop to call the above method. + @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "VarArgsQualifiedInstanceMethod") + public static class VarArgsFromJava { + @JsProperty(namespace = JsPackage.GLOBAL) + public static VarArgsFromJava INSTANCE; + public native int getLength(Object... values); + } + + // This plain Java method accepts varargs, and tries to pass them into jsinterop. + private static int nonNativeMethod(Object... values) { + return VarArgsFromJava.INSTANCE.getLength(values); + } + + public void testVarargsObjects() { + assertEquals(new VarargsSummary<>(1, null, null), + varargInstance().acceptsObjects((Object) null)); + assertEquals(new VarargsSummary<>(0, null, null), + varargInstance().acceptsObjects()); + assertEquals(new VarargsSummary<>(1, String.class, null), + varargInstance().acceptsObjects("hello")); + assertEquals(new VarargsSummary<>(2, String.class, null), + varargInstance().acceptsObjects("hello", "world")); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(2, String.class, null), + varargInstance().acceptsObjects(new String[]{"hello", "world"})); + assertEquals(new VarargsSummary<>(2, String.class, null), + varargInstance().acceptsObjects(new Object[]{"hello", "world"})); + } + + private static VarargMethodHolderFromJava varargInstance() { + return new VarargMethodHolderFromJava(); + } + + // Java impl of the jsinterop type we'll call below. + @JsType(namespace = JsPackage.GLOBAL) + public static class VarargMethodHolder { + public VarargsSummary acceptsObjects(Object... values) { + // Note that unlike the VarargsTest version, values can never be null, and the values + // array is always Object[] since it is spliced from js's "arguments". + assertNotNull(values); + return new VarargsSummary<>( + values.length, + (values.length == 0 || values[0] == null) ? null : values[0].getClass(), + null); + } + } + + // Declaring this type lets us use jsinterop to call the above method. + @JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "VarargMethodHolder") + public static class VarargMethodHolderFromJava { + public native VarargsSummary acceptsObjects(Object... values); + } + + @JsType + public static final class VarargsSummary { + private final int count; + private final Class firstItemType; + private final T value; + + public VarargsSummary(int count, Class firstItemType, T value) { + this.count = count; + this.firstItemType = firstItemType; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VarargsSummary that = (VarargsSummary) o; + return count == that.count + && Objects.equals(firstItemType, that.firstItemType) + && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(count, firstItemType, value); + } + + @Override + public String toString() { + return "count=" + count + + ", firstItemType=" + firstItemType + + ", value=" + value; + } + } } diff --git a/user/test/com/google/gwt/dev/jjs/test/VarargsTest.java b/user/test/com/google/gwt/dev/jjs/test/VarargsTest.java index 23e0a034380..7d7e2b066a0 100644 --- a/user/test/com/google/gwt/dev/jjs/test/VarargsTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/VarargsTest.java @@ -18,6 +18,7 @@ import com.google.gwt.junit.client.GWTTestCase; import java.util.Arrays; +import java.util.Objects; /** * Tests the new JDK 1.5 varargs functionality. @@ -87,4 +88,153 @@ private Foo[] fooIdent(Foo[] args) { private int[] varargUnboxed(int... args) { return args; } + + public void testVarargsObjects() { + assertEquals(new VarargsSummary<>(1, Object[].class, null), + acceptsObjects((Object) null)); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(-1, null, null), + acceptsObjects(null)); + assertEquals(new VarargsSummary<>(-1, null, null), + acceptsObjects((Object[]) null)); + assertEquals(new VarargsSummary<>(0, Object[].class, null), + acceptsObjects()); + assertEquals(new VarargsSummary<>(1, Object[].class, null), + acceptsObjects("hello")); + assertEquals(new VarargsSummary<>(2, Object[].class, null), + acceptsObjects("hello", "world")); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(2, String[].class, null), + acceptsObjects(new String[]{"hello", "world"})); + assertEquals(new VarargsSummary<>(2, Object[].class, null), + acceptsObjects(new Object[]{"hello", "world"})); + } + + private VarargsSummary acceptsObjects(Object... values) { + return new VarargsSummary<>( + values == null ? -1 : values.length, + values == null ? null : values.getClass(), + null); + } + + public void testVarargsObjectsWithOtherParam() { + assertEquals(new VarargsSummary<>(1, Object[].class, 1), + acceptsObjectsAndOtherParam(1, (Object) null)); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(-1, null, 2), + acceptsObjectsAndOtherParam(2, null)); + assertEquals(new VarargsSummary<>(-1, null, 3), + acceptsObjectsAndOtherParam(3, (Object[]) null)); + assertEquals(new VarargsSummary<>(0, Object[].class, 4), + acceptsObjectsAndOtherParam(4)); + assertEquals(new VarargsSummary<>(1, Object[].class, 5), + acceptsObjectsAndOtherParam(5, "hello")); + assertEquals(new VarargsSummary<>(2, Object[].class, 6), + acceptsObjectsAndOtherParam(6, "hello", "world")); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(2, String[].class, 7), + acceptsObjectsAndOtherParam(7, new String[]{"hello", "world"})); + assertEquals(new VarargsSummary<>(2, Object[].class, 8), + acceptsObjectsAndOtherParam(8, new Object[]{"hello", "world"})); + } + + private VarargsSummary acceptsObjectsAndOtherParam(int number, Object... values) { + return new VarargsSummary<>( + values == null ? -1 : values.length, + values == null ? null : values.getClass(), + number); + } + + public void testObjectsWithOtherVarargsParam() { + assertEquals(new VarargsSummary<>(1, Object[].class, 1), + acceptsObjectsAndGenericParam(1, (Object) null)); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(-1, null, "2"), + acceptsObjectsAndGenericParam("2", null)); + assertEquals(new VarargsSummary<>(-1, null, null), + acceptsObjectsAndGenericParam(null, (Object[]) null)); + assertEquals(new VarargsSummary<>(0, Object[].class, 4), + acceptsObjectsAndGenericParam(4)); + assertEquals(new VarargsSummary<>(1, Object[].class, 5), + acceptsObjectsAndGenericParam(5, "hello")); + assertEquals(new VarargsSummary<>(2, Object[].class, 6), + acceptsObjectsAndGenericParam(6, "hello", "world")); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(2, String[].class, 7), + acceptsObjectsAndGenericParam(7, new String[]{"hello", "world"})); + assertEquals(new VarargsSummary<>(2, Object[].class, 8), + acceptsObjectsAndGenericParam(8, new Object[]{"hello", "world"})); + } + + private VarargsSummary acceptsObjectsAndGenericParam(T generic, Object... values) { + return new VarargsSummary<>( + values == null ? -1 : values.length, + values == null ? null : values.getClass(), + generic); + } + + public void testGenericVarargsWithOtherParam() { + assertEquals(new VarargsSummary<>(1, Object[].class, 1), + acceptsGenericVarargsAndOtherParam(1, (Object) null)); + // noinspection ConfusingArgumentToVarargsMethod + assertEquals(new VarargsSummary<>(-1, null, 2), + acceptsGenericVarargsAndOtherParam(2, null)); + assertEquals(new VarargsSummary<>(-1, null, 3), + acceptsGenericVarargsAndOtherParam(3, (Object[]) null)); + assertEquals(new VarargsSummary<>(0, Object[].class, 4), + acceptsGenericVarargsAndOtherParam(4)); + assertEquals(new VarargsSummary<>(1, String[].class, 5), + acceptsGenericVarargsAndOtherParam(5, "hello")); + assertEquals(new VarargsSummary<>(2, String[].class, 6), + acceptsGenericVarargsAndOtherParam(6, "hello", "world")); + assertEquals(new VarargsSummary<>(2, String[].class, 7), + acceptsGenericVarargsAndOtherParam(7, new String[]{"hello", "world"})); + assertEquals(new VarargsSummary<>(2, Object[].class, 8), + acceptsGenericVarargsAndOtherParam(8, new Object[]{"hello", "world"})); + } + + private VarargsSummary acceptsGenericVarargsAndOtherParam(int number, T... values) { + return new VarargsSummary<>( + values == null ? -1 : values.length, + values == null ? null : values.getClass(), + number); + } + + public static final class VarargsSummary { + private final int count; + private final Class paramType; + private final T value; + + public VarargsSummary(int count, Class paramType, T value) { + this.count = count; + this.paramType = paramType; + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VarargsSummary that = (VarargsSummary) o; + return count == that.count + && Objects.equals(paramType, that.paramType) + && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(count, paramType, value); + } + + @Override + public String toString() { + return "count=" + count + + ", paramType=" + paramType + + ", value=" + value; + } + } } From a53bb51669ed7dbb894b1ef0d9aa6dd50ff2e126 Mon Sep 17 00:00:00 2001 From: "Ahmad K. Bawaneh" Date: Wed, 14 Aug 2024 16:17:48 +0300 Subject: [PATCH 12/12] Add test for negative instanceof variable outside if scope (#9993) Follow-up #9917 Co-authored-by: Zbynek Konecny --- .../com/google/gwt/dev/jjs/test/Java17Test.java | 8 ++++++++ .../com/google/gwt/dev/jjs/test/Java17Test.java | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java index a15c35acf64..52b37a47861 100644 --- a/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java +++ b/user/test-super/com/google/gwt/dev/jjs/super/com/google/gwt/dev/jjs/test/Java17Test.java @@ -379,4 +379,12 @@ public void testInstanceOfPatternMatchingAsReturn() { Bar bar = new Bar(); assertTrue(bar.isSquare()); } + + public void testNegativeInstanceOfPatternOutsideIfScope() { + Object bar = new Bar(); + if (!(bar instanceof Bar b)) { + throw new RuntimeException(); + } + assertTrue(b.isSquare()); + } } diff --git a/user/test/com/google/gwt/dev/jjs/test/Java17Test.java b/user/test/com/google/gwt/dev/jjs/test/Java17Test.java index f14ad1f227b..ea0162a6ae4 100644 --- a/user/test/com/google/gwt/dev/jjs/test/Java17Test.java +++ b/user/test/com/google/gwt/dev/jjs/test/Java17Test.java @@ -62,6 +62,10 @@ public void testInstanceOfPatternMatching() { assertFalse(isGwtSourceLevel17()); } + public void testInstanceOfPatternMatchingWithSideEffectExpression() { + assertFalse(isGwtSourceLevel17()); + } + public void testInstanceOfPatternMatchingWithAnd() { assertFalse(isGwtSourceLevel17()); } @@ -78,6 +82,14 @@ public void testMultipleInstanceOfPatternMatchingWithSameVariableName() { assertFalse(isGwtSourceLevel17()); } + public void testMultipleInstanceOfPatternMatchingWithSameVariableNameWithDifferentTypes() { + assertFalse(isGwtSourceLevel17()); + } + + public void testInstanceOfPatternMatchingIsFalse() { + assertFalse(isGwtSourceLevel17()); + } + public void testInstanceOfPatternMatchingInLambda() { assertFalse(isGwtSourceLevel17()); } @@ -86,6 +98,10 @@ public void testInstanceOfPatternMatchingAsReturn() { assertFalse(isGwtSourceLevel17()); } + public void testNegativeInstanceOfPatternOutsideIfScope() { + assertFalse(isGwtSourceLevel17()); + } + private boolean isGwtSourceLevel17() { return JUnitShell.getCompilerOptions().getSourceLevel().compareTo(SourceLevel.JAVA17) >= 0; }