Skip to content

Commit

Permalink
implement getfield overloading (#24960)
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.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
  • Loading branch information
vtjnash committed Dec 18, 2017
1 parent a28f495 commit 4d3d49d
Show file tree
Hide file tree
Showing 21 changed files with 237 additions and 169 deletions.
15 changes: 9 additions & 6 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ New language features
iterated using `pairs(kw)`. `kw` can no longer contain multiple entries for the same
argument name ([#4916]).

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

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

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

* Field access via dot-syntax can now be overloaded by adding methods to
`Base.getproperty` and `Base.setproperty!` ([#1974]).

Language changes
----------------

Expand Down
6 changes: 6 additions & 0 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ import Core: arraysize, arrayset, arrayref
vect() = Vector{Any}()
vect(X::T...) where {T} = T[ X[i] for i = 1:length(X) ]

"""
vect(X...)
Create a Vector with element type computed from the promote_typeof of the argument,
containing the argument list.
"""
function vect(X...)
T = promote_typeof(X...)
#T[ X[i] for i=1:length(X) ]
Expand Down
3 changes: 3 additions & 0 deletions base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,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
31 changes: 27 additions & 4 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1236,8 +1236,8 @@ tuple
"""
getfield(value, name::Symbol)
Extract a named field from a `value` of composite type. The syntax `a.b` calls
`getfield(a, :b)`.
Extract a named field from a `value` of composite type.
See also [`getproperty`](@ref Base.getproperty).
# Examples
```jldoctest
Expand All @@ -1256,8 +1256,9 @@ getfield
"""
setfield!(value, name::Symbol, x)
Assign `x` to a named field in `value` of composite type. The syntax `a.b = c` calls
`setfield!(a, :b, c)`. `value` must be mutable.
Assign `x` to a named field in `value` of composite type.
The `value` must be mutable and `x` must be a subtype of `fieldtype(typeof(value), name)`.
See also [`setproperty!`](@ref Base.setproperty!).
# Examples
```jldoctest
Expand Down Expand Up @@ -1768,4 +1769,26 @@ The base library of Julia.
"""
kw"Base"

"""
typeassert(x, type)
Throw a TypeError unless `x isa type`.
The syntax `x::type` calls this function.
"""
typeassert

"""
getproperty(value, name::Symbol)
The syntax `a.b` calls `getproperty(a, :b)`.
"""
Base.getproperty

"""
setproperty!(value, name::Symbol, x)
The syntax `a.b = c` calls `setproperty!(a, :b, c)`.
"""
Base.setproperty!

end
2 changes: 2 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -899,6 +899,8 @@ export

# types
convert,
# getproperty,
# setproperty!,
fieldoffset,
fieldname,
fieldnames,
Expand Down
17 changes: 10 additions & 7 deletions base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1961,7 +1961,7 @@ function abstract_call_gf_by_type(@nospecialize(f), argtypes::Vector{Any}, @nosp
# if there's a possibility we could constant-propagate a better result
# (hopefully without doing too much work), try to do that now
# TODO: it feels like this could be better integrated into abstract_call_method / typeinf_edge
const_rettype = abstract_call_method_with_const_args(argtypes, applicable[1]::SimpleVector, sv)
const_rettype = abstract_call_method_with_const_args(f, argtypes, applicable[1]::SimpleVector, sv)
if const_rettype rettype
# use the better result, if it's a refinement of rettype
rettype = const_rettype
Expand Down Expand Up @@ -2020,7 +2020,7 @@ function cache_lookup(code::MethodInstance, argtypes::Vector{Any}, cache::Vector
return nothing
end

function abstract_call_method_with_const_args(argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState)
function abstract_call_method_with_const_args(@nospecialize(f), argtypes::Vector{Any}, match::SimpleVector, sv::InferenceState)
method = match[3]::Method
nargs::Int = method.nargs
method.isva && (nargs -= 1)
Expand Down Expand Up @@ -2053,11 +2053,14 @@ function abstract_call_method_with_const_args(argtypes::Vector{Any}, match::Simp
end
end
if !cache_inlineable && !sv.params.aggressive_constant_propagation
# in this case, see if all of the arguments are constants
for i in 1:nargs
a = argtypes[i]
if !isa(a, Const) && !isconstType(a)
return Any
tm = _topmod(sv)
if !istopfunction(tm, f, :getproperty) && !istopfunction(tm, f, :setproperty!)
# in this case, see if all of the arguments are constants
for i in 1:nargs
a = argtypes[i]
if !isa(a, Const) && !isconstType(a)
return Any
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion base/libgit2/gitcredential.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,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), String(value))
Base.setproperty!(cred, Symbol(key), 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
11 changes: 11 additions & 0 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ 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
# (and these don't need the extra convert code)
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
22 changes: 12 additions & 10 deletions doc/src/manual/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,16 +158,18 @@ Under the name `f`, the function does not support infix notation, however.

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

| Expression | Calls |
|:----------------- |:---------------------- |
| `[A B C ...]` | [`hcat`](@ref) |
| `[A; B; C; ...]` | [`vcat`](@ref) |
| `[A B; C D; ...]` | [`hvcat`](@ref) |
| `A'` | [`adjoint`](@ref) |
| `A.'` | [`transpose`](@ref) |
| `1:n` | [`colon`](@ref) |
| `A[i]` | [`getindex`](@ref) |
| `A[i]=x` | [`setindex!`](@ref) |
| Expression | Calls |
|:----------------- |:----------------------- |
| `[A B C ...]` | [`hcat`](@ref) |
| `[A; B; C; ...]` | [`vcat`](@ref) |
| `[A B; C D; ...]` | [`hvcat`](@ref) |
| `A'` | [`adjoint`](@ref) |
| `A.'` | [`transpose`](@ref) |
| `1:n` | [`colon`](@ref) |
| `A[i]` | [`getindex`](@ref) |
| `A[i] = x` | [`setindex!`](@ref) |
| `A.n` | [`getproperty`](@ref Base.getproperty) |
| `A.n = x` | [`setproperty!`](@ref Base.setproperty!) |

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

Expand Down
1 change: 1 addition & 0 deletions doc/src/stdlib/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Base.cat
Base.vcat
Base.hcat
Base.hvcat
Base.vect
Base.flipdim
Base.circshift
Base.circshift!
Expand Down
9 changes: 6 additions & 3 deletions doc/src/stdlib/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Base.isless
Base.ifelse
Base.lexcmp
Base.lexless
Core.typeassert
Core.typeof
Core.tuple
Base.ntuple
Expand All @@ -124,6 +125,10 @@ Base.finalizer
Base.finalize
Base.copy
Base.deepcopy
Base.getproperty
Base.setproperty!
Core.getfield
Core.setfield!
Core.isdefined
Base.@isdefined
Base.convert
Expand All @@ -133,7 +138,7 @@ Base.widen
Base.identity
```

## Dealing with Types
## Properties of Types

```@docs
Base.supertype
Expand All @@ -150,8 +155,6 @@ Base.eps(::Type{<:AbstractFloat})
Base.eps(::AbstractFloat)
Base.promote_type
Base.promote_rule
Core.getfield
Core.setfield!
Base.fieldoffset
Core.fieldtype
Base.isimmutable
Expand Down
30 changes: 15 additions & 15 deletions doc/src/stdlib/punctuation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

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

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

2 comments on commit 4d3d49d

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Executing the daily benchmark build, I will reply here when finished:

@nanosoldier runbenchmarks(ALL, isdaily = true)

@nanosoldier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something went wrong when running your job:

NanosoldierError: failed to run benchmarks against primary commit: failed process: Process(`sudo cset shield -e su nanosoldier -- -c ./benchscript.sh`, ProcessExited(1)) [1]

Logs and partial data can be found here
cc @ararslan

Please sign in to comment.