onec-interactive-runtime-core 0.1.19__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.
- onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
- onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
- onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
- onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
- onec_runtime/AGENTS.md +7 -0
- onec_runtime/__init__.py +3 -0
- onec_runtime/artifacts.py +59 -0
- onec_runtime/bootstrap.py +310 -0
- onec_runtime/breakpoint_workspace.py +249 -0
- onec_runtime/bsl/__init__.py +139 -0
- onec_runtime/bsl/diagnostics.py +1046 -0
- onec_runtime/bsl/full_ast_worker_projection.py +522 -0
- onec_runtime/bsl/generated_semantic_parser.py +6322 -0
- onec_runtime/bsl/lexer.py +164 -0
- onec_runtime/bsl/module_catalog.py +420 -0
- onec_runtime/bsl/module_delta.py +1079 -0
- onec_runtime/bsl/module_universe.py +1877 -0
- onec_runtime/bsl/notebook_cells.py +232 -0
- onec_runtime/bsl/notebook_method_globals.py +74 -0
- onec_runtime/bsl/notebook_methods.py +175 -0
- onec_runtime/bsl/parser_artifact_identity.py +119 -0
- onec_runtime/bsl/parser_target.py +388 -0
- onec_runtime/bsl/preprocessor.py +16 -0
- onec_runtime/bsl/semantic_lowering.py +1539 -0
- onec_runtime/bsl/source_maps.py +2411 -0
- onec_runtime/bsl/worker_dependency_resolver.py +289 -0
- onec_runtime/bsl/worker_preprocessor.py +247 -0
- onec_runtime/bsl/worker_projection_model.py +137 -0
- onec_runtime/bsl/worker_reload_source_map.py +1697 -0
- onec_runtime/capture.py +113 -0
- onec_runtime/capture_source.py +453 -0
- onec_runtime/compact_table.py +376 -0
- onec_runtime/compact_table_backend.py +500 -0
- onec_runtime/config.py +198 -0
- onec_runtime/configurator_agent.py +520 -0
- onec_runtime/controller_worker.py +400 -0
- onec_runtime/credentials.py +18 -0
- onec_runtime/epf_container.py +196 -0
- onec_runtime/errors.py +205 -0
- onec_runtime/experiment.py +130 -0
- onec_runtime/extension_bundle.py +944 -0
- onec_runtime/extension_lifecycle.py +402 -0
- onec_runtime/extension_state.py +222 -0
- onec_runtime/fault_injection.py +30 -0
- onec_runtime/kernel.py +179 -0
- onec_runtime/lease.py +123 -0
- onec_runtime/observation.py +287 -0
- onec_runtime/performance_profile.py +245 -0
- onec_runtime/privacy.py +263 -0
- onec_runtime/processes.py +265 -0
- onec_runtime/prototype_runtime.py +2644 -0
- onec_runtime/rdbg/__init__.py +1 -0
- onec_runtime/rdbg/models.py +130 -0
- onec_runtime/rdbg/reconnect.py +59 -0
- onec_runtime/rdbg/session.py +913 -0
- onec_runtime/rdbg/transport.py +138 -0
- onec_runtime/rdbg/xml_codec.py +980 -0
- onec_runtime/recovery.py +106 -0
- onec_runtime/recovery_journal.py +79 -0
- onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
- onec_runtime/resources/extension/extension-manifest.json +108 -0
- onec_runtime/runtime_api.py +5272 -0
- onec_runtime/runtime_contracts.py +196 -0
- onec_runtime/server_worker.py +2034 -0
- onec_runtime/session.py +2123 -0
- onec_runtime/startup_diagnostics.py +45 -0
- onec_runtime/stop_routing.py +65 -0
- onec_runtime/supervised_processes.py +232 -0
- onec_runtime/supervised_runtime.py +512 -0
- onec_runtime/supervisor.py +363 -0
- onec_runtime/supervisor_model.py +148 -0
- onec_runtime/supervisor_protocol.py +77 -0
- onec_runtime/table_materialization.py +471 -0
- onec_runtime/table_transfer_backend.py +203 -0
- onec_runtime/table_value.py +311 -0
- onec_runtime/toolchain.py +730 -0
- onec_runtime/value_materialization.py +432 -0
- onec_runtime/value_transfer_backend.py +199 -0
- onec_runtime/worker_breakpoints.py +1431 -0
- onec_runtime/worker_epf.py +313 -0
- onec_runtime/worker_epf_template.py +53 -0
- onec_runtime/worker_stage_protocol.py +516 -0
- onec_runtime/worker_universe.py +3772 -0
- onec_runtime_build/__init__.py +1 -0
- onec_runtime_build/parser_target_development.py +116 -0
|
@@ -0,0 +1,1079 @@
|
|
|
1
|
+
"""Session-retained semantic state for incremental Worker module reloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, fields
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from onec_runtime.bsl.module_catalog import CommonModuleCatalogSnapshot
|
|
9
|
+
from onec_runtime.bsl.module_universe import (
|
|
10
|
+
AMBIGUOUS_BINDING,
|
|
11
|
+
DEPENDENCY_ALIAS_DECLARATION_REGION,
|
|
12
|
+
DEPENDENCY_ALIAS_INITIALIZER_REGION,
|
|
13
|
+
DEPENDENCY_FIELD_REGION,
|
|
14
|
+
AnalyzedWorkerModule,
|
|
15
|
+
DependencyUse,
|
|
16
|
+
LoweredWorkerModule,
|
|
17
|
+
MethodInsertionPoints,
|
|
18
|
+
WorkerMethodAnalysis,
|
|
19
|
+
WorkerModuleAnalysisContext,
|
|
20
|
+
WorkerModuleUnit,
|
|
21
|
+
_AliasTransformPlan,
|
|
22
|
+
_analysis_error,
|
|
23
|
+
_canonical_dependency_bindings_sha256,
|
|
24
|
+
_dependency_binding,
|
|
25
|
+
_mint_worker_semantic_admission,
|
|
26
|
+
_plan_alias_transform,
|
|
27
|
+
_prepare_isolated_worker_method,
|
|
28
|
+
dependency_export_name,
|
|
29
|
+
analyze_worker_module,
|
|
30
|
+
lower_worker_module,
|
|
31
|
+
)
|
|
32
|
+
from onec_runtime.bsl.lexer import tokenize
|
|
33
|
+
from onec_runtime.bsl.parser_target import PythonParserTarget
|
|
34
|
+
from onec_runtime.bsl.source_maps import (
|
|
35
|
+
MappedSource,
|
|
36
|
+
MappingRelation,
|
|
37
|
+
SourceArtifactKind,
|
|
38
|
+
SourceMap,
|
|
39
|
+
SourceMapSegment,
|
|
40
|
+
SourceSpan,
|
|
41
|
+
SourceTransformBuilder,
|
|
42
|
+
_DeferredMappedSource,
|
|
43
|
+
_NORMALIZED_WORKER_AUTHORITY,
|
|
44
|
+
_RetainedTextFragment,
|
|
45
|
+
_validate_retained_source,
|
|
46
|
+
)
|
|
47
|
+
from onec_runtime.performance_profile import PhaseRecorder
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_CURRENT_WORKER_TRANSFORM_VERSION = next(
|
|
51
|
+
field.default
|
|
52
|
+
for field in fields(LoweredWorkerModule)
|
|
53
|
+
if field.name == "transform_version"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _SegmentReuseUnsupported(Exception):
|
|
58
|
+
"""The retained map cannot prove one unambiguous exact splice."""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _OriginSegmentIndex:
|
|
62
|
+
"""One immutable origin interval index shared by all delta lookups."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, source_map: SourceMap) -> None:
|
|
65
|
+
grouped: dict[tuple[object, MappingRelation], list[SourceMapSegment]] = {}
|
|
66
|
+
for segment in source_map.segments:
|
|
67
|
+
if segment.origin_ref is not None and segment.origin is not None:
|
|
68
|
+
grouped.setdefault((segment.origin_ref, segment.relation), []).append(
|
|
69
|
+
segment
|
|
70
|
+
)
|
|
71
|
+
self._segments: dict[
|
|
72
|
+
tuple[object, MappingRelation], tuple[SourceMapSegment, ...]
|
|
73
|
+
] = {}
|
|
74
|
+
self._cursor: dict[tuple[object, MappingRelation], int] = {}
|
|
75
|
+
self._last_origin_start: dict[tuple[object, MappingRelation], int] = {}
|
|
76
|
+
self._ambiguous: set[tuple[object, MappingRelation]] = set()
|
|
77
|
+
for key, items in grouped.items():
|
|
78
|
+
ordered = tuple(items)
|
|
79
|
+
self._segments[key] = ordered
|
|
80
|
+
if any(
|
|
81
|
+
current.origin is None
|
|
82
|
+
or following.origin is None
|
|
83
|
+
or current.origin.start > following.origin.start
|
|
84
|
+
or current.origin.end > following.origin.start
|
|
85
|
+
for current, following in zip(ordered, ordered[1:], strict=False)
|
|
86
|
+
):
|
|
87
|
+
self._ambiguous.add(key)
|
|
88
|
+
|
|
89
|
+
def generated_span(
|
|
90
|
+
self,
|
|
91
|
+
origin_ref: object,
|
|
92
|
+
origin: SourceSpan,
|
|
93
|
+
relation: MappingRelation,
|
|
94
|
+
) -> SourceSpan:
|
|
95
|
+
key = (origin_ref, relation)
|
|
96
|
+
if key in self._ambiguous:
|
|
97
|
+
raise _SegmentReuseUnsupported
|
|
98
|
+
segments = self._segments.get(key, ())
|
|
99
|
+
if origin.start < self._last_origin_start.get(key, -1):
|
|
100
|
+
raise _SegmentReuseUnsupported
|
|
101
|
+
self._last_origin_start[key] = origin.start
|
|
102
|
+
index = self._cursor.get(key, 0)
|
|
103
|
+
while (
|
|
104
|
+
index < len(segments)
|
|
105
|
+
and segments[index].origin is not None
|
|
106
|
+
and segments[index].origin.end <= origin.start
|
|
107
|
+
):
|
|
108
|
+
index += 1
|
|
109
|
+
self._cursor[key] = index
|
|
110
|
+
if index >= len(segments):
|
|
111
|
+
raise _SegmentReuseUnsupported
|
|
112
|
+
segment = segments[index]
|
|
113
|
+
if (
|
|
114
|
+
segment.origin is None
|
|
115
|
+
or segment.origin.start > origin.start
|
|
116
|
+
or origin.end > segment.origin.end
|
|
117
|
+
):
|
|
118
|
+
raise _SegmentReuseUnsupported
|
|
119
|
+
if relation is MappingRelation.EXACT:
|
|
120
|
+
return SourceSpan(
|
|
121
|
+
segment.generated.start + origin.start - segment.origin.start,
|
|
122
|
+
segment.generated.start + origin.end - segment.origin.start,
|
|
123
|
+
)
|
|
124
|
+
if segment.origin != origin:
|
|
125
|
+
raise _SegmentReuseUnsupported
|
|
126
|
+
return segment.generated
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class _GeneratedSegmentCursor:
|
|
130
|
+
"""Resolve monotonically increasing generated offsets in one pass."""
|
|
131
|
+
|
|
132
|
+
def __init__(self, source_map: SourceMap) -> None:
|
|
133
|
+
self._segments = tuple(
|
|
134
|
+
segment
|
|
135
|
+
for segment in source_map.segments
|
|
136
|
+
if segment.generated.start < segment.generated.end
|
|
137
|
+
)
|
|
138
|
+
self._index = 0
|
|
139
|
+
|
|
140
|
+
def at(self, offset: int) -> SourceMapSegment:
|
|
141
|
+
while (
|
|
142
|
+
self._index < len(self._segments)
|
|
143
|
+
and self._segments[self._index].generated.end <= offset
|
|
144
|
+
):
|
|
145
|
+
self._index += 1
|
|
146
|
+
if self._index >= len(self._segments):
|
|
147
|
+
raise _SegmentReuseUnsupported
|
|
148
|
+
segment = self._segments[self._index]
|
|
149
|
+
if not segment.generated.start <= offset < segment.generated.end:
|
|
150
|
+
raise _SegmentReuseUnsupported
|
|
151
|
+
return segment
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
155
|
+
class WorkerSemanticSnapshot:
|
|
156
|
+
"""One full admitted semantic result eligible to become a session base."""
|
|
157
|
+
|
|
158
|
+
analysis: AnalyzedWorkerModule
|
|
159
|
+
lowered: LoweredWorkerModule
|
|
160
|
+
catalog_identity: tuple[str, str, int, str]
|
|
161
|
+
parser_identity: tuple[str, str]
|
|
162
|
+
delta_evidence: WorkerSemanticDeltaEvidence | None = None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass(frozen=True, slots=True)
|
|
166
|
+
class WorkerSemanticDeltaEvidence:
|
|
167
|
+
method_name: str
|
|
168
|
+
old_span: SourceSpan
|
|
169
|
+
new_span: SourceSpan
|
|
170
|
+
length_delta: int
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def build_full_worker_semantic_snapshot(
|
|
174
|
+
unit: WorkerModuleUnit,
|
|
175
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
176
|
+
parser_target: PythonParserTarget,
|
|
177
|
+
*,
|
|
178
|
+
profiler: PhaseRecorder | None = None,
|
|
179
|
+
) -> WorkerSemanticSnapshot:
|
|
180
|
+
"""Build the full semantic result and retain its process-private admission."""
|
|
181
|
+
|
|
182
|
+
analysis = analyze_worker_module(
|
|
183
|
+
unit,
|
|
184
|
+
catalog,
|
|
185
|
+
parser_target,
|
|
186
|
+
profiler=profiler,
|
|
187
|
+
)
|
|
188
|
+
lowered = lower_worker_module(analysis, profiler=profiler)
|
|
189
|
+
return WorkerSemanticSnapshot(
|
|
190
|
+
analysis,
|
|
191
|
+
lowered,
|
|
192
|
+
analysis.catalog_identity,
|
|
193
|
+
analysis.parser_identity,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def select_worker_semantic_snapshot_base(
|
|
198
|
+
previous: WorkerSemanticSnapshot | None,
|
|
199
|
+
unit: WorkerModuleUnit,
|
|
200
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
201
|
+
parser_target: PythonParserTarget,
|
|
202
|
+
) -> WorkerSemanticSnapshot | None:
|
|
203
|
+
"""Return an exact compatible base, or ``None`` for conservative full fallback."""
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
if (
|
|
207
|
+
not isinstance(previous, WorkerSemanticSnapshot)
|
|
208
|
+
or not isinstance(unit, WorkerModuleUnit)
|
|
209
|
+
or not isinstance(catalog, CommonModuleCatalogSnapshot)
|
|
210
|
+
or not isinstance(parser_target, PythonParserTarget)
|
|
211
|
+
):
|
|
212
|
+
return None
|
|
213
|
+
analysis = previous.analysis
|
|
214
|
+
lowered = previous.lowered
|
|
215
|
+
prior_unit = analysis.unit
|
|
216
|
+
catalog_identity = previous.catalog_identity
|
|
217
|
+
parser_identity = previous.parser_identity
|
|
218
|
+
candidate_parser_identity = (
|
|
219
|
+
parser_target.grammar_sha256,
|
|
220
|
+
parser_target.metadata.parsergen_package_sha256 or "",
|
|
221
|
+
)
|
|
222
|
+
return (
|
|
223
|
+
previous
|
|
224
|
+
if (
|
|
225
|
+
isinstance(analysis, AnalyzedWorkerModule)
|
|
226
|
+
and isinstance(lowered, LoweredWorkerModule)
|
|
227
|
+
and lowered.analysis is analysis
|
|
228
|
+
and previous.catalog_identity == analysis.catalog_identity
|
|
229
|
+
and previous.parser_identity == analysis.parser_identity
|
|
230
|
+
and prior_unit.logical_name.casefold() == unit.logical_name.casefold()
|
|
231
|
+
and prior_unit.kind == unit.kind
|
|
232
|
+
and (
|
|
233
|
+
unit.revision > prior_unit.revision
|
|
234
|
+
or (
|
|
235
|
+
unit.revision == prior_unit.revision
|
|
236
|
+
and unit.mapped_source.artifact.source_sha256
|
|
237
|
+
== prior_unit.mapped_source.artifact.source_sha256
|
|
238
|
+
and unit.mapped_source.source_map_sha256
|
|
239
|
+
== prior_unit.mapped_source.source_map_sha256
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
and len(catalog_identity) == 4
|
|
243
|
+
and catalog.profile == catalog_identity[0]
|
|
244
|
+
and catalog.preprocessor_profile == catalog_identity[1]
|
|
245
|
+
and catalog.revision >= catalog_identity[2]
|
|
246
|
+
and catalog.sha256 == catalog_identity[3]
|
|
247
|
+
and candidate_parser_identity == parser_identity
|
|
248
|
+
and lowered.transform_version == _CURRENT_WORKER_TRANSFORM_VERSION
|
|
249
|
+
)
|
|
250
|
+
else None
|
|
251
|
+
)
|
|
252
|
+
except (AttributeError, IndexError, TypeError, ValueError):
|
|
253
|
+
return None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def try_build_worker_semantic_delta(
|
|
257
|
+
previous: WorkerSemanticSnapshot,
|
|
258
|
+
unit: WorkerModuleUnit,
|
|
259
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
260
|
+
parser_target: PythonParserTarget,
|
|
261
|
+
*,
|
|
262
|
+
profiler: PhaseRecorder | None = None,
|
|
263
|
+
) -> WorkerSemanticSnapshot | None:
|
|
264
|
+
"""Build one admitted method-local candidate, or request full fallback."""
|
|
265
|
+
|
|
266
|
+
if (
|
|
267
|
+
select_worker_semantic_snapshot_base(
|
|
268
|
+
previous,
|
|
269
|
+
unit,
|
|
270
|
+
catalog,
|
|
271
|
+
parser_target,
|
|
272
|
+
)
|
|
273
|
+
is not previous
|
|
274
|
+
):
|
|
275
|
+
return None
|
|
276
|
+
old_source = previous.analysis.unit.mapped_source.text
|
|
277
|
+
new_source = unit.mapped_source.text
|
|
278
|
+
hunk = _single_unambiguous_hunk(old_source, new_source)
|
|
279
|
+
if hunk is None:
|
|
280
|
+
return None
|
|
281
|
+
old_hunk, new_hunk = hunk
|
|
282
|
+
containing = tuple(
|
|
283
|
+
(index, method)
|
|
284
|
+
for index, method in enumerate(previous.analysis.methods)
|
|
285
|
+
if _span_contains_edit(method.executable_body, old_hunk)
|
|
286
|
+
)
|
|
287
|
+
if len(containing) != 1:
|
|
288
|
+
return None
|
|
289
|
+
method_index, old_method = containing[0]
|
|
290
|
+
if _edit_touches_directive(old_source, old_hunk) or _edit_touches_directive(
|
|
291
|
+
new_source,
|
|
292
|
+
new_hunk,
|
|
293
|
+
):
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
length_delta = len(new_source) - len(old_source)
|
|
297
|
+
candidate_method_span = SourceSpan(
|
|
298
|
+
old_method.method_declaration.start,
|
|
299
|
+
old_method.method_declaration.end + length_delta,
|
|
300
|
+
)
|
|
301
|
+
if (
|
|
302
|
+
candidate_method_span.end < candidate_method_span.start
|
|
303
|
+
or candidate_method_span.end > len(new_source)
|
|
304
|
+
):
|
|
305
|
+
return None
|
|
306
|
+
prepared = _prepare_isolated_worker_method(
|
|
307
|
+
unit,
|
|
308
|
+
catalog,
|
|
309
|
+
parser_target,
|
|
310
|
+
candidate_method_span,
|
|
311
|
+
previous.analysis.context,
|
|
312
|
+
profiler=profiler,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def analyze_and_merge() -> AnalyzedWorkerModule | None:
|
|
316
|
+
isolated = prepared.analyze()
|
|
317
|
+
if isolated is None:
|
|
318
|
+
return None
|
|
319
|
+
new_method = isolated.method
|
|
320
|
+
if (
|
|
321
|
+
new_method.method_name != old_method.method_name
|
|
322
|
+
or new_method.exported != old_method.exported
|
|
323
|
+
or new_method.signature_sha256 != old_method.signature_sha256
|
|
324
|
+
or not _span_contains_edit(new_method.executable_body, new_hunk)
|
|
325
|
+
):
|
|
326
|
+
return None
|
|
327
|
+
return _merge_analysis(
|
|
328
|
+
previous.analysis,
|
|
329
|
+
unit,
|
|
330
|
+
catalog,
|
|
331
|
+
parser_target,
|
|
332
|
+
method_index,
|
|
333
|
+
isolated.method,
|
|
334
|
+
isolated.insertion_point,
|
|
335
|
+
old_method.method_declaration.end,
|
|
336
|
+
length_delta,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
merged = (
|
|
340
|
+
analyze_and_merge()
|
|
341
|
+
if profiler is None
|
|
342
|
+
else profiler.measure("dependency_analysis", analyze_and_merge)
|
|
343
|
+
)
|
|
344
|
+
if merged is None:
|
|
345
|
+
return None
|
|
346
|
+
evidence = WorkerSemanticDeltaEvidence(
|
|
347
|
+
old_method.method_name,
|
|
348
|
+
old_hunk,
|
|
349
|
+
new_hunk,
|
|
350
|
+
length_delta,
|
|
351
|
+
)
|
|
352
|
+
lowered = lower_worker_module_delta(
|
|
353
|
+
previous,
|
|
354
|
+
merged,
|
|
355
|
+
evidence,
|
|
356
|
+
profiler=profiler,
|
|
357
|
+
)
|
|
358
|
+
if lowered is None:
|
|
359
|
+
lowered = lower_worker_module(merged, profiler=profiler)
|
|
360
|
+
return WorkerSemanticSnapshot(
|
|
361
|
+
merged,
|
|
362
|
+
lowered,
|
|
363
|
+
merged.catalog_identity,
|
|
364
|
+
merged.parser_identity,
|
|
365
|
+
evidence,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def lower_worker_module_delta(
|
|
370
|
+
previous: WorkerSemanticSnapshot,
|
|
371
|
+
analysis: AnalyzedWorkerModule,
|
|
372
|
+
evidence: WorkerSemanticDeltaEvidence,
|
|
373
|
+
*,
|
|
374
|
+
profiler: PhaseRecorder | None = None,
|
|
375
|
+
) -> LoweredWorkerModule | None:
|
|
376
|
+
"""Lower one merged method delta by importing unchanged mapped segments."""
|
|
377
|
+
if not _valid_delta_lowering_inputs(previous, analysis, evidence):
|
|
378
|
+
return None
|
|
379
|
+
prior_plan = _plan_alias_transform(previous.analysis)
|
|
380
|
+
plan_operation = lambda: _plan_alias_transform(analysis)
|
|
381
|
+
plan = (
|
|
382
|
+
plan_operation()
|
|
383
|
+
if profiler is None
|
|
384
|
+
else profiler.measure("alias_transform", plan_operation)
|
|
385
|
+
)
|
|
386
|
+
if not _previous_lowering_is_reusable(previous, prior_plan, evidence):
|
|
387
|
+
return None
|
|
388
|
+
compose = lambda: _compose_worker_module_delta_source(
|
|
389
|
+
previous,
|
|
390
|
+
analysis,
|
|
391
|
+
plan,
|
|
392
|
+
evidence,
|
|
393
|
+
)
|
|
394
|
+
try:
|
|
395
|
+
mapped_source = (
|
|
396
|
+
compose()
|
|
397
|
+
if profiler is None
|
|
398
|
+
else profiler.measure("source_map_composition", compose)
|
|
399
|
+
)
|
|
400
|
+
except (_SegmentReuseUnsupported, ValueError):
|
|
401
|
+
return None
|
|
402
|
+
lowered = LoweredWorkerModule(
|
|
403
|
+
analysis,
|
|
404
|
+
mapped_source,
|
|
405
|
+
_canonical_dependency_bindings_sha256(analysis.dependencies),
|
|
406
|
+
)
|
|
407
|
+
_mint_worker_semantic_admission(lowered)
|
|
408
|
+
return lowered
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def _valid_delta_lowering_inputs(
|
|
412
|
+
previous: WorkerSemanticSnapshot,
|
|
413
|
+
analysis: AnalyzedWorkerModule,
|
|
414
|
+
evidence: WorkerSemanticDeltaEvidence,
|
|
415
|
+
) -> bool:
|
|
416
|
+
try:
|
|
417
|
+
old_source = previous.analysis.unit.mapped_source.text
|
|
418
|
+
new_source = analysis.unit.mapped_source.text
|
|
419
|
+
old_method = next(
|
|
420
|
+
method
|
|
421
|
+
for method in previous.analysis.methods
|
|
422
|
+
if method.method_name.casefold() == evidence.method_name.casefold()
|
|
423
|
+
)
|
|
424
|
+
new_method = next(
|
|
425
|
+
method
|
|
426
|
+
for method in analysis.methods
|
|
427
|
+
if method.method_name.casefold() == evidence.method_name.casefold()
|
|
428
|
+
)
|
|
429
|
+
return bool(
|
|
430
|
+
isinstance(previous, WorkerSemanticSnapshot)
|
|
431
|
+
and isinstance(analysis, AnalyzedWorkerModule)
|
|
432
|
+
and isinstance(evidence, WorkerSemanticDeltaEvidence)
|
|
433
|
+
and previous.lowered.analysis is previous.analysis
|
|
434
|
+
and analysis.unit.logical_name.casefold()
|
|
435
|
+
== previous.analysis.unit.logical_name.casefold()
|
|
436
|
+
and analysis.unit.kind == previous.analysis.unit.kind
|
|
437
|
+
and evidence.length_delta == len(new_source) - len(old_source)
|
|
438
|
+
and evidence.old_span.end - evidence.old_span.start
|
|
439
|
+
+ evidence.length_delta
|
|
440
|
+
== evidence.new_span.end - evidence.new_span.start
|
|
441
|
+
and _single_unambiguous_hunk(old_source, new_source)
|
|
442
|
+
== (evidence.old_span, evidence.new_span)
|
|
443
|
+
and _span_contains_edit(old_method.executable_body, evidence.old_span)
|
|
444
|
+
and _span_contains_edit(new_method.executable_body, evidence.new_span)
|
|
445
|
+
)
|
|
446
|
+
except (AttributeError, StopIteration, TypeError, ValueError):
|
|
447
|
+
return False
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _previous_lowering_is_reusable(
|
|
451
|
+
previous: WorkerSemanticSnapshot,
|
|
452
|
+
prior_plan: _AliasTransformPlan,
|
|
453
|
+
evidence: WorkerSemanticDeltaEvidence,
|
|
454
|
+
) -> bool:
|
|
455
|
+
try:
|
|
456
|
+
raw = previous.analysis.unit.mapped_source
|
|
457
|
+
mapped = previous.lowered.mapped_source
|
|
458
|
+
projection_source = mapped.transform_parent
|
|
459
|
+
_validate_retained_source(mapped)
|
|
460
|
+
if isinstance(projection_source, (MappedSource, _DeferredMappedSource)):
|
|
461
|
+
_validate_retained_source(projection_source)
|
|
462
|
+
lineage_offset = len(raw.lineage)
|
|
463
|
+
if (
|
|
464
|
+
not isinstance(projection_source, (MappedSource, _DeferredMappedSource))
|
|
465
|
+
or len(mapped.lineage) != lineage_offset + 2
|
|
466
|
+
or mapped.lineage[:lineage_offset] != raw.lineage
|
|
467
|
+
):
|
|
468
|
+
return False
|
|
469
|
+
projection = mapped.lineage[lineage_offset]
|
|
470
|
+
wrapper = mapped.lineage[lineage_offset + 1]
|
|
471
|
+
if (
|
|
472
|
+
projection.generated.kind is not SourceArtifactKind.WORKER_PROJECTION
|
|
473
|
+
or wrapper.generated.kind is not SourceArtifactKind.WORKER_MODULE
|
|
474
|
+
or wrapper is not mapped.local_source_map
|
|
475
|
+
or wrapper.generated != mapped.artifact
|
|
476
|
+
or projection_source.artifact != projection.generated
|
|
477
|
+
or projection_source.local_source_map != projection
|
|
478
|
+
or projection_source.source_map.generated != projection.generated
|
|
479
|
+
):
|
|
480
|
+
return False
|
|
481
|
+
if any(
|
|
482
|
+
segment.origin_ref != projection.generated
|
|
483
|
+
for segment in wrapper.segments
|
|
484
|
+
if segment.relation is not MappingRelation.SYNTHETIC
|
|
485
|
+
):
|
|
486
|
+
return False
|
|
487
|
+
if any(
|
|
488
|
+
segment.anchor_ref is not None
|
|
489
|
+
and segment.anchor_ref != projection.generated
|
|
490
|
+
for segment in wrapper.segments
|
|
491
|
+
):
|
|
492
|
+
return False
|
|
493
|
+
|
|
494
|
+
exact = tuple(
|
|
495
|
+
segment
|
|
496
|
+
for segment in projection.segments
|
|
497
|
+
if segment.relation is MappingRelation.EXACT
|
|
498
|
+
)
|
|
499
|
+
cursor = 0
|
|
500
|
+
for segment in exact:
|
|
501
|
+
if segment.origin_ref != raw.artifact or segment.origin is None:
|
|
502
|
+
return False
|
|
503
|
+
if segment.origin.start != cursor:
|
|
504
|
+
return False
|
|
505
|
+
cursor = segment.origin.end
|
|
506
|
+
if cursor != len(raw.text):
|
|
507
|
+
return False
|
|
508
|
+
hunk_segments = tuple(
|
|
509
|
+
segment
|
|
510
|
+
for segment in exact
|
|
511
|
+
if segment.origin is not None
|
|
512
|
+
and _span_contains_edit(segment.origin, evidence.old_span)
|
|
513
|
+
)
|
|
514
|
+
if len(hunk_segments) != 1:
|
|
515
|
+
return False
|
|
516
|
+
|
|
517
|
+
fields = prior_plan.fields
|
|
518
|
+
field_segments: list[SourceMapSegment] = []
|
|
519
|
+
for segment in projection.segments:
|
|
520
|
+
if segment.synthetic_region != DEPENDENCY_FIELD_REGION:
|
|
521
|
+
break
|
|
522
|
+
field_segments.append(segment)
|
|
523
|
+
if len(field_segments) != len(fields):
|
|
524
|
+
return False
|
|
525
|
+
generated_cursor = 0
|
|
526
|
+
for segment, (text, anchor, region) in zip(
|
|
527
|
+
field_segments,
|
|
528
|
+
fields,
|
|
529
|
+
strict=True,
|
|
530
|
+
):
|
|
531
|
+
if (
|
|
532
|
+
segment.generated.start != generated_cursor
|
|
533
|
+
or segment.generated.end - segment.generated.start != len(text)
|
|
534
|
+
or segment.relation is not MappingRelation.SYNTHETIC
|
|
535
|
+
or segment.synthetic_region != region
|
|
536
|
+
or segment.anchor_ref != raw.artifact
|
|
537
|
+
or segment.anchor_span != anchor
|
|
538
|
+
):
|
|
539
|
+
return False
|
|
540
|
+
generated_cursor = segment.generated.end
|
|
541
|
+
|
|
542
|
+
expected_aliases = _planned_alias_fragments(prior_plan)
|
|
543
|
+
alias_segments = tuple(
|
|
544
|
+
segment
|
|
545
|
+
for segment in projection.segments
|
|
546
|
+
if segment.relation is MappingRelation.DERIVED
|
|
547
|
+
)
|
|
548
|
+
if len(alias_segments) != len(expected_aliases):
|
|
549
|
+
return False
|
|
550
|
+
for segment, (text, use, region) in zip(
|
|
551
|
+
alias_segments,
|
|
552
|
+
expected_aliases,
|
|
553
|
+
strict=True,
|
|
554
|
+
):
|
|
555
|
+
if (
|
|
556
|
+
segment.generated.end - segment.generated.start != len(text)
|
|
557
|
+
or segment.origin_ref != raw.artifact
|
|
558
|
+
or segment.origin != use.span
|
|
559
|
+
or segment.synthetic_region != region
|
|
560
|
+
or segment.anchor_ref != raw.artifact
|
|
561
|
+
or segment.anchor_span != use.method_declaration
|
|
562
|
+
):
|
|
563
|
+
return False
|
|
564
|
+
return all(
|
|
565
|
+
segment.relation is MappingRelation.EXACT
|
|
566
|
+
or segment.synthetic_region
|
|
567
|
+
in {
|
|
568
|
+
DEPENDENCY_FIELD_REGION,
|
|
569
|
+
DEPENDENCY_ALIAS_DECLARATION_REGION,
|
|
570
|
+
DEPENDENCY_ALIAS_INITIALIZER_REGION,
|
|
571
|
+
}
|
|
572
|
+
for segment in projection.segments
|
|
573
|
+
)
|
|
574
|
+
except (AttributeError, IndexError, TypeError, ValueError):
|
|
575
|
+
return False
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def _planned_alias_fragments(
|
|
579
|
+
plan: _AliasTransformPlan,
|
|
580
|
+
) -> tuple[tuple[str, DependencyUse, str], ...]:
|
|
581
|
+
source = plan.source
|
|
582
|
+
line_ending = plan.line_ending
|
|
583
|
+
declarations = plan.declarations
|
|
584
|
+
initializers = plan.initializers
|
|
585
|
+
fragments: list[tuple[str, DependencyUse, str]] = []
|
|
586
|
+
for offset in sorted({*declarations, *initializers}):
|
|
587
|
+
for index, (text, use, region) in enumerate(declarations.get(offset, ())):
|
|
588
|
+
if index == 0 and offset > 0 and source.text[offset - 1] not in "\r\n":
|
|
589
|
+
text = f"{line_ending}{text}"
|
|
590
|
+
fragments.append((text, use, region))
|
|
591
|
+
fragments.extend(initializers.get(offset, ()))
|
|
592
|
+
return tuple(fragments)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _compose_worker_module_delta_source(
|
|
596
|
+
previous: WorkerSemanticSnapshot,
|
|
597
|
+
analysis: AnalyzedWorkerModule,
|
|
598
|
+
plan: _AliasTransformPlan,
|
|
599
|
+
evidence: WorkerSemanticDeltaEvidence,
|
|
600
|
+
) -> MappedSource:
|
|
601
|
+
source = plan.source
|
|
602
|
+
line_ending = plan.line_ending
|
|
603
|
+
fields = plan.fields
|
|
604
|
+
declarations = plan.declarations
|
|
605
|
+
initializers = plan.initializers
|
|
606
|
+
new_method = next(
|
|
607
|
+
method
|
|
608
|
+
for method in analysis.methods
|
|
609
|
+
if method.method_name.casefold() == evidence.method_name.casefold()
|
|
610
|
+
)
|
|
611
|
+
changed_method = new_method.method_declaration
|
|
612
|
+
previous_module = previous.lowered.mapped_source
|
|
613
|
+
previous_projection = previous_module.transform_parent
|
|
614
|
+
if not isinstance(previous_projection, (MappedSource, _DeferredMappedSource)):
|
|
615
|
+
raise _SegmentReuseUnsupported
|
|
616
|
+
prior_projection_map = previous_projection.local_source_map
|
|
617
|
+
prior_projection_index = _OriginSegmentIndex(prior_projection_map)
|
|
618
|
+
prior_plan = _plan_alias_transform(previous.analysis)
|
|
619
|
+
|
|
620
|
+
prior_field_segments = tuple(
|
|
621
|
+
segment
|
|
622
|
+
for segment in prior_projection_map.segments
|
|
623
|
+
if segment.synthetic_region == DEPENDENCY_FIELD_REGION
|
|
624
|
+
)
|
|
625
|
+
if len(prior_field_segments) != len(prior_plan.fields):
|
|
626
|
+
raise _SegmentReuseUnsupported
|
|
627
|
+
prior_fields = {
|
|
628
|
+
(binding.target_module.casefold(), binding.export_variable.casefold()): (
|
|
629
|
+
text,
|
|
630
|
+
segment.generated,
|
|
631
|
+
)
|
|
632
|
+
for binding, (text, _anchor, _region), segment in zip(
|
|
633
|
+
previous.analysis.dependencies,
|
|
634
|
+
prior_plan.fields,
|
|
635
|
+
prior_field_segments,
|
|
636
|
+
strict=True,
|
|
637
|
+
)
|
|
638
|
+
}
|
|
639
|
+
if len(prior_fields) != len(prior_field_segments):
|
|
640
|
+
raise _SegmentReuseUnsupported
|
|
641
|
+
prior_alias_segments = tuple(
|
|
642
|
+
segment
|
|
643
|
+
for segment in prior_projection_map.segments
|
|
644
|
+
if segment.relation is MappingRelation.DERIVED
|
|
645
|
+
)
|
|
646
|
+
prior_alias_fragments = _planned_alias_fragments(prior_plan)
|
|
647
|
+
if len(prior_alias_segments) != len(prior_alias_fragments):
|
|
648
|
+
raise _SegmentReuseUnsupported
|
|
649
|
+
prior_aliases = {
|
|
650
|
+
(use.method_name.casefold(), region, text): segment.generated
|
|
651
|
+
for (text, use, region), segment in zip(
|
|
652
|
+
prior_alias_fragments,
|
|
653
|
+
prior_alias_segments,
|
|
654
|
+
strict=True,
|
|
655
|
+
)
|
|
656
|
+
}
|
|
657
|
+
if len(prior_aliases) != len(prior_alias_segments):
|
|
658
|
+
raise _SegmentReuseUnsupported
|
|
659
|
+
|
|
660
|
+
builder = SourceTransformBuilder(source)
|
|
661
|
+
|
|
662
|
+
def append_raw(start: int, end: int) -> None:
|
|
663
|
+
boundaries = sorted(
|
|
664
|
+
{
|
|
665
|
+
start,
|
|
666
|
+
end,
|
|
667
|
+
*(
|
|
668
|
+
point
|
|
669
|
+
for point in (changed_method.start, changed_method.end)
|
|
670
|
+
if start < point < end
|
|
671
|
+
),
|
|
672
|
+
}
|
|
673
|
+
)
|
|
674
|
+
for left, right in zip(boundaries, boundaries[1:], strict=False):
|
|
675
|
+
span = SourceSpan(left, right)
|
|
676
|
+
if changed_method.start <= left and right <= changed_method.end:
|
|
677
|
+
builder.copy(span)
|
|
678
|
+
else:
|
|
679
|
+
old_span = (
|
|
680
|
+
span
|
|
681
|
+
if right <= changed_method.start
|
|
682
|
+
else SourceSpan(
|
|
683
|
+
left - evidence.length_delta,
|
|
684
|
+
right - evidence.length_delta,
|
|
685
|
+
)
|
|
686
|
+
)
|
|
687
|
+
retained = prior_projection_index.generated_span(
|
|
688
|
+
previous.analysis.unit.mapped_source.artifact,
|
|
689
|
+
old_span,
|
|
690
|
+
MappingRelation.EXACT,
|
|
691
|
+
)
|
|
692
|
+
builder.retained_exact(previous_projection, retained, span)
|
|
693
|
+
|
|
694
|
+
offsets = sorted(
|
|
695
|
+
{
|
|
696
|
+
0,
|
|
697
|
+
len(source.text),
|
|
698
|
+
*declarations,
|
|
699
|
+
*initializers,
|
|
700
|
+
*plan.reuse_boundaries,
|
|
701
|
+
}
|
|
702
|
+
)
|
|
703
|
+
cursor = 0
|
|
704
|
+
for offset in offsets:
|
|
705
|
+
if cursor < offset:
|
|
706
|
+
append_raw(cursor, offset)
|
|
707
|
+
if offset == 0:
|
|
708
|
+
for binding, (text, anchor, region) in zip(
|
|
709
|
+
analysis.dependencies,
|
|
710
|
+
fields,
|
|
711
|
+
strict=True,
|
|
712
|
+
):
|
|
713
|
+
retained_field = prior_fields.get(
|
|
714
|
+
(
|
|
715
|
+
binding.target_module.casefold(),
|
|
716
|
+
binding.export_variable.casefold(),
|
|
717
|
+
)
|
|
718
|
+
)
|
|
719
|
+
if retained_field is not None and retained_field[0] == text:
|
|
720
|
+
builder.retained_synthetic(
|
|
721
|
+
previous_projection,
|
|
722
|
+
retained_field[1],
|
|
723
|
+
anchor,
|
|
724
|
+
region,
|
|
725
|
+
)
|
|
726
|
+
else:
|
|
727
|
+
builder.synthetic(text, anchor, region)
|
|
728
|
+
declaration_items = declarations.get(offset, ())
|
|
729
|
+
for index, (text, use, region) in enumerate(declaration_items):
|
|
730
|
+
if index == 0 and offset > 0 and source.text[offset - 1] not in "\r\n":
|
|
731
|
+
text = f"{line_ending}{text}"
|
|
732
|
+
retained_alias = prior_aliases.get(
|
|
733
|
+
(use.method_name.casefold(), region, text)
|
|
734
|
+
)
|
|
735
|
+
if (
|
|
736
|
+
retained_alias is not None
|
|
737
|
+
and use.method_name.casefold() != evidence.method_name.casefold()
|
|
738
|
+
):
|
|
739
|
+
builder.retained_derived(
|
|
740
|
+
previous_projection,
|
|
741
|
+
retained_alias,
|
|
742
|
+
use.span,
|
|
743
|
+
region,
|
|
744
|
+
anchor=use.method_declaration,
|
|
745
|
+
)
|
|
746
|
+
else:
|
|
747
|
+
builder.derived(text, use.span, region, anchor=use.method_declaration)
|
|
748
|
+
for text, use, region in initializers.get(offset, ()):
|
|
749
|
+
retained_alias = prior_aliases.get(
|
|
750
|
+
(use.method_name.casefold(), region, text)
|
|
751
|
+
)
|
|
752
|
+
if (
|
|
753
|
+
retained_alias is not None
|
|
754
|
+
and use.method_name.casefold() != evidence.method_name.casefold()
|
|
755
|
+
):
|
|
756
|
+
builder.retained_derived(
|
|
757
|
+
previous_projection,
|
|
758
|
+
retained_alias,
|
|
759
|
+
use.span,
|
|
760
|
+
region,
|
|
761
|
+
anchor=use.method_declaration,
|
|
762
|
+
)
|
|
763
|
+
else:
|
|
764
|
+
builder.derived(text, use.span, region, anchor=use.method_declaration)
|
|
765
|
+
cursor = offset
|
|
766
|
+
if cursor < len(source.text):
|
|
767
|
+
append_raw(cursor, len(source.text))
|
|
768
|
+
projection = builder.build_deferred(SourceArtifactKind.WORKER_PROJECTION)
|
|
769
|
+
return _prepare_worker_module_source_delta(
|
|
770
|
+
projection,
|
|
771
|
+
previous_projection,
|
|
772
|
+
previous_module,
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
|
|
776
|
+
def _prepare_worker_module_source_delta(
|
|
777
|
+
source: _DeferredMappedSource,
|
|
778
|
+
previous_projection: MappedSource | _DeferredMappedSource,
|
|
779
|
+
previous_module: MappedSource,
|
|
780
|
+
) -> MappedSource:
|
|
781
|
+
builder = SourceTransformBuilder(source)
|
|
782
|
+
prior_wrapper_map = previous_module.local_source_map
|
|
783
|
+
prior_wrapper_index = _OriginSegmentIndex(prior_wrapper_map)
|
|
784
|
+
parent_cursor = _GeneratedSegmentCursor(source.source_map)
|
|
785
|
+
|
|
786
|
+
for fragment in source.retained_fragments:
|
|
787
|
+
local_cursor = 0
|
|
788
|
+
index = fragment.text.find("\r")
|
|
789
|
+
while index >= 0:
|
|
790
|
+
if local_cursor < index:
|
|
791
|
+
_append_normalized_exact_fragment(
|
|
792
|
+
builder,
|
|
793
|
+
fragment,
|
|
794
|
+
SourceSpan(local_cursor, index),
|
|
795
|
+
previous_projection,
|
|
796
|
+
previous_module,
|
|
797
|
+
prior_wrapper_index,
|
|
798
|
+
)
|
|
799
|
+
end = index + 2 if fragment.text.startswith("\r\n", index) else index + 1
|
|
800
|
+
current_span = SourceSpan(
|
|
801
|
+
fragment.generated.start + index,
|
|
802
|
+
fragment.generated.start + end,
|
|
803
|
+
)
|
|
804
|
+
parent = parent_cursor.at(current_span.start)
|
|
805
|
+
region = (
|
|
806
|
+
parent.synthetic_region
|
|
807
|
+
if parent.relation is MappingRelation.DERIVED
|
|
808
|
+
and parent.synthetic_region is not None
|
|
809
|
+
else "worker_module_line_ending"
|
|
810
|
+
)
|
|
811
|
+
if fragment.retained_source is previous_projection:
|
|
812
|
+
assert fragment.retained_span is not None
|
|
813
|
+
old_span = SourceSpan(
|
|
814
|
+
fragment.retained_span.start + index,
|
|
815
|
+
fragment.retained_span.start + end,
|
|
816
|
+
)
|
|
817
|
+
retained = prior_wrapper_index.generated_span(
|
|
818
|
+
previous_projection.artifact,
|
|
819
|
+
old_span,
|
|
820
|
+
MappingRelation.DERIVED,
|
|
821
|
+
)
|
|
822
|
+
builder.retained_derived(
|
|
823
|
+
previous_module,
|
|
824
|
+
retained,
|
|
825
|
+
current_span,
|
|
826
|
+
region,
|
|
827
|
+
)
|
|
828
|
+
else:
|
|
829
|
+
builder.derived("\n", current_span, region)
|
|
830
|
+
local_cursor = end
|
|
831
|
+
index = fragment.text.find("\r", end)
|
|
832
|
+
if local_cursor < len(fragment.text):
|
|
833
|
+
_append_normalized_exact_fragment(
|
|
834
|
+
builder,
|
|
835
|
+
fragment,
|
|
836
|
+
SourceSpan(local_cursor, len(fragment.text)),
|
|
837
|
+
previous_projection,
|
|
838
|
+
previous_module,
|
|
839
|
+
prior_wrapper_index,
|
|
840
|
+
)
|
|
841
|
+
return builder.build_canonical_reused(
|
|
842
|
+
SourceArtifactKind.WORKER_MODULE,
|
|
843
|
+
_normalized_worker_authority=_NORMALIZED_WORKER_AUTHORITY,
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
def _append_normalized_exact_fragment(
|
|
848
|
+
builder: SourceTransformBuilder,
|
|
849
|
+
fragment: _RetainedTextFragment,
|
|
850
|
+
local_span: SourceSpan,
|
|
851
|
+
previous_projection: MappedSource | _DeferredMappedSource,
|
|
852
|
+
previous_module: MappedSource,
|
|
853
|
+
prior_wrapper_index: _OriginSegmentIndex,
|
|
854
|
+
) -> None:
|
|
855
|
+
current_span = SourceSpan(
|
|
856
|
+
fragment.generated.start + local_span.start,
|
|
857
|
+
fragment.generated.start + local_span.end,
|
|
858
|
+
)
|
|
859
|
+
if fragment.retained_source is previous_projection:
|
|
860
|
+
assert fragment.retained_span is not None
|
|
861
|
+
old_span = SourceSpan(
|
|
862
|
+
fragment.retained_span.start + local_span.start,
|
|
863
|
+
fragment.retained_span.start + local_span.end,
|
|
864
|
+
)
|
|
865
|
+
retained = prior_wrapper_index.generated_span(
|
|
866
|
+
previous_projection.artifact,
|
|
867
|
+
old_span,
|
|
868
|
+
MappingRelation.EXACT,
|
|
869
|
+
)
|
|
870
|
+
builder.retained_exact(previous_module, retained, current_span)
|
|
871
|
+
else:
|
|
872
|
+
width = local_span.end - local_span.start
|
|
873
|
+
if width > 100_000:
|
|
874
|
+
raise _SegmentReuseUnsupported
|
|
875
|
+
builder.exact(fragment.text[local_span.start : local_span.end], current_span)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def _single_unambiguous_hunk(
|
|
879
|
+
old: str,
|
|
880
|
+
new: str,
|
|
881
|
+
) -> tuple[SourceSpan, SourceSpan] | None:
|
|
882
|
+
if old == new:
|
|
883
|
+
return None
|
|
884
|
+
prefix = 0
|
|
885
|
+
limit = min(len(old), len(new))
|
|
886
|
+
while prefix < limit and old[prefix] == new[prefix]:
|
|
887
|
+
prefix += 1
|
|
888
|
+
suffix = 0
|
|
889
|
+
while (
|
|
890
|
+
suffix < len(old) - prefix
|
|
891
|
+
and suffix < len(new) - prefix
|
|
892
|
+
and old[len(old) - suffix - 1] == new[len(new) - suffix - 1]
|
|
893
|
+
):
|
|
894
|
+
suffix += 1
|
|
895
|
+
old_end = len(old) - suffix
|
|
896
|
+
new_end = len(new) - suffix
|
|
897
|
+
old_changed = old[prefix:old_end]
|
|
898
|
+
new_changed = new[prefix:new_end]
|
|
899
|
+
|
|
900
|
+
# Repeated characters can describe the same insertion/deletion at multiple
|
|
901
|
+
# offsets. Refuse that alignment instead of choosing one arbitrarily.
|
|
902
|
+
if not old_changed and new_changed:
|
|
903
|
+
if (
|
|
904
|
+
prefix > 0
|
|
905
|
+
and new_changed[-1] == old[prefix - 1]
|
|
906
|
+
or prefix < len(old)
|
|
907
|
+
and new_changed[0] == old[prefix]
|
|
908
|
+
):
|
|
909
|
+
return None
|
|
910
|
+
elif old_changed and not new_changed:
|
|
911
|
+
if (
|
|
912
|
+
prefix > 0
|
|
913
|
+
and old_changed[-1] == old[prefix - 1]
|
|
914
|
+
or old_end < len(old)
|
|
915
|
+
and old_changed[0] == old[old_end]
|
|
916
|
+
):
|
|
917
|
+
return None
|
|
918
|
+
return SourceSpan(prefix, old_end), SourceSpan(prefix, new_end)
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def _span_contains_edit(container: SourceSpan, edit: SourceSpan) -> bool:
|
|
922
|
+
if edit.start == edit.end:
|
|
923
|
+
return container.start <= edit.start <= container.end
|
|
924
|
+
return container.start <= edit.start and edit.end <= container.end
|
|
925
|
+
|
|
926
|
+
|
|
927
|
+
def _edit_touches_directive(source: str, edit: SourceSpan) -> bool:
|
|
928
|
+
start = source.rfind("\n", 0, edit.start) + 1
|
|
929
|
+
end = source.find("\n", edit.end)
|
|
930
|
+
if end < 0:
|
|
931
|
+
end = len(source)
|
|
932
|
+
for line in source[start:end].splitlines() or (source[start:end],):
|
|
933
|
+
if re.match(r"[ \t]*#", line):
|
|
934
|
+
return True
|
|
935
|
+
return False
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def _merge_analysis(
|
|
939
|
+
previous: AnalyzedWorkerModule,
|
|
940
|
+
unit: WorkerModuleUnit,
|
|
941
|
+
catalog: CommonModuleCatalogSnapshot,
|
|
942
|
+
parser_target: PythonParserTarget,
|
|
943
|
+
method_index: int,
|
|
944
|
+
new_method: WorkerMethodAnalysis,
|
|
945
|
+
new_insertion: MethodInsertionPoints,
|
|
946
|
+
shift_after: int,
|
|
947
|
+
length_delta: int,
|
|
948
|
+
) -> AnalyzedWorkerModule:
|
|
949
|
+
methods = tuple(
|
|
950
|
+
new_method
|
|
951
|
+
if index == method_index
|
|
952
|
+
else _shift_method(method, shift_after, length_delta)
|
|
953
|
+
for index, method in enumerate(previous.methods)
|
|
954
|
+
)
|
|
955
|
+
insertion_points = tuple(
|
|
956
|
+
new_insertion
|
|
957
|
+
if index == method_index
|
|
958
|
+
else _shift_insertion(point, shift_after, length_delta)
|
|
959
|
+
for index, point in enumerate(previous.method_insertion_points)
|
|
960
|
+
)
|
|
961
|
+
identifier_spans: dict[str, SourceSpan] = {}
|
|
962
|
+
for token in tokenize(unit.mapped_source.text):
|
|
963
|
+
if token.type == "ID":
|
|
964
|
+
identifier_spans.setdefault(
|
|
965
|
+
token.text.casefold(),
|
|
966
|
+
SourceSpan(token.start, token.end),
|
|
967
|
+
)
|
|
968
|
+
context = WorkerModuleAnalysisContext(
|
|
969
|
+
previous.context.module_variable_names,
|
|
970
|
+
previous.context.method_names,
|
|
971
|
+
frozenset(identifier_spans),
|
|
972
|
+
)
|
|
973
|
+
uses_by_module: dict[str, list[DependencyUse]] = {}
|
|
974
|
+
for method in methods:
|
|
975
|
+
for normalized, use in method.dependency_uses:
|
|
976
|
+
uses_by_module.setdefault(normalized, []).append(use)
|
|
977
|
+
catalog_by_name = {
|
|
978
|
+
descriptor.canonical_name.casefold(): descriptor
|
|
979
|
+
for descriptor in catalog.modules
|
|
980
|
+
}
|
|
981
|
+
generated_names: set[str] = set()
|
|
982
|
+
for normalized in sorted(uses_by_module):
|
|
983
|
+
descriptor = catalog_by_name.get(normalized)
|
|
984
|
+
if descriptor is None:
|
|
985
|
+
raise _analysis_error(
|
|
986
|
+
"worker module analysis input is invalid",
|
|
987
|
+
uses_by_module[normalized][0].span,
|
|
988
|
+
AMBIGUOUS_BINDING,
|
|
989
|
+
)
|
|
990
|
+
generated_name = dependency_export_name(
|
|
991
|
+
descriptor.canonical_name
|
|
992
|
+
).casefold()
|
|
993
|
+
if generated_name in generated_names:
|
|
994
|
+
raise _analysis_error(
|
|
995
|
+
"generated dependency names collide",
|
|
996
|
+
uses_by_module[normalized][0].span,
|
|
997
|
+
AMBIGUOUS_BINDING,
|
|
998
|
+
)
|
|
999
|
+
generated_names.add(generated_name)
|
|
1000
|
+
if generated_name in identifier_spans:
|
|
1001
|
+
raise _analysis_error(
|
|
1002
|
+
"generated dependency name collides with a source identifier",
|
|
1003
|
+
identifier_spans[generated_name],
|
|
1004
|
+
AMBIGUOUS_BINDING,
|
|
1005
|
+
)
|
|
1006
|
+
bindings = tuple(
|
|
1007
|
+
_dependency_binding(catalog_by_name[name], tuple(uses_by_module[name]))
|
|
1008
|
+
for name in sorted(uses_by_module)
|
|
1009
|
+
)
|
|
1010
|
+
return AnalyzedWorkerModule(
|
|
1011
|
+
unit,
|
|
1012
|
+
bindings,
|
|
1013
|
+
previous.exported_methods,
|
|
1014
|
+
insertion_points,
|
|
1015
|
+
context,
|
|
1016
|
+
methods,
|
|
1017
|
+
(
|
|
1018
|
+
catalog.profile,
|
|
1019
|
+
catalog.preprocessor_profile,
|
|
1020
|
+
catalog.revision,
|
|
1021
|
+
catalog.sha256,
|
|
1022
|
+
),
|
|
1023
|
+
(
|
|
1024
|
+
parser_target.grammar_sha256,
|
|
1025
|
+
parser_target.metadata.parsergen_package_sha256 or "",
|
|
1026
|
+
),
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
def _shift_method(
|
|
1031
|
+
method: WorkerMethodAnalysis,
|
|
1032
|
+
shift_after: int,
|
|
1033
|
+
amount: int,
|
|
1034
|
+
) -> WorkerMethodAnalysis:
|
|
1035
|
+
return WorkerMethodAnalysis(
|
|
1036
|
+
method.method_name,
|
|
1037
|
+
method.exported,
|
|
1038
|
+
method.signature_sha256,
|
|
1039
|
+
_shift_span(method.method_declaration, shift_after, amount),
|
|
1040
|
+
_shift_span(method.executable_body, shift_after, amount),
|
|
1041
|
+
_shift_offset(method.declarations_end, shift_after, amount),
|
|
1042
|
+
_shift_offset(method.first_statement_start, shift_after, amount),
|
|
1043
|
+
tuple(
|
|
1044
|
+
(
|
|
1045
|
+
normalized,
|
|
1046
|
+
DependencyUse(
|
|
1047
|
+
use.method_name,
|
|
1048
|
+
use.category,
|
|
1049
|
+
_shift_span(use.span, shift_after, amount),
|
|
1050
|
+
_shift_span(use.method_declaration, shift_after, amount),
|
|
1051
|
+
),
|
|
1052
|
+
)
|
|
1053
|
+
for normalized, use in method.dependency_uses
|
|
1054
|
+
),
|
|
1055
|
+
)
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def _shift_insertion(
|
|
1059
|
+
point: MethodInsertionPoints,
|
|
1060
|
+
shift_after: int,
|
|
1061
|
+
amount: int,
|
|
1062
|
+
) -> MethodInsertionPoints:
|
|
1063
|
+
return MethodInsertionPoints(
|
|
1064
|
+
point.method_name,
|
|
1065
|
+
_shift_span(point.method_declaration, shift_after, amount),
|
|
1066
|
+
_shift_offset(point.declarations_end, shift_after, amount),
|
|
1067
|
+
_shift_offset(point.first_statement_start, shift_after, amount),
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _shift_span(span: SourceSpan, shift_after: int, amount: int) -> SourceSpan:
|
|
1072
|
+
return SourceSpan(
|
|
1073
|
+
_shift_offset(span.start, shift_after, amount),
|
|
1074
|
+
_shift_offset(span.end, shift_after, amount),
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def _shift_offset(value: int, shift_after: int, amount: int) -> int:
|
|
1079
|
+
return value + amount if value >= shift_after else value
|