pycforge 0.15.2__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 (221) hide show
  1. pycforge/__init__.py +8 -0
  2. pycforge/__main__.py +2 -0
  3. pycforge/_version.py +1 -0
  4. pycforge/converter/__init__.py +0 -0
  5. pycforge/converter/analysis/__init__.py +5 -0
  6. pycforge/converter/analysis/functions.py +723 -0
  7. pycforge/converter/analysis/model.py +158 -0
  8. pycforge/converter/analysis/planning.py +464 -0
  9. pycforge/converter/analysis/stage.py +1976 -0
  10. pycforge/converter/analysis/symbols.py +237 -0
  11. pycforge/converter/analysis/validation.py +2166 -0
  12. pycforge/converter/c_output/__init__.py +2 -0
  13. pycforge/converter/c_output/conformance.py +297 -0
  14. pycforge/converter/c_output/renderer.py +220 -0
  15. pycforge/converter/conditional_regions/__init__.py +71 -0
  16. pycforge/converter/conditional_regions/analysis.py +481 -0
  17. pycforge/converter/conditional_regions/lowering.py +579 -0
  18. pycforge/converter/conditional_regions/model.py +243 -0
  19. pycforge/converter/conditional_regions/validation.py +690 -0
  20. pycforge/converter/containers/__init__.py +19 -0
  21. pycforge/converter/containers/analysis.py +542 -0
  22. pycforge/converter/containers/lowering.py +290 -0
  23. pycforge/converter/containers/model.py +146 -0
  24. pycforge/converter/contracts/__init__.py +0 -0
  25. pycforge/converter/contracts/configuration.py +178 -0
  26. pycforge/converter/contracts/identifiers.py +68 -0
  27. pycforge/converter/contracts/versions.py +71 -0
  28. pycforge/converter/core/__init__.py +0 -0
  29. pycforge/converter/core/artifact_io.py +87 -0
  30. pycforge/converter/core/cancellation.py +7 -0
  31. pycforge/converter/core/canonicalization.py +254 -0
  32. pycforge/converter/core/context.py +13 -0
  33. pycforge/converter/core/diagnostics.py +95 -0
  34. pycforge/converter/core/enums.py +23 -0
  35. pycforge/converter/core/fingerprint.py +22 -0
  36. pycforge/converter/core/pipeline.py +8 -0
  37. pycforge/converter/core/progress.py +25 -0
  38. pycforge/converter/core/request.py +50 -0
  39. pycforge/converter/core/resource_policy.py +31 -0
  40. pycforge/converter/core/result.py +23 -0
  41. pycforge/converter/core/serialization.py +36 -0
  42. pycforge/converter/core/stage.py +10 -0
  43. pycforge/converter/core/stage_artifact.py +80 -0
  44. pycforge/converter/core/stage_outcome.py +15 -0
  45. pycforge/converter/decision_trace/__init__.py +0 -0
  46. pycforge/converter/decision_trace/recorder.py +52 -0
  47. pycforge/converter/facade.py +459 -0
  48. pycforge/converter/frontend/__init__.py +4 -0
  49. pycforge/converter/frontend/normalizer.py +59 -0
  50. pycforge/converter/frontend/parser.py +20 -0
  51. pycforge/converter/frontend/source_document.py +93 -0
  52. pycforge/converter/frontend/stages.py +518 -0
  53. pycforge/converter/frontend/validation.py +76 -0
  54. pycforge/converter/io/__init__.py +0 -0
  55. pycforge/converter/io/atomic_writer.py +33 -0
  56. pycforge/converter/ir/__init__.py +1 -0
  57. pycforge/converter/ir/c_ir/__init__.py +4 -0
  58. pycforge/converter/ir/c_ir/builder.py +26 -0
  59. pycforge/converter/ir/c_ir/model.py +318 -0
  60. pycforge/converter/ir/c_ir/serialization.py +88 -0
  61. pycforge/converter/ir/c_ir/validation.py +834 -0
  62. pycforge/converter/ir/python_ir/__init__.py +17 -0
  63. pycforge/converter/ir/python_ir/nodes.py +86 -0
  64. pycforge/converter/keyword_calls/__init__.py +68 -0
  65. pycforge/converter/keyword_calls/analysis.py +458 -0
  66. pycforge/converter/keyword_calls/lowering.py +271 -0
  67. pycforge/converter/keyword_calls/model.py +304 -0
  68. pycforge/converter/keyword_calls/validation.py +802 -0
  69. pycforge/converter/keyword_only_calls/__init__.py +68 -0
  70. pycforge/converter/keyword_only_calls/analysis.py +640 -0
  71. pycforge/converter/keyword_only_calls/lowering.py +376 -0
  72. pycforge/converter/keyword_only_calls/model.py +328 -0
  73. pycforge/converter/keyword_only_calls/validation.py +1357 -0
  74. pycforge/converter/lowering.py +1000 -0
  75. pycforge/converter/modules/__init__.py +13 -0
  76. pycforge/converter/modules/analysis.py +1250 -0
  77. pycforge/converter/modules/lowering.py +200 -0
  78. pycforge/converter/modules/model.py +73 -0
  79. pycforge/converter/modules/stage.py +192 -0
  80. pycforge/converter/numeric_semantics/__init__.py +23 -0
  81. pycforge/converter/numeric_semantics/analysis.py +420 -0
  82. pycforge/converter/numeric_semantics/lowering.py +153 -0
  83. pycforge/converter/numeric_semantics/model.py +123 -0
  84. pycforge/converter/records/__init__.py +34 -0
  85. pycforge/converter/records/analysis.py +1394 -0
  86. pycforge/converter/records/lowering.py +393 -0
  87. pycforge/converter/records/model.py +319 -0
  88. pycforge/converter/rules/__init__.py +3 -0
  89. pycforge/converter/support_templates/__init__.py +49 -0
  90. pycforge/converter/support_templates/assembly.py +106 -0
  91. pycforge/converter/support_templates/factories.py +348 -0
  92. pycforge/converter/support_templates/model.py +159 -0
  93. pycforge/converter/support_templates/registry.py +388 -0
  94. pycforge/converter/telemetry/__init__.py +0 -0
  95. pycforge/converter/telemetry/sink.py +39 -0
  96. pycforge/ide/__init__.py +19 -0
  97. pycforge/ide/__main__.py +5 -0
  98. pycforge/ide/_worker_event_codec.py +276 -0
  99. pycforge/ide/_worker_protocol_json.py +197 -0
  100. pycforge/ide/_worker_protocol_types.py +90 -0
  101. pycforge/ide/_worker_request_codec.py +200 -0
  102. pycforge/ide/_worker_request_data.py +260 -0
  103. pycforge/ide/_worker_result_data.py +343 -0
  104. pycforge/ide/action_contract.py +469 -0
  105. pycforge/ide/action_validation.py +229 -0
  106. pycforge/ide/command_palette.py +223 -0
  107. pycforge/ide/controller.py +904 -0
  108. pycforge/ide/controller_conversion.py +546 -0
  109. pycforge/ide/controller_io.py +205 -0
  110. pycforge/ide/editor.py +951 -0
  111. pycforge/ide/editor_commands_qt.py +274 -0
  112. pycforge/ide/editor_lexical.py +43 -0
  113. pycforge/ide/editor_sidebars.py +236 -0
  114. pycforge/ide/editor_syntax.py +243 -0
  115. pycforge/ide/find_replace.py +535 -0
  116. pycforge/ide/icons.py +91 -0
  117. pycforge/ide/io_service.py +196 -0
  118. pycforge/ide/model.py +164 -0
  119. pycforge/ide/panels.py +903 -0
  120. pycforge/ide/positions.py +220 -0
  121. pycforge/ide/process_worker.py +130 -0
  122. pycforge/ide/qt.py +230 -0
  123. pycforge/ide/qt_actions.py +418 -0
  124. pycforge/ide/qt_close.py +169 -0
  125. pycforge/ide/qt_command_palette.py +297 -0
  126. pycforge/ide/qt_contract.py +220 -0
  127. pycforge/ide/qt_documents.py +590 -0
  128. pycforge/ide/qt_editor_buffers.py +113 -0
  129. pycforge/ide/qt_editor_surfaces.py +112 -0
  130. pycforge/ide/qt_menus.py +324 -0
  131. pycforge/ide/qt_projection.py +500 -0
  132. pycforge/ide/qt_shell.py +348 -0
  133. pycforge/ide/qt_shell_interactions.py +320 -0
  134. pycforge/ide/qt_state.py +393 -0
  135. pycforge/ide/qt_workspace_features.py +398 -0
  136. pycforge/ide/qt_workspace_navigation.py +208 -0
  137. pycforge/ide/qt_workspace_observers.py +173 -0
  138. pycforge/ide/qt_workspace_panels.py +545 -0
  139. pycforge/ide/qt_workspace_widgets.py +228 -0
  140. pycforge/ide/resources/icons/about.svg +1 -0
  141. pycforge/ide/resources/icons/add-document.svg +1 -0
  142. pycforge/ide/resources/icons/brand-mark.svg +1 -0
  143. pycforge/ide/resources/icons/bundle-search.svg +1 -0
  144. pycforge/ide/resources/icons/cancel.svg +1 -0
  145. pycforge/ide/resources/icons/check.svg +1 -0
  146. pycforge/ide/resources/icons/chevron-right.svg +1 -0
  147. pycforge/ide/resources/icons/close.svg +1 -0
  148. pycforge/ide/resources/icons/collapse-all.svg +1 -0
  149. pycforge/ide/resources/icons/command-palette.svg +1 -0
  150. pycforge/ide/resources/icons/convert.svg +1 -0
  151. pycforge/ide/resources/icons/copy.svg +1 -0
  152. pycforge/ide/resources/icons/cut.svg +1 -0
  153. pycforge/ide/resources/icons/decision-trace.svg +1 -0
  154. pycforge/ide/resources/icons/details.svg +1 -0
  155. pycforge/ide/resources/icons/diagnostics.svg +1 -0
  156. pycforge/ide/resources/icons/duplicate-line.svg +1 -0
  157. pycforge/ide/resources/icons/exit.svg +1 -0
  158. pycforge/ide/resources/icons/expand-all.svg +1 -0
  159. pycforge/ide/resources/icons/export.svg +1 -0
  160. pycforge/ide/resources/icons/find.svg +1 -0
  161. pycforge/ide/resources/icons/go-to-line.svg +1 -0
  162. pycforge/ide/resources/icons/go-to-output.svg +1 -0
  163. pycforge/ide/resources/icons/go-to-source.svg +1 -0
  164. pycforge/ide/resources/icons/history.svg +1 -0
  165. pycforge/ide/resources/icons/indent.svg +1 -0
  166. pycforge/ide/resources/icons/link-c.svg +1 -0
  167. pycforge/ide/resources/icons/mappings.svg +1 -0
  168. pycforge/ide/resources/icons/module.svg +1 -0
  169. pycforge/ide/resources/icons/move-down.svg +1 -0
  170. pycforge/ide/resources/icons/move-line-down.svg +1 -0
  171. pycforge/ide/resources/icons/move-line-up.svg +1 -0
  172. pycforge/ide/resources/icons/move-up.svg +1 -0
  173. pycforge/ide/resources/icons/next.svg +1 -0
  174. pycforge/ide/resources/icons/open.svg +1 -0
  175. pycforge/ide/resources/icons/outdent.svg +1 -0
  176. pycforge/ide/resources/icons/outline.svg +1 -0
  177. pycforge/ide/resources/icons/paste.svg +1 -0
  178. pycforge/ide/resources/icons/previous.svg +1 -0
  179. pycforge/ide/resources/icons/primary-module.svg +1 -0
  180. pycforge/ide/resources/icons/redo.svg +1 -0
  181. pycforge/ide/resources/icons/remove-document.svg +1 -0
  182. pycforge/ide/resources/icons/replace.svg +1 -0
  183. pycforge/ide/resources/icons/save-as.svg +1 -0
  184. pycforge/ide/resources/icons/save.svg +1 -0
  185. pycforge/ide/resources/icons/select-all.svg +1 -0
  186. pycforge/ide/resources/icons/settings.svg +1 -0
  187. pycforge/ide/resources/icons/show-c.svg +1 -0
  188. pycforge/ide/resources/icons/split-view.svg +1 -0
  189. pycforge/ide/resources/icons/summary.svg +1 -0
  190. pycforge/ide/resources/icons/telemetry.svg +1 -0
  191. pycforge/ide/resources/icons/toggle-comment.svg +1 -0
  192. pycforge/ide/resources/icons/toggle-fold.svg +1 -0
  193. pycforge/ide/resources/icons/undo.svg +1 -0
  194. pycforge/ide/resources/icons/whitespace.svg +1 -0
  195. pycforge/ide/revisions.py +400 -0
  196. pycforge/ide/search_service.py +280 -0
  197. pycforge/ide/session_history.py +150 -0
  198. pycforge/ide/source_editing.py +417 -0
  199. pycforge/ide/source_structure.py +558 -0
  200. pycforge/ide/source_structure_async.py +191 -0
  201. pycforge/ide/supervisor.py +503 -0
  202. pycforge/ide/theme.py +119 -0
  203. pycforge/ide/theme_stylesheet.py +598 -0
  204. pycforge/ide/theme_workspace_stylesheet.py +155 -0
  205. pycforge/ide/visual_tokens.py +142 -0
  206. pycforge/ide/worker_protocol.py +80 -0
  207. pycforge/ide/workspace_search.py +462 -0
  208. pycforge/ide/workspace_session.py +256 -0
  209. pycforge/laboratory/__init__.py +6 -0
  210. pycforge/laboratory/audits.py +2244 -0
  211. pycforge/laboratory/checkpoint_e.py +2685 -0
  212. pycforge/laboratory/cli.py +119 -0
  213. pycforge/laboratory/exit_codes.py +11 -0
  214. pycforge/laboratory/keyword_audit.py +661 -0
  215. pycforge/laboratory/keyword_only_audit.py +481 -0
  216. pycforge-0.15.2.dist-info/METADATA +209 -0
  217. pycforge-0.15.2.dist-info/RECORD +221 -0
  218. pycforge-0.15.2.dist-info/WHEEL +5 -0
  219. pycforge-0.15.2.dist-info/entry_points.txt +5 -0
  220. pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
  221. pycforge-0.15.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,393 @@
1
+ """C IR lowering adapter for the Phase 13 immutable static-record profile.
2
+
3
+ The adapter consumes only published record facts. It does not rediscover
4
+ class semantics, allocate storage, introduce helpers, or own scalar expression
5
+ lowering. Those services are supplied by the cumulative lowerer so record
6
+ lowering remains a small, independently testable representation boundary.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ from dataclasses import dataclass
13
+ from typing import Any, Callable
14
+
15
+ from pycforge.converter.ir.c_ir import (
16
+ CIdentifier,
17
+ CIdentifierRef,
18
+ CMemberAccessExpr,
19
+ CMemberAccessMode,
20
+ CQualifier,
21
+ CRecordDefinition,
22
+ CRecordField,
23
+ CRecordInitializer,
24
+ CStorage,
25
+ CType,
26
+ CVariableDeclaration,
27
+ )
28
+
29
+
30
+ def _sid(prefix: str, *parts: str) -> str:
31
+ return prefix + hashlib.sha256("\x1f".join(parts).encode("utf-8")).hexdigest()[:20]
32
+
33
+
34
+ @dataclass(slots=True)
35
+ class RecordLoweringServices:
36
+ """Services and proved fact indexes required by record lowering.
37
+
38
+ Each fact mapping may use the corresponding semantic ID or source-node ID
39
+ as its key. :class:`RecordCIRLowerer` indexes values by their published
40
+ identity fields, which lets it consume the canonical fact-table mappings
41
+ directly without depending on a particular caller-side index choice.
42
+ """
43
+
44
+ nodes: dict[str, dict[str, Any]]
45
+ definitions: dict[str, dict[str, Any]]
46
+ fields: dict[str, dict[str, Any]]
47
+ initializers: dict[str, dict[str, Any]]
48
+ instances: dict[str, dict[str, Any]]
49
+ bindings: dict[str, dict[str, Any]]
50
+ accesses: dict[str, dict[str, Any]]
51
+ source_bindings_by_id: dict[str, dict[str, Any]]
52
+ generated_names: dict[str, str]
53
+ expression: Callable[..., tuple[tuple[Any, ...], Any]]
54
+ temporary: Callable[..., tuple[CVariableDeclaration, CIdentifierRef]]
55
+ category_type: Callable[[str], CType]
56
+ identifier: Callable[[dict[str, Any], dict[str, Any]], CIdentifier]
57
+ provenance: Callable[..., Any]
58
+ synthetic_provenance: Callable[..., Any]
59
+ reject: Callable[..., Any]
60
+ check_cancellation: Callable[[], None]
61
+
62
+
63
+ class RecordCIRLowerer:
64
+ """Lower complete immutable-record proofs into structured C IR."""
65
+
66
+ def __init__(self, services: RecordLoweringServices) -> None:
67
+ self.services = services
68
+ self.definition_by_record = self._index(services.definitions, "record_id")
69
+ self.field_by_id = self._index(services.fields, "field_id")
70
+ self.initializer_by_id = self._index(services.initializers, "initializer_id")
71
+ self.instance_by_assignment = self._index(services.instances, "assignment_node_id")
72
+ self.binding_by_id = self._index(services.bindings, "binding_id")
73
+ self.access_by_node = self._index(services.accesses, "access_node_id")
74
+ self.constructed_bindings: dict[str, CIdentifier] = {}
75
+
76
+ @staticmethod
77
+ def _index(
78
+ values: dict[str, dict[str, Any]], identity_field: str
79
+ ) -> dict[str, dict[str, Any]]:
80
+ return {
81
+ str(value[identity_field]): value
82
+ for value in values.values()
83
+ if isinstance(value, dict) and isinstance(value.get(identity_field), str)
84
+ }
85
+
86
+ def definitions(self) -> tuple[CRecordDefinition, ...]:
87
+ """Return record declarations in canonical fact-table order."""
88
+
89
+ self.services.check_cancellation()
90
+ declarations: list[CRecordDefinition] = []
91
+ for fact in self.services.definitions.values():
92
+ self.services.check_cancellation()
93
+ if not isinstance(fact, dict):
94
+ self.services.reject(
95
+ "PYC3601", "Static-record definition fact is malformed"
96
+ )
97
+ declarations.append(self._definition(fact))
98
+ return tuple(declarations)
99
+
100
+ def construction(self, node: dict[str, Any]) -> tuple[Any, ...]:
101
+ """Lower one proved constructor assignment with explicit LTR staging."""
102
+
103
+ s = self.services
104
+ s.check_cancellation()
105
+ fact = self.instance_by_assignment.get(node.get("node_id"))
106
+ if fact is None:
107
+ s.reject(
108
+ "PYC3605",
109
+ "Static-record construction lacks a published instance fact",
110
+ node,
111
+ )
112
+ if fact.get("assignment_node_id") != node.get("node_id"):
113
+ s.reject("PYC3605", "Static-record assignment identity is inconsistent", node)
114
+
115
+ definition = self.definition_by_record.get(fact.get("record_id"))
116
+ if definition is None:
117
+ s.reject("PYC3605", "Static-record construction type cannot be resolved", node)
118
+ self._require_closed_definition(definition)
119
+
120
+ initializer = self.initializer_by_id.get(definition.get("initializer_id"))
121
+ if (
122
+ initializer is None
123
+ or initializer.get("record_id") != definition.get("record_id")
124
+ or tuple(initializer.get("field_ids", ()))
125
+ != tuple(definition.get("field_ids", ()))
126
+ or initializer.get("receiver_model")
127
+ != "direct-addressed-initialization-receiver"
128
+ or initializer.get("evaluation_order")
129
+ != "field-declaration-order-left-to-right-once"
130
+ or initializer.get("initialization_completeness")
131
+ != "all-fields-exactly-once"
132
+ ):
133
+ s.reject(
134
+ "PYC3605",
135
+ "Static-record construction lacks its exact initializer proof",
136
+ node,
137
+ )
138
+
139
+ if (
140
+ fact.get("class_node_id") != definition.get("class_node_id")
141
+ or fact.get("mutable") is not False
142
+ or fact.get("storage_model") != "automatic-inline-record"
143
+ or fact.get("ownership_model") != "unique-lexical-owner"
144
+ or fact.get("lifetime_model") != "enclosing-function-activation"
145
+ or fact.get("aliasing_model") != "forbidden"
146
+ or fact.get("cleanup_model") != "none"
147
+ or fact.get("nullability_model") != "non-null-by-construction"
148
+ or fact.get("allocation_model") != "none"
149
+ ):
150
+ s.reject(
151
+ "PYC3606",
152
+ "Static-record instance lacks the automatic no-allocation proof",
153
+ node,
154
+ )
155
+
156
+ binding_fact = self.binding_by_id.get(fact.get("binding_id"))
157
+ if (
158
+ binding_fact is None
159
+ or binding_fact.get("instance_id") != fact.get("instance_id")
160
+ or binding_fact.get("record_id") != fact.get("record_id")
161
+ or binding_fact.get("declaration_node_id") != fact.get("target_node_id")
162
+ or binding_fact.get("single_assignment") is not True
163
+ or binding_fact.get("noalias") is not True
164
+ or binding_fact.get("escapes") is not False
165
+ ):
166
+ s.reject(
167
+ "PYC3606",
168
+ "Static-record construction lacks a unique, non-escaping owner proof",
169
+ node,
170
+ )
171
+
172
+ target = s.nodes.get(fact.get("target_node_id"))
173
+ source_binding = s.source_bindings_by_id.get(fact.get("binding_id"))
174
+ construction = s.nodes.get(fact.get("construction_node_id"))
175
+ if target is None or source_binding is None or construction is None:
176
+ s.reject(
177
+ "PYC3605",
178
+ "Static-record construction source binding or node is absent",
179
+ node,
180
+ )
181
+ if target.get("kind") != "Name" or construction.get("kind") != "Call":
182
+ s.reject("PYC3605", "Static-record construction shape is malformed", node)
183
+
184
+ field_ids = tuple(definition.get("field_ids", ()))
185
+ argument_ids = tuple(fact.get("argument_node_ids", ()))
186
+ if not field_ids or len(argument_ids) != len(field_ids):
187
+ s.reject(
188
+ "PYC3605", "Static-record constructor arity proof is inconsistent", construction
189
+ )
190
+
191
+ prelude: list[Any] = []
192
+ staged_arguments: list[Any] = []
193
+ for ordinal, (field_id, argument_id) in enumerate(
194
+ zip(field_ids, argument_ids)
195
+ ):
196
+ s.check_cancellation()
197
+ field = self.field_by_id.get(field_id)
198
+ argument = s.nodes.get(argument_id)
199
+ if (
200
+ field is None
201
+ or field.get("record_id") != definition.get("record_id")
202
+ or field.get("ordinal") != ordinal
203
+ or field.get("mutable") is not False
204
+ or argument is None
205
+ ):
206
+ s.reject(
207
+ "PYC3605",
208
+ "Static-record constructor field or argument proof is inconsistent",
209
+ argument or construction,
210
+ )
211
+ expression_prelude, expression = s.expression(argument)
212
+ s.check_cancellation()
213
+ prelude.extend(expression_prelude)
214
+ temporary, reference = s.temporary(
215
+ "record_arg",
216
+ construction,
217
+ ordinal,
218
+ s.category_type(str(field.get("category"))),
219
+ expression,
220
+ (argument_id, construction["node_id"], node["node_id"]),
221
+ )
222
+ prelude.append(temporary)
223
+ staged_arguments.append(reference)
224
+
225
+ s.check_cancellation()
226
+ record_spelling = self._planned_name(
227
+ str(definition.get("class_binding_id")),
228
+ s.nodes.get(definition.get("class_node_id"), node),
229
+ "record type",
230
+ )
231
+ initializer_expression = CRecordInitializer(
232
+ _sid("c-record-initializer-", construction["node_id"]),
233
+ CType(record_spelling),
234
+ tuple(staged_arguments),
235
+ s.provenance(construction),
236
+ )
237
+ identifier = s.identifier(source_binding, target)
238
+ declaration = CVariableDeclaration(
239
+ _sid("c-record-instance-", node["node_id"]),
240
+ identifier,
241
+ CType(record_spelling, (CQualifier.CONST,)),
242
+ initializer_expression,
243
+ CStorage.NONE,
244
+ s.provenance(node),
245
+ )
246
+ s.check_cancellation()
247
+ self.constructed_bindings[str(fact["binding_id"])] = identifier
248
+ return tuple(prelude) + (declaration,)
249
+
250
+ def access(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], CMemberAccessExpr]:
251
+ """Lower one statically-bound immutable field read."""
252
+
253
+ s = self.services
254
+ s.check_cancellation()
255
+ fact = self.access_by_node.get(node.get("node_id"))
256
+ if (
257
+ fact is None
258
+ or fact.get("statically_bound") is not True
259
+ or fact.get("access_mode") != "read"
260
+ ):
261
+ s.reject(
262
+ "PYC3607",
263
+ "Static-record field access lacks a complete read proof",
264
+ node,
265
+ )
266
+ field = self.field_by_id.get(fact.get("field_id"))
267
+ binding = self.binding_by_id.get(fact.get("binding_id"))
268
+ if (
269
+ field is None
270
+ or binding is None
271
+ or field.get("record_id") != fact.get("record_id")
272
+ or binding.get("record_id") != fact.get("record_id")
273
+ or binding.get("instance_id") != fact.get("instance_id")
274
+ or fact.get("access_node_id")
275
+ not in tuple(binding.get("allowed_field_access_node_ids", ()))
276
+ or field.get("mutable") is not False
277
+ ):
278
+ s.reject(
279
+ "PYC3607",
280
+ "Static-record receiver or field binding proof is inconsistent",
281
+ node,
282
+ )
283
+
284
+ receiver = s.nodes.get(node.get("fields", {}).get("value"))
285
+ identifier = self.constructed_bindings.get(str(fact.get("binding_id")))
286
+ if receiver is None or receiver.get("kind") != "Name" or identifier is None:
287
+ s.reject(
288
+ "PYC3607",
289
+ "Static-record field access occurs without its unique local owner",
290
+ node,
291
+ )
292
+ reference = CIdentifierRef(
293
+ _sid(
294
+ "c-record-owner-ref-",
295
+ node["node_id"],
296
+ identifier.binding_id,
297
+ ),
298
+ identifier.binding_id,
299
+ s.provenance(receiver),
300
+ )
301
+ s.check_cancellation()
302
+ return (), CMemberAccessExpr(
303
+ _sid("c-record-member-", node["node_id"], str(fact["field_id"])),
304
+ reference,
305
+ str(fact["field_id"]),
306
+ CMemberAccessMode.DIRECT,
307
+ s.provenance(node),
308
+ )
309
+
310
+ def _definition(self, fact: dict[str, Any]) -> CRecordDefinition:
311
+ s = self.services
312
+ s.check_cancellation()
313
+ self._require_closed_definition(fact)
314
+ class_node = s.nodes.get(fact.get("class_node_id"))
315
+ if class_node is None:
316
+ s.reject("PYC3601", "Static-record class source node is absent")
317
+ class_binding_id = str(fact.get("class_binding_id"))
318
+ record_identifier = CIdentifier(
319
+ class_binding_id,
320
+ self._planned_name(class_binding_id, class_node, "record type"),
321
+ s.provenance(class_node),
322
+ )
323
+
324
+ record_fields: list[CRecordField] = []
325
+ for ordinal, field_id in enumerate(tuple(fact.get("field_ids", ()))):
326
+ s.check_cancellation()
327
+ field = self.field_by_id.get(field_id)
328
+ field_node = s.nodes.get((field or {}).get("declaration_node_id"))
329
+ if (
330
+ field is None
331
+ or field.get("record_id") != fact.get("record_id")
332
+ or field.get("ordinal") != ordinal
333
+ or field.get("mutable") is not False
334
+ or field_node is None
335
+ ):
336
+ s.reject(
337
+ "PYC3602",
338
+ "Static-record field order or ownership proof is inconsistent",
339
+ field_node or class_node,
340
+ )
341
+ field_binding_id = str(field["field_id"])
342
+ field_identifier = CIdentifier(
343
+ field_binding_id,
344
+ self._planned_name(field_binding_id, field_node, "record field"),
345
+ s.provenance(field_node),
346
+ )
347
+ record_fields.append(
348
+ CRecordField(
349
+ _sid("c-record-field-", field_binding_id),
350
+ field_identifier,
351
+ s.category_type(str(field.get("category"))),
352
+ s.provenance(field_node),
353
+ )
354
+ )
355
+ return CRecordDefinition(
356
+ _sid("c-record-definition-", str(fact["record_id"])),
357
+ record_identifier,
358
+ tuple(record_fields),
359
+ s.provenance(class_node),
360
+ )
361
+
362
+ def _require_closed_definition(self, fact: dict[str, Any]) -> None:
363
+ if (
364
+ not isinstance(fact.get("record_id"), str)
365
+ or not isinstance(fact.get("class_binding_id"), str)
366
+ or not tuple(fact.get("field_ids", ()))
367
+ or fact.get("mutable") is not False
368
+ or fact.get("storage_model") != "automatic-inline-record"
369
+ or fact.get("ownership_model") != "unique-lexical-owner"
370
+ or fact.get("lifetime_model") != "enclosing-function-activation"
371
+ or fact.get("aliasing_model") != "forbidden"
372
+ or fact.get("cleanup_model") != "none"
373
+ or fact.get("nullability_model") != "non-null-by-construction"
374
+ ):
375
+ node = self.services.nodes.get(fact.get("class_node_id"))
376
+ self.services.reject(
377
+ "PYC3601",
378
+ "Static-record definition lacks the closed automatic ownership proof",
379
+ node,
380
+ )
381
+
382
+ def _planned_name(
383
+ self, binding_id: str, node: dict[str, Any] | None, subject: str
384
+ ) -> str:
385
+ spelling = self.services.generated_names.get(binding_id)
386
+ if not spelling:
387
+ self.services.reject(
388
+ "PYC2942", f"Generated {subject} identifier plan is missing", node
389
+ )
390
+ return str(spelling)
391
+
392
+
393
+ __all__ = ["RecordCIRLowerer", "RecordLoweringServices"]