Skip to content

DOMString and friends are too narrow #1003

Open

Description

@relrelb

DOMString, USVString, CSSOMString and ByteString are currently mapped to simply string:

...[...stringTypes].map((type) => [type, "string"] as [string, string]),

However it doesn't include objects that can be stringified using the ToString abstract operation.
For example, the following code is valid in JavaScript but doesn't compile in TypeScript:

new URL(window.location); // Error: Argument of type 'Location' is not assignable to parameter of type 'string'.

So I think this is a more general case of #331.

A possible solution would be to map them to string | { toString(): string } instead, but that would pollute many DOM APIs. For example, it would make even the following code to compile without errors, which is not necessarily desired:

new URL({ toString() { return "hello"; } });

A restriction that can be made is allowing only interfaces with stringifiers (URL is among them). From my understanding there are not so many such interfaces, so this wouldn't be much wider than string.

Activity

  1. changed the title [-]DOMString and friends are not handled properly[/-] [+]DOMString and friends are too narrow[/+] on Apr 23, 2021
  2. jsjoeio commented on Nov 17, 2021

    @jsjoeio

    Funny enough, my coworker Asher (@code-asher) and I were just discussing this — specifically the new URL(window.location); example.

    We were wondering if the type definition for new URL should be modified to accept Location but it sounds like that is only a partial solution.

    We're using new URL(window.location.toString()); as a current workaround.

  3. djrenren commented on Sep 4, 2025

    @djrenren

    Ran into this with CSSStyleDeclaration.setProperty which has the following type in typescript:

        setProperty(property: string, value: string | null, priority?: string): void;
    

    It's cool that it picks up the [LegacyNullToEmptyString] annotation on value but it's a pain that it doesn't support the string coercion from the spec.

    Specifically it's a pain that element.style.setProperty('x', 3.7); is rejected by TS when it's perfectly valid according to the spec.

    If we look at the DOMString definition:

    A JavaScript value V is converted to an IDL DOMString value by running the following algorithm:

    1. If V is null and the conversion is to an IDL type associated with the [LegacyNullToEmptyString] extended attribute, then return the DOMString value that represents the empty string.

    2. Let x be ? ToString(V).

    3. Return the IDL DOMString value that represents the same sequence of code units as the one the JavaScript String value x represents.

    We see that currently point 1 is handled (as seen with value above). But Point 2 is ignored.

    If we look up the definition of ToString in the ecmascript spec we get:

    1. If argument is a String, return argument.
    2. If argument is a Symbol, throw a TypeError exception.
    3. If argument is undefined, return "undefined".
    4. If argument is null, return "null".
    5. If argument is true, return "true".
    6. If argument is false, return "false".
    7. If argument is a Number, return Number::toString(argument, 10).
    8. If argument is a BigInt, return BigInt::toString(argument, 10).
    9. Assert: argument is an Object.
    10. Let primValue be ? ToPrimitive(argument, string).
    11. Assert: primValue is not an Object.
    12. Return ? ToString(primValue).

    Steps 1-8 are easy to support by making DOMString map to:

    type DOMStringInput = string | undefined | null | boolean | number | BigInt
    

    Note that this type is DOMStringInput not DOMString because the conversion only occurs on the JS -> DOMString side, and the type should not be used for IDL methods that return DOMString

    Steps 10-12 are tricky because it's not as simple as checking for a toString(): string method (as suggested above) because it can go unused if toPrmitive is defined (and if it is defined, we have no good way of knowing

    type ToStringable = { toString(): string; [Symbol.toPrimitive]?: (hint: string) => DOMStringInput } // Slight approximation, the spec just says "Not an Object"
    type DOMStringInput = string | undefined | null | boolean | number | BigInt | ToStringable
    

    Which is getting a bit convoluted. Anyway it's unclear if this change, which would allow more programs, would encourage correct programs, and should be supported or not, but some guidance from the project would be cool here.

  4. HolgerJeromin commented on Sep 5, 2025

    @HolgerJeromin
    Contributor

    In the past the maintainer stated that typescript should not mimic 100% the spec but be useful.
    https://github.com/microsoft/TypeScript/wiki/TypeScript-Design-Goals

    Non-goal:
    Apply a sound or "provably correct" type system. Instead, strike a balance between correctness and productivity.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions