K2: Rewrite delegate inference
The main idea is getting rid of stub types and using just type variables See more detailed description at docs/fir/delegated_property_inference.md The problem with stub types is that they need really special treatment in many places, and on the other hand, there are no clear contracts on how they should work (that regularly leads to bugs like KT-59529) ^KT-61060 Fixed ^KT-61075 Fixed ^KT-61077 Fixed ^KT-59529 Fixed ^KT-61633 Related ^KT-61618 Related ^KT-61740 Related ^KT-59107 Related ^KT-61747 Related ^KT-61077 Related ^KT-61781 Related
This commit is contained in:
committed by
Space Team
parent
cd5105c133
commit
a02cb16fb2
@@ -2,10 +2,6 @@
|
||||
See also: [Kotlin Spec: Builder-style type inference](https://kotlinlang.org/spec/type-inference.html#builder-style-type-inference)
|
||||
|
||||
### Glossary
|
||||
#### CS = Constraint system
|
||||
An instance of `org.jetbrains.kotlin.resolve.calls.inference.model.NewConstraintSystemImpl`
|
||||
#### Call-tree
|
||||
A tree of calls, in which constraint systems are joined and solved(completed) together
|
||||
#### 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.
|
||||
Selection of type variables to postpone happens during [Initiation](#initiation) phase
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
## 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)
|
||||
|
||||
### 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 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.
|
||||
|
||||
### 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
|
||||
#### 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.
|
||||
|
||||
*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`.
|
||||
|
||||
#### 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 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.
|
||||
@@ -0,0 +1,12 @@
|
||||
## 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
|
||||
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
|
||||
Reference in New Issue
Block a user