Conversation
tree-sitter-swift 0.7.3 — the only published release, so there is no version to bump to — accepts `await` in an `if let` binding only when the operand is a direct call. `if let r = await pending`, `if let r = await box.rings` and `if let r = try await mint()` are all valid Swift 6 and all parse as ERROR, which marks the file partially extracted and leaves every rule downstream walking a damaged subtree. `guard let` with the same operand parses, which places the gap in the if/while binding rule rather than in `await`. The repair blanks the `await` with SPACES rather than deleting it. That is the whole trick: the repaired source is byte-for-byte the same length, so every offset, line and column downstream still addresses what it did. `await` names no symbol, so the graph built from the repaired bytes is the graph of the original file. It is gated three ways. Swift only; only when the original parse already errored, so a file that parses is never rewritten; and the repaired tree is kept only when it buys something — no error at all, or a first error that moved further down, since a file can carry this grammar gap and a real mistake at once. A match sitting behind a `//` is skipped, so a commented-out binding keeps its text. Four mutants, each caught: the repair never applied, `await` deleted instead of blanked, the comment guard dropped, and `guard let` swept in. Fixes Graphify-Labs#3540
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Fixes Swift extraction aborting on if let/while let/if var bindings whose await operand isn't a direct call — a tree-sitter-swift grammar gap that reported valid Swift 6 files as partially extracted. When a Swift file parses with errors, _swift_blank_await_bindings overwrites the await/try await keyword with equal-length spaces so every downstream byte offset, line and column stays put, and reparses; the repair is kept only if it clears the error or pushes the first error further down, and commented-out (//) bindings are left untouched.
Worth a look
- Swift await repair skips real code when
//appears earlier in a string on the same line —graphify/extractors/engine.py:3173· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1127 functions depend on the 435 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 27 callees - new:
extract_files_direct()— 17 callers, 20 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - new:
dispatch_command()— 2 callers, 124 callees - new:
extract_objc()— 27 callers, 9 callees - new:
_extract_with_adaptive_retry()— 22 callers, 10 callees - …and 13 more — each is listed as a finding
Verification — 1127 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 822 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
276 of 276 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— impact, full-run-safetytests/test_astro_import_ids.py— full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— full-run-safetytests/test_charmap_encoding.py— impact, full-run-safetytests/test_chunking.py— impact, full-run-safetytests/test_cjs_module_extension.py— impact, full-run-safetytests/test_claude_cli_backend.py— impact, full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— impact, full-run-safetytests/test_cpp_objc_cross_file_calls.py— full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— full-run-safetytests/test_cross_language_call_resolution.py— full-run-safetytests/test_cross_repo_member_calls.py— full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— full-run-safetytests/test_csharp_enum_members.py— full-run-safety- … and 226 more
non-code file(s) changed (
CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 1 grounded finding(s) anchored inline below; 20 more finding(s) on lines outside this diff (see the check run).
| return _SWIFT_AWAIT_BINDING.sub(replace, source) | ||
|
|
||
|
|
||
| def _extract_generic( |
There was a problem hiding this comment.
_extract_generic()
fans out to 27 callees (efferent coupling); 18 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Graphify review on Graphify-Labs#3542, reproduced before it was fixed. The repair skipped a match when any `//` appeared earlier on the line, which is not the same question as "is this line commented out": let base = "https://api.example.com"; if let x = await f(base) { } The `//` belongs to the URL, the binding is real code, and the skip left the file unparseable, which is the exact defect this function exists to clear. A URL in Swift source is ordinary, so this was not a corner. `_swift_line_comment_start` scans the line tracking string state and returns the offset of the `//` that actually opens a comment. What it does not model is stated in its docstring rather than left implicit: raw strings, multi-line literals and interpolation. Getting one of those wrong costs nothing either way, since reading a comment as code rewrites an `await` inside a comment at equal length on a file that already failed to parse, and reading code as a comment is the behaviour being replaced. Two cells: three shapes with a `//` inside a string are repaired and keep their length, and a real line comment is still left alone in both positions it can open. Five mutations, four killed; the fifth turns `<` into `<=`, which is equivalent because the pattern starts at `if`/`while` and can never begin on the `/` the offset points at.
5279932 to
0ba46e2
Compare
|
The advisory holds up, and it is fixed at The
Two cells, and a fixture correction worth mentioning: my first draft used Five mutations, four killed: The survivor is equivalent rather than a gap: 70 passed, 6 skipped across every Swift test. Noting for the record that the finding was marked "agreed by 2 of 2 but NOT verified" and it was right anyway, so the escalation was worth making. |
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Repairs Swift extraction when an if let/while let/var binding's await operand isn't a direct call — shapes like if let r = await pending that tree-sitter-swift 0.7.3 mis-parses as errors and that previously left the rest of the file only partially extracted. On an errored Swift parse, _swift_blank_await_bindings overwrites the await (or try await) keyword with equal-length spaces so every downstream byte offset, line, and column stays put, then re-parses and keeps the result only if it clears the error or pushes the first error further down. Comment-marker detection in _swift_line_comment_start is quote-aware so a // inside a string literal (e.g. a URL) doesn't cause a needed binding to be skipped, while block comments, raw strings, and interpolation are left unmodeled as harmless at equal length.
No blocking issues surfaced. 8 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1133 functions depend on the 441 functions this change touches.
Health — this change adds coupling hotspots:
- new:
_extract_generic()— 18 callers, 27 callees - new:
extract_files_direct()— 17 callers, 20 callees - new:
extract_js()— 85 callers, 4 callees - new:
extract_xaml()— 19 callers, 17 callees - new:
extract_corpus_parallel()— 26 callers, 11 callees - new:
dispatch_command()— 2 callers, 124 callees - new:
extract_objc()— 27 callers, 9 callees - new:
_extract_with_adaptive_retry()— 22 callers, 10 callees - …and 13 more — each is listed as a finding
Verification — 1133 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 828 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
276 of 276 test file(s) selected (100%) via static blast radius.
Escalated to a full run for safety — the selection is not trustworthy on its own (see below). CI should run the whole suite.
tests/test_affected_cli.py— full-run-safetytests/test_affected_member_seed.py— full-run-safetytests/test_agents_platform.py— full-run-safetytests/test_analyze.py— full-run-safetytests/test_anthropic_custom_endpoint.py— full-run-safetytests/test_antigravity_install.py— full-run-safetytests/test_apm_fallback_version.py— full-run-safetytests/test_architecture_doc.py— full-run-safetytests/test_astro_extraction.py— impact, full-run-safetytests/test_astro_import_ids.py— full-run-safetytests/test_atomic_canvas_export.py— full-run-safetytests/test_atomic_version_stamp.py— full-run-safetytests/test_atomic_writes.py— full-run-safetytests/test_backend_env_isolation.py— full-run-safetytests/test_backend_extras.py— full-run-safetytests/test_benchmark.py— full-run-safetytests/test_benchmark_raw_graph.py— full-run-safetytests/test_build.py— impact, full-run-safetytests/test_build_merge_dedup_scope.py— full-run-safetytests/test_build_merge_hyperedges_and_prune.py— full-run-safetytests/test_build_merge_shrink_guard.py— full-run-safetytests/test_builtin_global_type_refs.py— full-run-safetytests/test_cache.py— full-run-safetytests/test_callflow_html.py— full-run-safetytests/test_cargo_introspect.py— full-run-safetytests/test_carried_hyperedge_remap.py— full-run-safetytests/test_case_sensitive_resolution.py— full-run-safetytests/test_charmap_encoding.py— impact, full-run-safetytests/test_chunking.py— impact, full-run-safetytests/test_cjs_module_extension.py— impact, full-run-safetytests/test_claude_cli_backend.py— impact, full-run-safetytests/test_claude_md.py— full-run-safetytests/test_cli_broken_pipe.py— full-run-safetytests/test_cli_export.py— full-run-safetytests/test_cli_help.py— full-run-safetytests/test_cluster.py— full-run-safetytests/test_codebuddy.py— full-run-safetytests/test_community_hub_labels.py— full-run-safetytests/test_community_labels_skill.py— full-run-safetytests/test_confidence.py— full-run-safetytests/test_corrupt_graph_json.py— full-run-safetytests/test_cpp_nested_and_cli.py— impact, full-run-safetytests/test_cpp_objc_cross_file_calls.py— full-run-safetytests/test_cpp_preprocess.py— full-run-safetytests/test_cross_extension_reexport_self_cycle.py— full-run-safetytests/test_cross_language_call_resolution.py— full-run-safetytests/test_cross_repo_member_calls.py— full-run-safetytests/test_cross_repo_shared_types.py— full-run-safetytests/test_csharp_call_site_generic_args.py— full-run-safetytests/test_csharp_enum_members.py— full-run-safety- … and 226 more
non-code file(s) changed (
CHANGELOG.md) → running the full suite for safety (a code graph can't see config/fixture/data deps)
changed code file(s) with no mapped test (
CHANGELOG.md) — a coverage gap or a missing link — running the full suite rather than only the selected tests
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Could not verify: Could not verify \_extract\_generic.
The verifier did not have enough to check \_extract\_generic, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
· 1 grounded finding(s) anchored inline below; 20 more finding(s) on lines outside this diff (see the check run).
| return _SWIFT_AWAIT_BINDING.sub(replace, source) | ||
|
|
||
|
|
||
| def _extract_generic( |
There was a problem hiding this comment.
_extract_generic()
fans out to 27 callees (efferent coupling); 18 callers depend on it (afferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
Fixes #3540
The gap, mapped
tree-sitter-swift 0.7.3 is the only published release — PyPI has
0.0.1,0.7.2,0.7.3and nothing else — so there is no version to bump to. Measured against the grammar directly:if let r = await fetchRings()if let r = try await mint()if let r = await pendingif let r = await box.ringsif var r = await pendingwhile let r = await pendingguard let r = await pending else {…}All valid Swift 6.
guardparsing places the gap in the if/while binding rule rather than inawait.The repair
awaitis blanked with spaces, not deleted. That is the whole trick: the repaired source is byte-for-byte the same length, so every offset, line and column downstream still addresses what it did.awaitnames no symbol, so the graph built from the repaired bytes is the graph of the original file — and the call insidetry await mint()is stillmint().Gated three ways:
config.ts_module.A match sitting behind a
//on its line is skipped, so a commented-out binding keeps its text. A block comment or a string literal holding this exact shape is still rewritten — harmless at equal length, and only on a file that already failed to parse. Stated in the code rather than left to be discovered.What I verified, and what I could not
Verified. Every failing shape above now parses; the file is no longer reported as partially extracted; the
parse_errorskey is gone. Four mutants, each caught by the test written for it:awaitdeleted instead of blanked (offsets shift)//guard droppedguard letThat first mutant initially passed, and it was my test's fault: I asserted
assertNotIn("parse_error", result)and the key the extractor sets isparse_errors, plural. The singular spelling is never present, so the assertion passed for any file at all. Corrected, and worth naming since it is the same shape of defect this change is about.Could not reproduce. I built five fixtures aimed at the symbol loss the report measured — a same-named member binding, a binding inside a closure, two failing bindings in one type, declarations nested after the binding, calls inside the
ifbody — and in every one the node and edge counts were identical with and without the fix. tree-sitter's recovery kept the following declarations each time. So I am not repeating the 53→15 numbers as if I had confirmed them; on your corpus the loss is real and measured, on my constructed files the only observable difference is the parse error and its warning.That is still worth fixing on its own terms: the warning is a true signal being spent on a file the extractor could read perfectly well, and every rule downstream is walking an ERROR subtree for no reason.
Suites
tests/test_swift_await_optional_binding.py(new): 4 tests, 14 subtests.-k swift: 68 passed, 6 skipped.Changelog entry added under 0.9.61 (unreleased).