From 44fd6c45caba8015ad565dcecf70184de2cbecc9 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sat, 14 Nov 2026 14:05:17 +0000 Subject: [PATCH 1/2] String.match returns string[] | null; lib.ts compiles under strict null match returned null through a result declared string[]. It is declared string[] | null now, in lib.ts and lib.d.ts, and a global match that finds nothing returns null too, as in JavaScript, where it returned an empty array. A caller that used the result without a null check no longer compiles: string_match.ts and string_match_lastIndex.ts check for null first. With that, `// @strict-null false` is gone from lib.ts. The compiler then rejected the null passed to three C functions, whose parameters are declared nullable in lib.native.d.ts: the end pointer of strtol and strtod, and the results argument (and the result) of regexp_exec. -mm=own: match puts `[...result]` in a local before returning it; returned directly as string[] | null it was "a second reference". Tested in release on Windows x64 for gc, rc, none and own, compiled and under the JIT; debug, Linux, x86 and wasm were not built locally. Co-Authored-By: Claude Fable 5.2 --- src/lib.d.ts | 4 ++-- src/lib.ts | 18 ++++++++++++------ src/native/lib.native.d.ts | 6 +++--- tests/string_match.ts | 15 +++++++++++---- tests/string_match_lastIndex.ts | 16 +++++++++++++--- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/lib.d.ts b/src/lib.d.ts index 721330b..150da74 100644 --- a/src/lib.d.ts +++ b/src/lib.d.ts @@ -317,7 +317,7 @@ declare namespace __String { function localeCompare(this: string, compareString: string, locale?: string): int; - function match(this: string, expr: RegExp): string[]; + function match(this: string, expr: RegExp): string[] | null; @borrows("this", "expr") function matchAll(this: string, expr: RegExp): Iterator; @@ -399,7 +399,7 @@ declare class String { localeCompare(this: string, compareString: string, locale?: string): int; - match(this: string, expr: RegExp): string[]; + match(this: string, expr: RegExp): string[] | null; @borrows("this", "expr") matchAll(this: string, expr: RegExp): Iterator; diff --git a/src/lib.ts b/src/lib.ts index 1fc348a..bd167bb 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -4,8 +4,6 @@ /// /// -// @strict-null false - export function parseInt(val: string, radix = 10) { return strtol(val, null, radix); } @@ -948,13 +946,15 @@ namespace __String { return result; } - export function match(this: string, expr: RegExp): string[] { + export function match(this: string, expr: RegExp): string[] | null { if (!expr.global) { const result = expr.exec(this); if (result != null) { - return [...result]; + // -mm=own: into a local first, which the return then moves (as matchAll's yield does) + const groups: string[] = [...result]; + return groups; } return null; @@ -976,7 +976,13 @@ namespace __String { break; } - return results; + // as in JavaScript: null, not an empty array, when nothing matched + if (results.length == 0) + { + return null; + } + + return results; } @borrows("this", "expr") @@ -1500,7 +1506,7 @@ export class String { return this.value.localeCompare(compareString, locale); } - public match(expr: RegExp): string[] { + public match(expr: RegExp): string[] | null { return this.value.match(expr); } diff --git a/src/native/lib.native.d.ts b/src/native/lib.native.d.ts index 6d3f0dd..06d1405 100644 --- a/src/native/lib.native.d.ts +++ b/src/native/lib.native.d.ts @@ -18,8 +18,8 @@ @nodrops declare function time(destTime: Reference); // String -@nodrops declare function strtol(str: string, end: Reference, base: int): int; -@nodrops declare function strtod(str: string, end: Reference): double; +@nodrops declare function strtol(str: string, end: Reference | null, base: int): int; +@nodrops declare function strtod(str: string, end: Reference | null): double; @nodrops declare function strncmp(str: string, other: string, count: index): int; // a pointer into `str`, not a string of its own: a `string` result from C is copied @nodrops declare function strstr(str: string, other: string): Opaque; @@ -111,7 +111,7 @@ declare function GC_gcollect(): void; // regex @nodrops declare function regexp_test(expr: string, flags: string, s: string): int; -@nodrops declare function regexp_exec(expr: string, flags: string, s: string, cm: Opaque): Opaque; +@nodrops declare function regexp_exec(expr: string, flags: string, s: string, cm: Opaque | null): Opaque | null; @nodrops declare function regexp_match_results_size(cm: Opaque): index; @nodrops declare function regexp_match_results_prefix_length(cm: Opaque): index; @nodrops declare function regexp_match_results_suffix_position(cm: Opaque): index; diff --git a/tests/string_match.ts b/tests/string_match.ts index 74fa7ba..03d56c0 100644 --- a/tests/string_match.ts +++ b/tests/string_match.ts @@ -1,11 +1,18 @@ const paragraph = 'The quick brown fox jumps over the lazy dog. It barked.'; const regex = /[A-Z]/g; +// match gives string[] | null, as in JavaScript: null when there is no match const found = paragraph.match(regex); -assert(found.length == 2); -assert(found == "T,I"); +assert(found != null); +if (found != null) { + assert(found.length == 2); + assert(found == "T,I"); -console.log(found); -// Expected output: Array ["T", "I"] + console.log(found); + // Expected output: Array ["T", "I"] +} + +assert(paragraph.match(/[0-9]/) == null, "no match is null"); +assert(paragraph.match(/[0-9]/g) == null, "and with the g flag too, not an empty array"); console.log("ALL DONE"); diff --git a/tests/string_match_lastIndex.ts b/tests/string_match_lastIndex.ts index 7e93ae4..6960e36 100644 --- a/tests/string_match_lastIndex.ts +++ b/tests/string_match_lastIndex.ts @@ -6,7 +6,11 @@ function main() { assert(re.lastIndex == 1, "test moved lastIndex"); const digits = "1a2b3".match(re); - assert(digits == "1,2,3", "match starts at the beginning"); + assert(digits != null, "match finds the digits"); + if (digits != null) { + assert(digits == "1,2,3", "match starts at the beginning"); + } + assert(re.lastIndex == 0, "match leaves lastIndex at 0"); assert(re.exec("1a2b3") != null, "exec finds the first digit"); @@ -19,10 +23,16 @@ function main() { // empty matches const empties = "abc".match(/x*/g); - assert(empties.length == 4, "an empty match at every position"); + assert(empties != null, "empty matches are matches"); + if (empties != null) { + assert(empties.length == 4, "an empty match at every position"); + } const words = "ab cd".match(/\w*/g); - assert(words == "ab,,cd,", "empty matches between the words"); + assert(words != null, "words and the empty matches between them"); + if (words != null) { + assert(words == "ab,,cd,", "empty matches between the words"); + } const emptyAll = [..."ab".matchAll(/x*/g)]; assert(emptyAll.length == 3, "matchAll: an empty match at every position"); From 7246563c753dc013f54e8ebbab1f543392cac1a0 Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Sat, 14 Nov 2026 14:10:07 +0000 Subject: [PATCH 2/2] String.match: a global match that finds nothing is an empty array again The commit before made it null, as JavaScript does. It goes back to the empty array it always was here, so a global match can be walked or counted without a check; only a pattern without the g flag gives null. string_match.ts asserts both. Co-Authored-By: Claude Fable 5.2 --- src/lib.ts | 8 ++------ tests/string_match.ts | 10 ++++++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib.ts b/src/lib.ts index bd167bb..61ad577 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -976,12 +976,8 @@ namespace __String { break; } - // as in JavaScript: null, not an empty array, when nothing matched - if (results.length == 0) - { - return null; - } - + // an empty array when nothing matched, not JavaScript's null: a global match can always be + // walked or counted without a check return results; } diff --git a/tests/string_match.ts b/tests/string_match.ts index 03d56c0..fbbcbb8 100644 --- a/tests/string_match.ts +++ b/tests/string_match.ts @@ -1,6 +1,6 @@ const paragraph = 'The quick brown fox jumps over the lazy dog. It barked.'; const regex = /[A-Z]/g; -// match gives string[] | null, as in JavaScript: null when there is no match +// match gives string[] | null: null when a pattern without the g flag does not match const found = paragraph.match(regex); assert(found != null); @@ -13,6 +13,12 @@ if (found != null) { } assert(paragraph.match(/[0-9]/) == null, "no match is null"); -assert(paragraph.match(/[0-9]/g) == null, "and with the g flag too, not an empty array"); + +// with the g flag no match is an empty array (JavaScript gives null there too) +const none = paragraph.match(/[0-9]/g); +assert(none != null, "a global match is never null"); +if (none != null) { + assert(none.length == 0, "and is empty when nothing matched"); +} console.log("ALL DONE");