| ## 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. |
| |
| *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 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 |
| var prop by delegateExpression() |
| ``` |
| is being desugared to something similar to this (see `org.jetbrains.kotlin.fir.builder.ConversionUtilsKt.generateAccessorsByDelegate`) |
| ``` |
| val prop = propertyNode { |
| delegate = delegate { |
| expression = "delegateExpression()" |
| delegateProvider = "expression.provideDelegate()" |
| } |
| |
| get() = delegate$field.getValue($thisRef, ::prop) |
| set(value) { |
| delegate$field.setValue($thisRef, ::prop, value) |
| } |
| } |
| ``` |
| |
| Even in case the property type is specified, all the content types are set as implicit. |
| |
| ### Delegated property inference algorithm |
| |
| #### Delegate expression inference |
| 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 `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`. |
| |
| 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 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 |
| might need to look into its member scope to find `getValue` call there. |
| |
| ```kotlin |
| val test: String by materializeDelegate() |
| |
| fun <T> materializeDelegate(): Delegate<T> = Delegate() |
| |
| operator fun <K> K.provideDelegate(receiver: Any?, property: kotlin.reflect.KProperty<*>): K = this |
| |
| class Delegate<V> { |
| operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): V = TODO() |
| } |
| ``` |
| |
| But the problem is that member scope is not defined for variables, thus we emulate K1 full completion behavior by |
| |
| - Looking if `provideDelegate` actually returns a type variable (`K`) |
| - Finding the current result type that might be used for fixation, but under the assumption that all outer type variables |
| (`[T]` in the example above) are considered proper, so could be used inside a fixation result |
| - Add equality constraint `K = FoundFixationResult` (`K = Delegate(Tv)` in the example above) |
| - When creating a final substitutor for `provideDelegate` expression, take that constraint into account |
| |
| See `org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.findResultTypeForInnerVariableIfNeeded`. |
| |
| If `provideDelegate` is completed without contradictions, we effectively replace `delegateExpression` with `delegateExpression.provideDelegate()`. |
| |
| #### getValue/setValue analysis |
| |
| If a property type is set explicitly, it's being propagated to the accessors' signatures at `FirDeclarationsResolveTransformer::transformAccessors` |
| (return type of getter and parameter type of setter). |
| Otherwise, the types remain implicit. |
| |
| Then, we effectively call `transformFunctionWithGivenSignature` on getter and _only_ if the property type was explicit, |
| call `transformFunctionWithGivenSignature` on setter, too. |
| |
| We do that under the same delegate inference session, so we've got callbacks for `FirInferenceSession.onCandidatesResolution` thus |
| exactly for `getValue`/`setValue`/`provideDelegate` calls, we set outer CS from the session. |
| |
| Note that there's no need to do that for the delegate expression or nested arguments (they should be resolved as usual in `ContextDependent` resolution mode). |
| |
| Also, while accessors have a shape like `delegate$field.getValue($thisRef, ::prop)`, `delegate$field` is a special reference which type |
| is being set to the current type of `delegateExpression` at `FirExpressionsResolveTransformer.transformQualifiedAccessExpression`. |
| |
| For those delegation operator calls, if they're successfully resolved, we preserve their CSs and use them as the main outer ones, |
| also we collect the calls as ones that need to be completed later (partially completed). |
| |
| Note that `transformFunctionWithGivenSignature` if a return type is implicit propagates one from the body of the function, thus |
| after getter resolution its return type (and the property one) might contain some type variables. |
| |
| See `org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirDeclarationsResolveTransformer.transformPropertyAccessorsWithDelegate`. |
| |
| #### Partial calls completion |
| |
| At that stage, we've got all those delegation operators calls resolved effectively under the same constraint system. |
| So, what we need to do further is solving that CS, thus find the result types for all the type variables from those calls. |
| |
| To achieve that, we run completion for all incomplete calls altogether as a list of `topLevelAtoms`, so it works mostly as regular FULL completion. |
| |
| See `org.jetbrains.kotlin.fir.resolve.inference.FirDelegatedPropertyInferenceSession.completeCandidates`. |
| |
| #### Completion results writing |
| |
| In case of successful completion, we get a final substitutor that we may apply to property return type and accessor signatures, too. |
| Thus, getting rid of potentially left type variables there. |
| |
| After that we run `FirCallCompletionResultsWriterTransformer` on each of the freshly completed calls, with a special mode |
| `DelegatedPropertyCompletion` that is only different in a meaning that for each qualified access it also, recursively ran on the explicit |
| receiver (that is necessary to update `delegate$field` references types for `getValue`/`setValue` calls). |
| |
| Another nasty tweak that is needed to be made is updating substituted member after completion. |
| Some of the final candidates might be obtained from member scopes of types with type arguments containing variables |
| (like `Delegate<T>` from the example above), but after body transformation they should be resolved to the corrected symbols from the |
| scopes with substituted type arguments. |
| |
| See `FirCallCompletionResultsWriterTransformer.updateSubstitutedMemberIfReceiverContainsTypeVariable` for details. |
| |
| #### Delegation inside PCLA |
| See the relevant part inside the [document](pcla.md) on PCLA. |