Open NagayamaToshiaki opened 2 days ago
There is no way TypeScript can guess that. For instance if your function was not pure (for instance it calls Math.random
), then it wouldn't be correct. And trying to detect which functions are pure is already way out of scope for TypeScript.
A workaround is to simply use find
:
function firstLetter(targets: string[]) {
const found = targets.find((target => target.length > 1));
if (found !== undefined){
return found[0];
}
return "";
}
Although this doesn't work in the same way if your array sometimes contains undefined
itself.
For instance if your function was not pure (for instance it calls
Math.random
), then it wouldn't be correct.
That's not really the issue. TypeScript does not handle pureness at all, you can call functions that modify your narrowed array and you end up with wrong types.
The issue here is that find()
does not know about the some()
call earlier, and that logically an element is always found. There's no special logic for the find/some
pair, and there's no suitable type for some()
to narrow to that would allow this logic without special hardcoded behavior (not going to happen).
Duplicate of #47404.
This isn't a bug in TypeScript, nothing is behaving in a way it's not supposed to. At worst this is a design limitation, although I'm not sure why you'd want to iterate an array twice if you can just do it once.
Anyway, I think this is the closest you can get to that behavior without TypeScript having to implement a bunch of new features:
type LongEnough = string & { __longEnough: true };
const isLongEnough = (target: string): target is LongEnough =>
target.length > 1
interface FindableArray<T, S extends T> extends Array<T> {
find(predicate: (value: T, index: number, obj: T[]) => value is S, thisArg?: any): S
}
// merge in an overload for some():
interface Array<T> {
some<S extends T>(
predicate: (value: T, index: number, array: T[]) => value is S,
thisArg?: any
): this is FindableArray<T, S>
}
function firstLetter(targets: string[]) {
if (targets.some(isLongEnough)) {
const found = targets.find(isLongEnough);
return found[0];
}
return "";
}
Essentially you'd need to convince TypeScript that your predicates act as type guards, since that's the only way two predicates can be reasonably considered "the same". But TypeScript doesn't have a type that means "string of at least 2 characters" (well, it has `${string}${string}${string}`
but that collapses to string
weirdly so let's ignore that). So then we need to make a nominal-ish LongEnough
type and say that your predicate narrows its input from string
to LongEnough
. Then we need to merge into the Array<T>
definition an overload for some()
that, when it encounters a type predicate callback, narrows the array to a FindableArray
where we know there's at least one element of the narrowed type that you'll find()
, and then... blegggh.
🔎 Search Terms
find some undefined
🕗 Version & Regression Information
I tried TS Playground with nightly.
⏯ Playground Link
https://www.typescriptlang.org/play/?ts=5.8.0-dev.20241110#code/GYVwdgxgLglg9mABMGAnAzlAMgUylHVACigENUBzPdALkU1RjAoG0BdASkQG8AoRRDGCIS5KlHQA6dHAC2OUZTyIAvAD5EZJVEkAbHMygALRBoCMHDnwECA9LfpMIOTWOqIjpAG4uZ8xDj68mASiADuRnDoLvqGJuQusnCoLsakSGYANMhw4AAmiLIgmIhgcFCIAEYuAOT5OChgOHk1VTgQpMUuAA4peTAdBILo9KTy-DYQCCXAuWAFKq7aUo15RIriqhpa4noGFMamiBYcANwTdg6VIBWz+YUwmKQA1ga6AJ6CYMCEI1Bw9CgjGYAB96o1mpojMUcrpdHAwkwKIgUlAQKgkMZUAiRoRsagLii8OikHd5iwAAxsc4CAC+E1RJMQACJmedaUA
💻 Code
🙁 Actual behavior
found[0] throws error "'found' is possibly 'undefined'.(18048)" Because
some
andfind
has same predicate andsome
is true, find must return string.🙂 Expected behavior
The code can be compiled without error.
Additional information about the issue
No response