They are mostly necessary for argument mapping during resolution.
To support a couple checkers, we transform named args for varargs
into "fake" spread expressions.
Other than that, named arguments aren't needed for anything and often
lead to bugs where we forget to unwrap them for something, so it's
better to get rid of them.
#KT-66124
When there is a return statement within a lambda, and the return
statement contains a lambda, data-flow information from the inner lambda
is not being passed correctly to the surrounding lambda. This is because
return statements which had a lambda target would drop all postponed
lambda exits, but should instead forward those exits to the surrounding
lambda exit.
^KT-59729 Fixed
^KT-64268 Fixed
The primary constructor of a class needs to be the first subgraph of the
class control-flow graph. Based on the Kotlin specification, class
initialization order goes first primary constructor, in-place
declarations (properties and init blocks), and then secondary
constructors. If the class doesn't have a primary constructor, then it
is just skipped in the order.
Unfortunately, the class control-flow graph had in-place declarations
first and then all constructors. Instead, we should treat the primary
constructor as the first in-place declaration, and then continue with
the existing processing as secondary constructors. This will guarantee
that super constructor calls have the correct property initialization
information.
^KT-65093 Fixed
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
UncaughtExceptionPath edges are used to influence smart-casting within
catch and finally blocks. Previously these edges were added from every
node which could throw an exception. But only assignment nodes influence
smart-casts by resetting inference back to some less specific type.
Therefore, instead of tracking every possible node which could throw an
exception - even though almost every statement node can - only add edges
from assignment nodes to catch and finally blocks. This fixes many
missing exception cases and also reduces the total number of incoming
edges to catch and finally blocks.
#KT-56872 Fixed
It would be nice to report more appropriate diagnostics at the
corresponding places, but right now it's more important to
fix greenness-redness problems. Plus, this is already how K1 works.
^KT-59900 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
This prevents `FirConflictsExpressionChecker.kt`
from missing conflicting local functions. It used
to due to inconsistencies in assigning `<local>`,
and this commit makes it a bit more
straightforward.
The change in KtClassTypeQualifierRenderer
prevents `FirOverrideImplementTest.testLocalClass`
from failing in `intellij`. It didn't fail for
callables, because `KtCallableSignatureRenderer`
doesn't try to render packages.
^KT-59186 Fixed
Before this change nodes unification was called if outer call was
completed in the FULL mode, but it may happen even if this call is
actually a part of some other outer call
The change is needed for the parallel resolution (^KT-55750), so we can resolve the declaration
under a lock that is specific to this declaration.
Previously, if LL FIR was resolving some FirClass, LL FIR resolved all its children too, and it had no control over what parts of the FIR tree were modified.
The same applied to the designation path, sometimes the classes on the designation path
might be unexpectedly (and without lock) modified.
This commit introduces LLFirResolveTarget, which specifies which exact declarations should be resolved during the lazy resolution of the declaration.
All elements outside the declarations specified for resolve in LLFirResolveTarget, should not be modified.
The logic of lazy transformers is the following:
- Go to target declaration collecting all scopes from the file and containing classes
- Resolve only declarations that are specified by the LLFirResolveTarget, performing the resolve under a separate lock for each declaration
^KT-56543
^KT-57619 Fixed
The only case when behavior is change is described at
computeNonTrivialTypeArgumentForScopeSubstitutor
The idea is to avoid depending on the presence of @UnsafeVariance
and instead approximate captured types in covariant argument positions
before building substitution scopes
It's correct because for Captured(*) <: Supertype,
Out<Captured(*)> <: Out<Supertype> and when we've got @UnsafeVariance
value parameters at Out, it's ok to allow passing Supertype there.
^KT-57602 Fixed
^KT-54894 Fixed
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
Also fix graphs for enums with specialized entries - since we don't
create property subgraphs for FirEnumEntry, there is no body to insert
AnonymousObjectEnterNode, AnonymousObjectExitNode, and
AnonymousObjectExpressionExitNode into.
They are only used in one place that can just as well use kinds.
Especially considering that "the one place" used them incorrectly and
would not attach local functions in property accessors as subgraphs.
function enter -> default 1 -> default 2 -> rest of function
\----------^ \----------^
This probably has no effect (in non-stupid code, at least), but it makes
graph construction more architecturally correct (now value parameters'
subgraphs get attached to a node).
Interpretation: a graph A is a subgraph of B if information available at
nodes of A depends on the paths taken in B. For example, local classes
are subgraphs of a graph in which they are declared, and members of
those classes are subgraphs of the local class itself - because these
members can reference captured values.
Consequences:
* if graph G is a subgraph of node N, then G is a subgraph of N's
owner;
* `ControlFlowAnalysisDiagnosticComponent` will only visit root graphs;
* `graph.traverse` will ignore subgraph boundaries, as if all subgraphs
are inlined into one huge root graph;
* if a control flow checker needs information from a declaration to
which a graph is attached, it must look at subgraphs explicitly.
For example, consider the `callsInPlace` checker. When a function
has a `callsInPlace` contract and a local declaration, the checker must
visit that local declaration to ensure it does not capture the allegedly
called-in-place argument - hence `graph.traverse` will look at the
nodes. However, the local declaration can also be a function with its
own `callsInPlace` contracts, so the checker should also run for it in
isolation. If that sounds quadratic, that's because unfortunately it is.
This is a temporary hack to avoid compiler crashes in some code that
uses builder inference, conditional early returns from lambdas, and
expected types in a certain way. It is not correct - dropping data flow
edges never is - but it is much easier to implement for now than a
proper fix.
This removes the need for hacks around the order in which function
call arguments are visited, fixes called-in-place lambda arguments
for augmented assignment operators, and makes CFG dumps a bit prettier.
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.
It's also not a backwards jump in do-while, unless it's in the loop's
condition, which is a stupid "feature" IMO. As you can probably tell
from the comments added in this commit.
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