diff --git a/src/macros.jl b/src/macros.jl index 6acfece..f88f28d 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -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))()`. @@ -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 @@ -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. @@ -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 \ No newline at end of file +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))) diff --git a/test/runtests.jl b/test/runtests.jl index 4d992a7..3b097b9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 @@ -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 @@ -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 \ No newline at end of file +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