Skip to content

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

Description

@timargra

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'

Activity

  1. ahejlsberg commented on Sep 28, 2018

    @ahejlsberg
    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.

  2. timargra commented on Oct 1, 2018

    @timargra
    Author

    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'
  3. weswigham commented on Oct 30, 2018

    @weswigham
    Member

    Anders Hejlsberg (@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.

  4. jcalz commented on May 28, 2022

    @jcalz
    Contributor

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

  5. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions