diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/MixinTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/MixinTests.java new file mode 100644 index 0000000000..0765ec4cea --- /dev/null +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/MixinTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2009-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.eclipse.jdt.groovy.core.tests.xform; + +import org.eclipse.jdt.groovy.core.tests.basic.GroovyCompilerTestSuite; +import org.junit.Test; + +/** + * Test cases for {@link groovy.lang.Mixin}. + */ +public final class MixinTests extends GroovyCompilerTestSuite { + + @Test + public void testMixin1() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class A {\n" + + " def getB() {\n" + + " 'works'\n" + + " }\n" + + "}\n" + + "@Mixin(A)\n" + + "class C {\n" + + " void test() {\n" + + " print b\n" + + " }\n" + + "}\n" + + "new C().test()\n", + }; + //@formatter:on + + runConformTest(sources, "works"); + } + + @Test // GROOVY-10200 + public void testMixin2() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class A {\n" + + " def getB() {\n" + + " 'works'\n" + + " }\n" + + "}\n" + + "class C {\n" + + " @Mixin(A)\n" + + " static class D {\n" + + " void test() {\n" + + " print b\n" + + " }\n" + + " }\n" + + "}\n" + + "new C.D().test()\n", + }; + //@formatter:on + + runConformTest(sources, "works"); + } +} diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java index 3038cd3f71..e0f4746f37 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java @@ -2893,11 +2893,6 @@ public void testCompileStatic9043_staticInnerToPackage2() { "1. ERROR in Main.groovy (at line 6)\n" + "\tprint value\n" + "\t ^^^^^\n" + - "Groovy:Apparent variable 'value' was found in a static scope but doesn't refer to a local variable, static field or class.\n" + - "----------\n" + - "2. ERROR in Main.groovy (at line 6)\n" + - "\tprint value\n" + - "\t ^^^^^\n" + "Groovy:[Static type checking] - The variable [value] is undeclared.\n" + "----------\n"); } diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/package.html b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/package.html index 7cc69c8527..26f0063395 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/package.html +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/package.html @@ -12,7 +12,7 @@
  • @groovy.lang.Grapes
  • @groovy.lang.Lazy
  • -
  • @groovy.lang.Mixin
  • +
  • @groovy.lang.Mixin ✓
  • @groovy.lang.Newify
  • @groovy.lang.Singleton ✓
  • diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java index ed1238c176..87a6d3ac41 100644 --- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java +++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java @@ -233,6 +233,7 @@ private Variable findVariableDeclaration(final String name) { ClassNode node = scope.getClassScope(); if (node != null) { Variable member = findClassMember(node, name); + /* GRECLIPSE edit -- GROOVY-5364, GROOVY-10200 while (member == null && node.getOuterClass() != null && !isAnonymous(node)) { crossingStaticContext = (crossingStaticContext || isStatic(node.getModifiers())); member = findClassMember((node = node.getOuterClass()), name); @@ -240,16 +241,24 @@ private Variable findVariableDeclaration(final String name) { if (member != null) { boolean staticScope = (crossingStaticContext || inSpecialConstructorCall), staticMember = member.isInStaticContext(); // prevent a static context (e.g. a static method) from accessing a non-static variable (e.g. a non-static field) - /* GRECLIPSE edit -- GROOVY-5364 if (!(staticScope && !staticMember)) { - */ - if (!(staticScope ? !staticMember : node.isScript())) { - // GRECLIPSE end variable = member; } } - // GROOVY-5961 - if (!isAnonymous(scope.getClassScope())) break; + */ + boolean requireStatic = (crossingStaticContext || inSpecialConstructorCall); + while (member == null && node.getOuterClass() != null && !isAnonymous(node)) { + requireStatic = requireStatic || isStatic(node.getModifiers()); + member = findClassMember((node = node.getOuterClass()), name); + } + if (member != null) { + // prevent a static context (e.g. a static method) from accessing a non-static member (e.g. a non-static field) + if (requireStatic ? member.isInStaticContext() : !node.isScript()) { + variable = member; + } + } + // GRECLIPSE end + if (!isAnonymous(scope.getClassScope())) break; // GROOVY-5961 } scope = scope.getParent(); } diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java index 282b8536f3..6594205583 100644 --- a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java +++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java @@ -236,6 +236,7 @@ private Variable findVariableDeclaration(final String name) { ClassNode node = scope.getClassScope(); if (node != null) { Variable member = findClassMember(node, name); + /* GRECLIPSE edit -- GROOVY-5364, GROOVY-10200 while (member == null && node.getOuterClass() != null && !isAnonymous(node)) { crossingStaticContext = (crossingStaticContext || isStatic(node.getModifiers())); member = findClassMember((node = node.getOuterClass()), name); @@ -243,16 +244,24 @@ private Variable findVariableDeclaration(final String name) { if (member != null) { boolean staticScope = (crossingStaticContext || inSpecialConstructorCall), staticMember = member.isInStaticContext(); // prevent a static context (e.g. a static method) from accessing a non-static variable (e.g. a non-static field) - /* GRECLIPSE edit -- GROOVY-5364 if (!(staticScope && !staticMember)) { - */ - if (!(staticScope ? !staticMember : node.isScript())) { - // GRECLIPSE end variable = member; } } - // GROOVY-5961 - if (!isAnonymous(scope.getClassScope())) break; + */ + boolean requireStatic = (crossingStaticContext || inSpecialConstructorCall); + while (member == null && node.getOuterClass() != null && !isAnonymous(node)) { + requireStatic = requireStatic || isStatic(node.getModifiers()); + member = findClassMember((node = node.getOuterClass()), name); + } + if (member != null) { + // prevent a static context (e.g. a static method) from accessing a non-static member (e.g. a non-static field) + if (requireStatic ? member.isInStaticContext() : !node.isScript()) { + variable = member; + } + } + // GRECLIPSE end + if (!isAnonymous(scope.getClassScope())) break; // GROOVY-5961 } scope = scope.getParent(); } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java index 282b8536f3..6594205583 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/VariableScopeVisitor.java @@ -236,6 +236,7 @@ private Variable findVariableDeclaration(final String name) { ClassNode node = scope.getClassScope(); if (node != null) { Variable member = findClassMember(node, name); + /* GRECLIPSE edit -- GROOVY-5364, GROOVY-10200 while (member == null && node.getOuterClass() != null && !isAnonymous(node)) { crossingStaticContext = (crossingStaticContext || isStatic(node.getModifiers())); member = findClassMember((node = node.getOuterClass()), name); @@ -243,16 +244,24 @@ private Variable findVariableDeclaration(final String name) { if (member != null) { boolean staticScope = (crossingStaticContext || inSpecialConstructorCall), staticMember = member.isInStaticContext(); // prevent a static context (e.g. a static method) from accessing a non-static variable (e.g. a non-static field) - /* GRECLIPSE edit -- GROOVY-5364 if (!(staticScope && !staticMember)) { - */ - if (!(staticScope ? !staticMember : node.isScript())) { - // GRECLIPSE end variable = member; } } - // GROOVY-5961 - if (!isAnonymous(scope.getClassScope())) break; + */ + boolean requireStatic = (crossingStaticContext || inSpecialConstructorCall); + while (member == null && node.getOuterClass() != null && !isAnonymous(node)) { + requireStatic = requireStatic || isStatic(node.getModifiers()); + member = findClassMember((node = node.getOuterClass()), name); + } + if (member != null) { + // prevent a static context (e.g. a static method) from accessing a non-static member (e.g. a non-static field) + if (requireStatic ? member.isInStaticContext() : !node.isScript()) { + variable = member; + } + } + // GRECLIPSE end + if (!isAnonymous(scope.getClassScope())) break; // GROOVY-5961 } scope = scope.getParent(); } diff --git a/ide-test/org.codehaus.groovy.alltests/src/org/codehaus/groovy/alltests/GroovyJDTTests.groovy b/ide-test/org.codehaus.groovy.alltests/src/org/codehaus/groovy/alltests/GroovyJDTTests.groovy index 163d493a5c..90d46d6024 100644 --- a/ide-test/org.codehaus.groovy.alltests/src/org/codehaus/groovy/alltests/GroovyJDTTests.groovy +++ b/ide-test/org.codehaus.groovy.alltests/src/org/codehaus/groovy/alltests/GroovyJDTTests.groovy @@ -50,6 +50,7 @@ import org.junit.runners.Suite org.eclipse.jdt.groovy.core.tests.xform.ImmutableTests, org.eclipse.jdt.groovy.core.tests.xform.InheritConstructorsTests, org.eclipse.jdt.groovy.core.tests.xform.LoggingTests, + org.eclipse.jdt.groovy.core.tests.xform.MixinTests, org.eclipse.jdt.groovy.core.tests.xform.NamedVariantTests, org.eclipse.jdt.groovy.core.tests.xform.NullCheckTests, org.eclipse.jdt.groovy.core.tests.xform.PackageScopeTests,