[groovy] make inferred substitutor lazy and do not compute it until returned from resolve.

- check applicability by shape in MethodResolverProcessor.
- erase argument types if by shape but use non-erased types for inferring substitutor anyways.
This commit is contained in:
Daniil Ovchinnikov
2015-12-10 16:48:00 +03:00
parent 3d5877da13
commit 1bc22997d5
4 changed files with 124 additions and 112 deletions
@@ -15,6 +15,7 @@
*/
package org.jetbrains.plugins.groovy.lang.psi.impl;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiSubstitutor;
@@ -24,20 +25,25 @@ import org.jetbrains.plugins.groovy.lang.psi.api.SpreadState;
public class GroovyMethodResult extends GroovyResolveResultImpl {
private final NotNullLazyValue<PsiSubstitutor> mySubstitutorInferer;
public GroovyMethodResult(@NotNull PsiMethod method,
@Nullable PsiElement resolveContext,
@Nullable SpreadState spreadState,
@NotNull PsiSubstitutor substitutor,
boolean isAccessible, boolean staticsOK) {
super(method, resolveContext, spreadState, substitutor, isAccessible, staticsOK, true, true);
mySubstitutorInferer = NotNullLazyValue.createConstantValue(substitutor);
}
public GroovyMethodResult(@NotNull PsiMethod method,
public GroovyMethodResult(@NotNull PsiMethod element,
@Nullable PsiElement resolveContext,
@Nullable SpreadState spreadState,
@NotNull PsiSubstitutor substitutor,
@NotNull PsiSubstitutor partialSubstitutor,
@NotNull NotNullLazyValue<PsiSubstitutor> substitutorInferer,
boolean isAccessible, boolean staticsOK, boolean isApplicable) {
super(method, resolveContext, spreadState, substitutor, isAccessible, staticsOK, false, isApplicable);
super(element, resolveContext, spreadState, partialSubstitutor, isAccessible, staticsOK, false, isApplicable);
mySubstitutorInferer = substitutorInferer;
}
@NotNull
@@ -45,4 +51,15 @@ public class GroovyMethodResult extends GroovyResolveResultImpl {
public PsiMethod getElement() {
return (PsiMethod)super.getElement();
}
@NotNull
@Override
public PsiSubstitutor getSubstitutor() {
return mySubstitutorInferer.getValue();
}
@NotNull
public PsiSubstitutor getSubstitutor(boolean infer) {
return infer ? mySubstitutorInferer.getValue() : super.getSubstitutor();
}
}
@@ -18,7 +18,10 @@ package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.ResolveCache;
@@ -42,7 +45,6 @@ import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.SpreadState;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement;
@@ -265,8 +267,8 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl<GrExpressi
return resolveMethodOrProperty(true, upToArgument, true);
}
private void processMethods(@NotNull MethodResolverProcessor methodResolver) {
new GrReferenceResolveRunner(this).resolveImpl(methodResolver);
private void processMethods(GrReferenceResolveRunner runner, @NotNull MethodResolverProcessor methodResolver) {
runner.resolveImpl(methodResolver);
if (methodResolver.hasApplicableCandidates()) {
return;
}
@@ -301,29 +303,21 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl<GrExpressi
}
}
final Pair<Boolean, GroovyResolveResult[]> shapeResults = resolveByShape(allVariants, upToArgument);
if (!genericsMatter && !allVariants && shapeResults.first) {
assertAllAreValid(shapeResults.second);
return shapeResults.second;
}
final MethodResolverProcessor methodProcessor = new MethodResolverProcessor(
name, this, false,
PsiImplUtil.getQualifierType(this),
PsiUtil.getArgumentTypes(this, false, upToArgument, !genericsMatter),
getTypeArguments(),
allVariants,
!genericsMatter
);
processMethods(resolveRunner, methodProcessor);
final GroovyResolveResult[] methodCandidates = hasMemberPointer() ? collapseReflectedMethods(methodProcessor.getCandidates())
: methodProcessor.getCandidates();
assertAllAreValid(methodCandidates);
MethodResolverProcessor methodResolver = null;
if (genericsMatter) {
methodResolver = createMethodProcessor(allVariants, name, false, upToArgument);
for (GroovyResolveResult result : shapeResults.second) {
final ResolveState state = ResolveState.initial().
put(PsiSubstitutor.KEY, result.getSubstitutor()).
put(ClassHint.RESOLVE_CONTEXT, result.getCurrentFileResolveContext()).
put(SpreadState.SPREAD_STATE, result.getSpreadState());
PsiElement element = result.getElement();
assert element != null;
methodResolver.execute(element, state);
}
if (!allVariants && methodResolver.hasApplicableCandidates()) {
return methodResolver.getCandidates();
}
if (!allVariants && methodProcessor.hasApplicableCandidates()) {
return methodCandidates;
}
//search for fields inside its class
@@ -339,7 +333,7 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl<GrExpressi
List<GroovyResolveResult> allCandidates = new ArrayList<GroovyResolveResult>();
ContainerUtil.addAll(allCandidates, propertyCandidates);
ContainerUtil.addAll(allCandidates, genericsMatter ? methodResolver.getCandidates() : shapeResults.second);
ContainerUtil.addAll(allCandidates, methodCandidates);
filterOutBindings(allCandidates);
@@ -379,39 +373,6 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl<GrExpressi
}
}
@NotNull
private Pair<Boolean, GroovyResolveResult[]> resolveByShape(boolean allVariants, @Nullable GrExpression upToArgument) {
if (allVariants) {
return doResolveByShape(true, upToArgument);
}
LOG.assertTrue(upToArgument == null);
return TypeInferenceHelper.getCurrentContext().getCachedValue(this, new NullableComputable<Pair<Boolean, GroovyResolveResult[]>>() {
@Override
public Pair<Boolean, GroovyResolveResult[]> compute() {
return doResolveByShape(false, null);
}
});
}
@NotNull
private Pair<Boolean, GroovyResolveResult[]> doResolveByShape(boolean allVariants, @Nullable GrExpression upToArgument) {
final String name = getReferenceName();
LOG.assertTrue(name != null);
final MethodResolverProcessor shapeProcessor = createMethodProcessor(allVariants, name, true, upToArgument);
processMethods(shapeProcessor);
GroovyResolveResult[] candidates = shapeProcessor.getCandidates();
assertAllAreValid(candidates);
if (hasMemberPointer()) {
candidates = collapseReflectedMethods(candidates);
}
return Pair.create(shapeProcessor.hasApplicableCandidates(), candidates);
}
@NotNull
private static GroovyResolveResult[] collapseReflectedMethods(GroovyResolveResult[] candidates) {
Set<GrMethod> visited = ContainerUtil.newHashSet();
@@ -437,21 +398,6 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl<GrExpressi
}
}
@NotNull
private MethodResolverProcessor createMethodProcessor(boolean allVariants,
@Nullable String name,
final boolean byShape,
@Nullable GrExpression upToArgument) {
final PsiType[] argTypes = PsiUtil.getArgumentTypes(this, false, upToArgument, byShape);
if (byShape && argTypes != null) {
for (int i = 0; i < argTypes.length; i++) {
argTypes[i] = TypeConversionUtil.erasure(argTypes[i]);
}
}
PsiType qualifierType = PsiImplUtil.getQualifierType(this);
return new MethodResolverProcessor(name, this, false, qualifierType, argTypes, getTypeArguments(), allVariants, byShape);
}
@Override
public void accept(GroovyElementVisitor visitor) {
visitor.visitReferenceExpression(this);
@@ -61,6 +61,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatem
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyMethodResult;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
@@ -543,34 +544,71 @@ public class ResolveUtil {
return elements;
}
/**
* The point is that we do not want to see repeating methods in completion.
* Candidates can have multiple toString() methods (e.g. from Object and from some inheritor) and we want to show only one.
*/
public static GroovyResolveResult[] filterSameSignatureCandidates(Collection<? extends GroovyResolveResult> candidates) {
GroovyResolveResult[] array = candidates.toArray(new GroovyResolveResult[candidates.size()]);
if (array.length == 1) return array;
if (candidates.size() == 0) return GroovyResolveResult.EMPTY_ARRAY;
if (candidates.size() == 1) return candidates.toArray(new GroovyResolveResult[candidates.size()]);
List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
result.add(array[0]);
final List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
final Iterator<? extends GroovyResolveResult> allIterator = candidates.iterator();
result.add(allIterator.next());
Outer:
for (int i = 1; i < array.length; i++) {
PsiElement currentElement = array[i].getElement();
if (currentElement instanceof PsiMethod) {
PsiMethod currentMethod = (PsiMethod)currentElement;
for (Iterator<GroovyResolveResult> iterator = result.iterator(); iterator.hasNext();) {
final GroovyResolveResult otherResolveResult = iterator.next();
PsiElement element = otherResolveResult.getElement();
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod)element;
if (dominated(currentMethod, array[i].getSubstitutor(), method, otherResolveResult.getSubstitutor())) {
continue Outer;
}
else if (dominated(method, otherResolveResult.getSubstitutor(), currentMethod, array[i].getSubstitutor())) {
iterator.remove();
}
}
while (allIterator.hasNext()) {
final GroovyResolveResult currentResult = allIterator.next();
final PsiMethod currentMethod;
final PsiSubstitutor currentSubstitutor;
if (currentResult instanceof GroovyMethodResult) {
final GroovyMethodResult currentMethodResult = (GroovyMethodResult)currentResult;
currentMethod = currentMethodResult.getElement();
currentSubstitutor = currentMethodResult.getSubstitutor(false);
}
else if (currentResult.getElement() instanceof PsiMethod) {
currentMethod = (PsiMethod)currentResult.getElement();
currentSubstitutor = currentResult.getSubstitutor();
}
else {
result.add(currentResult);
continue;
}
Inner:
for (Iterator<GroovyResolveResult> resultIterator = result.iterator(); resultIterator.hasNext(); ) {
final GroovyResolveResult otherResult = resultIterator.next();
final PsiMethod otherMethod;
final PsiSubstitutor otherSubstitutor;
if (otherResult instanceof GroovyMethodResult) {
final GroovyMethodResult otherMethodResult = (GroovyMethodResult)otherResult;
otherMethod = otherMethodResult.getElement();
otherSubstitutor = otherMethodResult.getSubstitutor(false);
}
else if (otherResult.getElement() instanceof PsiMethod) {
otherMethod = (PsiMethod)otherResult.getElement();
otherSubstitutor = otherResult.getSubstitutor();
}
else {
continue Inner;
}
if (dominated(currentMethod, currentSubstitutor, otherMethod, otherSubstitutor)) {
// if current method is dominated by other method
// then do not add current method to result and skip rest other methods
continue Outer;
}
else if (dominated(otherMethod, otherSubstitutor, currentMethod, currentSubstitutor)) {
// if other method is dominated by current method
// then remove other from result
resultIterator.remove();
}
}
result.add(array[i]);
result.add(currentResult);
}
return result.toArray(new GroovyResolveResult[result.size()]);
@@ -16,6 +16,7 @@
package org.jetbrains.plugins.groovy.lang.resolve.processors;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.psi.*;
import com.intellij.psi.scope.JavaScopeProcessorEvent;
import com.intellij.psi.util.InheritanceUtil;
@@ -82,10 +83,15 @@ public class MethodResolverProcessor extends ResolverProcessor<GroovyMethodResul
myIsConstructor = isConstructor;
myThisType = thisType;
myArgumentTypes = argumentTypes;
if (byShape && myArgumentTypes != null) {
for (int i = 0; i < myArgumentTypes.length; i++) {
myArgumentTypes[i] = TypeConversionUtil.erasure(myArgumentTypes[i]);
}
}
myAllVariants = allVariants;
myByShape = byShape;
mySubstitutorComputer = new SubstitutorComputer(myThisType, myArgumentTypes, typeArguments, myPlace, myPlace.getParent());
mySubstitutorComputer = new SubstitutorComputer(myThisType, argumentTypes, typeArguments, myPlace, myPlace.getParent());
}
@@ -95,22 +101,29 @@ public class MethodResolverProcessor extends ResolverProcessor<GroovyMethodResul
return false;
}
if (element instanceof PsiMethod) {
PsiMethod method = (PsiMethod) element;
final PsiMethod method = (PsiMethod)element;
if (method.isConstructor() != myIsConstructor) return true;
PsiSubstitutor substitutor = inferSubstitutor(method, state);
PsiElement resolveContext = state.get(RESOLVE_CONTEXT);
final PsiElement resolveContext = state.get(RESOLVE_CONTEXT);
final SpreadState spreadState = state.get(SpreadState.SPREAD_STATE);
final PsiSubstitutor partialSubstitutor = getSubstitutor(state);
final NotNullLazyValue<PsiSubstitutor> substitutorInferer
= myByShape ? NotNullLazyValue.createConstantValue(partialSubstitutor) : new NotNullLazyValue<PsiSubstitutor>() {
@NotNull
@Override
protected PsiSubstitutor compute() {
return mySubstitutorComputer.obtainSubstitutor(partialSubstitutor, method, resolveContext);
}
};
boolean isAccessible = isAccessible(method);
boolean isStaticsOK = isStaticsOK(method, resolveContext, false);
boolean isApplicable = PsiUtil.isApplicable(myArgumentTypes, method, substitutor, myPlace, myByShape);
boolean isApplicable = PsiUtil.isApplicable(myArgumentTypes, method, null, myPlace, true);
boolean isValidResult = isStaticsOK && isAccessible && isApplicable;
GroovyMethodResult candidate = new GroovyMethodResult(
method, resolveContext, spreadState, substitutor, isAccessible, isStaticsOK, isValidResult
method, resolveContext, spreadState, partialSubstitutor, substitutorInferer, isAccessible, isStaticsOK, isValidResult
);
if (!myAllVariants && isValidResult) {
@@ -132,12 +145,10 @@ public class MethodResolverProcessor extends ResolverProcessor<GroovyMethodResul
}
@NotNull
private PsiSubstitutor inferSubstitutor(@NotNull PsiMethod method, @NotNull ResolveState state) {
private static PsiSubstitutor getSubstitutor(@NotNull final ResolveState state) {
PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY);
if (substitutor == null) substitutor = PsiSubstitutor.EMPTY;
return myByShape ? substitutor
: mySubstitutorComputer.obtainSubstitutor(substitutor, method, state.get(RESOLVE_CONTEXT));
return substitutor;
}
@Override
@@ -230,10 +241,10 @@ public class MethodResolverProcessor extends ResolverProcessor<GroovyMethodResul
//method1 has more general parameter types thn method2
private boolean secondMethodIsPreferable(@NotNull GroovyMethodResult result1, @NotNull GroovyMethodResult result2) {
PsiMethod method1 = result1.getElement();
PsiSubstitutor substitutor1 = result1.getSubstitutor();
PsiSubstitutor substitutor1 = result1.getSubstitutor(false);
PsiElement resolveContext1 = result1.getCurrentFileResolveContext();
PsiMethod method2 = result2.getElement();
PsiSubstitutor substitutor2 = result2.getSubstitutor();
PsiSubstitutor substitutor2 = result2.getSubstitutor(false);
PsiElement resolveContext2 = result2.getCurrentFileResolveContext();
final Boolean custom = GrMethodComparator.checkDominated(method1, substitutor1, method2, substitutor2, this);