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.
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.
That issue might be fixed via changing
TypeVariableMarker.shouldBeFlexible at ConeConstraintSystemUtilContext
but this and some other tricks have been added because of incorrect
handling of constraints where type variable has a flexible bound
^KT-51168 Fixed
In qualified expression like `foo().`, selector expression is null.
Because of that the whole expression was marked as an error FIR
expression, and `foo()` part was not resolved at all (including
arguments and everything else).
This commit fixes the problem by providing receiver's FIR expression
as an underlying expression for error FIR expression. That way
it will be seen by all resolve transformers and will be successfully
resolved.
^KTIJ-21484 Fixed
1. Inner class constructor should have its outer class as a dispatch
receiver, since it is necessary for the call. Before it was null
2. Substituted inner class constructor should have its original dispatch
receiver type with the proper substitution. Before it was set to the
class itself (since the class was usually passed as a new dispatch
receiver)
Also, modify FIR renderer, so it properly renders the dispatch receiver
of the constructors
Consider the following code:
```
fun test(a: List<String>) {
a.first()
}
```
The dispatch receiver type of `first` in this case is `List<T>` before
this change. After this change, it's `List<String>`.
In addition, this change also replace the dispatch receiver type with
the more specific type if available. For example, consider the following
```
class MyList: ArrayList<String>()
fun test(a: MyList) {
a.get(0)
}
```
The dispatch receiver type of `get` is `MyList`, instead of
`ArrayList<String>`. That is, a fake override is created in this case.
JavaSymbolProvider uses KotlinPsiElementFinderWrapper for finding classes.
CliFinder looks for Java classing assuming that class with ClassId=a/b/C
lives in directory a/b and do not look into real package name of Java class.
This causes that we may find some classes which we should not see from current scope.
Also, the IDE implementation works correctly here (it also checks file package)
which cause different behaviour of FIR IDE and FIR
This change also requires to fix testdata and make Java classes live
in directory consistent with file package
Previously the callee reference of a delegated constructor call is
always the same as the call itself. This violates the contract that no
two FIR elements can have identical sources. In addition, this sets the
entire call expression as the source of the callee expression.
This change instead sets the proper constructor ref as the callee.
Also fixed EXPLICIT_DELEGATION_CALL_REQUIRED type. It should be an error
instead of a warning.
Update includes:
- Changing syntax of `OI/`NI` tags from `<!NI;TAG!>` to `<!TAG{NI}!>`
- Fix some incorrect directives
- Change order of diagnostics in some places
- Remove ignored diagnostics from FIR test data (previously `DIAGNOSTICS` didn't work)
- Update FIR dumps in some places and add `FIR_IDENTICAL` if needed
- Replace all JAVAC_SKIP with SKIP_JAVAC directive