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

Filter with a type guard fails to narrow when the type guard's predicate is a tuple supertype #59054

Closed
RobertSandiford opened this issue Jun 27, 2024 · 4 comments
Labels
Not a Defect This behavior is one of several equally-correct options

Comments

@RobertSandiford
Copy link

RobertSandiford commented Jun 27, 2024

πŸ”Ž Search Terms

filter type guard failing to narrow tuple supertype

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried

⏯ Playground Link

Playground Link

πŸ’» Code

import { expectType } from "ts-expect";

declare function Tuple0IsNumberOrString(tuple: [unknown]): tuple is [number | string]
declare const t: [number | null] // t[0] is string | number
if (Tuple0IsNumberOrString(t)) {
    t[0] // predictably the type guard narrows t[0] to number
}

// but filter fails to narrow
const list = [t]
const filtered = list.filter(Tuple0IsNumberOrString)
const f = filtered[0]![0] // number | null

// strangely if the typeguard is changed, narrowing now works
declare function Tuple0IsNumber(tuple: [unknown]): tuple is [number]
const filtered2 = list.filter(Tuple0IsNumber)
const f2 = filtered2[0]![0] // number

More examples on the playground

πŸ™ Actual behavior

[number | null] & [number | string] generally gives a result of [number], but when a type guard is used with filter (specifically), the result is not the expected type of [number][], but rather the input type of [number | null][].

The problem occurs when the type guard's type assertion is a supertype of the expected item result type [number], but goes away if the type assertion is the same type or a subtype, i.e. [number] or [5]. The problem does not occur without the tuple wrapper.

πŸ™‚ Expected behavior

The filter result should be [number][]

Additional information about the issue

No response

@RobertSandiford RobertSandiford changed the title Filter with a type guard fails to narrow when the type guard's assertion is a tuple supertype Filter with a type guard fails to narrow when the type guard's predicate is a tuple supertype Jun 27, 2024
@Andarist
Copy link
Contributor

This depends on the filter's definition that:

filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];

With your type predicate you end up with:

T // [number | null]
S // [number | string]

This S doesn't satisfy this T so this overload can't be picked.

@RobertSandiford
Copy link
Author

RobertSandiford commented Jun 27, 2024

The second overload here allows the typeguard the work.

interface Array<T> {
    filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
    filter<V, S extends V>(predicate: (value: V, index: number, array: T[]) => value is S, thisArg?: any): (T&S)[];
}

TS Playground Demo

The problem though is that T and may not be assignable to V, leading to unsafety. V needs a constraint that it is a supertype of T. e.g.

type AllowedV<V, T> = T extends V ? V : 'not allowed'

declare global {
    interface Array<T> {
        filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
        filter<V, S extends V>(predicate: (value: AllowedV<V, T>, index: number, array: T[]) => value is S, thisArg?: any): (T&S)[];
    }
}

TS Playground with supertype constraint

Seems like we need supertype constraints to make progress on this #14520

@RyanCavanaugh
Copy link
Member

You would need to write this as

declare function Tuple0IsNumberOrString<T>(tuple: [T]): tuple is [T & (number | string)]

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Jul 9, 2024
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Not a Defect" 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 Jul 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Not a Defect This behavior is one of several equally-correct options
Projects
None yet
Development

No branches or pull requests

4 participants