A.S.: The question is not about "Why is there an error?", but rather about "Why does the error go away if I barely change it?".
I have a relatively simple logic of creating parameters for a regex validator function that gathers the regex from either the combination of source
and flags
string inputs, or the regex input directly; it also takes a validation config in both cases. Some inputs are optional, some are required.
Here is how it looks like:
type Options = { foo: 'bar' }
type ArgsRegExp = [first: RegExp, second?: Options]
type ArgsString = [first: string, second?: string, third?: Options]
type Args = ArgsRegExp | ArgsString
interface Params {
readonly pattern: RegExp
readonly options?: Options
}
function getParams(args: ArgsRegExp): Params
function getParams(args: ArgsString): Params
function getParams([first, second, third]: Args): Params {
if (first instanceof RegExp) {
return {
pattern: first,
options: second,
// ^^^^^^ Error!
}
}
return {
pattern: new RegExp( // weird new lines are explained in the playground
first,
second
// ^^^^^^ Error!
),
options: third,
}
}
There is an error here: the second
arg is always string | Options | undefined
, and thus it is not assignable in neither cases (it must be Options | undefined
in the first case and string | undefined
in the second).
This error is expected, and (if I understand correctly) is related to https://github.com/microsoft/TypeScript/issues/30581
However, what is not expected is that a very similar piece of code (here's the diff view of them) works fine without producing any assignability issues, because it perfectly narrows down the items as you'd wish:
type Common = 'c'
type ArgsRegExp = [first: 'a0', second?: Common]
type ArgsString = [first: 'b0', second?: 'b1', third?: Common]
type Args = ArgsRegExp | ArgsString
interface Params {
readonly custom: string
readonly common?: Common
}
function getParams(args: ArgsRegExp): Params
function getParams(args: ArgsString): Params
function getParams([first, second, third]: Args): Params {
if (first === 'a0') {
return {
custom: first,
common: second,
// ~~~~~~ No error?
}
}
return {
custom: // weird new lines are explained in the playground
first +
second,
// ~~~~~~ No error?
common: third,
}
}
What's up with that? Why does this work, but the original example doesn't? I get that the types are different, but I can't pinpoint exactly which difference contributes to the two examples behaving differently.
In
type Options = { foo: 'bar' }
type ArgsRegExp = [first: RegExp, second?: Options]
type ArgsString = [first: string, second?: string, third?: Options]
type Args = ArgsRegExp | ArgsString
the type Args
is not a discriminated union. Discriminated unions in TypeScript require their discriminant property to be or include literal types. You are switching on the first tuple element, which is either a RegExp
or a string
, neither of which are literal types. So that element is not seen as a discriminant, and therefore no narrowing is done when checking that element.
On the other hand, in
type Common = 'c'
type ArgsRegExp = [first: 'a0', second?: Common]
type ArgsString = [first: 'b0', second?: 'b1', third?: Common]
type Args = ArgsRegExp | ArgsString
your first element is either of type "a0"
or "b0"
, both of which are literal types. So Args
here is a discriminated union, and you can check the first element and get the narrowing you want.