Description
Bug Report
🔎 Search Terms
type guard const narrow narrowing union
🕗 Version & Regression Information
- The comment "no TS error" became true between 4.2.3 and 4.3.5. That fix may be instructive, and possibly broadenable to work when used as a type parameter.
- The errors became a bit more sensible between versions 4.5.5 and 4.6.4.
- Changes related to the
Lowercase
example of tying input and output types together are not reported here because the comment still holds. - Nightly version at time of test: v5.0.0-dev.20230201
⏯ Playground Link
Playground link with relevant code
💻 Code
type G7Abbreviation = 'CA' | 'FR' | 'DE' | 'IT' | 'JP' | 'UK' | 'US' | 'EU';
type G8Abbreviation = G7Abbreviation | 'RU';
declare function takesG7WithParam<A extends G7Abbreviation>(arg0: A) : void;
declare function takesG7WithoutParam(arg0: G7Abbreviation) : void;
const takesG8 = function<
TN extends G8Abbreviation //Constrains other params/return type in inspiring example; type param required
>(abbreviation: TN) : Lowercase<TN> {
const abbreviationNarrowed = abbreviation; //type: G8Abbreviation at broadest
if(abbreviationNarrowed === 'RU') {
console.log('Legacy case handling, not passed on to next function.');
} else {
//This type should be Exclude<TN, 'RU'>, at most Exclude<G8Abbreviation, 'RU'>,
//which in this case happens to be at its broadest G7Abbreviation
const abbreviationNarrowedAgain = abbreviationNarrowed;
//Error 2344: Type '"RU"' is not assignable to type 'G7Abbreviation'.
takesG7WithParam<typeof abbreviationNarrowed>(abbreviationNarrowed);
//Error 2345: Same 1st line as above error but without explanation lines:
takesG7WithParam<typeof abbreviationNarrowedAgain>(abbreviationNarrowedAgain);
takesG7WithParam<typeof abbreviation>(abbreviation); // same as above line
//Next two lines should work the same, but first has error 2345:
//'G8Abbreviation' is not assignable to 'G7Abbreviation'.
takesG7WithoutParam(abbreviationNarrowedAgain);
takesG7WithoutParam(abbreviationNarrowed); //no TS error, but type params are sometimes required!
takesG7WithoutParam(abbreviation); //error because parameters aren't narrowed, not the Issue here
}
return abbreviation.toLowerCase() as Lowercase<TN>;
}
Note that a call to takesG7WithParam
specifying the type parameter as the full union G7Abbreviation
with the parameter abbreviationNarrowed
works, but sometimes the type parameter should be typed more narrowly (and maybe there could be an issue on whether the parameter is assignable to that, but typeof
with the same variable name should help.)
This demonstration doesn’t appear to be affected by known issues with conditional types, nor does it deal with function parameters or ArrayBuffer/Uint8Array types.
🙁 Actual behavior
Const types are not narrowed as expected, except that abbreviationNarrowed
is correctly narrowed for the purpose of assigning type to a value parameter (its typeof
result is not narrowed for the purpose of use as a type parameter though).
🙂 Expected behavior
- The assignment of one constant to another (here, to
abbreviationNarrowedAgain
) does not lead to a new error (e.g. the difference between the first two calls totakesG7WithoutParam
). - Both of the consts are narrowed to
Exclude<G8Abbreviation, 'RU'>
within the else block, for all purposes. - None of the calls involving
abbreviationNarrowed
orabbreviationNarrowedAgain
have errors. - All of the errors should have the additional lines of detail shown on the first one, but only the version of the first error from TS 4.6+. (Worse errors in older versions is not raised as an issue here with the current version.)
- When the type of a value is narrowed for the purpose of use as a value parameter, its
typeof
is also narrowed for the purpose of use as a type parameter. - The two declared functions should be equivalent, except that the one with a type parameter is more readily extensible to add other parameters which are connected to the first one through the generic type parameter.
- Also in the category of expected behavior, the last line shouldn't require a cast, but that's a separate issue (String case change methods should return intrinsic string manipulation types #44268) not core to the issue being raised here.
- Per comment by @MartinJohns below, the error in calls to
takesG7WithoutParam
should note that TN is the type that can't be assigned toG7Abbreviation
rather thanG8Abbreviation
.