typia 13.0.0-dev.20260520.2 → 13.0.0-dev.20260601.1

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.
@@ -3,7 +3,6 @@ package adapter
3
3
  import (
4
4
  "fmt"
5
5
  "os"
6
- "regexp"
7
6
  "runtime/debug"
8
7
  "strings"
9
8
 
@@ -138,10 +137,65 @@ func cleanupPrintedExpression(text string) string {
138
137
  return text
139
138
  }
140
139
 
141
- var singleParameterArrowPattern = regexp.MustCompile(`(^|[\s(=,:?])([A-Za-z_$][A-Za-z0-9_$]*) =>`)
142
-
140
+ // parenthesizeSingleParameterArrows wraps a bare single-parameter arrow head
141
+ // `x =>` into `(x) =>`. It reproduces the regex
142
+ // `(^|[\s(=,:?])([A-Za-z_$][A-Za-z0-9_$]*) =>` -> `${1}(${2}) =>` with a manual
143
+ // byte scan: the printed call-site expression is large and this pass ran on
144
+ // every site, where the regex backtracker dominated the cleanup CPU.
143
145
  func parenthesizeSingleParameterArrows(text string) string {
144
- return singleParameterArrowPattern.ReplaceAllString(text, `${1}(${2}) =>`)
146
+ arrow := strings.Index(text, " =>")
147
+ if arrow < 0 {
148
+ return text
149
+ }
150
+ var b strings.Builder
151
+ last := 0
152
+ for arrow >= 0 {
153
+ end := arrow // identifier ends just before the space of " =>"
154
+ start := end
155
+ for start > 0 && isArrowIdentByte(text[start-1]) {
156
+ start--
157
+ }
158
+ if end > start && isArrowIdentStart(text[start]) &&
159
+ (start == 0 || isArrowBoundaryByte(text[start-1])) {
160
+ if b.Cap() == 0 {
161
+ b.Grow(len(text) + 16)
162
+ }
163
+ b.WriteString(text[last:start])
164
+ b.WriteByte('(')
165
+ b.WriteString(text[start:end])
166
+ b.WriteByte(')')
167
+ last = end
168
+ }
169
+ next := strings.Index(text[arrow+3:], " =>")
170
+ if next < 0 {
171
+ break
172
+ }
173
+ arrow += 3 + next
174
+ }
175
+ if last == 0 {
176
+ return text
177
+ }
178
+ b.WriteString(text[last:])
179
+ return b.String()
180
+ }
181
+
182
+ func isArrowIdentStart(c byte) bool {
183
+ return c == '_' || c == '$' ||
184
+ (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
185
+ }
186
+
187
+ func isArrowIdentByte(c byte) bool {
188
+ return isArrowIdentStart(c) || (c >= '0' && c <= '9')
189
+ }
190
+
191
+ // isArrowBoundaryByte matches the regex class [\s(=,:?] (RE2 `\s` is
192
+ // [\t\n\f\r ], i.e. no vertical tab).
193
+ func isArrowBoundaryByte(c byte) bool {
194
+ switch c {
195
+ case ' ', '\t', '\n', '\f', '\r', '(', '=', ',', ':', '?':
196
+ return true
197
+ }
198
+ return false
145
199
  }
146
200
 
147
201
  func UnsupportedReason(site CallSite) string {
@@ -5,9 +5,49 @@ import (
5
5
  "regexp"
6
6
  "sort"
7
7
  "strings"
8
+ "sync"
8
9
  )
9
10
 
11
+ // Static cleanup patterns are compiled once at package load. Compiling them
12
+ // inside the cleanup functions recompiled the same expressions on every emitted
13
+ // file, which dominated the cleanup cost (regex compilation, not matching, was
14
+ // the hot spot).
15
+ const typiaCleanupTypeAtom = `([A-Za-z_$][A-Za-z0-9_$.]*(<[^()\n;{}]*>)?)`
16
+
17
+ var (
18
+ reImportTypeLine = regexp.MustCompile(`(?m)^import type \{([^{}\n]+)\} from`)
19
+ reImportTypeNormalize = regexp.MustCompile(`^import type \{\s*([^{}\n]+?)\s*\} from`)
20
+ reBlankBeforeDecl = regexp.MustCompile(`(?m)(^import [^\n]+;\n)\n+(const |let |var |export )`)
21
+ reInputIsParen = regexp.MustCompile(`input is \(([A-Za-z_$][A-Za-z0-9_$.]*)\)`)
22
+ reBlankBeforeCall = regexp.MustCompile(`\n\n([A-Za-z_$][A-Za-z0-9_$]*\([^;\n]*\);?)`)
23
+ reParenTypeArrow = regexp.MustCompile(`: \(` + typiaCleanupTypeAtom + `\)(\s*=>)`)
24
+ reParenNullUndefined = regexp.MustCompile(`\| \((null|undefined)\)`)
25
+ reConstAliasHead = regexp.MustCompile(`^const (\w+) =`)
26
+ reImportAliasHead = regexp.MustCompile(`^import (\w+) from`)
27
+ reRuntimeTransformAlias = regexp.MustCompile(`\b__typia_transform_([A-Za-z0-9_]+)\b`)
28
+ reESModuleOutput = regexp.MustCompile(`(?m)^(import\s|import\{|import\*|export\s)`)
29
+ )
30
+
31
+ // dynamicRegexpCache memoizes the alias/module-parameterized patterns used when
32
+ // checking for pre-existing runtime imports. Aliases and modules recur heavily
33
+ // across the files of a single build, so caching amortizes compilation.
34
+ var dynamicRegexpCache sync.Map // map[string]*regexp.Regexp
35
+
36
+ func cachedRegexp(pattern string) *regexp.Regexp {
37
+ if v, ok := dynamicRegexpCache.Load(pattern); ok {
38
+ return v.(*regexp.Regexp)
39
+ }
40
+ re := regexp.MustCompile(pattern)
41
+ dynamicRegexpCache.Store(pattern, re)
42
+ return re
43
+ }
44
+
10
45
  func CleanupTransformedText(text string) string {
46
+ // Every unused-import pattern references the "typia" module specifier, so the
47
+ // regex scan can be skipped entirely when the emitted file never mentions it.
48
+ if !strings.Contains(text, "typia") {
49
+ return injectRuntimeImports(text)
50
+ }
11
51
  for _, pattern := range unusedImportPatterns {
12
52
  loc := pattern.regex.FindStringIndex(text)
13
53
  for loc != nil {
@@ -34,14 +74,20 @@ func CleanupTransformedText(text string) string {
34
74
  func CleanupTypeScriptTransformText(text string) string {
35
75
  text = CleanupTransformedText(text)
36
76
  text = normalizeParenthesizedTypeAnnotations(text)
37
- text = regexp.MustCompile(`(?m)^import type \{([^{}\n]+)\} from`).ReplaceAllStringFunc(text, func(line string) string {
38
- return regexp.MustCompile(`^import type \{\s*([^{}\n]+?)\s*\} from`).ReplaceAllString(line, "import type { $1 } from")
39
- })
40
- text = regexp.MustCompile(`(?m)(^import [^\n]+;\n)\n+(const |let |var |export )`).ReplaceAllString(text, "$1$2")
77
+ if strings.Contains(text, "import type {") {
78
+ text = reImportTypeLine.ReplaceAllStringFunc(text, func(line string) string {
79
+ return reImportTypeNormalize.ReplaceAllString(line, "import type { $1 } from")
80
+ })
81
+ }
82
+ if strings.Contains(text, "\n\n") {
83
+ text = reBlankBeforeDecl.ReplaceAllString(text, "$1$2")
84
+ }
41
85
  text = strings.ReplaceAll(text, "=(() =>", "= (() =>")
42
86
  text = strings.ReplaceAll(text, ": (any) =>", ": any =>")
43
87
  text = strings.ReplaceAll(text, ": (boolean) =>", ": boolean =>")
44
- text = regexp.MustCompile(`input is \(([A-Za-z_$][A-Za-z0-9_$.]*)\)`).ReplaceAllString(text, "input is $1")
88
+ if strings.Contains(text, "input is (") {
89
+ text = reInputIsParen.ReplaceAllString(text, "input is $1")
90
+ }
45
91
  text = strings.ReplaceAll(text, "return (success ? ", "return success ? ")
46
92
  text = strings.ReplaceAll(text, "}) as any;", "} as any;")
47
93
  text = strings.ReplaceAll(text, "(() => {\n const ", "(() => { const ")
@@ -55,7 +101,9 @@ func CleanupTypeScriptTransformText(text string) string {
55
101
  text = strings.ReplaceAll(text, "\n }); let ", "\n}); let ")
56
102
  text = strings.ReplaceAll(text, ";\n})()", "; })()")
57
103
  text = strings.ReplaceAll(text, "\n ", "\n ")
58
- text = regexp.MustCompile(`\n\n([A-Za-z_$][A-Za-z0-9_$]*\([^;\n]*\);?)`).ReplaceAllString(text, "\n$1")
104
+ if strings.Contains(text, "\n\n") {
105
+ text = reBlankBeforeCall.ReplaceAllString(text, "\n$1")
106
+ }
59
107
  trimmed := strings.TrimRight(text, " \t\r\n")
60
108
  if strings.HasSuffix(trimmed, ")") && !strings.HasSuffix(trimmed, ";") {
61
109
  return trimmed + ";\n"
@@ -67,9 +115,12 @@ func CleanupTypeScriptTransformText(text string) string {
67
115
  }
68
116
 
69
117
  func normalizeParenthesizedTypeAnnotations(text string) string {
70
- typeAtom := `([A-Za-z_$][A-Za-z0-9_$.]*(<[^()\n;{}]*>)?)`
71
- text = regexp.MustCompile(`: \(`+typeAtom+`\)(\s*=>)`).ReplaceAllString(text, ": $1$3")
72
- text = regexp.MustCompile(`\| \((null|undefined)\)`).ReplaceAllString(text, "| $1")
118
+ if strings.Contains(text, ": (") {
119
+ text = reParenTypeArrow.ReplaceAllString(text, ": $1$3")
120
+ }
121
+ if strings.Contains(text, "| (") {
122
+ text = reParenNullUndefined.ReplaceAllString(text, "| $1")
123
+ }
73
124
  return text
74
125
  }
75
126
 
@@ -82,25 +133,25 @@ var unusedImportPatterns = []unusedImportPattern{
82
133
  {
83
134
  regex: regexp.MustCompile(`(?m)^const (typia(?:_\d+)?) = __importDefault\(require\("typia"\)\);$`),
84
135
  alias: func(s string) string {
85
- return firstSubmatch(`^const (\w+) =`, s)
136
+ return firstSubmatch(reConstAliasHead, s)
86
137
  },
87
138
  },
88
139
  {
89
140
  regex: regexp.MustCompile(`(?m)^const (typia(?:_\d+)?) = require\("typia"\);$`),
90
141
  alias: func(s string) string {
91
- return firstSubmatch(`^const (\w+) =`, s)
142
+ return firstSubmatch(reConstAliasHead, s)
92
143
  },
93
144
  },
94
145
  {
95
146
  regex: regexp.MustCompile(`(?m)^import (\w+) from "typia";$`),
96
147
  alias: func(s string) string {
97
- return firstSubmatch(`^import (\w+) from`, s)
148
+ return firstSubmatch(reImportAliasHead, s)
98
149
  },
99
150
  },
100
151
  }
101
152
 
102
- func firstSubmatch(pattern string, text string) string {
103
- match := regexp.MustCompile(pattern).FindStringSubmatch(text)
153
+ func firstSubmatch(re *regexp.Regexp, text string) string {
154
+ match := re.FindStringSubmatch(text)
104
155
  if len(match) < 2 {
105
156
  return ""
106
157
  }
@@ -108,10 +159,38 @@ func firstSubmatch(pattern string, text string) string {
108
159
  }
109
160
 
110
161
  func aliasStillReferenced(text, alias string, lineStart, lineEnd int) bool {
111
- head := text[:lineStart]
112
- tail := text[lineEnd:]
113
- pattern := regexp.MustCompile(`\b` + regexp.QuoteMeta(alias) + `\b`)
114
- return pattern.MatchString(head) || pattern.MatchString(tail)
162
+ // Equivalent to matching `\b<alias>\b`, but alias is always a `\w+` token, so
163
+ // a manual word-boundary scan avoids compiling a regex per import line.
164
+ return containsWord(text[:lineStart], alias) || containsWord(text[lineEnd:], alias)
165
+ }
166
+
167
+ // containsWord reports whether word appears in haystack delimited by ASCII word
168
+ // boundaries, matching Go regexp's `\b<word>\b` for word-only tokens.
169
+ func containsWord(haystack, word string) bool {
170
+ if word == "" {
171
+ return false
172
+ }
173
+ for i := 0; i+len(word) <= len(haystack); {
174
+ j := strings.Index(haystack[i:], word)
175
+ if j < 0 {
176
+ return false
177
+ }
178
+ start := i + j
179
+ end := start + len(word)
180
+ if (start == 0 || !isWordByte(haystack[start-1])) &&
181
+ (end == len(haystack) || !isWordByte(haystack[end])) {
182
+ return true
183
+ }
184
+ i = start + 1
185
+ }
186
+ return false
187
+ }
188
+
189
+ func isWordByte(b byte) bool {
190
+ return b == '_' ||
191
+ (b >= '0' && b <= '9') ||
192
+ (b >= 'a' && b <= 'z') ||
193
+ (b >= 'A' && b <= 'Z')
115
194
  }
116
195
 
117
196
  func injectRuntimeImports(text string) string {
@@ -141,9 +220,13 @@ func injectRuntimeImports(text string) string {
141
220
  }
142
221
 
143
222
  func collectRuntimeAliases(text string) []string {
144
- re := regexp.MustCompile(`\b__typia_transform_([A-Za-z0-9_]+)\b`)
223
+ // The alias regex can only match where the literal prefix appears; skipping
224
+ // the full-text scan when it is absent avoids work on most emitted files.
225
+ if !strings.Contains(text, "__typia_transform_") {
226
+ return nil
227
+ }
145
228
  seen := map[string]bool{}
146
- for _, match := range re.FindAllStringSubmatch(text, -1) {
229
+ for _, match := range reRuntimeTransformAlias.FindAllStringSubmatch(text, -1) {
147
230
  seen[match[0]] = true
148
231
  }
149
232
  aliases := make([]string, 0, len(seen))
@@ -198,14 +281,21 @@ func runtimeNameOf(alias string) string {
198
281
  }
199
282
 
200
283
  func runtimeImportAlreadyExists(text string, alias string, module string) bool {
201
- return regexp.MustCompile(`(?m)^import \* as `+regexp.QuoteMeta(alias)+` from ["']`+regexp.QuoteMeta(module)+`["'];$`).MatchString(text) ||
202
- regexp.MustCompile(`(?m)^import \{[^}\n]*\bas\s+`+regexp.QuoteMeta(alias)+`\b[^}\n]*\} from ["']`+regexp.QuoteMeta(module)+`["'];$`).MatchString(text) ||
203
- regexp.MustCompile(`(?m)^const `+regexp.QuoteMeta(alias)+` = require\(["']`+regexp.QuoteMeta(module)+`["']\);$`).MatchString(text) ||
204
- regexp.MustCompile(`(?m)^const \{[^}\n]*:\s*`+regexp.QuoteMeta(alias)+`\b[^}\n]*\} = require\(["']`+regexp.QuoteMeta(module)+`["']\);$`).MatchString(text)
284
+ // Fast reject: every shape below mentions both the alias and the module, so
285
+ // skip the regex work entirely when either is absent.
286
+ if !strings.Contains(text, alias) || !strings.Contains(text, module) {
287
+ return false
288
+ }
289
+ a := regexp.QuoteMeta(alias)
290
+ m := regexp.QuoteMeta(module)
291
+ return cachedRegexp(`(?m)^import \* as `+a+` from ["']`+m+`["'];$`).MatchString(text) ||
292
+ cachedRegexp(`(?m)^import \{[^}\n]*\bas\s+`+a+`\b[^}\n]*\} from ["']`+m+`["'];$`).MatchString(text) ||
293
+ cachedRegexp(`(?m)^const `+a+` = require\(["']`+m+`["']\);$`).MatchString(text) ||
294
+ cachedRegexp(`(?m)^const \{[^}\n]*:\s*`+a+`\b[^}\n]*\} = require\(["']`+m+`["']\);$`).MatchString(text)
205
295
  }
206
296
 
207
297
  func isESModuleOutput(text string) bool {
208
- return regexp.MustCompile(`(?m)^(import\s|import\{|import\*|export\s)`).MatchString(text)
298
+ return reESModuleOutput.MatchString(text)
209
299
  }
210
300
 
211
301
  func runtimeImportInsertionIndex(text string, esModule bool) int {
@@ -36,10 +36,20 @@ func metadata_array_util_add_bool(array *[]bool, value bool) {
36
36
  *array = append(*array, value)
37
37
  }
38
38
 
39
- func metadata_type_full_name(checker *nativechecker.Checker, typ *nativechecker.Type) string {
39
+ func metadata_type_full_name(
40
+ checker *nativechecker.Checker,
41
+ typ *nativechecker.Type,
42
+ cache *schemametadata.MetadataCollection,
43
+ ) string {
40
44
  if checker == nil || typ == nil {
41
45
  return ""
42
46
  }
47
+ if cache != nil {
48
+ if cached, ok := cache.LookupTypeFullName(typ); ok {
49
+ return cached
50
+ }
51
+ }
52
+ var result string
43
53
  if typ.IsUnion() || typ.IsIntersection() {
44
54
  joiner := " | "
45
55
  if typ.IsIntersection() {
@@ -48,11 +58,16 @@ func metadata_type_full_name(checker *nativechecker.Checker, typ *nativechecker.
48
58
  children := typ.Types()
49
59
  names := make([]string, 0, len(children))
50
60
  for _, child := range children {
51
- names = append(names, metadata_type_full_name(checker, child))
61
+ names = append(names, metadata_type_full_name(checker, child, cache))
52
62
  }
53
- return strings.Join(names, joiner)
63
+ result = strings.Join(names, joiner)
64
+ } else {
65
+ result = checker.TypeToString(typ)
66
+ }
67
+ if cache != nil {
68
+ cache.StoreTypeFullName(typ, result)
54
69
  }
55
- return checker.TypeToString(typ)
70
+ return result
56
71
  }
57
72
 
58
73
  func metadata_get_type_arguments(checker *nativechecker.Checker, typ *nativechecker.Type) (output []*nativechecker.Type) {
@@ -205,7 +220,48 @@ func metadata_js_doc_parameter_name(tag *nativeast.Node) string {
205
220
  if name == nil {
206
221
  return ""
207
222
  }
208
- return name.Text()
223
+ return metadata_js_doc_name_text(name)
224
+ }
225
+
226
+ func metadata_js_doc_name_text(node *nativeast.Node) string {
227
+ if text := metadata_node_source_text(node); text != "" {
228
+ return text
229
+ }
230
+ if node == nil {
231
+ return ""
232
+ }
233
+ if node.Kind == nativeast.KindQualifiedName {
234
+ name := node.AsQualifiedName()
235
+ if name == nil {
236
+ return ""
237
+ }
238
+ left := metadata_js_doc_name_text(name.Left)
239
+ right := metadata_js_doc_name_text(name.Right)
240
+ if left == "" {
241
+ return right
242
+ }
243
+ if right == "" {
244
+ return left
245
+ }
246
+ return left + "." + right
247
+ }
248
+ return node.Text()
249
+ }
250
+
251
+ func metadata_node_source_text(node *nativeast.Node) string {
252
+ if node == nil {
253
+ return ""
254
+ }
255
+ file := nativeast.GetSourceFileOfNode(node)
256
+ if file == nil {
257
+ return ""
258
+ }
259
+ source := file.Text()
260
+ start, end := node.Pos(), node.End()
261
+ if start < 0 || end > len(source) || start >= end {
262
+ return ""
263
+ }
264
+ return strings.TrimSpace(source[start:end])
209
265
  }
210
266
 
211
267
  func metadata_js_doc_type_expression_text(tag *nativeast.Node) string {
@@ -12,7 +12,7 @@ func Iterate_metadata(props IMetadataIteratorProps) {
12
12
  if props.Type.IsTypeParameter() == true {
13
13
  if props.Errors != nil {
14
14
  *props.Errors = append(*props.Errors, MetadataFactory_IError{
15
- Name: metadata_type_full_name(props.Checker, props.Type),
15
+ Name: metadata_type_full_name(props.Checker, props.Type, props.Components),
16
16
  Explore: props.Explore,
17
17
  Messages: []string{"non-specified generic argument found."},
18
18
  })
@@ -11,7 +11,7 @@ func Iterate_metadata_map(props IMetadataIteratorProps) bool {
11
11
  return false
12
12
  }
13
13
  typ := props.Checker.GetApparentType(props.Type)
14
- name := metadata_type_full_name(props.Checker, typ)
14
+ name := metadata_type_full_name(props.Checker, typ, props.Components)
15
15
  if strings.HasPrefix(name, "Map<") == false {
16
16
  return false
17
17
  }
@@ -11,7 +11,7 @@ func Iterate_metadata_set(props IMetadataIteratorProps) bool {
11
11
  return false
12
12
  }
13
13
  typ := props.Checker.GetApparentType(props.Type)
14
- name := metadata_type_full_name(props.Checker, typ)
14
+ name := metadata_type_full_name(props.Checker, typ, props.Components)
15
15
  if strings.HasPrefix(name, "Set<") == false {
16
16
  return false
17
17
  }
@@ -454,12 +454,14 @@ func llmApplicationProgrammer_validateFunction(name string, fn *schemametadata.M
454
454
  return messages
455
455
  }
456
456
 
457
+ var llmApplicationProgrammer_namePattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
458
+
457
459
  func llmApplicationProgrammer_validateName(prefix string, name string) []string {
458
460
  output := []string{}
459
- if len(name) != 0 && regexp.MustCompile(`^[0-9]`).MatchString(name[:1]) {
461
+ if len(name) != 0 && name[0] >= '0' && name[0] <= '9' {
460
462
  output = append(output, prefix+" name cannot start with a number.")
461
463
  }
462
- if regexp.MustCompile(`^[a-zA-Z0-9_-]+$`).MatchString(name) == false {
464
+ if llmApplicationProgrammer_namePattern.MatchString(name) == false {
463
465
  output = append(output, prefix+" name must contain only alphanumeric characters, underscores, or hyphens.")
464
466
  }
465
467
  if len(name) > 64 {
@@ -26,6 +26,7 @@ type protobufMessageProgrammer_Hierarchy struct {
26
26
  Key string
27
27
  Object *schemametadata.MetadataObjectType
28
28
  Children map[string]*protobufMessageProgrammer_Hierarchy
29
+ Order []string
29
30
  }
30
31
 
31
32
  var protobufMessageProgrammer_factory = shimast.NewNodeFactory(shimast.NodeFactoryHooks{})
@@ -86,14 +87,12 @@ func protobufMessageProgrammer_emplace(hierarchies map[string]*protobufMessagePr
86
87
  *currentOrder = append(*currentOrder, access)
87
88
  }
88
89
  current = hierarchy.Children
90
+ // Track insertion order per level. Ranging the Children map directly (as the
91
+ // emit pass previously did) made nested message output non-deterministic.
92
+ currentOrder = &hierarchy.Order
89
93
  if i == len(accessors)-1 {
90
94
  hierarchy.Object = object
91
95
  }
92
- childOrder := []string{}
93
- for key := range current {
94
- childOrder = append(childOrder, key)
95
- }
96
- currentOrder = &childOrder
97
96
  }
98
97
  return order
99
98
  }
@@ -111,11 +110,7 @@ func protobufMessageProgrammer_write_hierarchy(hierarchy *protobufMessageProgram
111
110
  }
112
111
  }
113
112
  if len(hierarchy.Children) != 0 {
114
- keys := make([]string, 0, len(hierarchy.Children))
115
- for key := range hierarchy.Children {
116
- keys = append(keys, key)
117
- }
118
- for _, key := range keys {
113
+ for _, key := range hierarchy.Order {
119
114
  body := protobufMessageProgrammer_write_hierarchy(hierarchy.Children[key])
120
115
  lines := strings.Split(body, "\n")
121
116
  for i, line := range lines {
@@ -1,6 +1,8 @@
1
1
  package metadata
2
2
 
3
3
  import (
4
+ "maps"
5
+ "slices"
4
6
  "strings"
5
7
 
6
8
  nativeast "github.com/microsoft/typescript-go/shim/ast"
@@ -27,11 +29,35 @@ type MetadataCollection struct {
27
29
  tuples_order_ []*nativechecker.Type
28
30
 
29
31
  names_ map[string]map[*nativechecker.Type]string
32
+ type_full_names_ map[*nativechecker.Type]string
33
+ full_names_ map[*nativechecker.Type]string
30
34
  object_index_ int
31
35
  recursive_array_index_ int
32
36
  recursive_tuple_index_ int
33
37
  }
34
38
 
39
+ // LookupTypeFullName / StoreTypeFullName memoize the pure type -> full-name
40
+ // reconstruction (checker.TypeToString, recursing unions) per collection. The
41
+ // Set/Map iterators recompute it for every explored type just to test a name
42
+ // prefix, so the same pointer is resolved repeatedly within one analysis.
43
+ func (collection *MetadataCollection) LookupTypeFullName(typ *nativechecker.Type) (string, bool) {
44
+ if collection.type_full_names_ == nil {
45
+ return "", false
46
+ }
47
+ value, ok := collection.type_full_names_[typ]
48
+ return value, ok
49
+ }
50
+
51
+ func (collection *MetadataCollection) StoreTypeFullName(typ *nativechecker.Type, name string) {
52
+ if typ == nil {
53
+ return
54
+ }
55
+ if collection.type_full_names_ == nil {
56
+ collection.type_full_names_ = map[*nativechecker.Type]string{}
57
+ }
58
+ collection.type_full_names_[typ] = name
59
+ }
60
+
35
61
  func NewMetadataCollection(options ...*MetadataCollection_IOptions) *MetadataCollection {
36
62
  var opt *MetadataCollection_IOptions
37
63
  if len(options) > 0 {
@@ -60,37 +86,36 @@ func NewMetadataCollection(options ...*MetadataCollection_IOptions) *MetadataCol
60
86
  }
61
87
 
62
88
  func (collection *MetadataCollection) Clone() *MetadataCollection {
63
- output := NewMetadataCollection(collection.Options)
64
- for k, v := range collection.objects_ {
65
- output.objects_[k] = v
89
+ // Clone is the snapshot taken before every intersection exploration, so it is
90
+ // one of the hottest allocation sites. Pre-size each destination map (maps.Clone)
91
+ // instead of copying into the empty maps NewMetadataCollection allocates, which
92
+ // forced repeated rehashing as entries were re-inserted.
93
+ output := &MetadataCollection{
94
+ Options: collection.Options,
95
+
96
+ objects_: maps.Clone(collection.objects_),
97
+ object_unions_: make(map[string][]*MetadataObjectType, len(collection.object_unions_)),
98
+ aliases_: maps.Clone(collection.aliases_),
99
+ arrays_: maps.Clone(collection.arrays_),
100
+ tuples_: maps.Clone(collection.tuples_),
101
+
102
+ objects_order_: slices.Clone(collection.objects_order_),
103
+ object_unions_order_: slices.Clone(collection.object_unions_order_),
104
+ aliases_order_: slices.Clone(collection.aliases_order_),
105
+ arrays_order_: slices.Clone(collection.arrays_order_),
106
+ tuples_order_: slices.Clone(collection.tuples_order_),
107
+
108
+ names_: make(map[string]map[*nativechecker.Type]string, len(collection.names_)),
109
+ object_index_: collection.object_index_,
110
+ recursive_array_index_: collection.recursive_array_index_,
111
+ recursive_tuple_index_: collection.recursive_tuple_index_,
66
112
  }
67
113
  for k, v := range collection.object_unions_ {
68
- output.object_unions_[k] = append([]*MetadataObjectType{}, v...)
114
+ output.object_unions_[k] = slices.Clone(v)
69
115
  }
70
- for k, v := range collection.aliases_ {
71
- output.aliases_[k] = v
72
- }
73
- for k, v := range collection.arrays_ {
74
- output.arrays_[k] = v
75
- }
76
- for k, v := range collection.tuples_ {
77
- output.tuples_[k] = v
78
- }
79
- output.objects_order_ = append([]*nativechecker.Type{}, collection.objects_order_...)
80
- output.object_unions_order_ = append([]string{}, collection.object_unions_order_...)
81
- output.aliases_order_ = append([]*nativechecker.Type{}, collection.aliases_order_...)
82
- output.arrays_order_ = append([]*nativechecker.Type{}, collection.arrays_order_...)
83
- output.tuples_order_ = append([]*nativechecker.Type{}, collection.tuples_order_...)
84
116
  for k, v := range collection.names_ {
85
- duplicates := map[*nativechecker.Type]string{}
86
- for kt, vt := range v {
87
- duplicates[kt] = vt
88
- }
89
- output.names_[k] = duplicates
117
+ output.names_[k] = maps.Clone(v)
90
118
  }
91
- output.object_index_ = collection.object_index_
92
- output.recursive_array_index_ = collection.recursive_array_index_
93
- output.recursive_tuple_index_ = collection.recursive_tuple_index_
94
119
  return output
95
120
  }
96
121
 
@@ -145,7 +170,22 @@ func (collection *MetadataCollection) Tuples() []*MetadataTupleType {
145
170
  }
146
171
 
147
172
  func (collection *MetadataCollection) getName(checker *nativechecker.Checker, typ *nativechecker.Type) string {
148
- name := metadataCollection_getFullName(checker, typ)
173
+ // metadataCollection_getFullName (checker.TypeToString, recursing generics and
174
+ // unions) is pure for a given type pointer, but the duplicate-numbering logic
175
+ // below calls getName again for the same type. Cache only the full-name
176
+ // reconstruction; the numbering bookkeeping still runs every call so ordering
177
+ // is unaffected.
178
+ fullName, ok := collection.full_names_[typ]
179
+ if ok == false {
180
+ fullName = metadataCollection_getFullName(checker, typ)
181
+ if typ != nil {
182
+ if collection.full_names_ == nil {
183
+ collection.full_names_ = map[*nativechecker.Type]string{}
184
+ }
185
+ collection.full_names_[typ] = fullName
186
+ }
187
+ }
188
+ name := fullName
149
189
  name = strings.ToValidUTF8(name, "__")
150
190
  name = strings.ReplaceAll(name, "\uFFFD", "__")
151
191
  if collection.Options != nil && collection.Options.Replace != nil {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typia",
3
- "version": "13.0.0-dev.20260520.2",
3
+ "version": "13.0.0-dev.20260601.1",
4
4
  "description": "Superfast runtime validators with only one line",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -44,8 +44,8 @@
44
44
  "commander": "^10.0.0",
45
45
  "inquirer": "^8.2.5",
46
46
  "randexp": "^0.5.3",
47
- "@typia/interface": "^13.0.0-dev.20260520.2",
48
- "@typia/utils": "^13.0.0-dev.20260520.2"
47
+ "@typia/interface": "^13.0.0-dev.20260601.1",
48
+ "@typia/utils": "^13.0.0-dev.20260601.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "@rollup/plugin-commonjs": "^29.0.0",
@@ -54,7 +54,7 @@
54
54
  "@types/node": "^25.3.0",
55
55
  "@typescript-eslint/eslint-plugin": "^8.1.0",
56
56
  "@typescript-eslint/parser": "^8.1.0",
57
- "@typescript/native-preview": "7.0.0-dev.20260519.1",
57
+ "@typescript/native-preview": "7.0.0-dev.20260527.2",
58
58
  "chalk": "^4.0.0",
59
59
  "eslint-plugin-deprecation": "^3.0.0",
60
60
  "rimraf": "^6.1.2",
@@ -63,7 +63,7 @@
63
63
  "rollup-plugin-node-externals": "^8.1.2",
64
64
  "suppress-warnings": "^1.0.2",
65
65
  "tinyglobby": "^0.2.12",
66
- "ttsc": "^0.12.3"
66
+ "ttsc": "^0.14.1"
67
67
  },
68
68
  "sideEffects": [
69
69
  "./lib/_virtual/*.mjs",