mixin support for Groovy refactored

This commit is contained in:
Maxim Medvedev
2011-10-04 10:12:37 +04:00
parent d283c4e7d2
commit 91c43faa99
7 changed files with 188 additions and 30 deletions
@@ -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()
}
}
}
+1
View File
@@ -72,6 +72,7 @@
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.noncode.GrCollectionTypeMembersProvider"/>
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.noncode.LoggingContributor"/>
<membersContributor implementation="org.jetbrains.plugins.groovy.spock.SpockMemberContributor"/>
<membersContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.MixinMemberContributor"/>
<astTransformContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.DelegatedMethodsContributor"/>
<astTransformContributor implementation="org.jetbrains.plugins.groovy.lang.resolve.ast.AutoExternalizeContributor"/>
@@ -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() {
@@ -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<GdkMethodHolder>() {
@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<String, PsiMethod> originalMethodsByName;
private final NotNullLazyValue<MultiMap<String, PsiMethod>> 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<String, PsiMethod> map = new MultiMap<String, PsiMethod>();
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;
}
}
}
@@ -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<PsiClass> mixins = new ArrayList<PsiClass>();
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<PsiClass> mixins) {
if (value instanceof GrReferenceExpression) {
final PsiElement resolved = ((GrReferenceExpression)value).resolve();
if (resolved instanceof PsiClass) {
mixins.add((PsiClass)resolved);
}
}
}
}
@@ -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;
@@ -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().f<caret>oo()
""")
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().f<caret>oo()
""")
def resolved = ref.resolve()
assertInstanceOf(resolved, GrMethod)
assertTrue(resolved.isPhysical())
}
}