Skip to content

Commit c38e429

Browse files
simeonschaubvtjnashDilumAluthge
authored
implement setproperty! for modules (#44231)
This replaces #44137. As discussed on triage, instead of supporting modules in `setfield!`, this adds two new builtins `getglobal` and `setglobal!` explicitly for reading and modifying module bindings. We should probably consider `getfield(::Module, ::Symbol)` to be soft-deprecated, but I don't think we want to add any warnings since that will likely just annoy people. Co-authored-by: Jameson Nash <vtjnash@gmail.com> Co-authored-by: Dilum Aluthge <dilum@aluthge.com>
1 parent f750080 commit c38e429

20 files changed

+281
-110
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ Julia v1.9 Release Notes
44
New language features
55
---------------------
66

7+
* It is now possible to assign to bindings in another module using `setproperty!(::Module, ::Symbol, x)`. ([#44137])
78

89
Language changes
910
----------------
1011

12+
* New builtins `getglobal(::Module, ::Symbol[, order])` and `setglobal!(::Module, ::Symbol, x[, order])`
13+
for reading from and writing to globals. `getglobal` should now be preferred for accessing globals over
14+
`getfield`. ([#44137])
1115

1216
Compiler/Runtime improvements
1317
-----------------------------

base/Base.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ macro noinline() Expr(:meta, :noinline) end
2828
# Try to help prevent users from shooting them-selves in the foot
2929
# with ambiguities by defining a few common and critical operations
3030
# (and these don't need the extra convert code)
31-
getproperty(x::Module, f::Symbol) = (@inline; getfield(x, f))
32-
setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v) # to get a decent error
31+
getproperty(x::Module, f::Symbol) = (@inline; getglobal(x, f))
3332
getproperty(x::Type, f::Symbol) = (@inline; getfield(x, f))
3433
setproperty!(x::Type, f::Symbol, v) = error("setfield! fields of Types should not be changed")
3534
getproperty(x::Tuple, f::Int) = (@inline; getfield(x, f))
@@ -40,8 +39,12 @@ setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f),
4039

4140
dotgetproperty(x, f) = getproperty(x, f)
4241

43-
getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order))
44-
setproperty!(x::Module, f::Symbol, v, order::Symbol) = setfield!(x, f, v, order) # to get a decent error
42+
getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; getglobal(x, f, order))
43+
function setproperty!(x::Module, f::Symbol, v, order::Symbol=:monotonic)
44+
@inline
45+
val::Core.get_binding_type(x, f) = v
46+
return setglobal!(x, f, val, order)
47+
end
4548
getproperty(x::Type, f::Symbol, order::Symbol) = (@inline; getfield(x, f, order))
4649
setproperty!(x::Type, f::Symbol, v, order::Symbol) = error("setfield! fields of Types should not be changed")
4750
getproperty(x::Tuple, f::Int, order::Symbol) = (@inline; getfield(x, f, order))

base/boot.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ export
193193
# object model functions
194194
fieldtype, getfield, setfield!, swapfield!, modifyfield!, replacefield!,
195195
nfields, throw, tuple, ===, isdefined, eval,
196+
# access to globals
197+
getglobal, setglobal!,
196198
# ifelse, sizeof # not exported, to avoid conflicting with Base
197199
# type reflection
198200
<:, typeof, isa, typeassert,
@@ -201,7 +203,7 @@ export
201203
# constants
202204
nothing, Main
203205

204-
const getproperty = getfield
206+
const getproperty = getfield # TODO: use `getglobal` for modules instead
205207
const setproperty! = setfield!
206208

207209
abstract type Number end

base/compiler/abstractinterpretation.jl

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1998,10 +1998,8 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
19981998
end
19991999

20002000
function abstract_eval_global(M::Module, s::Symbol)
2001-
if isdefined(M,s)
2002-
if isconst(M,s)
2003-
return Const(getfield(M,s))
2004-
end
2001+
if isdefined(M, s) && isconst(M, s)
2002+
return Const(getglobal(M, s))
20052003
end
20062004
ty = ccall(:jl_binding_type, Any, (Any, Any), M, s)
20072005
ty === nothing && return Any

base/compiler/optimize.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -675,7 +675,7 @@ function statement_cost(ex::Expr, line::Int, src::Union{CodeInfo, IRCode}, sptyp
675675
# The efficiency of operations like a[i] and s.b
676676
# depend strongly on whether the result can be
677677
# inferred, so check the type of ex
678-
if f === Core.getfield || f === Core.tuple
678+
if f === Core.getfield || f === Core.tuple || f === Core.getglobal
679679
# we might like to penalize non-inferrability, but
680680
# tuple iteration/destructuring makes that impossible
681681
# return plus_saturate(argcost, isknowntype(extyp) ? 1 : params.inline_nonleaf_penalty)

base/compiler/ssair/passes.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ function lift_leaves(compact::IncrementalCompact,
418418
elseif isa(leaf, GlobalRef)
419419
mod, name = leaf.mod, leaf.name
420420
if isdefined(mod, name) && isconst(mod, name)
421-
leaf = getfield(mod, name)
421+
leaf = getglobal(mod, name)
422422
else
423423
return nothing
424424
end

base/compiler/tfuncs.jl

Lines changed: 96 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,37 @@ function arraysize_nothrow(argtypes::Vector{Any})
480480
return false
481481
end
482482

483+
struct MemoryOrder x::Cint end
484+
const MEMORY_ORDER_UNSPECIFIED = MemoryOrder(-2)
485+
const MEMORY_ORDER_INVALID = MemoryOrder(-1)
486+
const MEMORY_ORDER_NOTATOMIC = MemoryOrder(0)
487+
const MEMORY_ORDER_UNORDERED = MemoryOrder(1)
488+
const MEMORY_ORDER_MONOTONIC = MemoryOrder(2)
489+
const MEMORY_ORDER_CONSUME = MemoryOrder(3)
490+
const MEMORY_ORDER_ACQUIRE = MemoryOrder(4)
491+
const MEMORY_ORDER_RELEASE = MemoryOrder(5)
492+
const MEMORY_ORDER_ACQ_REL = MemoryOrder(6)
493+
const MEMORY_ORDER_SEQ_CST = MemoryOrder(7)
494+
495+
function get_atomic_order(order::Symbol, loading::Bool, storing::Bool)
496+
if order === :not_atomic
497+
return MEMORY_ORDER_NOTATOMIC
498+
elseif order === :unordered && (loading storing)
499+
return MEMORY_ORDER_UNORDERED
500+
elseif order === :monotonic && (loading | storing)
501+
return MEMORY_ORDER_MONOTONIC
502+
elseif order === :acquire && loading
503+
return MEMORY_ORDER_ACQUIRE
504+
elseif order === :release && storing
505+
return MEMORY_ORDER_RELEASE
506+
elseif order === :acquire_release && (loading & storing)
507+
return MEMORY_ORDER_ACQ_REL
508+
elseif order === :sequentially_consistent
509+
return MEMORY_ORDER_SEQ_CST
510+
end
511+
return MEMORY_ORDER_INVALID
512+
end
513+
483514
function pointer_eltype(@nospecialize(ptr))
484515
a = widenconst(ptr)
485516
if !has_free_typevars(a)
@@ -1704,6 +1735,8 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ
17041735
return true
17051736
end
17061737
return false
1738+
elseif f === getglobal
1739+
return getglobal_nothrow(argtypes)
17071740
elseif f === Core.get_binding_type
17081741
length(argtypes) == 2 || return false
17091742
return argtypes[1] Module && argtypes[2] Symbol
@@ -1721,7 +1754,7 @@ const _EFFECT_FREE_BUILTINS = [
17211754
fieldtype, apply_type, isa, UnionAll,
17221755
getfield, arrayref, const_arrayref, isdefined, Core.sizeof,
17231756
Core.kwfunc, Core.ifelse, Core._typevar, (<:),
1724-
typeassert, throw, arraysize
1757+
typeassert, throw, arraysize, getglobal,
17251758
]
17261759

17271760
const _CONSISTENT_BUILTINS = Any[
@@ -1774,16 +1807,20 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt)
17741807
# InferenceState.
17751808
nothrow = getfield_nothrow(argtypes[2], argtypes[3], true)
17761809
ipo_consistent &= nothrow
1777-
end
1810+
else
1811+
nothrow = isvarargtype(argtypes[end]) ? false :
1812+
builtin_nothrow(f, argtypes[2:end], rt)
1813+
end
1814+
effect_free = f === isdefined
1815+
elseif f === getglobal && length(argtypes) >= 3
1816+
nothrow = effect_free = getglobal_nothrow(argtypes[2:end])
1817+
ipo_consistent = nothrow && isconst((argtypes[2]::Const).val, (argtypes[3]::Const).val)
1818+
#effect_free = nothrow && isbindingresolved((argtypes[2]::Const).val, (argtypes[3]::Const).val)
17781819
else
17791820
ipo_consistent = contains_is(_CONSISTENT_BUILTINS, f)
1821+
effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)
1822+
nothrow = isvarargtype(argtypes[end]) ? false : builtin_nothrow(f, argtypes[2:end], rt)
17801823
end
1781-
# If we computed nothrow above for getfield, no need to repeat the procedure here
1782-
if !nothrow
1783-
nothrow = isvarargtype(argtypes[end]) ? false :
1784-
builtin_nothrow(f, argtypes[2:end], rt)
1785-
end
1786-
effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)
17871824

17881825
return Effects(
17891826
ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE,
@@ -2030,17 +2067,63 @@ function typename_static(@nospecialize(t))
20302067
return isType(t) ? _typename(t.parameters[1]) : Core.TypeName
20312068
end
20322069

2070+
function global_order_nothrow(@nospecialize(o), loading::Bool, storing::Bool)
2071+
o isa Const || return false
2072+
sym = o.val
2073+
if sym isa Symbol
2074+
order = get_atomic_order(sym, loading, storing)
2075+
return order !== MEMORY_ORDER_INVALID && order !== MEMORY_ORDER_NOTATOMIC
2076+
end
2077+
return false
2078+
end
2079+
function getglobal_nothrow(argtypes::Vector{Any})
2080+
2 length(argtypes) 3 || return false
2081+
if length(argtypes) == 3
2082+
global_order_nothrow(o, true, false) || return false
2083+
end
2084+
M, s = argtypes
2085+
if M isa Const && s isa Const
2086+
M, s = M.val, s.val
2087+
if M isa Module && s isa Symbol
2088+
return isdefined(M, s)
2089+
end
2090+
end
2091+
return false
2092+
end
2093+
function getglobal_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(_=Symbol))
2094+
if M isa Const && s isa Const
2095+
M, s = M.val, s.val
2096+
if M isa Module && s isa Symbol
2097+
return abstract_eval_global(M, s)
2098+
end
2099+
return Bottom
2100+
elseif !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol))
2101+
return Bottom
2102+
end
2103+
return Any
2104+
end
2105+
function setglobal!_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(v),
2106+
@nospecialize(_=Symbol))
2107+
if !(hasintersect(widenconst(M), Module) && hasintersect(widenconst(s), Symbol))
2108+
return Bottom
2109+
end
2110+
return v
2111+
end
2112+
add_tfunc(getglobal, 2, 3, getglobal_tfunc, 1)
2113+
add_tfunc(setglobal!, 3, 4, setglobal!_tfunc, 3)
2114+
20332115
function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s))
2034-
if M isa Const && widenconst(M) === Module &&
2035-
s isa Const && widenconst(s) === Symbol
2036-
return ccall(:jl_binding_type, Any, (Any, Any), M.val, s.val) !== nothing
2116+
if M isa Const && s isa Const
2117+
M, s = M.val, s.val
2118+
if M isa Module && s isa Symbol
2119+
return ccall(:jl_binding_type, Any, (Any, Any), M, s) !== nothing
2120+
end
20372121
end
20382122
return false
20392123
end
20402124
function get_binding_type_tfunc(@nospecialize(M), @nospecialize(s))
20412125
if get_binding_type_effect_free(M, s)
2042-
@assert M isa Const && s isa Const
2043-
return Const(Core.get_binding_type(M.val, s.val))
2126+
return Const(Core.get_binding_type((M::Const).val, (s::Const).val))
20442127
end
20452128
return Type
20462129
end

base/compiler/utilities.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function istopfunction(@nospecialize(f), name::Symbol)
5252
tn = typeof(f).name
5353
if tn.mt.name === name
5454
top = _topmod(tn.module)
55-
return isdefined(top, name) && isconst(top, name) && f === getfield(top, name)
55+
return isdefined(top, name) && isconst(top, name) && f === getglobal(top, name)
5656
end
5757
return false
5858
end

base/docs/basedocs.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2731,6 +2731,9 @@ The syntax `a.b = c` calls `setproperty!(a, :b, c)`.
27312731
The syntax `@atomic order a.b = c` calls `setproperty!(a, :b, c, :order)`
27322732
and the syntax `@atomic a.b = c` calls `getproperty(a, :b, :sequentially_consistent)`.
27332733
2734+
!!! compat "Julia 1.8"
2735+
`setproperty!` on modules requires at least Julia 1.8.
2736+
27342737
See also [`setfield!`](@ref Core.setfield!),
27352738
[`propertynames`](@ref Base.propertynames) and
27362739
[`getproperty`](@ref Base.getproperty).

doc/src/manual/variables-and-scoping.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,6 @@ julia> module D
9191
b = a # errors as D's global scope is separate from A's
9292
end;
9393
ERROR: UndefVarError: a not defined
94-
95-
julia> module E
96-
import ..A # make module A available
97-
A.a = 2 # throws below error
98-
end;
99-
ERROR: cannot assign variables in other modules
10094
```
10195

10296
If a top-level expression contains a variable declaration with keyword `local`,

doc/src/manual/variables.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,13 @@ julia> pi
8181
π = 3.1415926535897...
8282
8383
julia> pi = 3
84-
ERROR: cannot assign a value to variable MathConstants.pi from module Main
84+
ERROR: cannot assign a value to imported variable MathConstants.pi from module Main
8585
8686
julia> sqrt(100)
8787
10.0
8888
8989
julia> sqrt = 4
90-
ERROR: cannot assign a value to variable Base.sqrt from module Main
90+
ERROR: cannot assign a value to imported variable Base.sqrt from module Main
9191
```
9292

9393
## [Allowed Variable Names](@id man-allowed-variable-names)

src/builtin_proto.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ DECLARE_BUILTIN(_typebody);
5555
DECLARE_BUILTIN(typeof);
5656
DECLARE_BUILTIN(_typevar);
5757
DECLARE_BUILTIN(donotdelete);
58+
DECLARE_BUILTIN(getglobal);
5859

5960
JL_CALLABLE(jl_f_invoke_kwsorter);
6061
#ifdef DEFINE_BUILTIN_GLOBALS
@@ -70,6 +71,7 @@ JL_CALLABLE(jl_f__equiv_typedef);
7071
JL_CALLABLE(jl_f_get_binding_type);
7172
JL_CALLABLE(jl_f_set_binding_type);
7273
JL_CALLABLE(jl_f_donotdelete);
74+
JL_CALLABLE(jl_f_setglobal);
7375

7476
#ifdef __cplusplus
7577
}

0 commit comments

Comments
 (0)