Skip to content

Commit 4d3d49d

Browse files
authored
implement getfield overloading (#24960)
New functions `Base.getproperty` and `Base.setproperty!` can be overloaded to change the behavior of `x.p` and `x.p = v`, respectively. This forces inference constant propagation through get/setproperty, since it is very likely this method will yield better information after specializing on the field name (even if `convert` is too big to make us want to inline the generic version and trigger the heuristic normally). closes #16195 (and thus also closes #16226) fix #1974
1 parent a28f495 commit 4d3d49d

21 files changed

+237
-169
lines changed

NEWS.md

+9-6
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ New language features
1919
iterated using `pairs(kw)`. `kw` can no longer contain multiple entries for the same
2020
argument name ([#4916]).
2121

22-
* Custom infix operators can now be defined by appending Unicode
23-
combining marks, primes, and sub/superscripts to other operators.
24-
For example, `+̂ₐ″` is parsed as an infix operator with the same
25-
precedence as `+` ([#22089]).
22+
* Custom infix operators can now be defined by appending Unicode
23+
combining marks, primes, and sub/superscripts to other operators.
24+
For example, `+̂ₐ″` is parsed as an infix operator with the same
25+
precedence as `+` ([#22089]).
2626

27-
* The macro call syntax `@macroname[args]` is now available and is parsed
28-
as `@macroname([args])` ([#23519]).
27+
* The macro call syntax `@macroname[args]` is now available and is parsed
28+
as `@macroname([args])` ([#23519]).
2929

3030
* The construct `if @generated ...; else ...; end` can be used to provide both
3131
`@generated` and normal implementations of part of a function. Surrounding code
@@ -35,6 +35,9 @@ New language features
3535
missing values ([#24653]). It propagates through standard operators and mathematical functions,
3636
and implements three-valued logic, similar to SQLs `NULL` and R's `NA`.
3737

38+
* Field access via dot-syntax can now be overloaded by adding methods to
39+
`Base.getproperty` and `Base.setproperty!` ([#1974]).
40+
3841
Language changes
3942
----------------
4043

base/array.jl

+6
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ import Core: arraysize, arrayset, arrayref
9595
vect() = Vector{Any}()
9696
vect(X::T...) where {T} = T[ X[i] for i = 1:length(X) ]
9797

98+
"""
99+
vect(X...)
100+
101+
Create a Vector with element type computed from the promote_typeof of the argument,
102+
containing the argument list.
103+
"""
98104
function vect(X...)
99105
T = promote_typeof(X...)
100106
#T[ X[i] for i=1:length(X) ]

base/boot.jl

+3
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ export
153153
# constants
154154
nothing, Main
155155

156+
const getproperty = getfield
157+
const setproperty! = setfield!
158+
156159
abstract type Number end
157160
abstract type Real <: Number end
158161
abstract type AbstractFloat <: Real end

base/coreimg.jl

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3-
Main.Core.eval(Main.Core, :(baremodule Inference
3+
getfield(getfield(Main, :Core), :eval)(getfield(Main, :Core), :(baremodule Inference
44
using Core.Intrinsics
55
import Core: print, println, show, write, unsafe_write, STDOUT, STDERR
66

7+
const getproperty = getfield
8+
const setproperty! = setfield!
9+
710
ccall(:jl_set_istopmod, Void, (Any, Bool), Inference, false)
811

912
eval(x) = Core.eval(Inference, x)

base/docs/basedocs.jl

+27-4
Original file line numberDiff line numberDiff line change
@@ -1236,8 +1236,8 @@ tuple
12361236
"""
12371237
getfield(value, name::Symbol)
12381238
1239-
Extract a named field from a `value` of composite type. The syntax `a.b` calls
1240-
`getfield(a, :b)`.
1239+
Extract a named field from a `value` of composite type.
1240+
See also [`getproperty`](@ref Base.getproperty).
12411241
12421242
# Examples
12431243
```jldoctest
@@ -1256,8 +1256,9 @@ getfield
12561256
"""
12571257
setfield!(value, name::Symbol, x)
12581258
1259-
Assign `x` to a named field in `value` of composite type. The syntax `a.b = c` calls
1260-
`setfield!(a, :b, c)`. `value` must be mutable.
1259+
Assign `x` to a named field in `value` of composite type.
1260+
The `value` must be mutable and `x` must be a subtype of `fieldtype(typeof(value), name)`.
1261+
See also [`setproperty!`](@ref Base.setproperty!).
12611262
12621263
# Examples
12631264
```jldoctest
@@ -1768,4 +1769,26 @@ The base library of Julia.
17681769
"""
17691770
kw"Base"
17701771

1772+
"""
1773+
typeassert(x, type)
1774+
1775+
Throw a TypeError unless `x isa type`.
1776+
The syntax `x::type` calls this function.
1777+
"""
1778+
typeassert
1779+
1780+
"""
1781+
getproperty(value, name::Symbol)
1782+
1783+
The syntax `a.b` calls `getproperty(a, :b)`.
1784+
"""
1785+
Base.getproperty
1786+
1787+
"""
1788+
setproperty!(value, name::Symbol, x)
1789+
1790+
The syntax `a.b = c` calls `setproperty!(a, :b, c)`.
1791+
"""
1792+
Base.setproperty!
1793+
17711794
end

base/exports.jl

+2
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,8 @@ export
899899

900900
# types
901901
convert,
902+
# getproperty,
903+
# setproperty!,
902904
fieldoffset,
903905
fieldname,
904906
fieldnames,

base/inference.jl

+10-7
Original file line numberDiff line numberDiff line change
@@ -1961,7 +1961,7 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp
19611961
# if there's a possibility we could constant-propagate a better result
19621962
# (hopefully without doing too much work), try to do that now
19631963
# TODO: it feels like this could be better integrated into abstract_call_method / typeinf_edge
1964-
const_rettype = abstract_call_method_with_const_args(argtypes, applicable[1]::SimpleVector, sv)
1964+
const_rettype = abstract_call_method_with_const_args(f, argtypes, applicable[1]::SimpleVector, sv)
19651965
if const_rettype rettype
19661966
# use the better result, if it's a refinement of rettype
19671967
rettype = const_rettype
@@ -2020,7 +2020,7 @@ function cache_lookup(code::MethodInstance, argtypes::Vector{Any}, cache::Vector
20202020
return nothing
20212021
end
20222022

2023-
function abstract_call_method_with_const_args(argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState)
2023+
function abstract_call_method_with_const_args(@nospecialize(f), argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState)
20242024
method = match[3]::Method
20252025
nargs::Int = method.nargs
20262026
method.isva && (nargs -= 1)
@@ -2053,11 +2053,14 @@ function abstract_call_method_with_const_args(argtypes::Vector{Any}, match::Simp
20532053
end
20542054
end
20552055
if !cache_inlineable && !sv.params.aggressive_constant_propagation
2056-
# in this case, see if all of the arguments are constants
2057-
for i in 1:nargs
2058-
a = argtypes[i]
2059-
if !isa(a, Const) && !isconstType(a)
2060-
return Any
2056+
tm = _topmod(sv)
2057+
if !istopfunction(tm, f, :getproperty) && !istopfunction(tm, f, :setproperty!)
2058+
# in this case, see if all of the arguments are constants
2059+
for i in 1:nargs
2060+
a = argtypes[i]
2061+
if !isa(a, Const) && !isconstType(a)
2062+
return Any
2063+
end
20612064
end
20622065
end
20632066
end

base/libgit2/gitcredential.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ function Base.read!(io::IO, cred::GitCredential)
116116
# https://git-scm.com/docs/git-credential#git-credential-codeurlcode
117117
copy!(cred, parse(GitCredential, value))
118118
else
119-
setfield!(cred, Symbol(key), String(value))
119+
Base.setproperty!(cred, Symbol(key), String(value))
120120
end
121121
end
122122

base/show.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ function show_default(io::IO, @nospecialize(x))
146146
if !isdefined(x, f)
147147
print(io, undef_ref_str)
148148
else
149-
show(recur_io, getfield(x, f))
149+
show(recur_io, getfield(x, i))
150150
end
151151
if i < nf
152152
print(io, ", ")

base/sysimg.jl

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ baremodule Base
55
using Core.Intrinsics
66
ccall(:jl_set_istopmod, Void, (Any, Bool), Base, true)
77

8+
getproperty(x, f::Symbol) = getfield(x, f)
9+
setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), v))
10+
11+
# Try to help prevent users from shooting them-selves in the foot
12+
# with ambiguities by defining a few common and critical operations
13+
# (and these don't need the extra convert code)
14+
getproperty(x::Module, f::Symbol) = getfield(x, f)
15+
setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v)
16+
getproperty(x::Type, f::Symbol) = getfield(x, f)
17+
setproperty!(x::Type, f::Symbol, v) = setfield!(x, f, v)
18+
819
function include(mod::Module, path::AbstractString)
920
local result
1021
if INCLUDE_STATE === 1

doc/src/manual/functions.md

+12-10
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,18 @@ Under the name `f`, the function does not support infix notation, however.
158158

159159
A few special expressions correspond to calls to functions with non-obvious names. These are:
160160

161-
| Expression | Calls |
162-
|:----------------- |:---------------------- |
163-
| `[A B C ...]` | [`hcat`](@ref) |
164-
| `[A; B; C; ...]` | [`vcat`](@ref) |
165-
| `[A B; C D; ...]` | [`hvcat`](@ref) |
166-
| `A'` | [`adjoint`](@ref) |
167-
| `A.'` | [`transpose`](@ref) |
168-
| `1:n` | [`colon`](@ref) |
169-
| `A[i]` | [`getindex`](@ref) |
170-
| `A[i]=x` | [`setindex!`](@ref) |
161+
| Expression | Calls |
162+
|:----------------- |:----------------------- |
163+
| `[A B C ...]` | [`hcat`](@ref) |
164+
| `[A; B; C; ...]` | [`vcat`](@ref) |
165+
| `[A B; C D; ...]` | [`hvcat`](@ref) |
166+
| `A'` | [`adjoint`](@ref) |
167+
| `A.'` | [`transpose`](@ref) |
168+
| `1:n` | [`colon`](@ref) |
169+
| `A[i]` | [`getindex`](@ref) |
170+
| `A[i] = x` | [`setindex!`](@ref) |
171+
| `A.n` | [`getproperty`](@ref Base.getproperty) |
172+
| `A.n = x` | [`setproperty!`](@ref Base.setproperty!) |
171173

172174
## [Anonymous Functions](@id man-anonymous-functions)
173175

doc/src/stdlib/arrays.md

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ Base.cat
119119
Base.vcat
120120
Base.hcat
121121
Base.hvcat
122+
Base.vect
122123
Base.flipdim
123124
Base.circshift
124125
Base.circshift!

doc/src/stdlib/base.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Base.isless
115115
Base.ifelse
116116
Base.lexcmp
117117
Base.lexless
118+
Core.typeassert
118119
Core.typeof
119120
Core.tuple
120121
Base.ntuple
@@ -124,6 +125,10 @@ Base.finalizer
124125
Base.finalize
125126
Base.copy
126127
Base.deepcopy
128+
Base.getproperty
129+
Base.setproperty!
130+
Core.getfield
131+
Core.setfield!
127132
Core.isdefined
128133
Base.@isdefined
129134
Base.convert
@@ -133,7 +138,7 @@ Base.widen
133138
Base.identity
134139
```
135140

136-
## Dealing with Types
141+
## Properties of Types
137142

138143
```@docs
139144
Base.supertype
@@ -150,8 +155,6 @@ Base.eps(::Type{<:AbstractFloat})
150155
Base.eps(::AbstractFloat)
151156
Base.promote_type
152157
Base.promote_rule
153-
Core.getfield
154-
Core.setfield!
155158
Base.fieldoffset
156159
Core.fieldtype
157160
Base.isimmutable

doc/src/stdlib/punctuation.md

+15-15
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
Extended documentation for mathematical symbols & functions is [here](@ref math-ops).
44

5-
| symbol | meaning |
6-
|:----------- |:------------------------------------------------------------------------------------------- |
5+
| symbol | meaning |
6+
|:----------- |:----------------------------------------------------------------------------------------------------------------------------------------------- |
77
| `@m` | invoke macro `m`; followed by space-separated expressions |
8-
| `!` | prefix "not" operator |
9-
| `a!( )` | at the end of a function name, `!` indicates that a function modifies its argument(s) |
8+
| `!` | prefix "not" (logical negation) operator |
9+
| `a!( )` | at the end of a function name, `!` is used as a convention to indicate that a function modifies its argument(s) |
1010
| `#` | begin single line comment |
1111
| `#=` | begin multi-line comment (these are nestable) |
1212
| `=#` | end multi-line comment |
@@ -23,23 +23,23 @@ Extended documentation for mathematical symbols & functions is [here](@ref math-
2323
| `~` | bitwise not operator |
2424
| `\` | backslash operator |
2525
| `'` | complex transpose operator Aᴴ |
26-
| `a[]` | array indexing |
27-
| `[,]` | vertical concatenation |
28-
| `[;]` | also vertical concatenation |
29-
| `[   ]` | with space-separated expressions, horizontal concatenation |
26+
| `a[]` | array indexing (calling [`getindex`](@ref) or [`setindex!`](@ref)) |
27+
| `[,]` | vector literal constructor (calling [`vect`](@ref Base.vect)) |
28+
| `[;]` | vertical concatenation (calling [`vcat`](@ref) or [`hvcat`](@ref)) |
29+
| `[   ]` | with space-separated expressions, horizontal concatenation (calling [`hcat`](@ref) or [`hvcat`](@ref)) |
3030
| `T{ }` | parametric type instantiation |
3131
| `;` | statement separator |
3232
| `,` | separate function arguments or tuple components |
33-
| `?` | 3-argument conditional operator (conditional ? if_true : if_false) |
33+
| `?` | 3-argument conditional operator (used like: `conditional ? if_true : if_false`) |
3434
| `""` | delimit string literals |
3535
| `''` | delimit character literals |
3636
| ``` ` ` ``` | delimit external process (command) specifications |
37-
| `...` | splice arguments into a function call or declare a varargs function or type |
38-
| `.` | access named fields in objects/modules, also prefixes elementwise operator/function calls |
39-
| `a:b` | range a, a+1, a+2, ..., b |
40-
| `a:s:b` | range a, a+s, a+2s, ..., b |
41-
| `:` | index an entire dimension (1:end) |
42-
| `::` | type annotation, depending on context |
37+
| `...` | splice arguments into a function call or declare a varargs function |
38+
| `.` | access named fields in objects/modules (calling [`getproperty`](@ref Base.getproperty) or [`setproperty!`](@ref Base.setproperty!)), also prefixes elementwise function calls (calling [`broadcast`](@ref)) |
39+
| `a:b` | range a, a+1, a+2, ..., b (calling [`colon`](@ref)) |
40+
| `a:s:b` | range a, a+s, a+2s, ..., b (also calling [`colon`](@ref)) |
41+
| `:` | index an entire dimension (1:endof), see [`Colon`](@ref)) |
42+
| `::` | type annotation or [`typeassert`](@ref), depending on context |
4343
| `:( )` | quoted expression |
4444
| `:a` | symbol a |
4545
| `<:` | [`subtype operator`](@ref <:) |

src/julia-syntax.scm

+7-5
Original file line numberDiff line numberDiff line change
@@ -1752,7 +1752,7 @@
17521752
(if (and (pair? e) (eq? (car e) '|.|))
17531753
(let ((f (cadr e)) (x (caddr e)))
17541754
(cond ((or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
1755-
`(call (core getfield) ,f ,x))
1755+
`(call (top getproperty) ,f ,x))
17561756
((eq? (car x) 'tuple)
17571757
(make-fuse f (cdr x)))
17581758
(else
@@ -2039,10 +2039,12 @@
20392039
,.(if (eq? aa a) '() `((= ,aa ,(expand-forms a))))
20402040
,.(if (eq? bb b) '() `((= ,bb ,(expand-forms b))))
20412041
,.(if (eq? rr rhs) '() `((= ,rr ,(expand-forms rhs))))
2042-
(call (core setfield!) ,aa ,bb
2043-
(call (top convert)
2044-
(call (core fieldtype) (call (core typeof) ,aa) ,bb)
2045-
,rr))
2042+
(call (top setproperty!) ,aa ,bb
2043+
(if (call (top ===) (top setproperty!) (core setfield!))
2044+
(call (top convert)
2045+
(call (core fieldtype) (call (core typeof) ,aa) ,bb)
2046+
,rr)
2047+
,rr))
20462048
(unnecessary ,rr)))))
20472049
((tuple)
20482050
;; multiple assignment

0 commit comments

Comments
 (0)