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,1184 @@
1
+ package main
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "os/exec"
7
+ "path/filepath"
8
+ "strings"
9
+ "testing"
10
+ )
11
+
12
+ // TestCallableTypeLiteralBoundariesTransform verifies that how a callable or
13
+ // constructable type is spelled cannot change what typia emits or how the
14
+ // emitted validator answers (#2238).
15
+ //
16
+ // #2250 gave call-only and construct-only `interface` declarations the function
17
+ // metadata semantics their mutually assignable function-type aliases already
18
+ // had. The same shape written as a type literal — `type X = { (v: number):
19
+ // string }` or the anonymous body at the call site — still reached structural
20
+ // object metadata, so a real function or class was rejected by the literal
21
+ // spelling and accepted by both the interface and the alias spelling. This
22
+ // regression pins the whole declaration-spelling axis rather than that one
23
+ // witness, because a fix that keyed on one node kind, one position, or one
24
+ // transform option would move a single cell and leave the class intact.
25
+ //
26
+ // The oracles are chosen so that no assertion reads its expectation from the
27
+ // emit under test. Mutual assignability is proven by the compiler: every
28
+ // spelling group compiles a bidirectional `Same<>` assertion, so "these must
29
+ // agree" is a TypeScript fact rather than a claim of this file. For member-free
30
+ // callable shapes the absolute answers come from typia's published function-type
31
+ // contract (default options skip a function slot, `functional: true` requires a
32
+ // real function). For member-carrying shapes the interface spelling is the
33
+ // oracle, because #2250 merged it as the reviewed boundary and #2238 declares
34
+ // hybrid callables an explicit negative boundary whose declared members must not
35
+ // be erased; the anchors below therefore also pin one value that the structural
36
+ // path must still accept, so a collapse of every hybrid answer to `false` cannot
37
+ // masquerade as parity.
38
+ //
39
+ // 1. Compile one project that spells fourteen callable shapes as an interface,
40
+ // as a named type literal, as an anonymous literal at the call site, and —
41
+ // where a plain function twin exists — as a function-type alias, each across
42
+ // six positions, both call forms, and both transform options.
43
+ // 2. Execute every spelling group against one shared value set and require the
44
+ // spellings to agree, then pin absolute anchors the parity assertions cannot
45
+ // see.
46
+ // 3. Repeat the call-only and construct-only pairs across local, re-exported,
47
+ // and ambient declaration provenance.
48
+ // 4. Require equivalent spellings to share one support-or-diagnostic contract,
49
+ // compiling each shape alone so a diagnostic count attributes to one pair.
50
+ func TestCallableTypeLiteralBoundariesTransform(t *testing.T) {
51
+ failures := []string{}
52
+ // Each matrix gets its own project so one transform never has to compile the
53
+ // other's fixture, and so a diagnostic can never be attributed to the wrong
54
+ // file.
55
+ spellingProject := callableTypeLiteralProject(t, "spelling", map[string]string{
56
+ "src/main.ts": callableTypeLiteralSpellingSource(),
57
+ })
58
+ provenanceProject := callableTypeLiteralProject(t, "provenance", map[string]string{
59
+ "src/main.ts": callableTypeLiteralProvenanceSource,
60
+ "src/reexport.ts": callableTypeLiteralReexportSource,
61
+ "src/ambient.d.ts": callableTypeLiteralAmbientSource,
62
+ })
63
+ // Both fixtures state their premise as compiled `Same<>` assertions, and the
64
+ // transform host reports typia's diagnostics rather than the checker's, so
65
+ // they are typechecked before anything is concluded from them.
66
+ ttscTypiaTestTypecheck(t, spellingProject)
67
+ ttscTypiaTestTypecheck(t, provenanceProject)
68
+
69
+ emits := map[string]string{}
70
+ for _, mode := range callableTypeLiteralModes() {
71
+ emits[mode.Name] = callableTypeLiteralTransform(t, spellingProject, "src/main.ts", mode.Functional)
72
+ }
73
+ output, err := callableTypeLiteralRun(
74
+ t,
75
+ spellingProject,
76
+ "spelling-runtime",
77
+ callableTypeLiteralSpellingRunner(),
78
+ map[string]string{
79
+ "default.cjs": ttscTypiaTestRewriteCommonJS(t, emits["default"]),
80
+ "functional.cjs": ttscTypiaTestRewriteCommonJS(t, emits["functional"]),
81
+ },
82
+ )
83
+ if err != nil {
84
+ failures = append(failures, fmt.Sprintf("spelling runtime matrix failed: %v\n%s", err, output))
85
+ }
86
+ for _, expected := range []string{
87
+ // 2 modes x 14 shapes x 6 positions x 2 forms x 12 values.
88
+ "RAN 4032 SPELLING GROUPS",
89
+ // 2 modes x 2 member-spelling pairs x 6 positions x 2 forms x 12 values x
90
+ // 3 declaration spellings.
91
+ "RAN 1728 MEMBER PAIRS",
92
+ // 43 anchor rows x 3 declaration spellings.
93
+ "RAN 129 ANCHORS",
94
+ // 4 optional-member states x 2 modes.
95
+ "RAN 8 OPTIONAL MEMBER ROWS",
96
+ } {
97
+ if !strings.Contains(output, expected) {
98
+ failures = append(failures, fmt.Sprintf("spelling runner did not report %q; got:\n%s", expected, output))
99
+ }
100
+ }
101
+
102
+ provenanceEmits := map[string]string{}
103
+ for _, mode := range callableTypeLiteralModes() {
104
+ provenanceEmits[mode.Name] = callableTypeLiteralTransform(t, provenanceProject, "src/main.ts", mode.Functional)
105
+ }
106
+ // The provenance fixture declares nothing but member-free call and construct
107
+ // signatures, so the failure is legible in its emit alone: TypeScript augments
108
+ // a callable object's apparent members with the global `Function` members, and
109
+ // these fragments appear exactly when a signature-only shape was expanded as a
110
+ // structural object instead. Reading the emit stops the runtime matrix from
111
+ // passing on a coincidence, and it is asserted here rather than on the
112
+ // spelling fixture because that fixture deliberately contains member-carrying
113
+ // shapes, for which the same fragments are correct.
114
+ for _, mode := range callableTypeLiteralModes() {
115
+ for _, fragment := range []string{"input.apply", "input.bind", "input.caller", "input.prototype"} {
116
+ if strings.Contains(provenanceEmits[mode.Name], fragment) {
117
+ failures = append(failures, fmt.Sprintf(
118
+ "%s provenance emit expanded a member-free callable through apparent Function member %q:\n%s",
119
+ mode.Name, fragment, provenanceEmits[mode.Name]))
120
+ }
121
+ }
122
+ }
123
+ provenanceOutput, provenanceErr := callableTypeLiteralRun(
124
+ t,
125
+ provenanceProject,
126
+ "provenance-runtime",
127
+ callableTypeLiteralProvenanceRunner,
128
+ map[string]string{
129
+ "default.cjs": ttscTypiaTestRewriteCommonJS(t, provenanceEmits["default"]),
130
+ "functional.cjs": ttscTypiaTestRewriteCommonJS(t, provenanceEmits["functional"]),
131
+ },
132
+ )
133
+ if provenanceErr != nil {
134
+ failures = append(failures, fmt.Sprintf("provenance runtime matrix failed: %v\n%s", provenanceErr, provenanceOutput))
135
+ }
136
+ // 3 provenances x 2 kinds x 3 spellings x 2 forms x 2 modes x 2 values.
137
+ if expected := "RAN 144 PROVENANCE CALLS"; !strings.Contains(provenanceOutput, expected) {
138
+ failures = append(failures, fmt.Sprintf("provenance runner did not report %q; got:\n%s", expected, provenanceOutput))
139
+ }
140
+
141
+ failures = append(failures, callableTypeLiteralConsumerFamilies(t)...)
142
+ failures = append(failures, callableTypeLiteralDiagnosticTwins(t)...)
143
+
144
+ if len(failures) != 0 {
145
+ t.Fatalf(
146
+ "callable declaration-spelling mismatches:\n%s\n\ndefault emit:\n%s\n\nfunctional emit:\n%s",
147
+ strings.Join(failures, "\n\n"),
148
+ emits["default"],
149
+ emits["functional"],
150
+ )
151
+ }
152
+ }
153
+
154
+ func callableTypeLiteralModes() []struct {
155
+ Name string
156
+ Functional bool
157
+ } {
158
+ return []struct {
159
+ Name string
160
+ Functional bool
161
+ }{
162
+ {Name: "default", Functional: false},
163
+ {Name: "functional", Functional: true},
164
+ }
165
+ }
166
+
167
+ // callableTypeLiteralShape is one callable object shape under test. Body is the
168
+ // object-type body shared verbatim by the interface, named-literal, and inline
169
+ // spellings, which is the point of the fixture: only the declaration keyword
170
+ // differs between them.
171
+ type callableTypeLiteralShape struct {
172
+ Name string
173
+ Body string
174
+ // Alias is the plain function-type spelling of the same type, empty when the
175
+ // shape carries members and therefore has no function twin — its only alias
176
+ // spelling would be an intersection, which typia's intersection contract owns
177
+ // rather than this declaration-identity boundary.
178
+ Alias string
179
+ // IntersectionAlias is that intersection spelling. It is mutually assignable
180
+ // with the declaration spellings, so it is compiled and executed as evidence
181
+ // of where the declaration-identity contract legitimately hands over to the
182
+ // intersection contract, and never as a validator this change has to move.
183
+ IntersectionAlias string
184
+ // MemberTwin names the shape that is the same TypeScript type written with the
185
+ // other member spelling (`method(): void` against `method: () => void`).
186
+ MemberTwin string
187
+ }
188
+
189
+ // callableTypeLiteralShapes lists every callable shape whose declaration
190
+ // spelling must not matter. The list is deliberately wider than the call-only
191
+ // and construct-only pair #2238 reproduces, because the members typia treats as
192
+ // unchecked are where the spellings historically disagreed, and because a fix
193
+ // that only looked at signature-only shapes has to be shown not to swallow the
194
+ // member-carrying ones.
195
+ func callableTypeLiteralShapes() []callableTypeLiteralShape {
196
+ call := "((value: number) => string)"
197
+ construct := "(new (value: number) => { value: number })"
198
+ return []callableTypeLiteralShape{
199
+ {Name: "PureCall", Body: "{ (value: number): string }", Alias: call},
200
+ {Name: "PureConstruct", Body: "{ new (value: number): { value: number } }", Alias: construct},
201
+ {Name: "Overload", Body: "{ (value: number): string; (value: string): number }",
202
+ IntersectionAlias: call + " & ((value: string) => number)"},
203
+ {Name: "ConstructOverload", Body: "{ new (value: number): { value: number }; new (value: string): { value: string } }",
204
+ IntersectionAlias: construct + " & (new (value: string) => { value: string })"},
205
+ {Name: "CallAndConstruct", Body: "{ (value: number): string; new (value: number): { value: number } }",
206
+ IntersectionAlias: call + " & " + construct},
207
+ {Name: "Method", Body: "{ (value: number): string; method(): void }", MemberTwin: "MethodProperty",
208
+ IntersectionAlias: call + " & { method(): void }"},
209
+ {Name: "MethodProperty", Body: "{ (value: number): string; method: () => void }", MemberTwin: "Method",
210
+ IntersectionAlias: call + " & { method: () => void }"},
211
+ {Name: "OptionalMethod", Body: "{ (value: number): string; method?(): void }", MemberTwin: "OptionalMethodProperty",
212
+ IntersectionAlias: call + " & { method?(): void }"},
213
+ {Name: "OptionalMethodProperty", Body: "{ (value: number): string; method?: () => void }", MemberTwin: "OptionalMethod",
214
+ IntersectionAlias: call + " & { method?: () => void }"},
215
+ {Name: "SymbolMember", Body: "{ (value: number): string; [callableBrand]: string }",
216
+ IntersectionAlias: call + " & { [callableBrand]: string }"},
217
+ {Name: "OptionalSymbolMember", Body: "{ (value: number): string; readonly [callableBrand]?: never }",
218
+ IntersectionAlias: call + " & { readonly [callableBrand]?: never }"},
219
+ {Name: "OptionalMember", Body: "{ (value: number): string; label?: string }",
220
+ IntersectionAlias: call + " & { label?: string }"},
221
+ {Name: "RequiredMember", Body: "{ (value: number): string; label: string }",
222
+ IntersectionAlias: call + " & { label: string }"},
223
+ {Name: "IndexSignature", Body: "{ (value: number): string; [key: string]: unknown }",
224
+ IntersectionAlias: call + " & { [key: string]: unknown }"},
225
+ }
226
+ }
227
+
228
+ // callableTypeLiteralPositions places the shape where a validator can meet it.
229
+ // Top level and a nested property are the two the issue reproduces; the optional
230
+ // property, union arm, generic instantiation, and semantically empty
231
+ // intersection are the neighboring routes that reach the same classification
232
+ // through a different iterator, and each of them once produced a different
233
+ // answer for a function type than for an object type.
234
+ func callableTypeLiteralPositions() []struct {
235
+ Name string
236
+ Build func(target string) string
237
+ } {
238
+ return []struct {
239
+ Name string
240
+ Build func(target string) string
241
+ }{
242
+ {Name: "Top", Build: func(target string) string { return target }},
243
+ {Name: "Nested", Build: func(target string) string { return "{ fn: " + target + " }" }},
244
+ {Name: "OptionalProperty", Build: func(target string) string { return "{ fn?: " + target + " }" }},
245
+ {Name: "UnionArm", Build: func(target string) string { return "(" + target + `) | { kind: "data"; value: number }` }},
246
+ {Name: "Generic", Build: func(target string) string { return "CallableWrapper<" + target + ">" }},
247
+ {Name: "EmptyIntersection", Build: func(target string) string { return "(" + target + ") & Record<never, never>" }},
248
+ }
249
+ }
250
+
251
+ // callableTypeLiteralSpellings names the declaration spellings of one shape.
252
+ // `Inline` has no declaration at all: the object-type body is written straight
253
+ // into the type argument, which is the only form that never reaches the alias
254
+ // iterator, so leaving it out would let a fix that only handled named aliases
255
+ // pass.
256
+ func callableTypeLiteralSpellings() []string {
257
+ return []string{"Interface", "Literal", "Inline", "Alias"}
258
+ }
259
+
260
+ func callableTypeLiteralTarget(shape callableTypeLiteralShape, spelling string) string {
261
+ switch spelling {
262
+ case "Interface", "Literal":
263
+ return shape.Name + spelling
264
+ case "Inline":
265
+ return shape.Body
266
+ case "Alias":
267
+ return shape.Name + "Alias"
268
+ }
269
+ return ""
270
+ }
271
+
272
+ func callableTypeLiteralSpellingSource() string {
273
+ builder := &strings.Builder{}
274
+ builder.WriteString(`import typia from "typia";
275
+
276
+ declare const callableBrand: unique symbol;
277
+ type CallableWrapper<T> = { payload: T };
278
+ type Assert<T extends true> = T;
279
+ type Same<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;
280
+
281
+ `)
282
+ for _, shape := range callableTypeLiteralShapes() {
283
+ builder.WriteString("interface " + shape.Name + "Interface " + shape.Body + "\n")
284
+ builder.WriteString("type " + shape.Name + "Literal = " + shape.Body + ";\n")
285
+ if shape.Alias != "" {
286
+ builder.WriteString("type " + shape.Name + "Alias = " + shape.Alias + ";\n")
287
+ }
288
+ // The declaration axis, proven by the compiler rather than asserted here.
289
+ // These assertions are only load-bearing because the fixture is typechecked:
290
+ // the transform host reports typia's own diagnostics, not the checker's, so
291
+ // an unchecked fixture would let a false `Same<>` pass unnoticed and turn
292
+ // every parity claim below into a claim about two unrelated types.
293
+ builder.WriteString("type _" + shape.Name + "LiteralTwin = Assert<Same<" + shape.Name + "Interface, " + shape.Name + "Literal>>;\n")
294
+ builder.WriteString("type _" + shape.Name + "InlineTwin = Assert<Same<" + shape.Name + "Interface, " + shape.Body + ">>;\n")
295
+ if shape.Alias != "" {
296
+ builder.WriteString("type _" + shape.Name + "AliasTwin = Assert<Same<" + shape.Name + "Interface, " + shape.Name + "Alias>>;\n")
297
+ }
298
+ if shape.IntersectionAlias != "" {
299
+ // Type-level only: the intersection spelling is the declared hand-over
300
+ // point to typia's intersection contract, so it is proven equivalent here
301
+ // and exercised as a diagnostic, never as a validator.
302
+ builder.WriteString("type _" + shape.Name + "IntersectionTwin = Assert<Same<" + shape.Name + "Interface, " + shape.IntersectionAlias + ">>;\n")
303
+ }
304
+ if shape.MemberTwin != "" {
305
+ // The member axis: a method shorthand against a function-typed property.
306
+ builder.WriteString("type _" + shape.Name + "MemberTwin = Assert<Same<" + shape.Name + "Interface, " + shape.MemberTwin + "Interface>>;\n")
307
+ }
308
+ }
309
+ builder.WriteString("\n")
310
+ for _, shape := range callableTypeLiteralShapes() {
311
+ for _, spelling := range callableTypeLiteralSpellings() {
312
+ if spelling == "Alias" && shape.Alias == "" {
313
+ continue
314
+ }
315
+ for _, position := range callableTypeLiteralPositions() {
316
+ target := position.Build(callableTypeLiteralTarget(shape, spelling))
317
+ name := shape.Name + spelling + position.Name
318
+ builder.WriteString("export const direct" + name + " = (input: unknown): boolean => typia.is<" + target + ">(input);\n")
319
+ builder.WriteString("export const factory" + name + " = typia.createIs<" + target + ">();\n")
320
+ }
321
+ }
322
+ }
323
+ return builder.String()
324
+ }
325
+
326
+ // callableTypeLiteralSpellingRunner builds the runner from the same tables the
327
+ // fixture is generated from, so a shape, position, or spelling added to one can
328
+ // never go unexercised by the other.
329
+ func callableTypeLiteralSpellingRunner() string {
330
+ shapes := []string{}
331
+ aliased := []string{}
332
+ pairs := []string{}
333
+ positions := []string{}
334
+ for _, shape := range callableTypeLiteralShapes() {
335
+ shapes = append(shapes, `"`+shape.Name+`"`)
336
+ if shape.Alias != "" {
337
+ aliased = append(aliased, `"`+shape.Name+`"`)
338
+ }
339
+ if shape.MemberTwin != "" && shape.Name < shape.MemberTwin {
340
+ pairs = append(pairs, `["`+shape.Name+`", "`+shape.MemberTwin+`"]`)
341
+ }
342
+ }
343
+ for _, position := range callableTypeLiteralPositions() {
344
+ positions = append(positions, `"`+position.Name+`"`)
345
+ }
346
+ runner := callableTypeLiteralSpellingRunnerTemplate
347
+ for _, replacement := range []struct {
348
+ Placeholder string
349
+ Value string
350
+ }{
351
+ {Placeholder: "__SHAPES__", Value: strings.Join(shapes, ", ")},
352
+ {Placeholder: "__ALIASED__", Value: strings.Join(aliased, ", ")},
353
+ {Placeholder: "__MEMBER_PAIRS__", Value: strings.Join(pairs, ", ")},
354
+ {Placeholder: "__POSITIONS__", Value: strings.Join(positions, ", ")},
355
+ } {
356
+ runner = strings.Replace(runner, replacement.Placeholder, replacement.Value, 1)
357
+ }
358
+ return runner
359
+ }
360
+
361
+ func callableTypeLiteralProject(t *testing.T, name string, files map[string]string) string {
362
+ t.Helper()
363
+ root := ttscTypiaTestRepoRoot(t)
364
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
365
+ if err := os.MkdirAll(base, 0o755); err != nil {
366
+ t.Fatalf("mkdir temp base: %v", err)
367
+ }
368
+ dir, err := os.MkdirTemp(base, "callable-type-literal-"+name+"-")
369
+ if err != nil {
370
+ t.Fatalf("create temp fixture: %v", err)
371
+ }
372
+ t.Cleanup(func() { _ = os.RemoveAll(dir) })
373
+ if err := os.MkdirAll(filepath.Join(dir, "src"), 0o755); err != nil {
374
+ t.Fatalf("mkdir fixture src: %v", err)
375
+ }
376
+ contents := map[string]string{"tsconfig.json": compareEqualCoverTSConfig}
377
+ for file, content := range files {
378
+ contents[file] = content
379
+ }
380
+ for file, content := range contents {
381
+ if err := os.WriteFile(filepath.Join(dir, filepath.FromSlash(file)), []byte(content), 0o644); err != nil {
382
+ t.Fatalf("write fixture file %s: %v", file, err)
383
+ }
384
+ }
385
+ return dir
386
+ }
387
+
388
+ func callableTypeLiteralTransform(t *testing.T, project string, file string, functional bool) string {
389
+ t.Helper()
390
+ option := ""
391
+ if functional {
392
+ option = `,"functional":true`
393
+ }
394
+ payload := `[{"config":{"transform":"typia/lib/transform"` + option + `},"name":"typia","stage":"transform"}]`
395
+ out, errText, code := ttscTypiaTestCapture(func() int {
396
+ return runTransform([]string{
397
+ "--cwd", project,
398
+ "--tsconfig", "tsconfig.json",
399
+ "--file", file,
400
+ "--output", "js",
401
+ "--plugins-json", payload,
402
+ })
403
+ })
404
+ if code != 0 {
405
+ t.Fatalf("callable declaration transform %s (functional=%t) failed: code=%d stdout=\n%s\nstderr=\n%s", file, functional, code, out, errText)
406
+ }
407
+ return out
408
+ }
409
+
410
+ func callableTypeLiteralRun(
411
+ t *testing.T,
412
+ project string,
413
+ name string,
414
+ runner string,
415
+ modules map[string]string,
416
+ ) (string, error) {
417
+ t.Helper()
418
+ node, err := exec.LookPath("node")
419
+ if err != nil {
420
+ t.Skip("node executable not found")
421
+ return "", nil
422
+ }
423
+ runtimeDir := filepath.Join(project, name)
424
+ if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
425
+ t.Fatalf("mkdir %s: %v", name, err)
426
+ }
427
+ ttscTypiaTestWriteCommonRuntimeStubs(t, runtimeDir)
428
+ files := map[string]string{"run.cjs": runner}
429
+ for file, content := range modules {
430
+ files[file] = content
431
+ }
432
+ for file, content := range files {
433
+ if err := os.WriteFile(filepath.Join(runtimeDir, file), []byte(content), 0o644); err != nil {
434
+ t.Fatalf("write runtime file %s: %v", file, err)
435
+ }
436
+ }
437
+ cmd := exec.Command(node, filepath.Join(runtimeDir, "run.cjs"))
438
+ cmd.Dir = runtimeDir
439
+ output, err := cmd.CombinedOutput()
440
+ return string(output), err
441
+ }
442
+
443
+ // callableTypeLiteralDiagnosticTwins requires equivalent spellings to share one
444
+ // support-or-diagnostic contract, and pins where that contract legitimately
445
+ // stops being a declaration-identity question.
446
+ //
447
+ // Isolation is load-bearing. The transform reports diagnostics per file, so
448
+ // several shapes in one project collapse into a count that no longer says which
449
+ // pair produced which message, and a shape that stopped erroring cancels out a
450
+ // shape that started erroring twice. One project per shape per spelling keeps
451
+ // every count attributable and keeps a failing sibling from contaminating a
452
+ // spelling that must transform.
453
+ //
454
+ // The interface, named-literal, and inline spellings of one shape are the same
455
+ // declaration written three ways, so they must always agree. A member-carrying
456
+ // shape's only function-type spelling is an intersection, and typia rejects an
457
+ // intersection of runtime callability with a further obligation; that rejection
458
+ // belongs to typia's intersection contract, not to declaration identity, so it
459
+ // is recorded as the expected boundary rather than silently generalized.
460
+ func callableTypeLiteralDiagnosticTwins(t *testing.T) []string {
461
+ t.Helper()
462
+ failures := []string{}
463
+ for _, shape := range callableTypeLiteralShapes() {
464
+ observed := map[string]string{}
465
+ functionSpelling := shape.Alias
466
+ if functionSpelling == "" {
467
+ functionSpelling = shape.IntersectionAlias
468
+ }
469
+ for _, spelling := range callableTypeLiteralSpellings() {
470
+ declaration := ""
471
+ target := "Target"
472
+ switch spelling {
473
+ case "Interface":
474
+ declaration = "interface Target " + shape.Body + "\n"
475
+ case "Literal":
476
+ declaration = "type Target = " + shape.Body + ";\n"
477
+ case "Inline":
478
+ target = shape.Body
479
+ case "Alias":
480
+ declaration = "type Target = " + functionSpelling + ";\n"
481
+ }
482
+ // The equivalence of these spellings is established once, by typechecking
483
+ // the spelling fixture, rather than restated in every one of these
484
+ // single-shape projects.
485
+ source := "import typia from \"typia\";\n\ndeclare const callableBrand: unique symbol;\n" + declaration +
486
+ "export const direct = (input: unknown): boolean => typia.is<" + target + ">(input);\n" +
487
+ "export const factory = typia.createIs<" + target + ">();\n"
488
+ project := callableTypeLiteralProject(
489
+ t,
490
+ "twin-"+strings.ToLower(shape.Name)+"-"+strings.ToLower(spelling),
491
+ map[string]string{"src/main.ts": source},
492
+ )
493
+ _, errText, code := ttscTypiaTestCapture(func() int {
494
+ return runTransform([]string{
495
+ "--cwd", project,
496
+ "--tsconfig", "tsconfig.json",
497
+ "--file", "src/main.ts",
498
+ "--output", "js",
499
+ })
500
+ })
501
+ observed[spelling] = fmt.Sprintf("code=%d nonsensible=%d unsupported=%d",
502
+ code,
503
+ strings.Count(errText, "nonsensible intersection"),
504
+ strings.Count(errText, "does not support"),
505
+ )
506
+ }
507
+ for _, spelling := range []string{"Literal", "Inline"} {
508
+ if observed[spelling] != observed["Interface"] {
509
+ failures = append(failures, fmt.Sprintf(
510
+ "%s: %s spelling reported %s but the interface spelling reported %s",
511
+ shape.Name, spelling, observed[spelling], observed["Interface"]))
512
+ }
513
+ }
514
+ const supported = "code=0 nonsensible=0 unsupported=0"
515
+ if observed["Interface"] != supported {
516
+ failures = append(failures, fmt.Sprintf(
517
+ "%s: interface spelling reported %s; expected %s", shape.Name, observed["Interface"], supported))
518
+ }
519
+ // A member-free shape's function-type spelling is a plain function type, so
520
+ // it is inside this change's contract and must agree with the declaration
521
+ // spellings.
522
+ if shape.Alias != "" {
523
+ if observed["Alias"] != observed["Interface"] {
524
+ failures = append(failures, fmt.Sprintf(
525
+ "%s: alias spelling reported %s but the interface spelling reported %s",
526
+ shape.Name, observed["Alias"], observed["Interface"]))
527
+ }
528
+ continue
529
+ }
530
+ // Every function-type spelling must report what the declaration spellings
531
+ // report, including a member-carrying shape's, whose only such spelling is an
532
+ // intersection of a call signature with the rest.
533
+ //
534
+ // This row is what samchon/typia#2276 closed. Before it, six of the twelve
535
+ // shapes disagreed here — `{ method(): void }` was accepted while
536
+ // `{ method: () => void }` was refused, and `{ label?: string }` was accepted
537
+ // while `{ label: string }` was refused — because a call-signature-only arm
538
+ // was neither an object nor a phantom brand, so the intersection was called
539
+ // nonsensible while the interface spelling of the same type, which is one
540
+ // object carrying a call signature, was accepted.
541
+ if observed["Alias"] != observed["Interface"] {
542
+ failures = append(failures, fmt.Sprintf(
543
+ "%s: alias spelling reported %s but the interface spelling reported %s",
544
+ shape.Name, observed["Alias"], observed["Interface"]))
545
+ }
546
+ }
547
+ return failures
548
+ }
549
+
550
+ // callableTypeLiteralConsumerFamilies checks the invariant where it actually
551
+ // reaches users. `metadata_get_function_node` feeds MetadataFactory.Analyze, and
552
+ // every typia operation reads the same metadata, so a declaration-spelling split
553
+ // is not an `is` bug: it changes what `random` generates, what `plain.clone`
554
+ // copies, what `json.schema` and `llm.application` publish, and which
555
+ // diagnostics `compare` and `protobuf` report.
556
+ //
557
+ // The three spellings are compiled in separate projects under one shared type
558
+ // name, so their emits must be byte-identical rather than merely equivalent.
559
+ // That is the strongest oracle available here: it cannot be satisfied by two
560
+ // different validators that happen to agree on the executed inputs, and it needs
561
+ // no expected-value table that could be written from the implementation's own
562
+ // output. The runtime rows then pin the answers the emit alone cannot: a
563
+ // function slot is dropped from JSON, cloned away, and ignored by equality.
564
+ func callableTypeLiteralConsumerFamilies(t *testing.T) []string {
565
+ t.Helper()
566
+ failures := []string{}
567
+ spellings := []struct {
568
+ Name string
569
+ Declaration string
570
+ }{
571
+ // One line each, so a diagnostic's reported position is comparable across
572
+ // the three projects rather than shifted by the declaration's own height.
573
+ {Name: "interface", Declaration: "interface Consumer { (input: { value: number }): { value: string } }\n"},
574
+ {Name: "literal", Declaration: "type Consumer = { (input: { value: number }): { value: string } };\n"},
575
+ {Name: "alias", Declaration: "type Consumer = (input: { value: number }) => { value: string };\n"},
576
+ }
577
+
578
+ emits := map[string]string{}
579
+ for _, spelling := range spellings {
580
+ project := callableTypeLiteralProject(t, "consumers-"+spelling.Name, map[string]string{
581
+ "src/main.ts": spelling.Declaration + callableTypeLiteralConsumerSource,
582
+ })
583
+ emits[spelling.Name] = callableTypeLiteralTransform(t, project, "src/main.ts", false)
584
+ }
585
+ for _, spelling := range []string{"literal", "alias"} {
586
+ if emits[spelling] != emits["interface"] {
587
+ failures = append(failures, fmt.Sprintf(
588
+ "consumer emits differ between the interface and %s spellings\n\ninterface:\n%s\n\n%s:\n%s",
589
+ spelling, emits["interface"], spelling, emits[spelling]))
590
+ }
591
+ }
592
+
593
+ runtimeProject := callableTypeLiteralProject(t, "consumers-runtime", nil)
594
+ modules := map[string]string{
595
+ "http-stub.cjs": callableTypeLiteralHTTPStub,
596
+ "llm-stub.cjs": callableTypeLiteralLLMStub,
597
+ }
598
+ for name, js := range emits {
599
+ modules[name+".cjs"] = ttscTypiaTestRewriteCommonJS(t, callableTypeLiteralStubInternals(js))
600
+ }
601
+ output, err := callableTypeLiteralRun(t, runtimeProject, "runtime", callableTypeLiteralConsumerRunner, modules)
602
+ if err != nil {
603
+ failures = append(failures, fmt.Sprintf("consumer-family runtime matrix failed: %v\n%s", err, output))
604
+ }
605
+ // 23 consumer families x 3 spellings, plus 6 anchor rows.
606
+ if expected := "RAN 69 CONSUMER FAMILIES 6 ANCHORS"; !strings.Contains(output, expected) {
607
+ failures = append(failures, fmt.Sprintf("consumer runner did not report %q; got:\n%s", expected, output))
608
+ }
609
+
610
+ // The families whose contract is a rejection are checked by their exact
611
+ // diagnostic rather than by an exit code, and one isolated project per
612
+ // spelling keeps each message attributable.
613
+ rejections := map[string]string{}
614
+ for _, spelling := range spellings {
615
+ project := callableTypeLiteralProject(t, "consumer-rejections-"+spelling.Name, map[string]string{
616
+ "src/main.ts": spelling.Declaration + callableTypeLiteralConsumerRejectionSource,
617
+ })
618
+ _, errText, code := ttscTypiaTestCapture(func() int {
619
+ return runTransform([]string{
620
+ "--cwd", project,
621
+ "--tsconfig", "tsconfig.json",
622
+ "--file", "src/main.ts",
623
+ "--output", "js",
624
+ })
625
+ })
626
+ if code == 0 {
627
+ failures = append(failures, fmt.Sprintf(
628
+ "%s spelling: the unsupported-domain consumers transformed successfully", spelling.Name))
629
+ }
630
+ // Each spelling gets its own temporary project, so the only text that may
631
+ // legitimately differ between their diagnostics is that directory.
632
+ rejections[spelling.Name] = strings.ReplaceAll(
633
+ strings.ReplaceAll(errText, project, "<project>"),
634
+ strings.ReplaceAll(project, `\`, "/"), "<project>")
635
+ }
636
+ for _, spelling := range []string{"literal", "alias"} {
637
+ if rejections[spelling] != rejections["interface"] {
638
+ failures = append(failures, fmt.Sprintf(
639
+ "consumer diagnostics differ between the interface and %s spellings\n\ninterface:\n%s\n\n%s:\n%s",
640
+ spelling, rejections["interface"], spelling, rejections[spelling]))
641
+ }
642
+ }
643
+ return failures
644
+ }
645
+
646
+ // callableTypeLiteralStubInternals redirects the shipped runtime helpers that
647
+ // only the consumer fixture pulls in. ttscTypiaTestRewriteCommonJS covers the
648
+ // shared ones and fails loudly on anything left unresolved, so these have to be
649
+ // replaced before it runs.
650
+ func callableTypeLiteralStubInternals(js string) string {
651
+ for _, replacement := range []struct {
652
+ Helper string
653
+ Stub string
654
+ }{
655
+ {Helper: "_httpFormDataReadString", Stub: "http-stub.cjs"},
656
+ {Helper: "_httpQueryReadString", Stub: "http-stub.cjs"},
657
+ {Helper: "_httpQueryParseURLSearchParams", Stub: "http-stub.cjs"},
658
+ {Helper: "_llmApplicationFinalize", Stub: "llm-stub.cjs"},
659
+ } {
660
+ js = strings.ReplaceAll(js, `require("typia/lib/internal/`+replacement.Helper+`")`, `require("./`+replacement.Stub+`")`)
661
+ }
662
+ return js
663
+ }
664
+
665
+ const callableTypeLiteralHTTPStub = `module.exports._httpFormDataReadString = (input) =>
666
+ input === null ? undefined : input === "null" ? null : input;
667
+ module.exports._httpQueryReadString = (input) =>
668
+ input === null ? undefined : input === "null" ? null : input;
669
+ module.exports._httpQueryParseURLSearchParams = (input) =>
670
+ typeof input === "string" ? new URLSearchParams(input) : input;
671
+ `
672
+
673
+ const callableTypeLiteralLLMStub = `module.exports._llmApplicationFinalize = (app, config) => ({
674
+ ...app,
675
+ config: config ?? {},
676
+ functions: app.functions,
677
+ });
678
+ `
679
+
680
+ const callableTypeLiteralConsumerSource = `import typia from "typia";
681
+
682
+ interface ConsumerHolder {
683
+ fn: Consumer;
684
+ }
685
+ type ConsumerFunctional = (input: ConsumerHolder) => ConsumerHolder;
686
+
687
+ export const is = typia.createIs<ConsumerHolder>();
688
+ export const validate = typia.createValidate<ConsumerHolder>();
689
+ export const functional = typia.functional.isFunction<ConsumerFunctional>((input) => input);
690
+ export const random = typia.createRandom<ConsumerHolder>();
691
+ export const clone = typia.plain.createClone<ConsumerHolder>();
692
+ export const prune = typia.plain.createPrune<ConsumerHolder>();
693
+ export const classify = typia.plain.createClassify<ConsumerHolder>();
694
+ export const camel = typia.notations.createCamel<ConsumerHolder>();
695
+ export const pascal = typia.notations.createPascal<ConsumerHolder>();
696
+ export const equals = typia.compare.createEquals<ConsumerHolder>();
697
+ export const cover = typia.compare.createCover<ConsumerHolder>();
698
+ export const stringify = typia.json.createStringify<ConsumerHolder>();
699
+ export const jsonSchema = typia.json.schema<ConsumerHolder>();
700
+ export const jsonSchemas = typia.json.schemas<[ConsumerHolder]>();
701
+ export const jsonApplication = typia.json.application<ConsumerHolder>();
702
+ export const reflectSchema = typia.reflect.schema<ConsumerHolder>();
703
+ export const reflectSchemas = typia.reflect.schemas<[ConsumerHolder]>();
704
+ export const reflectName = typia.reflect.name<ConsumerHolder, true>();
705
+ export const llmSchema = typia.llm.schema<ConsumerHolder>({});
706
+ export const llmParameters = typia.llm.parameters<ConsumerHolder>();
707
+ export const httpFormData = typia.http.createFormData<ConsumerHolder>();
708
+ export const httpHeaders = typia.http.createHeaders<ConsumerHolder>();
709
+ export const httpQuery = typia.http.createQuery<ConsumerHolder>();
710
+ `
711
+
712
+ const callableTypeLiteralConsumerRejectionSource = `import typia from "typia";
713
+
714
+ interface RejectionHolder {
715
+ fn: Consumer;
716
+ }
717
+
718
+ typia.compare.createEquals<Consumer>();
719
+ typia.protobuf.createEncode<RejectionHolder>();
720
+ typia.protobuf.createDecode<RejectionHolder>();
721
+ `
722
+
723
+ const callableTypeLiteralConsumerRunner = `const modules = {
724
+ interface: require("./interface.cjs"),
725
+ literal: require("./literal.cjs"),
726
+ alias: require("./alias.cjs"),
727
+ };
728
+
729
+ let families = 0;
730
+ let anchors = 0;
731
+ const failures = [];
732
+ const fn = (input) => ({ value: String(input.value) });
733
+ const other = (input) => ({ value: "other:" + input.value });
734
+ const holder = () => ({ fn });
735
+
736
+ // Each family is reduced to a JSON-comparable observation so three independent
737
+ // emits can be compared as behavior rather than as text.
738
+ const observations = {
739
+ is: (mod) => [mod.is(holder()), mod.is({}), mod.is({ fn: {} })],
740
+ validate: (mod) => [mod.validate(holder()).success, mod.validate({}).success],
741
+ functional: (mod) => [typeof mod.functional, Object.keys(mod.functional(holder()) || {})],
742
+ random: (mod) => Object.keys(mod.random()),
743
+ clone: (mod) => mod.clone(holder()),
744
+ prune: (mod) => { const value = holder(); mod.prune(value); return Object.keys(value); },
745
+ classify: (mod) => mod.classify(holder()),
746
+ camel: (mod) => mod.camel(holder()),
747
+ pascal: (mod) => mod.pascal(holder()),
748
+ equals: (mod) => [mod.equals(holder(), holder()), mod.equals({ fn }, { fn: other })],
749
+ cover: (mod) => [mod.cover(holder(), holder())],
750
+ stringify: (mod) => mod.stringify(holder()),
751
+ jsonSchema: (mod) => mod.jsonSchema,
752
+ jsonSchemas: (mod) => mod.jsonSchemas,
753
+ jsonApplication: (mod) => mod.jsonApplication,
754
+ reflectSchema: (mod) => mod.reflectSchema,
755
+ reflectSchemas: (mod) => mod.reflectSchemas,
756
+ reflectName: (mod) => mod.reflectName,
757
+ llmSchema: (mod) => mod.llmSchema,
758
+ llmParameters: (mod) => mod.llmParameters,
759
+ httpFormData: (mod) => {
760
+ const value = new FormData();
761
+ value.set("fn", "value");
762
+ return mod.httpFormData(value);
763
+ },
764
+ httpHeaders: (mod) => mod.httpHeaders(new Headers({ fn: "value" })),
765
+ httpQuery: (mod) => mod.httpQuery(new URLSearchParams({ fn: "value" })),
766
+ };
767
+
768
+ for (const [family, observe] of Object.entries(observations)) {
769
+ const answers = {};
770
+ for (const spelling of ["interface", "literal", "alias"]) {
771
+ families += 1;
772
+ try {
773
+ answers[spelling] = JSON.stringify(observe(modules[spelling]));
774
+ } catch (error) {
775
+ answers[spelling] = "threw " + String(error && error.message);
776
+ }
777
+ }
778
+ for (const spelling of ["literal", "alias"]) {
779
+ if (answers[spelling] !== answers.interface) {
780
+ failures.push(
781
+ "consumer " + family + ": interface=" + answers.interface + " " + spelling + "=" + answers[spelling]);
782
+ }
783
+ }
784
+ }
785
+
786
+ // Parity cannot see a change that moves every spelling the same way, so these
787
+ // pin what a function slot means to the consumers that must act on it, taken
788
+ // from typia's published behavior for a function-typed member: it is not JSON
789
+ // data, it is not reconstructed by a plain copy, and it carries no identity that
790
+ // structural equality could compare.
791
+ const anchorRows = [
792
+ ["is accepts a real function", (mod) => mod.is(holder()), true],
793
+ ["stringify omits the function slot", (mod) => mod.stringify(holder()), "{}"],
794
+ ["clone drops the function slot", (mod) => mod.clone(holder()).fn, undefined],
795
+ ["classify drops the function slot", (mod) => mod.classify(holder()).fn, undefined],
796
+ ["prune keeps the declared slot", (mod) => { const value = holder(); mod.prune(value); return value.fn; }, fn],
797
+ ["equals ignores function identity", (mod) => mod.equals({ fn }, { fn: other }), true],
798
+ ];
799
+ for (const [label, observe, expected] of anchorRows) {
800
+ anchors += 1;
801
+ const actual = observe(modules.interface);
802
+ if (actual !== expected) {
803
+ failures.push("consumer anchor " + label + ": expected " + expected + " but got " + actual);
804
+ }
805
+ }
806
+
807
+ console.log("RAN " + families + " CONSUMER FAMILIES " + anchors + " ANCHORS");
808
+ if (failures.length !== 0) throw new Error("MISMATCHES:\n" + failures.join("\n"));
809
+ `
810
+
811
+ const callableTypeLiteralReexportSource = `export type ReexportedCallLiteral = { (value: number): string };
812
+ export type ReexportedCallAlias = (value: number) => string;
813
+ export interface ReexportedCallInterface {
814
+ (value: number): string;
815
+ }
816
+ export type ReexportedConstructLiteral = { new (value: number): { value: number } };
817
+ export type ReexportedConstructAlias = new (value: number) => { value: number };
818
+ export interface ReexportedConstructInterface {
819
+ new (value: number): { value: number };
820
+ }
821
+ `
822
+
823
+ const callableTypeLiteralAmbientSource = `declare module "ambient-callable-declarations" {
824
+ export type AmbientCallLiteral = { (value: number): string };
825
+ export type AmbientCallAlias = (value: number) => string;
826
+ export interface AmbientCallInterface {
827
+ (value: number): string;
828
+ }
829
+ export type AmbientConstructLiteral = { new (value: number): { value: number } };
830
+ export type AmbientConstructAlias = new (value: number) => { value: number };
831
+ export interface AmbientConstructInterface {
832
+ new (value: number): { value: number };
833
+ }
834
+ }
835
+ `
836
+
837
+ const callableTypeLiteralProvenanceSource = `import typia from "typia";
838
+ import type {
839
+ ReexportedCallAlias,
840
+ ReexportedCallInterface,
841
+ ReexportedCallLiteral,
842
+ ReexportedConstructAlias,
843
+ ReexportedConstructInterface,
844
+ ReexportedConstructLiteral,
845
+ } from "./reexport";
846
+ import type {
847
+ AmbientCallAlias,
848
+ AmbientCallInterface,
849
+ AmbientCallLiteral,
850
+ AmbientConstructAlias,
851
+ AmbientConstructInterface,
852
+ AmbientConstructLiteral,
853
+ } from "ambient-callable-declarations";
854
+
855
+ type LocalCallLiteral = { (value: number): string };
856
+ type LocalCallAlias = (value: number) => string;
857
+ interface LocalCallInterface {
858
+ (value: number): string;
859
+ }
860
+ type LocalConstructLiteral = { new (value: number): { value: number } };
861
+ type LocalConstructAlias = new (value: number) => { value: number };
862
+ interface LocalConstructInterface {
863
+ new (value: number): { value: number };
864
+ }
865
+
866
+ type Assert<T extends true> = T;
867
+ type Same<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;
868
+ type _LocalCall = Assert<Same<LocalCallLiteral, LocalCallAlias>>;
869
+ type _LocalCallInterface = Assert<Same<LocalCallLiteral, LocalCallInterface>>;
870
+ type _LocalConstruct = Assert<Same<LocalConstructLiteral, LocalConstructAlias>>;
871
+ type _LocalConstructInterface = Assert<Same<LocalConstructLiteral, LocalConstructInterface>>;
872
+ type _ReexportedCall = Assert<Same<ReexportedCallLiteral, ReexportedCallAlias>>;
873
+ type _ReexportedCallInterface = Assert<Same<ReexportedCallLiteral, ReexportedCallInterface>>;
874
+ type _ReexportedConstruct = Assert<Same<ReexportedConstructLiteral, ReexportedConstructAlias>>;
875
+ type _ReexportedConstructInterface = Assert<Same<ReexportedConstructLiteral, ReexportedConstructInterface>>;
876
+ type _AmbientCall = Assert<Same<AmbientCallLiteral, AmbientCallAlias>>;
877
+ type _AmbientCallInterface = Assert<Same<AmbientCallLiteral, AmbientCallInterface>>;
878
+ type _AmbientConstruct = Assert<Same<AmbientConstructLiteral, AmbientConstructAlias>>;
879
+ type _AmbientConstructInterface = Assert<Same<AmbientConstructLiteral, AmbientConstructInterface>>;
880
+
881
+ export const directLocalCallLiteral = (input: unknown): boolean => typia.is<LocalCallLiteral>(input);
882
+ export const factoryLocalCallLiteral = typia.createIs<LocalCallLiteral>();
883
+ export const directLocalCallAlias = (input: unknown): boolean => typia.is<LocalCallAlias>(input);
884
+ export const factoryLocalCallAlias = typia.createIs<LocalCallAlias>();
885
+ export const directLocalCallInterface = (input: unknown): boolean => typia.is<LocalCallInterface>(input);
886
+ export const factoryLocalCallInterface = typia.createIs<LocalCallInterface>();
887
+ export const directLocalConstructLiteral = (input: unknown): boolean => typia.is<LocalConstructLiteral>(input);
888
+ export const factoryLocalConstructLiteral = typia.createIs<LocalConstructLiteral>();
889
+ export const directLocalConstructAlias = (input: unknown): boolean => typia.is<LocalConstructAlias>(input);
890
+ export const factoryLocalConstructAlias = typia.createIs<LocalConstructAlias>();
891
+ export const directLocalConstructInterface = (input: unknown): boolean => typia.is<LocalConstructInterface>(input);
892
+ export const factoryLocalConstructInterface = typia.createIs<LocalConstructInterface>();
893
+
894
+ export const directReexportedCallLiteral = (input: unknown): boolean => typia.is<ReexportedCallLiteral>(input);
895
+ export const factoryReexportedCallLiteral = typia.createIs<ReexportedCallLiteral>();
896
+ export const directReexportedCallAlias = (input: unknown): boolean => typia.is<ReexportedCallAlias>(input);
897
+ export const factoryReexportedCallAlias = typia.createIs<ReexportedCallAlias>();
898
+ export const directReexportedCallInterface = (input: unknown): boolean => typia.is<ReexportedCallInterface>(input);
899
+ export const factoryReexportedCallInterface = typia.createIs<ReexportedCallInterface>();
900
+ export const directReexportedConstructLiteral = (input: unknown): boolean => typia.is<ReexportedConstructLiteral>(input);
901
+ export const factoryReexportedConstructLiteral = typia.createIs<ReexportedConstructLiteral>();
902
+ export const directReexportedConstructAlias = (input: unknown): boolean => typia.is<ReexportedConstructAlias>(input);
903
+ export const factoryReexportedConstructAlias = typia.createIs<ReexportedConstructAlias>();
904
+ export const directReexportedConstructInterface = (input: unknown): boolean => typia.is<ReexportedConstructInterface>(input);
905
+ export const factoryReexportedConstructInterface = typia.createIs<ReexportedConstructInterface>();
906
+
907
+ export const directAmbientCallLiteral = (input: unknown): boolean => typia.is<AmbientCallLiteral>(input);
908
+ export const factoryAmbientCallLiteral = typia.createIs<AmbientCallLiteral>();
909
+ export const directAmbientCallAlias = (input: unknown): boolean => typia.is<AmbientCallAlias>(input);
910
+ export const factoryAmbientCallAlias = typia.createIs<AmbientCallAlias>();
911
+ export const directAmbientCallInterface = (input: unknown): boolean => typia.is<AmbientCallInterface>(input);
912
+ export const factoryAmbientCallInterface = typia.createIs<AmbientCallInterface>();
913
+ export const directAmbientConstructLiteral = (input: unknown): boolean => typia.is<AmbientConstructLiteral>(input);
914
+ export const factoryAmbientConstructLiteral = typia.createIs<AmbientConstructLiteral>();
915
+ export const directAmbientConstructAlias = (input: unknown): boolean => typia.is<AmbientConstructAlias>(input);
916
+ export const factoryAmbientConstructAlias = typia.createIs<AmbientConstructAlias>();
917
+ export const directAmbientConstructInterface = (input: unknown): boolean => typia.is<AmbientConstructInterface>(input);
918
+ export const factoryAmbientConstructInterface = typia.createIs<AmbientConstructInterface>();
919
+ `
920
+
921
+ const callableTypeLiteralProvenanceRunner = `const modules = {
922
+ default: require("./default.cjs"),
923
+ functional: require("./functional.cjs"),
924
+ };
925
+
926
+ let calls = 0;
927
+ const failures = [];
928
+ const callable = (value) => String(value);
929
+ class Constructable { constructor(value) { this.value = value; } }
930
+
931
+ // A declaration's provenance is not part of its type, so the four-cell function
932
+ // contract has to hold identically for a local declaration, one imported from a
933
+ // sibling module, and one from an ambient module declaration.
934
+ const expectations = {
935
+ default: { real: true, placeholder: true },
936
+ functional: { real: true, placeholder: false },
937
+ };
938
+ for (const provenance of ["Local", "Reexported", "Ambient"]) {
939
+ for (const kind of ["Call", "Construct"]) {
940
+ const real = kind === "Call" ? callable : Constructable;
941
+ for (const spelling of ["Literal", "Alias", "Interface"]) {
942
+ for (const form of ["direct", "factory"]) {
943
+ const name = form + provenance + kind + spelling;
944
+ for (const mode of ["default", "functional"]) {
945
+ for (const [label, value] of [["real", real], ["placeholder", {}]]) {
946
+ calls += 1;
947
+ const validator = modules[mode][name];
948
+ if (typeof validator !== "function") {
949
+ failures.push("missing export " + mode + " " + name);
950
+ continue;
951
+ }
952
+ const actual = validator(value);
953
+ const expected = expectations[mode][label];
954
+ if (actual !== expected) {
955
+ failures.push(mode + " " + name + " " + label + ": expected " + expected + " but got " + actual);
956
+ }
957
+ }
958
+ }
959
+ }
960
+ }
961
+ }
962
+ }
963
+
964
+ console.log("RAN " + calls + " PROVENANCE CALLS");
965
+ if (failures.length !== 0) throw new Error("MISMATCHES:\n" + failures.join("\n"));
966
+ `
967
+
968
+ const callableTypeLiteralSpellingRunnerTemplate = `const modules = {
969
+ default: require("./default.cjs"),
970
+ functional: require("./functional.cjs"),
971
+ };
972
+
973
+ let groups = 0;
974
+ let memberPairs = 0;
975
+ let anchors = 0;
976
+ let optionalRows = 0;
977
+ const failures = [];
978
+
979
+ // A missing export means the fixture and this runner disagree about the matrix.
980
+ // Reporting it by name keeps that from surfacing as an unattributable TypeError
981
+ // halfway through the run.
982
+ const call = (mode, name, input) => {
983
+ const validator = modules[mode][name];
984
+ if (typeof validator !== "function") {
985
+ failures.push("missing export " + mode + " " + name);
986
+ return "MISSING";
987
+ }
988
+ return validator(input);
989
+ };
990
+
991
+ const callable = (value) => String(value);
992
+ class Constructable { constructor(value) { this.value = value; } }
993
+ const methoded = Object.assign((value) => String(value), { method: () => undefined });
994
+ const labeled = Object.assign((value) => String(value), { label: "present" });
995
+ const mislabeled = Object.assign((value) => String(value), { label: 123 });
996
+ const indexed = Object.assign((value) => String(value), { extra: 1 });
997
+ // An object carrying every apparent member TypeScript gives a callable object
998
+ // type — the global Function interface's apply/call/bind/toString/prototype/
999
+ // length/arguments/caller plus name — beside the members each shape declares.
1000
+ // It is the one value the structural path a member-carrying shape must keep is
1001
+ // supposed to accept, so without it a regression that answered false everywhere
1002
+ // would satisfy every parity assertion below.
1003
+ const apparent = {
1004
+ apply: () => undefined,
1005
+ call: () => undefined,
1006
+ bind: () => undefined,
1007
+ toString: () => "",
1008
+ length: 0,
1009
+ name: "",
1010
+ prototype: {},
1011
+ arguments: null,
1012
+ caller: () => undefined,
1013
+ label: "present",
1014
+ method: () => undefined,
1015
+ extra: 1,
1016
+ };
1017
+
1018
+ const shapes = [__SHAPES__];
1019
+ const aliasedShapes = new Set([__ALIASED__]);
1020
+ // Each pair differs only in whether its member is written as a method shorthand
1021
+ // or as a function-typed property. TypeScript calls them the same type, so every
1022
+ // validator built from either must answer identically.
1023
+ const memberSpellingPairs = [__MEMBER_PAIRS__];
1024
+ const positions = [__POSITIONS__];
1025
+ const values = [
1026
+ ["callable", callable],
1027
+ ["constructable", Constructable],
1028
+ ["methoded", methoded],
1029
+ ["labeled", labeled],
1030
+ ["mislabeled", mislabeled],
1031
+ ["indexed", indexed],
1032
+ ["apparent", apparent],
1033
+ ["placeholder", {}],
1034
+ ["dataOnly", { label: "present" }],
1035
+ ["badDataOnly", { label: 123 }],
1036
+ ["unionData", { kind: "data", value: 1 }],
1037
+ ["badUnionData", { kind: "data", value: "bad" }],
1038
+ ];
1039
+
1040
+ const wrap = (position, value) =>
1041
+ position === "Nested" || position === "OptionalProperty"
1042
+ ? { fn: value }
1043
+ : position === "Generic"
1044
+ ? { payload: value }
1045
+ : value;
1046
+
1047
+ for (const mode of ["default", "functional"]) {
1048
+ for (const position of positions) {
1049
+ for (const form of ["direct", "factory"]) {
1050
+ for (const [label, value] of values) {
1051
+ const input = wrap(position, value);
1052
+ for (const shape of shapes) {
1053
+ groups += 1;
1054
+ const spellings = ["Interface", "Literal", "Inline"];
1055
+ if (aliasedShapes.has(shape)) spellings.push("Alias");
1056
+ const answers = spellings.map((spelling) => call(mode, form + shape + spelling + position, input));
1057
+ for (let i = 1; i < answers.length; ++i) {
1058
+ if (answers[i] !== answers[0]) {
1059
+ failures.push(
1060
+ "spelling " + mode + " " + form + shape + position + " " + label +
1061
+ ": " + spellings[0] + "=" + answers[0] + " " + spellings[i] + "=" + answers[i]);
1062
+ }
1063
+ }
1064
+ }
1065
+ for (const [method, property] of memberSpellingPairs) {
1066
+ for (const spelling of ["Interface", "Literal", "Inline"]) {
1067
+ memberPairs += 1;
1068
+ const left = call(mode, form + method + spelling + position, input);
1069
+ const right = call(mode, form + property + spelling + position, input);
1070
+ if (left !== right) {
1071
+ failures.push(
1072
+ "member " + spelling + " " + mode + " " + form + method + "/" + property + position + " " + label +
1073
+ ": method=" + left + " property=" + right);
1074
+ }
1075
+ }
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1081
+
1082
+ // Parity cannot see a change that moves every spelling the same way, so these
1083
+ // anchors pin the answers themselves. A member-free callable is a function type,
1084
+ // which typia documents as a slot default options skip and functional options
1085
+ // require a real function for. A member-carrying callable keeps the structural
1086
+ // object shape #2250 merged and #2238 declares an explicit negative boundary:
1087
+ // its guard opens with a typeof "object" test, so a real function is rejected,
1088
+ // and it still requires the members it declares, so a bare {} is rejected too.
1089
+ const anchorRows = [
1090
+ ["default", "directPureCall", "Top", callable, true],
1091
+ ["functional", "directPureCall", "Top", callable, true],
1092
+ ["default", "directPureCall", "Top", {}, true],
1093
+ ["functional", "directPureCall", "Top", {}, false],
1094
+ ["default", "factoryPureCall", "Top", callable, true],
1095
+ ["functional", "factoryPureCall", "Top", {}, false],
1096
+ ["default", "directPureConstruct", "Top", Constructable, true],
1097
+ ["functional", "directPureConstruct", "Top", Constructable, true],
1098
+ ["default", "directPureConstruct", "Top", {}, true],
1099
+ ["functional", "directPureConstruct", "Top", {}, false],
1100
+ ["default", "directOverload", "Top", callable, true],
1101
+ ["functional", "directOverload", "Top", callable, true],
1102
+ ["functional", "directOverload", "Top", {}, false],
1103
+ ["default", "directConstructOverload", "Top", Constructable, true],
1104
+ ["functional", "directConstructOverload", "Top", {}, false],
1105
+ ["default", "directCallAndConstruct", "Top", callable, true],
1106
+ ["functional", "directCallAndConstruct", "Top", callable, true],
1107
+ ["functional", "directCallAndConstruct", "Top", {}, false],
1108
+ ["default", "directPureCall", "Nested", { fn: callable }, true],
1109
+ ["functional", "directPureCall", "Nested", { fn: callable }, true],
1110
+ ["functional", "directPureCall", "Nested", { fn: {} }, false],
1111
+ ["default", "directPureCall", "EmptyIntersection", callable, true],
1112
+ ["functional", "directPureCall", "EmptyIntersection", callable, true],
1113
+ ["functional", "directPureCall", "EmptyIntersection", {}, false],
1114
+ ["default", "directPureCall", "Generic", { payload: callable }, true],
1115
+ ["functional", "directPureCall", "Generic", { payload: {} }, false],
1116
+ ["default", "directPureCall", "UnionArm", callable, true],
1117
+ ["default", "directPureCall", "UnionArm", { kind: "data", value: 1 }, true],
1118
+ ["default", "directPureCall", "UnionArm", { kind: "data", value: "bad" }, false],
1119
+ ["functional", "directPureCall", "UnionArm", callable, true],
1120
+ ["functional", "directPureCall", "UnionArm", { kind: "data", value: 1 }, true],
1121
+ ["functional", "directPureCall", "UnionArm", { kind: "data", value: "bad" }, false],
1122
+
1123
+ ["default", "directRequiredMember", "Top", labeled, false],
1124
+ ["functional", "directRequiredMember", "Top", labeled, false],
1125
+ ["default", "directRequiredMember", "Top", {}, false],
1126
+ ["default", "directRequiredMember", "Top", apparent, true],
1127
+ ["functional", "directRequiredMember", "Top", apparent, true],
1128
+ ["default", "directIndexSignature", "Top", indexed, false],
1129
+ ["default", "directIndexSignature", "Top", apparent, true],
1130
+ ["default", "directMethod", "Top", methoded, false],
1131
+ ["default", "directMethod", "Top", apparent, true],
1132
+ ["default", "directMethodProperty", "Top", methoded, false],
1133
+ ["default", "directMethodProperty", "Top", apparent, true],
1134
+ ];
1135
+ for (const [mode, prefix, position, value, expected] of anchorRows) {
1136
+ for (const spelling of ["Interface", "Literal", "Inline"]) {
1137
+ anchors += 1;
1138
+ const name = prefix + spelling + position;
1139
+ const actual = call(mode, name, value);
1140
+ if (actual !== expected) {
1141
+ failures.push("anchor " + mode + " " + name + ": expected " + expected + " but got " + actual);
1142
+ }
1143
+ }
1144
+ }
1145
+
1146
+ // The optional data member is its own boundary: a callable shape whose only
1147
+ // member may be absent is the shape most likely to be mistaken for a member-free
1148
+ // one, so a fix that widened the signature-only rule too far would erase the
1149
+ // member and start accepting a bare function. Present-valid, omitted,
1150
+ // present-invalid, and data-only are the four states that distinguish "the
1151
+ // member is still checked" from "the member is gone", and the interface spelling
1152
+ // is the oracle #2250 merged for them.
1153
+ for (const [label, value] of [
1154
+ ["present-valid", labeled],
1155
+ ["omitted", callable],
1156
+ ["present-invalid", mislabeled],
1157
+ ["data-only", { label: "present" }],
1158
+ ]) {
1159
+ for (const mode of ["default", "functional"]) {
1160
+ optionalRows += 1;
1161
+ const oracle = call(mode, "directOptionalMemberInterfaceTop", value);
1162
+ for (const spelling of ["Literal", "Inline"]) {
1163
+ const actual = call(mode, "directOptionalMember" + spelling + "Top", value);
1164
+ if (actual !== oracle) {
1165
+ failures.push(
1166
+ "optional member " + mode + " " + spelling + " " + label +
1167
+ ": expected the interface oracle " + oracle + " but got " + actual);
1168
+ }
1169
+ }
1170
+ // A callable value that omits the optional member must not be accepted while
1171
+ // the declared member is still part of the type; if it were, the member had
1172
+ // been erased.
1173
+ if (label === "omitted" && oracle !== false) {
1174
+ failures.push("optional member " + mode + " omitted: interface oracle accepted a bare function");
1175
+ }
1176
+ }
1177
+ }
1178
+
1179
+ console.log("RAN " + groups + " SPELLING GROUPS");
1180
+ console.log("RAN " + memberPairs + " MEMBER PAIRS");
1181
+ console.log("RAN " + anchors + " ANCHORS");
1182
+ console.log("RAN " + optionalRows + " OPTIONAL MEMBER ROWS");
1183
+ if (failures.length !== 0) throw new Error("MISMATCHES:\n" + failures.join("\n"));
1184
+ `