Skip to content

Narrow String#toLowerCase/toUpperCase return types via Lowercase/Uppercase#63460

Open
ljharb wants to merge 1 commit into
microsoft:mainfrom
ljharb:lib-string-tocase-literal-types
Open

Narrow String#toLowerCase/toUpperCase return types via Lowercase/Uppercase#63460
ljharb wants to merge 1 commit into
microsoft:mainfrom
ljharb:lib-string-tocase-literal-types

Conversation

@ljharb

@ljharb ljharb commented May 5, 2026

Copy link
Copy Markdown
Contributor

Make String.prototype.toLowerCase and toUpperCase preserve string-literal types in their return type, by typing them as
<T extends string>(this: T): Lowercase<T> and Uppercase<T> respectively.

For non-literal string receivers, Lowercase<string> resolves to string, preserving existing behavior. For literal receivers (e.g. "FOO".toLowerCase()), the result narrows to the corresponding literal ("foo").

Also filed as microsoft/typescript-go#3711

Copilot AI review requested due to automatic review settings May 5, 2026 16:51
@typescript-bot typescript-bot added For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels May 5, 2026
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog May 5, 2026
@typescript-bot

Copy link
Copy Markdown
Contributor

This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise.

1 similar comment
@typescript-bot

Copy link
Copy Markdown
Contributor

This PR doesn't have any linked issues. Please open an issue that references this PR. From there we can discuss and prioritise.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@RyanCavanaugh

Copy link
Copy Markdown
Member

#44268 is not approved

@Renegade334

Copy link
Copy Markdown
Contributor

For non-literal string receivers, Lowercase<string> resolves to string, preserving existing behavior. For literal receivers (e.g. "FOO".toLowerCase()), the result narrows to the corresponding literal ("foo").

That's not the case, Lowercase<string> isn't flattened to string.

interface String {
  foo<T extends string>(this: T): Lowercase<T>;
}

function returnFoo(s: string) {
  return s.foo();
}

let x = returnFoo("bar");
//  ^ Lowercase<string>

x = escape(x);
// TS2322: Type 'string' is not assignable to type 'Lowercase<string>'.

@ljharb

ljharb commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

whoops. i'll try to fix that.

…rcase

Make String.prototype.toLowerCase and toUpperCase preserve string-literal
types in their return type, by typing them as
`<T extends string>(this: T): Lowercase<T>` and `Uppercase<T>` respectively.

For non-literal `string` receivers, `Lowercase<string>` resolves to `string`,
preserving existing behavior. For literal receivers (e.g. `"FOO".toLowerCase()`),
the result narrows to the corresponding literal (`"foo"`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ljharb ljharb force-pushed the lib-string-tocase-literal-types branch from c10ff7d to 7d2272d Compare May 5, 2026 21:47
@ljharb

ljharb commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

@RyanCavanaugh sorry, i didn't realize there was an issue already. is there any reason it can't be approved now?

@RyanCavanaugh

Copy link
Copy Markdown
Member

Is there any reason it can't be approved now?

See discussion in the "Read More" valley starting at #44268 (comment)

@ljharb

ljharb commented May 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, and fair on the "default -100" - but i don't think it should matter if something breaks someone modifying globals or global types; that's the risk they're taking. i'm pretty sure it wouldn't break anyone who was already "polyfilling" this change (as i'm doing). As far as introducing a new type error, if they're using a literal type already, presumably they want it to stay as such, otherwise it'd already be typed as string, no?

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

Labels

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

5 participants