blob: 8ff42ca085a03c145659cf85211de12ca29a43f4 [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 com.intellij.psi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* User: anna
* Date: 7/17/12
*/
public class LambdaUtil {
public static ThreadLocal<Map<PsiElement, PsiType>> ourFunctionTypes = new ThreadLocal<Map<PsiElement, PsiType>>();
private static final Logger LOG = Logger.getInstance("#" + LambdaUtil.class.getName());
@Nullable
public static PsiType getFunctionalInterfaceReturnType(PsiLambdaExpression expr) {
return getFunctionalInterfaceReturnType(expr.getFunctionalInterfaceType());
}
@Nullable
public static PsiType getFunctionalInterfaceReturnType(@Nullable PsiType functionalInterfaceType) {
final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
final PsiClass psiClass = resolveResult.getElement();
if (psiClass != null) {
final MethodSignature methodSignature = getFunction(psiClass);
if (methodSignature != null) {
final PsiType returnType = getReturnType(psiClass, methodSignature);
return resolveResult.getSubstitutor().substitute(returnType);
}
}
return null;
}
@Nullable
public static PsiMethod getFunctionalInterfaceMethod(@Nullable PsiType functionalInterfaceType) {
return getFunctionalInterfaceMethod(PsiUtil.resolveGenericsClassInType(functionalInterfaceType));
}
public static PsiMethod getFunctionalInterfaceMethod(@Nullable PsiElement element) {
if (element instanceof PsiLambdaExpression || element instanceof PsiMethodReferenceExpression) {
final PsiType samType = element instanceof PsiLambdaExpression
? ((PsiLambdaExpression)element).getFunctionalInterfaceType()
: ((PsiMethodReferenceExpression)element).getFunctionalInterfaceType();
return getFunctionalInterfaceMethod(samType);
}
return null;
}
@Nullable
public static PsiMethod getFunctionalInterfaceMethod(PsiClassType.ClassResolveResult result) {
return getFunctionalInterfaceMethod(result.getElement());
}
@Nullable
public static PsiMethod getFunctionalInterfaceMethod(PsiClass aClass) {
final MethodSignature methodSignature = getFunction(aClass);
if (methodSignature != null) {
return getMethod(aClass, methodSignature);
}
return null;
}
public static PsiSubstitutor getSubstitutor(@NotNull PsiMethod method, @NotNull PsiClassType.ClassResolveResult resolveResult) {
final PsiClass derivedClass = resolveResult.getElement();
LOG.assertTrue(derivedClass != null);
final PsiClass methodContainingClass = method.getContainingClass();
LOG.assertTrue(methodContainingClass != null);
PsiSubstitutor initialSubst = resolveResult.getSubstitutor();
final PsiSubstitutor superClassSubstitutor =
TypeConversionUtil.getSuperClassSubstitutor(methodContainingClass, derivedClass, PsiSubstitutor.EMPTY);
for (PsiTypeParameter param : superClassSubstitutor.getSubstitutionMap().keySet()) {
final PsiType substitute = superClassSubstitutor.substitute(param);
if (substitute != null) {
initialSubst = initialSubst.put(param, initialSubst.substitute(substitute));
}
}
return initialSubst;
}
public static boolean isFunctionalType(PsiType type) {
if (type instanceof PsiIntersectionType) {
return extractFunctionalConjunct((PsiIntersectionType)type) != null;
}
return isFunctionalClass(PsiUtil.resolveGenericsClassInType(type).getElement());
}
@Contract("null -> false")
public static boolean isFunctionalClass(PsiClass aClass) {
if (aClass != null) {
if (aClass instanceof PsiTypeParameter) return false;
final List<HierarchicalMethodSignature> signatures = findFunctionCandidates(aClass);
return signatures != null && signatures.size() == 1;
}
return false;
}
public static boolean isValidLambdaContext(@Nullable PsiElement context) {
return context instanceof PsiTypeCastExpression ||
context instanceof PsiAssignmentExpression ||
context instanceof PsiVariable ||
context instanceof PsiLambdaExpression ||
context instanceof PsiReturnStatement ||
context instanceof PsiExpressionList ||
context instanceof PsiParenthesizedExpression ||
context instanceof PsiArrayInitializerExpression ||
context instanceof PsiConditionalExpression && PsiTreeUtil.getParentOfType(context, PsiTypeCastExpression.class) == null;
}
public static boolean isLambdaFullyInferred(PsiLambdaExpression expression, PsiType functionalInterfaceType) {
final boolean hasParams = expression.getParameterList().getParametersCount() > 0;
if (hasParams || getFunctionalInterfaceReturnType(functionalInterfaceType) != PsiType.VOID) { //todo check that void lambdas without params check
return !dependsOnTypeParams(functionalInterfaceType, functionalInterfaceType, expression);
}
return true;
}
@Nullable
static MethodSignature getFunction(PsiClass psiClass) {
if (psiClass == null) return null;
final List<HierarchicalMethodSignature> functions = findFunctionCandidates(psiClass);
if (functions != null && functions.size() == 1) {
return functions.get(0);
}
return null;
}
private static boolean overridesPublicObjectMethod(PsiMethod psiMethod) {
boolean overrideObject = false;
for (PsiMethod superMethod : psiMethod.findDeepestSuperMethods()) {
final PsiClass containingClass = superMethod.getContainingClass();
if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) {
if (superMethod.hasModifierProperty(PsiModifier.PUBLIC)) {
overrideObject = true;
break;
}
}
}
return overrideObject;
}
private static MethodSignature getMethodSignature(PsiMethod method, PsiClass psiClass, PsiClass containingClass) {
final MethodSignature methodSignature;
if (containingClass != null && containingClass != psiClass) {
methodSignature = method.getSignature(TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY));
}
else {
methodSignature = method.getSignature(PsiSubstitutor.EMPTY);
}
return methodSignature;
}
@Nullable
private static List<HierarchicalMethodSignature> hasSubsignature(List<HierarchicalMethodSignature> signatures) {
for (HierarchicalMethodSignature signature : signatures) {
boolean subsignature = true;
for (HierarchicalMethodSignature methodSignature : signatures) {
if (!signature.equals(methodSignature)) {
if (!MethodSignatureUtil.isSubsignature(signature, methodSignature) &&
!skipMethod(signature, methodSignature)) {
subsignature = false;
break;
}
}
}
if (subsignature) return Collections.singletonList(signature);
}
return signatures;
}
private static boolean skipMethod(HierarchicalMethodSignature signature,
HierarchicalMethodSignature methodSignature) {
//not generic
if (methodSignature.getTypeParameters().length == 0) {
return false;
}
//foreign class
return signature.getMethod().getContainingClass() != methodSignature.getMethod().getContainingClass();
}
@Nullable
public static List<HierarchicalMethodSignature> findFunctionCandidates(PsiClass psiClass) {
if (psiClass != null && psiClass.isInterface() && !psiClass.isAnnotationType()) {
final List<HierarchicalMethodSignature> methods = new ArrayList<HierarchicalMethodSignature>();
final Collection<HierarchicalMethodSignature> visibleSignatures = psiClass.getVisibleSignatures();
for (HierarchicalMethodSignature signature : visibleSignatures) {
final PsiMethod psiMethod = signature.getMethod();
if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) continue;
if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) continue;
if (!overridesPublicObjectMethod(psiMethod)) {
methods.add(signature);
}
}
return hasSubsignature(methods);
}
return null;
}
@Nullable
private static PsiType getReturnType(PsiClass psiClass, MethodSignature methodSignature) {
final PsiMethod method = getMethod(psiClass, methodSignature);
if (method != null) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass == null) return null;
return TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY).substitute(method.getReturnType());
}
else {
return null;
}
}
@Nullable
private static PsiMethod getMethod(PsiClass psiClass, MethodSignature methodSignature) {
final PsiMethod[] methodsByName = psiClass.findMethodsByName(methodSignature.getName(), true);
for (PsiMethod psiMethod : methodsByName) {
if (MethodSignatureUtil
.areSignaturesEqual(getMethodSignature(psiMethod, psiClass, psiMethod.getContainingClass()), methodSignature)) {
return psiMethod;
}
}
return null;
}
public static int getLambdaIdx(PsiExpressionList expressionList, final PsiElement element) {
PsiExpression[] expressions = expressionList.getExpressions();
for (int i = 0; i < expressions.length; i++) {
PsiExpression expression = expressions[i];
if (PsiTreeUtil.isAncestor(expression, element, false)) {
return i;
}
}
return -1;
}
public static boolean dependsOnTypeParams(PsiType type,
PsiType functionalInterfaceType,
PsiElement lambdaExpression,
PsiTypeParameter... param2Check) {
return depends(type, new TypeParamsChecker(lambdaExpression,
PsiUtil.resolveClassInType(functionalInterfaceType)), param2Check);
}
public static boolean depends(PsiType type, TypeParamsChecker visitor, PsiTypeParameter... param2Check) {
if (!visitor.startedInference()) return false;
final Boolean accept = type.accept(visitor);
if (param2Check.length > 0) {
return visitor.used(param2Check);
}
return accept != null && accept.booleanValue();
}
@Nullable
public static PsiType getFunctionalInterfaceType(PsiElement expression, final boolean tryToSubstitute) {
PsiElement parent = expression.getParent();
PsiElement element = expression;
while (parent instanceof PsiParenthesizedExpression || parent instanceof PsiConditionalExpression) {
if (parent instanceof PsiConditionalExpression &&
((PsiConditionalExpression)parent).getThenExpression() != element &&
((PsiConditionalExpression)parent).getElseExpression() != element) break;
element = parent;
parent = parent.getParent();
}
if (parent instanceof PsiArrayInitializerExpression) {
final PsiType psiType = ((PsiArrayInitializerExpression)parent).getType();
if (psiType instanceof PsiArrayType) {
return ((PsiArrayType)psiType).getComponentType();
}
} else if (parent instanceof PsiTypeCastExpression) {
final PsiType castType = ((PsiTypeCastExpression)parent).getType();
if (castType instanceof PsiIntersectionType) {
final PsiType conjunct = extractFunctionalConjunct((PsiIntersectionType)castType);
if (conjunct != null) return conjunct;
}
return castType;
}
else if (parent instanceof PsiVariable) {
return ((PsiVariable)parent).getType();
}
else if (parent instanceof PsiAssignmentExpression && expression instanceof PsiExpression && !PsiUtil.isOnAssignmentLeftHand((PsiExpression)expression)) {
final PsiExpression lExpression = ((PsiAssignmentExpression)parent).getLExpression();
return lExpression.getType();
}
else if (parent instanceof PsiExpressionList) {
final PsiExpressionList expressionList = (PsiExpressionList)parent;
final int lambdaIdx = getLambdaIdx(expressionList, expression);
if (lambdaIdx > -1) {
PsiElement gParent = expressionList.getParent();
if (gParent instanceof PsiAnonymousClass) {
gParent = gParent.getParent();
}
if (gParent instanceof PsiCall) {
final PsiCall contextCall = (PsiCall)gParent;
final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(contextCall.getArgumentList());
if (properties != null && properties.isApplicabilityCheck()) { //todo simplification
final PsiParameter[] parameters = properties.getMethod().getParameterList().getParameters();
final int finalLambdaIdx = adjustLambdaIdx(lambdaIdx, properties.getMethod(), parameters);
if (finalLambdaIdx < parameters.length) {
return properties.getSubstitutor().substitute(getNormalizedType(parameters[finalLambdaIdx]));
}
}
final Map<PsiElement, PsiType> map = ourFunctionTypes.get();
if (map != null) {
final PsiType type = map.get(expression);
if (type != null) {
return type;
}
}
final JavaResolveResult resolveResult = contextCall.resolveMethodGenerics();
final PsiElement resolve = resolveResult.getElement();
if (resolve instanceof PsiMethod) {
final PsiParameter[] parameters = ((PsiMethod)resolve).getParameterList().getParameters();
final int finalLambdaIdx = adjustLambdaIdx(lambdaIdx, (PsiMethod)resolve, parameters);
if (finalLambdaIdx < parameters.length) {
if (!tryToSubstitute) return getNormalizedType(parameters[finalLambdaIdx]);
return PsiResolveHelper.ourGraphGuard.doPreventingRecursion(expression, true, new Computable<PsiType>() {
@Override
public PsiType compute() {
return resolveResult.getSubstitutor().substitute(getNormalizedType(parameters[finalLambdaIdx]));
}
});
}
}
return null;
}
}
}
else if (parent instanceof PsiReturnStatement) {
final PsiLambdaExpression gParent = PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class);
if (gParent != null) {
return getFunctionalInterfaceTypeByContainingLambda(gParent);
} else {
final PsiMethod method = PsiTreeUtil.getParentOfType(parent, PsiMethod.class);
if (method != null) {
return method.getReturnType();
}
}
}
else if (parent instanceof PsiLambdaExpression) {
return getFunctionalInterfaceTypeByContainingLambda((PsiLambdaExpression)parent);
}
return null;
}
@Nullable
private static PsiType extractFunctionalConjunct(PsiIntersectionType type) {
PsiType conjunct = null;
for (PsiType conjunctType : type.getConjuncts()) {
final PsiMethod interfaceMethod = getFunctionalInterfaceMethod(conjunctType);
if (interfaceMethod != null) {
if (conjunct != null && !conjunct.equals(conjunctType)) return null;
conjunct = conjunctType;
}
}
return conjunct;
}
private static PsiType getFunctionalInterfaceTypeByContainingLambda(@NotNull PsiLambdaExpression parentLambda) {
final PsiType parentInterfaceType = parentLambda.getFunctionalInterfaceType();
return parentInterfaceType != null ? getFunctionalInterfaceReturnType(parentInterfaceType) : null;
}
private static int adjustLambdaIdx(int lambdaIdx, PsiMethod resolve, PsiParameter[] parameters) {
final int finalLambdaIdx;
if (resolve.isVarArgs() && lambdaIdx >= parameters.length) {
finalLambdaIdx = parameters.length - 1;
} else {
finalLambdaIdx = lambdaIdx;
}
return finalLambdaIdx;
}
private static PsiType getNormalizedType(PsiParameter parameter) {
final PsiType type = parameter.getType();
if (type instanceof PsiEllipsisType) {
return ((PsiEllipsisType)type).getComponentType();
}
return type;
}
public static boolean notInferredType(PsiType typeByExpression) {
return typeByExpression instanceof PsiMethodReferenceType || typeByExpression instanceof PsiLambdaExpressionType || typeByExpression instanceof PsiLambdaParameterType;
}
public static List<PsiReturnStatement> getReturnStatements(PsiLambdaExpression lambdaExpression) {
final PsiElement body = lambdaExpression.getBody();
final List<PsiReturnStatement> result = new ArrayList<PsiReturnStatement>();
if (body != null) {
body.accept(new JavaRecursiveElementVisitor() {
@Override
public void visitReturnStatement(PsiReturnStatement statement) {
result.add(statement);
}
@Override
public void visitClass(PsiClass aClass) {
}
@Override
public void visitLambdaExpression(PsiLambdaExpression expression) {
}
});
}
return result;
}
public static List<PsiExpression> getReturnExpressions(PsiLambdaExpression lambdaExpression) {
final PsiElement body = lambdaExpression.getBody();
if (body instanceof PsiExpression) {
//if (((PsiExpression)body).getType() != PsiType.VOID) return Collections.emptyList();
return Collections.singletonList((PsiExpression)body);
}
final List<PsiExpression> result = new ArrayList<PsiExpression>();
for (PsiReturnStatement returnStatement : getReturnStatements(lambdaExpression)) {
final PsiExpression returnValue = returnStatement.getReturnValue();
if (returnValue != null) {
result.add(returnValue);
}
}
return result;
}
@Nullable
public static String checkFunctionalInterface(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel languageLevel) {
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && Comparing.strEqual(annotation.getQualifiedName(), CommonClassNames.JAVA_LANG_FUNCTIONAL_INTERFACE)) {
final PsiAnnotationOwner owner = annotation.getOwner();
if (owner instanceof PsiModifierList) {
final PsiElement parent = ((PsiModifierList)owner).getParent();
if (parent instanceof PsiClass) {
return LambdaHighlightingUtil.checkInterfaceFunctional((PsiClass)parent, ((PsiClass)parent).getName() + " is not a functional interface");
}
}
}
return null;
}
public static boolean isValidQualifier4InterfaceStaticMethodCall(@NotNull PsiMethod method,
@NotNull PsiReferenceExpression methodReferenceExpression,
@Nullable PsiElement scope, @NotNull LanguageLevel languageLevel) {
if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression();
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null && containingClass.isInterface() && method.hasModifierProperty(PsiModifier.STATIC)) {
return qualifierExpression == null && (scope instanceof PsiImportStaticStatement || PsiTreeUtil.isAncestor(containingClass, methodReferenceExpression, true))||
qualifierExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifierExpression).resolve() == containingClass;
}
}
return true;
}
//JLS 14.8 Expression Statements
public static boolean isExpressionStatementExpression(PsiElement body) {
return body instanceof PsiAssignmentExpression ||
body instanceof PsiPrefixExpression &&
(((PsiPrefixExpression)body).getOperationTokenType() == JavaTokenType.PLUSPLUS ||
((PsiPrefixExpression)body).getOperationTokenType() == JavaTokenType.MINUSMINUS) ||
body instanceof PsiPostfixExpression ||
body instanceof PsiCallExpression ||
body instanceof PsiReferenceExpression && !body.isPhysical();
}
public static class TypeParamsChecker extends PsiTypeVisitor<Boolean> {
private PsiMethod myMethod;
private final PsiClass myClass;
public final Set<PsiTypeParameter> myUsedTypeParams = new HashSet<PsiTypeParameter>();
public TypeParamsChecker(PsiElement expression, PsiClass aClass) {
myClass = aClass;
PsiElement parent = expression != null ? expression.getParent() : null;
while (parent instanceof PsiParenthesizedExpression) {
parent = parent.getParent();
}
if (parent instanceof PsiExpressionList) {
final PsiElement gParent = parent.getParent();
if (gParent instanceof PsiCall) {
final MethodCandidateInfo.CurrentCandidateProperties pair = MethodCandidateInfo.getCurrentMethod(parent);
myMethod = pair != null ? pair.getMethod() : null;
if (myMethod == null) {
myMethod = ((PsiCall)gParent).resolveMethod();
}
if (myMethod != null && PsiTreeUtil.isAncestor(myMethod, expression, false)) {
myMethod = null;
}
}
}
}
public boolean startedInference() {
return myMethod != null;
}
@Override
public Boolean visitClassType(PsiClassType classType) {
boolean used = false;
for (PsiType paramType : classType.getParameters()) {
final Boolean paramAccepted = paramType.accept(this);
used |= paramAccepted != null && paramAccepted.booleanValue();
}
final PsiClass resolve = classType.resolve();
if (resolve instanceof PsiTypeParameter) {
final PsiTypeParameter typeParameter = (PsiTypeParameter)resolve;
if (check(typeParameter)) {
myUsedTypeParams.add(typeParameter);
return true;
}
}
return used;
}
@Nullable
@Override
public Boolean visitWildcardType(PsiWildcardType wildcardType) {
final PsiType bound = wildcardType.getBound();
if (bound != null) return bound.accept(this);
return false;
}
@Nullable
@Override
public Boolean visitCapturedWildcardType(PsiCapturedWildcardType capturedWildcardType) {
return visitWildcardType(capturedWildcardType.getWildcard());
}
@Nullable
@Override
public Boolean visitLambdaExpressionType(PsiLambdaExpressionType lambdaExpressionType) {
return true;
}
@Nullable
@Override
public Boolean visitArrayType(PsiArrayType arrayType) {
return arrayType.getComponentType().accept(this);
}
@Override
public Boolean visitType(PsiType type) {
return false;
}
private boolean check(PsiTypeParameter check) {
final PsiTypeParameterListOwner owner = check.getOwner();
if (owner == myMethod) {
return true;
}
else if (owner == myClass) {
return true;
}
return false;
}
public boolean used(PsiTypeParameter... parameters) {
for (PsiTypeParameter parameter : parameters) {
if (myUsedTypeParams.contains(parameter)) return true;
}
return false;
}
}
}