Skip to content

Consistent intersection type with array and tupleΒ #50346

Open
@Phryxia

Description

Suggestion

πŸ” Search Terms

array, tuple, intersection, covariance, #32926, #33760

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

I think this is rather theoretical approach for the behavior of intersection types and covariance for the consistency.

If this doesn't meet the design policy, feel free to close this.

πŸ“ƒ Motivating Example

Comprehension check for the intersection type and covariance

With my understanding of TypeScript and set theory, intersection type seems to obey following:

A & B βŠ† A
A & B βŠ† B

Also covariance/contravariance can alter the direction of such relation.

A βŠ† B

If F needs to be covariant:
F<A> βŠ† F<B>

then F<A> & F<B> is equivalent to F<A>

If F needs to be contravariant:
F<A> βŠ‡ F<B>

then F<A> & F<B> is equivalent to F<B>

Current behavior of finite/infinite computed property

Now consider the following object types

type Small = {
  [k in 'a' | 'b']: number
}

type Big = {
  [k in 'a' | 'b' | 'c']: number
}

let small: Small = { a: 1, b: 1 }
let big: Big = { a: 1, b: 1, c: 1 }

// small = big    // (OK)
// big = small    // (Invalid)

This makes sense. Note that this somehow behaves contravariant, while with the following behaves covariant.

type Big = {
  [k in string]: number
}

Here is the #playground for a glimpse.

What should be T[] & [T]?

It's basically nonsensical to intersect a tuple type with a variable-length array type;

I already read this from #33760 (comment) . But with the theoretical point of view, I just wonder whether this is helpful for the consistent implementation for future or not.

I don't know the implementation detail of this, but along the empirical tests, I think [T] is considered like:

[T] = {
  length: 1
  "0": T
  // and other array's properties
}

And T[] resembles:

T[] = {
  length: number
  [index in number]: T
  // and other array's properties
}

Currently, with TypeScript v4.7.4, the result is a little bit weird. #playground

type T = number[] & [number]
let t: T[1] // ??? no error
let len: T["length"] // 1

Is this because 0 is not computed property? How about [T, T] or [T, T, T]?

πŸ’» Use Cases

Literal type 0 should be considered as finite subset of number, and by "Current behavior of finite/infinite computed property", the computed property of index, should be narrowed into 0. Note that length is already narrowed into 1.

// suggested behavior
type T = number[] & [number]
let t: T[1] // should be error or never
let len: T["length"] // 1

I understand that this might impractical, and nobody do number[] & [number]. But I'm not sure that, there is no guarantee for other complex composition never make such result. I hope this insight would be helpful for TypeScript team.

Metadata

Assignees

No one assigned

    Labels

    DiscussionIssues which may not have code impact

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions