/* * Copyright 2013-2017 consulo.io * * 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 consulo.csharp.lang.psi.impl.source.resolve.util; import java.util.ArrayList; import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.KeyWithDefaultValue; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiInvalidElementAccessException; import com.intellij.psi.ResolveResult; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.Processor; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import consulo.annotations.RequiredReadAction; import consulo.csharp.lang.psi.CSharpCodeFragment; import consulo.csharp.lang.psi.CSharpMethodDeclaration; import consulo.csharp.lang.psi.CSharpTypeDeclaration; import consulo.csharp.lang.psi.CSharpTypeDefStatement; import consulo.csharp.lang.psi.CSharpUsingListChild; import consulo.csharp.lang.psi.CSharpUsingListOwner; import consulo.csharp.lang.psi.impl.DotNetTypes2; import consulo.csharp.lang.psi.impl.source.CSharpForeachStatementImpl; import consulo.csharp.lang.psi.impl.source.resolve.CSharpResolveResult; import consulo.csharp.lang.psi.impl.source.resolve.ExecuteTarget; import consulo.csharp.lang.psi.impl.source.resolve.ExecuteTargetUtil; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpTypeRefByQName; import consulo.csharp.lang.psi.resolve.CSharpElementGroup; import consulo.csharp.lang.psi.resolve.CSharpNamedResolveSelector; import consulo.csharp.lang.psi.resolve.CSharpResolveSelector; import consulo.csharp.lang.psi.resolve.MemberByNameSelector; import consulo.dotnet.DotNetTypes; import consulo.dotnet.lang.psi.impl.BaseDotNetNamespaceAsElement; import consulo.dotnet.psi.DotNetExpression; import consulo.dotnet.psi.DotNetGenericParameter; import consulo.dotnet.psi.DotNetGenericParameterListOwner; import consulo.dotnet.psi.DotNetInheritUtil; import consulo.dotnet.psi.DotNetMethodDeclaration; import consulo.dotnet.psi.DotNetNamespaceDeclaration; import consulo.dotnet.psi.DotNetPropertyDeclaration; import consulo.dotnet.psi.DotNetQualifiedElement; import consulo.dotnet.resolve.DotNetArrayTypeRef; import consulo.dotnet.resolve.DotNetGenericExtractor; import consulo.dotnet.resolve.DotNetNamespaceAsElement; import consulo.dotnet.resolve.DotNetPsiSearcher; import consulo.dotnet.resolve.DotNetTypeRef; import consulo.dotnet.resolve.DotNetTypeResolveResult; import consulo.dotnet.util.ArrayUtil2; /** * @author VISTALL * @since 17.12.13. */ public class CSharpResolveUtil { private static final Logger LOGGER = Logger.getInstance(CSharpResolveUtil.class); public static final KeyWithDefaultValue<DotNetGenericExtractor> EXTRACTOR = new KeyWithDefaultValue<DotNetGenericExtractor>("dot-net-extractor") { @Override public DotNetGenericExtractor getDefaultValue() { return DotNetGenericExtractor.EMPTY; } }; public static final Key<DotNetQualifiedElement> ACCESSOR_VALUE_VARIABLE_OWNER = Key.create("accessor.value.variable"); public static final Key<CSharpMethodDeclaration> EXTENSION_METHOD_WRAPPER = Key.create("extension.method.wrapper"); public static final Key<CSharpMethodDeclaration> DELEGATE_METHOD_TYPE = Key.create("delegate.method.type"); public static final Key<CSharpResolveSelector> SELECTOR = Key.create("resolve.selector"); public static final Key<Boolean> WALK_DEEP = Key.create("walk.deep"); public static boolean treeWalkUp(@NotNull PsiScopeProcessor processor, @NotNull PsiElement entrance, @NotNull PsiElement sender, @Nullable PsiElement maxScope) { return treeWalkUp(processor, entrance, sender, maxScope, ResolveState.initial()); } public static boolean treeWalkUp(@NotNull final PsiScopeProcessor processor, @NotNull final PsiElement entrance, @NotNull final PsiElement sender, @Nullable PsiElement maxScope, @NotNull final ResolveState state) { if(!entrance.isValid()) { CSharpResolveUtil.LOGGER.error(new PsiInvalidElementAccessException(entrance)); } PsiElement prevParent = entrance; PsiElement scope = entrance; if(maxScope == null) { maxScope = sender.getContainingFile(); } while(scope != null) { ProgressIndicatorProvider.checkCanceled(); if(entrance != sender && scope instanceof PsiFile) { break; } if(!scope.processDeclarations(processor, state, prevParent, entrance)) { return false; // resolved } if(entrance != sender) { break; } if(scope == maxScope) { break; } prevParent = scope; scope = prevParent.getContext(); if(scope != null && scope != prevParent.getParent() && !scope.isValid()) { break; } } return true; } @RequiredReadAction public static boolean walkUsing(@NotNull final PsiScopeProcessor processor, @NotNull final PsiElement entrance, @Nullable PsiElement maxScope, @NotNull final ResolveState state) { if(!entrance.isValid()) { CSharpResolveUtil.LOGGER.error(new PsiInvalidElementAccessException(entrance)); } DotNetNamespaceAsElement root = DotNetPsiSearcher.getInstance(entrance.getProject()).findNamespace("", entrance.getResolveScope()); // skip null - indexing if(root == null) { return true; } if(!processor.execute(root, state)) { return false; } // we cant go to use list if(PsiTreeUtil.getParentOfType(entrance, CSharpUsingListChild.class) != null) { return true; } CSharpResolveSelector selector = state.get(SELECTOR); if(selector instanceof MemberByNameSelector) { ((MemberByNameSelector) selector).putUserData(BaseDotNetNamespaceAsElement.FILTER, DotNetNamespaceAsElement.ChildrenFilter.ONLY_ELEMENTS); } PsiElement prevParent = entrance; PsiElement scope = entrance; maxScope = validateMaxScope(entrance, maxScope); while(scope != null) { ProgressIndicatorProvider.checkCanceled(); if(scope instanceof CSharpUsingListOwner) { CSharpUsingListChild[] usingStatements = ((CSharpUsingListOwner) scope).getUsingStatements(); for(CSharpUsingListChild usingStatement : usingStatements) { if(!processor.execute(usingStatement, state)) { return false; } } } if(scope == maxScope) { break; } prevParent = scope; scope = prevParent.getContext(); if(scope != null && scope != prevParent.getParent() && !scope.isValid()) { break; } } return true; } private static PsiElement validateMaxScope(@NotNull PsiElement entrance, @Nullable PsiElement maxScope) { if(maxScope == null) { maxScope = entrance.getContainingFile(); if(maxScope instanceof CSharpCodeFragment) { PsiElement scopeElement = ((CSharpCodeFragment) maxScope).getScopeElement(); if(scopeElement != null) { maxScope = scopeElement.getContainingFile(); } } } return maxScope; } public static boolean walkGenericParameterList(@NotNull final PsiScopeProcessor processor, @NotNull Processor<ResolveResult> consumer, @NotNull final PsiElement entrance, @Nullable PsiElement maxScope, @NotNull final ResolveState state) { if(!ExecuteTargetUtil.canProcess(processor, ExecuteTarget.GENERIC_PARAMETER)) { return true; } if(!entrance.isValid()) { CSharpResolveUtil.LOGGER.error(new PsiInvalidElementAccessException(entrance)); } PsiElement prevParent = entrance; PsiElement scope = entrance; maxScope = validateMaxScope(entrance, maxScope); CSharpResolveSelector selector = state.get(SELECTOR); if(selector != null) { if(!(selector instanceof CSharpNamedResolveSelector)) { return true; } } while(scope != null) { ProgressIndicatorProvider.checkCanceled(); if(scope instanceof DotNetGenericParameterListOwner) { DotNetGenericParameter[] genericParameters = ((DotNetGenericParameterListOwner) scope).getGenericParameters(); if(genericParameters.length > 0) { if(selector != null) { for(DotNetGenericParameter genericParameter : genericParameters) { String name = genericParameter.getName(); if(name == null) { continue; } if(((CSharpNamedResolveSelector) selector).isNameEqual(name)) { consumer.process(new CSharpResolveResult(genericParameter)); return false; } } } else { for(DotNetGenericParameter genericParameter : genericParameters) { consumer.process(new CSharpResolveResult(genericParameter)); } } } } if(scope == maxScope) { break; } prevParent = scope; scope = prevParent.getContext(); if(scope != null && scope != prevParent.getParent() && !scope.isValid()) { break; } } return true; } @RequiredReadAction public static boolean walkChildren(@NotNull final PsiScopeProcessor processor, @NotNull final PsiElement entrance, boolean walkParent, boolean walkDeep, @NotNull ResolveState state) { if(walkDeep) { state = state.put(WALK_DEEP, Boolean.TRUE); } ProgressIndicatorProvider.checkCanceled(); GlobalSearchScope resolveScope = entrance.getResolveScope(); if(entrance instanceof CSharpTypeDeclaration) { if(!processor.execute(entrance, state)) { return false; } if(walkParent) { PsiElement parent = entrance.getContext(); if(parent == null) { return true; } if(!walkChildren(processor, parent, walkParent, walkDeep, state)) { return false; } } } else if(entrance instanceof CSharpTypeDefStatement) { DotNetTypeRef dotNetTypeRef = ((CSharpTypeDefStatement) entrance).toTypeRef(); DotNetTypeResolveResult typeResolveResult = dotNetTypeRef.resolve(); PsiElement element = typeResolveResult.getElement(); if(element == null) { return true; } CSharpResolveSelector selector = state.get(SELECTOR); ResolveState newState = ResolveState.initial().put(SELECTOR, selector).put(EXTRACTOR, typeResolveResult.getGenericExtractor()); return walkChildren(processor, element, walkParent, walkDeep, newState); } else if(entrance instanceof DotNetGenericParameter) { if(!processor.execute(entrance, state)) { return false; } } else if(entrance instanceof DotNetNamespaceAsElement) { state = state.put(BaseDotNetNamespaceAsElement.RESOLVE_SCOPE, resolveScope); state = state.put(BaseDotNetNamespaceAsElement.FILTER, DotNetNamespaceAsElement.ChildrenFilter.NONE); if(!processor.execute(entrance, state)) { return false; } String parentQName = ((DotNetNamespaceAsElement) entrance).getPresentableParentQName(); // dont go to root namespace if(StringUtil.isEmpty(parentQName)) { return true; } if(walkParent) { DotNetNamespaceAsElement parentNamespace = DotNetPsiSearcher.getInstance(entrance.getProject()).findNamespace(parentQName, resolveScope); if(parentNamespace != null && !walkChildren(processor, parentNamespace, walkParent, walkDeep, state)) { return false; } } } else if(entrance instanceof DotNetNamespaceDeclaration) { String presentableQName = ((DotNetNamespaceDeclaration) entrance).getPresentableQName(); if(presentableQName == null) { return true; } state = state.put(BaseDotNetNamespaceAsElement.RESOLVE_SCOPE, resolveScope); state = state.put(BaseDotNetNamespaceAsElement.FILTER, DotNetNamespaceAsElement.ChildrenFilter.NONE); DotNetNamespaceAsElement namespace = DotNetPsiSearcher.getInstance(entrance.getProject()).findNamespace(presentableQName, resolveScope); if(namespace != null && !walkChildren(processor, namespace, walkParent, walkDeep, state)) { return false; } } return true; } public static boolean walkForLabel(@NotNull final PsiScopeProcessor processor, @NotNull final PsiElement entrance, @NotNull ResolveState state) { PsiElement[] children = entrance.getChildren(); for(PsiElement child : children) { if(ExecuteTargetUtil.isMyElement(processor, child)) { if(!processor.execute(child, state)) { return false; } } if(!walkForLabel(processor, child, state)) { return false; } } return true; } @NotNull @RequiredReadAction public static DotNetTypeRef resolveIterableType(@NotNull CSharpForeachStatementImpl foreachStatement) { DotNetExpression iterableExpression = foreachStatement.getIterableExpression(); if(iterableExpression == null) { return DotNetTypeRef.ERROR_TYPE; } return resolveIterableType(iterableExpression, iterableExpression.toTypeRef(false)); } @NotNull @RequiredReadAction public static DotNetTypeRef resolveIterableType(@NotNull PsiElement scope, @NotNull DotNetTypeRef typeRef) { if(typeRef instanceof DotNetArrayTypeRef) { return ((DotNetArrayTypeRef) typeRef).getInnerTypeRef(); } DotNetMethodDeclaration method = CSharpSearchUtil.findMethodByName("GetEnumerator", DotNetTypes2.System.Collections.Generic.IEnumerable$1, typeRef, 0); if(method != null) { DotNetPropertyDeclaration current = CSharpSearchUtil.findPropertyByName("Current", DotNetTypes2.System.Collections.Generic.IEnumerator$1, method.getReturnTypeRef()); if(current == null) { return DotNetTypeRef.ERROR_TYPE; } return current.toTypeRef(false); } if(DotNetInheritUtil.isParentOrSelf(DotNetTypes2.System.Collections.IEnumerable, typeRef, scope, true)) { return new CSharpTypeRefByQName(scope, DotNetTypes.System.Object); } else { return DotNetTypeRef.ERROR_TYPE; } } @NotNull @SuppressWarnings("unchecked") public static <T extends PsiElement> List<T> mergeGroupsToIterable(@NotNull PsiElement[] elements) { List<T> list = new ArrayList<T>(); for(PsiElement element : elements) { if(element instanceof CSharpElementGroup) { list.addAll(((CSharpElementGroup) element).getElements()); } else { list.add((T) element); } } return list; } @NotNull @SuppressWarnings("unchecked") public static <T extends PsiElement> List<T> mergeGroupsToIterable(@NotNull Iterable<PsiElement> elements) { List<T> list = new ArrayList<T>(); for(PsiElement element : elements) { if(element instanceof CSharpElementGroup) { list.addAll(((CSharpElementGroup) element).getElements()); } else { list.add((T) element); } } return list; } @Nullable public static PsiElement findFirstValidElement(ResolveResult[] resolveResults) { ResolveResult firstValidResult = findFirstValidResult(resolveResults); return firstValidResult == null ? null : firstValidResult.getElement(); } @Nullable public static ResolveResult findFirstValidResult(ResolveResult[] resolveResults) { if(resolveResults.length == 0) { return null; } for(ResolveResult resolveResult : resolveResults) { if(resolveResult.isValidResult() && isAssignable(resolveResult)) { return resolveResult; } } return null; } @Nullable public static ResolveResult findValidOrFirstMaybeResult(ResolveResult[] resolveResults) { ResolveResult firstValidResult = findFirstValidResult(resolveResults); if(firstValidResult != null) { return firstValidResult; } return ArrayUtil2.safeGet(resolveResults, 0); } @NotNull public static ResolveResult[] filterValidResults(@NotNull ResolveResult[] resolveResults) { List<ResolveResult> filter = new SmartList<ResolveResult>(); for(ResolveResult resolveResult : resolveResults) { if(resolveResult.isValidResult() && isAssignable(resolveResult)) { filter.add(resolveResult); } } return ContainerUtil.toArray(filter, ResolveResult.EMPTY_ARRAY); } public static boolean isAssignable(ResolveResult resolveResult) { if(resolveResult instanceof CSharpResolveResult) { return ((CSharpResolveResult) resolveResult).isAssignable(); } return true; } }