/* * 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.genericInference; import gnu.trove.THashMap; import java.util.List; import java.util.Map; import org.jetbrains.annotations.NotNull; import com.intellij.openapi.util.Key; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import consulo.annotations.RequiredReadAction; import consulo.csharp.lang.psi.CSharpCallArgument; import consulo.csharp.lang.psi.CSharpCallArgumentListOwner; import consulo.csharp.lang.psi.CSharpReferenceExpression; import consulo.csharp.lang.psi.impl.CSharpTypeUtil; import consulo.csharp.lang.psi.impl.source.CSharpLambdaExpressionImpl; import consulo.csharp.lang.psi.impl.source.CSharpLambdaExpressionImplUtil; import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.MethodResolver; import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.arguments.NCallArgument; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpFastImplicitTypeRef; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpGenericExtractor; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpLambdaResolveResult; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpLambdaTypeRef; import consulo.csharp.lang.psi.impl.source.resolve.type.wrapper.GenericUnwrapTool; import consulo.dotnet.psi.DotNetExpression; import consulo.dotnet.psi.DotNetGenericParameter; import consulo.dotnet.psi.DotNetGenericParameterListOwner; import consulo.dotnet.psi.DotNetLikeMethodDeclaration; import consulo.dotnet.resolve.DotNetGenericExtractor; import consulo.dotnet.resolve.DotNetTypeRef; import consulo.dotnet.resolve.DotNetTypeResolveResult; import consulo.dotnet.util.ArrayUtil2; /** * @author VISTALL * @since 29.10.14 */ public class GenericInferenceUtil { public static final Key<GenericInferenceUtil.GenericInferenceResult> INFERENCE_RESULT = Key.create("inference.result"); public static class GenericInferenceResult { private boolean mySuccess; private DotNetGenericExtractor myExtractor; public GenericInferenceResult(boolean success, @NotNull DotNetGenericExtractor extractor) { mySuccess = success; myExtractor = extractor; } public boolean isSuccess() { return mySuccess; } @NotNull public DotNetGenericExtractor getExtractor() { return myExtractor; } } @NotNull @RequiredReadAction public static GenericInferenceResult inferenceGenericExtractor(@NotNull PsiElement referenceElement, @NotNull CSharpCallArgumentListOwner callArgumentListOwner, @NotNull DotNetLikeMethodDeclaration methodDeclaration) { CSharpCallArgument[] arguments = callArgumentListOwner.getCallArguments(); DotNetTypeRef[] typeArgumentListRef = DotNetTypeRef.EMPTY_ARRAY; if(referenceElement instanceof CSharpReferenceExpression) { typeArgumentListRef = ((CSharpReferenceExpression) referenceElement).getTypeArgumentListRefs(); } return inferenceGenericExtractor(arguments, typeArgumentListRef, callArgumentListOwner, methodDeclaration); } @NotNull @RequiredReadAction public static GenericInferenceResult inferenceGenericExtractor(@NotNull CSharpCallArgument[] callArguments, @NotNull DotNetTypeRef[] typeArgumentListRefs, @NotNull PsiElement scope, @NotNull DotNetLikeMethodDeclaration methodDeclaration) { DotNetGenericParameter[] genericParameters = methodDeclaration.getGenericParameters(); if(genericParameters.length == 0 || typeArgumentListRefs.length > 0) { DotNetGenericExtractor extractor = genericParameters.length != typeArgumentListRefs.length ? DotNetGenericExtractor.EMPTY : CSharpGenericExtractor.create(genericParameters, typeArgumentListRefs); return new GenericInferenceResult(genericParameters.length == typeArgumentListRefs.length, extractor); } List<NCallArgument> methodCallArguments = MethodResolver.buildCallArguments(callArguments, methodDeclaration, scope); if(methodCallArguments.isEmpty()) { return new GenericInferenceResult(true, DotNetGenericExtractor.EMPTY); } final Map<DotNetGenericParameter, DotNetTypeRef> map = new THashMap<>(); for(NCallArgument nCallArgument : methodCallArguments) { DotNetTypeRef parameterTypeRef = nCallArgument.getParameterTypeRef(); if(parameterTypeRef == null) { continue; } DotNetTypeRef expressionTypeRef = unwrapPossibleGenericTypeRefs(nCallArgument, parameterTypeRef, map, scope); if(expressionTypeRef instanceof CSharpFastImplicitTypeRef) { DotNetTypeRef mirror = ((CSharpFastImplicitTypeRef) expressionTypeRef).doMirror(parameterTypeRef, scope); if(mirror != null) { expressionTypeRef = mirror; } } DotNetTypeResolveResult parameterTypeResolveResult = parameterTypeRef.resolve(); DotNetTypeResolveResult expressionTypeResolveResult = expressionTypeRef.resolve(); if(parameterTypeResolveResult instanceof CSharpLambdaResolveResult && expressionTypeResolveResult instanceof CSharpLambdaResolveResult) { CSharpLambdaResolveResult pLambdaResolveResult = (CSharpLambdaResolveResult) parameterTypeResolveResult; CSharpLambdaResolveResult eLambdaResolveResult = (CSharpLambdaResolveResult) expressionTypeResolveResult; DotNetTypeRef[] pParameterTypeRefs = pLambdaResolveResult.getParameterTypeRefs(); DotNetTypeRef[] eParameterTypeRefs = eLambdaResolveResult.getParameterTypeRefs(); if(pParameterTypeRefs.length == eParameterTypeRefs.length) { for(int i = 0; i < eParameterTypeRefs.length; i++) { DotNetTypeRef pParameterTypeRef = pParameterTypeRefs[i]; DotNetTypeRef eParameterTypeRef = eParameterTypeRefs[i]; inferenceGenericFromExpressionTypeRefAndParameterTypeRef(genericParameters, map, pParameterTypeRef, eParameterTypeRef, scope); } } inferenceGenericFromExpressionTypeRefAndParameterTypeRef(genericParameters, map, pLambdaResolveResult.getReturnTypeRef(), eLambdaResolveResult.getReturnTypeRef(), scope); } inferenceGenericFromExpressionTypeRefAndParameterTypeRef(genericParameters, map, parameterTypeRef, expressionTypeRef, scope); } return new GenericInferenceResult(genericParameters.length == map.size(), CSharpGenericExtractor.create(map)); } @RequiredReadAction private static void inferenceGenericFromExpressionTypeRefAndParameterTypeRef(DotNetGenericParameter[] methodGenericParameters, Map<DotNetGenericParameter, DotNetTypeRef> map, DotNetTypeRef parameterTypeRef, DotNetTypeRef expressionTypeRef, PsiElement scope) { if(expressionTypeRef == DotNetTypeRef.AUTO_TYPE || expressionTypeRef == DotNetTypeRef.UNKNOWN_TYPE || expressionTypeRef == DotNetTypeRef.ERROR_TYPE) { return; } DotNetTypeResolveResult parameterTypeResolveResult = parameterTypeRef.resolve(); PsiElement parameterElement = parameterTypeResolveResult.getElement(); for(DotNetGenericParameter genericParameter : methodGenericParameters) { if(map.containsKey(genericParameter)) { continue; } if(genericParameter.isEquivalentTo(parameterElement)) { map.put(genericParameter, expressionTypeRef); return; } } DotNetTypeResolveResult typeRefFromExtends = CSharpTypeUtil.findTypeRefFromExtends(expressionTypeRef, parameterTypeRef, scope); if(typeRefFromExtends == null) { return; } PsiElement element = typeRefFromExtends.getElement(); DotNetGenericExtractor genericExtractor = typeRefFromExtends.getGenericExtractor(); if(element instanceof DotNetGenericParameterListOwner) { DotNetGenericParameter[] genericParametersOfResolved = ((DotNetGenericParameterListOwner) element).getGenericParameters(); for(DotNetGenericParameter genericParameter : methodGenericParameters) { if(map.containsKey(genericParameter)) { continue; } int indexOfGeneric = findIndexOfGeneric(parameterTypeResolveResult, genericParameter); if(indexOfGeneric == -1) { continue; } DotNetGenericParameter genericParameterOfResolved = ArrayUtil2.safeGet(genericParametersOfResolved, indexOfGeneric); if(genericParameterOfResolved == null) { continue; } DotNetTypeRef extract = genericExtractor.extract(genericParameterOfResolved); if(extract != null) { map.put(genericParameter, extract); } } } } @RequiredReadAction private static int findIndexOfGeneric(DotNetTypeResolveResult parameterTypeResolveResult, DotNetGenericParameter parameter) { PsiElement element = parameterTypeResolveResult.getElement(); if(element instanceof DotNetGenericParameterListOwner) { DotNetGenericParameter[] genericParameters = ((DotNetGenericParameterListOwner) element).getGenericParameters(); if(genericParameters.length == 0) { return -1; } DotNetGenericExtractor genericExtractor = parameterTypeResolveResult.getGenericExtractor(); for(int i = 0; i < genericParameters.length; i++) { DotNetTypeRef extractedTypeRef = genericExtractor.extract(genericParameters[i]); if(extractedTypeRef == null) { continue; } DotNetTypeResolveResult extractedTypeResolveResult = extractedTypeRef.resolve(); if(parameter.isEquivalentTo(extractedTypeResolveResult.getElement())) { return i; } } } return -1; } @NotNull @RequiredReadAction private static DotNetTypeRef unwrapPossibleGenericTypeRefs(@NotNull NCallArgument nCallArgument, @NotNull DotNetTypeRef parameterTypeRef, @NotNull Map<DotNetGenericParameter, DotNetTypeRef> map, @NotNull PsiElement scope) { DotNetTypeRef expressionTypeRef = nCallArgument.getTypeRef(); CSharpCallArgument callArgument = nCallArgument.getCallArgument(); if(callArgument == null) { return expressionTypeRef; } DotNetExpression argumentExpression = callArgument.getArgumentExpression(); if(!(argumentExpression instanceof CSharpLambdaExpressionImpl)) { return expressionTypeRef; } CSharpLambdaTypeRef baseTypeRefOfLambda = new CSharpLambdaTypeRef(scope, null, ((CSharpLambdaExpressionImpl) argumentExpression).getParameterInfos(), DotNetTypeRef.AUTO_TYPE); if(CSharpTypeUtil.isInheritable(parameterTypeRef, baseTypeRefOfLambda, scope)) { //TODO [VISTALL] find another way to duplicate expression final PsiFile fileCopy = (PsiFile) argumentExpression.getContainingFile().copy(); PsiElement elementAt = fileCopy.findElementAt(argumentExpression.getTextOffset()); CSharpLambdaExpressionImpl copy = PsiTreeUtil.getParentOfType(elementAt, CSharpLambdaExpressionImpl.class); assert copy != null; DotNetGenericExtractor extractor = CSharpGenericExtractor.create(map); DotNetTypeRef newParameterTypeRef = GenericUnwrapTool.exchangeTypeRef(parameterTypeRef, extractor, scope); copy.putUserData(CSharpLambdaExpressionImplUtil.TYPE_REF_OF_LAMBDA, newParameterTypeRef); return copy.toTypeRefForInference(); } return expressionTypeRef; } }