Description
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:
- Conditional types are incorrectly narrowed #30152 (maybe related)
- [API Question] Is there a type resolution depth limit difference between compiling and running the language service? #29511 (discussion around implicit casts to any)
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