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,237 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ from collections import defaultdict
5
+ from typing import Any
6
+
7
+ from pycforge.converter.ir.python_ir import python_ir_reference_ids
8
+
9
+ from .model import BindingFact, ScopeFact
10
+
11
+
12
+ def _stable_id(prefix: str, *parts: str) -> str:
13
+ seed = "\x1f".join(parts).encode("utf-8")
14
+ return prefix + hashlib.sha256(seed).hexdigest()[:20]
15
+
16
+
17
+ class PythonIRIndex:
18
+ def __init__(self, module: dict[str, Any]) -> None:
19
+ self.module = module
20
+ self.nodes = {node["node_id"]: node for node in module["nodes"]}
21
+ self.ordinals = {node["node_id"]: index for index, node in enumerate(module["nodes"])}
22
+
23
+ def node(self, node_id: str) -> dict[str, Any]:
24
+ return self.nodes[node_id]
25
+
26
+ def child_ids(self, node: dict[str, Any]) -> tuple[str, ...]:
27
+ found: list[str] = []
28
+ # Normalized fields retain ast.iter_fields order, which is semantic for
29
+ # calls, operators, and statement lists.
30
+ for key,value in node["fields"].items():
31
+ found.extend(python_ir_reference_ids(node["kind"],key,value,self.nodes))
32
+ return tuple(found)
33
+
34
+
35
+ class SymbolScopeAnalyzer:
36
+ """Deterministic Python lexical scopes without source-order misresolution.
37
+
38
+ Python decides function-local bindings for the whole function body. The
39
+ predeclaration pass below therefore records parameters, assignments, loop
40
+ targets, and nested-definition names before resolving any load occurrence.
41
+ This is essential for reporting use-before-binding rather than silently
42
+ treating an early load as a global.
43
+ """
44
+
45
+ def analyze(
46
+ self,
47
+ module: dict[str, Any],
48
+ *,
49
+ allow_records: bool = False,
50
+ ) -> tuple[tuple[ScopeFact, ...], tuple[BindingFact, ...], dict[str, str]]:
51
+ index = PythonIRIndex(module)
52
+ module_scope = _stable_id("scope-", module["document_id"], module["root_node_id"], "module")
53
+ scopes: dict[str, dict[str, Any]] = {
54
+ module_scope: {"kind": "module", "owner": module["root_node_id"], "parent": None, "children": [], "bindings": []}
55
+ }
56
+ scope_for_node: dict[str, str] = {}
57
+ declarations: dict[tuple[str, str], tuple[str, str, str]] = {}
58
+ occurrences: dict[str, list[str]] = defaultdict(list)
59
+ store_node_ids: set[str] = set()
60
+ for candidate in index.nodes.values():
61
+ kind = candidate["kind"]
62
+ fields = candidate["fields"]
63
+ if kind == "Assign":
64
+ store_node_ids.update(item for item in fields.get("targets", []) if isinstance(item, str))
65
+ elif kind in {"AnnAssign", "AugAssign", "NamedExpr", "For"}:
66
+ target_id = fields.get("target")
67
+ if isinstance(target_id, str):
68
+ store_node_ids.add(target_id)
69
+
70
+ def declare(scope_id: str, name: str, kind: str, node_id: str) -> str:
71
+ key = (scope_id, name)
72
+ if key not in declarations:
73
+ binding_id = _stable_id("bind-", scope_id, name, kind)
74
+ declarations[key] = (binding_id, kind, node_id)
75
+ scopes[scope_id]["bindings"].append(binding_id)
76
+ return declarations[key][0]
77
+
78
+ def resolve(scope_id: str, name: str) -> str:
79
+ current: str | None = scope_id
80
+ while current is not None:
81
+ key = (current, name)
82
+ if key in declarations:
83
+ return declarations[key][0]
84
+ current = scopes[current]["parent"]
85
+ return declare(module_scope, name, "implicit-global", module["root_node_id"])
86
+
87
+ def mark_annotation(node_id: str, scope_id: str) -> None:
88
+ node = index.node(node_id)
89
+ if node_id in scope_for_node:
90
+ return
91
+ scope_for_node[node_id] = scope_id
92
+ for child_id in index.child_ids(node):
93
+ mark_annotation(child_id, scope_id)
94
+
95
+ def predeclare_target(node_id: str, scope_id: str, kind: str = "local") -> None:
96
+ target = index.node(node_id)
97
+ if target["kind"] == "Name":
98
+ declare(scope_id, target["fields"].get("id", ""), kind, node_id)
99
+
100
+ def predeclare_function_body(node_ids: list[str], scope_id: str) -> None:
101
+ for node_id in node_ids:
102
+ node = index.node(node_id)
103
+ fields = node["fields"]
104
+ kind = node["kind"]
105
+ if kind in {"FunctionDef", "AsyncFunctionDef"}:
106
+ declare(scope_id, fields.get("name", ""), "nested-function", node_id)
107
+ continue
108
+ if kind == "Assign":
109
+ for target_id in fields.get("targets", []):
110
+ predeclare_target(target_id, scope_id)
111
+ elif kind in {"AnnAssign", "AugAssign", "NamedExpr"}:
112
+ target_id = fields.get("target")
113
+ if isinstance(target_id, str):
114
+ predeclare_target(target_id, scope_id)
115
+ elif kind == "For":
116
+ target_id = fields.get("target")
117
+ if isinstance(target_id, str):
118
+ predeclare_target(target_id, scope_id, "loop-target")
119
+ for field_name in ("body", "orelse", "finalbody"):
120
+ children = fields.get(field_name, [])
121
+ if isinstance(children, list):
122
+ predeclare_function_body(children, scope_id)
123
+
124
+ def walk(node_id: str, scope_id: str) -> None:
125
+ node = index.node(node_id)
126
+ if node_id in scope_for_node:
127
+ return
128
+ scope_for_node[node_id] = scope_id
129
+ kind = node["kind"]
130
+ fields = node["fields"]
131
+
132
+ if kind == "Module":
133
+ for child_id in fields.get("body", []):
134
+ child = index.node(child_id)
135
+ if child["kind"] in {"FunctionDef", "AsyncFunctionDef"}:
136
+ declare(scope_id, child["fields"].get("name", ""), "function", child_id)
137
+ elif allow_records and child["kind"] == "ClassDef":
138
+ declare(scope_id, child["fields"].get("name", ""), "record-class", child_id)
139
+ for child_id in fields.get("body", []):
140
+ walk(child_id, scope_id)
141
+ return
142
+
143
+ if allow_records and kind == "ClassDef":
144
+ declare(scope_id, fields.get("name", ""), "record-class", node_id)
145
+ child_scope = _stable_id("scope-", node_id, "record-class")
146
+ scopes[child_scope] = {
147
+ "kind": "record-class",
148
+ "owner": node_id,
149
+ "parent": scope_id,
150
+ "children": [],
151
+ "bindings": [],
152
+ }
153
+ scopes[scope_id]["children"].append(child_scope)
154
+ for child_id in fields.get("body", []):
155
+ child = index.node(child_id)
156
+ if child["kind"] == "AnnAssign":
157
+ target_id = child["fields"].get("target")
158
+ if isinstance(target_id, str):
159
+ target = index.node(target_id)
160
+ if target["kind"] == "Name":
161
+ declare(
162
+ child_scope,
163
+ target["fields"].get("id", ""),
164
+ "record-field",
165
+ target_id,
166
+ )
167
+ elif child["kind"] in {"FunctionDef", "AsyncFunctionDef"}:
168
+ declare(
169
+ child_scope,
170
+ child["fields"].get("name", ""),
171
+ "record-initializer" if child["fields"].get("name") == "__init__" else "record-method",
172
+ child_id,
173
+ )
174
+ for child_id in fields.get("body", []):
175
+ walk(child_id, child_scope)
176
+ return
177
+
178
+ if kind in {"FunctionDef", "AsyncFunctionDef"}:
179
+ declare(scope_id, fields.get("name", ""), "function" if scope_id == module_scope else "nested-function", node_id)
180
+ child_scope = _stable_id("scope-", node_id, "function")
181
+ scopes[child_scope] = {"kind": "function", "owner": node_id, "parent": scope_id, "children": [], "bindings": []}
182
+ scopes[scope_id]["children"].append(child_scope)
183
+ args_id = fields.get("args")
184
+ if isinstance(args_id, str):
185
+ args_node = index.node(args_id)
186
+ scope_for_node[args_id] = child_scope
187
+ parameter_ids = list(args_node["fields"].get("posonlyargs", [])) + list(args_node["fields"].get("args", [])) + list(args_node["fields"].get("kwonlyargs", []))
188
+ for arg_id in parameter_ids:
189
+ arg = index.node(arg_id)
190
+ declare(child_scope, arg["fields"]["arg"], "parameter", arg_id)
191
+ scope_for_node[arg_id] = child_scope
192
+ annotation = arg["fields"].get("annotation")
193
+ if isinstance(annotation, str) and annotation in index.nodes:
194
+ mark_annotation(annotation, child_scope)
195
+ predeclare_function_body(fields.get("body", []), child_scope)
196
+ returns = fields.get("returns")
197
+ if isinstance(returns, str) and returns in index.nodes:
198
+ mark_annotation(returns, child_scope)
199
+ for child in fields.get("body", []):
200
+ walk(child, child_scope)
201
+ return
202
+
203
+ if kind == "Name":
204
+ name = fields["id"]
205
+ context = "Store" if node_id in store_node_ids else "Load"
206
+ binding_id = declare(scope_id, name, "local", node_id) if context == "Store" else resolve(scope_id, name)
207
+ occurrences[binding_id].append(node_id)
208
+
209
+ for child_id in index.child_ids(node):
210
+ if child_id not in scope_for_node:
211
+ walk(child_id, scope_id)
212
+
213
+ walk(module["root_node_id"], module_scope)
214
+
215
+ scope_facts = tuple(
216
+ ScopeFact(
217
+ scope_id=scope_id,
218
+ scope_kind=data["kind"],
219
+ owner_node_id=data["owner"],
220
+ parent_scope_id=data["parent"],
221
+ child_scope_ids=tuple(sorted(data["children"])),
222
+ binding_ids=tuple(sorted(data["bindings"])),
223
+ )
224
+ for scope_id, data in sorted(scopes.items())
225
+ )
226
+ binding_facts = tuple(
227
+ BindingFact(
228
+ binding_id,
229
+ scope_id,
230
+ name,
231
+ kind,
232
+ declaration,
233
+ tuple(sorted(occurrences[binding_id], key=lambda item: index.ordinals[item])),
234
+ )
235
+ for (scope_id, name), (binding_id, kind, declaration) in sorted(declarations.items(), key=lambda item: item[1][0])
236
+ )
237
+ return scope_facts, binding_facts, scope_for_node