Repository navigation
Type narrowing not working correctly with Uppercase<string> & Lowercase<string>Β #63724
Description
Activity
It doesn't look like a bug to me. You are trying to fit a wider type inside a narrower type. The type of
prefixisUppercase<string>which meansany uppercase string. ThecountryCodeis"BE" | "LU" | "NL". You can't fit any uppercase string into something which accepts only the three possible strings hence the error. If you remove theas Uppercase<string>cast it works correctly because now it can be narrowed automatically.Reacted by Joe CalzarettaConsidering
"lu" satisfies Uppercase<string>accurately doesn't compile and"LU" satisfies Uppercase<string>does compile, I believe that TypeScript should have sufficient info to do the same type narrowing withUppercase<string>as withstring.If you remove
as Uppercase<string>,prefixjust becomes juststring, which is a wider type thanUppercase<string>, since you can pass it to anything that is just astring.So this does look like a bug to me.
I see what you mean but when you declare
let countryCode: "BE" | "LU" | "NL";the TS parser knows that "BE", "LU, and "NL" are strings because they are delimited by quotes. However, for the TS parser to know if they are uppercase, lowercase, starting with a capital letter, etc... it needs to parse each of them in several different ways which can be very ineffective especially if they are many. When you do"LU" satisfies Uppercase<string>the parser will just check theLUstring to see if it's uppercase so it's a single check for a single string so it's simple.Reacted by Joe CalzarettaMartinJohns commented
on Aug 6, 2026 ContributorMore actionsThe actual issue is that the compiler can't narrow down
Uppercase<string>toUppercase<"BE">when comparing to such a string. I don't know if there's an open issue for this specifically.This is at the very least a missing feature (if not a bug). One might expect that
Uppercase<string>could be narrowed to an uppercase string literal via equality check. If you write a user-defined type guard then it works as expected:const isEqual = <T, U extends T>(t: T, u: U): t is U => t === u if (isEqual(prefix, "BE") || isEqual(prefix, "LU") || isEqual(prefix, "NL")) { countryCode = prefix; // okay }
Reacted by mikoloism- addedBugA bug in TypeScriptA bug in TypeScriptHelp WantedYou can do thisYou can do this
on Aug 6, 2026 RyanCavanaugh commented
on Aug 6, 2026 MemberMore actionsIt's also interesting that the comparability relation doesn't properly account for this
function fn(foo: Uppercase<string>) { // No error, ? if (foo === "ba") {} } fn("boo"); // error fn("BOO"); // not error
Reacted by mikoloism- addedDomain: check: Control FlowThe issue relates to control flow analysisThe issue relates to control flow analysis
on Aug 11, 2026 I'd like to take this one β I'll follow up with a PR. (claiming via Stella (@LeonxLJX))
Hi! I'd like to fix type narrowing with
Uppercase<string> & Lowercase<string>. Plan: reproduce, adjust the narrowing logic, add a test. May I be assigned?
π Search Terms
"narrowing lowercase" "narrowing uppercase"
π Version & Regression Information
β― Playground Link
https://www.typescriptlang.org/play/?#code/DYUwLgBAxg9grgOzAJwJ4GEYBMQC4IBEAQgKIEQA+hAMgKrlUEBy1BA3AFAewIDOkAB2QgAZgEsAHhAC8hAEYgCAOjAxaAgSGToAhrxAAKAJQQ9EdZuRQ9IADz9kYhAHMAfJw5iREA0NGSZaVliMkoqP3EpIOC6BnDhSMDglgITAG8OCCzoeCQ0TBwZCAjJTgB6MqyAPQ4KrIAVVE0IAHILLWt9exQnNxaIMV4IBBhIPV4xZwQdOVAIVXmmkFaQhhp6SkIUlqVayohG5paHXv7B4dHTXgmpmbmFsCWV0jWCWM3mVh2OAF8gA
π» Code
π Actual behavior
TypeScript cannot narrow down an
Uppercase<string>orLowercase<string>type after explicitly comparing against specific values.π Expected behavior
TypeScript should accept the code above as
"BE" | "LU" | "NL"is strictly a sub-type ofUppercase<string>.Additional information about the issue
No response