@Contract("_, !null -> !null") public PsiSubstitutor findNestedSubstitutor( PsiElement arg, @Nullable PsiSubstitutor defaultSession) { InferenceSession session = myNestedSessions.get(PsiTreeUtil.getParentOfType(arg, PsiCall.class)); return session == null ? defaultSession : session.getInferenceSubstitution(); }
private static CompoundInitialState createState(InferenceSession topLevelSession) { final PsiSubstitutor topInferenceSubstitutor = replaceVariables(topLevelSession.getInferenceVariables()); final Map<PsiElement, InitialInferenceState> nestedStates = new LinkedHashMap<PsiElement, InitialInferenceState>(); final InferenceSessionContainer copy = new InferenceSessionContainer() { @Override public PsiSubstitutor findNestedSubstitutor( PsiElement arg, @Nullable PsiSubstitutor defaultSession) { // for the case foo(bar(a -> m())): top level inference won't touch lambda "a -> m()" // for the case foo(a -> bar(b -> m())): top level inference would go till nested lambda // "b -> m()" and the state from top level could be found here by "bar(b -> m())" // but proceeding with additional constraints from saved point would produce new // expression constraints with different inference variables (could be found in // myNestedSessions) // which won't be found in the system if we won't reject stored sessions in such cases final PsiSubstitutor substitutor = super.findNestedSubstitutor(arg, null); if (substitutor != null) { return substitutor; } final InitialInferenceState state = nestedStates.get(PsiTreeUtil.getParentOfType(arg, PsiCall.class)); if (state != null) { return state.getInferenceSubstitutor(); } return super.findNestedSubstitutor(arg, defaultSession); } }; final Map<PsiElement, InferenceSession> nestedSessions = topLevelSession.getInferenceSessionContainer().myNestedSessions; for (Map.Entry<PsiElement, InferenceSession> entry : nestedSessions.entrySet()) { nestedStates.put( entry.getKey(), entry .getValue() .createInitialState( copy, topLevelSession.getInferenceVariables(), topInferenceSubstitutor)); } PsiSubstitutor substitutor = PsiSubstitutor.EMPTY; for (InferenceVariable variable : topLevelSession.getInferenceVariables()) { final PsiType instantiation = variable.getInstantiation(); if (instantiation != PsiType.NULL) { final PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(topInferenceSubstitutor.substitute(variable)); if (psiClass instanceof InferenceVariable) { substitutor = substitutor.put((PsiTypeParameter) psiClass, instantiation); } } } return new CompoundInitialState(substitutor, nestedStates); }
void registerNestedSession( InferenceSession session, PsiType returnType, PsiExpression returnExpression) { final PsiSubstitutor callSession = findNestedSubstitutor(((PsiCallExpression) returnExpression).getArgumentList(), null); if (callSession == null) { final InferenceSession inferenceSession = ExpressionCompatibilityConstraint.reduceExpressionCompatibilityConstraint( session, returnExpression, returnType); if (inferenceSession != null && inferenceSession != session) { registerNestedSession(inferenceSession); session.propagateVariables( inferenceSession.getInferenceVariables(), inferenceSession.getRestoreNameSubstitution()); } } }
static PsiSubstitutor infer( @NotNull PsiTypeParameter[] typeParameters, @NotNull PsiParameter[] parameters, @NotNull PsiExpression[] arguments, @NotNull PsiSubstitutor partialSubstitutor, @NotNull final PsiElement parent, @NotNull final ParameterTypeInferencePolicy policy) { if (parent instanceof PsiCall) { final PsiExpressionList argumentList = ((PsiCall) parent).getArgumentList(); final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(argumentList); // overload resolution can't depend on outer call => should not traverse to top if (properties != null && !properties.isApplicabilityCheck() && // in order to to avoid caching of candidates's errors on parent (!) , so check for // overload resolution is left here // But overload resolution can depend on type of lambda parameter. As it can't depend on // lambda body, // traversing down would stop at lambda level and won't take into account overloaded // method !MethodCandidateInfo.ourOverloadGuard.currentStack().contains(argumentList)) { final PsiCall topLevelCall = PsiResolveHelper.ourGraphGuard.doPreventingRecursion( parent, false, new Computable<PsiCall>() { @Override public PsiCall compute() { if (parent instanceof PsiExpression && !PsiPolyExpressionUtil.isPolyExpression((PsiExpression) parent)) { return null; } return LambdaUtil.treeWalkUp(parent); } }); if (topLevelCall != null) { InferenceSession session; if (MethodCandidateInfo.isOverloadCheck() || !PsiDiamondType.ourDiamondGuard.currentStack().isEmpty() || LambdaUtil.isLambdaParameterCheck()) { session = startTopLevelInference(topLevelCall, policy); } else { session = CachedValuesManager.getCachedValue( topLevelCall, new CachedValueProvider<InferenceSession>() { @Nullable @Override public Result<InferenceSession> compute() { return new Result<InferenceSession>( startTopLevelInference(topLevelCall, policy), PsiModificationTracker.MODIFICATION_COUNT); } }); if (session != null) { // reject cached top level session if it was based on wrong candidate: check nested // session if candidate (it's type parameters) are the same // such situations are avoided when overload resolution is performed // (MethodCandidateInfo.isOverloadCheck above) // but situations when client code iterates through // PsiResolveHelper.getReferencedMethodCandidates or similar are impossible to guess final Map<PsiElement, InferenceSession> sessions = session.getInferenceSessionContainer().myNestedSessions; final InferenceSession childSession = sessions.get(parent); if (childSession != null) { for (PsiTypeParameter parameter : typeParameters) { if (!childSession .getInferenceSubstitution() .getSubstitutionMap() .containsKey(parameter)) { session = startTopLevelInference(topLevelCall, policy); break; } } } } } if (session != null) { final PsiSubstitutor childSubstitutor = inferNested( typeParameters, parameters, arguments, partialSubstitutor, (PsiCall) parent, policy, properties, session); if (childSubstitutor != null) return childSubstitutor; } else if (topLevelCall instanceof PsiMethodCallExpression) { return new InferenceSession( typeParameters, partialSubstitutor, parent.getManager(), parent, policy) .prepareSubstitution(); } } } } final InferenceSession inferenceSession = new InferenceSession( typeParameters, partialSubstitutor, parent.getManager(), parent, policy); inferenceSession.initExpressionConstraints(parameters, arguments, parent); return inferenceSession.infer(parameters, arguments, parent); }
public void registerNestedSession(InferenceSession session) { myNestedSessions.put(session.getContext(), session); myNestedSessions.putAll(session.getInferenceSessionContainer().myNestedSessions); }
private static PsiSubstitutor inferNested( final PsiTypeParameter[] typeParameters, @NotNull final PsiParameter[] parameters, @NotNull final PsiExpression[] arguments, final PsiSubstitutor partialSubstitutor, @NotNull final PsiCall parent, @NotNull final ParameterTypeInferencePolicy policy, final MethodCandidateInfo.CurrentCandidateProperties properties, final InferenceSession parentSession) { final CompoundInitialState compoundInitialState = createState(parentSession); InitialInferenceState initialInferenceState = compoundInitialState.getInitialState(parent); if (initialInferenceState != null) { final InferenceSession childSession = new InferenceSession(initialInferenceState); final List<String> errorMessages = parentSession.getIncompatibleErrorMessages(); if (errorMessages != null) { return childSession.prepareSubstitution(); } return childSession.collectAdditionalAndInfer( parameters, arguments, properties, compoundInitialState.getInitialSubstitutor()); } // we do not investigate lambda return expressions when lambda's return type is already inferred // (proper) // this way all calls from lambda's return expressions won't appear in nested sessions else { PsiElement gParent = PsiUtil.skipParenthesizedExprUp(parent.getParent()); // find the nearest parent which appears in the map and start inference with a provided target // type for a nested lambda while (true) { if (gParent instanceof PsiReturnStatement) { // process code block lambda final PsiElement returnContainer = gParent.getParent(); if (returnContainer instanceof PsiCodeBlock) { gParent = returnContainer.getParent(); } } if (gParent instanceof PsiLambdaExpression) { final PsiCall call = PsiTreeUtil.getParentOfType(gParent, PsiCall.class); if (call != null) { initialInferenceState = compoundInitialState.getInitialState(call); if (initialInferenceState != null) { final int idx = LambdaUtil.getLambdaIdx(call.getArgumentList(), gParent); final PsiMethod method = call.resolveMethod(); if (method != null && idx > -1) { final PsiType parameterType = PsiTypesUtil.getParameterType( method.getParameterList().getParameters(), idx, true); final PsiType parameterTypeInTermsOfSession = initialInferenceState.getInferenceSubstitutor().substitute(parameterType); final PsiType lambdaTargetType = compoundInitialState .getInitialSubstitutor() .substitute(parameterTypeInTermsOfSession); return LambdaUtil.performWithLambdaTargetType( (PsiLambdaExpression) gParent, lambdaTargetType, new Producer<PsiSubstitutor>() { @Nullable @Override public PsiSubstitutor produce() { if (call.equals(PsiTreeUtil.getParentOfType(parent, PsiCall.class, true))) { // parent was mentioned in the top inference session // just proceed with the target type final InferenceSession inferenceSession = new InferenceSession( typeParameters, partialSubstitutor, parent.getManager(), parent, policy); inferenceSession.initExpressionConstraints(parameters, arguments, parent); return inferenceSession.infer(parameters, arguments, parent); } // one of the grand parents were found in the top inference session // start from it as it is the top level call final InferenceSession sessionInsideLambda = startTopLevelInference(call, policy); return inferNested( typeParameters, parameters, arguments, partialSubstitutor, parent, policy, properties, sessionInsideLambda); } }); } } else { gParent = PsiUtil.skipParenthesizedExprUp(call.getParent()); continue; } } } break; } } return null; }