Skip to content

Native tsc reads a literal U+FFFD in a regex character class as 0xEF (false TS1517 / TS1516) #65076

Open

Description

🔎 Search Terms

regex U+FFFD, replacement character in regex, character class range, Range out of order in character class, TS1517, TS1516, native tsc, tsgo

🕗 Version & Regression Information

  • This changed between versions 6.0.3 and 7.0.2. 6.0.3 accepts the code. 7.0.2, 7.1.0-beta and 7.1.0-dev.20261031.1 all report it.

⏯ Playground Link

No response

💻 Code

The repro needs a literal U+FFFD character, which tends to get mangled by copy and paste, so this writes the file and runs the nightly:

node -e 'require("fs").writeFileSync("repro.ts", "export const r = /[\\uFFF9-\uFFFD]/\n")'
npx -p typescript@next tsc --noEmit repro.ts

repro.ts ends up as this. The last character in the class is a literal U+FFFD (bytes EF BF BD):

export const r = /[\uFFF9-�]/

🙁 Actual behavior

repro.ts(1,20): error TS1517: Range out of order in character class.

U+FFF9 to U+FFFD is in order, so the range is fine. With the u flag the same class gets a different wrong error:

error TS1516: A character class range must not be bounded by another character class.

🙂 Expected behavior

No error, same as 6.0.3. The regex is valid at runtime and matches U+FFF9 through U+FFFD.

Additional information about the issue

It looks like a literal U+FFFD gets read as 0xEF, the first byte of its UTF-8 encoding. Changing only the start of the range:

Character class 6.0.3 7.0.2 and 7.1.0-dev.20261031.1
[\u00EF-�] ok ok
[\u00F0-�] ok TS1517
[\u0100-�] ok TS1517
[\uFFF9-�] ok TS1517
[\uFFF9-\uFFFD] (escaped) ok ok

A lone literal U+FFFD (/�/) and one that starts a range (/[�-\uFFFF]/) are both fine on all three versions, and so is a string literal: const s: "\uFFFD" = "�" type-checks. So it seems specific to the regex scanner.

My guess, without having read the code: Go's utf8.RuneError is U+FFFD, so a check like r == utf8.RuneError treats a real U+FFFD as invalid UTF-8 unless it also looks at the size (3 bytes for a real U+FFFD, 1 for bad input).

I ran into this in a test that checks some text has no characters in that block, and worked around it with the escaped form.

Activity

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

      ⌂ Proxy