[
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)