[ 
https://issues.apache.org/jira/browse/GROOVY-12555?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

James Daugherty updated GROOVY-12555:
-------------------------------------
    Description: 
A class in the default package that refers to another class of the default 
package by its simple name finds that class while both are compiled together. 
When the other class is already compiled, the name resolves to a class of the 
default imports with the same simple name instead, here 
{{groovy.util.Factory}}, and the call fails at run time. In Java, a class of 
the same package, the unnamed one included, takes precedence over on-demand 
imports such as {{java.lang.*}}.

{code:groovy}
// Factory.groovy, default package
class Factory {
    static String hello() { 'hello from the Factory of this project' }
}

// Caller.groovy, default package
class Caller {
    static void main(String[] args) {
        println Factory.hello() // MissingMethodException for 
groovy.util.Factory when Factory is already compiled
    }
}
{code}

Reproduced with 4.0.33, 5.1.3 and master. A reproducer with a groovyc script 
and an incremental Gradle build: 
https://github.com/jdaugherty/groovy-default-package-resolution

*Cause*

{{ResolveVisitor.resolve}} tries, in order, the classes of the module, of the 
compilation unit, of the default imports, and then the class path. For a module 
of a named package, the module step also looks up {{package.Name}} on the class 
path, so a compiled class of the same package comes first. For the default 
package there is no such step, so the default imports come before a compiled 
class of the default package.

*Proposed fix*

For a class of the default package, look up the simple name on the class path 
after the compilation unit and before the default imports, as is done for a 
named package. The lookup is behind the optimization option 
{{defaultPackageFirst}} ({{CompilerConfiguration.DEFAULT_PACKAGE_FIRST}}; 
system property {{groovy.resolve.default.package.first}}; in Gradle, 
{{groovyOptions.optimizationOptions['defaultPackageFirst']}}, the only one of 
Gradle's settings that reaches the compiler's optimization options): on by 
default in Groovy 7, and an opt-in in 6.0.x.

*Compatibility*

For a compilation unit in the default package, a simple type name that is not 
declared or imported in the unit and not in the same {{CompilationUnit}} is 
looked up on the class path, and on the source path of the 
{{GroovyClassLoader}}, as a default-package class before the default imports 
({{java.lang}}, {{java.util}}, {{java.io}}, {{java.net}}, {{java.time}}, 
{{groovy.lang}}, {{groovy.util}}, {{BigInteger}}, {{BigDecimal}}). A 
default-package {{List}} or {{Node}} on the class path therefore shadows 
{{java.util.List}} or {{groovy.util.Node}} in default-package code, where the 
default import used to win. This matches JLS §6.4.1 (a type declared in the 
same package, the unnamed package included, shadows a type imported on demand), 
except that an explicit star import still takes precedence, as elsewhere in 
Groovy; it is also what Groovy already does when the class is in the same 
{{CompilationUnit}}, and what {{ResolveVisitor.resolveFromModule}} does for a 
named package. The change is not limited to incremental builds: the {{groovy}} 
command uses the current directory as the class path when none is given, so in 
a directory of scripts a sibling {{Node.groovy}}, {{Timer.groovy}} or 
{{Math.groovy}} stands for that name in the other scripts, and a sibling source 
that does not compile becomes a compile error for a script that uses its name; 
the roots of a {{GroovyScriptEngine}} are source paths of the same kind. Each 
default-package compilation unit also pays one class-path lookup per 
default-import type it names, as a named-package unit always has, and a 
{{GroovyShell}}, {{Eval}} or {{GroovyScriptEngine}} pays it per script, since 
the lookup cache is per unit. The entries in COMPATIBILITY.md have the details 
and the remedies.

Found while making incremental builds of a Grails application produce the same 
classes as a full build; see also GROOVY-12550 and GROOVY-12544.

  was:
A class in the default package that refers to another class of the default 
package by its simple name finds that class while both are compiled together. 
When the other class is already compiled, the name resolves to a class of the 
default imports with the same simple name instead, here 
{{groovy.util.Factory}}, and the call fails at run time. In Java, a class of 
the same package, the unnamed one included, takes precedence over on-demand 
imports such as {{java.lang.*}}.

{code:groovy}
// Factory.groovy, default package
class Factory {
    static String hello() { 'hello from the Factory of this project' }
}

// Caller.groovy, default package
class Caller {
    static void main(String[] args) {
        println Factory.hello() // MissingMethodException for 
groovy.util.Factory when Factory is already compiled
    }
}
{code}

Reproduced with 4.0.33, 5.1.3 and master. A reproducer with a groovyc script 
and an incremental Gradle build: 
https://github.com/jdaugherty/groovy-default-package-resolution

*Cause*

{{ResolveVisitor.resolve}} tries, in order, the classes of the module, of the 
compilation unit, of the default imports, and then the class path. For a module 
of a named package, the module step also looks up {{package.Name}} on the class 
path, so a compiled class of the same package comes first. For the default 
package there is no such step, so the default imports come before a compiled 
class of the default package.

*Proposed fix*

For a class of the default package, look up the simple name on the class path 
after the compilation unit and before the default imports, as is done for a 
named package.

*Compatibility*

For a compilation unit in the default package, a simple type name that is not 
declared or imported in the unit and not in the same {{CompilationUnit}} is now 
looked up on the class path, and on the source path of the 
{{GroovyClassLoader}}, as a default-package class before the default imports 
({{java.lang}}, {{java.util}}, {{java.io}}, {{java.net}}, {{java.time}}, 
{{groovy.lang}}, {{groovy.util}}, {{BigInteger}}, {{BigDecimal}}). A 
default-package {{List}} or {{Node}} on the class path therefore shadows 
{{java.util.List}} or {{groovy.util.Node}} in default-package code, where the 
default import used to win. This matches Java (JLS §6.4.1: a type declared in 
the same package, the unnamed package included, shadows a type imported on 
demand), what Groovy already does when the class is in the same 
{{CompilationUnit}}, and what {{ResolveVisitor.resolveFromModule}} does for a 
named package. The order only changes for an incremental or split build, which 
is the case where the old order produced different classes from a full build.

Found while making incremental builds of a Grails application produce the same 
classes as a full build; see also GROOVY-12550 and GROOVY-12544.


> A default-package class resolves a precompiled default-package class to a 
> default import of the same name
> ---------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12555
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12555
>             Project: Groovy
>          Issue Type: Bug
>          Components: Compiler
>    Affects Versions: 4.0.33, 5.1.3
>            Reporter: James Daugherty
>            Priority: Major
>
> A class in the default package that refers to another class of the default 
> package by its simple name finds that class while both are compiled together. 
> When the other class is already compiled, the name resolves to a class of the 
> default imports with the same simple name instead, here 
> {{groovy.util.Factory}}, and the call fails at run time. In Java, a class of 
> the same package, the unnamed one included, takes precedence over on-demand 
> imports such as {{java.lang.*}}.
> {code:groovy}
> // Factory.groovy, default package
> class Factory {
>     static String hello() { 'hello from the Factory of this project' }
> }
> // Caller.groovy, default package
> class Caller {
>     static void main(String[] args) {
>         println Factory.hello() // MissingMethodException for 
> groovy.util.Factory when Factory is already compiled
>     }
> }
> {code}
> Reproduced with 4.0.33, 5.1.3 and master. A reproducer with a groovyc script 
> and an incremental Gradle build: 
> https://github.com/jdaugherty/groovy-default-package-resolution
> *Cause*
> {{ResolveVisitor.resolve}} tries, in order, the classes of the module, of the 
> compilation unit, of the default imports, and then the class path. For a 
> module of a named package, the module step also looks up {{package.Name}} on 
> the class path, so a compiled class of the same package comes first. For the 
> default package there is no such step, so the default imports come before a 
> compiled class of the default package.
> *Proposed fix*
> For a class of the default package, look up the simple name on the class path 
> after the compilation unit and before the default imports, as is done for a 
> named package. The lookup is behind the optimization option 
> {{defaultPackageFirst}} ({{CompilerConfiguration.DEFAULT_PACKAGE_FIRST}}; 
> system property {{groovy.resolve.default.package.first}}; in Gradle, 
> {{groovyOptions.optimizationOptions['defaultPackageFirst']}}, the only one of 
> Gradle's settings that reaches the compiler's optimization options): on by 
> default in Groovy 7, and an opt-in in 6.0.x.
> *Compatibility*
> For a compilation unit in the default package, a simple type name that is not 
> declared or imported in the unit and not in the same {{CompilationUnit}} is 
> looked up on the class path, and on the source path of the 
> {{GroovyClassLoader}}, as a default-package class before the default imports 
> ({{java.lang}}, {{java.util}}, {{java.io}}, {{java.net}}, {{java.time}}, 
> {{groovy.lang}}, {{groovy.util}}, {{BigInteger}}, {{BigDecimal}}). A 
> default-package {{List}} or {{Node}} on the class path therefore shadows 
> {{java.util.List}} or {{groovy.util.Node}} in default-package code, where the 
> default import used to win. This matches JLS §6.4.1 (a type declared in the 
> same package, the unnamed package included, shadows a type imported on 
> demand), except that an explicit star import still takes precedence, as 
> elsewhere in Groovy; it is also what Groovy already does when the class is in 
> the same {{CompilationUnit}}, and what {{ResolveVisitor.resolveFromModule}} 
> does for a named package. The change is not limited to incremental builds: 
> the {{groovy}} command uses the current directory as the class path when none 
> is given, so in a directory of scripts a sibling {{Node.groovy}}, 
> {{Timer.groovy}} or {{Math.groovy}} stands for that name in the other 
> scripts, and a sibling source that does not compile becomes a compile error 
> for a script that uses its name; the roots of a {{GroovyScriptEngine}} are 
> source paths of the same kind. Each default-package compilation unit also 
> pays one class-path lookup per default-import type it names, as a 
> named-package unit always has, and a {{GroovyShell}}, {{Eval}} or 
> {{GroovyScriptEngine}} pays it per script, since the lookup cache is per 
> unit. The entries in COMPATIBILITY.md have the details and the remedies.
> Found while making incremental builds of a Grails application produce the 
> same classes as a full build; see also GROOVY-12550 and GROOVY-12544.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to