Skip to content

Commit

Permalink
implement getfield overloading
Browse files Browse the repository at this point in the history
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
  • Loading branch information
vtjnash committed Dec 11, 2017
1 parent 66b2090 commit 4cf08bb
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 124 deletions.
3 changes: 3 additions & 0 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ export
# constants
nothing, Main

const getproperty = getfield
const setproperty! = setfield!

abstract type Number end
abstract type Real <: Number end
abstract type AbstractFloat <: Real end
Expand Down
5 changes: 4 additions & 1 deletion base/coreimg.jl
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

Main.Core.eval(Main.Core, :(baremodule Inference
getfield(getfield(Main, :Core), :eval)(getfield(Main, :Core), :(baremodule Inference
using Core.Intrinsics
import Core: print, println, show, write, unsafe_write, STDOUT, STDERR

const getproperty = getfield
const setproperty! = setfield!

ccall(:jl_set_istopmod, Void, (Any, Bool), Inference, false)

eval(x) = Core.eval(Inference, x)
Expand Down
2 changes: 1 addition & 1 deletion base/distributed/cluster.jl
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ function launch_n_additional_processes(manager, frompid, fromconfig, cnt, launch

wconfig = WorkerConfig()
for x in [:host, :tunnel, :sshflags, :exeflags, :exename, :enable_threaded_blas]
setfield!(wconfig, x, getfield(fromconfig, x))
Base.setproperty!(wconfig, x, Base.getproperty(fromconfig, x))
end
wconfig.bind_addr = bind_addr
wconfig.port = port
Expand Down
2 changes: 2 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,8 @@ export

# types
convert,
# getproperty,
# setproperty!,
fieldoffset,
fieldname,
fieldnames,
Expand Down
2 changes: 1 addition & 1 deletion base/libgit2/gitcredential.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ function Base.read!(io::IO, cred::GitCredential)
# https://git-scm.com/docs/git-credential#git-credential-codeurlcode
copy!(cred, parse(GitCredential, value))
else
setfield!(cred, Symbol(key), Nullable(String(value)))
Base.setproperty!(cred, Symbol(key), Nullable(String(value)))
end
end

Expand Down
2 changes: 1 addition & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function show_default(io::IO, @nospecialize(x))
if !isdefined(x, f)
print(io, undef_ref_str)
else
show(recur_io, getfield(x, f))
show(recur_io, getfield(x, i))
end
if i < nf
print(io, ", ")
Expand Down
10 changes: 10 additions & 0 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ baremodule Base
using Core.Intrinsics
ccall(:jl_set_istopmod, Void, (Any, Bool), Base, true)

getproperty(x, f::Symbol) = getfield(x, f)
setproperty!(x, f::Symbol, v) = setfield!(x, f, convert(fieldtype(typeof(x), f), v))

# Try to help prevent users from shooting them-selves in the foot
# with ambiguities by defining a few common and critical operations
getproperty(x::Module, f::Symbol) = getfield(x, f)
setproperty!(x::Module, f::Symbol, v) = setfield!(x, f, v)
getproperty(x::Type, f::Symbol) = getfield(x, f)
setproperty!(x::Type, f::Symbol, v) = setfield!(x, f, v)

function include(mod::Module, path::AbstractString)
local result
if INCLUDE_STATE === 1
Expand Down
12 changes: 7 additions & 5 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -1752,7 +1752,7 @@
(if (and (pair? e) (eq? (car e) '|.|))
(let ((f (cadr e)) (x (caddr e)))
(cond ((or (eq? (car x) 'quote) (eq? (car x) 'inert) (eq? (car x) '$))
`(call (core getfield) ,f ,x))
`(call (top getproperty) ,f ,x))
((eq? (car x) 'tuple)
(make-fuse f (cdr x)))
(else
Expand Down Expand Up @@ -2039,10 +2039,12 @@
,.(if (eq? aa a) '() `((= ,aa ,(expand-forms a))))
,.(if (eq? bb b) '() `((= ,bb ,(expand-forms b))))
,.(if (eq? rr rhs) '() `((= ,rr ,(expand-forms rhs))))
(call (core setfield!) ,aa ,bb
(call (top convert)
(call (core fieldtype) (call (core typeof) ,aa) ,bb)
,rr))
(call (top setproperty!) ,aa ,bb
(if (call (top ===) (top setproperty!) (core setfield!))
(call (top convert)
(call (core fieldtype) (call (core typeof) ,aa) ,bb)
,rr)
,rr))
(unnecessary ,rr)))))
((tuple)
;; multiple assignment
Expand Down
107 changes: 53 additions & 54 deletions src/method.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,60 +41,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve
// ignore these
}
else {
size_t nargs = jl_expr_nargs(e);
if (e->head == call_sym && nargs > 0) {
jl_value_t *fe = jl_exprarg(e, 0);
if (jl_is_globalref(fe) && jl_binding_resolved_p(jl_globalref_mod(fe), jl_globalref_name(fe))) {
// look at some known called functions
jl_binding_t *b = jl_get_binding(jl_globalref_mod(fe), jl_globalref_name(fe));
jl_value_t *f = b && b->constp ? b->value : NULL;
if (f == jl_builtin_getfield && nargs == 3 &&
jl_is_quotenode(jl_exprarg(e, 2)) && module != NULL) {
// replace getfield(module_expr, :sym) with GlobalRef
jl_value_t *s = jl_fieldref(jl_exprarg(e, 2), 0);
if (jl_is_symbol(s)) {
jl_value_t *me = jl_exprarg(e, 1);
jl_module_t *me_mod = NULL;
jl_sym_t *me_sym = NULL;
if (jl_is_globalref(me)) {
me_mod = jl_globalref_mod(me);
me_sym = jl_globalref_name(me);
}
else if (jl_is_symbol(me) && jl_binding_resolved_p(module, (jl_sym_t*)me)) {
me_mod = module;
me_sym = (jl_sym_t*)me;
}
if (me_mod && me_sym) {
jl_binding_t *b = jl_get_binding(me_mod, me_sym);
if (b && b->constp) {
jl_value_t *m = b->value;
if (m && jl_is_module(m)) {
return jl_module_globalref((jl_module_t*)m, (jl_sym_t*)s);
}
}
}
}
}
else if (f == jl_builtin_tuple) {
size_t j;
for (j = 1; j < nargs; j++) {
if (!jl_is_quotenode(jl_exprarg(e,j)))
break;
}
if (j == nargs) {
jl_value_t *val = NULL;
JL_TRY {
val = jl_interpret_toplevel_expr_in(module, (jl_value_t*)e, NULL, sparam_vals);
}
JL_CATCH {
}
if (val)
return val;
}
}
}
}
size_t i = 0;
size_t i = 0, nargs = jl_array_len(e->args);
if (e->head == foreigncall_sym) {
JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, cc, narg)
jl_value_t *rt = jl_exprarg(e, 1);
Expand Down Expand Up @@ -139,6 +86,58 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve
// TODO: this should be making a copy, not mutating the source
jl_exprargset(e, i, resolve_globals(jl_exprarg(e, i), module, sparam_vals, binding_effects));
}
if (e->head == call_sym && jl_expr_nargs(e) == 3 &&
jl_is_globalref(jl_exprarg(e, 0)) &&
jl_is_globalref(jl_exprarg(e, 1)) &&
jl_is_quotenode(jl_exprarg(e, 2))) {
// replace module_expr.sym with GlobalRef(module, sym)
// for expressions pattern-matching to `getproperty(module_expr, :sym)` in a top-module
// (this is expected to help inference performance)
// TODO: this was broken by linear-IR
jl_value_t *s = jl_fieldref(jl_exprarg(e, 2), 0);
jl_value_t *me = jl_exprarg(e, 1);
jl_value_t *fe = jl_exprarg(e, 0);
jl_module_t *fe_mod = jl_globalref_mod(fe);
jl_sym_t *fe_sym = jl_globalref_name(fe);
jl_module_t *me_mod = jl_globalref_mod(me);
jl_sym_t *me_sym = jl_globalref_name(me);
if (fe_mod->istopmod && !strcmp(jl_symbol_name(fe_sym), "getproperty") && jl_is_symbol(s)) {
if (jl_binding_resolved_p(me_mod, me_sym)) {
jl_binding_t *b = jl_get_binding(me_mod, me_sym);
if (b && b->constp && b->value && jl_is_module(b->value)) {
return jl_module_globalref((jl_module_t*)b->value, (jl_sym_t*)s);
}
}
}
}
if (e->head == call_sym && nargs > 0 &&
jl_is_globalref(jl_exprarg(e, 0))) {
// TODO: this hack should be deleted once llvmcall is fixed
jl_value_t *fe = jl_exprarg(e, 0);
jl_module_t *fe_mod = jl_globalref_mod(fe);
jl_sym_t *fe_sym = jl_globalref_name(fe);
if (jl_binding_resolved_p(fe_mod, fe_sym)) {
// look at some known called functions
jl_binding_t *b = jl_get_binding(fe_mod, fe_sym);
if (b && b->constp && b->value == jl_builtin_tuple) {
size_t j;
for (j = 1; j < nargs; j++) {
if (!jl_is_quotenode(jl_exprarg(e, j)))
break;
}
if (j == nargs) {
jl_value_t *val = NULL;
JL_TRY {
val = jl_interpret_toplevel_expr_in(module, (jl_value_t*)e, NULL, sparam_vals);
}
JL_CATCH {
}
if (val)
return val;
}
}
}
}
}
}
return expr;
Expand Down
117 changes: 62 additions & 55 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -997,19 +997,21 @@ end

let
local z = complex(3, 4)
v = Int[0,0]
for i=1:2
v = Int[0, 0]
for i = 1:2
v[i] = getfield(z, i)
end
@test v == [3,4]
@test_throws BoundsError getfield(z, -1)
@test_throws BoundsError getfield(z, 0)
@test_throws BoundsError getfield(z, 3)
@test v == [3, 4]
@test_throws BoundsError(z, -1) getfield(z, -1)
@test_throws BoundsError(z, 0) getfield(z, 0)
@test_throws BoundsError(z, 3) getfield(z, 3)

strct = LoadError("yofile", 0, "bad")
@test_throws BoundsError getfield(strct, 10)
@test_throws ErrorException setfield!(strct, 0, "")
@test_throws ErrorException setfield!(strct, 4, "")
@test nfields(strct) == 3 # sanity test
@test_throws BoundsError(strct, 10) getfield(strct, 10)
@test_throws ErrorException("type LoadError is immutable") setfield!(strct, 0, "")
@test_throws ErrorException("type LoadError is immutable") setfield!(strct, 4, "")
@test_throws ErrorException("type is immutable") setfield!(strct, :line, 0)
@test strct.file == "yofile"
@test strct.line == 0
@test strct.error == "bad"
Expand All @@ -1018,15 +1020,17 @@ let
@test getfield(strct, 3) == "bad"

mstrct = TestMutable("melm", 1, nothing)
setfield!(mstrct, 2, 8)
Base.setproperty!(mstrct, :line, 8.0)
@test mstrct.line == 8
@test_throws TypeError(:setfield!, "", Int, 8.0) setfield!(mstrct, :line, 8.0)
@test_throws TypeError(:setfield!, "", Int, 8.0) setfield!(mstrct, 2, 8.0)
setfield!(mstrct, 3, "hi")
@test mstrct.error == "hi"
setfield!(mstrct, 1, "yo")
@test mstrct.file == "yo"
@test_throws BoundsError getfield(mstrct, 10)
@test_throws BoundsError setfield!(mstrct, 0, "")
@test_throws BoundsError setfield!(mstrct, 4, "")
@test_throws BoundsError(mstrct, 10) getfield(mstrct, 10)
@test_throws BoundsError(mstrct, 0) setfield!(mstrct, 0, "")
@test_throws BoundsError(mstrct, 4) setfield!(mstrct, 4, "")
end

# allow typevar in Union to match as long as the arguments contain
Expand Down Expand Up @@ -2175,10 +2179,9 @@ g7652() = fieldtype(DataType, :types)
h7652() = setfield!(a7652, 1, 2)
h7652()
@test a7652.a == 2
# commented out due to issue #16195: setfield! does not perform conversions
# i7652() = setfield!(a7652, 1, 3.0)
# i7652()
# @test a7652.a == 3
i7652() = Base.setproperty!(a7652, :a, 3.0)
i7652()
@test a7652.a == 3

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

# pull request #9534
@test try; a,b,c = 1,2; catch ex; (ex::BoundsError).a === (1,2) && ex.i == 3; end
# @test try; [][]; catch ex; isempty((ex::BoundsError).a::Array{Any,1}) && ex.i == (1,); end # TODO: Re-enable after PLI
@test try; [][1,2]; catch ex; isempty((ex::BoundsError).a::Array{Any,1}) && ex.i == (1,2); end
@test try; [][10]; catch ex; isempty((ex::BoundsError).a::Array{Any,1}) && ex.i == (10,); end
f9534a() = (a=1+2im; getfield(a, -100))
f9534a(x) = (a=1+2im; getfield(a, x))
@test try; f9534a() catch ex; (ex::BoundsError).a === 1+2im && ex.i == -100; end
@test try; f9534a(3) catch ex; (ex::BoundsError).a === 1+2im && ex.i == 3; end
f9534b() = (a=(1,2.,""); a[5])
f9534b(x) = (a=(1,2.,""); a[x])
@test try; f9534b() catch ex; (ex::BoundsError).a == (1,2.,"") && ex.i == 5; end
@test try; f9534b(4) catch ex; (ex::BoundsError).a == (1,2.,"") && ex.i == 4; end
f9534c() = (a=(1,2.); a[3])
f9534c(x) = (a=(1,2.); a[x])
@test try; f9534c() catch ex; (ex::BoundsError).a === (1,2.) && ex.i == 3; end
@test try; f9534c(0) catch ex; (ex::BoundsError).a === (1,2.) && ex.i == 0; end
f9534d() = (a=(1,2,4,6,7); a[7])
f9534d(x) = (a=(1,2,4,6,7); a[x])
@test try; f9534d() catch ex; (ex::BoundsError).a === (1,2,4,6,7) && ex.i == 7; end
@test try; f9534d(-1) catch ex; (ex::BoundsError).a === (1,2,4,6,7) && ex.i == -1; end
f9534e(x) = (a=IOBuffer(); setfield!(a, x, 3))
@test try; f9534e(-2) catch ex; isa((ex::BoundsError).a,Base.IOBuffer) && ex.i == -2; end
f9534f() = (a=IOBuffer(); getfield(a, -2))
f9534f(x) = (a=IOBuffer(); getfield(a, x))
@test try; f9534f() catch ex; isa((ex::BoundsError).a,Base.IOBuffer) && ex.i == -2; end
@test try; f9534f(typemin(Int)+2) catch ex; isa((ex::BoundsError).a,Base.IOBuffer) && ex.i == typemin(Int)+2; end
@test_throws BoundsError((1, 2), 3) begin; a, b, c = 1, 2; end
let a = []
@test_broken try; a[]; catch ex; (ex::BoundsError).a === a && ex.i == (1,); end # TODO: Re-enable after PLI
@test_throws BoundsError(a, (1, 2)) a[1, 2]
@test_throws BoundsError(a, (10,)) a[10]
end
f9534a() = (a = 1 + 2im; getfield(a, -100))
f9534a(x) = (a = 1 + 2im; getfield(a, x))
@test_throws BoundsError(1 + 2im, -100) f9534a()
@test_throws BoundsError(1 + 2im, 3) f9534a(3)
f9534b() = (a = (1, 2., ""); a[5])
f9534b(x) = (a = (1, 2., ""); a[x])
@test_throws BoundsError((1, 2., ""), 5) f9534b()
@test_throws BoundsError((1, 2., ""), 4) f9534b(4)
f9534c() = (a = (1, 2.); a[3])
f9534c(x) = (a = (1, 2.); a[x])
@test_throws BoundsError((1, 2.), 3) f9534c()
@test_throws BoundsError((1, 2.), 0) f9534c(0)
f9534d() = (a = (1, 2, 4, 6, 7); a[7])
f9534d(x) = (a = (1, 2, 4, 6, 7); a[x])
@test_throws BoundsError((1, 2, 4, 6, 7), 7) f9534d()
@test_throws BoundsError((1, 2, 4, 6, 7), -1) f9534d(-1)
let a = IOBuffer()
f9534e(x) = setfield!(a, x, 3)
@test_throws BoundsError(a, -2) f9534e(-2)
f9534f() = getfield(a, -2)
f9534f(x) = getfield(a, x)
@test_throws BoundsError(a, -2) f9534f()
@test_throws BoundsError(a, typemin(Int) + 2) f9534f(typemin(Int) + 2)
end
x9634 = 3
@test try; getfield(1+2im, x9634); catch ex; (ex::BoundsError).a === 1+2im && ex.i == 3; end
@test try; throw(BoundsError()) catch ex; !isdefined((ex::BoundsError), :a) && !isdefined((ex::BoundsError), :i); end
@test try; throw(BoundsError(Int)) catch ex; (ex::BoundsError).a == Int && !isdefined((ex::BoundsError), :i); end
@test try; throw(BoundsError(Int, typemin(Int))) catch ex; (ex::BoundsError).a == Int && (ex::BoundsError).i == typemin(Int); end
@test try; throw(BoundsError(Int, (:a,))) catch ex; (ex::BoundsError).a == Int && (ex::BoundsError).i == (:a,); end
f9534g(a,b,c...) = c[0]
@test try; f9534g(1,2,3,4,5,6) catch ex; (ex::BoundsError).a === (3,4,5,6) && ex.i == 0; end
f9534h(a,b,c...) = c[a]
@test f9534h(4,2,3,4,5,6) == 6
@test try; f9534h(5,2,3,4,5,6) catch ex; (ex::BoundsError).a === (3,4,5,6) && ex.i == 5; end
@test_throws BoundsError(1 + 2im, 3) getfield(1 + 2im, x9634)
@test try; throw(BoundsError()); catch ex; !isdefined((ex::BoundsError), :a) && !isdefined((ex::BoundsError), :i); end
@test try; throw(BoundsError(Int)); catch ex; (ex::BoundsError).a == Int && !isdefined((ex::BoundsError), :i); end
@test_throws BoundsError(Int, typemin(Int)) throw(BoundsError(Int, typemin(Int)))
@test_throws BoundsError(Int, (:a,)) throw(BoundsError(Int, (:a,)))
f9534g(a, b, c...) = c[0]
@test_throws BoundsError((3, 4, 5, 6), 0) f9534g(1, 2, 3, 4, 5, 6)
f9534h(a, b, c...) = c[a]
@test f9534h(4, 2, 3, 4, 5, 6) == 6
@test_throws BoundsError((3, 4, 5, 6), 5) f9534h(5, 2, 3, 4, 5, 6)

# issue #7978, comment 332352438
f7978a() = 1
@test try; a, b = f7978a() catch ex; (ex::BoundsError).a == 1 && ex.i == 2; end
@test_throws BoundsError(1, 2) begin; a, b = f7978a(); end
f7978b() = 1, 2
@test try; a, b, c = f7978b() catch ex; (ex::BoundsError).a == (1, 2) && ex.i == 3; end
@test_throws BoundsError((1, 2), 3) begin; a, b, c = f7978b(); end

# issue #9535
counter9535 = 0
Expand Down
Loading

0 comments on commit 4cf08bb

Please sign in to comment.