#TS throws an error for `arr["1.0"]`, but *not* for `arr["1.1"]` ?!

16 messages · Page 1 of 1 (latest)

sharp stone
#

Full details in playground and snapshot (see follow-up).

Here's the short version:

suppose you have const arr: any[] = []

TS throws an error if you try to access arr with a string, unless:
(i) the string is integer-like:

  • no error: arr["0"] = "this is not an error (expected)"
    (ii) the string is decimal-like, but only if the value of the decimal portion is zero
  • error: arr["2.0"] = "this is an error (expected)
  • no error ?!: arr["2.4"] = "this is *not* an error (super weird)"

Also weird: TS lets you try to access an array with negative integers and integer-like strings, and the behavior seems really weird - see the playground.

normal jay
#

@sharp stone Can you link the playground, rather than a screenshot?

sharp stone
#

yep - sorry hit send early!

wheat knollBOT
#
jstandfast#0

Preview:```ts
const arr: any[] = [];

// JS and TS both accept non-negative integers as valid array indices
arr[0] = 0 -> JS array index 0 | TS no error;

// same for non-negative, integer-like strings
arr["1"] = "1" -> JS array index 1 | TS no error;

// same for non-negative decimals - if the value of the decimal portion is zero
...```

sharp stone
rugged zodiac
#

Array is (partially) defined as:

interface Array<T> {
  [n: number]: T;
}

Because JS will coerce all numeric keys to strings, this actually means that any string which can be created when doing String(n) is a valid index. "4.0" isn't allowed because String(4.0) is "4", not "4.0" This has some additional weird consequences with big numbers:

const arr: unknown[] = []
arr["400000000000000000000"] = "ok"
arr["4000000000000000000000"] = "error"
arr["4e+20"] = "error"
arr["4e+21"] = "ok"
sharp stone
#

@rugged zodiac thanks - makes sense. anyone stumbling on this later, see below for another playground that illustrates @rugged zodiac 's point

wheat knollBOT
#
jstandfast#0

Preview:```ts
const arr: any[] = []

type IsValidArrayIndex<T extends string | number> =
T extends number
? true
: T extends ${infer N extends number}
? ${N} extends T
? true
: false
: false

type TestNeg = IsValidArrayIndex<-5>
arr[-5] = 5
...```

sharp stone
#

this is also related to issues around TS 4.9 - improved inference for infer types in template string types https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html#improved-inference-for-infer-types-in-template-string-types

link to the associated PR https://github.com/microsoft/TypeScript/pull/48094

GitHub

This modifies inference in template literal types when inferring to a constrained type variable by leveraging the constraint of the type variable to infer a more specific type:
// helper that enfor...

#

funny how long you can go without noticing that arr[Math.PI] isn't a TS error

dusky edge
#

it's not a JS error either!

> const arr = []
undefined
> arr[Math.PI]
undefined
> arr[Math.PI] = 'pie'
'pie'
> arr[Math.PI]
'pie'
sharp stone
#

sure, in JS you can assign any valid property to an array. But in JS, only non-negative integers are valid as array element indices, as opposed to object property keys.

fresh ridge
#

ts doesn't have a way to represent that currently, unfortunately

#

not a straightforward way that would work as a built-in, anyways

sharp stone
#

yep, that's what I've learned from working through this:

ideally, TS would emit an error for an expression arr[x] , where x has a string or number literal type, if and only if JS won't accept x as an index at runtime

but this isn't the ideal world, and there are cases when x has a literal type (i.e., its "value" is ~known at compile time) and is not a valid array index, but TS doesn't throw an error:

  • negative numbers
  • non-integer numbers
  • strings that can be parsed as a negative and/or non-integer number, except for strings that can be parsed as numbers, but can't be "round-tripped", i.e. if String(Number(x)) !== x , then TS does throw an error

and that's how you get to a world where:
arr[1.0] // no TS error (is a number), aligns with JS

arr[1.1] // *no* TS error (is a number), does *not* align with JS)

arr["1.0"] // TS error, aligns with JS (both fail round-trip test)

arr["1.1"] // *no* TS error (parses as a number, passes round-trip test), does *not* align with JS

fresh ridge
#

wdym by cases ts doesn't allow but js does for this situation