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.
@@ -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.NewIdentifier(fmt.Sprint(value)),
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(reflectTransformer_toPrimitive(value), emit...)
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.2",
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.2",
47
- "@typia/utils": "^14.0.2"
46
+ "@typia/interface": "^14.0.4",
47
+ "@typia/utils": "^14.0.4"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "ttsc": ">=0.19.2"