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,420 @@
|
|
|
1
|
+
"""Closed proof pass for helper-backed integer floor arithmetic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
from pycforge.converter.analysis.model import ValueCategory
|
|
9
|
+
from pycforge.converter.ir.python_ir import python_ir_reference_ids
|
|
10
|
+
from pycforge.converter.support_templates.factories import (
|
|
11
|
+
FLOOR_DIV_REFERENCE,
|
|
12
|
+
FLOOR_MOD_REFERENCE,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .model import (
|
|
16
|
+
BoundedNumericAnalysis,
|
|
17
|
+
FloorArithmeticKind,
|
|
18
|
+
NumericAnalysisCanceled,
|
|
19
|
+
NumericAnalysisError,
|
|
20
|
+
NumericOperationFact,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_PROHIBITED_CONTEXTS = frozenset(
|
|
25
|
+
{
|
|
26
|
+
"AsyncFor",
|
|
27
|
+
"AsyncFunctionDef",
|
|
28
|
+
"AsyncWith",
|
|
29
|
+
"Await",
|
|
30
|
+
"DictComp",
|
|
31
|
+
"FormattedValue",
|
|
32
|
+
"GeneratorExp",
|
|
33
|
+
"JoinedStr",
|
|
34
|
+
"Lambda",
|
|
35
|
+
"ListComp",
|
|
36
|
+
"Match",
|
|
37
|
+
"NamedExpr",
|
|
38
|
+
"SetComp",
|
|
39
|
+
"Try",
|
|
40
|
+
"TryStar",
|
|
41
|
+
"With",
|
|
42
|
+
"Yield",
|
|
43
|
+
"YieldFrom",
|
|
44
|
+
"comprehension",
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
_MAX_DIRECT_MAGNITUDE = 2**63 - 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _stable_id(prefix: str, *parts: str) -> str:
|
|
51
|
+
digest = hashlib.sha256("\x1f".join(parts).encode("utf-8")).hexdigest()[:20]
|
|
52
|
+
return prefix + digest
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class BoundedNumericAnalyzer:
|
|
56
|
+
"""Prove every ``//`` and ``%`` occurrence in an active Phase 14 plan."""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
module: dict[str, Any],
|
|
61
|
+
*,
|
|
62
|
+
categories: Mapping[str, ValueCategory],
|
|
63
|
+
function_records: Mapping[str, Mapping[str, Any]],
|
|
64
|
+
supported_call_node_ids: frozenset[str],
|
|
65
|
+
supported_container_access_node_ids: frozenset[str],
|
|
66
|
+
supported_record_access_node_ids: frozenset[str],
|
|
67
|
+
rejected_expression_diagnostics: Mapping[str, tuple[str, str]],
|
|
68
|
+
cancellation: Any,
|
|
69
|
+
) -> None:
|
|
70
|
+
self.module = module
|
|
71
|
+
self.nodes = {node["node_id"]: node for node in module["nodes"]}
|
|
72
|
+
self.ordinals = {
|
|
73
|
+
node["node_id"]: ordinal for ordinal, node in enumerate(module["nodes"])
|
|
74
|
+
}
|
|
75
|
+
self.categories = categories
|
|
76
|
+
self.function_records = function_records
|
|
77
|
+
self.supported_call_node_ids = supported_call_node_ids
|
|
78
|
+
self.supported_container_access_node_ids = supported_container_access_node_ids
|
|
79
|
+
self.supported_record_access_node_ids = supported_record_access_node_ids
|
|
80
|
+
self.rejected_expression_diagnostics = rejected_expression_diagnostics
|
|
81
|
+
self.cancellation = cancellation
|
|
82
|
+
self._approved_numeric_node_ids: set[str] = set()
|
|
83
|
+
self._integer_expression_cache: dict[str, bool] = {}
|
|
84
|
+
self.parents: dict[str, list[tuple[str, str]]] = {}
|
|
85
|
+
for parent in module["nodes"]:
|
|
86
|
+
self._check_cancellation()
|
|
87
|
+
for field_name, value in parent.get("fields", {}).items():
|
|
88
|
+
for child_id in python_ir_reference_ids(
|
|
89
|
+
parent["kind"], field_name, value, self.nodes
|
|
90
|
+
):
|
|
91
|
+
self.parents.setdefault(child_id, []).append(
|
|
92
|
+
(parent["node_id"], field_name)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def analyze(self) -> BoundedNumericAnalysis:
|
|
96
|
+
operations: list[NumericOperationFact] = []
|
|
97
|
+
candidates: list[tuple[dict[str, Any], dict[str, Any]]] = []
|
|
98
|
+
for node in self.module["nodes"]:
|
|
99
|
+
self._check_cancellation()
|
|
100
|
+
if node.get("kind") != "BinOp":
|
|
101
|
+
continue
|
|
102
|
+
operator = self.nodes.get(node.get("fields", {}).get("op"), {})
|
|
103
|
+
if operator.get("kind") not in {"FloorDiv", "Mod"}:
|
|
104
|
+
continue
|
|
105
|
+
candidates.append((node, operator))
|
|
106
|
+
candidates.sort(
|
|
107
|
+
key=lambda item: (-self._depth(item[0]["node_id"]), self.ordinals[item[0]["node_id"]])
|
|
108
|
+
)
|
|
109
|
+
for node, operator in candidates:
|
|
110
|
+
operation = self._operation(node, operator)
|
|
111
|
+
operations.append(operation)
|
|
112
|
+
self._approved_numeric_node_ids.add(node["node_id"])
|
|
113
|
+
return BoundedNumericAnalysis(tuple(operations))
|
|
114
|
+
|
|
115
|
+
def _operation(
|
|
116
|
+
self,
|
|
117
|
+
node: dict[str, Any],
|
|
118
|
+
operator: dict[str, Any],
|
|
119
|
+
) -> NumericOperationFact:
|
|
120
|
+
node_id = node["node_id"]
|
|
121
|
+
left_id = node.get("fields", {}).get("left")
|
|
122
|
+
right_id = node.get("fields", {}).get("right")
|
|
123
|
+
function_node_id = self._approved_context(node_id)
|
|
124
|
+
if (
|
|
125
|
+
not isinstance(left_id, str)
|
|
126
|
+
or left_id not in self.nodes
|
|
127
|
+
or not isinstance(right_id, str)
|
|
128
|
+
or right_id not in self.nodes
|
|
129
|
+
or function_node_id is None
|
|
130
|
+
):
|
|
131
|
+
self._reject(
|
|
132
|
+
"PYC3701",
|
|
133
|
+
"Integer floor arithmetic is supported only in a direct expression context of an understood top-level function",
|
|
134
|
+
node,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
rejection = self._first_rejected_expression(left_id)
|
|
138
|
+
if rejection is not None:
|
|
139
|
+
rejected_node_id, code, reason = rejection
|
|
140
|
+
self._reject(code, reason, self.nodes[rejected_node_id])
|
|
141
|
+
|
|
142
|
+
left_category = self.categories.get(left_id, ValueCategory.UNKNOWN)
|
|
143
|
+
right_category = self.categories.get(right_id, ValueCategory.UNKNOWN)
|
|
144
|
+
result_category = self.categories.get(node_id, ValueCategory.UNKNOWN)
|
|
145
|
+
if (
|
|
146
|
+
left_category is not ValueCategory.INTEGER
|
|
147
|
+
or right_category is not ValueCategory.INTEGER
|
|
148
|
+
or result_category is not ValueCategory.INTEGER
|
|
149
|
+
):
|
|
150
|
+
self._reject(
|
|
151
|
+
"PYC3701",
|
|
152
|
+
"Integer // and % require exact integer-like operands and an integer-like result; bool and mixed categories are excluded",
|
|
153
|
+
node,
|
|
154
|
+
)
|
|
155
|
+
if not self._approved_integer_expression(left_id):
|
|
156
|
+
self._reject(
|
|
157
|
+
"PYC3701",
|
|
158
|
+
"The left operand of integer // or % must be an already supported scalar integer expression",
|
|
159
|
+
self.nodes[left_id],
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
divisor = self._signed_literal(self.nodes[right_id])
|
|
163
|
+
if divisor is None:
|
|
164
|
+
self._reject(
|
|
165
|
+
"PYC3702",
|
|
166
|
+
"The divisor of integer // or % must be a directly recognized, lowerable signed int64 literal",
|
|
167
|
+
self.nodes[right_id],
|
|
168
|
+
)
|
|
169
|
+
divisor_value, literal_ids, literal_shape = divisor
|
|
170
|
+
if divisor_value in {0, -1}:
|
|
171
|
+
reason = (
|
|
172
|
+
"A zero divisor is excluded because Python would raise ZeroDivisionError"
|
|
173
|
+
if divisor_value == 0
|
|
174
|
+
else "A divisor of -1 is excluded by the frozen int64 helper precondition"
|
|
175
|
+
)
|
|
176
|
+
self._reject("PYC3702", reason, self.nodes[right_id])
|
|
177
|
+
|
|
178
|
+
is_division = operator["kind"] == "FloorDiv"
|
|
179
|
+
kind = (
|
|
180
|
+
FloorArithmeticKind.FLOOR_DIVIDE
|
|
181
|
+
if is_division
|
|
182
|
+
else FloorArithmeticKind.FLOOR_MODULO
|
|
183
|
+
)
|
|
184
|
+
helper = FLOOR_DIV_REFERENCE if is_division else FLOOR_MOD_REFERENCE
|
|
185
|
+
owner = self.function_records[function_node_id]
|
|
186
|
+
module_id = owner.get("module_id")
|
|
187
|
+
document_id = owner.get("document_id")
|
|
188
|
+
logical_name = owner.get("logical_name")
|
|
189
|
+
if not all(
|
|
190
|
+
isinstance(item, str) and item
|
|
191
|
+
for item in (module_id, document_id, logical_name)
|
|
192
|
+
):
|
|
193
|
+
self._reject(
|
|
194
|
+
"PYC3701",
|
|
195
|
+
"Integer floor arithmetic lacks exact module, document, or logical-source ownership",
|
|
196
|
+
node,
|
|
197
|
+
)
|
|
198
|
+
return NumericOperationFact(
|
|
199
|
+
operation_id=_stable_id("numeric-op-", node_id, kind.value),
|
|
200
|
+
binop_node_id=node_id,
|
|
201
|
+
function_node_id=function_node_id,
|
|
202
|
+
module_id=module_id,
|
|
203
|
+
document_id=document_id,
|
|
204
|
+
logical_name=logical_name,
|
|
205
|
+
operator_node_id=operator["node_id"],
|
|
206
|
+
operator_kind=kind,
|
|
207
|
+
left_node_id=left_id,
|
|
208
|
+
right_node_id=right_id,
|
|
209
|
+
left_category=left_category.value,
|
|
210
|
+
right_category=right_category.value,
|
|
211
|
+
result_category=result_category.value,
|
|
212
|
+
left_c_type="int64_t",
|
|
213
|
+
right_c_type="int64_t",
|
|
214
|
+
result_c_type="int64_t",
|
|
215
|
+
divisor_value=divisor_value,
|
|
216
|
+
divisor_literal_node_ids=literal_ids,
|
|
217
|
+
literal_shape=literal_shape,
|
|
218
|
+
divisor_in_admitted_domain=True,
|
|
219
|
+
divisor_nonzero_proved=True,
|
|
220
|
+
negative_one_divisor_excluded=True,
|
|
221
|
+
minimum_signed_divisor_excluded=True,
|
|
222
|
+
helper_requirement=helper.canonical,
|
|
223
|
+
evaluation_order=(left_id, right_id),
|
|
224
|
+
operands_evaluated_once=True,
|
|
225
|
+
c_type="int64_t",
|
|
226
|
+
failure_policy="caller-proved-no-runtime-failure-channel",
|
|
227
|
+
support_state="SupportedWithHelper",
|
|
228
|
+
parameter_ownership="scalar-values-by-value",
|
|
229
|
+
result_ownership="scalar-value-by-value",
|
|
230
|
+
allocation_model="none",
|
|
231
|
+
cleanup_model="none",
|
|
232
|
+
runtime_failure_channel="none",
|
|
233
|
+
target_contract="c11-portable-fixed-v1",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _approved_integer_expression(
|
|
237
|
+
self,
|
|
238
|
+
node_id: str,
|
|
239
|
+
active: frozenset[str] = frozenset(),
|
|
240
|
+
) -> bool:
|
|
241
|
+
self._check_cancellation()
|
|
242
|
+
if node_id in self._integer_expression_cache:
|
|
243
|
+
return self._integer_expression_cache[node_id]
|
|
244
|
+
if node_id in active or self.categories.get(node_id) is not ValueCategory.INTEGER:
|
|
245
|
+
return False
|
|
246
|
+
node = self.nodes[node_id]
|
|
247
|
+
kind = node["kind"]
|
|
248
|
+
fields = node.get("fields", {})
|
|
249
|
+
next_active = active | {node_id}
|
|
250
|
+
accepted = False
|
|
251
|
+
if kind == "Name":
|
|
252
|
+
accepted = True
|
|
253
|
+
elif kind == "Constant":
|
|
254
|
+
value = fields.get("value")
|
|
255
|
+
accepted = (
|
|
256
|
+
isinstance(value, int)
|
|
257
|
+
and not isinstance(value, bool)
|
|
258
|
+
and 0 <= value <= _MAX_DIRECT_MAGNITUDE
|
|
259
|
+
)
|
|
260
|
+
elif kind == "UnaryOp":
|
|
261
|
+
operator = self.nodes.get(fields.get("op"), {})
|
|
262
|
+
operand_id = fields.get("operand")
|
|
263
|
+
accepted = (
|
|
264
|
+
operator.get("kind") in {"UAdd", "USub"}
|
|
265
|
+
and isinstance(operand_id, str)
|
|
266
|
+
and operand_id in self.nodes
|
|
267
|
+
and self._approved_integer_expression(operand_id, next_active)
|
|
268
|
+
)
|
|
269
|
+
elif kind == "BinOp":
|
|
270
|
+
operator = self.nodes.get(fields.get("op"), {})
|
|
271
|
+
left_id = fields.get("left")
|
|
272
|
+
right_id = fields.get("right")
|
|
273
|
+
if operator.get("kind") in {"FloorDiv", "Mod"}:
|
|
274
|
+
accepted = node_id in self._approved_numeric_node_ids
|
|
275
|
+
else:
|
|
276
|
+
accepted = (
|
|
277
|
+
operator.get("kind") in {"Add", "Sub", "Mult"}
|
|
278
|
+
and isinstance(left_id, str)
|
|
279
|
+
and isinstance(right_id, str)
|
|
280
|
+
and left_id in self.nodes
|
|
281
|
+
and right_id in self.nodes
|
|
282
|
+
and self._approved_integer_expression(left_id, next_active)
|
|
283
|
+
and self._approved_integer_expression(right_id, next_active)
|
|
284
|
+
)
|
|
285
|
+
elif kind == "Call":
|
|
286
|
+
accepted = node_id in self.supported_call_node_ids
|
|
287
|
+
elif kind == "Subscript":
|
|
288
|
+
accepted = node_id in self.supported_container_access_node_ids
|
|
289
|
+
elif kind == "Attribute":
|
|
290
|
+
accepted = node_id in self.supported_record_access_node_ids
|
|
291
|
+
self._integer_expression_cache[node_id] = accepted
|
|
292
|
+
return accepted
|
|
293
|
+
|
|
294
|
+
def _first_rejected_expression(
|
|
295
|
+
self,
|
|
296
|
+
node_id: str,
|
|
297
|
+
active: frozenset[str] = frozenset(),
|
|
298
|
+
) -> tuple[str, str, str] | None:
|
|
299
|
+
"""Preserve an established nested call/container rejection diagnostic."""
|
|
300
|
+
self._check_cancellation()
|
|
301
|
+
if node_id in active:
|
|
302
|
+
return None
|
|
303
|
+
diagnostic = self.rejected_expression_diagnostics.get(node_id)
|
|
304
|
+
if diagnostic is not None:
|
|
305
|
+
return node_id, diagnostic[0], diagnostic[1]
|
|
306
|
+
node = self.nodes[node_id]
|
|
307
|
+
children: list[str] = []
|
|
308
|
+
for field_name, value in sorted(node.get("fields", {}).items()):
|
|
309
|
+
children.extend(
|
|
310
|
+
python_ir_reference_ids(node["kind"], field_name, value, self.nodes)
|
|
311
|
+
)
|
|
312
|
+
for child_id in sorted(
|
|
313
|
+
set(children),
|
|
314
|
+
key=lambda item: (self.ordinals.get(item, 2**63 - 1), item),
|
|
315
|
+
):
|
|
316
|
+
rejection = self._first_rejected_expression(
|
|
317
|
+
child_id,
|
|
318
|
+
active | {node_id},
|
|
319
|
+
)
|
|
320
|
+
if rejection is not None:
|
|
321
|
+
return rejection
|
|
322
|
+
return None
|
|
323
|
+
|
|
324
|
+
def _depth(self, node_id: str) -> int:
|
|
325
|
+
depth = 0
|
|
326
|
+
current = node_id
|
|
327
|
+
seen: set[str] = set()
|
|
328
|
+
while current not in seen:
|
|
329
|
+
seen.add(current)
|
|
330
|
+
candidates = self.parents.get(current, ())
|
|
331
|
+
if not candidates:
|
|
332
|
+
break
|
|
333
|
+
current = min(
|
|
334
|
+
candidates,
|
|
335
|
+
key=lambda item: (self.ordinals.get(item[0], 2**63 - 1), item[1]),
|
|
336
|
+
)[0]
|
|
337
|
+
depth += 1
|
|
338
|
+
return depth
|
|
339
|
+
|
|
340
|
+
def _approved_context(self, node_id: str) -> str | None:
|
|
341
|
+
current = node_id
|
|
342
|
+
seen: set[str] = set()
|
|
343
|
+
while current not in seen:
|
|
344
|
+
self._check_cancellation()
|
|
345
|
+
seen.add(current)
|
|
346
|
+
candidates = self.parents.get(current, ())
|
|
347
|
+
if not candidates:
|
|
348
|
+
return None
|
|
349
|
+
parent_id, field_name = min(
|
|
350
|
+
candidates,
|
|
351
|
+
key=lambda item: (self.ordinals.get(item[0], 2**63 - 1), item[1]),
|
|
352
|
+
)
|
|
353
|
+
parent = self.nodes[parent_id]
|
|
354
|
+
if parent["kind"] == "FunctionDef":
|
|
355
|
+
return (
|
|
356
|
+
parent_id
|
|
357
|
+
if field_name == "body" and parent_id in self.function_records
|
|
358
|
+
else None
|
|
359
|
+
)
|
|
360
|
+
if parent["kind"] in _PROHIBITED_CONTEXTS or parent["kind"] == "ClassDef":
|
|
361
|
+
return None
|
|
362
|
+
current = parent_id
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
def _signed_literal(
|
|
366
|
+
self,
|
|
367
|
+
node: dict[str, Any],
|
|
368
|
+
) -> tuple[int, tuple[str, ...], str] | None:
|
|
369
|
+
"""Recognize only source shapes the existing int64 literal lowerer closes.
|
|
370
|
+
|
|
371
|
+
``-2**63`` has a positive Constant child outside that lowerer's signed
|
|
372
|
+
literal domain, so this mini-phase deliberately rejects it rather than
|
|
373
|
+
smuggling an unrepresentable child through a helper call.
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
if node.get("kind") == "Constant":
|
|
377
|
+
value = node.get("fields", {}).get("value")
|
|
378
|
+
if (
|
|
379
|
+
isinstance(value, int)
|
|
380
|
+
and not isinstance(value, bool)
|
|
381
|
+
and 0 <= value <= _MAX_DIRECT_MAGNITUDE
|
|
382
|
+
):
|
|
383
|
+
return value, (node["node_id"],), "constant"
|
|
384
|
+
return None
|
|
385
|
+
if node.get("kind") != "UnaryOp":
|
|
386
|
+
return None
|
|
387
|
+
operator = self.nodes.get(node.get("fields", {}).get("op"), {})
|
|
388
|
+
operand = self.nodes.get(node.get("fields", {}).get("operand"), {})
|
|
389
|
+
value = operand.get("fields", {}).get("value")
|
|
390
|
+
if (
|
|
391
|
+
operator.get("kind") not in {"UAdd", "USub"}
|
|
392
|
+
or operand.get("kind") != "Constant"
|
|
393
|
+
or not isinstance(value, int)
|
|
394
|
+
or isinstance(value, bool)
|
|
395
|
+
or not 0 <= value <= _MAX_DIRECT_MAGNITUDE
|
|
396
|
+
):
|
|
397
|
+
return None
|
|
398
|
+
signed = -value if operator["kind"] == "USub" else value
|
|
399
|
+
shape = "unary-minus" if operator["kind"] == "USub" else "unary-plus"
|
|
400
|
+
return signed, (node["node_id"], operator["node_id"], operand["node_id"]), shape
|
|
401
|
+
|
|
402
|
+
def _reject(
|
|
403
|
+
self,
|
|
404
|
+
code: str,
|
|
405
|
+
message: str,
|
|
406
|
+
node: dict[str, Any],
|
|
407
|
+
) -> None:
|
|
408
|
+
raise NumericAnalysisError(
|
|
409
|
+
code,
|
|
410
|
+
message,
|
|
411
|
+
node["node_id"],
|
|
412
|
+
node.get("provenance", {}).get("source_span"),
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
def _check_cancellation(self) -> None:
|
|
416
|
+
if bool(getattr(self.cancellation, "is_canceled", False)):
|
|
417
|
+
raise NumericAnalysisCanceled
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
__all__ = ["BoundedNumericAnalyzer"]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Helper-call lowering for independently validated numeric facts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from pycforge.converter.ir.c_ir import (
|
|
10
|
+
CCallExpr,
|
|
11
|
+
CIdentifierRef,
|
|
12
|
+
CIntegerLiteral,
|
|
13
|
+
CProvenance,
|
|
14
|
+
CType,
|
|
15
|
+
CUnaryExpr,
|
|
16
|
+
CUnaryOp,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _sid(prefix: str, *parts: str) -> str:
|
|
21
|
+
digest = hashlib.sha256("\x1f".join(parts).encode("utf-8")).hexdigest()[:20]
|
|
22
|
+
return prefix + digest
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class NumericLoweringServices:
|
|
27
|
+
nodes: dict[str, dict[str, Any]]
|
|
28
|
+
operations: dict[str, dict[str, Any]]
|
|
29
|
+
expression: Callable[[dict[str, Any]], tuple[tuple[Any, ...], Any]]
|
|
30
|
+
temporary: Callable[[str, dict[str, Any], int, CType, Any, tuple[str, ...]], tuple[Any, Any]]
|
|
31
|
+
provenance: Callable[[dict[str, Any]], CProvenance]
|
|
32
|
+
synthetic_provenance: Callable[[tuple[str, ...], str | None], CProvenance]
|
|
33
|
+
reject: Callable[[str, str, dict[str, Any] | None], None]
|
|
34
|
+
check_cancellation: Callable[[], None]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NumericCIRLowerer:
|
|
38
|
+
def __init__(self, services: NumericLoweringServices) -> None:
|
|
39
|
+
self.services = services
|
|
40
|
+
|
|
41
|
+
def operation(self, node: dict[str, Any]) -> tuple[tuple[Any, ...], Any]:
|
|
42
|
+
s = self.services
|
|
43
|
+
s.check_cancellation()
|
|
44
|
+
fact = s.operations.get(node["node_id"])
|
|
45
|
+
if not fact:
|
|
46
|
+
s.reject(
|
|
47
|
+
"PYC3701",
|
|
48
|
+
"Integer floor arithmetic lacks an independently validated numeric fact",
|
|
49
|
+
node,
|
|
50
|
+
)
|
|
51
|
+
left_node = s.nodes.get(fact["left_node_id"])
|
|
52
|
+
right_node = s.nodes.get(fact["right_node_id"])
|
|
53
|
+
if left_node is None or right_node is None:
|
|
54
|
+
s.reject("PYC3701", "Numeric fact references an absent operand", node)
|
|
55
|
+
|
|
56
|
+
prelude: list[Any] = []
|
|
57
|
+
left_prelude, left_expression = s.expression(left_node)
|
|
58
|
+
prelude.extend(left_prelude)
|
|
59
|
+
left_declaration, left_reference = s.temporary(
|
|
60
|
+
"numeric_left",
|
|
61
|
+
node,
|
|
62
|
+
0,
|
|
63
|
+
CType("int64_t"),
|
|
64
|
+
left_expression,
|
|
65
|
+
(fact["left_node_id"], node["node_id"]),
|
|
66
|
+
)
|
|
67
|
+
prelude.append(left_declaration)
|
|
68
|
+
|
|
69
|
+
right_expression = self._literal_from_validated_fact(fact, right_node)
|
|
70
|
+
right_declaration, right_reference = s.temporary(
|
|
71
|
+
"numeric_right",
|
|
72
|
+
node,
|
|
73
|
+
0,
|
|
74
|
+
CType("int64_t"),
|
|
75
|
+
right_expression,
|
|
76
|
+
(fact["right_node_id"], node["node_id"]),
|
|
77
|
+
)
|
|
78
|
+
prelude.append(right_declaration)
|
|
79
|
+
|
|
80
|
+
helper = fact["helper_requirement"]
|
|
81
|
+
callee = CIdentifierRef(
|
|
82
|
+
_sid("c-numeric-helper-ref-", node["node_id"], helper),
|
|
83
|
+
f"helper-binding:{helper}:function",
|
|
84
|
+
s.synthetic_provenance((node["node_id"],), node["node_id"]),
|
|
85
|
+
)
|
|
86
|
+
call = CCallExpr(
|
|
87
|
+
_sid("c-numeric-helper-call-", node["node_id"], helper),
|
|
88
|
+
callee,
|
|
89
|
+
(left_reference, right_reference),
|
|
90
|
+
s.provenance(node),
|
|
91
|
+
)
|
|
92
|
+
result_declaration, result_reference = s.temporary(
|
|
93
|
+
"numeric_result",
|
|
94
|
+
node,
|
|
95
|
+
0,
|
|
96
|
+
CType("int64_t"),
|
|
97
|
+
call,
|
|
98
|
+
(node["node_id"],),
|
|
99
|
+
)
|
|
100
|
+
prelude.append(result_declaration)
|
|
101
|
+
s.check_cancellation()
|
|
102
|
+
return tuple(prelude), result_reference
|
|
103
|
+
|
|
104
|
+
def _literal_from_validated_fact(
|
|
105
|
+
self,
|
|
106
|
+
fact: dict[str, Any],
|
|
107
|
+
node: dict[str, Any],
|
|
108
|
+
) -> Any:
|
|
109
|
+
value = fact.get("divisor_value")
|
|
110
|
+
if (
|
|
111
|
+
not isinstance(value, int)
|
|
112
|
+
or isinstance(value, bool)
|
|
113
|
+
or value in {0, -1}
|
|
114
|
+
or not -(2**63 - 1) <= value <= 2**63 - 1
|
|
115
|
+
):
|
|
116
|
+
self.services.reject("PYC3702", "Numeric divisor proof is invalid", node)
|
|
117
|
+
magnitude = abs(value)
|
|
118
|
+
literal = CIntegerLiteral(
|
|
119
|
+
_sid("c-numeric-divisor-literal-", node["node_id"], str(value)),
|
|
120
|
+
magnitude,
|
|
121
|
+
"LL",
|
|
122
|
+
self.services.provenance(node),
|
|
123
|
+
)
|
|
124
|
+
if value < 0:
|
|
125
|
+
return CUnaryExpr(
|
|
126
|
+
_sid("c-numeric-divisor-negate-", node["node_id"], str(value)),
|
|
127
|
+
CUnaryOp.NEGATE,
|
|
128
|
+
literal,
|
|
129
|
+
self.services.provenance(node),
|
|
130
|
+
)
|
|
131
|
+
return literal
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def bind_numeric_lowerer(owner: Any) -> NumericCIRLowerer:
|
|
135
|
+
"""Bind the feature lowerer without expanding the cumulative hotspot."""
|
|
136
|
+
|
|
137
|
+
return NumericCIRLowerer(
|
|
138
|
+
NumericLoweringServices(
|
|
139
|
+
nodes=owner.nodes,
|
|
140
|
+
operations=owner._optional_values(
|
|
141
|
+
"numeric-operation-facts", "binop_node_id"
|
|
142
|
+
),
|
|
143
|
+
expression=owner._expression,
|
|
144
|
+
temporary=owner._temporary,
|
|
145
|
+
provenance=owner._prov,
|
|
146
|
+
synthetic_provenance=owner._synthetic_prov,
|
|
147
|
+
reject=owner._reject,
|
|
148
|
+
check_cancellation=owner._check_cancel,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
__all__ = ["NumericCIRLowerer", "NumericLoweringServices", "bind_numeric_lowerer"]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Immutable evidence for the bounded Phase 14A numeric slice."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FloorArithmeticKind(str, Enum):
|
|
11
|
+
FLOOR_DIVIDE = "floor-divide"
|
|
12
|
+
FLOOR_MODULO = "floor-modulo"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NumericAnalysisCanceled(Exception):
|
|
16
|
+
"""Raised internally when conversion cancellation retires numeric work."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NumericAnalysisError(Exception):
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
code: str,
|
|
23
|
+
message: str,
|
|
24
|
+
node_id: str,
|
|
25
|
+
source_span: dict[str, object] | None,
|
|
26
|
+
) -> None:
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
self.code = code
|
|
29
|
+
self.message = message
|
|
30
|
+
self.node_id = node_id
|
|
31
|
+
self.source_span = source_span
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class NumericOperationFact:
|
|
36
|
+
operation_id: str
|
|
37
|
+
binop_node_id: str
|
|
38
|
+
function_node_id: str
|
|
39
|
+
module_id: str
|
|
40
|
+
document_id: str
|
|
41
|
+
logical_name: str
|
|
42
|
+
operator_node_id: str
|
|
43
|
+
operator_kind: FloorArithmeticKind
|
|
44
|
+
left_node_id: str
|
|
45
|
+
right_node_id: str
|
|
46
|
+
left_category: str
|
|
47
|
+
right_category: str
|
|
48
|
+
result_category: str
|
|
49
|
+
left_c_type: str
|
|
50
|
+
right_c_type: str
|
|
51
|
+
result_c_type: str
|
|
52
|
+
divisor_value: int
|
|
53
|
+
divisor_literal_node_ids: tuple[str, ...]
|
|
54
|
+
literal_shape: str
|
|
55
|
+
divisor_in_admitted_domain: bool
|
|
56
|
+
divisor_nonzero_proved: bool
|
|
57
|
+
negative_one_divisor_excluded: bool
|
|
58
|
+
minimum_signed_divisor_excluded: bool
|
|
59
|
+
helper_requirement: str
|
|
60
|
+
evaluation_order: tuple[str, str]
|
|
61
|
+
operands_evaluated_once: bool
|
|
62
|
+
c_type: str
|
|
63
|
+
failure_policy: str
|
|
64
|
+
support_state: str
|
|
65
|
+
parameter_ownership: str
|
|
66
|
+
result_ownership: str
|
|
67
|
+
allocation_model: str
|
|
68
|
+
cleanup_model: str
|
|
69
|
+
runtime_failure_channel: str
|
|
70
|
+
target_contract: str
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> dict[str, Any]:
|
|
73
|
+
return {
|
|
74
|
+
"operation_id": self.operation_id,
|
|
75
|
+
"binop_node_id": self.binop_node_id,
|
|
76
|
+
"function_node_id": self.function_node_id,
|
|
77
|
+
"module_id": self.module_id,
|
|
78
|
+
"document_id": self.document_id,
|
|
79
|
+
"logical_name": self.logical_name,
|
|
80
|
+
"operator_node_id": self.operator_node_id,
|
|
81
|
+
"operator_kind": self.operator_kind.value,
|
|
82
|
+
"left_node_id": self.left_node_id,
|
|
83
|
+
"right_node_id": self.right_node_id,
|
|
84
|
+
"left_category": self.left_category,
|
|
85
|
+
"right_category": self.right_category,
|
|
86
|
+
"result_category": self.result_category,
|
|
87
|
+
"left_c_type": self.left_c_type,
|
|
88
|
+
"right_c_type": self.right_c_type,
|
|
89
|
+
"result_c_type": self.result_c_type,
|
|
90
|
+
"divisor_value": self.divisor_value,
|
|
91
|
+
"divisor_literal_node_ids": list(self.divisor_literal_node_ids),
|
|
92
|
+
"literal_shape": self.literal_shape,
|
|
93
|
+
"divisor_in_admitted_domain": self.divisor_in_admitted_domain,
|
|
94
|
+
"divisor_nonzero_proved": self.divisor_nonzero_proved,
|
|
95
|
+
"negative_one_divisor_excluded": self.negative_one_divisor_excluded,
|
|
96
|
+
"minimum_signed_divisor_excluded": self.minimum_signed_divisor_excluded,
|
|
97
|
+
"helper_requirement": self.helper_requirement,
|
|
98
|
+
"evaluation_order": list(self.evaluation_order),
|
|
99
|
+
"operands_evaluated_once": self.operands_evaluated_once,
|
|
100
|
+
"c_type": self.c_type,
|
|
101
|
+
"failure_policy": self.failure_policy,
|
|
102
|
+
"support_state": self.support_state,
|
|
103
|
+
"parameter_ownership": self.parameter_ownership,
|
|
104
|
+
"result_ownership": self.result_ownership,
|
|
105
|
+
"allocation_model": self.allocation_model,
|
|
106
|
+
"cleanup_model": self.cleanup_model,
|
|
107
|
+
"runtime_failure_channel": self.runtime_failure_channel,
|
|
108
|
+
"target_contract": self.target_contract,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True, slots=True)
|
|
113
|
+
class BoundedNumericAnalysis:
|
|
114
|
+
operations: tuple[NumericOperationFact, ...]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
__all__ = [
|
|
118
|
+
"BoundedNumericAnalysis",
|
|
119
|
+
"FloorArithmeticKind",
|
|
120
|
+
"NumericAnalysisCanceled",
|
|
121
|
+
"NumericAnalysisError",
|
|
122
|
+
"NumericOperationFact",
|
|
123
|
+
]
|