This is an automated email from the ASF dual-hosted git repository. paulk-asert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
commit a24f1c7300afc20bb857cb634650d89bf39a7faa Author: Paul King <[email protected]> AuthorDate: Wed May 6 20:22:32 2026 +1000 GROOVY-11998: Better support of intersection types (copilot review) --- ARCHITECTURE.md | 22 +++++++++++----------- .../groovy/ast/IntersectionTypeClassNode.java | 4 ++-- .../asm/sc/AbstractFunctionalInterfaceWriter.java | 2 +- .../classgen/asm/sc/StaticTypesClosureWriter.java | 2 +- .../groovy/runtime/IntersectionCastSupport.java | 2 +- .../groovy/tools/javac/JavaStubGenerator.java | 2 +- .../groovy/transform/RecordBaseASTStubber.java | 2 +- .../transform/stc/StaticTypeCheckingVisitor.java | 2 +- src/spec/doc/core-differences-java.adoc | 2 +- src/spec/doc/core-semantics.adoc | 2 +- src/test-resources/core/Groovydoc_02x.groovy | 4 ++-- src/test/groovy/bugs/Groovy11967.groovy | 2 +- .../transform/stc/IntersectionCastSTCTest.groovy | 6 +++--- .../antlr4/IntersectionCastParserTest.groovy | 2 -- .../groovy/tools/groovydoc/TagRenderer.java | 4 ++-- .../groovydoc/antlr4/GroovydocJavaVisitor.java | 2 +- .../groovy/tools/groovydoc/GroovyDocToolTest.java | 4 ++-- .../testfiles/ScriptWithMarkdownTopLevelDoc.groovy | 2 +- 18 files changed, 33 insertions(+), 35 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5c4a35d93c..4f6723d4e5 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -63,17 +63,17 @@ and exposed as the [`CompilePhase`](src/main/java/org/codehaus/groovy/control/CompilePhase.java) enum that AST transformations and customizers attach to: -| # | Phase | What happens | Driver classes | -|---|---|---|---| -| 1 | `INITIALIZATION` | Source files opened, `CompilationUnit` configured, customizers applied | `CompilationUnit`, `CompilerConfiguration` | -| 2 | `PARSING` | ANTLR4 lexer + parser produce a CST (parse tree) | `Antlr4ParserPlugin`, `GroovyLangLexer`, `GroovyLangParser` | -| 3 | `CONVERSION` | CST → AST (`ModuleNode` / `ClassNode` / `MethodNode` / ...) | `AstBuilder` | -| 4 | `SEMANTIC_ANALYSIS` | Class resolution, import handling, validity checks the grammar can't catch | `ResolveVisitor`, `StaticImportVisitor`, `AnnotationConstantsVisitor` | -| 5 | `CANONICALIZATION` | Fill in the AST: synthesised members, generic types, most local AST transforms run here | `ASTTransformationVisitor`, `GenericsVisitor` | +| # | Phase | What happens | Driver classes | +|---|---|-----------------------------------------------------------------------------------------|---| +| 1 | `INITIALIZATION` | Source files opened, `CompilationUnit` configured, customizers applied | `CompilationUnit`, `CompilerConfiguration` | +| 2 | `PARSING` | ANTLR4 lexer + parser produce a CST (parse tree) | `Antlr4ParserPlugin`, `GroovyLangLexer`, `GroovyLangParser` | +| 3 | `CONVERSION` | CST → AST (`ModuleNode` / `ClassNode` / `MethodNode` / ...) | `AstBuilder` | +| 4 | `SEMANTIC_ANALYSIS` | Class resolution, import handling, validity checks the grammar can't catch | `ResolveVisitor`, `StaticImportVisitor`, `AnnotationConstantsVisitor` | +| 5 | `CANONICALIZATION` | Fill in the AST: synthesized members, generic types, most local AST transforms run here | `ASTTransformationVisitor`, `GenericsVisitor` | | 6 | `INSTRUCTION_SELECTION` | Optimisations and instruction-set selection; `@CompileStatic` / `@TypeChecked` run here | `OptimizerVisitor`, `StaticTypeCheckingVisitor` | -| 7 | `CLASS_GENERATION` | AST → bytecode in memory | `AsmClassGenerator`, `Verifier`, classes under `classgen/asm/` | -| 8 | `OUTPUT` | Write generated `.class` files | `CompilationUnit` output stage | -| 9 | `FINALIZATION` | Cleanup, `Janitor` callbacks | `CompilationUnit`, `Janitor` | +| 7 | `CLASS_GENERATION` | AST → bytecode in memory | `AsmClassGenerator`, `Verifier`, classes under `classgen/asm/` | +| 8 | `OUTPUT` | Write generated `.class` files | `CompilationUnit` output stage | +| 9 | `FINALIZATION` | Cleanup, `Janitor` callbacks | `CompilationUnit`, `Janitor` | Each phase iterates over all `SourceUnit`s before the next phase begins. AST transformations declare which phase they run in; the @@ -140,7 +140,7 @@ verbatim keeps the reference precise; paraphrasing tends to drift. - `org.codehaus.groovy.classgen.AsmClassGenerator` walks the AST and emits bytecode via ASM. Supporting visitors run here too: - `Verifier` (synthesises bridge methods, accessors, default + `Verifier` (synthesizes bridge methods, accessors, default constructors), `EnumVisitor`, `EnumCompletionVisitor`, `InnerClassVisitor`, `InnerClassCompletionVisitor`, `VariableScopeVisitor`, `ReturnAdder`. diff --git a/src/main/java/org/codehaus/groovy/ast/IntersectionTypeClassNode.java b/src/main/java/org/codehaus/groovy/ast/IntersectionTypeClassNode.java index 906481d832..2b443fa7f0 100644 --- a/src/main/java/org/codehaus/groovy/ast/IntersectionTypeClassNode.java +++ b/src/main/java/org/codehaus/groovy/ast/IntersectionTypeClassNode.java @@ -35,7 +35,7 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; * * <p>Distinct from the implicit lowest-upper-bound nodes that * {@link org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor} - * synthesises during inference: an instance of this class records the ordered + * synthesizes during inference: an instance of this class records the ordered * list of components exactly as written by the user. That ordering is needed * for cast-conversion checks, error messages and (in later phases) bytecode * generation via {@code LambdaMetafactory.altMetafactory} markers. @@ -48,7 +48,7 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; * interfaces array contains only interface components and the superclass is * the (at most one) class component. * - * @since 5.0.0 + * @since 6.0.0 */ public final class IntersectionTypeClassNode extends ClassNode { diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java index 76eac91cb9..ff74bee97b 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/AbstractFunctionalInterfaceWriter.java @@ -90,7 +90,7 @@ public interface AbstractFunctionalInterfaceWriter { * {@code LambdaMetafactory.altMetafactory} via {@code FLAG_MARKERS} so the * generated lambda implements every component interface at runtime. * - * @since 5.0.0 + * @since 6.0.0 */ default void writeFunctionalInterfaceIndy(final MethodVisitor methodVisitor, final String samMethodName, final String invokedTypeDescriptor, diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java index 24201148ea..2ae86527df 100644 --- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java +++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesClosureWriter.java @@ -90,7 +90,7 @@ public class StaticTypesClosureWriter extends ClosureWriter { if (closureClass.implementsInterface(marker)) continue; // Only add interfaces with no abstract methods (true markers). For // interfaces that declare unimplemented abstract methods, we'd - // have to synthesise method bodies — out of scope here, fall back + // have to synthesize method bodies — out of scope here, fall back // to the runtime proxy path in IntersectionCastSupport.asType. if (hasAbstractMethods(marker)) continue; closureClass.addInterface(marker); diff --git a/src/main/java/org/codehaus/groovy/runtime/IntersectionCastSupport.java b/src/main/java/org/codehaus/groovy/runtime/IntersectionCastSupport.java index 06eec2654b..1306676b33 100644 --- a/src/main/java/org/codehaus/groovy/runtime/IntersectionCastSupport.java +++ b/src/main/java/org/codehaus/groovy/runtime/IntersectionCastSupport.java @@ -35,7 +35,7 @@ import java.util.Map; * reference (those cases are handled at compile time via * {@code LambdaMetafactory.altMetafactory} markers). * - * @since 5.0.0 + * @since 6.0.0 */ public final class IntersectionCastSupport { diff --git a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java index a7a5c13819..b3e89c8a41 100644 --- a/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java +++ b/src/main/java/org/codehaus/groovy/tools/javac/JavaStubGenerator.java @@ -542,7 +542,7 @@ public class JavaStubGenerator { } private void printMethods(final PrintWriter out, final ClassNode classNode, final boolean isEnum, final boolean isRecordStub) { - // For native record stubs, let javac auto-synthesise the canonical + // For native record stubs, let javac auto-synthesize the canonical // constructor; we cannot reliably emit a placeholder body that // satisfies record component definite-assignment rules. Non-canonical // user-declared constructors are not visible in the stub (known diff --git a/src/main/java/org/codehaus/groovy/transform/RecordBaseASTStubber.java b/src/main/java/org/codehaus/groovy/transform/RecordBaseASTStubber.java index be2b529f00..dc41f865ff 100644 --- a/src/main/java/org/codehaus/groovy/transform/RecordBaseASTStubber.java +++ b/src/main/java/org/codehaus/groovy/transform/RecordBaseASTStubber.java @@ -94,7 +94,7 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; * <p><b>Native records.</b> When the class would compile as a native JVM * record (target {@code >= JDK16} and {@code mode != EMULATE}), the stub * generator already renders {@code record Foo(...)} syntax via the - * back-channel introduced by GROOVY-11974, and {@code javac} synthesises + * back-channel introduced by GROOVY-11974, and {@code javac} synthesizes * the canonical constructor and component accessors itself. This stubber * detects that case via * {@link RecordTypeASTTransformation#wouldBeNativeRecord} and bails out; diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index f3cb833c23..5606a97d3e 100644 --- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -1628,7 +1628,7 @@ out: if ((samParameterTypes.length == 1 && isOrImplements(samParameterTypes[0 } /** - * Build a synthesised ListExpression of {@code rhs.getAt(i)} accesses, one per LHS slot, + * Build a synthesized ListExpression of {@code rhs.getAt(i)} accesses, one per LHS slot, * tagged with the inferred element type. Used by {@link #typeCheckMultipleAssignmentPositional} * when the RHS isn't a literal list/range/Tuple-typed expression. Returns the original * RHS unchanged if its static type doesn't support {@code getAt(int)}. diff --git a/src/spec/doc/core-differences-java.adoc b/src/spec/doc/core-differences-java.adoc index a3725ce055..47b5bfd9a2 100644 --- a/src/spec/doc/core-differences-java.adoc +++ b/src/spec/doc/core-differences-java.adoc @@ -244,7 +244,7 @@ or other marker interfaces, just like in Java: [source,groovy] ---- -Runnable r = (Runnable & Serializable) () -> println('hi') // serialisable lambda +Runnable r = (Runnable & Serializable) () -> println('hi') // serializable lambda ---- Groovy additionally accepts the `as` form, where parentheses are required around diff --git a/src/spec/doc/core-semantics.adoc b/src/spec/doc/core-semantics.adoc index ab9bebac5a..caa17a6771 100644 --- a/src/spec/doc/core-semantics.adoc +++ b/src/spec/doc/core-semantics.adoc @@ -793,7 +793,7 @@ include::../test/CoercionTest.groovy[tags=intersection_as_coercion_marker,indent ---- For closure literals and maps, `as` builds a -{@link groovy.util.ProxyGenerator} aggregate that implements every interface +`groovy.util.ProxyGenerator` aggregate that implements every interface component: [source,groovy] diff --git a/src/test-resources/core/Groovydoc_02x.groovy b/src/test-resources/core/Groovydoc_02x.groovy index 16e18c4501..8cb100208f 100644 --- a/src/test-resources/core/Groovydoc_02x.groovy +++ b/src/test-resources/core/Groovydoc_02x.groovy @@ -25,7 +25,7 @@ // // Known runtime-doc gaps (pre-existing, independent of GROOVY-8877): // * Script-level /**@: a leading /**@ on a bare script is not attached to -// the synthesised Script class — no AST node to claim it. +// the synthesized Script class — no AST node to claim it. // * @Field lift: /**@ before an @Field declaration is lost when the AST // transform lifts the local into a FieldNode; the annotation isn't // carried across. @@ -36,7 +36,7 @@ */ void m() {} -// /**@ on a script's top-level method reaches the synthesised method. +// /**@ on a script's top-level method reaches the synthesized method. def mMethod = this.class.getDeclaredMethods().find { it.name == 'm' } assert mMethod != null assert mMethod.groovydoc.isPresent() diff --git a/src/test/groovy/bugs/Groovy11967.groovy b/src/test/groovy/bugs/Groovy11967.groovy index b3f893072a..8a7b60c783 100644 --- a/src/test/groovy/bugs/Groovy11967.groovy +++ b/src/test/groovy/bugs/Groovy11967.groovy @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test import static groovy.test.GroovyAssert.assertScript /** - * Regression coverage for the synthesised lower-arity bridge constructor that + * Regression coverage for the synthesized lower-arity bridge constructor that * {@code @CompileStatic} emits for a constructor with a default-valued list * parameter. The bridge inlines the default {@code [...]} literal as a * {@code new ArrayList(n)} followed by {@code .add(...)} calls; when the diff --git a/src/test/groovy/groovy/transform/stc/IntersectionCastSTCTest.groovy b/src/test/groovy/groovy/transform/stc/IntersectionCastSTCTest.groovy index ba9186c10c..e7e01bd6fe 100644 --- a/src/test/groovy/groovy/transform/stc/IntersectionCastSTCTest.groovy +++ b/src/test/groovy/groovy/transform/stc/IntersectionCastSTCTest.groovy @@ -39,9 +39,9 @@ import org.junit.jupiter.api.Test /** * Tests for static type checking of intersection-cast targets (GROOVY-11998 PR2). * - * These tests stop compilation at {@link Phases#SEMANTIC_ANALYSIS} so they - * exercise resolution and STC without relying on bytecode generation, which - * is delivered in subsequent phases. + * These tests stop compilation at {@link Phases#INSTRUCTION_SELECTION} so they + * exercise resolution and STC without producing class files, which keeps the + * tests focused on resolution/STC behavior delivered in this PR. */ final class IntersectionCastSTCTest { diff --git a/src/test/groovy/org/apache/groovy/parser/antlr4/IntersectionCastParserTest.groovy b/src/test/groovy/org/apache/groovy/parser/antlr4/IntersectionCastParserTest.groovy index 5e6a54db0d..ae278f787d 100644 --- a/src/test/groovy/org/apache/groovy/parser/antlr4/IntersectionCastParserTest.groovy +++ b/src/test/groovy/org/apache/groovy/parser/antlr4/IntersectionCastParserTest.groovy @@ -22,7 +22,6 @@ import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.IntersectionTypeClassNode import org.codehaus.groovy.ast.ModuleNode import org.codehaus.groovy.ast.expr.CastExpression -import org.codehaus.groovy.ast.expr.ClassExpression import org.codehaus.groovy.ast.expr.DeclarationExpression import org.codehaus.groovy.ast.expr.Expression import org.codehaus.groovy.ast.expr.BinaryExpression @@ -36,7 +35,6 @@ import org.junit.jupiter.api.Test import static org.junit.jupiter.api.Assertions.assertNotNull import static org.junit.jupiter.api.Assertions.assertTrue import static org.junit.jupiter.api.Assertions.assertEquals -import static org.junit.jupiter.api.Assertions.assertThrows /** * Tests for the parser-level handling of intersection types in cast expressions diff --git a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/TagRenderer.java b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/TagRenderer.java index f8d7384ee4..9122c9abcf 100644 --- a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/TagRenderer.java +++ b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/TagRenderer.java @@ -60,7 +60,7 @@ import java.util.regex.PatternSyntaxException; * * <p>Supported block tags: {@code @see}, {@code @param}, {@code @return}, * {@code @throws} / {@code @exception}, {@code @since}, {@code @author}, - * {@code @version}, {@code @default}, plus synthesised {@code typeparam} + * {@code @version}, {@code @default}, plus synthesized {@code typeparam} * (from {@code @param <T>}). Unknown block tags fall through to a generic * {@code <DL>} rendering. * @@ -110,7 +110,7 @@ final class TagRenderer { COLLATED_TAGS.put("author", "Authors"); COLLATED_TAGS.put("version", "Version"); COLLATED_TAGS.put("default", "Default"); - // typeparam is synthesised from `@param <T> desc` + // typeparam is synthesized from `@param <T> desc` COLLATED_TAGS.put("typeparam", "Type Parameters"); } diff --git a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java index b967353c0f..49e673e65d 100644 --- a/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java +++ b/subprojects/groovy-groovydoc/src/main/java/org/codehaus/groovy/tools/groovydoc/antlr4/GroovydocJavaVisitor.java @@ -190,7 +190,7 @@ public class GroovydocJavaVisitor currentClassDoc.add(fieldDoc); applyJavadocComment(n.getJavadocComment(), fieldDoc); n.getDefaultValue().ifPresent(defValue -> { - // For Markdown-form comments (no `*` line prefix), the synthesised + // For Markdown-form comments (no `*` line prefix), the synthesized // @default tag goes on a bare line; traditional /** */ form keeps // the `* ` prefix for visual parity with existing continuation lines. String prefix = fieldDoc.isMarkdown() ? "\n@default " : "\n* @default "; diff --git a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java index 9a1e936da0..52a9a9b283 100644 --- a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java +++ b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/GroovyDocToolTest.java @@ -371,7 +371,7 @@ public class GroovyDocToolTest extends GroovyTestCase { String fixtureSourcePath = "src/test/resources/docfiles-fixture"; String pkg = "org/codehaus/groovy/tools/groovydoc/testfiles/docfiles"; - // Synthesise a class on the fly that uses {@snippet file=... region=...} + // Synthesize a class on the fly that uses {@snippet file=... region=...} // Actually, just use a testfile in src/test/groovy that we add to sourcepath. // Here we reuse a class with an inline doc reference via the resource-dir. Path tmp = Files.createTempDirectory("snippet-region-"); @@ -1613,7 +1613,7 @@ public class GroovyDocToolTest extends GroovyTestCase { String klass = "EnumWithAbstractMethodAndConstantBodies"; Properties props = new Properties(); // phase 7 = CLASS_GENERATION, by which point the per-constant anonymous - // inner classes have been synthesised. The fix must keep them out of + // inner classes have been synthesized. The fix must keep them out of // the doc output at any phase. props.put("phaseOverride", "7"); GroovyDocTool tool = makeHtmltool(new ArrayList<>(), null, props); diff --git a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/ScriptWithMarkdownTopLevelDoc.groovy b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/ScriptWithMarkdownTopLevelDoc.groovy index 81dc549148..a97bb0a5dc 100644 --- a/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/ScriptWithMarkdownTopLevelDoc.groovy +++ b/subprojects/groovy-groovydoc/src/test/groovy/org/codehaus/groovy/tools/groovydoc/testfiles/ScriptWithMarkdownTopLevelDoc.groovy @@ -18,7 +18,7 @@ */ /// GROOVY-11542 + GROOVY-8877 fixture: a **Markdown** script-level doc that -/// should be lifted to the synthesised Script class via the same rules used +/// should be lifted to the synthesized Script class via the same rules used /// for traditional Javadoc comments. package org.codehaus.groovy.tools.groovydoc.testfiles
