You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
bpart: Redesign representation of implicit imports
# Current Design
When we explicitly import a binding (via either `using M: x` or `import`),
the corresponding bpart representation is a direct pointer to the imported
binding. This is necessary, because that is the precise semantic representation
of the import.
However, for implicit imports (those created via `using M` without explicit
symbol name), the situation is a bit different. Here, there is no semantic
content to the particular binding being imported. Worse, the binding is not
necessarily unique. Our current semantics are essentially that we walk the
entire import graph (both explicit and implicit edges) and if all reachable
terminal nodes are either (i) the same binding or (ii) constant bindings with
the same value, the import is allowed. In this, we are supposed to ignore cycles,
although the current implementation has some trouble with this (#57638, #57699).
If the import succeeds, in the current implementation, we then record an arbitrary
implicit import edge as in the BindingPartition. In essence, we are creating a
spanning tree of the import graph (formed from both the implicit and explicit
import edges). However, dynamic algorithms for spanning tree maintenance are
complicated and we didn't implement any. As a result, it is possible for later
edge additions to accidentally introduce cycles (causing #57699).
An additional problem, even if we could keep a consistent spanning tree, is that
the spanning tree is not unique. In practice, this is not supposed to be observable,
but it is still odd to have a non-unique representation for a particular set of
imports. That said, we don't really need the spanning tree. The only place that
actually uses it is `which(::Module, ::Symbol)` which is not performance sensitive
and arguably a bad API for the above reason that the answer is ill-defined.
# This PR
With all these problems, let's just get rid of the spanning tree all together -
as mentioned we don't really need it. Instead, we split the PARTITION_KIND_IMPLICIT
into two, one for each of the two cases (importing a global binding, or
importing a particular constant value). This is actually a more efficient
implementation for the common case of needing to follow a lookup -
we no longer need to follow all the import edges. In exchange, more work
needs to be done during binding replacement to re-scan the entire import
graph. This is probably the right trade-off though, since binding replacement
is already a slow path.
Fixes#57638, #57699
0 commit comments