blob: b5e4188e1644babd0adf1e4100478acf8c904bb1 [file] [log] [blame]
/*
* 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.impl.source.resolve.graphInference.constraints;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.graphInference.FunctionalInterfaceParameterizationUtil;
import com.intellij.psi.impl.source.resolve.graphInference.InferenceSession;
import com.intellij.psi.impl.source.resolve.graphInference.PsiPolyExpressionUtil;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import java.util.List;
import java.util.Map;
/**
* User: anna
*/
public class PsiMethodReferenceCompatibilityConstraint implements ConstraintFormula {
private static final Logger LOG = Logger.getInstance("#" + PsiMethodReferenceCompatibilityConstraint.class.getName());
private final PsiMethodReferenceExpression myExpression;
private PsiType myT;
public PsiMethodReferenceCompatibilityConstraint(PsiMethodReferenceExpression expression, PsiType t) {
myExpression = expression;
myT = t;
}
@Override
public boolean reduce(InferenceSession session, List<ConstraintFormula> constraints) {
if (!LambdaUtil.isFunctionalType(myT)) {
return false;
}
final PsiType groundTargetType = FunctionalInterfaceParameterizationUtil.getGroundTargetType(myT);
final PsiClassType.ClassResolveResult classResolveResult = PsiUtil.resolveGenericsClassInType(groundTargetType);
final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(classResolveResult);
if (interfaceMethod == null) {
return false;
}
final PsiSubstitutor substitutor = LambdaUtil.getSubstitutor(interfaceMethod, classResolveResult);
final PsiParameter[] targetParameters = interfaceMethod.getParameterList().getParameters();
final PsiType interfaceMethodReturnType = interfaceMethod.getReturnType();
final PsiType returnType = substitutor.substitute(interfaceMethodReturnType);
final PsiType[] typeParameters = myExpression.getTypeParameters();
final PsiMethodReferenceUtil.QualifierResolveResult qualifierResolveResult = PsiMethodReferenceUtil.getQualifierResolveResult(myExpression);
if (!myExpression.isExact()) {
for (PsiParameter parameter : targetParameters) {
if (!session.isProperType(substitutor.substitute(parameter.getType()))) {
return false;
}
}
} else {
final PsiMember applicableMember = myExpression.getPotentiallyApplicableMember();
LOG.assertTrue(applicableMember != null);
final PsiClass applicableMemberContainingClass = applicableMember.getContainingClass();
final PsiClass containingClass = qualifierResolveResult.getContainingClass();
PsiSubstitutor psiSubstitutor = qualifierResolveResult.getSubstitutor();
psiSubstitutor = applicableMemberContainingClass == null || containingClass == null || myExpression.isConstructor()
? psiSubstitutor
: TypeConversionUtil.getSuperClassSubstitutor(applicableMemberContainingClass, containingClass, psiSubstitutor);
PsiType applicableMethodReturnType = applicableMember instanceof PsiMethod ? ((PsiMethod)applicableMember).getReturnType() : null;
int idx = 0;
for (PsiTypeParameter param : ((PsiTypeParameterListOwner)applicableMember).getTypeParameters()) {
if (idx < typeParameters.length) {
psiSubstitutor = psiSubstitutor.put(param, typeParameters[idx++]);
}
}
final PsiParameter[] parameters = applicableMember instanceof PsiMethod ? ((PsiMethod)applicableMember).getParameterList().getParameters() : PsiParameter.EMPTY_ARRAY;
if (targetParameters.length == parameters.length + 1) {
specialCase(session, constraints, substitutor, targetParameters, true);
for (int i = 1; i < targetParameters.length; i++) {
constraints.add(new TypeCompatibilityConstraint(session.substituteWithInferenceVariables(psiSubstitutor.substitute(parameters[i - 1].getType())),
substitutor.substitute(targetParameters[i].getType())));
}
} else if (targetParameters.length == parameters.length) {
for (int i = 0; i < targetParameters.length; i++) {
constraints.add(new TypeCompatibilityConstraint(session.substituteWithInferenceVariables(psiSubstitutor.substitute(parameters[i].getType())),
substitutor.substitute(targetParameters[i].getType())));
}
} else {
return false;
}
if (returnType != PsiType.VOID && returnType != null) {
if (applicableMethodReturnType == PsiType.VOID) {
return false;
}
if (applicableMethodReturnType != null) {
constraints.add(new TypeCompatibilityConstraint(returnType,
session.substituteWithInferenceVariables(psiSubstitutor.substitute(applicableMethodReturnType))));
}
else if (applicableMember instanceof PsiClass || applicableMember instanceof PsiMethod && ((PsiMethod)applicableMember).isConstructor()) {
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(applicableMember.getProject());
if (containingClass != null) {
final PsiType classType = session.substituteWithInferenceVariables(elementFactory.createType(containingClass, psiSubstitutor));
constraints.add(new TypeCompatibilityConstraint(returnType, classType));
}
}
}
return true;
}
final Map<PsiMethodReferenceExpression, PsiType> map = PsiMethodReferenceUtil.getFunctionalTypeMap();
final PsiType added = map.put(myExpression, session.startWithFreshVars(groundTargetType));
final JavaResolveResult resolve;
try {
resolve = myExpression.advancedResolve(true);
}
finally {
if (added == null) {
map.remove(myExpression);
}
}
final PsiElement element = resolve.getElement();
if (element == null) {
return false;
}
if (PsiType.VOID.equals(returnType) || returnType == null) {
return true;
}
if (element instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)element;
final PsiType referencedMethodReturnType;
final PsiClass containingClass = method.getContainingClass();
LOG.assertTrue(containingClass != null, method);
PsiClass qContainingClass = qualifierResolveResult.getContainingClass();
PsiSubstitutor psiSubstitutor = qualifierResolveResult.getSubstitutor();
if (qContainingClass != null) {
if ( PsiUtil.isRawSubstitutor(qContainingClass, psiSubstitutor)) {
psiSubstitutor = PsiSubstitutor.EMPTY;
}
if (qContainingClass.isInheritor(containingClass, true)) {
psiSubstitutor = TypeConversionUtil.getClassSubstitutor(containingClass, qContainingClass, PsiSubstitutor.EMPTY);
LOG.assertTrue(psiSubstitutor != null);
}
}
if (method.isConstructor()) {
referencedMethodReturnType = JavaPsiFacade.getElementFactory(method.getProject()).createType(containingClass, PsiSubstitutor.EMPTY);
}
else {
referencedMethodReturnType = method.getReturnType();
}
LOG.assertTrue(referencedMethodReturnType != null, method);
session.initBounds(myExpression, method.getTypeParameters());
if (!PsiTreeUtil.isContextAncestor(containingClass, myExpression, false) ||
PsiUtil.getEnclosingStaticElement(myExpression, containingClass) != null) {
session.initBounds(myExpression, containingClass.getTypeParameters());
}
//if i) the method reference elides NonWildTypeArguments,
// ii) the compile-time declaration is a generic method, and
// iii) the return type of the compile-time declaration mentions at least one of the method's type parameters;
if (typeParameters.length == 0 && method.getTypeParameters().length > 0) {
final PsiClass interfaceClass = classResolveResult.getElement();
LOG.assertTrue(interfaceClass != null);
if (PsiPolyExpressionUtil.mentionsTypeParameters(referencedMethodReturnType,
ContainerUtil.newHashSet(method.getTypeParameters()))) {
//the constraint reduces to the bound set B3 which would be used to determine the method reference's invocation type
//when targeting the return type of the function type, as defined in 18.5.2.
session.collectApplicabilityConstraints(myExpression, ((MethodCandidateInfo)resolve), groundTargetType);
session.registerReturnTypeConstraints(referencedMethodReturnType, returnType);
return true;
}
}
if (PsiType.VOID.equals(referencedMethodReturnType)) {
return false;
}
int idx = 0;
for (PsiTypeParameter param : method.getTypeParameters()) {
if (idx < typeParameters.length) {
psiSubstitutor = psiSubstitutor.put(param, typeParameters[idx++]);
}
}
final PsiParameter[] parameters = method.getParameterList().getParameters();
if (targetParameters.length == parameters.length + 1 && !method.isVarArgs() &&
PsiPolyExpressionUtil.mentionsTypeParameters(referencedMethodReturnType, ContainerUtil.newHashSet(containingClass.getTypeParameters()))) { //todo specification bug?
specialCase(session, constraints, substitutor, targetParameters, false);
}
constraints.add(new TypeCompatibilityConstraint(returnType, session.substituteWithInferenceVariables(psiSubstitutor.substitute(referencedMethodReturnType))));
}
return true;
}
private void specialCase(InferenceSession session,
List<ConstraintFormula> constraints,
PsiSubstitutor substitutor,
PsiParameter[] targetParameters,
boolean ignoreRaw) {
final PsiElement qualifier = myExpression.getQualifier();
PsiType qualifierType = null;
if (qualifier instanceof PsiTypeElement) {
qualifierType = ((PsiTypeElement)qualifier).getType();
final PsiClass qualifierClass = PsiUtil.resolveClassInType(qualifierType);
if (qualifierClass != null) {
qualifierType = JavaPsiFacade.getElementFactory(myExpression.getProject()).createType(qualifierClass, PsiSubstitutor.EMPTY);
}
}
else if (qualifier instanceof PsiExpression) {
qualifierType = ((PsiExpression)qualifier).getType();
if (qualifierType == null && qualifier instanceof PsiReferenceExpression) {
final JavaResolveResult resolveResult = ((PsiReferenceExpression)qualifier).advancedResolve(false);
final PsiElement res = resolveResult.getElement();
if (res instanceof PsiClass) {
PsiClass containingClass = (PsiClass)res;
final boolean isRawSubst = !ignoreRaw && !myExpression.isConstructor() && PsiUtil.isRawSubstitutor(containingClass, resolveResult.getSubstitutor());
qualifierType = JavaPsiFacade.getElementFactory(res.getProject()).createType(containingClass, isRawSubst ? PsiSubstitutor.EMPTY : resolveResult.getSubstitutor());
}
}
}
final PsiClass qualifierClass = PsiUtil.resolveClassInType(qualifierType);
if (qualifierClass != null) {
session.initBounds(myExpression, qualifierClass.getTypeParameters());
constraints.add(new StrictSubtypingConstraint(session.substituteWithInferenceVariables(qualifierType),
session.substituteWithInferenceVariables(substitutor.substitute(targetParameters[0].getType()))));
}
}
@Override
public void apply(PsiSubstitutor substitutor, boolean cache) {
myT = substitutor.substitute(myT);
}
@Override
public String toString() {
return myExpression.getText() + " -> " + myT.getPresentableText();
}
}