/* * Copyright 2000-2013 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.project.Project; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.util.*; import com.intellij.util.Function; // import com.sun.tools.javac.code.Kinds; // import com.sun.tools.javac.tree.TreeInfo; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.Map; /** * User: anna */ public class PsiMethodReferenceUtil { public static ThreadLocal<Map<PsiMethodReferenceExpression, PsiType>> ourRefs = new ThreadLocal<Map<PsiMethodReferenceExpression, PsiType>>(); public static final Logger LOG = Logger.getInstance("#" + PsiMethodReferenceUtil.class.getName()); public static boolean hasReceiver(PsiType[] parameterTypes, QualifierResolveResult qualifierResolveResult, PsiMethodReferenceExpression methodRef) { if (parameterTypes.length > 0 && isReceiverType(parameterTypes[0], qualifierResolveResult.getContainingClass(), qualifierResolveResult.getSubstitutor()) && isStaticallyReferenced(methodRef)) { return true; } return false; } public static String checkReturnType(PsiMethodReferenceExpression expression, JavaResolveResult result, PsiType functionalInterfaceType) { final QualifierResolveResult qualifierResolveResult = getQualifierResolveResult(expression); final PsiElement resolve = result.getElement(); if (resolve instanceof PsiMethod) { PsiSubstitutor subst = PsiSubstitutor.EMPTY; subst = subst.putAll(qualifierResolveResult.getSubstitutor()); subst = subst.putAll(result.getSubstitutor()); final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType); PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType(expression, expression, (PsiMethod)resolve, null, PsiUtil.getLanguageLevel(expression)); if (returnType == null) { returnType = ((PsiMethod)resolve).getReturnType(); } PsiType methodReturnType = subst.substitute(returnType); if (interfaceReturnType != null && interfaceReturnType != PsiType.VOID) { if (methodReturnType == null) { methodReturnType = JavaPsiFacade.getElementFactory(expression.getProject()).createType(((PsiMethod)resolve).getContainingClass(), subst); } if (!TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType, false)) { return "Bad return type in method reference: cannot convert " + methodReturnType.getCanonicalText() + " to " + interfaceReturnType.getCanonicalText(); } } } return null; } public static class QualifierResolveResult { private final PsiClass myContainingClass; private final PsiSubstitutor mySubstitutor; private final boolean myReferenceTypeQualified; public QualifierResolveResult(PsiClass containingClass, PsiSubstitutor substitutor, boolean referenceTypeQualified) { myContainingClass = containingClass; mySubstitutor = substitutor; myReferenceTypeQualified = referenceTypeQualified; } public PsiClass getContainingClass() { return myContainingClass; } public PsiSubstitutor getSubstitutor() { return mySubstitutor; } public boolean isReferenceTypeQualified() { return myReferenceTypeQualified; } } public static boolean isValidQualifier(PsiMethodReferenceExpression expression) { final PsiElement referenceNameElement = expression.getReferenceNameElement(); if (referenceNameElement instanceof PsiKeyword) { final PsiElement qualifier = expression.getQualifier(); if (qualifier instanceof PsiTypeElement) { return true; } if (qualifier instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifier).resolve() instanceof PsiClass) { return true; } } return false; } @NotNull public static QualifierResolveResult getQualifierResolveResult(@NotNull PsiMethodReferenceExpression methodReferenceExpression) { PsiClass containingClass = null; PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; final PsiExpression expression = methodReferenceExpression.getQualifierExpression(); if (expression != null) { final PsiType expressionType = getExpandedType(expression.getType(), expression); PsiClassType.ClassResolveResult result = PsiUtil.resolveGenericsClassInType(expressionType); containingClass = result.getElement(); if (containingClass != null) { substitutor = result.getSubstitutor(); } if (containingClass == null && expression instanceof PsiReferenceExpression) { final JavaResolveResult resolveResult = ((PsiReferenceExpression)expression).advancedResolve(false); final PsiElement resolve = resolveResult.getElement(); if (resolve instanceof PsiClass) { containingClass = (PsiClass)resolve; substitutor = resolveResult.getSubstitutor(); return new QualifierResolveResult(containingClass, substitutor, true); } } } else { final PsiTypeElement typeElement = methodReferenceExpression.getQualifierType(); if (typeElement != null) { PsiType type = getExpandedType(typeElement.getType(), typeElement); PsiClassType.ClassResolveResult result = PsiUtil.resolveGenericsClassInType(type); containingClass = result.getElement(); if (containingClass != null) { substitutor = result.getSubstitutor(); } } } return new QualifierResolveResult(containingClass, substitutor, false); } public static boolean isStaticallyReferenced(@NotNull PsiMethodReferenceExpression methodReferenceExpression) { final PsiExpression qualifierExpression = methodReferenceExpression.getQualifierExpression(); if (qualifierExpression != null) { return qualifierExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifierExpression).resolve() instanceof PsiClass; } return true; } public static boolean isAcceptable(@Nullable final PsiMethodReferenceExpression methodReferenceExpression, PsiType left) { if (methodReferenceExpression == null) return false; if (left instanceof PsiIntersectionType) { for (PsiType conjunct : ((PsiIntersectionType)left).getConjuncts()) { if (isAcceptable(methodReferenceExpression, conjunct)) return true; } return false; } Map<PsiMethodReferenceExpression, PsiType> map = ourRefs.get(); if (map == null) { map = new HashMap<PsiMethodReferenceExpression, PsiType>(); ourRefs.set(map); } final JavaResolveResult result; try { if (map.put(methodReferenceExpression, left) != null) { return false; } result = methodReferenceExpression.advancedResolve(false); } finally { map.remove(methodReferenceExpression); } final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(left); final PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); if (method != null) { final QualifierResolveResult qualifierResolveResult = getQualifierResolveResult(methodReferenceExpression); final PsiElement resolve = result.getElement(); if (resolve instanceof PsiMethod) { final MethodSignature signature1 = method.getSignature(LambdaUtil.getSubstitutor(method, resolveResult)); PsiSubstitutor subst = PsiSubstitutor.EMPTY; subst = subst.putAll(qualifierResolveResult.getSubstitutor()); subst = subst.putAll(result.getSubstitutor()); final MethodSignature signature2 = ((PsiMethod)resolve).getSignature(subst); final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(left); PsiType returnType = PsiTypesUtil.patchMethodGetClassReturnType(methodReferenceExpression, methodReferenceExpression, (PsiMethod)resolve, null, PsiUtil.getLanguageLevel(methodReferenceExpression)); if (returnType == null) { returnType = ((PsiMethod)resolve).getReturnType(); } PsiType methodReturnType = subst.substitute(returnType); if (interfaceReturnType != null && interfaceReturnType != PsiType.VOID) { if (methodReturnType == null) { methodReturnType = JavaPsiFacade.getElementFactory(methodReferenceExpression.getProject()).createType(((PsiMethod)resolve).getContainingClass(), subst); } //if (!TypeConversionUtil.isAssignable(interfaceReturnType, methodReturnType, false)) return false; } if (areAcceptable(signature1, signature2, qualifierResolveResult.getContainingClass(), qualifierResolveResult.getSubstitutor(), ((PsiMethod)resolve).isVarArgs())) return true; } else if (resolve instanceof PsiClass) { final PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(left); if (interfaceReturnType != null) { if (interfaceReturnType == PsiType.VOID) return true; final PsiParameter[] parameters = method.getParameterList().getParameters(); if (resolve == JavaPsiFacade.getElementFactory(resolve.getProject()).getArrayClass(PsiUtil.getLanguageLevel(resolve))) { if (!arrayCompatibleSignature(parameters, new Function<PsiParameter[], PsiType>() { @Override public PsiType fun(PsiParameter[] parameters) { return resolveResult.getSubstitutor().substitute(parameters[0].getType()); } })) return false; final PsiTypeParameter[] typeParameters = ((PsiClass)resolve).getTypeParameters(); if (typeParameters.length == 1) { final PsiType arrayComponentType = result.getSubstitutor().substitute(typeParameters[0]); return arrayComponentType != null && TypeConversionUtil.isAssignable(interfaceReturnType, arrayComponentType.createArrayType(), true); } return false; } final PsiClassType classType = JavaPsiFacade.getElementFactory(methodReferenceExpression.getProject()).createType((PsiClass)resolve, result.getSubstitutor()); if (TypeConversionUtil.isAssignable(interfaceReturnType, classType, !((PsiClass)resolve).hasTypeParameters())) { if (parameters.length == 0) return true; if (parameters.length == 1) { if (isReceiverType(resolveResult.getSubstitutor().substitute(parameters[0].getType()), qualifierResolveResult.getContainingClass(), qualifierResolveResult.getSubstitutor())) return true; } } } } } return false; } private static boolean isReceiverType(@Nullable PsiClass aClass, @Nullable PsiClass containingClass) { return InheritanceUtil.isInheritorOrSelf(aClass, containingClass, true); } public static boolean isReceiverType(PsiType receiverType, @Nullable PsiClass containingClass, PsiSubstitutor psiSubstitutor) { boolean arrayType = receiverType instanceof PsiArrayType; if (containingClass != null) { receiverType = getExpandedType(receiverType, containingClass); } final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(GenericsUtil.eliminateWildcards(receiverType)); final PsiClass receiverClass = resolveResult.getElement(); if (receiverClass != null && isReceiverType(receiverClass, containingClass)) { LOG.assertTrue(containingClass != null); return arrayType || resolveResult.getSubstitutor().equals(psiSubstitutor) || emptyOrRaw(containingClass, psiSubstitutor) || emptyOrRaw(receiverClass, resolveResult.getSubstitutor()); } return false; } private static boolean emptyOrRaw(PsiClass containingClass, PsiSubstitutor psiSubstitutor) { return PsiUtil.isRawSubstitutor(containingClass, psiSubstitutor) || (!containingClass.hasTypeParameters() && psiSubstitutor.getSubstitutionMap().isEmpty()); } public static boolean isReceiverType(PsiType functionalInterfaceType, PsiClass containingClass, @Nullable PsiMethod referencedMethod) { final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType); final MethodSignature function = LambdaUtil.getFunction(resolveResult.getElement()); if (function != null) { final int interfaceMethodParamsLength = function.getParameterTypes().length; if (interfaceMethodParamsLength > 0) { final PsiType firstParamType = resolveResult.getSubstitutor().substitute(function.getParameterTypes()[0]); boolean isReceiver = isReceiverType(firstParamType, containingClass, PsiUtil.resolveGenericsClassInType(firstParamType).getSubstitutor()); if (isReceiver) { if (referencedMethod == null){ if (interfaceMethodParamsLength == 1) return true; return false; } if (referencedMethod.getParameterList().getParametersCount() != interfaceMethodParamsLength - 1) { return false; } return true; } } } return false; } public static boolean areAcceptable(MethodSignature signature1, MethodSignature signature2, PsiClass psiClass, PsiSubstitutor psiSubstitutor, boolean isVarargs) { int offset = 0; final PsiType[] signatureParameterTypes1 = signature1.getParameterTypes(); final PsiType[] signatureParameterTypes2 = signature2.getParameterTypes(); if (signatureParameterTypes1.length != signatureParameterTypes2.length) { if (signatureParameterTypes1.length == signatureParameterTypes2.length + 1) { if (isReceiverType(signatureParameterTypes1[0], psiClass, psiSubstitutor)) { offset++; } else if (!isVarargs){ return false; } } else if (!isVarargs) { return false; } } final int min = Math.min(signatureParameterTypes2.length, signatureParameterTypes1.length); for (int i = 0; i < min; i++) { final PsiType type1 = GenericsUtil.eliminateWildcards(psiSubstitutor.substitute(signatureParameterTypes1[offset + i])); if (isVarargs && i == min - 1) { if (!(signatureParameterTypes2[i] instanceof PsiArrayType)) { return false; } if (!TypeConversionUtil.isAssignable(((PsiArrayType)signatureParameterTypes2[i]).getComponentType(), type1) && !TypeConversionUtil.isAssignable(signatureParameterTypes2[i], type1)) { return false; } } else { if (!TypeConversionUtil.isAssignable(signatureParameterTypes2[i], type1)) { return false; } } } return true; } public static boolean onArrayType(PsiClass containingClass, MethodSignature signature) { if (arrayCompatibleSignature(signature.getParameterTypes(), new Function<PsiType[], PsiType>() { @Override public PsiType fun(PsiType[] types) { return types[0]; } })) { if (containingClass != null) { final Project project = containingClass.getProject(); final LanguageLevel level = PsiUtil.getLanguageLevel(containingClass); return containingClass == JavaPsiFacade.getElementFactory(project).getArrayClass(level); } } return false; } private static <T> boolean arrayCompatibleSignature(T[] paramTypes, Function<T[], PsiType> fun) { if (paramTypes.length == 1) { final PsiType paramType = fun.fun(paramTypes); if (paramType != null && TypeConversionUtil.isAssignable(PsiType.INT, paramType)) return true; } return false; } private static PsiType getExpandedType(PsiType type, @NotNull PsiElement typeElement) { if (type instanceof PsiArrayType) { type = JavaPsiFacade.getElementFactory(typeElement.getProject()) .getArrayClassType(((PsiArrayType)type).getComponentType(), PsiUtil.getLanguageLevel(typeElement)); } return type; } public static String checkMethodReferenceContext(PsiMethodReferenceExpression methodRef) { final PsiElement resolve = methodRef.resolve(); if (resolve == null) return null; final PsiClass containingClass = resolve instanceof PsiMethod ? ((PsiMethod)resolve).getContainingClass() : (PsiClass)resolve; final boolean isStaticSelector = isStaticallyReferenced(methodRef); final PsiElement qualifier = methodRef.getQualifier(); boolean isMethodStatic = false; boolean receiverReferenced = false; boolean isConstructor = true; if (resolve instanceof PsiMethod) { final PsiMethod method = (PsiMethod)resolve; isMethodStatic = method.hasModifierProperty(PsiModifier.STATIC); isConstructor = method.isConstructor(); receiverReferenced = hasReceiver(methodRef, method); if (method.hasModifierProperty(PsiModifier.ABSTRACT) && qualifier instanceof PsiSuperExpression) { return "Abstract method '" + method.getName() + "' cannot be accessed directly"; } } if (!receiverReferenced && isStaticSelector && !isMethodStatic && !isConstructor) { return "Non-static method cannot be referenced from a static context"; } if (!receiverReferenced && !isStaticSelector && isMethodStatic) { return "Static method referenced through non-static qualifier"; } if (receiverReferenced && isStaticSelector && isMethodStatic && !isConstructor) { return "Static method referenced through receiver"; } if (isMethodStatic && isStaticSelector && qualifier instanceof PsiTypeElement) { final PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.getChildOfType(qualifier, PsiJavaCodeReferenceElement.class); if (referenceElement != null) { final PsiReferenceParameterList parameterList = referenceElement.getParameterList(); if (parameterList != null && parameterList.getTypeArguments().length > 0) { return "Parameterized qualifier on static method reference"; } } } if (isConstructor) { if (containingClass != null && PsiUtil.isInnerClass(containingClass) && containingClass.isPhysical()) { PsiClass outerClass = containingClass.getContainingClass(); if (outerClass != null && !InheritanceUtil.hasEnclosingInstanceInScope(outerClass, methodRef, true, false)) { return "An enclosing instance of type " + PsiFormatUtil.formatClass(outerClass, PsiFormatUtilBase.SHOW_NAME) + " is not in scope"; } } } return null; } public static boolean hasReceiver(@NotNull PsiMethodReferenceExpression methodRef, @NotNull PsiMethod method) { final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(methodRef.getFunctionalInterfaceType()); final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); final MethodSignature signature = interfaceMethod != null ? interfaceMethod.getSignature(LambdaUtil.getSubstitutor(interfaceMethod, resolveResult)) : null; LOG.assertTrue(signature != null); final PsiType[] parameterTypes = signature.getParameterTypes(); final QualifierResolveResult qualifierResolveResult = getQualifierResolveResult(methodRef); return method.getParameterList().getParametersCount() + 1 == parameterTypes.length && hasReceiver(parameterTypes, qualifierResolveResult, methodRef); } }