Skip to content

Conditional types incorrect for complex types #35533

Closed
@ostrowr

Description

@ostrowr

TypeScript Version: 3.7.2

I'm seeing incorrect behavior when I extend a computed type that's more than a few levels deep. When I explicitly define the type, I don't see the same behavior. I'm guessing that complex types are getting implicitly cast to any in this circumstance – tsc should probably either instantiate the correct type or cast them to unknown with a warning when they're too big.

This is admittedly a contrived example but I've run into similar issues when, for example, developing https://github.com/ostrowr/ts-json-validator

Search Terms:
depth, conditional types, extends, implicit any

Expected behavior:
shouldBeFalse and correctlyFalseWhenExpanded are both false (see code snippet below)

Actual behavior:
shouldBeFalse is True (i.e. 6 == 5 in my contrived version of Peano arithmetic)

Related Issues:

Code

type Natural = { prev: Natural }
type Zero = { prev: never }

type Equals<A extends Natural, B extends Natural> =
    A extends B ?
        B extends A ?
        true :
        false : 
    false

type S<T extends Natural> = { prev: T }

type One = S<Zero>
type Two = S<One>
type Three = S<Two>
type Four = S<Three>
type Five = S<Four>
type FiveByHand = { // this is the exact same type as Five, just manually expanded
    prev: {
        prev: {
            prev: {
                prev: {
                    prev: {
                        prev: never;
                    };
                };
            };
        };
    };
}
type Six = S<Five>
type SixByHand = { // this is the exact same type as Six, just manually expanded
    prev: {
        prev: {
            prev: {
                prev: {
                    prev: {
                        prev: {
                            prev: never;
                        };
                    };
                };
            };
        };
    };
}
type correctlyFalseForShallowTypes = Equals<Four, Five> // type: false
type correctlyTrue = Equals<Four, Four> // type: true
type shouldBeFalse = Equals<Five, Six> // type: true (should be false!)
type correctlyFalseWhenExpanded = Equals<FiveByHand, SixByHand> // type: false (as expected)
Output
"use strict";
Compiler Options
{
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictPropertyInitialization": true,
    "strictBindCallApply": true,
    "noImplicitThis": true,
    "noImplicitReturns": true,
    "useDefineForClassFields": false,
    "alwaysStrict": true,
    "allowUnreachableCode": false,
    "allowUnusedLabels": false,
    "downlevelIteration": false,
    "noEmitHelpers": false,
    "noLib": false,
    "noStrictGenericChecks": false,
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "esModuleInterop": true,
    "preserveConstEnums": false,
    "removeComments": false,
    "skipLibCheck": false,
    "checkJs": false,
    "allowJs": false,
    "declaration": true,
    "experimentalDecorators": false,
    "emitDecoratorMetadata": false,
    "target": "ES2017",
    "module": "ESNext"
  }
}

Playground Link: Provided

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