No automatic conversion with abstract type

question

#1

I have the following minimal example:

abstract type Gate{N} end;

struct IdentGate{N} <: Gate{N}
    wire::UInt8

    function IdentGate{N}(w::Int) where {N}
        @assert 1 <= w <= N
        new(w)
    end
end

GateProduct{N} = Array{Gate{N}, 1}
convert(::Type{GateProduct{N}}, g::Gate{N}) where {N} = GateProduct{N}([g])

Explicit conversion of IdentGate to GateProduct works.
However, it is never called automatically.

In particular,

a = Array{GateProduct{2}, 1}()
push!(a, IdentGate{2}(1))

Does not work. Is this expected behaviour?
As per documentation, convert should be called to construct GateProduct{2} from IdentGate{2}
which is a Gate{2} instance.


#2

I verified that the snippet I posted does indeed work when compiled separately.
What could render it broken?

The code is part of larger codebase which is included from REPL and it throws exception when part of it.


#3

You probably just forgot to import Base: convert.


#4

No, in fact import Base.convert was included more than once.

I have narrowed down the issue to one file which imported Base.convert being included more than once.
Once includes are made unique, the issue goes away.
Does this sound like a bug?


#5

Ok, the example in your top post works with an import Base: convert so would be good if you could supply an example that duplicates the broken behavior.


#6

Ok, I got a minimal repo: https://github.com/mikolajpp/inhibited-convert

The issue is following. For some reason in my codebase I ended up with one convert definition
which was not preceded with import Base.convert. Then all subsequent Base.convert import were inhibited until it hit Gate conversion.

I don’t know if this is technically bug, because I saw a warning in the console, so there is that :slight_smile:


#7

You should be able to work around this by explicitly defining Base.convert(::Type{GateProduct[N}}, g::Gate{N}) where {N} = ... - that way the definition of the conversion method doesn’t depend on convert not being overwritten before importing Base.convert.

Working
julia> struct Dummy end

julia> convert(::Type{Dummy}, x::Int) = 2
convert (generic function with 1 method)

julia> abstract type Gate{N} end;

julia> struct IdentGate{N} <: Gate{N}
          wire::UInt8

          function IdentGate{N}(w::Int) where {N}
                  @assert 1 <= w <= N
                   new(w)
          end
       end

julia> GateProduct{N} = Array{Gate{N}, 1}
Array{Gate{N},1} where N

julia> Base.convert(::Type{GateProduct{N}}, g::Gate{N}) where {N} = GateProduct{N}([g])

julia> using Test

julia> @testset "Double include overloading bug" begin
               a = Array{GateProduct{2}, 1}()
               push!(a, IdentGate{2}(1))

               @test true == true
           end
Test Summary:                  | Pass  Total
Double include overloading bug |    1      1
Test.DefaultTestSet("Double include overloading bug", Any[], 1, false)
Broken
julia> struct Dummy end

julia> convert(::Type{Dummy}, x::Int) = 2
convert (generic function with 1 method)

julia> import Base.convert
WARNING: import of Base.convert into Main conflicts with an existing identifier; ignored.

julia> abstract type Gate{N} end;

julia> struct IdentGate{N} <: Gate{N}
           wire::UInt8

           function IdentGate{N}(w::Int) where {N}
                 @assert 1 <= w <= N
                 new(w)
           end
        end

julia> GateProduct{N} = Array{Gate{N}, 1}
Array{Gate{N},1} where N

julia> convert(::Type{GateProduct{N}}, g::Gate{N}) where {N} = GateProduct{N}([g])
convert (generic function with 2 methods)

julia> using Test

julia> @testset "Double include overloading bug" begin

               a = Array{GateProduct{2}, 1}()
               push!(a, IdentGate{2}(1))

               @test true == true
         end
Double include overloading bug: Error During Test at REPL[9]:1
  Got exception outside of a @test
  MethodError: Cannot `convert` an object of type IdentGate{2} to an object of type Array{Gate{2},1}
  Closest candidates are:
    convert(::Type{T<:Array}, !Matched::AbstractArray) where T<:Array at array.jl:489
    convert(::Type{T<:AbstractArray}, !Matched::T<:AbstractArray) where T<:AbstractArray at abstractarray.jl:14

Note how the REPL already mentions the problem:

julia> struct Dummy end

julia> convert(::Type{Dummy}, x::Int) = 2
convert (generic function with 1 method)

julia> import Base.convert
WARNING: import of Base.convert into Main conflicts with an existing identifier; ignored.

I suspect this is intended behaviour, because the imported identfier really should not overwrite existing things.