Skip to content
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

Constraint in generic type forgotten in assignability check #58370

Closed
ehoogeveen-medweb opened this issue Apr 30, 2024 · 2 comments Β· Fixed by #58403
Closed

Constraint in generic type forgotten in assignability check #58370

ehoogeveen-medweb opened this issue Apr 30, 2024 · 2 comments Β· Fixed by #58403
Assignees
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue

Comments

@ehoogeveen-medweb
Copy link

ehoogeveen-medweb commented Apr 30, 2024

πŸ”Ž Search Terms

generic type constraint not applied inside type

πŸ•— Version & Regression Information

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.4.5#code/C4TwDgpgBAggdiA0hEUC8U4FcC2AjCAJygB8oBnYQgSzgHNSKR8B7AGwG4AoL0SKAEoRgWQnAAq4CMhAsAZgB4A8ngBWUCAA9gEOABNyUFmogBjYAD50UFeq079h46rPAuUD1AD8UANoBrFHkbNQBdDW1dAz84CAA3IlD3TxSfWITCZJSPAC4oAG8-GShaKEDZORD1AENDShp6UIBaLzyACgBKdCtigF9feto6JJS89KJuXikoGQBJOQBlajo4apFCCCU5W1cFLKqIh2jnVwAafeL7KMN4JBRzlKF1iSkZQwwn0RfIGXllNQs5ysH2EX0kPxQhiujignW6MxQ3lhbThaB6KC60OinzE4OkkKRxTG8SIXWJGUmWjALEIwCgfGgQj0WFMED0AEZ-nZIjCTuZTgjUFibggZAKAGrVNhYCAC2xvQ7XMpBSq2azlYK2YEXRHCwXzJYrNaiTbbEzmLkCmQWfapQWKmHygkAemdsDYAHdqiBDFQZQA6W3ZDw+Qq+RAlOCC0J5SXS6C9IPB8lEJNQFOESauqAAdRp-kM1EqphYcHq1VodOohg2TVMAAszIE9JHyNQ9NAGYGqTS6QzBGyWWyAExch3Hc3AK26nnRW5iqBxmVytQKvUa1VqdUqqralKXWciu4gNM+A9HQxzRbLVbrU07C22acgG3B4NnmcXqoK7MwT3e31CADNM31DIpI2jWMpRlKBEzfeCMxAzxEIQzASUzLggA

πŸ’» Code

type AnyKey = number | string | symbol;

type ReturnTypeKeyof<Obj extends object> = Obj extends object
    ? [keyof Obj] extends [never]
        ? never
        : { [Key in keyof Obj as string]-?: () => Key }[string]
    : never;

type KeyIfSignatureOfObject<
    Obj extends object,
    Key extends AnyKey,
    ReturnTypeKeys = ReturnTypeKeyof<Obj>,
> = ReturnTypeKeys extends () => Key ? ((() => Key) extends ReturnTypeKeys ? Key : never) : never;

export type Reduced<Obj extends object, Key extends AnyKey, Value, ObjKeys extends keyof Obj = keyof Obj> =
    Key extends KeyIfSignatureOfObject<Obj, Key>
        ? Key extends ObjKeys // Always true.
            ? { [K in Key]: Value }
            : never
        : never;

πŸ™ Actual behavior

Gives error Type 'ObjKeys & KeyIfSignatureOfObject<Obj, Key, ReturnTypeKeyof<Obj>> & Key' is not assignable to type 'string | number | symbol'.

πŸ™‚ Expected behavior

No error; Key was constrained to AnyKey and should be usable as a key.

Additional information about the issue

I reduced this from a much larger example, so the code here doesn't make much sense (the names of the helper types might also not make sense anymore). It might be possible to reduce it further but I wasn't able to find anything obvious.

As the playground shows, simply wrapping the body of the type with a Key extends AnyKey check makes the error go away, which makes me think that this must be a bug (I guess it distributes the union, but the entire union should be valid as a key type). Checking Key extends ObjKeys before KeyIfSignatureOfObject also makes the error go away.

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label May 1, 2024
@ahejlsberg
Copy link
Member

This one is quite subtle. We have logic in relationship checking that hoists union constraints of generic types in an intersection into a top-level union of intersections such that it can be properly related to another union. Because of the divide-and-conquer change, we break an intersection of four constituents into two intersections of two constituents. In the repro, one of those divided intersections so happens to qualify for constraint reduction (e.g. where T & string becomes just T because T extends string), and that in turn defeats the hoisting logic.

The fix is to not perform constraint reduction in intersections created for purposes of hoisting constraints. I'll put up a PR.

@ahejlsberg
Copy link
Member

Meanwhile, here's a simplified repro:

type Test1<K1 extends keyof any, K2 extends keyof any> =
    MustBeKey<Extract<K1, keyof any> & K1 & K2>;  // No error

type Test2<K1 extends keyof any, K2 extends keyof any> =
    MustBeKey<K1 & K2 & Extract<K1, keyof any>>;  // Error, wat?

type MustBeKey<K extends keyof any> = K;

@ahejlsberg ahejlsberg added Bug A bug in TypeScript and removed Needs Investigation This issue needs a team member to investigate its status. labels May 2, 2024
@ahejlsberg ahejlsberg added this to the TypeScript 5.5.1 milestone May 2, 2024
@typescript-bot typescript-bot added Fix Available A PR has been opened for this issue labels May 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Fix Available A PR has been opened for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants