Skip to content

Commit 4c0bff4

Browse files
committed
Add a warning for auto-import of types
This adds a warning for the auto-import of types cases (#25744) that we have long considered a bit of a bug, but didn't want to change because it is too breaking. The reason to do it now is that the binding rework has made this case more problematic (see #57290). To summarize, the question is what happens when the compiler sees `f(x) = ...` and `f` is currently and implicitly imported binding. There are two options: 1. We add a method to the generic function referred to by `f`, or 2. We create a new generic function `f` in the current module. Historically, case 1 has the additional complication that this error'd unless `f` is a type. It is my impression that a lot of existing code did not have a particularly good understanding of the resolved-ness dependence of this behavior. However, because case 1 errors for generic functions, it appears that existing code generally expects case 2. On the other hand, for types, there is existing code in both directions (#57290 is an example of case 2; see #57302 for examples of case 1). That said, case 1 is more common (because types tend to be resolved because they're used in signatures at toplevel). Thus, to retain compatibility, the current behavior on master (where resolvedness is no longer available) is that we always choose case 2 for functions and case 1 for types. This inconsistency is unfortunate, but I tried resolving this in either way (making all situations case 1 or all case 2) and the result was too breaking. Nevertheless, it is problematic that there is existing code that expects case 2 beavior for types and we should help users to know what the correct way to fix it is. The proposed resolution is thus: 1. Retain case 1 behavior for types 2. Make it a warning to use, encouraging people to explicitly import, since we generally consider the #25744 case a bug. Example: ``` julia> module Foo String(i::Int) = i end WARNING: Type Core.String was auto-`import`ed in `Foo`. NOTE: This behavior is deprecated and may change in future Julia versions. NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution. Hint: To retain the current behavior, add an explicit `import Core: String` in Foo. Hint: To create a new generic function of the same name use `function String end`. Main.Foo ```
1 parent 778e079 commit 4c0bff4

File tree

6 files changed

+28
-33
lines changed

6 files changed

+28
-33
lines changed

src/codegen.cpp

+4-21
Original file line numberDiff line numberDiff line change
@@ -1019,7 +1019,7 @@ static const auto jlgenericfunction_func = new JuliaFunction<>{
10191019
auto T_jlvalue = JuliaType::get_jlvalue_ty(C);
10201020
auto T_pjlvalue = PointerType::get(T_jlvalue, 0);
10211021
auto T_prjlvalue = PointerType::get(T_jlvalue, AddressSpace::Tracked);
1022-
return FunctionType::get(T_prjlvalue, {T_pjlvalue, T_pjlvalue, T_pjlvalue}, false);
1022+
return FunctionType::get(T_prjlvalue, {T_pjlvalue, T_pjlvalue}, false);
10231023
},
10241024
nullptr,
10251025
};
@@ -6868,8 +6868,6 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_
68686868
jl_value_t *mn = args[0];
68696869
assert(jl_is_symbol(mn) || jl_is_slotnumber(mn) || jl_is_globalref(mn));
68706870

6871-
Value *bp = NULL, *name;
6872-
jl_binding_t *bnd = NULL;
68736871
bool issym = jl_is_symbol(mn);
68746872
bool isglobalref = !issym && jl_is_globalref(mn);
68756873
jl_module_t *mod = ctx.module;
@@ -6878,26 +6876,11 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_
68786876
mod = jl_globalref_mod(mn);
68796877
mn = (jl_value_t*)jl_globalref_name(mn);
68806878
}
6881-
JL_TRY {
6882-
if (jl_symbol_name((jl_sym_t*)mn)[0] == '@')
6883-
jl_errorf("macro definition not allowed inside a local scope");
6884-
name = literal_pointer_val(ctx, mn);
6885-
bnd = jl_get_binding_for_method_def(mod, (jl_sym_t*)mn);
6886-
}
6887-
JL_CATCH {
6888-
jl_value_t *e = jl_current_exception(jl_current_task);
6889-
// errors. boo. :(
6890-
JL_GC_PUSH1(&e);
6891-
e = jl_as_global_root(e, 1);
6892-
JL_GC_POP();
6893-
raise_exception(ctx, literal_pointer_val(ctx, e));
6894-
return ghostValue(ctx, jl_nothing_type);
6895-
}
6896-
bp = julia_binding_gv(ctx, bnd);
68976879
jl_cgval_t gf = mark_julia_type(
68986880
ctx,
6899-
ctx.builder.CreateCall(prepare_call(jlgenericfunction_func), { bp,
6900-
literal_pointer_val(ctx, (jl_value_t*)mod), name
6881+
ctx.builder.CreateCall(prepare_call(jlgenericfunction_func), {
6882+
literal_pointer_val(ctx, (jl_value_t*)mod),
6883+
literal_pointer_val(ctx, (jl_value_t*)mn)
69016884
}),
69026885
true,
69036886
jl_function_type);

src/interpreter.c

+1-2
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ static jl_value_t *eval_methoddef(jl_expr_t *ex, interpreter_state *s)
9393
if (!jl_is_symbol(fname)) {
9494
jl_error("method: invalid declaration");
9595
}
96-
jl_binding_t *b = jl_get_binding_for_method_def(modu, fname);
97-
return jl_declare_const_gf(b, modu, fname);
96+
return jl_declare_const_gf(modu, fname);
9897
}
9998

10099
jl_value_t *atypes = NULL, *meth = NULL, *fname = NULL;

src/julia.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -1901,7 +1901,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b JL_PROPAGATES_ROOT
19011901
JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_const(jl_binding_t *b JL_PROPAGATES_ROOT);
19021902
JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved(jl_binding_t *b JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT;
19031903
JL_DLLEXPORT jl_value_t *jl_get_binding_value_if_resolved_and_const(jl_binding_t *b JL_PROPAGATES_ROOT) JL_NOTSAFEPOINT;
1904-
JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_binding_t *b, jl_module_t *mod, jl_sym_t *name);
1904+
JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_module_t *mod, jl_sym_t *name);
19051905
JL_DLLEXPORT jl_method_t *jl_method_def(jl_svec_t *argdata, jl_methtable_t *mt, jl_code_info_t *f, jl_module_t *module);
19061906
JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo, size_t world, jl_code_instance_t **cache);
19071907
JL_DLLEXPORT jl_code_info_t *jl_copy_code_info(jl_code_info_t *src);
@@ -2051,7 +2051,7 @@ JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var);
20512051
// get binding for assignment
20522052
JL_DLLEXPORT void jl_check_binding_currently_writable(jl_binding_t *b, jl_module_t *m, jl_sym_t *s);
20532053
JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var);
2054-
JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var);
2054+
JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var, size_t new_world);
20552055
JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import);
20562056
JL_DLLEXPORT int jl_defines_or_exports_p(jl_module_t *m, jl_sym_t *var);
20572057
JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var);

src/method.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -1050,10 +1050,11 @@ JL_DLLEXPORT void jl_check_gf(jl_value_t *gf, jl_sym_t *name)
10501050
jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(name));
10511051
}
10521052

1053-
JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_binding_t *b, jl_module_t *mod, jl_sym_t *name)
1053+
JL_DLLEXPORT jl_value_t *jl_declare_const_gf(jl_module_t *mod, jl_sym_t *name)
10541054
{
10551055
JL_LOCK(&world_counter_lock);
10561056
size_t new_world = jl_atomic_load_relaxed(&jl_world_counter) + 1;
1057+
jl_binding_t *b = jl_get_binding_for_method_def(mod, name, new_world);
10571058
jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world);
10581059
jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction);
10591060
jl_value_t *gf = NULL;

src/module.c

+18-6
Original file line numberDiff line numberDiff line change
@@ -588,10 +588,10 @@ JL_DLLEXPORT jl_value_t *jl_reresolve_binding_value_seqcst(jl_binding_t *b)
588588

589589
// get binding for adding a method
590590
// like jl_get_binding_wr, but has different error paths and messages
591-
JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var)
591+
JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_t *var, size_t new_world)
592592
{
593593
jl_binding_t *b = jl_get_module_binding(m, var, 1);
594-
jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
594+
jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world);
595595
jl_ptr_kind_union_t pku = jl_atomic_load_relaxed(&bpart->restriction);
596596
enum jl_partition_kind kind = decode_restriction_kind(pku);
597597
if (kind == BINDING_KIND_GLOBAL || kind == BINDING_KIND_DECLARED || jl_bkind_is_some_constant(decode_restriction_kind(pku)))
@@ -601,7 +601,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_
601601
return b;
602602
}
603603
jl_binding_t *ownerb = b;
604-
pku = jl_walk_binding_inplace(&ownerb, &bpart, jl_current_task->world_age);
604+
pku = jl_walk_binding_inplace(&ownerb, &bpart, new_world);
605605
jl_value_t *f = NULL;
606606
if (jl_bkind_is_some_constant(decode_restriction_kind(pku)))
607607
f = decode_restriction_value(pku);
@@ -629,10 +629,22 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_for_method_def(jl_module_t *m, jl_sym_
629629
jl_symbol_name(m->name), from ? jl_symbol_name(from->name) : "<null>", jl_symbol_name(var));
630630
}
631631
}
632-
else if (strcmp(jl_symbol_name(var), "=>") == 0 && (kind == BINDING_KIND_IMPLICIT || kind == BINDING_KIND_EXPLICIT)) {
632+
else if (kind != BINDING_KIND_IMPORTED) {
633+
int should_error = strcmp(jl_symbol_name(var), "=>") == 0;
633634
jl_module_t *from = jl_binding_dbgmodule(b, m, var);
634-
jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended",
635-
jl_symbol_name(m->name), from ? jl_symbol_name(from->name) : "<null>", jl_symbol_name(var));
635+
if (should_error)
636+
jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended",
637+
jl_symbol_name(m->name), from ? jl_symbol_name(from->name) : "<null>", jl_symbol_name(var));
638+
else
639+
jl_printf(JL_STDERR, "WARNING: Type %s.%s was auto-`import`ed in `%s`.\n"
640+
"NOTE: This behavior is deprecated and may error in future Julia versions.\n"
641+
"NOTE: This behavior may have differed in Julia versions prior to 1.12 depending on binding resolution."
642+
"Hint: To retain the current behavior, add an explicit `import %s: %s`\n"
643+
"Hint: To create a new generic function of the same name use `function %s end`.\n",
644+
from ? jl_symbol_name(from->name) : "<null>", jl_symbol_name(var),
645+
jl_symbol_name(m->name),
646+
from ? jl_symbol_name(from->name) : "<null>", jl_symbol_name(var),
647+
jl_symbol_name(var));
636648
}
637649
return ownerb;
638650
}

test/errorshow.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ let err_str
438438
@test occursin("For element-wise subtraction, use broadcasting with dot syntax: array .- scalar", err_str)
439439
end
440440

441-
441+
import Core: String
442442
method_defs_lineno = @__LINE__() + 1
443443
String() = throw(ErrorException("1"))
444444
(::String)() = throw(ErrorException("2"))

0 commit comments

Comments
 (0)