Repository navigation
GROOVY-9273 GROOVY-9668: support JPMS module declarations (module-info) - #3235
daniellansun wants to merge 7 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3235 +/- ##
==================================================
+ Coverage 74.5006% 74.5805% +0.0799%
- Complexity 44947 45273 +326
==================================================
Files 1703 1705 +2
Lines 152337 153205 +868
Branches 29187 29412 +225
==================================================
+ Hits 113492 114261 +769
- Misses 28413 28469 +56
- Partials 10432 10475 +43
🚀 New features to boost your workflow:
|
b6c6f64 to
e8e63bb
Compare
blackdrag
left a comment
There was a problem hiding this comment.
why do we want to solve this using node meta data? Does this not impact the joint compiler?
|
The module declaration is not a class (JLS §7.3, §7.7), and the per-class pipeline is what we needed to reuse. That does affect the joint compiler, and the stub generator is wrong for this carrier today.
interface module-info {
}into Metadata is not dropped inside this compilation unit. The stub generator reads the same primary What should change: I will skip that carrier in |
Parse and verify module-info aligned with javac 21: missing requires-static is an error; lone transitive is a module name; empty exports/opens packages are diagnosed; split packages, enum provides, and implicit requires java.base (ACC_MANDATED) match javac. Rename ModuleDeclarationCoverageTest to ModuleDeclarationVerifierEdgeCasesTest. Close GroovyClassLoader in ModuleDeclarationTest.compile() finally so Windows releases @tempdir JARs (demo-auto / cycle-b) before cleanup. Rename module-info AST types ModuleDeclarationNode/Verifier to ModuleInfoNode/ModuleInfoVerifier (org.codehaus.groovy.ast.ModuleNode and parser production moduleDeclaration unchanged).
JavaStubGenerator.generateClass skips the synthetic module-info carrier, so joint compilation does not write an illegal interface module-info.java. Sibling .java files are compiled from the classpath. Groovy still writes module-info.class.
e907122 to
5d28b82
Compare
…pe name Parse module-info through a separate start rule so compilationUnit stays a single alternative. The two-way decision at compilationUnit was raising NoViableAltException from 'import' through 'long', so `import long` reported Unexpected input: 'import long' instead of 'long' (GROOVY-12695). Also import groovy.io.FileType in the GROOVY-9273 stub test to satisfy CodeNarc UnnecessaryFullyQualifiedName.
…e resolution Address the Carbon review findings for PR apache#3235 that hold up against JLS §7.4-§7.7, JVMS §4.7.25-§4.7.26, and JEP 511, without weakening legal tests including GROOVY-12695. Joint compilation now indexes javac output so Java-only packages and providers belong to the current module, and module-info.java is parsed by Groovy rather than switching javac into single-module mode. Module graph resolution is decoupled from the host boot layer, uses Configuration.resolve, and treats requires static as readable at compile time. Split-package checks only consider packages exported to the current module. uses and provides resolve nested types, honor import shadowing, and accept static member-type imports. Providers are verified after default-argument synthesis. Unnamed packages, non-public enclosing types, and primitive provider() return types are rejected. The emitter fills ModulePackages from joint classes, keeps provider indices aligned, and records requires transitive java.base at class file version 25+. The module header allows a newline after `module`, Groovydoc is retained, and test ModuleFinders are passed as check() parameters.
A reused target directory can contain leftover .class files. Walking the entire tree after javac treated those leftovers as members of the module being compiled, which could falsely satisfy exports, pollute ModulePackages, or raise split-package and unnamed-package errors. Snapshot the target tree before javac and record new or replaced class files, plus inner classes of the just-compiled Java sources. A walk failure is reported and aborts compilation instead of leaving Java-only packages unindexed. Tests cover a stale com.stale.Old.class that must not satisfy exports com.stale, and two unrelated automatic JARs that share a package and provide a used service, which Configuration.resolve must not reject.
indexCompiledClassFiles was private and skipped work when no target directory was present, so a later joint compiler that keeps javac CLASS_OUTPUT off the filesystem would not record Java-only packages for module verification or ModulePackages. Snapshot the output before javac and collect binary names afterwards behind protected methods. The default still compares on-disk class file stamps and ignores leftovers in a reused target directory. A subclass that stores class bytes elsewhere, for example in memory, can override the collect hook; names are still recorded through CompileUnit.addCompiledClassName. The gotoPhase path always calls the hooks, even when there is no target directory. A test shows a subclass can satisfy exports com.hooked without a usable target-directory snapshot, while the default path still rejects a leftover class that javac did not write.
…-V25 target testNegativeRequires used the host-default CompilerConfiguration for "requires transitive java.base". On JDK 25 the default target bytecode is V25, where that modifier is legal (JEP 511), so fail() threw "expected compilation to fail for base-mod" in CI. Pin that negative case to CompilerConfiguration.JDK17, the project minimum, so the "requires java.base must not have modifiers" error is still asserted on every CI JDK. The legal V25 case remains covered by testRequiresTransitiveJavaBaseOnTarget25. requires static java.base stays illegal in every version and is still checked on the host default.
✅ All tests passed ✅🏷️ Commit: c106762 Learn more about TestLens at testlens.app/docs. |
GROOVY-9273 / GROOVY-9668: support JPMS module declarations (module-info)
Summary
Groovy now accepts a modular compilation unit (
module-info.groovyormodule-info.java) and compiles it to a realmodule-info.class(JVMS §4.7.25), following JLS §7.7.{Annotation} [open] module Identifier{.Identifier} { directives }, with imports allowed before the declaration (JLS §7.3). A newline is allowed aftermodule.requires,exports,opens,uses,provides,to,with,transitive, andopenare contextual identifiers inside that file only, so existing Groovy code that uses those names is unchanged.requires[transitive] [static] (either modifier order),exports/openswith an optionaltoclause,uses, andprovides…withone or more providers.to/withtargets, duplicate modifiers,opensin anopenmodule,requires static java.base(illegal),requires transitive java.base(allowed at class file version 25+),requiresin modulejava.base, self-requires and resolution failures (missing module, cycle), exports of a package with no compilation unit in the module, enum services, provider visibility / nesting / module membership, provider constructor orpublic static provider()method, unnamed packages in a named module, and readability of service and annotation types. An absentrequires java.baseis emitted asACC_MANDATED.import module(GROOVY-11896) is unchanged in ordinary compilation units and may appear before the module declaration.module-info.javathrough Groovy so javac stays in classpath mode for sibling.javafiles. After javac, only class files written by that invocation are recorded as members of the module; leftovers in a reused target directory are ignored. The collection step is overridable so a joint compiler that keeps javac class output off the filesystem can record the same names. Module graph resolution usesConfiguration.resolverather than the host boot layer, andrequires staticis readable at compile time. Split-package checks on a named module consider only packages it exports to the module being compiled.module-info.classis written with ASMClassWriter.visitModule: requires flagsTRANSITIVE/STATIC_PHASE, exports and opens (including qualified targets), uses, provides, concealed packages inModulePackages, and module annotations (ElementType.MODULE), including Groovydoc when present.Tests
The suite covers directive round-trips via
ModuleDescriptor.read, ServiceLoader,requires transitivereadability, automatic modules (Automatic-Module-Name), qualified exports,java --describe-module,jdeps, joint compilation (including a leftover class in a reused target directory), and two automatic modules that share a package.