blob: 2b88b4ddb55089b049f8217fb57a11a3b7ede3ff [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.expectedTypes;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationArrayInitializer;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationNameValuePair;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
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.clauses.GrCaseLabel;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrTraditionalForClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAnnotationMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author ven
*/
public class GroovyExpectedTypesProvider {
public static TypeConstraint[] calculateTypeConstraints(@NotNull final GrExpression expression) {
return TypeInferenceHelper.getCurrentContext().getCachedValue(expression, new Computable<TypeConstraint[]>() {
@Override
public TypeConstraint[] compute() {
MyCalculator calculator = new MyCalculator(expression);
final PsiElement parent = expression.getParent();
if (parent instanceof GroovyPsiElement) {
((GroovyPsiElement)parent).accept(calculator);
}
else {
parent.accept(new GroovyPsiElementVisitor(calculator));
}
final TypeConstraint[] result = calculator.getResult();
List<TypeConstraint> custom = ContainerUtil.newArrayList();
for (GroovyExpectedTypesContributor contributor : GroovyExpectedTypesContributor.EP_NAME.getExtensions()) {
custom.addAll(contributor.calculateTypeConstraints(expression));
}
if (!custom.isEmpty()) {
custom.addAll(0, Arrays.asList(result));
return custom.toArray(new TypeConstraint[custom.size()]);
}
return result;
}
});
}
public static List<PsiType> getDefaultExpectedTypes(@NotNull GrExpression element) {
TypeConstraint[] constraints = calculateTypeConstraints(element);
return ContainerUtil.map(constraints, new Function<TypeConstraint, PsiType>() {
@Override
public PsiType fun(TypeConstraint constraint) {
return constraint.getDefaultType();
}
});
}
private static class MyCalculator extends GroovyElementVisitor {
private TypeConstraint[] myResult;
private final GrExpression myExpression;
public MyCalculator(GrExpression expression) {
myExpression = (GrExpression)PsiUtil.skipParentheses(expression, true);
myResult = TypeConstraint.EMPTY_ARRAY;
}
@Override
public void visitReturnStatement(GrReturnStatement returnStatement) {
GrParametersOwner parent = PsiTreeUtil.getParentOfType(returnStatement, GrMethod.class, GrClosableBlock.class);
if (parent instanceof GrMethod) {
GrTypeElement typeElement = ((GrMethod)parent).getReturnTypeElementGroovy();
if (typeElement != null) {
PsiType type = typeElement.getType();
myResult = new TypeConstraint[]{SubtypeConstraint.create(type)};
}
}
}
@Override
public void visitVariable(GrVariable variable) {
if (myExpression.equals(variable.getInitializerGroovy())) {
PsiType type = variable.getType();
myResult = createSimpleSubTypeResult(type);
}
}
@Override
public void visitNamedArgument(GrNamedArgument argument) {
GrArgumentLabel label = argument.getLabel();
if (label != null) {
PsiElement pparent = argument.getParent().getParent();
if (pparent instanceof GrCall && resolvesToDefaultConstructor(((GrCall)pparent))) {
final GroovyResolveResult resolveResult = label.advancedResolve();
PsiElement resolved = resolveResult.getElement();
PsiType type = resolved instanceof PsiField ?
((PsiField)resolved).getType() :
resolved instanceof PsiMethod && GroovyPropertyUtils.isSimplePropertySetter((PsiMethod)resolved) ?
((PsiMethod)resolved).getParameterList().getParameters()[0].getType()
: null;
PsiType substituted = resolveResult.getSubstitutor().substitute(type);
if (substituted != null) {
myResult = createSimpleSubTypeResult(substituted);
}
}
}
}
private static boolean resolvesToDefaultConstructor(GrCall call) {
PsiMethod method = call.resolveMethod();
if (method != null && method.isConstructor() && method.getParameterList().getParametersCount() == 0) return true;
if (call instanceof GrConstructorCall) {
PsiElement resolved = PsiImplUtil.extractUniqueResult(((GrConstructorCall)call).multiResolveClass()).getElement();
if (resolved instanceof PsiClass) return true;
}
return false;
}
@Override
public void visitMethodCallExpression(GrMethodCallExpression methodCall) {
final GrExpression invokedExpression = methodCall.getInvokedExpression();
if (myExpression.equals(invokedExpression)) {
myResult = new TypeConstraint[]{SubtypeConstraint.create(GroovyCommonClassNames.GROOVY_LANG_CLOSURE, methodCall)};
return;
}
final GrClosableBlock[] closureArgs = methodCall.getClosureArguments();
if (ArrayUtil.contains(myExpression, closureArgs)) {
final GrArgumentList argumentList = methodCall.getArgumentList();
final GrNamedArgument[] namedArgs = argumentList.getNamedArguments();
final GrExpression[] expressionArgs = argumentList.getExpressionArguments();
final GroovyResolveResult[] callVariants = ResolveUtil.getCallVariants(myExpression);
processCallVariants(methodCall, callVariants, namedArgs, expressionArgs, closureArgs);
}
}
@Override
public void visitOpenBlock(GrOpenBlock block) {
final GrStatement[] statements = block.getStatements();
if (statements.length > 0 && myExpression.equals(statements[statements.length - 1])) {
checkExitPoint();
}
}
@Override
public void visitIfStatement(GrIfStatement ifStatement) {
if (myExpression.equals(ifStatement.getCondition())) {
myResult = new TypeConstraint[]{new SubtypeConstraint(TypesUtil.getJavaLangObject(ifStatement), PsiType.BOOLEAN)};
}
else if (myExpression.equals(ifStatement.getThenBranch()) || myExpression.equals(ifStatement.getElseBranch())) {
checkExitPoint();
}
}
@Override
public void visitAnnotationMethod(GrAnnotationMethod method) {
PsiType type = method.getReturnType();
if (type != null && isAcceptableAnnotationValueType(type)) {
myResult = createSimpleSubTypeResult(type);
}
}
@Override
public void visitAnnotationArrayInitializer(GrAnnotationArrayInitializer arrayInitializer) {
final GrAnnotationNameValuePair nameValuePair = PsiTreeUtil.getParentOfType(arrayInitializer, GrAnnotationNameValuePair.class, true, GrAnnotationMethod.class);
if (nameValuePair != null) {
final PsiClass annot = ResolveUtil.resolveAnnotation(arrayInitializer);
if (annot == null) return;
final String name = nameValuePair.getName();
if (name == null) return;
createResultFromAttrName(annot, name);
}
else {
final GrAnnotationMethod method = PsiTreeUtil.getParentOfType(arrayInitializer, GrAnnotationMethod.class);
assert method != null;
PsiType type = method.getReturnType();
int count = 1;
PsiElement parent = arrayInitializer.getParent();
while (parent instanceof GrAnnotationArrayInitializer) {
count++;
parent = parent.getParent();
}
while (type instanceof PsiArrayType && count > 0) {
type = ((PsiArrayType)type).getComponentType();
count--;
}
if (type != null && isAcceptableAnnotationValueType(type)) {
myResult = createSimpleSubTypeResult(type);
}
}
}
@Override
public void visitAnnotationNameValuePair(GrAnnotationNameValuePair nameValuePair) {
if (myExpression.equals(nameValuePair.getValue())) {
final PsiClass annot = ResolveUtil.resolveAnnotation(nameValuePair.getParent());
if (annot != null) {
final String name = nameValuePair.getName();
if (name != null) {
createResultFromAttrName(annot, name);
}
else {
final PsiMethod[] valueAttr = annot.findMethodsByName("value", false);
if (valueAttr.length > 0) {
boolean canHaveSimpleExpr = ContainerUtil.find(annot.getMethods(), new Condition<PsiMethod>() {
@Override
public boolean value(PsiMethod method) {
return !("value".equals(method.getName()) || method instanceof PsiAnnotationMethod && ((PsiAnnotationMethod)method).getDefaultValue() != null);
}
}) == null;
if (canHaveSimpleExpr) {
createResultFromAnnotationAttribute(valueAttr[0]);
}
}
}
}
}
}
private void createResultFromAttrName(PsiClass annotation, String attrName) {
final PsiMethod[] attrs = annotation.findMethodsByName(attrName, false);
if (attrs.length > 0) {
createResultFromAnnotationAttribute(attrs[0]);
}
}
private void createResultFromAnnotationAttribute(PsiMethod attr) {
PsiType type = attr.getReturnType();
while (type instanceof PsiArrayType) type = ((PsiArrayType)type).getComponentType();
if (type != null && isAcceptableAnnotationValueType(type)) {
myResult = createSimpleSubTypeResult(type);
}
}
private static boolean isAcceptableAnnotationValueType(PsiType type) {
//noinspection ConstantConditions
return type instanceof PsiPrimitiveType ||
type.equalsToText(CommonClassNames.JAVA_LANG_STRING) ||
type.equalsToText(CommonClassNames.JAVA_LANG_CLASS) ||
type instanceof PsiClassType && ((PsiClassType)type).resolve() != null && ((PsiClassType)type).resolve().isEnum();
}
@NotNull
private static TypeConstraint[] createSimpleSubTypeResult(@NotNull PsiType type) {
return new TypeConstraint[]{new SubtypeConstraint(type, type)};
}
private void checkExitPoint() {
final PsiElement element = PsiTreeUtil.getParentOfType(myExpression, PsiMethod.class, GrClosableBlock.class);
if (element instanceof GrMethod) {
final GrMethod method = (GrMethod)element;
ControlFlowUtils.visitAllExitPoints(method.getBlock(), new ControlFlowUtils.ExitPointVisitor() {
@Override
public boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue) {
if (returnValue == myExpression) {
final PsiType returnType = method.getReturnType();
if (returnType != null) {
myResult = createSimpleSubTypeResult(returnType);
}
return false;
}
return true;
}
});
}
}
@Override
public void visitWhileStatement(GrWhileStatement whileStatement) {
if (myExpression.equals(whileStatement.getCondition())) {
myResult = new TypeConstraint[]{new SubtypeConstraint(TypesUtil.getJavaLangObject(whileStatement), PsiType.BOOLEAN)};
}
}
@Override
public void visitTraditionalForClause(GrTraditionalForClause forClause) {
if (myExpression.equals(forClause.getCondition())) {
myResult = new TypeConstraint[]{new SubtypeConstraint(TypesUtil.getJavaLangObject(forClause), PsiType.BOOLEAN)};
}
}
@Override
public void visitArgumentList(GrArgumentList list) {
processCallVariants(list, ResolveUtil.getCallVariants(list), list.getNamedArguments(), list.getExpressionArguments(), GrClosableBlock.EMPTY_ARRAY);
}
private void processCallVariants(@NotNull PsiElement place,
@NotNull GroovyResolveResult[] variants,
@NotNull GrNamedArgument[] namedArguments,
@NotNull GrExpression[] expressionArguments,
@NotNull GrClosableBlock[] closureArguments) {
List<Pair<PsiParameter, PsiType>> expectedParams =
ResolveUtil.collectExpectedParamsByArg(place, variants, namedArguments, expressionArguments, closureArguments, myExpression);
collectExpectedTypeFromPossibleParams(expectedParams);
}
private void collectExpectedTypeFromPossibleParams(List<Pair<PsiParameter, PsiType>> expectedParams) {
List<TypeConstraint> constraints = ContainerUtil.newArrayList();
for (Pair<PsiParameter, PsiType> pair : expectedParams) {
final PsiType type = pair.second;
if (type != null) {
constraints.add(SubtypeConstraint.create(type));
if (type instanceof PsiArrayType && pair.first.isVarArgs()) {
constraints.add(SubtypeConstraint.create(((PsiArrayType)type).getComponentType()));
}
}
}
if (!constraints.isEmpty()) {
myResult = constraints.toArray(new TypeConstraint[constraints.size()]);
}
}
@Override
public void visitBinaryExpression(GrBinaryExpression expression) {
final IElementType type = expression.getOperationTokenType();
final GrExpression left = expression.getLeftOperand();
final GrExpression right = expression.getRightOperand();
final GrExpression other = myExpression == left ? right : left;
final PsiType otherType = other != null ? other.getType() : null;
if (otherType == null) return;
final GroovyResolveResult[] callVariants = expression.multiResolve(true);
if (myExpression == left || callVariants.length == 0) {
if (type == GroovyTokenTypes.mPLUS && otherType.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
final PsiClassType obj = TypesUtil.getJavaLangObject(expression);
myResult = createSimpleSubTypeResult(obj);
}
else if (type == GroovyTokenTypes.mREGEX_FIND || type == GroovyTokenTypes.mREGEX_MATCH) {
final PsiClassType string = TypesUtil.createType(CommonClassNames.JAVA_LANG_STRING, expression);
myResult = createSimpleSubTypeResult(string);
}
else {
myResult = createSimpleSubTypeResult(otherType);
}
}
else { //myExpression == right
processCallVariants(expression, callVariants, GrNamedArgument.EMPTY_ARRAY, new GrExpression[]{myExpression},
GrClosableBlock.EMPTY_ARRAY);
}
}
@Override
public void visitInstanceofExpression(GrInstanceOfExpression expression) {
final GrExpression operand = expression.getOperand();
final GrTypeElement typeElement = expression.getTypeElement();
if (typeElement == null) return;
if (myExpression == operand) {
final PsiType type = typeElement.getType();
myResult = new TypeConstraint[]{new SupertypeConstraint(type, type)};
}
}
@Override
public void visitAssignmentExpression(GrAssignmentExpression expression) {
GrExpression rValue = expression.getRValue();
GrExpression lValue = expression.getLValue();
if (myExpression.equals(rValue)) {
PsiType lType = lValue.getNominalType();
if (lType != null) {
myResult = new TypeConstraint[]{SubtypeConstraint.create(lType)};
}
else if (lValue instanceof GrReferenceExpression) {
GroovyResolveResult result = ((GrReferenceExpression)lValue).advancedResolve();
PsiElement resolved = result.getElement();
if (resolved instanceof GrVariable) {
PsiType type = ((GrVariable)resolved).getTypeGroovy();
if (type != null) {
myResult = new TypeConstraint[]{SubtypeConstraint.create(result.getSubstitutor().substitute(type))};
}
}
}
}
else if (myExpression.equals(lValue)) {
if (rValue != null) {
PsiType rType = rValue.getType();
if (rType != null) {
myResult = new TypeConstraint[]{SupertypeConstraint.create(rType)};
}
}
}
}
@Override
public void visitThrowStatement(GrThrowStatement throwStatement) {
final PsiClassType throwable = PsiType.getJavaLangThrowable(myExpression.getManager(), throwStatement.getResolveScope());
myResult = new TypeConstraint[]{SubtypeConstraint.create(throwable)};
}
@Override
public void visitUnaryExpression(final GrUnaryExpression expression) {
TypeConstraint constraint = new TypeConstraint(PsiType.INT) {
@Override
public boolean satisfied(PsiType type, @NotNull PsiElement context) {
final PsiType boxed = TypesUtil.boxPrimitiveType(type, context.getManager(), context.getResolveScope());
final IElementType opToken = expression.getOperationTokenType();
final GrExpression operand = expression.getOperand();
final GroovyResolveResult[] candidates =
TypesUtil.getOverloadedUnaryOperatorCandidates(boxed, opToken, operand != null ? operand : expression, PsiType.EMPTY_ARRAY);
return candidates.length > 0;
}
@NotNull
@Override
public PsiType getDefaultType() {
return PsiType.INT;
}
};
myResult = new TypeConstraint[]{constraint};
}
@Override
public void visitParenthesizedExpression(GrParenthesizedExpression expression) {
final PsiElement parent = expression.getParent();
if (parent instanceof GroovyPsiElement) {
((GroovyPsiElement)parent).accept(this);
}
else {
parent.accept(new GroovyPsiElementVisitor(this));
}
}
@Override
public void visitListOrMap(GrListOrMap listOrMap) {
if (listOrMap.isMap()) return;
final TypeConstraint[] constraints = calculateTypeConstraints(listOrMap);
List<PsiType> result=new ArrayList<PsiType>(constraints.length);
for (TypeConstraint constraint : constraints) {
if (constraint instanceof SubtypeConstraint) {
final PsiType type = constraint.getType();
final PsiType iterable = com.intellij.psi.util.PsiUtil.extractIterableTypeParameter(type, true);
if (iterable != null) {
result.add(iterable);
}
}
}
if (result.isEmpty()) {
myResult = TypeConstraint.EMPTY_ARRAY;
}
else {
myResult = new TypeConstraint[result.size()];
for (int i = 0; i < result.size(); i++) {
final PsiType type = result.get(i);
if (type!=null) {
myResult[i] = SubtypeConstraint.create(type);
}
}
}
}
@Override
public void visitCaseLabel(GrCaseLabel caseLabel) {
final PsiElement parent = caseLabel.getParent().getParent();
if (!(parent instanceof GrSwitchStatement)) return;
final GrExpression condition = ((GrSwitchStatement)parent).getCondition();
if (condition == null) return;
final PsiType type = condition.getType();
if (type == null) return;
myResult = new TypeConstraint[]{SubtypeConstraint.create(type)};
}
@Override
public void visitSwitchStatement(GrSwitchStatement switchStatement) {
final GrCaseSection[] sections = switchStatement.getCaseSections();
List<PsiType> types = new ArrayList<PsiType>(sections.length);
for (GrCaseSection section : sections) {
for (GrCaseLabel label : section.getCaseLabels()) {
final GrExpression value = label.getValue();
if (value != null) {
final PsiType type = value.getType();
if (type != null) {
types.add(type);
}
}
}
}
final PsiType upperBoundNullable = TypesUtil.getLeastUpperBoundNullable(types, switchStatement.getManager());
if (upperBoundNullable == null) return;
myResult = new TypeConstraint[]{SubtypeConstraint.create(upperBoundNullable)};
}
public TypeConstraint[] getResult() {
return myResult;
}
}
}