diff --git a/NEWS.md b/NEWS.md index a1167283d0b63..86e1de739d2af 100644 --- a/NEWS.md +++ b/NEWS.md @@ -22,6 +22,7 @@ New language features ability of these objects ([#43305]). * Empty n-dimensional arrays can now be created using multiple semicolons inside square brackets, i.e. `[;;;]` creates a 0×0×0 `Array`. ([#41618]) * Type annotations can now be added to global variables to make accessing the variable type stable. ([#43671]) +* It is now possible to assign to bindings in another module using `setproperty!(::Module, ::Symbol, x)`. ([#44137]) Language changes ---------------- diff --git a/base/Base.jl b/base/Base.jl index ece7cbe96df53..b4e50c156015f 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -29,7 +29,6 @@ macro noinline() Expr(:meta, :noinline) end # with ambiguities by defining a few common and critical operations # (and these don't need the extra convert code) getproperty(x::Module, f::Symbol) = (@inline; getfield(x, f)) -setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) # to get a decent error getproperty(x::Type, f::Symbol) = (@inline; getfield(x, f)) setproperty!(x::Type, f::Symbol, v) = error("setfield! fields of Types should not be changed") getproperty(x::Tuple, f::Int) = (@inline; getfield(x, f)) @@ -41,7 +40,11 @@ setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), dotgetproperty(x, f) = getproperty(x, f) getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order)) -setproperty!(x::Module, f::Symbol, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error +function setproperty!(x::Module, f::Symbol, v, order::Symbol=:monotonic) + @inline + val::Core.get_binding_type(x, f) = v + return setfield!(x, f, val, order) +end getproperty(x::Type, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order)) setproperty!(x::Type, f::Symbol, v, order::Symbol) = error("setfield! fields of Types should not be changed") getproperty(x::Tuple, f::Int, order::Symbol) = (@inline; getfield(x, f, order)) diff --git a/base/compiler/tfuncs.jl b/base/compiler/tfuncs.jl index 2238d43d65b27..856691c15af92 100644 --- a/base/compiler/tfuncs.jl +++ b/base/compiler/tfuncs.jl @@ -817,7 +817,6 @@ function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool if isa(name, Const) nv = name.val if isa(sv, Module) - setfield && return Bottom if isa(nv, Symbol) return abstract_eval_global(sv, nv) end @@ -865,7 +864,6 @@ function _getfield_tfunc(@nospecialize(s00), @nospecialize(name), setfield::Bool return Bottom end if s <: Module - setfield && return Bottom hasintersect(widenconst(name), Symbol) || return Bottom return Any end diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 131f0f26562cd..44645017d9326 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1979,6 +1979,9 @@ declared `@atomic`, this specification is mandatory. Otherwise, if not declared as `@atomic`, it must be `:not_atomic` if specified. See also [`setproperty!`](@ref Base.setproperty!). +!!! compat "Julia 1.8" + `setfield!` on modules requires at least Julia 1.8. + # Examples ```jldoctest julia> mutable struct MyMutableStruct @@ -2731,6 +2734,9 @@ The syntax `a.b = c` calls `setproperty!(a, :b, c)`. The syntax `@atomic order a.b = c` calls `setproperty!(a, :b, c, :order)` and the syntax `@atomic a.b = c` calls `getproperty(a, :b, :sequentially_consistent)`. +!!! compat "Julia 1.8" + `setproperty!` on modules requires at least Julia 1.8. + See also [`setfield!`](@ref Core.setfield!), [`propertynames`](@ref Base.propertynames) and [`getproperty`](@ref Base.getproperty). diff --git a/doc/src/manual/variables-and-scoping.md b/doc/src/manual/variables-and-scoping.md index 6e94037f3e564..f31e176038e2c 100644 --- a/doc/src/manual/variables-and-scoping.md +++ b/doc/src/manual/variables-and-scoping.md @@ -91,12 +91,6 @@ julia> module D b = a # errors as D's global scope is separate from A's end; ERROR: UndefVarError: a not defined - -julia> module E - import ..A # make module A available - A.a = 2 # throws below error - end; -ERROR: cannot assign variables in other modules ``` If a top-level expression contains a variable declaration with keyword `local`, diff --git a/doc/src/manual/variables.md b/doc/src/manual/variables.md index f61503d99a67c..0dfc4f508577f 100644 --- a/doc/src/manual/variables.md +++ b/doc/src/manual/variables.md @@ -81,13 +81,13 @@ julia> pi π = 3.1415926535897... julia> pi = 3 -ERROR: cannot assign a value to variable MathConstants.pi from module Main +ERROR: cannot assign a value to imported variable MathConstants.pi from module Main julia> sqrt(100) 10.0 julia> sqrt = 4 -ERROR: cannot assign a value to variable Base.sqrt from module Main +ERROR: cannot assign a value to imported variable Base.sqrt from module Main ``` ## [Allowed Variable Names](@id man-allowed-variable-names) diff --git a/src/builtins.c b/src/builtins.c index ca2f56adaf6d8..062ce3eb3b273 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -940,22 +940,33 @@ JL_CALLABLE(jl_f_setfield) enum jl_memory_order order = jl_memory_order_notatomic; JL_NARGS(setfield!, 3, 4); if (nargs == 4) { - JL_TYPECHK(getfield, symbol, args[3]); + JL_TYPECHK(setfield!, symbol, args[3]); order = jl_get_atomic_order_checked((jl_sym_t*)args[3], 0, 1); } jl_value_t *v = args[0]; jl_datatype_t *st = (jl_datatype_t*)jl_typeof(v); - size_t idx = get_checked_fieldindex("setfield!", st, v, args[1], 1); - int isatomic = !!jl_field_isatomic(st, idx); - if (isatomic == (order == jl_memory_order_notatomic)) - jl_atomic_error(isatomic ? "setfield!: atomic field cannot be written non-atomically" - : "setfield!: non-atomic field cannot be written atomically"); - jl_value_t *ft = jl_field_type_concrete(st, idx); - if (!jl_isa(args[2], ft)) - jl_type_error("setfield!", ft, args[2]); - if (order >= jl_memory_order_acq_rel || order == jl_memory_order_release) - jl_fence(); // `st->[idx]` will have at least relaxed ordering - set_nth_field(st, v, idx, args[2], isatomic); + if (st == jl_module_type) { + JL_TYPECHK(setfield!, symbol, args[1]); + if (order == jl_memory_order_notatomic) + jl_atomic_error("setfield!: module binding cannot be written non-atomically"); + jl_binding_t *b = jl_get_binding_wr((jl_module_t*)v, (jl_sym_t*)args[1], 1); + if (order >= jl_memory_order_acq_rel || order == jl_memory_order_release) + jl_fence(); + jl_checked_assignment(b, args[2]); + } + else { + size_t idx = get_checked_fieldindex("setfield!", st, v, args[1], 1); + int isatomic = !!jl_field_isatomic(st, idx); + if (isatomic == (order == jl_memory_order_notatomic)) + jl_atomic_error(isatomic ? "setfield!: atomic field cannot be written non-atomically" + : "setfield!: non-atomic field cannot be written atomically"); + jl_value_t *ft = jl_field_type_concrete(st, idx); + if (!jl_isa(args[2], ft)) + jl_type_error("setfield!", ft, args[2]); + if (order >= jl_memory_order_acq_rel || order == jl_memory_order_release) + jl_fence(); // `st->[idx]` will have at least relaxed ordering + set_nth_field(st, v, idx, args[2], isatomic); + } return args[2]; } diff --git a/src/codegen.cpp b/src/codegen.cpp index 46f6899028f13..31da4a85ea0f9 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3873,7 +3873,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t assert(b != NULL); if (b->owner != m) { char *msg; - (void)asprintf(&msg, "cannot assign a value to variable %s.%s from module %s", + (void)asprintf(&msg, "cannot assign a value to imported variable %s.%s from module %s", jl_symbol_name(b->owner->name), jl_symbol_name(s), jl_symbol_name(m->name)); emit_error(ctx, msg); free(msg); diff --git a/src/module.c b/src/module.c index 3de4cb91d5b3b..65d73ef2a48bf 100644 --- a/src/module.c +++ b/src/module.c @@ -185,7 +185,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, } else if (error) { JL_UNLOCK(&m->lock); - jl_errorf("cannot assign a value to variable %s.%s from module %s", + jl_errorf("cannot assign a value to imported variable %s.%s from module %s", jl_symbol_name(b->owner->name), jl_symbol_name(var), jl_symbol_name(m->name)); } } diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 79030bd910990..f1120694f6513 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1725,10 +1725,10 @@ end @test setfield!_tfunc(Some, Symbol, Int) === Union{} @test setfield!_tfunc(Some{Any}, Int, Int) === Union{} @test setfield!_tfunc(Some, Int, Int) === Union{} -@test setfield!_tfunc(Const(@__MODULE__), Const(:v), Int) === Union{} +@test setfield!_tfunc(Const(@__MODULE__), Const(:v), Int) === Int @test setfield!_tfunc(Const(@__MODULE__), Int, Int) === Union{} -@test setfield!_tfunc(Module, Const(:v), Int) === Union{} -@test setfield!_tfunc(Union{Module,Base.RefValue{Any}}, Const(:v), Int) === Union{} +@test setfield!_tfunc(Module, Const(:v), Int) === Int +@test setfield!_tfunc(Union{Module,Base.RefValue{Any}}, Const(:v), Int) === Int @test setfield!_tfunc(ABCDconst, Const(:a), Any) === Union{} @test setfield!_tfunc(ABCDconst, Const(:b), Any) === Union{} @test setfield!_tfunc(ABCDconst, Const(:d), Any) === Union{} diff --git a/test/core.jl b/test/core.jl index 43d6da062560b..557688ea47dad 100644 --- a/test/core.jl +++ b/test/core.jl @@ -7698,3 +7698,22 @@ end @test a == 1 @test b == Core.svec(2, 3) end + +@testset "setproperty! on modules" begin + m = Module() + @eval m global x::Int + + @test_throws ConcurrencyViolationError setfield!(m, :x, 1) + setfield!(m, :x, 2, :release) + @test m.x === 2 + @test_throws ConcurrencyViolationError setfield!(m, :x, 3, :not_atomic) + @test_throws ErrorException setfield!(m, :x, 4., :release) + + m.x = 1 + @test m.x === 1 + setproperty!(m, :x, 2, :release) + @test m.x === 2 + @test_throws ConcurrencyViolationError setproperty!(m, :x, 3, :not_atomic) + m.x = 4. + @test m.x === 4 +end diff --git a/test/misc.jl b/test/misc.jl index 9a92d0fda0076..5a50661429a10 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -393,16 +393,6 @@ let s = Set(1:100) @test summarysize([s]) > summarysize(s) end -# issue #13021 -let ex = try - Main.x13021 = 0 - nothing -catch ex - ex -end - @test isa(ex, ErrorException) && ex.msg == "cannot assign variables in other modules" -end - ## test conversion from UTF-8 to UTF-16 (for Windows APIs) # empty arrays