The code was already duplicated between FirDataFlowAnalyzer and
FirReturnsImpliesAnalyzer, so might as well use the latter to slightly
speed up the former.
This makes the `returns() implies` checker slightly cleaner, and also
fixes the case that I've missed where in RHS of `x ?:` type of `x` was
not set to `Nothing?`.
If a variable does not yet exist, then it should only be created if it
is a real variable and the implication or statement we're about to add
is about its type or nullability (which is also about the type).
Otherwise, the statement/implication will have no effect because there
are currently no implications referencing that variable, and if it's
synthetic then there will never be because we won't visit it again.
Hopefully this reduces the performance impact of flow forks.
This also fixes some returnsNotNull contracts because the old code added
an implication that `== true` => `!= null` then promptly removed any
statement that this could've affected if the argument was a synthetic
variable.
^KT-26612 tag fixed-in-k2
If the right-hand side is evaluated at all, then in its flow those
statements were already approved. Re-approving them erases the effect of
reassignments.
^KT-28369 tag fixed-in-k2
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
By necessity, type statements underapproximate. Therefore, inverting
doesn't always produce an equal result. It's best to propagate negations
inwards to preserve precision as much as possible.
And use them less in boolean expression analysis. This uncovers a fun
new case that doesn't work:
interface I { val x: String }
class C(override val x: String) : I
class D(override val x: String) : I
fun foo(x: Any?) {
if (x is D || (x as C).x != "") {
println(x.x.length) // actually OK (x is D || x is C => x is I)
}
}
I don't even know if this affects any real code, but it did uncover
some deeper issues, like the fact that `!is` did not add any statements
when `true`.
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
val c = C("...")
val d = c
if (c.x == null) return
c.x.length // c.x has type String
d.x.length // d.x -> c.x through d -> c
c = C(null) // remove alias d -> c
d.x.length // info from c.x moved to d.x
^KT-54824 Fixed
Second step for KT-52615
Get rid of PsiHandlingMode
Get rid of source in FirLazyBlock
Refactor lazy creation
Merge-request: KT-MR-7753
Merged-by: Egor Kulikov <Egor.Kulikov@jetbrains.com>
- The Java functions aren't recognized as candidates during the test
(`FULL_JDK` isn't helping), so I've replicated the tests with local
extension functions and confirmed that they uncover the same
exception.
- Call candidate collection sometimes provides candidate symbols to
`createConeSubstitutorFromTypeArguments` with fewer type parameters
than type arguments provided by `FirQualifiedAccess`, which lead to
an NPE. Because call candidates collected for the purposes of the
Analysis API are best-effort guesses, we can ignore the additional
type arguments.
^KTIJ-23373 fixed
^KTIJ-21506 fixed
K2 plugin API has a limitation that meta annotations from plugin predicates
can't be used on nested annotations because their resolve process includes supertypes
resolve, that can be affected by the plugin itself. Therefore, @MetaSerializable
can't be applied to nested annotation classes in K2, which is reflected by
this diagnostic.
For old FE, diagnostic is lowered to WARNING with deprecation message.