Skip to content
Draft
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
72 changes: 68 additions & 4 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ for struct_type in (:Struct, :Mutable, :CustomStruct, :OrderedStruct, :AbstractT
"""
@$(isolate_name($struct_type))(expr::Expr)
@$(isolate_name($struct_type))(expr::Symbol)

If `expr` is a struct definition, sets the `StructType` of the defined struct to
`$(isolate_name($struct_type))()`. If `expr` is the name of a `Type`, sets the `StructType` of that
type to `$(isolate_name($struct_type))()`.
Expand All @@ -18,7 +18,7 @@ for struct_type in (:Struct, :Mutable, :CustomStruct, :OrderedStruct, :AbstractT
```julia
StructTypes.StructType(::Type{MyStruct}) = StructType.Struct()
```
and
and
```julia
@$(isolate_name($struct_type)) struct MyStruct
val::Int
Expand Down Expand Up @@ -59,7 +59,7 @@ 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
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.

Expand Down Expand Up @@ -87,4 +87,68 @@ macro register_struct_subtype(abstract_type, struct_subtype)
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
end


#-----------------------------------------------------------------------------# @auto


"""
@auto AbstractSuperType
@auto AbstractSuperType _my_subtype_key_

Macro to automatically generate the StructTypes interface for every subtype of `AbstractSuperType`.
With the example of JSON3, this enables you to `JSON3.read(str, MyType)` where `MyType <: AbstractSuperType`.

`@auto` assumes that all subtypes of the provided `AbstractSuperType` have the default constructors
provided by Julia. If this is not the case, you'll need to overload:

StructTypes.construct(::Type{MyType}, named_tuple::StructTypes.lowertype(MyType)) = MyType(...)

"""
macro auto(T, subtypekey = :__type__)
esc(quote

if $T isa UnionAll
@warn "Cannot use @auto with UnionAll types."
else
function StructTypes.StructType(::Type{T}) where {T <: $T}
isconcretetype(T) ? StructTypes.CustomStruct() : StructTypes.AbstractType()
end

function StructTypes.lower(x::T) where {T <: $T}
($subtypekey = Symbol(T), NamedTuple(k => getfield(x, k) for k in fieldnames(T))...)
end

function StructTypes.lowertype(::Type{T}) where {T <: $T}
NamedTuple{($(QuoteNode(subtypekey)), fieldnames(T)...), Tuple{Symbol, fieldtypes(T)...}}
end

function StructTypes.construct(::Type{T}, nt::StructTypes.lowertype(Type{T})) where T <: $T
T((nt[x] for x in fieldnames(T))...)
end

function StructTypes.subtypes(::Type{T}) where {T <: $T}
StructTypes.SubTypeClosure() do x::Symbol
e = Meta.parse(string(x))
if StructTypes.is_valid_type_ex(e)
try # try needed to catch undefined symbols
S = eval(e)
S isa Type && S <: T && return S
catch
end
end
return Any
end
end

StructTypes.subtypekey(T::Type{<: $T}) = $(QuoteNode(subtypekey))
end
end)
end

is_valid_type_ex(x) = isbits(x)

is_valid_type_ex(s::Union{Symbol, QuoteNode}) = true

is_valid_type_ex(e::Expr) = ((e.head == :curly || e.head == :tuple || e.head == :.) && all(map(is_valid_type_ex, e.args)))
44 changes: 40 additions & 4 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ end
builtin_type_mapping = Dict(
StructTypes.AbstractType() => Union{
Core.IO,
Core.Number,
Core.Number,
Base.AbstractDisplay,
Base.VERSION <= v"1.2" ? Union{} : Union{
Base.AbstractMatch,
Base.AbstractPattern,
}
}
},
StructTypes.UnorderedStruct() => Union{
Core.Any, # Might be too open
Expand Down Expand Up @@ -676,7 +676,7 @@ function bicycle_subtypes(t_sym::Symbol)
isempty(t[1]) && t[2] == 1 && return Gravel # gravel bikes often have 1 x N chainring/cog setups
end
sub_type_closure = StructTypes.SubTypeClosure(bicycle_subtypes)
StructTypes.subtypes(::Type{Bicycle}) = sub_type_closure
StructTypes.subtypes(::Type{Bicycle}) = sub_type_closure

mutable struct C2
a::Int
Expand Down Expand Up @@ -855,4 +855,40 @@ StructTypes.@register_struct_subtype Vehicle2 Truck2
@test StructTypes.lowertype(Car2) === typeof(nt)
@test typeof(car) == Car2
@test car.make == "Mercedes-Benz"
end
end


# @auto
abstract type AbstractSuperType end
StructTypes.@auto AbstractSuperType

abstract type AbstractSubType <: AbstractSuperType end
struct AutoA <: AbstractSuperType
x::Int
y::String
z::Symbol
end
struct AutoB <: AbstractSubType
x::String
y::Symbol
z::Float64
end
struct AutoC{T, S} <: AbstractSubType
x::T
y::S
z::String
end


@testset "@auto" begin
@test StructTypes.StructType(AutoC) == StructTypes.AbstractType()
@test StructTypes.StructType(AutoC{Int,Int}) == StructTypes.CustomStruct()

a = AutoA(1, "2", :three)
b = AutoB("one", :two, 3.0)
c = AutoC(:one, "two", "three")

@test StructTypes.construct(AutoA, StructTypes.lower(a)) == a
@test StructTypes.construct(AutoB, StructTypes.lower(b)) == b
@test StructTypes.construct(AutoC{Symbol,String}, StructTypes.lower(c)) == c
end