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

Allowing type predicate non-assignable to parameter #49779

Closed
graphemecluster opened this issue Jul 4, 2022 · 3 comments
Closed

Allowing type predicate non-assignable to parameter #49779

graphemecluster opened this issue Jul 4, 2022 · 3 comments
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed

Comments

@graphemecluster
Copy link
Contributor

Search Terms: user-defined type guard, assignability

Suggestion: Playground link with relevant code

declare const array: ["foo", "bar", "baz"];
declare function pop<T extends unknown[]>(array: T): asserts array is T extends [...infer U, any] ? U : never;
//                                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// A type predicate's type must be assignable to its parameter's type.
//   Type 'T extends [...infer U, any] ? U : never' is not assignable to type 'T'.
//     'T extends [...infer U, any] ? U : never' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'unknown[]'.
//       Type 'unknown[]' is not assignable to type 'T'.
//         'unknown[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'unknown[]'.

pop(array);
//^?: function pop<["foo", "bar", "baz"]>(array: ["foo", "bar", "baz"]): asserts array is ["foo", "bar"]
array;
//^?: const array: never

Expected behavior: No errors, array is inferred as ["foo", "bar"]

Use Cases: uhyo/better-typescript-lib

Discussion: If it needs to be a new syntax, what would be the syntax?

@RyanCavanaugh RyanCavanaugh added Working as Intended The behavior described is the intended behavior; this is not a bug Design Limitation Constraints of the existing architecture prevent this from being fixed and removed Working as Intended The behavior described is the intended behavior; this is not a bug labels Jul 5, 2022
@RyanCavanaugh
Copy link
Member

Type guards, and the type system in general, cannot represent a variable changing to a type that is outside the domain of its originating declaration. Changing type predicates won't help the situation; other things would just start breaking instead.

@graphemecluster
Copy link
Contributor Author

Going through #17002 and coming up with another one:

declare function isArray<T>(arg: T): arg is ToArray<T>;

type ToArray<T> =
    0 extends (1 & T) ? any[] :
    T extends readonly unknown[] ? T :
    T extends Iterable<infer U> | ArrayLike<infer U> ? U[] :
    unknown extends T ? unknown[] :
    never;

declare const foo: ArrayLike<number> | string[] | number;

if (isArray(foo)) {
    // ^?: function isArray<number | ArrayLike<number> | string[]>(arg: number | ArrayLike<number> | string[]): arg is string[] | number[]
    foo;
    // ^?: string[]
}

It fails to resolve to the inferred type predicate result (while ArrayLike<number> | Iterable<string> | number does resolve to number[] | string[]). Needless to say, explicitly adding Extract or Cast to T to the type predicate doesn't help at all, despite that ts2677 is gone.

Here are my test cases:

any -> any[]
unknown -> unknown[]

number -> never
number | number[] -> number[]
number | readonly number[] -> readonly number[]
number | number[] | readonly number[] -> number[]
number | Iterable<number> -> number[]
number | Iterable<number> | number[] -> number[]
number | Iterable<number> | readonly number[] -> number[]
number | ArrayLike<number> -> number[]
number | ArrayLike<number> | number[] -> number[]
number | ArrayLike<number> | readonly number[] -> number[]
number | Iterable<number> | ArrayLike<number> -> number[]
number | Iterable<number> | ArrayLike<number> | number[] -> number[]
number | Iterable<number> | ArrayLike<number> | readonly number[] -> number[]

number | number[] | readonly string[] -> number[] | readonly string[]
number | Iterable<number> | string[] -> number[] | string[]
number | Iterable<number> | readonly string[] -> number[] | readonly string[]
number | ArrayLike<number> | string[] -> number[] | string
number | ArrayLike<number> | readonly string[] -> number[] | readonly string[]
number | Iterable<number> | ArrayLike<string> -> number[] | string[]
number | Iterable<number> | ArrayLike<number> | string[] -> number[] | string[]
number | Iterable<number> | ArrayLike<number> | readonly string[] -> number[] | readonly string[]

@graphemecluster
Copy link
Contributor Author

After asking a question in https://stackoverflow.com/questions/72853834, It is found that the above seems to be related to the algorithm described in #47283 (comment). I wonder if #47389 solves this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Limitation Constraints of the existing architecture prevent this from being fixed
Projects
None yet
Development

No branches or pull requests

2 participants