Skip to content

Commit

Permalink
implement setproperty! for modules
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
simeonschaub committed Feb 18, 2022
1 parent 1ad2396 commit df33941
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 93 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Julia v1.9 Release Notes
New language features
---------------------

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

Language changes
----------------
Expand Down
11 changes: 7 additions & 4 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ macro noinline() Expr(:meta, :noinline) end
# Try to help prevent users from shooting them-selves in the foot
# 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::Module, f::Symbol) = (@inline; Core.getglobal(x, f))
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))
Expand All @@ -40,8 +39,12 @@ 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
getproperty(x::Module, f::Symbol, order::Symbol) = (@inline; Core.getglobal(x, f, order))
function setproperty!(x::Module, f::Symbol, v, order::Symbol=:monotonic)
@inline
val::Core.get_binding_type(x, f) = v
return Core.setglobal!(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))
Expand Down
94 changes: 82 additions & 12 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,37 @@ function arraysize_nothrow(argtypes::Vector{Any})
return false
end

struct MemoryOrder x::Cint end
const MEMORY_ORDER_UNSPECIFIED = MemoryOrder(-2)
const MEMORY_ORDER_INVALID = MemoryOrder(-1)
const MEMORY_ORDER_NOTATOMIC = MemoryOrder(0)
const MEMORY_ORDER_UNORDERED = MemoryOrder(1)
const MEMORY_ORDER_MONOTONIC = MemoryOrder(2)
const MEMORY_ORDER_CONSUME = MemoryOrder(3)
const MEMORY_ORDER_ACQUIRE = MemoryOrder(4)
const MEMORY_ORDER_RELEASE = MemoryOrder(5)
const MEMORY_ORDER_ACQ_REL = MemoryOrder(6)
const MEMORY_ORDER_SEQ_CST = MemoryOrder(7)

function get_atomic_order(order::Symbol, loading::Bool, storing::Bool)
if order === :not_atomic
return MEMORY_ORDER_NOTATOMIC
elseif order === :unordered && (loading storing)
return MEMORY_ORDER_UNORDERED
elseif order === :monotonic && (loading | storing)
return MEMORY_ORDER_MONOTONIC
elseif order === :acquire && loading
return MEMORY_ORDER_ACQUIRE
elseif order === :release && storing
return MEMORY_ORDER_RELEASE
elseif order === :acquire_release && (loading & storing)
return MEMORY_ORDER_ACQ_REL
elseif order === :sequentially_consistent
return MEMORY_ORDER_SEQ_CST
end
return MEMORY_ORDER_INVALID
end

function pointer_eltype(@nospecialize(ptr))
a = widenconst(ptr)
if !has_free_typevars(a)
Expand Down Expand Up @@ -1704,6 +1735,8 @@ function _builtin_nothrow(@nospecialize(f), argtypes::Array{Any,1}, @nospecializ
return true
end
return false
elseif f === Core.getglobal
return getglobal_nothrow(argtypes)
elseif f === Core.get_binding_type
return length(argtypes) == 2
elseif f === donotdelete
Expand Down Expand Up @@ -1773,16 +1806,20 @@ function builtin_effects(f::Builtin, argtypes::Vector{Any}, rt)
# InferenceState.
nothrow = getfield_nothrow(argtypes[2], argtypes[3], true)
ipo_consistent &= nothrow
end
else
nothrow = isvarargtype(argtypes[end]) ? false :
builtin_nothrow(f, argtypes[2:end], rt)
end
effect_free = f === isdefined
elseif f === Core.getglobal && length(argtypes) >= 3
nothrow = effect_free = getglobal_nothrow(argtypes[2:end])
ipo_consistent = nothrow && isconst((argtypes[2]::Const).val, (argtypes[3]::Const).val)
effect_free = nothrow && isbindingresolved((argtypes[2]::Const).val, (argtypes[3]::Const).val)
else
ipo_consistent = contains_is(_CONSISTENT_BUILTINS, f)
effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)
nothrow = isvarargtype(argtypes[end]) ? false : builtin_nothrow(f, argtypes[2:end], rt)
end
# If we computed nothrow above for getfield, no need to repeat the procedure here
if !nothrow
nothrow = isvarargtype(argtypes[end]) ? false :
builtin_nothrow(f, argtypes[2:end], rt)
end
effect_free = contains_is(_EFFECT_FREE_BUILTINS, f) || contains_is(_PURE_BUILTINS, f)

return Effects(
ipo_consistent ? ALWAYS_TRUE : ALWAYS_FALSE,
Expand Down Expand Up @@ -2029,17 +2066,50 @@ function typename_static(@nospecialize(t))
return isType(t) ? _typename(t.parameters[1]) : Core.TypeName
end

function global_order_nothrow(@nospecialize(o), loading::Bool, storing::Bool)
o isa Const || return false
sym = o.val
if sym isa Symbol
order = get_atomic_order(sym, loading, storing)
return order !== MEMORY_ORDER_INVALID && order !== MEMORY_ORDER_NOTATOMIC
end
return false
end
function getglobal_nothrow(argtypes::Vector{Any})
2 length(argtypes) 3 || return false
if length(argtypes) == 3
global_order_nothrow(o, true, false) || return false
end
M, s = argtypes
if M isa Const && s isa Const
M, s = M.val, s.val
if M isa Module && s isa Symbol
return isdefined(M, s)
end
end
return false
end
function getglobal_tfunc(@nospecialize(M), @nospecialize(s), @nospecialize(_=:monotonic))
if get_binding_type_effect_free(M, s)
return abstract_eval_global((M::Const).val, (s::Const).val)
end
return Any
end
add_tfunc(Core.getglobal, 2, 3, getglobal_tfunc, 1)
add_tfunc(Core.setglobal!, 3, 4, (M, s, v, _=:monotonic) -> (@nospecialize; v), 1)

function get_binding_type_effect_free(@nospecialize(M), @nospecialize(s))
if M isa Const && widenconst(M) === Module &&
s isa Const && widenconst(s) === Symbol
return ccall(:jl_binding_type, Any, (Any, Any), M.val, s.val) !== nothing
if M isa Const && s isa Const
M, s = M.val, s.val
if M isa Module && s isa Symbol
return ccall(:jl_binding_type, Any, (Any, Any), M, s) !== nothing
end
end
return false
end
function get_binding_type_tfunc(@nospecialize(M), @nospecialize(s))
if get_binding_type_effect_free(M, s)
@assert M isa Const && s isa Const
return Const(Core.get_binding_type(M.val, s.val))
return Const(Core.get_binding_type((M::Const).val, (s::Const).val))
end
return Type
end
Expand Down
3 changes: 3 additions & 0 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2731,6 +2731,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).
Expand Down
6 changes: 0 additions & 6 deletions doc/src/manual/variables-and-scoping.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand Down
4 changes: 2 additions & 2 deletions doc/src/manual/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ JL_CALLABLE(jl_f__equiv_typedef);
JL_CALLABLE(jl_f_get_binding_type);
JL_CALLABLE(jl_f_set_binding_type);
JL_CALLABLE(jl_f_donotdelete);
JL_CALLABLE(jl_f_getglobal);
JL_CALLABLE(jl_f_setglobal);

#ifdef __cplusplus
}
Expand Down
Loading

0 comments on commit df33941

Please sign in to comment.