Type parameters & constructors

The {T} in ComplexNumber{T}(args...) is not part of the method signature, it’s part of the function name. At the language level, ComplexNumber(...) and ComplexNumber{T}(...) are two independent functions that just so happen to share a part of their names. This little demo might help appreciate this:

julia> struct Foo{T}
           x::T
           
           # Explicit inner constructor to eliminate the default outer constructor
           Foo{T}(x) where {T} = new(x)
       end

julia> methods(Foo{Int})  # This is the inner constructor we defined above 
# 1 method for type constructor:
 [1] Foo{T}(x) where T
     @ REPL[2]:5

julia> methods(Foo)  # Doesn't list the inner constructor because this is a different function
# 0 methods for type constructor

What is happening in your example is that this constructor

ComplexNumber(x::T, y::T) where {T<:Real} = ComplexNumber(promote(x, y)...)

dispatches to ComplexNumber, and the only method of this function is the ComplexNumber(x::T, y::T) where {T<:Real} which we started from; hence you get infinite recursion.

By contrast, this construct

ComplexNumber(x::T, y::T) where {T<:Real} = ComplexNumber{T}(promote(x, y)...)

dispatches to ComplexNumber{T} and therefore ends up calling the implicit inner constructor.

4 Likes