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.
- pycforge/__init__.py +8 -0
- pycforge/__main__.py +2 -0
- pycforge/_version.py +1 -0
- pycforge/converter/__init__.py +0 -0
- pycforge/converter/analysis/__init__.py +5 -0
- pycforge/converter/analysis/functions.py +723 -0
- pycforge/converter/analysis/model.py +158 -0
- pycforge/converter/analysis/planning.py +464 -0
- pycforge/converter/analysis/stage.py +1976 -0
- pycforge/converter/analysis/symbols.py +237 -0
- pycforge/converter/analysis/validation.py +2166 -0
- pycforge/converter/c_output/__init__.py +2 -0
- pycforge/converter/c_output/conformance.py +297 -0
- pycforge/converter/c_output/renderer.py +220 -0
- pycforge/converter/conditional_regions/__init__.py +71 -0
- pycforge/converter/conditional_regions/analysis.py +481 -0
- pycforge/converter/conditional_regions/lowering.py +579 -0
- pycforge/converter/conditional_regions/model.py +243 -0
- pycforge/converter/conditional_regions/validation.py +690 -0
- pycforge/converter/containers/__init__.py +19 -0
- pycforge/converter/containers/analysis.py +542 -0
- pycforge/converter/containers/lowering.py +290 -0
- pycforge/converter/containers/model.py +146 -0
- pycforge/converter/contracts/__init__.py +0 -0
- pycforge/converter/contracts/configuration.py +178 -0
- pycforge/converter/contracts/identifiers.py +68 -0
- pycforge/converter/contracts/versions.py +71 -0
- pycforge/converter/core/__init__.py +0 -0
- pycforge/converter/core/artifact_io.py +87 -0
- pycforge/converter/core/cancellation.py +7 -0
- pycforge/converter/core/canonicalization.py +254 -0
- pycforge/converter/core/context.py +13 -0
- pycforge/converter/core/diagnostics.py +95 -0
- pycforge/converter/core/enums.py +23 -0
- pycforge/converter/core/fingerprint.py +22 -0
- pycforge/converter/core/pipeline.py +8 -0
- pycforge/converter/core/progress.py +25 -0
- pycforge/converter/core/request.py +50 -0
- pycforge/converter/core/resource_policy.py +31 -0
- pycforge/converter/core/result.py +23 -0
- pycforge/converter/core/serialization.py +36 -0
- pycforge/converter/core/stage.py +10 -0
- pycforge/converter/core/stage_artifact.py +80 -0
- pycforge/converter/core/stage_outcome.py +15 -0
- pycforge/converter/decision_trace/__init__.py +0 -0
- pycforge/converter/decision_trace/recorder.py +52 -0
- pycforge/converter/facade.py +459 -0
- pycforge/converter/frontend/__init__.py +4 -0
- pycforge/converter/frontend/normalizer.py +59 -0
- pycforge/converter/frontend/parser.py +20 -0
- pycforge/converter/frontend/source_document.py +93 -0
- pycforge/converter/frontend/stages.py +518 -0
- pycforge/converter/frontend/validation.py +76 -0
- pycforge/converter/io/__init__.py +0 -0
- pycforge/converter/io/atomic_writer.py +33 -0
- pycforge/converter/ir/__init__.py +1 -0
- pycforge/converter/ir/c_ir/__init__.py +4 -0
- pycforge/converter/ir/c_ir/builder.py +26 -0
- pycforge/converter/ir/c_ir/model.py +318 -0
- pycforge/converter/ir/c_ir/serialization.py +88 -0
- pycforge/converter/ir/c_ir/validation.py +834 -0
- pycforge/converter/ir/python_ir/__init__.py +17 -0
- pycforge/converter/ir/python_ir/nodes.py +86 -0
- pycforge/converter/keyword_calls/__init__.py +68 -0
- pycforge/converter/keyword_calls/analysis.py +458 -0
- pycforge/converter/keyword_calls/lowering.py +271 -0
- pycforge/converter/keyword_calls/model.py +304 -0
- pycforge/converter/keyword_calls/validation.py +802 -0
- pycforge/converter/keyword_only_calls/__init__.py +68 -0
- pycforge/converter/keyword_only_calls/analysis.py +640 -0
- pycforge/converter/keyword_only_calls/lowering.py +376 -0
- pycforge/converter/keyword_only_calls/model.py +328 -0
- pycforge/converter/keyword_only_calls/validation.py +1357 -0
- pycforge/converter/lowering.py +1000 -0
- pycforge/converter/modules/__init__.py +13 -0
- pycforge/converter/modules/analysis.py +1250 -0
- pycforge/converter/modules/lowering.py +200 -0
- pycforge/converter/modules/model.py +73 -0
- pycforge/converter/modules/stage.py +192 -0
- pycforge/converter/numeric_semantics/__init__.py +23 -0
- pycforge/converter/numeric_semantics/analysis.py +420 -0
- pycforge/converter/numeric_semantics/lowering.py +153 -0
- pycforge/converter/numeric_semantics/model.py +123 -0
- pycforge/converter/records/__init__.py +34 -0
- pycforge/converter/records/analysis.py +1394 -0
- pycforge/converter/records/lowering.py +393 -0
- pycforge/converter/records/model.py +319 -0
- pycforge/converter/rules/__init__.py +3 -0
- pycforge/converter/support_templates/__init__.py +49 -0
- pycforge/converter/support_templates/assembly.py +106 -0
- pycforge/converter/support_templates/factories.py +348 -0
- pycforge/converter/support_templates/model.py +159 -0
- pycforge/converter/support_templates/registry.py +388 -0
- pycforge/converter/telemetry/__init__.py +0 -0
- pycforge/converter/telemetry/sink.py +39 -0
- pycforge/ide/__init__.py +19 -0
- pycforge/ide/__main__.py +5 -0
- pycforge/ide/_worker_event_codec.py +276 -0
- pycforge/ide/_worker_protocol_json.py +197 -0
- pycforge/ide/_worker_protocol_types.py +90 -0
- pycforge/ide/_worker_request_codec.py +200 -0
- pycforge/ide/_worker_request_data.py +260 -0
- pycforge/ide/_worker_result_data.py +343 -0
- pycforge/ide/action_contract.py +469 -0
- pycforge/ide/action_validation.py +229 -0
- pycforge/ide/command_palette.py +223 -0
- pycforge/ide/controller.py +904 -0
- pycforge/ide/controller_conversion.py +546 -0
- pycforge/ide/controller_io.py +205 -0
- pycforge/ide/editor.py +951 -0
- pycforge/ide/editor_commands_qt.py +274 -0
- pycforge/ide/editor_lexical.py +43 -0
- pycforge/ide/editor_sidebars.py +236 -0
- pycforge/ide/editor_syntax.py +243 -0
- pycforge/ide/find_replace.py +535 -0
- pycforge/ide/icons.py +91 -0
- pycforge/ide/io_service.py +196 -0
- pycforge/ide/model.py +164 -0
- pycforge/ide/panels.py +903 -0
- pycforge/ide/positions.py +220 -0
- pycforge/ide/process_worker.py +130 -0
- pycforge/ide/qt.py +230 -0
- pycforge/ide/qt_actions.py +418 -0
- pycforge/ide/qt_close.py +169 -0
- pycforge/ide/qt_command_palette.py +297 -0
- pycforge/ide/qt_contract.py +220 -0
- pycforge/ide/qt_documents.py +590 -0
- pycforge/ide/qt_editor_buffers.py +113 -0
- pycforge/ide/qt_editor_surfaces.py +112 -0
- pycforge/ide/qt_menus.py +324 -0
- pycforge/ide/qt_projection.py +500 -0
- pycforge/ide/qt_shell.py +348 -0
- pycforge/ide/qt_shell_interactions.py +320 -0
- pycforge/ide/qt_state.py +393 -0
- pycforge/ide/qt_workspace_features.py +398 -0
- pycforge/ide/qt_workspace_navigation.py +208 -0
- pycforge/ide/qt_workspace_observers.py +173 -0
- pycforge/ide/qt_workspace_panels.py +545 -0
- pycforge/ide/qt_workspace_widgets.py +228 -0
- pycforge/ide/resources/icons/about.svg +1 -0
- pycforge/ide/resources/icons/add-document.svg +1 -0
- pycforge/ide/resources/icons/brand-mark.svg +1 -0
- pycforge/ide/resources/icons/bundle-search.svg +1 -0
- pycforge/ide/resources/icons/cancel.svg +1 -0
- pycforge/ide/resources/icons/check.svg +1 -0
- pycforge/ide/resources/icons/chevron-right.svg +1 -0
- pycforge/ide/resources/icons/close.svg +1 -0
- pycforge/ide/resources/icons/collapse-all.svg +1 -0
- pycforge/ide/resources/icons/command-palette.svg +1 -0
- pycforge/ide/resources/icons/convert.svg +1 -0
- pycforge/ide/resources/icons/copy.svg +1 -0
- pycforge/ide/resources/icons/cut.svg +1 -0
- pycforge/ide/resources/icons/decision-trace.svg +1 -0
- pycforge/ide/resources/icons/details.svg +1 -0
- pycforge/ide/resources/icons/diagnostics.svg +1 -0
- pycforge/ide/resources/icons/duplicate-line.svg +1 -0
- pycforge/ide/resources/icons/exit.svg +1 -0
- pycforge/ide/resources/icons/expand-all.svg +1 -0
- pycforge/ide/resources/icons/export.svg +1 -0
- pycforge/ide/resources/icons/find.svg +1 -0
- pycforge/ide/resources/icons/go-to-line.svg +1 -0
- pycforge/ide/resources/icons/go-to-output.svg +1 -0
- pycforge/ide/resources/icons/go-to-source.svg +1 -0
- pycforge/ide/resources/icons/history.svg +1 -0
- pycforge/ide/resources/icons/indent.svg +1 -0
- pycforge/ide/resources/icons/link-c.svg +1 -0
- pycforge/ide/resources/icons/mappings.svg +1 -0
- pycforge/ide/resources/icons/module.svg +1 -0
- pycforge/ide/resources/icons/move-down.svg +1 -0
- pycforge/ide/resources/icons/move-line-down.svg +1 -0
- pycforge/ide/resources/icons/move-line-up.svg +1 -0
- pycforge/ide/resources/icons/move-up.svg +1 -0
- pycforge/ide/resources/icons/next.svg +1 -0
- pycforge/ide/resources/icons/open.svg +1 -0
- pycforge/ide/resources/icons/outdent.svg +1 -0
- pycforge/ide/resources/icons/outline.svg +1 -0
- pycforge/ide/resources/icons/paste.svg +1 -0
- pycforge/ide/resources/icons/previous.svg +1 -0
- pycforge/ide/resources/icons/primary-module.svg +1 -0
- pycforge/ide/resources/icons/redo.svg +1 -0
- pycforge/ide/resources/icons/remove-document.svg +1 -0
- pycforge/ide/resources/icons/replace.svg +1 -0
- pycforge/ide/resources/icons/save-as.svg +1 -0
- pycforge/ide/resources/icons/save.svg +1 -0
- pycforge/ide/resources/icons/select-all.svg +1 -0
- pycforge/ide/resources/icons/settings.svg +1 -0
- pycforge/ide/resources/icons/show-c.svg +1 -0
- pycforge/ide/resources/icons/split-view.svg +1 -0
- pycforge/ide/resources/icons/summary.svg +1 -0
- pycforge/ide/resources/icons/telemetry.svg +1 -0
- pycforge/ide/resources/icons/toggle-comment.svg +1 -0
- pycforge/ide/resources/icons/toggle-fold.svg +1 -0
- pycforge/ide/resources/icons/undo.svg +1 -0
- pycforge/ide/resources/icons/whitespace.svg +1 -0
- pycforge/ide/revisions.py +400 -0
- pycforge/ide/search_service.py +280 -0
- pycforge/ide/session_history.py +150 -0
- pycforge/ide/source_editing.py +417 -0
- pycforge/ide/source_structure.py +558 -0
- pycforge/ide/source_structure_async.py +191 -0
- pycforge/ide/supervisor.py +503 -0
- pycforge/ide/theme.py +119 -0
- pycforge/ide/theme_stylesheet.py +598 -0
- pycforge/ide/theme_workspace_stylesheet.py +155 -0
- pycforge/ide/visual_tokens.py +142 -0
- pycforge/ide/worker_protocol.py +80 -0
- pycforge/ide/workspace_search.py +462 -0
- pycforge/ide/workspace_session.py +256 -0
- pycforge/laboratory/__init__.py +6 -0
- pycforge/laboratory/audits.py +2244 -0
- pycforge/laboratory/checkpoint_e.py +2685 -0
- pycforge/laboratory/cli.py +119 -0
- pycforge/laboratory/exit_codes.py +11 -0
- pycforge/laboratory/keyword_audit.py +661 -0
- pycforge/laboratory/keyword_only_audit.py +481 -0
- pycforge-0.15.2.dist-info/METADATA +209 -0
- pycforge-0.15.2.dist-info/RECORD +221 -0
- pycforge-0.15.2.dist-info/WHEEL +5 -0
- pycforge-0.15.2.dist-info/entry_points.txt +5 -0
- pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
- pycforge-0.15.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1000 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import math
|
|
3
|
+
from dataclasses import fields, is_dataclass
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
from typing import Any
|
|
6
|
+
from .contracts.configuration import (
|
|
7
|
+
PHASE11_RULE_SET,
|
|
8
|
+
supports_containers,
|
|
9
|
+
supports_conditional_regions,
|
|
10
|
+
supports_functions,
|
|
11
|
+
supports_keyword_calls,
|
|
12
|
+
supports_keyword_only_calls,
|
|
13
|
+
supports_modules,
|
|
14
|
+
supports_numeric,
|
|
15
|
+
supports_records,
|
|
16
|
+
)
|
|
17
|
+
from .contracts.versions import (
|
|
18
|
+
GENERATED_C_SCHEMA,
|
|
19
|
+
PHASE14C_GENERATED_C_SCHEMA,
|
|
20
|
+
PHASE14B_GENERATED_C_SCHEMA,
|
|
21
|
+
PHASE14A_GENERATED_C_SCHEMA,
|
|
22
|
+
PHASE13_GENERATED_C_SCHEMA,
|
|
23
|
+
PHASE12_GENERATED_C_SCHEMA,
|
|
24
|
+
)
|
|
25
|
+
from .conditional_regions.lowering import bind_conditional_region_lowerer
|
|
26
|
+
from .containers.lowering import ContainerCIRLowerer, ContainerLoweringServices
|
|
27
|
+
from .core.diagnostics import Diagnostic
|
|
28
|
+
from .core.enums import Severity, StageTerminal
|
|
29
|
+
from .core.fingerprint import fingerprint
|
|
30
|
+
from .core.stage_artifact import StageArtifact
|
|
31
|
+
from .core.stage_outcome import StageOutcome
|
|
32
|
+
from .ir.c_ir import (
|
|
33
|
+
CAssignmentStatement,
|
|
34
|
+
CBinaryExpr,
|
|
35
|
+
CBinaryOp,
|
|
36
|
+
CBlock,
|
|
37
|
+
CBooleanLiteral,
|
|
38
|
+
CBreakStatement,
|
|
39
|
+
CCallExpr,
|
|
40
|
+
CContinueStatement,
|
|
41
|
+
CExpressionStatement,
|
|
42
|
+
CFloatLiteral,
|
|
43
|
+
CForStatement,
|
|
44
|
+
CFunctionDefinition,
|
|
45
|
+
CFunctionPrototype,
|
|
46
|
+
CIdentifier,
|
|
47
|
+
CIdentifierRef,
|
|
48
|
+
CIfStatement,
|
|
49
|
+
CInclude,
|
|
50
|
+
CIntegerLiteral,
|
|
51
|
+
CModuleManifestEntry,
|
|
52
|
+
CParameter,
|
|
53
|
+
CProvenance,
|
|
54
|
+
CQualifier,
|
|
55
|
+
CReturnStatement,
|
|
56
|
+
CStorage,
|
|
57
|
+
CStringLiteral,
|
|
58
|
+
CTranslationUnitBuilder,
|
|
59
|
+
CType,
|
|
60
|
+
CUnaryExpr,
|
|
61
|
+
CUnaryOp,
|
|
62
|
+
CVariableDeclaration,
|
|
63
|
+
CWhileStatement,
|
|
64
|
+
CONDITIONAL_SCHEMA_VERSION,
|
|
65
|
+
CONTAINER_SCHEMA_VERSION,
|
|
66
|
+
MODULE_SCHEMA_VERSION,
|
|
67
|
+
NUMERIC_SCHEMA_VERSION,
|
|
68
|
+
RECORD_SCHEMA_VERSION,
|
|
69
|
+
KEYWORD_CALL_SCHEMA_VERSION,
|
|
70
|
+
KEYWORD_ONLY_CALL_SCHEMA_VERSION,
|
|
71
|
+
SCHEMA_VERSION,
|
|
72
|
+
serialize_translation_unit,
|
|
73
|
+
validate_translation_unit,
|
|
74
|
+
)
|
|
75
|
+
from .modules.lowering import enrich_source_output_mappings, ordered_function_ids
|
|
76
|
+
from .records.lowering import RecordCIRLowerer, RecordLoweringServices
|
|
77
|
+
from .numeric_semantics import bind_numeric_lowerer
|
|
78
|
+
from .keyword_calls.lowering import bind_keyword_call_lowerer
|
|
79
|
+
from .keyword_calls.model import CUMULATIVE_KEYWORD_TARGET_DIAGNOSTIC_CODE, CUMULATIVE_KEYWORD_TARGET_REASON
|
|
80
|
+
from .keyword_only_calls.lowering import bind_keyword_only_call_lowerer, lowered_parameter_node_ids, lowering_supports_parameter_shape
|
|
81
|
+
from .keyword_only_calls.model import CUMULATIVE_KEYWORD_ONLY_TARGET_DIAGNOSTIC_CODE, CUMULATIVE_KEYWORD_ONLY_TARGET_REASON
|
|
82
|
+
from .c_output import CRenderer, validate_c_text
|
|
83
|
+
from .support_templates import (
|
|
84
|
+
FrozenHelperRegistry,
|
|
85
|
+
HelperRegistryError,
|
|
86
|
+
HelperResolutionCanceled,
|
|
87
|
+
assemble_translation_unit,
|
|
88
|
+
default_helper_registry,
|
|
89
|
+
)
|
|
90
|
+
def _sid(prefix: str, *parts: str) -> str:
|
|
91
|
+
import hashlib
|
|
92
|
+
return prefix + hashlib.sha256("\x1f".join(parts).encode()).hexdigest()[:20]
|
|
93
|
+
def _artifact(prior: StageArtifact, payload: dict[str, object], version: str) -> StageArtifact:
|
|
94
|
+
fp = fingerprint("stage-artifact", {"kind": "generated_c", "conversion_id": prior.conversion_id, "parent": prior.artifact_fingerprint.value, "payload": payload})
|
|
95
|
+
return StageArtifact("generated_c", version, prior.conversion_id, prior.artifact_fingerprint, MappingProxyType(payload), fp)
|
|
96
|
+
class FirstSliceLoweringStage:
|
|
97
|
+
"""Structured C construction for the cumulative scalar/function/container subset."""
|
|
98
|
+
stage_id = "lowering.first_slice"
|
|
99
|
+
input_schema = "conversion-plan/0.14.3"
|
|
100
|
+
input_schemas = ("conversion-plan/0.5", "conversion-plan/0.9", "conversion-plan/0.11", "conversion-plan/0.12", "conversion-plan/0.13", "conversion-plan/0.14", "conversion-plan/0.14.1", "conversion-plan/0.14.2", "conversion-plan/0.14.3")
|
|
101
|
+
output_schema = GENERATED_C_SCHEMA
|
|
102
|
+
def __init__(self, helper_registry: FrozenHelperRegistry | None = None) -> None:
|
|
103
|
+
self.helper_registry = helper_registry
|
|
104
|
+
def run(self, artifact: StageArtifact, services: Any) -> StageOutcome:
|
|
105
|
+
if services.context.cancellation.is_canceled:
|
|
106
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
107
|
+
registry = None
|
|
108
|
+
try:
|
|
109
|
+
registry = self.helper_registry or default_helper_registry()
|
|
110
|
+
unit, helper_plan = _Lowerer(
|
|
111
|
+
artifact.payload,
|
|
112
|
+
services.context.cancellation,
|
|
113
|
+
registry,
|
|
114
|
+
).lower()
|
|
115
|
+
except (LoweringCanceled, HelperResolutionCanceled):
|
|
116
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
117
|
+
except HelperRegistryError as exc:
|
|
118
|
+
requirements = tuple(sorted(artifact.payload.get("helper_requirements", ())))
|
|
119
|
+
manifest = {
|
|
120
|
+
item["reference"]: item
|
|
121
|
+
for item in (registry.manifest if registry is not None else ())
|
|
122
|
+
}
|
|
123
|
+
obligations = tuple(sorted({
|
|
124
|
+
obligation
|
|
125
|
+
for reference in requirements
|
|
126
|
+
for obligation in manifest.get(reference, {}).get("semantic_obligations", ())
|
|
127
|
+
}))
|
|
128
|
+
diagnostic = Diagnostic(
|
|
129
|
+
exc.code,
|
|
130
|
+
Severity.INTERNAL_ERROR,
|
|
131
|
+
self.stage_id,
|
|
132
|
+
exc.message,
|
|
133
|
+
fact_references=tuple(f"helper-requirement:{item}" for item in requirements),
|
|
134
|
+
obligation_references=obligations,
|
|
135
|
+
explanation="The declared helper closure could not be validated and no helper output was published.",
|
|
136
|
+
remediation="Repair the project-owned helper registry or RulePlan requirement before conversion.",
|
|
137
|
+
)
|
|
138
|
+
return StageOutcome(StageTerminal.INTERNAL_FAILURE, diagnostics=(diagnostic,))
|
|
139
|
+
except RecursionError:
|
|
140
|
+
return StageOutcome(StageTerminal.REJECTED, diagnostics=(Diagnostic("PYC2006", Severity.ERROR, self.stage_id, "Structural nesting exceeds the safe lowering and validation ceiling"),))
|
|
141
|
+
except UnsupportedSlice as exc:
|
|
142
|
+
return StageOutcome(StageTerminal.REJECTED, diagnostics=(Diagnostic(
|
|
143
|
+
exc.code,
|
|
144
|
+
Severity.ERROR,
|
|
145
|
+
self.stage_id,
|
|
146
|
+
exc.message,
|
|
147
|
+
source_span=exc.source_span,
|
|
148
|
+
rule_id=exc.rule_id,
|
|
149
|
+
fact_references=exc.fact_references,
|
|
150
|
+
obligation_references=exc.obligation_references,
|
|
151
|
+
explanation=exc.message,
|
|
152
|
+
remediation="Rewrite the source to the documented supported subset or select a future rule set that explicitly supports this form.",
|
|
153
|
+
),))
|
|
154
|
+
if services.context.cancellation.is_canceled:
|
|
155
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled before C IR validation"),))
|
|
156
|
+
try:
|
|
157
|
+
validation = validate_translation_unit(unit)
|
|
158
|
+
if not validation.accepted:
|
|
159
|
+
return StageOutcome(StageTerminal.INTERNAL_FAILURE, diagnostics=(Diagnostic("PYC9601", Severity.INTERNAL_ERROR, self.stage_id, "; ".join(validation.errors)),))
|
|
160
|
+
if services.context.cancellation.is_canceled:
|
|
161
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled before C rendering"),))
|
|
162
|
+
rendered = CRenderer().render(unit)
|
|
163
|
+
if services.context.cancellation.is_canceled:
|
|
164
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled before generated-text validation"),))
|
|
165
|
+
conformance = validate_c_text(rendered.text)
|
|
166
|
+
except RecursionError:
|
|
167
|
+
return StageOutcome(StageTerminal.REJECTED, diagnostics=(Diagnostic("PYC2006", Severity.ERROR, self.stage_id, "Structural nesting exceeds the safe lowering and validation ceiling"),))
|
|
168
|
+
if not conformance.accepted:
|
|
169
|
+
return StageOutcome(StageTerminal.INTERNAL_FAILURE, diagnostics=(Diagnostic("PYC9602", Severity.INTERNAL_ERROR, self.stage_id, conformance.message),))
|
|
170
|
+
if services.context.cancellation.is_canceled:
|
|
171
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled before generated-C publication"),))
|
|
172
|
+
rule_set = artifact.payload.get("rule_set_version")
|
|
173
|
+
keyword_calls_enabled = supports_keyword_calls(rule_set)
|
|
174
|
+
keyword_only_calls_enabled = supports_keyword_only_calls(rule_set)
|
|
175
|
+
conditional_enabled = supports_conditional_regions(rule_set)
|
|
176
|
+
modules_enabled = supports_modules(rule_set)
|
|
177
|
+
records_enabled = supports_records(rule_set)
|
|
178
|
+
numeric_enabled = supports_numeric(rule_set)
|
|
179
|
+
functions_enabled = supports_functions(rule_set)
|
|
180
|
+
phase11_surface = rule_set == PHASE11_RULE_SET
|
|
181
|
+
generated_schema = (
|
|
182
|
+
GENERATED_C_SCHEMA if keyword_only_calls_enabled
|
|
183
|
+
else PHASE14C_GENERATED_C_SCHEMA if keyword_calls_enabled
|
|
184
|
+
else PHASE14B_GENERATED_C_SCHEMA if conditional_enabled
|
|
185
|
+
else PHASE14A_GENERATED_C_SCHEMA if numeric_enabled
|
|
186
|
+
else PHASE13_GENERATED_C_SCHEMA if records_enabled
|
|
187
|
+
else PHASE12_GENERATED_C_SCHEMA
|
|
188
|
+
if modules_enabled
|
|
189
|
+
else "generated-c/0.11" if phase11_surface
|
|
190
|
+
else "generated-c/0.10" if functions_enabled or helper_plan.assets else "generated-c/0.8"
|
|
191
|
+
)
|
|
192
|
+
payload = dict(artifact.payload)
|
|
193
|
+
payload.update(
|
|
194
|
+
{
|
|
195
|
+
"stage_order": tuple(artifact.payload["stage_order"]) + (self.stage_id,),
|
|
196
|
+
"c_ir": serialize_translation_unit(unit),
|
|
197
|
+
"c_ir_schema": unit.schema_version,
|
|
198
|
+
"generated_c": rendered.text,
|
|
199
|
+
"source_output_mappings": enrich_source_output_mappings(artifact.payload, rendered.mappings),
|
|
200
|
+
"output_fingerprint": fingerprint("generated-output", rendered.text).to_dict(),
|
|
201
|
+
"helper_registry_version": helper_plan.registry_version,
|
|
202
|
+
"helper_registry_fingerprint": helper_plan.registry_fingerprint,
|
|
203
|
+
"helper_manifest": list(helper_plan.manifest_dicts()),
|
|
204
|
+
"helper_manifest_fingerprint": helper_plan.manifest_fingerprint,
|
|
205
|
+
"schema_version": generated_schema,
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
services.trace.record({
|
|
209
|
+
"kind": "helper_resolution",
|
|
210
|
+
"stage": self.stage_id,
|
|
211
|
+
"requirements": [item.canonical for item in helper_plan.requirements],
|
|
212
|
+
"helpers": [item.reference.canonical for item in helper_plan.manifest],
|
|
213
|
+
"manifest_fingerprint": helper_plan.manifest_fingerprint,
|
|
214
|
+
})
|
|
215
|
+
services.trace.record({"kind": "c_ir_published", "stage": self.stage_id, "c_ir_node": unit.node_id})
|
|
216
|
+
services.trace.record({"kind": "c_rendered", "stage": self.stage_id, "output_fingerprint": payload["output_fingerprint"]})
|
|
217
|
+
artifact_version = "0.14.3" if keyword_only_calls_enabled else "0.14.2" if keyword_calls_enabled else "0.14.1" if conditional_enabled else "0.14" if numeric_enabled else "0.13" if records_enabled else "0.12" if modules_enabled else "0.11" if phase11_surface else "0.10" if functions_enabled or helper_plan.assets else "0.6"
|
|
218
|
+
return StageOutcome(StageTerminal.COMPLETED, _artifact(artifact, payload, artifact_version))
|
|
219
|
+
def validate(self, artifact: StageArtifact, services: Any) -> tuple[bool, str]:
|
|
220
|
+
if artifact.kind != "generated_c" or artifact.schema_version not in {"0.6", "0.9", "0.10", "0.11", "0.12", "0.13", "0.14", "0.14.1", "0.14.2", "0.14.3"}:
|
|
221
|
+
return False, "invalid generated-C artifact identity"
|
|
222
|
+
text = artifact.payload.get("generated_c")
|
|
223
|
+
if not isinstance(text, str) or not text:
|
|
224
|
+
return False, "generated C is absent"
|
|
225
|
+
expected = artifact.payload.get("output_fingerprint")
|
|
226
|
+
if expected != fingerprint("generated-output", text).to_dict():
|
|
227
|
+
return False, "generated-C output fingerprint mismatch"
|
|
228
|
+
conformance = validate_c_text(text)
|
|
229
|
+
return conformance.accepted, conformance.message
|
|
230
|
+
class UnsupportedSlice(Exception):
|
|
231
|
+
def __init__(self, code: str, message: str, source_span: dict[str, object] | None = None, *, rule_id: str | None = None, fact_references: tuple[str, ...] = (), obligation_references: tuple[str, ...] = ()) -> None:
|
|
232
|
+
super().__init__(message)
|
|
233
|
+
self.code = code
|
|
234
|
+
self.message = message
|
|
235
|
+
self.source_span = source_span
|
|
236
|
+
self.rule_id = rule_id
|
|
237
|
+
self.fact_references = fact_references
|
|
238
|
+
self.obligation_references = obligation_references
|
|
239
|
+
class LoweringCanceled(Exception):
|
|
240
|
+
pass
|
|
241
|
+
class _Lowerer:
|
|
242
|
+
def __init__(self, payload: Any, cancellation: Any, helper_registry: FrozenHelperRegistry) -> None:
|
|
243
|
+
self.payload = payload
|
|
244
|
+
self.cancellation = cancellation
|
|
245
|
+
self.helper_registry = helper_registry
|
|
246
|
+
module = payload["python_ir"]
|
|
247
|
+
self.nodes = {node["node_id"]: node for node in module["nodes"]}
|
|
248
|
+
self.document_id = module["document_id"]
|
|
249
|
+
self.root = self.nodes[module["root_node_id"]]
|
|
250
|
+
self.plans = {plan["source_node_id"]: plan for plan in payload["rule_plans"]}
|
|
251
|
+
binding_table = self._table("binding-facts")
|
|
252
|
+
self.bindings = [record["value"] for record in binding_table["records"]]
|
|
253
|
+
self.binding_by_id = {binding["binding_id"]: binding for binding in self.bindings}
|
|
254
|
+
self.binding_by_decl = {binding["declaration_node_id"]: binding for binding in self.bindings}
|
|
255
|
+
self.binding_by_occurrence = {occurrence: binding for binding in self.bindings for occurrence in binding["occurrence_node_ids"]}
|
|
256
|
+
self.names = {plan["binding_id"]: plan["generated_name"] for plan in payload["generated_name_plans"]}
|
|
257
|
+
self.generated_spellings = set(self.names.values())
|
|
258
|
+
self.temporary_spellings: dict[str, str] = {}
|
|
259
|
+
category_table = self._table("value-category-facts")
|
|
260
|
+
self.categories = {record["key"]: record["value"] for record in category_table["records"]}
|
|
261
|
+
self.rule_set = payload.get("rule_set_version", "phase8-control-flow-v0.8")
|
|
262
|
+
self.phase12 = supports_modules(self.rule_set)
|
|
263
|
+
self.phase13 = supports_records(self.rule_set)
|
|
264
|
+
self.phase14 = supports_numeric(self.rule_set)
|
|
265
|
+
self.phase14b = supports_conditional_regions(self.rule_set)
|
|
266
|
+
self.phase14c = supports_keyword_calls(self.rule_set)
|
|
267
|
+
self.phase14d = supports_keyword_only_calls(self.rule_set)
|
|
268
|
+
self.phase11 = supports_containers(self.rule_set)
|
|
269
|
+
self.phase9 = supports_functions(self.rule_set)
|
|
270
|
+
self.keyword_call_lowerer = bind_keyword_call_lowerer(self)
|
|
271
|
+
self.keyword_only_call_lowerer = bind_keyword_only_call_lowerer(self)
|
|
272
|
+
self.target_contract = payload.get("target_contract", "c11-portable-fixed-v1")
|
|
273
|
+
self.signatures = self._optional_values("function-signature-facts", "function_node_id")
|
|
274
|
+
self.calls = self._optional_values("call-target-facts", "call_node_id")
|
|
275
|
+
self.return_paths = self._optional_values("return-path-facts", "function_node_id")
|
|
276
|
+
self.local_facts = self._optional_values("local-declaration-facts", "function_node_id")
|
|
277
|
+
self.container_shapes = self._optional_values("container-shape-facts", "literal_node_id")
|
|
278
|
+
self.container_bindings = self._optional_values("container-binding-facts", "binding_id")
|
|
279
|
+
self.container_binding_by_assignment = {
|
|
280
|
+
item["assignment_node_id"]: item
|
|
281
|
+
for item in self.container_bindings.values()
|
|
282
|
+
}
|
|
283
|
+
self.container_accesses = self._optional_values("container-access-facts", "subscript_node_id")
|
|
284
|
+
self.container_iterations = self._optional_values("container-iteration-facts", "for_node_id")
|
|
285
|
+
self.record_definitions = self._optional_values("record-definition-facts", "record_id")
|
|
286
|
+
self.record_fields = self._optional_values("record-field-facts", "field_id")
|
|
287
|
+
self.record_initializers = self._optional_values("record-initializer-facts", "initializer_id")
|
|
288
|
+
self.record_instances = self._optional_values("record-instance-facts", "instance_id")
|
|
289
|
+
self.record_instance_by_assignment = {item["assignment_node_id"]: item for item in self.record_instances.values()}
|
|
290
|
+
self.record_bindings = self._optional_values("record-binding-facts", "binding_id")
|
|
291
|
+
self.record_accesses = self._optional_values("record-access-facts", "access_node_id")
|
|
292
|
+
self.module_identities = self._optional_values("module-identity-facts", "module_id")
|
|
293
|
+
self.module_functions = self._optional_values("module-function-facts", "function_node_id")
|
|
294
|
+
self.module_sources = self._optional_values("module-source-facts", "module_id")
|
|
295
|
+
module_initialization = self._optional_values("module-initialization-facts", None)
|
|
296
|
+
self.module_initialization = next(iter(module_initialization.values()), {})
|
|
297
|
+
graph_values = self._optional_values("call-graph-facts", None)
|
|
298
|
+
self.call_graph = next(iter(graph_values.values()), {"recursive_function_node_ids": [], "recursive_call_node_ids": []})
|
|
299
|
+
self.control_depth = 0
|
|
300
|
+
self.loop_depth = 0
|
|
301
|
+
self.container_lowerer = ContainerCIRLowerer(
|
|
302
|
+
ContainerLoweringServices(
|
|
303
|
+
nodes=self.nodes,
|
|
304
|
+
shapes=self.container_shapes,
|
|
305
|
+
bindings=self.container_bindings,
|
|
306
|
+
bindings_by_assignment=self.container_binding_by_assignment,
|
|
307
|
+
accesses=self.container_accesses,
|
|
308
|
+
iterations=self.container_iterations,
|
|
309
|
+
source_bindings_by_id=self.binding_by_id,
|
|
310
|
+
source_bindings_by_occurrence=self.binding_by_occurrence,
|
|
311
|
+
generated_names=self.names,
|
|
312
|
+
generated_spellings=self.generated_spellings,
|
|
313
|
+
expression=self._expression,
|
|
314
|
+
temporary=self._temporary,
|
|
315
|
+
category_type=self._category_type,
|
|
316
|
+
identifier=self._identifier,
|
|
317
|
+
provenance=self._prov,
|
|
318
|
+
synthetic_provenance=self._synthetic_prov,
|
|
319
|
+
block_statements=self._block_statements,
|
|
320
|
+
temporary_spelling=self._temporary_spelling,
|
|
321
|
+
reject=self._reject,
|
|
322
|
+
control_depth=lambda: self.control_depth,
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
self.record_lowerer = RecordCIRLowerer(
|
|
326
|
+
RecordLoweringServices(
|
|
327
|
+
nodes=self.nodes,
|
|
328
|
+
definitions=self.record_definitions,
|
|
329
|
+
fields=self.record_fields,
|
|
330
|
+
initializers=self.record_initializers,
|
|
331
|
+
instances=self.record_instances,
|
|
332
|
+
bindings=self.record_bindings,
|
|
333
|
+
accesses=self.record_accesses,
|
|
334
|
+
source_bindings_by_id=self.binding_by_id,
|
|
335
|
+
generated_names=self.names,
|
|
336
|
+
expression=self._expression,
|
|
337
|
+
temporary=self._temporary,
|
|
338
|
+
category_type=self._category_type,
|
|
339
|
+
identifier=self._identifier,
|
|
340
|
+
provenance=self._prov,
|
|
341
|
+
synthetic_provenance=self._synthetic_prov,
|
|
342
|
+
reject=self._reject,
|
|
343
|
+
check_cancellation=self._check_cancel,
|
|
344
|
+
)
|
|
345
|
+
) if self.phase13 else None
|
|
346
|
+
self.numeric_lowerer = bind_numeric_lowerer(self) if self.phase14 else None
|
|
347
|
+
self.conditional_region_lowerer = bind_conditional_region_lowerer(self)
|
|
348
|
+
def _table(self, table_id: str) -> dict[str, Any]:
|
|
349
|
+
return next(table for table in self.payload["fact_tables"] if table["table_id"] == table_id)
|
|
350
|
+
def _optional_values(self, table_id: str, key_field: str | None) -> dict[str, dict[str, Any]]:
|
|
351
|
+
table = next((item for item in self.payload["fact_tables"] if item["table_id"] == table_id), None)
|
|
352
|
+
if not table:
|
|
353
|
+
return {}
|
|
354
|
+
result: dict[str, dict[str, Any]] = {}
|
|
355
|
+
for record in table["records"]:
|
|
356
|
+
value = record["value"]
|
|
357
|
+
result[value[key_field] if key_field else record["key"]] = value
|
|
358
|
+
return result
|
|
359
|
+
def _check_cancel(self) -> None:
|
|
360
|
+
if self.cancellation.is_canceled:
|
|
361
|
+
raise LoweringCanceled
|
|
362
|
+
def _reject(self, code: str, message: str, node: dict[str, Any] | None = None) -> None:
|
|
363
|
+
span = node.get("provenance", {}).get("source_span") if node else None
|
|
364
|
+
plan = self.plans.get(node["node_id"]) if node else None
|
|
365
|
+
facts: tuple[str, ...] = ()
|
|
366
|
+
obligations: tuple[str, ...] = ()
|
|
367
|
+
if plan:
|
|
368
|
+
facts = tuple(plan.get("facts_used", ()))
|
|
369
|
+
obligations = tuple(plan.get("semantic_obligations", ()))
|
|
370
|
+
elif node and node["kind"] == "Call" and node["node_id"] in self.calls:
|
|
371
|
+
facts = (f"call-target-fact:{node['node_id']}",)
|
|
372
|
+
elif node and self.phase11:
|
|
373
|
+
node_id = node["node_id"]
|
|
374
|
+
if node_id in self.container_shapes:
|
|
375
|
+
facts = (f"container-shape:{node_id}",)
|
|
376
|
+
obligations = ("container-shape-and-capacity-proved",)
|
|
377
|
+
elif node_id in self.container_accesses:
|
|
378
|
+
facts = (f"container-access:{node_id}",)
|
|
379
|
+
obligations = ("container-bounds-or-key-presence-proved",)
|
|
380
|
+
elif node_id in self.container_iterations:
|
|
381
|
+
facts = (f"container-iteration:{node_id}",)
|
|
382
|
+
obligations = ("container-iteration-bound-and-order-proved",)
|
|
383
|
+
else:
|
|
384
|
+
related = tuple(
|
|
385
|
+
sorted(
|
|
386
|
+
(
|
|
387
|
+
item
|
|
388
|
+
for item in self.container_bindings.values()
|
|
389
|
+
if node_id in {
|
|
390
|
+
item.get("assignment_node_id"),
|
|
391
|
+
item.get("literal_node_id"),
|
|
392
|
+
item.get("rejection_node_id"),
|
|
393
|
+
}
|
|
394
|
+
or node_id in item.get("invalid_use_node_ids", ())
|
|
395
|
+
),
|
|
396
|
+
key=lambda item: item["binding_id"],
|
|
397
|
+
)
|
|
398
|
+
)
|
|
399
|
+
if related:
|
|
400
|
+
facts = tuple(f"container-binding:{item['binding_id']}" for item in related)
|
|
401
|
+
obligations = ("container-single-binding-no-alias-or-escape",)
|
|
402
|
+
raise UnsupportedSlice(code, message, span, rule_id=plan.get("rule_id") if plan else None, fact_references=facts, obligation_references=obligations)
|
|
403
|
+
def lower(self):
|
|
404
|
+
self._check_cancel()
|
|
405
|
+
body_ids = self.root["fields"].get("body", [])
|
|
406
|
+
if self.phase9:
|
|
407
|
+
module_rejection = self.payload.get("module_rejection")
|
|
408
|
+
if module_rejection:
|
|
409
|
+
rejection_node = self.nodes.get(module_rejection[2], self.root) if len(module_rejection) > 2 else self.root
|
|
410
|
+
self._reject(module_rejection[0], module_rejection[1], rejection_node)
|
|
411
|
+
function_ids, invalid_top_level = ordered_function_ids(
|
|
412
|
+
self.payload,
|
|
413
|
+
self.root,
|
|
414
|
+
self.nodes,
|
|
415
|
+
imports_allowed=self.phase12,
|
|
416
|
+
records_allowed=self.phase13,
|
|
417
|
+
)
|
|
418
|
+
if not function_ids or invalid_top_level:
|
|
419
|
+
self._reject("PYC2902", "Phase 9 requires one or more top-level function definitions", self.nodes.get(invalid_top_level, self.root))
|
|
420
|
+
if self.phase11:
|
|
421
|
+
self._validate_phase11_facts()
|
|
422
|
+
self._validate_phase9_facts(function_ids)
|
|
423
|
+
record_declarations = self.record_lowerer.definitions() if self.record_lowerer else ()
|
|
424
|
+
functions = tuple(self._function(self.nodes[node_id]) for node_id in function_ids)
|
|
425
|
+
prototypes = tuple(self._prototype(self.nodes[node_id]) for node_id in function_ids)
|
|
426
|
+
declarations = record_declarations + prototypes + functions
|
|
427
|
+
schema = KEYWORD_ONLY_CALL_SCHEMA_VERSION if self.phase14d else KEYWORD_CALL_SCHEMA_VERSION if self.phase14c else CONDITIONAL_SCHEMA_VERSION if self.phase14b else NUMERIC_SCHEMA_VERSION if self.phase14 else RECORD_SCHEMA_VERSION if self.phase13 else MODULE_SCHEMA_VERSION if self.phase12 else CONTAINER_SCHEMA_VERSION if self.phase11 else SCHEMA_VERSION
|
|
428
|
+
else:
|
|
429
|
+
if len(body_ids) != 1 or self.nodes[body_ids[0]]["kind"] != "FunctionDef":
|
|
430
|
+
self._reject("PYC2601", "The legacy Phase 6/8 rule set supports exactly one annotated top-level function", self.root)
|
|
431
|
+
declarations = (self._function(self.nodes[body_ids[0]]),)
|
|
432
|
+
schema = "c-ir/0.8"
|
|
433
|
+
manifest: tuple[CModuleManifestEntry, ...] = ()
|
|
434
|
+
module_order: tuple[str, ...] = ()
|
|
435
|
+
module_dependencies: tuple[tuple[str, str], ...] = ()
|
|
436
|
+
if self.phase12:
|
|
437
|
+
module_order = tuple(self.module_initialization.get("module_order", ()))
|
|
438
|
+
manifest = tuple(
|
|
439
|
+
CModuleManifestEntry(
|
|
440
|
+
module_id,
|
|
441
|
+
self.module_identities[module_id]["document_id"],
|
|
442
|
+
self.module_identities[module_id]["logical_name"],
|
|
443
|
+
self.module_identities[module_id]["bundle_ordinal"],
|
|
444
|
+
self.module_identities[module_id]["is_primary"],
|
|
445
|
+
)
|
|
446
|
+
for module_id in module_order
|
|
447
|
+
)
|
|
448
|
+
module_dependencies = tuple(
|
|
449
|
+
(item["importer_module_id"], item["target_module_id"])
|
|
450
|
+
for item in self.module_initialization.get("dependency_edges", ())
|
|
451
|
+
)
|
|
452
|
+
builder = CTranslationUnitBuilder(
|
|
453
|
+
self.target_contract,
|
|
454
|
+
node_id=_sid("c-tu-", self.root["node_id"]),
|
|
455
|
+
provenance=self._prov(self.root),
|
|
456
|
+
schema_version=schema,
|
|
457
|
+
module_manifest=manifest,
|
|
458
|
+
module_order=module_order,
|
|
459
|
+
module_dependencies=module_dependencies,
|
|
460
|
+
)
|
|
461
|
+
if self._uses_base(declarations, "int64_t"):
|
|
462
|
+
builder.add_include(CInclude(_sid("c-inc-", "stdint.h"), "stdint.h", True, CProvenance("synthetic")))
|
|
463
|
+
if self._uses_base(declarations, "bool") or self._contains_instance(declarations, CBooleanLiteral):
|
|
464
|
+
builder.add_include(CInclude(_sid("c-inc-", "stdbool.h"), "stdbool.h", True, CProvenance("synthetic")))
|
|
465
|
+
for declaration in declarations:
|
|
466
|
+
builder.add_declaration(declaration)
|
|
467
|
+
source_unit = builder.build()
|
|
468
|
+
expected_helper_policy = self.payload.get("helper_policy_version")
|
|
469
|
+
if expected_helper_policy != self.helper_registry.registry_version:
|
|
470
|
+
raise HelperRegistryError(
|
|
471
|
+
"PYC3305",
|
|
472
|
+
"conversion plan and helper registry policy identities disagree",
|
|
473
|
+
)
|
|
474
|
+
helper_plan = self.helper_registry.resolve(
|
|
475
|
+
self.payload.get("helper_requirements", ()),
|
|
476
|
+
target_contract=self.target_contract,
|
|
477
|
+
cancellation=self.cancellation,
|
|
478
|
+
)
|
|
479
|
+
return assemble_translation_unit(source_unit, helper_plan), helper_plan
|
|
480
|
+
def _validate_phase11_facts(self) -> None:
|
|
481
|
+
"""Reject the earliest unsupported container boundary before generic lowering."""
|
|
482
|
+
candidates: list[tuple[tuple[int, str], int, str, str, dict[str, Any]]] = []
|
|
483
|
+
def add(node_id: str | None, priority: int, code: str | None, reason: str | None) -> None:
|
|
484
|
+
node = self.nodes.get(node_id or "")
|
|
485
|
+
if node is None:
|
|
486
|
+
return
|
|
487
|
+
candidates.append(
|
|
488
|
+
(
|
|
489
|
+
self._source_ordinal(node["node_id"]),
|
|
490
|
+
priority,
|
|
491
|
+
code or "PYC3403",
|
|
492
|
+
reason or "Unsupported fixed-container form",
|
|
493
|
+
node,
|
|
494
|
+
)
|
|
495
|
+
)
|
|
496
|
+
bound_literals = {
|
|
497
|
+
item["literal_node_id"]
|
|
498
|
+
for item in self.container_bindings.values()
|
|
499
|
+
}
|
|
500
|
+
for shape in self.container_shapes.values():
|
|
501
|
+
if not shape["valid"]:
|
|
502
|
+
add(shape["literal_node_id"], 0, shape.get("diagnostic_code"), shape.get("reason"))
|
|
503
|
+
elif shape["literal_node_id"] not in bound_literals:
|
|
504
|
+
add(
|
|
505
|
+
shape["literal_node_id"],
|
|
506
|
+
4,
|
|
507
|
+
"PYC3403",
|
|
508
|
+
"A supported container literal must be assigned once to a direct function-local name",
|
|
509
|
+
)
|
|
510
|
+
for binding in self.container_bindings.values():
|
|
511
|
+
if not binding["valid"]:
|
|
512
|
+
add(binding.get("rejection_node_id") or binding["assignment_node_id"], 1, binding.get("diagnostic_code"), binding.get("reason"))
|
|
513
|
+
for access in self.container_accesses.values():
|
|
514
|
+
if not access["supported"]:
|
|
515
|
+
add(access["subscript_node_id"], 2, access.get("diagnostic_code"), access.get("reason"))
|
|
516
|
+
elif not self.container_bindings.get(access.get("binding_id"), {}).get("valid"):
|
|
517
|
+
add(access["subscript_node_id"], 2, "PYC3403", "Container access depends on an invalid fixed binding")
|
|
518
|
+
for iteration in self.container_iterations.values():
|
|
519
|
+
if not iteration["supported"]:
|
|
520
|
+
add(iteration["for_node_id"], 2, iteration.get("diagnostic_code"), iteration.get("reason"))
|
|
521
|
+
elif not self.container_bindings.get(iteration.get("binding_id"), {}).get("valid"):
|
|
522
|
+
add(iteration["for_node_id"], 2, "PYC3403", "Container iteration depends on an invalid fixed binding")
|
|
523
|
+
if candidates:
|
|
524
|
+
_, _, code, reason, node = min(
|
|
525
|
+
candidates,
|
|
526
|
+
key=lambda item: (item[0], item[1], item[2], item[3]),
|
|
527
|
+
)
|
|
528
|
+
self._reject(code, reason, node)
|
|
529
|
+
def _validate_phase9_facts(self, body_ids: list[str]) -> None:
|
|
530
|
+
recursive = set(self.call_graph.get("recursive_function_node_ids", []))
|
|
531
|
+
# Signature shape is authoritative for calls found in defaults,
|
|
532
|
+
# decorators, or annotations: those forms reject as signature-policy
|
|
533
|
+
# violations rather than as misleading dynamic calls.
|
|
534
|
+
for function_id in body_ids:
|
|
535
|
+
node = self.nodes[function_id]
|
|
536
|
+
signature = self.signatures.get(function_id)
|
|
537
|
+
if not signature:
|
|
538
|
+
self._reject("PYC2932", "Function signature facts are missing", node)
|
|
539
|
+
if not signature["eligible"]:
|
|
540
|
+
reason = signature.get("rejection_reason") or "Function signature is unsupported"
|
|
541
|
+
code = "PYC2911" if any(word in reason for word in ("default", "variadic", "keyword-only", "duplicate positional parameter")) else "PYC2914" if "decorated" in reason else "PYC2902" if "rebound" in reason else "PYC2932"
|
|
542
|
+
self._reject(code, reason, node)
|
|
543
|
+
# Preserve the root unsupported call diagnostic before a containing
|
|
544
|
+
# return is rejected merely because that call's result category is
|
|
545
|
+
# intentionally unknown.
|
|
546
|
+
for call in sorted(self.calls.values(), key=lambda item: self._source_ordinal(item["call_node_id"])):
|
|
547
|
+
cumulative_keyword_target = (call.get("diagnostic_code"), call.get("reason")) in {
|
|
548
|
+
(CUMULATIVE_KEYWORD_TARGET_DIAGNOSTIC_CODE, CUMULATIVE_KEYWORD_TARGET_REASON),
|
|
549
|
+
(CUMULATIVE_KEYWORD_ONLY_TARGET_DIAGNOSTIC_CODE, CUMULATIVE_KEYWORD_ONLY_TARGET_REASON),
|
|
550
|
+
}
|
|
551
|
+
if not call["supported"] and not cumulative_keyword_target:
|
|
552
|
+
self._reject(call.get("diagnostic_code") or "PYC2901", call.get("reason") or "Unsupported call", self.nodes[call["call_node_id"]])
|
|
553
|
+
for function_id in body_ids:
|
|
554
|
+
node = self.nodes[function_id]
|
|
555
|
+
local = self.local_facts.get(function_id)
|
|
556
|
+
if local:
|
|
557
|
+
if local["use_before_binding_node_ids"]:
|
|
558
|
+
self._reject("PYC2940", "Local name may be used before its first binding", self.nodes[local["use_before_binding_node_ids"][0]])
|
|
559
|
+
if local["loop_target_escape_node_ids"]:
|
|
560
|
+
self._reject("PYC2941", "A loop-local target is used outside its valid C lifetime", self.nodes[local["loop_target_escape_node_ids"][0]])
|
|
561
|
+
if local["first_definitions_in_control_node_ids"]:
|
|
562
|
+
self._reject("PYC2870", "Bindings first defined inside a branch or loop remain unsupported", self.nodes[local["first_definitions_in_control_node_ids"][0]])
|
|
563
|
+
if local.get("representation_conflict_node_ids"):
|
|
564
|
+
self._reject("PYC2943", "A binding is assigned incompatible C representations", self.nodes[local["representation_conflict_node_ids"][0]])
|
|
565
|
+
if local.get("loop_target_rebind_node_ids"):
|
|
566
|
+
self._reject("PYC2944", "Reusing a parameter, local, or earlier loop target as a range target is unsupported", self.nodes[local["loop_target_rebind_node_ids"][0]])
|
|
567
|
+
if local.get("loop_target_mutation_node_ids"):
|
|
568
|
+
self._reject("PYC2847", "Mutating the active range loop target would change Python iteration semantics", self.nodes[local["loop_target_mutation_node_ids"][0]])
|
|
569
|
+
path = self.return_paths.get(function_id)
|
|
570
|
+
if path and path["fallthrough_possible"]:
|
|
571
|
+
self._reject("PYC2931", "A reachable function path falls through to implicit None", node)
|
|
572
|
+
if not path or not path["compatible"]:
|
|
573
|
+
self._reject("PYC2930", "Every explicit return must match the declared return representation", node)
|
|
574
|
+
if function_id in recursive:
|
|
575
|
+
self._reject("PYC2920", "Direct and mutual recursion are unsupported in Phase 9", node)
|
|
576
|
+
def _signature(self, node: dict[str, Any]) -> dict[str, Any]:
|
|
577
|
+
if node["node_id"] in self.signatures:
|
|
578
|
+
return self.signatures[node["node_id"]]
|
|
579
|
+
args_node = self.nodes[node["fields"]["args"]]
|
|
580
|
+
arg_ids = lowered_parameter_node_ids(args_node["fields"], include_required_keyword_only=self.phase14d)
|
|
581
|
+
parameters = []
|
|
582
|
+
for ordinal, arg_id in enumerate(arg_ids):
|
|
583
|
+
self._check_cancel()
|
|
584
|
+
arg = self.nodes[arg_id]
|
|
585
|
+
binding = self.binding_by_decl.get(arg_id)
|
|
586
|
+
category = self._annotation_category(arg["fields"].get("annotation"))
|
|
587
|
+
parameters.append({"parameter_node_id": arg_id, "binding_id": binding["binding_id"] if binding else "", "source_name": arg["fields"].get("arg", ""), "ordinal": ordinal, "category": category, "c_type": self._category_type_name(category)})
|
|
588
|
+
return_category = self._annotation_category(node["fields"].get("returns"))
|
|
589
|
+
binding = self.binding_by_decl.get(node["node_id"])
|
|
590
|
+
return {"function_node_id": node["node_id"], "binding_id": binding["binding_id"] if binding else None, "source_name": node["fields"].get("name", ""), "parameters": parameters, "return_category": return_category, "return_c_type": self._category_type_name(return_category), "eligible": True}
|
|
591
|
+
def _prototype(self, node: dict[str, Any]) -> CFunctionPrototype:
|
|
592
|
+
signature = self._signature(node)
|
|
593
|
+
binding = self.binding_by_decl.get(node["node_id"])
|
|
594
|
+
if not binding:
|
|
595
|
+
self._reject("PYC2603", "Function binding could not be resolved", node)
|
|
596
|
+
parameters = tuple(self._parameter(self.nodes[item["parameter_node_id"]], prototype=True, function_node=node) for item in signature["parameters"])
|
|
597
|
+
module_function = self.module_functions.get(node["node_id"], {})
|
|
598
|
+
return CFunctionPrototype(
|
|
599
|
+
_sid("c-prototype-", node["node_id"]),
|
|
600
|
+
self._identifier(binding, node),
|
|
601
|
+
self._type_from_name(signature["return_c_type"]),
|
|
602
|
+
parameters,
|
|
603
|
+
CStorage.NONE,
|
|
604
|
+
self._synthetic_prov((node["node_id"],), node["node_id"]),
|
|
605
|
+
owner_module_id=module_function.get("module_id"),
|
|
606
|
+
owner_document_id=module_function.get("document_id"),
|
|
607
|
+
bundle_function_ordinal=module_function.get("bundle_function_ordinal"),
|
|
608
|
+
)
|
|
609
|
+
def _function(self, node: dict[str, Any]) -> CFunctionDefinition:
|
|
610
|
+
self._check_cancel()
|
|
611
|
+
fields = node["fields"]
|
|
612
|
+
if fields.get("decorator_list") or fields.get("type_params"):
|
|
613
|
+
self._reject("PYC2914" if self.phase9 else "PYC2602", "Decorated or generic functions are outside the selected subset", node)
|
|
614
|
+
binding = self.binding_by_decl.get(node["node_id"])
|
|
615
|
+
if not binding:
|
|
616
|
+
self._reject("PYC2603", "Function binding could not be resolved", node)
|
|
617
|
+
args_node = self.nodes[fields["args"]]
|
|
618
|
+
if not lowering_supports_parameter_shape(args_node["fields"], allow_required_keyword_only=self.phase14d):
|
|
619
|
+
message = "Only annotated positional and required keyword-only parameters without defaults are supported; variadics and all defaults are unsupported" if self.phase14d else "Only positional annotated parameters without defaults are supported"
|
|
620
|
+
self._reject("PYC2911" if self.phase9 else "PYC2604", message, node)
|
|
621
|
+
signature = self._signature(node)
|
|
622
|
+
parameters = tuple(self._parameter(self.nodes[item["parameter_node_id"]], prototype=False, function_node=node) for item in signature["parameters"])
|
|
623
|
+
statements = tuple(item for statement_id in fields.get("body", []) for item in self._statements(self.nodes[statement_id]))
|
|
624
|
+
module_function = self.module_functions.get(node["node_id"], {})
|
|
625
|
+
return CFunctionDefinition(
|
|
626
|
+
_sid("c-fn-", node["node_id"]),
|
|
627
|
+
self._identifier(binding, node),
|
|
628
|
+
self._type_from_name(signature["return_c_type"]),
|
|
629
|
+
parameters,
|
|
630
|
+
CBlock(_sid("c-block-", node["node_id"]), statements, self._prov(node)),
|
|
631
|
+
CStorage.NONE,
|
|
632
|
+
self._prov(node),
|
|
633
|
+
owner_module_id=module_function.get("module_id"),
|
|
634
|
+
owner_document_id=module_function.get("document_id"),
|
|
635
|
+
bundle_function_ordinal=module_function.get("bundle_function_ordinal"),
|
|
636
|
+
)
|
|
637
|
+
def _parameter(self, node: dict[str, Any], *, prototype: bool, function_node: dict[str, Any]) -> CParameter:
|
|
638
|
+
self._check_cancel()
|
|
639
|
+
binding = self.binding_by_decl.get(node["node_id"])
|
|
640
|
+
if not binding:
|
|
641
|
+
self._reject("PYC2605", "Parameter binding could not be resolved", node)
|
|
642
|
+
category = self._annotation_category(node["fields"].get("annotation"))
|
|
643
|
+
if category == "unknown":
|
|
644
|
+
self._reject("PYC2932" if self.phase9 else "PYC2632", f"Unsupported annotation for parameter {node['fields'].get('arg')}", node)
|
|
645
|
+
prefix = "c-proto-param-" if prototype else "c-param-"
|
|
646
|
+
provenance = self._prov(node, fallback_plan_node=function_node["node_id"])
|
|
647
|
+
return CParameter(_sid(prefix, node["node_id"]), self._identifier(binding, node), self._category_type(category), provenance)
|
|
648
|
+
def _block_statements(self, node_ids: list[str], *, loop: bool = False) -> tuple[Any, ...]:
|
|
649
|
+
self.control_depth += 1
|
|
650
|
+
if loop: self.loop_depth += 1
|
|
651
|
+
try:
|
|
652
|
+
return tuple(item for node_id in node_ids for item in self._statements(self.nodes[node_id]))
|
|
653
|
+
finally:
|
|
654
|
+
if loop: self.loop_depth -= 1
|
|
655
|
+
self.control_depth -= 1
|
|
656
|
+
def _statements(self, node: dict[str, Any]) -> tuple[Any, ...]:
|
|
657
|
+
self._check_cancel()
|
|
658
|
+
kind = node["kind"]
|
|
659
|
+
fields = node["fields"]
|
|
660
|
+
if kind == "Return":
|
|
661
|
+
value_id = fields.get("value")
|
|
662
|
+
if not value_id:
|
|
663
|
+
self._reject("PYC2930" if self.phase9 else "PYC2821", "Bare return is unsupported for an annotated non-None signature", node)
|
|
664
|
+
prelude, value = self._expression(self.nodes[value_id])
|
|
665
|
+
return prelude + (CReturnStatement(_sid("c-ret-", node["node_id"]), value, self._prov(node)),)
|
|
666
|
+
if kind == "Assign":
|
|
667
|
+
if self.phase13 and node["node_id"] in self.record_instance_by_assignment:
|
|
668
|
+
if self.control_depth:
|
|
669
|
+
self._reject("PYC3605", "Static-record construction must be direct in a function body", node)
|
|
670
|
+
return self.record_lowerer.construction(node)
|
|
671
|
+
if self.phase11 and node["node_id"] in self.container_binding_by_assignment:
|
|
672
|
+
return self.container_lowerer.declaration(node)
|
|
673
|
+
targets = fields.get("targets", [])
|
|
674
|
+
if len(targets) != 1 or self.nodes[targets[0]]["kind"] != "Name":
|
|
675
|
+
self._reject("PYC2810", "Only single-name assignment is supported", node)
|
|
676
|
+
target = self.nodes[targets[0]]
|
|
677
|
+
binding = self.binding_by_occurrence.get(target["node_id"])
|
|
678
|
+
if not binding:
|
|
679
|
+
self._reject("PYC2811", "Assignment target binding could not be resolved", target)
|
|
680
|
+
prelude, value = self._expression(self.nodes[fields["value"]])
|
|
681
|
+
if binding["declaration_node_id"] == target["node_id"]:
|
|
682
|
+
if self.control_depth:
|
|
683
|
+
self._reject("PYC2870", "Bindings first defined inside a branch or loop are unsupported", target)
|
|
684
|
+
category = self.categories.get(fields["value"], "unknown")
|
|
685
|
+
declaration = CVariableDeclaration(_sid("c-var-", node["node_id"]), self._identifier(binding, target), self._category_type(category), value, CStorage.NONE, self._prov(node))
|
|
686
|
+
return prelude + (declaration,)
|
|
687
|
+
assignment = CAssignmentStatement(_sid("c-assign-", node["node_id"]), self._ref(binding, target, "c-target-"), value, self._prov(node))
|
|
688
|
+
return prelude + (assignment,)
|
|
689
|
+
if kind == "Expr":
|
|
690
|
+
value_node = self.nodes[fields["value"]]
|
|
691
|
+
if value_node["kind"] == "Call":
|
|
692
|
+
prelude, call = self._call(value_node, materialize=False)
|
|
693
|
+
return prelude + (CExpressionStatement(_sid("c-expr-stmt-", node["node_id"]), call, self._prov(node)),)
|
|
694
|
+
self._reject("PYC2812", "Only understood call expressions may be used as expression statements", node)
|
|
695
|
+
if kind == "If":
|
|
696
|
+
prelude, condition = self._truthy(self.nodes[fields["test"]])
|
|
697
|
+
body = CBlock(_sid("c-if-body-", node["node_id"]), self._block_statements(fields.get("body", [])), self._prov(node))
|
|
698
|
+
other_ids = fields.get("orelse", [])
|
|
699
|
+
other = CBlock(_sid("c-if-else-", node["node_id"]), self._block_statements(other_ids), self._prov(node)) if other_ids else None
|
|
700
|
+
statement = CIfStatement(_sid("c-if-", node["node_id"]), condition, body, other, self._prov(node))
|
|
701
|
+
return prelude + (statement,)
|
|
702
|
+
if kind == "While":
|
|
703
|
+
if fields.get("orelse"):
|
|
704
|
+
self._reject("PYC2830", "while else is not supported", node)
|
|
705
|
+
condition_prelude, condition = self._truthy(self.nodes[fields["test"]])
|
|
706
|
+
source_body = self._block_statements(fields.get("body", []), loop=True)
|
|
707
|
+
if condition_prelude:
|
|
708
|
+
negated = CUnaryExpr(_sid("c-while-not-", node["node_id"]), CUnaryOp.LOGICAL_NOT, condition, self._synthetic_prov((fields["test"],), node["node_id"]))
|
|
709
|
+
guard = CIfStatement(
|
|
710
|
+
_sid("c-while-guard-", node["node_id"]),
|
|
711
|
+
negated,
|
|
712
|
+
CBlock(_sid("c-while-guard-body-", node["node_id"]), (CBreakStatement(_sid("c-while-guard-break-", node["node_id"]), self._synthetic_prov((node["node_id"],), node["node_id"])),), self._synthetic_prov((node["node_id"],), node["node_id"])),
|
|
713
|
+
None,
|
|
714
|
+
self._synthetic_prov((node["node_id"],), node["node_id"]),
|
|
715
|
+
)
|
|
716
|
+
body = CBlock(_sid("c-while-body-", node["node_id"]), condition_prelude + (guard,) + source_body, self._prov(node))
|
|
717
|
+
condition = CBooleanLiteral(_sid("c-while-true-", node["node_id"]), True, self._synthetic_prov((node["node_id"],), node["node_id"]))
|
|
718
|
+
else:
|
|
719
|
+
body = CBlock(_sid("c-while-body-", node["node_id"]), source_body, self._prov(node))
|
|
720
|
+
return (CWhileStatement(_sid("c-while-", node["node_id"]), condition, body, self._prov(node)),)
|
|
721
|
+
if kind == "For":
|
|
722
|
+
if self.phase11 and node["node_id"] in self.container_iterations:
|
|
723
|
+
return self.container_lowerer.iteration(node)
|
|
724
|
+
return self._for_range(node)
|
|
725
|
+
if kind == "Break":
|
|
726
|
+
if not self.loop_depth: self._reject("PYC2871", "break requires an enclosing supported loop", node)
|
|
727
|
+
return (CBreakStatement(_sid("c-break-", node["node_id"]), self._prov(node)),)
|
|
728
|
+
if kind == "Continue":
|
|
729
|
+
if not self.loop_depth: self._reject("PYC2872", "continue requires an enclosing supported loop", node)
|
|
730
|
+
return (CContinueStatement(_sid("c-continue-", node["node_id"]), self._prov(node)),)
|
|
731
|
+
if kind in {"FunctionDef", "AsyncFunctionDef"}:
|
|
732
|
+
self._reject("PYC2915", "Nested functions and closures are unsupported in Phase 9", node)
|
|
733
|
+
self._reject("PYC2812", f"Unsupported statement in the selected subset: {kind}", node)
|
|
734
|
+
def _for_range(self, node: dict[str, Any]) -> tuple[Any, ...]:
|
|
735
|
+
fields = node["fields"]
|
|
736
|
+
if fields.get("orelse"):
|
|
737
|
+
self._reject("PYC2840", "for else is not supported", node)
|
|
738
|
+
target = self.nodes[fields["target"]]
|
|
739
|
+
iterator = self.nodes[fields["iter"]]
|
|
740
|
+
if target["kind"] != "Name" or iterator["kind"] != "Call":
|
|
741
|
+
self._reject("PYC2841", "for supports only a single name over range(...) ", node)
|
|
742
|
+
function = self.nodes[iterator["fields"]["func"]]
|
|
743
|
+
args = iterator["fields"].get("args", [])
|
|
744
|
+
function_binding = self.binding_by_occurrence.get(function["node_id"])
|
|
745
|
+
recognized_builtin = bool(function_binding and function_binding["binding_kind"] == "implicit-global")
|
|
746
|
+
call_fact = self.calls.get(iterator["node_id"])
|
|
747
|
+
if self.phase9 and (not call_fact or call_fact.get("resolution") != "recognized-range"):
|
|
748
|
+
recognized_builtin = False
|
|
749
|
+
if function["kind"] != "Name" or function["fields"].get("id") != "range" or not recognized_builtin or iterator["fields"].get("keywords") or not 1 <= len(args) <= 3:
|
|
750
|
+
self._reject("PYC2842", "for supports only positional range(stop), range(start, stop), or range(start, stop, step)", iterator)
|
|
751
|
+
if any(self.categories.get(arg_id) != "integer-like" for arg_id in args):
|
|
752
|
+
self._reject("PYC2846", "range arguments require stable integer representations", iterator)
|
|
753
|
+
step_sign = 1
|
|
754
|
+
if len(args) == 3:
|
|
755
|
+
step_sign = self._literal_integer_sign(self.nodes[args[2]])
|
|
756
|
+
if step_sign == 0:
|
|
757
|
+
self._reject("PYC2843", "range step cannot be zero", self.nodes[args[2]])
|
|
758
|
+
if step_sign is None:
|
|
759
|
+
self._reject("PYC2845", "Phase 8/9 range step must be a nonzero integer literal", self.nodes[args[2]])
|
|
760
|
+
prelude: list[Any] = []
|
|
761
|
+
argument_refs: list[Any] = []
|
|
762
|
+
for ordinal, argument_id in enumerate(args):
|
|
763
|
+
expression_prelude, expression = self._expression(self.nodes[argument_id])
|
|
764
|
+
prelude.extend(expression_prelude)
|
|
765
|
+
declaration, reference = self._temporary("range", iterator, ordinal, CType("int64_t"), expression, (argument_id,))
|
|
766
|
+
prelude.append(declaration)
|
|
767
|
+
argument_refs.append(reference)
|
|
768
|
+
zero = CIntegerLiteral(_sid("c-range-zero-", node["node_id"]), 0, "LL", self._synthetic_prov((node["node_id"],), node["node_id"]))
|
|
769
|
+
one = CIntegerLiteral(_sid("c-range-one-", node["node_id"]), 1, "LL", self._synthetic_prov((node["node_id"],), node["node_id"]))
|
|
770
|
+
start, stop, step = (zero, argument_refs[0], one) if len(args) == 1 else ((argument_refs[0], argument_refs[1], one) if len(args) == 2 else tuple(argument_refs))
|
|
771
|
+
binding = self.binding_by_occurrence.get(target["node_id"])
|
|
772
|
+
if not binding:
|
|
773
|
+
self._reject("PYC2844", "Loop target binding could not be resolved", target)
|
|
774
|
+
identifier = self._identifier(binding, target)
|
|
775
|
+
initializer = CVariableDeclaration(_sid("c-for-init-", node["node_id"]), identifier, CType("int64_t"), start, CStorage.NONE, self._prov(target, fallback_plan_node=node["node_id"]))
|
|
776
|
+
condition = CBinaryExpr(_sid("c-for-cond-", node["node_id"]), CBinaryOp.GREATER if step_sign < 0 else CBinaryOp.LESS, self._ref(binding, target, "c-for-cond-ref-"), stop, self._prov(node))
|
|
777
|
+
increment = CBinaryExpr(_sid("c-for-inc-expr-", node["node_id"]), CBinaryOp.ADD, self._ref(binding, target, "c-for-inc-left-"), step, self._prov(node))
|
|
778
|
+
update = CAssignmentStatement(_sid("c-for-update-", node["node_id"]), self._ref(binding, target, "c-for-update-target-"), increment, self._prov(node))
|
|
779
|
+
body = CBlock(_sid("c-for-body-", node["node_id"]), self._block_statements(fields.get("body", []), loop=True), self._prov(node))
|
|
780
|
+
return tuple(prelude) + (CForStatement(_sid("c-for-", node["node_id"]), initializer, condition, update, body, self._prov(node)),)
|
|
781
|
+
def _expression(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], Any]:
|
|
782
|
+
self._check_cancel()
|
|
783
|
+
kind = node["kind"]
|
|
784
|
+
provenance = self._prov(node)
|
|
785
|
+
if kind == "Attribute" and self.phase13 and node["node_id"] in self.record_accesses:
|
|
786
|
+
return self.record_lowerer.access(node)
|
|
787
|
+
if kind == "Subscript" and self.phase11:
|
|
788
|
+
return self.container_lowerer.access(node)
|
|
789
|
+
if kind == "Name":
|
|
790
|
+
binding = self.binding_by_occurrence.get(node["node_id"])
|
|
791
|
+
if not binding:
|
|
792
|
+
self._reject("PYC2820", f"Unresolved name: {node['fields'].get('id')}", node)
|
|
793
|
+
if binding["binding_kind"] in {"function", "nested-function"}:
|
|
794
|
+
self._reject("PYC2901", "First-class function values are unsupported; use a direct understood call", node)
|
|
795
|
+
if binding["binding_id"] in self.container_bindings:
|
|
796
|
+
self._reject("PYC3403", "A fixed container name is valid only as a proved access base or bounded iterable", node)
|
|
797
|
+
if binding["binding_id"] in self.record_bindings:
|
|
798
|
+
self._reject("PYC3606", "A static-record name is valid only as a proved direct field receiver", node)
|
|
799
|
+
return (), self._ref(binding, node, "c-ref-")
|
|
800
|
+
if kind == "Constant":
|
|
801
|
+
value = node["fields"].get("value")
|
|
802
|
+
if isinstance(value,(list,tuple)) and len(value)>=2 and tuple(value[:2]) == ("unsupported-python-value","float-nonfinite"):
|
|
803
|
+
self._reject("PYC2822", "Non-finite floating literals are unsupported", node)
|
|
804
|
+
if isinstance(value, bool): return (), CBooleanLiteral(_sid("c-bool-", node["node_id"]), value, provenance)
|
|
805
|
+
if isinstance(value, int):
|
|
806
|
+
if value < 0 or value > 2**63-1: self._reject("PYC2822", "Integer literal is outside the supported non-negative int64_t literal domain", node)
|
|
807
|
+
return (), CIntegerLiteral(_sid("c-int-", node["node_id"]), value, "LL", provenance)
|
|
808
|
+
if isinstance(value, float):
|
|
809
|
+
if not math.isfinite(value): self._reject("PYC2822", "Non-finite floating literals are unsupported", node)
|
|
810
|
+
return (), CFloatLiteral(_sid("c-float-", node["node_id"]), value, provenance)
|
|
811
|
+
if isinstance(value, str):
|
|
812
|
+
if "\x00" in value: self._reject("PYC2821", "Embedded NUL string literals are unsupported", node)
|
|
813
|
+
return (), CStringLiteral(_sid("c-str-", node["node_id"]), value, "utf-8", provenance)
|
|
814
|
+
self._reject("PYC2821", f"Unsupported literal: {type(value).__name__}", node)
|
|
815
|
+
if kind == "UnaryOp":
|
|
816
|
+
operator_kind = self.nodes[node["fields"]["op"]]["kind"]
|
|
817
|
+
if operator_kind == "Not":
|
|
818
|
+
truth_prelude, truth = self._truthy(self.nodes[node["fields"]["operand"]])
|
|
819
|
+
return truth_prelude, CUnaryExpr(_sid("c-not-", node["node_id"]), CUnaryOp.LOGICAL_NOT, truth, provenance)
|
|
820
|
+
prelude, operand = self._expression(self.nodes[node["fields"]["operand"]])
|
|
821
|
+
if operator_kind == "USub": return prelude, CUnaryExpr(_sid("c-neg-", node["node_id"]), CUnaryOp.NEGATE, operand, provenance)
|
|
822
|
+
if operator_kind == "UAdd": return prelude, operand
|
|
823
|
+
self._reject("PYC2827", f"Unsupported unary operator: {operator_kind}", node)
|
|
824
|
+
if kind == "Call":
|
|
825
|
+
return self._call(node, materialize=True)
|
|
826
|
+
if kind in {"List", "Tuple", "Dict", "ListComp", "DictComp", "GeneratorExp"}:
|
|
827
|
+
shape = self.container_shapes.get(node["node_id"])
|
|
828
|
+
self._reject(
|
|
829
|
+
shape.get("diagnostic_code") if shape else "PYC3403",
|
|
830
|
+
shape.get("reason") if shape else "Container literals require an approved fixed local binding",
|
|
831
|
+
node,
|
|
832
|
+
)
|
|
833
|
+
if kind == "BoolOp":
|
|
834
|
+
return self.conditional_region_lowerer.boolean(node)
|
|
835
|
+
if kind == "Compare":
|
|
836
|
+
return self.conditional_region_lowerer.comparison(node)
|
|
837
|
+
if kind == "BinOp":
|
|
838
|
+
operator_kind = self.nodes[node["fields"]["op"]]["kind"]
|
|
839
|
+
if operator_kind in {"FloorDiv", "Mod"} and self.phase14:
|
|
840
|
+
return self.numeric_lowerer.operation(node)
|
|
841
|
+
operators = {"Add": CBinaryOp.ADD, "Sub": CBinaryOp.SUBTRACT, "Mult": CBinaryOp.MULTIPLY, "Div": CBinaryOp.DIVIDE}
|
|
842
|
+
if operator_kind not in operators:
|
|
843
|
+
self._reject("PYC2622", f"Unsupported arithmetic operator: {operator_kind}", node)
|
|
844
|
+
category = self.categories.get(node["node_id"], "unknown")
|
|
845
|
+
if category == "string-like": self._reject("PYC2823", "String concatenation remains outside the literal-only string boundary", node)
|
|
846
|
+
if category not in {"integer-like", "floating-like"}: self._reject("PYC2824", "Arithmetic operands do not have a supported stable representation", node)
|
|
847
|
+
if operator_kind == "Div" and category != "floating-like": self._reject("PYC2824", "Python division is supported only for floating-represented operands", node)
|
|
848
|
+
left_prelude, left = self._expression(self.nodes[node["fields"]["left"]])
|
|
849
|
+
right_prelude, right = self._expression(self.nodes[node["fields"]["right"]])
|
|
850
|
+
return left_prelude + right_prelude, CBinaryExpr(_sid("c-bin-", node["node_id"]), operators[operator_kind], left, right, provenance)
|
|
851
|
+
self._reject("PYC2825", f"Unsupported expression in the selected subset: {kind}", node)
|
|
852
|
+
def _call(self, node: dict[str, Any], *, materialize: bool) -> tuple[tuple[Any, ...], Any]:
|
|
853
|
+
fact = self.calls.get(node["node_id"])
|
|
854
|
+
feature_keyword_only_call = bool(self.phase14d and self.keyword_only_call_lowerer.has_fact(node["node_id"]))
|
|
855
|
+
feature_keyword_call = bool(
|
|
856
|
+
not feature_keyword_only_call
|
|
857
|
+
and self.phase14c
|
|
858
|
+
and node.get("fields", {}).get("keywords")
|
|
859
|
+
and self.keyword_call_lowerer.has_fact(node["node_id"])
|
|
860
|
+
)
|
|
861
|
+
signature = (
|
|
862
|
+
self.signatures.get(fact.get("target_function_node_id"))
|
|
863
|
+
if fact
|
|
864
|
+
else None
|
|
865
|
+
)
|
|
866
|
+
if feature_keyword_only_call and signature is not None:
|
|
867
|
+
prelude_values, argument_values = self.keyword_only_call_lowerer.arguments(node, signature)
|
|
868
|
+
prelude = list(prelude_values)
|
|
869
|
+
arguments = list(argument_values)
|
|
870
|
+
elif feature_keyword_call and signature is not None:
|
|
871
|
+
prelude_values, argument_values = self.keyword_call_lowerer.arguments(
|
|
872
|
+
node,
|
|
873
|
+
signature,
|
|
874
|
+
)
|
|
875
|
+
prelude = list(prelude_values)
|
|
876
|
+
arguments = list(argument_values)
|
|
877
|
+
else:
|
|
878
|
+
prelude = []
|
|
879
|
+
arguments = []
|
|
880
|
+
if not fact or not fact.get("supported") or fact.get("resolution") != "understood-source-function":
|
|
881
|
+
code = fact.get("diagnostic_code") if fact else "PYC2901"
|
|
882
|
+
reason = fact.get("reason") if fact else "Call target is not an understood source-defined function"
|
|
883
|
+
self._reject(code or "PYC2901", reason or "Unsupported call", node)
|
|
884
|
+
if signature is None:
|
|
885
|
+
self._reject("PYC2901", "Resolved call target signature is absent", node)
|
|
886
|
+
if not (feature_keyword_only_call or feature_keyword_call):
|
|
887
|
+
for ordinal, (argument_id, parameter) in enumerate(zip(fact["argument_node_ids"], signature["parameters"])):
|
|
888
|
+
expression_prelude, expression = self._expression(self.nodes[argument_id])
|
|
889
|
+
prelude.extend(expression_prelude)
|
|
890
|
+
declaration, reference = self._temporary("arg", node, ordinal, self._type_from_name(parameter["c_type"]), expression, (argument_id, node["node_id"]))
|
|
891
|
+
prelude.append(declaration)
|
|
892
|
+
arguments.append(reference)
|
|
893
|
+
target_binding = self.binding_by_id.get(fact["target_binding_id"])
|
|
894
|
+
if not target_binding:
|
|
895
|
+
self._reject("PYC2901", "Resolved call target binding is absent", node)
|
|
896
|
+
callee_node = self.nodes[node["fields"]["func"]]
|
|
897
|
+
callee = self._ref(target_binding, callee_node, "c-callee-", fallback_plan_node=node["node_id"])
|
|
898
|
+
call = CCallExpr(_sid("c-call-", node["node_id"]), callee, tuple(arguments), self._prov(node))
|
|
899
|
+
if not materialize:
|
|
900
|
+
return tuple(prelude), call
|
|
901
|
+
declaration, reference = self._temporary("call", node, 0, self._type_from_name(signature["return_c_type"]), call, (node["node_id"],))
|
|
902
|
+
prelude.append(declaration)
|
|
903
|
+
return tuple(prelude), reference
|
|
904
|
+
def _truthy(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], Any]:
|
|
905
|
+
category = self.categories.get(node["node_id"], "unknown")
|
|
906
|
+
prelude, expression = self._expression(node)
|
|
907
|
+
if category == "boolean-like": return prelude, expression
|
|
908
|
+
if category in {"integer-like", "floating-like"}:
|
|
909
|
+
zero = CFloatLiteral(_sid("c-zero-", node["node_id"]), 0.0, self._prov(node)) if category == "floating-like" else CIntegerLiteral(_sid("c-zero-", node["node_id"]), 0, "LL", self._prov(node))
|
|
910
|
+
return prelude, CBinaryExpr(_sid("c-truth-", node["node_id"]), CBinaryOp.NOT_EQUAL, expression, zero, self._prov(node))
|
|
911
|
+
self._reject("PYC2850", f"No supported truthiness representation for {category}", node)
|
|
912
|
+
def _temporary(self, purpose: str, owner: dict[str, Any], ordinal: int, type_ref: CType, initializer: Any, origin_ids: tuple[str, ...]) -> tuple[CVariableDeclaration, CIdentifierRef]:
|
|
913
|
+
binding_id = _sid(f"bind-{purpose}-", owner["node_id"], str(ordinal), *origin_ids)
|
|
914
|
+
spelling = self._temporary_spelling(purpose, binding_id)
|
|
915
|
+
provenance = self._synthetic_prov(origin_ids, owner["node_id"])
|
|
916
|
+
identifier = CIdentifier(binding_id, spelling, provenance)
|
|
917
|
+
declaration = CVariableDeclaration(_sid(f"c-{purpose}-temp-", owner["node_id"], str(ordinal), *origin_ids), identifier, type_ref, initializer, CStorage.NONE, provenance)
|
|
918
|
+
reference = CIdentifierRef(_sid(f"c-{purpose}-ref-", owner["node_id"], str(ordinal), *origin_ids), binding_id, provenance)
|
|
919
|
+
return declaration, reference
|
|
920
|
+
def _temporary_spelling(self, purpose: str, binding_id: str) -> str:
|
|
921
|
+
existing = self.temporary_spellings.get(binding_id)
|
|
922
|
+
if existing:
|
|
923
|
+
return existing
|
|
924
|
+
base = f"pycf_{purpose}_{binding_id[-12:]}"
|
|
925
|
+
candidate = base
|
|
926
|
+
suffix = 1
|
|
927
|
+
while candidate in self.generated_spellings:
|
|
928
|
+
suffix += 1
|
|
929
|
+
candidate = f"{base}_{suffix}"
|
|
930
|
+
self.generated_spellings.add(candidate)
|
|
931
|
+
self.temporary_spellings[binding_id] = candidate
|
|
932
|
+
return candidate
|
|
933
|
+
def _literal_integer_sign(self, node: dict[str, Any]) -> int | None:
|
|
934
|
+
if node["kind"] == "Constant" and isinstance(node["fields"].get("value"), int) and not isinstance(node["fields"].get("value"), bool):
|
|
935
|
+
value = node["fields"]["value"]
|
|
936
|
+
return 1 if value > 0 else -1 if value < 0 else 0
|
|
937
|
+
if node["kind"] == "UnaryOp":
|
|
938
|
+
operator = self.nodes[node["fields"]["op"]]["kind"]
|
|
939
|
+
operand = self.nodes[node["fields"]["operand"]]
|
|
940
|
+
if operand["kind"] == "Constant" and isinstance(operand["fields"].get("value"), int) and not isinstance(operand["fields"].get("value"), bool):
|
|
941
|
+
value = operand["fields"]["value"]
|
|
942
|
+
if operator == "USub": value = -value
|
|
943
|
+
elif operator != "UAdd": return None
|
|
944
|
+
return 1 if value > 0 else -1 if value < 0 else 0
|
|
945
|
+
return None
|
|
946
|
+
def _annotation_category(self, node_id: str | None) -> str:
|
|
947
|
+
if not node_id or node_id not in self.nodes: return "unknown"
|
|
948
|
+
node = self.nodes[node_id]
|
|
949
|
+
if node["kind"] != "Name": return "unknown"
|
|
950
|
+
return {"int": "integer-like", "float": "floating-like", "bool": "boolean-like", "str": "string-like"}.get(node["fields"].get("id"), "unknown")
|
|
951
|
+
@staticmethod
|
|
952
|
+
def _category_type_name(category: str) -> str | None:
|
|
953
|
+
return {"integer-like": "int64_t", "floating-like": "double", "boolean-like": "bool", "string-like": "const char *"}.get(category)
|
|
954
|
+
def _category_type(self, category: str) -> CType:
|
|
955
|
+
name = self._category_type_name(category)
|
|
956
|
+
if not name: self._reject("PYC2633", f"No C representation for {category}")
|
|
957
|
+
return self._type_from_name(name)
|
|
958
|
+
@staticmethod
|
|
959
|
+
def _type_from_name(name: str | None) -> CType:
|
|
960
|
+
if name == "int64_t": return CType("int64_t")
|
|
961
|
+
if name == "double": return CType("double")
|
|
962
|
+
if name == "bool": return CType("bool")
|
|
963
|
+
if name == "const char *": return CType("char", (CQualifier.CONST,), 1)
|
|
964
|
+
raise ValueError(f"unsupported planned C type: {name}")
|
|
965
|
+
def _identifier(self, binding: dict[str, Any], node: dict[str, Any]) -> CIdentifier:
|
|
966
|
+
spelling = self.names.get(binding["binding_id"])
|
|
967
|
+
if not spelling: self._reject("PYC2942", "Generated identifier plan is missing", node)
|
|
968
|
+
return CIdentifier(binding["binding_id"], spelling, self._prov(node))
|
|
969
|
+
def _ref(self, binding: dict[str, Any], node: dict[str, Any], prefix: str, *, fallback_plan_node: str | None = None) -> CIdentifierRef:
|
|
970
|
+
return CIdentifierRef(_sid(prefix, node["node_id"], binding["binding_id"]), binding["binding_id"], self._prov(node, fallback_plan_node=fallback_plan_node))
|
|
971
|
+
def _prov(self, node: dict[str, Any], *, fallback_plan_node: str | None = None) -> CProvenance:
|
|
972
|
+
provenance = node.get("provenance", {})
|
|
973
|
+
span = provenance.get("source_span")
|
|
974
|
+
document_id = span.get("document_id") if isinstance(span, dict) else self.document_id
|
|
975
|
+
return CProvenance("direct-source-conversion", document_id, (node["node_id"],), span, self._plan_id(node["node_id"]) or self._plan_id(fallback_plan_node))
|
|
976
|
+
def _synthetic_prov(self, origin_ids: tuple[str, ...], plan_node_id: str | None) -> CProvenance:
|
|
977
|
+
first = self.nodes.get(origin_ids[0]) if origin_ids else None
|
|
978
|
+
span = first.get("provenance", {}).get("source_span") if first else None
|
|
979
|
+
document_id = span.get("document_id") if isinstance(span, dict) else self.document_id
|
|
980
|
+
return CProvenance("synthetic", document_id, origin_ids, span, self._plan_id(plan_node_id))
|
|
981
|
+
def _plan_id(self, node_id: str | None) -> str | None:
|
|
982
|
+
plan = self.plans.get(node_id) if node_id else None
|
|
983
|
+
return plan["plan_id"] if plan else None
|
|
984
|
+
def _source_ordinal(self, node_id: str) -> tuple[int, str]:
|
|
985
|
+
span = self.nodes[node_id].get("provenance", {}).get("source_span") or {}
|
|
986
|
+
start = span.get("start", {})
|
|
987
|
+
offset = start.get("offset")
|
|
988
|
+
return (offset if isinstance(offset, int) else 2**63 - 1, node_id)
|
|
989
|
+
@classmethod
|
|
990
|
+
def _uses_base(cls, value: Any, base: str) -> bool:
|
|
991
|
+
if isinstance(value, CType): return value.base == base
|
|
992
|
+
if isinstance(value, tuple): return any(cls._uses_base(item, base) for item in value)
|
|
993
|
+
if is_dataclass(value): return any(cls._uses_base(getattr(value, field.name), base) for field in fields(value))
|
|
994
|
+
return False
|
|
995
|
+
@classmethod
|
|
996
|
+
def _contains_instance(cls, value: Any, expected: type) -> bool:
|
|
997
|
+
if isinstance(value, expected): return True
|
|
998
|
+
if isinstance(value, tuple): return any(cls._contains_instance(item, expected) for item in value)
|
|
999
|
+
if is_dataclass(value): return any(cls._contains_instance(getattr(value, field.name), expected) for field in fields(value))
|
|
1000
|
+
return False
|