Skip to content

Commit

Permalink
Merge pull request #25 from cscherrer/Better-static_hasmethod-checks
Browse files Browse the repository at this point in the history
static_hasmethod tweaks
  • Loading branch information
cscherrer authored Jun 14, 2021
2 parents d77cfd0 + 34ab999 commit 15518be
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "KeywordCalls"
uuid = "4d827475-d3e4-43d6-abe3-9688362ede9f"
authors = ["Chad Scherrer <[email protected]> and contributors"]
version = "0.1.8"
version = "0.2.0"

[deps]
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
Expand Down
53 changes: 35 additions & 18 deletions src/KeywordCalls.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ function alias(f, nt::NamedTuple{K}) where {K}
NamedTuple{newnames}(values(nt))
end


function has_kwargs end
function build end

function _call_in_default_order end

Expand All @@ -41,16 +42,19 @@ function _kwcall(__module__, ex)
f_sym = ex.args[1]
f_esc = esc(f_sym)
args, defaults = _parse_args(ex.args[2:end])

@assert isdefined(__module__, f_sym)

f = getproperty(__module__, f_sym)

argnames = QuoteNode.(args)
sorted_argnames = QuoteNode.(sort(args))

alias = KeywordCalls.alias
_sort = KeywordCalls._sort
instance_type = KeywordCalls.instance_type


q = quote
const inst = $instance_type($f)

Expand All @@ -59,7 +63,8 @@ function _kwcall(__module__, ex)
end
end

if !kw_exists(f, args)

if !static_hasmethod(has_kwargs, Tuple{typeof(f)})
namedtuplemethod = quote
@inline function $f_esc(nt::NamedTuple)
aliased = $alias($f, nt)
Expand All @@ -73,12 +78,13 @@ function _kwcall(__module__, ex)

kwmethod = quote
$f_esc(;kw...) = $f_esc(NamedTuple(kw))
KeywordCalls.has_kwargs(::typeof($f_esc)) = true
end

push!(q.args, kwmethod)
end

return (f_esc=f_esc, args=args, defaults=defaults, sorted_argnames=sorted_argnames, q=q)
return (f_sym=f_sym, args=args, defaults=defaults, sorted_argnames=sorted_argnames, q=q)
end

function _parse_args(args)
Expand All @@ -95,9 +101,9 @@ _get_arg(s::Symbol) = s
export @kwstruct

"""
@kwstruct Foo(b,a,c=0)
@kwstruct Foo(b,a,c)
Equivalent to `@kwcall Foo(b,a,c=0)` plus a definition
Equivalent to `@kwcall Foo(b,a,c)` plus a definition
Foo(nt::NamedTuple{(:b, :a, :c), T}) where {T} = Foo{(:b, :a, :c), T}(nt)
Expand All @@ -107,20 +113,37 @@ Note that this assumes existence of a `Foo` struct of the form
someFieldName :: NamedTuple{N,T}
end
Unlike `@kwcall`, `@kwstruct` always creates a new method for generic named
tuples. This is needed because defining a struct adds a method for the constructor.
NOTE: Default values (as in `@kwcall`) currently do not work for `@kwstruct`.
They can work at the REPL, but this seems to be because of a world age issue.
This feature may be supported again in a future release.
"""
macro kwstruct(ex)
_kwstruct(__module__, ex)
end

function _kwstruct(__module__, ex)
setup = _kwcall(__module__, ex)
(f_esc, args, defaults, q) = setup.f_esc, setup.args, setup.defaults, setup.q
argnames = QuoteNode.(args)
(f_sym, args, defaults, q) = setup.f_sym, setup.args, setup.defaults, setup.q
f_esc = esc(f_sym)
f = getproperty(__module__, f_sym)

# `Tricks.static_hasmethod` currently doesn't work on constructors
# (see https://github.com/oxinabox/Tricks.jl/issues/17)
# But we can fake it by creating a `build` method that's defined iff the
# constructor has the corresponding method. Then we can check for presence
# of the `build` method and know whether the constructor method is defined.
if !static_hasmethod(build, Tuple{instance_type(f), Tuple{NamedTuple{((args...),)}}})
argnames = QuoteNode.(args)

new_method = quote
const inst = $instance_type($f_esc)
$f_esc(nt::NamedTuple{($(argnames...),),T}) where {T} = $f_esc{($(argnames...),), T}(nt)
KeywordCalls.build(::inst, ::NamedTuple{($(argnames...),),T}) where {T} = true
end

push!(q.args, new_method)
end

push!(q.args, :($f_esc(nt::NamedTuple{($(argnames...),),T}) where {T} = $f_esc{($(argnames...),), T}(nt)))

return q
end

Expand Down Expand Up @@ -159,12 +182,6 @@ function _kwalias(f, aliasmap)
return q
end

function kw_exists(f, args)
args = tuple(args...)
nt = _sort(NamedTuple{args}(ntuple(i -> 1, length(args))))
static_hasmethod(_call_in_default_order, Tuple{instance_type(f), typeof(nt)})
end

# See https://github.com/cscherrer/KeywordCalls.jl/issues/22
instance_type(f::F) where {F<:Function} = F
instance_type(f::UnionAll) = Type{f}
Expand Down
9 changes: 6 additions & 3 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using KeywordCalls
using Test, Pkg
using BenchmarkTools

@testset "No warnings on import" begin
@test_nowarn @eval using KeywordCalls
end

f(nt::NamedTuple{(:c,:b,:a)}) = nt.a^3 + nt.b^2 + nt.c
@kwcall f(c,b,a)

Expand Down Expand Up @@ -49,8 +52,8 @@ end
end

@testset "Constructors with defaults" begin
@test @inferred Bar((b=1,a=2)).nt == (a=2,b=1,c=0)
@test @inferred Bar((b=1,a=2,c=5)).nt == (a=2,b=1,c=5)
@test_broken @inferred Bar((b=1,a=2)).nt == (a=2,b=1,c=0)
@test_broken @inferred Bar((b=1,a=2,c=5)).nt == (a=2,b=1,c=5)
end

@testset "Keyword aliases" begin
Expand Down

2 comments on commit 15518be

@cscherrer
Copy link
Owner Author

Choose a reason for hiding this comment

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

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/38819

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.0 -m "<description of version>" 15518be9f7643851059dde834bc42f8f5e16e83c
git push origin v0.2.0

Please sign in to comment.