typia 14.0.2 → 14.0.4
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/reflect_literals_bigint_transform_test.go +148 -0
- package/native/cmd/ttsc-typia/reflect_literals_non_literal_rejection_transform_test.go +98 -0
- package/native/cmd/ttsc-typia/reflect_schema_bigint_transform_test.go +173 -0
- package/native/core/factories/ExpressionFactory.go +11 -1
- package/native/core/factories/LiteralFactory.go +3 -0
- package/native/core/factories/internal/metadata/iterate_metadata_constant.go +3 -0
- package/native/core/programmers/reflect/ReflectLiteralsProgrammer.go +6 -0
- 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
|
@@ -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,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
|
+
`
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestReflectLiteralsNonLiteralRejectionTransform verifies every argument that
|
|
11
|
+
// names no listable literal is refused at compile time.
|
|
12
|
+
//
|
|
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).
|
|
18
|
+
//
|
|
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.
|
|
30
|
+
// 3. Assert each fails, and with the diagnostic its composition calls for.
|
|
31
|
+
func TestReflectLiteralsNonLiteralRejectionTransform(t *testing.T) {
|
|
32
|
+
cases := []struct {
|
|
33
|
+
Name string
|
|
34
|
+
Argument string
|
|
35
|
+
Message string
|
|
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."},
|
|
41
|
+
{"atomic", "string", "no constant literal type found."},
|
|
42
|
+
{"any", "any", "no constant literal type found."},
|
|
43
|
+
{"tag branded atomic", `string & tags.Format<"uuid">`, "no constant literal type found."},
|
|
44
|
+
{"nullable atomic", "string | null", "no constant literal type found."},
|
|
45
|
+
{"literal beside atomic", `"a" | number`, "only constant literal types are allowed."},
|
|
46
|
+
{"literal beside template", "`prefix${number}` | \"a\"", "only constant literal types are allowed."},
|
|
47
|
+
{"renderable atomic beside atomic", "boolean | number", "only constant literal types are allowed."},
|
|
48
|
+
}
|
|
49
|
+
for _, tc := range cases {
|
|
50
|
+
tc := tc
|
|
51
|
+
t.Run(tc.Name, func(t *testing.T) {
|
|
52
|
+
project := reflectLiteralsRejectionProject(t, tc.Argument)
|
|
53
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
54
|
+
return runTransform([]string{
|
|
55
|
+
"--cwd", project,
|
|
56
|
+
"--tsconfig", "tsconfig.json",
|
|
57
|
+
"--file", "src/main.ts",
|
|
58
|
+
"--output", "js",
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
if code == 0 {
|
|
62
|
+
t.Fatalf("reflect.literals<%s> transformed successfully, want rejection:\n%s", tc.Argument, out)
|
|
63
|
+
}
|
|
64
|
+
if !strings.Contains(errText, "typia transform error") {
|
|
65
|
+
t.Fatalf("reflect.literals<%s> diagnostics missing:\nstdout=%s\nstderr=%s", tc.Argument, out, errText)
|
|
66
|
+
}
|
|
67
|
+
if !strings.Contains(errText, tc.Message) {
|
|
68
|
+
t.Fatalf("reflect.literals<%s> reported the wrong reason, want %q:\n%s", tc.Argument, tc.Message, errText)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func reflectLiteralsRejectionProject(t *testing.T, argument string) string {
|
|
75
|
+
t.Helper()
|
|
76
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
77
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
78
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
79
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
80
|
+
}
|
|
81
|
+
dir, err := os.MkdirTemp(base, "reflect-literals-reject-")
|
|
82
|
+
if err != nil {
|
|
83
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
84
|
+
}
|
|
85
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
86
|
+
src := filepath.Join(dir, "src")
|
|
87
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
88
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
89
|
+
}
|
|
90
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(atomicIntersectionSchemaTSConfig), 0o644); err != nil {
|
|
91
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
92
|
+
}
|
|
93
|
+
source := "import typia, { tags } from \"typia\";\n\nexport const values = typia.reflect.literals<" + argument + ">();\n"
|
|
94
|
+
if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(source), 0o644); err != nil {
|
|
95
|
+
t.Fatalf("write source: %v", err)
|
|
96
|
+
}
|
|
97
|
+
return dir
|
|
98
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"os/exec"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestReflectSchemaBigintTransform verifies the metadata a reflect operation
|
|
11
|
+
// hands back carries a bigint constant as a bigint.
|
|
12
|
+
//
|
|
13
|
+
// `IMetadataSchema.IValue<Atomic.Type>` declares `value: bigint` for a bigint
|
|
14
|
+
// constant, but this transformer lowered the whole metadata tree through
|
|
15
|
+
// `encoding/json` before writing it. JSON has no bigint, so that one member
|
|
16
|
+
// could not survive the trip: it arrived as whatever object its fields happened
|
|
17
|
+
// to spell. The trip was also redundant -- it marshaled structs into maps and
|
|
18
|
+
// lowercased each key's initial, which is what `LiteralFactory` already does
|
|
19
|
+
// when it reflects a struct -- so writing the tree directly both fixes the
|
|
20
|
+
// value and removes the step.
|
|
21
|
+
//
|
|
22
|
+
// `literals` and `schema` reach the emit by different routes, so a case on one
|
|
23
|
+
// says nothing about the other.
|
|
24
|
+
//
|
|
25
|
+
// 1. Reflect a bigint constant union through `schema` and through `schemas`.
|
|
26
|
+
// 2. Execute the emitted CommonJS.
|
|
27
|
+
// 3. Assert each reported value is `typeof "bigint"` and exact, and that the
|
|
28
|
+
// surrounding metadata still reports the constant and its neighbors.
|
|
29
|
+
func TestReflectSchemaBigintTransform(t *testing.T) {
|
|
30
|
+
node, err := exec.LookPath("node")
|
|
31
|
+
if err != nil {
|
|
32
|
+
t.Skip("node executable not found")
|
|
33
|
+
}
|
|
34
|
+
root := ttscTypiaTestRepoRoot(t)
|
|
35
|
+
base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
|
|
36
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
37
|
+
t.Fatalf("mkdir temp base: %v", err)
|
|
38
|
+
}
|
|
39
|
+
dir, err := os.MkdirTemp(base, "reflect-schema-bigint-")
|
|
40
|
+
if err != nil {
|
|
41
|
+
t.Fatalf("create temp fixture: %v", err)
|
|
42
|
+
}
|
|
43
|
+
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
44
|
+
src := filepath.Join(dir, "src")
|
|
45
|
+
if err := os.MkdirAll(src, 0o755); err != nil {
|
|
46
|
+
t.Fatalf("mkdir fixture src: %v", err)
|
|
47
|
+
}
|
|
48
|
+
if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(atomicIntersectionSchemaTSConfig), 0o644); err != nil {
|
|
49
|
+
t.Fatalf("write tsconfig: %v", err)
|
|
50
|
+
}
|
|
51
|
+
if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(reflectSchemaBigintSource), 0o644); err != nil {
|
|
52
|
+
t.Fatalf("write source: %v", err)
|
|
53
|
+
}
|
|
54
|
+
ttscTypiaTestTypecheck(t, dir)
|
|
55
|
+
|
|
56
|
+
out, errText, code := ttscTypiaTestCapture(func() int {
|
|
57
|
+
return runTransform([]string{
|
|
58
|
+
"--cwd", dir,
|
|
59
|
+
"--tsconfig", "tsconfig.json",
|
|
60
|
+
"--file", "src/main.ts",
|
|
61
|
+
"--output", "js",
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
if code != 0 {
|
|
65
|
+
t.Fatalf("reflect.schema bigint transform failed: code=%d stderr=\n%s", code, errText)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
runtimeDir := filepath.Join(dir, "runtime")
|
|
69
|
+
if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
|
|
70
|
+
t.Fatalf("mkdir runtime dir: %v", err)
|
|
71
|
+
}
|
|
72
|
+
ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
|
|
73
|
+
runtimeJS := ttscTypiaTestRewriteCommonJS(t, out)
|
|
74
|
+
if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
|
|
75
|
+
t.Fatalf("write runtime module: %v", err)
|
|
76
|
+
}
|
|
77
|
+
runner := filepath.Join(runtimeDir, "run.cjs")
|
|
78
|
+
if err := os.WriteFile(runner, []byte(reflectSchemaBigintRunner), 0o644); err != nil {
|
|
79
|
+
t.Fatalf("write runtime runner: %v", err)
|
|
80
|
+
}
|
|
81
|
+
cmd := exec.Command(node, runner)
|
|
82
|
+
cmd.Dir = runtimeDir
|
|
83
|
+
output, err := cmd.CombinedOutput()
|
|
84
|
+
if err != nil {
|
|
85
|
+
t.Fatalf("reflect.schema bigint runtime cases failed: %v\n%s", err, output)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const reflectSchemaBigintSource = `import typia, { tags } from "typia";
|
|
90
|
+
|
|
91
|
+
export const unit = typia.reflect.schema<1n | 9007199254740993n>();
|
|
92
|
+
export const collection = typia.reflect.schemas<[2n, "a" | 3]>();
|
|
93
|
+
export const objects = typia.reflect.schema<{ big: 7n; name: string }>();
|
|
94
|
+
|
|
95
|
+
// A tag reaches a constant by matching the child's value against the merged
|
|
96
|
+
// parent's, so the constant value has to compare by value. The number and
|
|
97
|
+
// string constants are the controls that were never at risk.
|
|
98
|
+
export const tagged = typia.reflect.schema<(1n | 2n) & tags.Type<"int64">>();
|
|
99
|
+
export const taggedNumber = typia.reflect.schema<(1 | 2) & tags.Type<"uint32">>();
|
|
100
|
+
`
|
|
101
|
+
|
|
102
|
+
const reflectSchemaBigintRunner = `const mod = require("./main.cjs");
|
|
103
|
+
|
|
104
|
+
const render = (value) =>
|
|
105
|
+
typeof value === "bigint" ? value.toString() + "n" : JSON.stringify(value);
|
|
106
|
+
|
|
107
|
+
const constantsOf = (schema, type) => {
|
|
108
|
+
const constant = (schema.constants ?? []).find((c) => c.type === type);
|
|
109
|
+
return constant === undefined ? [] : constant.values.map((v) => v.value);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const check = (label, actual, expected) => {
|
|
113
|
+
if (actual.length !== expected.length) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
label + ": expected " + expected.map(render).join(", ") +
|
|
116
|
+
", got " + actual.map(render).join(", "),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
actual.forEach((item, index) => {
|
|
120
|
+
if (typeof item !== typeof expected[index] || item !== expected[index]) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
label + "[" + index + "]: expected " + render(expected[index]) +
|
|
123
|
+
" (" + typeof expected[index] + "), got " + render(item) +
|
|
124
|
+
" (" + typeof item + ")",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
check("schema", constantsOf(mod.unit.schema, "bigint"), [1n, 9007199254740993n]);
|
|
131
|
+
check("schemas[0]", constantsOf(mod.collection.schemas[0], "bigint"), [2n]);
|
|
132
|
+
|
|
133
|
+
// A neighboring constant kind must keep its own representation.
|
|
134
|
+
check("schemas[1] string", constantsOf(mod.collection.schemas[1], "string"), ["a"]);
|
|
135
|
+
check("schemas[1] number", constantsOf(mod.collection.schemas[1], "number"), [3]);
|
|
136
|
+
|
|
137
|
+
// The surrounding tree still reports the object and its members.
|
|
138
|
+
const object = mod.objects.components.objects[0];
|
|
139
|
+
if (object === undefined || object.properties.length !== 2) {
|
|
140
|
+
throw new Error("object metadata was not emitted: " + JSON.stringify(mod.objects, (k, v) => typeof v === "bigint" ? v.toString() : v));
|
|
141
|
+
}
|
|
142
|
+
const big = object.properties.find(
|
|
143
|
+
(p) => constantsOf(p.key, "string")[0] === "big",
|
|
144
|
+
);
|
|
145
|
+
if (big === undefined) {
|
|
146
|
+
throw new Error("the 'big' property is missing from the object metadata");
|
|
147
|
+
}
|
|
148
|
+
check("object property", constantsOf(big.value, "bigint"), [7n]);
|
|
149
|
+
|
|
150
|
+
const tagNames = (schema, type) => {
|
|
151
|
+
const constant = (schema.constants ?? []).find((c) => c.type === type);
|
|
152
|
+
if (constant === undefined) {
|
|
153
|
+
throw new Error("no " + type + " constant was emitted");
|
|
154
|
+
}
|
|
155
|
+
return constant.values.map((v) =>
|
|
156
|
+
(v.tags ?? []).flat().map((t) => t.name).join(","),
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// Every member keeps the tag the intersection put on it.
|
|
161
|
+
const expectTags = (label, actual, expected) => {
|
|
162
|
+
if (actual.length === 0 || actual.some((names) => names !== expected)) {
|
|
163
|
+
throw new Error(
|
|
164
|
+
label + ": expected every member tagged " + expected +
|
|
165
|
+
", got " + JSON.stringify(actual),
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
expectTags("bigint constant", tagNames(mod.tagged.schema, "bigint"), 'Type<"int64">');
|
|
170
|
+
expectTags("number constant", tagNames(mod.taggedNumber.schema, "number"), 'Type<"uint32">');
|
|
171
|
+
|
|
172
|
+
console.log("ok");
|
|
173
|
+
`
|
|
@@ -66,6 +66,16 @@ func (expressionFactoryNamespace) Number(value any, emit ...*shimprinter.EmitCon
|
|
|
66
66
|
return f.NewNumericLiteral(text, shimast.TokenFlagsNone)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Bigint emits `BigInt("<value>")` for a bigint literal.
|
|
70
|
+
//
|
|
71
|
+
// The argument has to stay a string literal. This used to spell the digits as a
|
|
72
|
+
// bare `number` literal, so `9007199254740993n` reached the emit as
|
|
73
|
+
// `BigInt(9007199254740993)` and JavaScript rounded the literal to
|
|
74
|
+
// 9007199254740992 before `BigInt` ever parsed it -- exactly the rounding
|
|
75
|
+
// `numericRangeFactory_bigint_literal` and `_isTypeInt64Bigint` already spell
|
|
76
|
+
// out and avoid. Every bigint an argument can carry is exact in base 10, and
|
|
77
|
+
// `BigInt` parses the digits of a string directly, so the string form is exact
|
|
78
|
+
// at any magnitude.
|
|
69
79
|
func (expressionFactoryNamespace) Bigint(value any, emit ...*shimprinter.EmitContext) *shimast.Node {
|
|
70
80
|
f := nativecontext.EmitFactoryOf(expressionFactory_factory, emit...)
|
|
71
81
|
return f.NewCallExpression(
|
|
@@ -73,7 +83,7 @@ func (expressionFactoryNamespace) Bigint(value any, emit ...*shimprinter.EmitCon
|
|
|
73
83
|
nil,
|
|
74
84
|
nil,
|
|
75
85
|
f.NewNodeList([]*shimast.Node{
|
|
76
|
-
f.
|
|
86
|
+
f.NewStringLiteral(fmt.Sprint(value), shimast.TokenFlagsNone),
|
|
77
87
|
}),
|
|
78
88
|
shimast.NodeFlagsNone,
|
|
79
89
|
)
|
|
@@ -11,6 +11,7 @@ import (
|
|
|
11
11
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
12
12
|
shimprinter "github.com/microsoft/typescript-go/shim/printer"
|
|
13
13
|
nativecontext "github.com/samchon/typia/packages/typia/native/core/context"
|
|
14
|
+
schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
type literalFactoryNamespace struct{}
|
|
@@ -93,6 +94,8 @@ func (literalFactoryNamespace) Write(input any, emit ...*shimprinter.EmitContext
|
|
|
93
94
|
switch value := input.(type) {
|
|
94
95
|
case bool:
|
|
95
96
|
return literalFactory_writeBoolean(value, emit...)
|
|
97
|
+
case schemametadata.MetadataBigint:
|
|
98
|
+
return literalFactory_writeBigint(value, emit...)
|
|
96
99
|
case *big.Int:
|
|
97
100
|
return literalFactory_writeBigint(value, emit...)
|
|
98
101
|
case int:
|
|
@@ -50,6 +50,9 @@ func Iterate_metadata_constant(props IMetadataIteratorProps) bool {
|
|
|
50
50
|
typ = "number"
|
|
51
51
|
} else if filter(nativechecker.TypeFlagsBigIntLiteral) {
|
|
52
52
|
typ = "bigint"
|
|
53
|
+
// Normalize away the checker's unnameable `jsnum.PseudoBigInt`; see
|
|
54
|
+
// MetadataBigint for why the stand-in has to be a comparable value.
|
|
55
|
+
value = schemametadata.MetadataBigint{Text: fmt.Sprint(value)}
|
|
53
56
|
}
|
|
54
57
|
constant := iterate_metadata_constant_take(props.Metadata, typ)
|
|
55
58
|
info := comment()
|
|
@@ -29,6 +29,12 @@ func (reflectLiteralsProgrammerNamespace) Write(props ReflectLiteralsProgrammer_
|
|
|
29
29
|
Escape: true,
|
|
30
30
|
Constant: true,
|
|
31
31
|
Absorb: true,
|
|
32
|
+
// Only a constant literal, the `boolean` atomic (which stands for the
|
|
33
|
+
// `true | false` constants), and `null` beside them are members this
|
|
34
|
+
// operation can hand back. Anything else -- an unconstrained atomic, a
|
|
35
|
+
// template, a bucket of any other kind -- is rejected, and so is an
|
|
36
|
+
// argument carrying no member at all: `never` has no literal to list, so
|
|
37
|
+
// it is a compile error rather than an empty array (issue #2377).
|
|
32
38
|
Validate: func(next struct {
|
|
33
39
|
Metadata *nativemetadata.MetadataSchema
|
|
34
40
|
Explore nativefactories.MetadataFactory_IExplore
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package metadata
|
|
2
|
+
|
|
3
|
+
// MetadataBigint is the value of a `bigint` constant.
|
|
4
|
+
//
|
|
5
|
+
// typescript-go reports a bigint literal as a `jsnum.PseudoBigInt`, a struct in
|
|
6
|
+
// an internal package the shim does not re-export. Nothing downstream could
|
|
7
|
+
// name it, so consumers that had to render the value reflected its fields
|
|
8
|
+
// instead and emitted `{ base10Value: "2", negative: false }` where the caller
|
|
9
|
+
// declared `bigint`. This is the nameable stand-in.
|
|
10
|
+
//
|
|
11
|
+
// It is a comparable struct on purpose. Every other value a
|
|
12
|
+
// `MetadataConstantValue` carries -- `string`, `bool`, the number -- is
|
|
13
|
+
// comparable, and the factories compare those values with `==`; the
|
|
14
|
+
// intersection tag assigner is one such site. A pointer type such as
|
|
15
|
+
// `*math/big.Int` would compare identity there and, for bigints alone,
|
|
16
|
+
// silently drop whatever the comparison decides.
|
|
17
|
+
type MetadataBigint struct {
|
|
18
|
+
// Text is the exact value in base 10, prefixed with `-` when negative. A
|
|
19
|
+
// bigint exists to hold what a float64 cannot, so the digits are the only
|
|
20
|
+
// representation that stays exact at every magnitude.
|
|
21
|
+
Text string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// String renders the base-10 digits, which is what every consumer that lowers a
|
|
25
|
+
// bigint into emitted code reads through `fmt.Sprint`.
|
|
26
|
+
func (obj MetadataBigint) String() string {
|
|
27
|
+
return obj.Text
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// MarshalJSON writes the digits unquoted, so metadata marshaled by a
|
|
31
|
+
// downstream tool carries the same JSON number a plain integer would.
|
|
32
|
+
func (obj MetadataBigint) MarshalJSON() ([]byte, error) {
|
|
33
|
+
if obj.Text == "" {
|
|
34
|
+
return []byte("0"), nil
|
|
35
|
+
}
|
|
36
|
+
return []byte(obj.Text), nil
|
|
37
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package metadata
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestMetadataBigintIsComparable pins the property the whole representation
|
|
10
|
+
// rests on: two independently built values of the same bigint are `==`.
|
|
11
|
+
//
|
|
12
|
+
// A `MetadataConstantValue.Value` is compared with `==` inside the factories --
|
|
13
|
+
// the intersection tag assigner matches a child's constant against the merged
|
|
14
|
+
// parent's that way -- and every other value it can hold (`string`, `bool`, the
|
|
15
|
+
// number) is a comparable value. A pointer stand-in such as `*math/big.Int`
|
|
16
|
+
// builds a fresh allocation per literal, so those comparisons would test
|
|
17
|
+
// identity and, for bigints alone, silently decide "not the same constant":
|
|
18
|
+
// `(1n | 2n) & tags.Type<"int64">` lost its tag that way. This is the guard,
|
|
19
|
+
// held one level below the transform so a future change to the representation
|
|
20
|
+
// fails here rather than as a dropped tag three packages away.
|
|
21
|
+
//
|
|
22
|
+
// 1. Build the same value twice, independently, and compare with `==`.
|
|
23
|
+
// 2. Compare a different value, and a negative against its positive.
|
|
24
|
+
// 3. Check the two renderings every consumer reads: `fmt.Sprint` for the emit
|
|
25
|
+
// and `encoding/json` for metadata a downstream tool marshals.
|
|
26
|
+
func TestMetadataBigintIsComparable(t *testing.T) {
|
|
27
|
+
const digits = "9007199254740993"
|
|
28
|
+
left := MetadataBigint{Text: digits}
|
|
29
|
+
right := MetadataBigint{Text: digits}
|
|
30
|
+
if left != right {
|
|
31
|
+
t.Fatalf("two values of the same bigint must be ==: %v vs %v", left, right)
|
|
32
|
+
}
|
|
33
|
+
var boxed any = left
|
|
34
|
+
if boxed != any(right) {
|
|
35
|
+
t.Fatalf("the same bigint must stay == once boxed in the `any` a constant value holds")
|
|
36
|
+
}
|
|
37
|
+
if left == (MetadataBigint{Text: "9007199254740992"}) {
|
|
38
|
+
t.Fatalf("distinct bigints must not compare equal")
|
|
39
|
+
}
|
|
40
|
+
if left == (MetadataBigint{Text: "-" + digits}) {
|
|
41
|
+
t.Fatalf("a negative must not compare equal to its positive")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if got := fmt.Sprint(left); got != digits {
|
|
45
|
+
t.Fatalf("fmt.Sprint must render the base-10 digits, got %q", got)
|
|
46
|
+
}
|
|
47
|
+
if got := fmt.Sprint(MetadataBigint{Text: "-5"}); got != "-5" {
|
|
48
|
+
t.Fatalf("a negative must keep its sign, got %q", got)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
encoded, err := json.Marshal(left)
|
|
52
|
+
if err != nil {
|
|
53
|
+
t.Fatalf("marshal bigint: %v", err)
|
|
54
|
+
}
|
|
55
|
+
if string(encoded) != digits {
|
|
56
|
+
t.Fatalf("marshaled bigint must be the unquoted digits, got %s", encoded)
|
|
57
|
+
}
|
|
58
|
+
zero, err := json.Marshal(MetadataBigint{})
|
|
59
|
+
if err != nil {
|
|
60
|
+
t.Fatalf("marshal zero bigint: %v", err)
|
|
61
|
+
}
|
|
62
|
+
if string(zero) != "0" {
|
|
63
|
+
t.Fatalf("the zero value must marshal as 0, got %s", zero)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
package reflect
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"encoding/json"
|
|
5
4
|
"strings"
|
|
6
|
-
"unicode"
|
|
7
|
-
"unicode/utf8"
|
|
8
5
|
|
|
9
6
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
10
7
|
shimprinter "github.com/microsoft/typescript-go/shim/printer"
|
|
@@ -67,50 +64,22 @@ func (reflectSchemaTransformerNamespace) Transform(props nativetransform.ITransf
|
|
|
67
64
|
}, props.Context.Emit)
|
|
68
65
|
}
|
|
69
66
|
|
|
67
|
+
// reflectTransformer_literal lowers a metadata tree into its object literal.
|
|
68
|
+
//
|
|
69
|
+
// This used to round-trip the tree through `encoding/json` and then lowercase
|
|
70
|
+
// each key's initial, which is exactly what `LiteralFactory` already does when
|
|
71
|
+
// it reflects a struct. The round trip was not merely redundant: JSON has no
|
|
72
|
+
// bigint, so a `bigint` constant -- the one value in the tree that is neither
|
|
73
|
+
// a string nor a JSON number -- could not survive it. It came back as the
|
|
74
|
+
// object its fields happened to spell, and `IMetadataSchema.IValue` declares
|
|
75
|
+
// `bigint` there.
|
|
76
|
+
//
|
|
77
|
+
// Writing the tree directly leaves every other member identical (diffed over a
|
|
78
|
+
// metadata tree spanning objects, optional and nullable members, tags, arrays,
|
|
79
|
+
// tuples, sets, maps, natives, functions, aliases, and unions: the bigint
|
|
80
|
+
// values were the only difference) and lets a bigint stay a bigint.
|
|
70
81
|
func reflectTransformer_literal(value any, emit ...*shimprinter.EmitContext) *shimast.Node {
|
|
71
|
-
return nativefactories.LiteralFactory.Write(
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
func reflectTransformer_toPrimitive(value any) any {
|
|
75
|
-
data, err := json.Marshal(value)
|
|
76
|
-
if err != nil {
|
|
77
|
-
return nil
|
|
78
|
-
}
|
|
79
|
-
var decoded any
|
|
80
|
-
if err := json.Unmarshal(data, &decoded); err != nil {
|
|
81
|
-
return nil
|
|
82
|
-
}
|
|
83
|
-
return reflectTransformer_lowerKeys(decoded)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
func reflectTransformer_lowerKeys(value any) any {
|
|
87
|
-
switch v := value.(type) {
|
|
88
|
-
case map[string]any:
|
|
89
|
-
output := map[string]any{}
|
|
90
|
-
for key, elem := range v {
|
|
91
|
-
output[reflectTransformer_lowerInitial(key)] = reflectTransformer_lowerKeys(elem)
|
|
92
|
-
}
|
|
93
|
-
return output
|
|
94
|
-
case []any:
|
|
95
|
-
output := make([]any, 0, len(v))
|
|
96
|
-
for _, elem := range v {
|
|
97
|
-
output = append(output, reflectTransformer_lowerKeys(elem))
|
|
98
|
-
}
|
|
99
|
-
return output
|
|
100
|
-
default:
|
|
101
|
-
return value
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
func reflectTransformer_lowerInitial(str string) string {
|
|
106
|
-
if str == "" {
|
|
107
|
-
return str
|
|
108
|
-
}
|
|
109
|
-
r, size := utf8.DecodeRuneInString(str)
|
|
110
|
-
if r == utf8.RuneError && size == 0 {
|
|
111
|
-
return str
|
|
112
|
-
}
|
|
113
|
-
return string(unicode.ToLower(r)) + str[size:]
|
|
82
|
+
return nativefactories.LiteralFactory.Write(value, emit...)
|
|
114
83
|
}
|
|
115
84
|
|
|
116
85
|
func reflectTransformer_errors(errors []nativefactories.MetadataFactory_IError) []nativetransform.TransformerError_MetadataFactory_IError {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typia",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.4",
|
|
4
4
|
"description": "Superfast runtime validators with only one line",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"homepage": "https://typia.io",
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"randexp": "^0.5.3",
|
|
46
|
-
"@typia/interface": "^14.0.
|
|
47
|
-
"@typia/utils": "^14.0.
|
|
46
|
+
"@typia/interface": "^14.0.4",
|
|
47
|
+
"@typia/utils": "^14.0.4"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"ttsc": ">=0.19.2"
|