Skip to content

Commit 0ea3c95

Browse files
authored
feat: Added custom interface forwarding (#11)
fix: Fix map_func argument forwarding
1 parent 04a41b7 commit 0ea3c95

6 files changed

+52
-17
lines changed

Project.toml

+2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ version = "1.4.2"
55

66
[deps]
77
MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078"
8+
MacroUtilities = "274a0f0b-05f4-4024-87ff-de4a5b524b4f"
89

910
[compat]
1011
MLStyle = "0.4"
12+
MacroUtilities = "1.6.1"
1113
julia = "1.6"

src/ForwardMethods.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module ForwardMethods
2-
using MLStyle
2+
using MacroUtilities, MLStyle
33

44
export @forward_methods, @forward_interface, @define_interface
55

src/define_interface.jl

+11-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
define_interface_method(x) = nothing
2-
31
"""
42
properties_interface(T; delegated_fields, [is_mutable=false])
53
@@ -12,7 +10,6 @@ If `T` is a mutable type, also defines `Base.setproperty!(x::T, name::Symbol, va
1210
# Arguments
1311
`delegated_fields` can be either a `Symbol`, or a `vect` or `tuple` `Expr` of `Symbol`s, corresponding to fieldnames of `T`
1412
15-
1613
"""
1714
function properties_interface(T; delegated_fields, kwargs...)
1815
delegated_fields_sym = parse_vect_of_symbols(delegated_fields; kwarg_name=:delegated_fields)
@@ -108,19 +105,26 @@ function equality_interface(T; omit::AbstractVector{Symbol}=Symbol[], equality_o
108105
return wrap_define_interface(T, :equality, equality_expr)
109106
end
110107

111-
const define_interfaces_available = (:properties, :equality, :setfields, :getfields)
108+
const default_define_interfaces = (:properties, :equality, :setfields, :getfields)
112109

113-
for f in define_interfaces_available
110+
for f in default_define_interfaces
114111
@eval define_interface_method(::Val{$(QuoteNode(f))}) = $(Symbol(string(f)*"_interface"))
115112
end
116113

114+
@static if VERSION < v"1.10"
115+
@method_def_constant define_interface_method(::Val{::Symbol}) define_interfaces_available
116+
else
117+
define_interfaces_available() = default_define_interfaces
118+
end
119+
117120
function define_interface_expr(T, kwargs::Dict{Symbol,Any}=Dict{Symbol,Any}())
118121
interfaces = interface_kwarg!(kwargs)
119122
omit = omit_kwarg!(kwargs)
120123
output = Expr(:block)
124+
interfaces_available = define_interfaces_available()
121125
for interface in interfaces
126+
interface in interfaces_available || error("No interface found with name $interface -- must be one of `$interfaces_available`")
122127
f = define_interface_method(Val(interface))
123-
isnothing(f) && error("No interface found with name $interface -- must be one of `$define_interfaces_available`")
124128
push!(output.args, f(T; omit, kwargs...))
125129
end
126130
return output
@@ -132,7 +136,7 @@ end
132136
Defines the `interface` for objects of type `T`
133137
134138
# Arguments
135-
`name` must be one of $(define_interfaces_available), with `name` value `f` corresponding to the interface definition function `\$f_interface` (e.g., `array` => `array_interface`).
139+
`name` must be one of $(default_define_interfaces), with `name` value `f` corresponding to the interface definition function `\$f_interface` (e.g., `array` => `array_interface`).
136140
137141
The `key=value` pairs will be forwarded to the corresponding interface definition method. In particular, specifying `omit=func1` or `omit=[func1,func2, ..., funcn]` will omit `func1`, ..., `funcn` from being forwarded by this macro.
138142

src/forward_interface.jl

+18-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
forward_interface_method(x) = nothing
1+
function forward_interface_method end
2+
23
interface_field_name_required(x) = false
34
interface_at_macroexpand_time(x) = true
45
base_forward_expr(f, args...) = Expr(:call, f, args...)
@@ -246,7 +247,7 @@ end
246247
247248
Given `x::T`, forwards the method `\$field(x::T)` to `getfield(x, \$field)`, for each `field in fieldnames(T)`
248249
"""
249-
function getfields_interface(T; omit::AbstractVector{Symbol}=Symbol[])
250+
function getfields_interface(T; field::Union{Nothing,Symbol}=nothing, omit::AbstractVector{Symbol}=Symbol[])
250251
return wrap_define_interface(T, :getfields, Base.remove_linenums!(quote
251252
local omit_fields = $(Expr(:tuple, QuoteNode.(omit)...))
252253
local fields = fieldnames($T)
@@ -269,7 +270,7 @@ interface_at_macroexpand_time(::typeof(getfields_interface)) = false
269270
270271
Given `x::T`, forwards the method `\$field!(x::T, value)` to `setfield!(x, \$field, value)`, for each `field in fieldnames(T)`
271272
"""
272-
function setfields_interface(T; omit::AbstractVector{Symbol}=Symbol[])
273+
function setfields_interface(T; field::Union{Nothing,Symbol}=nothing, omit::AbstractVector{Symbol}=Symbol[])
273274
return wrap_define_interface(T, :setfields, Base.remove_linenums!(quote
274275
local omit_fields = $(Expr(:tuple, QuoteNode.(omit)...))
275276
local fields = fieldnames($T)
@@ -287,13 +288,18 @@ end
287288

288289
interface_at_macroexpand_time(::typeof(setfields_interface)) = false
289290

291+
const default_forward_interfaces = (:iteration, :indexing, :array, :vector, :dict, :lockable, :getfields, :setfields)
290292

291-
const forward_interfaces_available = (:iteration, :indexing, :array, :vector, :dict, :lockable, :getfields, :setfields)
292-
293-
for f in forward_interfaces_available
293+
for f in default_forward_interfaces
294294
@eval forward_interface_method(::Val{$(QuoteNode(f))}) = $(Symbol(string(f)*"_interface"))
295295
end
296296

297+
@static if VERSION < v"1.10"
298+
@method_def_constant forward_interface_method(::Val{::Symbol}) forward_interfaces_available
299+
else
300+
forward_interfaces_available() = default_forward_interfaces
301+
end
302+
297303
function forward_interface_expr(T, kwargs::Dict{Symbol,Any}=Dict{Symbol,Any}(); _sourceinfo=nothing)
298304
interfaces = interface_kwarg!(kwargs)
299305
omit = omit_kwarg!(kwargs)
@@ -312,9 +318,13 @@ function forward_interface_expr(T, kwargs::Dict{Symbol,Any}=Dict{Symbol,Any}();
312318
end
313319

314320
_output = Expr(:block)
321+
322+
available_interfaces = forward_interfaces_available()
323+
315324
for interface in interfaces
325+
interface in available_interfaces || error("No interface found with name $interface -- must be one of `$available_interfaces`")
326+
316327
f = forward_interface_method(Val(interface))
317-
isnothing(f) && error("No interface found with name $interface -- must be one of `$forward_interfaces_available`")
318328

319329
if interface_at_macroexpand_time(f)
320330
isnothing(field_funcs) && error("Expected `field` from keyword arguments for interface `$interface`")
@@ -341,7 +351,7 @@ end
341351
Forwards the methods defined for `interface` to objects of type `T`
342352
343353
# Arguments
344-
`name` must be one of $(forward_interfaces_available), with `name` value `f` corresponding to the interface definition function `\$f_interface` (e.g., `array` => `array_interface`).
354+
`name` must be one of $(default_forward_interfaces), with `name` value `f` corresponding to the interface definition function `\$f_interface` (e.g., `array` => `array_interface`).
345355
346356
If `name` is either `getfields` or `setfields`, then the `field` keyword argument is ignored
347357

src/forward_methods.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ function forward_methods_expr(Type, field_expr, args...; _sourceinfo=nothing)
228228

229229
output = Expr(:block)
230230
for arg in method_exprs
231-
push!(output.args, forward_method_signature(Type, field_funcs, arg; _sourceinfo))
231+
push!(output.args, forward_method_signature(Type, field_funcs, map_func, arg; _sourceinfo))
232232
end
233233
return output
234234
end

test/TestForwardMethods.jl

+19
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,26 @@ module TestForwardMethods
1313
return output |> esc
1414
end
1515

16+
custom_func_to_forward(t) = t[1]
17+
18+
function custom_interface(T; omit::AbstractVector{Symbol}=Symbol[])
19+
return [:custom_func_to_forward]
20+
end
21+
@static if VERSION < v"1.10"
22+
@Test !(:custom in ForwardMethods.forward_interfaces_available())
23+
end
24+
ForwardMethods.forward_interface_method(::Val{:custom}) = custom_interface
25+
@static if VERSION < v"1.10"
26+
@Test :custom in ForwardMethods.forward_interfaces_available()
27+
end
28+
1629
struct A
1730
v::Vector{Int}
1831
end
1932
@forward_methods A field=v Base.length(x::A) Base.getindex(_, k) Base.eltype(::Type{A})
33+
@static if VERSION < v"1.10"
34+
@forward_interface A field=v interface=custom
35+
end
2036

2137
test_func(v::Vector) = v[1]
2238

@@ -299,6 +315,9 @@ module TestForwardMethods
299315
@test matches
300316
end
301317

318+
@static if VERSION < v"1.10"
319+
@Test custom_func_to_forward(A([0])) == 0
320+
end
302321
@Test length(A([0])) == 1
303322
@Test A([0])[1] == 0
304323
@Test eltype(A) == Int

0 commit comments

Comments
 (0)