Skip to content

T extends unknown is observably-different from an unconstrained T #61242

Closed as not planned

Description

@mkantor

🔎 Search Terms

"extends unknown" "type parameter" "generic" "conditional type" "keyof" "string" "assignable"

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about type parameter constraints

⏯ Playground Link

https://www.typescriptlang.org/play/?jsx=0#code/C4TwDgpgBAogHgQwLZgDYQDwBUB8UC8UWUEcwEAdgCYDOUA3gL5QD8UA1hCAPYBmRUAFxQawAE4BLCgHMAULIDG3CqKgBXCuwrcA7hQDCy0WIRTgBKNhJlKtdZu16cACjjD4yNJlwBKYcalpAjw4eSUVc21DCJMzC2wXN1hEFHQEvxFxQOCoOCA

💻 Code

type Example<T> = T extends {} ? keyof T : string

const unknownConstraint = <T extends unknown>(x: Example<T>): string => x

const noConstraint = <T>(x: Example<T>): string => x
//                                                 ^
// Type 'string | number | symbol' is not assignable to type 'string'.
//   Type 'number' is not assignable to type 'string'.

🙁 Actual behavior

The T extends unknown version behaves differently from the unconstrained version.

🙂 Expected behavior

The two versions should have the same behavior, probably both being rejected since code like this is unsafe:

const key = Symbol()
const bad = unknownConstraint<{ [key]: unknown }>(key)
bad.toUpperCase() // boom

Additional information about the issue

I was asked about this and eventually narrowed down the behavior to the example above.

#61203 is also possibly related?

Activity

  1. LukeAbby commented on Feb 21, 2025

    @LukeAbby
    Contributor

    My theory is that this is for backwards compatibility when symbol keys were being added.

    You can also get this behavior with any type whenever the apparent type of keyof T does NOT extend symbol | number.

    For example:

    const objectConstraint = <T extends { abc: number }>(x: Example<T>): string => x // Ok
    const numberConstraint = <T extends 123>(x: Example<T>): string => x // Ok
    //                                                            ^ `keyof 123` happens to be only strings so also okay.

    Notably this means that T extends {} actually errors. This furthers my hypothesis that the test is extends symbol | number ? error : do nothing because never technically extends symbol | number. To further this T extends {} | 1 doesn't error.

    This does still leave unknown as a second special case though.

  2. mkantor commented on Feb 21, 2025

    @mkantor
    ContributorAuthor

    #56652 seems possibly-relevant as well.

  3. RyanCavanaugh commented on Mar 24, 2025

    @RyanCavanaugh
    Member

    This is indeed a back-compat allowance; for a long time the default constraint was closer to {} than unknown (admittedly really neither). Hopefully we can eventually remove it but for now, unconstrained type parameters will sometimes appear to extend {}.

  4. typescript-bot commented on Mar 27, 2025

    @typescript-bot
    Contributor

    This issue has been marked as "Not a Defect" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  5. locked as resolved and limited conversation to collaborators on Oct 22, 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

    Not a DefectThis behavior is one of several equally-correct options

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions