morphir-python 0.0.1__py3-none-any.whl

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 (99) hide show
  1. morphir/__init__.py +14 -0
  2. morphir/ir/__init__.py +110 -0
  3. morphir/ir/access_controlled.py +15 -0
  4. morphir/ir/decorations.py +19 -0
  5. morphir/ir/diagnostics.py +105 -0
  6. morphir/ir/distribution.py +21 -0
  7. morphir/ir/document.py +37 -0
  8. morphir/ir/documented.py +11 -0
  9. morphir/ir/format_version.py +483 -0
  10. morphir/ir/fqname.py +9 -0
  11. morphir/ir/json.py +11 -0
  12. morphir/ir/literal.py +25 -0
  13. morphir/ir/meta.py +11 -0
  14. morphir/ir/migrate.py +1267 -0
  15. morphir/ir/module.py +17 -0
  16. morphir/ir/name.py +27 -0
  17. morphir/ir/package.py +15 -0
  18. morphir/ir/path.py +25 -0
  19. morphir/ir/qname.py +9 -0
  20. morphir/ir/ref.py +17 -0
  21. morphir/ir/type.py +37 -0
  22. morphir/ir/type_constraints.py +33 -0
  23. morphir/ir/type_def.py +19 -0
  24. morphir/ir/type_spec.py +25 -0
  25. morphir/ir/v3/__init__.py +64 -0
  26. morphir/ir/v3/_legacy_value.py +201 -0
  27. morphir/ir/v3/access_controlled.py +31 -0
  28. morphir/ir/v3/codec.py +1275 -0
  29. morphir/ir/v3/codec_v1.py +491 -0
  30. morphir/ir/v3/codec_v2.py +187 -0
  31. morphir/ir/v3/distribution.py +42 -0
  32. morphir/ir/v3/documented.py +13 -0
  33. morphir/ir/v3/fqname.py +30 -0
  34. morphir/ir/v3/literal.py +46 -0
  35. morphir/ir/v3/module.py +32 -0
  36. morphir/ir/v3/name.py +42 -0
  37. morphir/ir/v3/package.py +23 -0
  38. morphir/ir/v3/path.py +29 -0
  39. morphir/ir/v3/qname.py +16 -0
  40. morphir/ir/v3/type.py +126 -0
  41. morphir/ir/v3/value.py +235 -0
  42. morphir/ir/v4/__init__.py +160 -0
  43. morphir/ir/v4/access_controlled.py +17 -0
  44. morphir/ir/v4/annotation.py +75 -0
  45. morphir/ir/v4/codec.py +2161 -0
  46. morphir/ir/v4/decorations.py +88 -0
  47. morphir/ir/v4/distribution.py +56 -0
  48. morphir/ir/v4/document.py +69 -0
  49. morphir/ir/v4/documented.py +10 -0
  50. morphir/ir/v4/fqname.py +69 -0
  51. morphir/ir/v4/incompleteness.py +78 -0
  52. morphir/ir/v4/json.py +103 -0
  53. morphir/ir/v4/literal.py +51 -0
  54. morphir/ir/v4/meta.py +27 -0
  55. morphir/ir/v4/module.py +30 -0
  56. morphir/ir/v4/name.py +69 -0
  57. morphir/ir/v4/package.py +19 -0
  58. morphir/ir/v4/path.py +50 -0
  59. morphir/ir/v4/qname.py +34 -0
  60. morphir/ir/v4/ref.py +35 -0
  61. morphir/ir/v4/type.py +140 -0
  62. morphir/ir/v4/type_constraints.py +91 -0
  63. morphir/ir/v4/type_def.py +38 -0
  64. morphir/ir/v4/type_spec.py +64 -0
  65. morphir/ir/v4/value.py +480 -0
  66. morphir/ir/value.py +81 -0
  67. morphir/py.typed +0 -0
  68. morphir/sdk/__init__.py +110 -0
  69. morphir/sdk/_compare.py +187 -0
  70. morphir/sdk/aggregate.py +421 -0
  71. morphir/sdk/basics.py +504 -0
  72. morphir/sdk/char.py +110 -0
  73. morphir/sdk/decimal.py +311 -0
  74. morphir/sdk/dict.py +336 -0
  75. morphir/sdk/instant.py +118 -0
  76. morphir/sdk/int.py +86 -0
  77. morphir/sdk/key.py +522 -0
  78. morphir/sdk/list.py +356 -0
  79. morphir/sdk/local_date.py +436 -0
  80. morphir/sdk/local_time.py +267 -0
  81. morphir/sdk/maybe.py +135 -0
  82. morphir/sdk/number.py +237 -0
  83. morphir/sdk/regex.py +287 -0
  84. morphir/sdk/result.py +162 -0
  85. morphir/sdk/result_list.py +169 -0
  86. morphir/sdk/rule.py +103 -0
  87. morphir/sdk/set.py +214 -0
  88. morphir/sdk/stateful_app.py +49 -0
  89. morphir/sdk/string.py +427 -0
  90. morphir/sdk/tuple.py +43 -0
  91. morphir/sdk/uuid.py +209 -0
  92. morphir_python-0.0.1.dist-info/METADATA +269 -0
  93. morphir_python-0.0.1.dist-info/RECORD +99 -0
  94. morphir_python-0.0.1.dist-info/WHEEL +4 -0
  95. morphir_python-0.0.1.dist-info/entry_points.txt +2 -0
  96. morphir_python-0.0.1.dist-info/licenses/LICENSE +201 -0
  97. morphir_tools/__init__.py +13 -0
  98. morphir_tools/cli.py +223 -0
  99. morphir_tools/py.typed +0 -0
morphir/ir/migrate.py ADDED
@@ -0,0 +1,1267 @@
1
+ """Migrations between Morphir IR format 3 and IR v4.
2
+
3
+ `v3_to_v4` always succeeds for a valid v3 Library or Specs. `v4_to_v3` collects
4
+ every construct format 3 cannot represent in one pass and fails with all of them,
5
+ so users fix every problem at once. A v4 Specs becomes a v3 Specs (format 3.1.0);
6
+ a v4 Application has no v3 form. Nothing is invented or dropped, except:
7
+
8
+ - the package release version, which v3 cannot hold (``package_version_dropped``);
9
+ - value source locations, only when ``drop_source_locations=True``
10
+ (``source_location_dropped``);
11
+ - value-definition input attributes, only when ``drop_input_attributes=True`` or
12
+ through `drop_input_attributes` (``input_attributes_dropped``). v4 JSON spells
13
+ an input as a bare type, so an input whose attributes hold more than its
14
+ declared type cannot be encoded without this drop.
15
+
16
+ The mapping follows morphir-moonbit ``migration/v3/migration.mbt``, adjusted for
17
+ the Python v4 model. Diagnostic codes are defined in `morphir.ir.diagnostics` and
18
+ may be renamed when finos bead ``morphir-diwy`` settles the ecosystem rules.
19
+
20
+ Example:
21
+ >>> from morphir.ir import v3
22
+ >>> lib = v3.Library(v3.Path(()), v3.package.Definition())
23
+ >>> result = v3_to_v4(lib, package_version="1.2.0")
24
+ >>> result.value.package.version
25
+ '1.2.0'
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import dataclasses
31
+ import re
32
+ from dataclasses import dataclass, field
33
+ from typing import cast, overload
34
+
35
+ from . import diagnostics as d
36
+ from .diagnostics import Diagnostic, Location
37
+ from .v3 import access_controlled as ac3
38
+ from .v3 import distribution as dist3
39
+ from .v3 import documented as doc3
40
+ from .v3 import fqname as fq3
41
+ from .v3 import literal as lit3
42
+ from .v3 import module as mod3
43
+ from .v3 import name as name3
44
+ from .v3 import package as pkg3
45
+ from .v3 import path as path3
46
+ from .v3 import type as t3
47
+ from .v3 import value as v3
48
+ from .v4 import access_controlled as ac4
49
+ from .v4 import annotation as ann4
50
+ from .v4 import distribution as dist4
51
+ from .v4 import documented as doc4
52
+ from .v4 import fqname as fq4
53
+ from .v4 import literal as lit4
54
+ from .v4 import module as mod4
55
+ from .v4 import name as name4
56
+ from .v4 import package as pkg4
57
+ from .v4 import path as path4
58
+ from .v4 import type as t4
59
+ from .v4 import type_def as td4
60
+ from .v4 import type_spec as ts4
61
+ from .v4 import value as v4
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class Ok[T]:
66
+ """A successful migration, with any warnings about dropped information."""
67
+
68
+ value: T
69
+ warnings: tuple[Diagnostic, ...] = ()
70
+
71
+
72
+ @dataclass(frozen=True)
73
+ class Err:
74
+ """A failed migration holding every diagnostic found."""
75
+
76
+ diagnostics: tuple[Diagnostic, ...]
77
+
78
+
79
+ type MigrationResult[T] = Ok[T] | Err
80
+
81
+
82
+ # --- v3 -> v4 ---
83
+
84
+
85
+ def _name_up(n: name3.Name) -> name4.Name:
86
+ return name4.Name(tuple(n))
87
+
88
+
89
+ def _path_up(p: path3.Path) -> path4.Path:
90
+ return path4.Path(tuple(_name_up(n) for n in p))
91
+
92
+
93
+ def _fqname_up(f: fq3.FQName) -> fq4.FQName:
94
+ return fq4.FQName(
95
+ _path_up(f.package_path), _path_up(f.module_path), _name_up(f.local_name)
96
+ )
97
+
98
+
99
+ def _access_up[A, B](
100
+ item: ac3.AccessControlled[A], value: B
101
+ ) -> ac4.AccessControlled[B]:
102
+ if item.access is ac3.Access.PUBLIC:
103
+ return ac4.Public(value)
104
+ return ac4.Private(value)
105
+
106
+
107
+ def _literal_up(literal: lit3.Literal) -> lit4.Literal:
108
+ match literal:
109
+ case lit3.BoolLiteral(b):
110
+ return lit4.BoolLiteral(b)
111
+ case lit3.CharLiteral(c):
112
+ return lit4.CharLiteral(c)
113
+ case lit3.StringLiteral(s):
114
+ return lit4.StringLiteral(s)
115
+ case lit3.WholeNumberLiteral(i):
116
+ return lit4.IntegerLiteral(i)
117
+ case lit3.FloatLiteral(f):
118
+ return lit4.FloatLiteral(f)
119
+ case lit3.DecimalLiteral(x):
120
+ return lit4.DecimalLiteral(x)
121
+
122
+
123
+ def _type_up(tpe: t3.Type[None]) -> t4.Type:
124
+ a = t4.TypeAttributes()
125
+ match tpe:
126
+ case t3.Variable(_, n):
127
+ return t4.Variable(a, _name_up(n))
128
+ case t3.Reference(_, f, args):
129
+ return t4.Reference(a, _fqname_up(f), [_type_up(x) for x in args])
130
+ case t3.Tuple(_, elements):
131
+ return t4.Tuple(a, [_type_up(x) for x in elements])
132
+ case t3.Record(_, fields):
133
+ return t4.Record(
134
+ a, [t4.Field(_name_up(f.name), _type_up(f.tpe)) for f in fields]
135
+ )
136
+ case t3.ExtensibleRecord(_, var, fields):
137
+ return t4.ExtensibleRecord(
138
+ a,
139
+ _name_up(var),
140
+ [t4.Field(_name_up(f.name), _type_up(f.tpe)) for f in fields],
141
+ )
142
+ case t3.Function(_, arg, ret):
143
+ return t4.Function(a, _type_up(arg), _type_up(ret))
144
+ case t3.Unit(_):
145
+ return t4.Unit(a)
146
+
147
+
148
+ def _ctors_up(ctors: t3.Constructors[None]) -> ts4.Constructors:
149
+ return {
150
+ _name_up(n): [(_name_up(an), _type_up(at)) for an, at in args]
151
+ for n, args in ctors.items()
152
+ }
153
+
154
+
155
+ def _params_up(params: tuple[name3.Name, ...]) -> list[name4.Name]:
156
+ return [_name_up(p) for p in params]
157
+
158
+
159
+ def _type_spec_up(spec: t3.Specification[None]) -> ts4.Specification:
160
+ match spec:
161
+ case t3.TypeAliasSpecification(params, tpe):
162
+ return ts4.TypeAliasSpecification(_params_up(params), _type_up(tpe))
163
+ case t3.OpaqueTypeSpecification(params):
164
+ return ts4.OpaqueTypeSpecification(_params_up(params))
165
+ case t3.CustomTypeSpecification(params, ctors):
166
+ return ts4.CustomTypeSpecification(_params_up(params), _ctors_up(ctors))
167
+ case t3.DerivedTypeSpecification(params, base, from_base, to_base):
168
+ return ts4.DerivedTypeSpecification(
169
+ _params_up(params),
170
+ ts4.DerivedTypeSpecificationDetails(
171
+ _type_up(base), _fqname_up(from_base), _fqname_up(to_base)
172
+ ),
173
+ )
174
+
175
+
176
+ def _type_def_up(defn: t3.Definition[None]) -> td4.Definition:
177
+ match defn:
178
+ case t3.TypeAliasDefinition(params, tpe):
179
+ return td4.TypeAliasDefinition(_params_up(params), _type_up(tpe))
180
+ case t3.CustomTypeDefinition(params, ctors):
181
+ return td4.CustomTypeDefinition(
182
+ _params_up(params), _access_up(ctors, _ctors_up(ctors.value))
183
+ )
184
+
185
+
186
+ def _va_up(tpe: t3.Type[None]) -> v4.ValueAttributes:
187
+ return v4.ValueAttributes(inferred_type=_type_up(tpe))
188
+
189
+
190
+ type TypedValue3 = v3.Value[None, t3.Type[None]]
191
+ type TypedPattern3 = v3.Pattern[t3.Type[None]]
192
+ type TypedDefinition3 = v3.Definition[None, t3.Type[None]]
193
+
194
+
195
+ def _pattern_up(p: TypedPattern3) -> v4.Pattern:
196
+ a = _va_up(p.attributes)
197
+ match p:
198
+ case v3.WildcardPattern(_):
199
+ return v4.WildcardPattern(a)
200
+ case v3.AsPattern(_, inner, n):
201
+ return v4.AsPattern(a, _pattern_up(inner), _name_up(n))
202
+ case v3.TuplePattern(_, elements):
203
+ return v4.TuplePattern(a, [_pattern_up(x) for x in elements])
204
+ case v3.ConstructorPattern(_, f, args):
205
+ return v4.ConstructorPattern(
206
+ a, _fqname_up(f), [_pattern_up(x) for x in args]
207
+ )
208
+ case v3.EmptyListPattern(_):
209
+ return v4.EmptyListPattern(a)
210
+ case v3.HeadTailPattern(_, head, tail):
211
+ return v4.HeadTailPattern(a, _pattern_up(head), _pattern_up(tail))
212
+ case v3.LiteralPattern(_, literal):
213
+ return v4.LiteralPattern(a, _literal_up(literal))
214
+ case v3.UnitPattern(_):
215
+ return v4.UnitPattern(a)
216
+
217
+
218
+ def _value_up(val: TypedValue3) -> v4.Value:
219
+ a = _va_up(val.attributes)
220
+ match val:
221
+ case v3.LiteralValue(_, literal):
222
+ return v4.LiteralValue(a, _literal_up(literal))
223
+ case v3.Constructor(_, f):
224
+ return v4.Constructor(a, _fqname_up(f))
225
+ case v3.Tuple(_, elements):
226
+ return v4.Tuple(a, [_value_up(x) for x in elements])
227
+ case v3.List(_, items):
228
+ return v4.List(a, [_value_up(x) for x in items])
229
+ case v3.Record(_, fields):
230
+ return v4.Record(
231
+ a, [(_name_up(n), _value_up(x)) for n, x in fields.items()]
232
+ )
233
+ case v3.Variable(_, n):
234
+ return v4.Variable(a, _name_up(n))
235
+ case v3.Reference(_, f):
236
+ return v4.Reference(a, _fqname_up(f))
237
+ case v3.Field(_, subject, n):
238
+ return v4.Field(a, _value_up(subject), _name_up(n))
239
+ case v3.FieldFunction(_, n):
240
+ return v4.FieldFunction(a, _name_up(n))
241
+ case v3.Apply(_, function, argument):
242
+ return v4.Apply(a, _value_up(function), _value_up(argument))
243
+ case v3.Lambda(_, pattern, body):
244
+ return v4.Lambda(a, _pattern_up(pattern), _value_up(body))
245
+ case v3.LetDefinition(_, n, defn, in_value):
246
+ return v4.LetDefinition(
247
+ a, _name_up(n), _value_def_up(defn), _value_up(in_value)
248
+ )
249
+ case v3.LetRecursion(_, defs, in_value):
250
+ return v4.LetRecursion(
251
+ a,
252
+ {_name_up(n): _value_def_up(x) for n, x in defs.items()},
253
+ _value_up(in_value),
254
+ )
255
+ case v3.Destructure(_, pattern, subject, in_value):
256
+ return v4.Destructure(
257
+ a, _pattern_up(pattern), _value_up(subject), _value_up(in_value)
258
+ )
259
+ case v3.IfThenElse(_, cond, then_branch, else_branch):
260
+ return v4.IfThenElse(
261
+ a, _value_up(cond), _value_up(then_branch), _value_up(else_branch)
262
+ )
263
+ case v3.PatternMatch(_, subject, cases):
264
+ return v4.PatternMatch(
265
+ a,
266
+ _value_up(subject),
267
+ [(_pattern_up(p), _value_up(b)) for p, b in cases],
268
+ )
269
+ case v3.UpdateRecord(_, subject, fields):
270
+ return v4.UpdateRecord(
271
+ a,
272
+ _value_up(subject),
273
+ [(_name_up(n), _value_up(x)) for n, x in fields.items()],
274
+ )
275
+ case v3.Unit(_):
276
+ return v4.Unit(a)
277
+
278
+
279
+ def _value_def_up(defn: TypedDefinition3) -> v4.Definition:
280
+ return v4.Definition(
281
+ [(_name_up(n), _va_up(va), _type_up(tpe)) for n, va, tpe in defn.input_types],
282
+ _type_up(defn.output_type),
283
+ _value_up(defn.body),
284
+ )
285
+
286
+
287
+ def _value_spec_up(spec: v3.Specification[None]) -> v4.Specification:
288
+ return v4.Specification(
289
+ [(_name_up(n), _type_up(tp)) for n, tp in spec.inputs], _type_up(spec.output)
290
+ )
291
+
292
+
293
+ def _documented_up[A, B](item: doc3.Documented[A], value: B) -> doc4.Documented[B]:
294
+ return doc4.Documented(item.doc, value)
295
+
296
+
297
+ def _module_spec_up(spec: mod3.Specification[None]) -> mod4.Specification:
298
+ return mod4.Specification(
299
+ types={
300
+ _name_up(n): _documented_up(x, _type_spec_up(x.value))
301
+ for n, x in spec.types.items()
302
+ },
303
+ values={
304
+ _name_up(n): _documented_up(x, _value_spec_up(x.value))
305
+ for n, x in spec.values.items()
306
+ },
307
+ doc=spec.doc,
308
+ )
309
+
310
+
311
+ def _module_def_up(defn: mod3.Definition[None, t3.Type[None]]) -> mod4.Definition:
312
+ return mod4.Definition(
313
+ types={
314
+ _name_up(n): _access_up(
315
+ x, _documented_up(x.value, _type_def_up(x.value.value))
316
+ )
317
+ for n, x in defn.types.items()
318
+ },
319
+ values={
320
+ _name_up(n): _access_up(
321
+ x, _documented_up(x.value, _value_def_up(x.value.value))
322
+ )
323
+ for n, x in defn.values.items()
324
+ },
325
+ doc=defn.doc,
326
+ )
327
+
328
+
329
+ def _package_spec_up(spec: pkg3.Specification[None]) -> pkg4.Specification:
330
+ return pkg4.Specification(
331
+ {_path_up(p): _module_spec_up(m) for p, m in spec.modules.items()}
332
+ )
333
+
334
+
335
+ def _package_def_up(defn: pkg3.Definition[None, t3.Type[None]]) -> pkg4.Definition:
336
+ return pkg4.Definition(
337
+ {
338
+ _path_up(p): _access_up(m, _module_def_up(m.value))
339
+ for p, m in defn.modules.items()
340
+ }
341
+ )
342
+
343
+
344
+ @overload
345
+ def v3_to_v4(
346
+ dist: dist3.Library,
347
+ *,
348
+ package_version: str | None = None,
349
+ drop_input_attributes: bool = False,
350
+ ) -> Ok[dist4.LibraryDistribution]: ...
351
+ @overload
352
+ def v3_to_v4(
353
+ dist: dist3.Specs,
354
+ *,
355
+ package_version: str | None = None,
356
+ drop_input_attributes: bool = False,
357
+ ) -> Ok[dist4.SpecsDistribution]: ...
358
+ def v3_to_v4(
359
+ dist: dist3.Distribution,
360
+ *,
361
+ package_version: str | None = None,
362
+ drop_input_attributes: bool = False,
363
+ ) -> Ok[dist4.LibraryDistribution] | Ok[dist4.SpecsDistribution]:
364
+ """Migrate a format-3 Library or Specs to the same v4 distribution kind.
365
+
366
+ Unit type attributes become empty `TypeAttributes`; each value attribute type
367
+ becomes ``inferred_type``; ``WholeNumberLiteral`` becomes ``IntegerLiteral``.
368
+ A Specs maps its package specification as a Library maps its dependencies.
369
+
370
+ Args:
371
+ dist: The v3 Library or Specs.
372
+ package_version: Optional release version for the v4 package identity.
373
+ Without it the version is absent; nothing is invented.
374
+ drop_input_attributes: Reset each value-definition input's attributes to
375
+ its declared type, as `drop_input_attributes` does, so the result can
376
+ be encoded as v4 JSON. Each input that loses information is reported.
377
+
378
+ Returns:
379
+ ``Ok`` with the v4 Library or Specs and any ``input_attributes_dropped``
380
+ warnings. Forward migration has no failure paths.
381
+ """
382
+ package = dist4.PackageInfo(_path_up(dist.package_name), package_version)
383
+ dependencies = {
384
+ _path_up(p): _package_spec_up(s) for p, s in dist.dependencies.items()
385
+ }
386
+ if isinstance(dist, dist3.Specs):
387
+ specs = dist4.SpecsDistribution(
388
+ package=package,
389
+ specification=_package_spec_up(dist.specification),
390
+ dependencies=dependencies,
391
+ )
392
+ if drop_input_attributes:
393
+ return _drop_input_attributes(specs)
394
+ return Ok(specs)
395
+ library = dist4.LibraryDistribution(
396
+ package=package,
397
+ definition=_package_def_up(dist.definition),
398
+ dependencies=dependencies,
399
+ )
400
+ if drop_input_attributes:
401
+ return _drop_input_attributes(library)
402
+ return Ok(library)
403
+
404
+
405
+ # --- Per-input attributes ---
406
+
407
+
408
+ class _InputDrop:
409
+ """Resets value-definition input attributes, recording each loss."""
410
+
411
+ def __init__(self) -> None:
412
+ self.warnings: list[Diagnostic] = []
413
+
414
+ def definition(
415
+ self, defn: v4.ValueDefinitionBody, loc: Location
416
+ ) -> v4.ValueDefinitionBody:
417
+ match defn:
418
+ case v4.Definition():
419
+ return self.expression(defn, loc)
420
+ case v4.ExternalBody(inputs, output, externals, fallback):
421
+ here = (*loc, "ExternalBody", "body")
422
+ return v4.ExternalBody(
423
+ inputs,
424
+ output,
425
+ externals,
426
+ None if fallback is None else self.value(fallback, here),
427
+ )
428
+ case v4.IncompleteBody(inputs, incompleteness, output, partial):
429
+ here = (*loc, "IncompleteBody", "partial_body")
430
+ return v4.IncompleteBody(
431
+ inputs,
432
+ incompleteness,
433
+ output,
434
+ None if partial is None else self.value(partial, here),
435
+ )
436
+ case v4.NativeBody():
437
+ return defn
438
+
439
+ def expression(self, defn: v4.Definition, loc: Location) -> v4.Definition:
440
+ inputs: list[tuple[name4.Name, v4.ValueAttributes, t4.Type]] = []
441
+ for n, attrs, tpe in defn.input_types:
442
+ declared = v4.ValueAttributes(inferred_type=tpe)
443
+ if attrs not in (v4.ValueAttributes(), declared):
444
+ self.warnings.append(
445
+ Diagnostic(
446
+ d.INPUT_ATTRIBUTES_DROPPED,
447
+ "ValueDefinitionInput",
448
+ (*loc, "input_types", name4.to_string(n)),
449
+ f"input {name4.to_string(n)!r} kept only its declared type; "
450
+ "v4 JSON spells an input as a bare type",
451
+ )
452
+ )
453
+ inputs.append((n, declared, tpe))
454
+ return v4.Definition(
455
+ inputs, defn.output_type, self.value(defn.body, (*loc, "body"))
456
+ )
457
+
458
+ def value(self, val: v4.Value, loc: Location) -> v4.Value:
459
+ here = (*loc, type(val).__name__)
460
+ a = val.attributes
461
+ match val:
462
+ case v4.Tuple(_, elements):
463
+ return v4.Tuple(
464
+ a, [self.value(x, (*here, i)) for i, x in enumerate(elements)]
465
+ )
466
+ case v4.List(_, items):
467
+ return v4.List(
468
+ a, [self.value(x, (*here, i)) for i, x in enumerate(items)]
469
+ )
470
+ case v4.Record(_, fields):
471
+ return v4.Record(a, self.fields(fields, here))
472
+ case v4.Field(_, subject, n):
473
+ return v4.Field(a, self.value(subject, (*here, "subject")), n)
474
+ case v4.Apply(_, function, argument):
475
+ return v4.Apply(
476
+ a,
477
+ self.value(function, (*here, "function")),
478
+ self.value(argument, (*here, "argument")),
479
+ )
480
+ case v4.Lambda(_, pattern, body):
481
+ return v4.Lambda(a, pattern, self.value(body, (*here, "body")))
482
+ case v4.LetDefinition(_, n, defn, in_value):
483
+ return v4.LetDefinition(
484
+ a,
485
+ n,
486
+ self.definition(defn, (*here, "definition")),
487
+ self.value(in_value, (*here, "in_value")),
488
+ )
489
+ case v4.LetRecursion(_, defs, in_value):
490
+ return v4.LetRecursion(
491
+ a,
492
+ {
493
+ k: self.definition(
494
+ x, (*here, "definitions", name4.to_string(k))
495
+ )
496
+ for k, x in defs.items()
497
+ },
498
+ self.value(in_value, (*here, "in_value")),
499
+ )
500
+ case v4.Destructure(_, pattern, subject, in_value):
501
+ return v4.Destructure(
502
+ a,
503
+ pattern,
504
+ self.value(subject, (*here, "value_to_destructure")),
505
+ self.value(in_value, (*here, "in_value")),
506
+ )
507
+ case v4.IfThenElse(_, cond, then_branch, else_branch):
508
+ return v4.IfThenElse(
509
+ a,
510
+ self.value(cond, (*here, "condition")),
511
+ self.value(then_branch, (*here, "then_branch")),
512
+ self.value(else_branch, (*here, "else_branch")),
513
+ )
514
+ case v4.PatternMatch(_, subject, cases):
515
+ return v4.PatternMatch(
516
+ a,
517
+ self.value(subject, (*here, "branch_on")),
518
+ [
519
+ (p, self.value(b, (*here, "cases", i)))
520
+ for i, (p, b) in enumerate(cases)
521
+ ],
522
+ )
523
+ case v4.UpdateRecord(_, subject, fields):
524
+ return v4.UpdateRecord(
525
+ a,
526
+ self.value(subject, (*here, "value_to_update")),
527
+ self.fields(fields, here),
528
+ )
529
+ case _:
530
+ return val
531
+
532
+ def fields(
533
+ self, items: list[tuple[name4.Name, v4.Value]], loc: Location
534
+ ) -> list[tuple[name4.Name, v4.Value]]:
535
+ return [(k, self.value(x, (*loc, name4.to_string(k)))) for k, x in items]
536
+
537
+ def package_def(self, defn: pkg4.Definition, loc: Location) -> pkg4.Definition:
538
+ modules: dict[path4.Path, ac4.AccessControlled[mod4.Definition]] = {}
539
+ for p, m in defn.modules.items():
540
+ here = (*loc, path4.to_string(p))
541
+ values = {
542
+ k: _with_access(
543
+ x,
544
+ doc4.Documented(
545
+ x.value.doc,
546
+ self.definition(
547
+ x.value.value, (*here, "values", name4.to_string(k))
548
+ ),
549
+ ),
550
+ )
551
+ for k, x in m.value.values.items()
552
+ }
553
+ modules[p] = _with_access(
554
+ m, mod4.Definition(m.value.types, values, m.value.doc)
555
+ )
556
+ return pkg4.Definition(modules)
557
+
558
+ # Specifications: annotation arguments are expressions too.
559
+
560
+ def annotations(
561
+ self, items: list[ann4.Annotation], loc: Location
562
+ ) -> list[ann4.Annotation]:
563
+ result: list[ann4.Annotation] = []
564
+ for i, item in enumerate(items):
565
+ here = (*loc, "annotations", i)
566
+ if isinstance(item, ann4.CompactAnnotation):
567
+ result.append(item)
568
+ continue
569
+ arguments: list[ann4.AnnotationArgument] = []
570
+ for j, arg in enumerate(item.arguments):
571
+ at = (*here, "arguments", j)
572
+ match arg:
573
+ case ann4.PositionalArgument(value):
574
+ arguments.append(ann4.PositionalArgument(self.value(value, at)))
575
+ case ann4.NamedArgument(name, value):
576
+ arguments.append(
577
+ ann4.NamedArgument(name, self.value(value, (*at, "value")))
578
+ )
579
+ result.append(ann4.StructuredAnnotation(item.name, arguments))
580
+ return result
581
+
582
+ def module_spec(
583
+ self, spec: mod4.Specification, loc: Location
584
+ ) -> mod4.Specification:
585
+ types = {
586
+ k: doc4.Documented(
587
+ x.doc,
588
+ dataclasses.replace(
589
+ x.value,
590
+ annotations=self.annotations(
591
+ x.value.annotations, (*loc, "types", name4.to_string(k))
592
+ ),
593
+ ),
594
+ )
595
+ for k, x in spec.types.items()
596
+ }
597
+ values = {
598
+ k: doc4.Documented(
599
+ x.doc,
600
+ dataclasses.replace(
601
+ x.value,
602
+ annotations=self.annotations(
603
+ x.value.annotations, (*loc, "values", name4.to_string(k))
604
+ ),
605
+ ),
606
+ )
607
+ for k, x in spec.values.items()
608
+ }
609
+ return mod4.Specification(
610
+ types, values, spec.doc, self.annotations(spec.annotations, loc)
611
+ )
612
+
613
+ def package_spec(
614
+ self, spec: pkg4.Specification, loc: Location
615
+ ) -> pkg4.Specification:
616
+ return pkg4.Specification(
617
+ {
618
+ p: self.module_spec(m, (*loc, path4.to_string(p)))
619
+ for p, m in spec.modules.items()
620
+ }
621
+ )
622
+
623
+
624
+ def _with_access[A, B](
625
+ item: ac4.AccessControlled[A], value: B
626
+ ) -> ac4.AccessControlled[B]:
627
+ return ac4.Public(value) if isinstance(item, ac4.Public) else ac4.Private(value)
628
+
629
+
630
+ def drop_input_attributes[D: dist4.Distribution](dist: D) -> Ok[D]:
631
+ """Reset every value-definition input's attributes to its declared type.
632
+
633
+ v4 JSON spells an input as a bare type (kit case ``definitions-0027``), so
634
+ the draft codec refuses an input whose attributes hold anything else, such as
635
+ the resolved types morphir-elm writes for typed inputs. This drop makes such
636
+ a distribution encodable. Module-level and let-bound definitions are covered.
637
+
638
+ Args:
639
+ dist: Any v4 distribution. Dependencies and the expressions in
640
+ specification annotation arguments are covered too.
641
+
642
+ Returns:
643
+ ``Ok`` with the distribution and one ``input_attributes_dropped`` warning
644
+ per input whose attributes were neither empty nor its declared type.
645
+ """
646
+ return _drop_input_attributes(dist)
647
+
648
+
649
+ def _drop_input_attributes[D: dist4.Distribution](dist: D) -> Ok[D]:
650
+ job = _InputDrop()
651
+ loc: Location = ("distribution", "modules")
652
+
653
+ def dep(p: path4.Path) -> Location:
654
+ return ("distribution", "dependencies", path4.to_string(p))
655
+
656
+ result: dist4.Distribution
657
+ match dist:
658
+ case dist4.SpecsDistribution(package, specification, deps):
659
+ result = dist4.SpecsDistribution(
660
+ package,
661
+ job.package_spec(specification, loc),
662
+ {p: job.package_spec(s, dep(p)) for p, s in deps.items()},
663
+ )
664
+ case dist4.LibraryDistribution(package, definition, deps):
665
+ result = dist4.LibraryDistribution(
666
+ package,
667
+ job.package_def(definition, loc),
668
+ {p: job.package_spec(s, dep(p)) for p, s in deps.items()},
669
+ )
670
+ case dist4.ApplicationDistribution(package, definition, deps, entry_points):
671
+ result = dist4.ApplicationDistribution(
672
+ package,
673
+ job.package_def(definition, loc),
674
+ {p: job.package_def(x, dep(p)) for p, x in deps.items()},
675
+ entry_points,
676
+ )
677
+ return Ok(cast("D", result), tuple(job.warnings))
678
+
679
+
680
+ # --- v4 -> v3 ---
681
+
682
+ _CLASSIC_WORD = re.compile(r"^[a-z0-9]+$")
683
+
684
+
685
+ @dataclass
686
+ class _Downgrade:
687
+ drop_source_locations: bool
688
+ errors: list[Diagnostic] = field(default_factory=list)
689
+ dropped_sources: int = 0
690
+
691
+ def error(self, code: str, construct: str, loc: Location, message: str) -> None:
692
+ self.errors.append(Diagnostic(code, construct, loc, message))
693
+
694
+ # Names
695
+
696
+ def name(self, n: name4.Name, loc: Location) -> name3.Name:
697
+ for word in n:
698
+ if not _CLASSIC_WORD.match(word):
699
+ self.error(
700
+ d.INVALID_NAME,
701
+ "Name",
702
+ loc,
703
+ f"name word {word!r} has no classic (v3) form",
704
+ )
705
+ return name3.Name(tuple(n))
706
+
707
+ def path(self, p: path4.Path, loc: Location) -> path3.Path:
708
+ return path3.Path(tuple(self.name(n, loc) for n in p))
709
+
710
+ def fqname(self, f: fq4.FQName, loc: Location) -> fq3.FQName:
711
+ return fq3.FQName(
712
+ self.path(f.package_path, loc),
713
+ self.path(f.module_path, loc),
714
+ self.name(f.local_name, loc),
715
+ )
716
+
717
+ def access[A, B](
718
+ self, item: ac4.AccessControlled[A], value: B
719
+ ) -> ac3.AccessControlled[B]:
720
+ if isinstance(item, ac4.Public):
721
+ return ac3.public(value)
722
+ return ac3.private(value)
723
+
724
+ def documented[A, B](
725
+ self, item: doc4.Documented[A], value: B, construct: str, loc: Location
726
+ ) -> doc3.Documented[B]:
727
+ if item.doc is None:
728
+ self.error(
729
+ d.UNSUPPORTED_DOCUMENTATION_DOWNGRADE,
730
+ construct,
731
+ loc,
732
+ f"{construct} has no documentation; v3 member documentation is a "
733
+ "required string",
734
+ )
735
+ return doc3.Documented("", value)
736
+ return doc3.Documented(item.doc, value)
737
+
738
+ # Attributes
739
+
740
+ def type_attributes(self, a: t4.TypeAttributes, loc: Location) -> None:
741
+ present = [
742
+ label
743
+ for label, on in (
744
+ ("source", a.source is not None),
745
+ ("constraints", a.constraints is not None),
746
+ ("extensions", bool(a.extensions)),
747
+ )
748
+ if on
749
+ ]
750
+ if present:
751
+ self.error(
752
+ d.UNSUPPORTED_ATTRIBUTES_DOWNGRADE,
753
+ "TypeAttributes",
754
+ loc,
755
+ f"type attributes carry {', '.join(present)}, which v3 cannot hold",
756
+ )
757
+
758
+ def value_attributes(
759
+ self, a: v4.ValueAttributes, construct: str, loc: Location
760
+ ) -> t3.Type[None]:
761
+ if a.extensions:
762
+ self.error(
763
+ d.UNSUPPORTED_ATTRIBUTES_DOWNGRADE,
764
+ construct,
765
+ loc,
766
+ "value attributes carry extensions, which v3 cannot hold",
767
+ )
768
+ if a.source is not None:
769
+ if self.drop_source_locations:
770
+ self.dropped_sources += 1
771
+ else:
772
+ self.error(
773
+ d.UNSUPPORTED_ATTRIBUTES_DOWNGRADE,
774
+ construct,
775
+ loc,
776
+ "value attributes carry a source location; pass "
777
+ "drop_source_locations=True to drop it",
778
+ )
779
+ if a.inferred_type is None:
780
+ self.error(
781
+ d.MISSING_INFERRED_TYPE,
782
+ construct,
783
+ loc,
784
+ f"{construct} has no inferred_type; v3 value attributes are types",
785
+ )
786
+ return t3.Unit(None)
787
+ return self.type(a.inferred_type, (*loc, "inferred_type"))
788
+
789
+ # Types
790
+
791
+ def type(self, tpe: t4.Type, loc: Location) -> t3.Type[None]:
792
+ self.type_attributes(tpe.attributes, loc)
793
+ here = (*loc, type(tpe).__name__)
794
+ match tpe:
795
+ case t4.Variable(_, n):
796
+ return t3.Variable(None, self.name(n, here))
797
+ case t4.Reference(_, f, args):
798
+ return t3.Reference(
799
+ None,
800
+ self.fqname(f, here),
801
+ tuple(self.type(x, (*here, i)) for i, x in enumerate(args)),
802
+ )
803
+ case t4.Tuple(_, elements):
804
+ return t3.Tuple(
805
+ None,
806
+ tuple(self.type(x, (*here, i)) for i, x in enumerate(elements)),
807
+ )
808
+ case t4.Record(_, fields):
809
+ return t3.Record(None, self.fields(fields, here))
810
+ case t4.ExtensibleRecord(_, var, fields):
811
+ return t3.ExtensibleRecord(
812
+ None, self.name(var, here), self.fields(fields, here)
813
+ )
814
+ case t4.Function(_, arg, ret):
815
+ return t3.Function(
816
+ None,
817
+ self.type(arg, (*here, "argument_type")),
818
+ self.type(ret, (*here, "return_type")),
819
+ )
820
+ case t4.Unit(_):
821
+ return t3.Unit(None)
822
+ case t4.Union():
823
+ self.unsupported("Union", here)
824
+ return t3.Unit(None)
825
+
826
+ def fields(
827
+ self, fields: list[t4.Field], loc: Location
828
+ ) -> tuple[t3.Field[None], ...]:
829
+ return tuple(
830
+ t3.Field(self.name(f.name, loc), self.type(f.tpe, (*loc, "fields", i)))
831
+ for i, f in enumerate(fields)
832
+ )
833
+
834
+ def ctors(self, ctors: ts4.Constructors, loc: Location) -> t3.Constructors[None]:
835
+ return {
836
+ self.name(n, loc): tuple(
837
+ (self.name(an, loc), self.type(at, (*loc, name4.to_string(n), i)))
838
+ for i, (an, at) in enumerate(args)
839
+ )
840
+ for n, args in ctors.items()
841
+ }
842
+
843
+ def params(self, params: list[name4.Name], loc: Location) -> tuple[name3.Name, ...]:
844
+ return tuple(self.name(p, loc) for p in params)
845
+
846
+ def annotations(self, items: list[ann4.Annotation], loc: Location) -> None:
847
+ if items:
848
+ self.unsupported("Annotations", (*loc, "annotations"))
849
+
850
+ def type_spec(
851
+ self, spec: ts4.Specification, loc: Location
852
+ ) -> t3.Specification[None]:
853
+ self.annotations(spec.annotations, loc)
854
+ params = self.params(spec.type_params, loc)
855
+ match spec:
856
+ case ts4.TypeAliasSpecification(_, tpe):
857
+ return t3.TypeAliasSpecification(params, self.type(tpe, loc))
858
+ case ts4.OpaqueTypeSpecification(_):
859
+ return t3.OpaqueTypeSpecification(params)
860
+ case ts4.CustomTypeSpecification(_, ctors):
861
+ return t3.CustomTypeSpecification(params, self.ctors(ctors, loc))
862
+ case ts4.DerivedTypeSpecification(_, details):
863
+ return t3.DerivedTypeSpecification(
864
+ params,
865
+ self.type(details.base_type, loc),
866
+ self.fqname(details.from_base_type, loc),
867
+ self.fqname(details.to_base_type, loc),
868
+ )
869
+
870
+ def type_def(self, defn: td4.Definition, loc: Location) -> t3.Definition[None]:
871
+ params = self.params(defn.type_params, loc)
872
+ match defn:
873
+ case td4.TypeAliasDefinition(_, tpe):
874
+ return t3.TypeAliasDefinition(params, self.type(tpe, loc))
875
+ case td4.CustomTypeDefinition(_, ctors):
876
+ return t3.CustomTypeDefinition(
877
+ params, self.access(ctors, self.ctors(ctors.value, loc))
878
+ )
879
+ case td4.IncompleteTypeDefinition():
880
+ self.unsupported(
881
+ "IncompleteTypeDefinition", (*loc, "IncompleteTypeDefinition")
882
+ )
883
+ return t3.TypeAliasDefinition(params, t3.Unit(None))
884
+
885
+ # Values
886
+
887
+ def literal(self, literal: lit4.Literal, loc: Location) -> lit3.Literal:
888
+ match literal:
889
+ case lit4.BoolLiteral(b):
890
+ return lit3.BoolLiteral(b)
891
+ case lit4.CharLiteral(c):
892
+ return lit3.CharLiteral(c)
893
+ case lit4.StringLiteral(s):
894
+ return lit3.StringLiteral(s)
895
+ case lit4.IntegerLiteral(i):
896
+ return lit3.WholeNumberLiteral(i)
897
+ case lit4.FloatLiteral(f):
898
+ return lit3.FloatLiteral(f)
899
+ case lit4.DecimalLiteral(x):
900
+ return lit3.DecimalLiteral(x)
901
+ case lit4.DocumentLiteral():
902
+ self.unsupported("DocumentLiteral", loc)
903
+ return lit3.StringLiteral("")
904
+
905
+ def unsupported(self, construct: str, loc: Location) -> None:
906
+ self.error(
907
+ d.UNSUPPORTED_V4_DOWNGRADE,
908
+ construct,
909
+ loc,
910
+ f"{construct} exists only in IR v4 and cannot be written as format 3.0.0",
911
+ )
912
+
913
+ def pattern(self, p: v4.Pattern, loc: Location) -> TypedPattern3:
914
+ construct = type(p).__name__
915
+ here = (*loc, construct)
916
+ a = self.value_attributes(p.attributes, construct, here)
917
+ match p:
918
+ case v4.WildcardPattern(_):
919
+ return v3.WildcardPattern(a)
920
+ case v4.AsPattern(_, inner, n):
921
+ return v3.AsPattern(
922
+ a, self.pattern(inner, (*here, "pattern")), self.name(n, here)
923
+ )
924
+ case v4.TuplePattern(_, elements):
925
+ return v3.TuplePattern(
926
+ a,
927
+ tuple(self.pattern(x, (*here, i)) for i, x in enumerate(elements)),
928
+ )
929
+ case v4.ConstructorPattern(_, f, args):
930
+ return v3.ConstructorPattern(
931
+ a,
932
+ self.fqname(f, here),
933
+ tuple(self.pattern(x, (*here, i)) for i, x in enumerate(args)),
934
+ )
935
+ case v4.EmptyListPattern(_):
936
+ return v3.EmptyListPattern(a)
937
+ case v4.HeadTailPattern(_, head, tail):
938
+ return v3.HeadTailPattern(
939
+ a,
940
+ self.pattern(head, (*here, "head")),
941
+ self.pattern(tail, (*here, "tail")),
942
+ )
943
+ case v4.LiteralPattern(_, literal):
944
+ return v3.LiteralPattern(a, self.literal(literal, here))
945
+ case v4.UnitPattern(_):
946
+ return v3.UnitPattern(a)
947
+
948
+ def named_values(
949
+ self, items: list[tuple[name4.Name, v4.Value]], construct: str, loc: Location
950
+ ) -> dict[name3.Name, TypedValue3]:
951
+ result: dict[name3.Name, TypedValue3] = {}
952
+ for n, x in items:
953
+ key = self.name(n, loc)
954
+ if key in result:
955
+ self.error(
956
+ d.DUPLICATE_MEMBER,
957
+ construct,
958
+ loc,
959
+ f"field {name4.to_string(n)!r} appears more than once; v3 record "
960
+ "fields are a dictionary",
961
+ )
962
+ result[key] = self.value(x, (*loc, name4.to_string(n)))
963
+ return result
964
+
965
+ def value(self, val: v4.Value, loc: Location) -> TypedValue3:
966
+ construct = type(val).__name__
967
+ here = (*loc, construct)
968
+ if isinstance(val, (v4.Hole, v4.Native, v4.External)):
969
+ self.unsupported(construct, here)
970
+ return v3.Unit(t3.Unit(None))
971
+ a = self.value_attributes(val.attributes, construct, here)
972
+ match val:
973
+ case v4.LiteralValue(_, literal):
974
+ return v3.LiteralValue(a, self.literal(literal, here))
975
+ case v4.Constructor(_, f):
976
+ return v3.Constructor(a, self.fqname(f, here))
977
+ case v4.Tuple(_, elements):
978
+ return v3.Tuple(
979
+ a, tuple(self.value(x, (*here, i)) for i, x in enumerate(elements))
980
+ )
981
+ case v4.List(_, items):
982
+ return v3.List(
983
+ a, tuple(self.value(x, (*here, i)) for i, x in enumerate(items))
984
+ )
985
+ case v4.Record(_, fields):
986
+ return v3.Record(a, self.named_values(fields, construct, here))
987
+ case v4.Variable(_, n):
988
+ return v3.Variable(a, self.name(n, here))
989
+ case v4.Reference(_, f):
990
+ return v3.Reference(a, self.fqname(f, here))
991
+ case v4.Field(_, subject, n):
992
+ return v3.Field(
993
+ a, self.value(subject, (*here, "subject")), self.name(n, here)
994
+ )
995
+ case v4.FieldFunction(_, n):
996
+ return v3.FieldFunction(a, self.name(n, here))
997
+ case v4.Apply(_, function, argument):
998
+ return v3.Apply(
999
+ a,
1000
+ self.value(function, (*here, "function")),
1001
+ self.value(argument, (*here, "argument")),
1002
+ )
1003
+ case v4.Lambda(_, pattern, body):
1004
+ return v3.Lambda(
1005
+ a,
1006
+ self.pattern(pattern, (*here, "pattern")),
1007
+ self.value(body, (*here, "body")),
1008
+ )
1009
+ case v4.LetDefinition(_, n, defn, in_value):
1010
+ return v3.LetDefinition(
1011
+ a,
1012
+ self.name(n, here),
1013
+ self.value_def(defn, (*here, "definition")),
1014
+ self.value(in_value, (*here, "in_value")),
1015
+ )
1016
+ case v4.LetRecursion(_, defs, in_value):
1017
+ return v3.LetRecursion(
1018
+ a,
1019
+ {
1020
+ self.name(n, here): self.value_def(
1021
+ x, (*here, "definitions", name4.to_string(n))
1022
+ )
1023
+ for n, x in defs.items()
1024
+ },
1025
+ self.value(in_value, (*here, "in_value")),
1026
+ )
1027
+ case v4.Destructure(_, pattern, subject, in_value):
1028
+ return v3.Destructure(
1029
+ a,
1030
+ self.pattern(pattern, (*here, "pattern")),
1031
+ self.value(subject, (*here, "value_to_destructure")),
1032
+ self.value(in_value, (*here, "in_value")),
1033
+ )
1034
+ case v4.IfThenElse(_, cond, then_branch, else_branch):
1035
+ return v3.IfThenElse(
1036
+ a,
1037
+ self.value(cond, (*here, "condition")),
1038
+ self.value(then_branch, (*here, "then_branch")),
1039
+ self.value(else_branch, (*here, "else_branch")),
1040
+ )
1041
+ case v4.PatternMatch(_, subject, cases):
1042
+ return v3.PatternMatch(
1043
+ a,
1044
+ self.value(subject, (*here, "branch_on")),
1045
+ tuple(
1046
+ (
1047
+ self.pattern(p, (*here, "cases", i)),
1048
+ self.value(b, (*here, "cases", i)),
1049
+ )
1050
+ for i, (p, b) in enumerate(cases)
1051
+ ),
1052
+ )
1053
+ case v4.UpdateRecord(_, subject, fields):
1054
+ return v3.UpdateRecord(
1055
+ a,
1056
+ self.value(subject, (*here, "value_to_update")),
1057
+ self.named_values(fields, construct, here),
1058
+ )
1059
+ case _:
1060
+ return v3.Unit(a)
1061
+
1062
+ def value_def(
1063
+ self, defn: v4.ValueDefinitionBody, loc: Location
1064
+ ) -> TypedDefinition3:
1065
+ if not isinstance(defn, v4.Definition):
1066
+ construct = type(defn).__name__
1067
+ self.unsupported(construct, (*loc, construct))
1068
+ return v3.Definition((), t3.Unit(None), v3.Unit(t3.Unit(None)))
1069
+ inputs = []
1070
+ for n, va, tpe in defn.input_types:
1071
+ here = (*loc, "input_types", name4.to_string(n))
1072
+ inputs.append(
1073
+ (
1074
+ self.name(n, here),
1075
+ self.value_attributes(va, "ValueDefinitionInput", here),
1076
+ self.type(tpe, here),
1077
+ )
1078
+ )
1079
+ return v3.Definition(
1080
+ tuple(inputs),
1081
+ self.type(defn.output_type, (*loc, "output_type")),
1082
+ self.value(defn.body, (*loc, "body")),
1083
+ )
1084
+
1085
+ def value_spec(
1086
+ self, spec: v4.Specification, loc: Location
1087
+ ) -> v3.Specification[None]:
1088
+ self.annotations(spec.annotations, loc)
1089
+ return v3.Specification(
1090
+ tuple(
1091
+ (self.name(n, loc), self.type(tp, (*loc, "inputs", name4.to_string(n))))
1092
+ for n, tp in spec.inputs
1093
+ ),
1094
+ self.type(spec.output, (*loc, "output")),
1095
+ )
1096
+
1097
+ # Modules and packages
1098
+
1099
+ def module_spec(
1100
+ self, spec: mod4.Specification, loc: Location
1101
+ ) -> mod3.Specification[None]:
1102
+ self.annotations(spec.annotations, loc)
1103
+ types = {}
1104
+ for n, x in spec.types.items():
1105
+ here = (*loc, "types", name4.to_string(n))
1106
+ types[self.name(n, here)] = self.documented(
1107
+ x, self.type_spec(x.value, here), "TypeSpecification", here
1108
+ )
1109
+ values = {}
1110
+ for n, y in spec.values.items():
1111
+ here = (*loc, "values", name4.to_string(n))
1112
+ values[self.name(n, here)] = self.documented(
1113
+ y, self.value_spec(y.value, here), "ValueSpecification", here
1114
+ )
1115
+ return mod3.Specification(types, values, spec.doc)
1116
+
1117
+ def module_def(
1118
+ self, defn: mod4.Definition, loc: Location
1119
+ ) -> mod3.Definition[None, t3.Type[None]]:
1120
+ types = {}
1121
+ for n, x in defn.types.items():
1122
+ here = (*loc, "types", name4.to_string(n))
1123
+ types[self.name(n, here)] = self.access(
1124
+ x,
1125
+ self.documented(
1126
+ x.value, self.type_def(x.value.value, here), "TypeDefinition", here
1127
+ ),
1128
+ )
1129
+ values = {}
1130
+ for n, y in defn.values.items():
1131
+ here = (*loc, "values", name4.to_string(n))
1132
+ values[self.name(n, here)] = self.access(
1133
+ y,
1134
+ self.documented(
1135
+ y.value,
1136
+ self.value_def(y.value.value, here),
1137
+ "ValueDefinition",
1138
+ here,
1139
+ ),
1140
+ )
1141
+ return mod3.Definition(types, values, defn.doc)
1142
+
1143
+ def package_spec(
1144
+ self, spec: pkg4.Specification, loc: Location
1145
+ ) -> pkg3.Specification[None]:
1146
+ return pkg3.Specification(
1147
+ {
1148
+ self.path(p, loc): self.module_spec(m, (*loc, path4.to_string(p)))
1149
+ for p, m in spec.modules.items()
1150
+ }
1151
+ )
1152
+
1153
+ def package_def(
1154
+ self, defn: pkg4.Definition, loc: Location
1155
+ ) -> pkg3.Definition[None, t3.Type[None]]:
1156
+ return pkg3.Definition(
1157
+ {
1158
+ self.path(p, loc): self.access(
1159
+ m, self.module_def(m.value, (*loc, path4.to_string(p)))
1160
+ )
1161
+ for p, m in defn.modules.items()
1162
+ }
1163
+ )
1164
+
1165
+
1166
+ @overload
1167
+ def v4_to_v3(
1168
+ dist: dist4.LibraryDistribution, *, drop_source_locations: bool = False
1169
+ ) -> MigrationResult[dist3.Library]: ...
1170
+ @overload
1171
+ def v4_to_v3(
1172
+ dist: dist4.SpecsDistribution, *, drop_source_locations: bool = False
1173
+ ) -> MigrationResult[dist3.Specs]: ...
1174
+ @overload
1175
+ def v4_to_v3(
1176
+ dist: dist4.ApplicationDistribution, *, drop_source_locations: bool = False
1177
+ ) -> Err: ...
1178
+ def v4_to_v3(
1179
+ dist: dist4.Distribution, *, drop_source_locations: bool = False
1180
+ ) -> MigrationResult[dist3.Library] | MigrationResult[dist3.Specs]:
1181
+ """Migrate a v4 Library or Specs to format 3, or report why it cannot.
1182
+
1183
+ Every unsupported construct is collected in one pass. A Specs becomes a v3
1184
+ Specs, which format 3.1.0 introduced; an Application has no format-3 form.
1185
+
1186
+ Args:
1187
+ dist: The v4 distribution.
1188
+ drop_source_locations: Drop value source locations instead of failing.
1189
+ This is the only opt-in loss.
1190
+
1191
+ Returns:
1192
+ ``Ok`` with the v3 Library or Specs and any warnings, or ``Err`` with
1193
+ every diagnostic found.
1194
+ """
1195
+ if isinstance(dist, dist4.ApplicationDistribution):
1196
+ return Err(
1197
+ (
1198
+ Diagnostic(
1199
+ d.UNSUPPORTED_V4_DOWNGRADE,
1200
+ "Application",
1201
+ ("distribution",),
1202
+ "Application distributions cannot be written as format 3, "
1203
+ "which has no Application",
1204
+ ),
1205
+ )
1206
+ )
1207
+ job = _Downgrade(drop_source_locations)
1208
+ loc: Location = ("distribution",)
1209
+
1210
+ def dependencies() -> dict[path3.Path, pkg3.Specification[None]]:
1211
+ return {
1212
+ job.path(p, loc): job.package_spec(
1213
+ s, (*loc, "dependencies", path4.to_string(p))
1214
+ )
1215
+ for p, s in dist.dependencies.items()
1216
+ }
1217
+
1218
+ # Fields are migrated in declaration order, so diagnostics keep that order.
1219
+ result: dist3.Library | dist3.Specs
1220
+ if isinstance(dist, dist4.SpecsDistribution):
1221
+ result = dist3.Specs(
1222
+ package_name=job.path(dist.package.name, (*loc, "package")),
1223
+ specification=job.package_spec(dist.specification, (*loc, "modules")),
1224
+ dependencies=dependencies(),
1225
+ )
1226
+ else:
1227
+ result = dist3.Library(
1228
+ package_name=job.path(dist.package.name, (*loc, "package")),
1229
+ definition=job.package_def(dist.definition, (*loc, "modules")),
1230
+ dependencies=dependencies(),
1231
+ )
1232
+ if job.errors:
1233
+ return Err(tuple(job.errors))
1234
+ warnings: list[Diagnostic] = []
1235
+ if dist.package.version is not None:
1236
+ warnings.append(
1237
+ Diagnostic(
1238
+ d.PACKAGE_VERSION_DROPPED,
1239
+ "PackageInfo",
1240
+ (*loc, "package", "version"),
1241
+ f"package version {dist.package.version!r} was dropped; format 3 "
1242
+ "has no package version",
1243
+ )
1244
+ )
1245
+ if job.dropped_sources:
1246
+ warnings.append(
1247
+ Diagnostic(
1248
+ d.SOURCE_LOCATION_DROPPED,
1249
+ "ValueAttributes",
1250
+ loc,
1251
+ f"{job.dropped_sources} value source location(s) were dropped",
1252
+ )
1253
+ )
1254
+ # Ok is invariant, so each kind is returned under its own narrowed type.
1255
+ if isinstance(result, dist3.Specs):
1256
+ return Ok(result, tuple(warnings))
1257
+ return Ok(result, tuple(warnings))
1258
+
1259
+
1260
+ __all__ = [
1261
+ "Err",
1262
+ "MigrationResult",
1263
+ "Ok",
1264
+ "drop_input_attributes",
1265
+ "v3_to_v4",
1266
+ "v4_to_v3",
1267
+ ]