Skip to content

Type narrowing not working correctly with Uppercase<string> & Lowercase<string>Β #63724

Description

πŸ”Ž Search Terms

"narrowing lowercase" "narrowing uppercase"

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about Common Bugs and Type System Behavior

⏯ Playground Link

https://www.typescriptlang.org/play/?#code/DYUwLgBAxg9grgOzAJwJ4GEYBMQC4IBEAQgKIEQA+hAMgKrlUEBy1BA3AFAewIDOkAB2QgAZgEsAHhAC8hAEYgCAOjAxaAgSGToAhrxAAKAJQQ9EdZuRQ9IADz9kYhAHMAfJw5iREA0NGSZaVliMkoqP3EpIOC6BnDhSMDglgITAG8OCCzoeCQ0TBwZCAjJTgB6MqyAPQ4KrIAVVE0IAHILLWt9exQnNxaIMV4IBBhIPV4xZwQdOVAIVXmmkFaQhhp6SkIUlqVayohG5paHXv7B4dHTXgmpmbmFsCWV0jWCWM3mVh2OAF8gA

πŸ’» Code

let countryCode: "BE" | "LU" | "NL";

const prefix = "be".toUpperCase() as Uppercase<string>;

if (prefix === "BE" || prefix === "LU" || prefix === "NL") {
    countryCode = prefix;
//  ^
//  Type 'Uppercase<string>' is not assignable to type '"BE" | "LU" | "NL"'.
//  Type 'string' is not assignable to type '"BE" | "LU" | "NL"'.
}

πŸ™ Actual behavior

TypeScript cannot narrow down an Uppercase<string> or Lowercase<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 of Uppercase<string>.

Additional information about the issue

No response

Activity

  1. alex-vukov commented on Aug 6, 2026

    @alex-vukov

    It doesn't look like a bug to me. You are trying to fit a wider type inside a narrower type. The type of prefix is Uppercase<string> which means any uppercase string. The countryCode is "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 the as Uppercase<string> cast it works correctly because now it can be narrowed automatically.

  2. wartab commented on Aug 6, 2026

    @wartab
    Author

    Considering "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 with Uppercase<string> as with string.

    If you remove as Uppercase<string>, prefix just becomes just string, which is a wider type than Uppercase<string>, since you can pass it to anything that is just a string.

    So this does look like a bug to me.

  3. alex-vukov commented on Aug 6, 2026

    @alex-vukov

    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 the LU string to see if it's uppercase so it's a single check for a single string so it's simple.

  4. MartinJohns commented on Aug 6, 2026

    @MartinJohns
    Contributor

    The actual issue is that the compiler can't narrow down Uppercase<string> to Uppercase<"BE"> when comparing to such a string. I don't know if there's an open issue for this specifically.

  5. jcalz commented on Aug 6, 2026

    @jcalz
    Contributor

    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
    }

    Playground link

  6. RyanCavanaugh commented on Aug 6, 2026

    @RyanCavanaugh
    Member

    It'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
  7. LeonxLJX commented on Sep 1, 2026

    @LeonxLJX

    I'd like to take this one β€” I'll follow up with a PR. (claiming via Stella (@LeonxLJX))

  8. LeonxLJX commented on Sep 1, 2026

    @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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions