Why can't this named tuple be converted to a supertype?

In the following two somewhat similar chunks of code the first generates an error, the other does not. Any ideas about why?

using DataFrames, Dates

NT1 = NamedTuple{(:DFPrecalc,:VPrecalc), Tuple{DataFrame,S}} where S

nt1 = (DFPrecalc = DataFrame(:A => [1]), VPrecalc = [Dates.today()])

cv1 = convert(NT1, nt1)
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] convert(#unused#::Type{NamedTuple{(:DFPrecalc, :VPrecalc), Tuple{DataFrame, S}} where S}, nt::NamedTuple{(:DFPrecalc, :VPrecalc), Tuple{DataFrame, Vector{Date}}})
   @ Base .\namedtuple.jl:151
 [2] top-level scope
   @ REPL[4]:1

NT2 = NamedTuple{(:A,:B), Tuple{Int,Vector{T}}} where T

nt2 = (A = 1, B = [1])

cv2 = convert(NT2, nt2)
(A = 1, B = [1])

After looking at the comments and making some mistakes I realized that the problem is that the parameter S is representing a Vector. So if the second example is modified to have the parameter S to represent the vector and not the eltype of the vector the code does not work, as in the first example. This is shown below.

NT3 = NamedTuple{(:A,:B), Tuple{Int,S}} where S

nt3 = (A = 1, B = [1])

cv3 = convert(NT3, nt3)
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] convert(#unused#::Type{NamedTuple{(:A, :B), Tuple{Int64, T}} where T}, nt::NamedTuple{(:A, :B), Tuple{Int64, Vector{Int64}}})
   @ Base .\namedtuple.jl:151
 [2] top-level scope
   @ REPL[32]:1

This looks like a bug to me. Smaller demonstration:

julia> convert(@NamedTuple{a::Vector{S}} where S, (; a=[1]))
(a = [1],)

julia> convert(@NamedTuple{a::S} where S, (; a=1))
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] convert(#unused#::Type{NamedTuple{(:a,), Tuple{S}} where S}, nt::NamedTuple{(:a,), Tuple{Int64}})
   @ Base ./namedtuple.jl:152
 [2] top-level scope
   @ REPL[71]:1

You might want to file an issue.

This works:

julia> convert(@NamedTuple{a::S where S}, (; a=1))
NamedTuple{(:a,), Tuple{Any}}((1,))

I think you just accidentally put the where S in the wrong “scope”. That is, it’s the inner Tuple that is parameterized by S, not the NamedTuple

I think both should work, and putting where on the outside is often required to reference the type parameter. Here’s an example where you have to put where S as in OP’s code:

julia> (a=[1], b=1) isa (@NamedTuple{a::Vector{S}, b::S} where S)
true

julia> (a=[1], b=1.0) isa (@NamedTuple{a::Vector{S}, b::S} where S)
false

Yes, you might be right.

Wait, I think I might be confused. As far as I understand, it’s the same as the following example:

julia> convert(
           NamedTuple{(:a,), Tuple{S}} where S,
           (;a=1)
       )
ERROR: UndefVarError: T not defined

However, it’s legitimately unspecified what it’s supposed to convert into. NamedTuple{(:a,), Tuple{S}} where S is not a concrete type.

The other example, namely this:

julia> convert(
           NamedTuple{(:a,), Tuple{Vector{S}}} where S,
           (;a=[1])
       )
(a = [1],)

Is somewhat different. Here, NamedTuple{(:a,), Tuple{Vector{S}}} where S is not a concrete type either, but the input type (a = [1],) is a subtype of the target unionall-type, so it’s obvious it can just return the input.

Edit: Ah, but in the first example, the input is also an instance of the type…

Indeed I’m not sure the original code makes sense, it’s like doing convert(Real, 1), which does nothing since Int is already Real. @Soldalma maybe you could clarify what the end goal is? Note that in Julia every value has a concrete type and you cannot “remove” it by converting to an abstract type.

But whatever the goal, it seems to me this has uncovered a bug.

1 Like

I poosted some new cases in the original post.

I filed an issue here: convert on named tuple can give "T not defined" error · Issue #47604 · JuliaLang/julia · GitHub

@Soldalma it’s not clear what you are trying to do. It doesn’t make sense to do convert(NT3, nt3) because nt3 is already an NT3: the value nt3 has concrete type NamedTuple{(:A, :B), Tuple{Int64, Vector{Int64}}} and this concrete type is already a subtype of NamedTuple{(:A,:B), Tuple{Int,S}} where S:

julia> nt3 = (A = 1, B = [1]);

julia> nt3 isa (NamedTuple{(:A,:B), Tuple{Int,S}} where S)
true

so there is nothing to convert. It seems you found a bug, but maybe this bug should not really affect you because you don’t need this convert call anyway.

Well, if there was nothing to convert my program would not crash. I spent many hours on this and finally fixed the problem switching from a named tuple to a struct.

1 Like

By crash you mean the ERROR: UndefVarError: T not defined?

Anyway there is really nothing to convert here:

NT3 = NamedTuple{(:A,:B), Tuple{Int,S}} where S
nt3 = (A = 1, B = [1])

julia> nt3 isa NT3
true

As you can see the nt3 value is already a NT3. Of course NT3 is not the concrete type of nt3 (that’s impossible, a value cannot have an abstract type like NT3 as concrete type) but it’s a supertype of the concrete type. Really nt3 is as much NT3 as possible, you cannot make it “more NT3”!

It’s like the value 1.0 is a Float64 (concrete type) but it’s also a Real (abstract supertype of Float64). You cannot make a value that is “more Real” than 1.0.

2 Likes