Skip to content

Commit 23ca145

Browse files
committed
bindings: Add native automatic re-export feature
This implements a native version of automatic re-export, similar to the macro from Reexport.jl. Doing this natively in the binding system has two key advantages: 1. It properly participates in binding resolution and world ages. If a new binding is Revise'd into a re-exported module, this will now propagate. 2. The re-exported bindings are allocated lazily, improving performance. An accessor for this functionality is provided as `@Base.Experimental.reexport`. However, the only supported argument to this macro is single `using` statement (unlike the Reexport.jl version, which has more syntax). It is my expectation that Reexport.jl will be updated to make use of the underlying funtionality here directly, the base version of the macro is mostly for testing. There are a few design warts here - in particular, we inherit the module name exporting behavior (JuliaLang/Reexport.jl#39). However, I think that would be best addressed by not exporting the module name itself from the modules but rather introducing the module name as an additional binding on `using` statements. However, this would be potentially breaking (even if the effect is unlikely to be seen in practice), so I'm not proposing it here. The Reexport.jl version of the macro can do whatever it needs to, including creating an explicit non-exported binding to suppress the automatic re-export for this if desired. Partially written by Claude Code.
1 parent 3a50d6f commit 23ca145

File tree

11 files changed

+288
-35
lines changed

11 files changed

+288
-35
lines changed

base/experimental.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,4 +690,55 @@ function wait_with_timeout(c::GenericCondition; first::Bool=false, timeout::Real
690690
end
691691
end
692692

693+
"""
694+
Base.Experimental.@reexport using Module
695+
696+
Automatically re-export all exported names from a module when using it.
697+
698+
# Examples
699+
700+
```jldoctest
701+
julia> module A
702+
export foo
703+
foo() = "foo from A"
704+
end
705+
A
706+
707+
julia> module B
708+
using Base.Experimental: @reexport
709+
@reexport using ..A
710+
# Now B exports foo, even though it's defined in A
711+
end
712+
B
713+
714+
julia> using .B
715+
716+
julia> foo()
717+
"foo from A"
718+
```
719+
720+
!!! warning
721+
This interface is experimental and subject to change or removal without notice.
722+
"""
723+
macro reexport(ex)
724+
if !Meta.isexpr(ex, :using) || isempty(ex.args)
725+
error("@reexport must be used with a `using` statement, e.g., `@reexport using MyModule`")
726+
end
727+
728+
# Check for `using Foo: x, y` syntax (not supported)
729+
if any(arg -> Meta.isexpr(arg, :(:)), ex.args)
730+
error("@reexport does not support `using Module: names` syntax")
731+
end
732+
733+
# Generate _eval_using calls for each module in the using statement
734+
calls = Expr(:block)
735+
for mod_path in ex.args
736+
push!(calls.args, :($(Core._eval_using)($(__module__), $(QuoteNode(mod_path)), $(Base.JL_MODULE_USING_REEXPORT))))
737+
end
738+
push!(calls.args, Expr(:latestworld))
739+
push!(calls.args, :nothing)
740+
741+
return esc(calls)
742+
end
743+
693744
end # module

base/module.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ using A.B => _module_using(Main, Expr(:., :A, :B))
133133
134134
See also [`_using`](@ref Core._using).
135135
"""
136-
function _eval_using(to::Module, path::Expr)
136+
function _eval_using(to::Module, path::Expr, flags::UInt8=UInt8(0))
137137
from = eval_import_path_all(to, path, "using")
138-
Core._using(to, from)
138+
Core._using(to, from, flags)
139139
is_package = length(path.args) == 1 && path.args[1] !== :.
140140
if to == Main && is_package
141141
Core._import(to, from, nameof(from))

base/runtime_internals.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,15 @@ const PARTITION_KIND_BACKDATED_CONST = 0xb
250250
const PARTITION_FLAG_EXPORTED = 0x10
251251
const PARTITION_FLAG_DEPRECATED = 0x20
252252
const PARTITION_FLAG_DEPWARN = 0x40
253+
const PARTITION_FLAG_IMPLICITLY_EXPORTED = 0x80
253254

254255
const PARTITION_MASK_KIND = 0x0f
255256
const PARTITION_MASK_FLAG = 0xf0
256257

257258
const BINDING_FLAG_ANY_IMPLICIT_EDGES = 0x8
258259

260+
const JL_MODULE_USING_REEXPORT = 0x1
261+
259262
is_defined_const_binding(kind::UInt8) = (kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_BACKDATED_CONST)
260263
is_some_const_binding(kind::UInt8) = (is_defined_const_binding(kind) || kind == PARTITION_KIND_UNDEF_CONST)
261264
is_some_imported(kind::UInt8) = (kind == PARTITION_KIND_IMPLICIT_GLOBAL || kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_EXPLICIT || kind == PARTITION_KIND_IMPORTED)

base/show.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3294,6 +3294,10 @@ function print_partition(io::IO, partition::Core.BindingPartition)
32943294
if (partition.kind & PARTITION_FLAG_EXPORTED) != 0
32953295
print(io, "exported")
32963296
end
3297+
if (partition.kind & PARTITION_FLAG_IMPLICITLY_EXPORTED) != 0
3298+
first ? (first = false) : print(io, ",")
3299+
print(io, "re-exported")
3300+
end
32973301
if (partition.kind & PARTITION_FLAG_DEPRECATED) != 0
32983302
first ? (first = false) : print(io, ",")
32993303
print(io, "deprecated")

src/builtins.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1579,10 +1579,15 @@ JL_CALLABLE(jl_f__import)
15791579
// _using(to::Module, from::Module)
15801580
JL_CALLABLE(jl_f__using)
15811581
{
1582-
JL_NARGS(_using, 2, 2);
1582+
JL_NARGS(_using, 2, 3);
15831583
JL_TYPECHK(_using, module, args[0]);
15841584
JL_TYPECHK(_using, module, args[1]);
1585-
jl_module_using((jl_module_t *)args[0], (jl_module_t *)args[1]);
1585+
size_t flags = 0;
1586+
if (nargs == 3) {
1587+
JL_TYPECHK(_using, uint8, args[2]);
1588+
flags = jl_unbox_uint8(args[2]);
1589+
}
1590+
jl_module_using((jl_module_t *)args[0], (jl_module_t *)args[1], flags);
15861591
return jl_nothing;
15871592
}
15881593

src/gc-stock.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,9 +2168,9 @@ STATIC_INLINE void gc_mark_module_binding(jl_ptls_t ptls, jl_module_t *mb_parent
21682168
jl_value_t *obj_parent = (jl_value_t *)mb_parent;
21692169
struct _jl_module_using *objary_begin = (struct _jl_module_using *)mb_parent->usings.items;
21702170
struct _jl_module_using *objary_end = objary_begin + nusings;
2171-
static_assert(sizeof(struct _jl_module_using) == 3*sizeof(void *), "Mismatch in _jl_module_using size");
2171+
static_assert(sizeof(struct _jl_module_using) == 4*sizeof(void *), "Mismatch in _jl_module_using size");
21722172
static_assert(offsetof(struct _jl_module_using, mod) == 0, "Expected `mod` at the beginning of _jl_module_using");
2173-
gc_mark_objarray(ptls, obj_parent, (jl_value_t**)objary_begin, (jl_value_t**)objary_end, 3, nptr);
2173+
gc_mark_objarray(ptls, obj_parent, (jl_value_t**)objary_begin, (jl_value_t**)objary_end, 4, nptr);
21742174
}
21752175
else {
21762176
gc_mark_push_remset(ptls, (jl_value_t *)mb_parent, nptr);

src/julia.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,9 @@ static const uint8_t PARTITION_FLAG_DEPRECATED = 0x20;
765765
// implies _DEPRECATED. However, the reverse is not true. Such bindings are usually used for functions,
766766
// where calling the function itself will provide a (better) deprecation warning/error.
767767
static const uint8_t PARTITION_FLAG_DEPWARN = 0x40;
768+
// _IMPLICITLY_EXPORTED: This binding partition is implicitly exported via @reexport. Unlike _EXPORTED,
769+
// this flag is set during implicit resolution and can be removed if the resolution changes.
770+
static const uint8_t PARTITION_FLAG_IMPLICITLY_EXPORTED = 0x80;
768771

769772
#if defined(_COMPILER_MICROSOFT_)
770773
#define JL_ALIGNED_ATTR(alignment) \
@@ -853,6 +856,8 @@ typedef struct _jl_module_t {
853856
int8_t max_methods;
854857
// If cleared no binding partition in this module has PARTITION_FLAG_EXPORTED and min_world > jl_require_world.
855858
_Atomic(int8_t) export_set_changed_since_require_world;
859+
// Set if this module has any reexport usings (used to bypass fast-path in implicit resolution)
860+
_Atomic(int8_t) has_reexports;
856861
jl_mutex_t lock;
857862
intptr_t hash;
858863
} jl_module_t;
@@ -861,8 +866,12 @@ struct _jl_module_using {
861866
jl_module_t *mod;
862867
size_t min_world;
863868
size_t max_world;
869+
size_t flags;
864870
};
865871

872+
// Flags for _jl_module_using.flags
873+
static const uint8_t JL_MODULE_USING_REEXPORT = 0x1;
874+
866875
struct _jl_globalref_t {
867876
JL_DATA_TYPE
868877
jl_module_t *mod;
@@ -2038,7 +2047,7 @@ JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val(jl_binding_t *b JL_
20382047
JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val2(jl_binding_t *b JL_ROOTING_ARGUMENT, jl_module_t *mod, jl_sym_t *var, jl_value_t *val JL_ROOTED_ARGUMENT JL_MAYBE_UNROOTED, enum jl_partition_kind);
20392048
JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici);
20402049
JL_DLLEXPORT void jl_import_module(jl_task_t *ct, jl_module_t *m, jl_module_t *import, jl_sym_t *asname);
2041-
JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from);
2050+
JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from, size_t flags);
20422051
int jl_module_public_(jl_module_t *from, jl_sym_t *s, int exported, size_t new_world);
20432052
JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *s);
20442053
JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var);

src/julia_internal.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -913,19 +913,19 @@ typedef struct _modstack_t {
913913
// The analyzer doesn't like looking through the arraylist, so just model the
914914
// access for it using this function
915915
STATIC_INLINE struct _jl_module_using *module_usings_getidx(jl_module_t *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT {
916-
return (struct _jl_module_using *)&(m->usings.items[3*i]);
916+
return (struct _jl_module_using *)&(m->usings.items[4*i]);
917917
}
918918
STATIC_INLINE jl_module_t *module_usings_getmod(jl_module_t *m JL_PROPAGATES_ROOT, size_t i) JL_NOTSAFEPOINT {
919919
return module_usings_getidx(m, i)->mod;
920920
}
921921
#endif
922922

923923
STATIC_INLINE size_t module_usings_length(jl_module_t *m) JL_NOTSAFEPOINT {
924-
return m->usings.len/3;
924+
return m->usings.len/4;
925925
}
926926

927927
STATIC_INLINE size_t module_usings_max(jl_module_t *m) JL_NOTSAFEPOINT {
928-
return m->usings.max/3;
928+
return m->usings.max/4;
929929
}
930930

931931
JL_DLLEXPORT jl_sym_t *jl_module_name(jl_module_t *m) JL_NOTSAFEPOINT;
@@ -1026,6 +1026,10 @@ STATIC_INLINE int jl_bkind_is_real_constant(enum jl_partition_kind kind) JL_NOTS
10261026
return kind == PARTITION_KIND_IMPLICIT_CONST || kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_CONST_IMPORT;
10271027
}
10281028

1029+
STATIC_INLINE int jl_bpart_is_exported(uint8_t flags) JL_NOTSAFEPOINT {
1030+
return flags & (PARTITION_FLAG_EXPORTED | PARTITION_FLAG_IMPLICITLY_EXPORTED);
1031+
}
1032+
10291033
JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world) JL_GLOBALLY_ROOTED;
10301034
JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_with_hint(jl_binding_t *b JL_PROPAGATES_ROOT, jl_binding_partition_t *previous_part, size_t world) JL_GLOBALLY_ROOTED;
10311035
JL_DLLEXPORT jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b JL_PROPAGATES_ROOT, size_t min_world, size_t max_world) JL_GLOBALLY_ROOTED;

0 commit comments

Comments
 (0)