Skip to content

Commit 2774d62

Browse files
committed
add back depwarn option if invokelatest succeeded
Also remove `isdefined` shim, as that feels less logical to me with the current implementation that uses invokelatest. If the implementation was changed to use `invokenext`, I think it would make sense to add that back and also to update `names` to return these also. But invoke next (similar to backdated constants and #59735) prints an incorrect suggestion of using invokelatest to get the same behavior. That message is correct with this current commit state, but wrong relative to the current implementations that use backdated.
1 parent 6a3b6e5 commit 2774d62

File tree

4 files changed

+85
-61
lines changed

4 files changed

+85
-61
lines changed

src/julia.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -792,7 +792,8 @@ enum jl_binding_flags {
792792
BINDING_FLAG_PUBLICP = 0x2,
793793
// Set if any methods defined in this module implicitly reference
794794
// this binding. If not, invalidation is optimized.
795-
BINDING_FLAG_ANY_IMPLICIT_EDGES = 0x4
795+
BINDING_FLAG_ANY_IMPLICIT_EDGES = 0x4,
796+
BINDING_FLAG_DID_PRINT_INVOKELATEST_ADMONITION = 0x8
796797
};
797798

798799
typedef struct _jl_binding_t {

src/module.c

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,22 @@ JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var
822822
return b ? b->globalref->mod : m;
823823
}
824824

825+
static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT
826+
{
827+
jl_safe_printf(
828+
"WARNING: Detected access to binding `%s.%s` in a world prior to its definition world.\n"
829+
" Julia 1.12 has introduced more strict world age semantics for global bindings.\n"
830+
" !!! This code will error in future versions of Julia.\n"
831+
"Hint: Add an appropriate `invokelatest` around the access to this binding.\n"
832+
"To make this warning an error, and hence obtain a stack trace, use `julia --depwarn=error`.\n",
833+
jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name));
834+
}
835+
836+
static inline void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT
837+
{
838+
if (!(jl_atomic_fetch_or_relaxed(&b->flags, BINDING_FLAG_DID_PRINT_INVOKELATEST_ADMONITION) & BINDING_FLAG_DID_PRINT_INVOKELATEST_ADMONITION))
839+
print_backdate_admonition(b);
840+
}
825841

826842
JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b)
827843
{
@@ -833,18 +849,20 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_in_world(jl_binding_t *b, size_t w
833849
jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
834850
jl_walk_binding_inplace(&b, &bpart, world);
835851
enum jl_partition_kind kind = jl_binding_kind(bpart);
836-
if (kind == PARTITION_KIND_GUARD) {
837-
// Retry lookup in current world counter for guard partitions (unless in pure callback)
838-
if (!jl_current_task->ptls->in_pure_callback) {
852+
if (jl_bkind_is_some_guard(kind)) {
853+
// Retry lookup in current world counter for guard partitions (unless in pure callback or with deprecations disabled)
854+
if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR && !jl_current_task->ptls->in_pure_callback) {
839855
size_t current_world = jl_atomic_load_acquire(&jl_world_counter);
840-
if (current_world != world) {
841-
return jl_get_binding_value_in_world(b, current_world);
856+
if (current_world != world) { {
857+
jl_value_t *futurevalue = jl_get_binding_value_in_world(b, current_world);
858+
if (futurevalue)
859+
check_backdated_binding(b, kind);
860+
return futurevalue;
861+
}
842862
}
843863
}
844864
return NULL;
845865
}
846-
if (jl_bkind_is_some_guard(kind))
847-
return NULL;
848866
if (jl_bkind_is_some_constant(kind)) {
849867
return bpart->restriction;
850868
}
@@ -865,18 +883,19 @@ static jl_value_t *jl_get_binding_value_depwarn(jl_binding_t *b, size_t world)
865883
jl_walk_binding_inplace(&b, &bpart, world);
866884
}
867885
enum jl_partition_kind kind = jl_binding_kind(bpart);
868-
if (kind == PARTITION_KIND_GUARD) {
869-
// Retry lookup in current world counter for guard partitions (unless in pure callback)
870-
if (!jl_current_task->ptls->in_pure_callback) {
886+
if (jl_bkind_is_some_guard(kind)) {
887+
// Retry lookup in current world counter for guard partitions (unless in pure callback or with deprecations disabled)
888+
if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR && !jl_current_task->ptls->in_pure_callback) {
871889
size_t current_world = jl_atomic_load_acquire(&jl_world_counter);
872890
if (current_world != world) {
873-
return jl_get_binding_value_depwarn(b, current_world);
891+
jl_value_t *futurevalue = jl_get_binding_value_depwarn(b, current_world);
892+
if (futurevalue)
893+
check_backdated_binding(b, kind);
894+
return futurevalue;
874895
}
875896
}
876897
return NULL;
877898
}
878-
if (jl_bkind_is_some_guard(kind))
879-
return NULL;
880899
if (jl_bkind_is_some_constant(kind)) {
881900
return bpart->restriction;
882901
}
@@ -890,18 +909,19 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b)
890909
jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
891910
jl_walk_binding_inplace(&b, &bpart, world);
892911
enum jl_partition_kind kind = jl_binding_kind(bpart);
893-
if (kind == PARTITION_KIND_GUARD) {
894-
// Retry lookup in current world counter for guard partitions (unless in pure callback)
895-
if (!jl_current_task->ptls->in_pure_callback) {
912+
if (jl_bkind_is_some_guard(kind)) {
913+
// Retry lookup in current world counter for guard partitions (unless in pure callback or with deprecations disabled)
914+
if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR && !jl_current_task->ptls->in_pure_callback) {
896915
size_t current_world = jl_atomic_load_acquire(&jl_world_counter);
897916
if (current_world != world) {
898-
return jl_get_binding_value_in_world(b, current_world);
917+
jl_value_t *futurevalue = jl_get_binding_value_in_world(b, current_world);
918+
if (futurevalue)
919+
check_backdated_binding(b, kind);
920+
return futurevalue;
899921
}
900922
}
901923
return NULL;
902924
}
903-
if (jl_bkind_is_some_guard(kind))
904-
return NULL;
905925
if (jl_bkind_is_some_constant(kind)) {
906926
return bpart->restriction;
907927
}
@@ -1438,31 +1458,7 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // u
14381458
jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age);
14391459
}
14401460
enum jl_partition_kind kind = jl_binding_kind(bpart);
1441-
if (kind == PARTITION_KIND_GUARD) {
1442-
// Retry lookup in current world counter for guard partitions (unless in pure callback)
1443-
if (!jl_current_task->ptls->in_pure_callback) {
1444-
size_t current_world = jl_atomic_load_acquire(&jl_world_counter);
1445-
if (current_world != jl_current_task->world_age) {
1446-
jl_binding_partition_t *current_bpart = jl_get_binding_partition(b, current_world);
1447-
if (!current_bpart)
1448-
return 0;
1449-
if (!allow_import) {
1450-
if (!current_bpart || jl_bkind_is_some_import(jl_binding_kind(current_bpart)))
1451-
return 0;
1452-
} else {
1453-
jl_walk_binding_inplace(&b, &current_bpart, current_world);
1454-
}
1455-
enum jl_partition_kind current_kind = jl_binding_kind(current_bpart);
1456-
if (jl_bkind_is_some_guard(current_kind))
1457-
return 0;
1458-
if (jl_bkind_is_defined_constant(current_kind)) {
1459-
return 1;
1460-
}
1461-
return jl_atomic_load(&b->value) != NULL;
1462-
}
1463-
}
1464-
return 0;
1465-
}
1461+
// isdefined queries only check in the current world, and do not retry with invokelatest
14661462
if (jl_bkind_is_some_guard(kind))
14671463
return 0;
14681464
if (jl_bkind_is_defined_constant(kind)) {

test/rebinding.jl

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,42 +111,70 @@ module Rebinding
111111
# Test world counter retry for newly defined bindings
112112
@testset "invokelatest for guard bindings" begin
113113
test_func_1 = (x) -> (@eval const new_binding_1 = $x; @isdefined(new_binding_1))
114-
@test Base.invokelatest(test_func_1, 1)
114+
@test !Base.invokelatest(test_func_1, 1)
115115
@test Base.invokelatest(test_func_1, 2)
116116
Base.delete_binding(Rebinding, :new_binding_1)
117-
@test Base.invokelatest(test_func_1, 3)
117+
@test !Base.invokelatest(test_func_1, 3)
118118

119119
test_func_2 = (x) -> (@eval const new_binding_2 = $x; new_binding_2)
120-
@test Base.invokelatest(test_func_2, 3) === 3
120+
if Base.JLOptions().depwarn <= 1
121+
@test 3 === @test_warn "Detected access to binding `Rebinding.new_binding_2`" Base.invokelatest(test_func_2, 3)
122+
else
123+
@test_throws UndefVarError Base.invokelatest(test_func_2, 3)
124+
end
121125
@test Base.invokelatest(test_func_2, 4) === 3
122126
@test Base.invokelatest(test_func_2, 5) === 4
123127
Base.delete_binding(Rebinding, :new_binding_2)
124-
@test Base.invokelatest(test_func_2, 6) === 6
128+
if Base.JLOptions().depwarn <= 1
129+
@test 6 === @test_warn "Detected access to binding `Rebinding.new_binding_2`" Base.invokelatest(test_func_2, 6)
130+
else
131+
@test_throws UndefVarError Base.invokelatest(test_func_2, 6)
132+
end
125133

126134
test_func_3 = (x) -> (@eval new_binding_3 = $x; @isdefined(new_binding_3))
127-
@test Base.invokelatest(test_func_3, 5)
135+
@test !Base.invokelatest(test_func_3, 5)
128136
@test Base.invokelatest(test_func_3, 6)
129137
Base.delete_binding(Rebinding, :new_binding_3)
130-
@test Base.invokelatest(test_func_3, 7)
138+
@test !Base.invokelatest(test_func_3, 7)
131139

132140
test_func_4 = (x) -> (@eval new_binding_4 = $x; new_binding_4)
133-
@test Base.invokelatest(test_func_4, 7) === 7
141+
if Base.JLOptions().depwarn <= 1
142+
@test 7 === @test_warn "Detected access to binding `Rebinding.new_binding_4`" Base.invokelatest(test_func_4, 7)
143+
else
144+
@test_throws UndefVarError Base.invokelatest(test_func_4, 7)
145+
end
134146
@test Base.invokelatest(test_func_4, 8) === 8
135147
@test Base.invokelatest(test_func_4, 9) === 9
136148
Base.delete_binding(Rebinding, :new_binding_4)
137-
@test Base.invokelatest(test_func_4, 10) === 10
149+
if Base.JLOptions().depwarn <= 1
150+
@test 10 === @test_warn "Detected access to binding `Rebinding.new_binding_4`" Base.invokelatest(test_func_4, 10)
151+
else
152+
@test_throws UndefVarError Base.invokelatest(test_func_4, 10)
153+
end
138154

139155
test_func_5 = (x) -> (@eval using Base: $x as new_import_1; @isdefined(new_import_1))
140-
@test Base.invokelatest(test_func_5, :sin)
156+
@test !Base.invokelatest(test_func_5, :sin)
141157
Base.delete_binding(Rebinding, :new_import_1)
142-
@test Base.invokelatest(test_func_5, :cos)
158+
@test !Base.invokelatest(test_func_5, :cos)
143159

144160
test_func_6 = (x) -> (@eval using Base: $x as new_import_2; new_import_2)
145-
@test Base.invokelatest(test_func_6, :sin) === Base.sin
161+
if Base.JLOptions().depwarn <= 1
162+
@test Base.sin === @test_warn "Detected access to binding `Rebinding.new_import_2`" Base.invokelatest(test_func_6, :sin)
163+
else
164+
@test_throws UndefVarError Base.invokelatest(test_func_6, :sin)
165+
end
146166
Base.delete_binding(Rebinding, :new_import_2)
147-
@test Base.invokelatest(test_func_6, :cos) === Base.cos
167+
if Base.JLOptions().depwarn <= 1
168+
@test Base.cos === @test_warn "Detected access to binding `Rebinding.new_import_2`" Base.invokelatest(test_func_6, :cos)
169+
else
170+
@test_throws UndefVarError Base.invokelatest(test_func_6, :cos)
171+
end
148172
Base.delete_binding(Rebinding, :new_import_2)
149-
@test Base.invokelatest(test_func_6, :tan) === Base.tan
173+
if Base.JLOptions().depwarn <= 1
174+
@test Base.tan === @test_warn "Detected access to binding `Rebinding.new_import_2`" Base.invokelatest(test_func_6, :tan)
175+
else
176+
@test_throws UndefVarError Base.invokelatest(test_func_6, :tan)
177+
end
150178

151179
@generated function test_generated_future(x)
152180
return future_generated_binding

test/worlds.jl

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -557,9 +557,8 @@ struct FooBackdated
557557

558558
FooBackdated() = new(FooBackdated[])
559559
end
560-
# With world counter retry, `isdefinedglobal` will retry with current world counter
561-
# and successfully find the binding regardless of depwarn setting.
562-
@test Base.invoke_in_world(before_backdate_age, isdefinedglobal, @__MODULE__, :FooBackdated)
560+
@test isdefinedglobal(@__MODULE__, :FooBackdated)
561+
@test !Base.invoke_in_world(before_backdate_age, isdefinedglobal, @__MODULE__, :FooBackdated)
563562

564563
# Test that ambiguous binding intersect the using'd binding's world ranges
565564
module AmbigWorldTest

0 commit comments

Comments
 (0)