Repository navigation
[true, false] extends [false, false] sometimes it's true(when use infer,happen with a probability ), and sometime it's false(in most cases)??? #51090
Description
Activity
DanielRosenwasser commented
on Oct 7, 2022 MemberMore actionsIt's a bit of an aside, but in newer versions of TypeScript you can actually capture and constrain an
infertype parameter.export type Sub2<A extends number, B extends number> = [IsNegative<A>, IsNegative<B>] extends (infer R extends [false, false]) ? ['Why?', R] : 'Expected' export type Sub3<A extends number, B extends number> = [IsNegative<A>, IsNegative<B>][number] extends (infer R extends false) ? ['Why?', R] : 'Expected'
- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Oct 7, 2022 - addedBugA bug in TypeScriptA bug in TypeScript
on Oct 7, 2022 Daniel Rosenwasser (@DanielRosenwasser) Yes
infer X extends SomeTypeis a great feature , but I want to cache the result of this calculation into variable R so that I don't have to re-evaluate it later, just like variables in a programming languageDaniel Rosenwasser (@DanielRosenwasser) A bit of an aside, but I've always wondered why TypeScript can't capture the constraint of an infer type parameter on its own 😅 would you mind shedding some light on this one?
DanielRosenwasser commented
on Oct 7, 2022 MemberMore actionsI might be missing something. Let me try to answer both of these points
but I want to cache the result of this calculation into variable R so that I don't have to re-evaluate it later
I think that's what my example is doing - your first two examples had to do this by using two nested conditionals. Your second two examples seemed to drop the second tuple element - I assumed this was so that you could simplify with one conditional. I'm just mentioning that you can still capture the type with an
infer R extends.A bit of an aside, but I've always wondered why TypeScript can't capture the constraint of an infer type parameter on its own
Disclaimer: you might be more familiar with the checker than me at this point.
Technically we propagate the implicit constraint through an internal kind of type called "substitution types", which you can sort of think of as intersection (and in a sense, you can think of intersections as an alternative model of constraining type parameters). Substitution types would need some sort of new notation, or we would have to just replace them with intersection types.
Here's one reason I think we don't keep them around: If we captured these types, you'd start seeing intersections you didn't really write which could be an odd experience; plus, when you instantiate a type sufficiently for a conditional type to resolve, you don't need the substitution type anymore - so it's really better to substitute the type as it was written instead of writing an intersection which won't always be simplified away.
I think there's another element of keeping the design space open. Right now substitution types are mostly an implementation detail. The way that they're used is to validate that a type variable satisfies the requirements in how they're used (e.g. with
type Foo<T extends SomeType>,Uis sufficiently constrained to be used inFoo<U>). But I said "in a sense, you can think of intersections as an alternative model of constraining type parameters". This is something we toy with from time to time - but are we set on the intersection representation? I'm not entirely sure.Others might have more thoughts here, and I'd welcome corrections or other thoughts.
DanielRosenwasser commented
on Oct 7, 2022 MemberMore actionsI spoke with Anders Hejlsberg (@ahejlsberg) and I totally confused myself - I was thinking about substitution types from the left side of the
extends. Substitution types don't come into play withinfertype variables. Please disregard my comment. 😅Yes, we could possibly do something like that. It might be worth opening a separate issue for it.
Daniel Rosenwasser (@DanielRosenwasser)
I think that's what my example is doing
Here's what I want to do:(
[IsNegative<A>, IsNegative<B>]only need to calculate it once, and you can use it many times later)export type Sub<A extends number, B extends number> = [IsNegative<A>, IsNegative<B>] extends (infer R extends [boolean, boolean]) ? R extends [true, true] ? '--' : R extends [true, false] ? '-+' : R extends [false, true] ? '+-' : '++' : never; type T1 = Sub<-15, 90>; // '-+' work well export type Sub1<A extends number, B extends number> = [IsNegative<A>, IsNegative<B>] extends (infer R extends [boolean, boolean]) ? R extends [false, false] ? '++' : R extends [true, false] ? '-+' : R extends [false, true] ? '+-' : '--' : never; type T2 = Sub1<-15, 90>; // '++' Oh no!, expected to '-+'
Just switch the order of the judgment statements, and the result actually changes. Now I'm almost certain there's some kind of problem here.
This is working as intended. The issue is that the
StartsWith<S, SearchString>type doesn't appropriately account forSbeingstring. For example,StartsWith<string, '-'>producesfalse, which clearly isn't correct for all strings. Even though none of the examples passstringas a type argument, it happens during constraint evaluation when determining whether to resolve the conditional type or defer it.A better way to write the type is:
export type StartsWith<S extends string, SearchString extends string> = string extends S ? boolean : S extends `${SearchString}${string}` ? true : false;
This appropriately accounts for arbitrary strings by answering
boolean, i.e. could be eitherfalseortrue.Reacted by kawayiLinLin, Ryan Cavanaugh and Max- addedWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bugand removedBugA bug in TypeScriptA bug in TypeScriptNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Oct 8, 2022 Anders Hejlsberg (@ahejlsberg) Good explanation and even helped me find bugs in other places😉, thank you
Anders Hejlsberg (@ahejlsberg) Sorry to ask you for help again, a similar problem arises, but this time it looks even weirder!
type PositiveInfinity = 1e999; type NegativeInfinity = -1e999; export type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends (<G>() => G extends B ? 1 : 2) ? true : false; export type Add<A extends number, B extends number> = [ IsEqual<A, PositiveInfinity>, IsEqual<A, NegativeInfinity>, IsEqual<B, PositiveInfinity>, IsEqual<B, NegativeInfinity>, ] extends infer R extends [boolean, boolean, boolean, boolean] ? [true, false] extends ([R[0], R[3]]) // Note: with parentheses ? PositiveInfinity : 'failed' : never; export type AddWithoutParentheses<A extends number, B extends number> = [ IsEqual<A, PositiveInfinity>, IsEqual<A, NegativeInfinity>, IsEqual<B, PositiveInfinity>, IsEqual<B, NegativeInfinity>, ] extends infer R extends [boolean, boolean, boolean, boolean] ? [true, false] extends [R[0], R[3]] // Note: without parentheses ? PositiveInfinity : 'failed' : never; type AddTest0 = Add<PositiveInfinity, PositiveInfinity>; // failed type AddTest1 = AddWithoutParentheses<PositiveInfinity, PositiveInfinity>; // Infinity
I guess the parentheses make it a complex type?
and, Isn'tIsEqualimplemented correctly either? :(Thank God, I found it works well in this way(with
infer)!export type AddWithInfer<A extends number, B extends number> = [ IsEqual<A, PositiveInfinity>, IsEqual<A, NegativeInfinity>, IsEqual<B, PositiveInfinity>, IsEqual<B, NegativeInfinity>, ] extends infer R extends [boolean, boolean, boolean, boolean] ? [true, false] extends (infer _ extends [R[0], R[3]]) // Note: with infer ? PositiveInfinity : 'failed' : never; type AddTest2 = AddWithInfer<PositiveInfinity, PositiveInfinity>; // Infinity, works too!
- locked as resolved and limited conversation to collaborators
on Oct 22, 2025
Bug Report
🔎 Search Terms
extends when infer, lazy evaluation
🕗 Version & Regression Information
⏯ Playground Link
Playground link with relevant code
more detail can also see: stackOverflow
💻 Code
🙁 Actual behavior
type T1 = ["Why?", [true, false]]'🙂 Expected behavior
type T1 = 'Expected'(Same as T2)