Code Generation with macro

@abraemer I really appreciate your idea on property types and how to write the registration function and macro.

Here is my final solution, in case anyone facing the same issue:

module MyModuleA

export
    TypeA,
    PropertyTypes,
    Property,
    list_properties,
    @properties,
    properties

module PropertyTypes
abstract type AbstractProperty end
struct Symmetric <: AbstractProperty end
struct Inverse <: AbstractProperty end
struct IllCond <: AbstractProperty end
struct PosDef <: AbstractProperty end
struct Eigen <: AbstractProperty end
struct Sparse <: AbstractProperty end
struct Random <: AbstractProperty end
struct RegProb <: AbstractProperty end
struct Graph <: AbstractProperty end
end

struct Property
    name::Symbol
end

const PROPERTIES = Dict{Type{<:PropertyTypes.AbstractProperty},Property}(
    type => Property(symbol) for (type, symbol) = Dict(
        PropertyTypes.Symmetric => :symmetric,
        PropertyTypes.Inverse => :inverse,
        PropertyTypes.IllCond => :illcond,
        PropertyTypes.PosDef => :posdef,
        PropertyTypes.Eigen => :eigen,
        PropertyTypes.Sparse => :sparse,
        PropertyTypes.Random => :random,
        PropertyTypes.RegProb => :regprob,
        PropertyTypes.Graph => :graph,
    )
)

# list properties
list_properties() = collect(values(PROPERTIES))

# check property types
function check_propertie_types(props::DataType...)
    for prop = props
        prop <: PropertyTypes.AbstractProperty || throw(ArgumentError("$prop is not a property type"))
    end
end

# check properties exists
function check_properties_exists(props::Property...)
    for prop = props
        prop ∈ values(PROPERTIES) || throw(ArgumentError("Property $prop not exists"))
    end
end

# properties types to properties
function property_types_to_properties(props::DataType...)::Vector{Property}
    check_propertie_types(props...)
    return [PROPERTIES[prop] for prop = props]
end

# register properties macro
macro properties(type::Symbol, ex::Expr)
    return quote
        register_properties($(esc(type)), $ex)
    end
end

# register properties
function register_properties(T::Type, props::Vector{Property})
    # check props
    check_properties_exists(props...)

    # register properties
    @eval properties(::Union{Type{$T},Type{$T{T}}}) where {T} = $props
end

# register properties alternative interfaces
register_properties(T::Type, props::Property...) = register_properties(T, collect(props))
register_properties(T::Type, props::Symbol...) = register_properties(T, collect(props))
register_properties(T::Type, props::Vector{Symbol}) = register_properties(T, [Property(prop) for prop = props])
register_properties(T::Type, props::DataType...) = register_properties(T, collect(props))
register_properties(T::Type, props::Vector{DataType}) = register_properties(T, property_types_to_properties(props...))
register_properties(T::Type, props::PropertyTypes.AbstractProperty...) = register_properties(T, collect(props))
register_properties(T::Type, props::Vector{PropertyTypes.AbstractProperty}) = register_properties(T, [typeof(prop) for prop = props])

# properties function interfaces
properties(::Type{<:AbstractMatrix})::Vector{Property} = []
properties(m::AbstractMatrix) = properties(typeof(m))

# struct
struct TypeA{T<:Number} <: AbstractMatrix{T}
    n::Int

    function TypeA{T}(n::Int) where {T<:Number}
        n > 0 || throw(ArgumentError("$n ≤ 0"))
        return new{T}(n)
    end
end

TypeA(n::Int) = TypeA{Int}(n)

import LinearAlgebra: size, getindex
size(s::TypeA) = (s.n, s.n)
getindex(A::TypeA{T}, i::Int, j::Int) where {T} = 1

@properties TypeA [:symmetric, :inverse, :posdef]
end

module UserModuleB

using ..MyModuleA

export
    TypeC

struct TypeC{T<:Int} <: AbstractMatrix{T}
    n::Int
end

@properties TypeC [:symmetric, :inverse, :graph]
end

# testing codes
using .MyModuleA, .UserModuleB

# reason for Property instead of types
# display(list_properties())
# display(subtypes(PropertyTypes.AbstractProperty))
# display([T() for T = subtypes(PropertyTypes.AbstractProperty)])

"""
use cases:
1. properties of a matrix
2. search matrices by properties
3. define properties for their new matrices
"""

# 1. properties of a matrix
# @show properties(AbstractMatrix)
# @show properties(AbstractMatrix{Int})
# @show properties(Matrix)
# @show properties(Matrix{Int})
# @show properties([1 2; 3 4])
# @show properties(TypeA)
# @show properties(TypeA{Int})
# @show properties(TypeA(5))

# 3. define properties for their new matrices
# display(properties(TypeC))
# @properties TypeC [:symmetric, :inverse, :eigen]
# @properties TypeC [Property(:symmetric), Property(:inverse), Property(:eigen)]
# @properties TypeC [PropertyTypes.Symmetric, PropertyTypes.Inverse, PropertyTypes.Eigen]
# @properties TypeC [PropertyTypes.Symmetric(), PropertyTypes.Inverse(), PropertyTypes.Eigen()]
# display(properties(TypeC))

I would like to highlight a change in register_properties function, which uses Union to accept both type and wrapper types:

@eval properties(::Union{Type{$T},Type{$T{T}}}) where {T} = $props

Also in the macro, type should be escaped:

macro properties(type::Symbol, ex::Expr)
    return quote
        register_properties($(esc(type)), $ex)
    end
end