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,158 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum
5
+ from typing import Any
6
+
7
+
8
+ class Completeness(str, Enum):
9
+ COMPLETE = "complete"
10
+ PARTIAL = "partial"
11
+
12
+
13
+ class ValueCategory(str, Enum):
14
+ INTEGER = "integer-like"
15
+ FLOAT = "floating-like"
16
+ BOOLEAN = "boolean-like"
17
+ STRING = "string-like"
18
+ LIST = "fixed-list-like"
19
+ TUPLE = "fixed-tuple-like"
20
+ DICTIONARY = "fixed-dictionary-like"
21
+ RECORD = "static-record-like"
22
+ NONE = "none-like"
23
+ CALLABLE = "callable-like"
24
+ UNKNOWN = "unknown"
25
+ CONTRADICTORY = "contradictory"
26
+
27
+
28
+ class EffectKind(str, Enum):
29
+ PURE = "pure-value-construction"
30
+ READS_STATE = "reads-state"
31
+ WRITES_STATE = "writes-state"
32
+ MAY_FAIL = "may-encounter-unsupported-python-failure"
33
+ CONTROL_FLOW = "control-flow-boundary"
34
+
35
+
36
+ class SupportState(str, Enum):
37
+ SUPPORTED_DIRECT = "SupportedDirect"
38
+ SUPPORTED_WITH_HELPER = "SupportedWithHelper"
39
+ SUPPORTED_APPROXIMATION = "SupportedApproximation"
40
+ UNSUPPORTED = "Unsupported"
41
+ BLOCKED = "BlockedByDependency"
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class FactProvenance:
46
+ source_node_ids: tuple[str, ...]
47
+ evidence: tuple[str, ...]
48
+
49
+ def to_dict(self) -> dict[str, Any]:
50
+ return {"source_node_ids": list(self.source_node_ids), "evidence": list(self.evidence)}
51
+
52
+
53
+ @dataclass(frozen=True, slots=True)
54
+ class FactRecord:
55
+ key: str
56
+ value: Any
57
+ provenance: FactProvenance
58
+
59
+ def to_dict(self) -> dict[str, Any]:
60
+ value = self.value.value if isinstance(self.value, Enum) else self.value
61
+ if hasattr(value, "to_dict"):
62
+ value = value.to_dict()
63
+ return {"key": self.key, "value": value, "provenance": self.provenance.to_dict()}
64
+
65
+
66
+ @dataclass(frozen=True, slots=True)
67
+ class FactTable:
68
+ schema_version: str
69
+ table_id: str
70
+ producer_stage: str
71
+ key_domain: str
72
+ completeness: Completeness
73
+ invalidation_dependencies: tuple[str, ...]
74
+ records: tuple[FactRecord, ...]
75
+
76
+ def to_dict(self) -> dict[str, Any]:
77
+ return {
78
+ "schema_version": self.schema_version,
79
+ "table_id": self.table_id,
80
+ "producer_stage": self.producer_stage,
81
+ "key_domain": self.key_domain,
82
+ "completeness": self.completeness.value,
83
+ "invalidation_dependencies": list(self.invalidation_dependencies),
84
+ "records": [record.to_dict() for record in self.records],
85
+ }
86
+
87
+
88
+ @dataclass(frozen=True, slots=True)
89
+ class ScopeFact:
90
+ scope_id: str
91
+ scope_kind: str
92
+ owner_node_id: str
93
+ parent_scope_id: str | None
94
+ child_scope_ids: tuple[str, ...]
95
+ binding_ids: tuple[str, ...]
96
+
97
+ def to_dict(self) -> dict[str, Any]:
98
+ return {
99
+ "scope_id": self.scope_id,
100
+ "scope_kind": self.scope_kind,
101
+ "owner_node_id": self.owner_node_id,
102
+ "parent_scope_id": self.parent_scope_id,
103
+ "child_scope_ids": list(self.child_scope_ids),
104
+ "binding_ids": list(self.binding_ids),
105
+ }
106
+
107
+
108
+ @dataclass(frozen=True, slots=True)
109
+ class BindingFact:
110
+ binding_id: str
111
+ scope_id: str
112
+ source_name: str
113
+ binding_kind: str
114
+ declaration_node_id: str
115
+ occurrence_node_ids: tuple[str, ...]
116
+
117
+ def to_dict(self) -> dict[str, Any]:
118
+ return {
119
+ "binding_id": self.binding_id,
120
+ "scope_id": self.scope_id,
121
+ "source_name": self.source_name,
122
+ "binding_kind": self.binding_kind,
123
+ "declaration_node_id": self.declaration_node_id,
124
+ "occurrence_node_ids": list(self.occurrence_node_ids),
125
+ }
126
+
127
+
128
+ @dataclass(frozen=True, slots=True)
129
+ class RepresentationPlan:
130
+ plan_id: str
131
+ decision_key: str
132
+ c_type: str | None
133
+ passing: str
134
+ ownership: str
135
+ lifetime_region: str
136
+ obligations: tuple[str, ...]
137
+ unresolved_obligations: tuple[str, ...]
138
+
139
+ def to_dict(self) -> dict[str, Any]:
140
+ return {
141
+ "plan_id": self.plan_id,
142
+ "decision_key": self.decision_key,
143
+ "c_type": self.c_type,
144
+ "passing": self.passing,
145
+ "ownership": self.ownership,
146
+ "lifetime_region": self.lifetime_region,
147
+ "obligations": list(self.obligations),
148
+ "unresolved_obligations": list(self.unresolved_obligations),
149
+ }
150
+
151
+
152
+ @dataclass(frozen=True, slots=True)
153
+ class NamePlan:
154
+ binding_id: str
155
+ generated_name: str
156
+
157
+ def to_dict(self) -> dict[str, str]:
158
+ return {"binding_id": self.binding_id, "generated_name": self.generated_name}
@@ -0,0 +1,464 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import re
5
+ from collections import defaultdict, deque
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable
8
+
9
+ from pycforge.converter.contracts.identifiers import (
10
+ C11_EXTERNAL_IDENTIFIERS as _C11_EXTERNAL_IDENTIFIERS,
11
+ C_KEYWORDS as _C_KEYWORDS,
12
+ TARGET_RESERVED_NAMES as _TARGET_RESERVED_NAMES,
13
+ )
14
+ from pycforge.converter.keyword_calls.model import KEYWORD_CALL_OBLIGATIONS
15
+ from pycforge.converter.keyword_only_calls.model import (
16
+ KEYWORD_ONLY_CALL_OBLIGATIONS,
17
+ )
18
+
19
+ from .model import (EffectKind, NamePlan, RepresentationPlan, SupportState,
20
+ ValueCategory)
21
+ from .symbols import PythonIRIndex
22
+
23
+ class AnalysisCanceled(Exception):
24
+ pass
25
+
26
+
27
+ def stable_id(prefix: str, *parts: str) -> str:
28
+ return prefix + hashlib.sha256("\x1f".join(parts).encode()).hexdigest()[:20]
29
+
30
+
31
+ @dataclass(frozen=True, slots=True)
32
+ class RulePlan:
33
+ plan_id: str
34
+ decision_key: str
35
+ rule_id: str
36
+ rule_version: str
37
+ source_node_id: str
38
+ support_state: SupportState
39
+ facts_used: tuple[str, ...]
40
+ semantic_obligations: tuple[str, ...]
41
+ resolved_obligations: tuple[str, ...]
42
+ unresolved_obligations: tuple[str, ...]
43
+ helper_requirements: tuple[str, ...]
44
+ explanation_tokens: tuple[str, ...]
45
+
46
+ def to_dict(self) -> dict[str, Any]:
47
+ return {
48
+ "plan_id": self.plan_id,
49
+ "decision_key": self.decision_key,
50
+ "rule_id": self.rule_id,
51
+ "rule_version": self.rule_version,
52
+ "source_node_id": self.source_node_id,
53
+ "support_state": self.support_state.value,
54
+ "facts_used": list(self.facts_used),
55
+ "semantic_obligations": list(self.semantic_obligations),
56
+ "resolved_obligations": list(self.resolved_obligations),
57
+ "unresolved_obligations": list(self.unresolved_obligations),
58
+ "helper_requirements": list(self.helper_requirements),
59
+ "explanation_tokens": list(self.explanation_tokens),
60
+ }
61
+
62
+
63
+ @dataclass(frozen=True, slots=True)
64
+ class RuleDefinition:
65
+ rule_id: str
66
+ rule_version: str
67
+ node_kind: str
68
+ specificity: tuple[int, ...]
69
+ predicate: Callable[[dict[str, Any], dict[str, ValueCategory]], bool]
70
+ obligations: tuple[str, ...]
71
+
72
+
73
+ class FrozenRuleRegistry:
74
+ def __init__(self, rules: tuple[RuleDefinition, ...]) -> None:
75
+ ordered = tuple(sorted(rules, key=lambda r: (r.node_kind, tuple(-x for x in r.specificity), r.rule_id, r.rule_version)))
76
+ ids = [(r.rule_id, r.rule_version) for r in ordered]
77
+ if len(ids) != len(set(ids)):
78
+ raise ValueError("duplicate rule identity")
79
+ self._rules = ordered
80
+ self._audit_overlaps()
81
+
82
+ @property
83
+ def manifest(self) -> tuple[dict[str, Any], ...]:
84
+ return tuple({"rule_id": r.rule_id, "rule_version": r.rule_version, "node_kind": r.node_kind, "specificity": list(r.specificity)} for r in self._rules)
85
+
86
+ def select(self, node: dict[str, Any], categories: dict[str, ValueCategory], decision_key: str) -> RulePlan | None:
87
+ matches = [r for r in self._rules if r.node_kind == node["kind"] and r.predicate(node, categories)]
88
+ if not matches:
89
+ return None
90
+ matches.sort(key=lambda r: (tuple(-x for x in r.specificity), r.rule_id, r.rule_version))
91
+ best = matches[0]
92
+ if len(matches) > 1 and matches[1].specificity == best.specificity:
93
+ raise ValueError(f"ambiguous best rule for {node['node_id']}")
94
+ obligations = best.obligations
95
+ return RulePlan(
96
+ stable_id("plan-", decision_key, best.rule_id, best.rule_version), decision_key,
97
+ best.rule_id, best.rule_version, node["node_id"], SupportState.SUPPORTED_DIRECT,
98
+ tuple(sorted({f"value-category:{categories.get(node['node_id'], ValueCategory.UNKNOWN).value}"})),
99
+ obligations, obligations, (), (),
100
+ ("selected", best.rule_id, "for", node["kind"]),
101
+ )
102
+
103
+ def _audit_overlaps(self) -> None:
104
+ for i, left in enumerate(self._rules):
105
+ for right in self._rules[i + 1:]:
106
+ if left.node_kind == right.node_kind and left.specificity == right.specificity:
107
+ raise ValueError(f"unapproved equal-specificity overlap: {left.rule_id}/{right.rule_id}")
108
+
109
+
110
+ def default_registry(
111
+ *,
112
+ include_records: bool = False,
113
+ include_numeric: bool = False,
114
+ include_conditional_regions: bool = False,
115
+ include_keyword_calls: bool = False,
116
+ include_keyword_only_calls: bool = False,
117
+ ) -> FrozenRuleRegistry:
118
+ known = lambda node, cats: cats.get(node["node_id"]) not in {None, ValueCategory.UNKNOWN, ValueCategory.CONTRADICTORY}
119
+ supported_value = lambda node, cats: cats.get(node["node_id"]) in {ValueCategory.INTEGER, ValueCategory.FLOAT, ValueCategory.BOOLEAN, ValueCategory.STRING}
120
+ representable_name = lambda node, cats: cats.get(node["node_id"]) in {ValueCategory.INTEGER, ValueCategory.FLOAT, ValueCategory.BOOLEAN, ValueCategory.STRING, ValueCategory.CALLABLE}
121
+ numeric_arithmetic = lambda node, cats: node["kind"] == "BinOp" and _operator_kind(node) in {"Add", "Sub", "Mult", "Div"} and cats.get(node["node_id"]) in {ValueCategory.INTEGER, ValueCategory.FLOAT}
122
+ rules = (
123
+ RuleDefinition("phase6.literal.known", "0.6", "Constant", (10,), supported_value, ("literal-representation-known",)),
124
+ RuleDefinition("phase6.name.bound", "0.6", "Name", (10,), representable_name, ("binding-resolved", "representation-known")),
125
+ RuleDefinition("phase6.assignment.simple", "0.6", "Assign", (10,), supported_value, ("target-binding-resolved", "rhs-representation-known", "evaluation-order-preserved")),
126
+ RuleDefinition("phase6.numeric.arithmetic", "0.6", "BinOp", (20,), numeric_arithmetic, ("numeric-representation-selected", "overflow-and-division-policy-declared", "left-before-right-evaluation")),
127
+ RuleDefinition("phase6.return.simple", "0.6", "Return", (10,), lambda n,c: known(n,c) and n.get("return_eligible", True), ("return-representation-compatible",)),
128
+ RuleDefinition("phase6.function.annotated", "0.6", "FunctionDef", (10,), lambda n,c: known(n,c) and n.get("function_eligible", True), ("annotation-trust-policy-applied", "parameter-representations-known", "return-representation-known")),
129
+ RuleDefinition("phase8.if.control", "0.8", "If", (20,), lambda n,c: True, ("truthiness-representation-known", "branch-order-preserved", "branch-binding-policy-declared")),
130
+ RuleDefinition("phase8.boolean.short_circuit", "0.8", "BoolOp", (20,), lambda n,c: c.get(n["node_id"]) is ValueCategory.BOOLEAN, ("truthiness-representation-known", "short-circuit-order-preserved")),
131
+ RuleDefinition("phase8.comparison.chain", "0.8", "Compare", (20,), lambda n,c: c.get(n["node_id"]) is ValueCategory.BOOLEAN, ("comparison-representations-compatible", "operands-evaluated-once-in-source-order")),
132
+ RuleDefinition("phase8.while.control", "0.8", "While", (20,), lambda n,c: True, ("truthiness-representation-known", "zero-iteration-policy-declared", "break-continue-scope-valid")),
133
+ RuleDefinition("phase8.for.range", "0.8", "For", (20,), lambda n,c: True, ("bounded-range-form-proved", "loop-bound-evaluated-once", "loop-target-lifetime-declared")),
134
+ RuleDefinition("phase8.break.control", "0.8", "Break", (20,), lambda n,c: True, ("enclosing-loop-proved",)),
135
+ RuleDefinition("phase8.continue.control", "0.8", "Continue", (20,), lambda n,c: True, ("enclosing-loop-proved",)),
136
+ RuleDefinition("phase8.range.bound", "0.8", "Call", (20,), lambda n,c: n.get("call_target_kind") == "recognized-range", ("range-call-shape-validated", "loop-bound-evaluated-once")),
137
+ RuleDefinition("phase9.call.understood_target", "0.9", "Call", (30,), lambda n,c: n.get("call_target_kind") == "understood-source-function", ("target-resolved-once", "positional-arity-exact", "argument-representations-compatible", "arguments-evaluated-left-to-right-once", "parameter-ownership-boundary-explicit", "recursion-policy-satisfied")),
138
+ RuleDefinition("phase11.container.list_literal", "0.11", "List", (30,), lambda n,c: bool(n.get("container_shape_supported")), ("fixed-capacity-proved", "homogeneous-element-representation-proved", "local-storage-lifetime-proved", "no-alias-or-cleanup-required", "elements-evaluated-left-to-right-once")),
139
+ RuleDefinition("phase11.container.tuple_literal", "0.11", "Tuple", (30,), lambda n,c: bool(n.get("container_shape_supported")), ("fixed-capacity-proved", "homogeneous-element-representation-proved", "tuple-immutability-boundary-proved", "local-storage-lifetime-proved", "elements-evaluated-left-to-right-once")),
140
+ RuleDefinition("phase11.container.dict_literal", "0.11", "Dict", (30,), lambda n,c: bool(n.get("container_shape_supported")), ("fixed-capacity-proved", "homogeneous-key-value-representations-proved", "distinct-literal-keys-proved", "insertion-order-preserved", "no-runtime-lookup-or-cleanup-required")),
141
+ RuleDefinition("phase11.container.index.proved", "0.11", "Subscript", (30,), lambda n,c: bool(n.get("container_access_supported")), ("container-binding-resolved", "index-or-key-statically-proved", "bounds-or-presence-proved", "result-representation-known")),
142
+ RuleDefinition("phase11.container.for.bounded", "0.11", "For", (30,), lambda n,c: bool(n.get("container_iteration_supported")), ("container-binding-resolved", "fixed-iteration-bound-proved", "iteration-order-preserved", "loop-target-lifetime-declared", "break-continue-scope-valid")),
143
+ RuleDefinition("phase11.container.assignment", "0.11", "Assign", (30,), lambda n,c: bool(n.get("container_binding_supported")), ("target-binding-resolved", "fixed-container-shape-resolved", "single-assignment-proved", "evaluation-order-preserved")),
144
+ RuleDefinition("phase11.container.name", "0.11", "Name", (30,), lambda n,c: bool(n.get("container_name_supported")), ("container-binding-resolved", "container-use-context-approved")),
145
+ RuleDefinition("phase12.module.document", "0.12", "ModuleDocument", (40,), lambda n,c: bool(n.get("module_document_supported")), ("module-identity-canonical", "logical-source-identity-unique", "explicit-sourcebundle-membership-proved", "module-top-level-profile-closed")),
146
+ RuleDefinition("phase12.module.import_from", "0.12", "ImportFrom", (40,), lambda n,c: bool(n.get("module_import_supported")), ("explicit-sourcebundle-membership-proved", "import-target-function-proved", "module-namespace-binding-unique", "dependency-order-proved", "no-runtime-module-initialization", "no-source-driven-discovery")),
147
+ RuleDefinition("phase12.module.imported_binding", "0.12", "alias", (40,), lambda n,c: bool(n.get("module_alias_supported")), ("imported-function-binding-exact", "alias-not-rebound-or-reexported", "target-signature-reused", "no-module-object-materialized")),
148
+ RuleDefinition("phase12.module.function_namespace", "0.12", "FunctionDef", (40,), lambda n,c: bool(n.get("module_function_supported")) and n.get("function_eligible", True), ("module-function-membership-proved", "bundle-global-name-collision-absent", "source-function-external-linkage", "prototype-definition-module-order-consistent")),
149
+ RuleDefinition("phase12.module.cross_call", "0.12", "Call", (40,), lambda n,c: bool(n.get("cross_module_call_supported")), ("foreign-target-binding-resolved-once", "target-signature-reused", "arguments-evaluated-left-to-right-once", "bundle-call-graph-acyclic", "source-output-call-mapping-required")),
150
+ RuleDefinition("phase12.module.initialization", "0.12", "ModuleInitialization", (40,), lambda n,c: bool(n.get("module_initialization_supported")), ("dependency-graph-acyclic", "dependency-first-order-deterministic", "runtime-module-initialization-absent")),
151
+ RuleDefinition("phase12.module.bundle_assembly", "0.12", "ModuleBundleAssembly", (40,), lambda n,c: bool(n.get("module_bundle_assembly_supported")), ("single-translation-unit", "all-source-prototypes-before-definitions", "registered-includes-only", "no-compile-link-or-execution", "all-source-mappings-document-qualified")),
152
+ )
153
+ if include_records:
154
+ rules += (
155
+ RuleDefinition("phase13.record.class", "0.13", "ClassDef", (50,), lambda n,c: bool(n.get("record_class_supported")), ("record-layout-closed", "object-model-surface-closed", "automatic-storage-policy-selected")),
156
+ RuleDefinition("phase13.record.field", "0.13", "AnnAssign", (50,), lambda n,c: bool(n.get("record_field_supported")), ("field-ordinal-and-type-proved", "field-default-absent", "field-name-unique")),
157
+ RuleDefinition("phase13.record.initializer", "0.13", "FunctionDef", (50,), lambda n,c: bool(n.get("record_initializer_supported")), ("initializer-signature-exact", "field-coverage-complete", "initializer-effects-bounded")),
158
+ RuleDefinition("phase13.record.construction", "0.13", "Call", (50,), lambda n,c: bool(n.get("record_construction_supported")), ("constructor-target-static", "arguments-evaluated-left-to-right-once", "aggregate-fully-initialized")),
159
+ RuleDefinition("phase13.record.binding", "0.13", "Assign", (50,), lambda n,c: bool(n.get("record_binding_supported")), ("fresh-function-local-owner", "single-construction-proved", "alias-rebind-escape-absent")),
160
+ RuleDefinition("phase13.record.name", "0.13", "Name", (50,), lambda n,c: bool(n.get("record_name_supported")), ("record-binding-resolved", "record-use-context-approved")),
161
+ RuleDefinition("phase13.record.attribute_read", "0.13", "Attribute", (50,), lambda n,c: bool(n.get("record_access_supported")), ("record-binding-resolved", "field-binding-exact", "post-initialization-read-proved")),
162
+ )
163
+ if include_numeric:
164
+ rules += (
165
+ RuleDefinition(
166
+ "phase14.numeric.floor_arithmetic",
167
+ "0.14",
168
+ "BinOp",
169
+ (60,),
170
+ lambda n, c: bool(n.get("numeric_operation_supported")),
171
+ (
172
+ "exact-int64-operands-proved",
173
+ "direct-safe-divisor-literal-proved",
174
+ "left-before-right-evaluation-once",
175
+ "helper-preconditions-proved",
176
+ "python-floor-semantics-preserved",
177
+ "no-runtime-failure-channel",
178
+ "scalar-value-ownership-by-value",
179
+ "allocation-and-cleanup-absent",
180
+ "source-provenance-anchored",
181
+ "cancellation-safe-points-honored",
182
+ "target-contract-exact",
183
+ ),
184
+ ),
185
+ )
186
+ if include_conditional_regions:
187
+ conditional_obligations = (
188
+ "scalar-operand-representations-proved",
189
+ "unconditional-prefix-proved",
190
+ "guard-polarity-proved",
191
+ "short-circuit-order-preserved",
192
+ "operands-evaluated-left-to-right-once",
193
+ "prerequisite-statements-branch-contained",
194
+ "intermediate-values-reused-without-reevaluation",
195
+ "structured-c-ir-only",
196
+ "result-materialized-once",
197
+ "allocation-and-cleanup-absent",
198
+ "runtime-failure-channel-unchanged",
199
+ "source-provenance-anchored",
200
+ "cancellation-safe-points-honored",
201
+ "target-contract-exact",
202
+ )
203
+ rules += (
204
+ RuleDefinition(
205
+ "phase14.conditional.boolean_region",
206
+ "0.14.1",
207
+ "BoolOp",
208
+ (70,),
209
+ lambda n, c: bool(n.get("conditional_boolean_region_supported")),
210
+ conditional_obligations,
211
+ ),
212
+ RuleDefinition(
213
+ "phase14.conditional.comparison_region",
214
+ "0.14.1",
215
+ "Compare",
216
+ (70,),
217
+ lambda n, c: bool(n.get("conditional_comparison_region_supported")),
218
+ conditional_obligations,
219
+ ),
220
+ )
221
+ if include_keyword_calls:
222
+ rules += (
223
+ RuleDefinition(
224
+ "phase14.keyword_call.exact_binding",
225
+ "0.14.2",
226
+ "Call",
227
+ (80,),
228
+ lambda n, c: bool(n.get("keyword_call_binding_supported")),
229
+ KEYWORD_CALL_OBLIGATIONS,
230
+ ),
231
+ )
232
+ if include_keyword_only_calls:
233
+ rules += (
234
+ RuleDefinition(
235
+ "phase14.keyword_only_call.exact_binding",
236
+ "0.14.3",
237
+ "Call",
238
+ (90,),
239
+ lambda n, c: bool(n.get("keyword_only_call_binding_supported")),
240
+ KEYWORD_ONLY_CALL_OBLIGATIONS,
241
+ ),
242
+ )
243
+ return FrozenRuleRegistry(rules)
244
+
245
+
246
+ def _operator_kind(node: dict[str, Any]) -> str | None:
247
+ return node.get("op_kind")
248
+
249
+
250
+ class AnalysisPlanner:
251
+ def analyze_categories(self, module: dict[str, Any], binding_by_occurrence: dict[str, str], binding_categories: dict[str, ValueCategory], call_return_categories: dict[str, ValueCategory] | None = None, cancellation: Any | None = None, container_access_categories: dict[str, ValueCategory] | None = None, container_iteration_categories: dict[str, ValueCategory] | None = None, record_access_categories: dict[str, ValueCategory] | None = None) -> dict[str, ValueCategory]:
252
+ index = PythonIRIndex(module)
253
+ categories: dict[str, ValueCategory] = {}
254
+ call_return_categories = call_return_categories or {}
255
+ container_access_categories = container_access_categories or {}
256
+ container_iteration_categories = container_iteration_categories or {}
257
+ record_access_categories = record_access_categories or {}
258
+ bindings = dict(binding_categories)
259
+ parents: dict[str, list[str]] = defaultdict(list)
260
+ edge_count = 0
261
+ for parent_id, parent in index.nodes.items():
262
+ for child_id in index.child_ids(parent):
263
+ parents[child_id].append(parent_id)
264
+ edge_count += 1
265
+ occurrences_by_binding: dict[str, list[str]] = defaultdict(list)
266
+ for occurrence_id, binding_id in binding_by_occurrence.items():
267
+ if occurrence_id in index.nodes:
268
+ occurrences_by_binding[binding_id].append(occurrence_id)
269
+ for node_id, node in index.nodes.items():
270
+ if node["kind"] == "Constant":
271
+ value = node["fields"].get("value")
272
+ categories[node_id] = self._literal_category(value)
273
+ elif node["kind"] == "Name" and node["fields"].get("id") in {"int", "float", "bool", "str"}:
274
+ categories[node_id] = ValueCategory.CALLABLE
275
+
276
+ queue = deque(index.nodes)
277
+ queued = set(index.nodes)
278
+
279
+ def schedule(node_ids: list[str]) -> None:
280
+ for scheduled_id in sorted(set(node_ids), key=index.ordinals.__getitem__):
281
+ if scheduled_id not in queued:
282
+ queue.append(scheduled_id)
283
+ queued.add(scheduled_id)
284
+
285
+ steps = 0
286
+ maximum_steps = max(64, (len(index.nodes) + edge_count + len(binding_by_occurrence)) * 16)
287
+ while queue:
288
+ if cancellation is not None and cancellation.is_canceled:
289
+ raise AnalysisCanceled
290
+ steps += 1
291
+ if steps > maximum_steps:
292
+ raise RuntimeError("value-category analysis did not converge")
293
+ node_id = queue.popleft()
294
+ queued.remove(node_id)
295
+ node = index.nodes[node_id]
296
+ prior = categories.get(node_id, ValueCategory.UNKNOWN)
297
+ category = self._infer_node(node, index, categories, binding_by_occurrence, bindings, call_return_categories, container_access_categories, record_access_categories)
298
+ if category != prior:
299
+ categories[node_id] = category
300
+ schedule(parents.get(node_id, []))
301
+ if node["kind"] == "Assign":
302
+ value_category = categories.get(node["fields"].get("value"), ValueCategory.UNKNOWN)
303
+ for target_id in node["fields"].get("targets", []):
304
+ binding_id = binding_by_occurrence.get(target_id)
305
+ joined = self._join_categories(bindings.get(binding_id, ValueCategory.UNKNOWN), value_category)
306
+ if binding_id and bindings.get(binding_id, ValueCategory.UNKNOWN) != joined:
307
+ bindings[binding_id] = joined
308
+ schedule(occurrences_by_binding.get(binding_id, []))
309
+ elif node["kind"] == "For":
310
+ target_id = node["fields"].get("target")
311
+ binding_id = binding_by_occurrence.get(target_id)
312
+ iterator_id = node["fields"].get("iter")
313
+ iterator = index.nodes.get(iterator_id, {})
314
+ target_category = container_iteration_categories.get(node_id)
315
+ if target_category is None and iterator.get("kind") == "Call":
316
+ target_category = ValueCategory.INTEGER
317
+ if target_category is None:
318
+ continue
319
+ joined = self._join_categories(bindings.get(binding_id, ValueCategory.UNKNOWN), target_category)
320
+ if binding_id and bindings.get(binding_id, ValueCategory.UNKNOWN) != joined:
321
+ bindings[binding_id] = joined
322
+ schedule(occurrences_by_binding.get(binding_id, []))
323
+ for node_id in index.nodes:
324
+ categories.setdefault(node_id, ValueCategory.UNKNOWN)
325
+ binding_categories.clear()
326
+ binding_categories.update(bindings)
327
+ return categories
328
+
329
+ def _infer_node(self, node: dict[str, Any], index: PythonIRIndex, categories: dict[str, ValueCategory], occurrence_bindings: dict[str, str], binding_categories: dict[str, ValueCategory], call_return_categories: dict[str, ValueCategory], container_access_categories: dict[str, ValueCategory], record_access_categories: dict[str, ValueCategory]) -> ValueCategory:
330
+ kind, fields = node["kind"], node["fields"]
331
+ if kind == "Name":
332
+ return binding_categories.get(occurrence_bindings.get(node["node_id"], ""), categories.get(node["node_id"], ValueCategory.UNKNOWN))
333
+ if kind == "BoolOp":
334
+ values = [categories.get(item, ValueCategory.UNKNOWN) for item in fields.get("values", [])]
335
+ if values and all(item is ValueCategory.BOOLEAN for item in values):
336
+ return ValueCategory.BOOLEAN
337
+ if any(item is ValueCategory.CONTRADICTORY for item in values) or any(item not in {ValueCategory.UNKNOWN, ValueCategory.BOOLEAN} for item in values):
338
+ return ValueCategory.CONTRADICTORY
339
+ return ValueCategory.UNKNOWN
340
+ if kind == "Compare":
341
+ operands = [fields.get("left")] + list(fields.get("comparators", []))
342
+ values = [categories.get(item, ValueCategory.UNKNOWN) for item in operands]
343
+ supported = {ValueCategory.INTEGER, ValueCategory.FLOAT, ValueCategory.BOOLEAN}
344
+ if values and all(item == values[0] and item in supported for item in values):
345
+ return ValueCategory.BOOLEAN
346
+ if any(item is ValueCategory.CONTRADICTORY for item in values) or (all(item is not ValueCategory.UNKNOWN for item in values) and len(set(values)) > 1):
347
+ return ValueCategory.CONTRADICTORY
348
+ return ValueCategory.UNKNOWN
349
+ if kind == "BinOp":
350
+ left, right = categories.get(fields.get("left"), ValueCategory.UNKNOWN), categories.get(fields.get("right"), ValueCategory.UNKNOWN)
351
+ op_id = fields.get("op")
352
+ op_kind = index.nodes.get(op_id, {}).get("kind")
353
+ if op_kind == "Div":
354
+ if left is ValueCategory.FLOAT and right is ValueCategory.FLOAT:
355
+ return ValueCategory.FLOAT
356
+ if ValueCategory.UNKNOWN in {left, right}:
357
+ return ValueCategory.UNKNOWN
358
+ return ValueCategory.CONTRADICTORY
359
+ if left == right and left in {ValueCategory.INTEGER, ValueCategory.FLOAT, ValueCategory.STRING}:
360
+ return left
361
+ if ValueCategory.CONTRADICTORY in {left, right} or (left != right and ValueCategory.UNKNOWN not in {left, right}):
362
+ return ValueCategory.CONTRADICTORY
363
+ if kind == "UnaryOp":
364
+ operand = categories.get(fields.get("operand"), ValueCategory.UNKNOWN)
365
+ op_kind = index.nodes.get(fields.get("op"), {}).get("kind")
366
+ if op_kind == "Not" and operand in {ValueCategory.BOOLEAN, ValueCategory.INTEGER, ValueCategory.FLOAT}:
367
+ return ValueCategory.BOOLEAN
368
+ if op_kind in {"UAdd", "USub"} and operand in {ValueCategory.INTEGER, ValueCategory.FLOAT}:
369
+ return operand
370
+ return ValueCategory.CONTRADICTORY if operand is ValueCategory.CONTRADICTORY else ValueCategory.UNKNOWN
371
+ if kind == "Call":
372
+ return call_return_categories.get(node["node_id"], ValueCategory.UNKNOWN)
373
+ if kind == "List":
374
+ return ValueCategory.LIST
375
+ if kind == "Tuple":
376
+ return ValueCategory.TUPLE
377
+ if kind == "Dict":
378
+ return ValueCategory.DICTIONARY
379
+ if kind == "Subscript":
380
+ return container_access_categories.get(node["node_id"], ValueCategory.UNKNOWN)
381
+ if kind == "Attribute":
382
+ return record_access_categories.get(node["node_id"], ValueCategory.UNKNOWN)
383
+ if kind in {"Assign", "Return", "Expr"}:
384
+ return categories.get(fields.get("value"), ValueCategory.UNKNOWN)
385
+ if kind == "FunctionDef":
386
+ return ValueCategory.CALLABLE
387
+ if kind == "ClassDef":
388
+ return ValueCategory.CALLABLE
389
+ return categories.get(node["node_id"], ValueCategory.UNKNOWN)
390
+
391
+ @staticmethod
392
+ def _join_categories(left: ValueCategory, right: ValueCategory) -> ValueCategory:
393
+ if left is ValueCategory.UNKNOWN:
394
+ return right
395
+ if right is ValueCategory.UNKNOWN:
396
+ return left
397
+ if left is right:
398
+ return left
399
+ return ValueCategory.CONTRADICTORY
400
+
401
+ @staticmethod
402
+ def _literal_category(value: Any) -> ValueCategory:
403
+ if isinstance(value,(list,tuple)) and len(value)>=2 and tuple(value[:2]) == ("unsupported-python-value","float-nonfinite"):
404
+ return ValueCategory.FLOAT
405
+ if value is None: return ValueCategory.NONE
406
+ if isinstance(value, bool): return ValueCategory.BOOLEAN
407
+ if isinstance(value, int): return ValueCategory.INTEGER
408
+ if isinstance(value, float): return ValueCategory.FLOAT
409
+ if isinstance(value, str): return ValueCategory.STRING
410
+ return ValueCategory.UNKNOWN
411
+
412
+ def representation(self, decision_key: str, category: ValueCategory) -> RepresentationPlan:
413
+ mapping = {
414
+ ValueCategory.INTEGER: ("int64_t", "value", "not-applicable", ("fixed-width-overflow-policy",)),
415
+ ValueCategory.FLOAT: ("double", "value", "not-applicable", ("floating-policy",)),
416
+ ValueCategory.BOOLEAN: ("bool", "value", "not-applicable", ("boolean-representation",)),
417
+ ValueCategory.STRING: ("const char *", "pointer", "borrowed", ("utf8-literal-lifetime",)),
418
+ ValueCategory.LIST: ("bounded-local-array", "local-only", "automatic-owner", ("fixed-capacity", "no-alias", "no-cleanup")),
419
+ ValueCategory.TUPLE: ("bounded-local-const-array", "local-only", "automatic-owner", ("fixed-capacity", "immutable-observation", "no-cleanup")),
420
+ ValueCategory.DICTIONARY: ("parallel-bounded-local-arrays", "local-only", "automatic-owner", ("fixed-capacity", "insertion-order", "no-runtime-lookup", "no-cleanup")),
421
+ ValueCategory.RECORD: ("static-record", "local-only", "automatic-owner", ("fully-initialized", "no-alias", "no-escape", "no-cleanup")),
422
+ ValueCategory.NONE: (None, "not-applicable", "not-applicable", ("none-use-policy",)),
423
+ ValueCategory.CALLABLE: ("function-signature", "direct-call-only", "not-applicable", ("understood-target-only",)),
424
+ }
425
+ if category not in mapping:
426
+ return RepresentationPlan(stable_id("repr-", decision_key), decision_key, None, "unresolved", "unresolved", "unresolved", (), ("value-category-known",))
427
+ c_type, passing, ownership, obligations = mapping[category]
428
+ return RepresentationPlan(stable_id("repr-", decision_key), decision_key, c_type, passing, ownership, "lexical-scope", obligations, ())
429
+
430
+ def effects(self, node: dict[str, Any]) -> tuple[EffectKind, ...]:
431
+ kind = node["kind"]
432
+ effects = {EffectKind.PURE}
433
+ if kind == "Name": effects.add(EffectKind.READS_STATE)
434
+ if kind in {"Assign", "AnnAssign", "AugAssign"}: effects.add(EffectKind.WRITES_STATE)
435
+ if kind in {"Return", "FunctionDef", "If", "While", "For"}: effects.add(EffectKind.CONTROL_FLOW)
436
+ if kind in {"BinOp", "Call", "Subscript"}: effects.add(EffectKind.MAY_FAIL)
437
+ return tuple(sorted(effects, key=lambda e: e.value))
438
+
439
+ def generated_names(self, bindings: tuple[dict[str, Any], ...]) -> tuple[NamePlan, ...]:
440
+ used: set[str] = set()
441
+ plans: list[NamePlan] = []
442
+ for binding in sorted(bindings, key=lambda b: b["binding_id"]):
443
+ base = re.sub(r"[^A-Za-z0-9_]", "_", binding["source_name"])
444
+ if not base or not (base[0].isalpha() or base[0] == "_"):
445
+ base = "py_" + base
446
+ # Avoid every leading underscore. C reserves all such names at
447
+ # file scope, and a single allocator policy is safer than relying
448
+ # on the eventual declaration scope.
449
+ if base.startswith("_"):
450
+ base = "py" + base
451
+ external_collision = binding.get("binding_kind") in {"function", "record-class"} and base in _C11_EXTERNAL_IDENTIFIERS
452
+ reserved_project_prefix = base.startswith("pycf_") or (
453
+ base.startswith("pycm_") and not binding.get("module_generated_name", False)
454
+ )
455
+ if base in _C_KEYWORDS or base in _TARGET_RESERVED_NAMES or reserved_project_prefix or external_collision:
456
+ base = "py_" + base
457
+ candidate = base
458
+ suffix = 1
459
+ while candidate in used:
460
+ suffix += 1
461
+ candidate = f"{base}_{suffix}"
462
+ used.add(candidate)
463
+ plans.append(NamePlan(binding["binding_id"], candidate))
464
+ return tuple(plans)