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.
Files changed (87) hide show
  1. onec_interactive_runtime_core-0.1.19.dist-info/METADATA +12 -0
  2. onec_interactive_runtime_core-0.1.19.dist-info/RECORD +87 -0
  3. onec_interactive_runtime_core-0.1.19.dist-info/WHEEL +4 -0
  4. onec_interactive_runtime_core-0.1.19.dist-info/licenses/COPYRIGHT +9 -0
  5. onec_interactive_runtime_core-0.1.19.dist-info/licenses/LICENSE +232 -0
  6. onec_interactive_runtime_core-0.1.19.dist-info/licenses/THIRD_PARTY_NOTICES.txt +32 -0
  7. onec_runtime/AGENTS.md +7 -0
  8. onec_runtime/__init__.py +3 -0
  9. onec_runtime/artifacts.py +59 -0
  10. onec_runtime/bootstrap.py +310 -0
  11. onec_runtime/breakpoint_workspace.py +249 -0
  12. onec_runtime/bsl/__init__.py +139 -0
  13. onec_runtime/bsl/diagnostics.py +1046 -0
  14. onec_runtime/bsl/full_ast_worker_projection.py +522 -0
  15. onec_runtime/bsl/generated_semantic_parser.py +6322 -0
  16. onec_runtime/bsl/lexer.py +164 -0
  17. onec_runtime/bsl/module_catalog.py +420 -0
  18. onec_runtime/bsl/module_delta.py +1079 -0
  19. onec_runtime/bsl/module_universe.py +1877 -0
  20. onec_runtime/bsl/notebook_cells.py +232 -0
  21. onec_runtime/bsl/notebook_method_globals.py +74 -0
  22. onec_runtime/bsl/notebook_methods.py +175 -0
  23. onec_runtime/bsl/parser_artifact_identity.py +119 -0
  24. onec_runtime/bsl/parser_target.py +388 -0
  25. onec_runtime/bsl/preprocessor.py +16 -0
  26. onec_runtime/bsl/semantic_lowering.py +1539 -0
  27. onec_runtime/bsl/source_maps.py +2411 -0
  28. onec_runtime/bsl/worker_dependency_resolver.py +289 -0
  29. onec_runtime/bsl/worker_preprocessor.py +247 -0
  30. onec_runtime/bsl/worker_projection_model.py +137 -0
  31. onec_runtime/bsl/worker_reload_source_map.py +1697 -0
  32. onec_runtime/capture.py +113 -0
  33. onec_runtime/capture_source.py +453 -0
  34. onec_runtime/compact_table.py +376 -0
  35. onec_runtime/compact_table_backend.py +500 -0
  36. onec_runtime/config.py +198 -0
  37. onec_runtime/configurator_agent.py +520 -0
  38. onec_runtime/controller_worker.py +400 -0
  39. onec_runtime/credentials.py +18 -0
  40. onec_runtime/epf_container.py +196 -0
  41. onec_runtime/errors.py +205 -0
  42. onec_runtime/experiment.py +130 -0
  43. onec_runtime/extension_bundle.py +944 -0
  44. onec_runtime/extension_lifecycle.py +402 -0
  45. onec_runtime/extension_state.py +222 -0
  46. onec_runtime/fault_injection.py +30 -0
  47. onec_runtime/kernel.py +179 -0
  48. onec_runtime/lease.py +123 -0
  49. onec_runtime/observation.py +287 -0
  50. onec_runtime/performance_profile.py +245 -0
  51. onec_runtime/privacy.py +263 -0
  52. onec_runtime/processes.py +265 -0
  53. onec_runtime/prototype_runtime.py +2644 -0
  54. onec_runtime/rdbg/__init__.py +1 -0
  55. onec_runtime/rdbg/models.py +130 -0
  56. onec_runtime/rdbg/reconnect.py +59 -0
  57. onec_runtime/rdbg/session.py +913 -0
  58. onec_runtime/rdbg/transport.py +138 -0
  59. onec_runtime/rdbg/xml_codec.py +980 -0
  60. onec_runtime/recovery.py +106 -0
  61. onec_runtime/recovery_journal.py +79 -0
  62. onec_runtime/resources/extension/OnecInteractiveRuntime.cfe +0 -0
  63. onec_runtime/resources/extension/extension-manifest.json +108 -0
  64. onec_runtime/runtime_api.py +5272 -0
  65. onec_runtime/runtime_contracts.py +196 -0
  66. onec_runtime/server_worker.py +2034 -0
  67. onec_runtime/session.py +2123 -0
  68. onec_runtime/startup_diagnostics.py +45 -0
  69. onec_runtime/stop_routing.py +65 -0
  70. onec_runtime/supervised_processes.py +232 -0
  71. onec_runtime/supervised_runtime.py +512 -0
  72. onec_runtime/supervisor.py +363 -0
  73. onec_runtime/supervisor_model.py +148 -0
  74. onec_runtime/supervisor_protocol.py +77 -0
  75. onec_runtime/table_materialization.py +471 -0
  76. onec_runtime/table_transfer_backend.py +203 -0
  77. onec_runtime/table_value.py +311 -0
  78. onec_runtime/toolchain.py +730 -0
  79. onec_runtime/value_materialization.py +432 -0
  80. onec_runtime/value_transfer_backend.py +199 -0
  81. onec_runtime/worker_breakpoints.py +1431 -0
  82. onec_runtime/worker_epf.py +313 -0
  83. onec_runtime/worker_epf_template.py +53 -0
  84. onec_runtime/worker_stage_protocol.py +516 -0
  85. onec_runtime/worker_universe.py +3772 -0
  86. onec_runtime_build/__init__.py +1 -0
  87. 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