Contravariance broke in 3.8 when combining spreading parameter list with conditional type
See original GitHub issueTypeScript Version: 3.8.3
Search Terms: variance contravariance
Description: An Injector in typed-inject is a dependency injection container that is type safe. It can only provide values for tokens it knows. A small example:
declare const fooInjector: Injector<{foo: string}>; // this injector can provide string values for the token 'foo'
const foo = fooInjector.resolve('foo');
// => typeof foo === 'string'
fooInjector.resolve('bar');
// => Error ts(2345) Argument of type '"bar"' is not assignable to parameter of type '"foo"'
It makes sense that an injector Injector<{}>
is not assignable to Injector<{foo: string}>
, since it cannot provide a value for token 'foo'
. This was the case in TS 3.7. However, since TS 3.8, Injector<{}>
is assignable to Injector<{foo: string}>
😪.
declare const rootInjector: Injector<{}>;
const fooInjector: Injector<{ foo: string}> = rootInjector;
Expected behavior: Type 'Injector<{}>' is not assignable to type 'Injector<{ foo: string; }>'.
Actual behavior: No error
Related Issues: Couldn’t find any 🤷♂️
Code
I think I’ve trimmed it down to the essentials.
interface Injector<TContext> {
injectFunction<Tokens extends (keyof TContext)[]>(todo:
(...args: { [K in keyof Tokens]: Tokens[K] extends keyof TContext ? TContext[Tokens[K]] : never; }) => void): void;
}
declare const rootInjector: Injector<{}>;
const fooInjector: Injector<{ foo: string}> = rootInjector;
Output
(none)
Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": 2,
"target": "ES2017",
"jsx": "React",
"module": "ESNext"
}
}
Playground Link: Provided
Simpler contravariant examples like this still work as expected.
type Action<T> = (arg: T) => void;
declare let b: Action<{}>;
declare let a: Action<{foo: number}>;
b = a
// => Error! Type 'Action<{ foo: number; }>' is not assignable to type 'Action<{}>'.
Issue Analytics
- State:
- Created 4 years ago
- Reactions:4
- Comments:11 (5 by maintainers)
Top GitHub Comments
I closed #31029 because it had been sitting stale for a year after we decided in the design meeting that it needed some more design work. It’s still worthwhile to follow up on the design meeting recommendations and see if they work out.
Based on my understanding on the workings of the problem, yes.