Skip to content

Wrong branch of non-distributing conditional type inference near recursion limit for complex nested type #42223

@MMF2

Description

@MMF2

Bug Report

Schematic description of the bug: Two generic conditional types
VeryComplexNestedTypeNearRecursionLimit<T> extends true ? 'yes' : 'no'
and
[VeryComplexNestedTypeNearRecursionLimit<T>] extends [true] ? 'yes' : 'no'
evaluate, when instantiated with T := 6 (the specific instantiation that I have tested) in the first case to 'yes', in the second case to 'no'. The problem has nothing to do with distributive vs. non-distributive behavior, VeryComplexNestedTypeNearRecursionLimit<6> evaluates in my specific example to true, i.e. is not a union of values.

I suspect the problem is connected with touching the allowed recursion/nesting limit, as wrapping as singleton array increases the nesting level by one. In my actual code (see Playground link below) the schematic VeryComplexNestedTypeNearRecursionLimit is realized as type IsNaturalNumber. Unfortunately, I cannot give a shorter example as the problem seems to arise only for very complex types.

🔎 Search Terms

wrong branch, wrong branch recursive

🕗 Version & Regression Information

TypeScript 4.1.3, not found in FAQ, not solved by nightly version 4.2.0-dev-20210105.
I was unable to test this on prior versions because the construed types depend on 4.1 features.

⏯ Playground Link

Playground link with relevant code

💻 Code

type CheckedNaturalNumber<ToBeChecked> = 
    IsNaturalNumber<ToBeChecked> extends true
      ? number 
      : 'inadmissible';

const testIsNaturalNumber11: IsNaturalNumber<6> = true; // OK      
// EQ checks the equivalence of types, EQ<T, U> evaluates to true iff T and V are equivalent
const testCheckedNaturalNumber1: EQ<CheckedNaturalNumber<6>, number> = true; // OK
const testCheckedNaturalNumber2: CheckedNaturalNumber<6> = 4; // OK


type CheckedNaturalNumber2<ToBeChecked> = 
    [IsNaturalNumber<ToBeChecked>] extends [true]
      ? number 
      : 'inadmissible';
const testCheckedNaturalNumber2_1: EQ<CheckedNaturalNumber2<6>, number> = false;  // type is false, but should be true
const testCheckedNaturalNumber2_2: CheckedNaturalNumber2<6> = 4;  // error, type is 'inadmissible' but should be number

🙁 Actual behavior

The instantiated type CheckedNaturalNumber2<6> (last line) evaluated to 'inadmissible'. This is the wrong branch of the (non-distributive) conditional type [IsNaturalNumber<ToBeChecked>] extends [true] ? ... .

🙂 Expected behavior

CheckedNaturalNumber2<6> should evaluate to number, i.e. to the true-branch of the conditional type as in the parallel example of CheckedNaturalNumber<6>. The only difference between the types CheckedNaturalNumber and CheckedNaturalNumber2 is that the condition in CheckedNaturalNumber2 is wrapped as an array. That is, IsNaturalNumber<ToBeChecked> extends true ? ... of CheckedNaturalNumber is replaced by [IsNaturalNumber<ToBeChecked>] extends [true] ? ... within CheckedNaturalNumber2, what should leave the semantics untouched. This has nothing to do with distributive or non-distributive behavior as IsNaturalNumber<ToBeChecked> evaluates to true, i.e. it is no union of types.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Design LimitationConstraints of the existing architecture prevent this from being fixed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions