tl;dr the current design of klibs does not allow to properly deserialize
the list of sealed subclasses in a sound way. It is possible that
a subclass of a sealed class is declared in a different file, AND is
private in that file.
A more detailed explanation:
Right now we don't serialize file signatures at all.
However, a private declaration's signature must necessarily include
the file signature.
How do we serialize a private declaration's signature into a klib
and deserialize it later?
**Serialization** is simple: we just serialize the file signature as
an empty protobuf message.
When we are **deserializing** a private declaration, we look at the file
that is being deserialized right now, and construct the file signature
based on that.
This logic, however, doesn't always work. An example is KT-54028.
Basically, if we have a sealed interface with a private implementor
declared in a different file, this breaks:
1. We are deserializing the sealed interface. The deserializer knows
that we are now in the file in which the sealed interface is declared.
2. As part of deserializing the interface, we deserialize its sealed
subclasses.
3. Naturally, we come to deserializing the private implementor that is
declared in another file, but the deserializer still thinks that we are
in the file in which the interface is declared. A wrong signature is
created, which leads to linkage failure.
We *could* fix this by properly serializing the file signature,
i.e. instead of an empty protobuf message we could write the file path
and its package to the klib. However, there a problems with this
approach:
- The current design of signatures allows a situation where two
different files can have the same relative path
(for example, with the help of the `-Xklib-relative-path-base` compiler
flag) *and* the same package, which would introduce ambiguity during
linkage.
- Most importantly, this appoach won't work well with incremental
compilation of klibs. Currently we rely on the assumption that all
cross-file references are handled with public signatures, and private
signatures are only used inside a single file. This allows to move
declarations across files without recompiling it's use sites.
It has been decided to apply the following hacky solution: we just don't
deserialize the list of sealed subclasses from klibs.
The list of sealed subclasses is not used in lowerings, so it should be
safe.
#KT-54028 Fixed
Add a special annotation @JsExternalInheritorsOnly for marking
external interfaces and classes. The marked interface or class
can’t be a parent for non external interfaces, classes or objects.
^KT-57423 Fixed
In K1 .isFun is always false for Java classes, so extra check
is added for that. This is not needed for K2, because .isFun is
true for all Java classes. Here it is not necessary to check
that interface has only one method, because such check will be
done in the place where interface implementation is created.
^KT-39362 Fixed
This will allow IDE plugins to contribute compiler plugins to analysis,
above and beyond those used for the actual compilation step. These
plugins can be used to, for example, provide declarations for code that
is generated during build by an external tool.
^KT-57763 fixed
Since we put object's hash code in toString, we end up in putting
non-stable hash code to constant table, which leads to unstable
binaries.
#KTI-1193 Fixed
- `SymbolProviderMerger` encapsulates some boilerplate, which should
make `mergeDependencySymbolProvidersInto` easier to read and also much
easier to extend.
- This Caffeine cache is limited to classes for now, but may also be
tried with callables.
- The cache has a small memory footprint, but still avoids most of the
unnecessary index accesses. In my local tests, this approach takes 50%
of the time compared to no caches. A full cache has no performance
advantage over the limited-size cache in my local tests.
- `NullableCaffeineCache` wraps a Caffeine cache and allows storing
`null` values returned by the computation in the form of explicit
`NullValue`s in the cache.
- Our current FIR caches are based on `ConcurrentMap` and thereby do not
support size and lifetime limits out of the box. For example,
first-layer caches with a limited size can speed up access of the most
frequently used elements, while having a small memory footprint.
- Caffeine is a modern and well optimized caching library that allows us
to create thread-safe and performant caches with various size or
lifetime limits.
- The cache must support concurrency because session components such as
symbol providers may be accessed concurrently once parallel resolve in
the Analysis API has been implemented (see KT-55750). Caffeine caches
support concurrency.
- Getting top-level names from `declarationProvider` directly instead of
from subordinate symbol providers has a lot of potential for
performance, but the current performance of the index access is worse
than the iterative version because scopes aren't optimized yet.
- This commit prepares `LLFirCombinedKotlinSymbolProvider` for the
switch to getting top-level names from `declarationProvider`.