Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add edit, less and code_* macros, mirroring which #5832

Merged
merged 1 commit into from
Apr 8, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ Library improvements

* New functions `minmax` and `extrema` ([#5275]).

* New macros `@edit`, `@less`, `@code_typed`, `@code_lowered`, `@code_llvm` and `@code_native` that all function like `@which` ([#5832]).

* `consume(p)` extended to `consume(p, args...)`, allowing it
to optionally pass `args...` back to the producer ([#4775]).

Expand Down Expand Up @@ -756,6 +758,7 @@ Too numerous to mention.
[#3605]: https://github.com/JuliaLang/julia/pull/3605
[#3233]: https://github.com/JuliaLang/julia/pull/3233
[#4811]: https://github.com/JuliaLang/julia/pull/4811
[#5832]: https://github.com/JuliaLang/julia/pull/5832

[packages chapter]: http://docs.julialang.org/en/latest/manual/packages/
[sorting functions]: http://docs.julialang.org/en/latest/stdlib/sort/
Expand Down
2 changes: 2 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,5 @@ end
export nnz

scale!{T<:Base.LinAlg.BlasReal}(X::Array{T}, s::Complex) = error("scale!: Cannot scale a real array by a complex value in-place. Use scale(X::Array{Real}, s::Complex) instead.")

@deprecate which(f::Callable, args...) @which f(args...)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this should be deprecated. I'd still want to use the function form in various cases. And doesn't the macro generate calls to it?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, the macro generates calls to which(f,t::(Type…)). What I'm deprecating here is the previous definition of which, which determines the types of its arguments itself.

Both could stay in base, but if the user called a function which(f,(Int,)) there's an ambiguity between whether the user was looking for the method that would be called with f((Int,)) or f(1). It's a really slight corner-case, though, and it fails gracefully to the more common case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Basically, in the current definitions for all the methods (which, edit, less, code_...), which is the odd man out. All the others get the arguments as tuple of types. So for this macro to generically call all of them I needed to change which's signature.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, you are right.

6 changes: 6 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,12 @@ export
@allocated,
@profile,
@which,
@edit,
@less,
@code_typed,
@code_lowered,
@code_llvm,
@code_native,
@windows,
@unix,
@osx,
Expand Down
2 changes: 1 addition & 1 deletion base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2697,7 +2697,7 @@ function replace_tupleref!(ast, e::ANY, tupname, vals, sv, i0)
end
end

function code_typed(f::Callable, types)
function code_typed(f::Callable, types::(Type...))
asts = {}
for x in _methods(f,types,-1)
linfo = x[3].func.code
Expand Down
56 changes: 35 additions & 21 deletions base/interactiveutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,10 @@ function less(file::String, line::Integer)
run(`$pager +$(line)g $file`)
end
less(file::String) = less(file, 1)

edit(f::Union(Function,DataType)) = edit(functionloc(f)...)
edit(f::Union(Function,DataType), t) = edit(functionloc(f,t)...)
less(f::Union(Function,DataType)) = less(functionloc(f)...)
less(f::Union(Function,DataType), t) = less(functionloc(f,t)...)
edit(f::Callable) = edit(functionloc(f)...)
edit(f::Callable, t::(Type...)) = edit(functionloc(f,t)...)
less(f::Callable) = less(functionloc(f)...)
less(f::Callable, t::(Type...)) = less(functionloc(f,t)...)

function edit( m::Method )
tv, decls, file, line = arg_decl_parts(m)
Expand Down Expand Up @@ -195,53 +194,68 @@ versioninfo(verbose::Bool) = versioninfo(STDOUT,verbose)

# searching definitions

function which(f::Callable, args...)
function which(f::Callable, t::(Type...))
if !isgeneric(f)
throw(ErrorException("not a generic function, no methods available"))
end
ms = methods(f, map(a->(isa(a,Type) ? Type{a} : typeof(a)), args))
isempty(ms) && throw(MethodError(f, args))
ms = methods(f, t)
isempty(ms) && throw(MethodError(f, t))
Copy link
Member

Choose a reason for hiding this comment

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

This isa(a,Type) behavior is necessary.

Copy link
Member

Choose a reason for hiding this comment

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

This is a bit unfortunate --- MethodError normally takes the actual arguments, not types. We should throw a generic error instead, or have a separate version of which (renamed) that accepts actual arguments.

Copy link
Member Author

Choose a reason for hiding this comment

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

Unfortunate indeed. Good catch. The others have to deal with this same issue:

code_llvm/native throw: error("no method found for the specified argument types")
edit/less throw: error("could not find function definition")
code_typed/lowered: returns an empty array

ms[1]
end

macro which(ex0)
if isa(ex0,Expr) &&
any(a->(Meta.isexpr(a,:kw) || Meta.isexpr(a,:parameters)), ex0.args)
typesof(args...) = map(a->(isa(a,Type) ? Type{a} : typeof(a)), args)

function gen_call_with_extracted_types(fcn, ex0)
if isa(ex0, Expr) &&
any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args)
# keyword args not used in dispatch, so just remove them
args = filter(a->!(Meta.isexpr(a,:kw) || Meta.isexpr(a,:parameters)), ex0.args)
return Expr(:call, :which, map(esc, args)...)
args = filter(a->!(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args)
return Expr(:call, fcn, esc(args[1]),
Expr(:call, :typesof, map(esc, args[2:end])...))
end
if isa(ex0, Expr) && ex0.head == :call
return Expr(:call, :which, map(esc, ex0.args)...)
return Expr(:call, fcn, esc(ex0.args[1]),
Expr(:call, :typesof, map(esc, ex0.args[2:end])...))
end
ex = expand(ex0)
exret = Expr(:call, :error, "expression is not a function call")
if !isa(ex, Expr)
# do nothing -> error
elseif ex.head == :call
if any(e->(isa(e,Expr) && e.head==:(...)), ex0.args) &&
isa(ex.args[1],TopNode) && ex.args[1].name == :apply
exret = Expr(:call, ex.args[1], :which,
if any(e->(isa(e, Expr) && e.head==:(...)), ex0.args) &&
isa(ex.args[1], TopNode) && ex.args[1].name == :apply
exret = Expr(:call, ex.args[1], fcn,
Expr(:tuple, esc(ex.args[2])),
map(esc, ex.args[3:end])...)
Expr(:call, :typesof, map(esc, ex.args[3:end])...))
else
exret = Expr(:call, :which, map(esc, ex.args)...)
exret = Expr(:call, fcn, esc(ex.args[1]),
Expr(:call, :typesof, map(esc, ex.args[2:end])...))
end
elseif ex.head == :body
a1 = ex.args[1]
if isa(a1, Expr) && a1.head == :call
a11 = a1.args[1]
if a11 == :setindex!
exret = Expr(:call, :which, a11, map(esc, a1.args[2:end])...)
exret = Expr(:call, fcn, a11,
Expr(:call, :typesof, map(esc, a1.args[2:end])...))
end
end
elseif ex.head == :thunk
exret = Expr(:call, :error, "expression is not a function call, or is too complex for @which to analyze; "
exret = Expr(:call, :error, "expression is not a function call, "
* "or is too complex for @which to analyze; "
* "break it down to simpler parts if possible")
end
exret
end

for fname in [:which, :less, :edit, :code_typed, :code_lowered, :code_llvm, :code_native]
@eval begin
Copy link
Member

Choose a reason for hiding this comment

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

This @eval loop should be removed. There is no reason to copy this entire function body when it is basically identical for each case. Instead, there should be a single code-generating function that takes fname as an argument, and each macro can be a trivial wrapper around this.

Copy link
Member

Choose a reason for hiding this comment

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

Ah yes. Something about having a macro-generating macro struck me as not right, but a better way didn't occur to me off the top of my head. That's much simpler.

macro ($fname)(ex0)
gen_call_with_extracted_types($fname, ex0)
end
end
end

# `methodswith` -- shows a list of methods using the type given

function methodswith(t::Type, m::Module, showparents::Bool=false)
Expand Down
10 changes: 5 additions & 5 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ isgeneric(f::ANY) = (isa(f,Function)||isa(f,DataType)) && isa(f.env,MethodTable)

function_name(f::Function) = isgeneric(f) ? f.env.name : (:anonymous)

code_lowered(f::Function,t::Tuple) = map(m->uncompressed_ast(m.func.code), methods(f,t))
code_lowered(f::Function,t::(Type...)) = map(m->uncompressed_ast(m.func.code), methods(f,t))
methods(f::ANY,t::ANY) = map(m->m[3], _methods(f,t,-1))::Array{Any,1}
_methods(f::ANY,t::ANY,lim) = _methods(f,{(t::Tuple)...},length(t::Tuple),lim,{})
function _methods(f::ANY,t::Array,i,lim::Integer,matching::Array{Any,1})
Expand Down Expand Up @@ -136,10 +136,10 @@ function _dump_function(f, t::ANY, native, wrapper)
str
end

code_llvm (f::Callable, types::Tuple) = print(_dump_function(f, types, false, false))
code_native(f::Callable, types::Tuple) = print(_dump_function(f, types, true, false))
code_llvm (f::Callable, types::(Type...)) = print(_dump_function(f, types, false, false))
code_native(f::Callable, types::(Type...)) = print(_dump_function(f, types, true, false))

function functionlocs(f::Union(Function,DataType), types=(Any...))
function functionlocs(f::Callable, types=(Type...))
locs = Any[]
for m in methods(f, types)
lsd = m.func.code::LambdaStaticData
Expand All @@ -154,7 +154,7 @@ function functionlocs(f::Union(Function,DataType), types=(Any...))
locs
end

functionloc(f::Union(Function,DataType), types=(Any...)) = functionlocs(f, types)[1]
functionloc(f::Callable, types=(Any...)) = functionlocs(f, types)[1]

function function_module(f::Function, types=(Any...))
m = methods(f, types)
Expand Down
28 changes: 26 additions & 2 deletions doc/stdlib/base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ Getting Around

Edit the definition of a function, optionally specifying a tuple of types to indicate which method to edit.

.. function:: @edit

Evaluates the arguments to the function call, determines their types, and calls the ``edit`` function on the resulting expression

.. function:: less(file::String, [line])

Show a file using the default pager, optionally providing a starting line number. Returns to the julia prompt when you quit the pager.
Expand All @@ -57,6 +61,10 @@ Getting Around

Show the definition of a function using the default pager, optionally specifying a tuple of types to indicate which method to see.

.. function:: @less

Evaluates the arguments to the function call, determines their types, and calls the ``less`` function on the resulting expression

.. function:: clipboard(x)

Send a printed form of ``x`` to the operating system clipboard ("copy").
Expand Down Expand Up @@ -89,9 +97,9 @@ Getting Around

Search documentation for functions related to ``string``.

.. function:: which(f, args...)
.. function:: which(f, types)

Return the method of ``f`` (a ``Method`` object) that will be called for the given arguments.
Return the method of ``f`` (a ``Method`` object) that will be called for arguments with the given types.

.. function:: @which

Expand Down Expand Up @@ -5520,18 +5528,34 @@ Internals

Returns an array of lowered ASTs for the methods matching the given generic function and type signature.

.. function:: @code_lowered

Evaluates the arguments to the function call, determines their types, and calls the ``code_lowered`` function on the resulting expression

.. function:: code_typed(f, types)

Returns an array of lowered and type-inferred ASTs for the methods matching the given generic function and type signature.

.. function:: @code_typed

Evaluates the arguments to the function call, determines their types, and calls the ``code_typed`` function on the resulting expression

.. function:: code_llvm(f, types)

Prints the LLVM bitcodes generated for running the method matching the given generic function and type signature to STDOUT.

.. function:: @code_llvm

Evaluates the arguments to the function call, determines their types, and calls the ``code_llvm`` function on the resulting expression

.. function:: code_native(f, types)

Prints the native assembly instructions generated for running the method matching the given generic function and type signature to STDOUT.

.. function:: @code_native

Evaluates the arguments to the function call, determines their types, and calls the ``code_native`` function on the resulting expression

.. function:: precompile(f,args::(Any...,))

Compile the given function `f` for the argument tuple (of types) `args`, but do not execute it.