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,1976 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from dataclasses import replace
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from pycforge.converter.contracts.configuration import (
|
|
9
|
+
PHASE9_RULE_SET,
|
|
10
|
+
PHASE11_RULE_SET,
|
|
11
|
+
supports_containers,
|
|
12
|
+
supports_conditional_regions,
|
|
13
|
+
supports_functions,
|
|
14
|
+
supports_keyword_calls,
|
|
15
|
+
supports_keyword_only_calls,
|
|
16
|
+
supports_modules,
|
|
17
|
+
supports_numeric,
|
|
18
|
+
supports_records,
|
|
19
|
+
)
|
|
20
|
+
from pycforge.converter.contracts.versions import (
|
|
21
|
+
CONDITIONAL_FACT_SCHEMA,
|
|
22
|
+
CONTAINER_FACT_SCHEMA,
|
|
23
|
+
CONVERSION_PLAN_SCHEMA,
|
|
24
|
+
NUMERIC_FACT_SCHEMA,
|
|
25
|
+
PHASE14B_CONVERSION_PLAN_SCHEMA,
|
|
26
|
+
PHASE14C_CONVERSION_PLAN_SCHEMA,
|
|
27
|
+
PHASE14A_CONVERSION_PLAN_SCHEMA,
|
|
28
|
+
PHASE13_CONVERSION_PLAN_SCHEMA,
|
|
29
|
+
PHASE12_CONVERSION_PLAN_SCHEMA,
|
|
30
|
+
RECORD_FACT_SCHEMA,
|
|
31
|
+
)
|
|
32
|
+
from pycforge.converter.containers.analysis import BoundedContainerAnalyzer
|
|
33
|
+
from pycforge.converter.core.diagnostics import Diagnostic
|
|
34
|
+
from pycforge.converter.core.enums import Severity, StageTerminal
|
|
35
|
+
from pycforge.converter.core.fingerprint import fingerprint
|
|
36
|
+
from pycforge.converter.core.stage_artifact import StageArtifact
|
|
37
|
+
from pycforge.converter.core.stage_outcome import StageOutcome
|
|
38
|
+
from pycforge.converter.records import (
|
|
39
|
+
RecordAnalysisCanceled,
|
|
40
|
+
RecordAnalysisError,
|
|
41
|
+
StaticRecordAnalyzer,
|
|
42
|
+
)
|
|
43
|
+
from pycforge.converter.numeric_semantics import (
|
|
44
|
+
BoundedNumericAnalyzer,
|
|
45
|
+
NumericAnalysisCanceled,
|
|
46
|
+
NumericAnalysisError,
|
|
47
|
+
)
|
|
48
|
+
from pycforge.converter.conditional_regions.analysis import ConditionalRegionAnalyzer
|
|
49
|
+
from pycforge.converter.conditional_regions.model import (
|
|
50
|
+
CONDITIONAL_REGION_KEY_DOMAIN,
|
|
51
|
+
CONDITIONAL_REGION_PROVENANCE_EVIDENCE,
|
|
52
|
+
CONDITIONAL_REGION_TABLE_DEPENDENCIES,
|
|
53
|
+
CONDITIONAL_REGION_TABLE_ID,
|
|
54
|
+
ConditionalRegionAnalysisCanceled,
|
|
55
|
+
ConditionalRegionAnalysisError,
|
|
56
|
+
ConditionalRegionValidationCanceled,
|
|
57
|
+
)
|
|
58
|
+
from pycforge.converter.keyword_calls.analysis import KeywordCallAnalyzer
|
|
59
|
+
from pycforge.converter.keyword_calls.model import (
|
|
60
|
+
CUMULATIVE_KEYWORD_TARGET_DIAGNOSTIC_CODE,
|
|
61
|
+
CUMULATIVE_KEYWORD_TARGET_REASON,
|
|
62
|
+
KEYWORD_CALL_KEY_DOMAIN,
|
|
63
|
+
KEYWORD_CALL_PROVENANCE_EVIDENCE,
|
|
64
|
+
KEYWORD_CALL_TABLE_DEPENDENCIES,
|
|
65
|
+
KEYWORD_CALL_TABLE_ID,
|
|
66
|
+
KeywordCallAnalysis,
|
|
67
|
+
KeywordCallAnalysisCanceled,
|
|
68
|
+
KeywordCallAnalysisError,
|
|
69
|
+
KeywordCallRejection,
|
|
70
|
+
KeywordCallValidationCanceled,
|
|
71
|
+
)
|
|
72
|
+
from pycforge.converter.contracts.versions import KEYWORD_CALL_FACT_SCHEMA
|
|
73
|
+
from pycforge.converter.keyword_only_calls.analysis import KeywordOnlyCallAnalyzer
|
|
74
|
+
from pycforge.converter.keyword_only_calls.model import (
|
|
75
|
+
CUMULATIVE_KEYWORD_ONLY_TARGET_DIAGNOSTIC_CODE,
|
|
76
|
+
CUMULATIVE_KEYWORD_ONLY_TARGET_REASON,
|
|
77
|
+
KEYWORD_ONLY_CALL_KEY_DOMAIN,
|
|
78
|
+
KEYWORD_ONLY_CALL_PROVENANCE_EVIDENCE,
|
|
79
|
+
KEYWORD_ONLY_CALL_TABLE_DEPENDENCIES,
|
|
80
|
+
KEYWORD_ONLY_CALL_TABLE_ID,
|
|
81
|
+
KeywordOnlyCallAnalysis,
|
|
82
|
+
KeywordOnlyCallAnalysisCanceled,
|
|
83
|
+
KeywordOnlyCallAnalysisError,
|
|
84
|
+
KeywordOnlyCallRejection,
|
|
85
|
+
KeywordOnlyCallValidationCanceled,
|
|
86
|
+
)
|
|
87
|
+
from pycforge.converter.contracts.versions import KEYWORD_ONLY_CALL_FACT_SCHEMA
|
|
88
|
+
|
|
89
|
+
from .functions import (
|
|
90
|
+
CallTargetFact,
|
|
91
|
+
FunctionAnalysisCanceled,
|
|
92
|
+
FunctionFactsAnalyzer,
|
|
93
|
+
)
|
|
94
|
+
from .model import Completeness, FactProvenance, FactRecord, FactTable, SupportState, ValueCategory
|
|
95
|
+
from .planning import AnalysisCanceled, AnalysisPlanner, RulePlan, default_registry, stable_id
|
|
96
|
+
from .symbols import SymbolScopeAnalyzer
|
|
97
|
+
from .validation import validate_analysis_payload
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _artifact(prior: StageArtifact, payload: dict[str, object], version: str) -> StageArtifact:
|
|
101
|
+
fp = fingerprint("stage-artifact", {"kind": "conversion_plan", "conversion_id": prior.conversion_id, "parent": prior.artifact_fingerprint.value, "payload": payload})
|
|
102
|
+
return StageArtifact("conversion_plan", version, prior.conversion_id, prior.artifact_fingerprint, MappingProxyType(payload), fp)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _extend_plan(plan: RulePlan, *, facts: tuple[str, ...] = (), obligations: tuple[str, ...] = (), explanation: tuple[str, ...] = ()) -> RulePlan:
|
|
106
|
+
merged_facts = tuple(sorted(set(plan.facts_used) | set(facts)))
|
|
107
|
+
merged_obligations = tuple(dict.fromkeys(plan.semantic_obligations + obligations))
|
|
108
|
+
return replace(
|
|
109
|
+
plan,
|
|
110
|
+
facts_used=merged_facts,
|
|
111
|
+
semantic_obligations=merged_obligations,
|
|
112
|
+
resolved_obligations=merged_obligations,
|
|
113
|
+
explanation_tokens=plan.explanation_tokens + explanation,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class AnalysisPlanningStage:
|
|
118
|
+
stage_id = "analysis.plan"
|
|
119
|
+
input_schema = "python-ir/0.4"
|
|
120
|
+
input_schemas = ("python-ir/0.3", "python-ir/0.4")
|
|
121
|
+
output_schema = CONVERSION_PLAN_SCHEMA
|
|
122
|
+
|
|
123
|
+
def run(self, artifact: StageArtifact, services: Any) -> StageOutcome:
|
|
124
|
+
if services.context.cancellation.is_canceled:
|
|
125
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
126
|
+
module = artifact.payload["python_ir"]
|
|
127
|
+
module_fact_tables = tuple(artifact.payload.get("module_fact_tables", ()))
|
|
128
|
+
module_table_values = {
|
|
129
|
+
table["table_id"]: tuple(record["value"] for record in table.get("records", ()))
|
|
130
|
+
for table in module_fact_tables
|
|
131
|
+
}
|
|
132
|
+
module_imports = module_table_values.get("module-import-facts", ())
|
|
133
|
+
module_functions = module_table_values.get("module-function-facts", ())
|
|
134
|
+
module_identities = module_table_values.get("module-identity-facts", ())
|
|
135
|
+
module_initialization = module_table_values.get("module-initialization-facts", ())
|
|
136
|
+
module_sources = module_table_values.get("module-source-facts", ())
|
|
137
|
+
imports_by_node: dict[str, list[dict[str, Any]]] = {}
|
|
138
|
+
for item in module_imports:
|
|
139
|
+
imports_by_node.setdefault(item.get("import_node_id", ""), []).append(item)
|
|
140
|
+
imports_by_alias = {item.get("alias_node_id"): item for item in module_imports}
|
|
141
|
+
functions_by_node = {item.get("function_node_id"): item for item in module_functions}
|
|
142
|
+
document_plan_nodes = {item.get("document_plan_node_id"): item for item in module_identities}
|
|
143
|
+
initialization_by_node = {item.get("initialization_node_id"): item for item in module_initialization}
|
|
144
|
+
assembly_node_id = artifact.payload.get("module_bundle_assembly_node_id")
|
|
145
|
+
request = services.context.canonical.request
|
|
146
|
+
records_enabled = supports_records(request.rule_set_version)
|
|
147
|
+
numeric_enabled = supports_numeric(request.rule_set_version)
|
|
148
|
+
conditional_enabled = supports_conditional_regions(request.rule_set_version)
|
|
149
|
+
keyword_calls_enabled = supports_keyword_calls(request.rule_set_version)
|
|
150
|
+
keyword_only_calls_enabled = supports_keyword_only_calls(
|
|
151
|
+
request.rule_set_version
|
|
152
|
+
)
|
|
153
|
+
containers_enabled = supports_containers(request.rule_set_version)
|
|
154
|
+
modules_enabled = supports_modules(request.rule_set_version)
|
|
155
|
+
scopes, bindings, scope_for_node = SymbolScopeAnalyzer().analyze(
|
|
156
|
+
module,
|
|
157
|
+
allow_records=records_enabled,
|
|
158
|
+
)
|
|
159
|
+
if services.context.cancellation.is_canceled:
|
|
160
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during symbol analysis"),))
|
|
161
|
+
binding_dicts = tuple(binding.to_dict() for binding in bindings)
|
|
162
|
+
occurrence_bindings = {occurrence: binding.binding_id for binding in bindings for occurrence in binding.occurrence_node_ids}
|
|
163
|
+
binding_by_declaration = {binding.declaration_node_id: binding.binding_id for binding in bindings}
|
|
164
|
+
nodes_by_id = {node["node_id"]: node for node in module["nodes"]}
|
|
165
|
+
binding_categories = self._annotation_categories(module, bindings)
|
|
166
|
+
module_source_by_id = {item.get("module_id"): item for item in module_sources}
|
|
167
|
+
function_records = {
|
|
168
|
+
node_id: {
|
|
169
|
+
**item,
|
|
170
|
+
"logical_name": module_source_by_id.get(item.get("module_id"), {}).get("logical_name", "<memory>"),
|
|
171
|
+
}
|
|
172
|
+
for node_id, item in functions_by_node.items()
|
|
173
|
+
}
|
|
174
|
+
module_records = {
|
|
175
|
+
node_id: {
|
|
176
|
+
**dict(item),
|
|
177
|
+
"binding_id": binding_by_declaration.get(node_id),
|
|
178
|
+
"logical_name": module_source_by_id.get(item.get("module_id"), {}).get("logical_name", "<memory>"),
|
|
179
|
+
}
|
|
180
|
+
for node_id, item in dict(artifact.payload.get("module_record_by_node", {})).items()
|
|
181
|
+
} if records_enabled else {}
|
|
182
|
+
record_class_binding_ids = {
|
|
183
|
+
item["binding_id"] for item in module_records.values() if item.get("binding_id")
|
|
184
|
+
}
|
|
185
|
+
for binding_id in record_class_binding_ids:
|
|
186
|
+
binding_categories[binding_id] = ValueCategory.CALLABLE
|
|
187
|
+
annotation_binding_categories = dict(binding_categories)
|
|
188
|
+
constructor_call_ids = frozenset(
|
|
189
|
+
node["node_id"]
|
|
190
|
+
for node in module["nodes"]
|
|
191
|
+
if node["kind"] == "Call"
|
|
192
|
+
and occurrence_bindings.get(node["fields"].get("func")) in record_class_binding_ids
|
|
193
|
+
)
|
|
194
|
+
try:
|
|
195
|
+
function_analyzer = FunctionFactsAnalyzer(
|
|
196
|
+
module,
|
|
197
|
+
binding_dicts,
|
|
198
|
+
ignored_call_node_ids=constructor_call_ids,
|
|
199
|
+
allow_required_keyword_only=keyword_only_calls_enabled,
|
|
200
|
+
cancellation=services.context.cancellation,
|
|
201
|
+
)
|
|
202
|
+
signatures = function_analyzer.signatures()
|
|
203
|
+
except FunctionAnalysisCanceled:
|
|
204
|
+
return StageOutcome(
|
|
205
|
+
StageTerminal.CANCELED,
|
|
206
|
+
diagnostics=(Diagnostic(
|
|
207
|
+
"PYC1901",
|
|
208
|
+
Severity.ERROR,
|
|
209
|
+
self.stage_id,
|
|
210
|
+
"Conversion canceled during signature analysis",
|
|
211
|
+
),),
|
|
212
|
+
)
|
|
213
|
+
if services.context.cancellation.is_canceled:
|
|
214
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during signature analysis"),))
|
|
215
|
+
planner = AnalysisPlanner()
|
|
216
|
+
preliminary_returns = function_analyzer.preliminary_call_returns(signatures)
|
|
217
|
+
preliminary_returns.update({node_id: ValueCategory.RECORD for node_id in constructor_call_ids})
|
|
218
|
+
try:
|
|
219
|
+
categories = planner.analyze_categories(
|
|
220
|
+
module,
|
|
221
|
+
occurrence_bindings,
|
|
222
|
+
binding_categories,
|
|
223
|
+
preliminary_returns,
|
|
224
|
+
services.context.cancellation,
|
|
225
|
+
)
|
|
226
|
+
except AnalysisCanceled:
|
|
227
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
228
|
+
container_shapes = ()
|
|
229
|
+
container_bindings = ()
|
|
230
|
+
container_accesses = ()
|
|
231
|
+
container_iterations = ()
|
|
232
|
+
access_categories: dict[str, ValueCategory] = {}
|
|
233
|
+
iteration_categories: dict[str, ValueCategory] = {}
|
|
234
|
+
if containers_enabled:
|
|
235
|
+
container_shapes, container_bindings, container_accesses, container_iterations = BoundedContainerAnalyzer(
|
|
236
|
+
module,
|
|
237
|
+
binding_dicts,
|
|
238
|
+
categories,
|
|
239
|
+
).analyze()
|
|
240
|
+
access_categories = {
|
|
241
|
+
item.subscript_node_id: item.result_category
|
|
242
|
+
for item in container_accesses
|
|
243
|
+
if item.supported
|
|
244
|
+
}
|
|
245
|
+
iteration_categories = {
|
|
246
|
+
item.for_node_id: item.target_category
|
|
247
|
+
for item in container_iterations
|
|
248
|
+
if item.supported
|
|
249
|
+
}
|
|
250
|
+
binding_categories.clear()
|
|
251
|
+
binding_categories.update(annotation_binding_categories)
|
|
252
|
+
try:
|
|
253
|
+
categories = planner.analyze_categories(
|
|
254
|
+
module,
|
|
255
|
+
occurrence_bindings,
|
|
256
|
+
binding_categories,
|
|
257
|
+
preliminary_returns,
|
|
258
|
+
services.context.cancellation,
|
|
259
|
+
access_categories,
|
|
260
|
+
iteration_categories,
|
|
261
|
+
)
|
|
262
|
+
except AnalysisCanceled:
|
|
263
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
264
|
+
container_shapes, container_bindings, container_accesses, container_iterations = BoundedContainerAnalyzer(
|
|
265
|
+
module,
|
|
266
|
+
binding_dicts,
|
|
267
|
+
categories,
|
|
268
|
+
).analyze()
|
|
269
|
+
record_analysis = None
|
|
270
|
+
record_access_categories: dict[str, ValueCategory] = {}
|
|
271
|
+
if records_enabled:
|
|
272
|
+
primary_identity = next(
|
|
273
|
+
(item for item in module_identities if item.get("is_primary")),
|
|
274
|
+
module_identities[0] if module_identities else {},
|
|
275
|
+
)
|
|
276
|
+
try:
|
|
277
|
+
record_analysis = StaticRecordAnalyzer(
|
|
278
|
+
module,
|
|
279
|
+
module_records=module_records,
|
|
280
|
+
function_records=function_records,
|
|
281
|
+
bindings=binding_dicts,
|
|
282
|
+
categories=categories,
|
|
283
|
+
cancellation=services.context.cancellation,
|
|
284
|
+
default_module_id=str(primary_identity.get("module_id", "__main__")),
|
|
285
|
+
default_logical_name=str(primary_identity.get("logical_name", "<memory>")),
|
|
286
|
+
).analyze()
|
|
287
|
+
except RecordAnalysisCanceled:
|
|
288
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during static-record analysis"),))
|
|
289
|
+
except RecordAnalysisError as exc:
|
|
290
|
+
return self._record_rejection(exc)
|
|
291
|
+
record_access_categories = {
|
|
292
|
+
item.access_node_id: item.field_category for item in record_analysis.accesses
|
|
293
|
+
}
|
|
294
|
+
binding_categories.clear()
|
|
295
|
+
binding_categories.update(annotation_binding_categories)
|
|
296
|
+
try:
|
|
297
|
+
categories = planner.analyze_categories(
|
|
298
|
+
module,
|
|
299
|
+
occurrence_bindings,
|
|
300
|
+
binding_categories,
|
|
301
|
+
preliminary_returns,
|
|
302
|
+
services.context.cancellation,
|
|
303
|
+
access_categories,
|
|
304
|
+
iteration_categories,
|
|
305
|
+
record_access_categories,
|
|
306
|
+
)
|
|
307
|
+
except AnalysisCanceled:
|
|
308
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
309
|
+
if containers_enabled:
|
|
310
|
+
container_shapes, container_bindings, container_accesses, container_iterations = BoundedContainerAnalyzer(
|
|
311
|
+
module,
|
|
312
|
+
binding_dicts,
|
|
313
|
+
categories,
|
|
314
|
+
).analyze()
|
|
315
|
+
access_categories = {
|
|
316
|
+
item.subscript_node_id: item.result_category
|
|
317
|
+
for item in container_accesses
|
|
318
|
+
if item.supported
|
|
319
|
+
}
|
|
320
|
+
iteration_categories = {
|
|
321
|
+
item.for_node_id: item.target_category
|
|
322
|
+
for item in container_iterations
|
|
323
|
+
if item.supported
|
|
324
|
+
}
|
|
325
|
+
binding_categories.clear()
|
|
326
|
+
binding_categories.update(annotation_binding_categories)
|
|
327
|
+
try:
|
|
328
|
+
categories = planner.analyze_categories(
|
|
329
|
+
module,
|
|
330
|
+
occurrence_bindings,
|
|
331
|
+
binding_categories,
|
|
332
|
+
preliminary_returns,
|
|
333
|
+
services.context.cancellation,
|
|
334
|
+
access_categories,
|
|
335
|
+
iteration_categories,
|
|
336
|
+
record_access_categories,
|
|
337
|
+
)
|
|
338
|
+
except AnalysisCanceled:
|
|
339
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
340
|
+
try:
|
|
341
|
+
record_analysis = StaticRecordAnalyzer(
|
|
342
|
+
module,
|
|
343
|
+
module_records=module_records,
|
|
344
|
+
function_records=function_records,
|
|
345
|
+
bindings=binding_dicts,
|
|
346
|
+
categories=categories,
|
|
347
|
+
cancellation=services.context.cancellation,
|
|
348
|
+
default_module_id=str(primary_identity.get("module_id", "__main__")),
|
|
349
|
+
default_logical_name=str(primary_identity.get("logical_name", "<memory>")),
|
|
350
|
+
).analyze()
|
|
351
|
+
except RecordAnalysisCanceled:
|
|
352
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during static-record analysis"),))
|
|
353
|
+
except RecordAnalysisError as exc:
|
|
354
|
+
return self._record_rejection(exc)
|
|
355
|
+
keyword_call_analysis = None
|
|
356
|
+
if keyword_calls_enabled:
|
|
357
|
+
try:
|
|
358
|
+
keyword_call_analysis = KeywordCallAnalyzer(
|
|
359
|
+
module,
|
|
360
|
+
bindings=binding_dicts,
|
|
361
|
+
signatures=signatures,
|
|
362
|
+
categories=categories,
|
|
363
|
+
cancellation=services.context.cancellation,
|
|
364
|
+
ignored_call_node_ids=constructor_call_ids,
|
|
365
|
+
).analyze()
|
|
366
|
+
except KeywordCallAnalysisCanceled:
|
|
367
|
+
return StageOutcome(
|
|
368
|
+
StageTerminal.CANCELED,
|
|
369
|
+
diagnostics=(Diagnostic(
|
|
370
|
+
"PYC1901",
|
|
371
|
+
Severity.ERROR,
|
|
372
|
+
self.stage_id,
|
|
373
|
+
"Conversion canceled during keyword-call analysis",
|
|
374
|
+
),),
|
|
375
|
+
)
|
|
376
|
+
except KeywordCallAnalysisError as exc:
|
|
377
|
+
return self._keyword_call_rejection(exc)
|
|
378
|
+
keyword_only_call_analysis = None
|
|
379
|
+
if keyword_only_calls_enabled:
|
|
380
|
+
try:
|
|
381
|
+
keyword_only_call_analysis = KeywordOnlyCallAnalyzer(
|
|
382
|
+
module,
|
|
383
|
+
bindings=binding_dicts,
|
|
384
|
+
signatures=signatures,
|
|
385
|
+
categories=categories,
|
|
386
|
+
cancellation=services.context.cancellation,
|
|
387
|
+
ignored_call_node_ids=constructor_call_ids,
|
|
388
|
+
).analyze()
|
|
389
|
+
except KeywordOnlyCallAnalysisCanceled:
|
|
390
|
+
return StageOutcome(
|
|
391
|
+
StageTerminal.CANCELED,
|
|
392
|
+
diagnostics=(Diagnostic(
|
|
393
|
+
"PYC1901",
|
|
394
|
+
Severity.ERROR,
|
|
395
|
+
self.stage_id,
|
|
396
|
+
"Conversion canceled during keyword-only call analysis",
|
|
397
|
+
),),
|
|
398
|
+
)
|
|
399
|
+
except KeywordOnlyCallAnalysisError as exc:
|
|
400
|
+
return self._keyword_only_call_rejection(exc)
|
|
401
|
+
calls = function_analyzer.calls(signatures, categories)
|
|
402
|
+
if keyword_call_analysis is not None:
|
|
403
|
+
keyword_facts_by_call = keyword_call_analysis.fact_by_call_node_id
|
|
404
|
+
calls = tuple(
|
|
405
|
+
replace(
|
|
406
|
+
call,
|
|
407
|
+
argument_node_ids=feature.source_argument_node_ids,
|
|
408
|
+
argument_categories=tuple(
|
|
409
|
+
ValueCategory(item)
|
|
410
|
+
for item in feature.source_argument_categories
|
|
411
|
+
),
|
|
412
|
+
evaluation_order=feature.evaluation_order,
|
|
413
|
+
arguments_evaluated_once=feature.arguments_evaluated_once,
|
|
414
|
+
resolution=(
|
|
415
|
+
"understood-source-function"
|
|
416
|
+
if feature.supported
|
|
417
|
+
else "ineligible-source-function"
|
|
418
|
+
),
|
|
419
|
+
supported=feature.supported,
|
|
420
|
+
diagnostic_code=feature.diagnostic_code,
|
|
421
|
+
reason=feature.reason,
|
|
422
|
+
)
|
|
423
|
+
if (feature := keyword_facts_by_call.get(call.call_node_id)) is not None
|
|
424
|
+
else call
|
|
425
|
+
for call in calls
|
|
426
|
+
)
|
|
427
|
+
if keyword_only_call_analysis is not None:
|
|
428
|
+
keyword_only_facts_by_call = (
|
|
429
|
+
keyword_only_call_analysis.fact_by_call_node_id
|
|
430
|
+
)
|
|
431
|
+
calls = tuple(
|
|
432
|
+
replace(
|
|
433
|
+
call,
|
|
434
|
+
argument_node_ids=feature.source_argument_node_ids,
|
|
435
|
+
argument_categories=tuple(
|
|
436
|
+
ValueCategory(item)
|
|
437
|
+
for item in feature.source_argument_categories
|
|
438
|
+
),
|
|
439
|
+
evaluation_order=feature.evaluation_order,
|
|
440
|
+
arguments_evaluated_once=feature.arguments_evaluated_once,
|
|
441
|
+
resolution=(
|
|
442
|
+
"understood-source-function"
|
|
443
|
+
if feature.supported
|
|
444
|
+
else "ineligible-source-function"
|
|
445
|
+
),
|
|
446
|
+
supported=feature.supported,
|
|
447
|
+
diagnostic_code=feature.diagnostic_code,
|
|
448
|
+
reason=feature.reason,
|
|
449
|
+
)
|
|
450
|
+
if (
|
|
451
|
+
feature := keyword_only_facts_by_call.get(call.call_node_id)
|
|
452
|
+
) is not None
|
|
453
|
+
else call
|
|
454
|
+
for call in calls
|
|
455
|
+
)
|
|
456
|
+
if services.context.cancellation.is_canceled:
|
|
457
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during call analysis"),))
|
|
458
|
+
graph = function_analyzer.call_graph(calls)
|
|
459
|
+
if services.context.cancellation.is_canceled:
|
|
460
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during call-graph analysis"),))
|
|
461
|
+
recursive_calls = set(graph.recursive_call_node_ids)
|
|
462
|
+
if recursive_calls:
|
|
463
|
+
calls = tuple(
|
|
464
|
+
replace(
|
|
465
|
+
call,
|
|
466
|
+
resolution="recursive-target",
|
|
467
|
+
supported=False,
|
|
468
|
+
diagnostic_code="PYC2920",
|
|
469
|
+
reason="Direct and mutual recursion are unsupported in Phase 9",
|
|
470
|
+
) if call.call_node_id in recursive_calls else call
|
|
471
|
+
for call in calls
|
|
472
|
+
)
|
|
473
|
+
numeric_analysis = None
|
|
474
|
+
if numeric_enabled:
|
|
475
|
+
try:
|
|
476
|
+
numeric_analysis = BoundedNumericAnalyzer(
|
|
477
|
+
module,
|
|
478
|
+
categories=categories,
|
|
479
|
+
function_records=function_records,
|
|
480
|
+
supported_call_node_ids=frozenset(
|
|
481
|
+
item.call_node_id for item in calls if item.supported
|
|
482
|
+
),
|
|
483
|
+
supported_container_access_node_ids=frozenset(
|
|
484
|
+
item.subscript_node_id
|
|
485
|
+
for item in container_accesses
|
|
486
|
+
if item.supported
|
|
487
|
+
),
|
|
488
|
+
supported_record_access_node_ids=frozenset(
|
|
489
|
+
item.access_node_id
|
|
490
|
+
for item in (record_analysis.accesses if record_analysis else ())
|
|
491
|
+
),
|
|
492
|
+
rejected_expression_diagnostics={
|
|
493
|
+
**{
|
|
494
|
+
item.call_node_id: (
|
|
495
|
+
item.diagnostic_code or "PYC2901",
|
|
496
|
+
item.reason or "Call expression is unsupported",
|
|
497
|
+
)
|
|
498
|
+
for item in calls
|
|
499
|
+
if not item.supported
|
|
500
|
+
},
|
|
501
|
+
**{
|
|
502
|
+
item.subscript_node_id: (
|
|
503
|
+
item.diagnostic_code or "PYC3404",
|
|
504
|
+
item.reason or "Container access is unsupported",
|
|
505
|
+
)
|
|
506
|
+
for item in container_accesses
|
|
507
|
+
if not item.supported
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
cancellation=services.context.cancellation,
|
|
511
|
+
).analyze()
|
|
512
|
+
except NumericAnalysisCanceled:
|
|
513
|
+
return StageOutcome(
|
|
514
|
+
StageTerminal.CANCELED,
|
|
515
|
+
diagnostics=(Diagnostic(
|
|
516
|
+
"PYC1901",
|
|
517
|
+
Severity.ERROR,
|
|
518
|
+
self.stage_id,
|
|
519
|
+
"Conversion canceled during bounded numeric analysis",
|
|
520
|
+
),),
|
|
521
|
+
)
|
|
522
|
+
except NumericAnalysisError as exc:
|
|
523
|
+
return self._numeric_rejection(exc)
|
|
524
|
+
return_paths = function_analyzer.return_paths(signatures, categories)
|
|
525
|
+
if services.context.cancellation.is_canceled:
|
|
526
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during return-path analysis"),))
|
|
527
|
+
local_facts = function_analyzer.local_declarations(categories, signatures)
|
|
528
|
+
if services.context.cancellation.is_canceled:
|
|
529
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled during local-declaration analysis"),))
|
|
530
|
+
owners = function_analyzer.owner_by_node()
|
|
531
|
+
|
|
532
|
+
conditional_analysis = None
|
|
533
|
+
if conditional_enabled:
|
|
534
|
+
try:
|
|
535
|
+
conditional_analysis = ConditionalRegionAnalyzer(
|
|
536
|
+
module,
|
|
537
|
+
categories=categories,
|
|
538
|
+
function_records=function_records,
|
|
539
|
+
owner_by_node=owners,
|
|
540
|
+
supported_call_node_ids=frozenset(
|
|
541
|
+
item.call_node_id for item in calls if item.supported
|
|
542
|
+
),
|
|
543
|
+
numeric_operation_node_ids=frozenset(
|
|
544
|
+
item.binop_node_id
|
|
545
|
+
for item in (numeric_analysis.operations if numeric_analysis else ())
|
|
546
|
+
),
|
|
547
|
+
supported_container_access_node_ids=frozenset(
|
|
548
|
+
item.subscript_node_id
|
|
549
|
+
for item in container_accesses
|
|
550
|
+
if item.supported
|
|
551
|
+
),
|
|
552
|
+
supported_record_access_node_ids=frozenset(
|
|
553
|
+
item.access_node_id
|
|
554
|
+
for item in (record_analysis.accesses if record_analysis else ())
|
|
555
|
+
),
|
|
556
|
+
cancellation=services.context.cancellation,
|
|
557
|
+
target_contract=request.target_contract,
|
|
558
|
+
).analyze()
|
|
559
|
+
except ConditionalRegionAnalysisCanceled:
|
|
560
|
+
return StageOutcome(
|
|
561
|
+
StageTerminal.CANCELED,
|
|
562
|
+
diagnostics=(Diagnostic(
|
|
563
|
+
"PYC1901",
|
|
564
|
+
Severity.ERROR,
|
|
565
|
+
self.stage_id,
|
|
566
|
+
"Conversion canceled during conditional-region analysis",
|
|
567
|
+
),),
|
|
568
|
+
)
|
|
569
|
+
except ConditionalRegionAnalysisError as exc:
|
|
570
|
+
return self._conditional_rejection(exc)
|
|
571
|
+
|
|
572
|
+
signature_by_node = {item.function_node_id: item for item in signatures}
|
|
573
|
+
return_by_function = {item.function_node_id: item for item in return_paths}
|
|
574
|
+
local_by_function = {item.function_node_id: item for item in local_facts}
|
|
575
|
+
try:
|
|
576
|
+
function_eligible = self._cumulative_function_eligibility(
|
|
577
|
+
signature_by_node,
|
|
578
|
+
return_by_function,
|
|
579
|
+
local_by_function,
|
|
580
|
+
calls,
|
|
581
|
+
owners,
|
|
582
|
+
graph,
|
|
583
|
+
services.context.cancellation,
|
|
584
|
+
)
|
|
585
|
+
except AnalysisCanceled:
|
|
586
|
+
return StageOutcome(
|
|
587
|
+
StageTerminal.CANCELED,
|
|
588
|
+
diagnostics=(Diagnostic(
|
|
589
|
+
"PYC1901",
|
|
590
|
+
Severity.ERROR,
|
|
591
|
+
self.stage_id,
|
|
592
|
+
"Conversion canceled during cumulative function eligibility analysis",
|
|
593
|
+
),),
|
|
594
|
+
)
|
|
595
|
+
if keyword_call_analysis is not None:
|
|
596
|
+
keyword_call_analysis, calls = self._gate_keyword_calls_by_target(
|
|
597
|
+
keyword_call_analysis,
|
|
598
|
+
calls,
|
|
599
|
+
function_eligible,
|
|
600
|
+
nodes_by_id,
|
|
601
|
+
)
|
|
602
|
+
if keyword_only_call_analysis is not None:
|
|
603
|
+
keyword_only_call_analysis, calls = (
|
|
604
|
+
self._gate_keyword_only_calls_by_target(
|
|
605
|
+
keyword_only_call_analysis,
|
|
606
|
+
calls,
|
|
607
|
+
function_eligible,
|
|
608
|
+
nodes_by_id,
|
|
609
|
+
)
|
|
610
|
+
)
|
|
611
|
+
call_by_node = {item.call_node_id: item for item in calls}
|
|
612
|
+
|
|
613
|
+
if modules_enabled:
|
|
614
|
+
for imported in sorted(module_imports, key=lambda item: item.get("import_item_id", "")):
|
|
615
|
+
target_function_id = imported.get("target_function_node_id")
|
|
616
|
+
if function_eligible.get(target_function_id, False):
|
|
617
|
+
continue
|
|
618
|
+
alias_node = nodes_by_id.get(imported.get("alias_node_id"), {})
|
|
619
|
+
target_node = nodes_by_id.get(target_function_id, {})
|
|
620
|
+
source_span = alias_node.get("provenance", {}).get("source_span")
|
|
621
|
+
target_span = target_node.get("provenance", {}).get("source_span")
|
|
622
|
+
return StageOutcome(
|
|
623
|
+
StageTerminal.REJECTED,
|
|
624
|
+
diagnostics=(Diagnostic(
|
|
625
|
+
"PYC3505",
|
|
626
|
+
Severity.ERROR,
|
|
627
|
+
self.stage_id,
|
|
628
|
+
f"Imported member {imported.get('target_module_id')}.{imported.get('imported_name')} is not an eligible direct function",
|
|
629
|
+
source_span=source_span,
|
|
630
|
+
related_spans=(target_span,) if isinstance(target_span, dict) else (),
|
|
631
|
+
fact_references=(f"module-import:{imported.get('import_item_id')}", f"module-function:{target_function_id}"),
|
|
632
|
+
obligation_references=("imported-target-eligible",),
|
|
633
|
+
explanation="Imported functions must satisfy the cumulative function, call, return, local, and recursion boundaries.",
|
|
634
|
+
),),
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
registry = default_registry(
|
|
638
|
+
include_records=records_enabled,
|
|
639
|
+
include_numeric=numeric_enabled,
|
|
640
|
+
include_conditional_regions=conditional_enabled,
|
|
641
|
+
include_keyword_calls=keyword_calls_enabled,
|
|
642
|
+
include_keyword_only_calls=keyword_only_calls_enabled,
|
|
643
|
+
)
|
|
644
|
+
target = request.target_contract
|
|
645
|
+
semantics = request.semantic_policy
|
|
646
|
+
ruleset = request.rule_set_version
|
|
647
|
+
plans: list[dict[str, Any]] = []
|
|
648
|
+
support: list[dict[str, Any]] = []
|
|
649
|
+
representations: list[dict[str, Any]] = []
|
|
650
|
+
effects: list[dict[str, Any]] = []
|
|
651
|
+
truthiness: list[dict[str, Any]] = []
|
|
652
|
+
order: list[dict[str, Any]] = []
|
|
653
|
+
shape_by_node = {item.literal_node_id: item for item in container_shapes}
|
|
654
|
+
container_binding_by_assignment = {item.assignment_node_id: item for item in container_bindings}
|
|
655
|
+
container_binding_by_id = {item.binding_id: item for item in container_bindings}
|
|
656
|
+
access_by_node = {item.subscript_node_id: item for item in container_accesses}
|
|
657
|
+
iteration_by_node = {item.for_node_id: item for item in container_iterations}
|
|
658
|
+
record_definition_by_class = {
|
|
659
|
+
item.class_node_id: item for item in (record_analysis.definitions if record_analysis else ())
|
|
660
|
+
}
|
|
661
|
+
record_field_by_declaration = {
|
|
662
|
+
item.declaration_node_id: item for item in (record_analysis.fields if record_analysis else ())
|
|
663
|
+
}
|
|
664
|
+
record_initializer_by_function = {
|
|
665
|
+
item.function_node_id: item for item in (record_analysis.initializers if record_analysis else ())
|
|
666
|
+
}
|
|
667
|
+
record_instance_by_construction = {
|
|
668
|
+
item.construction_node_id: item for item in (record_analysis.instances if record_analysis else ())
|
|
669
|
+
}
|
|
670
|
+
record_instance_by_assignment = {
|
|
671
|
+
item.assignment_node_id: item for item in (record_analysis.instances if record_analysis else ())
|
|
672
|
+
}
|
|
673
|
+
record_binding_by_id = {
|
|
674
|
+
item.binding_id: item for item in (record_analysis.bindings if record_analysis else ())
|
|
675
|
+
}
|
|
676
|
+
record_access_by_node = {
|
|
677
|
+
item.access_node_id: item for item in (record_analysis.accesses if record_analysis else ())
|
|
678
|
+
}
|
|
679
|
+
record_name_node_ids = {
|
|
680
|
+
occurrence
|
|
681
|
+
for binding in binding_dicts
|
|
682
|
+
if binding.get("binding_id") in record_class_binding_ids
|
|
683
|
+
for occurrence in binding.get("occurrence_node_ids", ())
|
|
684
|
+
}
|
|
685
|
+
record_name_node_ids.update(
|
|
686
|
+
occurrence
|
|
687
|
+
for binding in record_binding_by_id.values()
|
|
688
|
+
for occurrence in binding.occurrence_node_ids
|
|
689
|
+
)
|
|
690
|
+
numeric_operation_by_node = {
|
|
691
|
+
item.binop_node_id: item
|
|
692
|
+
for item in (numeric_analysis.operations if numeric_analysis else ())
|
|
693
|
+
}
|
|
694
|
+
conditional_region_by_node = {
|
|
695
|
+
item.region_node_id: item
|
|
696
|
+
for item in (conditional_analysis.regions if conditional_analysis else ())
|
|
697
|
+
}
|
|
698
|
+
keyword_call_by_node = (
|
|
699
|
+
keyword_call_analysis.fact_by_call_node_id
|
|
700
|
+
if keyword_call_analysis is not None
|
|
701
|
+
else {}
|
|
702
|
+
)
|
|
703
|
+
keyword_only_call_by_node = (
|
|
704
|
+
keyword_only_call_analysis.fact_by_call_node_id
|
|
705
|
+
if keyword_only_call_analysis is not None
|
|
706
|
+
else {}
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
for ordinal, node in enumerate(sorted(module["nodes"], key=lambda item: item["node_id"])):
|
|
710
|
+
if services.context.cancellation.is_canceled:
|
|
711
|
+
return StageOutcome(StageTerminal.CANCELED, diagnostics=(Diagnostic("PYC1901", Severity.ERROR, self.stage_id, "Conversion canceled"),))
|
|
712
|
+
node_id = node["node_id"]
|
|
713
|
+
decision_key = stable_id("decision-", node_id, scope_for_node.get(node_id, ""), target, semantics, ruleset)
|
|
714
|
+
category = categories[node_id]
|
|
715
|
+
selectable_node = dict(node)
|
|
716
|
+
if node["kind"] == "BinOp":
|
|
717
|
+
op_node = nodes_by_id.get(node["fields"].get("op"))
|
|
718
|
+
selectable_node["op_kind"] = op_node["kind"] if op_node else None
|
|
719
|
+
if node["kind"] == "Call":
|
|
720
|
+
selectable_node["call_target_kind"] = call_by_node.get(node_id).resolution if node_id in call_by_node and call_by_node[node_id].supported else None
|
|
721
|
+
if node["kind"] == "FunctionDef":
|
|
722
|
+
selectable_node["function_eligible"] = function_eligible.get(node_id, False)
|
|
723
|
+
if node["kind"] == "Return":
|
|
724
|
+
owner = owners.get(node_id)
|
|
725
|
+
expected = signature_by_node.get(owner).return_category if owner in signature_by_node else ValueCategory.UNKNOWN
|
|
726
|
+
selectable_node["return_eligible"] = category is expected and expected not in {ValueCategory.UNKNOWN, ValueCategory.CONTRADICTORY}
|
|
727
|
+
if modules_enabled:
|
|
728
|
+
if node_id in document_plan_nodes:
|
|
729
|
+
selectable_node["module_document_supported"] = True
|
|
730
|
+
if node_id in imports_by_node:
|
|
731
|
+
selectable_node["module_import_supported"] = all(item.get("supported") for item in imports_by_node[node_id])
|
|
732
|
+
if node_id in imports_by_alias:
|
|
733
|
+
selectable_node["module_alias_supported"] = bool(imports_by_alias[node_id].get("supported"))
|
|
734
|
+
if node_id in functions_by_node:
|
|
735
|
+
selectable_node["module_function_supported"] = True
|
|
736
|
+
if node_id in initialization_by_node:
|
|
737
|
+
selectable_node["module_initialization_supported"] = True
|
|
738
|
+
if node_id == assembly_node_id:
|
|
739
|
+
selectable_node["module_bundle_assembly_supported"] = True
|
|
740
|
+
if node["kind"] == "Call" and node_id in call_by_node:
|
|
741
|
+
call = call_by_node[node_id]
|
|
742
|
+
caller = functions_by_node.get(owners.get(node_id))
|
|
743
|
+
callee = functions_by_node.get(call.target_function_node_id)
|
|
744
|
+
selectable_node["cross_module_call_supported"] = bool(
|
|
745
|
+
call.supported and caller and callee and caller.get("module_id") != callee.get("module_id")
|
|
746
|
+
)
|
|
747
|
+
if containers_enabled:
|
|
748
|
+
if node_id in shape_by_node:
|
|
749
|
+
selectable_node["container_shape_supported"] = shape_by_node[node_id].valid
|
|
750
|
+
if node_id in access_by_node:
|
|
751
|
+
selectable_node["container_access_supported"] = access_by_node[node_id].supported
|
|
752
|
+
if node_id in iteration_by_node:
|
|
753
|
+
selectable_node["container_iteration_supported"] = iteration_by_node[node_id].supported
|
|
754
|
+
if node_id in container_binding_by_assignment:
|
|
755
|
+
selectable_node["container_binding_supported"] = container_binding_by_assignment[node_id].valid
|
|
756
|
+
if node["kind"] == "Name":
|
|
757
|
+
binding_id = occurrence_bindings.get(node_id)
|
|
758
|
+
container_binding = container_binding_by_id.get(binding_id)
|
|
759
|
+
selectable_node["container_name_supported"] = bool(
|
|
760
|
+
container_binding
|
|
761
|
+
and container_binding.valid
|
|
762
|
+
and node_id in container_binding.allowed_use_node_ids
|
|
763
|
+
)
|
|
764
|
+
if records_enabled:
|
|
765
|
+
if node_id in record_definition_by_class:
|
|
766
|
+
selectable_node["record_class_supported"] = True
|
|
767
|
+
if node_id in record_field_by_declaration:
|
|
768
|
+
selectable_node["record_field_supported"] = True
|
|
769
|
+
if node_id in record_initializer_by_function:
|
|
770
|
+
selectable_node["record_initializer_supported"] = True
|
|
771
|
+
if node_id in record_instance_by_construction:
|
|
772
|
+
selectable_node["record_construction_supported"] = True
|
|
773
|
+
if node_id in record_instance_by_assignment:
|
|
774
|
+
selectable_node["record_binding_supported"] = True
|
|
775
|
+
if node_id in record_name_node_ids:
|
|
776
|
+
selectable_node["record_name_supported"] = True
|
|
777
|
+
if node_id in record_access_by_node:
|
|
778
|
+
selectable_node["record_access_supported"] = True
|
|
779
|
+
if numeric_enabled and node_id in numeric_operation_by_node:
|
|
780
|
+
selectable_node["numeric_operation_supported"] = True
|
|
781
|
+
if conditional_enabled and node_id in conditional_region_by_node:
|
|
782
|
+
region = conditional_region_by_node[node_id]
|
|
783
|
+
selectable_node[
|
|
784
|
+
"conditional_boolean_region_supported"
|
|
785
|
+
if region.region_kind.value == "boolean-short-circuit"
|
|
786
|
+
else "conditional_comparison_region_supported"
|
|
787
|
+
] = True
|
|
788
|
+
if keyword_calls_enabled and node_id in keyword_call_by_node:
|
|
789
|
+
selectable_node["keyword_call_binding_supported"] = bool(
|
|
790
|
+
keyword_call_by_node[node_id].supported
|
|
791
|
+
and call_by_node.get(node_id)
|
|
792
|
+
and call_by_node[node_id].supported
|
|
793
|
+
)
|
|
794
|
+
if keyword_only_calls_enabled and node_id in keyword_only_call_by_node:
|
|
795
|
+
selectable_node["keyword_only_call_binding_supported"] = bool(
|
|
796
|
+
keyword_only_call_by_node[node_id].supported
|
|
797
|
+
and call_by_node.get(node_id)
|
|
798
|
+
and call_by_node[node_id].supported
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
plan = registry.select(selectable_node, categories, decision_key)
|
|
802
|
+
if plan:
|
|
803
|
+
plan = self._phase9_plan(plan, node, signature_by_node, call_by_node, owners, return_by_function, local_by_function)
|
|
804
|
+
if containers_enabled:
|
|
805
|
+
plan = self._phase11_plan(
|
|
806
|
+
plan,
|
|
807
|
+
node,
|
|
808
|
+
shape_by_node,
|
|
809
|
+
container_binding_by_assignment,
|
|
810
|
+
access_by_node,
|
|
811
|
+
iteration_by_node,
|
|
812
|
+
)
|
|
813
|
+
if modules_enabled:
|
|
814
|
+
plan = self._phase12_plan(
|
|
815
|
+
plan,
|
|
816
|
+
node,
|
|
817
|
+
imports_by_node,
|
|
818
|
+
imports_by_alias,
|
|
819
|
+
functions_by_node,
|
|
820
|
+
document_plan_nodes,
|
|
821
|
+
initialization_by_node,
|
|
822
|
+
call_by_node,
|
|
823
|
+
owners,
|
|
824
|
+
assembly_node_id,
|
|
825
|
+
)
|
|
826
|
+
if records_enabled:
|
|
827
|
+
plan = self._phase13_plan(
|
|
828
|
+
plan,
|
|
829
|
+
node,
|
|
830
|
+
record_definition_by_class,
|
|
831
|
+
record_field_by_declaration,
|
|
832
|
+
record_initializer_by_function,
|
|
833
|
+
record_instance_by_construction,
|
|
834
|
+
record_instance_by_assignment,
|
|
835
|
+
record_binding_by_id,
|
|
836
|
+
record_access_by_node,
|
|
837
|
+
occurrence_bindings,
|
|
838
|
+
)
|
|
839
|
+
if numeric_enabled:
|
|
840
|
+
plan = self._phase14_plan(
|
|
841
|
+
plan,
|
|
842
|
+
node,
|
|
843
|
+
numeric_operation_by_node,
|
|
844
|
+
)
|
|
845
|
+
if conditional_enabled:
|
|
846
|
+
plan = self._phase14b_plan(
|
|
847
|
+
plan,
|
|
848
|
+
node,
|
|
849
|
+
conditional_region_by_node,
|
|
850
|
+
)
|
|
851
|
+
if keyword_calls_enabled:
|
|
852
|
+
plan = self._phase14c_plan(
|
|
853
|
+
plan,
|
|
854
|
+
node,
|
|
855
|
+
keyword_call_by_node,
|
|
856
|
+
)
|
|
857
|
+
if keyword_only_calls_enabled:
|
|
858
|
+
plan = self._phase14d_plan(
|
|
859
|
+
plan,
|
|
860
|
+
node,
|
|
861
|
+
keyword_only_call_by_node,
|
|
862
|
+
signature_by_node,
|
|
863
|
+
nodes_by_id,
|
|
864
|
+
)
|
|
865
|
+
state = plan.support_state if plan else SupportState.UNSUPPORTED
|
|
866
|
+
cause = self._unsupported_cause(node, signature_by_node, call_by_node, owners, return_by_function, local_by_function)
|
|
867
|
+
if not plan and containers_enabled:
|
|
868
|
+
if node_id in shape_by_node:
|
|
869
|
+
cause = shape_by_node[node_id].reason or "Container literal lacks an approved fixed representation"
|
|
870
|
+
elif node_id in access_by_node:
|
|
871
|
+
cause = access_by_node[node_id].reason or "Container access is not statically proved"
|
|
872
|
+
elif node_id in iteration_by_node:
|
|
873
|
+
cause = iteration_by_node[node_id].reason or "Container iteration is not bounded"
|
|
874
|
+
elif node_id in container_binding_by_assignment:
|
|
875
|
+
cause = container_binding_by_assignment[node_id].reason or "Container binding is invalid"
|
|
876
|
+
elif node["kind"] == "Name":
|
|
877
|
+
binding_id = occurrence_bindings.get(node_id)
|
|
878
|
+
container_binding = container_binding_by_id.get(binding_id)
|
|
879
|
+
if container_binding and not container_binding.valid:
|
|
880
|
+
cause = container_binding.reason
|
|
881
|
+
support.append({"decision_key": decision_key, "node_id": node_id, "state": state.value, "rule_plan_id": plan.plan_id if plan else None, "cause": None if plan else cause})
|
|
882
|
+
if plan:
|
|
883
|
+
plan_dict = plan.to_dict()
|
|
884
|
+
plans.append(plan_dict)
|
|
885
|
+
services.trace.record({"kind": "rule_plan", "stage": self.stage_id, "plan": plan_dict})
|
|
886
|
+
representations.append(planner.representation(decision_key, category).to_dict())
|
|
887
|
+
effects.append({"node_id": node_id, "effects": [effect.value for effect in planner.effects(node)]})
|
|
888
|
+
truthiness.append({"node_id": node_id, "category": category.value, "strategy": self._truthiness_strategy(category)})
|
|
889
|
+
order.append({"node_id": node_id, "ordinal": ordinal, "child_order": list(function_analyzer.index.child_ids(node))})
|
|
890
|
+
|
|
891
|
+
fact_tables: list[FactTable] = [
|
|
892
|
+
FactTable("fact-table/0.5", "scope-facts", self.stage_id, "scope-id", Completeness.COMPLETE, (artifact.artifact_fingerprint.value,), tuple(FactRecord(item.scope_id, item, FactProvenance((item.owner_node_id,), ("lexical-structure",))) for item in scopes)),
|
|
893
|
+
FactTable("fact-table/0.5", "binding-facts", self.stage_id, "binding-id", Completeness.COMPLETE, (artifact.artifact_fingerprint.value,), tuple(FactRecord(item.binding_id, item, FactProvenance((item.declaration_node_id,) + item.occurrence_node_ids, ("lexical-binding",))) for item in bindings)),
|
|
894
|
+
FactTable("fact-table/0.5", "value-category-facts", self.stage_id, "python-node-id", Completeness.COMPLETE, ("binding-facts",), tuple(FactRecord(key, value, FactProvenance((key,), ("literal-annotation-call-or-constraint",))) for key, value in sorted(categories.items()))),
|
|
895
|
+
]
|
|
896
|
+
functions_enabled = supports_functions(ruleset)
|
|
897
|
+
if functions_enabled:
|
|
898
|
+
fact_tables.extend(
|
|
899
|
+
(
|
|
900
|
+
FactTable("fact-table/0.9", "function-signature-facts", self.stage_id, "function-node-id", Completeness.COMPLETE, ("binding-facts", "value-category-facts"), tuple(FactRecord(item.function_node_id, item, FactProvenance(tuple(filter(None, (item.function_node_id, item.return_annotation_node_id))) + tuple(parameter.parameter_node_id for parameter in item.parameters), ("exact-built-in-annotations", "phase9-signature-policy"))) for item in signatures)),
|
|
901
|
+
FactTable("fact-table/0.9", "call-target-facts", self.stage_id, "call-node-id", Completeness.COMPLETE, ("function-signature-facts", "binding-facts", "value-category-facts"), tuple(FactRecord(item.call_node_id, item, FactProvenance((item.call_node_id,) + item.argument_node_ids, ("deterministic-target-resolution", "positional-source-order"))) for item in calls)),
|
|
902
|
+
FactTable("fact-table/0.9", "return-path-facts", self.stage_id, "function-node-id", Completeness.COMPLETE, ("function-signature-facts", "value-category-facts"), tuple(FactRecord(item.function_node_id, item, FactProvenance((item.function_node_id,) + item.return_node_ids, ("reachable-return-analysis", "implicit-none-policy"))) for item in return_paths)),
|
|
903
|
+
FactTable("fact-table/0.9", "local-declaration-facts", self.stage_id, "function-node-id", Completeness.COMPLETE, ("binding-facts", "evaluation-order-facts"), tuple(FactRecord(item.function_node_id, item, FactProvenance((item.function_node_id,), ("definite-binding-analysis", "loop-target-lifetime"))) for item in local_facts)),
|
|
904
|
+
FactTable("fact-table/0.9", "call-graph-facts", self.stage_id, "module", Completeness.COMPLETE, ("call-target-facts",), (FactRecord("module-call-graph", graph, FactProvenance((module["root_node_id"],), ("direct-and-mutual-recursion-policy",))),)),
|
|
905
|
+
)
|
|
906
|
+
)
|
|
907
|
+
if containers_enabled:
|
|
908
|
+
fact_tables.extend(
|
|
909
|
+
(
|
|
910
|
+
FactTable(CONTAINER_FACT_SCHEMA, "container-shape-facts", self.stage_id, "python-container-literal-node-id", Completeness.COMPLETE, ("value-category-facts",), tuple(FactRecord(item.literal_node_id, item, FactProvenance((item.literal_node_id,) + item.element_node_ids + item.key_node_ids + item.value_node_ids, ("fixed-capacity", "homogeneous-representation", "approved-container-profile"))) for item in container_shapes)),
|
|
911
|
+
FactTable(CONTAINER_FACT_SCHEMA, "container-binding-facts", self.stage_id, "binding-id", Completeness.COMPLETE, ("binding-facts", "container-shape-facts"), tuple(FactRecord(item.binding_id, item, FactProvenance((item.assignment_node_id, item.target_node_id, item.literal_node_id) + item.allowed_use_node_ids + item.invalid_use_node_ids, ("single-local-binding", "no-alias-or-escape"))) for item in container_bindings)),
|
|
912
|
+
FactTable(CONTAINER_FACT_SCHEMA, "container-access-facts", self.stage_id, "subscript-node-id", Completeness.COMPLETE, ("container-binding-facts",), tuple(FactRecord(item.subscript_node_id, item, FactProvenance(tuple(filter(None, (item.subscript_node_id, item.slice_node_id))), ("static-index-or-key-proof", "fixed-capacity-or-presence-proof"))) for item in container_accesses)),
|
|
913
|
+
FactTable(CONTAINER_FACT_SCHEMA, "container-iteration-facts", self.stage_id, "for-node-id", Completeness.COMPLETE, ("container-binding-facts",), tuple(FactRecord(item.for_node_id, item, FactProvenance(tuple(filter(None, (item.for_node_id, item.target_node_id))), ("fixed-bound", "source-insertion-order"))) for item in container_iterations)),
|
|
914
|
+
)
|
|
915
|
+
)
|
|
916
|
+
if records_enabled:
|
|
917
|
+
fact_tables.extend(self._record_fact_tables(record_analysis))
|
|
918
|
+
if numeric_enabled:
|
|
919
|
+
fact_tables.append(self._numeric_fact_table(numeric_analysis))
|
|
920
|
+
if conditional_enabled:
|
|
921
|
+
fact_tables.append(self._conditional_fact_table(conditional_analysis))
|
|
922
|
+
if keyword_calls_enabled:
|
|
923
|
+
fact_tables.append(self._keyword_call_fact_table(keyword_call_analysis))
|
|
924
|
+
if keyword_only_calls_enabled:
|
|
925
|
+
fact_tables.append(
|
|
926
|
+
self._keyword_only_call_fact_table(keyword_only_call_analysis)
|
|
927
|
+
)
|
|
928
|
+
if modules_enabled:
|
|
929
|
+
fact_tables.extend(module_fact_tables)
|
|
930
|
+
|
|
931
|
+
plan_schema = (
|
|
932
|
+
CONVERSION_PLAN_SCHEMA
|
|
933
|
+
if keyword_only_calls_enabled
|
|
934
|
+
else PHASE14C_CONVERSION_PLAN_SCHEMA
|
|
935
|
+
if keyword_calls_enabled
|
|
936
|
+
else PHASE14B_CONVERSION_PLAN_SCHEMA
|
|
937
|
+
if conditional_enabled
|
|
938
|
+
else PHASE14A_CONVERSION_PLAN_SCHEMA
|
|
939
|
+
if numeric_enabled
|
|
940
|
+
else PHASE13_CONVERSION_PLAN_SCHEMA
|
|
941
|
+
if records_enabled
|
|
942
|
+
else PHASE12_CONVERSION_PLAN_SCHEMA
|
|
943
|
+
if modules_enabled
|
|
944
|
+
else "conversion-plan/0.11"
|
|
945
|
+
if containers_enabled
|
|
946
|
+
else "conversion-plan/0.9"
|
|
947
|
+
if functions_enabled
|
|
948
|
+
else "conversion-plan/0.5"
|
|
949
|
+
)
|
|
950
|
+
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 containers_enabled else "0.9" if functions_enabled else "0.5"
|
|
951
|
+
helper_requirements = sorted(
|
|
952
|
+
{
|
|
953
|
+
requirement
|
|
954
|
+
for plan in plans
|
|
955
|
+
for requirement in plan.get("helper_requirements", ())
|
|
956
|
+
}
|
|
957
|
+
)
|
|
958
|
+
generated_name_inputs = tuple(
|
|
959
|
+
{
|
|
960
|
+
**binding,
|
|
961
|
+
"module_generated_name": bool(
|
|
962
|
+
functions_by_node.get(binding.get("declaration_node_id"), {}).get("module_generated_name")
|
|
963
|
+
or module_records.get(binding.get("declaration_node_id"), {}).get("module_generated_name")
|
|
964
|
+
),
|
|
965
|
+
}
|
|
966
|
+
for binding in binding_dicts
|
|
967
|
+
) + tuple(
|
|
968
|
+
{
|
|
969
|
+
"binding_id": field.field_id,
|
|
970
|
+
"source_name": field.source_name,
|
|
971
|
+
"binding_kind": "record-layout-field",
|
|
972
|
+
"declaration_node_id": field.declaration_node_id,
|
|
973
|
+
"occurrence_node_ids": (),
|
|
974
|
+
}
|
|
975
|
+
for field in (record_analysis.fields if record_analysis else ())
|
|
976
|
+
)
|
|
977
|
+
payload = {
|
|
978
|
+
"stage_order": tuple(artifact.payload["stage_order"]) + (self.stage_id,),
|
|
979
|
+
"python_ir": module,
|
|
980
|
+
"python_ir_fingerprint": artifact.artifact_fingerprint.value,
|
|
981
|
+
"target_contract": target,
|
|
982
|
+
"semantic_policy": semantics,
|
|
983
|
+
"rule_set_version": ruleset,
|
|
984
|
+
"renderer_version": request.renderer_version,
|
|
985
|
+
"helper_policy_version": request.helper_policy_version,
|
|
986
|
+
"container_policy_version": request.container_policy_version,
|
|
987
|
+
"fact_tables": [table.to_dict() if hasattr(table, "to_dict") else dict(table) for table in fact_tables],
|
|
988
|
+
"evaluation_order_facts": order,
|
|
989
|
+
"effect_facts": effects,
|
|
990
|
+
"truthiness_facts": truthiness,
|
|
991
|
+
"representation_plans": representations,
|
|
992
|
+
"generated_name_plans": [
|
|
993
|
+
item.to_dict()
|
|
994
|
+
for item in planner.generated_names(generated_name_inputs)
|
|
995
|
+
],
|
|
996
|
+
"support_decisions": support,
|
|
997
|
+
"rule_registry_manifest": list(registry.manifest),
|
|
998
|
+
"rule_plans": plans,
|
|
999
|
+
"helper_requirements": helper_requirements,
|
|
1000
|
+
"module_rejection": function_analyzer.module_rejection(
|
|
1001
|
+
allow_imports=modules_enabled,
|
|
1002
|
+
allow_records=records_enabled,
|
|
1003
|
+
),
|
|
1004
|
+
"module_policy_version": request.module_policy_version,
|
|
1005
|
+
"schema_version": plan_schema,
|
|
1006
|
+
}
|
|
1007
|
+
if records_enabled:
|
|
1008
|
+
payload["record_policy_version"] = request.record_policy_version
|
|
1009
|
+
if numeric_enabled:
|
|
1010
|
+
payload["numeric_policy_version"] = request.numeric_policy_version
|
|
1011
|
+
if modules_enabled:
|
|
1012
|
+
for key in ("module_bundle", "module_resolution", "module_bundle_assembly_node_id"):
|
|
1013
|
+
if key in artifact.payload:
|
|
1014
|
+
payload[key] = artifact.payload[key]
|
|
1015
|
+
if records_enabled and "module_record_by_node" in artifact.payload:
|
|
1016
|
+
payload["module_record_by_node"] = artifact.payload["module_record_by_node"]
|
|
1017
|
+
try:
|
|
1018
|
+
valid, message = validate_analysis_payload(
|
|
1019
|
+
payload,
|
|
1020
|
+
cancellation=services.context.cancellation,
|
|
1021
|
+
)
|
|
1022
|
+
except ConditionalRegionValidationCanceled:
|
|
1023
|
+
return StageOutcome(
|
|
1024
|
+
StageTerminal.CANCELED,
|
|
1025
|
+
diagnostics=(Diagnostic(
|
|
1026
|
+
"PYC1901",
|
|
1027
|
+
Severity.ERROR,
|
|
1028
|
+
self.stage_id,
|
|
1029
|
+
"Conversion canceled during conditional-region validation",
|
|
1030
|
+
),),
|
|
1031
|
+
)
|
|
1032
|
+
except KeywordCallValidationCanceled:
|
|
1033
|
+
return StageOutcome(
|
|
1034
|
+
StageTerminal.CANCELED,
|
|
1035
|
+
diagnostics=(Diagnostic(
|
|
1036
|
+
"PYC1901",
|
|
1037
|
+
Severity.ERROR,
|
|
1038
|
+
self.stage_id,
|
|
1039
|
+
"Conversion canceled during keyword-call validation",
|
|
1040
|
+
),),
|
|
1041
|
+
)
|
|
1042
|
+
except KeywordOnlyCallValidationCanceled:
|
|
1043
|
+
return StageOutcome(
|
|
1044
|
+
StageTerminal.CANCELED,
|
|
1045
|
+
diagnostics=(Diagnostic(
|
|
1046
|
+
"PYC1901",
|
|
1047
|
+
Severity.ERROR,
|
|
1048
|
+
self.stage_id,
|
|
1049
|
+
"Conversion canceled during keyword-only call validation",
|
|
1050
|
+
),),
|
|
1051
|
+
)
|
|
1052
|
+
if not valid:
|
|
1053
|
+
return StageOutcome(StageTerminal.INTERNAL_FAILURE, diagnostics=(Diagnostic("PYC9501", Severity.INTERNAL_ERROR, self.stage_id, message),))
|
|
1054
|
+
return StageOutcome(StageTerminal.COMPLETED, _artifact(artifact, payload, artifact_version))
|
|
1055
|
+
|
|
1056
|
+
def validate(self, artifact: StageArtifact, services: Any) -> tuple[bool, str]:
|
|
1057
|
+
if artifact.kind != "conversion_plan":
|
|
1058
|
+
return False, "invalid analysis artifact kind"
|
|
1059
|
+
try:
|
|
1060
|
+
return validate_analysis_payload(
|
|
1061
|
+
dict(artifact.payload),
|
|
1062
|
+
cancellation=services.context.cancellation,
|
|
1063
|
+
)
|
|
1064
|
+
except (
|
|
1065
|
+
ConditionalRegionValidationCanceled,
|
|
1066
|
+
KeywordCallValidationCanceled,
|
|
1067
|
+
KeywordOnlyCallValidationCanceled,
|
|
1068
|
+
):
|
|
1069
|
+
# The facade checks the token immediately after validation and
|
|
1070
|
+
# retires the unpublished successor artifact as Canceled.
|
|
1071
|
+
return True, ""
|
|
1072
|
+
|
|
1073
|
+
@staticmethod
|
|
1074
|
+
def _cumulative_function_eligibility(
|
|
1075
|
+
signatures: dict[str, Any],
|
|
1076
|
+
returns: dict[str, Any],
|
|
1077
|
+
locals_by_function: dict[str, Any],
|
|
1078
|
+
calls: tuple[CallTargetFact, ...],
|
|
1079
|
+
owners: dict[str, str],
|
|
1080
|
+
graph: Any,
|
|
1081
|
+
cancellation: Any,
|
|
1082
|
+
) -> dict[str, bool]:
|
|
1083
|
+
calls_by_owner: dict[str, list[CallTargetFact]] = {}
|
|
1084
|
+
callers_by_target: dict[str, list[str]] = {}
|
|
1085
|
+
for call in calls:
|
|
1086
|
+
if bool(getattr(cancellation, "is_canceled", False)):
|
|
1087
|
+
raise AnalysisCanceled
|
|
1088
|
+
owner = owners.get(call.call_node_id)
|
|
1089
|
+
if owner is not None:
|
|
1090
|
+
calls_by_owner.setdefault(owner, []).append(call)
|
|
1091
|
+
if call.target_function_node_id in signatures:
|
|
1092
|
+
callers_by_target.setdefault(
|
|
1093
|
+
call.target_function_node_id,
|
|
1094
|
+
[],
|
|
1095
|
+
).append(owner)
|
|
1096
|
+
recursive_functions = set(graph.recursive_function_node_ids)
|
|
1097
|
+
eligible: dict[str, bool] = {}
|
|
1098
|
+
for function_id, signature in signatures.items():
|
|
1099
|
+
if bool(getattr(cancellation, "is_canceled", False)):
|
|
1100
|
+
raise AnalysisCanceled
|
|
1101
|
+
eligible[function_id] = bool(
|
|
1102
|
+
signature.eligible
|
|
1103
|
+
and returns[function_id].compatible
|
|
1104
|
+
and not returns[function_id].fallthrough_possible
|
|
1105
|
+
and locals_by_function[function_id].valid
|
|
1106
|
+
and function_id not in recursive_functions
|
|
1107
|
+
and all(
|
|
1108
|
+
call.supported
|
|
1109
|
+
for call in calls_by_owner.get(function_id, ())
|
|
1110
|
+
)
|
|
1111
|
+
)
|
|
1112
|
+
pending = deque(
|
|
1113
|
+
function_id
|
|
1114
|
+
for function_id, supported in eligible.items()
|
|
1115
|
+
if not supported
|
|
1116
|
+
)
|
|
1117
|
+
while pending:
|
|
1118
|
+
if bool(getattr(cancellation, "is_canceled", False)):
|
|
1119
|
+
raise AnalysisCanceled
|
|
1120
|
+
target = pending.popleft()
|
|
1121
|
+
for caller in callers_by_target.get(target, ()):
|
|
1122
|
+
if eligible.get(caller, False):
|
|
1123
|
+
eligible[caller] = False
|
|
1124
|
+
pending.append(caller)
|
|
1125
|
+
return eligible
|
|
1126
|
+
|
|
1127
|
+
@staticmethod
|
|
1128
|
+
def _gate_keyword_calls_by_target(
|
|
1129
|
+
analysis: KeywordCallAnalysis,
|
|
1130
|
+
calls: tuple[CallTargetFact, ...],
|
|
1131
|
+
function_eligible: dict[str, bool],
|
|
1132
|
+
nodes_by_id: dict[str, dict[str, Any]],
|
|
1133
|
+
) -> tuple[KeywordCallAnalysis, tuple[CallTargetFact, ...]]:
|
|
1134
|
+
downgraded_call_ids = {
|
|
1135
|
+
fact.call_node_id
|
|
1136
|
+
for fact in analysis.facts
|
|
1137
|
+
if fact.supported
|
|
1138
|
+
and not function_eligible.get(fact.target_function_node_id, False)
|
|
1139
|
+
}
|
|
1140
|
+
if not downgraded_call_ids:
|
|
1141
|
+
return analysis, calls
|
|
1142
|
+
facts = tuple(
|
|
1143
|
+
replace(
|
|
1144
|
+
fact,
|
|
1145
|
+
runtime_binding_failure="compile-time-rejected",
|
|
1146
|
+
supported=False,
|
|
1147
|
+
diagnostic_code=CUMULATIVE_KEYWORD_TARGET_DIAGNOSTIC_CODE,
|
|
1148
|
+
reason=CUMULATIVE_KEYWORD_TARGET_REASON,
|
|
1149
|
+
rejection_node_id=fact.target_function_node_id,
|
|
1150
|
+
)
|
|
1151
|
+
if fact.call_node_id in downgraded_call_ids
|
|
1152
|
+
else fact
|
|
1153
|
+
for fact in analysis.facts
|
|
1154
|
+
)
|
|
1155
|
+
rejections = tuple(
|
|
1156
|
+
KeywordCallRejection(
|
|
1157
|
+
fact.call_node_id,
|
|
1158
|
+
fact.diagnostic_code or "PYC2910",
|
|
1159
|
+
fact.reason or "Unsupported keyword-call binding",
|
|
1160
|
+
fact.rejection_node_id or fact.call_node_id,
|
|
1161
|
+
nodes_by_id.get(
|
|
1162
|
+
fact.rejection_node_id or fact.call_node_id,
|
|
1163
|
+
{},
|
|
1164
|
+
).get("provenance", {}).get("source_span"),
|
|
1165
|
+
)
|
|
1166
|
+
for fact in facts
|
|
1167
|
+
if not fact.supported
|
|
1168
|
+
)
|
|
1169
|
+
gated_calls = tuple(
|
|
1170
|
+
replace(
|
|
1171
|
+
call,
|
|
1172
|
+
resolution="ineligible-source-function",
|
|
1173
|
+
supported=False,
|
|
1174
|
+
diagnostic_code=CUMULATIVE_KEYWORD_TARGET_DIAGNOSTIC_CODE,
|
|
1175
|
+
reason=CUMULATIVE_KEYWORD_TARGET_REASON,
|
|
1176
|
+
)
|
|
1177
|
+
if call.call_node_id in downgraded_call_ids
|
|
1178
|
+
else call
|
|
1179
|
+
for call in calls
|
|
1180
|
+
)
|
|
1181
|
+
return KeywordCallAnalysis(facts, rejections), gated_calls
|
|
1182
|
+
|
|
1183
|
+
@staticmethod
|
|
1184
|
+
def _gate_keyword_only_calls_by_target(
|
|
1185
|
+
analysis: KeywordOnlyCallAnalysis,
|
|
1186
|
+
calls: tuple[CallTargetFact, ...],
|
|
1187
|
+
function_eligible: dict[str, bool],
|
|
1188
|
+
nodes_by_id: dict[str, dict[str, Any]],
|
|
1189
|
+
) -> tuple[KeywordOnlyCallAnalysis, tuple[CallTargetFact, ...]]:
|
|
1190
|
+
downgraded_call_ids = {
|
|
1191
|
+
fact.call_node_id
|
|
1192
|
+
for fact in analysis.facts
|
|
1193
|
+
if fact.supported
|
|
1194
|
+
and not function_eligible.get(fact.target_function_node_id, False)
|
|
1195
|
+
}
|
|
1196
|
+
if not downgraded_call_ids:
|
|
1197
|
+
return analysis, calls
|
|
1198
|
+
facts = tuple(
|
|
1199
|
+
replace(
|
|
1200
|
+
fact,
|
|
1201
|
+
runtime_binding_failure="compile-time-rejected",
|
|
1202
|
+
supported=False,
|
|
1203
|
+
diagnostic_code=CUMULATIVE_KEYWORD_ONLY_TARGET_DIAGNOSTIC_CODE,
|
|
1204
|
+
reason=CUMULATIVE_KEYWORD_ONLY_TARGET_REASON,
|
|
1205
|
+
rejection_node_id=fact.target_function_node_id,
|
|
1206
|
+
)
|
|
1207
|
+
if fact.call_node_id in downgraded_call_ids
|
|
1208
|
+
else fact
|
|
1209
|
+
for fact in analysis.facts
|
|
1210
|
+
)
|
|
1211
|
+
rejections = tuple(
|
|
1212
|
+
KeywordOnlyCallRejection(
|
|
1213
|
+
fact.call_node_id,
|
|
1214
|
+
fact.diagnostic_code or "PYC2910",
|
|
1215
|
+
fact.reason or "Unsupported required keyword-only call",
|
|
1216
|
+
fact.rejection_node_id or fact.call_node_id,
|
|
1217
|
+
nodes_by_id.get(
|
|
1218
|
+
fact.rejection_node_id or fact.call_node_id,
|
|
1219
|
+
{},
|
|
1220
|
+
).get("provenance", {}).get("source_span"),
|
|
1221
|
+
)
|
|
1222
|
+
for fact in facts
|
|
1223
|
+
if not fact.supported
|
|
1224
|
+
)
|
|
1225
|
+
gated_calls = tuple(
|
|
1226
|
+
replace(
|
|
1227
|
+
call,
|
|
1228
|
+
resolution="ineligible-source-function",
|
|
1229
|
+
supported=False,
|
|
1230
|
+
diagnostic_code=CUMULATIVE_KEYWORD_ONLY_TARGET_DIAGNOSTIC_CODE,
|
|
1231
|
+
reason=CUMULATIVE_KEYWORD_ONLY_TARGET_REASON,
|
|
1232
|
+
)
|
|
1233
|
+
if call.call_node_id in downgraded_call_ids
|
|
1234
|
+
else call
|
|
1235
|
+
for call in calls
|
|
1236
|
+
)
|
|
1237
|
+
return KeywordOnlyCallAnalysis(facts, rejections), gated_calls
|
|
1238
|
+
|
|
1239
|
+
def _record_rejection(self, exc: RecordAnalysisError) -> StageOutcome:
|
|
1240
|
+
return StageOutcome(
|
|
1241
|
+
StageTerminal.REJECTED,
|
|
1242
|
+
diagnostics=(Diagnostic(
|
|
1243
|
+
exc.code,
|
|
1244
|
+
Severity.ERROR,
|
|
1245
|
+
self.stage_id,
|
|
1246
|
+
exc.message,
|
|
1247
|
+
source_span=exc.source_span,
|
|
1248
|
+
fact_references=(f"python-node:{exc.node_id}",),
|
|
1249
|
+
obligation_references=("phase13-static-record-profile",),
|
|
1250
|
+
explanation=exc.message,
|
|
1251
|
+
remediation="Use the documented immutable, module-local, automatic-record subset.",
|
|
1252
|
+
source_module_id=exc.module_id,
|
|
1253
|
+
source_logical_name=exc.logical_name,
|
|
1254
|
+
),),
|
|
1255
|
+
)
|
|
1256
|
+
|
|
1257
|
+
def _numeric_rejection(self, exc: NumericAnalysisError) -> StageOutcome:
|
|
1258
|
+
return StageOutcome(
|
|
1259
|
+
StageTerminal.REJECTED,
|
|
1260
|
+
diagnostics=(Diagnostic(
|
|
1261
|
+
exc.code,
|
|
1262
|
+
Severity.ERROR,
|
|
1263
|
+
self.stage_id,
|
|
1264
|
+
exc.message,
|
|
1265
|
+
source_span=exc.source_span,
|
|
1266
|
+
fact_references=(f"python-node:{exc.node_id}",),
|
|
1267
|
+
obligation_references=("phase14-proved-floor-arithmetic",),
|
|
1268
|
+
explanation=exc.message,
|
|
1269
|
+
remediation=(
|
|
1270
|
+
"Use exact int operands and a direct signed int64 divisor literal "
|
|
1271
|
+
"other than 0 or -1 inside an understood top-level function."
|
|
1272
|
+
),
|
|
1273
|
+
),),
|
|
1274
|
+
)
|
|
1275
|
+
|
|
1276
|
+
def _keyword_call_rejection(self, exc: KeywordCallAnalysisError) -> StageOutcome:
|
|
1277
|
+
return StageOutcome(
|
|
1278
|
+
StageTerminal.REJECTED,
|
|
1279
|
+
diagnostics=(Diagnostic(
|
|
1280
|
+
exc.code,
|
|
1281
|
+
Severity.ERROR,
|
|
1282
|
+
self.stage_id,
|
|
1283
|
+
exc.message,
|
|
1284
|
+
source_span=exc.source_span,
|
|
1285
|
+
fact_references=(f"python-node:{exc.node_id}",),
|
|
1286
|
+
obligation_references=("phase14c-exact-keyword-call-profile",),
|
|
1287
|
+
explanation=exc.message,
|
|
1288
|
+
remediation=(
|
|
1289
|
+
"Use only explicit positional and named arguments that bind "
|
|
1290
|
+
"every required source-function parameter exactly once."
|
|
1291
|
+
),
|
|
1292
|
+
),),
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
def _keyword_only_call_rejection(
|
|
1296
|
+
self,
|
|
1297
|
+
exc: KeywordOnlyCallAnalysisError,
|
|
1298
|
+
) -> StageOutcome:
|
|
1299
|
+
return StageOutcome(
|
|
1300
|
+
StageTerminal.REJECTED,
|
|
1301
|
+
diagnostics=(Diagnostic(
|
|
1302
|
+
exc.code,
|
|
1303
|
+
Severity.ERROR,
|
|
1304
|
+
self.stage_id,
|
|
1305
|
+
exc.message,
|
|
1306
|
+
source_span=exc.source_span,
|
|
1307
|
+
fact_references=(f"python-node:{exc.node_id}",),
|
|
1308
|
+
obligation_references=(
|
|
1309
|
+
"phase14d-required-keyword-only-call-profile",
|
|
1310
|
+
),
|
|
1311
|
+
explanation=exc.message,
|
|
1312
|
+
remediation=(
|
|
1313
|
+
"Use only required, exactly annotated keyword-only "
|
|
1314
|
+
"parameters supplied once by explicit keyword."
|
|
1315
|
+
),
|
|
1316
|
+
),),
|
|
1317
|
+
)
|
|
1318
|
+
|
|
1319
|
+
def _conditional_rejection(
|
|
1320
|
+
self,
|
|
1321
|
+
exc: ConditionalRegionAnalysisError,
|
|
1322
|
+
) -> StageOutcome:
|
|
1323
|
+
return StageOutcome(
|
|
1324
|
+
StageTerminal.REJECTED,
|
|
1325
|
+
diagnostics=(Diagnostic(
|
|
1326
|
+
exc.code,
|
|
1327
|
+
Severity.ERROR,
|
|
1328
|
+
self.stage_id,
|
|
1329
|
+
exc.message,
|
|
1330
|
+
source_span=exc.source_span,
|
|
1331
|
+
fact_references=(f"python-node:{exc.node_id}",),
|
|
1332
|
+
obligation_references=("phase14b-conditional-region-profile",),
|
|
1333
|
+
explanation=exc.message,
|
|
1334
|
+
remediation=(
|
|
1335
|
+
"Keep the conditional expression inside the documented "
|
|
1336
|
+
"already-supported scalar subset, or use an explicit statement."
|
|
1337
|
+
),
|
|
1338
|
+
),),
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1341
|
+
def _numeric_fact_table(self, analysis: Any) -> FactTable:
|
|
1342
|
+
operations = tuple(
|
|
1343
|
+
sorted(analysis.operations, key=lambda item: item.binop_node_id)
|
|
1344
|
+
)
|
|
1345
|
+
return FactTable(
|
|
1346
|
+
NUMERIC_FACT_SCHEMA,
|
|
1347
|
+
"numeric-operation-facts",
|
|
1348
|
+
self.stage_id,
|
|
1349
|
+
"binop-node-id",
|
|
1350
|
+
Completeness.COMPLETE,
|
|
1351
|
+
("value-category-facts", "evaluation-order-facts"),
|
|
1352
|
+
tuple(
|
|
1353
|
+
FactRecord(
|
|
1354
|
+
item.binop_node_id,
|
|
1355
|
+
item,
|
|
1356
|
+
FactProvenance(
|
|
1357
|
+
(
|
|
1358
|
+
item.binop_node_id,
|
|
1359
|
+
item.function_node_id,
|
|
1360
|
+
item.operator_node_id,
|
|
1361
|
+
item.left_node_id,
|
|
1362
|
+
*item.divisor_literal_node_ids,
|
|
1363
|
+
),
|
|
1364
|
+
(
|
|
1365
|
+
"exact-int64-categories",
|
|
1366
|
+
"direct-safe-divisor-literal",
|
|
1367
|
+
"left-to-right-once-staging",
|
|
1368
|
+
"frozen-floor-helper-contract",
|
|
1369
|
+
),
|
|
1370
|
+
),
|
|
1371
|
+
)
|
|
1372
|
+
for item in operations
|
|
1373
|
+
),
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
def _conditional_fact_table(self, analysis: Any) -> FactTable:
|
|
1377
|
+
regions = tuple(
|
|
1378
|
+
sorted(analysis.regions, key=lambda item: item.region_node_id)
|
|
1379
|
+
)
|
|
1380
|
+
return FactTable(
|
|
1381
|
+
CONDITIONAL_FACT_SCHEMA,
|
|
1382
|
+
CONDITIONAL_REGION_TABLE_ID,
|
|
1383
|
+
self.stage_id,
|
|
1384
|
+
CONDITIONAL_REGION_KEY_DOMAIN,
|
|
1385
|
+
Completeness.COMPLETE,
|
|
1386
|
+
CONDITIONAL_REGION_TABLE_DEPENDENCIES,
|
|
1387
|
+
tuple(
|
|
1388
|
+
FactRecord(
|
|
1389
|
+
item.region_node_id,
|
|
1390
|
+
item,
|
|
1391
|
+
FactProvenance(
|
|
1392
|
+
item.provenance_node_ids,
|
|
1393
|
+
CONDITIONAL_REGION_PROVENANCE_EVIDENCE,
|
|
1394
|
+
),
|
|
1395
|
+
)
|
|
1396
|
+
for item in regions
|
|
1397
|
+
),
|
|
1398
|
+
)
|
|
1399
|
+
|
|
1400
|
+
def _keyword_call_fact_table(self, analysis: Any) -> FactTable:
|
|
1401
|
+
facts = tuple(sorted(analysis.facts, key=lambda item: item.call_node_id))
|
|
1402
|
+
return FactTable(
|
|
1403
|
+
KEYWORD_CALL_FACT_SCHEMA,
|
|
1404
|
+
KEYWORD_CALL_TABLE_ID,
|
|
1405
|
+
self.stage_id,
|
|
1406
|
+
KEYWORD_CALL_KEY_DOMAIN,
|
|
1407
|
+
Completeness.COMPLETE,
|
|
1408
|
+
KEYWORD_CALL_TABLE_DEPENDENCIES,
|
|
1409
|
+
tuple(
|
|
1410
|
+
FactRecord(
|
|
1411
|
+
item.call_node_id,
|
|
1412
|
+
item,
|
|
1413
|
+
FactProvenance(
|
|
1414
|
+
item.provenance_node_ids,
|
|
1415
|
+
KEYWORD_CALL_PROVENANCE_EVIDENCE,
|
|
1416
|
+
),
|
|
1417
|
+
)
|
|
1418
|
+
for item in facts
|
|
1419
|
+
),
|
|
1420
|
+
)
|
|
1421
|
+
|
|
1422
|
+
def _keyword_only_call_fact_table(self, analysis: Any) -> FactTable:
|
|
1423
|
+
facts = tuple(sorted(analysis.facts, key=lambda item: item.call_node_id))
|
|
1424
|
+
return FactTable(
|
|
1425
|
+
KEYWORD_ONLY_CALL_FACT_SCHEMA,
|
|
1426
|
+
KEYWORD_ONLY_CALL_TABLE_ID,
|
|
1427
|
+
self.stage_id,
|
|
1428
|
+
KEYWORD_ONLY_CALL_KEY_DOMAIN,
|
|
1429
|
+
Completeness.COMPLETE,
|
|
1430
|
+
KEYWORD_ONLY_CALL_TABLE_DEPENDENCIES,
|
|
1431
|
+
tuple(
|
|
1432
|
+
FactRecord(
|
|
1433
|
+
item.call_node_id,
|
|
1434
|
+
item,
|
|
1435
|
+
FactProvenance(
|
|
1436
|
+
item.provenance_node_ids,
|
|
1437
|
+
KEYWORD_ONLY_CALL_PROVENANCE_EVIDENCE,
|
|
1438
|
+
),
|
|
1439
|
+
)
|
|
1440
|
+
for item in facts
|
|
1441
|
+
),
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
def _record_fact_tables(self, analysis: Any) -> tuple[FactTable, ...]:
|
|
1445
|
+
definitions = tuple(sorted(analysis.definitions, key=lambda item: item.record_id))
|
|
1446
|
+
fields = tuple(sorted(analysis.fields, key=lambda item: item.field_id))
|
|
1447
|
+
initializers = tuple(sorted(analysis.initializers, key=lambda item: item.initializer_id))
|
|
1448
|
+
instances = tuple(sorted(analysis.instances, key=lambda item: item.instance_id))
|
|
1449
|
+
bindings = tuple(sorted(analysis.bindings, key=lambda item: item.binding_id))
|
|
1450
|
+
accesses = tuple(sorted(analysis.accesses, key=lambda item: item.access_node_id))
|
|
1451
|
+
fields_by_record: dict[str, tuple[Any, ...]] = {}
|
|
1452
|
+
for definition in definitions:
|
|
1453
|
+
fields_by_record[definition.record_id] = tuple(
|
|
1454
|
+
item for item in fields if item.record_id == definition.record_id
|
|
1455
|
+
)
|
|
1456
|
+
initializer_by_id = {item.initializer_id: item for item in initializers}
|
|
1457
|
+
return (
|
|
1458
|
+
FactTable(
|
|
1459
|
+
RECORD_FACT_SCHEMA,
|
|
1460
|
+
"record-definition-facts",
|
|
1461
|
+
self.stage_id,
|
|
1462
|
+
"record-id",
|
|
1463
|
+
Completeness.COMPLETE,
|
|
1464
|
+
("binding-facts", "module-identity-facts"),
|
|
1465
|
+
tuple(
|
|
1466
|
+
FactRecord(
|
|
1467
|
+
item.record_id,
|
|
1468
|
+
item,
|
|
1469
|
+
FactProvenance(
|
|
1470
|
+
(item.class_node_id,)
|
|
1471
|
+
+ tuple(field.declaration_node_id for field in fields_by_record[item.record_id])
|
|
1472
|
+
+ (initializer_by_id[item.initializer_id].function_node_id,),
|
|
1473
|
+
("closed-class-shape", "automatic-inline-record", "immutable-noalias-noescape"),
|
|
1474
|
+
),
|
|
1475
|
+
)
|
|
1476
|
+
for item in definitions
|
|
1477
|
+
),
|
|
1478
|
+
),
|
|
1479
|
+
FactTable(
|
|
1480
|
+
RECORD_FACT_SCHEMA,
|
|
1481
|
+
"record-field-facts",
|
|
1482
|
+
self.stage_id,
|
|
1483
|
+
"record-field-id",
|
|
1484
|
+
Completeness.COMPLETE,
|
|
1485
|
+
("record-definition-facts", "binding-facts"),
|
|
1486
|
+
tuple(
|
|
1487
|
+
FactRecord(
|
|
1488
|
+
item.field_id,
|
|
1489
|
+
item,
|
|
1490
|
+
FactProvenance(
|
|
1491
|
+
(item.declaration_node_id, item.target_node_id, item.annotation_node_id),
|
|
1492
|
+
("exact-scalar-annotation", "source-field-order", "immutable-field"),
|
|
1493
|
+
),
|
|
1494
|
+
)
|
|
1495
|
+
for item in fields
|
|
1496
|
+
),
|
|
1497
|
+
),
|
|
1498
|
+
FactTable(
|
|
1499
|
+
RECORD_FACT_SCHEMA,
|
|
1500
|
+
"record-initializer-facts",
|
|
1501
|
+
self.stage_id,
|
|
1502
|
+
"record-initializer-id",
|
|
1503
|
+
Completeness.COMPLETE,
|
|
1504
|
+
("record-definition-facts", "record-field-facts"),
|
|
1505
|
+
tuple(
|
|
1506
|
+
FactRecord(
|
|
1507
|
+
item.initializer_id,
|
|
1508
|
+
item,
|
|
1509
|
+
FactProvenance(
|
|
1510
|
+
(item.function_node_id, item.arguments_node_id, item.self_parameter_node_id)
|
|
1511
|
+
+ item.parameter_node_ids
|
|
1512
|
+
+ item.assignment_node_ids,
|
|
1513
|
+
("exact-positional-initializer", "all-fields-once", "left-to-right-field-order"),
|
|
1514
|
+
),
|
|
1515
|
+
)
|
|
1516
|
+
for item in initializers
|
|
1517
|
+
),
|
|
1518
|
+
),
|
|
1519
|
+
FactTable(
|
|
1520
|
+
RECORD_FACT_SCHEMA,
|
|
1521
|
+
"record-instance-facts",
|
|
1522
|
+
self.stage_id,
|
|
1523
|
+
"record-instance-id",
|
|
1524
|
+
Completeness.COMPLETE,
|
|
1525
|
+
("record-definition-facts", "record-initializer-facts", "value-category-facts"),
|
|
1526
|
+
tuple(
|
|
1527
|
+
FactRecord(
|
|
1528
|
+
item.instance_id,
|
|
1529
|
+
item,
|
|
1530
|
+
FactProvenance(
|
|
1531
|
+
(item.assignment_node_id, item.target_node_id, item.construction_node_id)
|
|
1532
|
+
+ item.argument_node_ids,
|
|
1533
|
+
("fresh-function-local", "automatic-storage", "no-allocation-or-cleanup"),
|
|
1534
|
+
),
|
|
1535
|
+
)
|
|
1536
|
+
for item in instances
|
|
1537
|
+
),
|
|
1538
|
+
),
|
|
1539
|
+
FactTable(
|
|
1540
|
+
RECORD_FACT_SCHEMA,
|
|
1541
|
+
"record-binding-facts",
|
|
1542
|
+
self.stage_id,
|
|
1543
|
+
"binding-id",
|
|
1544
|
+
Completeness.COMPLETE,
|
|
1545
|
+
("record-instance-facts", "binding-facts"),
|
|
1546
|
+
tuple(
|
|
1547
|
+
FactRecord(
|
|
1548
|
+
item.binding_id,
|
|
1549
|
+
item,
|
|
1550
|
+
FactProvenance(
|
|
1551
|
+
(item.declaration_node_id,)
|
|
1552
|
+
+ item.occurrence_node_ids
|
|
1553
|
+
+ item.allowed_field_access_node_ids,
|
|
1554
|
+
("single-assignment", "no-alias", "no-escape"),
|
|
1555
|
+
),
|
|
1556
|
+
)
|
|
1557
|
+
for item in bindings
|
|
1558
|
+
),
|
|
1559
|
+
),
|
|
1560
|
+
FactTable(
|
|
1561
|
+
RECORD_FACT_SCHEMA,
|
|
1562
|
+
"record-access-facts",
|
|
1563
|
+
self.stage_id,
|
|
1564
|
+
"attribute-node-id",
|
|
1565
|
+
Completeness.COMPLETE,
|
|
1566
|
+
("record-binding-facts", "record-field-facts"),
|
|
1567
|
+
tuple(
|
|
1568
|
+
FactRecord(
|
|
1569
|
+
item.access_node_id,
|
|
1570
|
+
item,
|
|
1571
|
+
FactProvenance(
|
|
1572
|
+
(item.access_node_id,),
|
|
1573
|
+
("direct-owner-read", "statically-bound-field", "immutable-after-construction"),
|
|
1574
|
+
),
|
|
1575
|
+
)
|
|
1576
|
+
for item in accesses
|
|
1577
|
+
),
|
|
1578
|
+
),
|
|
1579
|
+
)
|
|
1580
|
+
|
|
1581
|
+
@staticmethod
|
|
1582
|
+
def _phase13_plan(
|
|
1583
|
+
plan: RulePlan,
|
|
1584
|
+
node: dict[str, Any],
|
|
1585
|
+
definitions: dict[str, Any],
|
|
1586
|
+
fields: dict[str, Any],
|
|
1587
|
+
initializers: dict[str, Any],
|
|
1588
|
+
constructions: dict[str, Any],
|
|
1589
|
+
assignments: dict[str, Any],
|
|
1590
|
+
bindings: dict[str, Any],
|
|
1591
|
+
accesses: dict[str, Any],
|
|
1592
|
+
occurrence_bindings: dict[str, str],
|
|
1593
|
+
) -> RulePlan:
|
|
1594
|
+
node_id = node["node_id"]
|
|
1595
|
+
if node_id in definitions:
|
|
1596
|
+
item = definitions[node_id]
|
|
1597
|
+
return _extend_plan(
|
|
1598
|
+
plan,
|
|
1599
|
+
facts=(f"record-definition:{item.record_id}", f"record-layout-fields:{len(item.field_ids)}"),
|
|
1600
|
+
obligations=("record-automatic-storage", "record-no-allocation-cleanup-or-null", "record-immutable-noalias-noescape"),
|
|
1601
|
+
explanation=("materialized-c-record", item.source_name),
|
|
1602
|
+
)
|
|
1603
|
+
if node_id in fields:
|
|
1604
|
+
item = fields[node_id]
|
|
1605
|
+
return _extend_plan(
|
|
1606
|
+
plan,
|
|
1607
|
+
facts=(f"record-field:{item.field_id}", f"record-field-ordinal:{item.ordinal}", f"record-field-category:{item.category.value}"),
|
|
1608
|
+
)
|
|
1609
|
+
if node_id in initializers:
|
|
1610
|
+
item = initializers[node_id]
|
|
1611
|
+
return _extend_plan(
|
|
1612
|
+
plan,
|
|
1613
|
+
facts=(f"record-initializer:{item.initializer_id}", f"record-definition:{item.record_id}"),
|
|
1614
|
+
obligations=("initializer-erased-after-exact-field-copy-proof",),
|
|
1615
|
+
)
|
|
1616
|
+
if node_id in constructions:
|
|
1617
|
+
item = constructions[node_id]
|
|
1618
|
+
return _extend_plan(
|
|
1619
|
+
plan,
|
|
1620
|
+
facts=(f"record-instance:{item.instance_id}", f"record-definition:{item.record_id}"),
|
|
1621
|
+
obligations=("constructor-arguments-staged-left-to-right-once", "const-aggregate-initialization"),
|
|
1622
|
+
)
|
|
1623
|
+
if node_id in assignments:
|
|
1624
|
+
item = assignments[node_id]
|
|
1625
|
+
return _extend_plan(
|
|
1626
|
+
plan,
|
|
1627
|
+
facts=(f"record-instance:{item.instance_id}", f"record-binding:{item.binding_id}"),
|
|
1628
|
+
obligations=("unique-lexical-owner", "record-binding-single-assignment"),
|
|
1629
|
+
)
|
|
1630
|
+
if node_id in accesses:
|
|
1631
|
+
item = accesses[node_id]
|
|
1632
|
+
return _extend_plan(
|
|
1633
|
+
plan,
|
|
1634
|
+
facts=(f"record-access:{item.access_node_id}", f"record-field:{item.field_id}", f"record-binding:{item.binding_id}"),
|
|
1635
|
+
obligations=("direct-member-read", "field-mutation-absent"),
|
|
1636
|
+
)
|
|
1637
|
+
binding = bindings.get(occurrence_bindings.get(node_id, ""))
|
|
1638
|
+
if binding is not None:
|
|
1639
|
+
return _extend_plan(
|
|
1640
|
+
plan,
|
|
1641
|
+
facts=(f"record-binding:{binding.binding_id}", f"record-instance:{binding.instance_id}"),
|
|
1642
|
+
obligations=("record-name-use-context-approved",),
|
|
1643
|
+
)
|
|
1644
|
+
if node["kind"] == "Name" and occurrence_bindings.get(node_id):
|
|
1645
|
+
return _extend_plan(
|
|
1646
|
+
plan,
|
|
1647
|
+
facts=(f"record-class-binding:{occurrence_bindings[node_id]}",),
|
|
1648
|
+
obligations=("direct-constructor-name-only",),
|
|
1649
|
+
)
|
|
1650
|
+
return plan
|
|
1651
|
+
|
|
1652
|
+
@staticmethod
|
|
1653
|
+
def _phase14_plan(
|
|
1654
|
+
plan: RulePlan,
|
|
1655
|
+
node: dict[str, Any],
|
|
1656
|
+
operations: dict[str, Any],
|
|
1657
|
+
) -> RulePlan:
|
|
1658
|
+
operation = operations.get(node["node_id"])
|
|
1659
|
+
if operation is None:
|
|
1660
|
+
return plan
|
|
1661
|
+
extended = _extend_plan(
|
|
1662
|
+
plan,
|
|
1663
|
+
facts=(
|
|
1664
|
+
f"numeric-operation:{operation.binop_node_id}",
|
|
1665
|
+
f"numeric-divisor:{operation.divisor_value}",
|
|
1666
|
+
f"numeric-helper:{operation.helper_requirement}",
|
|
1667
|
+
f"numeric-target:{operation.target_contract}",
|
|
1668
|
+
),
|
|
1669
|
+
obligations=(
|
|
1670
|
+
"operands-materialized-left-to-right-once",
|
|
1671
|
+
"helper-result-materialized-once",
|
|
1672
|
+
"zero-and-negative-one-divisors-excluded",
|
|
1673
|
+
),
|
|
1674
|
+
explanation=(
|
|
1675
|
+
operation.operator_kind.value,
|
|
1676
|
+
"via",
|
|
1677
|
+
operation.helper_requirement,
|
|
1678
|
+
),
|
|
1679
|
+
)
|
|
1680
|
+
return replace(
|
|
1681
|
+
extended,
|
|
1682
|
+
support_state=SupportState.SUPPORTED_WITH_HELPER,
|
|
1683
|
+
helper_requirements=(operation.helper_requirement,),
|
|
1684
|
+
)
|
|
1685
|
+
|
|
1686
|
+
@staticmethod
|
|
1687
|
+
def _phase14b_plan(
|
|
1688
|
+
plan: RulePlan,
|
|
1689
|
+
node: dict[str, Any],
|
|
1690
|
+
regions: dict[str, Any],
|
|
1691
|
+
) -> RulePlan:
|
|
1692
|
+
region = regions.get(node["node_id"])
|
|
1693
|
+
if region is None:
|
|
1694
|
+
return plan
|
|
1695
|
+
return _extend_plan(
|
|
1696
|
+
plan,
|
|
1697
|
+
facts=region.rule_facts,
|
|
1698
|
+
explanation=region.explanation_tokens,
|
|
1699
|
+
)
|
|
1700
|
+
|
|
1701
|
+
@staticmethod
|
|
1702
|
+
def _phase14c_plan(
|
|
1703
|
+
plan: RulePlan,
|
|
1704
|
+
node: dict[str, Any],
|
|
1705
|
+
bindings: dict[str, Any],
|
|
1706
|
+
) -> RulePlan:
|
|
1707
|
+
binding = bindings.get(node["node_id"])
|
|
1708
|
+
if binding is None or not binding.supported:
|
|
1709
|
+
return plan
|
|
1710
|
+
return _extend_plan(
|
|
1711
|
+
plan,
|
|
1712
|
+
facts=binding.rule_facts,
|
|
1713
|
+
explanation=binding.explanation_tokens,
|
|
1714
|
+
)
|
|
1715
|
+
|
|
1716
|
+
@staticmethod
|
|
1717
|
+
def _phase14d_plan(
|
|
1718
|
+
plan: RulePlan,
|
|
1719
|
+
node: dict[str, Any],
|
|
1720
|
+
bindings: dict[str, Any],
|
|
1721
|
+
signatures: dict[str, Any],
|
|
1722
|
+
nodes: dict[str, dict[str, Any]],
|
|
1723
|
+
) -> RulePlan:
|
|
1724
|
+
binding = bindings.get(node["node_id"])
|
|
1725
|
+
if binding is not None and binding.supported:
|
|
1726
|
+
return _extend_plan(
|
|
1727
|
+
plan,
|
|
1728
|
+
facts=binding.rule_facts,
|
|
1729
|
+
explanation=binding.explanation_tokens,
|
|
1730
|
+
)
|
|
1731
|
+
signature = signatures.get(node["node_id"])
|
|
1732
|
+
if signature is None or not signature.eligible:
|
|
1733
|
+
return plan
|
|
1734
|
+
arguments = nodes.get(node.get("fields", {}).get("args"), {})
|
|
1735
|
+
fields = arguments.get("fields", {})
|
|
1736
|
+
keyword_only_ids = tuple(fields.get("kwonlyargs", ()))
|
|
1737
|
+
if (
|
|
1738
|
+
not keyword_only_ids
|
|
1739
|
+
or tuple(fields.get("kw_defaults", ()))
|
|
1740
|
+
!= (None,) * len(keyword_only_ids)
|
|
1741
|
+
or fields.get("defaults")
|
|
1742
|
+
or fields.get("vararg")
|
|
1743
|
+
or fields.get("kwarg")
|
|
1744
|
+
):
|
|
1745
|
+
return plan
|
|
1746
|
+
keyword_only_names = tuple(
|
|
1747
|
+
str(nodes.get(parameter_id, {}).get("fields", {}).get("arg", ""))
|
|
1748
|
+
for parameter_id in keyword_only_ids
|
|
1749
|
+
)
|
|
1750
|
+
return _extend_plan(
|
|
1751
|
+
plan,
|
|
1752
|
+
facts=(
|
|
1753
|
+
f"keyword-only-signature:{node['node_id']}",
|
|
1754
|
+
f"keyword-only-parameter-count:{len(keyword_only_ids)}",
|
|
1755
|
+
"keyword-only-c-interface:mode-erased-after-static-binding",
|
|
1756
|
+
*(
|
|
1757
|
+
f"keyword-only-parameter:{ordinal}:{parameter_id}:{name}"
|
|
1758
|
+
for ordinal, (parameter_id, name) in enumerate(
|
|
1759
|
+
zip(keyword_only_ids, keyword_only_names)
|
|
1760
|
+
)
|
|
1761
|
+
),
|
|
1762
|
+
),
|
|
1763
|
+
obligations=(
|
|
1764
|
+
"required-keyword-only-parameters-exact",
|
|
1765
|
+
"keyword-only-parameter-kinds-preserved",
|
|
1766
|
+
"c-interface-mode-erasure-after-static-binding",
|
|
1767
|
+
"defaults-and-variadics-absent",
|
|
1768
|
+
),
|
|
1769
|
+
explanation=(
|
|
1770
|
+
"required-keyword-only-signature",
|
|
1771
|
+
str(len(keyword_only_ids)),
|
|
1772
|
+
"c-interface-mode-erasure",
|
|
1773
|
+
"after-static-binding",
|
|
1774
|
+
),
|
|
1775
|
+
)
|
|
1776
|
+
|
|
1777
|
+
@staticmethod
|
|
1778
|
+
def _phase9_plan(plan: RulePlan, node: dict[str, Any], signatures: dict[str, Any], calls: dict[str, CallTargetFact], owners: dict[str, str], returns: dict[str, Any], locals_by_function: dict[str, Any]) -> RulePlan:
|
|
1779
|
+
if node["kind"] == "FunctionDef" and node["node_id"] in signatures:
|
|
1780
|
+
signature = signatures[node["node_id"]]
|
|
1781
|
+
facts = tuple(filter(None, (f"signature:{signature.function_node_id}", f"return-annotation:{signature.return_annotation_node_id}"))) + tuple(f"parameter-annotation:{item.annotation_node_id}" for item in signature.parameters)
|
|
1782
|
+
return _extend_plan(plan, facts=facts, obligations=("prototype-signature-consistent", "all-reachable-returns-compatible", "implicit-none-fallthrough-rejected", "parameter-return-ownership-explicit", "local-declarations-definite", "recursion-policy-explicit"), explanation=("signature", signature.source_name, "uses", "exact-built-in-annotation-evidence"))
|
|
1783
|
+
if node["kind"] == "Call" and node["node_id"] in calls:
|
|
1784
|
+
call = calls[node["node_id"]]
|
|
1785
|
+
facts = (f"call-target:{call.target_binding_id}",) + tuple(f"annotation-evidence:{item}" for item in call.annotation_evidence) + tuple(f"argument-category:{item.value}" for item in call.argument_categories)
|
|
1786
|
+
return _extend_plan(plan, facts=facts, obligations=("call-result-representation-known", "source-output-call-mapping-required"), explanation=("resolved-target", call.target_name or "range", "argument-order", *call.evaluation_order))
|
|
1787
|
+
if node["kind"] == "Return":
|
|
1788
|
+
owner = owners.get(node["node_id"])
|
|
1789
|
+
if owner in returns:
|
|
1790
|
+
return _extend_plan(plan, facts=(f"return-path:{owner}", f"expected-category:{returns[owner].expected_category.value}"), obligations=("fallthrough-policy-checked", "return-ownership-boundary-explicit"))
|
|
1791
|
+
if node["kind"] == "Assign":
|
|
1792
|
+
owner = owners.get(node["node_id"])
|
|
1793
|
+
if owner in locals_by_function:
|
|
1794
|
+
return _extend_plan(plan, facts=(f"local-declarations:{owner}",), obligations=("use-before-binding-absent", "generated-name-collision-absent"))
|
|
1795
|
+
return plan
|
|
1796
|
+
|
|
1797
|
+
@staticmethod
|
|
1798
|
+
def _phase11_plan(
|
|
1799
|
+
plan: RulePlan,
|
|
1800
|
+
node: dict[str, Any],
|
|
1801
|
+
shapes: dict[str, Any],
|
|
1802
|
+
bindings: dict[str, Any],
|
|
1803
|
+
accesses: dict[str, Any],
|
|
1804
|
+
iterations: dict[str, Any],
|
|
1805
|
+
) -> RulePlan:
|
|
1806
|
+
node_id = node["node_id"]
|
|
1807
|
+
if node_id in shapes:
|
|
1808
|
+
shape = shapes[node_id]
|
|
1809
|
+
return _extend_plan(
|
|
1810
|
+
plan,
|
|
1811
|
+
facts=(
|
|
1812
|
+
f"container-shape:{node_id}",
|
|
1813
|
+
f"container-kind:{shape.container_kind}",
|
|
1814
|
+
f"container-capacity:{shape.capacity}",
|
|
1815
|
+
),
|
|
1816
|
+
obligations=(
|
|
1817
|
+
"container-storage-automatic",
|
|
1818
|
+
"container-allocation-failure-absent",
|
|
1819
|
+
"container-cleanup-not-required",
|
|
1820
|
+
),
|
|
1821
|
+
explanation=("fixed-container", shape.container_kind, "capacity", str(shape.capacity)),
|
|
1822
|
+
)
|
|
1823
|
+
if node_id in bindings:
|
|
1824
|
+
binding = bindings[node_id]
|
|
1825
|
+
return _extend_plan(
|
|
1826
|
+
plan,
|
|
1827
|
+
facts=(
|
|
1828
|
+
f"container-binding:{binding.binding_id}",
|
|
1829
|
+
f"container-literal:{binding.literal_node_id}",
|
|
1830
|
+
),
|
|
1831
|
+
obligations=("container-single-assignment-proved", "container-alias-and-escape-absent"),
|
|
1832
|
+
)
|
|
1833
|
+
if node_id in accesses:
|
|
1834
|
+
access = accesses[node_id]
|
|
1835
|
+
return _extend_plan(
|
|
1836
|
+
plan,
|
|
1837
|
+
facts=(
|
|
1838
|
+
f"container-access:{node_id}",
|
|
1839
|
+
f"container-binding:{access.binding_id}",
|
|
1840
|
+
f"container-offset:{access.resolved_offset}",
|
|
1841
|
+
),
|
|
1842
|
+
obligations=("container-access-failure-proved-absent", "container-access-mapping-required"),
|
|
1843
|
+
)
|
|
1844
|
+
if node_id in iterations:
|
|
1845
|
+
iteration = iterations[node_id]
|
|
1846
|
+
return _extend_plan(
|
|
1847
|
+
plan,
|
|
1848
|
+
facts=(
|
|
1849
|
+
f"container-iteration:{node_id}",
|
|
1850
|
+
f"container-binding:{iteration.binding_id}",
|
|
1851
|
+
f"container-capacity:{iteration.capacity}",
|
|
1852
|
+
),
|
|
1853
|
+
obligations=("container-iteration-bound-fixed", "container-iteration-order-explicit"),
|
|
1854
|
+
)
|
|
1855
|
+
return plan
|
|
1856
|
+
|
|
1857
|
+
@staticmethod
|
|
1858
|
+
def _phase12_plan(
|
|
1859
|
+
plan: RulePlan,
|
|
1860
|
+
node: dict[str, Any],
|
|
1861
|
+
imports_by_node: dict[str, list[dict[str, Any]]],
|
|
1862
|
+
imports_by_alias: dict[str, dict[str, Any]],
|
|
1863
|
+
functions_by_node: dict[str, dict[str, Any]],
|
|
1864
|
+
document_plan_nodes: dict[str, dict[str, Any]],
|
|
1865
|
+
initialization_by_node: dict[str, dict[str, Any]],
|
|
1866
|
+
calls: dict[str, CallTargetFact],
|
|
1867
|
+
owners: dict[str, str],
|
|
1868
|
+
assembly_node_id: str | None,
|
|
1869
|
+
) -> RulePlan:
|
|
1870
|
+
node_id = node["node_id"]
|
|
1871
|
+
if node_id in document_plan_nodes:
|
|
1872
|
+
item = document_plan_nodes[node_id]
|
|
1873
|
+
return _extend_plan(
|
|
1874
|
+
plan,
|
|
1875
|
+
facts=(f"module:{item.get('module_id')}", f"document:{item.get('document_id')}"),
|
|
1876
|
+
explanation=("explicit-module-document", str(item.get("module_id"))),
|
|
1877
|
+
)
|
|
1878
|
+
if node_id in imports_by_node:
|
|
1879
|
+
items = imports_by_node[node_id]
|
|
1880
|
+
return _extend_plan(
|
|
1881
|
+
plan,
|
|
1882
|
+
facts=tuple(
|
|
1883
|
+
value
|
|
1884
|
+
for item in items
|
|
1885
|
+
for value in (
|
|
1886
|
+
f"module-import:{item.get('import_item_id')}",
|
|
1887
|
+
f"target-module:{item.get('target_module_id')}",
|
|
1888
|
+
f"target-function:{item.get('target_function_node_id')}",
|
|
1889
|
+
)
|
|
1890
|
+
),
|
|
1891
|
+
explanation=("sourcebundle-only-import", *(str(item.get("target_module_id")) for item in items)),
|
|
1892
|
+
)
|
|
1893
|
+
if node_id in imports_by_alias:
|
|
1894
|
+
item = imports_by_alias[node_id]
|
|
1895
|
+
return _extend_plan(
|
|
1896
|
+
plan,
|
|
1897
|
+
facts=(f"module-import:{item.get('import_item_id')}", f"local-import-binding:{item.get('local_name')}"),
|
|
1898
|
+
)
|
|
1899
|
+
if node_id in functions_by_node:
|
|
1900
|
+
item = functions_by_node[node_id]
|
|
1901
|
+
return _extend_plan(
|
|
1902
|
+
plan,
|
|
1903
|
+
facts=(f"module:{item.get('module_id')}", f"module-function:{node_id}", f"linkage:{item.get('linkage')}"),
|
|
1904
|
+
explanation=("module-function", str(item.get("source_name")), "generated-as", str(item.get("flattened_name"))),
|
|
1905
|
+
)
|
|
1906
|
+
if node_id in calls:
|
|
1907
|
+
call = calls[node_id]
|
|
1908
|
+
caller = functions_by_node.get(owners.get(node_id))
|
|
1909
|
+
callee = functions_by_node.get(call.target_function_node_id)
|
|
1910
|
+
if caller and callee and caller.get("module_id") != callee.get("module_id"):
|
|
1911
|
+
return _extend_plan(
|
|
1912
|
+
plan,
|
|
1913
|
+
facts=(f"caller-module:{caller.get('module_id')}", f"callee-module:{callee.get('module_id')}", f"target-function:{call.target_function_node_id}"),
|
|
1914
|
+
obligations=("cross-module-call-uses-target-binding", "bundle-wide-recursion-policy-explicit"),
|
|
1915
|
+
explanation=("cross-module-direct-call", str(caller.get("module_id")), "to", str(callee.get("module_id"))),
|
|
1916
|
+
)
|
|
1917
|
+
if node_id in initialization_by_node:
|
|
1918
|
+
item = initialization_by_node[node_id]
|
|
1919
|
+
return _extend_plan(
|
|
1920
|
+
plan,
|
|
1921
|
+
facts=tuple(f"module-order:{ordinal}:{module_id}" for ordinal, module_id in enumerate(item.get("module_order", ()))),
|
|
1922
|
+
explanation=("compile-time-module-order", *(str(value) for value in item.get("module_order", ()))),
|
|
1923
|
+
)
|
|
1924
|
+
if node_id == assembly_node_id:
|
|
1925
|
+
return _extend_plan(
|
|
1926
|
+
plan,
|
|
1927
|
+
facts=("translation-unit-count:1", "module-runtime-initializers:0"),
|
|
1928
|
+
explanation=("single-c-translation-unit",),
|
|
1929
|
+
)
|
|
1930
|
+
return plan
|
|
1931
|
+
|
|
1932
|
+
@staticmethod
|
|
1933
|
+
def _unsupported_cause(node: dict[str, Any], signatures: dict[str, Any], calls: dict[str, CallTargetFact], owners: dict[str, str], returns: dict[str, Any], locals_by_function: dict[str, Any]) -> str | None:
|
|
1934
|
+
if node["kind"] == "Call" and node["node_id"] in calls:
|
|
1935
|
+
return calls[node["node_id"]].reason
|
|
1936
|
+
if node["kind"] == "FunctionDef" and node["node_id"] in signatures:
|
|
1937
|
+
signature = signatures[node["node_id"]]
|
|
1938
|
+
if not signature.eligible:
|
|
1939
|
+
return signature.rejection_reason
|
|
1940
|
+
path = returns.get(node["node_id"])
|
|
1941
|
+
if path and (not path.compatible or path.fallthrough_possible):
|
|
1942
|
+
return "Return representations are incompatible or a reachable path falls through"
|
|
1943
|
+
local = locals_by_function.get(node["node_id"])
|
|
1944
|
+
if local and not local.valid:
|
|
1945
|
+
return "Local declaration or lifetime policy is not satisfied"
|
|
1946
|
+
if node["kind"] == "Return":
|
|
1947
|
+
owner = owners.get(node["node_id"])
|
|
1948
|
+
path = returns.get(owner)
|
|
1949
|
+
if path and not path.compatible:
|
|
1950
|
+
return "Return representation is incompatible with the declared signature"
|
|
1951
|
+
return None
|
|
1952
|
+
|
|
1953
|
+
@staticmethod
|
|
1954
|
+
def _annotation_categories(module: dict[str, Any], bindings: tuple[Any, ...]) -> dict[str, ValueCategory]:
|
|
1955
|
+
nodes = {node["node_id"]: node for node in module["nodes"]}
|
|
1956
|
+
by_decl = {binding.declaration_node_id: binding.binding_id for binding in bindings}
|
|
1957
|
+
result: dict[str, ValueCategory] = {}
|
|
1958
|
+
mapping = {"int": ValueCategory.INTEGER, "float": ValueCategory.FLOAT, "bool": ValueCategory.BOOLEAN, "str": ValueCategory.STRING}
|
|
1959
|
+
for node_id, binding_id in by_decl.items():
|
|
1960
|
+
node = nodes.get(node_id)
|
|
1961
|
+
if not node or node["kind"] != "arg":
|
|
1962
|
+
continue
|
|
1963
|
+
annotation = nodes.get(node["fields"].get("annotation"))
|
|
1964
|
+
if annotation and annotation["kind"] == "Name":
|
|
1965
|
+
result[binding_id] = mapping.get(annotation["fields"].get("id"), ValueCategory.UNKNOWN)
|
|
1966
|
+
return result
|
|
1967
|
+
|
|
1968
|
+
@staticmethod
|
|
1969
|
+
def _truthiness_strategy(category: ValueCategory) -> str:
|
|
1970
|
+
return {
|
|
1971
|
+
ValueCategory.INTEGER: "nonzero",
|
|
1972
|
+
ValueCategory.FLOAT: "nonzero",
|
|
1973
|
+
ValueCategory.BOOLEAN: "identity",
|
|
1974
|
+
ValueCategory.STRING: "nonempty",
|
|
1975
|
+
ValueCategory.NONE: "always-false",
|
|
1976
|
+
}.get(category, "unresolved")
|