Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array.prototype.at should return a more precise type when operating on a tuple #47660

Closed
5 tasks done
RebeccaStevens opened this issue Jan 29, 2022 · 15 comments
Closed
5 tasks done
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds

Comments

@RebeccaStevens
Copy link

RebeccaStevens commented Jan 29, 2022

Suggestion

Extracted out of #45512

🔍 Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

Tuple Array.prototype.at

✅ 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

at called on a tuple should work consistently with how index access on tuples works.

const tuple = [1, 2, 3] as const;
const element = tuple.at(1);
type T = typeof element; // currently `number | undefined`, ideally should be `2`

// Should also work with negatives.
const element2 = tuple.at(-1);
type U = typeof element2; // ideally should be `3`

// Compiler error or `undefined` for out of bounds numbers?
const element3 = tuple.at(100); // should be compiler error or
type V = typeof element3; // should be undefined

📃 Motivating Example

This is how tuples work when using index access.

💻 Use Cases

Preferring .at(index) to [index].

Partial Hack Solution

as suggested by @djmisterjon

interface ReadonlyArray<T> {
    /**
     * Returns the item located at the specified index.
     * @param index The zero-based index of the desired code unit. A negative index will count back from the last item.
     */
    at<U extends number>(index: U): this[U];
}
@fatcerberus
Copy link

I don’t think there’s a way to handle negative indices without the compiler special-casing .at internally (it’s just another method); you can’t do arithmetic on literal types. The hack you posted is about the closest you can get.

@RebeccaStevens RebeccaStevens changed the title Array.prototype.at should return a more precises type when operating on a tuple Array.prototype.at should return a more precise type when operating on a tuple Jan 29, 2022
@jcalz
Copy link
Contributor

jcalz commented Jan 30, 2022

You can get negative indices to work for tuples (template literals to drop the minus sign and then use the numberlike string to index into a shifted reversed tuple), but nobody would want this in the actual TS library. Still, enjoy:

⚙🛠
type Reverse<T extends readonly any[], U extends any[] = []> =
  T extends readonly [infer F, ...infer R] ? Reverse<R, [F, ...U]> : U

type At<T extends readonly any[], I extends number> =
  `${I}` extends `-${infer J}` ? [never, ...Reverse<T>] extends infer R ? J extends keyof R ? R[J] :
  undefined : never : T[I];

interface ReadonlyArray<T> {
  at<I extends number>(index: I): At<this, I>;
}
interface Array<T> {
  at<I extends number>(index: I): At<this, I>;
}
const tuple = [1, 2, 3] as const;
const element = tuple.at(1);
type T = typeof element; // 2
const element2 = tuple.at(-1);
type U = typeof element2; 3
const element3 = tuple.at(100); 
type V = typeof element3; // undefined

Playground link

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds labels Feb 1, 2022
@RyanCavanaugh
Copy link
Member

For positive indices I really don't see a reason not to use a[n] vs a.at(n), and negative indices are not really representable today.

@jcalz ow my soul

@Jamesernator
Copy link

Jamesernator commented Feb 1, 2022

and negative indices are not really representable today.

Like small numbers could at least just be hardcoded right? This seems to work fine in the playground:

declare global {
    interface ReadonlyArray<T> {
        at<R>(this: readonly [...any[], R], index: -1): R;
    }
}

const a = [1,'foo', { x: 10 }] as const;

const z = a.at(-1);

@RebeccaStevens
Copy link
Author

Yeah, if support for positive indexes and -1 are added, that should handle most use cases.

@jcalz
Copy link
Contributor

jcalz commented Feb 1, 2022

@jcalz ow my soul

@RyanCavanaugh ah yes, the theological argument for #26382

@iacore
Copy link

iacore commented Apr 22, 2022

The return type of array[idx] is wrong. Should I open an issue for this?

let a = [Array(0), Array(0)]
let b: number = a[2]

@wesbos
Copy link

wesbos commented Apr 29, 2022

Had this problem, I think @Jamesernator's code should be:

declare global {
  interface ReadonlyArray<T> {
    at<R>(this: readonly [...any[], R], index: number): R;
  }
}

so it allows reference by any index

@jamiebuilds
Copy link

@wesbos That type is not correct, R will always be the type of the last item in the array:

const instruments = ["guitar", "drums", "base", "vocals", "keys"] as const
type Instrument = typeof instruments[number];
let value = instruments.at(2);
//  ^? "keys"

[Playground]

@jamiebuilds
Copy link

@wesbos I think you want something more like this:

interface ReadonlyArray<T> {
    at<R extends ReadonlyArray<T>, I extends keyof R>(this: readonly [...R], index: I): R[I];
}

[Playground]

@wesbos
Copy link

wesbos commented Apr 29, 2022

@jamiebuilds hmm.. Works for me, your playground correctly logs the 3rd item

@Harpush
Copy link

Harpush commented Apr 29, 2022

@jamiebuilds Why do you need this: readonly [...R] and not just this: R?

@typescript-bot
Copy link
Collaborator

This issue has been marked as "Too Complex" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jun 21, 2023
@jonlepage
Copy link

"Too Complex" mean, i am too lazy to code, but if you give me 1M$ , i will do it.

@ThaJay
Copy link

ThaJay commented Aug 16, 2023

Too complex?
undefined has no place being hardcoded in the type of Array.prototype.at().

It would be nice if that undefined only showed up when relevant.

There have even been suggested improvements in this topic. How to proceed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Suggestion An idea for TypeScript Too Complex An issue which adding support for may be too complex for the value it adds
Projects
None yet
Development

No branches or pull requests