typia 14.0.6 → 15.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 (72) hide show
  1. package/README.md +3 -0
  2. package/lib/internal/_createLlmEvaluation.d.ts +1 -0
  3. package/lib/internal/_createLlmEvaluation.js +486 -0
  4. package/lib/internal/_createLlmEvaluation.js.map +1 -0
  5. package/lib/internal/_createLlmEvaluation.mjs +382 -0
  6. package/lib/internal/_createLlmEvaluation.mjs.map +1 -0
  7. package/lib/internal/_stringLengthGte.d.ts +1 -0
  8. package/lib/internal/_stringLengthGte.js +14 -0
  9. package/lib/internal/_stringLengthGte.js.map +1 -0
  10. package/lib/internal/_stringLengthGte.mjs +11 -0
  11. package/lib/internal/_stringLengthGte.mjs.map +1 -0
  12. package/lib/internal/_stringLengthLte.d.ts +1 -0
  13. package/lib/internal/_stringLengthLte.js +14 -0
  14. package/lib/internal/_stringLengthLte.js.map +1 -0
  15. package/lib/internal/_stringLengthLte.mjs +11 -0
  16. package/lib/internal/_stringLengthLte.mjs.map +1 -0
  17. package/lib/llm.d.ts +54 -1
  18. package/lib/llm.js +5 -0
  19. package/lib/llm.js.map +1 -1
  20. package/lib/llm.mjs +6 -1
  21. package/lib/llm.mjs.map +1 -1
  22. package/lib/re-exports.d.ts +1 -1
  23. package/lib/re-exports.js.map +1 -1
  24. package/native/cmd/ttsc-typia/build.go +2 -0
  25. package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +1 -1
  26. package/native/cmd/ttsc-typia/llm_evaluation_accepts_property_probability_test.go +27 -0
  27. package/native/cmd/ttsc-typia/llm_evaluation_accepts_unselected_source_probability_test.go +26 -0
  28. package/native/cmd/ttsc-typia/llm_evaluation_complete_object_shape_test.go +96 -0
  29. package/native/cmd/ttsc-typia/llm_evaluation_ignores_declaration_probability_without_call_test.go +18 -0
  30. package/native/cmd/ttsc-typia/llm_evaluation_plan_emission_transform_test.go +90 -0
  31. package/native/cmd/ttsc-typia/llm_evaluation_program_scan_dependencies_test.go +69 -0
  32. package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_comment_test.go +44 -0
  33. package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_refinement_test.go +45 -0
  34. package/native/cmd/ttsc-typia/llm_evaluation_rejects_computed_method_probability_test.go +30 -0
  35. package/native/cmd/ttsc-typia/llm_evaluation_rejects_imported_declaration_probability_test.go +48 -0
  36. package/native/cmd/ttsc-typia/llm_evaluation_rejects_invalid_probability_test.go +199 -0
  37. package/native/cmd/ttsc-typia/llm_evaluation_rejects_refinements_test.go +94 -0
  38. package/native/cmd/ttsc-typia/llm_evaluation_rejects_undecidable_types_test.go +232 -0
  39. package/native/cmd/ttsc-typia/metadata_type_tag_sole_malformed_diagnostic_test.go +94 -0
  40. package/native/cmd/ttsc-typia/template_literal_type_tags_transform_test.go +3 -2
  41. package/native/cmd/ttsc-typia/transform.go +2 -0
  42. package/native/cmd/ttsc-typia/transform_helpers_test.go +17 -3
  43. package/native/core/context/ITypiaContext.go +4 -0
  44. package/native/core/factories/IdentifierFactory.go +10 -0
  45. package/native/core/factories/LiteralFactory.go +16 -0
  46. package/native/core/factories/MetadataCommentTagFactory.go +12 -10
  47. package/native/core/factories/MetadataTypeTagFactory.go +97 -24
  48. package/native/core/factories/MetadataTypeTagSchemaFactory.go +5 -0
  49. package/native/core/factories/internal/metadata/IMetadataIteratorProps.go +6 -2
  50. package/native/core/factories/internal/metadata/emplace_metadata_object.go +49 -15
  51. package/native/core/factories/literal_factory_null_survives_objects_test.go +43 -0
  52. package/native/core/factories/metadata_comment_tag_factory_string_length_helpers_test.go +55 -0
  53. package/native/core/factories/metadata_type_tag_factory_reports_all_invalid_candidates_test.go +73 -0
  54. package/native/core/programmers/llm/LlmApplicationProgrammer.go +1 -0
  55. package/native/core/programmers/llm/LlmEvaluationProgrammer.go +555 -0
  56. package/native/core/programmers/llm/LlmParametersProgrammer.go +6 -5
  57. package/native/core/programmers/llm/LlmReferenceDescription.go +113 -0
  58. package/native/core/programmers/llm/llm_reference_description_test.go +134 -0
  59. package/native/core/schemas/metadata/MetadataDependency.go +9 -0
  60. package/native/transform/CallExpressionTransformer.go +1 -0
  61. package/native/transform/FileTransformer.go +5 -0
  62. package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations.go +94 -0
  63. package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations_test.go +50 -0
  64. package/native/transform/features/llm/LlmEvaluationTransformer.go +59 -0
  65. package/native/transform/features/llm/LlmSchemaTransformer.go +14 -11
  66. package/native/transform/features/llm/llm_transformer_entry_coverage_test.go +29 -27
  67. package/package.json +3 -3
  68. package/src/internal/_createLlmEvaluation.ts +678 -0
  69. package/src/internal/_stringLengthGte.ts +6 -0
  70. package/src/internal/_stringLengthLte.ts +6 -0
  71. package/src/llm.ts +64 -0
  72. package/src/re-exports.ts +1 -0
@@ -155,6 +155,8 @@ func ttscTypiaTestRewriteCommonJS(t *testing.T, js string) string {
155
155
  runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_functionalTypeGuardErrorFactory")`, `require("./functional-error-stub.cjs")`)
156
156
  runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_accessExpressionAsString")`, `require("./access-expression-stub.cjs")`)
157
157
  runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_stringLength")`, `require("./string-length-stub.cjs")`)
158
+ runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_stringLengthGte")`, `require("./string-length-stub.cjs")`)
159
+ runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_stringLengthLte")`, `require("./string-length-stub.cjs")`)
158
160
  runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_jsonStringifyArray")`, `require("./json-stringify-array-stub.cjs")`)
159
161
  runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_jsonStringifyProperty")`, `require("./json-stringify-property-stub.cjs")`)
160
162
  runtimeJS = strings.ReplaceAll(runtimeJS, `require("typia/lib/internal/_jsonStringifyElement")`, `require("./json-stringify-element-stub.cjs")`)
@@ -292,14 +294,26 @@ const ttscTypiaTestRandomStringStub = `module.exports._randomString = (props) =>
292
294
  };
293
295
  `
294
296
 
295
- // A double for packages/typia/src/internal/_stringLength.ts: the string
296
- // iterator walks code points, so a surrogate pair counts once, which is what
297
- // `MinLength` and `MaxLength` now mean.
297
+ // Doubles for the string-length helpers. The string iterator walks code points,
298
+ // so a surrogate pair counts once; the comparisons stop as soon as their
299
+ // boundary determines the answer.
298
300
  const ttscTypiaTestStringLengthStub = `module.exports._stringLength = (value) => {
299
301
  let count = 0;
300
302
  for (const _ of value) ++count;
301
303
  return count;
302
304
  };
305
+ module.exports._stringLengthGte = (value, length) => {
306
+ let count = 0;
307
+ if (length <= count) return true;
308
+ for (const _ of value) if (length <= ++count) return true;
309
+ return false;
310
+ };
311
+ module.exports._stringLengthLte = (value, length) => {
312
+ let count = 0;
313
+ if (!(count <= length)) return false;
314
+ for (const _ of value) if (!(++count <= length)) return false;
315
+ return true;
316
+ };
303
317
  `
304
318
 
305
319
  const ttscTypiaTestAccessExpressionStub = `const reserved = new Set([
@@ -1,6 +1,8 @@
1
1
  package context
2
2
 
3
3
  import (
4
+ "sync"
5
+
4
6
  shimast "github.com/microsoft/typescript-go/shim/ast"
5
7
  shimchecker "github.com/microsoft/typescript-go/shim/checker"
6
8
  shimcore "github.com/microsoft/typescript-go/shim/core"
@@ -21,10 +23,12 @@ type ITypiaContext struct {
21
23
  Emit *shimprinter.EmitContext
22
24
  Importer *ImportProgrammer
23
25
  Extras ITypiaContext_Extras
26
+ Shared *sync.Map
24
27
  }
25
28
 
26
29
  type ITypiaContext_Extras struct {
27
30
  AddDiagnostic func(diag *ITypiaDiagnostic) int
31
+ Shared *sync.Map
28
32
  }
29
33
 
30
34
  type ITypiaDiagnostic struct {
@@ -94,6 +94,16 @@ func (identifierFactoryNamespace) Postfix(str string) string {
94
94
  return identifierFactory_source("[" + identifierFactory_json(str) + "]")
95
95
  }
96
96
 
97
+ // PathPostfix returns the accessor text `_accessExpressionAsString` builds at
98
+ // runtime for the same key: `.key` for an identifier, `["key"]` otherwise. It
99
+ // is the path text itself, not a JavaScript literal that spells it.
100
+ func (identifierFactoryNamespace) PathPostfix(str string) string {
101
+ if identifierFactory_variable(str) {
102
+ return "." + str
103
+ }
104
+ return "[" + identifierFactory_json(str) + "]"
105
+ }
106
+
97
107
  // identifierFactory_json renders `str` as JSON text, matching JSON.stringify so
98
108
  // that the path this lands in is the one _accessExpressionAsString builds for
99
109
  // the same key at runtime, and so JSON.parse in _createStandardSchema reads the
@@ -18,6 +18,19 @@ type literalFactoryNamespace struct{}
18
18
 
19
19
  var LiteralFactory = literalFactoryNamespace{}
20
20
 
21
+ // LiteralFactory_Null is an explicit JSON `null` value.
22
+ //
23
+ // Object writers treat a Go `nil` value as absent and skip it, which is what
24
+ // most schema builders mean by it, so a real `null`, such as the value of
25
+ // `tags.Example<null>`, needs its own marker to survive into the emitted
26
+ // literal and into encoding/json output (samchon/typia#2403).
27
+ type LiteralFactory_Null struct{}
28
+
29
+ // MarshalJSON writes the marker as JSON `null`.
30
+ func (LiteralFactory_Null) MarshalJSON() ([]byte, error) {
31
+ return []byte("null"), nil
32
+ }
33
+
21
34
  // LiteralFactory_OrderedObject is an object literal whose property order is
22
35
  // fixed by Keys rather than inferred from a Go map's (unordered) iteration or
23
36
  // from propertyRank sorting. Use it for objects whose insertion order is
@@ -71,6 +84,9 @@ func (literalFactoryNamespace) Write(input any, emit ...*shimprinter.EmitContext
71
84
  if input == nil {
72
85
  return f.NewKeywordExpression(shimast.KindNullKeyword)
73
86
  }
87
+ if _, ok := input.(LiteralFactory_Null); ok {
88
+ return f.NewKeywordExpression(shimast.KindNullKeyword)
89
+ }
74
90
  if node, ok := input.(*shimast.Node); ok {
75
91
  if node.Kind == shimast.KindArrowFunction || node.Kind == shimast.KindCallExpression || node.Kind == shimast.KindIdentifier {
76
92
  return node
@@ -216,11 +216,12 @@ func (metadataCommentTagFactoryNamespace) Get(props struct {
216
216
  // The rule: a template may name only a helper that every `typia` inside the
217
217
  // caret range already ships. `isTypeInt8` and its siblings satisfy it --
218
218
  // `@typia/interface` 13.0.0 named them and `typia` 13.0.0 shipped them. The
219
- // four this file and `Type.ts` now name do not: `_stringLength`, `_isMultipleOf`
220
- // (both first published in 13.1.19), `_isTypeInt64Bigint`, and
221
- // `_isTypeUint64Bigint` (13.1.19). They are safe only because the release that
222
- // carries them is a major, where the caret cannot reach a `typia` that predates
223
- // them. A minor or patch release of this tree reopens #2330.
219
+ // helpers this file and the tag declarations now name do not:
220
+ // `_stringLengthGte`, `_stringLengthLte`, `_isMultipleOf`,
221
+ // `_isTypeInt64Bigint`, and `_isTypeUint64Bigint`. They are safe only because
222
+ // the release that carries them is a major, where the caret cannot reach a
223
+ // `typia` that predates them. A minor or patch release of this tree reopens
224
+ // #2330.
224
225
  //
225
226
  // Nothing here can enforce that; the release version is the maintainer's, and a
226
227
  // campaign pull request never assigns one. This comment is the record.
@@ -331,10 +332,11 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
331
332
  // `$input.length` counts UTF-16 code units, while the `minLength` and
332
333
  // `maxLength` keywords these tags emit count characters, so one astral
333
334
  // character would measure 2 against a schema that measures 1.
334
- // `_stringLength` first shipped in 13.1.19; see the doc comment above.
335
+ // The comparison helpers keep that code-point walk but stop when the
336
+ // declared boundary already determines the answer.
335
337
  return metadataCommentTagFactory_TagRecord{"string": {
336
- {Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: props.Value + " <= $importInternal(\"_stringLength\")($input)", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}},
337
- {Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLength\")($input) <= " + props.Value, Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}},
338
+ {Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: "$importInternal(\"_stringLengthGte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}},
339
+ {Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLengthLte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}},
338
340
  }}
339
341
  },
340
342
  "minLength": func(props struct {
@@ -342,14 +344,14 @@ var metadataCommentTagFactory_PARSER = map[string]metadataCommentTagFactory_pars
342
344
  Value string
343
345
  }) metadataCommentTagFactory_TagRecord {
344
346
  value := metadataCommentTagFactory_parse_number(props)
345
- return metadataCommentTagFactory_TagRecord{"string": {{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: props.Value + " <= $importInternal(\"_stringLength\")($input)", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}}}}
347
+ return metadataCommentTagFactory_TagRecord{"string": {{Name: "MinLength<" + props.Value + ">", Target: "string", Kind: "minLength", Value: value, Validate: "$importInternal(\"_stringLengthGte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("minLength"), Schema: map[string]any{"minLength": value}}}}
346
348
  },
347
349
  "maxLength": func(props struct {
348
350
  Report func(msg string) any
349
351
  Value string
350
352
  }) metadataCommentTagFactory_TagRecord {
351
353
  value := metadataCommentTagFactory_parse_number(props)
352
- return metadataCommentTagFactory_TagRecord{"string": {{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLength\")($input) <= " + props.Value, Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}}}}
354
+ return metadataCommentTagFactory_TagRecord{"string": {{Name: "MaxLength<" + props.Value + ">", Target: "string", Kind: "maxLength", Value: value, Validate: "$importInternal(\"_stringLengthLte\")($input, " + props.Value + ")", Exclusive: metadataCommentTagFactory_exclusive("maxLength"), Schema: map[string]any{"maxLength": value}}}}
353
355
  },
354
356
  }
355
357
 
@@ -141,7 +141,9 @@ func (metadataTypeTagFactoryNamespace) Analyze(props struct {
141
141
  }
142
142
  }
143
143
  if len(filtered) == 0 {
144
- return []schemametadata.IMetadataTypeTag{}
144
+ // every tag object was malformed: their messages must still be reported,
145
+ // exactly as when a valid tag accompanies them (samchon/typia#2400)
146
+ return metadataTypeTagFactory_flush(props.Errors, props.Type, props.Objects, props.Explore, messages, []schemametadata.IMetadataTypeTag{})
145
147
  }
146
148
 
147
149
  tagList := []*metadataTypeTagFactory_ITypeTag{}
@@ -193,21 +195,35 @@ func (metadataTypeTagFactoryNamespace) Analyze(props struct {
193
195
  Tags: output,
194
196
  })
195
197
 
196
- if len(messages) > 0 {
197
- if props.Errors != nil {
198
- names := []string{props.Type}
199
- for _, object := range props.Objects {
200
- names = append(names, object.Name)
201
- }
202
- metadataTypeTagFactory_append_error(props.Errors, MetadataFactory_IError{
203
- Name: strings.Join(names, " & "),
204
- Explore: props.Explore,
205
- Messages: messages,
206
- })
198
+ return metadataTypeTagFactory_flush(props.Errors, props.Type, props.Objects, props.Explore, messages, output)
199
+ }
200
+
201
+ // metadataTypeTagFactory_flush reports the collected tag messages as one
202
+ // error named after the tagged type, and then yields no tags; without
203
+ // messages it yields the analyzed tags.
204
+ func metadataTypeTagFactory_flush(
205
+ errors *[]MetadataFactory_IError,
206
+ typ string,
207
+ objects []*schemametadata.MetadataObjectType,
208
+ explore MetadataFactory_IExplore,
209
+ messages []string,
210
+ output []schemametadata.IMetadataTypeTag,
211
+ ) []schemametadata.IMetadataTypeTag {
212
+ if len(messages) == 0 {
213
+ return output
214
+ }
215
+ if errors != nil {
216
+ names := []string{typ}
217
+ for _, object := range objects {
218
+ names = append(names, object.Name)
207
219
  }
208
- return []schemametadata.IMetadataTypeTag{}
220
+ metadataTypeTagFactory_append_error(errors, MetadataFactory_IError{
221
+ Name: strings.Join(names, " & "),
222
+ Explore: explore,
223
+ Messages: messages,
224
+ })
209
225
  }
210
- return output
226
+ return []schemametadata.IMetadataTypeTag{}
211
227
  }
212
228
 
213
229
  func (metadataTypeTagFactoryNamespace) Validate(props struct {
@@ -315,12 +331,18 @@ func metadataTypeTagFactory_validate_property(props struct {
315
331
  Message string
316
332
  }{Property: property, Message: "must be a string literal type"})
317
333
  }
318
- if props.Key == "value" && !((props.Value.Size() == 0 && props.Value.IsRequired() == false) ||
319
- (props.Value.Size() == 1 && (len(props.Value.Objects) == 1 || len(props.Value.Constants) == 1))) {
320
- return props.Report(struct {
321
- Property *string
322
- Message string
323
- }{Property: property, Message: "must be a literal type or undefined value"})
334
+ // the value must be one concrete literal shape that the tag schema factory
335
+ // can materialize: a constant, `null`, a tuple of such values, or an object
336
+ // whose content reaches the schema through `schema`. `tags.Default` reads
337
+ // the value back, while `tags.Example` and `tags.Examples` only need the
338
+ // shape to be accepted (samchon/typia#2400, samchon/typia#2403)
339
+ if props.Key == "value" {
340
+ if _, ok := metadataTypeTagFactory_get_value(props.Value); ok == false {
341
+ return props.Report(struct {
342
+ Property *string
343
+ Message string
344
+ }{Property: property, Message: "must be a literal, literal tuple, object, or undefined type"})
345
+ }
324
346
  }
325
347
  if props.Key == "exclusive" {
326
348
  return metadataTypeTagFactory_get_exclusive(struct {
@@ -344,11 +366,13 @@ func metadataTypeTagFactory_validate_property(props struct {
344
366
  }
345
367
  target := []string{}
346
368
  if len(props.Value.Objects) == 0 {
347
- targetProperty := "target"
369
+ // `validate` itself is malformed here, e.g. a template literal built from
370
+ // a non-literal tag value, so name it rather than `target`
371
+ validateProperty := "validate"
348
372
  return props.Report(struct {
349
373
  Property *string
350
374
  Message string
351
- }{Property: &targetProperty, Message: "must be one of 'boolean', 'bigint', 'number', 'string', 'array', 'object"})
375
+ }{Property: &validateProperty, Message: "must be a string literal, or an object whose keys are 'boolean', 'bigint', 'number', 'string', 'array', or 'object'"})
352
376
  }
353
377
  for _, property := range props.Value.Objects[0].Type.Properties {
354
378
  if lit := property.Key.GetSoleLiteral(); lit != nil {
@@ -415,8 +439,8 @@ func metadataTypeTagFactory_create_metadata_type_tag(props struct {
415
439
  kind := fmt.Sprint(kindProperty.Value.Constants[0].Values[0].Value)
416
440
 
417
441
  var value any
418
- if valueProperty := find("value"); valueProperty != nil && len(valueProperty.Value.Constants) != 0 && len(valueProperty.Value.Constants[0].Values) != 0 {
419
- value = valueProperty.Value.Constants[0].Values[0].Value
442
+ if valueProperty := find("value"); valueProperty != nil {
443
+ value, _ = metadataTypeTagFactory_get_value(valueProperty.Value)
420
444
  }
421
445
  exclusive := metadataTypeTagFactory_get_exclusive(struct {
422
446
  Report func(struct {
@@ -490,6 +514,55 @@ func metadataTypeTagFactory_create_metadata_type_tag(props struct {
490
514
  }
491
515
  }
492
516
 
517
+ // metadataTypeTagFactory_get_value reads a tag's `value` as one concrete
518
+ // literal value, and reports whether the metadata names one at all. The
519
+ // accepted shapes mirror what metadataTypeTagSchemaFactory_iterate can
520
+ // materialize: `undefined`, `null`, a single constant, a fixed tuple of such
521
+ // values, or an object. `null` and an object yield a Go nil value; a tuple
522
+ // yields its members in order.
523
+ func metadataTypeTagFactory_get_value(metadata *schemametadata.MetadataSchema) (any, bool) {
524
+ if metadata == nil {
525
+ return nil, false
526
+ }
527
+ if metadata.Size() == 0 {
528
+ // `null` is one literal value; a missing value is the optional `undefined`
529
+ return nil, metadata.Nullable || metadata.IsRequired() == false
530
+ }
531
+ if metadata.Size() != 1 || metadata.Nullable {
532
+ return nil, false
533
+ }
534
+ if len(metadata.Constants) == 1 && len(metadata.Constants[0].Values) == 1 {
535
+ return metadata.Constants[0].Values[0].Value, true
536
+ }
537
+ if metadata.Any || len(metadata.Arrays) != 0 || len(metadata.Atomics) != 0 || len(metadata.Natives) != 0 || len(metadata.Functions) != 0 {
538
+ return nil, false
539
+ }
540
+ if len(metadata.Tuples) == 1 {
541
+ tuple := metadata.Tuples[0].Type
542
+ if tuple.Recursive || tuple.IsRest() {
543
+ return nil, false
544
+ }
545
+ output := make([]any, 0, len(tuple.Elements))
546
+ for _, elem := range tuple.Elements {
547
+ if elem.IsRequired() == false {
548
+ return nil, false
549
+ }
550
+ value, ok := metadataTypeTagFactory_get_value(elem)
551
+ if ok == false {
552
+ return nil, false
553
+ }
554
+ output = append(output, value)
555
+ }
556
+ return output, true
557
+ }
558
+ if len(metadata.Objects) == 1 {
559
+ // Object-valued metadata predates tuple support. Its concrete value is
560
+ // carried by the tag's schema object rather than IMetadataTypeTag.Value.
561
+ return nil, true
562
+ }
563
+ return nil, false
564
+ }
565
+
493
566
  func metadataTypeTagFactory_get_exclusive(props struct {
494
567
  Report func(struct {
495
568
  Property *string
@@ -33,6 +33,11 @@ func (metadataTypeTagSchemaFactoryNamespace) Object(props struct {
33
33
  metadata: property.Value,
34
34
  })
35
35
  if ok {
36
+ // a property literally typed `null` keeps its value; a Go nil would read
37
+ // as absent to every object writer (samchon/typia#2403)
38
+ if value == nil && property.Value.Nullable && property.Value.Size() == 0 {
39
+ value = LiteralFactory_Null{}
40
+ }
36
41
  output[*key] = value
37
42
  }
38
43
  }
@@ -14,8 +14,12 @@ type MetadataFactory_IOptions struct {
14
14
  // method) into the collected properties as function-typed values, without
15
15
  // switching on full Functional analysis. Used by the compare programmers to
16
16
  // detect user-defined comparison methods.
17
- Methods bool
18
- Validate func(props struct {
17
+ Methods bool
18
+ // StrictObjectMembers is for consumers whose emitted value promises to
19
+ // satisfy the entire input type. Include accessors and methods in the
20
+ // structural shape and diagnose members that have no JSON representation.
21
+ StrictObjectMembers bool
22
+ Validate func(props struct {
19
23
  Metadata *schemametadata.MetadataSchema
20
24
  Explore MetadataFactory_IExplore
21
25
  Top *schemametadata.MetadataSchema
@@ -89,8 +89,40 @@ func Emplace_metadata_object(props IMetadataIteratorProps) *schemametadata.Metad
89
89
  }
90
90
 
91
91
  isClass := props.Type != nil && props.Type.IsClass()
92
- isProperty := emplace_metadata_object_significant(props.Options.Functional, props.Options.Methods)
92
+ isProperty := emplace_metadata_object_significant(props.Options.Functional, props.Options.Methods, props.Options.StrictObjectMembers)
93
+ rejectMember := func(name string, message string) {
94
+ if props.Options.StrictObjectMembers == false || props.Errors == nil {
95
+ return
96
+ }
97
+ explore := props.Explore
98
+ explore.Top = false
99
+ explore.Object = obj
100
+ explore.Property = name
101
+ *props.Errors = append(*props.Errors, MetadataFactory_IError{
102
+ Name: obj.Name,
103
+ Explore: explore,
104
+ Messages: []string{message},
105
+ })
106
+ }
107
+ if props.Options.StrictObjectMembers && props.Checker != nil && props.Type != nil {
108
+ if len(props.Checker.GetSignaturesOfType(props.Type, nativechecker.SignatureKindCall)) != 0 {
109
+ rejectMember("[call]", "LLM evaluation does not support callable object types.")
110
+ }
111
+ if len(props.Checker.GetSignaturesOfType(props.Type, nativechecker.SignatureKindConstruct)) != 0 {
112
+ rejectMember("[construct]", "LLM evaluation does not support constructable object types.")
113
+ }
114
+ }
93
115
  pred := func(symbol *nativeast.Symbol, node *nativeast.Node) bool {
116
+ name := "<anonymous>"
117
+ if symbol != nil {
118
+ name = symbol.Name
119
+ }
120
+ // Diagnose ES #private before the symbol-key filter so JEV reports the
121
+ // nominal member rather than its compiler-generated mangled name.
122
+ if node != nil && (isClass || props.Options.StrictObjectMembers) && node.Name() != nil && node.Name().Kind == nativeast.KindPrivateIdentifier {
123
+ rejectMember(name, "LLM evaluation does not support private class members.")
124
+ return false
125
+ }
94
126
  // A `symbol`-keyed member (`[uniqueSymbol]: T`, `[Symbol.toStringTag]: T`,
95
127
  // ...) is not a string / number key: it is unreachable through the string
96
128
  // index access typia emits, `JSON.stringify` ignores it, and JSON Schema has
@@ -104,25 +136,21 @@ func Emplace_metadata_object(props IMetadataIteratorProps) *schemametadata.Metad
104
136
  // SYMBOL rather than a declaration node so a late-bound member with no
105
137
  // reachable declaration is excluded too.
106
138
  if symbol != nil && iterate_metadata_intersection_is_symbol_name(symbol.Name) {
139
+ rejectMember(name, "LLM evaluation does not support symbol-keyed properties.")
107
140
  return false
108
141
  }
109
142
  if node == nil {
110
143
  return true
111
144
  }
112
- if isClass && node.ModifierFlags()&(nativeast.ModifierFlagsPrivate|nativeast.ModifierFlagsProtected) != 0 {
145
+ if (isClass || props.Options.StrictObjectMembers) && node.ModifierFlags()&(nativeast.ModifierFlagsPrivate|nativeast.ModifierFlagsProtected) != 0 {
146
+ rejectMember(name, "LLM evaluation does not support private or protected class members.")
113
147
  return false
114
148
  }
115
- // An ES `#private` member (field, method, or accessor) carries no
116
- // Private/Protected modifier — its identity is a KindPrivateIdentifier name.
117
- // It is installed only by the constructor and is unreachable via index
118
- // access, so it is not part of the structural shape; exclude it exactly as a
119
- // `private`/`protected` keyword member (see ClassNonPublic). ApparentProperties
120
- // otherwise surfaces it as a mangled key that no runtime object carries, so
121
- // every real instance would fail its own guard (#2213).
122
- if isClass && node.Name() != nil && node.Name().Kind == nativeast.KindPrivateIdentifier {
149
+ if isProperty(node) == false {
150
+ rejectMember(name, "LLM evaluation does not support this object member.")
123
151
  return false
124
152
  }
125
- return isProperty(node)
153
+ return true
126
154
  }
127
155
 
128
156
  insert := func(next emplace_metadata_object_insert) *schemametadata.MetadataProperty {
@@ -135,7 +163,9 @@ func Emplace_metadata_object(props IMetadataIteratorProps) *schemametadata.Metad
135
163
  }
136
164
 
137
165
  for _, symbol := range props.Components.ApparentProperties(props.Checker, props.Type) {
138
- if metadata_is_internal(symbol) {
166
+ // A complete-shape decoder must see even @internal members so its
167
+ // programmer can reject them; the ordinary structural consumers omit them.
168
+ if metadata_is_internal(symbol) && props.Options.StrictObjectMembers == false {
139
169
  continue
140
170
  }
141
171
  var node *nativeast.Node
@@ -188,6 +218,7 @@ func Emplace_metadata_object(props IMetadataIteratorProps) *schemametadata.Metad
188
218
  // intersections so every symbol-only form stays outside typia's structural
189
219
  // JSON shape, just like an explicit symbol-keyed member (#2240).
190
220
  if emplace_metadata_object_is_symbol_index_key(index.KeyType()) {
221
+ rejectMember("[symbol]", "LLM evaluation does not support symbol-keyed properties.")
191
222
  continue
192
223
  }
193
224
  analyzer := func(typ *nativechecker.Type) func(any) *schemametadata.MetadataSchema {
@@ -546,7 +577,7 @@ func emplace_metadata_object_intersection_append(
546
577
  return false
547
578
  }
548
579
  for _, symbol := range props.Components.ApparentProperties(props.Checker, child) {
549
- if metadata_is_internal(symbol) {
580
+ if metadata_is_internal(symbol) && props.Options.StrictObjectMembers == false {
550
581
  continue
551
582
  }
552
583
  var node *nativeast.Node
@@ -652,10 +683,10 @@ func emplace_metadata_object_clone_property(property *schemametadata.MetadataPro
652
683
  })
653
684
  }
654
685
 
655
- func emplace_metadata_object_significant(functional bool, methods bool) func(node *nativeast.Node) bool {
686
+ func emplace_metadata_object_significant(functional bool, methods bool, strict bool) func(node *nativeast.Node) bool {
656
687
  if functional {
657
688
  return func(node *nativeast.Node) bool {
658
- return node.Kind != nativeast.KindGetAccessor && node.Kind != nativeast.KindSetAccessor
689
+ return strict || (node.Kind != nativeast.KindGetAccessor && node.Kind != nativeast.KindSetAccessor)
659
690
  }
660
691
  }
661
692
  return func(node *nativeast.Node) bool {
@@ -670,6 +701,9 @@ func emplace_metadata_object_significant(functional bool, methods bool) func(nod
670
701
  case nativeast.KindMethodDeclaration,
671
702
  nativeast.KindMethodSignature:
672
703
  return methods
704
+ case nativeast.KindGetAccessor,
705
+ nativeast.KindSetAccessor:
706
+ return strict
673
707
  default:
674
708
  return false
675
709
  }
@@ -0,0 +1,43 @@
1
+ package factories
2
+
3
+ import (
4
+ "encoding/json"
5
+ "testing"
6
+ )
7
+
8
+ // TestLiteralFactoryNullSurvivesObjects verifies an explicit JSON null is kept
9
+ // where a Go nil is dropped.
10
+ //
11
+ // Object writers treat a nil value as absent, so a tag schema holding a real
12
+ // `null` (`tags.Example<null>`) lost it on emission (samchon/typia#2403). The
13
+ // LiteralFactory_Null marker must survive encoding/json, which `typia.reflect`
14
+ // metadata goes through, including inside an ordered object, and must not be
15
+ // classified as nil-like, while a plain nil keeps meaning absent. The printed
16
+ // literal path is covered by `test_json_schema_example_tuple_and_null`.
17
+ //
18
+ // 1. Marshal a map and an ordered object holding the marker and a plain nil.
19
+ // 2. Assert the marker is written as null and the plain nil is omitted where
20
+ // the writer omits it.
21
+ // 3. Assert the marker is not nil-like.
22
+ func TestLiteralFactoryNullSurvivesObjects(t *testing.T) {
23
+ encoded, err := json.Marshal(map[string]any{"example": LiteralFactory_Null{}})
24
+ if err != nil || string(encoded) != `{"example":null}` {
25
+ t.Fatalf("map marker should marshal as null, got %s (%v)", encoded, err)
26
+ }
27
+ ordered, err := json.Marshal(LiteralFactory_OrderedObject{
28
+ Keys: []string{"kept", "absent"},
29
+ Values: map[string]any{
30
+ "kept": LiteralFactory_Null{},
31
+ "absent": nil,
32
+ },
33
+ })
34
+ if err != nil || string(ordered) != `{"kept":null}` {
35
+ t.Fatalf("ordered object should keep the marker and drop nil, got %s (%v)", ordered, err)
36
+ }
37
+ if literalFactory_isNilLike(LiteralFactory_Null{}) {
38
+ t.Fatal("the null marker must not be nil-like")
39
+ }
40
+ if literalFactory_isNilLike(nil) == false {
41
+ t.Fatal("a plain nil must stay nil-like")
42
+ }
43
+ }
@@ -0,0 +1,55 @@
1
+ package factories
2
+
3
+ import (
4
+ "testing"
5
+
6
+ schemametadata "github.com/samchon/typia/packages/typia/native/core/schemas/metadata"
7
+ )
8
+
9
+ // TestMetadataCommentTagFactoryStringLengthHelpers verifies every JSDoc length
10
+ // spelling emits the same short-circuiting predicates as the public type tags.
11
+ //
12
+ // The type-tag declarations and JSDoc parser are independent metadata sources.
13
+ // Updating only one would preserve schema output while making runtime behavior
14
+ // and helper imports depend on which spelling a user chose.
15
+ //
16
+ // 1. Parse @length and require both comparison helpers at the same boundary.
17
+ // 2. Parse @minLength and @maxLength and require their corresponding helper.
18
+ func TestMetadataCommentTagFactoryStringLengthHelpers(t *testing.T) {
19
+ parse := func(name string, value string) []schemametadata.IMetadataTypeTag {
20
+ t.Helper()
21
+ reports := []string{}
22
+ record := metadataCommentTagFactory_parse(struct {
23
+ Report func(msg string) any
24
+ Tag schemametadata.IJsDocTagInfo
25
+ }{
26
+ Report: func(msg string) any {
27
+ reports = append(reports, msg)
28
+ return nil
29
+ },
30
+ Tag: schemametadata.IJsDocTagInfo{
31
+ Name: name,
32
+ Text: []schemametadata.IJsDocTagInfo_IText{{Text: value}},
33
+ },
34
+ })
35
+ if len(reports) != 0 {
36
+ t.Fatalf("@%s %s reported errors: %#v", name, value, reports)
37
+ }
38
+ return record["string"]
39
+ }
40
+
41
+ length := parse("length", "3")
42
+ if len(length) != 2 ||
43
+ length[0].Validate != `$importInternal("_stringLengthGte")($input, 3)` ||
44
+ length[1].Validate != `$importInternal("_stringLengthLte")($input, 3)` {
45
+ t.Fatalf("@length must emit both comparison helpers: %#v", length)
46
+ }
47
+ minimum := parse("minLength", "2")
48
+ if len(minimum) != 1 || minimum[0].Validate != `$importInternal("_stringLengthGte")($input, 2)` {
49
+ t.Fatalf("@minLength must emit the lower-bound helper: %#v", minimum)
50
+ }
51
+ maximum := parse("maxLength", "4")
52
+ if len(maximum) != 1 || maximum[0].Validate != `$importInternal("_stringLengthLte")($input, 4)` {
53
+ t.Fatalf("@maxLength must emit the upper-bound helper: %#v", maximum)
54
+ }
55
+ }