Skip to content

Commit

Permalink
Add macro for subtypes lowering with additional subtypekey field (#94)
Browse files Browse the repository at this point in the history
* add macro `register_struct_subtype`

* version bump

* Update Project.toml

* fix non-exported name

---------

Co-authored-by: Jacob Quinn <quinn.jacobd@gmail.com>
  • Loading branch information
sairus7 and quinnj authored May 23, 2023
1 parent 4dbad5b commit 9cca137
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "StructTypes"
uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4"
authors = ["Jacob Quinn"]
version = "1.10.1"
version = "1.11.0"

[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Expand Down
32 changes: 32 additions & 0 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,36 @@ function macro_constructor(expr::Symbol, structtype)
throw(ArgumentError("StructType macros can only be used with a struct definition or a Type"))
end
end)
end

"""
Macro to add subtypes for an abstract type without the need for type field.
For a given `abstract_type`` and `struct_subtype` generates custom lowered NameTuple
with all subtype fields and additional `StructTypes.subtypekey` field
used for identifying the appropriate concrete subtype.
usage:
```julia
abstract type Vehicle end
struct Car <: Vehicle
make::String
end
StructTypes.subtypekey(::Type{Vehicle}) = :type
StructTypes.subtypes(::Type{Vehicle}) = (car=Car, truck=Truck)
StructTypes.@register_struct_subtype Vehicle Car
```
"""
macro register_struct_subtype(abstract_type, struct_subtype)
AT = Core.eval(__module__, abstract_type)
T = Core.eval(__module__, struct_subtype)
field_name = StructTypes.subtypekey(AT)
field_value = findfirst(x->x === T, StructTypes.subtypes(AT))
x_names = [:(x.$(e)) for e in fieldnames(T)] # x.a, x.b, ...
name_types = [:($n::$(esc(t))) for (n, t) in zip(fieldnames(T), fieldtypes(T))] # a::Int, b::String, ...
quote
StructTypes.StructType(::Type{$(esc(struct_subtype))}) = StructTypes.CustomStruct()
StructTypes.lower(x::$(esc(struct_subtype))) = ($(esc(field_name)) = $(QuoteNode(field_value)), $(x_names...))
StructTypes.lowertype(::Type{$(esc(struct_subtype))}) = @NamedTuple{$field_name::$(esc(Symbol)), $(name_types...)}
$(esc(struct_subtype))(x::@NamedTuple{$field_name::$(esc(Symbol)), $(name_types...)}) = $(esc(struct_subtype))($(x_names...))
end
end
32 changes: 32 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -824,3 +824,35 @@ StructTypes.defaults(::Type{Defaultable}) = (b="b",)
@test StructTypes.constructfrom(Defaultable, Dict(:a=>"a")) == Defaultable("a", "b")
@test StructTypes.constructfrom(Defaultable, (a="a", b="c")) == Defaultable("a", "c")
end

# CustomStruct subtypes
abstract type Vehicle2 end

struct Car2 <: Vehicle2
make::String
model::String
seatingCapacity::Int
topSpeed::Float64
end

struct Truck2 <: Vehicle2
# type::String no need of this
make::String
model::String
payloadCapacity::Float64
end

StructTypes.StructType(::Type{Vehicle2}) = StructTypes.AbstractType()
StructTypes.subtypekey(::Type{Vehicle2}) = :type
StructTypes.subtypes(::Type{Vehicle2}) = (car=Car2, truck=Truck2)
StructTypes.@register_struct_subtype Vehicle2 Car2
StructTypes.@register_struct_subtype Vehicle2 Truck2

@testset "register_struct_subtype" begin
nt = (; :type => :car, :make => "Mercedes-Benz", :model => "S500", :seatingCapacity => 5, :topSpeed => 250.1)
car = Car2(nt)
@test StructTypes.lower(car) === nt
@test StructTypes.lowertype(Car2) === typeof(nt)
@test typeof(car) == Car2
@test car.make == "Mercedes-Benz"
end

0 comments on commit 9cca137

Please sign in to comment.