Skip to content

Generic constraints are "shallow", don't account for depth subtyping #62514

@sliminality

Description

@sliminality

🔎 Search Terms

ts1360 ts2322 generic fields , generic bound subtyping, generic constraint subtyping, could be instantiated with a different subtype of constraint, depth subtyping generics, parametric polymorphism depth subtyping

🕗 Version & Regression Information

⏯ Playground Link

Playground link

💻 Code

Errors, as intended:

function safe<T extends string>( name: string, // Unrelated to T. ): T { return name // 'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string'. (2322) 👍 }

Does not error, unexpectedly:

function unsafe<T extends { value: string }>( name: string, ): T["value"] { return name // No error. Why is this allowed? } // Example uncaught error: unsafe<{ value: "cat" } | { value: "dog" }>( "fish" ) satisfies "cat" | "dog"

🙁 Actual behavior

When T extends string, TypeScript correctly identifies that an arbitrary other string cannot be safely assigned to T, because T could be instantiated with any subtype of string.

When T extends { value: string }, TypeScript incorrectly allows an arbitrary other string to be assigned to T["value"], even though T could be instantiated with any subtype of { value: string }, and the depth subtyping rule for objects implies that the type of T["value"] is also a subtype of string.

🙂 Expected behavior

  • Both examples should fail with the same error. It should not be possible to return name in the function unsafe.

    More generally, TypeScript should be consistent in applying subtyping rules to generic constraints T extends U. If U is a primitive (like string or "A" | "B") and T is checked as if it could be instantiated with any subtype of U, then the same rule should apply when U is an object type.

  • If this is working as intended, the generics documentation should be updated to clarify that generic bounds are "shallow," and the correct approach is to use a second generic parameter.

    Ideally this disclaimer could go under both the section on Generic Constraints (stipulating that only the top level parameter is treated as generic), as well as Using Type Parameters in Generic Constraints (to the effect of "note that this is the only way to handle this pattern").

Additional information about the issue

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    DocsThe issue relates to how you learn TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions