/* * 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; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredReadAction; import consulo.csharp.lang.psi.CSharpCallArgument; import consulo.csharp.lang.psi.CSharpCallArgumentList; import consulo.csharp.lang.psi.CSharpCallArgumentListOwner; import consulo.csharp.lang.psi.CSharpElementVisitor; import consulo.csharp.lang.psi.CSharpQualifiedNonReference; import consulo.csharp.lang.psi.CSharpSimpleLikeMethodAsElement; import consulo.csharp.lang.psi.CSharpTokenSets; import consulo.csharp.lang.psi.CSharpTokens; import consulo.csharp.lang.psi.impl.CSharpNullableTypeUtil; import consulo.csharp.lang.psi.impl.CSharpTypeUtil; import consulo.csharp.lang.psi.impl.light.CSharpLightCallArgument; import consulo.csharp.lang.psi.impl.light.builder.CSharpLightMethodDeclarationBuilder; import consulo.csharp.lang.psi.impl.light.builder.CSharpLightParameterBuilder; import consulo.csharp.lang.psi.impl.source.resolve.AsPsiElementProcessor; import consulo.csharp.lang.psi.impl.source.resolve.CSharpResolveOptions; 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.MemberResolveScopeProcessor; import consulo.csharp.lang.psi.impl.source.resolve.MethodResolveResult; import consulo.csharp.lang.psi.impl.source.resolve.StubElementResolveResult; import consulo.csharp.lang.psi.impl.source.resolve.WeightUtil; import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.MethodCalcResult; import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.MethodResolver; import consulo.csharp.lang.psi.impl.source.resolve.operatorResolving.ImplicitCastInfo; import consulo.csharp.lang.psi.impl.source.resolve.operatorResolving.ImplicitOperatorArgumentAsCallArgumentWrapper; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpOperatorNameHelper; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpPointerTypeRef; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpTypeRefByQName; import consulo.csharp.lang.psi.impl.source.resolve.util.CSharpResolveUtil; import consulo.csharp.lang.psi.resolve.OperatorByTokenSelector; import consulo.dotnet.DotNetTypes; import consulo.dotnet.psi.DotNetExpression; import consulo.dotnet.psi.DotNetLikeMethodDeclaration; import consulo.dotnet.resolve.DotNetPointerTypeRef; import consulo.dotnet.resolve.DotNetTypeRef; import consulo.dotnet.resolve.DotNetTypeResolveResult; import consulo.dotnet.util.ArrayUtil2; import com.intellij.lang.ASTNode; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiPolyVariantReference; import com.intellij.psi.PsiReference; import com.intellij.psi.ResolveResult; import com.intellij.psi.ResolveState; import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; import com.intellij.util.IncorrectOperationException; import com.intellij.util.ObjectUtil; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import consulo.csharp.lang.CSharpCastType; /** * @author VISTALL * @since 12.03.14 */ public class CSharpOperatorReferenceImpl extends CSharpElementImpl implements PsiReference, PsiPolyVariantReference, CSharpCallArgumentListOwner, CSharpQualifiedNonReference { private static class OurResolver implements ResolveCache.PolyVariantResolver<CSharpOperatorReferenceImpl> { private static final OurResolver INSTANCE = new OurResolver(); @NotNull @Override @RequiredReadAction public ResolveResult[] resolve(@NotNull CSharpOperatorReferenceImpl reference, boolean incompleteCode) { if(!incompleteCode) { return multiResolveImpl(reference); } else { ResolveResult[] resolveResults = reference.multiResolve(false); List<ResolveResult> filter = new SmartList<ResolveResult>(); for(ResolveResult resolveResult : resolveResults) { if(resolveResult.isValidResult()) { filter.add(resolveResult); } } return ContainerUtil.toArray(filter, ResolveResult.EMPTY_ARRAY); } } @NotNull @RequiredReadAction private ResolveResult[] multiResolveImpl(CSharpOperatorReferenceImpl reference) { Object o = reference.resolveImpl(); List<ResolveResult> elements = new SmartList<ResolveResult>(); if(o instanceof MethodResolveResult[]) { MethodResolveResult[] array = (MethodResolveResult[]) o; ContainerUtil.addAll(elements, array); } else if(o instanceof PsiElement) { elements.add(new CSharpResolveResult((PsiElement) o)); } else if(o instanceof DotNetTypeRef) { elements.add(new StubElementResolveResult(reference, true, (DotNetTypeRef) o)); } return ContainerUtil.toArray(elements, ResolveResult.EMPTY_ARRAY); } } private static final TokenSet ourMergeSet = TokenSet.orSet(CSharpTokenSets.OVERLOADING_OPERATORS, CSharpTokenSets.ASSIGNMENT_OPERATORS, TokenSet.create(CSharpTokens.ANDAND, CSharpTokens.OROR)); private static final String[] ourPointerArgumentTypes = new String[]{ DotNetTypes.System.SByte, DotNetTypes.System.Byte, DotNetTypes.System.Int16, DotNetTypes.System.UInt16, DotNetTypes.System.Int32, DotNetTypes.System.UInt32, DotNetTypes.System.Int64, DotNetTypes.System.UInt64, }; private static final Map<IElementType, IElementType> ourAssignmentOperatorMap = new HashMap<IElementType, IElementType>() { { put(CSharpTokens.MULEQ, CSharpTokens.MUL); put(CSharpTokens.PLUSEQ, CSharpTokens.PLUS); put(CSharpTokens.MINUSEQ, CSharpTokens.MINUS); put(CSharpTokens.DIVEQ, CSharpTokens.DIV); put(CSharpTokens.GTEQ, CSharpTokens.GT); put(CSharpTokens.LTEQ, CSharpTokens.LT); put(CSharpTokens.GTGTEQ, CSharpTokens.GTGT); put(CSharpTokens.LTLTEQ, CSharpTokens.LTLT); put(CSharpTokens.ANDEQ, CSharpTokens.AND); put(CSharpTokens.OREQ, CSharpTokens.OR); put(CSharpTokens.XOREQ, CSharpTokens.XOR); } }; public CSharpOperatorReferenceImpl(@NotNull ASTNode node) { super(node); } @Override public PsiReference getReference() { return this; } @Override public void accept(@NotNull CSharpElementVisitor visitor) { visitor.visitOperatorReference(this); } @Override public PsiElement getElement() { return this; } @Override @RequiredReadAction public TextRange getRangeInElement() { PsiElement operator = getOperatorElement(); return new TextRange(0, operator.getTextLength()); } @NotNull @RequiredReadAction public PsiElement getOperatorElement() { return findNotNullChildByFilter(ourMergeSet); } @NotNull @RequiredReadAction public IElementType getOperatorElementType() { return PsiUtilCore.getElementType(getOperatorElement()); } @Nullable @Override public PsiElement resolve() { ResolveResult[] resolveResults = multiResolve(true); return CSharpResolveUtil.findFirstValidElement(resolveResults); } @RequiredReadAction private Object resolveImpl() { final IElementType temp = getOperatorElementType(); final IElementType elementType = ObjectUtil.notNull(ourAssignmentOperatorMap.get(temp), temp); boolean isAssignmentOperator = ourAssignmentOperatorMap.containsKey(temp); PsiElement parent = getParent(); if(parent instanceof CSharpPrefixExpressionImpl) { if(elementType == CSharpTokens.AND) { DotNetExpression dotNetExpression = ArrayUtil2.safeGet(getParameterExpressions(), 0); if(dotNetExpression == null) { return DotNetTypeRef.ERROR_TYPE; } return new CSharpPointerTypeRef(this, dotNetExpression.toTypeRef(true)); } else if(elementType == CSharpTokens.MUL) { DotNetExpression dotNetExpression = ArrayUtil2.safeGet(getParameterExpressions(), 0); if(dotNetExpression == null) { return DotNetTypeRef.ERROR_TYPE; } DotNetTypeRef expressionTypeRef = dotNetExpression.toTypeRef(true); if(expressionTypeRef instanceof DotNetPointerTypeRef) { return ((DotNetPointerTypeRef) expressionTypeRef).getInnerTypeRef(); } else { return DotNetTypeRef.ERROR_TYPE; } } } if(parent instanceof CSharpPostfixExpressionImpl) { if(elementType == CSharpTokens.PLUSPLUS || elementType == CSharpTokens.MINUSMINUS) { DotNetExpression expression = ArrayUtil2.safeGet(getParameterExpressions(), 0); if(expression == null) { return DotNetTypeRef.ERROR_TYPE; } DotNetTypeRef expressionTypeRef = expression.toTypeRef(true); if(expressionTypeRef instanceof DotNetPointerTypeRef) { return expressionTypeRef; } } } if(parent instanceof CSharpExpressionWithOperatorImpl) { if(elementType == CSharpTokenSets.OROR || elementType == CSharpTokens.ANDAND) { return new CSharpTypeRefByQName(this, DotNetTypes.System.Boolean); } DotNetExpression[] parameterExpressions = getParameterExpressions(); if(elementType == CSharpTokenSets.EQ) { if(parameterExpressions.length > 0) { return parameterExpressions[0].toTypeRef(false); } return new CSharpTypeRefByQName(this, DotNetTypes.System.Void); } final Set<MethodResolveResult> resolveResults = new LinkedHashSet<MethodResolveResult>(); for(final DotNetExpression dotNetExpression : parameterExpressions) { final DotNetTypeRef expressionTypeRef = dotNetExpression.toTypeRef(true); resolveUserDefinedOperators(elementType, expressionTypeRef, expressionTypeRef, resolveResults, null); processImplicitCasts(expressionTypeRef, parent, new Consumer<DotNetTypeRef>() { @Override @RequiredReadAction public void consume(DotNetTypeRef implicitTypeRef) { resolveUserDefinedOperators(elementType, expressionTypeRef, implicitTypeRef, resolveResults, dotNetExpression); } }); } // += -= and others have some hack for nullable types // A? + A is not work - but A? += A work if(isAssignmentOperator && parameterExpressions.length > 0) { DotNetExpression parameterExpression = parameterExpressions[0]; DotNetTypeRef expressionTypeRef = parameterExpression.toTypeRef(true); DotNetTypeRef unboxTypeRef = CSharpNullableTypeUtil.unbox(expressionTypeRef); // we have extracted type if(unboxTypeRef != expressionTypeRef) { resolveUserDefinedOperators(elementType, expressionTypeRef, unboxTypeRef, resolveResults, parameterExpression); } } MethodResolveResult[] results = ContainerUtil.toArray(resolveResults, MethodResolveResult.ARRAY_FACTORY); Arrays.sort(results, WeightUtil.ourComparator); return results; } return null; } @RequiredReadAction private void processImplicitCasts(DotNetTypeRef expressionTypeRef, PsiElement parent, @NotNull Consumer<DotNetTypeRef> consumer) { for(DotNetExpression dotNetExpression : ((CSharpExpressionWithOperatorImpl) parent).getParameterExpressions()) { List<DotNetTypeRef> implicitOrExplicitTypeRefs = CSharpTypeUtil.getImplicitOrExplicitTypeRefs(dotNetExpression.toTypeRef(true), expressionTypeRef, CSharpCastType.IMPLICIT, this); for(DotNetTypeRef implicitOrExplicitTypeRef : implicitOrExplicitTypeRefs) { consumer.consume(implicitOrExplicitTypeRef); } } } @NotNull @RequiredReadAction public DotNetTypeRef resolveToTypeRef() { ResolveResult[] resolveResults = multiResolve(true); if(resolveResults.length == 0) { return DotNetTypeRef.ERROR_TYPE; } ResolveResult resolveResult = CSharpResolveUtil.findValidOrFirstMaybeResult(resolveResults); if(resolveResult instanceof StubElementResolveResult) { return ((StubElementResolveResult) resolveResult).getTypeRef(); } PsiElement element = resolveResult.getElement(); if(element instanceof CSharpSimpleLikeMethodAsElement) { return ((CSharpSimpleLikeMethodAsElement) element).getReturnTypeRef(); } else { return CSharpReferenceExpressionImplUtil.toTypeRef(element); } } @RequiredReadAction public void resolveUserDefinedOperators(@NotNull IElementType elementType, @NotNull DotNetTypeRef originalTypeRef, @NotNull DotNetTypeRef typeRef, @NotNull Set<MethodResolveResult> last, @Nullable DotNetExpression implicitExpression) { Set<PsiElement> psiElements = resolveElements(elementType, typeRef); if(psiElements == null) { return; } CSharpCallArgument[] arguments = getCallArguments(originalTypeRef, implicitExpression, typeRef); List<DotNetLikeMethodDeclaration> elements = CSharpResolveUtil.mergeGroupsToIterable(psiElements); for(DotNetLikeMethodDeclaration psiElement : elements) { MethodCalcResult calc = MethodResolver.calc(arguments, psiElement, this, true); if(implicitExpression != null) { calc = calc.dupWithResult(-3000000); } last.add(MethodResolveResult.createResult(calc, psiElement, null)); } } @Nullable @RequiredReadAction private Set<PsiElement> resolveElements(@NotNull IElementType elementType, @NotNull DotNetTypeRef typeRef) { if(typeRef instanceof DotNetPointerTypeRef) { if(elementType != CSharpTokens.PLUS && elementType != CSharpTokens.MINUS) { return Collections.emptySet(); } Set<PsiElement> elements = new HashSet<PsiElement>(); for(String pointerArgumentType : ourPointerArgumentTypes) { elements.add(buildOperatorForPointer(elementType, typeRef, pointerArgumentType)); } return elements; } else { DotNetTypeResolveResult typeResolveResult = typeRef.resolve(); PsiElement element = typeResolveResult.getElement(); if(element == null) { return null; } AsPsiElementProcessor psiElementProcessor = new AsPsiElementProcessor(); MemberResolveScopeProcessor processor = new MemberResolveScopeProcessor(CSharpResolveOptions.build().element(this), psiElementProcessor, new ExecuteTarget[]{ExecuteTarget.ELEMENT_GROUP}); ResolveState state = ResolveState.initial(); state = state.put(CSharpResolveUtil.SELECTOR, new OperatorByTokenSelector(elementType)); state = state.put(CSharpResolveUtil.EXTRACTOR, typeResolveResult.getGenericExtractor()); CSharpResolveUtil.walkChildren(processor, element, false, true, state); Set<PsiElement> psiElements = psiElementProcessor.getElements(); if(psiElements.isEmpty()) { return null; } return psiElements; } } @RequiredReadAction @NotNull private PsiElement buildOperatorForPointer(IElementType operatorElementType, DotNetTypeRef leftTypeRef, String typeVmQName) { Project project = getProject(); CSharpLightMethodDeclarationBuilder builder = new CSharpLightMethodDeclarationBuilder(project); builder.withReturnType(leftTypeRef); builder.addParameter(new CSharpLightParameterBuilder(project).withName("p0").withTypeRef(leftTypeRef)); builder.addParameter(new CSharpLightParameterBuilder(project).withName("p0").withTypeRef(new CSharpTypeRefByQName(this, typeVmQName))); builder.setOperator(operatorElementType); return builder; } @NotNull @Override @RequiredReadAction public String getCanonicalText() { String operatorName = CSharpOperatorNameHelper.getOperatorName(getOperatorElementType()); assert operatorName != null : getOperatorElementType(); return operatorName; } @Override public PsiElement handleElementRename(String s) throws IncorrectOperationException { return null; } @Override public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException { return null; } @Override public boolean isReferenceTo(PsiElement element) { return resolve() == element; } @NotNull @Override public Object[] getVariants() { return ArrayUtil.EMPTY_OBJECT_ARRAY; } @Override public boolean isSoft() { return resolve() == this; } @NotNull public DotNetTypeRef[] getTypeRefs() { DotNetExpression[] parameterExpressions = getParameterExpressions(); DotNetTypeRef[] typeRefs = new DotNetTypeRef[parameterExpressions.length]; for(int i = 0; i < parameterExpressions.length; i++) { DotNetExpression parameterExpression = parameterExpressions[i]; typeRefs[i] = parameterExpression.toTypeRef(true); } return typeRefs; } @Override public boolean canResolve() { return true; } @Nullable @Override public CSharpCallArgumentList getParameterList() { return null; } @Nullable @Override public PsiElement resolveToCallable() { return resolve(); } @NotNull @Override public ResolveResult[] multiResolve(boolean incompleteCode) { return ResolveCache.getInstance(getProject()).resolveWithCaching(this, OurResolver.INSTANCE, false, incompleteCode); } @NotNull @Override public DotNetExpression[] getParameterExpressions() { PsiElement parent = getParent(); if(parent instanceof CSharpExpressionWithOperatorImpl) { return ((CSharpExpressionWithOperatorImpl) parent).getParameterExpressions(); } return DotNetExpression.EMPTY_ARRAY; } @NotNull @Override public CSharpCallArgument[] getCallArguments() { return getCallArguments(null, null, null); } @NotNull public CSharpCallArgument[] getCallArguments(DotNetTypeRef originalTypeRef, DotNetExpression wrapExpression, DotNetTypeRef toTypeRef) { DotNetExpression[] parameterExpressions = getParameterExpressions(); CSharpCallArgument[] array = new CSharpCallArgument[parameterExpressions.length]; for(int i = 0; i < parameterExpressions.length; i++) { DotNetExpression parameterExpression = parameterExpressions[i]; if(parameterExpression == wrapExpression) { ImplicitOperatorArgumentAsCallArgumentWrapper wrapper = new ImplicitOperatorArgumentAsCallArgumentWrapper(wrapExpression, toTypeRef); wrapper.putUserData(ImplicitCastInfo.IMPLICIT_CAST_INFO, new ImplicitCastInfo(originalTypeRef, toTypeRef)); array[i] = wrapper; } else { array[i] = new CSharpLightCallArgument(parameterExpression); } } return array; } @RequiredReadAction @Nullable @Override public String getReferenceName() { throw new UnsupportedOperationException(); } @RequiredReadAction @Nullable @Override public String getReferenceNameWithAt() { throw new UnsupportedOperationException(); } @RequiredReadAction @Nullable @Override public PsiElement getQualifier() { return null; } }