blob: 2c91d969b91928e5047ddfb6a6540487d7c843fc [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.psi.impl.statements.expressions.path;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.hash.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
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.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrLiteralClassType;
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.GrCallExpressionTypeCalculator;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import java.util.Set;
/**
* @author sergey.evdokimov
*/
public class DefaultCallExpressionTypeCalculator extends GrCallExpressionTypeCalculator {
private static final Logger LOG = Logger.getInstance(DefaultCallExpressionTypeCalculator.class);
@Override
public PsiType calculateReturnType(@NotNull GrMethodCall callExpression, GroovyResolveResult[] resolveResults) {
GrExpression invoked = callExpression.getInvokedExpression();
if (invoked instanceof GrReferenceExpression) {
GrReferenceExpression refExpr = (GrReferenceExpression) invoked;
PsiManager manager = callExpression.getManager();
PsiType result = null;
for (GroovyResolveResult resolveResult : resolveResults) {
PsiType returnType = calculateReturnTypeInner(callExpression, refExpr, resolveResult);
if (returnType == null) return null;
PsiType nonVoid = PsiType.VOID.equals(returnType) ? PsiType.NULL : returnType;
PsiType normalized = nonVoid instanceof GrLiteralClassType
? nonVoid
: TypesUtil.substituteBoxAndNormalizeType(nonVoid, resolveResult.getSubstitutor(), resolveResult.getSpreadState(), callExpression);
LOG.assertTrue(normalized != null, "return type: " + returnType + "; substitutor: " + resolveResult.getSubstitutor());
if (result == null || normalized.isAssignableFrom(result)) {
result = normalized;
}
else if (!result.isAssignableFrom(normalized)) {
result = TypesUtil.getLeastUpperBound(result, normalized, manager);
}
}
return result;
}
else {
return extractReturnTypeFromType(invoked.getType(), false, callExpression);
}
}
@Nullable
private static PsiType calculateReturnTypeInner(GrMethodCall callExpression,
GrReferenceExpression refExpr,
GroovyResolveResult resolveResult) {
PsiElement resolved = resolveResult.getElement();
if (resolved instanceof PsiMethod) {
PsiMethod method = (PsiMethod)resolved;
if (resolveResult.isInvokedOnProperty()) {
final PsiType propertyType = PsiUtil.getSmartReturnType(method);
return extractReturnTypeFromType(propertyType, true, callExpression);
}
else {
PsiType closureReturnType = getClosureMethodsReturnType(callExpression, refExpr, method);
if (closureReturnType != null) {
return closureReturnType;
}
else {
final PsiType smartReturnType = PsiUtil.getSmartReturnType(method);
return smartReturnType;
}
}
}
else if (resolved instanceof GrVariable) {
PsiType refType = refExpr.getType();
final PsiType type = refType == null ? ((GrVariable)resolved).getTypeGroovy() : refType;
return extractReturnTypeFromType(type, false, callExpression);
}
return null;
}
@Nullable
private static PsiType extractReturnTypeFromType(PsiType type, boolean returnTypeIfFail, GrMethodCall callExpression) {
PsiType returnType = returnTypeIfFail ? type: null;
if (type instanceof GrClosureType) {
returnType = GrClosureSignatureUtil.getReturnType(((GrClosureType)type).getSignature(), callExpression);
}
else if (TypesUtil.isPsiClassTypeToClosure(type)) {
assert type instanceof PsiClassType;
final PsiType[] parameters = ((PsiClassType)type).getParameters();
if (parameters.length == 1) {
returnType = parameters[0];
}
}
else if (type instanceof PsiClassType) {
final GrExpression invoked = callExpression.getInvokedExpression();
final GroovyResolveResult[] calls = ResolveUtil
.getMethodCandidates(type, "call", invoked != null ? invoked : callExpression, PsiUtil.getArgumentTypes(invoked, false));
returnType = null;
final PsiManager manager = callExpression.getManager();
for (GroovyResolveResult call : calls) {
final PsiType substituted = ResolveUtil.extractReturnTypeFromCandidate(call, callExpression, PsiUtil.getArgumentTypes(invoked, true));
returnType = TypesUtil.getLeastUpperBoundNullable(returnType, substituted, manager);
}
}
return returnType;
}
private static final Set<String> CLOSURE_METHODS = new HashSet<String>();
static {
CLOSURE_METHODS.add("call");
CLOSURE_METHODS.add("curry");
CLOSURE_METHODS.add("ncurry");
CLOSURE_METHODS.add("rcurry");
CLOSURE_METHODS.add("memoize");
CLOSURE_METHODS.add("trampoline");
}
@Nullable
private static PsiType getClosureMethodsReturnType(GrMethodCall callExpression, GrReferenceExpression refExpr, PsiMethod resolved) {
PsiClass clazz = resolved.getContainingClass();
if (clazz == null || !GroovyCommonClassNames.GROOVY_LANG_CLOSURE.equals(clazz.getQualifiedName())) return null;
if (!CLOSURE_METHODS.contains(resolved.getName())) return null;
GrExpression qualifier = refExpr.getQualifierExpression();
if (qualifier == null) return null;
PsiType qType = qualifier.getType();
if (!(qType instanceof GrClosureType)) return null;
if ("call".equals(resolved.getName())) {
return GrClosureSignatureUtil.getReturnType(((GrClosureType)qType).getSignature(), callExpression);
}
else if ("curry".equals(resolved.getName()) || "trampoline".equals(resolved.getName())) {
return ((GrClosureType)qType).curry(PsiUtil.getArgumentTypes(refExpr, false), 0, callExpression);
}
else if ("memoize".equals(resolved.getName())) {
return qType;
}
else if ("rcurry".equals(resolved.getName())) {
return ((GrClosureType)qType).curry(PsiUtil.getArgumentTypes(refExpr, false), -1, callExpression);
}
else if ("ncurry".equals(resolved.getName())) {
final GrArgumentList argList = callExpression.getArgumentList();
if (argList != null) {
final GrExpression[] arguments = argList.getExpressionArguments();
if (arguments.length > 0) {
final GrExpression first = arguments[0];
if (first instanceof GrLiteral) {
final Object value = ((GrLiteral)first).getValue();
if (value instanceof Integer) {
final PsiType[] argTypes = PsiUtil.getArgumentTypes(refExpr, false);
if (argTypes != null) {
return ((GrClosureType)qType).curry(ArrayUtil.remove(argTypes, 0), (Integer)value, callExpression);
}
}
}
}
}
return qType;
}
return null;
}
}