# Limit subtype to immediate parent in method signature?

**URL:** <https://discourse.julialang.org/t/limit-subtype-to-immediate-parent-in-method-signature/133177>\
**Category:** General Usage\
**Tags:** macros, type\
**Created:** [October 15, 2025, 5:28pm UTC](https://discourse.julialang.org/t/limit-subtype-to-immediate-parent-in-method-signature/133177 "2025-10-15T17:28:24Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![bcon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bcon/32/23495_2.png) [@bcon](https://discourse.julialang.org/u/bcon)\
**Post date:** [October 15, 2025, 5:28pm UTC](https://discourse.julialang.org/t/limit-subtype-to-immediate-parent-in-method-signature/133177/1 "2025-10-15T17:28:24Z")

</div>

In [UnitTypes](https://github.com/mechanomy/UnitTypes.jl/tree/main) I need to restrict a method’s arguments to types that all subtype the same parent, though there are multiple families of parents and child types. Here’s an example:

```julia
abstract type AbstractMeasure end

abstract type AbstractLength <: AbstractMeasure end
struct MilliMeter <: AbstractLength
  value::Number
end
finb = 1/1000
struct Inch <: AbstractLength
  value::Number
end
finc = 0.0254

abstract type AbstractSound <: AbstractMeasure end
struct Growl <: AbstractSound
  value::Number
end
struct Roar <: AbstractSound
  value::Number
end

```

This yields the type tree:

```julia-auto
# AbstractMeasure
# AbstractLength
# MilliMeter
# Inch
# AbstractSound
# Growl
# Roar

```

It makes sense to convert children of AbstractLength or AbstractSound, but converting a length into a sound should error. Since users are free to add additional measures under AbstractMeasure, I want to write the convert() once and have it be correctly restricted to measures of Length or Sound or etc while disallowing cross-conversion.

This would be nice but it fails:

```julia
function connie(::Type{T}, y::U) where {T<:AbstractMeasure, U<:supertype(T)}
  return T(y.value * finc/finb)
end
@show connie(Growl, Inch(3.4))

```

Layering wheres should work,

```julia
function connie(::Type{T}, y::U) where {T<:N, U<:N} where N
  return T(y.value * finc/finb)
end
@show connie(Growl, Inch(3.4))

```

but \<: just reaches up to AbstractMeasure or Any instead of stopping at the immediate parent, AbstractLength.

```julia
code_warntype(connie)
# MethodInstance for connie(::Type{T}, ::U) where {N, T<:N, U<:N}
# from connie(::Type{T}, y::U) where {N, T<:N, U<:N} @ Main ..\typesDev.jl:29
# Static Parameters
# N <: Any
# T <: N
# U <: N 
# Arguments
# #self#::Core.Const(Main.connie)
# _::Type{T} where {N, T<:N}
# y::Any
# Body::Any
# 1 ─ %1 = $(Expr(:static_parameter, 2))::Type{T} where {N, T<:N}
# │ %2 = Main.:/::Core.Const(/)
# │ %3 = Main.:*::Core.Const(*)
# │ %4 = Base.getproperty(y, :value)::Any
# │ %5 = Main.finc::Any
# │ %6 = (%3)(%4, %5)::Any
# │ %7 = Main.finb::Any   
# │ %8 = (%2)(%6, %7)::Any
# │ %9 = (%1)(%8)::Any    
# └── return %9

```

Similarly introducing another layer

```julia
function connie(::Type{T}, y::U) where {T<:N, U<:N} where N<:O where O<:AbstractMeasure
  return T(y.value * finc/finb)
end
# MethodInstance for connie(::Type{T}, ::U) where {O<:AbstractMeasure, N<:O, T<:N, U<:N}
# from connie(::Type{T}, y::U) where {O<:AbstractMeasure, N<:O, T<:N, U<:N} @ Main w:\mechgits\dev\251013_unitTypesFaster\typesDev.jl:31
# Static Parameters
# O <: AbstractMeasure
# N <: O<:AbstractMeasure
# T <: N<:O<:AbstractMeasure
# U <: N<:O<:AbstractMeasure
# Arguments
# #self#::Core.Const(Main.connie)
# _::Type{T} where {O<:AbstractMeasure, N<:O, T<:N}
# y::AbstractMeasure
# Body::Any
# 1 ─ %1 = $(Expr(:static_parameter, 3))::Type{T} where {O<:AbstractMeasure, N<:O, T<:N}
# │ %2 = Main.:/::Core.Const(/)
# │ %3 = Main.:*::Core.Const(*)
# │ %4 = Base.getproperty(y, :value)::Any
# │ %5 = Main.finc::Any
# │ %6 = (%3)(%4, %5)::Any
# │ %7 = Main.finb::Any
# │ %8 = (%2)(%6, %7)::Any
# │ %9 = (%1)(%8)::Any
# └── return %9

```

makes N and O both AbstractMeasure while AbstractLength is again skipped over.

Is there any way to limit how far subtype reaches?

Thanks

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [October 15, 2025, 6:33pm UTC](https://discourse.julialang.org/t/limit-subtype-to-immediate-parent-in-method-signature/133177/2 "2025-10-15T18:33:53Z")

</div>

> [@bcon](#):
>
> ```julia-auto
> function connie(::Type{T}, y::U) where {T<:N, U<:N} where N
> return T(y.value * finc/finb)
> end
> @show connie(Growl, Inch(3.4))
> 
> ```
> 
> but \<: just reaches up to AbstractMeasure or Any instead of stopping at the immediate parent, AbstractLength.

If the idea is to just throw an error with cross-conversions, you could perhaps do somthing like this? Though, I’m not sure what’s going to happen in more complicated cases.

```julia-auto
function connie(::Type{T}, y::U) where {T<:AbstractMeasure, U<:AbstractMeasure}
  @assert typejoin(T,U) <: supertype(T) "Can't convert $U to $T"
  return T(y.value * finc/finb)
end
@show connie(Growl, Inch(3.4))

```

---

<div class="post-metadata">

**Author:** ![bcon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bcon/32/23495_2.png) [@bcon](https://discourse.julialang.org/u/bcon)\
**Post date:** [October 16, 2025, 11:28am UTC](https://discourse.julialang.org/t/limit-subtype-to-immediate-parent-in-method-signature/133177/3 "2025-10-16T11:28:51Z")

</div>

Thanks @sgaure I think I’ll have to do that.

Summarizing my attempts,

- macros fail because they evaluate at parsing, so they don’t have any greater knowledge of the function types than the author. This can be seen in

```julia
  macro emitFunctions(expr)
    display(dump(expr))
  end
  @emitFunctions connie(::Type{T}, y::U) where {T<:AbstractMeasure, U<:supertype(T) }
  connie(MilliMeter, Inch(3.4)) # == 3.4mB

```

where the `expr` tree is only made up of T and U, not the types as later invoked.

- @generated fails because it can only affect the method’s body, not its signature

```julia
  @generated function connie(::Type{T}, y::U) where {T<:AbstractLength, U<:AbstractLength}
    # T := DataType
    # y := Inch
    # U := DataType
    return :(T(y.value * finc/finb) )
  end
  connie(MilliMeter, Inch(3.4)) # == 3.4mB

```

that is nothing I do in the function body to build the quoted return will change the signature, and that’s the part that needs to be changed to correctly dispatch.
