Repository navigation
Type constraint is not respected when using nested generic types B<A> where A is a property type of B #27427
Description
Activity
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.
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'
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 allR,R['type']is assignable to the constraintstring. (Because of the circular definition, it clearly is - aDocumentRef's type member must always be someTand thatTis constrained tostring). Unfortunately, when checking the instantiation ofPartialDocument, we instantiate the type parameters with the arguments provided - soRbecomes{type: number}, soR['type']is number.{type: number}trivially extends aDocumentRef<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.Looks like this got fixed sometime, not sure where or when
Reacted by Ryan Cavanaugh- locked as resolved and limited conversation to collaborators
on Oct 21, 2025
TypeScript Version: 3.2.0-dev.20180927
Code
Expected behavior:
Actual behavior: