The main idea is pre-computing the sets of names that might be
met there, that helps to decrease the sizes of the backing maps
(by avoiding irrelevant keys)
Totally, this branch with previous commits speeds up MT Full Kotlin
approximately on 3 seconds (~5%)
`FirDeclarationGenerationExtension.generateClassLikeDeclaration` was split
into two functions: one for generating top level classes, and one for
nested classes. Such change reduces verbosity and error-proness of
this extension and also allows to smoothly run plugins on local classes
^KT-55248 Fixed
UncaughtExceptionPath label out of a finally block matches every label
that is not handled by another edge, and a labeled edge from the middle
of a finally block aborts the jump and so should merge all available
data.
Instead, rely on the variable assignment analyzer to properly restrict
smart casts. This makes error messages more consistent, but otherwise
should have no effect.
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.
* `do { continue } while (x)`: block exit is dead, condition is not;
* `try { a?.incomplete() } catch (e) { x }`: when the call is completed
and found to return `Nothing`, the safe call exit node is still live
because `a` could still be null;
* `f(a@{ g { return@a } }, x)`: if call to `g` is not completed, there
should not be a dead data flow edge from the lambda to `f` because
there's no such thing as a dead data flow edge (DeadForward is CF+DF).
The receiver of the provideDelegate call is the same FirExpression as
the delegate itself, so there's only one copy of the nodes in the first
place; trying to remove subgraphs completely detaches objects inside it
from the parent graph, which is not great for checkers.
Note that currently if provideDelegate is not selected, there will be a
stray FunctionCallExit node in the control flow graph. This commit *does
not change that*. It has been there for a while. Don't @ me. I'll try to
fix that. No promises.
While it is theoretically useful to know that `{ while(true) {} }`
returns Nothing, CFG node deadness is not precise enough to do that: if
the entire lambda is dead, it's no longer possible to find out whether
the loop is terminating. Besides, `while (true)` and `if (true)` are
pretty much the only constructs like that anyway.
Note that this commit does not affect resolution for lambdas that end in
a Nothing-returning expression, e.g. `throw`.