Not sure if this is a bug, but here is a MWE:
Keys(::Type{NamedTuple{names, data}}) where {names, data} = names
@generated function foo(x)
Core.println(Keys(x))
nothing
end
foo(NamedTuple{(:a, :b)})
#+RESULTS:
MethodError: no method matching Keys(::Type{Type{NamedTuple{(:a, :b)}}})
Anyone know what might be happening here? Should I open an issue?
I find it very bizarre that Type{Type{NamedTuple{(:a, :b)}}} got constructed, that seems like a bug.
versioninfo()
#+RESULTS:
: Julia Version 1.7.1
: Commit ac5cc99908* (2021-12-22 19:35 UTC)
: Platform Info:
: OS: Linux (x86_64-pc-linux-gnu)
: CPU: AMD Ryzen 5 2600 Six-Core Processor
: WORD_SIZE: 64
: LIBM: libopenlibm
: LLVM: libLLVM-12.0.1 (ORCJIT, znver1)
: Environment:
: JULIA_NUM_THREADS = 6
That seems like the correct behavior, right?
If you call foo(a), then "inside" the generated function, x = typeof(a), right?
You called foo(a) where a = NamedTuple, and thus typeof(a) = Type{NamedTuple}.
So inside the generated function, we have x = typeof(a) = Type{NamedTuple}, right?
So, now, inside the generated function, you call Keys(x).
If x = Type{NamedTuple}, and you call Keys(x), then what should the type signature of that Keys method be?
It should be Keys(::Type{Type{NamedTuple}}) = ..., right?
I guess I've just never experienced that before when passing types to generated functions. I thought that Type just existed so that we didn't get DataType, so I thought we didn't double wrap things in Type
Well, we need to be able to distinguish between e.g. foo(NamedTuple()) and foo(NamedTuple), right?
That's not the same thing
It'd be confusing if a @generated function changes its behavior when the argument is a type or not. So, I'd say the current behavior of @generated is correct and "ergonomic."
We generally don't want to distinguish between Type{T} and T when T is a type.
If you know the argument is a type, you can use @generated foo(::T) where {T} .... Then T is the same for generator code and the runtime code.
Huh. Today I learned that Core.Typeof(Type{T}) gives Type{Type{T}}. For some reason I thought I had remembered that we didn't do this
Takafumi Arakaki (tkf) said:
If you know the argument is a type, you can use
@generated foo(::T) where {T} .... ThenTis the same for generator code and the runtime code.
Yeah, that's what I normally do, but the use case where I ran into this, I had a variable number of heterogeneous arguments so that wasn't an option.
It is important to note that T and Type{T} are different.
julia> 1 isa Int
true
julia> 1 isa Type{Int}
false
Type{T} is a type of T
julia> Int isa Type{Int}
true
julia> Int isa DataType
true
julia> Type{Int} <: DataType
true
Yeah, makes sense, thanks for the clarification
Type{T} is useful because it is a singleton representation of "type of T"
I guess I'll need to splat the arguments, then have an inner method that I pass the arguments to as a tuple.
Ahh... yes, I remember that vararg is a bit weird inside @generated. I'd probably define a function to unwrap the Type
julia> @generated f(xs...) = (global XS = xs; nothing);
julia> f(Int, Nothing)
julia> XS
(Type{Int64}, Type{Nothing})
julia> instanceof(::Type{Type{T}}) where {T} = T;
julia> map(instanceof, XS)
(Int64, Nothing)
Yeah, good idea. I'll just build that into Keys.
I was fiddling again today with something @Brian Chen asked about yesterday on Slack, and the part I left as an 'exercise to the reader' was a bit more frustrating than I had anticipated, so here it is in case you want it Brian. A vararg complement to Base.structdiff that isn't slow at runtime:
Keys(::Type{<:NamedTuple{names}}) where {names} = names
Keys(::Type{Type{T}}) where {T} = Keys(T)
@generated function structintersect(a::NamedTuple{an}, rest::Union{NamedTuple, Type{<:NamedTuple}}...) where {an}
names = Tuple(intersect(an, Keys.(rest)...))
data = Expr(:tuple, (:(getproperty(a, $(QuoteNode(name)))) for name in names)...)
:(NamedTuple{$names}($data))
end
structintersect((;a=1, b=2, c=3, d=4), (;b=1, c=3, d=4), NamedTuple{(:b, :d)})
#+RESULTS:
: (b = 2, d = 4)
Mason Protter has marked this topic as resolved.
Think I should bother trying to open a PR to Base with this? I suspect Base.structdiff only exists because they have a need to use it internally
It would have been nice if we had a uniform dict/set API so that we could motivate this a bit better as a prerequisite of intersect-on-NamedTuple
Not that I'm against it or anything. I just don't know if it's OK or not
That’s my feeling also.
Last updated: Nov 18 2025 at 04:43 UTC