mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Pin the Intl-over-ICU forms against Node in the corpus
Three corpus programs execute the new forms under both Node and the compiled binary: default-locale number formatting (grouping, CLDR fraction digits, infinities, -0, the capability probe), default-locale Date formatting and time formatting, and word segmentation (segments, isWordLike, re-iteration, containing). Their ts7 order/preflight baselines record each as one module with no diagnostics. The harness learns two directives: `@env: NAME=VALUE` pins TZ/LC_ALL for BOTH sides (the directive is part of the program bytes the oracle cache key hashes), and `@requires-icu` skips programs on hosts whose packs do not carry ICU in this slice. The npm-static word-segmenter pilot no longer expects the package's load-time segmenter to fence into a Node island: word segmentation lowers now, so the package compiles statically (checked on the linux-x64 pack; other machines check the analysis and stop before the link). The coverage snapshots and the mixed/tier fixtures move with the narrowed refusal text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
77faf0c9e6
commit
995eb54dbf
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/intl-date-tolocalestring-default.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/intl-number-tolocalestring-default.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/intl-word-segmenter.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// @requires-icu
|
||||
// @env: TZ=UTC,LC_ALL=de_DE.UTF-8
|
||||
// Date.prototype.toLocaleString() and toLocaleTimeString() with no
|
||||
// arguments: the DEFAULT locale's date-time and time patterns, in the host
|
||||
// time zone (scr_intl.c asks ICU for the best pattern of the skeleton V8
|
||||
// asks for, "yMdjms" and "jms", and applies V8's U+202F → U+0020 revert).
|
||||
// The corpus pins TZ and LC_ALL so the two sides read the same zone and
|
||||
// locale; everything below — field order, separators, the 24-hour cycle
|
||||
// this locale prefers, and the Invalid Date text — compares byte for byte.
|
||||
const dates = [
|
||||
new Date(0),
|
||||
new Date(1700000000000),
|
||||
new Date(1234567890123),
|
||||
new Date(2024, 1, 29, 0, 0, 0),
|
||||
new Date(1999, 11, 31, 23, 59, 59, 999),
|
||||
];
|
||||
for (const d of dates) console.log(d.toLocaleString(), "|", d.toLocaleTimeString());
|
||||
|
||||
// The morning/afternoon boundary and the yMdjms pattern's own separators.
|
||||
console.log(new Date(2023, 10, 14, 9, 5, 7).toLocaleString());
|
||||
console.log(new Date(2023, 10, 14, 13, 5, 7).toLocaleString());
|
||||
console.log(new Date(2023, 10, 14, 13, 5, 7).toLocaleTimeString());
|
||||
|
||||
// An invalid date answers "Invalid Date" on both methods.
|
||||
const invalid = new Date(NaN);
|
||||
console.log(invalid.toLocaleString(), "|", invalid.toLocaleTimeString());
|
||||
|
||||
// The results are ordinary strings.
|
||||
const label = `Zeit: ${new Date(1700000000000).toLocaleTimeString()}`;
|
||||
console.log(label, label.length, new Date(0).toLocaleString().length);
|
||||
@@ -0,0 +1,34 @@
|
||||
// @requires-icu
|
||||
// @env: LC_ALL=de_DE.UTF-8
|
||||
// Number.prototype.toLocaleString() at the DEFAULT locale, which is the one
|
||||
// form of the method that only ICU can answer (scr_intl.c over the vendored
|
||||
// ICU archive): grouping, the decimal separator and the CLDR fraction-digit
|
||||
// rules come from the locale's data, not from the engine-free en-US path
|
||||
// that 2562-intl-numberformat-en-us.ts pins. The bare call and an explicit
|
||||
// undefined are the same operation in EMCAScript; both lower here.
|
||||
const xs = [0, 1, 100, 1000, 1234.5, 1234567.891, -9876543.21, 0.5, 1e6, 1e21];
|
||||
for (const x of xs) console.log(x.toLocaleString());
|
||||
console.log((1234.5).toLocaleString(undefined), (-0).toLocaleString());
|
||||
console.log((0.0625).toLocaleString(), (1.0005).toLocaleString(), (7.995).toLocaleString());
|
||||
console.log((NaN).toLocaleString(), (1 / 0).toLocaleString(), (-1 / 0).toLocaleString());
|
||||
console.log((5e-4).toLocaleString(), (1e-7).toLocaleString(), (5e-324).toLocaleString());
|
||||
|
||||
// The result is an ordinary string: template interpolation, concatenation
|
||||
// and methods all see the formatted text.
|
||||
const label = `${(1234.5).toLocaleString()} Einheiten`;
|
||||
console.log(label, label.length, (42).toLocaleString().padStart(8, "."));
|
||||
const mapped = xs.map((x) => x.toLocaleString());
|
||||
console.log(mapped.join("|"));
|
||||
|
||||
// An explicit undefined argument may carry effects and is evaluated first.
|
||||
let order = "";
|
||||
function undef(value: string): undefined {
|
||||
order += value;
|
||||
return undefined;
|
||||
}
|
||||
console.log((1234.5).toLocaleString(undef("a")), order);
|
||||
|
||||
// The capability probe Node answers from its own ICU build: an Intl program
|
||||
// links ICU, so the binary's answer is Node's (the number 1), and reading it
|
||||
// is what links ICU.
|
||||
console.log(process.config.variables.v8_enable_i18n_support);
|
||||
@@ -0,0 +1,45 @@
|
||||
// @requires-icu
|
||||
// @env: LC_ALL=en_US.UTF-8
|
||||
// Intl.Segmenter({ granularity: "word" }) — ICU's UAX #29 word break
|
||||
// iterator (scr_intl.c over the ICU archive), which is the one
|
||||
// Intl form pi's tui layer needs: the segments carry isWordLike, exactly as
|
||||
// Node's do. Word boundaries at the default locale and under a locale tag,
|
||||
// punctuation and digits, re-iteration, and the containing() lookup all pin
|
||||
// byte-identically against Node.
|
||||
const text = "Hello, world! It's 3.14 — don't stop, Übermensch.";
|
||||
const segments = new Intl.Segmenter(undefined, { granularity: "word" }).segment(text);
|
||||
for (const { segment, index, input, isWordLike } of segments) {
|
||||
console.log(JSON.stringify(segment), index, input === text, isWordLike);
|
||||
}
|
||||
|
||||
// The same input through a locale tag: German word breaking is still
|
||||
// UAX #29 root rules at heart, but the segmenter must accept the tag.
|
||||
const german = new Intl.Segmenter("de", { granularity: "word" }).segment("Straße 42, größer.");
|
||||
let words = "";
|
||||
let wordCount = 0;
|
||||
for (const part of german) {
|
||||
if (part.isWordLike === true) {
|
||||
words += `${wordCount === 0 ? "" : "|"}${part.segment}`;
|
||||
wordCount += 1;
|
||||
}
|
||||
}
|
||||
console.log(words, wordCount);
|
||||
|
||||
// Re-iterating one Segments object restarts from the first segment.
|
||||
const again = new Intl.Segmenter("en", { granularity: "word" }).segment("one two");
|
||||
let first = "";
|
||||
for (const part of again) first += `[${part.segment}]`;
|
||||
let second = "";
|
||||
for (const part of again) second += `[${part.segment}]`;
|
||||
console.log(first, second, first === second);
|
||||
|
||||
// containing() answers the segment covering a position and undefined past
|
||||
// the end, the same contract as the grapheme segmenter's.
|
||||
const lookup = new Intl.Segmenter("en", { granularity: "word" }).segment("a b");
|
||||
for (const position of [-1, 0, 1, 2, 3, 4]) {
|
||||
const result = lookup.containing(position);
|
||||
console.log(
|
||||
position,
|
||||
result === undefined ? "none" : `${result.index}:${result.segment}:${result.isWordLike}`,
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,6 @@ scriptc coverage tests/coverage-fixtures/dynamic-mix.ts
|
||||
in dynamic tier 2 (operate on values typed only at runtime; --explain-dynamic lists them)
|
||||
|
||||
blockers:
|
||||
×1 SC2020 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
at tests/coverage-fixtures/dynamic-mix.ts:6:12
|
||||
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
|
||||
×1 SC2020 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
|
||||
at tests/coverage-fixtures/dynamic-mix.ts:6:35
|
||||
hint: what lowers is the default locale (no argument) and the composed en-US form — other locales are ICU data the binary does not negotiate yet
|
||||
@@ -10,7 +10,5 @@
|
||||
<- SC1090 reflection at tests/coverage-fixtures/tier-demotions.ts:10:1: assignment to non-variables are not supported yet
|
||||
-> function <top level> turns dynamic
|
||||
function <top level> tests/coverage-fixtures/tier-demotions.ts:1:1 (1 site inside)
|
||||
function precise tests/coverage-fixtures/tier-demotions.ts:21:1 (1 site inside)
|
||||
binding n in precise tests/coverage-fixtures/tier-demotions.ts:21:18 (1 request)
|
||||
<- SC2020 operation at tests/coverage-fixtures/tier-demotions.ts:22:10: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
-> function precise turns dynamic
|
||||
function precise tests/coverage-fixtures/tier-demotions.ts:21:1 (1 request, 1 site inside)
|
||||
<- SC2020 operation at tests/coverage-fixtures/tier-demotions.ts:22:27: 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
|
||||
@@ -8,6 +8,6 @@ scriptc coverage tests/coverage-fixtures/tier-demotions.ts
|
||||
×1 SC1090 assignment to non-variables
|
||||
at tests/coverage-fixtures/tier-demotions.ts:10:1
|
||||
hint: this form is valid TypeScript but does not compile yet — rewrite it with a supported equivalent of the construct the message names (https://scriptc.dev/limitations lists the boundary)
|
||||
×1 SC2020 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
at tests/coverage-fixtures/tier-demotions.ts:22:10
|
||||
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
|
||||
×1 SC2020 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
|
||||
at tests/coverage-fixtures/tier-demotions.ts:22:27
|
||||
hint: what lowers is the default locale (no argument) and the composed en-US form — other locales are ICU data the binary does not negotiate yet
|
||||
@@ -1,17 +1,17 @@
|
||||
date-formatters.ts:4:1 - error SC2020: 'Date.prototype.toLocaleTimeString' is part of the standard library types but has no scriptc lowering yet
|
||||
date-formatters.ts:6:1 - error SC2020: 'Date.prototype.toLocaleDateString' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
3 | const date = new Date(0);
|
||||
4 | date.toLocaleTimeString();
|
||||
5 | const date = new Date(0);
|
||||
6 | date.toLocaleDateString();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
5 | date.toLocaleString();
|
||||
7 | date.toLocaleString("en-US");
|
||||
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), toLocaleString(), toLocaleTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale and options arguments, toLocaleDateString() and the Intl.DateTimeFormat surface have no lowering
|
||||
|
||||
date-formatters.ts:5:1 - error SC2020: 'Date.prototype.toLocaleString' is part of the standard library types but has no scriptc lowering yet
|
||||
date-formatters.ts:7:1 - error SC2020: 'Date.prototype.toLocaleString with arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
4 | date.toLocaleTimeString();
|
||||
5 | date.toLocaleString();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~
|
||||
6 |
|
||||
6 | date.toLocaleDateString();
|
||||
7 | date.toLocaleString("en-US");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
8 |
|
||||
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale formatters have no lowering
|
||||
hint: getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), toLocaleString(), toLocaleTimeString(), the local/UTC calendar getters and setters, setTime(), and getTimezoneOffset() are supported; locale and options arguments, toLocaleDateString() and the Intl.DateTimeFormat surface have no lowering
|
||||
@@ -1,8 +1,8 @@
|
||||
dynamic-surface.ts:13:15 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
dynamic-surface.ts:13:38 - error SC2020: 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
12 | const tau = Math.PI * 2;
|
||||
13 | const price = (19.99).toLocaleString();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||
13 | const price = (19.99).toLocaleString("de-DE");
|
||||
| ^~~~~~~
|
||||
14 | const n = Number.parseFloat("3.14");
|
||||
|
||||
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
|
||||
hint: what lowers is the default locale (no argument) and the composed en-US form — other locales are ICU data the binary does not negotiate yet
|
||||
@@ -1,26 +1,26 @@
|
||||
intl-segmenter.ts:2:1 - error SC2020: 'Intl.Segmenter locale negotiation' is part of the standard library types but has no scriptc lowering yet
|
||||
intl-segmenter.ts:4:1 - error SC2020: 'Intl.Segmenter locale negotiation' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
1 | // The native implementation covers Unicode grapheme segmentation.
|
||||
2 | new Intl.Segmenter(["en-US", "de"]);
|
||||
3 | // sentence segmentation stay fenced.
|
||||
4 | new Intl.Segmenter(["en-US", "de"]);
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
3 | new Intl.Segmenter(undefined, { granularity: "word" });
|
||||
5 | new Intl.Segmenter(undefined, { granularity: "word", localeMatcher: "best fit" });
|
||||
|
||||
hint: omit the locale, pass undefined, or pass one language tag as a string literal
|
||||
|
||||
intl-segmenter.ts:3:31 - error SC2020: 'Intl.Segmenter options' is part of the standard library types but has no scriptc lowering yet
|
||||
intl-segmenter.ts:5:31 - error SC2020: 'Intl.Segmenter options' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
2 | new Intl.Segmenter(["en-US", "de"]);
|
||||
3 | new Intl.Segmenter(undefined, { granularity: "word" });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~
|
||||
4 | new Intl.Segmenter(undefined, { granularity: "sentence" });
|
||||
4 | new Intl.Segmenter(["en-US", "de"]);
|
||||
5 | new Intl.Segmenter(undefined, { granularity: "word", localeMatcher: "best fit" });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
6 | new Intl.Segmenter(undefined, { granularity: "sentence" });
|
||||
|
||||
hint: the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported
|
||||
hint: the native segmenter supports default options, { granularity: "grapheme" } and { granularity: "word" }; sentence segmentation remains unsupported
|
||||
|
||||
intl-segmenter.ts:4:31 - error SC2020: 'Intl.Segmenter options' is part of the standard library types but has no scriptc lowering yet
|
||||
intl-segmenter.ts:6:31 - error SC2020: 'Intl.Segmenter options' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
3 | new Intl.Segmenter(undefined, { granularity: "word" });
|
||||
4 | new Intl.Segmenter(undefined, { granularity: "sentence" });
|
||||
5 | new Intl.Segmenter(undefined, { granularity: "word", localeMatcher: "best fit" });
|
||||
6 | new Intl.Segmenter(undefined, { granularity: "sentence" });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
5 | // End of the diagnostic fixture.
|
||||
7 | // End of the diagnostic fixture.
|
||||
|
||||
hint: the native segmenter supports default options and { granularity: "grapheme" }; word and sentence segmentation remain unsupported
|
||||
hint: the native segmenter supports default options, { granularity: "grapheme" } and { granularity: "word" }; sentence segmentation remains unsupported
|
||||
@@ -32,7 +32,7 @@ intl.ts:13:7 - error SC2020: 'NumberFormat' is part of the standard library type
|
||||
| ^~~~~~~
|
||||
14 | const viaValue = hoisted.format(1234.5);
|
||||
|
||||
hint: Intl formatter values have no representation — the COMPOSED en-US forms lower: new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options; the rest is ICU locale data the binary does not carry
|
||||
hint: Intl formatter values have no representation — the COMPOSED forms lower: new Intl.NumberFormat("en-US").format(x), x.toLocaleString("en-US") and x.toLocaleString() with default options, d.toLocaleString(), d.toLocaleTimeString(), and Intl.Segmenter with grapheme or word granularity; the formatter objects themselves (locale negotiation, resolvedOptions, options bags) remain unsupported
|
||||
|
||||
intl.ts:13:17 - error SC2020: 'new Intl.NumberFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
@@ -50,67 +50,58 @@ intl.ts:14:18 - error SC2020: 'NumberFormat.format' is part of the standard libr
|
||||
| ^~~~~~~~~~~~~~
|
||||
15 |
|
||||
|
||||
hint: locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options, plus default Unicode grapheme segmentation with Intl.Segmenter; locale negotiation, word/sentence segmentation, and resolvedOptions remain unsupported
|
||||
hint: collation and locale negotiation stay outside the static runtime (the localeCompare stance: code-unit order, no collation) — what lowers: the composed new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options, their default-locale forms (x.toLocaleString(), d.toLocaleString(), d.toLocaleTimeString()), and Intl.Segmenter with grapheme or word granularity; sentence segmentation, resolvedOptions, and the remaining Intl constructors remain unsupported
|
||||
|
||||
intl.ts:17:20 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:17:40 - error SC2020: 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
16 | // toLocaleString's unlowered variants.
|
||||
17 | const envDefault = (1234.5).toLocaleString();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
18 | const german = (1234.5).toLocaleString("de-DE");
|
||||
|
||||
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
|
||||
|
||||
intl.ts:18:40 - error SC2020: 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
17 | const envDefault = (1234.5).toLocaleString();
|
||||
18 | const german = (1234.5).toLocaleString("de-DE");
|
||||
17 | const german = (1234.5).toLocaleString("de-DE");
|
||||
| ^~~~~~~
|
||||
19 | const grouped = (1234.5).toLocaleString("en-US", { useGrouping: false });
|
||||
18 | const grouped = (1234.5).toLocaleString("en-US", { useGrouping: false });
|
||||
|
||||
hint: "en-US" (Node's default-build locale) is the one locale whose data the runtime embeds — everything else is ICU data the binary does not carry
|
||||
hint: what lowers is the default locale (no argument) and the composed en-US form — other locales are ICU data the binary does not negotiate yet
|
||||
|
||||
intl.ts:19:17 - error SC2020: 'Number.prototype.toLocaleString with an options bag' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:18:17 - error SC2020: 'Number.prototype.toLocaleString with an options bag' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
18 | const german = (1234.5).toLocaleString("de-DE");
|
||||
19 | const grouped = (1234.5).toLocaleString("en-US", { useGrouping: false });
|
||||
17 | const german = (1234.5).toLocaleString("de-DE");
|
||||
18 | const grouped = (1234.5).toLocaleString("en-US", { useGrouping: false });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
20 |
|
||||
19 |
|
||||
|
||||
hint: the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction digits, grouping) — x.toLocaleString("en-US")
|
||||
hint: what lowers is the DEFAULT options shape (decimal notation, up to 3 fraction digits, grouping) at the default locale or at en-US — omit the options bag
|
||||
|
||||
intl.ts:22:14 - error SC2020: 'new Intl.DateTimeFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:21:14 - error SC2020: 'new Intl.DateTimeFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
21 | // The rest of Intl keeps the ICU story.
|
||||
22 | const when = new Intl.DateTimeFormat("en-US").format();
|
||||
20 | // The rest of Intl keeps the ICU story.
|
||||
21 | const when = new Intl.DateTimeFormat("en-US").format();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
23 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
22 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
intl.ts:23:13 - error SC2020: 'new Intl.DurationFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:22:13 - error SC2020: 'new Intl.DurationFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
22 | const when = new Intl.DateTimeFormat("en-US").format();
|
||||
23 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
21 | const when = new Intl.DateTimeFormat("en-US").format();
|
||||
22 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
24 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
23 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
intl.ts:24:14 - error SC2020: 'new Intl.PluralRules' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:23:14 - error SC2020: 'new Intl.PluralRules' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
23 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
24 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
22 | const dur = new Intl.DurationFormat("en-US").format({ hours: 1, minutes: 23 });
|
||||
23 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
25 | const canon = Intl.getCanonicalLocales("en-us");
|
||||
24 | const canon = Intl.getCanonicalLocales("en-us");
|
||||
|
||||
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
intl.ts:25:15 - error SC2020: 'Intl.getCanonicalLocales' is part of the standard library types but has no scriptc lowering yet
|
||||
intl.ts:24:15 - error SC2020: 'Intl.getCanonicalLocales' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
24 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
25 | const canon = Intl.getCanonicalLocales("en-us");
|
||||
23 | const rule = new Intl.PluralRules("en-US").select(1);
|
||||
24 | const canon = Intl.getCanonicalLocales("en-us");
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||
26 |
|
||||
25 |
|
||||
|
||||
hint: locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options, plus default Unicode grapheme segmentation with Intl.Segmenter; locale negotiation, word/sentence segmentation, and resolvedOptions remain unsupported
|
||||
hint: collation and locale negotiation stay outside the static runtime (the localeCompare stance: code-unit order, no collation) — what lowers: the composed new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options, their default-locale forms (x.toLocaleString(), d.toLocaleString(), d.toLocaleTimeString()), and Intl.Segmenter with grapheme or word granularity; sentence segmentation, resolvedOptions, and the remaining Intl constructors remain unsupported
|
||||
@@ -32,7 +32,7 @@ stdlib-fence.ts:20:7 - error SC2020: 'NumberFormat' is part of the standard libr
|
||||
| ^~~
|
||||
21 |
|
||||
|
||||
hint: Intl formatter values have no representation — the COMPOSED en-US forms lower: new Intl.NumberFormat("en-US").format(x) and x.toLocaleString("en-US") with default options; the rest is ICU locale data the binary does not carry
|
||||
hint: Intl formatter values have no representation — the COMPOSED forms lower: new Intl.NumberFormat("en-US").format(x), x.toLocaleString("en-US") and x.toLocaleString() with default options, d.toLocaleString(), d.toLocaleTimeString(), and Intl.Segmenter with grapheme or word granularity; the formatter objects themselves (locale negotiation, resolvedOptions, options bags) remain unsupported
|
||||
|
||||
stdlib-fence.ts:20:13 - error SC2020: 'new Intl.NumberFormat' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
@@ -77,14 +77,14 @@ stdlib-fence.ts:39:31 - error SC1090: '.split()' on a 'RegExp | string' separato
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
40 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer)
|
||||
|
||||
stdlib-fence.ts:41:19 - error SC2020: 'Number.prototype.toLocaleString without a locale' is part of the standard library types but has no scriptc lowering yet
|
||||
stdlib-fence.ts:41:43 - error SC2020: 'Number.prototype.toLocaleString at locale "de-DE"' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
40 | const fixed = (1.5).toFixed(); // digit-free toFixed lowers now (the static ties-up integer)
|
||||
41 | const localized = (1234.5).toLocaleString();
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
41 | const localized = (1234.5).toLocaleString("de-DE");
|
||||
| ^~~~~~~
|
||||
42 | // Unlowered call FORMS of lowered members. (push is fully variadic now —
|
||||
|
||||
hint: the default locale is the host environment's, which a compiled binary cannot carry — pass it explicitly: x.toLocaleString("en-US")
|
||||
hint: what lowers is the default locale (no argument) and the composed en-US form — other locales are ICU data the binary does not negotiate yet
|
||||
|
||||
stdlib-fence.ts:47:1 - error SC2020: '.forEach with 2 arguments' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 6283, "san": 187569 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5069, "san": 54111 }
|
||||
@@ -0,0 +1 @@
|
||||
{ "plain": 5195, "san": 90586 }
|
||||
@@ -107,6 +107,25 @@ function wantsTransformTypes(file: string): boolean {
|
||||
return programInputs(file).some((f) => /\benum\s+[A-Za-z_$]/.test(readFileSync(f, "utf8")));
|
||||
}
|
||||
|
||||
/** `// @env: NAME=VALUE[,NAME=VALUE]` in the entry file's directive head:
|
||||
* the variables are set for BOTH sides — the Node oracle and the compiled
|
||||
* binary — so a program can pin the ambient state that decides Intl output
|
||||
* (TZ for the time zone, LC_ALL/LANG for the default locale). The directive
|
||||
* lives in the program bytes the oracle cache key hashes, so a recorded
|
||||
* verdict can never be replayed under different variables. */
|
||||
function programEnv(file: string): Record<string, string> {
|
||||
const env: Record<string, string> = {};
|
||||
for (const line of directiveHead(file)) {
|
||||
const m = /^\/\/ @env:\s*(.+?)\s*$/.exec(line);
|
||||
if (m === null) continue;
|
||||
for (const pair of m[1]!.split(",")) {
|
||||
const eq = pair.indexOf("=");
|
||||
if (eq > 0) env[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
||||
}
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
/** `// @tsc-decorators` in the entry file's directive head: the Node side
|
||||
* runs tsc's ES-decorator downlevel of the program (target ES2022) —
|
||||
* decorators are the one supported construct Node cannot execute AT ALL
|
||||
@@ -156,6 +175,20 @@ function wantsNoDeprecation(file: string): boolean {
|
||||
return directiveHead(file).some((l) => /^\/\/ @no-deprecation\s*$/.test(l));
|
||||
}
|
||||
|
||||
/** `// @requires-icu` in the directive head: the program's Intl forms are
|
||||
* answered by ICU, which the runtime packs build only for the Linux x64
|
||||
* target in this slice. Other lanes (the macOS-native contracts, the wasm
|
||||
* and Windows packs) skip these programs instead of failing: the compiler
|
||||
* refuses the forms there with SC3002. */
|
||||
function requiresIcu(file: string): boolean {
|
||||
return directiveHead(file).some((l) => /^\/\/ @requires-icu\s*$/.test(l));
|
||||
}
|
||||
|
||||
/** Whether this machine's runtime pack can link ICU (see requiresIcu). */
|
||||
function icuAvailable(): boolean {
|
||||
return process.platform === "linux" && process.arch === "x64";
|
||||
}
|
||||
|
||||
/** The Node oracle's argv for a corpus entry (shims + the per-program
|
||||
* transform flag). */
|
||||
/** `// @disallow-code-generation` in the directive head: the Node oracle
|
||||
@@ -187,7 +220,11 @@ function nodeOracleArgs(file: string): string[] {
|
||||
* (readFileSync(0)), and the default open pipe would block both sides
|
||||
* forever — this way Node and the native binary see the same empty,
|
||||
* closed stream. */
|
||||
async function runBinary(cmd: string, args: string[]): Promise<RunResult> {
|
||||
async function runBinary(
|
||||
cmd: string,
|
||||
args: string[],
|
||||
env: Record<string, string> = {},
|
||||
): Promise<RunResult> {
|
||||
// Linux fork/exec race (observed on the sandbox lane, llvm flavor): a
|
||||
// sibling worker's fork can inherit the freshly-linked binary's write
|
||||
// fd across its own spawn window, and exec answers ETXTBSY until that
|
||||
@@ -195,7 +232,13 @@ async function runBinary(cmd: string, args: string[]): Promise<RunResult> {
|
||||
// briefly, the npm/cargo stance. (No-op on macOS: exec there never
|
||||
// answers ETXTBSY.)
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
const pending = execFileAsync(cmd, args, { encoding: "buffer" });
|
||||
const pending = execFileAsync(cmd, args, {
|
||||
encoding: "buffer",
|
||||
// The program's `// @env:` pins layer over the inherited environment,
|
||||
// so both sides see the same TZ/locale as the rest of the corpus sees
|
||||
// SCRIPTC_TEST_ENV.
|
||||
env: { ...process.env, ...env },
|
||||
});
|
||||
pending.child.stdin?.end();
|
||||
try {
|
||||
const { stdout, stderr } = await pending;
|
||||
@@ -349,7 +392,7 @@ async function runNode(file: string): Promise<RunResult> {
|
||||
// Node 24 strips types natively; the supported subset is erasable by
|
||||
// construction — except `// @transform-types` programs (namespaces),
|
||||
// which run under Node's transform mode.
|
||||
const res = await runBinary("node", nodeOracleArgs(file));
|
||||
const res = await runBinary("node", nodeOracleArgs(file), programEnv(file));
|
||||
if (cachePath !== null) {
|
||||
try {
|
||||
const tmp = `${cachePath}.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
||||
@@ -396,7 +439,7 @@ async function compileAndRun(file: string): Promise<RunResult> {
|
||||
result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"),
|
||||
);
|
||||
}
|
||||
return runBinary(result.binaryPath, []);
|
||||
return runBinary(result.binaryPath, [], programEnv(file));
|
||||
}
|
||||
|
||||
describe(`differential corpus (${files.length} programs${sanitize ? ", sanitized" : ""}${shardSuffix()})`, () => {
|
||||
@@ -412,6 +455,7 @@ describe(`differential corpus (${files.length} programs${sanitize ? ", sanitized
|
||||
// `// @requires-libnode`: the binary loads Node from a libnode pack,
|
||||
// required in the Linux CI lanes (libnode.ts), else skipped without one.
|
||||
if (requiresLibnode(file) && libnodePack() === null) skip();
|
||||
if (requiresIcu(file) && !icuAvailable()) skip();
|
||||
const [nodeRes, nativeRes] = await Promise.all([runNode(file), compileAndRun(file)]);
|
||||
// Compare as bytes; decode only for the failure diff.
|
||||
if (!nodeRes.stdout.equals(nativeRes.stdout)) {
|
||||
|
||||
@@ -67,16 +67,51 @@ function expectedExitCode(file: string): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function runBinary(cmd: string, args: string[], cpuLevel?: string): Promise<RunResult> {
|
||||
/** `// @env: NAME=VALUE[,NAME=VALUE]` (differential.test.ts's twin): the
|
||||
* variables are set for BOTH sides, so a program can pin the ambient state
|
||||
* that decides Intl output (TZ, LC_ALL/LANG) and the oracle and the binary
|
||||
* read the same one. */
|
||||
function programEnv(file: string): Record<string, string> {
|
||||
const env: Record<string, string> = {};
|
||||
for (const line of directiveHead(file)) {
|
||||
const m = /^\/\/ @env:\s*(.+?)\s*$/.exec(line);
|
||||
if (m === null) continue;
|
||||
for (const pair of m[1]!.split(",")) {
|
||||
const eq = pair.indexOf("=");
|
||||
if (eq > 0) env[pair.slice(0, eq).trim()] = pair.slice(eq + 1).trim();
|
||||
}
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
/** `// @requires-icu` (differential.test.ts's twin): the program's Intl
|
||||
* forms are answered by ICU, which only the linux-x64 runtime pack carries
|
||||
* in this slice; other lanes skip instead of hitting the refusal. */
|
||||
function requiresIcu(file: string): boolean {
|
||||
return directiveHead(file).some((l) => /^\/\/ @requires-icu\s*$/.test(l));
|
||||
}
|
||||
|
||||
function icuAvailable(): boolean {
|
||||
return process.platform === "linux" && process.arch === "x64";
|
||||
}
|
||||
|
||||
async function runBinary(
|
||||
cmd: string,
|
||||
args: string[],
|
||||
cpuLevel?: string,
|
||||
extraEnv: Record<string, string> = {},
|
||||
): Promise<RunResult> {
|
||||
// Linux fork/exec race (observed on the sandbox lane): a sibling
|
||||
// worker's fork can inherit the freshly-linked binary's write fd across
|
||||
// its own spawn window, and exec answers ETXTBSY until that fd closes.
|
||||
// The condition is transient by construction — retry briefly, the
|
||||
// npm/cargo stance.
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
const env: NodeJS.ProcessEnv = { ...process.env, ...extraEnv };
|
||||
if (cpuLevel !== undefined) env["SCRIPTC_CPU_LEVEL"] = cpuLevel;
|
||||
const pending = execFileAsync(cmd, args, {
|
||||
encoding: "buffer",
|
||||
...(cpuLevel === undefined ? {} : { env: { ...process.env, SCRIPTC_CPU_LEVEL: cpuLevel } }),
|
||||
...(cpuLevel === undefined && Object.keys(extraEnv).length === 0 ? {} : { env }),
|
||||
});
|
||||
pending.child.stdin?.end();
|
||||
try {
|
||||
@@ -207,7 +242,13 @@ describe(`llvm differential corpus (${files.length} programs, ${optimizationMode
|
||||
async ([rel, file], { skip }) => {
|
||||
// Programs that call into Node need a libnode pack (libnode.ts).
|
||||
if (requiresLibnode(file) && libnodePack() === null) skip();
|
||||
const oracle = await runBinary(process.execPath, nodeOracleArgs(file));
|
||||
if (requiresIcu(file) && !icuAvailable()) skip();
|
||||
const oracle = await runBinary(
|
||||
process.execPath,
|
||||
nodeOracleArgs(file),
|
||||
undefined,
|
||||
programEnv(file),
|
||||
);
|
||||
const expectedExit = expectedExitCode(file);
|
||||
expect(oracle.exitCode).toBe(expectedExit);
|
||||
for (const optimization of optimizationModes) {
|
||||
@@ -220,7 +261,7 @@ describe(`llvm differential corpus (${files.length} programs, ${optimizationMode
|
||||
expect(result.backend).toBe("llvm");
|
||||
expect(result.llvmPath.endsWith(".ll")).toBe(true);
|
||||
for (const level of cpuLevels) {
|
||||
const actual = await runBinary(result.binaryPath, [], level);
|
||||
const actual = await runBinary(result.binaryPath, [], level, programEnv(file));
|
||||
const lane = level === undefined ? optimization : `${optimization} level ${level}`;
|
||||
expect(actual.stdout, `${lane} stdout`).toEqual(oracle.stdout);
|
||||
if (expectedExit === 0)
|
||||
|
||||
@@ -15,7 +15,6 @@ import { promisify } from "node:util";
|
||||
import { beforeEach, describe, expect, test } from "vitest";
|
||||
import { analyze, compile } from "@scriptc/compiler";
|
||||
import { skipUnownedTest } from "./shard.js";
|
||||
import { libnodePack } from "./libnode.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const repoRoot = join(import.meta.dirname, "../..");
|
||||
@@ -636,14 +635,18 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => {
|
||||
}
|
||||
});
|
||||
|
||||
// The package builds its segmenter at module load, a run-time fence that
|
||||
// surely fires, so the package runs in its Node island and segments as
|
||||
// Node does.
|
||||
test("a word segmenter a package builds at load runs in its Node island", async () => {
|
||||
// The package builds its word segmenter at module load. Word segmentation
|
||||
// lowers to ICU now, so nothing fences: the package compiles into the
|
||||
// program itself (no Node island) and splits words as Node does. The ICU
|
||||
// supporting units live in the linux-x64 runtime pack only in this slice,
|
||||
// so other machines check the analysis and stop before the link.
|
||||
test("a word segmenter a package builds at load compiles statically", async () => {
|
||||
const entry = join(pilotRoot, "word-segments-cli.ts");
|
||||
const { coverage } = analyze(entry, { npmStatic: "auto" });
|
||||
expect(coverage.npmStatic).toEqual([{ package: "word-segments", status: "static" }]);
|
||||
expect(coverage.diagnostics).toHaveLength(0);
|
||||
expect(coverage.runtimeFences ?? []).toHaveLength(0);
|
||||
if (process.platform !== "linux" || process.arch !== "x64") return;
|
||||
const outDir = join(cacheDir, "word-segments", sanitize ? "san" : "plain");
|
||||
const result = await compile(entry, {
|
||||
backend: "llvm",
|
||||
@@ -652,11 +655,6 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => {
|
||||
outDir,
|
||||
outPath: join(outDir, "program"),
|
||||
});
|
||||
if (libnodePack() === null) {
|
||||
expect(result.ok, "no libnode pack: the compile stops").toBe(false);
|
||||
if (!result.ok) expect(result.diagnostics.map((d) => d.code)).toContain("SC8005");
|
||||
return;
|
||||
}
|
||||
if (!result.ok)
|
||||
throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"));
|
||||
const nativeRes = await runBinary(result.binaryPath, []);
|
||||
|
||||
Reference in New Issue
Block a user