Consider type Foo as:
type Foo = {
name: string,
bar: {
buzz: string,
buzzy: number,
buzzAldrin: boolean
},
active: boolean,
}
I want to define a type which represents a "part" of Foo. For example:
{name: string}{name: string, active: boolean}{bar: { buzz: string } }
Another way to see it is, a type that’s Foo‘s parent type when viewed as a contravariant parameter. So, naming that type CFoo, it would satisfy:
((arg: CFoo) => any) extends ((arg: Foo) => any)
I can represent that relationship with a type that takes both Foo and CFoo and performs the assertion above:
type Contra<T, G> = ((a: G) => void) extends ((a: T) => void) ? G : never;
type Foo = {
shape: string,
name: 'circle',
b: {
d: string
}
}
function test<T, G>(a: T, b: G extends Contra<T, G> ? G : never) {};
let a = {} as Foo;
test(a, {b: {d: 'asd'}}); // No error
test(a, {shape: 'test'}) // No error
test(a, {shape: 123}) // Error
But, what I really want to do is define a generic type that, when passed Foo produces a general contravariant parent type that I can use more succinctly.
type Contra<T> = // type definition
function test<T>(a: T, b: Contra<T>) {} // Should accept parameters just like the example above.
>Solution :
I think you over-engineered it:
function test<T, G extends T extends G ? unknown : T>(a: T, b: G) {};
You are also allowed to return something other than never. You get a nicer error message by returning T, and is not going to make the type invariant because it’s only evaluated when the first comparison fails.
Now, this is also an option:
function test<T extends G, G>(a: T, b: G) {};
Of course the error does not pop up in the same spot, but it does not always matter.