I am trying to define a method with a parameter that has a generic type, using unknown as the generic type because I don't need it: function f(op: Operation<unknown>): void {...}.
It does not work in every case, it does not work if Operation uses its generic type in a method signature.
If instead of a method having the generic Context in parameter I use directly a generic Context member, it compiles without errors.
Can someone explain why I can't use unknown if the generic is in the signature of a method?
I am trying to figure out why this sample does not compile:
export interface Operation<Context> {
process: (context: Context) => void;
//context: Context;
n:number;
}
type MyContext = {
info: string;
}
const op : Operation<MyContext> = {
process: (context: MyContext) => { console.log("process",context.info); },
//context: { info:"context.info" },
n:42
}
function fGeneric<Context>(op: Operation<Context>): void {
console.log("fGeneric", op.n);
}
console.log(fGeneric(op));
function fUnknown(op: Operation<unknown>): void {
console.log("fUnknown", op.n);
}
console.log(fUnknown(op));
// Argument of type 'Operation<MyContext>' is not assignable to parameter of type 'Operation<unknown>'.
// Type 'unknown' is not assignable to type 'MyContext'.
Commenting out process and uncommenting context compiles without error.
(Obviously this is a simplified example, boiled down to the minimum to exhibit the problem.)
Use the bottom type instead of the top type.
unknownis the top type: it contains all possible values. A function taking an argument of typeunknownshould accept any value whatsoever as the argument. A function that only accepts values of some types cannot possibly conform to(_: unknown) => void; however, a function(_: unknown) => voidwill conform to(_: T) => voidfor anyT. So we have thatTis a subtype ofunknown, but on the other hand(_: unknown) => voidis a subtype of(_: T) => void. This situation is known as contravariance, and we say that the type constructor that takesTto a type of functions-that-take-a-Tis contravariant inT.In your case, since the definition of
Operation<T>specifies a property with a type of functions takingT,Operation<T>is contravariant inTas well. That meansOperation<unknown>is a subtype ofOperation<T>for any otherT, not a supertype as you want. (When you changedOperation<T>to include a property of type justT, it instead became covariant inT, which means the subtype relationship is not reversed.)Instead, you can use
Operation<never>:The bottom type
nevercontains no values and is a subtype of all types. BecauseOperation<T>is contravariant inT, this means thatOperation<never>is a supertype of allOperation<T>. In plainer language, by taking anOperation<never>, you don’t requireprocessto be callable with anything in particular, and in effect you promise you will never callprocessat all: becausenevercontains no values, a function taking aneverargument is uncallable.