typia 14.0.3 → 14.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/native/cmd/ttsc-typia/bigint_literal_precision_transform_test.go +135 -0
  2. package/native/cmd/ttsc-typia/recursive_container_helper_index_transform_test.go +206 -0
  3. package/native/cmd/ttsc-typia/reflect_literals_bigint_transform_test.go +148 -0
  4. package/native/cmd/ttsc-typia/reflect_literals_non_literal_rejection_transform_test.go +24 -18
  5. package/native/cmd/ttsc-typia/reflect_schema_bigint_transform_test.go +173 -0
  6. package/native/cmd/ttsc-typia/wrong_type_tag_target_diagnostic_test.go +88 -0
  7. package/native/core/factories/ExpressionFactory.go +11 -1
  8. package/native/core/factories/LiteralFactory.go +3 -0
  9. package/native/core/factories/MetadataTypeTagFactory.go +27 -6
  10. package/native/core/factories/internal/metadata/iterate_metadata_constant.go +3 -0
  11. package/native/core/programmers/RandomProgrammer.go +10 -4
  12. package/native/core/programmers/internal/CheckerProgrammer.go +14 -6
  13. package/native/core/programmers/json/JsonStringifyProgrammer.go +14 -6
  14. package/native/core/programmers/notations/NotationGeneralProgrammer.go +14 -6
  15. package/native/core/programmers/plain/PlainClassifyProgrammer.go +14 -6
  16. package/native/core/programmers/plain/PlainCloneProgrammer.go +14 -6
  17. package/native/core/programmers/plain/PlainPruneProgrammer.go +30 -10
  18. package/native/core/programmers/reflect/ReflectLiteralsProgrammer.go +10 -27
  19. package/native/core/schemas/metadata/MetadataBigint.go +37 -0
  20. package/native/core/schemas/metadata/metadata_bigint_is_comparable_test.go +65 -0
  21. package/native/transform/features/reflect/ReflectSchemaTransformer.go +15 -46
  22. package/package.json +3 -3
  23. package/native/cmd/ttsc-typia/reflect_literals_empty_union_transform_test.go +0 -161
@@ -0,0 +1,135 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "os/exec"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // TestBigintLiteralPrecisionTransform verifies a bigint literal reaches the
12
+ // emit exactly, at any magnitude.
13
+ //
14
+ // `ExpressionFactory.Bigint` spelled its argument as a bare number literal, so
15
+ // `9007199254740993n` was emitted as `BigInt(9007199254740993)` and JavaScript
16
+ // rounded that literal to 9007199254740992 before `BigInt` ever parsed it. The
17
+ // validator it built therefore accepted the wrong value: `is<9007199254740993n>`
18
+ // returned true for 9007199254740992n and false for the literal it was asked
19
+ // about. The repository already spells this hazard out for the two places that
20
+ // avoided it -- `numericRangeFactory_bigint_literal` and `_isTypeInt64Bigint` --
21
+ // and this factory is the one that did not.
22
+ //
23
+ // The factory feeds every bigint the transform emits, so the case sweeps the
24
+ // operations that reach it rather than `is` alone.
25
+ //
26
+ // 1. Build validators over bigint literals past 2**53 and at the int64 bounds.
27
+ // 2. Require the emitted code to pass the digits as a string, never as a
28
+ // number literal that rounds before `BigInt` sees it.
29
+ // 3. Execute them and assert each accepts its own literal and rejects the
30
+ // adjacent value a rounded literal would have collapsed onto.
31
+ func TestBigintLiteralPrecisionTransform(t *testing.T) {
32
+ node, err := exec.LookPath("node")
33
+ if err != nil {
34
+ t.Skip("node executable not found")
35
+ }
36
+ root := ttscTypiaTestRepoRoot(t)
37
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
38
+ if err := os.MkdirAll(base, 0o755); err != nil {
39
+ t.Fatalf("mkdir temp base: %v", err)
40
+ }
41
+ dir, err := os.MkdirTemp(base, "bigint-precision-")
42
+ if err != nil {
43
+ t.Fatalf("create temp fixture: %v", err)
44
+ }
45
+ t.Cleanup(func() { _ = os.RemoveAll(dir) })
46
+ src := filepath.Join(dir, "src")
47
+ if err := os.MkdirAll(src, 0o755); err != nil {
48
+ t.Fatalf("mkdir fixture src: %v", err)
49
+ }
50
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(atomicIntersectionSchemaTSConfig), 0o644); err != nil {
51
+ t.Fatalf("write tsconfig: %v", err)
52
+ }
53
+ if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(bigintLiteralPrecisionSource), 0o644); err != nil {
54
+ t.Fatalf("write source: %v", err)
55
+ }
56
+ ttscTypiaTestTypecheck(t, dir)
57
+
58
+ out, errText, code := ttscTypiaTestCapture(func() int {
59
+ return runTransform([]string{
60
+ "--cwd", dir,
61
+ "--tsconfig", "tsconfig.json",
62
+ "--file", "src/main.ts",
63
+ "--output", "js",
64
+ })
65
+ })
66
+ if code != 0 {
67
+ t.Fatalf("bigint precision transform failed: code=%d stderr=\n%s", code, errText)
68
+ }
69
+ // A number-literal argument is the defect itself, so reject the spelling as
70
+ // well as the behavior: an emit that reads `BigInt(9007199254740993)` has
71
+ // already lost the value even where a later comparison happens to agree.
72
+ if strings.Contains(out, "BigInt(9007199254740993)") {
73
+ t.Fatalf("bigint literal must be passed as a string, not a number literal:\n%s", out)
74
+ }
75
+
76
+ runtimeDir := filepath.Join(dir, "runtime")
77
+ if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
78
+ t.Fatalf("mkdir runtime dir: %v", err)
79
+ }
80
+ ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
81
+ runtimeJS := ttscTypiaTestRewriteCommonJS(t, out)
82
+ if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
83
+ t.Fatalf("write runtime module: %v", err)
84
+ }
85
+ runner := filepath.Join(runtimeDir, "run.cjs")
86
+ if err := os.WriteFile(runner, []byte(bigintLiteralPrecisionRunner), 0o644); err != nil {
87
+ t.Fatalf("write runtime runner: %v", err)
88
+ }
89
+ cmd := exec.Command(node, runner)
90
+ cmd.Dir = runtimeDir
91
+ output, err := cmd.CombinedOutput()
92
+ if err != nil {
93
+ t.Fatalf("bigint precision runtime cases failed: %v\n%s", err, output)
94
+ }
95
+ }
96
+
97
+ const bigintLiteralPrecisionSource = `import typia from "typia";
98
+
99
+ // 2**53 + 1 is the smallest integer a double cannot represent; rounding it
100
+ // lands on 2**53, the value each validator below must reject.
101
+ export const isUnsafe = typia.createIs<9007199254740993n>();
102
+ export const isInt64Max = typia.createIs<9223372036854775807n>();
103
+ export const isInt64Min = typia.createIs<-9223372036854775808n>();
104
+ export const isUnion = typia.createIs<9007199254740993n | 9007199254740995n>();
105
+ export const isSafe = typia.createIs<2n>();
106
+ `
107
+
108
+ const bigintLiteralPrecisionRunner = `const mod = require("./main.cjs");
109
+
110
+ const check = (label, actual, expected) => {
111
+ if (actual !== expected) {
112
+ throw new Error(label + ": expected " + expected + ", got " + actual);
113
+ }
114
+ };
115
+
116
+ // Each literal is accepted, and the neighbor a rounded emit would have
117
+ // collapsed it onto is rejected.
118
+ check("isUnsafe(exact)", mod.isUnsafe(9007199254740993n), true);
119
+ check("isUnsafe(rounded)", mod.isUnsafe(9007199254740992n), false);
120
+ check("isInt64Max(exact)", mod.isInt64Max(9223372036854775807n), true);
121
+ check("isInt64Max(rounded)", mod.isInt64Max(9223372036854775808n), false);
122
+ check("isInt64Min(exact)", mod.isInt64Min(-9223372036854775808n), true);
123
+ check("isInt64Min(neighbor)", mod.isInt64Min(-9223372036854775807n), false);
124
+ check("isUnion(first)", mod.isUnion(9007199254740993n), true);
125
+ check("isUnion(second)", mod.isUnion(9007199254740995n), true);
126
+ check("isUnion(between)", mod.isUnion(9007199254740994n), false);
127
+ check("isUnion(rounded)", mod.isUnion(9007199254740992n), false);
128
+
129
+ // A magnitude a double does hold must keep working unchanged.
130
+ check("isSafe(exact)", mod.isSafe(2n), true);
131
+ check("isSafe(other)", mod.isSafe(3n), false);
132
+ check("isSafe(number)", mod.isSafe(2), false);
133
+
134
+ console.log("ok");
135
+ `
@@ -0,0 +1,206 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "os/exec"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // TestRecursiveContainerHelperIndexTransform verifies recursive container helpers
12
+ // remain callable when ordinary containers precede them in collection order.
13
+ func TestRecursiveContainerHelperIndexTransform(t *testing.T) {
14
+ project := recursiveContainerHelperIndexProject(t)
15
+ js := recursiveContainerHelperIndexTransform(t, project)
16
+ recursiveContainerHelperIndexRunRuntimeCases(t, project, js)
17
+ }
18
+
19
+ func recursiveContainerHelperIndexProject(t *testing.T) string {
20
+ t.Helper()
21
+ root := ttscTypiaTestRepoRoot(t)
22
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
23
+ if err := os.MkdirAll(base, 0o755); err != nil {
24
+ t.Fatalf("mkdir temp base: %v", err)
25
+ }
26
+ dir, err := os.MkdirTemp(base, "recursive-container-helper-index-")
27
+ if err != nil {
28
+ t.Fatalf("create temp fixture: %v", err)
29
+ }
30
+ t.Cleanup(func() { _ = os.RemoveAll(dir) })
31
+ src := filepath.Join(dir, "src")
32
+ if err := os.MkdirAll(src, 0o755); err != nil {
33
+ t.Fatalf("mkdir fixture src: %v", err)
34
+ }
35
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(recursiveContainerHelperIndexTSConfig), 0o644); err != nil {
36
+ t.Fatalf("write tsconfig: %v", err)
37
+ }
38
+ if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(recursiveContainerHelperIndexSource), 0o644); err != nil {
39
+ t.Fatalf("write source: %v", err)
40
+ }
41
+ return dir
42
+ }
43
+
44
+ func recursiveContainerHelperIndexTransform(t *testing.T, project string) string {
45
+ t.Helper()
46
+ out, errText, code := ttscTypiaTestCapture(func() int {
47
+ return runTransform([]string{
48
+ "--cwd", project,
49
+ "--tsconfig", "tsconfig.json",
50
+ "--file", "src/main.ts",
51
+ "--output", "js",
52
+ })
53
+ })
54
+ if code != 0 {
55
+ t.Fatalf("recursive container helper transform failed: code=%d stderr=\n%s", code, errText)
56
+ }
57
+ return out
58
+ }
59
+
60
+ func recursiveContainerHelperIndexRunRuntimeCases(t *testing.T, project string, js string) {
61
+ t.Helper()
62
+ node, err := exec.LookPath("node")
63
+ if err != nil {
64
+ t.Skip("node executable not found")
65
+ }
66
+ runtimeDir := filepath.Join(project, "runtime")
67
+ if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
68
+ t.Fatalf("mkdir runtime dir: %v", err)
69
+ }
70
+ ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
71
+ if err := os.WriteFile(filepath.Join(runtimeDir, "feature-stub.cjs"), []byte(recursiveContainerHelperIndexFeatureStub), 0o644); err != nil {
72
+ t.Fatalf("write feature stub: %v", err)
73
+ }
74
+ for _, helper := range []string{
75
+ "_jsonStringifyNumber",
76
+ "_jsonStringifyString",
77
+ "_randomArray",
78
+ "_randomNumber",
79
+ "_randomPick",
80
+ "_throwTypeGuardError",
81
+ } {
82
+ js = strings.ReplaceAll(
83
+ js,
84
+ `require("typia/lib/internal/`+helper+`")`,
85
+ `require("./feature-stub.cjs")`,
86
+ )
87
+ }
88
+ if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(ttscTypiaTestRewriteCommonJS(t, js)), 0o644); err != nil {
89
+ t.Fatalf("write runtime module: %v", err)
90
+ }
91
+ runner := filepath.Join(runtimeDir, "run.cjs")
92
+ if err := os.WriteFile(runner, []byte(recursiveContainerHelperIndexRuntimeRunner), 0o644); err != nil {
93
+ t.Fatalf("write runtime runner: %v", err)
94
+ }
95
+ cmd := exec.Command(node, runner)
96
+ cmd.Dir = runtimeDir
97
+ output, err := cmd.CombinedOutput()
98
+ if err != nil {
99
+ t.Fatalf("recursive container helper runtime cases failed: %v\n%s", err, output)
100
+ }
101
+ }
102
+
103
+ const recursiveContainerHelperIndexTSConfig = `{
104
+ "compilerOptions": {
105
+ "target": "ES2022",
106
+ "module": "commonjs",
107
+ "moduleResolution": "bundler",
108
+ "ignoreDeprecations": "6.0",
109
+ "types": ["*"],
110
+ "esModuleInterop": true,
111
+ "strict": true,
112
+ "skipLibCheck": true
113
+ },
114
+ "include": ["src"]
115
+ }
116
+ `
117
+
118
+ const recursiveContainerHelperIndexSource = `import typia from "typia";
119
+
120
+ type JsonPrimitive = string | number | boolean | null;
121
+ type JsonArray = JsonValue[];
122
+ type JsonObject = { [key: string]: JsonValue };
123
+ type JsonValue = JsonPrimitive | JsonArray | JsonObject;
124
+
125
+ interface ArrayWitness {
126
+ ordinary: string[];
127
+ value: JsonValue;
128
+ }
129
+
130
+ type RecursiveTuple = [string, RecursiveTuple | null];
131
+ interface TupleWitness {
132
+ ordinary: [number];
133
+ value: RecursiveTuple;
134
+ }
135
+
136
+ type RandomArray = Array<string | RandomArray>;
137
+ interface RandomArrayWitness {
138
+ ordinary: string[];
139
+ value: RandomArray;
140
+ }
141
+
142
+ export const isArray = typia.createIs<ArrayWitness>();
143
+ export const isTuple = typia.createIs<TupleWitness>();
144
+ export const stringifyArray = typia.json.createStringify<ArrayWitness>();
145
+ export const stringifyTuple = typia.json.createStringify<TupleWitness>();
146
+ export const camelArray = typia.notations.createCamel<ArrayWitness>();
147
+ export const camelTuple = typia.notations.createCamel<TupleWitness>();
148
+ export const cloneArray = typia.plain.createClone<ArrayWitness>();
149
+ export const cloneTuple = typia.plain.createClone<TupleWitness>();
150
+ export const classifyArray = typia.plain.createClassify<ArrayWitness>();
151
+ export const classifyTuple = typia.plain.createClassify<TupleWitness>();
152
+ export const pruneArray = typia.plain.createPrune<ArrayWitness>();
153
+ export const pruneTuple = typia.plain.createPrune<TupleWitness>();
154
+ export const randomArray = typia.createRandom<RandomArrayWitness>({
155
+ array: () => [],
156
+ string: () => "generated",
157
+ });
158
+ export const randomTuple = typia.createRandom<TupleWitness>({
159
+ boolean: () => false,
160
+ number: () => 1,
161
+ string: () => "generated",
162
+ });
163
+ `
164
+
165
+ const recursiveContainerHelperIndexRuntimeRunner = `const mod = require("./main.cjs");
166
+
167
+ const arrayValue = { ordinary: ["a"], value: [1, { nested: [true, null] }] };
168
+ const tupleValue = { ordinary: [1], value: ["root", ["child", null]] };
169
+
170
+ const expect = (label, actual, expected) => {
171
+ if (actual !== expected) {
172
+ throw new Error(label + ": expected " + expected + ", got " + actual);
173
+ }
174
+ };
175
+
176
+ expect("is array", mod.isArray(arrayValue), true);
177
+ expect("is tuple", mod.isTuple(tupleValue), true);
178
+ expect("stringify array", mod.stringifyArray(arrayValue), JSON.stringify(arrayValue));
179
+ expect("stringify tuple", mod.stringifyTuple(tupleValue), JSON.stringify(tupleValue));
180
+ expect("notation array", mod.camelArray(arrayValue).value[1].nested[0], true);
181
+ expect("notation tuple", mod.camelTuple(tupleValue).value[1][0], "child");
182
+ expect("clone array", mod.cloneArray(arrayValue).value[1].nested[1], null);
183
+ expect("clone tuple", mod.cloneTuple(tupleValue).value[1][0], "child");
184
+ expect("classify array", mod.classifyArray(arrayValue).value[1].nested[0], true);
185
+ expect("classify tuple", mod.classifyTuple(tupleValue).value[1][0], "child");
186
+
187
+ const prunedArray = { ...arrayValue, extra: true };
188
+ mod.pruneArray(prunedArray);
189
+ expect("prune array", "extra" in prunedArray, false);
190
+ const prunedTuple = { ...tupleValue, extra: true };
191
+ mod.pruneTuple(prunedTuple);
192
+ expect("prune tuple", "extra" in prunedTuple, false);
193
+
194
+ const generatedArray = mod.randomArray();
195
+ expect("random array", Array.isArray(generatedArray.value), true);
196
+ const generatedTuple = mod.randomTuple();
197
+ expect("random tuple", generatedTuple.value[0], "generated");
198
+ `
199
+
200
+ const recursiveContainerHelperIndexFeatureStub = `module.exports._jsonStringifyNumber = (value) => Number.isFinite(value) ? value : null;
201
+ module.exports._jsonStringifyString = (value) => JSON.stringify(value);
202
+ module.exports._randomArray = () => [];
203
+ module.exports._randomNumber = () => 1;
204
+ module.exports._randomPick = (values) => values[0];
205
+ module.exports._throwTypeGuardError = (props) => { throw Object.assign(new Error(props.expected), props); };
206
+ `
@@ -0,0 +1,148 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "os/exec"
6
+ "path/filepath"
7
+ "testing"
8
+ )
9
+
10
+ // TestReflectLiteralsBigintTransform verifies a bigint member comes back as a
11
+ // bigint, exactly.
12
+ //
13
+ // typescript-go hands a bigint literal back as a `jsnum.PseudoBigInt`, a struct
14
+ // in an internal package the shim does not re-export. Nothing downstream could
15
+ // name that type, so `LiteralFactory` reflected its fields and emitted
16
+ // `{ base10Value: "2", negative: false }` where `literals<2n>(): 2n[]` promises
17
+ // a bigint. Normalizing the constant to `*big.Int` at the metadata boundary
18
+ // gives every consumer a nameable exact value.
19
+ //
20
+ // Magnitude is the part a happy-path case would miss. A bigint exists precisely
21
+ // to hold what a `number` cannot, so the emit is only correct if it survives
22
+ // past 2**53.
23
+ //
24
+ // 1. Transform a module whose `reflect.literals` arguments span a bigint union,
25
+ // a bigint mixed with other literal kinds, and magnitudes on both sides of
26
+ // the double-precision limit including the int64 bounds.
27
+ // 2. Execute the emitted CommonJS.
28
+ // 3. Assert every member is `typeof "bigint"` and equal to the literal the
29
+ // source declared, digit for digit.
30
+ func TestReflectLiteralsBigintTransform(t *testing.T) {
31
+ node, err := exec.LookPath("node")
32
+ if err != nil {
33
+ t.Skip("node executable not found")
34
+ }
35
+ root := ttscTypiaTestRepoRoot(t)
36
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
37
+ if err := os.MkdirAll(base, 0o755); err != nil {
38
+ t.Fatalf("mkdir temp base: %v", err)
39
+ }
40
+ dir, err := os.MkdirTemp(base, "reflect-literals-bigint-")
41
+ if err != nil {
42
+ t.Fatalf("create temp fixture: %v", err)
43
+ }
44
+ t.Cleanup(func() { _ = os.RemoveAll(dir) })
45
+ src := filepath.Join(dir, "src")
46
+ if err := os.MkdirAll(src, 0o755); err != nil {
47
+ t.Fatalf("mkdir fixture src: %v", err)
48
+ }
49
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(atomicIntersectionSchemaTSConfig), 0o644); err != nil {
50
+ t.Fatalf("write tsconfig: %v", err)
51
+ }
52
+ if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(reflectLiteralsBigintSource), 0o644); err != nil {
53
+ t.Fatalf("write source: %v", err)
54
+ }
55
+ ttscTypiaTestTypecheck(t, dir)
56
+
57
+ out, errText, code := ttscTypiaTestCapture(func() int {
58
+ return runTransform([]string{
59
+ "--cwd", dir,
60
+ "--tsconfig", "tsconfig.json",
61
+ "--file", "src/main.ts",
62
+ "--output", "js",
63
+ })
64
+ })
65
+ if code != 0 {
66
+ t.Fatalf("reflect.literals bigint transform failed: code=%d stderr=\n%s", code, errText)
67
+ }
68
+
69
+ runtimeDir := filepath.Join(dir, "runtime")
70
+ if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
71
+ t.Fatalf("mkdir runtime dir: %v", err)
72
+ }
73
+ ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
74
+ runtimeJS := ttscTypiaTestRewriteCommonJS(t, out)
75
+ if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
76
+ t.Fatalf("write runtime module: %v", err)
77
+ }
78
+ runner := filepath.Join(runtimeDir, "run.cjs")
79
+ if err := os.WriteFile(runner, []byte(reflectLiteralsBigintRunner), 0o644); err != nil {
80
+ t.Fatalf("write runtime runner: %v", err)
81
+ }
82
+ cmd := exec.Command(node, runner)
83
+ cmd.Dir = runtimeDir
84
+ output, err := cmd.CombinedOutput()
85
+ if err != nil {
86
+ t.Fatalf("reflect.literals bigint runtime cases failed: %v\n%s", err, output)
87
+ }
88
+ }
89
+
90
+ const reflectLiteralsBigintSource = `import typia from "typia";
91
+
92
+ export const small = typia.reflect.literals<1n | 2n>();
93
+ export const mixed = typia.reflect.literals<"A" | "B" | 1 | 2n>();
94
+ export const negative = typia.reflect.literals<-5n | 5n>();
95
+ export const zero = typia.reflect.literals<0n>();
96
+
97
+ // 2**53 + 1 is the smallest integer a double cannot hold, and the int64 bounds
98
+ // are where a rounded literal would land on the wrong side of the range.
99
+ export const unsafe = typia.reflect.literals<9007199254740993n>();
100
+ export const int64 = typia.reflect.literals<-9223372036854775808n | 9223372036854775807n>();
101
+ `
102
+
103
+ const reflectLiteralsBigintRunner = `const mod = require("./main.cjs");
104
+
105
+ const render = (value) =>
106
+ typeof value === "bigint" ? value.toString() + "n" : JSON.stringify(value);
107
+
108
+ const check = (label, actual, expected) => {
109
+ if (Array.isArray(actual) === false) {
110
+ throw new Error(label + ": expected an array, got " + render(actual));
111
+ }
112
+ if (actual.length !== expected.length) {
113
+ throw new Error(
114
+ label +
115
+ ": expected " +
116
+ expected.map(render).join(", ") +
117
+ ", got " +
118
+ actual.map(render).join(", "),
119
+ );
120
+ }
121
+ actual.forEach((item, index) => {
122
+ // typeof is asserted separately: a rounded BigInt(9007199254740993) is
123
+ // still a bigint, and an object literal is still deep-equal to nothing, so
124
+ // neither check alone catches both defects.
125
+ if (typeof item !== typeof expected[index]) {
126
+ throw new Error(
127
+ label + "[" + index + "]: expected typeof " + typeof expected[index] +
128
+ ", got " + typeof item + " (" + render(item) + ")",
129
+ );
130
+ }
131
+ if (item !== expected[index]) {
132
+ throw new Error(
133
+ label + "[" + index + "]: expected " + render(expected[index]) +
134
+ ", got " + render(item),
135
+ );
136
+ }
137
+ });
138
+ };
139
+
140
+ check("small", mod.small, [1n, 2n]);
141
+ check("mixed", mod.mixed, ["A", "B", 1, 2n]);
142
+ check("negative", mod.negative, [-5n, 5n]);
143
+ check("zero", mod.zero, [0n]);
144
+ check("unsafe", mod.unsafe, [9007199254740993n]);
145
+ check("int64", mod.int64, [-9223372036854775808n, 9223372036854775807n]);
146
+
147
+ console.log("ok");
148
+ `
@@ -7,24 +7,26 @@ import (
7
7
  "testing"
8
8
  )
9
9
 
10
- // TestReflectLiteralsNonLiteralRejectionTransform is the negative twin of the
11
- // empty-union admission fixed for issue #2377.
10
+ // TestReflectLiteralsNonLiteralRejectionTransform verifies every argument that
11
+ // names no listable literal is refused at compile time.
12
12
  //
13
- // Widening the admission predicate so that the empty union and the bare `null`
14
- // flag both pass must not widen it any further: every argument that carries a
15
- // member the emitter cannot render still has to abort the transform. The two
16
- // diagnostics stay distinguishable as well, because they say different things --
17
- // `NO` reports that nothing literal was found at all, while `ONLY` reports that
18
- // something literal was found next to something that is not. Collapsing them
19
- // would hide which half of a mixed argument is wrong.
13
+ // `reflect.literals` hands back the members of a union, so an argument it
14
+ // cannot enumerate has no answer to give. Refusing it is the point: emitting an
15
+ // empty array for `never`, or dropping the half it cannot render from a mixed
16
+ // argument, would hand the caller a list that silently disagrees with the type
17
+ // it was derived from (issue #2377).
20
18
  //
21
- // 1. Transform arguments holding no literal at all: a bare atomic, `any`, and
22
- // a tag-branded atomic, whose brand raises the member count without adding
23
- // anything the emitter can render.
24
- // 2. Transform arguments mixing a renderable member with a non-literal one:
25
- // `string | null`, the one-axis twin of the newly admitted `null`, and
26
- // `boolean | number`, where the renderable member is an atomic rather than
27
- // a constant.
19
+ // The two diagnostics stay distinguishable because they say different things:
20
+ // `NO` reports that nothing listable was found at all, while `ONLY` reports
21
+ // that something listable was found beside something that is not. Collapsing
22
+ // them would hide which half of a mixed argument is wrong.
23
+ //
24
+ // 1. Transform arguments naming no listable member: `never` and the bare
25
+ // `null` flag, which carry nothing, and `string`, `any`, and a tag-branded
26
+ // atomic, which carry a member that cannot be enumerated.
27
+ // 2. Transform arguments mixing a listable member with one that is not,
28
+ // including `boolean | number`, where the listable half is an atomic rather
29
+ // than a constant.
28
30
  // 3. Assert each fails, and with the diagnostic its composition calls for.
29
31
  func TestReflectLiteralsNonLiteralRejectionTransform(t *testing.T) {
30
32
  cases := []struct {
@@ -32,13 +34,17 @@ func TestReflectLiteralsNonLiteralRejectionTransform(t *testing.T) {
32
34
  Argument string
33
35
  Message string
34
36
  }{
37
+ {"never", "never", "no constant literal type found."},
38
+ {"never alias", "Empty", "no constant literal type found."},
39
+ {"exhaustive exclude", `Exclude<"a" | "b", "a" | "b">`, "no constant literal type found."},
40
+ {"bare null", "null", "no constant literal type found."},
35
41
  {"atomic", "string", "no constant literal type found."},
36
42
  {"any", "any", "no constant literal type found."},
37
- {"nullable atomic", "string | null", "only constant literal types are allowed."},
43
+ {"tag branded atomic", `string & tags.Format<"uuid">`, "no constant literal type found."},
44
+ {"nullable atomic", "string | null", "no constant literal type found."},
38
45
  {"literal beside atomic", `"a" | number`, "only constant literal types are allowed."},
39
46
  {"literal beside template", "`prefix${number}` | \"a\"", "only constant literal types are allowed."},
40
47
  {"renderable atomic beside atomic", "boolean | number", "only constant literal types are allowed."},
41
- {"tag branded atomic", `string & tags.Format<"uuid">`, "no constant literal type found."},
42
48
  }
43
49
  for _, tc := range cases {
44
50
  tc := tc