| /* |
| * 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; |
| } |
| |
| } |