diff --git a/plugins/groovy/resources/standardDsls/categoryTransform.gdsl b/plugins/groovy/resources/standardDsls/categoryTransform.gdsl
index 24df1224b240..d7279e4a6449 100644
--- a/plugins/groovy/resources/standardDsls/categoryTransform.gdsl
+++ b/plugins/groovy/resources/standardDsls/categoryTransform.gdsl
@@ -20,12 +20,3 @@ contributor(ctype:hasAnnotation("groovy.lang.Category")) {
}
}
-contributor(ctype:hasAnnotation("groovy.lang.Mixin")) {
- def annotations = psiClass?.getAnnotations("groovy.lang.Mixin")
- for (a in annotations) {
- def refs = a.findAttributeValue("value")
- for (c in refs?.asList()) {
- delegatesTo c?.bind()
- }
- }
-}
\ No newline at end of file
diff --git a/plugins/groovy/src/META-INF/plugin.xml b/plugins/groovy/src/META-INF/plugin.xml
index 40bec478ac61..9cde000e8359 100644
--- a/plugins/groovy/src/META-INF/plugin.xml
+++ b/plugins/groovy/src/META-INF/plugin.xml
@@ -72,6 +72,7 @@
+
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java
index 82c33a84cf7b..4d603346469c 100644
--- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/util/GroovyCommonClassNames.java
@@ -46,7 +46,8 @@ public final class GroovyCommonClassNames {
@NonNls public static final String GROOVY_TRANSFORM_AUTO_EXTERNALIZE = "groovy.transform.AutoExternalize";
@NonNls public static final String GROOVY_TRANSFORM_AUTO_CLONE = "groovy.transform.AutoClone";
@NonNls public static final String GROOVY_LANG_CATEGORY = "groovy.lang.Category";
- public static final String GROOVY_LANG_USE = "groovy.lang.Use";
+ @NonNls public static final String GROOVY_LANG_USE = "groovy.lang.Use";
+ @NonNls public static final String GROOVY_LANG_MIXIN = "groovy.lang.Mixin";
private GroovyCommonClassNames() {
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/GdkMethodDslProvider.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/GdkMethodDslProvider.java
index 2d9a834d199c..65030b728a6d 100644
--- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/GdkMethodDslProvider.java
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/GdkMethodDslProvider.java
@@ -75,9 +75,9 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
}
public static GdkMethodHolder retrieveMethodMap(final Project project,
- final GlobalSearchScope scope,
- final boolean isStatic,
- @NotNull final PsiClass categoryClass) {
+ final GlobalSearchScope scope,
+ final boolean isStatic,
+ @NotNull final PsiClass categoryClass) {
return CachedValuesManager.getManager(project)
.getCachedValue(categoryClass, METHOD_KEY, new CachedValueProvider() {
@Override
@@ -86,7 +86,8 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
final ProjectRootManager rootManager = ProjectRootManager.getInstance(project);
final VirtualFile vfile = categoryClass.getContainingFile().getVirtualFile();
- if (vfile != null && (rootManager.getFileIndex().isInLibraryClasses(vfile) || rootManager.getFileIndex().isInLibrarySource(vfile))) {
+ if (vfile != null &&
+ (rootManager.getFileIndex().isInLibraryClasses(vfile) || rootManager.getFileIndex().isInLibrarySource(vfile))) {
return Result.create(result, rootManager);
}
@@ -94,7 +95,7 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
}
}, false);
}
-
+
private static class GdkMethodHolder {
private final MultiMap originalMethodsByName;
private final NotNullLazyValue> originalMethodByType;
@@ -110,7 +111,7 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
for (PsiMethod m : categoryClass.getMethods()) {
final PsiParameter[] params = m.getParameterList().getParameters();
if (params.length == 0) continue;
-
+
byName.putValue(m.getName(), m);
}
this.originalMethodsByName = byName;
@@ -121,7 +122,6 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
MultiMap map = new MultiMap();
for (PsiMethod method : originalMethodsByName.values()) {
map.putValue(getCategoryTargetType(method).getCanonicalText(), method);
-
}
return map;
}
@@ -142,7 +142,7 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
if (name != null) {
for (PsiMethod method : originalMethodsByName.get(name)) {
if (getCategoryTargetType(method).isAssignableFrom(psiType)) {
- if (!processor.execute(new GrGdkMethodImpl(method, myStatic), state)) {
+ if (!processor.execute(GrGdkMethodImpl.createGdkMethod(method, myStatic), state)) {
return false;
}
}
@@ -153,14 +153,13 @@ public class GdkMethodDslProvider implements GdslMembersProvider {
for (String superType : ResolveUtil.getAllSuperTypes(psiType, descriptor.getProject()).keySet()) {
for (PsiMethod method : originalMethodByType.getValue().get(superType)) {
- if (!processor.execute(new GrGdkMethodImpl(method, myStatic), state)) {
+ if (!processor.execute(GrGdkMethodImpl.createGdkMethod(method, myStatic), state)) {
return false;
}
}
}
return true;
-
}
}
}
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/MixinMemberContributor.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/MixinMemberContributor.java
new file mode 100644
index 000000000000..2e1d8fd17c47
--- /dev/null
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/MixinMemberContributor.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2000-2011 JetBrains s.r.o.
+ *
+ * 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
+ *
+ * http://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.jetbrains.plugins.groovy.lang.resolve;
+
+import com.intellij.psi.*;
+import com.intellij.psi.scope.DelegatingScopeProcessor;
+import com.intellij.psi.scope.PsiScopeProcessor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotation;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer;
+import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationMemberValue;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
+import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
+import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrGdkMethodImpl;
+import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Max Medvedev
+ */
+public class MixinMemberContributor extends NonCodeMembersContributor {
+ @Override
+ public void processDynamicElements(@NotNull final PsiType qualifierType,
+ PsiScopeProcessor processor,
+ GroovyPsiElement place,
+ ResolveState state) {
+ if (!(qualifierType instanceof PsiClassType)) return;
+ if (isInAnnotation(place)) return;
+ final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)qualifierType).resolveGenerics();
+ final PsiClass aClass = resolveResult.getElement();
+ if (aClass == null) return;
+
+ final PsiModifierList modifierList = aClass.getModifierList();
+ if (modifierList == null) return;
+
+ final PsiAnnotation annotation = modifierList.findAnnotation(GroovyCommonClassNames.GROOVY_LANG_MIXIN);
+ if (annotation == null) return;
+
+ final PsiAnnotationMemberValue value = annotation.findAttributeValue("value");
+
+ List mixins = new ArrayList();
+ if (value instanceof GrAnnotationArrayInitializer) {
+ final GrAnnotationMemberValue[] initializers = ((GrAnnotationArrayInitializer)value).getInitializers();
+ for (GrAnnotationMemberValue initializer : initializers) {
+ addMixin(initializer, mixins);
+ }
+ }
+ else if (value instanceof GrExpression) {
+ addMixin((GrExpression)value, mixins);
+ }
+
+ for (PsiClass mixin : mixins) {
+ if (!mixin.processDeclarations(new DelegatingScopeProcessor(processor) {
+ @Override
+ public boolean execute(PsiElement element, ResolveState state) {
+ if (isCategoryMethod(element, qualifierType)) {
+ return super.execute(GrGdkMethodImpl.createGdkMethod((PsiMethod)element, false), state);
+ }
+ else {
+ return super.execute(element, state);
+ }
+ }
+ }, state, null, place)) {
+ return;
+ }
+ }
+ }
+
+ private static boolean isCategoryMethod(PsiElement element, PsiType qualifierType) {
+ if (!(element instanceof PsiMethod)) return false;
+ if (!((PsiMethod)element).hasModifierProperty(PsiModifier.STATIC)) return false;
+
+ final PsiParameter[] parameters = ((PsiMethod)element).getParameterList().getParameters();
+ if (parameters.length == 0) return false;
+
+ final PsiParameter selfParam = parameters[0];
+ final PsiType selfType = selfParam.getType();
+
+ return TypesUtil.isAssignable(selfType, qualifierType, element.getManager(), element.getResolveScope());
+ }
+
+ private static boolean isInAnnotation(GroovyPsiElement place) {
+ return place.getParent() instanceof GrAnnotation || place.getParent() instanceof GrAnnotationArrayInitializer;
+ }
+
+ private static void addMixin(GrAnnotationMemberValue value, List mixins) {
+ if (value instanceof GrReferenceExpression) {
+ final PsiElement resolved = ((GrReferenceExpression)value).resolve();
+ if (resolved instanceof PsiClass) {
+ mixins.add((PsiClass)resolved);
+ }
+ }
+ }
+}
diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java
index 4f7fe69dc602..6c2dc9d2e79b 100644
--- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java
+++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/resolve/ResolveUtil.java
@@ -42,6 +42,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
+import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
@@ -75,12 +76,21 @@ public class ResolveUtil {
private ResolveUtil() {
}
+ /**
+ *
+ * @param place - place to start tree walk up
+ * @param processor
+ * @param processNonCodeMethods. this parameter tells us if we need non code members. But non code members are started to process only after we walk up any code block or script
+ * @return
+ */
public static boolean treeWalkUp(@NotNull GroovyPsiElement place, PsiScopeProcessor processor, boolean processNonCodeMethods) {
PsiElement lastParent = null;
PsiElement run = place;
final Project project = place.getProject();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
+
+ boolean doProcessNonCodeMembers = false;
while (run != null) {
if (!run.processDeclarations(processor, ResolveState.initial(), lastParent, place)) return false;
@@ -89,18 +99,24 @@ public class ResolveUtil {
if (superClass != null && !superClass.processDeclarations(processor, ResolveState.initial(), null, place)) return false;
}
if (processNonCodeMethods) {
- if (run instanceof GrTypeDefinition) {
- if (!processNonCodeMembers(factory.createType(((GrTypeDefinition)run)), processor, place)) return false;
+ if (!doProcessNonCodeMembers) {
+ if (run instanceof GrCodeBlock) doProcessNonCodeMembers = true;
+ else if (run instanceof GrStatement && run.getContext() instanceof GroovyFile) doProcessNonCodeMembers = true;
}
- else if ((run instanceof GroovyFileBase) && ((GroovyFileBase)run).isScript()) {
- final PsiClass psiClass = ((GroovyFileBase)run).getScriptClass();
- if (psiClass != null) {
- if (!processNonCodeMembers(factory.createType(psiClass), processor, place)) return false;
+ if (doProcessNonCodeMembers) {
+ if (run instanceof GrTypeDefinition) {
+ if (!processNonCodeMembers(factory.createType(((GrTypeDefinition)run)), processor, place)) return false;
+ }
+ else if ((run instanceof GroovyFileBase) && ((GroovyFileBase)run).isScript()) {
+ final PsiClass psiClass = ((GroovyFileBase)run).getScriptClass();
+ if (psiClass != null) {
+ if (!processNonCodeMembers(factory.createType(psiClass), processor, place)) return false;
+ }
+ }
+ else if (run instanceof GrClosableBlock) {
+ if (!GdkMethodUtil.categoryIteration((GrClosableBlock)run, processor)) return false;
+ if (!GdkMethodUtil.withIteration((GrClosableBlock)run, processor, place)) return false;
}
- }
- else if (run instanceof GrClosableBlock) {
- if (!GdkMethodUtil.categoryIteration((GrClosableBlock)run, processor)) return false;
- if (!GdkMethodUtil.withIteration((GrClosableBlock)run, processor, place)) return false;
}
}
lastParent = run;
diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy
index f9c7d123dfbf..f6c9cdc369e2 100644
--- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy
+++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/ResolveMethodTest.groovy
@@ -757,4 +757,43 @@ class Foo {
assertInstanceOf(resolved, GrReflectedMethod)
assertTrue(resolved.modifierList.hasModifierProperty(PsiModifier.STATIC))
}
+
+ void testMixinAndCategory() {
+ def ref = configureByText("""
+@Category(B)
+class A {
+ def foo() {print getName()}
+}
+
+@Mixin(A)
+class B {
+ def getName('B');
+}
+
+print new B().foo()
+""")
+
+ def resolved = ref.resolve()
+ assertInstanceOf(resolved, GrGdkMethod)
+ assertInstanceOf(resolved.staticMethod, GrReflectedMethod)
+ }
+
+ void testOnlyMixin() {
+ def ref = configureByText("""
+class A {
+ def foo() {print getName()}
+}
+
+@Mixin(A)
+class B {
+ def getName('B');
+}
+
+print new B().foo()
+""")
+
+ def resolved = ref.resolve()
+ assertInstanceOf(resolved, GrMethod)
+ assertTrue(resolved.isPhysical())
+ }
}