Introduce documentation for PCLA implementation

^KT-65637 Fixed
This commit is contained in:
Denis.Zharkov
2024-02-07 15:16:16 +01:00
committed by Space Team
parent 41cc926953
commit cabf7e4fae
5 changed files with 619 additions and 20 deletions
+26 -11
View File
@@ -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.