| /* |
| * Copyright 2000-2014 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.psi.impl.statements.expressions; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.NullableComputable; |
| 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; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.psi.util.PropertyUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.GroovyLanguage; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; |
| 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; |
| 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; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeArgumentList; |
| import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.*; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.literals.GrLiteralImpl; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrBindingVariable; |
| import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrReferenceTypeEnhancer; |
| import org.jetbrains.plugins.groovy.lang.psi.util.*; |
| import org.jetbrains.plugins.groovy.lang.resolve.ClosureMissingMethodContributor; |
| import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; |
| import org.jetbrains.plugins.groovy.lang.resolve.processors.*; |
| import org.jetbrains.plugins.groovy.util.ResolveProfiler; |
| |
| import java.util.*; |
| |
| /** |
| * @author ilyas |
| */ |
| public class GrReferenceExpressionImpl extends GrReferenceElementImpl<GrExpression> implements GrReferenceExpression { |
| |
| private static final Logger LOG = Logger.getInstance(GrReferenceExpressionImpl.class); |
| |
| public GrReferenceExpressionImpl(@NotNull ASTNode node) { |
| super(node); |
| } |
| |
| private boolean findClassOrPackageAtFirst() { |
| final String name = getReferenceName(); |
| if (StringUtil.isEmpty(name) || hasAt()) return false; |
| |
| return Character.isUpperCase(name.charAt(0)) && !isMethodCallRef() || |
| getParent() instanceof GrReferenceExpressionImpl && ((GrReferenceExpressionImpl)getParent()).findClassOrPackageAtFirst(); |
| } |
| |
| private boolean isMethodCallRef() { |
| final PsiElement parent = getParent(); |
| return parent instanceof GrMethodCall || |
| parent instanceof GrReferenceExpressionImpl && ((GrReferenceExpressionImpl)parent).isMethodCallRef(); |
| } |
| |
| private boolean isDefinitelyKeyOfMap() { |
| final GrExpression qualifier = ResolveUtil.getSelfOrWithQualifier(this); |
| if (qualifier == null) return false; |
| if (qualifier instanceof GrReferenceExpression) { //key in 'java.util.Map.key' is not access to map, it is access to static property of field |
| final PsiElement resolved = ((GrReferenceExpression)qualifier).resolve(); |
| if (resolved instanceof PsiClass) return false; |
| } |
| |
| final PsiType type = qualifier.getType(); |
| if (type == null) return false; |
| |
| if (!InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_MAP)) return false; |
| |
| final String qname = TypesUtil.getQualifiedName(type); |
| if (qname != null) { |
| if (qname.startsWith("java.")) return true; //so we have jdk map here |
| if (GroovyCommonClassNames.GROOVY_UTIL_CONFIG_OBJECT.equals(qname)) return false; |
| if (qname.startsWith("groovy.")) return true; //we have gdk map here |
| } |
| |
| return false; |
| } |
| |
| @NotNull |
| private GroovyResolveResult[] resolveTypeOrProperty() { |
| if (isDefinitelyKeyOfMap()) return GroovyResolveResult.EMPTY_ARRAY; |
| |
| final GroovyResolveResult[] results = resolveTypeOrPropertyInner(); |
| if (results.length == 0) return GroovyResolveResult.EMPTY_ARRAY; |
| |
| if (!ResolveUtil.mayBeKeyOfMap(this)) return results; |
| |
| //filter out all members from super classes. We should return only accessible members from map classes |
| List<GroovyResolveResult> filtered = new ArrayList<GroovyResolveResult>(); |
| for (GroovyResolveResult result : results) { |
| final PsiElement element = result.getElement(); |
| if (element instanceof PsiMember) { |
| if (((PsiMember)element).hasModifierProperty(PsiModifier.PRIVATE)) continue; |
| final PsiClass containingClass = ((PsiMember)element).getContainingClass(); |
| if (containingClass != null) { |
| if (!InheritanceUtil.isInheritor(containingClass, CommonClassNames.JAVA_UTIL_MAP)) continue; |
| final String name = containingClass.getQualifiedName(); |
| if (name != null && name.startsWith("java.")) continue; |
| if (containingClass.getLanguage() != GroovyLanguage.INSTANCE && |
| !InheritanceUtil.isInheritor(containingClass, GroovyCommonClassNames.DEFAULT_BASE_CLASS_NAME)) { |
| continue; |
| } |
| } |
| } |
| filtered.add(result); |
| } |
| |
| return ContainerUtil.toArray(filtered, new GroovyResolveResult[filtered.size()]); |
| } |
| |
| @NotNull |
| private GroovyResolveResult[] resolveTypeOrPropertyInner() { |
| PsiElement nameElement = getReferenceNameElement(); |
| String name = getReferenceName(); |
| |
| if (name == null || nameElement == null) return GroovyResolveResult.EMPTY_ARRAY; |
| |
| IElementType nameType = nameElement.getNode().getElementType(); |
| if (nameType == GroovyTokenTypes.kTHIS) { |
| GroovyResolveResult[] results = GrThisReferenceResolver.resolveThisExpression(this); |
| if (results != null) { |
| return results; |
| } |
| } |
| else if (nameType == GroovyTokenTypes.kSUPER) { |
| GroovyResolveResult[] results = GrSuperReferenceResolver.resolveSuperExpression(this); |
| if (results != null) { |
| return results; |
| } |
| } |
| |
| |
| EnumSet<ClassHint.ResolveKind> kinds = getParent() instanceof GrReferenceExpression |
| ? ClassHint.RESOLVE_KINDS_CLASS_PACKAGE |
| : ClassHint.RESOLVE_KINDS_CLASS; |
| |
| GroovyResolveResult[] classCandidates = null; |
| |
| GrReferenceResolveRunner resolveRunner = new GrReferenceResolveRunner(this); |
| |
| ResolverProcessor processor = new PropertyResolverProcessor(name, this); |
| resolveRunner.resolveImpl(processor); |
| final GroovyResolveResult[] fieldCandidates = processor.getCandidates(); |
| |
| if (hasAt()) { |
| return fieldCandidates; |
| } |
| |
| |
| boolean canBeClassOrPackage = ResolveUtil.canBeClassOrPackage(this); |
| |
| if (canBeClassOrPackage && findClassOrPackageAtFirst()) { |
| ResolverProcessor classProcessor = new ClassResolverProcessor(name, this, kinds); |
| resolveRunner.resolveImpl(classProcessor); |
| classCandidates = classProcessor.getCandidates(); |
| if (classCandidates.length > 0 && containsPackage(classCandidates)) return classCandidates; |
| } |
| |
| //if reference expression is in class we need to return field instead of accessor method |
| for (GroovyResolveResult candidate : fieldCandidates) { |
| final PsiElement element = candidate.getElement(); |
| if (element instanceof PsiField) { |
| final PsiClass containingClass = ((PsiField)element).getContainingClass(); |
| if (containingClass != null && PsiUtil.getContextClass(this) == containingClass) return fieldCandidates; |
| } |
| else if (!(element instanceof GrBindingVariable)) { |
| return fieldCandidates; |
| } |
| } |
| |
| if (classCandidates != null && classCandidates.length > 0) return classCandidates; |
| |
| final boolean isLValue = PsiUtil.isLValue(this); |
| String[] accessorNames = isLValue ? GroovyPropertyUtils.suggestSettersName(name) : GroovyPropertyUtils.suggestGettersName(name); |
| List<GroovyResolveResult> accessorResults = new ArrayList<GroovyResolveResult>(); |
| for (String accessorName : accessorNames) { |
| AccessorResolverProcessor accessorResolver = |
| new AccessorResolverProcessor(accessorName, name, this, !isLValue, false, PsiImplUtil.getQualifierType(this), getTypeArguments()); |
| resolveRunner.resolveImpl(accessorResolver); |
| final GroovyResolveResult[] candidates = accessorResolver.getCandidates(); |
| |
| //can be only one correct candidate or some incorrect |
| if (candidates.length == 1 && candidates[0].isStaticsOK() && candidates[0].isAccessible()) { |
| return candidates; |
| } |
| else { |
| ContainerUtil.addAll(accessorResults, candidates); |
| } |
| } |
| |
| final ArrayList<GroovyResolveResult> fieldList = ContainerUtil.newArrayList(fieldCandidates); |
| filterOutBindings(fieldList); |
| if (!fieldList.isEmpty()) { |
| return fieldList.toArray(new GroovyResolveResult[fieldList.size()]); |
| } |
| |
| if (classCandidates == null && canBeClassOrPackage ) { |
| ResolverProcessor classProcessor = new ClassResolverProcessor(name, this, kinds); |
| resolveRunner.resolveImpl(classProcessor); |
| classCandidates = classProcessor.getCandidates(); |
| } |
| |
| if (classCandidates != null && classCandidates.length > 0) return classCandidates; |
| if (!accessorResults.isEmpty()) return new GroovyResolveResult[]{accessorResults.get(0)}; |
| return GroovyResolveResult.EMPTY_ARRAY; |
| } |
| |
| private static boolean containsPackage(@NotNull GroovyResolveResult[] candidates) { |
| for (GroovyResolveResult candidate : candidates) { |
| if (candidate.getElement() instanceof PsiPackage) return true; |
| } |
| return false; |
| } |
| |
| @NotNull |
| public GroovyResolveResult[] getCallVariants(@Nullable GrExpression upToArgument) { |
| return resolveMethodOrProperty(true, upToArgument, true); |
| } |
| |
| private void processMethods(@NotNull MethodResolverProcessor methodResolver) { |
| new GrReferenceResolveRunner(this).resolveImpl(methodResolver); |
| if (methodResolver.hasApplicableCandidates()) { |
| return; |
| } |
| |
| // Search in ClosureMissingMethodContributor |
| if (!isQualified() && getContext() instanceof GrMethodCall) { |
| ClosureMissingMethodContributor.processMethodsFromClosures(this, methodResolver); |
| } |
| } |
| |
| /** |
| * priority: inside class C: local variable, c.method, c.property, c.getter |
| * in other places: local variable, c.method, c.getter, c.property |
| */ |
| @NotNull |
| private GroovyResolveResult[] resolveMethodOrProperty(boolean allVariants, @Nullable GrExpression upToArgument, boolean genericsMatter) { |
| final String name = getReferenceName(); |
| if (name == null) return GroovyResolveResult.EMPTY_ARRAY; |
| |
| GrReferenceResolveRunner resolveRunner = new GrReferenceResolveRunner(this); |
| |
| PropertyResolverProcessor propertyResolver = new PropertyResolverProcessor(name, this); |
| resolveRunner.resolveImpl(propertyResolver); |
| final GroovyResolveResult[] propertyCandidates = propertyResolver.getCandidates(); |
| |
| if (!allVariants) { //search for local variables |
| for (GroovyResolveResult candidate : propertyCandidates) { |
| final PsiElement element = candidate.getElement(); |
| if (element instanceof GrVariable && !(element instanceof GrField || element instanceof GrBindingVariable)) { |
| return propertyCandidates; |
| } |
| } |
| } |
| |
| final Pair<Boolean, GroovyResolveResult[]> shapeResults = resolveByShape(allVariants, upToArgument); |
| if (!genericsMatter && !allVariants && shapeResults.first) { |
| assertAllAreValid(shapeResults.second); |
| return shapeResults.second; |
| } |
| |
| 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(); |
| } |
| } |
| |
| //search for fields inside its class |
| if (!allVariants) { |
| for (GroovyResolveResult candidate : propertyCandidates) { |
| final PsiElement element = candidate.getElement(); |
| if (element instanceof GrField) { |
| final PsiClass containingClass = ((PsiField)element).getContainingClass(); |
| if (containingClass != null && PsiTreeUtil.isContextAncestor(containingClass, this, true)) return propertyCandidates; |
| } |
| } |
| } |
| |
| List<GroovyResolveResult> allCandidates = new ArrayList<GroovyResolveResult>(); |
| ContainerUtil.addAll(allCandidates, propertyCandidates); |
| ContainerUtil.addAll(allCandidates, genericsMatter ? methodResolver.getCandidates() : shapeResults.second); |
| |
| filterOutBindings(allCandidates); |
| |
| //search for getters |
| for (String getterName : GroovyPropertyUtils.suggestGettersName(name)) { |
| AccessorResolverProcessor getterResolver = |
| new AccessorResolverProcessor(getterName, name, this, true, genericsMatter, PsiImplUtil.getQualifierType(this), getTypeArguments()); |
| resolveRunner.resolveImpl(getterResolver); |
| final GroovyResolveResult[] candidates = getterResolver.getCandidates(); //can be only one candidate |
| if (!allVariants && candidates.length == 1) { |
| return candidates; |
| } |
| ContainerUtil.addAll(allCandidates, candidates); |
| } |
| |
| if (!allCandidates.isEmpty()) { |
| return allCandidates.toArray(new GroovyResolveResult[allCandidates.size()]); |
| } |
| return GroovyResolveResult.EMPTY_ARRAY; |
| } |
| |
| private static void filterOutBindings(@NotNull List<GroovyResolveResult> candidates) { |
| boolean hasNonBinding = false; |
| for (GroovyResolveResult candidate : candidates) { |
| if (!(candidate.getElement() instanceof GrBindingVariable)) { |
| hasNonBinding = true; |
| } |
| } |
| |
| if (hasNonBinding) { |
| for (Iterator<GroovyResolveResult> iterator = candidates.iterator(); iterator.hasNext(); ) { |
| GroovyResolveResult candidate = iterator.next(); |
| if (candidate.getElement() instanceof GrBindingVariable) { |
| iterator.remove(); |
| } |
| } |
| } |
| } |
| |
| @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); |
| return Pair.create(shapeProcessor.hasApplicableCandidates(), candidates); |
| } |
| |
| private static void assertAllAreValid(@NotNull GroovyResolveResult[] candidates) { |
| for (GroovyResolveResult candidate : candidates) { |
| final PsiElement element = candidate.getElement(); |
| LOG.assertTrue(element == null || element.isValid()); |
| } |
| } |
| |
| @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); |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement getReferenceNameElement() { |
| final ASTNode lastChild = getNode().getLastChildNode(); |
| if (lastChild == null) return null; |
| if (TokenSets.REFERENCE_NAMES.contains(lastChild.getElementType())) { |
| return lastChild.getPsi(); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| @NotNull |
| public PsiReference getReference() { |
| return this; |
| } |
| |
| @Override |
| @Nullable |
| public GrExpression getQualifier() { |
| return getQualifierExpression(); |
| } |
| |
| @Override |
| @Nullable |
| public String getReferenceName() { |
| PsiElement nameElement = getReferenceNameElement(); |
| if (nameElement != null) { |
| IElementType nodeType = nameElement.getNode().getElementType(); |
| if (TokenSets.STRING_LITERAL_SET.contains(nodeType)) { |
| final Object value = GrLiteralImpl.getLiteralValue(nameElement); |
| if (value instanceof String) { |
| return (String)value; |
| } |
| } |
| |
| return nameElement.getText(); |
| } |
| return null; |
| } |
| |
| @Override |
| public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { |
| final GroovyResolveResult result = advancedResolve(); |
| if (result.isInvokedOnProperty()) { |
| final String name = GroovyPropertyUtils.getPropertyNameByAccessorName(newElementName); |
| if (name != null) { |
| newElementName = name; |
| } |
| } |
| if (PsiUtil.isThisOrSuperRef(this)) return this; |
| |
| return handleElementRenameSimple(newElementName); |
| } |
| |
| @Override |
| protected GrReferenceExpression bindWithQualifiedRef(@NotNull String qName) { |
| GrReferenceExpression qualifiedRef = GroovyPsiElementFactory.getInstance(getProject()).createReferenceExpressionFromText(qName); |
| final GrTypeArgumentList list = getTypeArgumentList(); |
| if (list != null) { |
| qualifiedRef.getNode().addChild(list.copy().getNode()); |
| } |
| getNode().getTreeParent().replaceChild(getNode(), qualifiedRef.getNode()); |
| return qualifiedRef; |
| } |
| |
| @Override |
| public boolean isFullyQualified() { |
| if (getKind() == Kind.TYPE_OR_PROPERTY && resolve() instanceof PsiPackage) return true; |
| |
| final GrExpression qualifier = getQualifier(); |
| if (!(qualifier instanceof GrReferenceExpressionImpl)) return false; |
| return ((GrReferenceExpressionImpl)qualifier).isFullyQualified(); |
| } |
| |
| @Override |
| public PsiElement handleElementRenameSimple(String newElementName) throws IncorrectOperationException { |
| if (!PsiUtil.isValidReferenceName(newElementName)) { |
| final PsiElement old = getReferenceNameElement(); |
| if (old == null) throw new IncorrectOperationException("ref has no name element"); |
| |
| PsiElement element = GroovyPsiElementFactory.getInstance(getProject()).createStringLiteralForReference(newElementName); |
| old.replace(element); |
| return this; |
| } |
| |
| return super.handleElementRenameSimple(newElementName); |
| } |
| |
| public String toString() { |
| return "Reference expression"; |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement resolve() { |
| final GroovyResolveResult[] results = resolveByShape(); |
| return results.length == 1 ? results[0].getElement() : null; |
| } |
| |
| @Override |
| public GroovyResolveResult[] resolveByShape() { |
| final InferenceContext context = TypeInferenceHelper.getCurrentContext(); |
| return context.getCachedValue(this, new Computable<GroovyResolveResult[]>() { |
| @Override |
| public GroovyResolveResult[] compute() { |
| Pair<GrReferenceExpressionImpl, InferenceContext> key = Pair.create(GrReferenceExpressionImpl.this, context); |
| GroovyResolveResult[] value = RecursionManager.doPreventingRecursion(key, true, new Computable<GroovyResolveResult[]>() { |
| @Override |
| public GroovyResolveResult[] compute() { |
| return doPolyResolve(false, false); |
| } |
| }); |
| return value == null ? GroovyResolveResult.EMPTY_ARRAY : value; |
| } |
| }); |
| } |
| |
| private static final ResolveCache.PolyVariantResolver<GrReferenceExpressionImpl> POLY_RESOLVER = new ResolveCache.PolyVariantResolver<GrReferenceExpressionImpl>() { |
| @Override |
| @NotNull |
| public GroovyResolveResult[] resolve(@NotNull GrReferenceExpressionImpl refExpr, boolean incompleteCode) { |
| return refExpr.doPolyResolve(incompleteCode, true); |
| } |
| }; |
| private static final OurTypesCalculator TYPES_CALCULATOR = new OurTypesCalculator(); |
| |
| @Override |
| @Nullable |
| public PsiType getNominalType() { |
| final GroovyResolveResult resolveResult = advancedResolve(); |
| PsiElement resolved = resolveResult.getElement(); |
| |
| for (GrReferenceTypeEnhancer enhancer : GrReferenceTypeEnhancer.EP_NAME.getExtensions()) { |
| PsiType type = enhancer.getReferenceType(this, resolved); |
| if (type != null) { |
| return type; |
| } |
| } |
| |
| IElementType dotType = getDotTokenType(); |
| if (dotType == GroovyTokenTypes.mMEMBER_POINTER) { |
| return GrClosureType.create(multiResolve(false), this); |
| } |
| |
| if (isDefinitelyKeyOfMap()) { |
| final PsiType type = getTypeFromMapAccess(this); |
| if (type != null) { |
| return type; |
| } |
| } |
| |
| PsiType result = getNominalTypeInner(resolved); |
| if (result == null) return null; |
| |
| result = TypesUtil.substituteBoxAndNormalizeType(result, resolveResult.getSubstitutor(), resolveResult.getSpreadState(), this); |
| return result; |
| } |
| |
| @Nullable |
| private PsiType getNominalTypeInner(@Nullable PsiElement resolved) { |
| if (resolved == null && !"class".equals(getReferenceName())) { |
| resolved = resolve(); |
| } |
| |
| if (resolved instanceof PsiClass) { |
| final PsiElementFactory factory = JavaPsiFacade.getInstance(getProject()).getElementFactory(); |
| if (PsiUtil.isInstanceThisRef(this)) { |
| final PsiClassType categoryType = GdkMethodUtil.getCategoryType((PsiClass)resolved); |
| if (categoryType != null) { |
| return categoryType; |
| } |
| else { |
| return factory.createType((PsiClass)resolved); |
| } |
| } |
| else if (PsiUtil.isSuperReference(this)) { |
| PsiClass contextClass = PsiUtil.getContextClass(this); |
| if (GrTraitUtil.isTrait(contextClass)) { |
| PsiClassType[] extendsTypes = contextClass.getExtendsListTypes(); |
| PsiClassType[] implementsTypes = contextClass.getImplementsListTypes(); |
| |
| PsiClassType[] superTypes = ArrayUtil.mergeArrays(implementsTypes, extendsTypes, PsiClassType.ARRAY_FACTORY); |
| |
| return PsiIntersectionType.createIntersection(ArrayUtil.reverseArray(superTypes)); |
| } |
| return factory.createType((PsiClass)resolved); |
| } |
| if (getParent() instanceof GrReferenceExpression) { |
| return factory.createType((PsiClass)resolved); |
| } |
| else { |
| return TypesUtil.createJavaLangClassType(factory.createType((PsiClass)resolved), getProject(), getResolveScope()); |
| } |
| } |
| |
| if (resolved instanceof GrVariable) { |
| return ((GrVariable)resolved).getDeclaredType(); |
| } |
| |
| if (resolved instanceof PsiVariable) { |
| return ((PsiVariable)resolved).getType(); |
| } |
| |
| if (resolved instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)resolved; |
| if (PropertyUtil.isSimplePropertySetter(method) && !method.getName().equals(getReferenceName())) { |
| return method.getParameterList().getParameters()[0].getType(); |
| } |
| |
| //'class' property with explicit generic |
| PsiClass containingClass = method.getContainingClass(); |
| if (containingClass != null && |
| CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName()) && |
| "getClass".equals(method.getName())) { |
| return TypesUtil.createJavaLangClassType(PsiImplUtil.getQualifierType(this), getProject(), getResolveScope()); |
| } |
| |
| return PsiUtil.getSmartReturnType(method); |
| } |
| |
| if (resolved == null) { |
| final PsiType fromClassRef = getTypeFromClassRef(this); |
| if (fromClassRef != null) { |
| return fromClassRef; |
| } |
| |
| final PsiType fromMapAccess = getTypeFromMapAccess(this); |
| if (fromMapAccess != null) { |
| return fromMapAccess; |
| } |
| |
| final PsiType fromSpreadOperator = getTypeFromSpreadOperator(this); |
| if (fromSpreadOperator != null) { |
| return fromSpreadOperator; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static PsiType getTypeFromMapAccess(@NotNull GrReferenceExpressionImpl ref) { |
| //map access |
| GrExpression qualifier = ref.getQualifierExpression(); |
| if (qualifier != null) { |
| PsiType qType = qualifier.getNominalType(); |
| if (qType instanceof PsiClassType) { |
| PsiClassType.ClassResolveResult qResult = ((PsiClassType)qType).resolveGenerics(); |
| PsiClass clazz = qResult.getElement(); |
| if (clazz != null) { |
| PsiClass mapClass = JavaPsiFacade.getInstance(ref.getProject()).findClass(CommonClassNames.JAVA_UTIL_MAP, ref.getResolveScope()); |
| if (mapClass != null && mapClass.getTypeParameters().length == 2) { |
| PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(mapClass, clazz, qResult.getSubstitutor()); |
| if (substitutor != null) { |
| PsiType substituted = substitutor.substitute(mapClass.getTypeParameters()[1]); |
| if (substituted != null) { |
| return PsiImplUtil.normalizeWildcardTypeByPosition(substituted, ref); |
| } |
| } |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PsiType getTypeFromSpreadOperator(@NotNull GrReferenceExpressionImpl ref) { |
| if (ref.getDotTokenType() == GroovyTokenTypes.mSPREAD_DOT) { |
| return TypesUtil.createType(CommonClassNames.JAVA_UTIL_LIST, ref); |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static PsiType getTypeFromClassRef(@NotNull GrReferenceExpressionImpl ref) { |
| if ("class".equals(ref.getReferenceName())) { |
| return TypesUtil.createJavaLangClassType(PsiImplUtil.getQualifierType(ref), ref.getProject(), ref.getResolveScope()); |
| } |
| return null; |
| } |
| |
| private static final class OurTypesCalculator implements Function<GrReferenceExpressionImpl, PsiType> { |
| @Override |
| @Nullable |
| public PsiType fun(GrReferenceExpressionImpl refExpr) { |
| if (ResolveUtil.isClassReference(refExpr)) { |
| GrExpression qualifier = refExpr.getQualifier(); |
| LOG.assertTrue(qualifier != null); |
| return TypesUtil.createJavaLangClassType(qualifier.getType(), refExpr.getProject(), refExpr.getResolveScope()); |
| } |
| |
| final PsiElement resolved = refExpr.resolve(); |
| final PsiType nominal = refExpr.getNominalType(); |
| |
| Boolean reassigned = GrReassignedLocalVarsChecker.isReassignedVar(refExpr); |
| if (reassigned != null && reassigned.booleanValue()) { |
| return GrReassignedLocalVarsChecker.getReassignedVarType(refExpr, true); |
| } |
| |
| final PsiType inferred = getInferredTypes(refExpr, resolved); |
| if (inferred == null) { |
| if (nominal == null) { |
| //inside nested closure we could still try to infer from variable initializer. Not sound, but makes sense |
| if (resolved instanceof GrVariable) { |
| LOG.assertTrue(resolved.isValid()); |
| return ((GrVariable)resolved).getTypeGroovy(); |
| } |
| } |
| |
| return nominal; |
| } |
| |
| if (nominal == null) return inferred; |
| if (!TypeConversionUtil.isAssignable(TypeConversionUtil.erasure(nominal), inferred, false)) { |
| if (resolved instanceof GrVariable && ((GrVariable)resolved).getTypeElementGroovy() != null) { |
| return nominal; |
| } |
| } |
| return inferred; |
| } |
| } |
| |
| @Nullable |
| private static PsiType getInferredTypes(@NotNull GrReferenceExpressionImpl refExpr, @Nullable PsiElement resolved) { |
| final GrExpression qualifier = refExpr.getQualifier(); |
| if (qualifier == null && !(resolved instanceof PsiClass || resolved instanceof PsiPackage)) { |
| return TypeInferenceHelper.getCurrentContext().getVariableType(refExpr); |
| } |
| else if (qualifier != null) { |
| //map access |
| PsiType qType = qualifier.getType(); |
| if (qType instanceof PsiClassType && !(qType instanceof GrMapType)) { |
| final PsiType mapValueType = getTypeFromMapAccess(refExpr); |
| if (mapValueType != null) { |
| return mapValueType; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public PsiType getType() { |
| return TypeInferenceHelper.getCurrentContext().getExpressionType(this, TYPES_CALCULATOR); |
| } |
| |
| @Override |
| public GrExpression replaceWithExpression(@NotNull GrExpression newExpr, boolean removeUnnecessaryParentheses) { |
| return PsiImplUtil.replaceExpression(this, newExpr, removeUnnecessaryParentheses); |
| } |
| |
| @NotNull |
| private GroovyResolveResult[] doPolyResolve(boolean incompleteCode, boolean genericsMatter) { |
| String name = getReferenceName(); |
| if (name == null) return GroovyResolveResult.EMPTY_ARRAY; |
| |
| if (incompleteCode) { |
| ResolverProcessor processor = CompletionProcessor.createRefSameNameProcessor(this, name); |
| new GrReferenceResolveRunner(this).resolveImpl(processor); |
| GroovyResolveResult[] propertyCandidates = processor.getCandidates(); |
| if (propertyCandidates.length > 0 && !PsiUtil.isSingleBindingVariant(propertyCandidates)) return propertyCandidates; |
| } |
| |
| try { |
| ResolveProfiler.start(); |
| switch (getKind()) { |
| case METHOD_OR_PROPERTY: |
| return resolveMethodOrProperty(false, null, genericsMatter); |
| case TYPE_OR_PROPERTY: |
| return resolveTypeOrProperty(); |
| case METHOD_OR_PROPERTY_OR_TYPE: |
| GroovyResolveResult[] results = resolveMethodOrProperty(false, null, genericsMatter); |
| if (results.length == 0) results = resolveTypeOrProperty(); |
| return results; |
| default: |
| return GroovyResolveResult.EMPTY_ARRAY; |
| } |
| } |
| finally { |
| final long time = ResolveProfiler.finish(); |
| ResolveProfiler.write("ref " + getText() + " " + hashCode() + " : " + time); |
| } |
| } |
| |
| enum Kind { |
| TYPE_OR_PROPERTY, |
| METHOD_OR_PROPERTY, |
| METHOD_OR_PROPERTY_OR_TYPE |
| } |
| |
| @NotNull |
| private Kind getKind() { |
| if (getDotTokenType() == GroovyTokenTypes.mMEMBER_POINTER) return Kind.METHOD_OR_PROPERTY; |
| |
| PsiElement parent = getParent(); |
| if (parent instanceof GrMethodCallExpression || parent instanceof GrApplicationStatement) { |
| return Kind.METHOD_OR_PROPERTY_OR_TYPE; |
| } |
| |
| return Kind.TYPE_OR_PROPERTY; |
| } |
| |
| @Override |
| @NotNull |
| public String getCanonicalText() { |
| return getRangeInElement().substring(getElement().getText()); |
| } |
| |
| @Override |
| public boolean hasAt() { |
| return findChildByType(GroovyTokenTypes.mAT) != null; |
| } |
| |
| @Override |
| public boolean hasMemberPointer() { |
| return findChildByType(GroovyTokenTypes.mMEMBER_POINTER) != null; |
| } |
| |
| @Override |
| public boolean isReferenceTo(PsiElement element) { |
| PsiElement baseTarget = resolve(); |
| if (getManager().areElementsEquivalent(element, baseTarget)) { |
| return true; |
| } |
| |
| PsiElement target = GroovyTargetElementEvaluator.correctSearchTargets(baseTarget); |
| if (target != baseTarget && getManager().areElementsEquivalent(element, target)) { |
| return true; |
| } |
| |
| if (element instanceof PsiMethod && target instanceof PsiMethod) { |
| PsiMethod[] superMethods = ((PsiMethod)target).findSuperMethods(false); |
| //noinspection SuspiciousMethodCalls |
| if (Arrays.asList(superMethods).contains(element)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| @NotNull |
| public Object[] getVariants() { |
| return ArrayUtil.EMPTY_OBJECT_ARRAY; |
| } |
| |
| |
| @Override |
| public boolean isSoft() { |
| return false; |
| } |
| |
| @Override |
| @Nullable |
| public GrExpression getQualifierExpression() { |
| return findExpressionChild(this); |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement getDotToken() { |
| return findChildByType(TokenSets.DOTS); |
| } |
| |
| @Override |
| public void replaceDotToken(PsiElement newDot) { |
| if (newDot == null) return; |
| if (!TokenSets.DOTS.contains(newDot.getNode().getElementType())) return; |
| final PsiElement oldDot = getDotToken(); |
| if (oldDot == null) return; |
| |
| getNode().replaceChild(oldDot.getNode(), newDot.getNode()); |
| } |
| |
| @Override |
| @Nullable |
| public IElementType getDotTokenType() { |
| PsiElement dot = getDotToken(); |
| return dot == null ? null : dot.getNode().getElementType(); |
| } |
| |
| @Override |
| public GroovyResolveResult advancedResolve() { |
| ResolveResult[] results = TypeInferenceHelper.getCurrentContext().multiResolve(this, false, POLY_RESOLVER); |
| return results.length == 1 ? (GroovyResolveResult)results[0] : GroovyResolveResult.EMPTY_RESULT; |
| } |
| |
| @Override |
| @NotNull |
| public GroovyResolveResult[] multiResolve(boolean incomplete) { //incomplete means we do not take arguments into consideration |
| final ResolveResult[] results = TypeInferenceHelper.getCurrentContext().multiResolve(this, incomplete, POLY_RESOLVER); |
| return results.length == 0 ? GroovyResolveResult.EMPTY_ARRAY : (GroovyResolveResult[])results; |
| } |
| |
| @Override |
| @NotNull |
| public GroovyResolveResult[] getSameNameVariants() { |
| return doPolyResolve(true, true); |
| } |
| |
| @Override |
| public GrReferenceExpression bindToElementViaStaticImport(@NotNull PsiMember member) { |
| if (getQualifier() != null) { |
| throw new IncorrectOperationException("Reference has qualifier"); |
| } |
| |
| if (StringUtil.isEmpty(getReferenceName())) { |
| throw new IncorrectOperationException("Reference has empty name"); |
| } |
| |
| PsiClass containingClass = member.getContainingClass(); |
| if (containingClass == null) { |
| throw new IncorrectOperationException("Member has no containing class"); |
| } |
| final PsiFile file = getContainingFile(); |
| if (file instanceof GroovyFile) { |
| GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(getProject()); |
| String text = "import static " + containingClass.getQualifiedName() + "." + member.getName(); |
| final GrImportStatement statement = factory.createImportStatementFromText(text); |
| ((GroovyFile)file).addImport(statement); |
| } |
| return this; |
| } |
| } |