Introduce documentation for PCLA implementation
^KT-65637 Fixed
This commit is contained in:
committed by
Space Team
parent
41cc926953
commit
cabf7e4fae
+3
-1
@@ -25,7 +25,9 @@ import org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImp
|
||||
import org.jetbrains.kotlin.types.model.TypeConstructorMarker
|
||||
import org.jetbrains.kotlin.types.model.defaultType
|
||||
|
||||
|
||||
/**
|
||||
* @see [docs/fir/pcla.md]
|
||||
*/
|
||||
class FirPCLAInferenceSession(
|
||||
private val outerCandidate: Candidate,
|
||||
private val inferenceComponents: InferenceComponents,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
## FIR/Builder inference
|
||||
See also: [Kotlin Spec: Builder-style type inference](https://kotlinlang.org/spec/type-inference.html#builder-style-type-inference)
|
||||
|
||||
**NB:** This document is quite obsolete, see [pcla.md](pcla.md).
|
||||
|
||||
### Glossary
|
||||
#### Postponed type variable
|
||||
Type-variable, that was used to build a stub type on, and fixation of which is postponed until the end of lambda analysis.
|
||||
@@ -217,4 +219,4 @@ It leads to violation of contract in `org.jetbrains.kotlin.fir.resolve.inference
|
||||
To avoid such problem, we have [workaround for lambdas with Unit return-type](#Note-Incomplete-calls-in-return-arguments), but problem still
|
||||
occurs:
|
||||
- When we have [lambdas with non-Unit return-type](https://youtrack.jetbrains.com/issue/KT-58741)
|
||||
- When the [incomplete call is present in a return argument that isn't last expression](https://youtrack.jetbrains.com/issue/KT-58742)
|
||||
- When the [incomplete call is present in a return argument that isn't last expression](https://youtrack.jetbrains.com/issue/KT-58742)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
## FIR/Delegated property inference
|
||||
See also: [Kotlin Spec: Delegated property declaration](https://kotlinlang.org/spec/declarations.html#delegated-property-declaration) and some [Common inference terms definition](inference.md)
|
||||
|
||||
In many ways, delegated property inference works as a simpler version of PCLA, so it's worth beginning with [pcla.md](pcla.md).
|
||||
|
||||
### Glossary
|
||||
#### Delegation related operator calls
|
||||
Synthetically generated by Raw FIR builder calls to `getValue`, `setValue` or `provideDelegate` functions.
|
||||
@@ -8,12 +10,14 @@ Synthetically generated by Raw FIR builder calls to `getValue`, `setValue` or `p
|
||||
*NB:* They might be differentiated from regular calls by comparing origin to `FirFunctionCallOrigin.Operator`
|
||||
|
||||
#### Outer CS
|
||||
A constraint system that defines type variables with their constraints not brought by the call itself or its arguments.
|
||||
A constraint system that defines type variables with their constraints is not brought by the call itself or its arguments.
|
||||
The general idea is that before running resolution of a specific "inner" candidate, we copy all the outer variables to its own CS in the very
|
||||
beginning, and after some successful candidate is chosen, we apply the "relevant" changes to the outer CS.
|
||||
|
||||
By "apply" operation currently we mean literally replacement of the old CS with the new one.
|
||||
|
||||
See `FirDelegatedPropertyInferenceSession.parentConstraintSystem`.
|
||||
|
||||
### Desugaring at Raw FIR building stage
|
||||
The code like this
|
||||
```kotlin
|
||||
@@ -37,20 +41,28 @@ val prop = propertyNode {
|
||||
Even in case the property type is specified, all the content types are set as implicit.
|
||||
|
||||
### Delegated property inference algorithm
|
||||
#### Initiation
|
||||
At first, we set up a delegate inference session that is necessary to preserve the common CS and partially completed calls that need to be
|
||||
fully completed in the end.
|
||||
|
||||
For reference, see `org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.BodyResolveContext.forPropertyDelegateAccessors`.
|
||||
|
||||
#### Delegate expression inference
|
||||
At first, we resolve delegate expression with `ContextDependent.Delegate` resolution mode that behaves just like the regular `ContextDependent`
|
||||
but it makes DelegateInferenceSession to remember resulting incomplete CS from the call.
|
||||
At first, we resolve delegate expression with `Delegate` resolution mode that behaves just like the regular `ContextDependent`
|
||||
but it has some small differences (see usages of the relevant enum entry):
|
||||
- If the delegate expression is a lambda, we would resolve it as it was an independent expression
|
||||
- We treat it specially inside [PCLA](pcla.md).
|
||||
|
||||
Note that we do that outside delegate inference session (it's not created yet), so if delegated property inside a PCLA lambda, the delegate
|
||||
expression would be analyzed under PCLA session.
|
||||
|
||||
*NB:* If the delegate expression is simple enough, i.e., it does not contain type parameters, or they might be inferred from the call itself,
|
||||
we just run FULL completion on it as for regular `ContextDependent` and don't store its CS.
|
||||
|
||||
For reference, see `org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformWrappedDelegateExpression`.
|
||||
For reference, see `FirDeclarationsResolveTransformer.transformPropertyAccessorsWithDelegate`.
|
||||
|
||||
#### Session introduction
|
||||
After delegate expression is analyzed, we create `FirDelegatedPropertyInferenceSession` and use it as an inference session for resolving
|
||||
operator calls.
|
||||
|
||||
As an outer/parent CS we use either:
|
||||
- A constraint system of the delegate expression if it's a `FirResolvable` (under PCLA, it would have *shared CS*)
|
||||
- Otherwise, if delegate is some simple/non-call-like expression, obtain shared CS if PCLA is present, or just create the empty one
|
||||
|
||||
#### provideDelegate resolution
|
||||
After delegate expression is resolved, we start regular resolution of `provideDelegate()` call stored at `FirWrappedDelegateExpression::delegateProvider`.
|
||||
@@ -58,13 +70,13 @@ After delegate expression is resolved, we start regular resolution of `provideDe
|
||||
Note that as explicit receiver it uses just the same instance of `delegateExpression` we've just resolved on the previous step.
|
||||
As the receiver still might contain some type variables, we use CS of it as [Outer CS](#outer-cs) for all the `provideDelegate` candidates.
|
||||
|
||||
If there is no most specific successful candidate, then we just drop and forget the call.
|
||||
If there is no single most specific successful candidate, then we just drop and forget the call.
|
||||
|
||||
Otherwise, after the resulting candidate is chosen, it has some state of CS that contains both "inner" type variables of the candidate itself and some
|
||||
global ones brought by Outer CS.
|
||||
|
||||
Note that we don't force FULL completion (unlike K1 did), thus potentially leaving some of the type variables not fixed.
|
||||
The most problematic part with that approach is that in some cases the return type of `provideDelegate` is a type variable _and_ we
|
||||
The most problematic part with that approach is that in some cases the return type of `provideDelegate` is a type variable, _and_ we
|
||||
might need to look into its member scope to find `getValue` call there.
|
||||
|
||||
```kotlin
|
||||
@@ -140,3 +152,6 @@ Some of the final candidates might be obtained from member scopes of types with
|
||||
scopes with substituted type arguments.
|
||||
|
||||
See `FirCallCompletionResultsWriterTransformer.updateSubstitutedMemberIfReceiverContainsTypeVariable` for details.
|
||||
|
||||
#### Delegation inside PCLA
|
||||
See the relevant part inside the [document](pcla.md) on PCLA.
|
||||
|
||||
+55
-7
@@ -1,12 +1,60 @@
|
||||
## Inference
|
||||
# Inference
|
||||
|
||||
Currently, this document contains some basic terms that are common for different specific inference types.
|
||||
Lately, it might be extended to include some basic description of how inference works.
|
||||
|
||||
### Glossary
|
||||
#### CS = Constraint system
|
||||
An instance of `org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl`
|
||||
#### Call-tree
|
||||
## Glossary
|
||||
### TV = type variable
|
||||
To avoid confusion with type parameter types, we mostly use `Tv` for referencing type variable based on a type parameter named `T`
|
||||
### CS = Constraint system
|
||||
- Mostly, it’s just a collection of TVs and the constraints for them in a form of `Xi <: SomeType` or `SomeType <: Xi` or `Xi = SomeType`
|
||||
- Represented as an instance of `org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl`
|
||||
|
||||
### Related TVs
|
||||
|
||||
See `TypeVariableDependencyInformationProvider` for details.
|
||||
|
||||
#### Shallow relation
|
||||
Two variables `Xv` and `Yv` are shallowly related if there is some chain of a constraints
|
||||
`Xv ~ a_1 ~ .. ~ a_n ~ Yv` where (`~` is either `<:` or `>:`)
|
||||
|
||||
#### Deep relation
|
||||
Two variables `Xv` and `Yv` are deeply related if there is some chain of a constraints
|
||||
`a_1 ~ .. ~ a_n` where (`~` is either `<:` or `>:`) **and** `a_1` contains `Xv` while `a_n` contains `Yv`.
|
||||
|
||||
### Call-tree
|
||||
A tree of calls, in which constraint systems are joined and solved(completed) together
|
||||
#### Proper constraint
|
||||
A constraint that doesn't reference any type variables
|
||||
### Proper constraint
|
||||
A constraint that doesn't reference any type variables
|
||||
### Input types of a lambda
|
||||
- Receiver type
|
||||
- Value parameter types
|
||||
|
||||
**Completion** is a process that for a given call, its CS and postponed atoms (lambdas and callable references) tries to infer some TV and analyze some lambdas
|
||||
|
||||
**Completion mode**
|
||||
|
||||
Completion mode (`ConstraintSystemCompletionMode`) defines how actively/forcefully the given call is being completed
|
||||
* `PARTIAL` — used for calls that are other calls arguments
|
||||
* It prevents from fixing TV that are related somehow to return types because their actual result depends on the chosen outer call
|
||||
* Otherwise, it tries to infer as much as possible, but doesn’t lead to reporting not-inferred-type-parameter or running PCLA lambdas
|
||||
* On one hand, we might’ve not run that kind of completion at all, and just accumulate nested argument calls into an outer as is,
|
||||
* but inferring something allows disambiguating outer candidates
|
||||
* ```
|
||||
val x: Int = 1
|
||||
fun main() {
|
||||
x.plus(run { x })
|
||||
}
|
||||
```
|
||||
* `FULL` — used for top statement-level calls and receivers
|
||||
* It tries to fix/analyze everything we have
|
||||
* And start PCLA analysis if necessary
|
||||
* Reports errors if for some TV there’s no enough information to infer them
|
||||
* `UNTIL_FIRST_LAMBDA` — used for OverloadByLambdaReturnType
|
||||
* Similar to FULL, but stops after first lambda analyzed
|
||||
* `PCLA_POSTPONED_CALL` — see [pcla.md](pcla.md).
|
||||
|
||||
## Inference session
|
||||
|
||||
A set of callbacks related to inference that being called during function body transformations.
|
||||
See `FirInferenceSession`.
|
||||
|
||||
@@ -0,0 +1,532 @@
|
||||
# Partially Constrained Lambda Analysis
|
||||
|
||||
**Prerequisites:** That document is built under the assumption that the reader is more or less aware of how inference
|
||||
works for calls without lambdas or with lambdas for which all input-types are proper at the point where
|
||||
they should be analyzed. Also, some terms that are used here described at [Common inference terms definition](inference.md)
|
||||
|
||||
See also: [Kotlin Spec: Builder-style type inference](https://kotlinlang.org/spec/type-inference.html#builder-style-type-inference)
|
||||
|
||||
The most basic scenario where PCLA is used is calls to builder-like functions:
|
||||
|
||||
```kotlin
|
||||
fun <E> buildList(builderAction: MutableList<E>.() -> Unit): List<E> = ...
|
||||
|
||||
fun main() {
|
||||
buildList {
|
||||
add("")
|
||||
} // E is inferred to `String`
|
||||
}
|
||||
```
|
||||
|
||||
There in the `buildList` call there is no other source of information what type should be inferred for `E` beside the body of the lambda.
|
||||
That is the case where partially constrained lambda analysis (PCLA) starts.
|
||||
|
||||
## Glossary
|
||||
|
||||
**Regular lambda analysis**
|
||||
|
||||
Currently, we start lambda analysis only during call completion (`ConstraintSystemCompleter.runCompletion`).
|
||||
Let's call "regular lambda analysis" situation when we start body resolution of the anonymous function when all input types do not contain
|
||||
uninferred TVs.
|
||||
|
||||
**PCLA (Partially Constrained Lambda Analysis)**
|
||||
|
||||
PCLA is the way of lambda analysis during call completion that happens when some input types are not properly inferred, and there are no other sources of constraints.
|
||||
|
||||
**PCLA lambda** = lambda analyzed with PCLA
|
||||
|
||||
**Main call/candidate** is the call that contains PCLA lambda
|
||||
|
||||
**Nested call/candidate** is a call being resolved in other than `ContextDependent` mode (usually statement-level) that belongs to PCLA lambda
|
||||
* **Postponed nested calls** are nested calls which we decided not to complete in the FULL mode immediately
|
||||
|
||||
**Shared CS** is a constraint system being shared between postponed nested calls inside PCLA lambda
|
||||
|
||||
**Outer CS**
|
||||
* Defined only for nested postponed candidates (for shared CS)
|
||||
* It consists of type variables of the containing PCLA lambdas and all the type variables of already processed inner candidates
|
||||
* For inner CS, all the outer CS-related type variables are always coming at the beginning of the list of all variables
|
||||
|
||||
**Outer Type Variable (TV)**
|
||||
* In the context of *shared CS*, it's a subset of type variables that are brought from the *outer CS*
|
||||
* In the list of all variables (`NewConstraintSystemImpl.allTypeVariables`) outer TV always comes in the beginning.
|
||||
* See `NewConstraintSystemImpl.outerSystemVariablesPrefixSize`
|
||||
|
||||
## Entry-point to PCLA
|
||||
|
||||
The conditions to run a lambda resolution in PCLA mode is the following:
|
||||
- Completion mode for the current call tree is `FULL`
|
||||
- There are no other ways to infer some new constraints for any type variable.
|
||||
- There is some lambda that among its input types has at least one with a not fixed TV being used as type argument
|
||||
- So, if one of the input types is `MutableList<Ev>` that lambda suits for PCLA
|
||||
- But if some of the input types are proper and other are top-level type variables (`Tv`, `Tv?`, `Tv & Any`, etc.), it doesn't suit
|
||||
|
||||
**References in code:**
|
||||
- `ConstraintSystemCompleter.tryToCompleteWithPCLA`
|
||||
|
||||
During PCLA, there is a quite specific behavior of how actually lambda body resolution
|
||||
happens, and describing it is actually a goal of the following parts document.
|
||||
|
||||
## Algorithm backbone
|
||||
|
||||
The basic idea is that we mostly run regular lambda body analysis, with special treatment for all calls inside. Namely,
|
||||
- They use **shared CS**: adding their new variables there and constraints related to both *outer CS* and their own variables
|
||||
- They are not being fully completed (leaving them postponed)
|
||||
|
||||
After lambda's body traversal, the whole **shared CS** is added to the CS of the *main candidate*, the resulting CS should be resolved
|
||||
as usual, and after that at completion results writing phase all type variables are replaced with their result types.
|
||||
|
||||
### More details
|
||||
|
||||
* **NB:** This algorithm doesn't use any stub types, just regular type variables everywhere
|
||||
* Once we see a need to start PCLA (see [Entry-point to PCLA](#entry-point-to-pcla)), we create
|
||||
* `FirPCLAInferenceSession` (introduced at `LambdaAnalyzerImpl.analyzeAndGetLambdaReturnArguments`)
|
||||
* Shared CS (see `FirPCLAInferenceSession.currentCommonSystem` property)
|
||||
* It's mostly a copy of **outer CS** (the CS of the containing candidate), but we mark all the variables as *outer*
|
||||
* See `FirInferenceSession.Companion.prepareSharedBaseSystem`
|
||||
* After that, we run analysis inside the lambda
|
||||
* For nested candidates that are really trivial (no receivers with TV, no lambdas, no value arguments using outer CS), we just regularly complete them
|
||||
* For other calls
|
||||
* Instead of empty initial CS, we supply a **shared CS**
|
||||
* So, before introducing its own TVs, CS already contains of outer TV
|
||||
* Run candidate resolution within this CS
|
||||
* Do not run full completion, just gather all the constraints and variable back to the **shared CS**
|
||||
|
||||
* In the end, the **root CS** (aka CS for the Main candidate) contains TVs for all incomplete nested candidates.
|
||||
* And may just continue `FULL` completion process on the Main, i.e., fixing variables for which we now have new proper constraints,
|
||||
and analyze other lambdas.
|
||||
* In the end, beside completion results writing for the main call, we also write results (inferred type arguments/expression type update)
|
||||
for all incomplete/postponed calls (see `FirCallCompletionResultsWriterTransformer.runPCLARelatedTasksForCandidate`).
|
||||
|
||||
### Example 1
|
||||
|
||||
```
|
||||
fun foo() {}
|
||||
|
||||
fun main() {
|
||||
val x = listOf("")
|
||||
buildList /* Main candidate/call */ {
|
||||
foo() // Irrelevant call that doesn't refer PCLA variables, might be fully completed
|
||||
|
||||
// Uses shared CS
|
||||
// Adds String <: Ev constraint
|
||||
// Pushes all constraints back to the shared CS
|
||||
add("") // Postponed nested call
|
||||
|
||||
// Uses shared CS
|
||||
// Candidate declaration: fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C
|
||||
// Adds Tv, Rv, Cv type variables
|
||||
// And constraints
|
||||
// String <: Tv [from List<String> <: Iterable<Tv>]
|
||||
// MutableList<Ev> <: Cv
|
||||
// Rv <: Ev [incorporation from MutableList<Ev> <: MutableCollection<in Rv>
|
||||
// Fixing Tv := String
|
||||
// Starting lamda analysis
|
||||
// Having
|
||||
// Rv <: String constraint
|
||||
//
|
||||
// Then add all the new constraints to the shared CS
|
||||
x.mapTo(this) { it } // Postponed nested call
|
||||
}
|
||||
|
||||
// And start looking for CS solution that would be sound
|
||||
// Ev := String
|
||||
// Rv := String
|
||||
// Cv := MutableList<String>
|
||||
```
|
||||
|
||||
### Example 2
|
||||
```kotlin
|
||||
fun <A, B, C> twoSteps(first: Inv<A>.() -> Unit, b: B, second: (B) -> C): Triple<A, B, C> {
|
||||
val a = Inv<A>().apply(first)
|
||||
val c = second(value)
|
||||
return Triple(a, value, c)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
twoSteps(
|
||||
first = { set(1) },
|
||||
b = "b",
|
||||
second = { it }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Here, during the call completion of the call to `twoSteps` function we would:
|
||||
1. Fix `Bv <:> String`, from the `String <: Bv` constraint.
|
||||
2. Analyze `second` lambda, since it has enough type info, `Bv <:> String` is known now.
|
||||
- `first` lambda still has `Av` in its input types: `/* input type */ Inv<Av>.() -> /* output type */ Unit`
|
||||
- `second` lambda input types are known: `(/* input type */ Bv=String) -> /*output type*/ Cv`
|
||||
- Get additional type constraint `Cv <: String` from the usage of it
|
||||
3. Fix `Cv <:> String`, from the newly inferred lambda type info.
|
||||
4. Realize, that the `first` lambda still doesn't have enough type info and its
|
||||
input types are still unknown.
|
||||
5. tryToCompleteWithPCLA
|
||||
- Check if the `first` lambda type shape is suitable for PCLA
|
||||
- Analyze the `first` lambda in PCLA mode
|
||||
|
||||
### Shared CS definition
|
||||
|
||||
- It's been initialized at `FirPCLAInferenceSession.currentCommonSystem`.
|
||||
- Initially, it's an empty CS with the main candidate's system being added as an outer CS.
|
||||
|
||||
## Inference session callbacks
|
||||
|
||||
Currently, there are three implementations of inference session
|
||||
- `Default` that effectively does nothing
|
||||
- `FirPCLAInferenceSession` (being used during PCLA lambda resolution)
|
||||
- `FirDelegatedPropertyInferenceSession` (being used for delegated operator resolution)
|
||||
|
||||
### baseConstraintStorageForCandidate
|
||||
|
||||
It returns *shared CS* if the candidate is considered applicable (non-trivial/postponed nested).
|
||||
|
||||
That callback is supposed to be called for each created candidate of all nested calls, and the
|
||||
content of returned CS should be integrated into the candidate CS.
|
||||
|
||||
See `org.jetbrains.kotlin.fir.resolve.calls.Candidate.getSystem`.
|
||||
|
||||
The shared CS is not supposed to be modified during candidate resolution
|
||||
(because there might be more than one of them for each call, while only a single one should be chosen).
|
||||
|
||||
### customCompletionModeInsteadOfFull
|
||||
|
||||
Being called after the single successful candidate is chosen just before the completion begins.
|
||||
|
||||
Currently, it returns `PCLA_POSTPONED_CALL` if the candidate is *postponed*.
|
||||
|
||||
See more details on `PCLA_POSTPONED_CALL` completion mode in the [section](#pclapostponedcall-completion-mode) below.
|
||||
|
||||
### processPartiallyResolvedCall
|
||||
|
||||
This callback is assumed to be called after `PARTIAL` or `PCLA_POSTPONED_CALL` completion terminates.
|
||||
|
||||
Mostly, it collects all *postponed* candidates and integrates their CS content into the shared CS.
|
||||
|
||||
Also, here we substitute the type of that given call expression with substitutor replacing already fixed type variables
|
||||
with their result types.
|
||||
Otherwise, using fixed TVs as expression types further might lead to `TypeCheckerStateForConstraintInjector.fixedTypeVariable`
|
||||
throwing an exception.
|
||||
|
||||
```kotlin
|
||||
fun <S> id(e: S) = e
|
||||
|
||||
fun main() {
|
||||
buildList {
|
||||
// `id(this.size) ` type is `Sv`.
|
||||
// But we actually fixed `Sv` to Int because it's resolved in independent context and `Sv` is not related
|
||||
// to the outer CS.
|
||||
//
|
||||
// But since we don't run completion writing results, no one would replace the expression type of the call
|
||||
// with new resulting type.
|
||||
// So, we do it at `processPartiallyResolvedCall`
|
||||
val x = id(this.size)
|
||||
|
||||
add(x)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note that this situation with fixed TVs can't happen outside PCLA context because
|
||||
- For partial completion, we avoid fixing TVs that are used inside return types
|
||||
- For FULL completion, we would run completion results writing, so there would be no candidates and type variables inside return types.
|
||||
|
||||
And the last part is fixing TVs for receiver (see the relevant [section](#on-demand-variable-fixation) for details).
|
||||
|
||||
### runLambdaCompletion
|
||||
|
||||
This part is rather controversial and hacky, hope we could get rid of it at some point.
|
||||
|
||||
When resolving a lambda at some nested call inside PCLA lambda, like
|
||||
|
||||
```kotlin
|
||||
buildList {
|
||||
myOtherCallWithLambda {
|
||||
add("")
|
||||
Unit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After completion for `add("")` call, we haven't yet called `processPartiallyResolvedCall` for `myOtherCallWithLambda { ... }`,
|
||||
because it's not yet completed, and we are already going to call `processPartiallyResolvedCall` for `add("")`, that would contribute some
|
||||
constraints to `buildList` type variable `Ev` in shared CS.
|
||||
|
||||
And after that we finally call `processPartiallyResolvedCall` for `myOtherCallWithLambda { ... }`, but it doesn't contain constraints
|
||||
on `Ev` from `add(""")`, thus overwriting existing information.
|
||||
|
||||
That might be workarounded with some kind of merging constraints for the same TV instead of just replacement
|
||||
(see `NewConstraintSystemImpl.addOtherSystem`), but that seems to be hacky too.
|
||||
|
||||
So, the current approach is the following: during nested lambda resolution we assume that currently *shared CS* is equal to the CS of the
|
||||
`myOtherCallWithLambda` candidate. Thus, the constraints from `add` call are being merged into the `myOtherCallWithLambda`'s CS and then
|
||||
finally into regular shared CS.
|
||||
|
||||
**Hacky place**: the proper solution might be just replacing instance of shared CS with current candidate's CS, but that needs
|
||||
some further investigation.
|
||||
|
||||
Also, there's some special handling of overload resolution by lambda return type, but they are quite expected when we don't have a final
|
||||
candidate yet (see `analyzeLambdaAndReduceNumberOfCandidatesRegardingOverloadResolutionByLambdaReturnType`)
|
||||
|
||||
### runCallableReferenceResolution
|
||||
|
||||
TODO: Mostly, the idea as the same as for lambdas, but for callable references
|
||||
|
||||
### addSubtypeConstraintIfCompatible
|
||||
|
||||
In some situations, some constraints might be originated not just from calls, but from other kinds of expression,
|
||||
like from variable assignments:
|
||||
|
||||
```kotlin
|
||||
var <F> MutableList<F>.firstElement
|
||||
get() = get(0)
|
||||
set(f: F) {
|
||||
set(0, f)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
buildList {
|
||||
firstElement = ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We don't specially resolve the call to the setter, thus to declare that `String <: Fv`, thus `String <: Ev`, and finally `Ev := String`,
|
||||
there's a need to somehow send this information to the inference session.
|
||||
And that's how `addSubtypeConstraintIfCompatible` might be used.
|
||||
|
||||
**Hacky place**: In general, this approach is quite fragile, and we might've forgotten some places where this method should be triggered.
|
||||
One of the ideas particularly for assignment is that they should be resolved via setter call, thus the necessary constraint would be
|
||||
introduced naturally when string literal would be an argument for `Fv` value parameter.
|
||||
|
||||
## PCLA_POSTPONED_CALL completion mode
|
||||
|
||||
This mode is assumed to be used for postponed nested calls inside PCLA lambdas instead of FULL mode (i.e., mostly for top-level calls).
|
||||
|
||||
The set of the type variables that might be considered for fixation are obtained from the call tree of the supplied nested call itself,
|
||||
i.e., it would be a set of fresh type variables of the candidate itself, type variable of its arguments and from the return statements
|
||||
of lambdas. That part works the same way as for regular calls outside PCLA context.
|
||||
|
||||
### Example
|
||||
```
|
||||
fun foo(x: List<String>) {
|
||||
buildList {
|
||||
x.mapTo(this) { // `mapTo` analyzed in PCLA_POSTPONED_CALL
|
||||
add("") // analyzed in PCLA_POSTPONED_CALL
|
||||
it.length // analyzed in PCLA_POSTPONED_CALL
|
||||
}
|
||||
|
||||
println("") // analyzed in FULL mode (because it's a trivial call, irrelevant to outer CS)
|
||||
|
||||
add(x.get(0) /* analyzed in PARTIAL */) // analyzed in PCLA_POSTPONED_CALL
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Short description
|
||||
|
||||
- Type variables are gathered from the call tree of the supplied *nested* call.
|
||||
- Variable fixation is not allowed if the variable is deeply related to some TV from the outer CS.
|
||||
- All the lambdas belonging to the supplied call need to be analyzed during this completion.
|
||||
- We don't run completion writing in this mode.
|
||||
|
||||
### Variable fixation limitation
|
||||
|
||||
The only limitation that we've got for `PCLA_POSTPONED_CALL` is that we don't allow fixing TVs that are *deeply related* to some outer TV
|
||||
(for definition, see the relevant part at [inference.md](inference.md)).
|
||||
|
||||
**References in code:** `TypeVariableFixationReadiness.OUTER_TYPE_VARIABLE_DEPENDENCY`
|
||||
|
||||
See the following example,
|
||||
|
||||
```kotlin
|
||||
interface A
|
||||
object B : A
|
||||
object C : A
|
||||
|
||||
class GenericController<T> {
|
||||
fun yield(t: T) {}
|
||||
}
|
||||
|
||||
fun <K> GenericController<K>.yieldAll(s: Collection<K>) {}
|
||||
|
||||
fun <S> generate(g: suspend GenericController<S>.() -> Unit): S = TODO()
|
||||
|
||||
fun foo(x: Collection<B>) {
|
||||
generate {
|
||||
// GenericController<K> <: GenericController<S> => K := S
|
||||
// Collection<B> <: Collection<K>
|
||||
// B <: K
|
||||
// Let's imagine we fix K to B, then it would lead to the constraint S := B
|
||||
yieldAll(x)
|
||||
|
||||
// And to constraint error here: C <: B
|
||||
yield(C)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
While in fact, `Sv` might be easily inferred to `A` as a common supertype. To make it happen we just postpone fixation of the `Sv` as one
|
||||
having deep
|
||||
|
||||
### Forcing lambda analysis
|
||||
|
||||
Unlike `FULL` mode, it doesn't require fixing all the variables, in that sense it works more like `PARTIAL`.
|
||||
|
||||
But unlike `PARTIAL`, if forces analysis of all the lambdas even if some input types are not properly inferred yet.
|
||||
It's crucial because otherwise, contract-affecting information gained would stop working across different statements.
|
||||
|
||||
For lambdas, there might be three kinds of situations:
|
||||
- There's some lambda which input types do not contain any not fixed type variables => might be analyzed in a regular way
|
||||
- For some lambda, some of the input types satisfy PCLA requirement (see [Entry-point to PCLA](#Entry-point-to-PCLA))
|
||||
=> might be analyzed in recursive PCLA mode (see [Nested PCLA](#nested-pcla))
|
||||
- For all lambdas, all the input types are either proper or a top-level type variable (most complicated).
|
||||
|
||||
In the third (and sometimes in the second) situation, we start regular lambda analysis, but with the following modifications:
|
||||
- If the lambda parameter has a type of some not fixed TV, we just leave it there.
|
||||
- If the receiver type of lambda is some type variable [on demand](#on-demand-variable-fixation). One of the options would be just leaving TV
|
||||
not fixed as for value parameter, but that seems meaningless because almost any call inside the lambda might require the member scope
|
||||
of the receiver, thus need to fix its result type.
|
||||
|
||||
## On demand variable fixation
|
||||
|
||||
Unlike non-PCLA context, here there might be legit situations when someone needs to look into member scope of some type variable.
|
||||
|
||||
For example, see the following case:
|
||||
```kotlin
|
||||
buildList {
|
||||
// MutableList<Ev> <: Tv
|
||||
// T can't be fixed, because it's deeply related to Ev
|
||||
this.let { it -> // it: Tv
|
||||
it.add("") // requesting member scope of Tv
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There, we've got `it` used as a receiver in a call `it.add("")`, thus the first question we need to answer to is which member scope it has.
|
||||
|
||||
And the answer is that before starting the selector (`add("")`), we simply try to fix necessary type variable with the following steps:
|
||||
- Don't do anything in case the type variable belongs, to outer CS
|
||||
- Temporary override what *proper type* means
|
||||
- Type variables from outer CS might be used as a type argument for a proper type (e.g., `MutableList<Ev>` is proper).
|
||||
- Even type variables from outer CS can't be used as top-level proper type (e.g, we can't fix `Tv` to `Ev`).
|
||||
- See `ConstraintSystemCompletionContext.withTypeVariablesThatAreCountedAsProperTypes`
|
||||
and `TypeSystemInferenceExtensionContext.isProperTypeForFixation` extension.
|
||||
- If there is some proper type for that TV, run regular result type computation `ResultType`.
|
||||
- Add constraint `Tv := ResultType` and use `ResultType` as a member scope
|
||||
- **NB:** we don't actually *fix* the variable via `NewConstraintSystemImpl.fixVariable`, because there's an assumption that resulting
|
||||
type should not contain other type variables that might be not satisfied during on-demand fixation. But `fixVariable` would anyway be called
|
||||
during final completion when all other type variables types would be properly substituted in the given equality constraint.
|
||||
|
||||
As follows, from the rules above, there are some scenarios where the variable can't be fixed. In those cases, we implicitly forbid
|
||||
using them as receivers:
|
||||
- By assuming that we've got their member scope empty
|
||||
- Reporting a diagnostic at `org.jetbrains.kotlin.fir.resolve.calls.TypeVariablesInExplicitReceivers` resolution stage
|
||||
(currently `BUILDER_INFERENCE_STUB_RECEIVER` being used).
|
||||
|
||||
```kotlin
|
||||
fun <T> myLet(t: T, b: (T) -> Unit) {}
|
||||
|
||||
buildList {
|
||||
get(0).toString() // BUILDER_INFERENCE_STUB_RECEIVER is reported with PCLA
|
||||
|
||||
myLet(get(0)) { it ->
|
||||
get(0).toString() // BUILDER_INFERENCE_STUB_RECEIVER is reported with PCLA
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See the implementations details at `FirPCLAInferenceSession.fixVariablesForMemberScope`.
|
||||
|
||||
## CST computation
|
||||
|
||||
When computing the result type for TV referencing other variables from outer CS there might be a situation when we need to compute
|
||||
`CommonSuperType(Xv, ...)`:
|
||||
|
||||
```kotlin
|
||||
fun <S> selectL(x: S, y: S, l: (S) -> Unit) {}
|
||||
|
||||
fun foo(x: MutableList<String>) {
|
||||
buildList {
|
||||
// Adding the constraints
|
||||
// MutableList<String> <: Sv
|
||||
// MutableList<Ev <: Sv
|
||||
// But to analyze lambda we fix Sv to CST(MutableList<String>, MutableList<Ev>) = MutableList<CST(String, Ev)>
|
||||
// The question is what is CST(String, Ev)
|
||||
selectL(x, this) { x ->
|
||||
x.add(<!ARGUMENT_TYPE_MISMATCH!>1<!>)
|
||||
x.add("")
|
||||
}
|
||||
} // inferred to List<String>
|
||||
}
|
||||
```
|
||||
|
||||
**References:** `org.jetbrains.kotlin.resolve.calls.inference.components.ResultTypeResolver.prepareLowerConstraints`
|
||||
|
||||
In general, such a problem existed and be resolved before PCLA:
|
||||
- On the fixation stage, type variable should have at least one proper constraint, but other might be not-proper, but still being used
|
||||
- For that case, we do a controversial thing, namely
|
||||
- Substituting all TVs with special stub types
|
||||
- Compute CST with those substituted types
|
||||
- For those stub types, there’s effectively a rule that for any type `Tv` and stub `XStub`: `CST(Tv, XStub) = Tv`
|
||||
- Intuitively, such an approach seems correct because it just chooses some result and incorporates it with other TVs, at least not hiding any contradictions
|
||||
- In the example above, we would get `Sv := MutableList<String>` and during incorporation we would assert the subtyping `Collection<S> <: Collection<String>`, thus having constraint `S <: String` making the whole CS sound
|
||||
|
||||
But unlike a non-PCLA case, our TV might have a single constraint containing a reference to some other (outer) TV, so we need to make
|
||||
some tweaks:
|
||||
- Because we've overridden what "proper" means here, but `prepareLowerConstraints` uses it in a sense of the usage some TV inside the type,
|
||||
for PCLA context, we unconditionally perform stub substitution (a bit of dirty place)
|
||||
- And after that we might need to substitute "local" stubs back to TVs
|
||||
(impossible in non-PCLA, because in that case, at least one constraint is "proper" in regular sense, i.e., doesn't contain any TVs)
|
||||
|
||||
## Nested PCLA
|
||||
|
||||
```kotlin
|
||||
buildList {
|
||||
add("")
|
||||
|
||||
addAll(buildSet l2@{
|
||||
add(1)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
For nested PCLA lambdas, everything works pretty straightforward:
|
||||
- We create own `FirPCLAInferenceSession` for it
|
||||
- Run analysis of the nested lambda with that session just the same way as for an outer
|
||||
- But the main candidate for `l2` automatically uses the shared CS for `l1` (thus, it's used as an outer CS for `l2`)
|
||||
- But we don't run completion writing results at this stage yet
|
||||
- Just applying the system for `l2` to the candidate of nested candidate owning the lambda ( `addAll` in the example), and after that it
|
||||
automatically propagates to the shared CS during `processPartiallyResolvedCall`.
|
||||
|
||||
Thus, in the end, we would get the CS for the root call containing all variables from the whole PCLA-tree.
|
||||
|
||||
Completion results writing for nested lambdas happen recursively when it starts for the root call:
|
||||
- In the example we start with `buildList`.
|
||||
- Go to `FirCallCompletionResultsWriterTransformer.runPCLARelatedTasksForCandidate`.
|
||||
- Find the postponed call `addAll`.
|
||||
- Recursively run `runPCLARelatedTasksForCandidate` for it at some point.
|
||||
- And while iterating the postponed call for the latter will write results for `add(1)` call.
|
||||
|
||||
## Nested Delegation inference
|
||||
|
||||
```kotlin
|
||||
buildList {
|
||||
val x by lazy {
|
||||
get(0)
|
||||
}
|
||||
|
||||
add("")
|
||||
add(x)
|
||||
}
|
||||
```
|
||||
|
||||
Inference for delegated properties inside PCLA works in the following way:
|
||||
- Delegate expression is being resolved as a regular postponed nested call under the same PCLA session.
|
||||
- All the operator calls are being resolved under specially created delegate `FirDelegatedPropertyInferenceSession` in `PARTIAL` mode.
|
||||
- *After* that we run completion for delegate expression via `PCLA_POSTPONED_CALL` mode.
|
||||
- Then, we integrate delegation related CS into PCLA shared session and all the delegation related calls as regular postponed ones
|
||||
of the PCLA session.
|
||||
|
||||
For more details, read [delegated_property_inference.md](delegated_property_inference.md),
|
||||
`FirDelegatedPropertyInferenceSession.completeSessionOrPostponeIfNonRoot` and `FirPCLAInferenceSession.integrateChildSession`.
|
||||
Reference in New Issue
Block a user