Skip to content

Notes about String.replace #54387

Description

@m-gallesio

(Moved from rom microsoft/TypeScript-DOM-lib-generator#1562)

I am using TypeScript 5.0.4.
I have noticed the typing of String.replace seem oddly convoluted.

From lib.es5.d.ts:

interface String {
    replace(searchValue: string | RegExp, replaceValue: string): string;
    replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string;
}

From lib.es2015.symbol.wellknown.d.ts:

interface String {
    replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
    replace(searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, replacer: (substring: string, ...args: any[]) => string): string;
}

Like the first parameter is declared as an union, I would expect the second to be the same, at least in the lib.es5 version.

Notably, this makes the following code not compile:

// The second argument is an union of replaceValue and replacer from lib.es5
const replacementSteps: ([RegExp, string | ((substring: string, ...args: any[]) => string)])[] = [
    [/(a|b)/gi, "a"],
    [/(c|d)/gi, match => match.toUpperCase()],
];

let myString = "abcdef";

for (const [searchValue, replacement] of replacementSteps)
    myString = myString.replace(searchValue, replacement); // TS2345 - the second parameter is considered invalid

By adding this into my declaration, the code works:

declare interface String {
    replace(searchValue: string | RegExp, replaceValue: string | ((substring: string, ...args: any[]) => string)): string;
}

I cannot propose a solution other than my last hack, and of course the variants with Symbol would have to be taken into account. Please consider this just a note.

See also this: #54223

Activity

  1. RyanCavanaugh commented on May 26, 2023

    @RyanCavanaugh
    Member

    I believe the use of overloads here instead of a union was a holdover from when union types couldn't provide contextual types, or maybe even when we didn't have unions at all. I think this can be updated without breaking anything.

  2. graphemecluster commented on Sep 9, 2023

    @graphemecluster
    Contributor

    I am happy to modify #50452 to fix this.

  3. jwasnoggin commented on Jan 19, 2024

    @jwasnoggin

    This definition allows the use of a union while preserving the Symbol.replace bit:

    replace<T extends string | ((substring: string, ...args: any[]) => string)>(searchValue: { [Symbol.replace](string: string, replaceValue: T): string; }, replaceValue: T): string;
  4. electrovir commented on Oct 6, 2025

    @electrovir

    The current implementation also blocks any replaceValue parameter that is a union of the two available types:

    const myReplacer = '' as string | ((substring: string, ...args: any[]) => string);
    
    ''.replace(/whatever/, myReplacer)

    throws:

    No overload matches this call.
      The last overload gave the following error.
        Argument of type 'Replacer' is not assignable to parameter of type '(substring: string, ...args: any[]) => string'.
          Type 'string' is not assignable to type '(substring: string, ...args: any[]) => string'.ts(2769)
    

    playground link

  5. RyanCavanaugh commented on Sep 19, 2026

    @RyanCavanaugh
    Member

    String.prototype.replace accepts either a replacement string or a replacement function at runtime, but the separate lib.d.ts overloads reject a value whose type is the union of those two forms. This still reports TS2769 in TypeScript 5.0.4, 6.0.3, and the current native build.

    declare const replacement: string | ((substring: string, ...args: any[]) => string);
    
    // Expected: replace accepts either a string replacement or a replacement function.
    // Actual: TypeScript rejects the union argument with TS2769.
    "text".replace(/./, replacement);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptDomain: lib.d.tsThe issue relates to the different libraries shipped with TypeScript

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions