[ 
https://issues.apache.org/jira/browse/GROOVY-11896?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18074027#comment-18074027
 ] 

ASF GitHub Bot commented on GROOVY-11896:
-----------------------------------------

Copilot commented on code in PR #2429:
URL: https://github.com/apache/groovy/pull/2429#discussion_r3094317864


##########
src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java:
##########
@@ -388,6 +406,124 @@ public ImportNode visitImportDeclaration(final 
ImportDeclarationContext ctx) {
         return configureAST(importNode, ctx);
     }
 
+    /**
+     * Expands {@code import module java.base} into star imports for all
+     * packages exported (unqualified) by the named module and, recursively,
+     * by any modules it {@code requires transitive} (per JEP 476).
+     * Packages already covered by Groovy's default imports or existing
+     * star imports are skipped to avoid redundant resolution work.
+     * <p>
+     * Modules are located from system modules (JDK) first, then from
+     * modular JARs on the compilation classpath. Automatic modules
+     * (JARs with an {@code Automatic-Module-Name} manifest entry but no
+     * {@code module-info.class}) are supported — all packages in the JAR
+     * are imported since automatic modules have no explicit exports.
+     * <p>
+     * As per JLS 6.4.1, explicit single-type imports and type-on-demand (star)
+     * imports take priority over module-expanded imports. Ambiguous class 
names
+     * from multiple module imports produce a compile-time error (JLS 7.5.5).
+     * <p>
+     * Known difference from Java: Groovy's star imports (including 
module-expanded
+     * ones) can resolve package-private types, whereas Java only exposes 
public
+     * types from exported packages (see GROOVY-11916).
+     */
+    private ImportNode expandModuleImport(final String moduleName, final 
List<AnnotationNode> annotations, final ImportDeclarationContext ctx) {
+        ModuleFinder finder = ModuleFinder.compose(
+                ModuleFinder.ofSystem(), classpathModuleFinder());
+        ModuleReference moduleRef = finder.find(moduleName).orElse(null);
+        if (moduleRef == null) {
+            throw createParsingFailedException("Unknown module: " + 
moduleName, ctx);
+        }
+        Set<String> skip = new HashSet<>(Arrays.asList(
+                org.codehaus.groovy.control.ResolveVisitor.DEFAULT_IMPORTS));
+        
moduleNode.getStarImports().stream().map(ImportNode::getPackageName).forEach(skip::add);
+        
moduleNode.getModuleStarImports().stream().map(ImportNode::getPackageName).forEach(skip::add);
+        ImportNode lastImport = null;
+        // Collect exported packages from this module and its transitive 
dependencies
+        Set<String> visited = new HashSet<>();
+        List<String> packageNames = new ArrayList<>();
+        collectModuleExports(moduleRef, finder, packageNames, visited);
+        for (String pkg : packageNames) {
+            String packageName = pkg + DOT_STR;
+            if (!skip.contains(packageName)) {
+                // Separate list so resolution can apply JLS 6.4.1 shadowing:
+                // a user-written `import foo.*` beats a module-expanded 
`foo.*`.
+                moduleNode.addModuleStarImport(packageName, annotations);
+                lastImport = last(moduleNode.getModuleStarImports());
+                skip.add(packageName);
+            }
+        }
+        if (lastImport == null) {
+            // All exported packages were already covered by existing imports
+            List<ImportNode> existing = moduleNode.getModuleStarImports();
+            lastImport = !existing.isEmpty() ? last(existing) : 
last(moduleNode.getStarImports());

Review Comment:
   `expandModuleImport` can end up with `lastImport == null` when all exported 
packages are skipped (e.g., they are already covered by default imports / 
existing star imports). In that case `last(existing)` / 
`last(moduleNode.getStarImports())` can throw on an empty list, or 
`configureAST(lastImport, ctx)` will NPE. Ensure a non-null `ImportNode` is 
always returned/configured even when the expansion adds no new imports.
   ```suggestion
               if (!existing.isEmpty()) {
                   lastImport = last(existing);
               } else if (!moduleNode.getStarImports().isEmpty()) {
                   lastImport = last(moduleNode.getStarImports());
               } else {
                   String packageName = !packageNames.isEmpty() ? 
packageNames.get(0) + DOT_STR : moduleName + DOT_STR;
                   lastImport = new ImportNode(packageName);
               }
   ```



##########
src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java:
##########
@@ -137,18 +137,31 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
+import java.lang.module.FindException;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;

Review Comment:
   `Files` is imported but the implementation uses 
`java.nio.file.Files.exists(...)` (fully-qualified), leaving this import unused 
and causing a Java compilation error. Use the imported `Files` (i.e., 
`Files.exists(...)`) or remove the import.
   ```suggestion
   
   ```



##########
subprojects/groovy-jmx/build.gradle:
##########
@@ -32,3 +32,8 @@ dependencies {
     testRuntimeOnly projects.groovyGrapeIvy
     testRuntimeOnly projects.groovySwing
 }
+
+tasks.named('test') {
+    dependsOn 'jarjar'
+    systemProperty 'groovy.jmx.jar', tasks.named('jarjar').flatMap { 
it.outputFile }.get().asFile.path

Review Comment:
   The `systemProperty` value forces provider resolution at configuration time 
via `.get()`, which can break task avoidance/configuration cache and eagerly 
realizes `jarjar`. Prefer keeping it lazy (e.g., pass a Provider-mapped value) 
so the path is only queried at execution time.
   ```suggestion
       systemProperty 'groovy.jmx.jar', tasks.named('jarjar').flatMap { 
it.outputFile }.map { it.asFile.path }
   ```



##########
src/main/java/org/codehaus/groovy/control/customizers/ImportCustomizer.java:
##########
@@ -68,8 +83,82 @@ public void call(final SourceUnit source, final 
GeneratorContext context, final
                 case star:
                     ast.addStarImport(anImport.star);
                     break;
+                case moduleImport:
+                    expandModuleImport(source, ast, anImport.star);
+                    break;
+            }
+        }
+    }
+
+    private static void expandModuleImport(final SourceUnit source, final 
ModuleNode ast, final String moduleName) {
+        ModuleFinder finder = ModuleFinder.compose(
+                ModuleFinder.ofSystem(), classpathModuleFinder(source));
+        ModuleReference moduleRef = finder.find(moduleName).orElse(null);
+        if (moduleRef == null) {
+            throw new IllegalArgumentException("Unknown module: " + 
moduleName);
+        }
+        Set<String> skip = new 
HashSet<>(Arrays.asList(ResolveVisitor.DEFAULT_IMPORTS));
+        ast.getStarImports().stream().map(n -> 
n.getPackageName()).forEach(skip::add);
+        Set<String> visited = new HashSet<>();
+        List<String> packageNames = new ArrayList<>();
+        collectModuleExports(moduleRef, finder, packageNames, visited);
+        for (String pkg : packageNames) {
+            String packageName = pkg + ".";
+            if (!skip.contains(packageName)) {
+                ast.addStarImport(packageName);

Review Comment:
   `ImportCustomizer` expands module imports into 
`ModuleNode.addStarImport(...)`, which gives them the same precedence as 
user-written `import foo.*`. Per the new `moduleStarImports` tiering (JLS 6.4.1 
shadowing), these should be added via `addModuleStarImport(...)` so user star 
imports can shadow module-expanded imports.
   ```suggestion
                   ast.addModuleStarImport(packageName);
   ```



##########
subprojects/groovy-groovysh/src/main/groovy/org/apache/groovy/groovysh/jline/GroovyEngine.java:
##########
@@ -407,7 +407,12 @@ public String getBuffer() {
     @Override
     public Object execute(String statement) throws Exception {
         Object out = null;
-        if 
(statement.matches("import\\s+(static\\s+)?(([^;\\s])+)(?:\\s+as\\s+(" + 
BASE_REGEX_VAR + "))?\\s*(;)?")) {
+        if (statement.matches("import\\s+module\\s+([^;\\s]+)\\s*(;)?")) {
+            String[] p = statement.split("\\s+");
+            String moduleName = p[2].replaceAll(";", "");
+            executeStatement(shell, snippets, EnumSet.of(SnippetType.IMPORT), 
statement);
+            imports.put("module " + moduleName, addSnippet(SnippetType.IMPORT, 
statement));
+        } else if 
(statement.matches("import\\s+(static\\s+)?(([^;\\s])+)(?:\\s+as\\s+(" + 
BASE_REGEX_VAR + "))?\\s*(;)?")) {

Review Comment:
   Groovysh tracks imported types for completion via `imports`/`nameClass` (see 
`refreshNameClass()`/`addToNameClass()`), but the new `import module ...` 
branch stores the key as `"module <name>"` and does not populate `nameClass`. 
As a result, method/property completion for module-imported simple names (e.g. 
`Connection.`) won’t work, and `removeImport` will treat the key like a class 
name. Consider expanding the module into package star imports for the 
completion cache (or teaching `addToNameClass`/`removeImport` to handle `module 
...`).



##########
src/main/java/org/apache/groovy/parser/antlr4/AstBuilder.java:
##########
@@ -137,18 +137,31 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.lang.annotation.Annotation;
+import java.lang.module.FindException;

Review Comment:
   `FindException` is imported but the code catches 
`java.lang.module.FindException` with a fully-qualified name, which makes this 
import unused and will fail Java compilation. Either remove the import or use 
the unqualified `FindException` in the catch clause.
   ```suggestion
   
   ```





> Support module import declarations
> ----------------------------------
>
>                 Key: GROOVY-11896
>                 URL: https://issues.apache.org/jira/browse/GROOVY-11896
>             Project: Groovy
>          Issue Type: New Feature
>            Reporter: Paul King
>            Assignee: Paul King
>            Priority: Major
>
> In Java, _module import declarations_ allow you to import all exported 
> packages of a module with a single statement. It was introduced as a preview 
> feature in Java 23 ([JEP 476|https://openjdk.org/jeps/476]) and finalized in 
> Java 25 ([JEP 511|https://openjdk.org/jeps/511]).
> The syntax is as follows:
> {code:java}
> import module java.base;    // Java
> {code}
> h3. Implementation highlights
> *  Grammar extended with a dedicated import module <qualifiedName> 
> alternative (no .* or as alias — rejected at parse time)                      
>                                                                               
>                  
> *  Modules resolved via ModuleFinder.compose(ofSystem(), 
> classpathModuleFinder()) — system/JDK modules first, then modular JARs on the 
> compilation classpath                                                         
>                              
> *  Explicit JPMS modules (with module-info.class) import all unqualified 
> exports; automatic modules (with Automatic-Module-Name manifest entry) import 
> all packages                                                                  
>              
> *   Default imports and existing star imports are skipped to avoid redundant 
> resolution                                                                    
>                                                                               
>           
> *   module remains usable as an identifier (context-sensitive keyword, like 
> record/sealed)                                                                
>                                                                               
>            
> Passing test scenarios:
> {code}                                                                        
>                                                                               
>                                                                               
>                
>   // System module — JDK classes available without individual imports
>   import module java.base                                                     
>                                                                               
>                                                                               
>          
>   LocalDate date = LocalDate.now()
>   CountDownLatch latch = new CountDownLatch(1)                                
>                                                                               
>                                                                               
>          
>                                                             
>   // System module — java.sql                                                 
>                                                                               
>                                                                               
>          
>   import module java.sql                                    
>   assert Connection.name == 'java.sql.Connection'                             
>                                                                               
>                                                                               
>          
>   assert DriverManager.name == 'java.sql.DriverManager'     
>                                                                               
>                                                                               
>                                                                               
>          
>   // JPMS module from classpath (module-info.class)                           
>                                                                               
>                                                                               
>          
>   import module org.junit.jupiter.api                                         
>                                                                               
>                                                                               
>          
>   assert Test.name == 'org.junit.jupiter.api.Test'                            
>                                                                               
>                                                                               
>          
>   assert DisabledIf.name == 'org.junit.jupiter.api.condition.DisabledIf'
>                                                                               
>                                                                               
>                                                                               
>          
>   // Automatic module from classpath (Automatic-Module-Name in MANIFEST.MF)   
>                                                                               
>                                                                               
>          
>   import module org.apache.groovy.jmx                                         
>                                                                               
>                                                                               
>          
>   assert GroovyMBean.name == 'groovy.jmx.GroovyMBean'                         
>                                                                               
>                                                                               
>          
>                                                                               
>                                                                               
>                                                                               
>          
>   // Unknown module — compile error
>   import module no.such.module  // fails: "Unknown module: no.such.module"    
>                                                                               
>                                                                               
>          
>                                                                               
>                                                                               
>                                                                               
>          
>   // Star/alias not permitted with module imports — parse error               
>                                                                               
>                                                                               
>          
>   import module java.base.*     // fails                                      
>                                                                               
>                                                                               
>          
>   import module java.base as jb // fails                                      
>                                                                               
>                                                                               
>          
>                                                                               
>                                                                               
>                                                                               
>          
>   // 'module' still works as an identifier
>   def module = 'hello'                                                        
>                                                                               
>                                                                               
>          
>   assert module.toUpperCase() == 'HELLO'
> {code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to