#at(-1) type guard

89 messages · Page 1 of 1 (latest)

elfin shadow
#

Why is this not a valid type guard?

if (files.length === 0) {
  continue;
}

const currentFolder = files.at(-1); // currentFolder can still be undefined
earnest arch
#

at is just a function, it has no relation to the length check - and also sparse arrays technically exist

elfin shadow
earnest arch
#

yes

#

all the "elements" (of which there are 0) fulfill MyType

#

it's kind of like assigning an empty array

elfin shadow
#

can I guard against sparse arrays instead?

earnest arch
#

i don't think you reasonably can, and i don't think it'd be particularly useful. typescript doesn't have a way to represent "dense array" in types anyways

elfin shadow
#

so what is the correct way of specifying my use case

earnest arch
#

you can't change the return type of at through any narrowing

#

i'd just get the element and narrow the element itself, rather than the array

finite verge
elfin shadow
#

somewhere. it is guaranteed to be MyType[]

earnest arch
elfin shadow
#

oh, ignore the typo 🙂

finite verge
#

if you can guarantee that it's [MyType] instead (or somesuch) from the beginning that'd also work, which is why i asked where it comes from

elfin shadow
#

I can, but TypeScript is not convinced

finite verge
finite verge
earnest arch
#

.at would not change regardless of narrowing

#

but [] would

#

i'm not seeing a [string] there or whatever

elfin shadow
#

right, I can [] even if I think it's ugly

earnest arch
#

you could also destructure

#

though i guess not to get the last one

elfin shadow
#

I dun goofed this is the correct jetbrains output

earnest arch
#

right so readonly GalleryFile[] and [GalleryFile] are not the same thing
and as mentioned before, .at just won't have a different type no matter how you narrow

finite verge
#

do you know what tuples are?

elfin shadow
#

I can do file.path[file.path.length - 1] like a barbarian ES5 speaker

earnest arch
#

it also wouldn't be narrowed

elfin shadow
#

it does

earnest arch
#

ah, you have nUIA disabled

#

it's not narrowed

#

it just doesn't have | undefined in to begin with

elfin shadow
#

to be fair I inherited this project and the tsconfigs are out of whack

earnest arch
#

you could do file.path.length + 40 and it'd still say GalleryFile

earnest arch
#

but yeah if it can't be a tuple, just use at(-1) and check the result

finite verge
#

in case it wasn't clear, what chris meant [up here](#1484515881521250386 message) (and also immediately above) is this:

const currentFolder = files.at(-1); 
if (currentFolder === undefined) {
  throw new Error('No folders found');
}
// the rest of your code goes here (where `currentFolder` will be narrowed)
elfin shadow
#

yes, that is exactly the boilerplate I want to avoid

#

but if I have no other option then shrug emoji

earnest arch
#

how come?

#

to be clear you also don't need the length check prior with that

elfin shadow
#

ah, that changes things

finite verge
#

you either have to prove to typescript that files is not empty before this code, or you have to deal with the possibility that it may be empty. you haven't really shown enough context for us to suggest ways to do the former

earnest arch
#

there's also the option of using !, but.. why use an assertion when you can narrow directly lol

finite verge
#

(i guess a third forth option is writing a custom type guard function to narrow files from readonly GalleryFile[] to readonly [GalleryFile], but i'm not sure if that'll be sufficient since you specifically want the last element)

elfin shadow
finite verge
#

actually wait, can there be more than one element? if so i might've been causing confusion with my [GalleryFile] mentions

earnest arch
#

those aren't communicated to typescript though

finite verge
#

(i'm not a valibot user)

earnest arch
#

even if it were [GalleryFile, ...GalleryFile[]] that still wouldn't change the types of x.at(number) or x[number]

elfin shadow
earnest arch
#

not sure what you mean by that

#

as it stands you just aren't really narrowing anything

finite verge
earnest arch
#

i assumed it being the last element was significant

#

so uh, is it

finite verge
#
const reversedX = x.reverse()
if (validateItsNotEmpty(reversedX)) {
  const whatIWant = reversedX[0]
}

(kidding; don't do this when there are multiple better options)

earnest arch
#

i was thinking about that too lmao

#

O(n) array access to satisfy static requirements..

elfin shadow
#

either way, typescript says it is a GalleryFile[] so I don't have a reason to believe otherwise when at() ing. Is there no way to tell typescript the minimum length of an array on a type level?

#

(this is entering curiosity level)

earnest arch
#

there's a tuple, but typescript is a static type system so it'd have to be a specific length you check for and you'd also need to cast/predicate since typescript doesn't narrow from an array to a tuple

elfin shadow
#

Yeah it's not so much a tuple and more that the dynamic array can have at least 5 or 7 elements etc

#

I'm not surprised that it can't though

earnest arch
#

at including undefined isn't about the array including undefined, it's about at retrieving elements out of bounds

finite verge
elfin shadow
earnest arch
#

yeah no they can't

finite verge
#

i've used custom type guards like this before to do this kind of narrowing (but from what i understand of your scenario, probably would not bother in your case):

elfin shadow
#

well that's a bubble of mine burst

abstract gladeBOT
#
/** Creates a readonly tuple containing `T` repeated `N` times. */
type Repeat<T, N extends number> = number extends N ? readonly T[] : BuildTuple<T, N, readonly []>
type BuildTuple<T, N extends number, A extends readonly T[]> = A extends { length: N }
  ? A
  : BuildTuple<T, N, readonly [...A, T]>

const isAtLeastOfLength =
  <N extends number>(length: N) =>
  <A>(as: readonly A[]): as is Repeat<A, N> & readonly A[] =>
    as.length >= length


const stuff = Array.from({ length: Math.random() * 10 }, _ => 'blah')

const mightNotBeDefined = stuff[2]
//    ^? - const mightNotBeDefined: string
if (isAtLeastOfLength(3)(stuff)) {
  const definitelyDefined = stuff[2]
//      ^? - const definitelyDefined: string
}
finite verge
elfin shadow
#

7 years 🥲

#

but thanks, I'll follow that issue

elfin shadow
#

!solved

finite verge
elfin shadow
#

I contemplated sending a "good luck on your first day at school" card to vercel

abstract gladeBOT
#

/** Creates a readonly tuple containing `T` repeated `N` times. */
type Repeat<T, N extends number> = number extends N ? readonly T[] : BuildTuple<T, N, readonly []>
type BuildTuple<T, N extends number, A extends readonly T[]> = A extends { length: N }
  ? A
  : BuildTuple<T, N, readonly [...A, T]>

const isAtLeastOfLength =
  <N extends number>(length: N) =>
  <A>(as: readonly A[]): as is Repeat<A, N> & readonly A[] =>
    as.length >= length


const stuff = Array.from({ length: Math.random() * 10 }, _ => 'blah')

const mightNotBeDefined = stuff[2]
//    ^? - const mightNotBeDefined: string | undefined
if (isAtLeastOfLength(3)(stuff)) {
  const definitelyDefined = stuff[2]
//      ^? - const definitelyDefined: string
}
earnest arch
#

(the above snippet with nUIA enabled)