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
the type
Argsis 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 aRegExpor astring, 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
your first element is either of type
"a0"or"b0", both of which are literal types. SoArgshere is a discriminated union, and you can check the first element and get the narrowing you want.