blob: 548ba5348de3d52d62b8ffe4e8adafd389e9e6f3 [file] [log] [blame]
/*
* 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.resolve.processors;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrClosureSignature;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrClassInitializer;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrThrowStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrClosureParameter;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureToSamConverter;
import org.jetbrains.plugins.groovy.lang.psi.util.GdkMethodUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import java.util.Set;
/**
* @author Max Medvedev
*/
public class SubstitutorComputer {
private static final Logger LOG = Logger.getInstance(SubstitutorComputer.class);
protected final PsiElement myPlace;
private final PsiType myThisType;
@Nullable private final PsiType[] myArgumentTypes;
private final PsiType[] myTypeArguments;
private final GrControlFlowOwner myFlowOwner;
private final PsiElement myPlaceToInferContext;
private final PsiResolveHelper myHelper;
public SubstitutorComputer(PsiType thisType,
@Nullable PsiType[] argumentTypes,
PsiType[] typeArguments,
PsiElement place,
PsiElement placeToInferContext) {
myThisType = thisType;
myArgumentTypes = argumentTypes;
myTypeArguments = typeArguments;
myPlace = place;
myPlaceToInferContext = placeToInferContext;
if (canBeExitPoint(place)) {
myFlowOwner = ControlFlowUtils.findControlFlowOwner(place);
}
else {
myFlowOwner = null;
}
myHelper = JavaPsiFacade.getInstance(myPlace.getProject()).getResolveHelper();
}
@Nullable
protected PsiType inferContextType() {
final PsiElement parent = myPlaceToInferContext.getParent();
if (parent instanceof GrReturnStatement || exitsContains(myPlaceToInferContext)) {
final GrMethod method = PsiTreeUtil.getParentOfType(parent, GrMethod.class, true, GrClosableBlock.class);
if (method != null) {
return method.getReturnType();
}
}
else if (parent instanceof GrAssignmentExpression && myPlaceToInferContext.equals(((GrAssignmentExpression)parent).getRValue())) {
return ((GrAssignmentExpression)parent).getLValue().getType();
}
else if (parent instanceof GrVariable) {
return ((GrVariable)parent).getDeclaredType();
}
return null;
}
private static boolean canBeExitPoint(PsiElement place) {
while (place != null) {
if (place instanceof GrMethod || place instanceof GrClosableBlock || place instanceof GrClassInitializer) return true;
if (place instanceof GrThrowStatement || place instanceof GrTypeDefinitionBody || place instanceof GroovyFile) return false;
place = place.getParent();
}
return false;
}
public PsiSubstitutor obtainSubstitutor(PsiSubstitutor substitutor, PsiMethod method, ResolveState state) {
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
if (myTypeArguments.length == typeParameters.length) {
for (int i = 0; i < typeParameters.length; i++) {
PsiTypeParameter typeParameter = typeParameters[i];
final PsiType typeArgument = myTypeArguments[i];
substitutor = substitutor.put(typeParameter, typeArgument);
}
return substitutor;
}
if (myArgumentTypes != null && method.hasTypeParameters()) {
PsiType[] argTypes = myArgumentTypes;
final PsiElement resolveContext = state.get(ClassHint.RESOLVE_CONTEXT);
if (method instanceof GrGdkMethod) {
//type inference should be performed from static method
PsiType[] newArgTypes = PsiType.createArray(argTypes.length + 1);
if (GdkMethodUtil.isInWithContext(resolveContext)) {
newArgTypes[0] = ((GrExpression)resolveContext).getType();
}
else {
newArgTypes[0] = myThisType;
}
System.arraycopy(argTypes, 0, newArgTypes, 1, argTypes.length);
argTypes = newArgTypes;
method = ((GrGdkMethod)method).getStaticMethod();
LOG.assertTrue(method.isValid());
}
return inferMethodTypeParameters(method, substitutor, typeParameters, argTypes);
}
return substitutor;
}
private PsiSubstitutor inferMethodTypeParameters(@NotNull PsiMethod method,
@NotNull PsiSubstitutor partialSubstitutor,
@NotNull PsiTypeParameter[] typeParameters,
@NotNull PsiType[] argTypes) {
if (typeParameters.length == 0 || myArgumentTypes == null) return partialSubstitutor;
final GrClosureSignature erasedSignature = GrClosureSignatureUtil.createSignatureWithErasedParameterTypes(method);
final GrClosureSignature signature = GrClosureSignatureUtil.createSignature(method, partialSubstitutor);
final GrClosureParameter[] params = signature.getParameters();
final GrClosureSignatureUtil.ArgInfo<PsiType>[] argInfos = GrClosureSignatureUtil.mapArgTypesToParameters(erasedSignature, argTypes, myPlace, true);
if (argInfos == null) return partialSubstitutor;
int max = Math.max(params.length, argTypes.length);
PsiType[] parameterTypes = PsiType.createArray(max);
PsiType[] argumentTypes = PsiType.createArray(max);
int i = 0;
for (int paramIndex = 0; paramIndex < argInfos.length; paramIndex++) {
PsiType paramType = params[paramIndex].getType();
GrClosureSignatureUtil.ArgInfo<PsiType> argInfo = argInfos[paramIndex];
if (argInfo != null) {
if (argInfo.isMultiArg) {
if (paramType instanceof PsiArrayType) paramType = ((PsiArrayType)paramType).getComponentType();
}
for (PsiType type : argInfo.args) {
argumentTypes[i] = handleConversion(paramType, type);
parameterTypes[i] = paramType;
i++;
}
}
else {
parameterTypes[i] = paramType;
argumentTypes[i] = PsiType.NULL;
i++;
}
}
PsiSubstitutor substitutor = myHelper.inferTypeArguments(typeParameters, parameterTypes, argumentTypes, LanguageLevel.JDK_1_7);
for (PsiTypeParameter typeParameter : typeParameters) {
if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) {
substitutor = inferFromContext(typeParameter, PsiUtil.getSmartReturnType(method), substitutor);
if (!substitutor.getSubstitutionMap().containsKey(typeParameter)) {
substitutor = substitutor.put(typeParameter, null);
}
}
}
return partialSubstitutor.putAll(substitutor);
}
@Nullable
private PsiType handleConversion(@Nullable PsiType paramType, @Nullable PsiType argType) {
if (ClosureToSamConverter.isSamConversionAllowed(myPlace) &&
InheritanceUtil.isInheritor(argType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE) &&
!TypesUtil.isClassType(paramType, GroovyCommonClassNames.GROOVY_LANG_CLOSURE)) {
PsiType converted = handleConversionOfSAMType(paramType, (PsiClassType)argType);
if (converted != null) {
return converted;
}
return argType;
}
if (!TypesUtil.isAssignable(TypeConversionUtil.erasure(paramType), argType, myPlace) &&
TypesUtil.isAssignableByMethodCallConversion(paramType, argType, myPlace)) {
return paramType;
}
return argType;
}
@Nullable
private PsiType handleConversionOfSAMType(@Nullable PsiType samType, @NotNull PsiClassType closure) {
if (samType instanceof PsiClassType) {
PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)samType).resolveGenerics();
PsiClass samClass = resolveResult.getElement();
if (samClass != null && samClass.getTypeParameters().length != 0) {
MethodSignature samSignature = ClosureToSamConverter.findSingleAbstractMethod(samClass, PsiSubstitutor.EMPTY);
if (samSignature != null) {
PsiMethod samMethod = MethodSignatureUtil.findMethodBySignature(samClass, samSignature, true);
if (samMethod != null) {
PsiType[] closureArgs = closure.getParameters();
if (closureArgs.length == 1 && samMethod.getReturnType() != null) {
PsiSubstitutor substitutor = myHelper.inferTypeArguments(samClass.getTypeParameters(),
new PsiType[]{samMethod.getReturnType()},
closureArgs,
LanguageLevel.JDK_1_7);
if (!substitutor.getSubstitutionMap().isEmpty()) {
return JavaPsiFacade.getElementFactory(myPlace.getProject()).createType(samClass, substitutor);
}
}
}
}
}
}
return null;
}
private PsiSubstitutor inferFromContext(@NotNull PsiTypeParameter typeParameter,
@Nullable PsiType lType,
@NotNull PsiSubstitutor substitutor) {
if (myPlace == null) return substitutor;
final PsiType inferred = myHelper.getSubstitutionForTypeParameter(typeParameter, lType, inferContextType(), false, LanguageLevel.JDK_1_7);
if (inferred != PsiType.NULL) {
return substitutor.put(typeParameter, inferred);
}
return substitutor;
}
private Set<PsiElement> myExitPoints;
protected boolean exitsContains(PsiElement place) {
if (myFlowOwner == null) return false;
if (myExitPoints == null) {
myExitPoints = new HashSet<PsiElement>();
myExitPoints.addAll(ControlFlowUtils.collectReturns(myFlowOwner));
}
return myExitPoints.contains(place);
}
public PsiType[] getTypeArguments() {
return myTypeArguments;
}
}