Skip to content

Commit 4cf08bb

Browse files
committed
implement getfield overloading
New functions `Base.getproperty` and `Base.setproperty!` can be overloaded to change the behavior of `x.f` and `x.f = v`, respectively. fix #16226 (close #16195) fix #1974
1 parent 66b2090 commit 4cf08bb

13 files changed

+150
-124
lines changed

base/boot.jl

+3
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ export
149149
# constants
150150
nothing, Main
151151

152+
const getproperty = getfield
153+
const setproperty! = setfield!
154+
152155
abstract type Number end
153156
abstract type Real <: Number end
154157
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/distributed/cluster.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ function launch_n_additional_processes(manager, frompid, fromconfig, cnt, launch
486486

487487
wconfig = WorkerConfig()
488488
for x in [:host, :tunnel, :sshflags, :exeflags, :exename, :enable_threaded_blas]
489-
setfield!(wconfig, x, getfield(fromconfig, x))
489+
Base.setproperty!(wconfig, x, Base.getproperty(fromconfig, x))
490490
end
491491
wconfig.bind_addr = bind_addr
492492
wconfig.port = port

base/exports.jl

+2
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,8 @@ export
914914

915915
# types
916916
convert,
917+
# getproperty,
918+
# setproperty!,
917919
fieldoffset,
918920
fieldname,
919921
fieldnames,

base/libgit2/gitcredential.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ function Base.read!(io::IO, cred::GitCredential)
130130
# https://git-scm.com/docs/git-credential#git-credential-codeurlcode
131131
copy!(cred, parse(GitCredential, value))
132132
else
133-
setfield!(cred, Symbol(key), Nullable(String(value)))
133+
Base.setproperty!(cred, Symbol(key), Nullable(String(value)))
134134
end
135135
end
136136

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

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ 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+
getproperty(x::Module, f::Symbol) = getfield(x, f)
14+
setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v)
15+
getproperty(x::Type, f::Symbol) = getfield(x, f)
16+
setproperty!(x::Type, f::Symbol, v) = setfield!(x, f, v)
17+
818
function include(mod::Module, path::AbstractString)
919
local result
1020
if INCLUDE_STATE === 1

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

src/method.c

+53-54
Original file line numberDiff line numberDiff line change
@@ -41,60 +41,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve
4141
// ignore these
4242
}
4343
else {
44-
size_t nargs = jl_expr_nargs(e);
45-
if (e->head == call_sym && nargs > 0) {
46-
jl_value_t *fe = jl_exprarg(e, 0);
47-
if (jl_is_globalref(fe) && jl_binding_resolved_p(jl_globalref_mod(fe), jl_globalref_name(fe))) {
48-
// look at some known called functions
49-
jl_binding_t *b = jl_get_binding(jl_globalref_mod(fe), jl_globalref_name(fe));
50-
jl_value_t *f = b && b->constp ? b->value : NULL;
51-
if (f == jl_builtin_getfield && nargs == 3 &&
52-
jl_is_quotenode(jl_exprarg(e, 2)) && module != NULL) {
53-
// replace getfield(module_expr, :sym) with GlobalRef
54-
jl_value_t *s = jl_fieldref(jl_exprarg(e, 2), 0);
55-
if (jl_is_symbol(s)) {
56-
jl_value_t *me = jl_exprarg(e, 1);
57-
jl_module_t *me_mod = NULL;
58-
jl_sym_t *me_sym = NULL;
59-
if (jl_is_globalref(me)) {
60-
me_mod = jl_globalref_mod(me);
61-
me_sym = jl_globalref_name(me);
62-
}
63-
else if (jl_is_symbol(me) && jl_binding_resolved_p(module, (jl_sym_t*)me)) {
64-
me_mod = module;
65-
me_sym = (jl_sym_t*)me;
66-
}
67-
if (me_mod && me_sym) {
68-
jl_binding_t *b = jl_get_binding(me_mod, me_sym);
69-
if (b && b->constp) {
70-
jl_value_t *m = b->value;
71-
if (m && jl_is_module(m)) {
72-
return jl_module_globalref((jl_module_t*)m, (jl_sym_t*)s);
73-
}
74-
}
75-
}
76-
}
77-
}
78-
else if (f == jl_builtin_tuple) {
79-
size_t j;
80-
for (j = 1; j < nargs; j++) {
81-
if (!jl_is_quotenode(jl_exprarg(e,j)))
82-
break;
83-
}
84-
if (j == nargs) {
85-
jl_value_t *val = NULL;
86-
JL_TRY {
87-
val = jl_interpret_toplevel_expr_in(module, (jl_value_t*)e, NULL, sparam_vals);
88-
}
89-
JL_CATCH {
90-
}
91-
if (val)
92-
return val;
93-
}
94-
}
95-
}
96-
}
97-
size_t i = 0;
44+
size_t i = 0, nargs = jl_array_len(e->args);
9845
if (e->head == foreigncall_sym) {
9946
JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, cc, narg)
10047
jl_value_t *rt = jl_exprarg(e, 1);
@@ -139,6 +86,58 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve
13986
// TODO: this should be making a copy, not mutating the source
14087
jl_exprargset(e, i, resolve_globals(jl_exprarg(e, i), module, sparam_vals, binding_effects));
14188
}
89+
if (e->head == call_sym && jl_expr_nargs(e) == 3 &&
90+
jl_is_globalref(jl_exprarg(e, 0)) &&
91+
jl_is_globalref(jl_exprarg(e, 1)) &&
92+
jl_is_quotenode(jl_exprarg(e, 2))) {
93+
// replace module_expr.sym with GlobalRef(module, sym)
94+
// for expressions pattern-matching to `getproperty(module_expr, :sym)` in a top-module
95+
// (this is expected to help inference performance)
96+
// TODO: this was broken by linear-IR
97+
jl_value_t *s = jl_fieldref(jl_exprarg(e, 2), 0);
98+
jl_value_t *me = jl_exprarg(e, 1);
99+
jl_value_t *fe = jl_exprarg(e, 0);
100+
jl_module_t *fe_mod = jl_globalref_mod(fe);
101+
jl_sym_t *fe_sym = jl_globalref_name(fe);
102+
jl_module_t *me_mod = jl_globalref_mod(me);
103+
jl_sym_t *me_sym = jl_globalref_name(me);
104+
if (fe_mod->istopmod && !strcmp(jl_symbol_name(fe_sym), "getproperty") && jl_is_symbol(s)) {
105+
if (jl_binding_resolved_p(me_mod, me_sym)) {
106+
jl_binding_t *b = jl_get_binding(me_mod, me_sym);
107+
if (b && b->constp && b->value && jl_is_module(b->value)) {
108+
return jl_module_globalref((jl_module_t*)b->value, (jl_sym_t*)s);
109+
}
110+
}
111+
}
112+
}
113+
if (e->head == call_sym && nargs > 0 &&
114+
jl_is_globalref(jl_exprarg(e, 0))) {
115+
// TODO: this hack should be deleted once llvmcall is fixed
116+
jl_value_t *fe = jl_exprarg(e, 0);
117+
jl_module_t *fe_mod = jl_globalref_mod(fe);
118+
jl_sym_t *fe_sym = jl_globalref_name(fe);
119+
if (jl_binding_resolved_p(fe_mod, fe_sym)) {
120+
// look at some known called functions
121+
jl_binding_t *b = jl_get_binding(fe_mod, fe_sym);
122+
if (b && b->constp && b->value == jl_builtin_tuple) {
123+
size_t j;
124+
for (j = 1; j < nargs; j++) {
125+
if (!jl_is_quotenode(jl_exprarg(e, j)))
126+
break;
127+
}
128+
if (j == nargs) {
129+
jl_value_t *val = NULL;
130+
JL_TRY {
131+
val = jl_interpret_toplevel_expr_in(module, (jl_value_t*)e, NULL, sparam_vals);
132+
}
133+
JL_CATCH {
134+
}
135+
if (val)
136+
return val;
137+
}
138+
}
139+
}
140+
}
142141
}
143142
}
144143
return expr;

test/core.jl

+62-55
Original file line numberDiff line numberDiff line change
@@ -997,19 +997,21 @@ end
997997

998998
let
999999
local z = complex(3, 4)
1000-
v = Int[0,0]
1001-
for i=1:2
1000+
v = Int[0, 0]
1001+
for i = 1:2
10021002
v[i] = getfield(z, i)
10031003
end
1004-
@test v == [3,4]
1005-
@test_throws BoundsError getfield(z, -1)
1006-
@test_throws BoundsError getfield(z, 0)
1007-
@test_throws BoundsError getfield(z, 3)
1004+
@test v == [3, 4]
1005+
@test_throws BoundsError(z, -1) getfield(z, -1)
1006+
@test_throws BoundsError(z, 0) getfield(z, 0)
1007+
@test_throws BoundsError(z, 3) getfield(z, 3)
10081008

10091009
strct = LoadError("yofile", 0, "bad")
1010-
@test_throws BoundsError getfield(strct, 10)
1011-
@test_throws ErrorException setfield!(strct, 0, "")
1012-
@test_throws ErrorException setfield!(strct, 4, "")
1010+
@test nfields(strct) == 3 # sanity test
1011+
@test_throws BoundsError(strct, 10) getfield(strct, 10)
1012+
@test_throws ErrorException("type LoadError is immutable") setfield!(strct, 0, "")
1013+
@test_throws ErrorException("type LoadError is immutable") setfield!(strct, 4, "")
1014+
@test_throws ErrorException("type is immutable") setfield!(strct, :line, 0)
10131015
@test strct.file == "yofile"
10141016
@test strct.line == 0
10151017
@test strct.error == "bad"
@@ -1018,15 +1020,17 @@ let
10181020
@test getfield(strct, 3) == "bad"
10191021

10201022
mstrct = TestMutable("melm", 1, nothing)
1021-
setfield!(mstrct, 2, 8)
1023+
Base.setproperty!(mstrct, :line, 8.0)
10221024
@test mstrct.line == 8
1025+
@test_throws TypeError(:setfield!, "", Int, 8.0) setfield!(mstrct, :line, 8.0)
1026+
@test_throws TypeError(:setfield!, "", Int, 8.0) setfield!(mstrct, 2, 8.0)
10231027
setfield!(mstrct, 3, "hi")
10241028
@test mstrct.error == "hi"
10251029
setfield!(mstrct, 1, "yo")
10261030
@test mstrct.file == "yo"
1027-
@test_throws BoundsError getfield(mstrct, 10)
1028-
@test_throws BoundsError setfield!(mstrct, 0, "")
1029-
@test_throws BoundsError setfield!(mstrct, 4, "")
1031+
@test_throws BoundsError(mstrct, 10) getfield(mstrct, 10)
1032+
@test_throws BoundsError(mstrct, 0) setfield!(mstrct, 0, "")
1033+
@test_throws BoundsError(mstrct, 4) setfield!(mstrct, 4, "")
10301034
end
10311035

10321036
# allow typevar in Union to match as long as the arguments contain
@@ -2175,10 +2179,9 @@ g7652() = fieldtype(DataType, :types)
21752179
h7652() = setfield!(a7652, 1, 2)
21762180
h7652()
21772181
@test a7652.a == 2
2178-
# commented out due to issue #16195: setfield! does not perform conversions
2179-
# i7652() = setfield!(a7652, 1, 3.0)
2180-
# i7652()
2181-
# @test a7652.a == 3
2182+
i7652() = Base.setproperty!(a7652, :a, 3.0)
2183+
i7652()
2184+
@test a7652.a == 3
21822185

21832186
# issue #7679
21842187
@test map(f->f(), Any[ ()->i for i=1:3 ]) == Any[1,2,3]
@@ -2358,49 +2361,53 @@ let
23582361
end
23592362

23602363
# pull request #9534
2361-
@test try; a,b,c = 1,2; catch ex; (ex::BoundsError).a === (1,2) && ex.i == 3; end
2362-
# @test try; [][]; catch ex; isempty((ex::BoundsError).a::Array{Any,1}) && ex.i == (1,); end # TODO: Re-enable after PLI
2363-
@test try; [][1,2]; catch ex; isempty((ex::BoundsError).a::Array{Any,1}) && ex.i == (1,2); end
2364-
@test try; [][10]; catch ex; isempty((ex::BoundsError).a::Array{Any,1}) && ex.i == (10,); end
2365-
f9534a() = (a=1+2im; getfield(a, -100))
2366-
f9534a(x) = (a=1+2im; getfield(a, x))
2367-
@test try; f9534a() catch ex; (ex::BoundsError).a === 1+2im && ex.i == -100; end
2368-
@test try; f9534a(3) catch ex; (ex::BoundsError).a === 1+2im && ex.i == 3; end
2369-
f9534b() = (a=(1,2.,""); a[5])
2370-
f9534b(x) = (a=(1,2.,""); a[x])
2371-
@test try; f9534b() catch ex; (ex::BoundsError).a == (1,2.,"") && ex.i == 5; end
2372-
@test try; f9534b(4) catch ex; (ex::BoundsError).a == (1,2.,"") && ex.i == 4; end
2373-
f9534c() = (a=(1,2.); a[3])
2374-
f9534c(x) = (a=(1,2.); a[x])
2375-
@test try; f9534c() catch ex; (ex::BoundsError).a === (1,2.) && ex.i == 3; end
2376-
@test try; f9534c(0) catch ex; (ex::BoundsError).a === (1,2.) && ex.i == 0; end
2377-
f9534d() = (a=(1,2,4,6,7); a[7])
2378-
f9534d(x) = (a=(1,2,4,6,7); a[x])
2379-
@test try; f9534d() catch ex; (ex::BoundsError).a === (1,2,4,6,7) && ex.i == 7; end
2380-
@test try; f9534d(-1) catch ex; (ex::BoundsError).a === (1,2,4,6,7) && ex.i == -1; end
2381-
f9534e(x) = (a=IOBuffer(); setfield!(a, x, 3))
2382-
@test try; f9534e(-2) catch ex; isa((ex::BoundsError).a,Base.IOBuffer) && ex.i == -2; end
2383-
f9534f() = (a=IOBuffer(); getfield(a, -2))
2384-
f9534f(x) = (a=IOBuffer(); getfield(a, x))
2385-
@test try; f9534f() catch ex; isa((ex::BoundsError).a,Base.IOBuffer) && ex.i == -2; end
2386-
@test try; f9534f(typemin(Int)+2) catch ex; isa((ex::BoundsError).a,Base.IOBuffer) && ex.i == typemin(Int)+2; end
2364+
@test_throws BoundsError((1, 2), 3) begin; a, b, c = 1, 2; end
2365+
let a = []
2366+
@test_broken try; a[]; catch ex; (ex::BoundsError).a === a && ex.i == (1,); end # TODO: Re-enable after PLI
2367+
@test_throws BoundsError(a, (1, 2)) a[1, 2]
2368+
@test_throws BoundsError(a, (10,)) a[10]
2369+
end
2370+
f9534a() = (a = 1 + 2im; getfield(a, -100))
2371+
f9534a(x) = (a = 1 + 2im; getfield(a, x))
2372+
@test_throws BoundsError(1 + 2im, -100) f9534a()
2373+
@test_throws BoundsError(1 + 2im, 3) f9534a(3)
2374+
f9534b() = (a = (1, 2., ""); a[5])
2375+
f9534b(x) = (a = (1, 2., ""); a[x])
2376+
@test_throws BoundsError((1, 2., ""), 5) f9534b()
2377+
@test_throws BoundsError((1, 2., ""), 4) f9534b(4)
2378+
f9534c() = (a = (1, 2.); a[3])
2379+
f9534c(x) = (a = (1, 2.); a[x])
2380+
@test_throws BoundsError((1, 2.), 3) f9534c()
2381+
@test_throws BoundsError((1, 2.), 0) f9534c(0)
2382+
f9534d() = (a = (1, 2, 4, 6, 7); a[7])
2383+
f9534d(x) = (a = (1, 2, 4, 6, 7); a[x])
2384+
@test_throws BoundsError((1, 2, 4, 6, 7), 7) f9534d()
2385+
@test_throws BoundsError((1, 2, 4, 6, 7), -1) f9534d(-1)
2386+
let a = IOBuffer()
2387+
f9534e(x) = setfield!(a, x, 3)
2388+
@test_throws BoundsError(a, -2) f9534e(-2)
2389+
f9534f() = getfield(a, -2)
2390+
f9534f(x) = getfield(a, x)
2391+
@test_throws BoundsError(a, -2) f9534f()
2392+
@test_throws BoundsError(a, typemin(Int) + 2) f9534f(typemin(Int) + 2)
2393+
end
23872394
x9634 = 3
2388-
@test try; getfield(1+2im, x9634); catch ex; (ex::BoundsError).a === 1+2im && ex.i == 3; end
2389-
@test try; throw(BoundsError()) catch ex; !isdefined((ex::BoundsError), :a) && !isdefined((ex::BoundsError), :i); end
2390-
@test try; throw(BoundsError(Int)) catch ex; (ex::BoundsError).a == Int && !isdefined((ex::BoundsError), :i); end
2391-
@test try; throw(BoundsError(Int, typemin(Int))) catch ex; (ex::BoundsError).a == Int && (ex::BoundsError).i == typemin(Int); end
2392-
@test try; throw(BoundsError(Int, (:a,))) catch ex; (ex::BoundsError).a == Int && (ex::BoundsError).i == (:a,); end
2393-
f9534g(a,b,c...) = c[0]
2394-
@test try; f9534g(1,2,3,4,5,6) catch ex; (ex::BoundsError).a === (3,4,5,6) && ex.i == 0; end
2395-
f9534h(a,b,c...) = c[a]
2396-
@test f9534h(4,2,3,4,5,6) == 6
2397-
@test try; f9534h(5,2,3,4,5,6) catch ex; (ex::BoundsError).a === (3,4,5,6) && ex.i == 5; end
2395+
@test_throws BoundsError(1 + 2im, 3) getfield(1 + 2im, x9634)
2396+
@test try; throw(BoundsError()); catch ex; !isdefined((ex::BoundsError), :a) && !isdefined((ex::BoundsError), :i); end
2397+
@test try; throw(BoundsError(Int)); catch ex; (ex::BoundsError).a == Int && !isdefined((ex::BoundsError), :i); end
2398+
@test_throws BoundsError(Int, typemin(Int)) throw(BoundsError(Int, typemin(Int)))
2399+
@test_throws BoundsError(Int, (:a,)) throw(BoundsError(Int, (:a,)))
2400+
f9534g(a, b, c...) = c[0]
2401+
@test_throws BoundsError((3, 4, 5, 6), 0) f9534g(1, 2, 3, 4, 5, 6)
2402+
f9534h(a, b, c...) = c[a]
2403+
@test f9534h(4, 2, 3, 4, 5, 6) == 6
2404+
@test_throws BoundsError((3, 4, 5, 6), 5) f9534h(5, 2, 3, 4, 5, 6)
23982405

23992406
# issue #7978, comment 332352438
24002407
f7978a() = 1
2401-
@test try; a, b = f7978a() catch ex; (ex::BoundsError).a == 1 && ex.i == 2; end
2408+
@test_throws BoundsError(1, 2) begin; a, b = f7978a(); end
24022409
f7978b() = 1, 2
2403-
@test try; a, b, c = f7978b() catch ex; (ex::BoundsError).a == (1, 2) && ex.i == 3; end
2410+
@test_throws BoundsError((1, 2), 3) begin; a, b, c = f7978b(); end
24042411

24052412
# issue #9535
24062413
counter9535 = 0

0 commit comments

Comments
 (0)