1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.util.registry.Registry;
6 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
7 import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
8 import com.intellij.psi.infos.MethodCandidateInfo;
9 import com.intellij.psi.util.*;
10 import com.intellij.util.Consumer;
11 import com.intellij.util.IncorrectOperationException;
12 import com.intellij.util.ObjectUtils;
13 import com.intellij.util.containers.ContainerUtil;
14 import org.jetbrains.annotations.Contract;
15 import org.jetbrains.annotations.NotNull;
16 import org.jetbrains.annotations.Nullable;
19 import java.util.function.Function;
20 import java.util.function.Supplier;
25 public class LambdaUtil {
26 private static final Logger LOG = Logger.getInstance(LambdaUtil.class);
29 public static PsiType getFunctionalInterfaceReturnType(PsiFunctionalExpression expr) {
30 return getFunctionalInterfaceReturnType(expr.getFunctionalInterfaceType());
34 public static PsiType getFunctionalInterfaceReturnType(@Nullable PsiType functionalInterfaceType) {
35 final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
36 final PsiClass psiClass = resolveResult.getElement();
37 if (psiClass != null) {
38 final MethodSignature methodSignature = getFunction(psiClass);
39 if (methodSignature != null) {
40 final PsiType returnType = getReturnType(psiClass, methodSignature);
41 return resolveResult.getSubstitutor().substitute(returnType);
47 @Contract("null -> null")
49 public static PsiMethod getFunctionalInterfaceMethod(@Nullable PsiType functionalInterfaceType) {
50 return getFunctionalInterfaceMethod(PsiUtil.resolveGenericsClassInType(functionalInterfaceType));
53 public static PsiMethod getFunctionalInterfaceMethod(@Nullable PsiElement element) {
54 if (element instanceof PsiFunctionalExpression) {
55 final PsiType samType = ((PsiFunctionalExpression)element).getFunctionalInterfaceType();
56 return getFunctionalInterfaceMethod(samType);
62 public static PsiMethod getFunctionalInterfaceMethod(@NotNull PsiClassType.ClassResolveResult result) {
63 return getFunctionalInterfaceMethod(result.getElement());
66 @Contract("null -> null")
68 public static PsiMethod getFunctionalInterfaceMethod(PsiClass aClass) {
69 final MethodSignature methodSignature = getFunction(aClass);
70 if (methodSignature != null) {
71 return getMethod(aClass, methodSignature);
76 public static PsiSubstitutor getSubstitutor(@NotNull PsiMethod method, @NotNull PsiClassType.ClassResolveResult resolveResult) {
77 final PsiClass derivedClass = resolveResult.getElement();
78 LOG.assertTrue(derivedClass != null);
80 final PsiClass methodContainingClass = method.getContainingClass();
81 LOG.assertTrue(methodContainingClass != null);
82 PsiSubstitutor initialSubst = resolveResult.getSubstitutor();
83 final PsiSubstitutor superClassSubstitutor =
84 TypeConversionUtil.getSuperClassSubstitutor(methodContainingClass, derivedClass, PsiSubstitutor.EMPTY);
85 for (PsiTypeParameter param : superClassSubstitutor.getSubstitutionMap().keySet()) {
86 initialSubst = initialSubst.put(param, initialSubst.substitute(superClassSubstitutor.substitute(param)));
91 public static boolean isFunctionalType(PsiType type) {
92 if (type instanceof PsiIntersectionType) {
93 return extractFunctionalConjunct((PsiIntersectionType)type) != null;
95 return isFunctionalClass(PsiUtil.resolveClassInClassTypeOnly(type));
98 @Contract("null -> false")
99 public static boolean isFunctionalClass(PsiClass aClass) {
100 if (aClass != null) {
101 if (aClass instanceof PsiTypeParameter) return false;
102 return getFunction(aClass) != null;
107 @Contract("null -> false")
108 public static boolean isValidLambdaContext(@Nullable PsiElement context) {
109 context = PsiUtil.skipParenthesizedExprUp(context);
110 if (isAssignmentOrInvocationContext(context) || context instanceof PsiTypeCastExpression) {
113 if (context instanceof PsiConditionalExpression) {
114 PsiElement parentContext = PsiUtil.skipParenthesizedExprUp(context.getParent());
115 if (isAssignmentOrInvocationContext(parentContext)) return true;
116 if (parentContext instanceof PsiConditionalExpression) {
117 return isValidLambdaContext(parentContext);
120 if (context instanceof PsiBreakStatement) {
121 PsiElement element = ((PsiBreakStatement)context).findExitedElement();
122 if (element instanceof PsiSwitchExpression) {
123 return isValidLambdaContext(element.getParent());
126 if (context instanceof PsiExpressionStatement) {
127 PsiElement parent = context.getParent();
128 if (parent instanceof PsiSwitchLabeledRuleStatement) {
129 PsiSwitchBlock switchBlock = ((PsiSwitchLabeledRuleStatement)parent).getEnclosingSwitchBlock();
130 if (switchBlock != null) {
131 return isValidLambdaContext(switchBlock.getParent());
138 @Contract("null -> false")
139 private static boolean isAssignmentOrInvocationContext(PsiElement context) {
140 return isAssignmentContext(context) || isInvocationContext(context);
143 private static boolean isInvocationContext(@Nullable PsiElement context) {
144 return context instanceof PsiExpressionList;
147 private static boolean isAssignmentContext(PsiElement context) {
148 return context instanceof PsiLambdaExpression ||
149 context instanceof PsiReturnStatement ||
150 context instanceof PsiAssignmentExpression ||
151 context instanceof PsiVariable && !withInferredType((PsiVariable)context) ||
152 context instanceof PsiArrayInitializerExpression;
155 private static boolean withInferredType(PsiVariable variable) {
156 PsiTypeElement typeElement = variable.getTypeElement();
157 return typeElement != null && typeElement.isInferredType();
160 @Contract("null -> null")
162 public static MethodSignature getFunction(final PsiClass psiClass) {
163 if (isPlainInterface(psiClass)) {
164 return CachedValuesManager.getCachedValue(psiClass, () -> CachedValueProvider.Result
165 .create(calcFunction(psiClass), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT));
170 private static boolean isPlainInterface(PsiClass psiClass) {
171 return psiClass != null && psiClass.isInterface() && !psiClass.isAnnotationType();
175 private static MethodSignature calcFunction(@NotNull PsiClass psiClass) {
176 if (hasManyOwnAbstractMethods(psiClass) || hasManyInheritedAbstractMethods(psiClass)) return null;
178 final List<HierarchicalMethodSignature> functions = findFunctionCandidates(psiClass);
179 return functions != null && functions.size() == 1 ? functions.get(0) : null;
182 private static boolean hasManyOwnAbstractMethods(@NotNull PsiClass psiClass) {
183 int abstractCount = 0;
184 for (PsiMethod method : psiClass.getMethods()) {
185 if (isDefinitelyAbstractInterfaceMethod(method) && ++abstractCount > 1) {
192 private static boolean isDefinitelyAbstractInterfaceMethod(PsiMethod method) {
193 return method.hasModifierProperty(PsiModifier.ABSTRACT) && !isPublicObjectMethod(method.getName());
196 private static boolean isPublicObjectMethod(String methodName) {
197 return "equals".equals(methodName) || "hashCode".equals(methodName) || "toString".equals(methodName);
200 private static boolean hasManyInheritedAbstractMethods(@NotNull PsiClass psiClass) {
201 final Set<String> abstractNames = new HashSet<>();
202 final Set<String> defaultNames = new HashSet<>();
203 InheritanceUtil.processSupers(psiClass, true, psiClass1 -> {
204 for (PsiMethod method : psiClass1.getMethods()) {
205 if (isDefinitelyAbstractInterfaceMethod(method)) {
206 abstractNames.add(method.getName());
208 else if (method.hasModifierProperty(PsiModifier.DEFAULT)) {
209 defaultNames.add(method.getName());
214 abstractNames.removeAll(defaultNames);
215 return abstractNames.size() > 1;
218 private static boolean overridesPublicObjectMethod(HierarchicalMethodSignature psiMethod) {
219 final List<HierarchicalMethodSignature> signatures = psiMethod.getSuperSignatures();
220 if (signatures.isEmpty()) {
221 final PsiMethod method = psiMethod.getMethod();
222 final PsiClass containingClass = method.getContainingClass();
223 if (containingClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) {
224 if (method.hasModifierProperty(PsiModifier.PUBLIC)) {
229 for (HierarchicalMethodSignature superMethod : signatures) {
230 if (overridesPublicObjectMethod(superMethod)) {
237 private static MethodSignature getMethodSignature(PsiMethod method, PsiClass psiClass, PsiClass containingClass) {
238 final MethodSignature methodSignature;
239 if (containingClass != null && containingClass != psiClass) {
240 methodSignature = method.getSignature(TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY));
243 methodSignature = method.getSignature(PsiSubstitutor.EMPTY);
245 return methodSignature;
249 private static List<HierarchicalMethodSignature> hasSubSignature(List<HierarchicalMethodSignature> signatures) {
250 for (HierarchicalMethodSignature signature : signatures) {
251 boolean subSignature = true;
252 for (HierarchicalMethodSignature methodSignature : signatures) {
253 if (!signature.equals(methodSignature) && !skipMethod(signature, methodSignature)) {
254 subSignature = false;
258 if (subSignature) return Collections.singletonList(signature);
263 private static boolean skipMethod(HierarchicalMethodSignature signature,
264 HierarchicalMethodSignature methodSignature) {
266 if (methodSignature.getTypeParameters().length == 0) {
270 return signature.getMethod().getContainingClass() != methodSignature.getMethod().getContainingClass();
273 @Contract("null -> null")
275 public static List<HierarchicalMethodSignature> findFunctionCandidates(@Nullable final PsiClass psiClass) {
276 if (!isPlainInterface(psiClass)) return null;
278 final List<HierarchicalMethodSignature> methods = new ArrayList<>();
279 final Map<MethodSignature, Set<PsiMethod>> overrideEquivalents = PsiSuperMethodUtil.collectOverrideEquivalents(psiClass);
280 final Collection<HierarchicalMethodSignature> visibleSignatures = psiClass.getVisibleSignatures();
281 for (HierarchicalMethodSignature signature : visibleSignatures) {
282 final PsiMethod psiMethod = signature.getMethod();
283 if (!psiMethod.hasModifierProperty(PsiModifier.ABSTRACT)) continue;
284 if (psiMethod.hasModifierProperty(PsiModifier.STATIC)) continue;
285 final Set<PsiMethod> equivalentMethods = overrideEquivalents.get(signature);
286 if (equivalentMethods != null && equivalentMethods.size() > 1) {
287 boolean hasNonAbstractOverrideEquivalent = false;
288 for (PsiMethod method : equivalentMethods) {
289 if (!method.hasModifierProperty(PsiModifier.ABSTRACT) && !MethodSignatureUtil.isSuperMethod(method, psiMethod)) {
290 hasNonAbstractOverrideEquivalent = true;
294 if (hasNonAbstractOverrideEquivalent) continue;
296 if (!overridesPublicObjectMethod(signature)) {
297 methods.add(signature);
301 return hasSubSignature(methods);
306 private static PsiType getReturnType(PsiClass psiClass, MethodSignature methodSignature) {
307 final PsiMethod method = getMethod(psiClass, methodSignature);
308 if (method != null) {
309 final PsiClass containingClass = method.getContainingClass();
310 if (containingClass == null) return null;
311 return TypeConversionUtil.getSuperClassSubstitutor(containingClass, psiClass, PsiSubstitutor.EMPTY).substitute(method.getReturnType());
319 private static PsiMethod getMethod(PsiClass psiClass, MethodSignature methodSignature) {
320 if (methodSignature instanceof MethodSignatureBackedByPsiMethod) {
321 return ((MethodSignatureBackedByPsiMethod)methodSignature).getMethod();
324 final PsiMethod[] methodsByName = psiClass.findMethodsByName(methodSignature.getName(), true);
325 for (PsiMethod psiMethod : methodsByName) {
326 if (MethodSignatureUtil
327 .areSignaturesEqual(getMethodSignature(psiMethod, psiClass, psiMethod.getContainingClass()), methodSignature)) {
334 public static int getLambdaIdx(PsiExpressionList expressionList, final PsiElement element) {
335 PsiExpression[] expressions = expressionList.getExpressions();
336 for (int i = 0; i < expressions.length; i++) {
337 PsiExpression expression = expressions[i];
338 if (PsiTreeUtil.isAncestor(expression, element, false)) {
346 public static PsiType getFunctionalInterfaceType(PsiElement expression, final boolean tryToSubstitute) {
347 PsiElement parent = expression.getParent();
348 PsiElement element = expression;
349 while (parent instanceof PsiParenthesizedExpression || parent instanceof PsiConditionalExpression) {
350 if (parent instanceof PsiConditionalExpression && ((PsiConditionalExpression)parent).getCondition() == element) {
351 return PsiType.BOOLEAN;
354 parent = parent.getParent();
357 PsiType type = ThreadLocalTypes.getElementType(expression);
358 if (type == null) type = ThreadLocalTypes.getElementType(element);
363 if (parent instanceof PsiArrayInitializerExpression) {
364 final PsiType psiType = ((PsiArrayInitializerExpression)parent).getType();
365 if (psiType instanceof PsiArrayType) {
366 return ((PsiArrayType)psiType).getComponentType();
369 else if (parent instanceof PsiTypeCastExpression) {
370 // Ensure no capture is performed to target type of cast expression, from 15.16 "Cast Expressions":
371 // Casts can be used to explicitly "tag" a lambda expression or a method reference expression with a particular target type.
372 // To provide an appropriate degree of flexibility, the target type may be a list of types denoting an intersection type,
373 // provided the intersection induces a functional interface (p9.8).
374 final PsiTypeElement castTypeElement = ((PsiTypeCastExpression)parent).getCastType();
375 final PsiType castType = castTypeElement != null ? castTypeElement.getType() : null;
376 if (castType instanceof PsiIntersectionType) {
377 final PsiType conjunct = extractFunctionalConjunct((PsiIntersectionType)castType);
378 if (conjunct != null) return conjunct;
382 else if (parent instanceof PsiVariable) {
383 return ((PsiVariable)parent).getType();
385 else if (parent instanceof PsiAssignmentExpression && expression instanceof PsiExpression && !PsiUtil.isOnAssignmentLeftHand((PsiExpression)expression)) {
386 final PsiExpression lExpression = ((PsiAssignmentExpression)parent).getLExpression();
387 return lExpression.getType();
389 else if (parent instanceof PsiExpressionList) {
390 final PsiExpressionList expressionList = (PsiExpressionList)parent;
391 final int lambdaIdx = getLambdaIdx(expressionList, expression);
392 if (lambdaIdx >= 0) {
393 PsiElement gParent = expressionList.getParent();
395 if (gParent instanceof PsiAnonymousClass) {
396 gParent = gParent.getParent();
399 if (gParent instanceof PsiCall) {
400 final PsiCall contextCall = (PsiCall)gParent;
401 LOG.assertTrue(!MethodCandidateInfo.isOverloadCheck(contextCall.getArgumentList()));
402 JavaResolveResult resolveResult = PsiDiamondType.getDiamondsAwareResolveResult(contextCall);
403 return getSubstitutedType(expression, tryToSubstitute, lambdaIdx, resolveResult);
407 else if (parent instanceof PsiReturnStatement) {
408 return PsiTypesUtil.getMethodReturnType(parent);
410 else if (parent instanceof PsiLambdaExpression) {
411 return getFunctionalInterfaceTypeByContainingLambda((PsiLambdaExpression)parent);
413 PsiSwitchExpression switchExpression = PsiTreeUtil.getParentOfType(element, PsiSwitchExpression.class);
414 if (switchExpression != null && PsiUtil.getSwitchResultExpressions(switchExpression).contains(element)) {
415 return getFunctionalInterfaceType(switchExpression, tryToSubstitute);
421 private static PsiType getSubstitutedType(PsiElement expression,
422 boolean tryToSubstitute,
424 final JavaResolveResult resolveResult) {
425 final PsiElement resolve = resolveResult.getElement();
426 if (resolve instanceof PsiMethod) {
427 final PsiParameter[] parameters = ((PsiMethod)resolve).getParameterList().getParameters();
428 final int finalLambdaIdx = adjustLambdaIdx(lambdaIdx, (PsiMethod)resolve, parameters);
429 if (finalLambdaIdx < parameters.length) {
430 if (!tryToSubstitute) return getNormalizedType(parameters[finalLambdaIdx]);
431 return PsiResolveHelper.ourGraphGuard.doPreventingRecursion(expression, !MethodCandidateInfo.isOverloadCheck(), () -> {
432 final PsiType normalizedType = getNormalizedType(parameters[finalLambdaIdx]);
433 if (resolveResult instanceof MethodCandidateInfo && ((MethodCandidateInfo)resolveResult).isRawSubstitution()) {
434 return TypeConversionUtil.erasure(normalizedType);
437 return resolveResult.getSubstitutor().substitute(normalizedType);
445 public static boolean processParentOverloads(PsiFunctionalExpression functionalExpression, final Consumer<? super PsiType> overloadProcessor) {
446 LOG.assertTrue(PsiTypesUtil.getExpectedTypeByParent(functionalExpression) == null);
447 PsiElement parent = functionalExpression.getParent();
448 PsiElement expr = functionalExpression;
449 while (parent instanceof PsiParenthesizedExpression || parent instanceof PsiConditionalExpression) {
450 if (parent instanceof PsiConditionalExpression &&
451 ((PsiConditionalExpression)parent).getThenExpression() != expr &&
452 ((PsiConditionalExpression)parent).getElseExpression() != expr) break;
454 parent = parent.getParent();
456 if (parent instanceof PsiExpressionList) {
457 final PsiExpressionList expressionList = (PsiExpressionList)parent;
458 final int lambdaIdx = getLambdaIdx(expressionList, functionalExpression);
459 if (lambdaIdx > -1) {
461 PsiElement gParent = expressionList.getParent();
463 if (gParent instanceof PsiAnonymousClass) {
464 gParent = gParent.getParent();
467 JavaResolveResult[] results = null;
468 if (gParent instanceof PsiMethodCallExpression) {
469 results = ((PsiMethodCallExpression)gParent).getMethodExpression().multiResolve(true);
471 else if (gParent instanceof PsiConstructorCall){
472 results = getConstructorCandidates((PsiConstructorCall)gParent);
475 if (results != null) {
476 final Set<PsiType> types = new HashSet<>();
477 for (JavaResolveResult result : results) {
478 final PsiType functionalExpressionType = MethodCandidateInfo.ourOverloadGuard.doPreventingRecursion(functionalExpression, false, () -> getSubstitutedType(functionalExpression, true, lambdaIdx, result));
479 if (functionalExpressionType != null && types.add(functionalExpressionType)) {
480 overloadProcessor.consume(functionalExpressionType);
491 private static JavaResolveResult[] getConstructorCandidates(PsiConstructorCall parentCall) {
492 final JavaPsiFacade facade = JavaPsiFacade.getInstance(parentCall.getProject());
493 PsiExpressionList argumentList = parentCall.getArgumentList();
494 if (argumentList != null) {
495 PsiClassType classType = null;
496 if (parentCall instanceof PsiNewExpression) {
497 PsiJavaCodeReferenceElement ref = ((PsiNewExpression)parentCall).getClassReference();
498 classType = ref != null ? facade.getElementFactory().createType(ref) : null;
500 else if (parentCall instanceof PsiEnumConstant) {
501 PsiClass containingClass = ((PsiEnumConstant)parentCall).getContainingClass();
502 classType = containingClass != null ? facade.getElementFactory().createType(containingClass) : null;
505 if (classType != null) {
506 return facade.getResolveHelper().multiResolveConstructor(classType, argumentList, parentCall);
513 private static PsiType extractFunctionalConjunct(PsiIntersectionType type) {
514 PsiType conjunct = null;
515 MethodSignature commonSignature = null;
516 for (PsiType psiType : type.getConjuncts()) {
517 PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(psiType);
518 if (aClass instanceof PsiTypeParameter) continue;
519 MethodSignature signature = getFunction(aClass);
520 if (signature == null) continue;
521 if (commonSignature == null) {
522 commonSignature = signature;
524 else if (!MethodSignatureUtil.areSignaturesEqual(commonSignature, signature)) {
533 private static PsiType getFunctionalInterfaceTypeByContainingLambda(@NotNull PsiLambdaExpression parentLambda) {
534 final PsiType parentInterfaceType = parentLambda.getFunctionalInterfaceType();
535 return parentInterfaceType != null ? getFunctionalInterfaceReturnType(parentInterfaceType) : null;
538 private static int adjustLambdaIdx(int lambdaIdx, PsiMethod resolve, PsiParameter[] parameters) {
539 final int finalLambdaIdx;
540 if (resolve.isVarArgs() && lambdaIdx >= parameters.length) {
541 finalLambdaIdx = parameters.length - 1;
543 finalLambdaIdx = lambdaIdx;
545 return finalLambdaIdx;
548 private static PsiType getNormalizedType(PsiParameter parameter) {
549 final PsiType type = parameter.getType();
550 if (type instanceof PsiEllipsisType) {
551 return ((PsiEllipsisType)type).getComponentType();
556 @Contract(value = "null -> false", pure = true)
557 public static boolean notInferredType(PsiType typeByExpression) {
558 return typeByExpression instanceof PsiMethodReferenceType ||
559 typeByExpression instanceof PsiLambdaExpressionType ||
560 typeByExpression instanceof PsiLambdaParameterType;
564 public static PsiReturnStatement[] getReturnStatements(PsiLambdaExpression lambdaExpression) {
565 final PsiElement body = lambdaExpression.getBody();
566 return body instanceof PsiCodeBlock ? PsiUtil.findReturnStatements((PsiCodeBlock)body) : PsiReturnStatement.EMPTY_ARRAY;
569 public static List<PsiExpression> getReturnExpressions(PsiLambdaExpression lambdaExpression) {
570 final PsiElement body = lambdaExpression.getBody();
571 if (body instanceof PsiExpression) {
572 //if (((PsiExpression)body).getType() != PsiType.VOID) return Collections.emptyList();
573 return Collections.singletonList((PsiExpression)body);
575 final List<PsiExpression> result = new ArrayList<>();
576 for (PsiReturnStatement returnStatement : getReturnStatements(lambdaExpression)) {
577 final PsiExpression returnValue = returnStatement.getReturnValue();
578 if (returnValue != null) {
579 result.add(returnValue);
585 //JLS 14.8 Expression Statements
586 @Contract("null -> false")
587 public static boolean isExpressionStatementExpression(PsiElement body) {
588 return body instanceof PsiAssignmentExpression ||
589 PsiUtil.isIncrementDecrementOperation(body) ||
590 body instanceof PsiMethodCallExpression || //method invocation
591 body instanceof PsiNewExpression && !((PsiNewExpression)body).isArrayCreation() || //class instance creation
592 body instanceof PsiReferenceExpression && !body.isPhysical();
595 public static PsiExpression extractSingleExpressionFromBody(PsiElement body) {
596 PsiExpression expression = null;
597 if (body instanceof PsiExpression) {
598 expression = (PsiExpression)body;
600 else if (body instanceof PsiCodeBlock) {
601 final PsiStatement[] statements = ((PsiCodeBlock)body).getStatements();
602 if (statements.length == 1) {
603 if (statements[0] instanceof PsiReturnStatement) {
604 expression = ((PsiReturnStatement)statements[0]).getReturnValue();
606 else if (statements[0] instanceof PsiExpressionStatement) {
607 expression = ((PsiExpressionStatement)statements[0]).getExpression();
609 else if (statements[0] instanceof PsiBlockStatement) {
610 return extractSingleExpressionFromBody(((PsiBlockStatement)statements[0]).getCodeBlock());
614 else if (body instanceof PsiBlockStatement) {
615 return extractSingleExpressionFromBody(((PsiBlockStatement)body).getCodeBlock());
617 else if (body instanceof PsiExpressionStatement) {
618 expression = ((PsiExpressionStatement)body).getExpression();
623 // http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.1
624 // A lambda expression or a method reference expression is potentially compatible with a type variable
625 // if the type variable is a type parameter of the candidate method.
626 public static boolean isPotentiallyCompatibleWithTypeParameter(PsiFunctionalExpression expression,
627 PsiExpressionList argsList,
629 if (!Registry.is("JDK8042508.bug.fixed", false)) {
630 final PsiCallExpression callExpression = PsiTreeUtil.getParentOfType(argsList, PsiCallExpression.class);
631 if (callExpression == null || callExpression.getTypeArguments().length > 0) {
636 final int lambdaIdx = getLambdaIdx(argsList, expression);
637 if (lambdaIdx >= 0) {
638 final PsiParameter[] parameters = method.getParameterList().getParameters();
639 final PsiParameter lambdaParameter = parameters[Math.min(lambdaIdx, parameters.length - 1)];
640 final PsiClass paramClass = PsiUtil.resolveClassInType(lambdaParameter.getType());
641 if (paramClass instanceof PsiTypeParameter && ((PsiTypeParameter)paramClass).getOwner() == method) {
648 public static Map<PsiElement, String> checkReturnTypeCompatible(PsiLambdaExpression lambdaExpression, PsiType functionalInterfaceReturnType) {
649 Map<PsiElement, String> errors = new LinkedHashMap<>();
650 if (PsiType.VOID.equals(functionalInterfaceReturnType)) {
651 final PsiElement body = lambdaExpression.getBody();
652 if (body instanceof PsiCodeBlock) {
653 for (PsiExpression expression : getReturnExpressions(lambdaExpression)) {
654 errors.put(expression, "Unexpected return value");
657 else if (body instanceof PsiExpression) {
658 final PsiType type = ((PsiExpression)body).getType();
660 if (!PsiUtil.isStatement(JavaPsiFacade.getElementFactory(body.getProject()).createStatementFromText(body.getText(), body))) {
661 if (PsiType.VOID.equals(type)) {
662 errors.put(body, "Lambda body must be a statement expression");
665 errors.put(body, "Bad return type in lambda expression: " + (type == PsiType.NULL || type == null ? "<null>" : type.getPresentableText()) + " cannot be converted to void");
669 catch (IncorrectOperationException ignore) {
673 else if (functionalInterfaceReturnType != null) {
674 final List<PsiExpression> returnExpressions = getReturnExpressions(lambdaExpression);
675 for (final PsiExpression expression : returnExpressions) {
676 final PsiType expressionType = PsiResolveHelper.ourGraphGuard.doPreventingRecursion(expression, true, expression::getType);
677 if (expressionType != null && !functionalInterfaceReturnType.isAssignableFrom(expressionType)) {
678 errors.put(expression, "Bad return type in lambda expression: " + expressionType.getPresentableText() + " cannot be converted to " + functionalInterfaceReturnType.getPresentableText());
681 final PsiReturnStatement[] returnStatements = getReturnStatements(lambdaExpression);
682 if (returnStatements.length > returnExpressions.size()) {
683 for (PsiReturnStatement statement : returnStatements) {
684 final PsiExpression value = statement.getReturnValue();
686 errors.put(statement, "Missing return value");
690 else if (returnExpressions.isEmpty() && !lambdaExpression.isVoidCompatible()) {
691 errors.put(lambdaExpression, "Missing return value");
694 return errors.isEmpty() ? null : errors;
698 public static PsiType getLambdaParameterFromType(PsiType functionalInterfaceType, int parameterIndex) {
699 final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(functionalInterfaceType);
700 final PsiMethod method = getFunctionalInterfaceMethod(functionalInterfaceType);
701 if (method != null) {
702 final PsiParameter[] parameters = method.getParameterList().getParameters();
703 if (parameterIndex < parameters.length) {
704 return getSubstitutor(method, resolveResult).substitute(parameters[parameterIndex].getType());
711 public static PsiCall treeWalkUp(PsiElement context) {
713 PsiElement parent = PsiTreeUtil.getParentOfType(context,
714 PsiExpressionList.class,
715 PsiLambdaExpression.class,
716 PsiConditionalExpression.class,
717 PsiSwitchExpression.class,
718 PsiAssignmentExpression.class,
722 if (parent instanceof PsiCall || parent instanceof PsiAssignmentExpression) {
726 final PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class);
727 if (parent instanceof PsiCodeBlock) {
728 if (lambdaExpression == null) {
732 boolean inReturnExpressions = false;
733 for (PsiExpression expression : getReturnExpressions(lambdaExpression)) {
734 inReturnExpressions |= PsiTreeUtil.isAncestor(expression, context, false);
737 if (!inReturnExpressions) {
741 if (ThreadLocalTypes.hasBindingFor(lambdaExpression)) {
747 if ((parent instanceof PsiConditionalExpression || parent instanceof PsiSwitchExpression) && !PsiPolyExpressionUtil.isPolyExpression((PsiExpression)parent)) {
751 if (parent instanceof PsiLambdaExpression && ThreadLocalTypes.hasBindingFor(parent)) {
755 final PsiCall psiCall = PsiTreeUtil.getParentOfType(parent, PsiCall.class, false, PsiMember.class, PsiVariable.class);
756 if (psiCall == null) {
759 if (MethodCandidateInfo.isOverloadCheck(psiCall.getArgumentList()) ||
760 lambdaExpression != null && ThreadLocalTypes.hasBindingFor(lambdaExpression)) {
765 if (top instanceof PsiExpression && PsiPolyExpressionUtil.isPolyExpression((PsiExpression)top)) {
766 parent = PsiTreeUtil.getParentOfType(parent.getParent(), PsiExpressionList.class, PsiLambdaExpression.class, PsiAssignmentExpression.class, PsiCodeBlock.class);
777 final PsiExpressionList argumentList = top.getArgumentList();
778 if (argumentList == null) {
782 LOG.assertTrue(!MethodCandidateInfo.isOverloadCheck(argumentList));
786 public static PsiCall copyTopLevelCall(@NotNull PsiCall call) {
787 if (call instanceof PsiEnumConstant) {
788 PsiClass containingClass = ((PsiEnumConstant)call).getContainingClass();
789 if (containingClass == null) {
792 String enumName = containingClass.getName();
793 if (enumName == null) {
796 PsiMethod resolveMethod = call.resolveMethod();
797 if (resolveMethod == null) {
800 PsiElement contextFile = call.getContainingFile().copy();
801 PsiClass anEnum = (PsiClass)contextFile.add(JavaPsiFacade.getElementFactory(call.getProject()).createEnum(enumName));
802 anEnum.add(resolveMethod);
803 return (PsiCall)anEnum.add(call);
805 PsiElement expressionForType = call;
807 PsiElement parent = PsiUtil.skipParenthesizedExprUp(expressionForType.getParent());
808 if (!(parent instanceof PsiConditionalExpression) ||
809 PsiTreeUtil.isAncestor(((PsiConditionalExpression)parent).getCondition(), expressionForType, false)) {
812 expressionForType = parent;
814 PsiType type = PsiTypesUtil.getExpectedTypeByParent(expressionForType);
815 if (type != null && PsiTypesUtil.isDenotableType(type, call)) {
816 return (PsiCall)copyWithExpectedType(call, type);
818 return (PsiCall)call.copy();
821 public static <T> T performWithSubstitutedParameterBounds(final PsiTypeParameter[] typeParameters,
822 final PsiSubstitutor substitutor,
823 final Supplier<? extends T> producer) {
824 return ThreadLocalTypes.performWithTypes(map -> {
825 for (PsiTypeParameter parameter : typeParameters) {
826 final PsiClassType[] types = parameter.getExtendsListTypes();
827 if (types.length > 0) {
828 final List<PsiType> conjuncts = ContainerUtil.map(types, substitutor::substitute);
829 //don't glb to avoid flattening = Object&Interface would be preserved
830 //otherwise methods with different signatures could get same erasure
831 final PsiType upperBound = PsiIntersectionType.createIntersection(false, conjuncts.toArray(PsiType.EMPTY_ARRAY));
832 map.forceType(parameter, upperBound);
835 return producer.get();
839 public static <T> T performWithTargetType(@NotNull PsiElement element, @NotNull PsiType targetType, @NotNull Supplier<? extends T> producer) {
840 return ThreadLocalTypes.performWithTypes(types -> {
841 types.forceType(element, targetType);
842 return producer.get();
847 * Generate lambda text for single argument expression lambda
849 * @param variable lambda sole argument
850 * @param expression lambda body (expression)
851 * @return lambda text
853 public static String createLambda(@NotNull PsiVariable variable, @NotNull PsiExpression expression) {
854 return variable.getName() + " -> " + expression.getText();
858 * Returns true if lambda has single parameter and its return value is the same as parameter.
861 * The lambdas like this are considered identity lambda: {@code x -> x}, {@code x -> {return x;}}
862 * {@code (String x) -> (x)}, etc.</p>
865 * This method does not check the lambda type, also it does not check whether auto-(un)boxing occurs,
866 * so a lambda like {@code ((Predicate<Boolean>)b -> b)} is also identity lambda even though it performs
870 * @param lambda a lambda to check
871 * @return true if the supplied lambda is an identity lambda
873 public static boolean isIdentityLambda(PsiLambdaExpression lambda) {
874 PsiParameterList parameters = lambda.getParameterList();
875 if (parameters.getParametersCount() != 1) return false;
876 PsiExpression expression = PsiUtil.skipParenthesizedExprDown(extractSingleExpressionFromBody(lambda.getBody()));
877 return expression instanceof PsiReferenceExpression &&
878 ((PsiReferenceExpression)expression).isReferenceTo(parameters.getParameters()[0]);
881 private static boolean isSafeLambdaReplacement(@NotNull PsiLambdaExpression lambda,
882 @NotNull Function<? super PsiLambdaExpression, ? extends PsiExpression> replacer) {
883 PsiElement body = lambda.getBody();
884 if (body == null) return false;
885 final PsiCall call = treeWalkUp(body);
887 JavaResolveResult result = call.resolveMethodGenerics();
888 PsiElement oldTarget = result.getElement();
889 if (oldTarget != null) {
890 String origErrorMessage = result instanceof MethodCandidateInfo ? ((MethodCandidateInfo)result).getInferenceErrorMessage() : null;
891 Object marker = new Object();
892 PsiTreeUtil.mark(lambda, marker);
893 PsiType origType = call instanceof PsiExpression ? ((PsiExpression)call).getType() : null;
894 PsiCall copyCall = copyTopLevelCall(call);
895 if (copyCall == null) return false;
896 PsiLambdaExpression lambdaCopy = ObjectUtils.tryCast(PsiTreeUtil.releaseMark(copyCall, marker), PsiLambdaExpression.class);
897 if (lambdaCopy == null) return false;
898 PsiExpression function = replacer.apply(lambdaCopy);
899 if (function == null) return false;
900 JavaResolveResult resultCopy = copyCall.resolveMethodGenerics();
901 if (resultCopy.getElement() != oldTarget) return false;
902 String copyMessage = resultCopy instanceof MethodCandidateInfo ? ((MethodCandidateInfo)resultCopy).getInferenceErrorMessage() : null;
903 if (!Objects.equals(origErrorMessage, copyMessage)) return false;
904 if (function instanceof PsiFunctionalExpression && ((PsiFunctionalExpression)function).getFunctionalInterfaceType() == null) {
907 PsiType newType = copyCall instanceof PsiExpression ? ((PsiExpression)copyCall).getType() : null;
908 if (origType instanceof PsiClassType && newType instanceof PsiClassType &&
909 ((PsiClassType)origType).isRaw() != ((PsiClassType)newType).isRaw()) {
918 * Returns false if after suggested replacement of lambda body, containing method call would resolve to something else
919 * or its return type will change.
921 * @param lambda a lambda whose body is going to be replaced
922 * @param newFunctionSupplier replacement for lambda to check,
923 * lazy computed for lambdas in invocation context only.
924 * Replacement evaluated to {@code null} is treated as invalid overload
926 public static boolean isSafeLambdaReplacement(@NotNull PsiLambdaExpression lambda, @NotNull Supplier<? extends PsiExpression> newFunctionSupplier) {
927 return isSafeLambdaReplacement(lambda, l -> {
928 PsiExpression replacement = newFunctionSupplier.get();
929 return replacement == null ? null : (PsiExpression)l.replace(replacement);
934 * Returns false if after suggested replacement of lambda body, containing method call would resolve to something else
935 * or its return type will change.
937 * @param lambda a lambda whose body is going to be replaced
938 * @param replacementText a text of new expression to replace lambda
940 public static boolean isSafeLambdaReplacement(@NotNull PsiLambdaExpression lambda, @NotNull String replacementText) {
941 return isSafeLambdaReplacement(lambda, () -> JavaPsiFacade.getElementFactory(lambda.getProject())
942 .createExpressionFromText(replacementText, lambda.getParent()));
946 * Returns false if after suggested replacement of lambda body, containing method call would resolve to something else
947 * or its return type will change.
950 * True will be returned for lambdas in non-invocation context as well as for lambdas in invocation context,
951 * when invoked method is not overloaded or all overloads are 'lambda friendly'
954 * Value-compatible lambda like {@code () -> foo() == true} can be converted to value-compatible AND void-compatible
955 * {@code () -> foo()} during simplification. This could lead to ambiguity during containing method call resolution and thus
956 * to the errors after applying the suggestion.
959 * @param lambda a lambda whose body is going to be replaced
960 * @param newBodySupplier replacement for lambda's body to check,
961 * lazy computed for lambdas in invocation context only.
962 * Replacement evaluated to {@code null} is treated as invalid overload
964 public static boolean isSafeLambdaBodyReplacement(@NotNull PsiLambdaExpression lambda, @NotNull Supplier<? extends PsiElement> newBodySupplier) {
965 return isSafeLambdaReplacement(lambda, l -> {
966 PsiElement oldBody = l.getBody();
967 PsiElement newBody = newBodySupplier.get();
968 if (oldBody == null || newBody == null) return null;
969 oldBody.replace(newBody);
975 * {@link #isSafeLambdaBodyReplacement(PsiLambdaExpression, Supplier)} overload to test the same lambda body,
976 * but with only return value {@code expression} changed to {@code replacement}
977 * @param lambdaReturnExpression a return expression inside lambda body
978 * @param replacement a replacement for return expression
980 public static boolean isSafeLambdaReturnValueReplacement(@NotNull PsiExpression lambdaReturnExpression,
981 @NotNull PsiExpression replacement) {
982 if (lambdaReturnExpression.getParent() instanceof PsiReturnStatement || lambdaReturnExpression.getParent() instanceof PsiLambdaExpression) {
983 PsiLambdaExpression lambdaExpression = PsiTreeUtil.getParentOfType(lambdaReturnExpression, PsiLambdaExpression.class, true, PsiMethod.class);
984 if (lambdaExpression != null &&
985 !isSafeLambdaBodyReplacement(lambdaExpression, () -> {
986 PsiLambdaExpression lambdaExpression1 = PsiTreeUtil.getParentOfType(lambdaReturnExpression, PsiLambdaExpression.class);
987 if (lambdaExpression1 == null) return null;
988 PsiElement body = lambdaExpression1.getBody();
989 if (body == null) return null;
990 Object marker = new Object();
991 PsiTreeUtil.mark(lambdaReturnExpression, marker);
992 PsiElement copy = body.copy();
993 PsiElement exprInReturn = PsiTreeUtil.releaseMark(copy, marker);
994 if (exprInReturn == null) return null;
995 if (exprInReturn == copy) {
996 return exprInReturn.replace(replacement);
998 exprInReturn.replace(replacement);
1007 public static PsiElement copyWithExpectedType(PsiElement expression, PsiType type) {
1008 String canonicalText = type.getCanonicalText();
1009 if (!PsiUtil.isLanguageLevel8OrHigher(expression)) {
1010 final String arrayInitializer = "new " + canonicalText + "[]{0}";
1011 PsiNewExpression newExpr = (PsiNewExpression)createExpressionFromText(arrayInitializer, expression);
1012 final PsiArrayInitializerExpression initializer = newExpr.getArrayInitializer();
1013 LOG.assertTrue(initializer != null);
1014 return initializer.getInitializers()[0].replace(expression);
1017 final String callableWithExpectedType = "(java.util.concurrent.Callable<" + canonicalText + ">)() -> x";
1018 PsiTypeCastExpression typeCastExpr = (PsiTypeCastExpression)createExpressionFromText(callableWithExpectedType, expression);
1019 PsiLambdaExpression lambdaExpression = (PsiLambdaExpression)typeCastExpr.getOperand();
1020 LOG.assertTrue(lambdaExpression != null);
1021 PsiElement body = lambdaExpression.getBody();
1022 LOG.assertTrue(body instanceof PsiExpression);
1023 return body.replace(expression);
1026 private static PsiExpression createExpressionFromText(String exprText, PsiElement context) {
1027 PsiExpression expr = JavaPsiFacade.getElementFactory(context.getProject())
1028 .createExpressionFromText(exprText, context);
1029 //ensure refs to inner classes are collapsed to avoid raw types (container type would be raw in qualified text)
1030 return (PsiExpression)JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(expr);
1034 * Returns true if given lambda captures any variable or "this" reference.
1035 * @param lambda lambda to check
1036 * @return true if given lambda captures any variable or "this" reference.
1038 public static boolean isCapturingLambda(PsiLambdaExpression lambda) {
1039 PsiElement body = lambda.getBody();
1040 if (body == null) return false;
1041 class CapturingLambdaVisitor extends JavaRecursiveElementWalkingVisitor {
1045 public void visitReferenceExpression(PsiReferenceExpression expression) {
1046 super.visitReferenceExpression(expression);
1047 if (expression instanceof PsiMethodReferenceExpression) return;
1048 if (expression.getParent() instanceof PsiMethodCallExpression && expression.getQualifierExpression() != null) return;
1049 PsiElement target = expression.resolve();
1050 // variable/parameter/field/method/class are always PsiModifierListOwners
1051 if (target instanceof PsiModifierListOwner &&
1052 !((PsiModifierListOwner)target).hasModifierProperty(PsiModifier.STATIC) &&
1053 !PsiTreeUtil.isAncestor(lambda, target, true)) {
1054 if (target instanceof PsiClass && ((PsiClass)target).getContainingClass() == null) return;
1061 public void visitSuperExpression(PsiSuperExpression expression) {
1067 public void visitThisExpression(PsiThisExpression expression) {
1072 CapturingLambdaVisitor visitor = new CapturingLambdaVisitor();
1073 body.accept(visitor);
1074 return visitor.capturing;
1078 * Resolves a functional interface class for given functional expression
1080 * @param expression functional expression
1081 * @return resolved class or null if cannot be resolved
1084 public static PsiClass resolveFunctionalInterfaceClass(@NotNull PsiFunctionalExpression expression) {
1085 // First try to avoid substitution
1086 PsiType type = expression.getGroundTargetType(getFunctionalInterfaceType(expression, false));
1087 PsiClass actualClass = PsiUtil.resolveClassInClassTypeOnly(type);
1088 if (actualClass instanceof PsiTypeParameter) {
1089 // Rare case when function is resolved to a type parameter: perform substitution then
1090 return PsiUtil.resolveClassInClassTypeOnly(expression.getFunctionalInterfaceType());