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.
- package/native/cmd/ttsc-typia/bigint_literal_precision_transform_test.go +135 -0
- package/native/cmd/ttsc-typia/recursive_container_helper_index_transform_test.go +206 -0
- package/native/cmd/ttsc-typia/reflect_literals_bigint_transform_test.go +148 -0
- package/native/cmd/ttsc-typia/reflect_literals_non_literal_rejection_transform_test.go +24 -18
- package/native/cmd/ttsc-typia/reflect_schema_bigint_transform_test.go +173 -0
- package/native/cmd/ttsc-typia/wrong_type_tag_target_diagnostic_test.go +88 -0
- package/native/core/factories/ExpressionFactory.go +11 -1
- package/native/core/factories/LiteralFactory.go +3 -0
- package/native/core/factories/MetadataTypeTagFactory.go +27 -6
- package/native/core/factories/internal/metadata/iterate_metadata_constant.go +3 -0
- package/native/core/programmers/RandomProgrammer.go +10 -4
- package/native/core/programmers/internal/CheckerProgrammer.go +14 -6
- package/native/core/programmers/json/JsonStringifyProgrammer.go +14 -6
- package/native/core/programmers/notations/NotationGeneralProgrammer.go +14 -6
- package/native/core/programmers/plain/PlainClassifyProgrammer.go +14 -6
- package/native/core/programmers/plain/PlainCloneProgrammer.go +14 -6
- package/native/core/programmers/plain/PlainPruneProgrammer.go +30 -10
- package/native/core/programmers/reflect/ReflectLiteralsProgrammer.go +10 -27
- package/native/core/schemas/metadata/MetadataBigint.go +37 -0
- package/native/core/schemas/metadata/metadata_bigint_is_comparable_test.go +65 -0
- package/native/transform/features/reflect/ReflectSchemaTransformer.go +15 -46
- package/package.json +3 -3
- 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
|
|
11
|
-
//
|
|
10
|
+
// TestReflectLiteralsNonLiteralRejectionTransform verifies every argument that
|
|
11
|
+
// names no listable literal is refused at compile time.
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
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
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
// a
|
|
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
|
-
{"
|
|
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
|