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

Structural never type breaks conditional types #57210

Closed
rotu opened this issue Jan 29, 2024 · 8 comments
Closed

Structural never type breaks conditional types #57210

rotu opened this issue Jan 29, 2024 · 8 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@rotu
Copy link

rotu commented Jan 29, 2024

πŸ”Ž Search Terms

never, intersection, contradiction

πŸ•— Version & Regression Information

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

⏯ Playground Link

https://www.typescriptlang.org/dev/bug-workbench/?#code/PTAEBUAsFMGdtANwIYCcCWyAu6D2A7WUA0faRaVUNBAI1y0lFWgAddUtoATUWgT1Dp8XADaj08QgmREABmQqo5AOgCwAKBARI2AORFkoVqly1R0ALZ9oAY2QBXeKEbRBtaLoqhu6AGZ+lNAiooLC1KCwDrRYFqAA7sj8mlj8rAgAcriWwsiiGeSUoAC8pIWomtqgoAB6APwpaQgAylioDrZYDqh5BUoloADeAB4AXACMAL6gAGRDYwBMk5Vg1fWaK6AA6gjx6OI2oNDDrKIcwgDmLpCSCeiM13QO+zj4oABK0F2o+OBNoBdgpR0LYXE1Nql0h8vt1fk0ADzgI7DLj4bhEAAUKmxaAusFG1Hw-AAlCUAHyE-gU0pI46o9GgLE41B4gnIImk4oU4SBKjvUB1D6gNlEgDcGy0YE+3zhUNsBFgbWQwiIrgBQIwoNguFIBAAtPZxMhzAhIXAAehvEYnNA-A5REdUKYqJY4LBkIDNgABLCwPXHdKdf1OjiNKHvAAMA2lsL+6XhIwmkwp2koztAeqQlEEMFErDtogAhBLtApynIhKqWNgeJFA5hxO5BGr5Wj7nh8HkwXKFW0OlgITBQK3fDgCF3brBIBwsAb0KhbM8uLx2bwWwRRx2u+Q8g4a6qdWWlBWVVxkLxcH4jvcYFQ1X2EBxQH48s5aD18LZIOoNGaPuNoxhH442geFFEoFMwHAipJWqWoGg0TZaAcLA7gedkhEsdhYFgdATSEERKHgToOwAGk2cYVEebsEFbRUemEVDuFwc18AYUAAGsQQ4gjqEMSJkECC491QbhNgWKj1zbMdOwdW42NQqcZznBcl1rLAdT0aC9E2ABmSShxHdtxwdHdRD3LgD2oh8+A-L9iHwUIjgoN5GFwBwLiYVwWAMXVmGgWQSA04wWF5a9vNs9kv0HBAWBlGjKwI3kWBXeQHHwDi2PifA5FIyJoCsS4nI8ewbSELBfIwhxWHSKh6HSi9XKHP9WDQZBXS4GDtD-d4FgGdLMtwbLRWRQNlzKJQwwQXrAJlECEzGKZZnmUYljJTY1gQzZCjefwIji2EEtuAAqej+26PJQmOnw7FEGhuDy+5EvQC42JSvhm3+Hkgk-aAIX+d5dP6jKsvwEaAzscaACIAA0oamj4gdKGNgIRRMlrmRMlmWjEmRUXF8UpABtABdTkKVhqHiXW2DNqAA

πŸ’» Code

// These variations on never are both reported by intellisense as `never`.
// That's a problem because they behave differently in a subtle way
type NominalNever = never
//   ^?
type StructuralNever = {x:1} & {x:2}
//   ^?

// We will be exploring this with the builtin ReturnType generic type
// type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

// ReturnType constrains the generic so non-callable types give a useful error message
// @ts-expect-error
type R0 = ReturnType<{x:1}> // error - very helpful!

// `never` is treated specially by the conditional type construct
// the conditional is short-circuited and the conditional evaluates to `never` instead of either the true or false branch.
type R1 = ReturnType<never> // never
//   ^?

// but with an impossible intersection,
// 1. the type constraint does not kick in as a safeguard
// 2. the conditional is not short-circuited to 'never'
// 3. the conditional evaluates to the true branch only even though there's no reason to prefer either branch
// the return type is inferred as `unknown`, seemingly because it's an upper bound on the type parameter
// type R2 = unknown; expected never
type R2 = ReturnType<{x:1} & {x:2}>
//   ^?

// even if a return type is *structurally* declared, it is ignored by type inference
// type R3 = unknown; expected "X"
type R3 = ReturnType<{x:1} & {x:2} & ((...args: any[]) => "X")>
//   ^?

πŸ™ Actual behavior

When intersecting two object types with a conflicting value, the user-observable hinted type is never, but the type interacts with generic templates in a nonsensical way.

πŸ™‚ Expected behavior

I expect either (or both):

  1. conflicting intersections to treated the same as the nominal never type for constrained generics and conditional types. In particular:
    • The generic constraint would need to be (re)checked, so that an error is reported if the type constraint is refuted.
    • The conditional type will need to be (re)checked, so that the conditional is never
  2. conflicting intersections to be represented by their structural properties instead of an opaque "never" type.

Additional information about the issue

No response

@rotu
Copy link
Author

rotu commented Jan 29, 2024

Related to #53027, which observes that impossible intersections behave differently from never and attributes the change to #36696

Very similar in nature (possibly a re-appearance of the same bug?) to #42859 and looks like it should have been fixed by #42917.

Also, even though a the literal never types absorbs all non-any types under intersection, that is not the case for a never created by object intersection. You can "escape" the collapse to never with a never-typed property (though note an any-typed property does not do this for some some reason)

type R0 = ReturnType<{x:0} & {x:1} & {x:never} & (()=>"X")> // type R0 = "X"
//   ^?

type R1 = ReturnType<{x:0} & {x:1} & {x:any} & (()=>"X")> // type R1 = unknown
//   ^?

Workbench Repro

@fatcerberus
Copy link

You can "escape" the collapse to never with a never-typed property

This part, at least, seems to have been fully intentional per the linked #36696:

in at least one group of properties with the same name, some property has a literal type and no property has type never

@rotu
Copy link
Author

rotu commented Jan 29, 2024

This part, at least, seems to have been fully intentional per the linked #36696:

in at least one group of properties with the same name, some property has a literal type and no property has type never

Yeah, definitely intentional, but also strange. The rationale given was that if a property had disjoint types, the object type can't exist. But the never type is disjoint from all types by definition!

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Feb 1, 2024

It's not possible to statically detect all "structural nevers" -- the conflicting property can be arbitrarily deep and may involve unbounded computation. We can't remove this problem, only make it more difficult to understand by making it more inconsistent as to when it appears.

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Feb 1, 2024
@fatcerberus
Copy link

@RyanCavanaugh This issue is about intersections that are already statically reduced to never behaving differently from never, though.

@rotu
Copy link
Author

rotu commented Feb 1, 2024

It's not possible to statically detect all "structural nevers" -- the conflicting property can be arbitrarily deep and may involve unbounded computation. We can't remove this problem, only make it more difficult to understand by making it more inconsistent as to when it appears.

I'm not saying we should detect all structural nevers. {x:{y:number}} & {x:{y:string}} is not reduced to never and that's okay because, in type expressions, it behaves structurally. For example, ReturnType rejects it as having no call signature.

The case type R2 = ReturnType<{x:1} & {x:2}> above is especially troubling. Either it should be never because of the set-of-values interpretation or it should be an error under a structure-of-values interpretation. In neither case should it be the current result, unknown.

This issue is about intersections that are already statically reduced to never behaving differently from never, though.

πŸ‘

Previously type IsNever<X> = X extends never ? true : false had two possible outcomes: false or never. Now it has three false, never, or true.

@typescript-bot
Copy link
Collaborator

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

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Feb 4, 2024
@rotu
Copy link
Author

rotu commented Feb 4, 2024

@RyanCavanaugh please reopen. I believe you missed the thrust of this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants