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/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/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 extends Element> classesIterator = pack.getEnclosedElements()
.stream()
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/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 3663c96a94f..91359c61a1b 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;
@@ -1158,6 +1159,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);
@@ -1203,6 +1206,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/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 010e320b9ef..91ef6a00afc 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;
@@ -225,6 +226,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;
@@ -2863,12 +2865,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);
@@ -3346,13 +3342,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;
}
@@ -3362,7 +3358,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.
@@ -4209,8 +4206,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);
}
@@ -4219,7 +4222,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)
@@ -4274,6 +4277,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);
@@ -4380,17 +4412,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) {
@@ -4398,7 +4429,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));
}
@@ -4410,26 +4441,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;
}
@@ -4508,8 +4539,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/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/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/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/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() {
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 fa18477e096..4e855c5cedb 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
@@ -134,18 +134,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 testSwitchExpressions() throws UnableToCompleteException {
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/tools/api-checker/config/gwt211_212userapi.conf b/tools/api-checker/config/gwt211_212userapi.conf
new file mode 100644
index 00000000000..ecd503d03df
--- /dev/null
+++ b/tools/api-checker/config/gwt211_212userapi.conf
@@ -0,0 +1,156 @@
+#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.
+
+# 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/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();
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.
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/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/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/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 super String> 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/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 f0cda6fe90b..0aea58348e7 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) {
@@ -213,6 +380,14 @@ public void testInstanceOfPatternMatchingAsReturn() {
assertTrue(bar.isSquare());
}
+ public void testNegativeInstanceOfPatternOutsideIfScope() {
+ Object bar = new Bar();
+ if (!(bar instanceof Bar b)) {
+ throw new RuntimeException();
+ }
+ assertTrue(b.isSquare());
+ }
+
public void testSwitchExpressionOnConstant() {
int value = switch(0) {
default -> 17;
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/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/Java17Test.java b/user/test/com/google/gwt/dev/jjs/test/Java17Test.java
index 59a8c3cbc24..89784bbf03b 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,10 +50,22 @@ public void testSealedClassesPermitted() {
assertFalse(isGwtSourceLevel17());
}
+ public void testRecordClasses() {
+ assertFalse(isGwtSourceLevel17());
+ }
+
+ public void testJsTypeRecords() {
+ assertFalse(isGwtSourceLevel17());
+ }
+
public void testInstanceOfPatternMatching() {
assertFalse(isGwtSourceLevel17());
}
+ public void testInstanceOfPatternMatchingWithSideEffectExpression() {
+ assertFalse(isGwtSourceLevel17());
+ }
+
public void testInstanceOfPatternMatchingWithAnd() {
assertFalse(isGwtSourceLevel17());
}
@@ -70,6 +82,14 @@ public void testMultipleInstanceOfPatternMatchingWithSameVariableName() {
assertFalse(isGwtSourceLevel17());
}
+ public void testMultipleInstanceOfPatternMatchingWithSameVariableNameWithDifferentTypes() {
+ assertFalse(isGwtSourceLevel17());
+ }
+
+ public void testInstanceOfPatternMatchingIsFalse() {
+ assertFalse(isGwtSourceLevel17());
+ }
+
public void testInstanceOfPatternMatchingInLambda() {
assertFalse(isGwtSourceLevel17());
}
@@ -78,6 +98,10 @@ public void testInstanceOfPatternMatchingAsReturn() {
assertFalse(isGwtSourceLevel17());
}
+ public void testNegativeInstanceOfPatternOutsideIfScope() {
+ assertFalse(isGwtSourceLevel17());
+ }
+
public void testSwitchExpressionOnConstant() {
assertFalse(isGwtSourceLevel17());
}
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;
+ }
+ }
}
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/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));
+ }
}
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..ea12fee74c1 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");
@@ -895,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() {
@@ -911,40 +902,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) {
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.
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;
+ }
+}