navigation from literals to the constructors they represent

overloaded constructors found in groovy 'new' syntax, too
This commit is contained in:
peter
2010-08-04 22:05:57 +01:00
parent cbe8a8d06d
commit caca2f7728
9 changed files with 199 additions and 112 deletions
@@ -109,7 +109,7 @@ public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiRefere
@Override
public boolean process(GrNewExpression grNewExpression) {
final PsiMethod resolvedConstructor = grNewExpression.resolveConstructor();
if (constructor.getManager().areElementsEquivalent(resolvedConstructor, constructor)) {
if (includeOverloads || constructor.getManager().areElementsEquivalent(resolvedConstructor, constructor)) {
return consumer.process(grNewExpression.getReferenceElement());
}
return true;
@@ -217,7 +217,7 @@ public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiRefere
for (PsiType psiType : GroovyExpectedTypesProvider.getDefaultExpectedTypes(argument)) {
if (psiType instanceof PsiClassType &&
targetClass.getManager().areElementsEquivalent(targetClass, ((PsiClassType)psiType).resolve()) &&
!literalProcessor.processLiteral((GrListOrMap)argument, (PsiClassType)psiType)) {
!literalProcessor.processLiteral((GrListOrMap)argument)) {
return false;
}
}
@@ -271,7 +271,7 @@ public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiRefere
final GrVariable[] vars = ((GrVariableDeclaration)grandpa).getVariables();
if (vars.length == 1) {
final GrVariable variable = vars[0];
if (!checkLiteralInstantiation(variable.getInitializerGroovy(), typeElement, literalProcessor)) {
if (!checkLiteralInstantiation(variable.getInitializerGroovy(), literalProcessor)) {
return false;
}
}
@@ -282,7 +282,7 @@ public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiRefere
ControlFlowUtils.visitAllExitPoints(method.getBlock(), new ControlFlowUtils.ExitPointVisitor() {
@Override
public boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue) {
if (!checkLiteralInstantiation(returnValue, typeElement, literalProcessor)) {
if (!checkLiteralInstantiation(returnValue, literalProcessor)) {
return false;
}
return true;
@@ -293,14 +293,14 @@ public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiRefere
else if (grandpa instanceof GrTypeCastExpression) {
final GrTypeCastExpression cast = (GrTypeCastExpression)grandpa;
if (cast.getCastTypeElement() == typeElement &&
!checkLiteralInstantiation(cast.getOperand(), typeElement, literalProcessor)) {
!checkLiteralInstantiation(cast.getOperand(), literalProcessor)) {
return false;
}
}
else if (grandpa instanceof GrSafeCastExpression) {
final GrSafeCastExpression cast = (GrSafeCastExpression)grandpa;
if (cast.getCastTypeElement() == typeElement &&
!checkLiteralInstantiation(cast.getOperand(), typeElement, literalProcessor)) {
!checkLiteralInstantiation(cast.getOperand(), literalProcessor)) {
return false;
}
}
@@ -336,14 +336,10 @@ public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiRefere
}
private static boolean checkLiteralInstantiation(GrExpression expression,
final GrTypeElement typeElement,
final LiteralConstructorSearcher literalProcessor) {
if (expression instanceof GrListOrMap) {
final PsiType expectedType = typeElement.getType();
if (expectedType instanceof PsiClassType) {
return literalProcessor.processLiteral((GrListOrMap)expression, (PsiClassType)expectedType);
}
return literalProcessor.processLiteral((GrListOrMap)expression);
}
return true;
}
@@ -1,17 +1,26 @@
package org.jetbrains.plugins.groovy.findUsages;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.psi.PsiType;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.*;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.CollectionFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSafeCastExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrTypeCastExpression;
import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrMapType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.Collections;
import java.util.List;
/**
* @author peter
*/
@@ -19,10 +28,66 @@ public class LiteralConstructorReference extends PsiReferenceBase.Poly<GrListOrM
private final PsiClassType myConstructedClass;
public LiteralConstructorReference(@NotNull GrListOrMap element, @NotNull PsiClassType constructedClassType) {
super(element, TextRange.from(0, 1), false);
super(element, TextRange.from(0, 0), false);
myConstructedClass = constructedClassType;
}
private static boolean isConstructorCall(PsiClassType expectedType,
@Nullable PsiType[] argTypes,
PsiMethod constructor,
GroovyPsiElement context) {
for (GroovyResolveResult candidate : PsiUtil.getConstructorCandidates(expectedType, argTypes, context)) {
if (constructor.getManager().areElementsEquivalent(candidate.getElement(), constructor)) {
return true;
}
}
return false;
}
public static List<ResolveResult> getConstructorCandidates(PsiClassType classType,
@NotNull GroovyPsiElement context, @Nullable PsiType[] argTypes) {
PsiClass psiClass = classType.resolve();
if (psiClass == null) return Collections.emptyList();
List<ResolveResult> applicable = CollectionFactory.arrayList();
final List<ResolveResult> byName = CollectionFactory.arrayList();
for (PsiMethod constructor : psiClass.getConstructors()) {
final ResolveResult resolveResult = new PsiElementResolveResult(constructor);
byName.add(resolveResult);
if (argTypes != null && isConstructorCall(classType, argTypes, constructor, context)) {
applicable.add(resolveResult);
}
}
if (applicable.isEmpty()) {
applicable.addAll(byName);
}
return applicable;
}
@Nullable
public static PsiClassType getTargetConversionType(@NotNull GrExpression expression) {
//todo hack
if (expression.getParent() instanceof GrSafeCastExpression) {
final PsiType type = ((GrSafeCastExpression)expression.getParent()).getType();
if (type instanceof PsiClassType) {
return (PsiClassType)type;
}
}
if (expression.getParent() instanceof GrTypeCastExpression) {
final PsiType type = ((GrTypeCastExpression)expression.getParent()).getType();
if (type instanceof PsiClassType) {
return (PsiClassType)type;
}
}
for (PsiType type : GroovyExpectedTypesProvider.getDefaultExpectedTypes(expression)) {
if (type instanceof PsiClassType) {
return (PsiClassType)type;
}
}
return null;
}
@Nullable
private PsiType[] argTypes() {
final GrListOrMap literal = getElement();
@@ -30,7 +95,7 @@ public class LiteralConstructorReference extends PsiReferenceBase.Poly<GrListOrM
if (listType instanceof GrTupleType) {
return ((GrTupleType)listType).getComponentTypes();
}
else if (listType instanceof GrMapType && ((GrMapType)listType).getValueType("super") == null) {
else if (listType instanceof GrMapType) {
return PsiType.EMPTY_ARRAY;
}
return null;
@@ -39,10 +104,17 @@ public class LiteralConstructorReference extends PsiReferenceBase.Poly<GrListOrM
@NotNull
@Override
public ResolveResult[] multiResolve(boolean incompleteCode) {
final PsiType[] psiTypes = argTypes();
if (psiTypes == null) return ResolveResult.EMPTY_ARRAY;
final GrListOrMap literal = getElement();
final GrNamedArgument superConstructor = literal.findNamedArgument("super");
if (superConstructor != null) {
final PsiReference reference = ObjectUtils.assertNotNull(superConstructor.getLabel()).getReference();
if (reference instanceof PsiPolyVariantReference) {
return ((PsiPolyVariantReference)reference).multiResolve(incompleteCode);
}
}
return PsiUtil.getConstructorCandidates(myConstructedClass, psiTypes, getElement());
final List<ResolveResult> candidates = getConstructorCandidates(myConstructedClass, literal, argTypes());
return candidates.toArray(new ResolveResult[candidates.size()]);
}
@NotNull
@@ -1,6 +1,5 @@
package org.jetbrains.plugins.groovy.findUsages;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiReference;
import com.intellij.util.ObjectUtils;
@@ -23,7 +22,7 @@ public class LiteralConstructorSearcher {
myIncludeOverloads = includeOverloads;
}
public boolean processLiteral(GrListOrMap literal, PsiClassType expectedType) {
public boolean processLiteral(GrListOrMap literal) {
if (literal.isMap()) {
final GrNamedArgument argument = literal.findNamedArgument("super");
if (argument != null) {
@@ -31,13 +30,13 @@ public class LiteralConstructorSearcher {
}
}
return processConstructorReference(new LiteralConstructorReference(literal, expectedType));
return processConstructorReference(literal.getReference());
}
private boolean processConstructorReference(@Nullable PsiReference reference) {
if (reference != null && (myIncludeOverloads || reference.isReferenceTo(myConstructor))) {
return myConsumer.process(reference);
}
return myConsumer.process(reference);
}
return true;
}
}
@@ -9,54 +9,38 @@ import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.CollectionFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.findUsages.LiteralConstructorReference;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
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.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrSafeCastExpression;
import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.GroovyExpectedTypesProvider;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrTupleType;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author peter
*/
public class GppReferenceContributor extends PsiReferenceContributor {
public static boolean mayInvokeConstructor(PsiClassType expectedType, PsiMethod constructor, GrExpression args) {
if (args == null) {
return true;
@Nullable
private static PsiType[] getSuperConstructorArguments(GrExpression superAttributeValue) {
if (superAttributeValue == null) {
return null;
}
final PsiType type = args.getType();
final PsiType type = superAttributeValue.getType();
if (type == null) {
return true;
return null;
}
if (type instanceof GrTupleType) {
return isConstructorCall(expectedType, ((GrTupleType)type).getComponentTypes(), constructor, args);
return ((GrTupleType)type).getComponentTypes();
}
return isConstructorCall(expectedType, new PsiType[]{type}, constructor, args);
}
public static boolean isConstructorCall(PsiClassType expectedType,
PsiType[] argTypes,
PsiMethod constructor,
GroovyPsiElement context) {
for (GroovyResolveResult candidate : PsiUtil.getConstructorCandidates(expectedType, argTypes, context)) {
if (constructor.getManager().areElementsEquivalent(candidate.getElement(), constructor)) {
return true;
}
}
return false;
return new PsiType[]{type};
}
@Override
@@ -90,67 +74,38 @@ public class GppReferenceContributor extends PsiReferenceContributor {
public ResolveResult[] multiResolve(boolean incompleteCode) {
final GrArgumentLabel context = getElement();
final GrNamedArgument namedArgument = (GrNamedArgument) context.getParent();
for (PsiType type : getTargetConversionTypes((GrExpression)namedArgument.getParent())) {
if (type instanceof PsiClassType) {
final PsiClassType classType = (PsiClassType)type;
final PsiClass psiClass = classType.resolve();
if (psiClass != null) {
final GrExpression value = namedArgument.getExpression();
final PsiClassType classType = LiteralConstructorReference.getTargetConversionType((GrExpression)namedArgument.getParent());
if (classType != null) {
final PsiClass psiClass = classType.resolve();
if (psiClass != null) {
final GrExpression value = namedArgument.getExpression();
final List<ResolveResult> applicable = addMethodCandidates(classType, value);
final List<ResolveResult> applicable = addMethodCandidates(classType, value);
final String memberName = getValue();
if ("super".equals(memberName) && GppTypeConverter.hasTypedContext(myElement)) {
applicable.addAll(addConstructorCandidates(classType, psiClass, value));
}
final String memberName = getValue();
if ("super".equals(memberName) && GppTypeConverter.hasTypedContext(myElement)) {
applicable.addAll(LiteralConstructorReference.getConstructorCandidates(classType, context, getSuperConstructorArguments(value)));
}
if (value == null || applicable.isEmpty()) {
final PsiMethod setter = PropertyUtil.findPropertySetter(psiClass, memberName, false, true);
if (setter != null) {
applicable.add(new PsiElementResolveResult(setter));
} else {
final PsiField field = PropertyUtil.findPropertyField(psiClass.getProject(), psiClass, memberName, false);
if (field != null) {
applicable.add(new PsiElementResolveResult(field));
}
if (value == null || applicable.isEmpty()) {
final PsiMethod setter = PropertyUtil.findPropertySetter(psiClass, memberName, false, true);
if (setter != null) {
applicable.add(new PsiElementResolveResult(setter));
} else {
final PsiField field = PropertyUtil.findPropertyField(psiClass.getProject(), psiClass, memberName, false);
if (field != null) {
applicable.add(new PsiElementResolveResult(field));
}
}
return applicable.toArray(new ResolveResult[applicable.size()]);
}
return applicable.toArray(new ResolveResult[applicable.size()]);
}
}
return ResolveResult.EMPTY_ARRAY;
}
private static Set<PsiType> getTargetConversionTypes(GrExpression expression) {
//todo hack
if (expression.getParent() instanceof GrSafeCastExpression) {
final PsiType type = ((GrSafeCastExpression)expression.getParent()).getType();
if (type != null) {
return Collections.singleton(type);
}
}
return GroovyExpectedTypesProvider.getDefaultExpectedTypes(expression);
}
private static List<ResolveResult> addConstructorCandidates(PsiClassType classType, PsiClass psiClass, GrExpression value) {
List<ResolveResult> applicable = CollectionFactory.arrayList();
final List<ResolveResult> byName = CollectionFactory.arrayList();
for (PsiMethod constructor : psiClass.getConstructors()) {
final ResolveResult resolveResult = new PsiElementResolveResult(constructor);
byName.add(resolveResult);
if (mayInvokeConstructor(classType, constructor, value)) {
applicable.add(resolveResult);
}
}
if (applicable.isEmpty()) {
applicable.addAll(byName);
}
return applicable;
}
private List<ResolveResult> addMethodCandidates(PsiClassType classType, GrExpression value) {
PsiType valueType = value == null ? null : value.getType();
@@ -21,6 +21,8 @@ import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
@@ -37,6 +39,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrUnaryE
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.types.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
@@ -142,14 +145,9 @@ public class GroovyExpectedTypesProvider {
@Override
public void visitOpenBlock(GrOpenBlock block) {
if (block.getParent() instanceof PsiMethod) {
final GrStatement[] statements = block.getStatements();
if (statements.length > 0 && myExpression.equals(statements[statements.length - 1])) {
final PsiType type = ((PsiMethod)block.getParent()).getReturnType();
if (type != null) {
myResult = new TypeConstraint[]{new SubtypeConstraint(type, type)};
}
}
final GrStatement[] statements = block.getStatements();
if (statements.length > 0 && myExpression.equals(statements[statements.length - 1])) {
checkExitPoint();
}
}
@@ -157,6 +155,27 @@ public class GroovyExpectedTypesProvider {
if (myExpression.equals(ifStatement.getCondition())) {
myResult = new TypeConstraint[]{new SubtypeConstraint(TypesUtil.getJavaLangObject(ifStatement), PsiType.BOOLEAN)};
}
else if (myExpression.equals(ifStatement.getThenBranch()) || myExpression.equals(ifStatement.getElseBranch())) {
checkExitPoint();
}
}
private void checkExitPoint() {
final PsiElement element = PsiTreeUtil.getParentOfType(myExpression, PsiMethod.class, GrClosableBlock.class);
if (element instanceof GrMethod) {
final GrMethod method = (GrMethod)element;
ControlFlowUtils.visitAllExitPoints(method.getBlock(), new ControlFlowUtils.ExitPointVisitor() {
@Override
public boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue) {
if (returnValue == myExpression) {
final PsiType returnType = method.getReturnType();
myResult = new TypeConstraint[]{new SubtypeConstraint(returnType, returnType)};
return false;
}
return true;
}
});
}
}
public void visitWhileStatement(GrWhileStatement whileStatement) {
@@ -24,6 +24,7 @@ import com.intellij.psi.tree.TokenSet;
import com.intellij.util.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.findUsages.LiteralConstructorReference;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
@@ -103,6 +104,16 @@ public class GrListOrMapImpl extends GrExpressionImpl implements GrListOrMap {
return null;
}
@Override
public PsiReference getReference() {
final PsiClassType conversionType = LiteralConstructorReference.getTargetConversionType(this);
if (conversionType != null) {
return new LiteralConstructorReference(this, conversionType);
}
return null;
}
private static class MyTypesCalculator implements Function<GrListOrMapImpl, PsiType> {
@Nullable
public PsiType fun(GrListOrMapImpl listOrMap) {
@@ -407,6 +407,29 @@ class Point {
assertEquals 2, multiResolveReference().size()
}
public void testGotoSuperConstructorFromLiteralOnsets() throws Exception {
PsiClass point = myFixture.addClass("""
class Point {
Point() {}
Point(int y) {}
}""")
configureGppScript "Point p = <caret>[super: 2]"
assertEquals point.constructors[1], resolveReference()
configureGppScript "Point p = <caret>[2]"
assertEquals point.constructors[1], resolveReference()
configureGppScript "Point p = <caret>[]"
assertEquals point.constructors[0], resolveReference()
configureGppScript "Point p = <caret>[:]"
assertEquals point.constructors[0], resolveReference()
configureGppScript "Point p = <caret>[239, 42]"
assertEquals 2, multiResolveReference().size()
}
public void testResolveTraitMethod() throws Exception {
configureScript """
@Trait
@@ -27,8 +27,14 @@ class LiteralConstructorUsagesTest extends LightCodeInsightFixtureTestCase {
Foo() {}
}
""")
myFixture.addFileToProject "a.groovy", "Foo foo() { if (true) [] else return [] }"
assertEquals(2, ReferencesSearch.search(foo.constructors[0]).findAll().size())
myFixture.addFileToProject "a.groovy", """
Foo foo() {
if (true) []
else return []
}
Foo bar() { [] }
"""
assertEquals(3, ReferencesSearch.search(foo.constructors[0]).findAll().size())
}
public void testList_Cast() throws Exception {
@@ -71,7 +71,13 @@ public class FindUsagesTest extends LightGroovyTestCase {
}
public void testConstructorUsageInNewExpression() throws Throwable {
doTestImpl("ConstructorUsageInNewExpression.groovy", 2);
myFixture.configureByFile("ConstructorUsageInNewExpression.groovy");
final PsiElement resolved = TargetElementUtilBase.findTargetElement(myFixture.getEditor(),
TargetElementUtilBase.getInstance().getReferenceSearchFlags());
assertNotNull("Could not resolve reference", resolved);
final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(myFixture.getProject());
assertEquals(2, MethodReferencesSearch.search((PsiMethod)resolved, projectScope, true).findAll().size());
assertEquals(4, MethodReferencesSearch.search((PsiMethod)resolved, projectScope, false).findAll().size());
}
public void testGotoConstructor() throws Throwable {