-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Mapped types does not work with generic type arguments extending an object #35647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This is a correct error, because the code makes a promise it's not keeping - specifically that for any // Is correctly identified as an error:
function genericMethodWithExtends<T extends Schema>(): Partial<T> {
type PartialSchema2 = Partial<T>;
const b1: PartialSchema2 = { _id: 'sd' };
return b1;
}
type MySchema = { id?: "foo" }
const k = genericMethodWithExtends<MySchema>();
// The only legal inhabitants of 'v' are "foo" and "undefined", but
// v has value "sd" instead.
const v = k.id; |
Well I mean... maybe the barkeep is just cutting you off because you've already ordered too many drinks that night? |
This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes. |
I understand the classic OOP argument: class Cat extends Animal {}
// this is "correct": as Cat is bigger than Animal so we won't see a wrong property access
const a: Animal = new Cat();
// but this is "wrong": as you may access a property on Cat that does not exist on Animal
const c: Cat = new Animal(); You are right, I did not consider So in a language without I still have some unanswered questions: 1) I still don't understand why the following throws? function genericMethodWithExtends<T extends 'sd'>(): T {
return 'sd';
} We've reached the root of type hierarchy here, nobody can extend 'sd' that causes this to break. 2) considering string literal types, does TS infer incorrect types here? const a = { p1: 12, p2: 'sdsdf' }
// type of a is: { p1: number, p2: string }
// it should be: { p1: 12, p2: 'sdsdf' } 3) Why const myFunction = <T extends { _id: string }>(): Promise<void> => {
type AType = keyof T extends '_id' ? string : number;
// this throws error
let a: AType = 'apples';
// this also throws error
let a: AType = 4;
} |
function genericMethodWithExtends<T extends 'sd'>(): T {
return 'sd';
} There's const a = { p1: 12, p2: 'sdsdf' } as const const myFunction = <T extends { _id: string }>(): Promise<void> => {
type AType = keyof T extends '_id' ? string : number;
// this throws error
let a: AType = 'apples';
// this also throws error
let a: AType = 4;
} Evaluating generic conditional types is deferred because it doesn't know what
|
Thank you @AnyhowStep.
|
It's a "const assertion" http://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions
If
It is possible for the conditional type to evaluate to the |
I totally follow the original question and answer (all Beers are Beverages, all Beverages are not Beers), but not @RyanCavanaugh's answer (why is there interface Schema {
id: string;
}
function genericMethodWithExtends<T extends Schema>(): void {
type PartialOfT = Partial<T>;
type PartialOfSchema = Partial<Schema>;
const b1: PartialOfSchema = { id: 'sd' }; // <- This is valid.
const b2: PartialOfT = { id: 'sd' }; // <- This is not. Why?
}
type MySchema = { name: string, id: string }
genericMethodWithExtends<MySchema>(); Should the assignment to a To continue the glass/beer analogy: The waiter gave me my beer in a glass and I promise to return the glass and/or the beer. And for reference, the types created above show up in intellisense like so: type PartialOfT = { [P in keyof T]?: T[P] | undefined; }
type PartialOfSchema = { id?: string | undefined; } |
Consider type PartialOfT = { id? : "lol"|undefined };
const b2: PartialOfT = { id : "sd" }; //Makes sense this is an error |
@jimbuck as @AnyhowStep said the problem with your code again is that you should also consider that |
Ahh, the thing that got me was the fact that when you extend an interface/type you can "override" a interface Schema {
id: string;
}
function genericMethodWithExtends<T extends Schema>(): void {
type PartialOfT = Partial<T>;
type PartialOfSchema = Partial<Schema>;
const b1: PartialOfSchema = { id: 'sd' }; // <- This is valid.
const b2: PartialOfT = { id: 'sd' }; // <- For T this is supposed to be 'lol', hence the error.
}
interface MySchema extends Schema {
id: 'lol' // This can be "overriden" since 'lol' is a string (no rules broken)
}
genericMethodWithExtends<MySchema>(); I know that |
It's not even really about interface subtyping. type Even though |
Fixes inversify#170 At the moment it's impossible to create generic helper functions to deal with `TypedContainer`: ```ts interface MyMap { foo: string; } function fn<T extends MyMap>(container: TypedContainer<T>) { container.get('foo') // error } ``` This is because of the `Synchronous` type that guards against calling `.get()` on `Promise` bindings. Under the hood, this type is a mapped type, which [doesn't work well with generics][1] (by design). Rather than drop this guard all together, this change aims to strike a balance by removing the `Synchronous` mapped type, and instead changing the return type of synchronous `get()` methods to be `never` if the binding is a `Promise`. This won't error as obviously or as immediately as before, but will still at least flag to the developer semantically that this binding will never return a value (since it will throw), and should cause compilation errors if consumers try to do anything with the returned value. In return, we gain the ability to use generic helper functions. [1]: microsoft/TypeScript#35647
Fixes inversify#170 At the moment it's impossible to create generic helper functions to deal with `TypedContainer`: ```ts interface MyMap { foo: string; } function fn<T extends MyMap>(container: TypedContainer<T>) { container.get('foo') // error } ``` This is because of the `Synchronous` type that guards against calling `.get()` on `Promise` bindings. Under the hood, this type is a mapped type, which [doesn't work well with generics][1] (by design). Rather than drop this guard all together, this change aims to strike a balance by removing the `Synchronous` mapped type, and instead changing the return type of synchronous `get()` methods to be `never` if the binding is a `Promise`. This won't error as obviously or as immediately as before, but will still at least flag to the developer semantically that this binding will never return a value (since it will throw), and should cause compilation errors if consumers try to do anything with the returned value. In return, we gain the ability to use generic helper functions. [1]: microsoft/TypeScript#35647
TypeScript Version: 3.7.x-dev.201xxxxx
Search Terms: mapped types generics bug
Code
I can confirm that this does not work:
And this does work:
Expected behavior:
IMO TypeScript should assume generic parameter above is something like this:
and does not throw errors.
Playground Link: example link
Related Issues: I'm one of the maintainers of the
@types/mongodb
in definitely typed. This is the original issue DefinitelyTyped/DefinitelyTyped#39358The text was updated successfully, but these errors were encountered: