I found this via a bug report in JuMP: Error vcat(::NonlinearExpr, ::VariableRef, ::Float64) · Issue #3671 · jump-dev/JuMP.jl · GitHub.
promote_type
has an implicit assumption of associativity, i.e.:
promote_type(A, promote_type(B, C)) == promote_type(promote_type(A, B), C)
That’s because the implementation is essentially promote_type(A, args...) = promote_type(A, promote_type(args...))
.
This causes issues:
julia> struct Foo; x::Int end
julia> struct Bar; x::Int end
julia> Base.convert(::Type{Foo}, b::Bar) = Foo(b.x)
julia> Base.convert(::Type{Bar}, x::Int) = Bar(x)
julia> Base.promote_rule(::Type{Foo}, ::Type{Bar}) = Foo
julia> Base.promote_rule(::Type{Bar}, ::Type{Int}) = Bar
julia> promote_type(Foo, Bar, Int)
Foo
julia> promote_type(Foo, Int, Bar)
Foo
julia> promote_type(Bar, Foo, Int)
Any
julia> promote_type(Bar, Int, Foo)
Any
julia> promote_type(Int, Foo, Bar)
Any
julia> promote_type(Int, Foo, Bar)
Any
julia> promote_type(Int, Bar, Foo)
Any
Depending on the order of arguments, the type might be Foo
or Any
.
If it is Foo
, then we are missing a direct convert method convert(::Type{Foo}, ::Int)
julia> [3, Foo(1), Bar(2)]
3-element Vector{Any}:
3
Foo(1)
Bar(2)
julia> [Foo(1), Bar(2), 3]
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type Foo
Closest candidates are:
convert(::Type{Foo}, ::Bar)
@ Main REPL[105]:1
convert(::Type{T}, ::T) where T
@ Base Base.jl:84
Foo(::Int64)
@ Main REPL[103]:1
...
Stacktrace:
[1] setindex!(A::Vector{Foo}, x::Int64, i1::Int64)
@ Base ./array.jl:1021
[2] (::Base.var"#114#115"{Vector{Foo}})(i::Int64, v::Int64)
@ Base ./array.jl:456
[3] afoldl(::Base.var"#114#115"{Vector{Foo}}, ::Int64, ::Foo, ::Bar, ::Int64)
@ Base ./operators.jl:546
[4] getindex(::Type{Foo}, ::Foo, ::Bar, ::Int64)
@ Base ./array.jl:455
[5] vect(::Foo, ::Vararg{Any})
@ Base ./array.jl:187
[6] top-level scope
@ REPL[115]:1
Questions:
- Am I a fool for not defining the necessary
promote_rule
andconvert
s needed for associativity? I didn’t really wantInt
to be promoted toFoo
! I wanted theAny
. (But I do wantInt
to be promoted toBar
when needed.)
or in other words:
- Is it expected behavior? I assume so.
- How can one ensure that any user-defined
promote_rule
s satisfy associativity? Just manual inspection + tests?