-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Narrowing fails with union of functions #51639
Comments
I can't be sure right now if the root issue is the same but this issue feels related: #48663 |
We only infer contextual parameter types from a union of contextual function types when the contextual function types have identical parameter lists, so this is effectively a design limitation. We could possibly do better by inferring the widest possible parameter types in such situations. |
It's very very rarely useful to contextually type by a union of functions -- the only cases where it really helps are basically places where you could have used the subtype instead, as is the case here (using |
@RyanCavanaugh For the record, manual subtype reduction isn't possible here because one has a return type of |
Similar. Here I am writing a dictionary of "checks" ( type Valish = (value: string) => boolean
type CurryValish = (...args: number[]) => Valish
const check = {
vin: (value) => true, // β any, should be string
cin: (curry) => (value) => true, // β any any, should be number string
} satisfies {
[key: string]:
| Valish
| CurryValish
} Some other examples of what the object is supposed to hold. const c = (regex) => new RegExp(regex)
const r = (characters) => new RegExp(`^[${characters}]+$`)
let minLength, isMatching
export const check = {
isTrue: (value) => value == true,
/* numbers */
min: (min: number) => (value: Numberish) => Number(value) >= min,
max: (max: number) => (value: Numberish) => Number(value) <= max,
clamp: (min: number, max: number) => (value: Numberish) => Number(value) >= min && Number(value) <= max,
/* strings */
maxLength: (length: number) => (s: string) => s.length <= length,
minLength: (minLength = (length: number) => (s: string) => s.length >= length),
notEmpty: minLength(1),
isTrimmed: (s: string) => s.trim() === s,
/* regex */
/** Matches a provided regex. */
isMatching: (isMatching = (r) => (s) => r.test(s)),
isAlphaNumeric: isMatching(c(ALNUM)),
isNumeric: isMatching(c(NUMBER)),
isDecimal: (decimals?: number) => isMatching(r(DECIMAL_TEMPLATE(decimals))),
isAddress: isMatching(r(ADDRESS)),
isText: isMatching(c(TEXT)),
isUUID: isMatching(r(UUID)),
} as const satisfies { [key: string]: Validator | ((...args: any[]) => Validator) }
type Validator = (value: string) => boolean |
TS can't pick one of the union members here for any given key. This is the same "problem" as this one: type Valish = (value: string) => boolean;
type CurryValish = (...args: number[]) => Valish;
const fn1: Valish | CurryValish = (value) => true; // implicit anys
const fn2: Valish | CurryValish = (curry) => (value) => true; // implicit anys And it stems from the fact that you can't really call a union of functions like this. How would you discriminate between members? type Valish = (value: string) => boolean;
type CurryValish = (...args: number[]) => Valish;
declare const fn: Valish | CurryValish
fn() // const fn: (arg0: never, ...args: number[]) => boolean | Valish As long as your arguments are different (and you have type Valish = (value: number) => boolean;
type CurryValish = (...args: number[]) => Valish;
declare const fn: Valish | CurryValish
fn(10) // ok This wouldn't help you with those contextual parameter types though, as per #51639 (comment) |
Bug Report
π Search Terms
function union, function union, multiple signatures
π Version & Regression Information
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
Parameter
text
implicitly has anany
type.π Expected behavior
No error plus
text
should be of type stringThe text was updated successfully, but these errors were encountered: