Giter Club home page Giter Club logo

Comments (6)

Andarist avatar Andarist commented on April 27, 2024 1

A somewhat smaller repro would be this:

type IsNumber<T> = [T] extends [number] ? true : false
type Shortcut<T> = false[] extends IsNumber<T>[] ? 0 : 1;
//   ^? type Shortcut<T> = 1

TS tries to check if the check type is assignable to a permissive instantiation of the extends type. That permissive instantiation is true[] here so it ends up checking false[] extends true[] ? 0 : 1 and it determined that this can never be true and picked up the false type as the result.

Some extra mechanisms that avoid over-eager simplifications were introduced in #56004 but that was only when computing constraints of conditional types and in this situation forConstraint (the variable introduced there) is false.

This is the most relevant piece of the implementation: link. Perhaps the biggest surprise (for me) is that this extends type (IsNumber<T>[]) is not deferred (it depends on a type variable after all).

The definition of isDeferredType is pretty simple:

    function isDeferredType(type: Type, checkTuples: boolean) {
        return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType);
    }

I have never formed a good mental model for what a generic type is internally (again, this one depends on a type variable but it's "just" a type reference to Array and this is not called a generic type internally, even if it has generic type arguments).

from typescript.

Andarist avatar Andarist commented on April 27, 2024 1

That's weird that the so-called "permissive instantiation" of IsNumber would be true[]

A permissive instantiation is an instantiation that maps type parameters to a wildcard type (an internal type of any). But even with a regular any we get this:

type IsNumber<T> = [T] extends [number] ? true : false
type Result = IsNumber<any>
//   ^? type Result = true

Especially weird to me that this works again if I define IsFalse with tuple types instead of array types.

To quote the source code:

// When the check and extends types are simple tuple types of the same arity, we defer resolution of the
// conditional type when any tuple elements are generic. This is such that non-distributable conditional
// types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`.
const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) &&
    length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements);

I don't know why tuples are special this way. I understand that wrapping types in tuples might be a common way to make your conditional types non-distributive but I don't think I've ever seen this to be documented as the only way to do that. My personally mental model was always that you can make conditional types non-distributive by simply making check type "non-naked" and T[] is something that I'd call non-naked.

But as I alluded to in my previous comment - the problem is not exclusive to arrays, likely just any other type reference (other than a tuple) with generic type parameters exhibits the same problem:

type IsNumber<T> = [T] extends [number] ? true : false;
type Shortcut<T> = Record<string, false> extends Record<string, IsNumber<T>> ? 0 : 1;
//   ^? type Shortcut<T> = 1

from typescript.

fatcerberus avatar fatcerberus commented on April 27, 2024

I feel like there was a recent issue about exactly this...

from typescript.

fatcerberus avatar fatcerberus commented on April 27, 2024

That's weird that the so-called "permissive instantiation" of IsNumber<T> would be true[]; I'd expect it to be boolean[] (or maybe never[], i.e. intersection of true and false types)1 since true and false are disjoint and there's seemingly no reason to prefer the true branch of the conditional over the false branch. Of course we'd have the same problem in the end, since the eager evaluation itself is the problem here, but c'est la vie.

Footnotes

  1. I'm interpreting "permissive" here to mean "compatible with whatever ultimately comes out of the conditional type", which of course depends on things like variance, how the true and false types are related, etc.

from typescript.

rotu avatar rotu commented on April 27, 2024

Especially weird to me that this works again if I define IsFalse with tuple types instead of array types.

type IsFalse2<T extends boolean> = [false] extends [T] ? true : false
type Shortcut2<T> = IsFalse2<IsNumber<T>> extends true ? 0 : 1;
type Indirect2 = Shortcut2<string>;
//   ^? type Indirect2 = 0

https://www.typescriptlang.org/play?jsx=0&ts=5.3.3#code/C4TwDgpgBAkgzgOQK4FsBGEBOAeAKgPigF4oBtXAXSggA9gIA7AEzjIdQ0yoH4phMk0AFxQAZgEMANnAgAoUJFhwAYlJl5qdRiyhoA9nskRxDQiQnSIpKrXrNWua1F79BUERZmz54aABEAS0wIAGNgYiVVS2x4ZHQsbDh+AIYAc3xCW21WV2heAAZ3KABGAG5ZAHoKqBqAPW5vBWgAZQALPUxgEKRgPDNItQgYxA4Egkytez4BPKhCkTKfRRhmINDwkjaOrp7E5LT8cqqaqHrG3wHLACYNLKn9Q2NTCNJPCBtJnXIeabcPQaWLXanW6wBuBAi8CiMhusVGOHGmjsOlyzjmRUWTVgq2CYSuES2IJ6NySmBS6SO1TqDSAA

from typescript.

fatcerberus avatar fatcerberus commented on April 27, 2024

@Andarist I believe it’s been stated by a maintainer (@ahejlsberg, perhaps?) that, while the documented behavior is “naked type parameters are distributive”, there’s special logic to handle the pattern [T] extends [Foo] ? …, for perf reasons I believe, because that’s the most common way to write non-distributive conditionals.

from typescript.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.