Skip to content

Type constraint is not respected when using nested generic types B<A> where A is a property type of B #27427

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

Closed
timargra opened this issue Sep 28, 2018 · 4 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@timargra
Copy link

timargra commented Sep 28, 2018

TypeScript Version: 3.2.0-dev.20180927

Code

export interface DocumentRef<T extends string> {
    id: number;
    type: T;
}

export type PartialDocument<R extends DocumentRef<R['type']> = R> = {
    [P in keyof R]?: R[P];
} & DocumentRef<R['type']>;

Expected behavior:

const test: PartialDocument = {id: 1, type: 1} // Error: Type 'number' is not assignable to type 'string'

Actual behavior:

const test: PartialDocument = {id: 1, type: 1} // No error, type of test.type is always 'unknown'
@ahejlsberg
Copy link
Member

Smaller repro:

type Test<T extends string = T> = { value: T }  // Why is this allowed?

let zz: Test = { foo: "abc" };  // Type is Test<{}> ?!?

Something is not right here. We're allowing a type parameter to be its own default value (I'm not sure what that even means), but we seem to completely ignore the constraint when the type parameter isn't specified.

@ahejlsberg ahejlsberg added the Bug A bug in TypeScript label Sep 28, 2018
@ahejlsberg ahejlsberg added this to the TypeScript 3.2 milestone Sep 28, 2018
@timargra
Copy link
Author

timargra commented Oct 1, 2018

The assignment also works without the default (I only left the default in my example above to show that TypeScript actually inferred the type 'unknown' instead of 'number'):

export interface DocumentRef<T extends string> {
    type: T;
}

export type PartialDocument<R extends DocumentRef<R['type']>> = {
    [P in keyof R]?: R[P];
}

const test: PartialDocument<{type: number}>; // Works, but should give error: type 'number' is not assignable to type 'string'

However, if you add the extends constraint of the DocumentRef generic as an intersection type to the property declaration bearing the generic type, it works as I would expect:

export interface DocumentRef<T extends string> {
    type: T & string; // <-- note the intersection type
}

export type PartialDocument<R extends DocumentRef<R['type']>> = {
    [P in keyof R]?: R[P];
}

const test: PartialDocument<{type: number}>; // Error: type 'number' is not assignable to type 'string'

@weswigham
Copy link
Member

@ahejlsberg your issue is different than @timargra 's, for sure. I have a fix ready for yours (its small - #28222), however the root cause' of @timargra 's is hard to fix. When we check R extends DocumentRef<R['type']>, we validate that for all R, R['type'] is assignable to the constraint string. (Because of the circular definition, it clearly is - a DocumentRef's type member must always be some T and that T is constrained to string). Unfortunately, when checking the instantiation of PartialDocument, we instantiate the type parameters with the arguments provided - so R becomes {type: number}, so R['type'] is number. {type: number} trivially extends a DocumentRef<number>. Nowehre do we actually check that it is actually safe to instantiate the type parameters with the arguments, because we're in the middle of performing that validation! The first thing we do is assume it's true, and check if the arguments hold if it is; but in doing so we miss that an inner constraint has been violated by the instantiation.

@jcalz
Copy link
Contributor

jcalz commented May 28, 2022

Looks like this got fixed sometime, not sure where or when

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests

6 participants