typia 13.2.0 → 14.0.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 (135) hide show
  1. package/lib/http.d.ts +3 -3
  2. package/lib/http.js.map +1 -1
  3. package/lib/http.mjs.map +1 -1
  4. package/lib/internal/_assertGuard.js +13 -3
  5. package/lib/internal/_assertGuard.js.map +1 -1
  6. package/lib/internal/_assertGuard.mjs +4 -2
  7. package/lib/internal/_assertGuard.mjs.map +1 -1
  8. package/lib/internal/_isFormatIdnHostname.js +5 -1
  9. package/lib/internal/_isFormatIdnHostname.js.map +1 -1
  10. package/lib/internal/_isFormatIdnHostname.mjs +1 -1
  11. package/lib/internal/_isFormatIdnHostname.mjs.map +1 -1
  12. package/lib/internal/_isFormatTime.js +19 -2
  13. package/lib/internal/_isFormatTime.js.map +1 -1
  14. package/lib/internal/_isFormatTime.mjs +9 -2
  15. package/lib/internal/_isFormatTime.mjs.map +1 -1
  16. package/lib/internal/_randomArray.js +37 -4
  17. package/lib/internal/_randomArray.js.map +1 -1
  18. package/lib/internal/_randomArray.mjs +16 -3
  19. package/lib/internal/_randomArray.mjs.map +1 -1
  20. package/lib/internal/_randomFormatByte.js +15 -12
  21. package/lib/internal/_randomFormatByte.js.map +1 -1
  22. package/lib/internal/_randomFormatByte.mjs +11 -10
  23. package/lib/internal/_randomFormatByte.mjs.map +1 -1
  24. package/lib/internal/_randomFormatDatetime.mjs +1 -1
  25. package/lib/internal/_randomFormatHostname.d.ts +2 -2
  26. package/lib/internal/_randomFormatHostname.js +2 -2
  27. package/lib/internal/_randomFormatHostname.mjs +2 -2
  28. package/lib/internal/_randomFormatHostname.mjs.map +1 -1
  29. package/lib/internal/_randomFormatIdnHostname.js +9 -10
  30. package/lib/internal/_randomFormatIdnHostname.js.map +1 -1
  31. package/lib/internal/_randomFormatIdnHostname.mjs +3 -4
  32. package/lib/internal/_randomFormatIdnHostname.mjs.map +1 -1
  33. package/lib/internal/_randomFormatRegex.js +4 -4
  34. package/lib/internal/_randomFormatRegex.js.map +1 -1
  35. package/lib/internal/_randomFormatRegex.mjs +1 -2
  36. package/lib/internal/_randomFormatRegex.mjs.map +1 -1
  37. package/lib/internal/_randomFormatTime.js +15 -10
  38. package/lib/internal/_randomFormatTime.js.map +1 -1
  39. package/lib/internal/_randomFormatTime.mjs +6 -3
  40. package/lib/internal/_randomFormatTime.mjs.map +1 -1
  41. package/lib/internal/_randomPattern.js +20 -3
  42. package/lib/internal/_randomPattern.js.map +1 -1
  43. package/lib/internal/_randomPattern.mjs +5 -1
  44. package/lib/internal/_randomPattern.mjs.map +1 -1
  45. package/lib/internal/_randomStringLength.js +9 -2
  46. package/lib/internal/_randomStringLength.js.map +1 -1
  47. package/lib/internal/_randomStringLength.mjs +3 -1
  48. package/lib/internal/_randomStringLength.mjs.map +1 -1
  49. package/lib/internal/private/__randomComposition.d.ts +8 -0
  50. package/lib/internal/private/__randomComposition.js +21 -1
  51. package/lib/internal/private/__randomComposition.js.map +1 -1
  52. package/lib/internal/private/__randomComposition.mjs +18 -1
  53. package/lib/internal/private/__randomComposition.mjs.map +1 -1
  54. package/lib/json.d.ts +2 -2
  55. package/lib/json.js.map +1 -1
  56. package/lib/json.mjs.map +1 -1
  57. package/lib/module.d.ts +28 -13
  58. package/lib/module.js.map +1 -1
  59. package/lib/module.mjs.map +1 -1
  60. package/lib/notations.d.ts +4 -4
  61. package/lib/notations.js.map +1 -1
  62. package/lib/notations.mjs.map +1 -1
  63. package/lib/plain.d.ts +3 -3
  64. package/lib/plain.js.map +1 -1
  65. package/lib/plain.mjs.map +1 -1
  66. package/lib/protobuf.d.ts +2 -2
  67. package/lib/protobuf.js.map +1 -1
  68. package/lib/protobuf.mjs.map +1 -1
  69. package/lib/transform.mjs +1 -1
  70. package/native/cmd/ttsc-typia/assert_guard_factory_contract_test.go +17 -4
  71. package/native/cmd/ttsc-typia/create_assert_error_factory_arity_test.go +309 -0
  72. package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +206 -0
  73. package/native/cmd/ttsc-typia/local_function_named_like_typia_operation_transform_test.go +185 -0
  74. package/native/cmd/ttsc-typia/notation_kebab_case_transform_test.go +2 -2
  75. package/native/cmd/ttsc-typia/project_catalog_transform_coverage_test.go +84 -85
  76. package/native/cmd/ttsc-typia/recursive_conditional_alias_name_transform_test.go +307 -0
  77. package/native/cmd/ttsc-typia/shadowed_typia_module_declaration_diagnostic_test.go +152 -0
  78. package/native/cmd/ttsc-typia/transform.go +2 -2
  79. package/native/cmd/ttsc-typia/transform_helpers_test.go +96 -18
  80. package/native/core/factories/FormatCheatSheet.go +2 -2
  81. package/native/core/factories/MetadataCommentTagFactory.go +91 -7
  82. package/native/core/factories/factory_utility_coverage_test.go +896 -907
  83. package/native/core/factories/internal/metadata/MetadataHelper.go +5 -28
  84. package/native/core/factories/metadata_comment_tag_factory_rejects_out_of_range_integer_test.go +124 -0
  85. package/native/core/programmers/AssertProgrammer.go +20 -6
  86. package/native/core/programmers/RandomProgrammer.go +78 -3
  87. package/native/core/programmers/ValidateProgrammer.go +21 -6
  88. package/native/core/programmers/helpers/RandomJoiner.go +8 -1
  89. package/native/core/programmers/iterate/check_dynamic_properties.go +177 -3
  90. package/native/core/programmers/iterate/check_object.go +12 -1
  91. package/native/core/schemas/metadata/MetadataCollection.go +47 -5
  92. package/native/transform/CallExpressionTransformer.go +167 -0
  93. package/package.json +3 -12
  94. package/src/http.ts +18 -4
  95. package/src/internal/_assertGuard.ts +13 -2
  96. package/src/internal/_isFormatIdnHostname.ts +5 -1
  97. package/src/internal/_isFormatTime.ts +19 -2
  98. package/src/internal/_randomArray.ts +42 -6
  99. package/src/internal/_randomFormatByte.ts +17 -12
  100. package/src/internal/_randomFormatHostname.ts +2 -2
  101. package/src/internal/_randomFormatIdnHostname.ts +8 -9
  102. package/src/internal/_randomFormatRegex.ts +4 -6
  103. package/src/internal/_randomFormatTime.ts +15 -10
  104. package/src/internal/_randomPattern.ts +18 -3
  105. package/src/internal/_randomStringLength.ts +9 -2
  106. package/src/internal/private/__randomComposition.ts +24 -0
  107. package/src/json.ts +16 -4
  108. package/src/module.ts +56 -22
  109. package/src/notations.ts +16 -4
  110. package/src/plain.ts +12 -3
  111. package/src/protobuf.ts +16 -4
  112. package/lib/executable/FileSystemIdentity.d.ts +0 -44
  113. package/lib/executable/FileSystemIdentity.js +0 -213
  114. package/lib/executable/FileSystemIdentity.js.map +0 -1
  115. package/lib/executable/FileSystemIdentity.mjs +0 -133
  116. package/lib/executable/FileSystemIdentity.mjs.map +0 -1
  117. package/lib/executable/TypiaGenerateWizard.d.ts +0 -9
  118. package/lib/executable/TypiaGenerateWizard.js +0 -852
  119. package/lib/executable/TypiaGenerateWizard.js.map +0 -1
  120. package/lib/executable/TypiaGenerateWizard.mjs +0 -618
  121. package/lib/executable/TypiaGenerateWizard.mjs.map +0 -1
  122. package/lib/executable/generate/ttsc.d.ts +0 -1
  123. package/lib/executable/generate/ttsc.js +0 -53
  124. package/lib/executable/generate/ttsc.js.map +0 -1
  125. package/lib/executable/generate/ttsc.mjs +0 -37
  126. package/lib/executable/generate/ttsc.mjs.map +0 -1
  127. package/lib/executable/typia.d.ts +0 -2
  128. package/lib/executable/typia.js +0 -84
  129. package/lib/executable/typia.js.map +0 -1
  130. package/lib/executable/typia.mjs +0 -60
  131. package/lib/executable/typia.mjs.map +0 -1
  132. package/src/executable/FileSystemIdentity.ts +0 -222
  133. package/src/executable/TypiaGenerateWizard.ts +0 -1133
  134. package/src/executable/generate/ttsc.ts +0 -70
  135. package/src/executable/typia.ts +0 -80
@@ -70,34 +70,11 @@ func metadata_type_full_name(
70
70
  return result
71
71
  }
72
72
 
73
- // metadata_type_symbol_name mirrors TypeFactory.getFullName({ aliasTypeArguments:
74
- // false }) from legacy typia: the name is derived from the type's *symbol*, not
75
- // from checker.typeToString. This matters for alias chains (e.g. MapAlias.MAP =
76
- // Map<K, V>) where typeToString prints the alias name ("MapAlias.MAP<...>")
77
- // instead of "Map<...>", which would defeat the Map/Set prefix guards.
78
- func metadata_type_symbol_name(checker *nativechecker.Checker, typ *nativechecker.Type) string {
79
- if checker == nil || typ == nil {
80
- return ""
81
- }
82
- symbol := typ.Symbol()
83
- if symbol == nil {
84
- return checker.TypeToString(typ)
85
- }
86
- name := metadata_symbol_name(symbol)
87
- generic := metadata_get_type_arguments(checker, typ)
88
- if len(generic) == 0 {
89
- return name
90
- }
91
- if name == "Promise" {
92
- return metadata_type_symbol_name(checker, generic[0])
93
- }
94
- names := make([]string, 0, len(generic))
95
- for _, child := range generic {
96
- names = append(names, metadata_type_symbol_name(checker, child))
97
- }
98
- return name + "<" + strings.Join(names, ", ") + ">"
99
- }
100
-
73
+ // metadata_type_symbol_base_name derives the name from the type's own *symbol*,
74
+ // never from checker.TypeToString. The Map / Set / native guards test this name
75
+ // against a prefix, and an alias chain (e.g. `type MAP = Map<K, V>`) makes
76
+ // TypeToString print the alias name ("MapAlias.MAP<...>") instead of "Map<...>",
77
+ // which would defeat those guards.
101
78
  func metadata_type_symbol_base_name(typ *nativechecker.Type) string {
102
79
  if typ == nil {
103
80
  return ""
@@ -0,0 +1,124 @@
1
+ package factories
2
+
3
+ import "testing"
4
+
5
+ // TestMetadataCommentTagFactoryRejectsOutOfRangeInteger pins the boundary of the
6
+ // float64-to-int conversion behind every numeric JSDoc tag.
7
+ //
8
+ // `metadataCommentTagFactory_parse_integer` used to end in a bare `int(parsed)`.
9
+ // A float64 outside the destination range converts by an implementation-defined
10
+ // rule rather than by wrapping, so on amd64 both `1e19` and `-1e19` became
11
+ // -9223372036854775808 -- a magnitude and its negation collapsing to the same
12
+ // wrong number, silently, inside `@minItems` and `@maxItems`.
13
+ //
14
+ // The destination is int64, not int. `int` is 32 bits on the 32-bit platforms
15
+ // ttsc publishes a binary for (`@ttsc/linux-arm`), so bounding by `int` would
16
+ // leave the same defect at 2**31 there while closing it at 2**63 on x64 -- and
17
+ // would make `@minimum 3000000000` on a `bigint` mean one thing per platform.
18
+ // Case 4 is what holds the boundary platform-independent.
19
+ //
20
+ // The guard has to be exclusive at 2**63 rather than inclusive at
21
+ // math.MaxInt64. An untyped constant 9223372036854775807.0 rounds to 2**63 once
22
+ // it meets a float64 operand, so an inclusive comparison lets exactly the one
23
+ // value through that int64 cannot hold. This test is what keeps that distinction
24
+ // from being "simplified" back.
25
+ //
26
+ // 1. Reject magnitudes at and beyond 2**63 in both directions.
27
+ // 2. Accept the true minimum and the largest float64 below the maximum, and
28
+ // require the value carried through to be the value parsed.
29
+ // 3. Keep the existing non-integer and unsigned diagnostics untouched.
30
+ // 4. Require the accepted range to be int64's on every platform, not the
31
+ // compiling platform's `int`.
32
+ func TestMetadataCommentTagFactoryRejectsOutOfRangeInteger(t *testing.T) {
33
+ parse := func(value string, unsigned bool) (*int64, []string) {
34
+ messages := []string{}
35
+ report := func(msg string) any {
36
+ messages = append(messages, msg)
37
+ return nil
38
+ }
39
+ return metadataCommentTagFactory_parse_integer(struct {
40
+ Report func(msg string) any
41
+ Unsigned bool
42
+ Value string
43
+ }{Report: report, Unsigned: unsigned, Value: value}), messages
44
+ }
45
+
46
+ //----
47
+ // 1. out of range, both directions
48
+ //----
49
+ for _, value := range []string{
50
+ "9223372036854775808", // 2**63 exactly -- the value an inclusive guard let through
51
+ "10000000000000000000", // 1e19
52
+ "-10000000000000000000", // -1e19, which collapsed to the same int as +1e19
53
+ "1e30",
54
+ } {
55
+ parsed, messages := parse(value, false)
56
+ if parsed != nil {
57
+ t.Fatalf("%s must be rejected, got %d", value, *parsed)
58
+ }
59
+ if len(messages) == 0 {
60
+ t.Fatalf("%s must be reported, not dropped silently", value)
61
+ }
62
+ }
63
+
64
+ //----
65
+ // 2. in range, carried through unchanged
66
+ //----
67
+ for _, tuple := range []struct {
68
+ value string
69
+ expected int64
70
+ }{
71
+ {value: "0", expected: 0},
72
+ {value: "1", expected: 1},
73
+ {value: "-1", expected: -1},
74
+ {value: "-9223372036854775808", expected: -9223372036854775808}, // the true minimum
75
+ {value: "9223372036854774784", expected: 9223372036854774784}, // largest float64 below 2**63
76
+ } {
77
+ parsed, messages := parse(tuple.value, false)
78
+ if parsed == nil {
79
+ t.Fatalf("%s must be accepted, reports=%#v", tuple.value, messages)
80
+ }
81
+ if *parsed != tuple.expected {
82
+ t.Fatalf("%s must parse to %d, got %d", tuple.value, tuple.expected, *parsed)
83
+ }
84
+ if len(messages) != 0 {
85
+ t.Fatalf("%s must report nothing, got %#v", tuple.value, messages)
86
+ }
87
+ }
88
+
89
+ //----
90
+ // 3. the diagnostics that already existed
91
+ //----
92
+ if parsed, messages := parse("1.5", false); parsed != nil || len(messages) == 0 {
93
+ t.Fatalf("a non-integer must still be rejected: %#v %#v", parsed, messages)
94
+ }
95
+ if parsed, messages := parse("-1", true); parsed != nil || len(messages) == 0 {
96
+ t.Fatalf("a negative unsigned integer must still be rejected: %#v %#v", parsed, messages)
97
+ }
98
+
99
+ //----
100
+ // 4. the boundary is int64's on every platform, not the compiling one's int
101
+ //----
102
+ // These sit above int32 and below int64. A guard written against `int`
103
+ // accepts them here and converts them out of range on `@ttsc/linux-arm` --
104
+ // the same defect this test closes, moved to a platform the suite does not
105
+ // usually run on. `GOARCH=arm go vet ./core/factories/` is what executes this
106
+ // case for real; on a 64-bit host it is the declared type that carries it.
107
+ for _, tuple := range []struct {
108
+ value string
109
+ expected int64
110
+ }{
111
+ {value: "2147483648", expected: 2147483648}, // 2**31
112
+ {value: "-2147483649", expected: -2147483649}, // -2**31 - 1
113
+ {value: "4294967296", expected: 4294967296}, // 2**32, past uint32 too
114
+ {value: "3000000000", expected: 3000000000}, // a plausible @minimum on a bigint
115
+ } {
116
+ parsed, messages := parse(tuple.value, false)
117
+ if parsed == nil {
118
+ t.Fatalf("%s is inside int64 and must be accepted on every platform, reports=%#v", tuple.value, messages)
119
+ }
120
+ if *parsed != tuple.expected {
121
+ t.Fatalf("%s must parse to %d, got %d", tuple.value, tuple.expected, *parsed)
122
+ }
123
+ }
124
+ }
@@ -65,13 +65,13 @@ func (assertProgrammerNamespace) Decompose(props AssertProgrammer_DecomposeProps
65
65
  Type: props.Type,
66
66
  Name: props.Name,
67
67
  Config: nativeinternal.CheckerProgrammer_IConfig{
68
- Prefix: "_a",
69
- Path: true,
70
- Trace: true,
71
- Numeric: nativehelpers.OptionPredicator.Numeric(props.Context.Options),
72
- Equals: props.Config.Equals,
68
+ Prefix: "_a",
69
+ Path: true,
70
+ Trace: true,
71
+ Numeric: nativehelpers.OptionPredicator.Numeric(props.Context.Options),
72
+ Equals: props.Config.Equals,
73
73
  ObjectParents: props.Config.Equals == false,
74
- Atomist: assertProgrammer_atomist(props),
74
+ Atomist: assertProgrammer_atomist(props),
75
75
  Combiner: assertProgrammer_combiner(struct {
76
76
  Config AssertProgrammer_IConfig
77
77
  Context nativecontext.ITypiaContext
@@ -323,6 +323,20 @@ func assertProgrammer_assert_object(props assertProgrammer_assertObjectProps) *s
323
323
  Input: input,
324
324
  })
325
325
  },
326
+ InvalidKey: func(input *shimast.Expression, expected string, description *shimast.Expression) *shimast.Node {
327
+ return assertProgrammer_create_guard_call(assertProgrammer_createGuardCallProps{
328
+ Context: props.Context,
329
+ Functor: props.Functor,
330
+ Path: assertProgrammer_binary(
331
+ f.NewIdentifier("_path"),
332
+ shimast.KindPlusToken,
333
+ f.NewCallExpression(assertProgrammer_internal(props.Context, "accessExpressionAsString"), nil, nil, f.NewNodeList([]*shimast.Node{f.NewIdentifier("key")}), shimast.NodeFlagsNone),
334
+ props.Context.Emit,
335
+ ),
336
+ Expected: expected,
337
+ Input: input,
338
+ })
339
+ },
326
340
  Halt: func(expr *shimast.Expression) *shimast.Node {
327
341
  return assertProgrammer_or(
328
342
  assertProgrammer_equal(f.NewKeywordExpression(shimast.KindFalseKeyword), f.NewIdentifier("_exceptionable"), props.Context.Emit),
@@ -546,6 +546,26 @@ func randomProgrammer_decode(props randomProgrammer_decodeProps) *shimast.Node {
546
546
  if len(expressions) == 1 {
547
547
  return expressions[0]
548
548
  }
549
+ if escape := randomProgrammer_recursion_escape(props); escape != nil {
550
+ return nativefactories.ExpressionFactory.Conditional(
551
+ f.NewBinaryExpression(
552
+ nil,
553
+ nativefactories.ExpressionFactory.Number(nativehelpers.RandomJoiner_RECURSION_CUTOFF, props.Context.Emit),
554
+ nil,
555
+ f.NewToken(shimast.KindGreaterThanEqualsToken),
556
+ f.NewIdentifier("_depth"),
557
+ ),
558
+ randomProgrammer_decode_pick(props, expressions),
559
+ escape,
560
+ props.Context.Emit,
561
+ )
562
+ }
563
+ return randomProgrammer_decode_pick(props, expressions)
564
+ }
565
+
566
+ // randomProgrammer_decode_pick draws one of the candidate expressions uniformly.
567
+ func randomProgrammer_decode_pick(props randomProgrammer_decodeProps, expressions []*shimast.Node) *shimast.Node {
568
+ f := nativecontext.EmitFactoryOf(randomProgrammer_factory, props.Context.Emit)
549
569
  pickers := make([]*shimast.Node, 0, len(expressions))
550
570
  for _, expr := range expressions {
551
571
  value := expr
@@ -576,6 +596,50 @@ func randomProgrammer_decode(props randomProgrammer_decodeProps) *shimast.Node {
576
596
  )
577
597
  }
578
598
 
599
+ // randomProgrammer_recursion_escape reports the terminating candidate a
600
+ // recursive leaf must take once the depth cutoff is reached, or nil when the
601
+ // leaf has none to force.
602
+ //
603
+ // A recursive array already stops at the cutoff -- RandomJoiner emits
604
+ // `CUTOFF >= _depth ? … : []` around it. The nullable and optional escapes had
605
+ // no such guard: `null` and `undefined` were simply two more candidates in the
606
+ // uniform `_randomPick`, so the depth of a `self?: T` or `self: T | null` chain
607
+ // was geometric with no bound at all. Measured before this guard, 20,000 draws
608
+ // of `{ value: string; self?: Self }` reached depth 16, and exceeded 8 on 44 of
609
+ // them -- which is why a suite asserting a depth bound failed at random.
610
+ //
611
+ // The guard needs `_depth` in scope, so it applies only inside a generated
612
+ // function (`Explore.Function`) reached recursively (`Explore.Recursive`), and
613
+ // only where the leaf actually recurses through an object or tuple. A leaf that
614
+ // is merely nullable, with no recursive candidate, keeps drawing uniformly.
615
+ func randomProgrammer_recursion_escape(props randomProgrammer_decodeProps) *shimast.Node {
616
+ if props.Explore.Function == false || props.Explore.Recursive == false {
617
+ return nil
618
+ }
619
+ recursive := false
620
+ for _, object := range props.Metadata.Objects {
621
+ if object.Type != nil && object.Type.Recursive {
622
+ recursive = true
623
+ }
624
+ }
625
+ for _, tuple := range props.Metadata.Tuples {
626
+ if tuple.Type != nil && tuple.Type.Recursive {
627
+ recursive = true
628
+ }
629
+ }
630
+ if recursive == false {
631
+ return nil
632
+ }
633
+ f := nativecontext.EmitFactoryOf(randomProgrammer_factory, props.Context.Emit)
634
+ if props.Metadata.Nullable {
635
+ return f.NewKeywordExpression(shimast.KindNullKeyword)
636
+ }
637
+ if props.Metadata.IsRequired() == false {
638
+ return f.NewIdentifier("undefined")
639
+ }
640
+ return nil
641
+ }
642
+
579
643
  func randomProgrammer_decode_atomic(props randomProgrammer_decodeAtomicProps) []*shimast.Node {
580
644
  var schemaList []nativeiterate.JsonSchema
581
645
  if props.Atomic.Type == "boolean" {
@@ -1441,9 +1505,20 @@ func randomProgrammer_is_typed_array(name string) bool {
1441
1505
  // copy of the 64-bit bound that is deliberately inexact. They become `minimum` and
1442
1506
  // `maximum` comment tags on a number-typed JSON schema, and `number` cannot
1443
1507
  // represent 2**63 - 1 or 2**64 - 1 -- both arrive rounded up to the next power of
1444
- // two. That costs nothing here: the generator only picks a value from the range,
1445
- // and the TypedArray constructor wraps whatever it is handed. Validation owns
1446
- // exactness instead, in _isTypeInt64Bigint and its siblings.
1508
+ // two. That costs nothing here, and not because the generator is careless: it
1509
+ // draws Math.floor(Math.random() * (maximum - minimum + 1)) + minimum, and that
1510
+ // span is 2**64 either way, where consecutive doubles are 2048 apart. So the
1511
+ // largest draw is 2**63 - 2048 for int64 and 2**64 - 2048 for uint64, both
1512
+ // inside the width. (Nearer the int64 maximum itself the spacing is 1024, which
1513
+ // is why the largest double below 2**63 is 2**63 - 1024; the span is what bounds
1514
+ // the draw.) The TypedArray constructor then wraps whatever it is handed anyway.
1515
+ //
1516
+ // Only the `bigint` path is exact, in _isTypeInt64Bigint and _isTypeUint64Bigint,
1517
+ // which take the true inclusive bound through a BigInt string a `number` literal
1518
+ // here could not spell. _isTypeInt64 and _isTypeUint64 on the `number` path
1519
+ // carry the same rounding this table does: their maxima are written 2**63 - 1
1520
+ // and 2**64 - 1, which are the powers of two as doubles, so each accepts one
1521
+ // value above its width -- the only float form that maximum has.
1447
1522
  func randomProgrammer_typed_array_range(name string) (string, string, string) {
1448
1523
  switch name {
1449
1524
  case "Uint8Array", "Uint8ClampedArray":
@@ -55,13 +55,13 @@ func (validateProgrammerNamespace) Decompose(props ValidateProgrammer_DecomposeP
55
55
  Type: props.Type,
56
56
  Name: props.Name,
57
57
  Config: nativeinternal.CheckerProgrammer_IConfig{
58
- Prefix: "_v",
59
- Path: true,
60
- Trace: true,
61
- Numeric: nativehelpers.OptionPredicator.Numeric(props.Context.Options),
62
- Equals: props.Config.Equals,
58
+ Prefix: "_v",
59
+ Path: true,
60
+ Trace: true,
61
+ Numeric: nativehelpers.OptionPredicator.Numeric(props.Context.Options),
62
+ Equals: props.Config.Equals,
63
63
  ObjectParents: props.Config.Equals == false,
64
- Atomist: validateProgrammer_atomist(props),
64
+ Atomist: validateProgrammer_atomist(props),
65
65
  Combiner: validateProgrammer_combine(struct {
66
66
  Config ValidateProgrammer_IConfig
67
67
  Context nativecontext.ITypiaContext
@@ -312,6 +312,21 @@ func validateProgrammer_validate_object(props validateProgrammer_validateObjectP
312
312
  Emit: props.Context.Emit,
313
313
  })
314
314
  },
315
+ InvalidKey: func(input *shimast.Expression, expected string, description *shimast.Expression) *shimast.Node {
316
+ return validateProgrammer_create_report_call(validateProgrammer_createReportCallProps{
317
+ Path: validateProgrammer_binary(
318
+ f.NewIdentifier("_path"),
319
+ shimast.KindPlusToken,
320
+ f.NewCallExpression(validateProgrammer_internal(props.Context, "accessExpressionAsString"), nil, nil, f.NewNodeList([]*shimast.Node{f.NewIdentifier("key")}), shimast.NodeFlagsNone),
321
+ props.Context.Emit,
322
+ ),
323
+ Expected: expected,
324
+ Value: input,
325
+ Description: description,
326
+ Functor: props.Functor,
327
+ Emit: props.Context.Emit,
328
+ })
329
+ },
315
330
  Halt: func(expr *shimast.Expression) *shimast.Node {
316
331
  return validateProgrammer_or(validateProgrammer_equal(f.NewKeywordExpression(shimast.KindFalseKeyword), f.NewIdentifier("_exceptionable"), props.Context.Emit), expr, props.Context.Emit)
317
332
  },
@@ -15,6 +15,13 @@ type randomJoinerNamespace struct{}
15
15
 
16
16
  var RandomJoiner = randomJoinerNamespace{}
17
17
 
18
+ // RandomJoiner_RECURSION_CUTOFF is the `_depth` past which a recursive draw
19
+ // stops descending: a recursive array empties, and a recursive leaf with a
20
+ // nullable or optional escape takes it. One constant because one number decides
21
+ // both -- a recursive value that stopped at two different depths depending on
22
+ // which escape it happened to own would make a depth bound unstatable.
23
+ const RandomJoiner_RECURSION_CUTOFF = 5
24
+
18
25
  type RandomJoiner_Decoder func(metadata *nativemetadata.MetadataSchema) *shimast.Node
19
26
 
20
27
  type RandomJoiner_ArrayProps struct {
@@ -126,7 +133,7 @@ func (randomJoinerNamespace) Array(props RandomJoiner_ArrayProps) *shimast.Node
126
133
  return nativefactories.ExpressionFactory.Conditional(
127
134
  f.NewBinaryExpression(
128
135
  nil,
129
- nativefactories.ExpressionFactory.Number(5, props.Emit),
136
+ nativefactories.ExpressionFactory.Number(RandomJoiner_RECURSION_CUTOFF, props.Emit),
130
137
  nil,
131
138
  f.NewToken(shimast.KindGreaterThanEqualsToken),
132
139
  f.NewIdentifier("_depth"),
@@ -1,11 +1,14 @@
1
1
  package iterate
2
2
 
3
3
  import (
4
+ "strings"
5
+
4
6
  shimast "github.com/microsoft/typescript-go/shim/ast"
5
7
  shimprinter "github.com/microsoft/typescript-go/shim/printer"
6
8
  nativecontext "github.com/samchon/typia/packages/typia/native/core/context"
7
9
  nativefactories "github.com/samchon/typia/packages/typia/native/core/factories"
8
10
  nativehelpers "github.com/samchon/typia/packages/typia/native/core/programmers/helpers"
11
+ nativemetadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
9
12
  )
10
13
 
11
14
  type Check_dynamic_propertiesProps struct {
@@ -126,7 +129,11 @@ func check_dynamic_property(props Check_dynamic_propertiesProps) *shimast.Node {
126
129
  nil,
127
130
  ))
128
131
  }
129
- broken := false
132
+ // terminated marks that `statements` already ends in an unconditional return,
133
+ // so nothing after it could run. A signature that accepts every key sets it
134
+ // from the loop; a declared key type that covers every key sets it from the
135
+ // tail below.
136
+ terminated := false
130
137
 
131
138
  if len(props.Regular) != 0 {
132
139
  add(is_regular_property(props.Regular, props.Context.Emit), props.Config.Positive)
@@ -156,13 +163,58 @@ func check_dynamic_property(props Check_dynamic_propertiesProps) *shimast.Node {
156
163
  })
157
164
  if condition.Kind == shimast.KindTrueKeyword {
158
165
  statements = append(statements, f.NewReturnStatement(entry.Expression))
159
- broken = true
166
+ terminated = true
160
167
  break
161
168
  }
162
169
  add(condition, entry.Expression)
163
170
  }
164
171
 
165
- if !broken {
172
+ if !terminated {
173
+ // Reaching here means the key satisfied no dynamic signature. Two different
174
+ // things bring a key here, and they are not the same failure.
175
+ //
176
+ // A key whose *declared type* no signature covers -- `wrong` against
177
+ // `[key: `prefix_${string}`]` -- is an extra property. Nothing declares it,
178
+ // so `is` accepts it as it accepts any surplus property, and `equals`
179
+ // reports it through `Superfluous`.
180
+ //
181
+ // A key the declaration does cover but whose *type tag* it breaks -- `ab`
182
+ // against `[key: string & tags.MinLength<3>]` -- is a declared property
183
+ // with an invalid key. It used to reach the same accepting tail, which made
184
+ // every key tag inert: `[key: string & tags.Format<"uuid">]` accepted any
185
+ // key at all, while the emitted check sat right above, used only to decide
186
+ // *which* signature's value type to apply (#2347).
187
+ //
188
+ // So the tag failure is separated out first, by re-asking the same key
189
+ // question with every type tag removed. What is left is exactly the
190
+ // declared key type, and a key that answers it is a member of the object
191
+ // whose key broke a constraint -- never a surplus property.
192
+ shape := check_dynamic_properties_key_shape(props, key)
193
+ if shape != nil {
194
+ var invalid *shimast.Node
195
+ if props.Config.InvalidKey != nil {
196
+ invalid = props.Config.InvalidKey(
197
+ value,
198
+ check_dynamic_properties_key_expected(props.Dynamic),
199
+ check_dynamic_properties_invalid_key_description(props.Context.Emit),
200
+ )
201
+ } else {
202
+ // A programmer with no better report reduces both to the same answer,
203
+ // as `is` does with `false`.
204
+ invalid = props.Config.Superfluous(value, check_dynamic_properties_superfluous_description(props.Context.Emit))
205
+ }
206
+ if shape.Kind == shimast.KindTrueKeyword {
207
+ // Every key the signature accepts is covered -- a string index
208
+ // signature covers every key `Object.keys` can yield -- so the surplus
209
+ // tail below is unreachable and is not emitted.
210
+ statements = append(statements, f.NewReturnStatement(invalid))
211
+ terminated = true
212
+ } else {
213
+ add(shape, invalid)
214
+ }
215
+ }
216
+ }
217
+ if !terminated {
166
218
  output := props.Config.Positive
167
219
  if props.Config.Equals {
168
220
  output = props.Config.Superfluous(value, check_dynamic_properties_superfluous_description(props.Context.Emit))
@@ -257,6 +309,128 @@ func check_dynamic_properties_internal(context nativecontext.ITypiaContext, name
257
309
  return f.NewIdentifier(name)
258
310
  }
259
311
 
312
+ // The condition that the key belongs to some index signature's declared key
313
+ // type while breaking that signature's type tags -- the question the tail has
314
+ // to answer to tell an invalid key from a surplus property.
315
+ //
316
+ // It is the same `Check_dynamic_key` question asked of every signature with its
317
+ // type tags removed, joined with `||`. The tail runs only after each full
318
+ // condition returned false, so a signature whose declared type still answers
319
+ // yes is one whose tags are what the key broke.
320
+ //
321
+ // Nil when no signature carries a key tag. Then each stripped condition equals
322
+ // the full one the tail already refuted, so the check could never fire, and an
323
+ // object with untagged index signatures emits exactly what it emitted before.
324
+ func check_dynamic_properties_key_shape(props Check_dynamic_propertiesProps, key *shimast.Expression) *shimast.Node {
325
+ tagged := false
326
+ for _, entry := range props.Dynamic {
327
+ if check_dynamic_properties_key_tagged(entry.Key) {
328
+ tagged = true
329
+ break
330
+ }
331
+ }
332
+ if !tagged {
333
+ return nil
334
+ }
335
+ conditions := make([]*shimast.Node, 0, len(props.Dynamic))
336
+ for _, entry := range props.Dynamic {
337
+ condition := Check_dynamic_key(Check_dynamic_keyProps{
338
+ Context: props.Context,
339
+ Metadata: check_dynamic_properties_key_untagged(entry.Key),
340
+ Input: key,
341
+ })
342
+ if condition.Kind == shimast.KindTrueKeyword {
343
+ return condition
344
+ }
345
+ conditions = append(conditions, condition)
346
+ }
347
+ return check_dynamic_key_reduce(conditions, shimast.KindBarBarToken, props.Context.Emit)
348
+ }
349
+
350
+ // Whether the key declaration constrains more than its own type, which is what
351
+ // makes the stripped question differ from the full one.
352
+ //
353
+ // One tag carrying a runtime check is enough, and the row it sits in need not
354
+ // carry them all. `Check_string` and `Check_number` filter per tag, so
355
+ // `[key: number & Minimum<0> & <a tag with no runtime check>]` still emits the
356
+ // minimum -- reading the row as all-or-nothing here would have declared that
357
+ // key untagged and left its minimum inert, which is the defect #2347 closed.
358
+ //
359
+ // Answering yes where the stripped question turns out identical costs an
360
+ // unreachable branch, never a wrong answer: the tail is reached only after
361
+ // every full condition refused the key, so a stripped condition equal to one of
362
+ // them refuses it too.
363
+ func check_dynamic_properties_key_tagged(metadata *nativemetadata.MetadataSchema) bool {
364
+ for _, atomic := range metadata.Atomics {
365
+ for _, row := range atomic.Tags {
366
+ for _, tag := range row {
367
+ if tag.Validate != "" {
368
+ return true
369
+ }
370
+ }
371
+ }
372
+ }
373
+ return false
374
+ }
375
+
376
+ // The key declaration with its type tags removed, leaving the declared type
377
+ // alone. Templates, constants, and natives carry no tags, so only the atomics
378
+ // are rebuilt; the clone keeps the original reportable name untouched.
379
+ func check_dynamic_properties_key_untagged(metadata *nativemetadata.MetadataSchema) *nativemetadata.MetadataSchema {
380
+ clone := *metadata
381
+ clone.Atomics = make([]*nativemetadata.MetadataAtomic, 0, len(metadata.Atomics))
382
+ for _, atomic := range metadata.Atomics {
383
+ clone.Atomics = append(clone.Atomics, nativemetadata.MetadataAtomic_create(nativemetadata.MetadataAtomic{
384
+ Type: atomic.Type,
385
+ }))
386
+ }
387
+ return &clone
388
+ }
389
+
390
+ // The type every declared key would have satisfied, for the report. Several
391
+ // signatures join with `|`, which is how the declaration reads.
392
+ func check_dynamic_properties_key_expected(dynamic []nativehelpers.IExpressionEntry) string {
393
+ names := make([]string, 0, len(dynamic))
394
+ for _, entry := range dynamic {
395
+ names = append(names, entry.Key.GetName())
396
+ }
397
+ return strings.Join(names, " | ")
398
+ }
399
+
400
+ // The advice `Superfluous` gives -- remove the property -- is wrong here. The
401
+ // property is declared; its key is what broke.
402
+ func check_dynamic_properties_invalid_key_description(emit ...*shimprinter.EmitContext) *shimast.Node {
403
+ f := nativecontext.EmitFactoryOf(check_dynamic_properties_factory, emit...)
404
+ return f.NewCallExpression(
405
+ nativefactories.IdentifierFactory.Access(
406
+ nil,
407
+ f.NewArrayLiteralExpression(
408
+ f.NewNodeList([]*shimast.Node{
409
+ f.NewTemplateExpression(
410
+ f.NewTemplateHead("The key `", "The key \\`", shimast.TokenFlagsNone),
411
+ f.NewNodeList([]*shimast.Node{
412
+ f.NewTemplateSpan(
413
+ f.NewIdentifier("key"),
414
+ f.NewTemplateTail("` does not satisfy the type its index signature declares.", "\\` does not satisfy the type its index signature declares.", shimast.TokenFlagsNone),
415
+ ),
416
+ }),
417
+ ),
418
+ f.NewStringLiteral("", shimast.TokenFlagsNone),
419
+ f.NewStringLiteral("Please correct the key next time.", shimast.TokenFlagsNone),
420
+ }),
421
+ true,
422
+ ),
423
+ "join",
424
+ ),
425
+ nil,
426
+ nil,
427
+ f.NewNodeList([]*shimast.Node{
428
+ f.NewStringLiteral("\n", shimast.TokenFlagsNone),
429
+ }),
430
+ shimast.NodeFlagsNone,
431
+ )
432
+ }
433
+
260
434
  func check_dynamic_properties_superfluous_description(emit ...*shimprinter.EmitContext) *shimast.Node {
261
435
  f := nativecontext.EmitFactoryOf(check_dynamic_properties_factory, emit...)
262
436
  return f.NewCallExpression(
@@ -22,7 +22,18 @@ type Check_object_IConfig struct {
22
22
  Reduce func(a *shimast.Expression, b *shimast.Expression) *shimast.Node
23
23
  Positive *shimast.Expression
24
24
  Superfluous func(value *shimast.Expression, description *shimast.Expression) *shimast.Node
25
- Entries *shimast.Expression
25
+ // InvalidKey reports a dynamic key an index signature declares but whose type
26
+ // tag it breaks. A key no signature declares at all is a surplus property and
27
+ // goes to `Superfluous` instead.
28
+ //
29
+ // `Superfluous` is the wrong report for this one: it says the property is not
30
+ // defined in the object type and advises removing it, which is false and
31
+ // unhelpful when the property *is* declared and only its key broke a
32
+ // constraint. A programmer that has no better answer may leave this nil, and
33
+ // `Superfluous` is used instead -- the outcome is identical, only the message
34
+ // differs.
35
+ InvalidKey func(value *shimast.Expression, expected string, description *shimast.Expression) *shimast.Node
36
+ Entries *shimast.Expression
26
37
  }
27
38
 
28
39
  func Check_object(props Check_objectProps) *shimast.Node {