There are many complications with the current design of passing data
from within in-place lambdas to surrounding code. Solving these
complications will involve more time to investigation than is available
within the K2 release. So we are disabling passing type statement
information from lambdas for the time being until more time can be
devoted to a more complete solution.
^KT-60958 Fixed
^KT-63530 Fixed
In order to properly analyze top-level property initialization, a
control-flow graph must be created for FirFiles. This change adds the
foundation for the file CFG and updates body resolve to create the CFG.
Checking the CFG for proper initialization is separated into a following
change to ease code review.
KT-56683
Namely, do not choose `Nothing?` result type when fixing a variable
that has other constraints besides the ones that came from
the relevant type parameter's upper bounds.
See more details in KT-55691.
In K1, the case from specialCallWithMaterializeAndExpectedType.kt
was working (inferred to String?) just because the branches
were analyzed independently with `String?` expected type.
This change became necessary after the previous commit when we united
inference subsystems for if/when branches (see motivation there).
NB: For K1, the behavior is left the same, but the code
was refactored a bit.
^KT-55691 Fixed
^KT-56448 Fixed
Quick quiz:
Q: In a CFG, what does `a -> b -> c -> d` mean?
A: `a`, then `b`, then `c`, then `d`.
Q: In a CFG, what does `a -> b -> d; a -> c -> d` mean?
A: `a`, then `b` or `c`, then `d`.
Q: So how do you encode "a, then (b, then c) or (c, then b), then d`?
A: You can't.
Problem is, you need to, because that's what `a; run2({ b }, { c }); d`
does when `run2` has a contract that it calls both its lambda arguments
in-place: `shuffle(listOf(block1, block2)).forEach { it() }` is a
perfectly valid implementation for it, as little sense as that makes.
So that's what union nodes solve. When a node implements
`UnionNodeMarker`, its inputs are interpreted as "all visited in some
order" instead of the normal "one of the inputs is visited".
Currently this is used for data flow. It *should* also be used for
control flow, but it isn't. But it should be. But that's not so easy.
BTW, `try` exit is NOT a union node; although lambdas in one branch can
be completed according to types' of lambdas in another, data does not
flow between the branches anyway (since we don't know how much of the
`try` executed before jumping into `catch`, and `catch`es are mutually
exclusive) so a `try` expression is more like `when` than a function
call with called-in-place-exactly-once arguments. The fact that
`exitTryExpression` used `processUnionOfArguments` in a weird way
should've hinted at that, but now we know for certain.
In theory, forking persistent flows should be cheap because of object
reuse, so the proposal here is to start from scratch and prove
redundancy of forks on a case-by-case basis. Something something better
safe than sorry.
^KT-28333 tag fixed-in-k2
^KT-28489 tag fixed-in-k2
callBothLambdas({ x = "..." }, { x is Int })
// the assignment always executes, so x is String | (String & Int);
// the latter is always a subtype of the former so it can be ignored
Make smart-casts non-transparent expression without delegation
to underlying FirQualifiedAccessExpression, as children delegation in
fir tree has unclear semantics
Remove two different kinds of tree nodes for smart-casts
var p: String? = something
if (p != null) {
foo(
run { p = null; n() },
// This lambda executes strictly after the one above by CFG,
// but data flow for type inference comes from before the call
// so p would get smartcasted if not forbidden.
run { p.length; 123 }
)
}
Consider a function `run2` that has 2 lambda arguments called in place.
We don't know the order in which they're called, so here:
var x: Any? = something
run2(
{ x = null },
{ x as String },
)
// <--
it's not correct to simply `&&` the statements together, as that would
produce `x is Nothing? && x is String && x is Any?`. Instead, statements
should be grouped by assignment first, and different groups are `||`-ed.
This means in the above example we now get `x is Nothing? || (x is Any?
&& x is String)` == `x is String?`.
For example:
foo(
// `if` joins A & B
if (condition)
run { ... } // A
else
run { ... }, // B
run { ... } // C
) // `foo` unifies `A & B` and `C`, so if it is not resolved itself,
// further `if`s, `when`s, safe calls outside it, etc. continue
// building the correct type predicate until the next completed
// call.
^KT-44512 Fixed