typia 14.0.3 → 14.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. package/native/cmd/ttsc-typia/bigint_literal_precision_transform_test.go +135 -0
  2. package/native/cmd/ttsc-typia/recursive_container_helper_index_transform_test.go +206 -0
  3. package/native/cmd/ttsc-typia/reflect_literals_bigint_transform_test.go +148 -0
  4. package/native/cmd/ttsc-typia/reflect_literals_non_literal_rejection_transform_test.go +24 -18
  5. package/native/cmd/ttsc-typia/reflect_schema_bigint_transform_test.go +173 -0
  6. package/native/cmd/ttsc-typia/wrong_type_tag_target_diagnostic_test.go +88 -0
  7. package/native/core/factories/ExpressionFactory.go +11 -1
  8. package/native/core/factories/LiteralFactory.go +3 -0
  9. package/native/core/factories/MetadataTypeTagFactory.go +27 -6
  10. package/native/core/factories/internal/metadata/iterate_metadata_constant.go +3 -0
  11. package/native/core/programmers/RandomProgrammer.go +10 -4
  12. package/native/core/programmers/internal/CheckerProgrammer.go +14 -6
  13. package/native/core/programmers/json/JsonStringifyProgrammer.go +14 -6
  14. package/native/core/programmers/notations/NotationGeneralProgrammer.go +14 -6
  15. package/native/core/programmers/plain/PlainClassifyProgrammer.go +14 -6
  16. package/native/core/programmers/plain/PlainCloneProgrammer.go +14 -6
  17. package/native/core/programmers/plain/PlainPruneProgrammer.go +30 -10
  18. package/native/core/programmers/reflect/ReflectLiteralsProgrammer.go +10 -27
  19. package/native/core/schemas/metadata/MetadataBigint.go +37 -0
  20. package/native/core/schemas/metadata/metadata_bigint_is_comparable_test.go +65 -0
  21. package/native/transform/features/reflect/ReflectSchemaTransformer.go +15 -46
  22. package/package.json +3 -3
  23. package/native/cmd/ttsc-typia/reflect_literals_empty_union_transform_test.go +0 -161
@@ -96,12 +96,16 @@ func plainCloneProgrammer_write_array_functions(props struct {
96
96
  }) []*shimast.Node {
97
97
  f := nativecontext.EmitFactoryOf(plainCloneProgrammer_factory, props.Context.Emit)
98
98
  output := []*shimast.Node{}
99
- for i, typ := range props.Collection.Arrays() {
99
+ for _, typ := range props.Collection.Arrays() {
100
100
  if typ.Recursive == false {
101
101
  continue
102
102
  }
103
+ index := 0
104
+ if typ.Index != nil {
105
+ index = *typ.Index
106
+ }
103
107
  output = append(output, nativefactories.StatementFactory.Constant(nativefactories.StatementFactory_ConstantProps{
104
- Name: fmt.Sprintf("%sa%d", props.Config.Prefix, i),
108
+ Name: fmt.Sprintf("%sa%d", props.Config.Prefix, index),
105
109
  Value: f.NewArrowFunction(
106
110
  nil,
107
111
  nil,
@@ -114,7 +118,7 @@ func plainCloneProgrammer_write_array_functions(props struct {
114
118
  nil,
115
119
  f.NewToken(shimast.KindEqualsGreaterThanToken),
116
120
  nativeinternal.FeatureProgrammer.VisitGuardRebuild(
117
- nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "a", i),
121
+ nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "a", index),
118
122
  true,
119
123
  plainCloneProgrammer_decode_array_inline(plainCloneProgrammer_decodeArrayProps{
120
124
  Context: props.Context,
@@ -148,12 +152,16 @@ func plainCloneProgrammer_write_tuple_functions(props struct {
148
152
  }) []*shimast.Node {
149
153
  f := nativecontext.EmitFactoryOf(plainCloneProgrammer_factory, props.Context.Emit)
150
154
  output := []*shimast.Node{}
151
- for i, tuple := range props.Collection.Tuples() {
155
+ for _, tuple := range props.Collection.Tuples() {
152
156
  if tuple.Recursive == false {
153
157
  continue
154
158
  }
159
+ index := 0
160
+ if tuple.Index != nil {
161
+ index = *tuple.Index
162
+ }
155
163
  output = append(output, nativefactories.StatementFactory.Constant(nativefactories.StatementFactory_ConstantProps{
156
- Name: fmt.Sprintf("%st%d", props.Config.Prefix, i),
164
+ Name: fmt.Sprintf("%st%d", props.Config.Prefix, index),
157
165
  Value: f.NewArrowFunction(
158
166
  nil,
159
167
  nil,
@@ -166,7 +174,7 @@ func plainCloneProgrammer_write_tuple_functions(props struct {
166
174
  nil,
167
175
  f.NewToken(shimast.KindEqualsGreaterThanToken),
168
176
  nativeinternal.FeatureProgrammer.VisitGuardRebuild(
169
- nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "t", i),
177
+ nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "t", index),
170
178
  true,
171
179
  plainCloneProgrammer_decode_tuple_inline(plainCloneProgrammer_decodeTupleInlineProps{
172
180
  Context: props.Context,
@@ -93,12 +93,16 @@ func plainPruneProgrammer_write_array_functions(props struct {
93
93
  }) []*shimast.Node {
94
94
  f := nativecontext.EmitFactoryOf(plainPruneProgrammer_factory, props.Context.Emit)
95
95
  output := []*shimast.Node{}
96
- for i, typ := range props.Collection.Arrays() {
96
+ for _, typ := range props.Collection.Arrays() {
97
97
  if typ.Recursive == false {
98
98
  continue
99
99
  }
100
+ index := 0
101
+ if typ.Index != nil {
102
+ index = *typ.Index
103
+ }
100
104
  output = append(output, nativefactories.StatementFactory.Constant(nativefactories.StatementFactory_ConstantProps{
101
- Name: fmt.Sprintf("%sa%d", props.Config.Prefix, i),
105
+ Name: fmt.Sprintf("%sa%d", props.Config.Prefix, index),
102
106
  Value: f.NewArrowFunction(
103
107
  nil,
104
108
  nil,
@@ -111,7 +115,7 @@ func plainPruneProgrammer_write_array_functions(props struct {
111
115
  nil,
112
116
  f.NewToken(shimast.KindEqualsGreaterThanToken),
113
117
  nativeinternal.FeatureProgrammer.VisitGuardSkip(
114
- nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "a", i),
118
+ nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "a", index),
115
119
  plainPruneProgrammer_decode_array_inline(plainPruneProgrammer_decodeArrayProps{
116
120
  Context: props.Context,
117
121
  Config: props.Config,
@@ -144,12 +148,16 @@ func plainPruneProgrammer_write_tuple_functions(props struct {
144
148
  }) []*shimast.Node {
145
149
  f := nativecontext.EmitFactoryOf(plainPruneProgrammer_factory, props.Context.Emit)
146
150
  output := []*shimast.Node{}
147
- for i, tuple := range props.Collection.Tuples() {
151
+ for _, tuple := range props.Collection.Tuples() {
148
152
  if tuple.Recursive == false {
149
153
  continue
150
154
  }
155
+ index := 0
156
+ if tuple.Index != nil {
157
+ index = *tuple.Index
158
+ }
151
159
  output = append(output, nativefactories.StatementFactory.Constant(nativefactories.StatementFactory_ConstantProps{
152
- Name: fmt.Sprintf("%st%d", props.Config.Prefix, i),
160
+ Name: fmt.Sprintf("%st%d", props.Config.Prefix, index),
153
161
  Value: f.NewArrowFunction(
154
162
  nil,
155
163
  nil,
@@ -162,7 +170,7 @@ func plainPruneProgrammer_write_tuple_functions(props struct {
162
170
  nil,
163
171
  f.NewToken(shimast.KindEqualsGreaterThanToken),
164
172
  nativeinternal.FeatureProgrammer.VisitGuardSkip(
165
- nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "t", i),
173
+ nativeinternal.FeatureProgrammer.VisitKey(props.Config.Prefix, "t", index),
166
174
  plainPruneProgrammer_decode_tuple_inline(plainPruneProgrammer_decodeTupleInlineProps{
167
175
  Context: props.Context,
168
176
  Config: props.Config,
@@ -805,6 +813,14 @@ func plainPruneProgrammer_initializer(props nativeinternal.FeatureProgrammer_Ini
805
813
  }
806
814
 
807
815
  func plainPruneProgrammer_filter(metadata *schemametadata.MetadataSchema) bool {
816
+ return plainPruneProgrammer_filter_visited(metadata, map[*schemametadata.MetadataSchema]bool{})
817
+ }
818
+
819
+ func plainPruneProgrammer_filter_visited(metadata *schemametadata.MetadataSchema, visited map[*schemametadata.MetadataSchema]bool) bool {
820
+ if visited[metadata] {
821
+ return false
822
+ }
823
+ visited[metadata] = true
808
824
  if metadata.Any {
809
825
  return false
810
826
  }
@@ -812,12 +828,12 @@ func plainPruneProgrammer_filter(metadata *schemametadata.MetadataSchema) bool {
812
828
  return true
813
829
  }
814
830
  for _, tuple := range metadata.Tuples {
815
- if plainPruneProgrammer_tuple_filter(tuple) {
831
+ if plainPruneProgrammer_tuple_filter_visited(tuple, visited) {
816
832
  return true
817
833
  }
818
834
  }
819
835
  for _, array := range metadata.Arrays {
820
- if plainPruneProgrammer_filter(array.Type.Value) {
836
+ if plainPruneProgrammer_filter_visited(array.Type.Value, visited) {
821
837
  return true
822
838
  }
823
839
  }
@@ -825,11 +841,15 @@ func plainPruneProgrammer_filter(metadata *schemametadata.MetadataSchema) bool {
825
841
  }
826
842
 
827
843
  func plainPruneProgrammer_tuple_filter(tuple *schemametadata.MetadataTuple) bool {
844
+ return plainPruneProgrammer_tuple_filter_visited(tuple, map[*schemametadata.MetadataSchema]bool{})
845
+ }
846
+
847
+ func plainPruneProgrammer_tuple_filter_visited(tuple *schemametadata.MetadataTuple, visited map[*schemametadata.MetadataSchema]bool) bool {
828
848
  return len(tuple.Type.Elements) != 0 && plainPruneProgrammer_some_schema(tuple.Type.Elements, func(elem *schemametadata.MetadataSchema) bool {
829
849
  if elem.Rest != nil {
830
- return plainPruneProgrammer_filter(elem.Rest)
850
+ return plainPruneProgrammer_filter_visited(elem.Rest, visited)
831
851
  }
832
- return plainPruneProgrammer_filter(elem)
852
+ return plainPruneProgrammer_filter_visited(elem, visited)
833
853
  })
834
854
  }
835
855
 
@@ -29,24 +29,12 @@ func (reflectLiteralsProgrammerNamespace) Write(props ReflectLiteralsProgrammer_
29
29
  Escape: true,
30
30
  Constant: true,
31
31
  Absorb: true,
32
- // Accept exactly what the emitter below can render: constant literals,
33
- // the `boolean` atomic (the `true | false` union), and `null`. `null` is
34
- // a `MetadataSchema` flag rather than a bucket, so `Size()` cannot see
35
- // it; weighing it on both sides keeps `length` meaning "renderable
36
- // members" against `size` meaning "members", which is what lets a
37
- // rejected argument carry the diagnostic its composition calls for
38
- // instead of collapsing onto `NO`.
39
- //
40
- // Equal counts with no member at all is an uninhabited argument: the
41
- // `never` keyword, an alias or `Exclude`/`Extract` that filters
42
- // everything away, or an intersection whose every distributed member
43
- // prunes to never. Its literal set is empty and
44
- // `literals<never>(): never[]` has exactly one inhabitant, so it emits
45
- // `[]` rather than a diagnostic (issue #2377). `undefined` and `void`
46
- // coalesce to the same empty metadata, but neither satisfies the public
47
- // `T extends Atomic.Type | null` bound, and typia refuses to transform
48
- // at all without `strictNullChecks` (`transform/transform.go`), so
49
- // neither reaches this predicate.
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).
50
38
  Validate: func(next struct {
51
39
  Metadata *nativemetadata.MetadataSchema
52
40
  Explore nativefactories.MetadataFactory_IExplore
@@ -61,18 +49,13 @@ func (reflectLiteralsProgrammerNamespace) Write(props ReflectLiteralsProgrammer_
61
49
  length++
62
50
  }
63
51
  }
64
- size := next.Metadata.Size()
65
- if next.Metadata.Nullable {
66
- length++
67
- size++
68
- }
69
- if size == length {
70
- return []string{}
71
- }
72
52
  if length == 0 {
73
53
  return []string{string(ReflectLiteralsProgrammer_ErrorMessages_NO)}
74
54
  }
75
- return []string{string(ReflectLiteralsProgrammer_ErrorMessages_ONLY)}
55
+ if next.Metadata.Size() != length {
56
+ return []string{string(ReflectLiteralsProgrammer_ErrorMessages_ONLY)}
57
+ }
58
+ return []string{}
76
59
  },
77
60
  },
78
61
  Components: nativemetadata.NewMetadataCollection(),
@@ -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.3",
3
+ "version": "14.0.5",
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.3",
47
- "@typia/utils": "^14.0.3"
46
+ "@typia/interface": "^14.0.5",
47
+ "@typia/utils": "^14.0.5"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "ttsc": ">=0.19.2"
@@ -1,161 +0,0 @@
1
- package main
2
-
3
- import (
4
- "os"
5
- "os/exec"
6
- "path/filepath"
7
- "testing"
8
- )
9
-
10
- // TestReflectLiteralsEmptyUnionTransform pins issue #2377: the literal set of an
11
- // empty union is the empty array, not a transform error.
12
- //
13
- // The programmer admits a type argument by counting the members it can render,
14
- // and it renders three kinds -- constant literals, the `boolean` atomic, and
15
- // `null`. `null` is a `MetadataSchema` flag rather than a bucket, so it was
16
- // missing from the count, which made two admissible arguments look like "no
17
- // constant literal type found": `never`, whose union is empty on both sides of
18
- // the comparison, and `null`, which the public
19
- // `literals<T extends Atomic.Type | null>()` signature documents. `never` is
20
- // vacuously literal-only and `literals<never>(): never[]` has exactly one
21
- // inhabitant, so both must transform and run.
22
- //
23
- // 1. Transform a module whose `reflect.literals` arguments span every
24
- // uninhabited spelling a caller reaches -- the keyword, an alias, an
25
- // exhaustive `Exclude`, an empty `Extract`, a TypeScript-collapsed
26
- // intersection, and one typia prunes itself -- plus `null`, a fully mixed
27
- // literal union, and the plain `boolean` atomic.
28
- // 2. Execute the emitted CommonJS.
29
- // 3. Assert `never` yields `[]`, `null` yields `[null]`, and the arguments that
30
- // already worked keep their exact members and order, including the sort the
31
- // guide documents.
32
- func TestReflectLiteralsEmptyUnionTransform(t *testing.T) {
33
- node, err := exec.LookPath("node")
34
- if err != nil {
35
- t.Skip("node executable not found")
36
- }
37
- root := ttscTypiaTestRepoRoot(t)
38
- base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
39
- if err := os.MkdirAll(base, 0o755); err != nil {
40
- t.Fatalf("mkdir temp base: %v", err)
41
- }
42
- dir, err := os.MkdirTemp(base, "reflect-literals-empty-")
43
- if err != nil {
44
- t.Fatalf("create temp fixture: %v", err)
45
- }
46
- t.Cleanup(func() { _ = os.RemoveAll(dir) })
47
- src := filepath.Join(dir, "src")
48
- if err := os.MkdirAll(src, 0o755); err != nil {
49
- t.Fatalf("mkdir fixture src: %v", err)
50
- }
51
- if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(atomicIntersectionSchemaTSConfig), 0o644); err != nil {
52
- t.Fatalf("write tsconfig: %v", err)
53
- }
54
- if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(reflectLiteralsEmptyUnionSource), 0o644); err != nil {
55
- t.Fatalf("write source: %v", err)
56
- }
57
- ttscTypiaTestTypecheck(t, dir)
58
-
59
- out, errText, code := ttscTypiaTestCapture(func() int {
60
- return runTransform([]string{
61
- "--cwd", dir,
62
- "--tsconfig", "tsconfig.json",
63
- "--file", "src/main.ts",
64
- "--output", "js",
65
- })
66
- })
67
- if code != 0 {
68
- t.Fatalf("reflect.literals empty union transform failed: code=%d stderr=\n%s", code, errText)
69
- }
70
-
71
- runtimeDir := filepath.Join(dir, "runtime")
72
- if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
73
- t.Fatalf("mkdir runtime dir: %v", err)
74
- }
75
- ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
76
- runtimeJS := ttscTypiaTestRewriteCommonJS(t, out)
77
- if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
78
- t.Fatalf("write runtime module: %v", err)
79
- }
80
- runner := filepath.Join(runtimeDir, "run.cjs")
81
- if err := os.WriteFile(runner, []byte(reflectLiteralsEmptyUnionRunner), 0o644); err != nil {
82
- t.Fatalf("write runtime runner: %v", err)
83
- }
84
- cmd := exec.Command(node, runner)
85
- cmd.Dir = runtimeDir
86
- output, err := cmd.CombinedOutput()
87
- if err != nil {
88
- t.Fatalf("reflect.literals empty union runtime cases failed: %v\n%s", err, output)
89
- }
90
- }
91
-
92
- const reflectLiteralsEmptyUnionSource = `import typia from "typia";
93
-
94
- type Color = "red" | "green" | "blue";
95
- type Empty = never;
96
-
97
- // The empty union: no member on either side of the admission comparison. Every
98
- // spelling a caller reaches it through has to transform, not only the keyword:
99
- // an alias hides it behind a name, and a conditional or an exhaustive Exclude
100
- // is how a generic caller produces one without writing "never" at all.
101
- export const empty = typia.reflect.literals<never>();
102
- export const emptyAlias = typia.reflect.literals<Empty>();
103
- export const emptyExclude = typia.reflect.literals<Exclude<Color, Color>>();
104
- export const emptyExtract = typia.reflect.literals<Extract<Color, "cyan">>();
105
-
106
- // Uninhabited without the keyword: TypeScript collapses one to never itself,
107
- // while the other stays an intersection whose every distributed member typia
108
- // prunes away. Both are empty on both sides of the comparison, so the rule the
109
- // keyword exercises has to cover them too.
110
- export const emptyCollapsed = typia.reflect.literals<string & number>();
111
- export const emptyPruned = typia.reflect.literals<(string | number) & { data: number }>();
112
-
113
- // A union whose only member is the nullable flag, which carries no bucket.
114
- export const onlyNull = typia.reflect.literals<null>();
115
-
116
- // Controls that already transformed: the flag must join the count without
117
- // displacing a constant, a boolean atomic, or their order.
118
- export const mixed = typia.reflect.literals<"a" | 1 | true | null>();
119
- export const booleanAtomic = typia.reflect.literals<boolean>();
120
- export const booleanAtomicNull = typia.reflect.literals<boolean | null>();
121
-
122
- // Declaration order deliberately disagrees with the emitted order: values of
123
- // one primitive type are sorted (iterate_metadata_sort), which is the behavior
124
- // the reflect.literals guide states.
125
- export const sorted = typia.reflect.literals<"b" | "a" | 10 | 2>();
126
- `
127
-
128
- const reflectLiteralsEmptyUnionRunner = `const mod = require("./main.cjs");
129
-
130
- const check = (label, actual, expected) => {
131
- if (Array.isArray(actual) === false) {
132
- throw new Error(label + ": expected an array, got " + JSON.stringify(actual));
133
- }
134
- if (
135
- actual.length !== expected.length ||
136
- actual.some((item, index) => item !== expected[index])
137
- ) {
138
- throw new Error(
139
- label +
140
- ": expected " +
141
- JSON.stringify(expected) +
142
- ", got " +
143
- JSON.stringify(actual),
144
- );
145
- }
146
- };
147
-
148
- check("never", mod.empty, []);
149
- check("never alias", mod.emptyAlias, []);
150
- check("exhaustive Exclude", mod.emptyExclude, []);
151
- check("empty Extract", mod.emptyExtract, []);
152
- check("collapsed intersection", mod.emptyCollapsed, []);
153
- check("pruned intersection", mod.emptyPruned, []);
154
- check("null", mod.onlyNull, [null]);
155
- check("mixed", mod.mixed, ["a", 1, true, null]);
156
- check("booleanAtomic", mod.booleanAtomic, [true, false]);
157
- check("booleanAtomicNull", mod.booleanAtomicNull, [true, false, null]);
158
- check("sorted", mod.sorted, ["a", "b", 2, 10]);
159
-
160
- console.log("ok");
161
- `