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,2685 @@
|
|
|
1
|
+
"""Independent Checkpoint E source-only audits.
|
|
2
|
+
|
|
3
|
+
The fuzz audit drives the public converter and inspects emitted text with the
|
|
4
|
+
independent C-text conformance parser. It deliberately does not invoke a C
|
|
5
|
+
compiler, linker, loader, debugger, or generated program.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import ast
|
|
11
|
+
from collections import Counter
|
|
12
|
+
from collections.abc import Iterable, Mapping
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
import hashlib
|
|
15
|
+
import json
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
import random
|
|
18
|
+
import re
|
|
19
|
+
import tomllib
|
|
20
|
+
from types import MappingProxyType
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from pycforge import (
|
|
24
|
+
ConversionRequest,
|
|
25
|
+
PythonToCConverter,
|
|
26
|
+
ResultStatus,
|
|
27
|
+
SourceBundle,
|
|
28
|
+
SourceDocumentInput,
|
|
29
|
+
)
|
|
30
|
+
from pycforge.converter.c_output import validate_c_text
|
|
31
|
+
from pycforge.converter.contracts.configuration import (
|
|
32
|
+
DEFAULT_CONTAINER_POLICY,
|
|
33
|
+
DEFAULT_HELPER_POLICY,
|
|
34
|
+
DEFAULT_MODULE_POLICY,
|
|
35
|
+
DEFAULT_NUMERIC_POLICY,
|
|
36
|
+
DEFAULT_RECORD_POLICY,
|
|
37
|
+
DEFAULT_RENDERER,
|
|
38
|
+
DEFAULT_RULE_SET,
|
|
39
|
+
DEFAULT_SEMANTIC_POLICY,
|
|
40
|
+
DEFAULT_TARGET_CONTRACT,
|
|
41
|
+
PHASE9_RULE_SET,
|
|
42
|
+
)
|
|
43
|
+
from pycforge.converter.contracts.versions import (
|
|
44
|
+
C_IR_SCHEMA,
|
|
45
|
+
CONDITIONAL_FACT_SCHEMA,
|
|
46
|
+
CONTAINER_FACT_SCHEMA,
|
|
47
|
+
CONVERSION_PLAN_SCHEMA,
|
|
48
|
+
CONVERSION_SUMMARY_SCHEMA,
|
|
49
|
+
CONVERTER_CONTRACT_VERSION,
|
|
50
|
+
DECISION_TRACE_SCHEMA,
|
|
51
|
+
GENERATED_C_SCHEMA,
|
|
52
|
+
KEYWORD_CALL_FACT_SCHEMA,
|
|
53
|
+
KEYWORD_ONLY_CALL_FACT_SCHEMA,
|
|
54
|
+
MODULE_FACT_SCHEMA,
|
|
55
|
+
NUMERIC_FACT_SCHEMA,
|
|
56
|
+
PYTHON_IR_BUNDLE_SCHEMA,
|
|
57
|
+
RECORD_FACT_SCHEMA,
|
|
58
|
+
RESULT_SCHEMA_VERSION,
|
|
59
|
+
SOURCE_BUNDLE_SCHEMA,
|
|
60
|
+
)
|
|
61
|
+
from pycforge.converter.core.request import ObservationOptions
|
|
62
|
+
from pycforge.converter.core.serialization import result_to_json
|
|
63
|
+
from pycforge.converter.keyword_calls import KEYWORD_CALL_RULE_ID
|
|
64
|
+
from pycforge.converter.keyword_only_calls import KEYWORD_ONLY_CALL_RULE_ID
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
FUZZ_SEED = 0x14E2026
|
|
68
|
+
DEFAULT_FUZZ_CASE_COUNT = 64
|
|
69
|
+
FEATURE_MATRIX_SCHEMA = "pycforge.feature-matrix/0.14.3"
|
|
70
|
+
FEATURE_MATRIX_ENTRY_COUNT = 69
|
|
71
|
+
FEATURE_MATRIX_SHA256 = (
|
|
72
|
+
"ca78dff3ea203130781f5e0fde879c0ca9d7b7a0e550a05ab5d46ea3432cc01a"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
EXPECTED_CONTRACT_IDENTITIES: dict[str, str] = {
|
|
76
|
+
"converter_contract": "0.14.3",
|
|
77
|
+
"source_bundle": "source-bundle/0.2",
|
|
78
|
+
"python_ir_bundle": "python-ir/0.4",
|
|
79
|
+
"container_facts": "fact-table/0.11",
|
|
80
|
+
"module_facts": "fact-table/0.12",
|
|
81
|
+
"record_facts": "fact-table/0.13",
|
|
82
|
+
"numeric_facts": "fact-table/0.14",
|
|
83
|
+
"conditional_facts": "fact-table/0.14.1",
|
|
84
|
+
"keyword_call_facts": "fact-table/0.14.2",
|
|
85
|
+
"keyword_only_call_facts": "fact-table/0.14.3",
|
|
86
|
+
"conversion_plan": "conversion-plan/0.14.3",
|
|
87
|
+
"c_ir": "c-ir/0.14.3",
|
|
88
|
+
"generated_c": "generated-c/0.14.3",
|
|
89
|
+
"conversion_summary": "pycforge.conversion-summary/0.14.3",
|
|
90
|
+
"decision_trace": "pycforge.decision-trace/0.14.3",
|
|
91
|
+
"result": "0.5",
|
|
92
|
+
"rule_set": "phase14-required-keyword-only-calls-v0.14.3",
|
|
93
|
+
"renderer": "c-renderer-v0.14.3",
|
|
94
|
+
"keyword_call_rule": "phase14.keyword_call.exact_binding",
|
|
95
|
+
"keyword_only_call_rule": "phase14.keyword_only_call.exact_binding",
|
|
96
|
+
"target_contract": "c11-portable-fixed-v1",
|
|
97
|
+
"semantic_policy": "strict-source-v1",
|
|
98
|
+
"helper_policy": "phase10-support-templates-v0.10",
|
|
99
|
+
"container_policy": "phase11-fixed-local-containers-v0.11",
|
|
100
|
+
"module_policy": "phase13-explicit-record-modules-v0.13",
|
|
101
|
+
"record_policy": "phase13-immutable-automatic-records-v0.13",
|
|
102
|
+
"numeric_policy": "phase14-proved-floor-arithmetic-v0.14",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
SUPPORTED_SUBSET_FAMILIES = frozenset(
|
|
106
|
+
{
|
|
107
|
+
"literals",
|
|
108
|
+
"assignments-and-arithmetic",
|
|
109
|
+
"functions-and-positional-calls",
|
|
110
|
+
"if-else",
|
|
111
|
+
"while-break-continue",
|
|
112
|
+
"bounded-range-for",
|
|
113
|
+
"list",
|
|
114
|
+
"tuple",
|
|
115
|
+
"dict",
|
|
116
|
+
"explicit-module-bundle",
|
|
117
|
+
"static-record",
|
|
118
|
+
"bounded-floor-arithmetic",
|
|
119
|
+
"boolean-conditional-region",
|
|
120
|
+
"comparison-conditional-region",
|
|
121
|
+
"direct-keyword-call",
|
|
122
|
+
"required-keyword-only-call",
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass(frozen=True, slots=True)
|
|
128
|
+
class SupportedSubsetCase:
|
|
129
|
+
"""One deterministic public-converter witness."""
|
|
130
|
+
|
|
131
|
+
case_id: str
|
|
132
|
+
family: str
|
|
133
|
+
primary_text: str
|
|
134
|
+
primary_name: str = "main.py"
|
|
135
|
+
primary_module: str = "main"
|
|
136
|
+
companions: tuple[tuple[str, str, str], ...] = ()
|
|
137
|
+
|
|
138
|
+
def request(self) -> ConversionRequest:
|
|
139
|
+
return ConversionRequest(
|
|
140
|
+
SourceBundle(
|
|
141
|
+
SourceDocumentInput(
|
|
142
|
+
self.primary_name,
|
|
143
|
+
self.primary_text,
|
|
144
|
+
self.primary_module,
|
|
145
|
+
),
|
|
146
|
+
tuple(
|
|
147
|
+
SourceDocumentInput(logical_name, text, module_id)
|
|
148
|
+
for module_id, logical_name, text in self.companions
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def manifest(self) -> dict[str, object]:
|
|
154
|
+
return {
|
|
155
|
+
"case_id": self.case_id,
|
|
156
|
+
"family": self.family,
|
|
157
|
+
"primary": {
|
|
158
|
+
"logical_name": self.primary_name,
|
|
159
|
+
"module_id": self.primary_module,
|
|
160
|
+
"text": self.primary_text,
|
|
161
|
+
},
|
|
162
|
+
"companions": [
|
|
163
|
+
{
|
|
164
|
+
"module_id": module_id,
|
|
165
|
+
"logical_name": logical_name,
|
|
166
|
+
"text": text,
|
|
167
|
+
}
|
|
168
|
+
for module_id, logical_name, text in self.companions
|
|
169
|
+
],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@dataclass(frozen=True, slots=True)
|
|
174
|
+
class FeatureMatrixExecutionProfile:
|
|
175
|
+
"""One explicit public-request profile for a matrix witness."""
|
|
176
|
+
|
|
177
|
+
profile_id: str
|
|
178
|
+
request_options: tuple[tuple[str, object], ...] = ()
|
|
179
|
+
expected_diagnostic: str | None = None
|
|
180
|
+
reason_contains: str | None = None
|
|
181
|
+
|
|
182
|
+
def options(self) -> dict[str, object]:
|
|
183
|
+
return dict(self.request_options)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass(frozen=True, slots=True)
|
|
187
|
+
class FeatureMatrixWitness:
|
|
188
|
+
"""Executable evidence for one exact frozen feature-matrix row."""
|
|
189
|
+
|
|
190
|
+
witness_id: str
|
|
191
|
+
construct: str
|
|
192
|
+
context: str
|
|
193
|
+
state: str
|
|
194
|
+
source: str
|
|
195
|
+
exercise: str
|
|
196
|
+
diagnostic: str | None = None
|
|
197
|
+
actual_diagnostic: str | None = None
|
|
198
|
+
actual_reason_contains: str | None = None
|
|
199
|
+
required_ast_kinds: tuple[str, ...] = ()
|
|
200
|
+
logical_name: str = "main.py"
|
|
201
|
+
module_id: str = "main"
|
|
202
|
+
companions: tuple[tuple[str, str, str], ...] = ()
|
|
203
|
+
request_options: tuple[tuple[str, object], ...] = ()
|
|
204
|
+
precedence_profiles: tuple[FeatureMatrixExecutionProfile, ...] = ()
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def key(self) -> tuple[str, str]:
|
|
208
|
+
return self.construct, self.context
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def expected_diagnostic(self) -> str | None:
|
|
212
|
+
return self.actual_diagnostic or self.diagnostic
|
|
213
|
+
|
|
214
|
+
def request(
|
|
215
|
+
self,
|
|
216
|
+
options: Mapping[str, object] | None = None,
|
|
217
|
+
) -> ConversionRequest:
|
|
218
|
+
request_options = (
|
|
219
|
+
dict(self.request_options) if options is None else dict(options)
|
|
220
|
+
)
|
|
221
|
+
return ConversionRequest(
|
|
222
|
+
SourceBundle(
|
|
223
|
+
SourceDocumentInput(
|
|
224
|
+
self.logical_name,
|
|
225
|
+
self.source,
|
|
226
|
+
self.module_id,
|
|
227
|
+
),
|
|
228
|
+
tuple(
|
|
229
|
+
SourceDocumentInput(logical_name, text, module_id)
|
|
230
|
+
for module_id, logical_name, text in self.companions
|
|
231
|
+
),
|
|
232
|
+
),
|
|
233
|
+
**request_options,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def manifest(self) -> dict[str, object]:
|
|
237
|
+
return {
|
|
238
|
+
"witness_id": self.witness_id,
|
|
239
|
+
"construct": self.construct,
|
|
240
|
+
"context": self.context,
|
|
241
|
+
"state": self.state,
|
|
242
|
+
"diagnostic": self.diagnostic,
|
|
243
|
+
"actual_diagnostic": self.expected_diagnostic,
|
|
244
|
+
"actual_reason_contains": self.actual_reason_contains,
|
|
245
|
+
"exercise": self.exercise,
|
|
246
|
+
"required_ast_kinds": list(self.required_ast_kinds),
|
|
247
|
+
"request_options": [
|
|
248
|
+
[key, value] for key, value in self.request_options
|
|
249
|
+
],
|
|
250
|
+
"primary": {
|
|
251
|
+
"logical_name": self.logical_name,
|
|
252
|
+
"module_id": self.module_id,
|
|
253
|
+
"source_sha256": hashlib.sha256(
|
|
254
|
+
self.source.encode("utf-8")
|
|
255
|
+
).hexdigest(),
|
|
256
|
+
},
|
|
257
|
+
"companions": [
|
|
258
|
+
{
|
|
259
|
+
"module_id": module_id,
|
|
260
|
+
"logical_name": logical_name,
|
|
261
|
+
"source_sha256": hashlib.sha256(
|
|
262
|
+
text.encode("utf-8")
|
|
263
|
+
).hexdigest(),
|
|
264
|
+
}
|
|
265
|
+
for module_id, logical_name, text in self.companions
|
|
266
|
+
],
|
|
267
|
+
"precedence_profiles": [
|
|
268
|
+
{
|
|
269
|
+
"profile_id": profile.profile_id,
|
|
270
|
+
"request_options": [
|
|
271
|
+
[key, value] for key, value in profile.request_options
|
|
272
|
+
],
|
|
273
|
+
"expected_diagnostic": profile.expected_diagnostic,
|
|
274
|
+
"reason_contains": profile.reason_contains,
|
|
275
|
+
}
|
|
276
|
+
for profile in self.precedence_profiles
|
|
277
|
+
],
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
_SIMPLE_FUNCTION = (
|
|
282
|
+
"def identity(value: int, /) -> int:\n"
|
|
283
|
+
" result = value\n"
|
|
284
|
+
" return result\n"
|
|
285
|
+
)
|
|
286
|
+
_POSITIONAL_CALL = (
|
|
287
|
+
"def add(left: int, right: int) -> int:\n"
|
|
288
|
+
" return left + right\n\n"
|
|
289
|
+
"def run(value: int) -> int:\n"
|
|
290
|
+
" return add(value, 3)\n"
|
|
291
|
+
)
|
|
292
|
+
_RANGE_LOOP = (
|
|
293
|
+
"def total() -> int:\n"
|
|
294
|
+
" value = 0\n"
|
|
295
|
+
" for item in range(1, 6, 2):\n"
|
|
296
|
+
" value = value + item\n"
|
|
297
|
+
" return value\n"
|
|
298
|
+
)
|
|
299
|
+
_KEYWORD_CALL = (
|
|
300
|
+
"def choose(left: int, middle: int, flag: bool) -> int:\n"
|
|
301
|
+
" if flag:\n"
|
|
302
|
+
" return left\n"
|
|
303
|
+
" return middle\n\n"
|
|
304
|
+
"def run(value: int, flag: bool) -> int:\n"
|
|
305
|
+
" return choose(value, flag=flag, middle=2)\n"
|
|
306
|
+
)
|
|
307
|
+
_KEYWORD_ONLY_CALL = (
|
|
308
|
+
"def choose(left: int, /, middle: int, *, scale: int) -> int:\n"
|
|
309
|
+
" return (left + middle) * scale\n\n"
|
|
310
|
+
"def run() -> int:\n"
|
|
311
|
+
" return choose(1, middle=2, scale=3)\n"
|
|
312
|
+
)
|
|
313
|
+
_LITERALS = (
|
|
314
|
+
"def integer_value() -> int:\n"
|
|
315
|
+
" return 7\n\n"
|
|
316
|
+
"def real_value() -> float:\n"
|
|
317
|
+
" return 1.5\n\n"
|
|
318
|
+
"def flag_value() -> bool:\n"
|
|
319
|
+
" return True\n\n"
|
|
320
|
+
"def text_value() -> str:\n"
|
|
321
|
+
' return "PyCForge"\n'
|
|
322
|
+
)
|
|
323
|
+
_FLOATING_ARITHMETIC = (
|
|
324
|
+
"def arithmetic(left: float, right: float) -> float:\n"
|
|
325
|
+
" value = left + right\n"
|
|
326
|
+
" value = value - 1.0\n"
|
|
327
|
+
" value = value * 2.0\n"
|
|
328
|
+
" return value / 3.0\n"
|
|
329
|
+
)
|
|
330
|
+
_FLOOR_DIV = (
|
|
331
|
+
"def floor_value(value: int) -> int:\n"
|
|
332
|
+
" return value // -3\n"
|
|
333
|
+
)
|
|
334
|
+
_FLOOR_MOD = (
|
|
335
|
+
"def mod_value(value: int) -> int:\n"
|
|
336
|
+
" return value % 5\n"
|
|
337
|
+
)
|
|
338
|
+
_BOOL_DIRECT = (
|
|
339
|
+
"def both(left: bool, right: bool) -> bool:\n"
|
|
340
|
+
" return left and right\n"
|
|
341
|
+
)
|
|
342
|
+
_BOOL_GUARDED = (
|
|
343
|
+
"def flag(value: bool) -> bool:\n"
|
|
344
|
+
" return value\n\n"
|
|
345
|
+
"def decide(left: bool, right: bool) -> bool:\n"
|
|
346
|
+
" return left and flag(right)\n"
|
|
347
|
+
)
|
|
348
|
+
_COMPARE_DIRECT = (
|
|
349
|
+
"def compare(left: int, right: int) -> bool:\n"
|
|
350
|
+
" return left <= right\n"
|
|
351
|
+
)
|
|
352
|
+
_COMPARE_GUARDED = (
|
|
353
|
+
"def identity(value: int) -> int:\n"
|
|
354
|
+
" return value\n\n"
|
|
355
|
+
"def compare(left: int, middle: int, right: int) -> bool:\n"
|
|
356
|
+
" return left < middle < identity(right)\n"
|
|
357
|
+
)
|
|
358
|
+
_IF_STATEMENT = (
|
|
359
|
+
"def choose(value: int, flag: bool) -> int:\n"
|
|
360
|
+
" if flag:\n"
|
|
361
|
+
" value = value + 1\n"
|
|
362
|
+
" else:\n"
|
|
363
|
+
" value = value - 1\n"
|
|
364
|
+
" return value\n"
|
|
365
|
+
)
|
|
366
|
+
_WHILE_STATEMENT = (
|
|
367
|
+
"def scan(limit: int) -> int:\n"
|
|
368
|
+
" value = 0\n"
|
|
369
|
+
" while value < limit:\n"
|
|
370
|
+
" value = value + 1\n"
|
|
371
|
+
" if value == 2:\n"
|
|
372
|
+
" continue\n"
|
|
373
|
+
" if value == 5:\n"
|
|
374
|
+
" break\n"
|
|
375
|
+
" return value\n"
|
|
376
|
+
)
|
|
377
|
+
_LIST_VALUE = (
|
|
378
|
+
"def pick() -> int:\n"
|
|
379
|
+
" values = [1, 2, 3]\n"
|
|
380
|
+
" return values[-1]\n"
|
|
381
|
+
)
|
|
382
|
+
_TUPLE_VALUE = (
|
|
383
|
+
"def pick() -> str:\n"
|
|
384
|
+
' values = ("a", "b")\n'
|
|
385
|
+
" return values[0]\n"
|
|
386
|
+
)
|
|
387
|
+
_DICT_VALUE = (
|
|
388
|
+
"def pick() -> int:\n"
|
|
389
|
+
' values = {"a": 1, "b": 2}\n'
|
|
390
|
+
' return values["b"]\n'
|
|
391
|
+
)
|
|
392
|
+
_CONTAINER_ITERATION = (
|
|
393
|
+
"def last() -> int:\n"
|
|
394
|
+
" values = [2, 4, 6]\n"
|
|
395
|
+
" result = 0\n"
|
|
396
|
+
" for item in values:\n"
|
|
397
|
+
" result = item\n"
|
|
398
|
+
" return result\n"
|
|
399
|
+
)
|
|
400
|
+
_MODULE_PRIMARY = (
|
|
401
|
+
"from lib import increment as step\n\n"
|
|
402
|
+
"class Point:\n"
|
|
403
|
+
" x: int\n"
|
|
404
|
+
" def __init__(self, x: int) -> None:\n"
|
|
405
|
+
" self.x = x\n\n"
|
|
406
|
+
"def run(value: int) -> int:\n"
|
|
407
|
+
" point = Point(step(value))\n"
|
|
408
|
+
" return point.x\n"
|
|
409
|
+
)
|
|
410
|
+
_MODULE_COMPANIONS = (
|
|
411
|
+
(
|
|
412
|
+
"lib",
|
|
413
|
+
"lib.py",
|
|
414
|
+
"def increment(value: int) -> int:\n"
|
|
415
|
+
" return value + 1\n",
|
|
416
|
+
),
|
|
417
|
+
)
|
|
418
|
+
_RECORD = (
|
|
419
|
+
"class Sample:\n"
|
|
420
|
+
" count: int\n"
|
|
421
|
+
" ratio: float\n"
|
|
422
|
+
" enabled: bool\n"
|
|
423
|
+
" def __init__(self, count: int, ratio: float, enabled: bool) -> None:\n"
|
|
424
|
+
" self.count = count\n"
|
|
425
|
+
" self.ratio = ratio\n"
|
|
426
|
+
" self.enabled = enabled\n\n"
|
|
427
|
+
"def read() -> int:\n"
|
|
428
|
+
" sample = Sample(7, 1.5, True)\n"
|
|
429
|
+
" return sample.count\n"
|
|
430
|
+
)
|
|
431
|
+
_POINT_RECORD = (
|
|
432
|
+
"class Point:\n"
|
|
433
|
+
" x: int\n"
|
|
434
|
+
" def __init__(self, x: int) -> None:\n"
|
|
435
|
+
" self.x = x\n"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _feature_matrix_witness_rows() -> tuple[FeatureMatrixWitness, ...]:
|
|
440
|
+
"""Return the ordered executable closure of the frozen 69-row matrix."""
|
|
441
|
+
|
|
442
|
+
def witness(
|
|
443
|
+
ordinal: int,
|
|
444
|
+
construct: str,
|
|
445
|
+
context: str,
|
|
446
|
+
state: str,
|
|
447
|
+
source: str,
|
|
448
|
+
exercise: str,
|
|
449
|
+
*,
|
|
450
|
+
diagnostic: str | None = None,
|
|
451
|
+
actual_diagnostic: str | None = None,
|
|
452
|
+
actual_reason_contains: str | None = None,
|
|
453
|
+
ast_kinds: tuple[str, ...] = (),
|
|
454
|
+
logical_name: str = "main.py",
|
|
455
|
+
module_id: str = "main",
|
|
456
|
+
companions: tuple[tuple[str, str, str], ...] = (),
|
|
457
|
+
request_options: tuple[tuple[str, object], ...] = (),
|
|
458
|
+
precedence_profiles: tuple[FeatureMatrixExecutionProfile, ...] = (),
|
|
459
|
+
) -> FeatureMatrixWitness:
|
|
460
|
+
return FeatureMatrixWitness(
|
|
461
|
+
f"feature-matrix-{ordinal:02d}",
|
|
462
|
+
construct,
|
|
463
|
+
context,
|
|
464
|
+
state,
|
|
465
|
+
source,
|
|
466
|
+
exercise,
|
|
467
|
+
diagnostic,
|
|
468
|
+
actual_diagnostic,
|
|
469
|
+
actual_reason_contains,
|
|
470
|
+
ast_kinds,
|
|
471
|
+
logical_name,
|
|
472
|
+
module_id,
|
|
473
|
+
companions,
|
|
474
|
+
request_options,
|
|
475
|
+
precedence_profiles,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
return (
|
|
479
|
+
witness(
|
|
480
|
+
0,
|
|
481
|
+
"Module",
|
|
482
|
+
"explicit-1-to-64-document-bundle/import-preamble-then-records-then-top-level-functions",
|
|
483
|
+
"supported",
|
|
484
|
+
_MODULE_PRIMARY,
|
|
485
|
+
"A two-document explicit bundle places an absolute import first, a static record second, and an annotated top-level function last.",
|
|
486
|
+
ast_kinds=("Module", "ImportFrom", "ClassDef", "FunctionDef"),
|
|
487
|
+
logical_name="app.py",
|
|
488
|
+
module_id="app",
|
|
489
|
+
companions=_MODULE_COMPANIONS,
|
|
490
|
+
),
|
|
491
|
+
witness(
|
|
492
|
+
1,
|
|
493
|
+
"FunctionDef",
|
|
494
|
+
"top-level/exact-annotated-signature",
|
|
495
|
+
"supported",
|
|
496
|
+
_SIMPLE_FUNCTION,
|
|
497
|
+
"The module contains one top-level function whose parameter and return are exactly annotated.",
|
|
498
|
+
ast_kinds=("FunctionDef",),
|
|
499
|
+
),
|
|
500
|
+
witness(
|
|
501
|
+
2,
|
|
502
|
+
"FunctionDef",
|
|
503
|
+
"nested-or-closure",
|
|
504
|
+
"unsupported",
|
|
505
|
+
(
|
|
506
|
+
"def outer(value: int) -> int:\n"
|
|
507
|
+
" def nested(other: int) -> int:\n"
|
|
508
|
+
" return value + other\n"
|
|
509
|
+
" return nested(value)\n"
|
|
510
|
+
),
|
|
511
|
+
"The inner FunctionDef closes over the outer parameter.",
|
|
512
|
+
diagnostic="PYC2915",
|
|
513
|
+
ast_kinds=("FunctionDef",),
|
|
514
|
+
),
|
|
515
|
+
witness(
|
|
516
|
+
3,
|
|
517
|
+
"arguments",
|
|
518
|
+
"positional-or-positional-only/no-default",
|
|
519
|
+
"supported",
|
|
520
|
+
_SIMPLE_FUNCTION,
|
|
521
|
+
"The identity signature has one positional-only parameter and no default.",
|
|
522
|
+
ast_kinds=("arguments", "arg"),
|
|
523
|
+
),
|
|
524
|
+
witness(
|
|
525
|
+
4,
|
|
526
|
+
"arguments",
|
|
527
|
+
"required-keyword-only/exact-annotations/no-defaults-or-variadics",
|
|
528
|
+
"supported",
|
|
529
|
+
_KEYWORD_ONLY_CALL,
|
|
530
|
+
"The choose signature includes an exactly annotated required keyword-only scale parameter with no default or variadic.",
|
|
531
|
+
ast_kinds=("arguments", "arg"),
|
|
532
|
+
),
|
|
533
|
+
witness(
|
|
534
|
+
5,
|
|
535
|
+
"arguments",
|
|
536
|
+
"positional-default-keyword-only-default-variadic-or-keyword-only-outside-required-profile",
|
|
537
|
+
"unsupported",
|
|
538
|
+
(
|
|
539
|
+
"def choose(value: int, *, flag: bool = True) -> int:\n"
|
|
540
|
+
" return value\n\n"
|
|
541
|
+
"def run(value: int, flag: bool) -> int:\n"
|
|
542
|
+
" return choose(value, flag=flag)\n"
|
|
543
|
+
),
|
|
544
|
+
"The keyword-only flag formal has a default, placing the signature outside the required-only profile.",
|
|
545
|
+
diagnostic="PYC2911",
|
|
546
|
+
ast_kinds=("arguments",),
|
|
547
|
+
),
|
|
548
|
+
witness(
|
|
549
|
+
6,
|
|
550
|
+
"Call",
|
|
551
|
+
"direct-understood-source-function/exact-positional-signature",
|
|
552
|
+
"supported",
|
|
553
|
+
_POSITIONAL_CALL,
|
|
554
|
+
"run directly calls the understood add definition with two exact positional arguments.",
|
|
555
|
+
ast_kinds=("Call",),
|
|
556
|
+
),
|
|
557
|
+
witness(
|
|
558
|
+
7,
|
|
559
|
+
"Call",
|
|
560
|
+
"unshadowed-range-for-iterator",
|
|
561
|
+
"supported",
|
|
562
|
+
_RANGE_LOOP,
|
|
563
|
+
"The For iterator is a direct call to the unshadowed built-in range.",
|
|
564
|
+
ast_kinds=("Call", "For"),
|
|
565
|
+
),
|
|
566
|
+
witness(
|
|
567
|
+
8,
|
|
568
|
+
"Call",
|
|
569
|
+
"unknown-indirect-aliased-dynamic",
|
|
570
|
+
"unsupported",
|
|
571
|
+
(
|
|
572
|
+
"def run(target: int, value: int) -> int:\n"
|
|
573
|
+
" return target(value)\n"
|
|
574
|
+
),
|
|
575
|
+
"The call target is a parameter value rather than a direct understood source function.",
|
|
576
|
+
diagnostic="PYC2901",
|
|
577
|
+
ast_kinds=("Call",),
|
|
578
|
+
),
|
|
579
|
+
witness(
|
|
580
|
+
9,
|
|
581
|
+
"Call",
|
|
582
|
+
"direct-understood-source-function/leading-positional-then-explicit-keywords/exact-required-parameter-coverage",
|
|
583
|
+
"supported",
|
|
584
|
+
_KEYWORD_CALL,
|
|
585
|
+
"run binds choose with one leading positional argument followed by explicit keywords that cover every remaining required parameter.",
|
|
586
|
+
ast_kinds=("Call", "keyword"),
|
|
587
|
+
),
|
|
588
|
+
witness(
|
|
589
|
+
10,
|
|
590
|
+
"Call",
|
|
591
|
+
"direct-understood-source-function/required-keyword-only-formals/exact-positional-and-named-coverage",
|
|
592
|
+
"supported",
|
|
593
|
+
_KEYWORD_ONLY_CALL,
|
|
594
|
+
"run calls choose with its positional-only, positional-or-keyword, and required keyword-only formals covered exactly.",
|
|
595
|
+
ast_kinds=("Call", "keyword"),
|
|
596
|
+
),
|
|
597
|
+
witness(
|
|
598
|
+
11,
|
|
599
|
+
"Call",
|
|
600
|
+
"missing-required-keyword-only-or-positional-overflow-into-keyword-only-range",
|
|
601
|
+
"unsupported",
|
|
602
|
+
(
|
|
603
|
+
"def sink(value: int, *, flag: bool) -> int:\n"
|
|
604
|
+
" return value\n\n"
|
|
605
|
+
"def run(value: int) -> int:\n"
|
|
606
|
+
" return sink(value)\n"
|
|
607
|
+
),
|
|
608
|
+
"The direct sink call omits its required keyword-only flag.",
|
|
609
|
+
diagnostic="PYC2904",
|
|
610
|
+
ast_kinds=("Call",),
|
|
611
|
+
),
|
|
612
|
+
witness(
|
|
613
|
+
12,
|
|
614
|
+
"Call",
|
|
615
|
+
"starred-or-unpacked-keywords",
|
|
616
|
+
"unsupported",
|
|
617
|
+
(
|
|
618
|
+
"def sink(value: int, *, flag: bool) -> int:\n"
|
|
619
|
+
" return value\n\n"
|
|
620
|
+
"def run(value: int, flag: bool) -> int:\n"
|
|
621
|
+
" return sink(*value, flag=flag)\n"
|
|
622
|
+
),
|
|
623
|
+
"The sink call contains a starred positional argument.",
|
|
624
|
+
diagnostic="PYC2910",
|
|
625
|
+
ast_kinds=("Call", "Starred"),
|
|
626
|
+
),
|
|
627
|
+
witness(
|
|
628
|
+
13,
|
|
629
|
+
"Call",
|
|
630
|
+
"unknown-duplicate-colliding-or-positional-only-keyword-name",
|
|
631
|
+
"unsupported",
|
|
632
|
+
(
|
|
633
|
+
"def sink(value: int, /, *, flag: bool) -> int:\n"
|
|
634
|
+
" return value\n\n"
|
|
635
|
+
"def run(value: int, flag: bool) -> int:\n"
|
|
636
|
+
" return sink(value=value, flag=flag)\n"
|
|
637
|
+
),
|
|
638
|
+
"The call illegally names the positional-only value formal as a keyword.",
|
|
639
|
+
diagnostic="PYC2912",
|
|
640
|
+
ast_kinds=("Call", "keyword"),
|
|
641
|
+
),
|
|
642
|
+
witness(
|
|
643
|
+
14,
|
|
644
|
+
"Return",
|
|
645
|
+
"explicit-compatible-all-paths",
|
|
646
|
+
"supported",
|
|
647
|
+
_IF_STATEMENT,
|
|
648
|
+
"choose has an explicit compatible return after its exhaustive conditional assignment.",
|
|
649
|
+
ast_kinds=("Return",),
|
|
650
|
+
),
|
|
651
|
+
witness(
|
|
652
|
+
15,
|
|
653
|
+
"Return",
|
|
654
|
+
"mismatch",
|
|
655
|
+
"unsupported",
|
|
656
|
+
"def run() -> int:\n return True\n",
|
|
657
|
+
"The explicit Boolean return conflicts with the annotated integer representation.",
|
|
658
|
+
diagnostic="PYC2930",
|
|
659
|
+
ast_kinds=("Return",),
|
|
660
|
+
),
|
|
661
|
+
witness(
|
|
662
|
+
16,
|
|
663
|
+
"FunctionFallthrough",
|
|
664
|
+
"annotated-non-none",
|
|
665
|
+
"unsupported",
|
|
666
|
+
(
|
|
667
|
+
"def run(flag: bool) -> int:\n"
|
|
668
|
+
" if flag:\n"
|
|
669
|
+
" return 1\n"
|
|
670
|
+
),
|
|
671
|
+
"The annotated non-None function has a reachable implicit-None fallthrough path.",
|
|
672
|
+
diagnostic="PYC2931",
|
|
673
|
+
ast_kinds=("FunctionDef", "If"),
|
|
674
|
+
),
|
|
675
|
+
witness(
|
|
676
|
+
17,
|
|
677
|
+
"Assign",
|
|
678
|
+
"single-local-name/stable-representation",
|
|
679
|
+
"supported",
|
|
680
|
+
_SIMPLE_FUNCTION,
|
|
681
|
+
"result is bound once as a direct function-local integer-like name.",
|
|
682
|
+
ast_kinds=("Assign", "Name"),
|
|
683
|
+
),
|
|
684
|
+
witness(
|
|
685
|
+
18,
|
|
686
|
+
"Assign",
|
|
687
|
+
"representation-conflict",
|
|
688
|
+
"unsupported",
|
|
689
|
+
(
|
|
690
|
+
"def run() -> int:\n"
|
|
691
|
+
" value = 1\n"
|
|
692
|
+
" value = True\n"
|
|
693
|
+
" return 0\n"
|
|
694
|
+
),
|
|
695
|
+
"The same local is assigned integer-like and Boolean-like representations.",
|
|
696
|
+
diagnostic="PYC2943",
|
|
697
|
+
ast_kinds=("Assign",),
|
|
698
|
+
),
|
|
699
|
+
witness(
|
|
700
|
+
19,
|
|
701
|
+
"Name",
|
|
702
|
+
"resolved-parameter-local-or-function",
|
|
703
|
+
"supported",
|
|
704
|
+
_POSITIONAL_CALL,
|
|
705
|
+
"The witness resolves parameter names, the local function binding add, and their return use.",
|
|
706
|
+
ast_kinds=("Name",),
|
|
707
|
+
),
|
|
708
|
+
witness(
|
|
709
|
+
20,
|
|
710
|
+
"Name",
|
|
711
|
+
"local-use-before-binding",
|
|
712
|
+
"unsupported",
|
|
713
|
+
(
|
|
714
|
+
"def run(value: int) -> int:\n"
|
|
715
|
+
" result = result + value\n"
|
|
716
|
+
" return result\n"
|
|
717
|
+
),
|
|
718
|
+
"result is read on the right-hand side before its first local binding.",
|
|
719
|
+
diagnostic="PYC2940",
|
|
720
|
+
ast_kinds=("Name",),
|
|
721
|
+
),
|
|
722
|
+
witness(
|
|
723
|
+
21,
|
|
724
|
+
"Constant",
|
|
725
|
+
"bounded-int-finite-float-bool-utf8-string",
|
|
726
|
+
"supported",
|
|
727
|
+
_LITERALS,
|
|
728
|
+
"Four functions exercise bounded int, finite float, bool, and UTF-8 string constants.",
|
|
729
|
+
ast_kinds=("Constant",),
|
|
730
|
+
),
|
|
731
|
+
witness(
|
|
732
|
+
22,
|
|
733
|
+
"BinOp",
|
|
734
|
+
"selected-stable-numeric-plus-minus-multiply-and-floating-divide",
|
|
735
|
+
"supported",
|
|
736
|
+
_FLOATING_ARITHMETIC,
|
|
737
|
+
"The floating-like function exercises plus, minus, multiply, and true division with stable representations.",
|
|
738
|
+
ast_kinds=("BinOp", "Div", "Add", "Sub", "Mult"),
|
|
739
|
+
),
|
|
740
|
+
witness(
|
|
741
|
+
23,
|
|
742
|
+
"BinOp.FloorDiv",
|
|
743
|
+
"integer-like-left/direct-safe-signed-int64-literal-divisor",
|
|
744
|
+
"supported",
|
|
745
|
+
_FLOOR_DIV,
|
|
746
|
+
"The integer-like left operand uses a direct safe signed int64 literal divisor of -3.",
|
|
747
|
+
ast_kinds=("BinOp", "FloorDiv"),
|
|
748
|
+
),
|
|
749
|
+
witness(
|
|
750
|
+
24,
|
|
751
|
+
"BinOp.Mod",
|
|
752
|
+
"integer-like-left/direct-safe-signed-int64-literal-divisor",
|
|
753
|
+
"supported",
|
|
754
|
+
_FLOOR_MOD,
|
|
755
|
+
"The integer-like left operand uses a direct safe signed int64 literal divisor of 5.",
|
|
756
|
+
ast_kinds=("BinOp", "Mod"),
|
|
757
|
+
),
|
|
758
|
+
witness(
|
|
759
|
+
25,
|
|
760
|
+
"BinOp.FloorDivOrMod",
|
|
761
|
+
"noninteger-or-unsupported-expression-context",
|
|
762
|
+
"unsupported",
|
|
763
|
+
"def run(value: float) -> float:\n return value // 2\n",
|
|
764
|
+
"Floor division is applied to a floating-like left operand.",
|
|
765
|
+
diagnostic="PYC3701",
|
|
766
|
+
ast_kinds=("BinOp", "FloorDiv"),
|
|
767
|
+
),
|
|
768
|
+
witness(
|
|
769
|
+
26,
|
|
770
|
+
"BinOp.FloorDivOrMod",
|
|
771
|
+
"zero-negative-one-INT64_MIN-dynamic-calculated-or-out-of-range-divisor",
|
|
772
|
+
"unsupported",
|
|
773
|
+
(
|
|
774
|
+
"def run(value: int, divisor: int) -> int:\n"
|
|
775
|
+
" return value // divisor\n"
|
|
776
|
+
),
|
|
777
|
+
"The divisor is dynamic rather than a directly proved safe signed int64 literal.",
|
|
778
|
+
diagnostic="PYC3702",
|
|
779
|
+
ast_kinds=("BinOp", "FloorDiv"),
|
|
780
|
+
),
|
|
781
|
+
witness(
|
|
782
|
+
27,
|
|
783
|
+
"BoolOp",
|
|
784
|
+
"boolean-represented/direct-expression-form",
|
|
785
|
+
"supported",
|
|
786
|
+
_BOOL_DIRECT,
|
|
787
|
+
"A direct and-expression combines two already Boolean-represented parameters.",
|
|
788
|
+
ast_kinds=("BoolOp", "And"),
|
|
789
|
+
),
|
|
790
|
+
witness(
|
|
791
|
+
28,
|
|
792
|
+
"BoolOp",
|
|
793
|
+
"boolean-represented/already-supported-scalar-prerequisites/flat-guarded-placement",
|
|
794
|
+
"supported",
|
|
795
|
+
_BOOL_GUARDED,
|
|
796
|
+
"The later Boolean operand is an understood scalar function call placed in one flat guarded region.",
|
|
797
|
+
ast_kinds=("BoolOp", "Call"),
|
|
798
|
+
),
|
|
799
|
+
witness(
|
|
800
|
+
29,
|
|
801
|
+
"Compare",
|
|
802
|
+
"numeric-or-boolean/direct-sequence-preservable",
|
|
803
|
+
"supported",
|
|
804
|
+
_COMPARE_DIRECT,
|
|
805
|
+
"A direct numeric comparison has a trivially preservable operand sequence.",
|
|
806
|
+
ast_kinds=("Compare",),
|
|
807
|
+
),
|
|
808
|
+
witness(
|
|
809
|
+
30,
|
|
810
|
+
"Compare",
|
|
811
|
+
"chained-numeric-or-boolean/already-supported-later-scalar-prerequisites/flat-guarded-placement",
|
|
812
|
+
"supported",
|
|
813
|
+
_COMPARE_GUARDED,
|
|
814
|
+
"The chained numeric comparison places an understood later scalar call in a flat guarded region.",
|
|
815
|
+
ast_kinds=("Compare", "Call"),
|
|
816
|
+
),
|
|
817
|
+
witness(
|
|
818
|
+
31,
|
|
819
|
+
"If",
|
|
820
|
+
"supported-truthiness",
|
|
821
|
+
"supported",
|
|
822
|
+
_IF_STATEMENT,
|
|
823
|
+
"The If condition is an exactly Boolean-represented parameter.",
|
|
824
|
+
ast_kinds=("If",),
|
|
825
|
+
),
|
|
826
|
+
witness(
|
|
827
|
+
32,
|
|
828
|
+
"While",
|
|
829
|
+
"supported-truthiness/no-else",
|
|
830
|
+
"supported",
|
|
831
|
+
_WHILE_STATEMENT,
|
|
832
|
+
"The While condition is a supported numeric comparison and the loop has no else.",
|
|
833
|
+
ast_kinds=("While",),
|
|
834
|
+
),
|
|
835
|
+
witness(
|
|
836
|
+
33,
|
|
837
|
+
"For",
|
|
838
|
+
"unshadowed-builtin-range/bounded-policy",
|
|
839
|
+
"supported",
|
|
840
|
+
_RANGE_LOOP,
|
|
841
|
+
"The For loop iterates an unshadowed range with direct bounded start, stop, and step.",
|
|
842
|
+
ast_kinds=("For", "Call"),
|
|
843
|
+
),
|
|
844
|
+
witness(
|
|
845
|
+
34,
|
|
846
|
+
"Break",
|
|
847
|
+
"supported-loop",
|
|
848
|
+
"supported",
|
|
849
|
+
_WHILE_STATEMENT,
|
|
850
|
+
"Break occurs lexically inside the supported no-else While loop.",
|
|
851
|
+
ast_kinds=("Break", "While"),
|
|
852
|
+
),
|
|
853
|
+
witness(
|
|
854
|
+
35,
|
|
855
|
+
"Continue",
|
|
856
|
+
"supported-loop",
|
|
857
|
+
"supported",
|
|
858
|
+
_WHILE_STATEMENT,
|
|
859
|
+
"Continue occurs lexically inside the supported no-else While loop.",
|
|
860
|
+
ast_kinds=("Continue", "While"),
|
|
861
|
+
),
|
|
862
|
+
witness(
|
|
863
|
+
36,
|
|
864
|
+
"List",
|
|
865
|
+
"nonempty-1-to-64/homogeneous-scalar/direct-local-single-binding",
|
|
866
|
+
"supported",
|
|
867
|
+
_LIST_VALUE,
|
|
868
|
+
"A nonempty three-element homogeneous integer list has one direct local binding.",
|
|
869
|
+
ast_kinds=("List", "Assign"),
|
|
870
|
+
),
|
|
871
|
+
witness(
|
|
872
|
+
37,
|
|
873
|
+
"Tuple",
|
|
874
|
+
"nonempty-1-to-64/homogeneous-scalar/direct-local-single-binding",
|
|
875
|
+
"supported",
|
|
876
|
+
_TUPLE_VALUE,
|
|
877
|
+
"A nonempty two-element homogeneous string tuple has one direct local binding.",
|
|
878
|
+
ast_kinds=("Tuple", "Assign"),
|
|
879
|
+
),
|
|
880
|
+
witness(
|
|
881
|
+
38,
|
|
882
|
+
"Dict",
|
|
883
|
+
"nonempty-1-to-64/distinct-homogeneous-literal-int-or-str-keys/homogeneous-scalar-values",
|
|
884
|
+
"supported",
|
|
885
|
+
_DICT_VALUE,
|
|
886
|
+
"A nonempty dictionary has distinct homogeneous literal string keys and homogeneous integer values.",
|
|
887
|
+
ast_kinds=("Dict", "Assign"),
|
|
888
|
+
),
|
|
889
|
+
witness(
|
|
890
|
+
39,
|
|
891
|
+
"Subscript",
|
|
892
|
+
"fixed-list-or-tuple/signed-literal-in-bounds",
|
|
893
|
+
"supported",
|
|
894
|
+
_LIST_VALUE,
|
|
895
|
+
"The fixed list is indexed by the signed literal -1, proved in bounds.",
|
|
896
|
+
ast_kinds=("Subscript", "List"),
|
|
897
|
+
),
|
|
898
|
+
witness(
|
|
899
|
+
40,
|
|
900
|
+
"Subscript",
|
|
901
|
+
"fixed-dict/present-literal-key",
|
|
902
|
+
"supported",
|
|
903
|
+
_DICT_VALUE,
|
|
904
|
+
"The fixed dictionary is indexed by its present literal key 'b'.",
|
|
905
|
+
ast_kinds=("Subscript", "Dict"),
|
|
906
|
+
),
|
|
907
|
+
witness(
|
|
908
|
+
41,
|
|
909
|
+
"For",
|
|
910
|
+
"direct-fixed-container-name/single-target/no-else",
|
|
911
|
+
"supported",
|
|
912
|
+
_CONTAINER_ITERATION,
|
|
913
|
+
"The loop directly names a fixed list, has one target, and has no else.",
|
|
914
|
+
ast_kinds=("For", "List"),
|
|
915
|
+
),
|
|
916
|
+
witness(
|
|
917
|
+
42,
|
|
918
|
+
"ContainerMutation",
|
|
919
|
+
"any",
|
|
920
|
+
"unsupported",
|
|
921
|
+
(
|
|
922
|
+
"def run() -> int:\n"
|
|
923
|
+
" values = [1, 2]\n"
|
|
924
|
+
" values[0] = 3\n"
|
|
925
|
+
" return values[0]\n"
|
|
926
|
+
),
|
|
927
|
+
"A subscript assignment mutates a fixed list after binding.",
|
|
928
|
+
diagnostic="PYC3406",
|
|
929
|
+
ast_kinds=("Assign", "Subscript", "List"),
|
|
930
|
+
),
|
|
931
|
+
witness(
|
|
932
|
+
43,
|
|
933
|
+
"ContainerAliasOrEscape",
|
|
934
|
+
"any",
|
|
935
|
+
"unsupported",
|
|
936
|
+
(
|
|
937
|
+
"def run() -> int:\n"
|
|
938
|
+
" values = [1, 2]\n"
|
|
939
|
+
" alias = values\n"
|
|
940
|
+
" return alias[0]\n"
|
|
941
|
+
),
|
|
942
|
+
"The fixed list escapes its single-binding proof through a second local alias.",
|
|
943
|
+
diagnostic="PYC3403",
|
|
944
|
+
ast_kinds=("Assign", "List", "Name"),
|
|
945
|
+
),
|
|
946
|
+
witness(
|
|
947
|
+
44,
|
|
948
|
+
"Comprehension",
|
|
949
|
+
"any",
|
|
950
|
+
"unsupported",
|
|
951
|
+
(
|
|
952
|
+
"def run() -> int:\n"
|
|
953
|
+
" values = [item for item in range(3)]\n"
|
|
954
|
+
" return 1\n"
|
|
955
|
+
),
|
|
956
|
+
"A ListComp attempts to construct a container dynamically.",
|
|
957
|
+
diagnostic="PYC3406",
|
|
958
|
+
ast_kinds=("ListComp", "comprehension"),
|
|
959
|
+
),
|
|
960
|
+
witness(
|
|
961
|
+
45,
|
|
962
|
+
"ImportFrom",
|
|
963
|
+
"absolute-exact-sourcebundle-module/direct-eligible-functions/optional-aliases/preamble",
|
|
964
|
+
"supported",
|
|
965
|
+
(
|
|
966
|
+
"from lib import increment as step\n\n"
|
|
967
|
+
"def run(value: int) -> int:\n"
|
|
968
|
+
" return step(value)\n"
|
|
969
|
+
),
|
|
970
|
+
"An absolute preamble import resolves an optionally aliased eligible function from the explicit companion.",
|
|
971
|
+
ast_kinds=("ImportFrom", "alias"),
|
|
972
|
+
logical_name="app.py",
|
|
973
|
+
module_id="app",
|
|
974
|
+
companions=_MODULE_COMPANIONS,
|
|
975
|
+
),
|
|
976
|
+
witness(
|
|
977
|
+
46,
|
|
978
|
+
"Import",
|
|
979
|
+
"plain-module-import",
|
|
980
|
+
"unsupported",
|
|
981
|
+
"import lib\n\ndef run() -> int:\n return 1\n",
|
|
982
|
+
"A plain Import statement names an otherwise explicit companion module.",
|
|
983
|
+
diagnostic="PYC3504",
|
|
984
|
+
ast_kinds=("Import",),
|
|
985
|
+
logical_name="app.py",
|
|
986
|
+
module_id="app",
|
|
987
|
+
companions=_MODULE_COMPANIONS,
|
|
988
|
+
),
|
|
989
|
+
witness(
|
|
990
|
+
47,
|
|
991
|
+
"ImportFrom",
|
|
992
|
+
"relative-star-late-local-conditional-or-dynamic",
|
|
993
|
+
"unsupported",
|
|
994
|
+
(
|
|
995
|
+
"if True:\n"
|
|
996
|
+
" from lib import increment\n\n"
|
|
997
|
+
"def run() -> int:\n"
|
|
998
|
+
" return 1\n"
|
|
999
|
+
),
|
|
1000
|
+
"The ImportFrom is conditional instead of an unconditional module preamble item.",
|
|
1001
|
+
diagnostic="PYC3504",
|
|
1002
|
+
ast_kinds=("ImportFrom", "If"),
|
|
1003
|
+
logical_name="app.py",
|
|
1004
|
+
module_id="app",
|
|
1005
|
+
companions=_MODULE_COMPANIONS,
|
|
1006
|
+
),
|
|
1007
|
+
witness(
|
|
1008
|
+
48,
|
|
1009
|
+
"ImportFrom",
|
|
1010
|
+
"missing-explicit-module",
|
|
1011
|
+
"unsupported",
|
|
1012
|
+
(
|
|
1013
|
+
"from absent import value\n\n"
|
|
1014
|
+
"def run() -> int:\n"
|
|
1015
|
+
" return value()\n"
|
|
1016
|
+
),
|
|
1017
|
+
"The absolute import target absent is not present in the explicit source bundle.",
|
|
1018
|
+
diagnostic="PYC3503",
|
|
1019
|
+
ast_kinds=("ImportFrom",),
|
|
1020
|
+
logical_name="app.py",
|
|
1021
|
+
module_id="app",
|
|
1022
|
+
),
|
|
1023
|
+
witness(
|
|
1024
|
+
49,
|
|
1025
|
+
"ImportFrom",
|
|
1026
|
+
"missing-or-ineligible-direct-member",
|
|
1027
|
+
"unsupported",
|
|
1028
|
+
(
|
|
1029
|
+
"from lib import absent\n\n"
|
|
1030
|
+
"def run() -> int:\n"
|
|
1031
|
+
" return 1\n"
|
|
1032
|
+
),
|
|
1033
|
+
"The explicit companion exists but does not define the directly imported member absent.",
|
|
1034
|
+
diagnostic="PYC3505",
|
|
1035
|
+
ast_kinds=("ImportFrom",),
|
|
1036
|
+
logical_name="app.py",
|
|
1037
|
+
module_id="app",
|
|
1038
|
+
companions=_MODULE_COMPANIONS,
|
|
1039
|
+
),
|
|
1040
|
+
witness(
|
|
1041
|
+
50,
|
|
1042
|
+
"ModuleDependency",
|
|
1043
|
+
"self-or-strongly-connected-cycle",
|
|
1044
|
+
"unsupported",
|
|
1045
|
+
(
|
|
1046
|
+
"from lib import increment\n\n"
|
|
1047
|
+
"def run(value: int) -> int:\n"
|
|
1048
|
+
" return increment(value)\n"
|
|
1049
|
+
),
|
|
1050
|
+
"The app-to-lib and lib-to-app imports form a two-module strongly connected dependency cycle.",
|
|
1051
|
+
diagnostic="PYC3507",
|
|
1052
|
+
ast_kinds=("ImportFrom",),
|
|
1053
|
+
logical_name="app.py",
|
|
1054
|
+
module_id="app",
|
|
1055
|
+
companions=(
|
|
1056
|
+
(
|
|
1057
|
+
"lib",
|
|
1058
|
+
"lib.py",
|
|
1059
|
+
(
|
|
1060
|
+
"from app import run\n\n"
|
|
1061
|
+
"def increment(value: int) -> int:\n"
|
|
1062
|
+
" return run(value)\n"
|
|
1063
|
+
),
|
|
1064
|
+
),
|
|
1065
|
+
),
|
|
1066
|
+
),
|
|
1067
|
+
witness(
|
|
1068
|
+
51,
|
|
1069
|
+
"ModuleInitialization",
|
|
1070
|
+
"compile-time-namespace-only",
|
|
1071
|
+
"supported",
|
|
1072
|
+
_MODULE_PRIMARY,
|
|
1073
|
+
"The module body contains only namespace-defining imports, a record declaration, and function declarations.",
|
|
1074
|
+
ast_kinds=("Module", "ImportFrom", "ClassDef", "FunctionDef"),
|
|
1075
|
+
logical_name="app.py",
|
|
1076
|
+
module_id="app",
|
|
1077
|
+
companions=_MODULE_COMPANIONS,
|
|
1078
|
+
),
|
|
1079
|
+
witness(
|
|
1080
|
+
52,
|
|
1081
|
+
"ModuleInitialization",
|
|
1082
|
+
"executable-top-level-state",
|
|
1083
|
+
"unsupported",
|
|
1084
|
+
(
|
|
1085
|
+
"value = 1\n\n"
|
|
1086
|
+
"def run() -> int:\n"
|
|
1087
|
+
" return value\n"
|
|
1088
|
+
),
|
|
1089
|
+
"The top-level assignment creates executable module initialization state.",
|
|
1090
|
+
diagnostic="PYC3509",
|
|
1091
|
+
ast_kinds=("Assign", "Module"),
|
|
1092
|
+
),
|
|
1093
|
+
witness(
|
|
1094
|
+
53,
|
|
1095
|
+
"ClassDef",
|
|
1096
|
+
"top-level-undecorated-base-free-static-record-before-functions",
|
|
1097
|
+
"supported",
|
|
1098
|
+
_RECORD,
|
|
1099
|
+
"Sample is a top-level undecorated base-free static record placed before the read function.",
|
|
1100
|
+
ast_kinds=("ClassDef",),
|
|
1101
|
+
),
|
|
1102
|
+
witness(
|
|
1103
|
+
54,
|
|
1104
|
+
"ClassDef",
|
|
1105
|
+
"nested-decorated-inherited-keyworded-or-general-class",
|
|
1106
|
+
"unsupported",
|
|
1107
|
+
(
|
|
1108
|
+
"class Point(Base):\n"
|
|
1109
|
+
" x: int\n"
|
|
1110
|
+
" def __init__(self, x: int) -> None:\n"
|
|
1111
|
+
" self.x = x\n\n"
|
|
1112
|
+
"def run() -> int:\n"
|
|
1113
|
+
" return 1\n"
|
|
1114
|
+
),
|
|
1115
|
+
"Point has an inherited base and therefore is outside the base-free static-record profile.",
|
|
1116
|
+
diagnostic="PYC3601",
|
|
1117
|
+
ast_kinds=("ClassDef",),
|
|
1118
|
+
),
|
|
1119
|
+
witness(
|
|
1120
|
+
55,
|
|
1121
|
+
"AnnAssign",
|
|
1122
|
+
"record-value-less-distinct-int-float-bool-field/1-to-64",
|
|
1123
|
+
"supported",
|
|
1124
|
+
_RECORD,
|
|
1125
|
+
"Sample declares three distinct value-less int, float, and bool record fields within capacity.",
|
|
1126
|
+
ast_kinds=("AnnAssign",),
|
|
1127
|
+
),
|
|
1128
|
+
witness(
|
|
1129
|
+
56,
|
|
1130
|
+
"AnnAssign",
|
|
1131
|
+
"record-default-unsupported-type-duplicate-or-capacity",
|
|
1132
|
+
"unsupported",
|
|
1133
|
+
(
|
|
1134
|
+
"class Point:\n"
|
|
1135
|
+
" x: str\n"
|
|
1136
|
+
" def __init__(self, x: str) -> None:\n"
|
|
1137
|
+
" self.x = x\n\n"
|
|
1138
|
+
"def run() -> int:\n"
|
|
1139
|
+
" return 1\n"
|
|
1140
|
+
),
|
|
1141
|
+
"The value-less record field uses unsupported str storage.",
|
|
1142
|
+
diagnostic="PYC3602",
|
|
1143
|
+
ast_kinds=("AnnAssign",),
|
|
1144
|
+
),
|
|
1145
|
+
witness(
|
|
1146
|
+
57,
|
|
1147
|
+
"FunctionDef",
|
|
1148
|
+
"record-exact-structural-__init__",
|
|
1149
|
+
"supported",
|
|
1150
|
+
_RECORD,
|
|
1151
|
+
"Sample.__init__ exactly assigns every declared field from its corresponding annotated parameter.",
|
|
1152
|
+
ast_kinds=("FunctionDef", "Attribute"),
|
|
1153
|
+
),
|
|
1154
|
+
witness(
|
|
1155
|
+
58,
|
|
1156
|
+
"FunctionDef",
|
|
1157
|
+
"record-nonstructural-__init__",
|
|
1158
|
+
"unsupported",
|
|
1159
|
+
(
|
|
1160
|
+
"class Point:\n"
|
|
1161
|
+
" x: int\n"
|
|
1162
|
+
" def __init__(self, x: int) -> int:\n"
|
|
1163
|
+
" self.x = x\n\n"
|
|
1164
|
+
"def run() -> int:\n"
|
|
1165
|
+
" return 1\n"
|
|
1166
|
+
),
|
|
1167
|
+
"Point.__init__ declares int rather than the exact structural None return.",
|
|
1168
|
+
diagnostic="PYC3603",
|
|
1169
|
+
ast_kinds=("FunctionDef",),
|
|
1170
|
+
),
|
|
1171
|
+
witness(
|
|
1172
|
+
59,
|
|
1173
|
+
"FunctionDef",
|
|
1174
|
+
"ordinary-record-method",
|
|
1175
|
+
"unsupported",
|
|
1176
|
+
(
|
|
1177
|
+
_POINT_RECORD
|
|
1178
|
+
+ " def get(self) -> int:\n"
|
|
1179
|
+
" return self.x\n\n"
|
|
1180
|
+
"def run() -> int:\n"
|
|
1181
|
+
" return 1\n"
|
|
1182
|
+
),
|
|
1183
|
+
"The record class declares an ordinary get method in addition to structural __init__.",
|
|
1184
|
+
diagnostic="PYC3604",
|
|
1185
|
+
ast_kinds=("FunctionDef",),
|
|
1186
|
+
),
|
|
1187
|
+
witness(
|
|
1188
|
+
60,
|
|
1189
|
+
"Call",
|
|
1190
|
+
"same-module-record-direct-positional-fresh-local-construction",
|
|
1191
|
+
"supported",
|
|
1192
|
+
_RECORD,
|
|
1193
|
+
"read directly constructs same-module Sample positionally into a fresh local.",
|
|
1194
|
+
ast_kinds=("Call", "ClassDef"),
|
|
1195
|
+
),
|
|
1196
|
+
witness(
|
|
1197
|
+
61,
|
|
1198
|
+
"Call",
|
|
1199
|
+
"record-indirect-keyword-nested-conditional-returned-or-mismatched-construction",
|
|
1200
|
+
"unsupported",
|
|
1201
|
+
(
|
|
1202
|
+
_POINT_RECORD
|
|
1203
|
+
+ "\ndef run() -> int:\n"
|
|
1204
|
+
" point = Point(x=1)\n"
|
|
1205
|
+
" return point.x\n"
|
|
1206
|
+
),
|
|
1207
|
+
"The record constructor is invoked with a keyword rather than the exact direct positional profile.",
|
|
1208
|
+
diagnostic="PYC3605",
|
|
1209
|
+
ast_kinds=("Call", "keyword"),
|
|
1210
|
+
),
|
|
1211
|
+
witness(
|
|
1212
|
+
62,
|
|
1213
|
+
"RecordValue",
|
|
1214
|
+
"alias-rebind-copy-escape-parameter-return-call-container-identity-or-truth",
|
|
1215
|
+
"unsupported",
|
|
1216
|
+
(
|
|
1217
|
+
_POINT_RECORD
|
|
1218
|
+
+ "\ndef run() -> int:\n"
|
|
1219
|
+
" point = Point(1)\n"
|
|
1220
|
+
" alias = point\n"
|
|
1221
|
+
" return point.x\n"
|
|
1222
|
+
),
|
|
1223
|
+
"The fresh record value is copied into alias, violating immutable no-alias ownership.",
|
|
1224
|
+
diagnostic="PYC3606",
|
|
1225
|
+
ast_kinds=("Assign", "Call"),
|
|
1226
|
+
),
|
|
1227
|
+
witness(
|
|
1228
|
+
63,
|
|
1229
|
+
"Attribute",
|
|
1230
|
+
"direct-statically-bound-record-field-read",
|
|
1231
|
+
"supported",
|
|
1232
|
+
_RECORD,
|
|
1233
|
+
"read returns sample.count through a direct statically bound field access.",
|
|
1234
|
+
ast_kinds=("Attribute",),
|
|
1235
|
+
),
|
|
1236
|
+
witness(
|
|
1237
|
+
64,
|
|
1238
|
+
"Attribute",
|
|
1239
|
+
"record-mutation-unknown-dynamic-or-chained-attribute",
|
|
1240
|
+
"unsupported",
|
|
1241
|
+
(
|
|
1242
|
+
_POINT_RECORD
|
|
1243
|
+
+ "\ndef run() -> int:\n"
|
|
1244
|
+
" point = Point(1)\n"
|
|
1245
|
+
" point.x = 2\n"
|
|
1246
|
+
" return point.x\n"
|
|
1247
|
+
),
|
|
1248
|
+
"An Attribute target mutates the immutable record field point.x.",
|
|
1249
|
+
diagnostic="PYC3607",
|
|
1250
|
+
ast_kinds=("Attribute", "Assign"),
|
|
1251
|
+
),
|
|
1252
|
+
witness(
|
|
1253
|
+
65,
|
|
1254
|
+
"ImportFrom",
|
|
1255
|
+
"record-class-import",
|
|
1256
|
+
"unsupported",
|
|
1257
|
+
(
|
|
1258
|
+
"from records import Point\n\n"
|
|
1259
|
+
"def run() -> int:\n"
|
|
1260
|
+
" point = Point(1)\n"
|
|
1261
|
+
" return point.x\n"
|
|
1262
|
+
),
|
|
1263
|
+
"The ImportFrom attempts to import a record class across module boundaries.",
|
|
1264
|
+
diagnostic="PYC3610",
|
|
1265
|
+
ast_kinds=("ImportFrom",),
|
|
1266
|
+
logical_name="app.py",
|
|
1267
|
+
module_id="app",
|
|
1268
|
+
companions=(
|
|
1269
|
+
(
|
|
1270
|
+
"records",
|
|
1271
|
+
"records.py",
|
|
1272
|
+
(
|
|
1273
|
+
_POINT_RECORD
|
|
1274
|
+
+ "\ndef local() -> int:\n"
|
|
1275
|
+
" point = Point(2)\n"
|
|
1276
|
+
" return point.x\n"
|
|
1277
|
+
),
|
|
1278
|
+
),
|
|
1279
|
+
),
|
|
1280
|
+
),
|
|
1281
|
+
witness(
|
|
1282
|
+
66,
|
|
1283
|
+
"Lambda",
|
|
1284
|
+
"call-target-or-value",
|
|
1285
|
+
"unsupported",
|
|
1286
|
+
(
|
|
1287
|
+
"def run(value: int) -> int:\n"
|
|
1288
|
+
" target = lambda item: item\n"
|
|
1289
|
+
" return target(value)\n"
|
|
1290
|
+
),
|
|
1291
|
+
"A Lambda is bound as an indirect call target value.",
|
|
1292
|
+
diagnostic="PYC2901",
|
|
1293
|
+
ast_kinds=("Lambda", "Call"),
|
|
1294
|
+
),
|
|
1295
|
+
witness(
|
|
1296
|
+
67,
|
|
1297
|
+
"AsyncFunctionDef",
|
|
1298
|
+
"any",
|
|
1299
|
+
"unsupported",
|
|
1300
|
+
"async def run() -> int:\n return 1\n",
|
|
1301
|
+
"The Phase 9 profile owns the original AsyncFunctionDef boundary and emits PYC2902; the current module profile rejects it earlier as ineligible module initialization.",
|
|
1302
|
+
diagnostic="PYC2902",
|
|
1303
|
+
ast_kinds=("AsyncFunctionDef",),
|
|
1304
|
+
request_options=(
|
|
1305
|
+
("rule_set_version", PHASE9_RULE_SET),
|
|
1306
|
+
("renderer_version", "c-renderer-v0.9"),
|
|
1307
|
+
),
|
|
1308
|
+
precedence_profiles=(
|
|
1309
|
+
FeatureMatrixExecutionProfile(
|
|
1310
|
+
"current-default-precedence",
|
|
1311
|
+
(),
|
|
1312
|
+
"PYC3509",
|
|
1313
|
+
"Only eligible synchronous top-level functions",
|
|
1314
|
+
),
|
|
1315
|
+
),
|
|
1316
|
+
),
|
|
1317
|
+
witness(
|
|
1318
|
+
68,
|
|
1319
|
+
"Match",
|
|
1320
|
+
"any",
|
|
1321
|
+
"deferred",
|
|
1322
|
+
(
|
|
1323
|
+
"def run(value: int) -> int:\n"
|
|
1324
|
+
" result = 0\n"
|
|
1325
|
+
" match value:\n"
|
|
1326
|
+
" case 1:\n"
|
|
1327
|
+
" result = 1\n"
|
|
1328
|
+
" return result\n"
|
|
1329
|
+
),
|
|
1330
|
+
"A Match statement is present while an explicit trailing return prevents fallthrough from masking the deferred boundary.",
|
|
1331
|
+
actual_diagnostic="PYC2812",
|
|
1332
|
+
actual_reason_contains="Unsupported statement in the selected subset: Match",
|
|
1333
|
+
ast_kinds=("Match",),
|
|
1334
|
+
),
|
|
1335
|
+
)
|
|
1336
|
+
|
|
1337
|
+
|
|
1338
|
+
FEATURE_MATRIX_WITNESS_ORDER = _feature_matrix_witness_rows()
|
|
1339
|
+
FEATURE_MATRIX_WITNESSES: Mapping[
|
|
1340
|
+
tuple[str, str], FeatureMatrixWitness
|
|
1341
|
+
] = MappingProxyType({item.key: item for item in FEATURE_MATRIX_WITNESS_ORDER})
|
|
1342
|
+
|
|
1343
|
+
UNLISTED_DEFAULT_WITNESS = FeatureMatrixWitness(
|
|
1344
|
+
"feature-matrix-unlisted-default",
|
|
1345
|
+
"Try",
|
|
1346
|
+
"unlisted-node/default-unsupported",
|
|
1347
|
+
"unsupported",
|
|
1348
|
+
(
|
|
1349
|
+
"def run() -> int:\n"
|
|
1350
|
+
" result = 0\n"
|
|
1351
|
+
" try:\n"
|
|
1352
|
+
" result = 1\n"
|
|
1353
|
+
" except Exception:\n"
|
|
1354
|
+
" result = 2\n"
|
|
1355
|
+
" return result\n"
|
|
1356
|
+
),
|
|
1357
|
+
"Try is intentionally absent from the 69-entry matrix; an explicit trailing return prevents fallthrough from masking the default unsupported boundary.",
|
|
1358
|
+
actual_diagnostic="PYC2812",
|
|
1359
|
+
actual_reason_contains="Unsupported statement in the selected subset: Try",
|
|
1360
|
+
required_ast_kinds=("Try",),
|
|
1361
|
+
)
|
|
1362
|
+
|
|
1363
|
+
|
|
1364
|
+
def current_contract_identities() -> dict[str, str]:
|
|
1365
|
+
"""Return the converter identities that Checkpoint E must not churn."""
|
|
1366
|
+
|
|
1367
|
+
return {
|
|
1368
|
+
"converter_contract": CONVERTER_CONTRACT_VERSION,
|
|
1369
|
+
"source_bundle": SOURCE_BUNDLE_SCHEMA,
|
|
1370
|
+
"python_ir_bundle": PYTHON_IR_BUNDLE_SCHEMA,
|
|
1371
|
+
"container_facts": CONTAINER_FACT_SCHEMA,
|
|
1372
|
+
"module_facts": MODULE_FACT_SCHEMA,
|
|
1373
|
+
"record_facts": RECORD_FACT_SCHEMA,
|
|
1374
|
+
"numeric_facts": NUMERIC_FACT_SCHEMA,
|
|
1375
|
+
"conditional_facts": CONDITIONAL_FACT_SCHEMA,
|
|
1376
|
+
"keyword_call_facts": KEYWORD_CALL_FACT_SCHEMA,
|
|
1377
|
+
"keyword_only_call_facts": KEYWORD_ONLY_CALL_FACT_SCHEMA,
|
|
1378
|
+
"conversion_plan": CONVERSION_PLAN_SCHEMA,
|
|
1379
|
+
"c_ir": C_IR_SCHEMA,
|
|
1380
|
+
"generated_c": GENERATED_C_SCHEMA,
|
|
1381
|
+
"conversion_summary": CONVERSION_SUMMARY_SCHEMA,
|
|
1382
|
+
"decision_trace": DECISION_TRACE_SCHEMA,
|
|
1383
|
+
"result": RESULT_SCHEMA_VERSION,
|
|
1384
|
+
"rule_set": DEFAULT_RULE_SET,
|
|
1385
|
+
"renderer": DEFAULT_RENDERER,
|
|
1386
|
+
"keyword_call_rule": KEYWORD_CALL_RULE_ID,
|
|
1387
|
+
"keyword_only_call_rule": KEYWORD_ONLY_CALL_RULE_ID,
|
|
1388
|
+
"target_contract": DEFAULT_TARGET_CONTRACT,
|
|
1389
|
+
"semantic_policy": DEFAULT_SEMANTIC_POLICY,
|
|
1390
|
+
"helper_policy": DEFAULT_HELPER_POLICY,
|
|
1391
|
+
"container_policy": DEFAULT_CONTAINER_POLICY,
|
|
1392
|
+
"module_policy": DEFAULT_MODULE_POLICY,
|
|
1393
|
+
"record_policy": DEFAULT_RECORD_POLICY,
|
|
1394
|
+
"numeric_policy": DEFAULT_NUMERIC_POLICY,
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
|
|
1398
|
+
def fixed_supported_subset_cases() -> tuple[SupportedSubsetCase, ...]:
|
|
1399
|
+
"""Return one positive witness for every promoted construct family."""
|
|
1400
|
+
|
|
1401
|
+
return (
|
|
1402
|
+
SupportedSubsetCase(
|
|
1403
|
+
"fixed-literals",
|
|
1404
|
+
"literals",
|
|
1405
|
+
(
|
|
1406
|
+
"def integer_value() -> int:\n"
|
|
1407
|
+
" return 7\n\n"
|
|
1408
|
+
"def real_value() -> float:\n"
|
|
1409
|
+
" return 1.5\n\n"
|
|
1410
|
+
"def flag_value() -> bool:\n"
|
|
1411
|
+
" return True\n\n"
|
|
1412
|
+
"def text_value() -> str:\n"
|
|
1413
|
+
' return "PyCForge"\n'
|
|
1414
|
+
),
|
|
1415
|
+
),
|
|
1416
|
+
SupportedSubsetCase(
|
|
1417
|
+
"fixed-arithmetic",
|
|
1418
|
+
"assignments-and-arithmetic",
|
|
1419
|
+
(
|
|
1420
|
+
"def calculate(a: int, b: int) -> int:\n"
|
|
1421
|
+
" value = a + b * 2\n"
|
|
1422
|
+
" value = value - 1\n"
|
|
1423
|
+
" return value\n\n"
|
|
1424
|
+
"def ratio(value: float) -> float:\n"
|
|
1425
|
+
" return value / 2.0\n"
|
|
1426
|
+
),
|
|
1427
|
+
),
|
|
1428
|
+
SupportedSubsetCase(
|
|
1429
|
+
"fixed-positional-call",
|
|
1430
|
+
"functions-and-positional-calls",
|
|
1431
|
+
(
|
|
1432
|
+
"def add(a: int, b: int) -> int:\n"
|
|
1433
|
+
" return a + b\n\n"
|
|
1434
|
+
"def run(value: int) -> int:\n"
|
|
1435
|
+
" return add(value, 3)\n"
|
|
1436
|
+
),
|
|
1437
|
+
),
|
|
1438
|
+
SupportedSubsetCase(
|
|
1439
|
+
"fixed-if-else",
|
|
1440
|
+
"if-else",
|
|
1441
|
+
(
|
|
1442
|
+
"def choose(value: int, flag: bool) -> int:\n"
|
|
1443
|
+
" if flag:\n"
|
|
1444
|
+
" value = value + 1\n"
|
|
1445
|
+
" else:\n"
|
|
1446
|
+
" value = value - 1\n"
|
|
1447
|
+
" return value\n"
|
|
1448
|
+
),
|
|
1449
|
+
),
|
|
1450
|
+
SupportedSubsetCase(
|
|
1451
|
+
"fixed-while",
|
|
1452
|
+
"while-break-continue",
|
|
1453
|
+
(
|
|
1454
|
+
"def scan(limit: int) -> int:\n"
|
|
1455
|
+
" value = 0\n"
|
|
1456
|
+
" while value < limit:\n"
|
|
1457
|
+
" value = value + 1\n"
|
|
1458
|
+
" if value == 2:\n"
|
|
1459
|
+
" continue\n"
|
|
1460
|
+
" if value == 5:\n"
|
|
1461
|
+
" break\n"
|
|
1462
|
+
" return value\n"
|
|
1463
|
+
),
|
|
1464
|
+
),
|
|
1465
|
+
SupportedSubsetCase(
|
|
1466
|
+
"fixed-range-for",
|
|
1467
|
+
"bounded-range-for",
|
|
1468
|
+
(
|
|
1469
|
+
"def total() -> int:\n"
|
|
1470
|
+
" value = 0\n"
|
|
1471
|
+
" for item in range(1, 6, 2):\n"
|
|
1472
|
+
" value = value + item\n"
|
|
1473
|
+
" return value\n"
|
|
1474
|
+
),
|
|
1475
|
+
),
|
|
1476
|
+
SupportedSubsetCase(
|
|
1477
|
+
"fixed-list",
|
|
1478
|
+
"list",
|
|
1479
|
+
(
|
|
1480
|
+
"def pick() -> int:\n"
|
|
1481
|
+
" values = [1, 2, 3]\n"
|
|
1482
|
+
" return values[-1]\n\n"
|
|
1483
|
+
"def iterate() -> int:\n"
|
|
1484
|
+
" values = [2, 4, 6]\n"
|
|
1485
|
+
" result = 0\n"
|
|
1486
|
+
" for item in values:\n"
|
|
1487
|
+
" result = item\n"
|
|
1488
|
+
" return result\n"
|
|
1489
|
+
),
|
|
1490
|
+
),
|
|
1491
|
+
SupportedSubsetCase(
|
|
1492
|
+
"fixed-tuple",
|
|
1493
|
+
"tuple",
|
|
1494
|
+
(
|
|
1495
|
+
"def pick() -> str:\n"
|
|
1496
|
+
' values = ("a", "b")\n'
|
|
1497
|
+
" return values[0]\n"
|
|
1498
|
+
),
|
|
1499
|
+
),
|
|
1500
|
+
SupportedSubsetCase(
|
|
1501
|
+
"fixed-dict",
|
|
1502
|
+
"dict",
|
|
1503
|
+
(
|
|
1504
|
+
"def pick() -> int:\n"
|
|
1505
|
+
' values = {"a": 1, "b": 2}\n'
|
|
1506
|
+
' return values["b"]\n'
|
|
1507
|
+
),
|
|
1508
|
+
),
|
|
1509
|
+
SupportedSubsetCase(
|
|
1510
|
+
"fixed-module-bundle",
|
|
1511
|
+
"explicit-module-bundle",
|
|
1512
|
+
(
|
|
1513
|
+
"from lib import increment\n\n"
|
|
1514
|
+
"def run(value: int) -> int:\n"
|
|
1515
|
+
" return increment(value)\n"
|
|
1516
|
+
),
|
|
1517
|
+
primary_name="app.py",
|
|
1518
|
+
primary_module="app",
|
|
1519
|
+
companions=(
|
|
1520
|
+
(
|
|
1521
|
+
"lib",
|
|
1522
|
+
"lib.py",
|
|
1523
|
+
(
|
|
1524
|
+
"def increment(value: int) -> int:\n"
|
|
1525
|
+
" return value + 1\n"
|
|
1526
|
+
),
|
|
1527
|
+
),
|
|
1528
|
+
),
|
|
1529
|
+
),
|
|
1530
|
+
SupportedSubsetCase(
|
|
1531
|
+
"fixed-record",
|
|
1532
|
+
"static-record",
|
|
1533
|
+
(
|
|
1534
|
+
"class Point:\n"
|
|
1535
|
+
" x: int\n"
|
|
1536
|
+
" y: int\n"
|
|
1537
|
+
" def __init__(self, x: int, y: int) -> None:\n"
|
|
1538
|
+
" self.x = x\n"
|
|
1539
|
+
" self.y = y\n\n"
|
|
1540
|
+
"def read() -> int:\n"
|
|
1541
|
+
" point = Point(2, 3)\n"
|
|
1542
|
+
" return point.x\n"
|
|
1543
|
+
),
|
|
1544
|
+
),
|
|
1545
|
+
SupportedSubsetCase(
|
|
1546
|
+
"fixed-floor-arithmetic",
|
|
1547
|
+
"bounded-floor-arithmetic",
|
|
1548
|
+
(
|
|
1549
|
+
"def arithmetic(value: int) -> int:\n"
|
|
1550
|
+
" return (value // -3) + (value % 5)\n"
|
|
1551
|
+
),
|
|
1552
|
+
),
|
|
1553
|
+
SupportedSubsetCase(
|
|
1554
|
+
"fixed-boolean-region",
|
|
1555
|
+
"boolean-conditional-region",
|
|
1556
|
+
(
|
|
1557
|
+
"def flag(value: bool) -> bool:\n"
|
|
1558
|
+
" return value\n\n"
|
|
1559
|
+
"def decide(a: bool, b: bool) -> bool:\n"
|
|
1560
|
+
" return a and flag(b)\n"
|
|
1561
|
+
),
|
|
1562
|
+
),
|
|
1563
|
+
SupportedSubsetCase(
|
|
1564
|
+
"fixed-comparison-region",
|
|
1565
|
+
"comparison-conditional-region",
|
|
1566
|
+
(
|
|
1567
|
+
"def identity(value: int) -> int:\n"
|
|
1568
|
+
" return value\n\n"
|
|
1569
|
+
"def compare(a: int, b: int, c: int) -> bool:\n"
|
|
1570
|
+
" return a < b < identity(c)\n"
|
|
1571
|
+
),
|
|
1572
|
+
),
|
|
1573
|
+
SupportedSubsetCase(
|
|
1574
|
+
"fixed-keyword-call",
|
|
1575
|
+
"direct-keyword-call",
|
|
1576
|
+
(
|
|
1577
|
+
"def choose(left: int, flag: bool) -> int:\n"
|
|
1578
|
+
" return left\n\n"
|
|
1579
|
+
"def run(value: int, flag: bool) -> int:\n"
|
|
1580
|
+
" return choose(flag=flag, left=value)\n"
|
|
1581
|
+
),
|
|
1582
|
+
),
|
|
1583
|
+
SupportedSubsetCase(
|
|
1584
|
+
"fixed-keyword-only-call",
|
|
1585
|
+
"required-keyword-only-call",
|
|
1586
|
+
(
|
|
1587
|
+
"def choose(left: int, /, middle: int, *, scale: int) -> int:\n"
|
|
1588
|
+
" return (left + middle) * scale\n\n"
|
|
1589
|
+
"def run() -> int:\n"
|
|
1590
|
+
" return choose(1, middle=2, scale=3)\n"
|
|
1591
|
+
),
|
|
1592
|
+
),
|
|
1593
|
+
)
|
|
1594
|
+
|
|
1595
|
+
|
|
1596
|
+
def generated_fuzz_cases(
|
|
1597
|
+
*,
|
|
1598
|
+
seed: int = FUZZ_SEED,
|
|
1599
|
+
count: int = DEFAULT_FUZZ_CASE_COUNT,
|
|
1600
|
+
) -> tuple[SupportedSubsetCase, ...]:
|
|
1601
|
+
"""Generate bounded, reproducible cases across every promoted family.
|
|
1602
|
+
|
|
1603
|
+
Each complete 16-case cycle contains every member of
|
|
1604
|
+
:data:`SUPPORTED_SUBSET_FAMILIES` exactly once in a seeded shuffled order.
|
|
1605
|
+
The family templates perturb safe constants, names, keys, bounds, and
|
|
1606
|
+
operator choices without crossing the frozen subset boundary.
|
|
1607
|
+
"""
|
|
1608
|
+
|
|
1609
|
+
if count < 0:
|
|
1610
|
+
raise ValueError("count must be non-negative")
|
|
1611
|
+
rng = random.Random(seed)
|
|
1612
|
+
cases: list[SupportedSubsetCase] = []
|
|
1613
|
+
family_cycle: list[str] = []
|
|
1614
|
+
|
|
1615
|
+
def next_family() -> str:
|
|
1616
|
+
nonlocal family_cycle
|
|
1617
|
+
if not family_cycle:
|
|
1618
|
+
family_cycle = sorted(SUPPORTED_SUBSET_FAMILIES)
|
|
1619
|
+
rng.shuffle(family_cycle)
|
|
1620
|
+
return family_cycle.pop()
|
|
1621
|
+
|
|
1622
|
+
for ordinal in range(count):
|
|
1623
|
+
family = next_family()
|
|
1624
|
+
first = rng.randint(1, 31)
|
|
1625
|
+
second = rng.randint(1, 31)
|
|
1626
|
+
third = rng.randint(1, 15)
|
|
1627
|
+
primary_name = "main.py"
|
|
1628
|
+
primary_module = "main"
|
|
1629
|
+
companions: tuple[tuple[str, str, str], ...] = ()
|
|
1630
|
+
if family == "literals":
|
|
1631
|
+
source = (
|
|
1632
|
+
f"def integer_{ordinal:03d}() -> int:\n"
|
|
1633
|
+
f" return {first}\n\n"
|
|
1634
|
+
f"def real_{ordinal:03d}() -> float:\n"
|
|
1635
|
+
f" return {second}.{third}\n\n"
|
|
1636
|
+
f"def flag_{ordinal:03d}() -> bool:\n"
|
|
1637
|
+
f" return {'True' if first % 2 else 'False'}\n\n"
|
|
1638
|
+
f"def text_{ordinal:03d}() -> str:\n"
|
|
1639
|
+
f' return "fuzz-{ordinal:03d}-{third}"\n'
|
|
1640
|
+
)
|
|
1641
|
+
elif family == "assignments-and-arithmetic":
|
|
1642
|
+
divisor = rng.randint(2, 9)
|
|
1643
|
+
source = (
|
|
1644
|
+
f"def arithmetic_{ordinal:03d}(value: int) -> int:\n"
|
|
1645
|
+
f" result = value + {first}\n"
|
|
1646
|
+
f" result = result * {third}\n"
|
|
1647
|
+
f" return result - {second}\n\n"
|
|
1648
|
+
f"def divide_{ordinal:03d}(value: float) -> float:\n"
|
|
1649
|
+
f" return value / {divisor}.0\n"
|
|
1650
|
+
)
|
|
1651
|
+
elif family == "functions-and-positional-calls":
|
|
1652
|
+
source = (
|
|
1653
|
+
f"def add_{ordinal:03d}(left: int, right: int) -> int:\n"
|
|
1654
|
+
" return left + right\n\n"
|
|
1655
|
+
f"def run_{ordinal:03d}(value: int) -> int:\n"
|
|
1656
|
+
f" return add_{ordinal:03d}(value, {first})\n"
|
|
1657
|
+
)
|
|
1658
|
+
elif family == "if-else":
|
|
1659
|
+
source = (
|
|
1660
|
+
f"def choose_{ordinal:03d}(value: int, flag: bool) -> int:\n"
|
|
1661
|
+
" if flag:\n"
|
|
1662
|
+
f" value = value + {first}\n"
|
|
1663
|
+
" else:\n"
|
|
1664
|
+
f" value = value - {second}\n"
|
|
1665
|
+
" return value\n"
|
|
1666
|
+
)
|
|
1667
|
+
elif family == "while-break-continue":
|
|
1668
|
+
continue_at = rng.randint(1, 3)
|
|
1669
|
+
break_at = rng.randint(4, 8)
|
|
1670
|
+
source = (
|
|
1671
|
+
f"def scan_{ordinal:03d}(limit: int) -> int:\n"
|
|
1672
|
+
" value = 0\n"
|
|
1673
|
+
" while value < limit:\n"
|
|
1674
|
+
" value = value + 1\n"
|
|
1675
|
+
f" if value == {continue_at}:\n"
|
|
1676
|
+
" continue\n"
|
|
1677
|
+
f" if value == {break_at}:\n"
|
|
1678
|
+
" break\n"
|
|
1679
|
+
" return value\n"
|
|
1680
|
+
)
|
|
1681
|
+
elif family == "bounded-range-for":
|
|
1682
|
+
start = rng.randint(0, 3)
|
|
1683
|
+
step = rng.randint(1, 3)
|
|
1684
|
+
stop = start + step * rng.randint(2, 7)
|
|
1685
|
+
source = (
|
|
1686
|
+
f"def total_{ordinal:03d}() -> int:\n"
|
|
1687
|
+
" result = 0\n"
|
|
1688
|
+
f" for item in range({start}, {stop}, {step}):\n"
|
|
1689
|
+
" result = result + item\n"
|
|
1690
|
+
" return result\n"
|
|
1691
|
+
)
|
|
1692
|
+
elif family == "list":
|
|
1693
|
+
values = (first, second, third)
|
|
1694
|
+
source = (
|
|
1695
|
+
f"def list_{ordinal:03d}() -> int:\n"
|
|
1696
|
+
f" values = [{values[0]}, {values[1]}, {values[2]}]\n"
|
|
1697
|
+
" result = 0\n"
|
|
1698
|
+
" for item in values:\n"
|
|
1699
|
+
" result = item\n"
|
|
1700
|
+
" return result\n"
|
|
1701
|
+
)
|
|
1702
|
+
elif family == "tuple":
|
|
1703
|
+
source = (
|
|
1704
|
+
f"def tuple_{ordinal:03d}() -> str:\n"
|
|
1705
|
+
f' values = ("left-{first}", "right-{second}")\n'
|
|
1706
|
+
f" return values[{ordinal % 2}]\n"
|
|
1707
|
+
)
|
|
1708
|
+
elif family == "dict":
|
|
1709
|
+
source = (
|
|
1710
|
+
f"def dict_{ordinal:03d}() -> str:\n"
|
|
1711
|
+
f' values = {{"left-{first}": {first}, '
|
|
1712
|
+
f'"right-{second}": {second}}}\n'
|
|
1713
|
+
' result = ""\n'
|
|
1714
|
+
" for key in values:\n"
|
|
1715
|
+
" result = key\n"
|
|
1716
|
+
" return result\n"
|
|
1717
|
+
)
|
|
1718
|
+
elif family == "explicit-module-bundle":
|
|
1719
|
+
primary_name = f"app_{ordinal:03d}.py"
|
|
1720
|
+
primary_module = f"app_{ordinal:03d}"
|
|
1721
|
+
companion_module = f"lib_{ordinal:03d}"
|
|
1722
|
+
companion_name = f"{companion_module}.py"
|
|
1723
|
+
helper_name = f"increment_{ordinal:03d}"
|
|
1724
|
+
source = (
|
|
1725
|
+
f"from {companion_module} import {helper_name}\n\n"
|
|
1726
|
+
f"def run_{ordinal:03d}(value: int) -> int:\n"
|
|
1727
|
+
f" return {helper_name}(value)\n"
|
|
1728
|
+
)
|
|
1729
|
+
companions = (
|
|
1730
|
+
(
|
|
1731
|
+
companion_module,
|
|
1732
|
+
companion_name,
|
|
1733
|
+
(
|
|
1734
|
+
f"def {helper_name}(value: int) -> int:\n"
|
|
1735
|
+
f" return value + {first}\n"
|
|
1736
|
+
),
|
|
1737
|
+
),
|
|
1738
|
+
)
|
|
1739
|
+
elif family == "static-record":
|
|
1740
|
+
class_name = f"Sample{ordinal:03d}"
|
|
1741
|
+
source = (
|
|
1742
|
+
f"class {class_name}:\n"
|
|
1743
|
+
" value: int\n"
|
|
1744
|
+
" def __init__(self, value: int) -> None:\n"
|
|
1745
|
+
" self.value = value\n\n"
|
|
1746
|
+
f"def record_{ordinal:03d}() -> int:\n"
|
|
1747
|
+
f" sample = {class_name}({first})\n"
|
|
1748
|
+
" return sample.value\n"
|
|
1749
|
+
)
|
|
1750
|
+
elif family == "bounded-floor-arithmetic":
|
|
1751
|
+
safe_divisors = (-9, -7, -5, -3, 2, 3, 5, 7, 9)
|
|
1752
|
+
floor_divisor = rng.choice(safe_divisors)
|
|
1753
|
+
mod_divisor = rng.choice(safe_divisors)
|
|
1754
|
+
source = (
|
|
1755
|
+
f"def floor_{ordinal:03d}(value: int) -> int:\n"
|
|
1756
|
+
f" return (value // {floor_divisor}) + "
|
|
1757
|
+
f"(value % {mod_divisor})\n"
|
|
1758
|
+
)
|
|
1759
|
+
elif family == "boolean-conditional-region":
|
|
1760
|
+
operator = "and" if ordinal % 2 else "or"
|
|
1761
|
+
source = (
|
|
1762
|
+
f"def flag_{ordinal:03d}(value: bool) -> bool:\n"
|
|
1763
|
+
" return value\n\n"
|
|
1764
|
+
f"def boolean_{ordinal:03d}(left: bool, right: bool) -> bool:\n"
|
|
1765
|
+
f" return left {operator} flag_{ordinal:03d}(right)\n"
|
|
1766
|
+
)
|
|
1767
|
+
elif family == "comparison-conditional-region":
|
|
1768
|
+
source = (
|
|
1769
|
+
f"def identity_{ordinal:03d}(value: int) -> int:\n"
|
|
1770
|
+
" return value\n\n"
|
|
1771
|
+
f"def compare_{ordinal:03d}("
|
|
1772
|
+
"left: int, middle: int, right: int) -> bool:\n"
|
|
1773
|
+
f" return left < middle < identity_{ordinal:03d}(right)\n"
|
|
1774
|
+
)
|
|
1775
|
+
elif family == "direct-keyword-call":
|
|
1776
|
+
source = (
|
|
1777
|
+
f"def choose_{ordinal:03d}("
|
|
1778
|
+
"left: int, middle: int, flag: bool) -> int:\n"
|
|
1779
|
+
" if flag:\n"
|
|
1780
|
+
" return left\n"
|
|
1781
|
+
" return middle\n\n"
|
|
1782
|
+
f"def keyword_{ordinal:03d}(value: int, flag: bool) -> int:\n"
|
|
1783
|
+
f" return choose_{ordinal:03d}("
|
|
1784
|
+
f"value, flag=flag, middle={first})\n"
|
|
1785
|
+
)
|
|
1786
|
+
elif family == "required-keyword-only-call":
|
|
1787
|
+
source = (
|
|
1788
|
+
f"def scale_{ordinal:03d}("
|
|
1789
|
+
"left: int, /, middle: int, *, factor: int) -> int:\n"
|
|
1790
|
+
" return (left + middle) * factor\n\n"
|
|
1791
|
+
f"def keyword_only_{ordinal:03d}() -> int:\n"
|
|
1792
|
+
f" return scale_{ordinal:03d}("
|
|
1793
|
+
f"{first}, middle={second}, factor={third})\n"
|
|
1794
|
+
)
|
|
1795
|
+
else: # pragma: no cover - the frozen family set is exhaustively routed.
|
|
1796
|
+
raise AssertionError(f"unrouted generated family: {family}")
|
|
1797
|
+
cases.append(
|
|
1798
|
+
SupportedSubsetCase(
|
|
1799
|
+
f"fuzz-{ordinal:03d}",
|
|
1800
|
+
family,
|
|
1801
|
+
source,
|
|
1802
|
+
primary_name,
|
|
1803
|
+
primary_module,
|
|
1804
|
+
companions,
|
|
1805
|
+
)
|
|
1806
|
+
)
|
|
1807
|
+
return tuple(cases)
|
|
1808
|
+
|
|
1809
|
+
|
|
1810
|
+
def _canonical_bytes(value: object) -> bytes:
|
|
1811
|
+
return json.dumps(
|
|
1812
|
+
value,
|
|
1813
|
+
sort_keys=True,
|
|
1814
|
+
separators=(",", ":"),
|
|
1815
|
+
ensure_ascii=False,
|
|
1816
|
+
allow_nan=False,
|
|
1817
|
+
).encode("utf-8")
|
|
1818
|
+
|
|
1819
|
+
|
|
1820
|
+
def _sha256_json(value: object) -> str:
|
|
1821
|
+
return hashlib.sha256(_canonical_bytes(value)).hexdigest()
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
def _ast_kinds_for_witness(witness: FeatureMatrixWitness) -> set[str]:
|
|
1825
|
+
kinds: set[str] = set()
|
|
1826
|
+
documents = (
|
|
1827
|
+
(witness.logical_name, witness.source),
|
|
1828
|
+
*((logical_name, text) for _, logical_name, text in witness.companions),
|
|
1829
|
+
)
|
|
1830
|
+
for logical_name, source in documents:
|
|
1831
|
+
tree = ast.parse(source, filename=logical_name, mode="exec")
|
|
1832
|
+
kinds.update(type(node).__name__ for node in ast.walk(tree))
|
|
1833
|
+
return kinds
|
|
1834
|
+
|
|
1835
|
+
|
|
1836
|
+
def _feature_matrix_entry_contract(
|
|
1837
|
+
entry: Mapping[str, object],
|
|
1838
|
+
) -> dict[str, object]:
|
|
1839
|
+
return {
|
|
1840
|
+
"construct": entry.get("construct"),
|
|
1841
|
+
"context": entry.get("context"),
|
|
1842
|
+
"state": entry.get("state"),
|
|
1843
|
+
"diagnostic": entry.get("diagnostic"),
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
|
|
1847
|
+
def _execute_feature_matrix_witness(
|
|
1848
|
+
converter: PythonToCConverter,
|
|
1849
|
+
witness: FeatureMatrixWitness,
|
|
1850
|
+
*,
|
|
1851
|
+
profile_id: str,
|
|
1852
|
+
request_options: Mapping[str, object] | None,
|
|
1853
|
+
expected_diagnostic: str | None,
|
|
1854
|
+
reason_contains: str | None,
|
|
1855
|
+
) -> tuple[dict[str, object], list[str]]:
|
|
1856
|
+
"""Execute one profile twice and return closed, source-only evidence."""
|
|
1857
|
+
|
|
1858
|
+
errors: list[str] = []
|
|
1859
|
+
request = witness.request(request_options)
|
|
1860
|
+
first = converter.convert(request)
|
|
1861
|
+
repeated = converter.convert(request)
|
|
1862
|
+
first_json = result_to_json(first)
|
|
1863
|
+
repeated_json = result_to_json(repeated)
|
|
1864
|
+
deterministic = first_json == repeated_json
|
|
1865
|
+
if not deterministic:
|
|
1866
|
+
errors.append(
|
|
1867
|
+
f"{witness.witness_id}/{profile_id}: repeated conversion is not "
|
|
1868
|
+
"byte-stable"
|
|
1869
|
+
)
|
|
1870
|
+
|
|
1871
|
+
diagnostic_codes = [item.code for item in first.diagnostics]
|
|
1872
|
+
diagnostic_messages = [item.message for item in first.diagnostics]
|
|
1873
|
+
expected_status = (
|
|
1874
|
+
ResultStatus.CONVERTED
|
|
1875
|
+
if witness.state == "supported"
|
|
1876
|
+
else ResultStatus.REJECTED
|
|
1877
|
+
)
|
|
1878
|
+
if first.status is not expected_status:
|
|
1879
|
+
errors.append(
|
|
1880
|
+
f"{witness.witness_id}/{profile_id}: expected "
|
|
1881
|
+
f"{expected_status.value}, got {first.status.value}"
|
|
1882
|
+
)
|
|
1883
|
+
|
|
1884
|
+
c_conformance_accepted: bool | None = None
|
|
1885
|
+
c_conformance_message: str | None = None
|
|
1886
|
+
if witness.state == "supported":
|
|
1887
|
+
if first.generated_c is None:
|
|
1888
|
+
errors.append(
|
|
1889
|
+
f"{witness.witness_id}/{profile_id}: supported witness did "
|
|
1890
|
+
"not publish C source"
|
|
1891
|
+
)
|
|
1892
|
+
else:
|
|
1893
|
+
conformance = validate_c_text(first.generated_c)
|
|
1894
|
+
c_conformance_accepted = conformance.accepted
|
|
1895
|
+
c_conformance_message = conformance.message
|
|
1896
|
+
if not conformance.accepted:
|
|
1897
|
+
errors.append(
|
|
1898
|
+
f"{witness.witness_id}/{profile_id}: emitted C source "
|
|
1899
|
+
f"failed independent conformance: {conformance.message}"
|
|
1900
|
+
)
|
|
1901
|
+
if first.diagnostics:
|
|
1902
|
+
errors.append(
|
|
1903
|
+
f"{witness.witness_id}/{profile_id}: supported witness "
|
|
1904
|
+
f"published diagnostics {diagnostic_codes}"
|
|
1905
|
+
)
|
|
1906
|
+
else:
|
|
1907
|
+
if first.generated_c is not None:
|
|
1908
|
+
errors.append(
|
|
1909
|
+
f"{witness.witness_id}/{profile_id}: rejected witness "
|
|
1910
|
+
"published C source"
|
|
1911
|
+
)
|
|
1912
|
+
if first.output_fingerprint is not None:
|
|
1913
|
+
errors.append(
|
|
1914
|
+
f"{witness.witness_id}/{profile_id}: rejected witness "
|
|
1915
|
+
"published an output fingerprint"
|
|
1916
|
+
)
|
|
1917
|
+
if expected_diagnostic is not None and expected_diagnostic not in diagnostic_codes:
|
|
1918
|
+
errors.append(
|
|
1919
|
+
f"{witness.witness_id}/{profile_id}: expected diagnostic "
|
|
1920
|
+
f"{expected_diagnostic}, got {diagnostic_codes}"
|
|
1921
|
+
)
|
|
1922
|
+
if reason_contains is not None and not any(
|
|
1923
|
+
reason_contains in message for message in diagnostic_messages
|
|
1924
|
+
):
|
|
1925
|
+
errors.append(
|
|
1926
|
+
f"{witness.witness_id}/{profile_id}: no diagnostic reason "
|
|
1927
|
+
f"contained {reason_contains!r}"
|
|
1928
|
+
)
|
|
1929
|
+
|
|
1930
|
+
evidence: dict[str, object] = {
|
|
1931
|
+
"profile_id": profile_id,
|
|
1932
|
+
"request_options": dict(request_options or {}),
|
|
1933
|
+
"expected_status": expected_status.value,
|
|
1934
|
+
"actual_status": first.status.value,
|
|
1935
|
+
"expected_diagnostic": expected_diagnostic,
|
|
1936
|
+
"actual_diagnostics": diagnostic_codes,
|
|
1937
|
+
"actual_reasons": diagnostic_messages,
|
|
1938
|
+
"deterministic": deterministic,
|
|
1939
|
+
"generated_c_published": first.generated_c is not None,
|
|
1940
|
+
"output_fingerprint_published": first.output_fingerprint is not None,
|
|
1941
|
+
"independent_c_conformance_accepted": c_conformance_accepted,
|
|
1942
|
+
"independent_c_conformance_message": c_conformance_message,
|
|
1943
|
+
"result_sha256": hashlib.sha256(first_json.encode("utf-8")).hexdigest(),
|
|
1944
|
+
}
|
|
1945
|
+
return evidence, errors
|
|
1946
|
+
|
|
1947
|
+
|
|
1948
|
+
def audit_feature_matrix(root: Path) -> dict[str, object]:
|
|
1949
|
+
"""Execute all 69 frozen rows and the unlisted-node default.
|
|
1950
|
+
|
|
1951
|
+
Every row is driven through :class:`PythonToCConverter`. The one
|
|
1952
|
+
cumulative diagnostic whose owning profile differs from the current
|
|
1953
|
+
default (``AsyncFunctionDef``) names its Phase 9 request explicitly and
|
|
1954
|
+
also runs the current-profile precedence companion.
|
|
1955
|
+
"""
|
|
1956
|
+
|
|
1957
|
+
matrix_path = root / "specifications" / "feature_matrix.json"
|
|
1958
|
+
errors: list[str] = []
|
|
1959
|
+
matrix_contract_mismatches: list[dict[str, object]] = []
|
|
1960
|
+
raw_matrix = b""
|
|
1961
|
+
matrix: dict[str, object] = {}
|
|
1962
|
+
if not matrix_path.is_file():
|
|
1963
|
+
errors.append(f"feature matrix is missing: {matrix_path}")
|
|
1964
|
+
else:
|
|
1965
|
+
raw_matrix = matrix_path.read_bytes()
|
|
1966
|
+
try:
|
|
1967
|
+
loaded = json.loads(raw_matrix.decode("utf-8"))
|
|
1968
|
+
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
1969
|
+
errors.append(f"feature matrix is not valid canonical UTF-8 JSON: {exc}")
|
|
1970
|
+
else:
|
|
1971
|
+
if not isinstance(loaded, dict):
|
|
1972
|
+
errors.append("feature matrix root must be an object")
|
|
1973
|
+
else:
|
|
1974
|
+
matrix = loaded
|
|
1975
|
+
|
|
1976
|
+
matrix_sha256 = hashlib.sha256(raw_matrix).hexdigest()
|
|
1977
|
+
if matrix_sha256 != FEATURE_MATRIX_SHA256:
|
|
1978
|
+
errors.append(
|
|
1979
|
+
"feature matrix SHA-256 drift: expected "
|
|
1980
|
+
f"{FEATURE_MATRIX_SHA256}, got {matrix_sha256}"
|
|
1981
|
+
)
|
|
1982
|
+
if matrix.get("schema") != FEATURE_MATRIX_SCHEMA:
|
|
1983
|
+
errors.append(
|
|
1984
|
+
"feature matrix schema drift: expected "
|
|
1985
|
+
f"{FEATURE_MATRIX_SCHEMA!r}, got {matrix.get('schema')!r}"
|
|
1986
|
+
)
|
|
1987
|
+
expected_metadata = {
|
|
1988
|
+
"grammar": "python-3.11",
|
|
1989
|
+
"rule_set": EXPECTED_CONTRACT_IDENTITIES["rule_set"],
|
|
1990
|
+
"renderer": EXPECTED_CONTRACT_IDENTITIES["renderer"],
|
|
1991
|
+
"helper_policy": EXPECTED_CONTRACT_IDENTITIES["helper_policy"],
|
|
1992
|
+
"container_policy": EXPECTED_CONTRACT_IDENTITIES["container_policy"],
|
|
1993
|
+
"module_policy": EXPECTED_CONTRACT_IDENTITIES["module_policy"],
|
|
1994
|
+
"record_policy": EXPECTED_CONTRACT_IDENTITIES["record_policy"],
|
|
1995
|
+
"numeric_policy": EXPECTED_CONTRACT_IDENTITIES["numeric_policy"],
|
|
1996
|
+
"conditional_fact_schema": EXPECTED_CONTRACT_IDENTITIES[
|
|
1997
|
+
"conditional_facts"
|
|
1998
|
+
],
|
|
1999
|
+
"keyword_call_fact_schema": EXPECTED_CONTRACT_IDENTITIES[
|
|
2000
|
+
"keyword_call_facts"
|
|
2001
|
+
],
|
|
2002
|
+
"keyword_only_call_fact_schema": EXPECTED_CONTRACT_IDENTITIES[
|
|
2003
|
+
"keyword_only_call_facts"
|
|
2004
|
+
],
|
|
2005
|
+
"default_state": "unsupported",
|
|
2006
|
+
}
|
|
2007
|
+
for key, expected in expected_metadata.items():
|
|
2008
|
+
actual = matrix.get(key)
|
|
2009
|
+
if actual != expected:
|
|
2010
|
+
errors.append(
|
|
2011
|
+
f"feature matrix {key} drift: expected {expected!r}, "
|
|
2012
|
+
f"got {actual!r}"
|
|
2013
|
+
)
|
|
2014
|
+
|
|
2015
|
+
raw_entries = matrix.get("entries", ())
|
|
2016
|
+
entries: list[Mapping[str, object]] = []
|
|
2017
|
+
if not isinstance(raw_entries, list):
|
|
2018
|
+
errors.append("feature matrix entries must be an array")
|
|
2019
|
+
else:
|
|
2020
|
+
for ordinal, entry in enumerate(raw_entries):
|
|
2021
|
+
if not isinstance(entry, Mapping):
|
|
2022
|
+
errors.append(
|
|
2023
|
+
f"feature matrix entry {ordinal} must be an object"
|
|
2024
|
+
)
|
|
2025
|
+
else:
|
|
2026
|
+
entries.append(entry)
|
|
2027
|
+
if len(entries) != FEATURE_MATRIX_ENTRY_COUNT:
|
|
2028
|
+
errors.append(
|
|
2029
|
+
"feature matrix entry-count drift: expected "
|
|
2030
|
+
f"{FEATURE_MATRIX_ENTRY_COUNT}, got {len(entries)}"
|
|
2031
|
+
)
|
|
2032
|
+
|
|
2033
|
+
witnesses = FEATURE_MATRIX_WITNESS_ORDER
|
|
2034
|
+
witness_keys = [item.key for item in witnesses]
|
|
2035
|
+
if len(witnesses) != FEATURE_MATRIX_ENTRY_COUNT:
|
|
2036
|
+
errors.append(
|
|
2037
|
+
"feature witness count drift: expected "
|
|
2038
|
+
f"{FEATURE_MATRIX_ENTRY_COUNT}, got {len(witnesses)}"
|
|
2039
|
+
)
|
|
2040
|
+
if len(FEATURE_MATRIX_WITNESSES) != len(witnesses):
|
|
2041
|
+
errors.append("feature witness keys are not unique")
|
|
2042
|
+
|
|
2043
|
+
actual_keys = [
|
|
2044
|
+
(str(entry.get("construct")), str(entry.get("context")))
|
|
2045
|
+
for entry in entries
|
|
2046
|
+
]
|
|
2047
|
+
if actual_keys != witness_keys:
|
|
2048
|
+
matrix_contract_mismatches.append(
|
|
2049
|
+
{
|
|
2050
|
+
"kind": "order-or-key-drift",
|
|
2051
|
+
"expected": [list(key) for key in witness_keys],
|
|
2052
|
+
"actual": [list(key) for key in actual_keys],
|
|
2053
|
+
}
|
|
2054
|
+
)
|
|
2055
|
+
|
|
2056
|
+
entry_by_key: dict[tuple[str, str], Mapping[str, object]] = {}
|
|
2057
|
+
for entry in entries:
|
|
2058
|
+
key = (str(entry.get("construct")), str(entry.get("context")))
|
|
2059
|
+
if key in entry_by_key:
|
|
2060
|
+
matrix_contract_mismatches.append(
|
|
2061
|
+
{
|
|
2062
|
+
"kind": "duplicate-entry-key",
|
|
2063
|
+
"key": list(key),
|
|
2064
|
+
}
|
|
2065
|
+
)
|
|
2066
|
+
entry_by_key[key] = entry
|
|
2067
|
+
|
|
2068
|
+
for witness in witnesses:
|
|
2069
|
+
entry = entry_by_key.get(witness.key)
|
|
2070
|
+
if entry is None:
|
|
2071
|
+
matrix_contract_mismatches.append(
|
|
2072
|
+
{
|
|
2073
|
+
"kind": "missing-entry",
|
|
2074
|
+
"key": list(witness.key),
|
|
2075
|
+
}
|
|
2076
|
+
)
|
|
2077
|
+
continue
|
|
2078
|
+
expected_contract = {
|
|
2079
|
+
"construct": witness.construct,
|
|
2080
|
+
"context": witness.context,
|
|
2081
|
+
"state": witness.state,
|
|
2082
|
+
"diagnostic": witness.diagnostic,
|
|
2083
|
+
}
|
|
2084
|
+
actual_contract = _feature_matrix_entry_contract(entry)
|
|
2085
|
+
if actual_contract != expected_contract:
|
|
2086
|
+
matrix_contract_mismatches.append(
|
|
2087
|
+
{
|
|
2088
|
+
"kind": "entry-contract-drift",
|
|
2089
|
+
"key": list(witness.key),
|
|
2090
|
+
"expected": expected_contract,
|
|
2091
|
+
"actual": actual_contract,
|
|
2092
|
+
}
|
|
2093
|
+
)
|
|
2094
|
+
unexpected_keys = sorted(set(entry_by_key) - set(witness_keys))
|
|
2095
|
+
for key in unexpected_keys:
|
|
2096
|
+
matrix_contract_mismatches.append(
|
|
2097
|
+
{
|
|
2098
|
+
"kind": "unexpected-entry",
|
|
2099
|
+
"key": list(key),
|
|
2100
|
+
}
|
|
2101
|
+
)
|
|
2102
|
+
|
|
2103
|
+
converter = PythonToCConverter()
|
|
2104
|
+
execution_rows: list[dict[str, object]] = []
|
|
2105
|
+
witness_errors: list[str] = []
|
|
2106
|
+
diagnostic_counts: Counter[str] = Counter()
|
|
2107
|
+
state_counts: Counter[str] = Counter(item.state for item in witnesses)
|
|
2108
|
+
actual_status_counts: Counter[str] = Counter()
|
|
2109
|
+
for witness in (*witnesses, UNLISTED_DEFAULT_WITNESS):
|
|
2110
|
+
try:
|
|
2111
|
+
ast_kinds = _ast_kinds_for_witness(witness)
|
|
2112
|
+
except SyntaxError as exc:
|
|
2113
|
+
witness_errors.append(
|
|
2114
|
+
f"{witness.witness_id}: witness source is invalid Python: {exc}"
|
|
2115
|
+
)
|
|
2116
|
+
ast_kinds = set()
|
|
2117
|
+
missing_ast_kinds = sorted(
|
|
2118
|
+
set(witness.required_ast_kinds) - ast_kinds
|
|
2119
|
+
)
|
|
2120
|
+
if missing_ast_kinds:
|
|
2121
|
+
witness_errors.append(
|
|
2122
|
+
f"{witness.witness_id}: source does not contain required AST "
|
|
2123
|
+
f"kinds {missing_ast_kinds}"
|
|
2124
|
+
)
|
|
2125
|
+
|
|
2126
|
+
primary, primary_errors = _execute_feature_matrix_witness(
|
|
2127
|
+
converter,
|
|
2128
|
+
witness,
|
|
2129
|
+
profile_id="matrix-owner",
|
|
2130
|
+
request_options=dict(witness.request_options),
|
|
2131
|
+
expected_diagnostic=witness.expected_diagnostic,
|
|
2132
|
+
reason_contains=witness.actual_reason_contains,
|
|
2133
|
+
)
|
|
2134
|
+
witness_errors.extend(primary_errors)
|
|
2135
|
+
actual_status_counts[str(primary["actual_status"])] += 1
|
|
2136
|
+
diagnostic_counts.update(
|
|
2137
|
+
str(item) for item in primary["actual_diagnostics"]
|
|
2138
|
+
)
|
|
2139
|
+
profiles = [primary]
|
|
2140
|
+
for profile in witness.precedence_profiles:
|
|
2141
|
+
evidence, profile_errors = _execute_feature_matrix_witness(
|
|
2142
|
+
converter,
|
|
2143
|
+
witness,
|
|
2144
|
+
profile_id=profile.profile_id,
|
|
2145
|
+
request_options=profile.options(),
|
|
2146
|
+
expected_diagnostic=profile.expected_diagnostic,
|
|
2147
|
+
reason_contains=profile.reason_contains,
|
|
2148
|
+
)
|
|
2149
|
+
profiles.append(evidence)
|
|
2150
|
+
witness_errors.extend(profile_errors)
|
|
2151
|
+
|
|
2152
|
+
if (
|
|
2153
|
+
witness.diagnostic is not None
|
|
2154
|
+
and witness.diagnostic
|
|
2155
|
+
not in set(str(item) for item in primary["actual_diagnostics"])
|
|
2156
|
+
):
|
|
2157
|
+
matrix_contract_mismatches.append(
|
|
2158
|
+
{
|
|
2159
|
+
"kind": "declared-diagnostic-drift",
|
|
2160
|
+
"key": list(witness.key),
|
|
2161
|
+
"profile_id": "matrix-owner",
|
|
2162
|
+
"expected": witness.diagnostic,
|
|
2163
|
+
"actual": primary["actual_diagnostics"],
|
|
2164
|
+
"actual_reasons": primary["actual_reasons"],
|
|
2165
|
+
}
|
|
2166
|
+
)
|
|
2167
|
+
execution_rows.append(
|
|
2168
|
+
{
|
|
2169
|
+
"witness_id": witness.witness_id,
|
|
2170
|
+
"matrix_key": list(witness.key),
|
|
2171
|
+
"state": witness.state,
|
|
2172
|
+
"exercise": witness.exercise,
|
|
2173
|
+
"required_ast_kinds": list(witness.required_ast_kinds),
|
|
2174
|
+
"observed_ast_kinds_sha256": _sha256_json(
|
|
2175
|
+
sorted(ast_kinds)
|
|
2176
|
+
),
|
|
2177
|
+
"profiles": profiles,
|
|
2178
|
+
}
|
|
2179
|
+
)
|
|
2180
|
+
|
|
2181
|
+
if matrix_contract_mismatches:
|
|
2182
|
+
errors.append(
|
|
2183
|
+
"feature matrix contract mismatches were found: "
|
|
2184
|
+
f"{len(matrix_contract_mismatches)}"
|
|
2185
|
+
)
|
|
2186
|
+
errors.extend(witness_errors)
|
|
2187
|
+
witness_manifest = [item.manifest() for item in witnesses]
|
|
2188
|
+
report: dict[str, object] = {
|
|
2189
|
+
"audit": "checkpoint-e-executable-feature-matrix",
|
|
2190
|
+
"passed": not errors,
|
|
2191
|
+
"errors": errors,
|
|
2192
|
+
"matrix_path": "specifications/feature_matrix.json",
|
|
2193
|
+
"matrix_schema": matrix.get("schema"),
|
|
2194
|
+
"matrix_sha256": matrix_sha256,
|
|
2195
|
+
"expected_matrix_sha256": FEATURE_MATRIX_SHA256,
|
|
2196
|
+
"matrix_entry_count": len(entries),
|
|
2197
|
+
"expected_matrix_entry_count": FEATURE_MATRIX_ENTRY_COUNT,
|
|
2198
|
+
"matrix_state_counts": dict(sorted(state_counts.items())),
|
|
2199
|
+
"matrix_witness_count": len(witnesses),
|
|
2200
|
+
"unlisted_default_witness_count": 1,
|
|
2201
|
+
"total_primary_execution_count": len(execution_rows),
|
|
2202
|
+
"precedence_profile_execution_count": sum(
|
|
2203
|
+
len(row["profiles"]) - 1 for row in execution_rows
|
|
2204
|
+
),
|
|
2205
|
+
"actual_status_counts": dict(sorted(actual_status_counts.items())),
|
|
2206
|
+
"actual_diagnostic_counts": dict(sorted(diagnostic_counts.items())),
|
|
2207
|
+
"matrix_contract_mismatches": matrix_contract_mismatches,
|
|
2208
|
+
"witness_errors": witness_errors,
|
|
2209
|
+
"witness_manifest_sha256": _sha256_json(witness_manifest),
|
|
2210
|
+
"execution_rows": execution_rows,
|
|
2211
|
+
"execution_sha256": _sha256_json(execution_rows),
|
|
2212
|
+
"coverage_complete": (
|
|
2213
|
+
len(witnesses) == FEATURE_MATRIX_ENTRY_COUNT
|
|
2214
|
+
and len(FEATURE_MATRIX_WITNESSES) == FEATURE_MATRIX_ENTRY_COUNT
|
|
2215
|
+
and not matrix_contract_mismatches
|
|
2216
|
+
and not witness_errors
|
|
2217
|
+
),
|
|
2218
|
+
"c_toolchain_invoked": False,
|
|
2219
|
+
"generated_c_compiled_or_executed": False,
|
|
2220
|
+
}
|
|
2221
|
+
report["report_sha256"] = _sha256_json(report)
|
|
2222
|
+
return report
|
|
2223
|
+
|
|
2224
|
+
|
|
2225
|
+
def _walk_mappings(value: object) -> Iterable[Mapping[str, object]]:
|
|
2226
|
+
if isinstance(value, Mapping):
|
|
2227
|
+
yield value
|
|
2228
|
+
for child in value.values():
|
|
2229
|
+
yield from _walk_mappings(child)
|
|
2230
|
+
elif isinstance(value, (list, tuple)):
|
|
2231
|
+
for child in value:
|
|
2232
|
+
yield from _walk_mappings(child)
|
|
2233
|
+
|
|
2234
|
+
|
|
2235
|
+
_GENERATED_TEMPORARY = re.compile(
|
|
2236
|
+
r"\bpycf_[A-Za-z][A-Za-z0-9_]*_[0-9a-f]{12}\b"
|
|
2237
|
+
)
|
|
2238
|
+
_GENERATED_COLLISION_SUFFIX = re.compile(
|
|
2239
|
+
r"\b([A-Za-z][A-Za-z0-9_]*)_[2-9][0-9]*\b"
|
|
2240
|
+
)
|
|
2241
|
+
|
|
2242
|
+
|
|
2243
|
+
def _normalized_generated_c(text: str | None) -> str | None:
|
|
2244
|
+
"""Alpha-rename source-ID-derived temporaries while retaining C semantics."""
|
|
2245
|
+
|
|
2246
|
+
if text is None:
|
|
2247
|
+
return None
|
|
2248
|
+
names: dict[str, str] = {}
|
|
2249
|
+
|
|
2250
|
+
def replace(match: re.Match[str]) -> str:
|
|
2251
|
+
name = match.group(0)
|
|
2252
|
+
replacement = names.get(name)
|
|
2253
|
+
if replacement is None:
|
|
2254
|
+
prefix = name.rsplit("_", 1)[0]
|
|
2255
|
+
replacement = f"{prefix}_METAVAR_{len(names):04d}"
|
|
2256
|
+
names[name] = replacement
|
|
2257
|
+
return replacement
|
|
2258
|
+
|
|
2259
|
+
temporaries_normalized = _GENERATED_TEMPORARY.sub(replace, text)
|
|
2260
|
+
return _GENERATED_COLLISION_SUFFIX.sub(r"\1", temporaries_normalized)
|
|
2261
|
+
|
|
2262
|
+
|
|
2263
|
+
def _semantic_shape(result: Any) -> dict[str, object]:
|
|
2264
|
+
"""Discard source-derived IDs while preserving observable structure."""
|
|
2265
|
+
|
|
2266
|
+
summary = result.conversion_summary or {}
|
|
2267
|
+
artifact = result.stage_artifact
|
|
2268
|
+
payload = artifact.payload if artifact is not None else {}
|
|
2269
|
+
plans = payload.get("rule_plans", ())
|
|
2270
|
+
tables = payload.get("fact_tables", ())
|
|
2271
|
+
c_ir = payload.get("c_ir", {})
|
|
2272
|
+
plan_counts = Counter(
|
|
2273
|
+
(
|
|
2274
|
+
str(item.get("rule_id")),
|
|
2275
|
+
str(item.get("rule_version")),
|
|
2276
|
+
str(item.get("support_state")),
|
|
2277
|
+
)
|
|
2278
|
+
for item in plans
|
|
2279
|
+
if isinstance(item, Mapping)
|
|
2280
|
+
)
|
|
2281
|
+
c_ir_kinds = Counter(
|
|
2282
|
+
str(item["kind"])
|
|
2283
|
+
for item in _walk_mappings(c_ir)
|
|
2284
|
+
if isinstance(item.get("kind"), str)
|
|
2285
|
+
)
|
|
2286
|
+
feature_names = (
|
|
2287
|
+
"helpers",
|
|
2288
|
+
"functions",
|
|
2289
|
+
"calls",
|
|
2290
|
+
"containers",
|
|
2291
|
+
"container_accesses",
|
|
2292
|
+
"container_iterations",
|
|
2293
|
+
"modules",
|
|
2294
|
+
"module_imports",
|
|
2295
|
+
"records",
|
|
2296
|
+
"record_fields",
|
|
2297
|
+
"record_initializers",
|
|
2298
|
+
"record_instances",
|
|
2299
|
+
"record_bindings",
|
|
2300
|
+
"record_accesses",
|
|
2301
|
+
"numeric_operations",
|
|
2302
|
+
"conditional_regions",
|
|
2303
|
+
"keyword_calls",
|
|
2304
|
+
"keyword_only_calls",
|
|
2305
|
+
)
|
|
2306
|
+
return {
|
|
2307
|
+
"status": result.status.value,
|
|
2308
|
+
"diagnostics": [
|
|
2309
|
+
(item.code, item.severity.value, item.stage) for item in result.diagnostics
|
|
2310
|
+
],
|
|
2311
|
+
"last_completed_stage": result.last_completed_stage,
|
|
2312
|
+
"stage_order": list(result.stage_order),
|
|
2313
|
+
"normalized_generated_c_sha256": (
|
|
2314
|
+
None
|
|
2315
|
+
if result.generated_c is None
|
|
2316
|
+
else hashlib.sha256(
|
|
2317
|
+
_normalized_generated_c(result.generated_c).encode("utf-8")
|
|
2318
|
+
).hexdigest()
|
|
2319
|
+
),
|
|
2320
|
+
"summary_contracts": {
|
|
2321
|
+
key: summary.get(key)
|
|
2322
|
+
for key in (
|
|
2323
|
+
"schema_version",
|
|
2324
|
+
"target_contract",
|
|
2325
|
+
"semantic_policy",
|
|
2326
|
+
"rule_set_version",
|
|
2327
|
+
"renderer_version",
|
|
2328
|
+
"helper_policy_version",
|
|
2329
|
+
"container_policy_version",
|
|
2330
|
+
"module_policy_version",
|
|
2331
|
+
"record_policy_version",
|
|
2332
|
+
"numeric_policy_version",
|
|
2333
|
+
"translation_unit_count",
|
|
2334
|
+
)
|
|
2335
|
+
},
|
|
2336
|
+
"feature_counts": {
|
|
2337
|
+
name: len(summary.get(name, ())) for name in feature_names
|
|
2338
|
+
},
|
|
2339
|
+
"artifact": None
|
|
2340
|
+
if artifact is None
|
|
2341
|
+
else {
|
|
2342
|
+
"kind": artifact.kind,
|
|
2343
|
+
"schema_version": artifact.schema_version,
|
|
2344
|
+
"payload_schema": payload.get("schema_version"),
|
|
2345
|
+
"c_ir_schema": payload.get("c_ir_schema"),
|
|
2346
|
+
},
|
|
2347
|
+
"helpers": list(payload.get("helper_requirements", ())),
|
|
2348
|
+
"rule_plan_counts": [
|
|
2349
|
+
[*key, count] for key, count in sorted(plan_counts.items())
|
|
2350
|
+
],
|
|
2351
|
+
"fact_tables": sorted(
|
|
2352
|
+
[
|
|
2353
|
+
[
|
|
2354
|
+
str(item.get("table_id")),
|
|
2355
|
+
str(item.get("schema_version")),
|
|
2356
|
+
len(item.get("records", ())),
|
|
2357
|
+
]
|
|
2358
|
+
for item in tables
|
|
2359
|
+
if isinstance(item, Mapping)
|
|
2360
|
+
]
|
|
2361
|
+
),
|
|
2362
|
+
"c_ir_kind_counts": [
|
|
2363
|
+
[kind, count] for kind, count in sorted(c_ir_kinds.items())
|
|
2364
|
+
],
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
|
|
2368
|
+
def _transform_case(
|
|
2369
|
+
case: SupportedSubsetCase,
|
|
2370
|
+
transformation: str,
|
|
2371
|
+
) -> SupportedSubsetCase:
|
|
2372
|
+
def transform(text: str) -> str:
|
|
2373
|
+
if transformation == "leading-comment":
|
|
2374
|
+
return "# Checkpoint E metamorphic witness\n" + text
|
|
2375
|
+
if transformation == "crlf":
|
|
2376
|
+
return text.replace("\n", "\r\n")
|
|
2377
|
+
if transformation == "blank-lines":
|
|
2378
|
+
return text.replace("\n\n", "\n\n\n") + "\n"
|
|
2379
|
+
raise ValueError(f"unknown transformation: {transformation}")
|
|
2380
|
+
|
|
2381
|
+
return SupportedSubsetCase(
|
|
2382
|
+
case_id=f"{case.case_id}:{transformation}",
|
|
2383
|
+
family=case.family,
|
|
2384
|
+
primary_text=transform(case.primary_text),
|
|
2385
|
+
primary_name=case.primary_name,
|
|
2386
|
+
primary_module=case.primary_module,
|
|
2387
|
+
companions=tuple(
|
|
2388
|
+
(module_id, logical_name, transform(text))
|
|
2389
|
+
for module_id, logical_name, text in case.companions
|
|
2390
|
+
),
|
|
2391
|
+
)
|
|
2392
|
+
|
|
2393
|
+
|
|
2394
|
+
def audit_full_supported_subset(
|
|
2395
|
+
*,
|
|
2396
|
+
seed: int = FUZZ_SEED,
|
|
2397
|
+
fuzz_case_count: int = DEFAULT_FUZZ_CASE_COUNT,
|
|
2398
|
+
) -> dict[str, object]:
|
|
2399
|
+
"""Run deterministic, observer-invariant, metamorphic source-only checks."""
|
|
2400
|
+
|
|
2401
|
+
fixed = fixed_supported_subset_cases()
|
|
2402
|
+
generated = generated_fuzz_cases(seed=seed, count=fuzz_case_count)
|
|
2403
|
+
cases = (*fixed, *generated)
|
|
2404
|
+
errors: list[str] = []
|
|
2405
|
+
families = {case.family for case in fixed}
|
|
2406
|
+
generated_family_counts = Counter(case.family for case in generated)
|
|
2407
|
+
generated_missing_families = sorted(
|
|
2408
|
+
SUPPORTED_SUBSET_FAMILIES - set(generated_family_counts)
|
|
2409
|
+
)
|
|
2410
|
+
missing_families = sorted(SUPPORTED_SUBSET_FAMILIES - families)
|
|
2411
|
+
if missing_families:
|
|
2412
|
+
errors.append(
|
|
2413
|
+
"fixed corpus lacks supported families: " + ", ".join(missing_families)
|
|
2414
|
+
)
|
|
2415
|
+
if (
|
|
2416
|
+
fuzz_case_count >= len(SUPPORTED_SUBSET_FAMILIES)
|
|
2417
|
+
and generated_missing_families
|
|
2418
|
+
):
|
|
2419
|
+
errors.append(
|
|
2420
|
+
"generated corpus lacks promoted families despite a complete "
|
|
2421
|
+
"family cycle: " + ", ".join(generated_missing_families)
|
|
2422
|
+
)
|
|
2423
|
+
|
|
2424
|
+
converter = PythonToCConverter()
|
|
2425
|
+
transformations = ("leading-comment", "crlf", "blank-lines")
|
|
2426
|
+
case_digests: list[dict[str, str]] = []
|
|
2427
|
+
for case in cases:
|
|
2428
|
+
full_observation = ObservationOptions("Full", True)
|
|
2429
|
+
none_observation = ObservationOptions("None", False)
|
|
2430
|
+
first = converter.convert(case.request(), observation=full_observation)
|
|
2431
|
+
repeated = converter.convert(case.request(), observation=full_observation)
|
|
2432
|
+
first_json = result_to_json(first)
|
|
2433
|
+
repeated_json = result_to_json(repeated)
|
|
2434
|
+
if first_json != repeated_json:
|
|
2435
|
+
errors.append(f"{case.case_id}: repeated conversion is not byte-stable")
|
|
2436
|
+
if first.status is not ResultStatus.CONVERTED:
|
|
2437
|
+
errors.append(
|
|
2438
|
+
f"{case.case_id}: expected Converted, got {first.status.value} "
|
|
2439
|
+
f"({','.join(item.code for item in first.diagnostics)})"
|
|
2440
|
+
)
|
|
2441
|
+
if first.generated_c is None:
|
|
2442
|
+
errors.append(f"{case.case_id}: converted result did not publish C text")
|
|
2443
|
+
else:
|
|
2444
|
+
conformance = validate_c_text(first.generated_c)
|
|
2445
|
+
if not conformance.accepted:
|
|
2446
|
+
errors.append(
|
|
2447
|
+
f"{case.case_id}: emitted C text failed source conformance: "
|
|
2448
|
+
f"{conformance.message}"
|
|
2449
|
+
)
|
|
2450
|
+
|
|
2451
|
+
unobserved = converter.convert(case.request(), observation=none_observation)
|
|
2452
|
+
if (
|
|
2453
|
+
result_to_json(first, include_observers=False)
|
|
2454
|
+
!= result_to_json(unobserved, include_observers=False)
|
|
2455
|
+
):
|
|
2456
|
+
errors.append(f"{case.case_id}: observer settings changed semantics")
|
|
2457
|
+
|
|
2458
|
+
base_shape = _semantic_shape(first)
|
|
2459
|
+
for transformation in transformations:
|
|
2460
|
+
variant = _transform_case(case, transformation)
|
|
2461
|
+
transformed = converter.convert(
|
|
2462
|
+
variant.request(),
|
|
2463
|
+
observation=full_observation,
|
|
2464
|
+
)
|
|
2465
|
+
if _semantic_shape(transformed) != base_shape:
|
|
2466
|
+
errors.append(
|
|
2467
|
+
f"{case.case_id}: {transformation} changed semantic shape"
|
|
2468
|
+
)
|
|
2469
|
+
if transformed.generated_c is not None:
|
|
2470
|
+
conformance = validate_c_text(transformed.generated_c)
|
|
2471
|
+
if not conformance.accepted:
|
|
2472
|
+
errors.append(
|
|
2473
|
+
f"{case.case_id}: {transformation} emitted "
|
|
2474
|
+
f"nonconforming C text: {conformance.message}"
|
|
2475
|
+
)
|
|
2476
|
+
case_digests.append(
|
|
2477
|
+
{
|
|
2478
|
+
"case_id": case.case_id,
|
|
2479
|
+
"result_sha256": hashlib.sha256(
|
|
2480
|
+
first_json.encode("utf-8")
|
|
2481
|
+
).hexdigest(),
|
|
2482
|
+
"shape_sha256": _sha256_json(base_shape),
|
|
2483
|
+
}
|
|
2484
|
+
)
|
|
2485
|
+
|
|
2486
|
+
corpus_manifest = [case.manifest() for case in cases]
|
|
2487
|
+
report: dict[str, object] = {
|
|
2488
|
+
"audit": "checkpoint-e-full-supported-subset",
|
|
2489
|
+
"passed": not errors,
|
|
2490
|
+
"errors": errors,
|
|
2491
|
+
"seed": seed,
|
|
2492
|
+
"fixed_case_count": len(fixed),
|
|
2493
|
+
"generated_case_count": len(generated),
|
|
2494
|
+
"case_count": len(cases),
|
|
2495
|
+
"metamorphic_transformations": list(transformations),
|
|
2496
|
+
"metamorphic_conversion_count": len(cases) * len(transformations),
|
|
2497
|
+
"supported_families": sorted(families),
|
|
2498
|
+
"missing_supported_families": missing_families,
|
|
2499
|
+
"generated_family_counts": dict(sorted(generated_family_counts.items())),
|
|
2500
|
+
"generated_missing_families": generated_missing_families,
|
|
2501
|
+
"generated_complete_family_cycles": (
|
|
2502
|
+
fuzz_case_count // len(SUPPORTED_SUBSET_FAMILIES)
|
|
2503
|
+
),
|
|
2504
|
+
"generated_feature_counts": {
|
|
2505
|
+
"fixed-container-iteration": sum(
|
|
2506
|
+
count
|
|
2507
|
+
for family, count in generated_family_counts.items()
|
|
2508
|
+
if family in {"dict", "list"}
|
|
2509
|
+
),
|
|
2510
|
+
"floating-division": generated_family_counts.get(
|
|
2511
|
+
"assignments-and-arithmetic",
|
|
2512
|
+
0,
|
|
2513
|
+
),
|
|
2514
|
+
},
|
|
2515
|
+
"fixed_feature_witnesses": {
|
|
2516
|
+
"fixed-container-iteration": "fixed-list",
|
|
2517
|
+
"floating-division": "fixed-arithmetic",
|
|
2518
|
+
},
|
|
2519
|
+
"corpus_sha256": _sha256_json(corpus_manifest),
|
|
2520
|
+
"case_digests": case_digests,
|
|
2521
|
+
"c_toolchain_invoked": False,
|
|
2522
|
+
"generated_c_compiled_or_executed": False,
|
|
2523
|
+
}
|
|
2524
|
+
report["report_sha256"] = _sha256_json(report)
|
|
2525
|
+
return report
|
|
2526
|
+
|
|
2527
|
+
|
|
2528
|
+
_FORBIDDEN_IMPORT_ROOTS = {
|
|
2529
|
+
"cffi",
|
|
2530
|
+
"ctypes",
|
|
2531
|
+
"pexpect",
|
|
2532
|
+
"subprocess",
|
|
2533
|
+
}
|
|
2534
|
+
_FORBIDDEN_BUILTIN_CALL_NAMES = {
|
|
2535
|
+
"compile",
|
|
2536
|
+
"eval",
|
|
2537
|
+
"exec",
|
|
2538
|
+
}
|
|
2539
|
+
_FORBIDDEN_ATTRIBUTE_CALL_NAMES = {
|
|
2540
|
+
"popen",
|
|
2541
|
+
"system",
|
|
2542
|
+
}
|
|
2543
|
+
_FORBIDDEN_ACTION_LABELS = {
|
|
2544
|
+
"Build",
|
|
2545
|
+
"Debug",
|
|
2546
|
+
"Plugin Marketplace",
|
|
2547
|
+
"Project Explorer",
|
|
2548
|
+
"Run",
|
|
2549
|
+
"Terminal",
|
|
2550
|
+
}
|
|
2551
|
+
_EXTERNAL_BRANDS = {
|
|
2552
|
+
"chatgpt",
|
|
2553
|
+
"codex",
|
|
2554
|
+
"jetbrains",
|
|
2555
|
+
"openai",
|
|
2556
|
+
"pycharm",
|
|
2557
|
+
"visual studio code",
|
|
2558
|
+
"vscode",
|
|
2559
|
+
}
|
|
2560
|
+
def scan_product_boundary(root: Path) -> list[str]:
|
|
2561
|
+
"""Find runtime imports, calls, labels, and brands outside product scope."""
|
|
2562
|
+
|
|
2563
|
+
violations: list[str] = []
|
|
2564
|
+
runtime_roots = (
|
|
2565
|
+
root / "pycforge" / "converter",
|
|
2566
|
+
root / "pycforge" / "ide",
|
|
2567
|
+
)
|
|
2568
|
+
for runtime_root in runtime_roots:
|
|
2569
|
+
if not runtime_root.exists():
|
|
2570
|
+
continue
|
|
2571
|
+
for path in sorted(runtime_root.rglob("*.py")):
|
|
2572
|
+
relative = path.relative_to(root).as_posix()
|
|
2573
|
+
try:
|
|
2574
|
+
tree = ast.parse(path.read_text(encoding="utf-8"), filename=relative)
|
|
2575
|
+
except (OSError, SyntaxError) as exc:
|
|
2576
|
+
violations.append(f"{relative}: cannot inspect source: {exc}")
|
|
2577
|
+
continue
|
|
2578
|
+
for node in ast.walk(tree):
|
|
2579
|
+
if isinstance(node, ast.Import):
|
|
2580
|
+
imported = [alias.name for alias in node.names]
|
|
2581
|
+
elif isinstance(node, ast.ImportFrom) and node.module:
|
|
2582
|
+
imported = [node.module]
|
|
2583
|
+
else:
|
|
2584
|
+
imported = []
|
|
2585
|
+
for name in imported:
|
|
2586
|
+
if name.split(".", 1)[0] in _FORBIDDEN_IMPORT_ROOTS:
|
|
2587
|
+
violations.append(
|
|
2588
|
+
f"{relative}:{node.lineno}: forbidden runtime import {name}"
|
|
2589
|
+
)
|
|
2590
|
+
if isinstance(node, ast.Call):
|
|
2591
|
+
if isinstance(node.func, ast.Name):
|
|
2592
|
+
call_name = node.func.id
|
|
2593
|
+
forbidden_call = (
|
|
2594
|
+
call_name in _FORBIDDEN_BUILTIN_CALL_NAMES
|
|
2595
|
+
)
|
|
2596
|
+
elif isinstance(node.func, ast.Attribute):
|
|
2597
|
+
call_name = node.func.attr
|
|
2598
|
+
forbidden_call = (
|
|
2599
|
+
call_name.casefold()
|
|
2600
|
+
in _FORBIDDEN_ATTRIBUTE_CALL_NAMES
|
|
2601
|
+
)
|
|
2602
|
+
else:
|
|
2603
|
+
call_name = ""
|
|
2604
|
+
forbidden_call = False
|
|
2605
|
+
if forbidden_call:
|
|
2606
|
+
violations.append(
|
|
2607
|
+
f"{relative}:{node.lineno}: forbidden runtime call "
|
|
2608
|
+
f"{call_name}"
|
|
2609
|
+
)
|
|
2610
|
+
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
2611
|
+
normalized = " ".join(node.value.split()).casefold()
|
|
2612
|
+
if node.value.strip() in _FORBIDDEN_ACTION_LABELS:
|
|
2613
|
+
violations.append(
|
|
2614
|
+
f"{relative}:{node.lineno}: out-of-scope action label "
|
|
2615
|
+
f"{node.value!r}"
|
|
2616
|
+
)
|
|
2617
|
+
for brand in sorted(_EXTERNAL_BRANDS):
|
|
2618
|
+
if brand in normalized:
|
|
2619
|
+
violations.append(
|
|
2620
|
+
f"{relative}:{node.lineno}: external brand {brand!r}"
|
|
2621
|
+
)
|
|
2622
|
+
pyproject = root / "pyproject.toml"
|
|
2623
|
+
if not pyproject.is_file():
|
|
2624
|
+
violations.append("missing pyproject.toml")
|
|
2625
|
+
else:
|
|
2626
|
+
try:
|
|
2627
|
+
metadata = tomllib.loads(pyproject.read_text(encoding="utf-8"))
|
|
2628
|
+
except (OSError, tomllib.TOMLDecodeError) as exc:
|
|
2629
|
+
violations.append(f"pyproject.toml cannot be inspected: {exc}")
|
|
2630
|
+
else:
|
|
2631
|
+
project = metadata.get("project", {})
|
|
2632
|
+
if project.get("name") != "pycforge":
|
|
2633
|
+
violations.append("project name is not exactly pycforge")
|
|
2634
|
+
scripts = project.get("scripts", {})
|
|
2635
|
+
if not isinstance(scripts, dict):
|
|
2636
|
+
violations.append("project scripts are not a TOML table")
|
|
2637
|
+
else:
|
|
2638
|
+
allowed_scripts = {"pycforge", "pycforge-workspace"}
|
|
2639
|
+
unexpected = sorted(set(scripts) - allowed_scripts)
|
|
2640
|
+
if unexpected:
|
|
2641
|
+
violations.append(
|
|
2642
|
+
"out-of-scope project scripts: " + ", ".join(unexpected)
|
|
2643
|
+
)
|
|
2644
|
+
return sorted(set(violations))
|
|
2645
|
+
|
|
2646
|
+
|
|
2647
|
+
def audit_architecture_branding_product_boundary(root: Path) -> dict[str, object]:
|
|
2648
|
+
"""Combine the cumulative architecture gate with Checkpoint E boundaries."""
|
|
2649
|
+
|
|
2650
|
+
from pycforge.laboratory.audits import audit_architecture
|
|
2651
|
+
|
|
2652
|
+
cumulative = audit_architecture(root)
|
|
2653
|
+
identities = current_contract_identities()
|
|
2654
|
+
identity_mismatches = {
|
|
2655
|
+
key: {"expected": expected, "actual": identities.get(key)}
|
|
2656
|
+
for key, expected in EXPECTED_CONTRACT_IDENTITIES.items()
|
|
2657
|
+
if identities.get(key) != expected
|
|
2658
|
+
}
|
|
2659
|
+
boundary_violations = scan_product_boundary(root)
|
|
2660
|
+
errors = [
|
|
2661
|
+
*(
|
|
2662
|
+
["cumulative architecture audit failed"]
|
|
2663
|
+
if not cumulative.get("passed")
|
|
2664
|
+
else []
|
|
2665
|
+
),
|
|
2666
|
+
*(
|
|
2667
|
+
["frozen 0.14.3 identities changed"]
|
|
2668
|
+
if identity_mismatches
|
|
2669
|
+
else []
|
|
2670
|
+
),
|
|
2671
|
+
*boundary_violations,
|
|
2672
|
+
]
|
|
2673
|
+
return {
|
|
2674
|
+
"audit": "checkpoint-e-architecture-branding-product-boundary",
|
|
2675
|
+
"passed": not errors,
|
|
2676
|
+
"errors": errors,
|
|
2677
|
+
"identity_mismatches": identity_mismatches,
|
|
2678
|
+
"contract_identities": identities,
|
|
2679
|
+
"cumulative_architecture": cumulative,
|
|
2680
|
+
"boundary_violations": boundary_violations,
|
|
2681
|
+
"front_facing_brand": "PyCForge",
|
|
2682
|
+
"product_boundary": "deterministic Python-to-C source transpiler",
|
|
2683
|
+
"c_toolchain_invoked": False,
|
|
2684
|
+
"generated_c_compiled_or_executed": False,
|
|
2685
|
+
}
|