typia 13.1.19 → 13.2.0

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 (169) hide show
  1. package/lib/executable/FileSystemIdentity.d.ts +23 -0
  2. package/lib/executable/FileSystemIdentity.js +30 -0
  3. package/lib/executable/FileSystemIdentity.js.map +1 -1
  4. package/lib/executable/FileSystemIdentity.mjs +4 -0
  5. package/lib/executable/FileSystemIdentity.mjs.map +1 -1
  6. package/lib/executable/TypiaGenerateWizard.js +15 -9
  7. package/lib/executable/TypiaGenerateWizard.js.map +1 -1
  8. package/lib/executable/TypiaGenerateWizard.mjs +11 -7
  9. package/lib/executable/TypiaGenerateWizard.mjs.map +1 -1
  10. package/lib/internal/_ProtobufReader.d.ts +18 -3
  11. package/lib/internal/_ProtobufReader.js +39 -11
  12. package/lib/internal/_ProtobufReader.js.map +1 -1
  13. package/lib/internal/_ProtobufReader.mjs +37 -10
  14. package/lib/internal/_ProtobufReader.mjs.map +1 -1
  15. package/lib/internal/_jsonStringifyArray.d.ts +1 -0
  16. package/lib/internal/_jsonStringifyArray.js +48 -0
  17. package/lib/internal/_jsonStringifyArray.js.map +1 -0
  18. package/lib/internal/_jsonStringifyArray.mjs +40 -0
  19. package/lib/internal/_jsonStringifyArray.mjs.map +1 -0
  20. package/lib/internal/_jsonStringifyElement.d.ts +1 -0
  21. package/lib/internal/_jsonStringifyElement.js +23 -0
  22. package/lib/internal/_jsonStringifyElement.js.map +1 -0
  23. package/lib/internal/_jsonStringifyElement.mjs +23 -0
  24. package/lib/internal/_jsonStringifyElement.mjs.map +1 -0
  25. package/lib/internal/_jsonStringifyProperty.d.ts +1 -0
  26. package/lib/internal/_jsonStringifyProperty.js +31 -0
  27. package/lib/internal/_jsonStringifyProperty.js.map +1 -0
  28. package/lib/internal/_jsonStringifyProperty.mjs +31 -0
  29. package/lib/internal/_jsonStringifyProperty.mjs.map +1 -0
  30. package/lib/internal/_llmApplicationFinalize.js +1 -1
  31. package/lib/internal/_llmApplicationFinalize.js.map +1 -1
  32. package/lib/internal/_llmApplicationFinalize.mjs +1 -1
  33. package/lib/internal/_llmApplicationFinalize.mjs.map +1 -1
  34. package/lib/internal/_randomFormatByte.js +24 -1
  35. package/lib/internal/_randomFormatByte.js.map +1 -1
  36. package/lib/internal/_randomFormatByte.mjs +20 -2
  37. package/lib/internal/_randomFormatByte.mjs.map +1 -1
  38. package/lib/internal/_randomFormatDate.d.ts +3 -6
  39. package/lib/internal/_randomFormatDate.js +2 -10
  40. package/lib/internal/_randomFormatDate.js.map +1 -1
  41. package/lib/internal/_randomFormatDate.mjs +2 -6
  42. package/lib/internal/_randomFormatDate.mjs.map +1 -1
  43. package/lib/internal/_randomFormatDatetime.d.ts +3 -6
  44. package/lib/internal/_randomFormatDatetime.js +25 -8
  45. package/lib/internal/_randomFormatDatetime.js.map +1 -1
  46. package/lib/internal/_randomFormatDatetime.mjs +17 -7
  47. package/lib/internal/_randomFormatDatetime.mjs.map +1 -1
  48. package/lib/internal/_randomFormatDuration.js +23 -15
  49. package/lib/internal/_randomFormatDuration.js.map +1 -1
  50. package/lib/internal/_randomFormatDuration.mjs +23 -15
  51. package/lib/internal/_randomFormatDuration.mjs.map +1 -1
  52. package/lib/internal/_randomFormatIpv4.js +31 -1
  53. package/lib/internal/_randomFormatIpv4.js.map +1 -1
  54. package/lib/internal/_randomFormatIpv4.mjs +23 -2
  55. package/lib/internal/_randomFormatIpv4.mjs.map +1 -1
  56. package/lib/internal/_randomFormatIpv6.js +57 -1
  57. package/lib/internal/_randomFormatIpv6.js.map +1 -1
  58. package/lib/internal/_randomFormatIpv6.mjs +45 -2
  59. package/lib/internal/_randomFormatIpv6.mjs.map +1 -1
  60. package/lib/internal/_randomFormatJsonPointer.js +14 -2
  61. package/lib/internal/_randomFormatJsonPointer.js.map +1 -1
  62. package/lib/internal/_randomFormatJsonPointer.mjs +13 -3
  63. package/lib/internal/_randomFormatJsonPointer.mjs.map +1 -1
  64. package/lib/internal/_randomFormatRegex.js +14 -1
  65. package/lib/internal/_randomFormatRegex.js.map +1 -1
  66. package/lib/internal/_randomFormatRegex.mjs +14 -2
  67. package/lib/internal/_randomFormatRegex.mjs.map +1 -1
  68. package/lib/internal/_randomFormatRelativeJsonPointer.js +19 -5
  69. package/lib/internal/_randomFormatRelativeJsonPointer.js.map +1 -1
  70. package/lib/internal/_randomFormatRelativeJsonPointer.mjs +21 -6
  71. package/lib/internal/_randomFormatRelativeJsonPointer.mjs.map +1 -1
  72. package/lib/internal/_randomFormatTime.js +33 -7
  73. package/lib/internal/_randomFormatTime.js.map +1 -1
  74. package/lib/internal/_randomFormatTime.mjs +37 -5
  75. package/lib/internal/_randomFormatTime.mjs.map +1 -1
  76. package/lib/internal/_randomPattern.js.map +1 -1
  77. package/lib/internal/_randomPattern.mjs.map +1 -1
  78. package/lib/internal/_randomStringLength.d.ts +41 -10
  79. package/lib/internal/_randomStringLength.js +49 -13
  80. package/lib/internal/_randomStringLength.js.map +1 -1
  81. package/lib/internal/_randomStringLength.mjs +48 -13
  82. package/lib/internal/_randomStringLength.mjs.map +1 -1
  83. package/lib/internal/private/__notationRename.js.map +1 -1
  84. package/lib/internal/private/__notationRename.mjs.map +1 -1
  85. package/lib/internal/private/__randomComposition.d.ts +23 -0
  86. package/lib/internal/private/__randomComposition.js +48 -0
  87. package/lib/internal/private/__randomComposition.js.map +1 -0
  88. package/lib/internal/private/__randomComposition.mjs +54 -0
  89. package/lib/internal/private/__randomComposition.mjs.map +1 -0
  90. package/lib/internal/private/__randomEpoch.d.ts +19 -0
  91. package/lib/internal/private/__randomEpoch.js +31 -0
  92. package/lib/internal/private/__randomEpoch.js.map +1 -0
  93. package/lib/internal/private/__randomEpoch.mjs +28 -0
  94. package/lib/internal/private/__randomEpoch.mjs.map +1 -0
  95. package/native/cmd/ttsc-typia/build.go +3 -0
  96. package/native/cmd/ttsc-typia/callable_interface_function_semantics_transform_test.go +331 -0
  97. package/native/cmd/ttsc-typia/callable_type_literal_boundaries_transform_test.go +1184 -0
  98. package/native/cmd/ttsc-typia/compare_functional_union_membership_transform_test.go +434 -0
  99. package/native/cmd/ttsc-typia/compare_template_literal_union_transform_test.go +241 -0
  100. package/native/cmd/ttsc-typia/default_library_spoof_native_identity_transform_test.go +216 -0
  101. package/native/cmd/ttsc-typia/json_schema_dynamic_key_determinism_transform_test.go +246 -0
  102. package/native/cmd/ttsc-typia/json_stringify_contextual_undefined_transform_test.go +556 -0
  103. package/native/cmd/ttsc-typia/lib_replacement_native_identity_transform_test.go +277 -0
  104. package/native/cmd/ttsc-typia/native_named_intersection_union_transform_test.go +390 -0
  105. package/native/cmd/ttsc-typia/node_buffer_native_exports_is_transform_test.go +526 -0
  106. package/native/cmd/ttsc-typia/package_declaration_native_identity_is_transform_test.go +293 -0
  107. package/native/cmd/ttsc-typia/protobuf_diagnostic_code_transform_test.go +167 -0
  108. package/native/cmd/ttsc-typia/symbol_index_domain_transform_test.go +316 -0
  109. package/native/cmd/ttsc-typia/transform.go +23 -0
  110. package/native/cmd/ttsc-typia/transform_helpers_test.go +77 -2
  111. package/native/cmd/ttsc-typia/user_global_native_identity_transform_test.go +484 -0
  112. package/native/core/factories/ProtobufFactory.go +6 -1
  113. package/native/core/factories/internal/metadata/MetadataHelper.go +185 -30
  114. package/native/core/factories/internal/metadata/emplace_metadata_object.go +25 -0
  115. package/native/core/factories/internal/metadata/iterate_metadata_function.go +1 -1
  116. package/native/core/factories/internal/metadata/iterate_metadata_intersection.go +103 -23
  117. package/native/core/factories/internal/metadata/iterate_metadata_map.go +3 -7
  118. package/native/core/factories/internal/metadata/iterate_metadata_native.go +32 -14
  119. package/native/core/factories/internal/metadata/iterate_metadata_set.go +3 -7
  120. package/native/core/programmers/compare/CompareEqualProgrammer.go +27 -13
  121. package/native/core/programmers/helpers/FunctionProgrammer.go +6 -13
  122. package/native/core/programmers/helpers/StringifyJoinder.go +129 -50
  123. package/native/core/programmers/helpers/joiner_branch_coverage_test.go +116 -120
  124. package/native/core/programmers/iterate/iterate_check_coverage_test.go +76 -158
  125. package/native/core/programmers/iterate/iterate_utility_coverage_test.go +231 -270
  126. package/native/core/programmers/iterate/json_schema_object.go +49 -7
  127. package/native/core/programmers/iterate/json_schema_pattern_property_order_test.go +44 -0
  128. package/native/core/programmers/iterate/openapi_v3_downgrader.go +34 -1
  129. package/native/core/programmers/iterate/template_runtime_pattern.go +11 -0
  130. package/native/core/programmers/json/JsonStringifyProgrammer.go +47 -6
  131. package/native/core/programmers/llm/LlmApplicationProgrammer.go +9 -1
  132. package/native/core/programmers/protobuf/ProtobufMessageProgrammer.go +1 -1
  133. package/native/core/schemas/metadata/MetadataCollection.go +0 -9
  134. package/native/core/schemas/metadata/MetadataDefaultLibrary.go +81 -0
  135. package/native/core/schemas/metadata/MetadataObjectType.go +7 -18
  136. package/native/go.mod +4 -4
  137. package/native/transform/features/protobuf/ProtobufMessageTransformer.go +1 -1
  138. package/package.json +3 -7
  139. package/src/executable/FileSystemIdentity.ts +33 -0
  140. package/src/executable/TypiaGenerateWizard.ts +18 -12
  141. package/src/internal/_ProtobufReader.ts +40 -11
  142. package/src/internal/_jsonStringifyArray.ts +50 -0
  143. package/src/internal/_jsonStringifyElement.ts +19 -0
  144. package/src/internal/_jsonStringifyProperty.ts +30 -0
  145. package/src/internal/_llmApplicationFinalize.ts +6 -1
  146. package/src/internal/_randomFormatByte.ts +30 -3
  147. package/src/internal/_randomFormatDate.ts +4 -20
  148. package/src/internal/_randomFormatDatetime.ts +34 -20
  149. package/src/internal/_randomFormatDuration.ts +19 -4
  150. package/src/internal/_randomFormatIpv4.ts +43 -3
  151. package/src/internal/_randomFormatIpv6.ts +67 -3
  152. package/src/internal/_randomFormatJsonPointer.ts +23 -3
  153. package/src/internal/_randomFormatRegex.ts +21 -3
  154. package/src/internal/_randomFormatRelativeJsonPointer.ts +23 -10
  155. package/src/internal/_randomFormatTime.ts +45 -12
  156. package/src/internal/_randomPattern.ts +4 -1
  157. package/src/internal/_randomStringLength.ts +61 -17
  158. package/src/internal/private/__notationRename.ts +1 -4
  159. package/src/internal/private/__randomComposition.ts +50 -0
  160. package/src/internal/private/__randomEpoch.ts +34 -0
  161. package/native/adapter/visit.go +0 -173
  162. package/native/adapter/visit_property_chain_edges_test.go +0 -129
  163. package/native/core/programmers/helpers/disable_function_programmer_declare.go +0 -10
  164. package/native/core/programmers/iterate/check_union_array_like.go +0 -422
  165. package/native/core/programmers/iterate/prune_object_properties.go +0 -105
  166. package/native/core/programmers/iterate/stringify_dynamic_properties.go +0 -219
  167. package/native/core/programmers/iterate/stringify_native.go +0 -14
  168. package/native/core/programmers/iterate/stringify_regular_properties.go +0 -112
  169. package/native/core/schemas/metadata/MetadataApplication.go +0 -41
@@ -0,0 +1,434 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "os/exec"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // TestCompareFunctionalUnionMembershipTransform verifies that compare.equals
12
+ // selects an object-union member with the same functional-property semantics as
13
+ // typia.is and the plain member routers (samchon/typia#2252).
14
+ //
15
+ // Object comparison deliberately ignores function-valued properties after it
16
+ // has selected a member. That must not make those properties invisible while
17
+ // selecting a member when the active `functional: true` option makes them part
18
+ // of membership. Otherwise a string-valued later member enters an earlier
19
+ // function-bearing member, and equality or cover can ignore the later member's
20
+ // distinguishing data. The default mode remains intentionally lenient for a
21
+ // sole function-valued property.
22
+ //
23
+ // 1. Transform functional and default-option variants of direct/factory is,
24
+ // equals, cover, clone, prune, and nested equality calls over both union
25
+ // orders.
26
+ // 2. Execute distinct valid, invalid, same-reference, reversed-order, nested,
27
+ // and actual-function-member controls against the emitted JavaScript. The
28
+ // order-symmetric pair makes the function member the later declaration and
29
+ // changes only its later-member label, so member routing rather than
30
+ // function identity decides the result.
31
+ // 3. Require compare.less to retain its existing multi-object-union diagnostic.
32
+ func TestCompareFunctionalUnionMembershipTransform(t *testing.T) {
33
+ project := compareFunctionalUnionMembershipProject(t)
34
+ functional := compareFunctionalUnionMembershipTransform(t, project, true)
35
+ defaultMode := compareFunctionalUnionMembershipTransform(t, project, false)
36
+ compareFunctionalUnionMembershipRunRuntimeCases(t, project, functional, defaultMode)
37
+ if strings.Contains(functional, `typeof v.handler === "function"`) == false {
38
+ t.Fatalf("functional equality emit did not retain the function membership guard:\n%s", functional)
39
+ }
40
+ compareFunctionalUnionMembershipRejectsLess(t, project)
41
+ }
42
+
43
+ func compareFunctionalUnionMembershipProject(t *testing.T) string {
44
+ t.Helper()
45
+ base := os.Getenv("TYPIA_TEST_TMPDIR")
46
+ if base == "" {
47
+ root := ttscTypiaTestRepoRoot(t)
48
+ base = filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
49
+ }
50
+ if err := os.MkdirAll(base, 0o755); err != nil {
51
+ t.Fatalf("mkdir temp base: %v", err)
52
+ }
53
+ dir, err := os.MkdirTemp(base, "compare-functional-union-")
54
+ if err != nil {
55
+ t.Fatalf("create temp fixture: %v", err)
56
+ }
57
+ t.Cleanup(func() {
58
+ _ = os.RemoveAll(dir)
59
+ })
60
+ src := filepath.Join(dir, "src")
61
+ if err := os.MkdirAll(src, 0o755); err != nil {
62
+ t.Fatalf("mkdir fixture src: %v", err)
63
+ }
64
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.main.json"), []byte(compareFunctionalUnionMembershipTSConfig("src/main.ts")), 0o644); err != nil {
65
+ t.Fatalf("write main tsconfig: %v", err)
66
+ }
67
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.less.json"), []byte(compareFunctionalUnionMembershipTSConfig("src/less.ts")), 0o644); err != nil {
68
+ t.Fatalf("write less tsconfig: %v", err)
69
+ }
70
+ compareFunctionalUnionMembershipWriteTypiaFixture(t, dir)
71
+ if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(compareFunctionalUnionMembershipSource), 0o644); err != nil {
72
+ t.Fatalf("write source: %v", err)
73
+ }
74
+ if err := os.WriteFile(filepath.Join(src, "less.ts"), []byte(compareFunctionalUnionMembershipLessSource), 0o644); err != nil {
75
+ t.Fatalf("write less source: %v", err)
76
+ }
77
+ return dir
78
+ }
79
+
80
+ func compareFunctionalUnionMembershipWriteTypiaFixture(t *testing.T, project string) {
81
+ t.Helper()
82
+ root := filepath.Join(project, "node_modules", "typia")
83
+ if err := os.MkdirAll(filepath.Join(root, "src"), 0o755); err != nil {
84
+ t.Fatalf("mkdir typia fixture: %v", err)
85
+ }
86
+ if err := os.MkdirAll(filepath.Join(root, "lib"), 0o755); err != nil {
87
+ t.Fatalf("mkdir typia fixture lib: %v", err)
88
+ }
89
+ files := map[string]string{
90
+ "package.json": `{
91
+ "name": "typia",
92
+ "version": "0.0.0-test",
93
+ "main": "./src/index.ts",
94
+ "types": "./src/index.ts",
95
+ "exports": {
96
+ ".": "./src/index.ts",
97
+ "./lib/transform": "./lib/transform.js"
98
+ },
99
+ "ttsc": {
100
+ "plugin": { "transform": "typia/lib/transform" }
101
+ }
102
+ }
103
+ `,
104
+ "src/index.ts": compareFunctionalUnionMembershipTypiaIndex,
105
+ "src/module.ts": compareFunctionalUnionMembershipTypiaModule,
106
+ "src/compare.ts": compareFunctionalUnionMembershipTypiaCompare,
107
+ "src/plain.ts": compareFunctionalUnionMembershipTypiaPlain,
108
+ "lib/transform.js": "module.exports = {};\n",
109
+ }
110
+ for name, body := range files {
111
+ if err := os.WriteFile(filepath.Join(root, name), []byte(body), 0o644); err != nil {
112
+ t.Fatalf("write typia fixture %s: %v", name, err)
113
+ }
114
+ }
115
+ }
116
+
117
+ func compareFunctionalUnionMembershipTransform(t *testing.T, project string, functional bool) string {
118
+ t.Helper()
119
+ payload := `[{"config":{"transform":"typia/lib/transform"},"name":"typia","stage":"transform"}]`
120
+ if functional {
121
+ payload = `[{"config":{"transform":"typia/lib/transform","functional":true},"name":"typia","stage":"transform"}]`
122
+ }
123
+ out, errText, code := ttscTypiaTestCapture(func() int {
124
+ return runTransform([]string{
125
+ "--cwd", project,
126
+ "--tsconfig", "tsconfig.main.json",
127
+ "--file", "src/main.ts",
128
+ "--output", "js",
129
+ "--plugins-json=" + payload,
130
+ })
131
+ })
132
+ if code != 0 {
133
+ t.Fatalf("functional=%t transform failed: code=%d stderr=\n%s", functional, code, errText)
134
+ }
135
+ return out
136
+ }
137
+
138
+ func compareFunctionalUnionMembershipRunRuntimeCases(t *testing.T, project string, functional string, defaultMode string) {
139
+ t.Helper()
140
+ node, err := exec.LookPath("node")
141
+ if err != nil {
142
+ t.Skip("node executable not found")
143
+ }
144
+ runtimeDir := filepath.Join(project, "runtime")
145
+ if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
146
+ t.Fatalf("mkdir runtime dir: %v", err)
147
+ }
148
+ ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
149
+ if err := os.WriteFile(filepath.Join(runtimeDir, "throw-type-guard-error-stub.cjs"), []byte(compareFunctionalUnionMembershipThrowStub), 0o644); err != nil {
150
+ t.Fatalf("write throw type guard error stub: %v", err)
151
+ }
152
+ for _, output := range []struct {
153
+ name string
154
+ js string
155
+ }{
156
+ {name: "functional.cjs", js: functional},
157
+ {name: "default.cjs", js: defaultMode},
158
+ } {
159
+ js := strings.ReplaceAll(output.js, `require("typia/lib/internal/_throwTypeGuardError")`, `require("./throw-type-guard-error-stub.cjs")`)
160
+ js = ttscTypiaTestRewriteCommonJS(t, js)
161
+ if err := os.WriteFile(filepath.Join(runtimeDir, output.name), []byte(js), 0o644); err != nil {
162
+ t.Fatalf("write %s: %v", output.name, err)
163
+ }
164
+ }
165
+ runner := filepath.Join(runtimeDir, "run.cjs")
166
+ if err := os.WriteFile(runner, []byte(compareFunctionalUnionMembershipRuntimeRunner), 0o644); err != nil {
167
+ t.Fatalf("write runtime runner: %v", err)
168
+ }
169
+ cmd := exec.Command(node, runner)
170
+ cmd.Dir = runtimeDir
171
+ output, err := cmd.CombinedOutput()
172
+ if err != nil {
173
+ t.Fatalf("functional union membership runtime cases failed: %v\n%s", err, output)
174
+ }
175
+ if strings.Contains(string(output), "RAN 52 CASES") == false {
176
+ t.Fatalf("functional union membership runner skipped cases:\n%s", output)
177
+ }
178
+ }
179
+
180
+ func compareFunctionalUnionMembershipRejectsLess(t *testing.T, project string) {
181
+ t.Helper()
182
+ payload := `[{"config":{"transform":"typia/lib/transform","functional":true},"name":"typia","stage":"transform"}]`
183
+ _, errText, code := ttscTypiaTestCapture(func() int {
184
+ return runTransform([]string{
185
+ "--cwd", project,
186
+ "--tsconfig", "tsconfig.less.json",
187
+ "--file", "src/less.ts",
188
+ "--output", "js",
189
+ "--plugins-json=" + payload,
190
+ })
191
+ })
192
+ if code == 0 || strings.Contains(errText, "unable to order a union of multiple object types") == false {
193
+ t.Fatalf("compare.less did not retain its object-union diagnostic: code=%d stderr=\n%s", code, errText)
194
+ }
195
+ }
196
+
197
+ func compareFunctionalUnionMembershipTSConfig(entry string) string {
198
+ return `{
199
+ "compilerOptions": {
200
+ "target": "ES2022",
201
+ "module": "commonjs",
202
+ "moduleResolution": "bundler",
203
+ "ignoreDeprecations": "6.0",
204
+ "types": ["*"],
205
+ "esModuleInterop": true,
206
+ "strict": true,
207
+ "skipLibCheck": true
208
+ },
209
+ "include": ["` + entry + `"]
210
+ }
211
+ `
212
+ }
213
+
214
+ const compareFunctionalUnionMembershipTypiaIndex = `import * as typia from "./module";
215
+
216
+ export default typia;
217
+ export * from "./module";
218
+ `
219
+
220
+ const compareFunctionalUnionMembershipTypiaModule = `export * as compare from "./compare";
221
+ export * as plain from "./plain";
222
+
223
+ export declare function is<T>(input: unknown): input is T;
224
+ export declare function createIs<T>(): (input: unknown) => input is T;
225
+ `
226
+
227
+ const compareFunctionalUnionMembershipTypiaCompare = `export type Cover<T> = T extends object
228
+ ? { [K in keyof T]?: Cover<T[K]> }
229
+ : T;
230
+
231
+ export declare function equals<T>(x: T, y: T): boolean;
232
+ export declare function cover<T>(x: T, y: Cover<T>): boolean;
233
+ export declare function less<T>(x: T, y: T): boolean;
234
+ export declare function createEquals<T>(): (x: T, y: T) => boolean;
235
+ export declare function createCover<T>(): (x: T, y: Cover<T>) => boolean;
236
+ `
237
+
238
+ const compareFunctionalUnionMembershipTypiaPlain = `export declare function clone<T>(input: T): T;
239
+ export declare function createClone<T>(): (input: T) => T;
240
+ export declare function createPrune<T extends object>(): (input: T) => void;
241
+ `
242
+
243
+ const compareFunctionalUnionMembershipThrowStub = `module.exports._throwTypeGuardError = (props) => {
244
+ const error = new Error("guard: " + props.expected);
245
+ error.name = "TypeGuardError";
246
+ throw error;
247
+ };
248
+ `
249
+
250
+ const compareFunctionalUnionMembershipSource = `import typia from "typia";
251
+
252
+ type FunctionUnion =
253
+ | { handler: (value: number) => string; common: string }
254
+ | { handler: string; common: string; label: string };
255
+ type StringFirstUnion =
256
+ | { handler: string; common: string; label: string }
257
+ | { handler: (value: number) => string; common: string };
258
+ type FunctionSecondUnion =
259
+ | { handler: string; common: string }
260
+ | { handler: (value: number) => string; common: string; label: string };
261
+ type CoverPartial = { common: string; label: string };
262
+ type Nested = { value: FunctionUnion };
263
+ type FunctionSecondNested = { value: FunctionSecondUnion };
264
+ interface Delegated {
265
+ value: string;
266
+ equals(input: Delegated): boolean;
267
+ }
268
+
269
+ export const directIs = (input: unknown): input is FunctionUnion =>
270
+ typia.is<FunctionUnion>(input);
271
+ export const factoryIs = typia.createIs<FunctionUnion>();
272
+ export const directEquals = (x: unknown, y: unknown): boolean =>
273
+ typia.compare.equals<FunctionUnion>(x as FunctionUnion, y as FunctionUnion);
274
+ export const factoryEquals = typia.compare.createEquals<FunctionUnion>();
275
+ export const directCover = (x: FunctionUnion, y: unknown): boolean =>
276
+ typia.compare.cover<FunctionUnion>(x, y as typia.compare.Cover<FunctionUnion>);
277
+ export const factoryCover = typia.compare.createCover<FunctionUnion>();
278
+ export const directClone = (input: FunctionUnion) =>
279
+ typia.plain.clone<FunctionUnion>(input);
280
+ export const factoryClone = typia.plain.createClone<FunctionUnion>();
281
+ export const prune = typia.plain.createPrune<FunctionUnion>();
282
+
283
+ export const reversedEquals = typia.compare.createEquals<StringFirstUnion>();
284
+ export const functionSecondDirectIs = (input: unknown): input is FunctionSecondUnion =>
285
+ typia.is<FunctionSecondUnion>(input);
286
+ export const functionSecondFactoryIs = typia.createIs<FunctionSecondUnion>();
287
+ export const functionSecondDirectEquals = (x: FunctionSecondUnion, y: FunctionSecondUnion): boolean =>
288
+ typia.compare.equals<FunctionSecondUnion>(x, y);
289
+ export const functionSecondFactoryEquals = typia.compare.createEquals<FunctionSecondUnion>();
290
+ export const functionSecondDirectCover = (x: FunctionSecondUnion, y: unknown): boolean =>
291
+ typia.compare.cover<FunctionSecondUnion>(x, y as typia.compare.Cover<FunctionSecondUnion>);
292
+ export const functionSecondFactoryCover = typia.compare.createCover<FunctionSecondUnion>();
293
+ export const coverPartialDirect = (x: CoverPartial, y: unknown): boolean =>
294
+ typia.compare.cover<CoverPartial>(x, y as typia.compare.Cover<CoverPartial>);
295
+ export const coverPartialFactory = typia.compare.createCover<CoverPartial>();
296
+ export const functionSecondNestedDirectEquals = (x: FunctionSecondNested, y: FunctionSecondNested): boolean =>
297
+ typia.compare.equals<FunctionSecondNested>(x, y);
298
+ export const functionSecondNestedFactoryEquals = typia.compare.createEquals<FunctionSecondNested>();
299
+ export const delegatedDirectEquals = (x: Delegated, y: Delegated): boolean =>
300
+ typia.compare.equals<Delegated>(x, y);
301
+ export const delegatedFactoryEquals = typia.compare.createEquals<Delegated>();
302
+ export const nestedEquals = typia.compare.createEquals<Nested>();
303
+ export const nestedDirectEquals = (x: Nested, y: Nested): boolean =>
304
+ typia.compare.equals<Nested>(x, y);
305
+ `
306
+
307
+ const compareFunctionalUnionMembershipLessSource = `import typia from "typia";
308
+
309
+ type FunctionUnion =
310
+ | { handler: (value: number) => string; common: string }
311
+ | { handler: string; common: string; label: string };
312
+
313
+ typia.compare.less<FunctionUnion>({} as FunctionUnion, {} as FunctionUnion);
314
+ `
315
+
316
+ const compareFunctionalUnionMembershipRuntimeRunner = `const functional = require("./functional.cjs");
317
+ const defaults = require("./default.cjs");
318
+
319
+ let ran = 0;
320
+ const failures = [];
321
+ const expect = (name, actual, expected) => {
322
+ ran += 1;
323
+ if (actual !== expected)
324
+ failures.push(name + ": expected " + expected + ", got " + actual);
325
+ };
326
+ const expectJson = (name, actual, expected) =>
327
+ expect(name, JSON.stringify(actual), JSON.stringify(expected));
328
+
329
+ const left = { handler: "text", common: "same", label: "left" };
330
+ const right = { handler: "text", common: "same", label: "right" };
331
+ const equal = { handler: "text", common: "same", label: "left" };
332
+ const invalidLeft = { handler: 1, common: "same", label: "left" };
333
+ const invalidRight = { handler: 1, common: "same", label: "right" };
334
+
335
+ // Functional mode must use handler while routing, then preserve equality's
336
+ // established post-selection treatment of actual function-valued properties.
337
+ expect("functional direct is accepts later member", functional.directIs(left), true);
338
+ expect("functional factory is accepts later member", functional.factoryIs(right), true);
339
+ expect("functional direct equals distinguishes later label", functional.directEquals(left, right), false);
340
+ expect("functional factory equals distinguishes later label", functional.factoryEquals(left, right), false);
341
+ expect("functional direct equals keeps equal later member", functional.directEquals(left, equal), true);
342
+ expect("functional factory equals keeps equal later member", functional.factoryEquals(left, equal), true);
343
+ expect("functional direct cover distinguishes later label", functional.directCover(left, right), false);
344
+ expect("functional factory cover distinguishes later label", functional.factoryCover(left, right), false);
345
+ expectJson("functional direct clone preserves later member", functional.directClone(left), left);
346
+ expectJson("functional factory clone preserves later member", functional.factoryClone(right), right);
347
+
348
+ const pruned = { ...left, extra: true };
349
+ functional.prune(pruned);
350
+ expectJson("functional prune retains selected later label", pruned, left);
351
+ expect("functional reversed union distinguishes later label", functional.reversedEquals(left, right), false);
352
+ expect("functional nested factory distinguishes later label", functional.nestedEquals({ value: left }, { value: right }), false);
353
+ expect("functional nested direct distinguishes later label", functional.nestedDirectEquals({ value: left }, { value: right }), false);
354
+
355
+ expect("functional direct is rejects invalid handler", functional.directIs(invalidLeft), false);
356
+ expect("functional factory is rejects invalid handler", functional.factoryIs(invalidRight), false);
357
+ expect("functional direct equals rejects distinct invalid operands", functional.directEquals(invalidLeft, invalidRight), false);
358
+ expect("functional factory equals rejects distinct invalid operands", functional.factoryEquals(invalidLeft, invalidRight), false);
359
+ expect("functional same invalid reference keeps identity fast path", functional.factoryEquals(invalidLeft, invalidLeft), true);
360
+
361
+ const functionLeft = { handler: (value) => String(value), common: "same" };
362
+ const functionRight = { handler: (value) => "#" + value, common: "same" };
363
+ const functionDifferentCommon = { handler: (value) => String(value), common: "different" };
364
+ expect("functional actual functions keep method identity ignored", functional.factoryEquals(functionLeft, functionRight), true);
365
+ expect("functional actual function member still compares data", functional.factoryEquals(functionLeft, functionDifferentCommon), false);
366
+
367
+ // This is the order-symmetric public-behavior control. The function arm follows
368
+ // the strict string arm and alone owns label. The string matcher must reject an
369
+ // actual function before the later arm is selected, then equality compares label.
370
+ const functionSecondHandler = (value) => String(value);
371
+ const functionSecondLeft = {
372
+ common: "same",
373
+ handler: functionSecondHandler,
374
+ label: "left",
375
+ };
376
+ const functionSecondRight = {
377
+ common: "same",
378
+ handler: functionSecondHandler,
379
+ label: "right",
380
+ };
381
+ const functionSecondEqual = {
382
+ common: "same",
383
+ handler: (value) => "!" + value,
384
+ label: "left",
385
+ };
386
+ const functionSecondString = { handler: "text", common: "same" };
387
+ const functionSecondExtra = { ...functionSecondLeft, extra: "ignored" };
388
+ const functionSecondInvalidLeft = { common: "same", handler: 1, label: "same" };
389
+ const functionSecondInvalidRight = { common: "same", handler: 1, label: "same" };
390
+ const coverPartialFull = { common: "same", label: "left" };
391
+ const coverPartialOmitted = { common: "same" };
392
+ const coverPartialUndefined = { common: "same", label: undefined };
393
+ expect("functional second function arm direct is accepts", functional.functionSecondDirectIs(functionSecondLeft), true);
394
+ expect("functional second function arm factory is accepts", functional.functionSecondFactoryIs(functionSecondRight), true);
395
+ expect("functional second function arm direct is rejects invalid handler", functional.functionSecondDirectIs(functionSecondInvalidLeft), false);
396
+ expect("functional second function arm factory is rejects invalid handler", functional.functionSecondFactoryIs(functionSecondInvalidRight), false);
397
+ expect("functional second function arm direct equals compares label", functional.functionSecondDirectEquals(functionSecondLeft, functionSecondRight), false);
398
+ expect("functional second function arm factory equals compares label", functional.functionSecondFactoryEquals(functionSecondLeft, functionSecondRight), false);
399
+ expect("functional second function arm direct equals rejects invalid equal-label handlers", functional.functionSecondDirectEquals(functionSecondInvalidLeft, functionSecondInvalidRight), false);
400
+ expect("functional second function arm factory equals rejects invalid equal-label handlers", functional.functionSecondFactoryEquals(functionSecondInvalidLeft, functionSecondInvalidRight), false);
401
+ expect("functional second function arm direct cover compares label", functional.functionSecondDirectCover(functionSecondLeft, functionSecondRight), false);
402
+ expect("functional second function arm factory cover compares label", functional.functionSecondFactoryCover(functionSecondLeft, functionSecondRight), false);
403
+ expect("functional second function arm nested direct equals compares label", functional.functionSecondNestedDirectEquals({ value: functionSecondLeft }, { value: functionSecondRight }), false);
404
+ expect("functional second function arm nested factory equals compares label", functional.functionSecondNestedFactoryEquals({ value: functionSecondLeft }, { value: functionSecondRight }), false);
405
+ expect("functional second function arm direct equals keeps equal label", functional.functionSecondDirectEquals(functionSecondLeft, functionSecondEqual), true);
406
+ expect("functional second function arm factory equals keeps equal label", functional.functionSecondFactoryEquals(functionSecondLeft, functionSecondEqual), true);
407
+ expect("functional function-first direct cross-member equals rejects", functional.directEquals(functionLeft, left), false);
408
+ expect("functional function-first factory cross-member equals rejects", functional.factoryEquals(functionLeft, left), false);
409
+ expect("functional function-second direct cross-member equals rejects", functional.functionSecondDirectEquals(functionSecondLeft, functionSecondString), false);
410
+ expect("functional function-second factory cross-member equals rejects", functional.functionSecondFactoryEquals(functionSecondLeft, functionSecondString), false);
411
+ expect("functional direct cover accepts omitted right property", functional.coverPartialDirect(coverPartialFull, coverPartialOmitted), true);
412
+ expect("functional factory cover accepts omitted right property", functional.coverPartialFactory(coverPartialFull, coverPartialOmitted), true);
413
+ expect("functional direct cover accepts undefined right property", functional.coverPartialDirect(coverPartialFull, coverPartialUndefined), true);
414
+ expect("functional factory cover accepts undefined right property", functional.coverPartialFactory(coverPartialFull, coverPartialUndefined), true);
415
+ expect("functional second function arm direct equals ignores extra key", functional.functionSecondDirectEquals(functionSecondLeft, functionSecondExtra), true);
416
+ expect("functional second function arm factory equals ignores extra key", functional.functionSecondFactoryEquals(functionSecondLeft, functionSecondExtra), true);
417
+
418
+ const delegatedLeft = { value: "left", equals: () => true };
419
+ const delegatedRight = { value: "right", equals: () => true };
420
+ expect("functional direct equals delegates declared method", functional.delegatedDirectEquals(delegatedLeft, delegatedRight), true);
421
+ expect("functional factory equals delegates declared method", functional.delegatedFactoryEquals(delegatedLeft, delegatedRight), true);
422
+
423
+ // Default mode deliberately treats a sole function-valued member as lenient.
424
+ // The option boundary must stay visible rather than changing compare globally.
425
+ expect("default direct is keeps function member lenient", defaults.directIs(invalidLeft), true);
426
+ expect("default factory is keeps function member lenient", defaults.factoryIs(invalidRight), true);
427
+ expect("default equals keeps function member ignored", defaults.factoryEquals(invalidLeft, invalidRight), true);
428
+ expect("default second function arm direct equals retains later label", defaults.functionSecondDirectEquals(functionSecondLeft, functionSecondRight), false);
429
+ expect("default second function arm factory equals retains later label", defaults.functionSecondFactoryEquals(functionSecondLeft, functionSecondRight), false);
430
+
431
+ if (failures.length !== 0)
432
+ throw new Error(failures.join("\n"));
433
+ console.log("RAN " + ran + " CASES");
434
+ `
@@ -0,0 +1,241 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "os/exec"
6
+ "path/filepath"
7
+ "strings"
8
+ "testing"
9
+ )
10
+
11
+ // TestCompareTemplateLiteralUnionTransform verifies template-literal object
12
+ // union members retain their runtime domains during equality dispatch.
13
+ //
14
+ // The #2225 first-match fallback must use the same structural template
15
+ // membership decision as is and clone. Weakening both template domains to
16
+ // `typeof string` routes valid `b_*` values through the `a_*` comparator and
17
+ // drops `b`.
18
+ //
19
+ // 1. Transform direct and factory is, clone, and equals calls for a tag-free
20
+ // template-literal object union and the same union nested in an object.
21
+ // 2. Prove is accepts both witnesses and clone selects the `b_*` member while
22
+ // preserving their different `b` values.
23
+ // 3. Exercise equal, cross-member, invalid-discriminator, nested, literal-
24
+ // discriminated, non-discriminable, and non-union controls at runtime.
25
+ func TestCompareTemplateLiteralUnionTransform(t *testing.T) {
26
+ project := compareTemplateLiteralUnionProject(t)
27
+ out, errText, code := ttscTypiaTestCapture(func() int {
28
+ return runTransform([]string{
29
+ "--cwd", project,
30
+ "--tsconfig", "tsconfig.json",
31
+ "--file", "src/main.ts",
32
+ "--output", "js",
33
+ })
34
+ })
35
+ if code != 0 {
36
+ t.Fatalf("compare template-literal union transform failed: code=%d stderr=\n%s", code, errText)
37
+ }
38
+ compareTemplateLiteralUnionRunRuntimeCases(t, project, out)
39
+ }
40
+
41
+ func compareTemplateLiteralUnionProject(t *testing.T) string {
42
+ t.Helper()
43
+ root := ttscTypiaTestRepoRoot(t)
44
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
45
+ if err := os.MkdirAll(base, 0o755); err != nil {
46
+ t.Fatalf("mkdir temp base: %v", err)
47
+ }
48
+ dir, err := os.MkdirTemp(base, "compare-template-literal-union-")
49
+ if err != nil {
50
+ t.Fatalf("create temp fixture: %v", err)
51
+ }
52
+ t.Cleanup(func() {
53
+ _ = os.RemoveAll(dir)
54
+ })
55
+ src := filepath.Join(dir, "src")
56
+ if err := os.MkdirAll(src, 0o755); err != nil {
57
+ t.Fatalf("mkdir fixture src: %v", err)
58
+ }
59
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(compareTemplateLiteralUnionTSConfig), 0o644); err != nil {
60
+ t.Fatalf("write tsconfig: %v", err)
61
+ }
62
+ if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(compareTemplateLiteralUnionSource), 0o644); err != nil {
63
+ t.Fatalf("write source: %v", err)
64
+ }
65
+ return dir
66
+ }
67
+
68
+ func compareTemplateLiteralUnionRunRuntimeCases(t *testing.T, project string, js string) {
69
+ t.Helper()
70
+ node, err := exec.LookPath("node")
71
+ if err != nil {
72
+ t.Skip("node executable not found")
73
+ }
74
+ runtimeDir := filepath.Join(project, "runtime")
75
+ if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
76
+ t.Fatalf("mkdir runtime dir: %v", err)
77
+ }
78
+ ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
79
+ if err := os.WriteFile(filepath.Join(runtimeDir, "throw-type-guard-error-stub.cjs"), []byte(compareTemplateLiteralUnionThrowStub), 0o644); err != nil {
80
+ t.Fatalf("write throw stub: %v", err)
81
+ }
82
+ js = strings.ReplaceAll(js, `require("typia/lib/internal/_throwTypeGuardError")`, `require("./throw-type-guard-error-stub.cjs")`)
83
+ runtimeJS := ttscTypiaTestRewriteCommonJS(t, js)
84
+ if err := os.WriteFile(filepath.Join(runtimeDir, "main.cjs"), []byte(runtimeJS), 0o644); err != nil {
85
+ t.Fatalf("write runtime module: %v", err)
86
+ }
87
+ runner := filepath.Join(runtimeDir, "run.cjs")
88
+ if err := os.WriteFile(runner, []byte(compareTemplateLiteralUnionRuntimeRunner), 0o644); err != nil {
89
+ t.Fatalf("write runtime runner: %v", err)
90
+ }
91
+ cmd := exec.Command(node, runner)
92
+ cmd.Dir = runtimeDir
93
+ output, err := cmd.CombinedOutput()
94
+ if err != nil {
95
+ t.Fatalf("compare template-literal union runtime cases failed: %v\n%s", err, output)
96
+ }
97
+ }
98
+
99
+ const compareTemplateLiteralUnionTSConfig = `{
100
+ "compilerOptions": {
101
+ "target": "ES2022",
102
+ "module": "commonjs",
103
+ "moduleResolution": "bundler",
104
+ "ignoreDeprecations": "6.0",
105
+ "types": ["*"],
106
+ "esModuleInterop": true,
107
+ "strict": true,
108
+ "skipLibCheck": true
109
+ },
110
+ "include": ["src"]
111
+ }
112
+ `
113
+
114
+ const compareTemplateLiteralUnionThrowStub = `module.exports._throwTypeGuardError = (props) => {
115
+ const error = new Error("guard: " + props.expected);
116
+ error.name = "TypeGuardError";
117
+ throw error;
118
+ };
119
+ `
120
+
121
+ const compareTemplateLiteralUnionSource = `import typia from "typia";
122
+
123
+ type TemplateUnion =
124
+ | { kind: ` + "`a_${string}`" + `; common: string }
125
+ | { kind: ` + "`b_${string}`" + `; common: string; b?: string };
126
+ type Nested = { value: TemplateUnion };
127
+
128
+ export const isTemplate = typia.createIs<TemplateUnion>();
129
+ export const cloneTemplate = typia.plain.createClone<TemplateUnion>();
130
+ export const eqTemplateFactory = typia.compare.createEquals<TemplateUnion>();
131
+ export const eqTemplateDirect = (x: TemplateUnion, y: TemplateUnion) =>
132
+ typia.compare.equals<TemplateUnion>(x, y);
133
+
134
+ export const isNested = typia.createIs<Nested>();
135
+ export const cloneNested = typia.plain.createClone<Nested>();
136
+ export const eqNestedFactory = typia.compare.createEquals<Nested>();
137
+ export const eqNestedDirect = (x: Nested, y: Nested) =>
138
+ typia.compare.equals<Nested>(x, y);
139
+
140
+ export const eqLiteral = typia.compare.createEquals<
141
+ { kind: "a"; value: number } | { kind: "b"; value: string }
142
+ >();
143
+ export const eqOptional = typia.compare.createEquals<
144
+ { a?: number } | { b?: string }
145
+ >();
146
+ export const eqPlain = typia.compare.createEquals<{
147
+ kind: string;
148
+ common: string;
149
+ b?: string;
150
+ }>();
151
+ `
152
+
153
+ const compareTemplateLiteralUnionRuntimeRunner = `const mod = require("./main.cjs");
154
+
155
+ let failures = 0;
156
+ const expect = (label, actual, expected) => {
157
+ if (actual !== expected) {
158
+ console.log("FAIL " + label + ": expected " + expected + ", got " + actual);
159
+ failures++;
160
+ }
161
+ };
162
+ const expectJson = (label, actual, expected) =>
163
+ expect(label, JSON.stringify(actual), JSON.stringify(expected));
164
+
165
+ const left = { kind: "b_x", common: "same", b: "left" };
166
+ const right = { kind: "b_x", common: "same", b: "right" };
167
+ expect("is left b member", mod.isTemplate(left), true);
168
+ expect("is right b member", mod.isTemplate(right), true);
169
+ expectJson("clone left preserves b", mod.cloneTemplate(left), left);
170
+ expectJson("clone right preserves b", mod.cloneTemplate(right), right);
171
+ expect("factory different b", mod.eqTemplateFactory(left, right), false);
172
+ expect("direct different b", mod.eqTemplateDirect(left, right), false);
173
+
174
+ expect("factory equal b", mod.eqTemplateFactory(
175
+ { kind: "b_x", common: "same", b: "same" },
176
+ { kind: "b_x", common: "same", b: "same" },
177
+ ), true);
178
+ expect("direct equal b", mod.eqTemplateDirect(
179
+ { kind: "b_x", common: "same", b: "same" },
180
+ { kind: "b_x", common: "same", b: "same" },
181
+ ), true);
182
+ expect("a member different common", mod.eqTemplateFactory(
183
+ { kind: "a_x", common: "left" },
184
+ { kind: "a_x", common: "right" },
185
+ ), false);
186
+ expect("a member equal", mod.eqTemplateFactory(
187
+ { kind: "a_x", common: "same" },
188
+ { kind: "a_x", common: "same" },
189
+ ), true);
190
+ expect("a versus b", mod.eqTemplateFactory(
191
+ { kind: "a_x", common: "same" },
192
+ { kind: "b_x", common: "same" },
193
+ ), false);
194
+
195
+ const invalidLeft = { kind: "c_x", common: "same" };
196
+ const invalidRight = { kind: "c_x", common: "same" };
197
+ expect("is rejects invalid discriminator", mod.isTemplate(invalidLeft), false);
198
+ expect("factory rejects invalid discriminator", mod.eqTemplateFactory(invalidLeft, invalidRight), false);
199
+ expect("direct rejects invalid discriminator", mod.eqTemplateDirect(invalidLeft, invalidRight), false);
200
+
201
+ const nestedLeft = { value: left };
202
+ const nestedRight = { value: right };
203
+ expect("nested is left", mod.isNested(nestedLeft), true);
204
+ expect("nested is right", mod.isNested(nestedRight), true);
205
+ expectJson("nested clone left preserves b", mod.cloneNested(nestedLeft), nestedLeft);
206
+ expectJson("nested clone right preserves b", mod.cloneNested(nestedRight), nestedRight);
207
+ expect("nested factory different b", mod.eqNestedFactory(nestedLeft, nestedRight), false);
208
+ expect("nested direct different b", mod.eqNestedDirect(nestedLeft, nestedRight), false);
209
+ expect("nested factory equal b", mod.eqNestedFactory(
210
+ { value: { kind: "b_x", common: "same", b: "same" } },
211
+ { value: { kind: "b_x", common: "same", b: "same" } },
212
+ ), true);
213
+ expect("nested invalid discriminator", mod.eqNestedFactory(
214
+ { value: invalidLeft },
215
+ { value: invalidRight },
216
+ ), false);
217
+
218
+ expect("literal member different", mod.eqLiteral(
219
+ { kind: "a", value: 1 },
220
+ { kind: "a", value: 2 },
221
+ ), false);
222
+ expect("literal cross member", mod.eqLiteral(
223
+ { kind: "a", value: 1 },
224
+ { kind: "b", value: "1" },
225
+ ), false);
226
+ expect("optional member different", mod.eqOptional({ a: 1 }, { a: 2 }), false);
227
+ expect("optional second payload follows first match", mod.eqOptional({ b: "left" }, { b: "right" }), true);
228
+ expect("plain different", mod.eqPlain(
229
+ { kind: "b_x", common: "same", b: "left" },
230
+ { kind: "b_x", common: "same", b: "right" },
231
+ ), false);
232
+ expect("plain equal", mod.eqPlain(
233
+ { kind: "b_x", common: "same", b: "same" },
234
+ { kind: "b_x", common: "same", b: "same" },
235
+ ), true);
236
+
237
+ if (failures > 0) {
238
+ throw new Error(failures + " assertion(s) failed");
239
+ }
240
+ console.log("all compare template-literal union cases passed");
241
+ `