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
@@ -12,6 +12,7 @@ import (
12
12
  nativefactories "github.com/samchon/typia/packages/typia/native/core/factories"
13
13
  nativehelpers "github.com/samchon/typia/packages/typia/native/core/programmers/helpers"
14
14
  nativeinternal "github.com/samchon/typia/packages/typia/native/core/programmers/internal"
15
+ nativeiterate "github.com/samchon/typia/packages/typia/native/core/programmers/iterate"
15
16
  schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
16
17
  )
17
18
 
@@ -31,9 +32,10 @@ type CompareEqualProgrammer_IProps struct {
31
32
  }
32
33
 
33
34
  type compareEqualProgrammerGenerator struct {
34
- Config CompareEqualProgrammer_IConfig
35
- Recursive bool
36
- Counter int
35
+ Config CompareEqualProgrammer_IConfig
36
+ Functional bool
37
+ Recursive bool
38
+ Counter int
37
39
  // Member-resolution checkers (`_ui<index>`) emitted on demand for the object
38
40
  // unions that need the is-match ladder, keyed by the object's collection index.
39
41
  Matchers []string
@@ -76,6 +78,7 @@ func (compareEqualProgrammerNamespace) Write(props CompareEqualProgrammer_IProps
76
78
 
77
79
  generator := &compareEqualProgrammerGenerator{
78
80
  Config: props.Config,
81
+ Functional: nativehelpers.OptionPredicator.Functional(props.Context.Options),
79
82
  Recursive: nativeinternal.FeatureProgrammer.CollectionRecursive(collection),
80
83
  MatcherIndex: map[int]bool{},
81
84
  }
@@ -307,9 +310,8 @@ func (g *compareEqualProgrammerGenerator) objectCall(object *schemametadata.Meta
307
310
  return fmt.Sprintf("_eo%d(%s, %s)", object.Index, g.wrap(x), g.wrap(y))
308
311
  }
309
312
 
310
- // objectFlatBranch renders one member of an object union as a guarded member
311
- // comparator (the pre-discrimination form). It is used for a single-object
312
- // "union" and as the fallback for a union no supported predicate can split.
313
+ // objectFlatBranch renders a single object member as a guarded comparator. A
314
+ // multi-object union always uses objectUnion's member dispatch instead.
313
315
  func (g *compareEqualProgrammerGenerator) objectFlatBranch(object *schemametadata.MetadataObjectType, natives []*schemametadata.MetadataNative, x string, y string) string {
314
316
  // Guard the object branch against the sibling native instances. `_eo` admits
315
317
  // any `typeof === "object" && !Array.isArray` value, so a native (a Date is
@@ -439,16 +441,15 @@ func (g *compareEqualProgrammerGenerator) matchCall(object *schemametadata.Metad
439
441
  // may be absent), and every extra key a dynamic index signature declares must
440
442
  // hold a value of the signature's type. It mirrors the `_io` checkers `is`
441
443
  // emits, at the abstraction level the rest of this programmer works at — shape
442
- // and typeof, not the value constraints (`typia.tags.*`, template patterns)
443
- // `compare` deliberately ignores.
444
+ // and typeof, plus structural template patterns. Function-valued properties
445
+ // participate in this routing check whenever `is` makes them meaningful; they
446
+ // remain excluded from object value comparison after a member is selected. It
447
+ // deliberately ignores `typia.tags.*` constraints.
444
448
  func (g *compareEqualProgrammerGenerator) matchObject(object *schemametadata.MetadataObjectType, v string) string {
445
449
  checks := []string{}
446
450
  regularKeys := []string{}
447
451
  dynamic := []*schemametadata.MetadataProperty{}
448
452
  for _, property := range object.Properties {
449
- if compareProgrammer_isMethod(property) {
450
- continue // methods carry no comparable data, so they do not route either
451
- }
452
453
  if sole := property.Key.GetSoleLiteral(); sole != nil {
453
454
  regularKeys = append(regularKeys, *sole)
454
455
  checks = append(checks, g.matchSlot(property.Value, g.property(v, *sole)))
@@ -504,9 +505,9 @@ func (g *compareEqualProgrammerGenerator) match(metadata *schemametadata.Metadat
504
505
  branches = append(branches, g.typeof(v, atomic.Type))
505
506
  }
506
507
  if len(metadata.Templates) != 0 {
507
- branches = append(branches, g.typeof(v, "string"))
508
+ branches = append(branches, g.and(g.typeof(v, "string"), g.template(metadata.Templates, v)))
508
509
  }
509
- if len(metadata.Functions) != 0 {
510
+ if len(metadata.Functions) != 0 && (g.Functional || metadata.Size() != 1) {
510
511
  branches = append(branches, g.typeof(v, "function"))
511
512
  }
512
513
  for _, native := range metadata.Natives {
@@ -767,6 +768,19 @@ func (g *compareEqualProgrammerGenerator) dynamicKey(metadata *schemametadata.Me
767
768
  return g.or(checks...)
768
769
  }
769
770
 
771
+ // template renders the structural membership test shared with the authoritative
772
+ // runtime template checker. Compare still ignores typia tag predicates when it
773
+ // compares values, but it must retain the template-literal domain while routing
774
+ // an object union to a declared member.
775
+ func (g *compareEqualProgrammerGenerator) template(templates []*schemametadata.MetadataTemplate, input string) string {
776
+ checks := make([]string, 0, len(templates))
777
+ for _, template := range templates {
778
+ pattern := nativeiterate.TemplateRuntimePattern(template.Row)
779
+ checks = append(checks, fmt.Sprintf("RegExp(%s).test(%s)", strconv.Quote(pattern), g.wrap(input)))
780
+ }
781
+ return g.or(checks...)
782
+ }
783
+
770
784
  func (g *compareEqualProgrammerGenerator) visit(kind string, index int) string {
771
785
  slot := fmt.Sprintf("_vctx.%s%d", kind, index)
772
786
  return fmt.Sprintf("const _map = %s || (%s = new WeakMap()); let _set = _map.get(x); if (_set && _set.has(y)) return true; if (!_set) _map.set(x, (_set = new WeakSet())); _set.add(y);", slot, slot)
@@ -15,12 +15,11 @@ type FunctionProgrammer struct {
15
15
  unions_ map[string]*functionProgrammer_union
16
16
  unionOrder_ []string
17
17
  variables_ map[string]*shimast.Node
18
- variableKeys_ map[string]string
18
+ variableKeys_ map[string]string
19
19
  variableOrder_ []string
20
- nameIndexes_ map[string]int
21
- reservedNames_ map[string]bool
20
+ nameIndexes_ map[string]int
21
+ reservedNames_ map[string]bool
22
22
  sequence_ int
23
- disableDeclare bool
24
23
  // visited_ turns on per-invocation visit tracking: set after metadata
25
24
  // analysis when the type graph carries a recursive component, so generated
26
25
  // functions thread a `_vctx` context and recursive ones guard against
@@ -54,10 +53,10 @@ func NewFunctionProgrammer(method string, emit ...*shimprinter.EmitContext) *Fun
54
53
  unions_: map[string]*functionProgrammer_union{},
55
54
  unionOrder_: []string{},
56
55
  variables_: map[string]*shimast.Node{},
57
- variableKeys_: map[string]string{},
56
+ variableKeys_: map[string]string{},
58
57
  variableOrder_: []string{},
59
- nameIndexes_: map[string]int{},
60
- reservedNames_: map[string]bool{},
58
+ nameIndexes_: map[string]int{},
59
+ reservedNames_: map[string]bool{},
61
60
  }
62
61
  if len(emit) != 0 {
63
62
  p.emit_ = emit[0]
@@ -93,9 +92,6 @@ func (p *FunctionProgrammer) HasLocal(name string) bool {
93
92
  }
94
93
 
95
94
  func (p *FunctionProgrammer) Declare(includeUnions ...bool) []*shimast.Node {
96
- if p.disableDeclare {
97
- return []*shimast.Node{}
98
- }
99
95
  output := []*shimast.Node{}
100
96
  for _, name := range p.variableOrder_ {
101
97
  value := p.variables_[name]
@@ -121,9 +117,6 @@ func (p *FunctionProgrammer) Declare(includeUnions ...bool) []*shimast.Node {
121
117
  }
122
118
 
123
119
  func (p *FunctionProgrammer) DeclareUnions() []*shimast.Node {
124
- if p.disableDeclare {
125
- return []*shimast.Node{}
126
- }
127
120
  output := []*shimast.Node{}
128
121
  for _, key := range p.unionOrder_ {
129
122
  tuple := p.unions_[key]
@@ -2,7 +2,6 @@ package helpers
2
2
 
3
3
  import (
4
4
  "encoding/json"
5
- "sort"
6
5
  "strings"
7
6
 
8
7
  shimast "github.com/microsoft/typescript-go/shim/ast"
@@ -22,9 +21,10 @@ type StringifyJoiner_ObjectProps struct {
22
21
  }
23
22
 
24
23
  type StringifyJoiner_ArrayProps struct {
25
- Input *shimast.Expression
26
- Arrow *shimast.Expression
27
- Emit *shimprinter.EmitContext
24
+ Context nativecontext.ITypiaContext
25
+ Input *shimast.Expression
26
+ Arrow *shimast.Expression
27
+ Emit *shimprinter.EmitContext
28
28
  }
29
29
 
30
30
  type StringifyJoiner_TupleProps struct {
@@ -49,17 +49,22 @@ func (stringifyJoinerNamespace) Object(props StringifyJoiner_ObjectProps) *shima
49
49
  dynamic = append(dynamic, entry)
50
50
  }
51
51
  }
52
- expressions := stringifyJoiner_regular_properties(regular, dynamic, props.Context.Emit)
52
+ expressions := stringifyJoiner_regular_properties(props.Context, regular, dynamic, props.Context.Emit)
53
53
  if len(dynamic) != 0 {
54
54
  keys := make([]string, 0, len(regular))
55
55
  for _, entry := range regular {
56
56
  keys = append(keys, *entry.Key.GetSoleLiteral())
57
57
  }
58
- expressions = append(expressions, stringifyJoiner_dynamic_properties(dynamic, keys, props.Context.Emit))
58
+ expressions = append(expressions, stringifyJoiner_dynamic_properties(props.Context, dynamic, keys, props.Context.Emit))
59
59
  }
60
60
 
61
+ // The tail helper removes the separator left behind when the last written
62
+ // member drops out at runtime, so it may only be skipped when the last regular
63
+ // member cannot drop. Required is not the same as cannot-drop: a required
64
+ // `any` member is omitted whenever its value serializes to `undefined`, which
65
+ // without this produced a trailing comma before `}` (#2253).
61
66
  filtered := expressions
62
- if !((len(regular) != 0 && regular[len(regular)-1].Meta.IsRequired() && len(dynamic) == 0) ||
67
+ if !((len(regular) != 0 && !stringifyJoiner_entry_omittable(regular[len(regular)-1]) && len(dynamic) == 0) ||
63
68
  (len(regular) == 0 && len(dynamic) != 0)) {
64
69
  filtered = []*shimast.Node{
65
70
  f.NewCallExpression(
@@ -79,27 +84,26 @@ func (stringifyJoinerNamespace) Object(props StringifyJoiner_ObjectProps) *shima
79
84
  return nativefactories.TemplateFactory.Generate(output, props.Context.Emit)
80
85
  }
81
86
 
87
+ // Array serializes elements through the `_jsonStringifyArray` helper rather than
88
+ // `input.map(...).join(",")`.
89
+ //
90
+ // `map` never visits a hole and leaves one behind, and `join` renders both a
91
+ // hole and a mapped `undefined` as empty text, so the two cases ECMAScript's
92
+ // SerializeJSONArray answers with `null` were emitted as nothing at all: a
93
+ // sparse array produced the malformed text `[,1]`, and an element serializing to
94
+ // `undefined` — a function, a symbol, or a `toJSON` returning nothing in an
95
+ // `any` or `unknown` slot — silently collapsed its position. A hole exists at
96
+ // runtime whatever the element type declares, so this is not confined to `any`
97
+ // (#2253).
82
98
  func (stringifyJoinerNamespace) Array(props StringifyJoiner_ArrayProps) *shimast.Node {
83
99
  f := nativecontext.EmitFactoryOf(stringifyJoiner_factory, props.Emit)
84
100
  return nativefactories.TemplateFactory.Generate([]*shimast.Node{
85
101
  f.NewStringLiteral("[", shimast.TokenFlagsNone),
86
102
  f.NewCallExpression(
87
- nativefactories.IdentifierFactory.Access(
88
- nil,
89
- f.NewCallExpression(
90
- nativefactories.IdentifierFactory.Access(nil, props.Input, "map"),
91
- nil,
92
- nil,
93
- f.NewNodeList([]*shimast.Node{props.Arrow}),
94
- shimast.NodeFlagsNone,
95
- ),
96
- "join",
97
- ),
103
+ stringifyJoiner_internal(props.Context, "jsonStringifyArray"),
98
104
  nil,
99
105
  nil,
100
- f.NewNodeList([]*shimast.Node{
101
- f.NewStringLiteral(",", shimast.TokenFlagsNone),
102
- }),
106
+ f.NewNodeList([]*shimast.Node{props.Input, props.Arrow}),
103
107
  shimast.NodeFlagsNone,
104
108
  ),
105
109
  f.NewStringLiteral("]", shimast.TokenFlagsNone),
@@ -132,12 +136,17 @@ func (stringifyJoinerNamespace) Tuple(props StringifyJoiner_TupleProps) *shimast
132
136
  return nativefactories.TemplateFactory.Generate(expressions, props.Emit)
133
137
  }
134
138
 
135
- func stringifyJoiner_regular_properties(regular []IExpressionEntry, dynamic []IExpressionEntry, emit ...*shimprinter.EmitContext) []*shimast.Node {
139
+ func stringifyJoiner_regular_properties(context nativecontext.ITypiaContext, regular []IExpressionEntry, dynamic []IExpressionEntry, emit ...*shimprinter.EmitContext) []*shimast.Node {
136
140
  f := nativecontext.EmitFactoryOf(stringifyJoiner_factory, emit...)
137
141
  output := []*shimast.Node{}
138
- sort.Slice(regular, func(i int, j int) bool {
139
- return stringifyJoiner_sequence(regular[i].Meta) < stringifyJoiner_sequence(regular[j].Meta)
140
- })
142
+ // Members keep their declaration order, which is what the guide promises and
143
+ // what `JSON.stringify` produces for a value assigned in that order. They used
144
+ // to be sorted so every omissible member sat ahead of the required ones,
145
+ // because the separator is appended to every entry but the last and a last
146
+ // member that drops at runtime would leave a trailing comma. The caller
147
+ // already repairs exactly that with `_jsonStringifyTail`, whose condition
148
+ // reads the last member and now sees the declared one, so the ordering
149
+ // constraint the sort existed for no longer applies (#2295).
141
150
  for index, entry := range regular {
142
151
  key := entry.Key.GetSoleLiteral()
143
152
  encoded, err := json.Marshal(*key)
@@ -149,19 +158,44 @@ func stringifyJoiner_regular_properties(regular []IExpressionEntry, dynamic []IE
149
158
  // re-escape the JSON bytes for the template-literal context: otherwise `\"`
150
159
  // decays to `"`, `\\` to `\`, a control-char escape's backslash is dropped, a
151
160
  // raw backtick closes the template, and `${` starts a live interpolation.
161
+ separator := ""
162
+ if index != len(regular)-1 || len(dynamic) != 0 {
163
+ separator = ","
164
+ }
152
165
  base := []*shimast.Node{
153
166
  f.NewStringLiteral(stringifyJoiner_escape_template(string(encoded))+":", shimast.TokenFlagsNone),
154
167
  entry.Expression,
155
168
  }
156
- if index != len(regular)-1 || len(dynamic) != 0 {
157
- base = append(base, f.NewStringLiteral(",", shimast.TokenFlagsNone))
169
+ if separator != "" {
170
+ base = append(base, f.NewStringLiteral(separator, shimast.TokenFlagsNone))
158
171
  }
159
172
  empty := (!entry.Meta.IsRequired() && !entry.Meta.Nullable && entry.Meta.Size() == 0) ||
160
173
  (len(entry.Meta.Functions) != 0 && !entry.Meta.Nullable && entry.Meta.Size() == 1)
161
174
  if empty {
162
175
  continue
163
176
  }
164
- if !entry.Meta.IsRequired() || len(entry.Meta.Functions) != 0 || entry.Meta.Any {
177
+ if stringifyJoiner_result_undefinable(entry.Meta) {
178
+ // An `any` or `unknown` member is serialized by JSON.stringify, which
179
+ // answers `undefined` for a function, a symbol, and a `toJSON` returning
180
+ // nothing while the value itself is present. Only the serialized result
181
+ // distinguishes those from a value that has text, so the member is written
182
+ // through the helper that reads that result — once, because a `toJSON` may
183
+ // answer differently on a second call (#2253). The input tests below stay
184
+ // for members whose serializer must not run on a missing value at all.
185
+ output = append(output, f.NewCallExpression(
186
+ stringifyJoiner_internal(context, "jsonStringifyProperty"),
187
+ nil,
188
+ nil,
189
+ f.NewNodeList([]*shimast.Node{
190
+ f.NewStringLiteral(string(encoded)+":", shimast.TokenFlagsNone),
191
+ entry.Expression,
192
+ f.NewStringLiteral(separator, shimast.TokenFlagsNone),
193
+ }),
194
+ shimast.NodeFlagsNone,
195
+ ))
196
+ continue
197
+ }
198
+ if !entry.Meta.IsRequired() || len(entry.Meta.Functions) != 0 {
165
199
  output = append(output, nativefactories.ExpressionFactory.Conditional(
166
200
  stringifyJoiner_regular_condition(entry, emit...),
167
201
  f.NewStringLiteral("", shimast.TokenFlagsNone),
@@ -175,6 +209,39 @@ func stringifyJoiner_regular_properties(regular []IExpressionEntry, dynamic []IE
175
209
  return output
176
210
  }
177
211
 
212
+ // stringifyJoiner_entry_omittable reports whether a member can be absent from
213
+ // the emitted object at runtime, which is exactly the set of members that are
214
+ // written through a conditional or through the property helper.
215
+ func stringifyJoiner_entry_omittable(entry IExpressionEntry) bool {
216
+ return !entry.Meta.IsRequired() || len(entry.Meta.Functions) != 0 ||
217
+ stringifyJoiner_result_undefinable(entry.Meta)
218
+ }
219
+
220
+ // stringifyJoiner_result_undefinable reports whether a value that is present can
221
+ // still serialize to nothing, which is the case an input test cannot see.
222
+ //
223
+ // `any` and `unknown` are serialized by JSON.stringify, which answers
224
+ // `undefined` for a function, a symbol, and a `toJSON` returning nothing. A
225
+ // declared `toJSON` is the same case without `any`: the member's text is its
226
+ // return value, so a return type that admits `undefined` admits a member with no
227
+ // text (#2253).
228
+ // StringifyJoiner_ResultUndefinable exposes the same question to the element
229
+ // positions the joiner does not build itself, so a tuple slot and an object
230
+ // member classify a value identically.
231
+ func StringifyJoiner_ResultUndefinable(meta *nativemetadata.MetadataSchema) bool {
232
+ return stringifyJoiner_result_undefinable(meta)
233
+ }
234
+
235
+ func stringifyJoiner_result_undefinable(meta *nativemetadata.MetadataSchema) bool {
236
+ if meta == nil {
237
+ return false
238
+ }
239
+ if meta.Any {
240
+ return true
241
+ }
242
+ return meta.Escaped != nil && meta.Escaped.Returns != nil && !meta.Escaped.Returns.IsRequired()
243
+ }
244
+
178
245
  func stringifyJoiner_regular_condition(entry IExpressionEntry, emit ...*shimprinter.EmitContext) *shimast.Node {
179
246
  f := nativecontext.EmitFactoryOf(stringifyJoiner_factory, emit...)
180
247
  conditions := []*shimast.Node{}
@@ -199,7 +266,7 @@ func stringifyJoiner_regular_condition(entry IExpressionEntry, emit ...*shimprin
199
266
  return stringifyJoiner_reduce(conditions, shimast.KindBarBarToken, emit...)
200
267
  }
201
268
 
202
- func stringifyJoiner_dynamic_properties(dynamic []IExpressionEntry, regular []string, emit ...*shimprinter.EmitContext) *shimast.Node {
269
+ func stringifyJoiner_dynamic_properties(context nativecontext.ITypiaContext, dynamic []IExpressionEntry, regular []string, emit ...*shimprinter.EmitContext) *shimast.Node {
203
270
  f := nativecontext.EmitFactoryOf(stringifyJoiner_factory, emit...)
204
271
  statements := []*shimast.Node{
205
272
  f.NewIfStatement(
@@ -298,7 +365,7 @@ func stringifyJoiner_dynamic_properties(dynamic []IExpressionEntry, regular []st
298
365
  }
299
366
  simple := len(dynamic) == 1 && dynamic[0].Key.Size() == 1 && len(dynamic[0].Key.Atomics) != 0 && dynamic[0].Key.Atomics[0].Type == "string"
300
367
  if simple {
301
- statements = append(statements, stringifyJoiner_stringify(dynamic[0], emit...))
368
+ statements = append(statements, stringifyJoiner_stringify(context, dynamic[0], emit...))
302
369
  return output()
303
370
  }
304
371
  for _, entry := range dynamic {
@@ -313,7 +380,7 @@ func stringifyJoiner_dynamic_properties(dynamic []IExpressionEntry, regular []st
313
380
  f.NewNodeList([]*shimast.Node{f.NewIdentifier("key")}),
314
381
  shimast.NodeFlagsNone,
315
382
  ),
316
- stringifyJoiner_stringify(entry, emit...),
383
+ stringifyJoiner_stringify(context, entry, emit...),
317
384
  nil,
318
385
  ))
319
386
  }
@@ -321,20 +388,39 @@ func stringifyJoiner_dynamic_properties(dynamic []IExpressionEntry, regular []st
321
388
  return output()
322
389
  }
323
390
 
324
- func stringifyJoiner_stringify(entry IExpressionEntry, emit ...*shimprinter.EmitContext) *shimast.Node {
391
+ func stringifyJoiner_stringify(context nativecontext.ITypiaContext, entry IExpressionEntry, emit ...*shimprinter.EmitContext) *shimast.Node {
325
392
  f := nativecontext.EmitFactoryOf(stringifyJoiner_factory, emit...)
393
+ head := nativefactories.TemplateFactory.Generate([]*shimast.Node{
394
+ f.NewCallExpression(
395
+ f.NewIdentifier("JSON.stringify"),
396
+ nil,
397
+ f.NewNodeList(nil),
398
+ f.NewNodeList([]*shimast.Node{f.NewIdentifier("key")}),
399
+ shimast.NodeFlagsNone,
400
+ ),
401
+ f.NewStringLiteral(":", shimast.TokenFlagsNone),
402
+ }, emit...)
403
+ if entry.Meta.Any {
404
+ // An index-signature member takes the same contextual decision a static one
405
+ // takes: `any` and `unknown` values are serialized by JSON.stringify, which
406
+ // answers `undefined` for a present function, symbol, or empty `toJSON`.
407
+ // Returning the empty string hands the surrounding filter the same sentinel
408
+ // it already uses for a missing value, so the member and its separator drop
409
+ // together instead of rendering the literal `undefined` (#2253).
410
+ return f.NewReturnStatement(f.NewCallExpression(
411
+ stringifyJoiner_internal(context, "jsonStringifyProperty"),
412
+ nil,
413
+ nil,
414
+ f.NewNodeList([]*shimast.Node{
415
+ head,
416
+ entry.Expression,
417
+ f.NewStringLiteral("", shimast.TokenFlagsNone),
418
+ }),
419
+ shimast.NodeFlagsNone,
420
+ ))
421
+ }
326
422
  return f.NewReturnStatement(
327
- nativefactories.TemplateFactory.Generate([]*shimast.Node{
328
- f.NewCallExpression(
329
- f.NewIdentifier("JSON.stringify"),
330
- nil,
331
- f.NewNodeList(nil),
332
- f.NewNodeList([]*shimast.Node{f.NewIdentifier("key")}),
333
- shimast.NodeFlagsNone,
334
- ),
335
- f.NewStringLiteral(":", shimast.TokenFlagsNone),
336
- entry.Expression,
337
- }, emit...),
423
+ nativefactories.TemplateFactory.Generate([]*shimast.Node{head, entry.Expression}, emit...),
338
424
  )
339
425
  }
340
426
 
@@ -380,13 +466,6 @@ func stringifyJoiner_reduce(expressions []*shimast.Node, operator shimast.Kind,
380
466
  return output
381
467
  }
382
468
 
383
- func stringifyJoiner_sequence(meta *nativemetadata.MetadataSchema) int {
384
- if meta.Any || !meta.IsRequired() || len(meta.Functions) != 0 {
385
- return 0
386
- }
387
- return 1
388
- }
389
-
390
469
  func stringifyJoiner_internal(context nativecontext.ITypiaContext, name string) *shimast.Node {
391
470
  if importer := context.Importer; importer != nil {
392
471
  return importer.Internal(name)