How to combine @nospecialize with type parameters?

It seems that type parameters in functions prevent @nospecialize from having an effect:

julia> f(@nospecialize x::T) where T = 0;

julia> f(1); f(1.0);

julia> m = only(methods(f)); m.specializations |> collect
7-element Vector{Any}:
 MethodInstance for f(::Int64)
 MethodInstance for f(::Float64)
 nothing
 nothing
 nothing
 nothing
 nothing

Is there a way to get around this? In the example above, one could write

f(@nospecialize x::T where T) = 0

or drop the T altogether. However, what about methods where the type parameter occurs in several arguments, as in

f(x::T, y::T) where T = 0

So far, method parameters are always specialized in the compiled method, even when just named as part of the parameter sequence instead of being used in the method body:

Function over-specialization on unused parts of types · Issue #26834 · JuliaLang/julia

…or specifying a shared value of annotation parameters during dispatch.

That evaluates to Any. The @nospecialize effect you’re going for does happen with a named parametric type like Vector{T} where T.

The method static parameter may be eliminated from that particular example like so:

f((x, y)::(Vararg{T, 2} where {T})) = 0

AFK, did not test the above. In principle it should work.

That’s a valid definition (interesting!). I’ve tried it out: when combined @nospecialize it specializes like a method with a type parameter. (Since @nospecialize doesn’t like the (x, y), it has to be put at the beginning of the function body.)

It doesn’t work, very unintuitively. I guess Vararg isn’t a normal type?

julia> k((x, y)::(Vararg{T, 2} where {T})) = 0
k (generic function with 1 method)

julia> methods(k)
# 1 method for generic function "k" from Main:
 [1] k(::Vararg{Any, 2})
     @ REPL[46]:1

julia> k(1, 2.0)
0

julia> Vararg{T, 2} where {T}
Vararg{Any, 2}

julia> Tuple{Vararg{T, 2} where {T}}
Tuple{Any, Any}

julia> NTuple{2, T} where {T}
Tuple{T, T} where T

I recently incurred into a similar issue, I needed a way to optionally specialize a function depending on a preference. I need to keep the type parameters around because the split is between when the package is loaded for trimming/compiled and when loaded in a dynamic environment. I could just rely on precompilation, but I am dealing with monster tuple types so the compiler overspecializes on every single type.
A (very hackish) solution I’ve landed on for now is having a @autospecialize x f(x::T) where T = 0 macro, that if you turn specialization on, it’s an identity, but if you turn it off it rewrites the function signature and removes all return types annotations and where parameters associated with the declared arguments.
To keep access to the type parameters it just moves them into the preamble for them to be a runtime value instead.

It’s not generic and you kind of have to write the function signatures taking into account the limitations

I really do wish there was a better system though for telling the compiler “this type parameter can be checked at runtime, don’t specialize on it”

I conclude that the answer is what @Benny said above:

and there seems to be no way around it.