Skip to content

fix(extract): resolve Kotlin member calls by receiver type - #1965

Open
oleksii-tumanov wants to merge 12 commits into
Graphify-Labs:v8from
oleksii-tumanov:fix/kotlin-typed-receiver-calls
Open

fix(extract): resolve Kotlin member calls by receiver type#1965
oleksii-tumanov wants to merge 12 commits into
Graphify-Labs:v8from
oleksii-tumanov:fix/kotlin-typed-receiver-calls

Conversation

@oleksii-tumanov

@oleksii-tumanov oleksii-tumanov commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #1699

Result

The #1699 fixture now emits one inferred function-level calls edge for each reported receiver shape, with confidence 0.85. Ambiguous, reassigned, external, builtin, namespace-colliding, and lexically shadowed receivers remain unresolved instead of producing a potentially wrong edge. Receiver facts stay isolated across nested classes, companions, object literals, and enum-entry bodies.

Testing

  • pytest -q tests/test_kotlin_member_calls.py tests/test_kotlin_grammar.py tests/test_language_resolvers.py (58 passed)
  • pytest -q tests/test_languages.py -k kotlin (12 passed, 368 deselected)
  • pytest -q tests/ (4916 passed, 11 skipped)
  • ruff check . (passed)
  • python -m tools.skillgen --check (passed)

fredagenttasks Bot pushed a commit to bizzabo/graphify that referenced this pull request Jul 31, 2026
…aphify-Labs#1965)

Ports oleksii-tumanov's upstream fix (Graphify-Labs#1965) into this
fork's extraction/type-resolution layer, extending the annotation-argument
capture and Kotlin annotation collector added in this branch.

- collect method-scoped Kotlin receiver types from class properties,
  constructor parameters, getter returns, and local constructor/getter
  bindings, threaded through walk_calls alongside the existing Graphify-Labs#2241
  extra_locals plumbing
- register a corpus-level kotlin_member_calls resolver
  (_resolve_kotlin_member_calls) that resolves a member call only when the
  receiver type and the selected method's owner are unambiguous, guarded by
  package/import scope, inheritance, extension-function dispatch, companion
  binding, and lexical shadowing
- preserve existing Kotlin dispatch for super, object/companion-qualified
  calls, private visibility, and inner-class dispatch receivers

This closes the receiver-typing gap tracked by Graphify-Labs#1965:
a Kotlin Spring Controller -> Service -> Repository chain (constructor
injection) now produces resolved calls edges end-to-end instead of 0, per
the new test_controller_service_repository_chain_resolves_end_to_end.

Co-authored-by: fred-agent <fred-agent@bizzabo.com>
@oleksii-tumanov
oleksii-tumanov force-pushed the fix/kotlin-typed-receiver-calls branch from 0dc2c1d to 0bddff6 Compare August 21, 2026 08:33

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin corpus-wide member-call resolution and an incremental invalidation preflight so unchanged callers get re-queued when type/method/factory inventories change. _kotlin_incremental_member_callers reads the existing graph, compares persisted per-file symbol inventories against fresh extraction, and returns dependant Kotlin files to re-extract; it fails closed by refreshing the whole live Kotlin corpus on incomplete parses, pre-v1 graphs, or path rehoming. extract_kotlin now stamps a _kotlin_member_symbol_inventory_complete marker, dispatch_command wires the preflight into incremental extract and carries the new _kotlin_* markers through the reconciliation context, and _resolve_kotlin_member_calls resolves member calls through a single exactly-matched receiver type.

Worth a look

  • Sentinel-node write assigns nodes to detached dict for zero-node resultsgraphify/extract.py:6129 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin resolver treats dotted relative receiver types as absolute FQNsgraphify/extract.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • per_file mutation may write to shared/None list without persisting into per_filegraphify/extract.py:5953 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Stamping loop mutates per_file entries without writing them backgraphify/extract.py:5960 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Sentinel-node fabrication loop also drops the created dict for None entriesgraphify/extract.py:6135 · 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 — 2040 functions depend on the 597 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 478 callers, 42 callees
  • new: _rebuild_code() — 105 callers, 51 callees
  • new: _extract_generic() — 18 callers, 27 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: dispatch_command() — 2 callers, 120 callees
  • new: extract_js() — 80 callers, 3 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 40 more — each is listed as a finding

Verification — 2040 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: 1993 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, 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: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, 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 `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_kotlin.

The verifier did not have enough to check extract\_kotlin, 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

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

Could not verify: Could not verify \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, 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 `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 5 grounded finding(s) anchored inline below; 43 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
})


def _kotlin_incremental_member_callers(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_kotlin_incremental_member_callers()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.


tables: dict[tuple[int, int], dict[str, str]] = {}

def process_owner(owner_node) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionprocess_owner()

fans out to 9 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert len(_incremental_member_edges(root)) == 1


def test_watch_requeues_caller_when_factory_is_added_and_removed(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_watch_requeues_caller_when_factory_is_added_and_removed()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert not _factory_call_targets(root) & {widget_render, product_render}


def test_watch_inventory_requeue_survives_invocation_style_changes(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_watch_inventory_requeue_survives_invocation_style_changes()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert len(_incremental_member_edges(root)) == 1


def test_cli_requeues_caller_when_factory_is_added_and_removed(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_cli_requeues_caller_when_factory_is_added_and_removed()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin incremental member-call invalidation via _kotlin_incremental_member_callers in extract.py, wired into dispatch_command to re-queue unchanged caller files when a type/method/factory symbol inventory changes. Tags extract_kotlin output with a _kotlin_member_symbol_inventory_complete marker and propagates Kotlin inventory fields (_kotlin_fqn, inventory version/contents) through the reconciliation context node builder. Introduces _resolve_kotlin_member_calls for fail-closed corpus-wide receiver-type resolution.

Worth a look

  • Stamp step also writes to a discarded dict for None/empty entriesgraphify/extract.py:5984 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • per_file mutation via or {} discards sentinel node when entry is falsygraphify/extract.py:6160 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Docs-only build now eagerly imports AST extractorgraphify/cli.py:3405 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin call deduplication suppresses calls when any edge already existsgraphify/extract.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Root-relative Kotlin paths are dropped by direct is_file checksgraphify/extract.py:3899 · 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 — 2045 functions depend on the 602 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 478 callers, 42 callees
  • new: _rebuild_code() — 105 callers, 51 callees
  • new: _extract_generic() — 18 callers, 29 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: dispatch_command() — 2 callers, 120 callees
  • new: extract_js() — 80 callers, 3 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 40 more — each is listed as a finding

Verification — 2045 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: 1998 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, 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: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, 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 `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_kotlin.

The verifier did not have enough to check extract\_kotlin, 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

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

Could not verify: Could not verify \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, 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 `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 5 grounded finding(s) anchored inline below; 43 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
})


def _kotlin_incremental_member_callers(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_kotlin_incremental_member_callers()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.


tables: dict[tuple[int, int], dict[str, str]] = {}

def process_owner(owner_node) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionprocess_owner()

fans out to 9 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert len(_incremental_member_edges(root)) == 1


def test_watch_requeues_caller_when_factory_is_added_and_removed(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_watch_requeues_caller_when_factory_is_added_and_removed()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert not _factory_call_targets(root) & {widget_render, product_render}


def test_watch_inventory_requeue_survives_invocation_style_changes(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_watch_inventory_requeue_survives_invocation_style_changes()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert len(_incremental_member_edges(root)) == 1


def test_cli_requeues_caller_when_factory_is_added_and_removed(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_cli_requeues_caller_when_factory_is_added_and_removed()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Graphify reviewed this change.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. No changes could be formally verified in this run.


Graphify review — findings

Adds Kotlin member-call incremental invalidation via _kotlin_incremental_member_callers, which reads the existing graph, compares per-file symbol inventories, and requeues unchanged callers when the corpus-wide type/method/factory inventory changes; dispatch_command wires this into extract before AST extraction and prints a re-queue notice. Stamps a completeness sentinel (_kotlin_member_symbol_inventory_complete) in extract_kotlin and carries the new _kotlin_* markers through the incremental context graph. Centralizes Kotlin-file detection in _is_kotlin_source_file and handles a one-time migration for pre-inventory graphs plus path-rehoming and parse-failure fallbacks that refresh the live Kotlin corpus.

Worth a look

  • Kotlin extraction reads a different package variable than it assignsgraphify/extractors/engine.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Kotlin extraction references an undefined package variablegraphify/extractors/engine.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • kotlin_package referenced when only _pkg computedgraphify/extractors/engine.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Incomplete test function leaves file syntactically invalidtests/test_kotlin_member_calls.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • changed-file inventory comparison keyed by identity(path) not persisted_identitygraphify/extract.py · 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 — 2087 functions depend on the 662 functions this change touches.

Health — this change adds coupling hotspots:

  • new: extract() — 478 callers, 42 callees
  • new: _rebuild_code() — 105 callers, 52 callees
  • new: _extract_generic() — 18 callers, 29 callees
  • new: extract_xaml() — 19 callers, 17 callees
  • new: extract_objc() — 27 callers, 9 callees
  • new: dispatch_command() — 2 callers, 120 callees
  • new: extract_js() — 80 callers, 3 callees
  • new: _get_extractor() — 26 callers, 6 callees
  • …and 40 more — each is listed as a finding

Verification — 2087 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: 2040 function(s) in the blast radius were not formally verified this run

Formal verification

Could not verify: Could not verify dispatch\_command.

The verifier did not have enough to check dispatch\_command, 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: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)

Could not verify: Could not verify extract.

The verifier did not have enough to check extract, 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 `cache_root` is annotated `Path | None` — outside the synthesizable primitive/collection set

Could not verify: Could not verify extract\_kotlin.

The verifier did not have enough to check extract\_kotlin, 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

Could not verify: Could not verify \_resolve\_kotlin\_import\_targets.

The verifier did not have enough to check \_resolve\_kotlin\_import\_targets, 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: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous

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

Could not verify: Could not verify run\_language\_resolvers.

The verifier did not have enough to check run\_language\_resolvers, 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 `paths` is annotated `Sequence` — outside the synthesizable primitive/collection set

Could not verify: Could not verify \_rebuild\_code.

The verifier did not have enough to check \_rebuild\_code, 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 `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 5 grounded finding(s) anchored inline below; 43 more finding(s) on lines outside this diff (see the check run).

Comment thread graphify/extract.py
return str(value).lower().endswith((".kt", ".kts"))


def _kotlin_incremental_member_callers(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regression_kotlin_incremental_member_callers()

fans out to 7 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

tables: dict[tuple[int, int], dict[str, str]] = {}
owner_tables: dict[tuple[int, int], dict[str, str]] = {}

def process_owner(owner_node) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressionprocess_owner()

fans out to 9 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert len(_incremental_member_edges(root)) == 1


def test_watch_requeues_caller_when_factory_is_added_and_removed(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_watch_requeues_caller_when_factory_is_added_and_removed()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert not _factory_call_targets(root) & {widget_render, product_render}


def test_watch_inventory_requeue_survives_invocation_style_changes(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_watch_inventory_requeue_survives_invocation_style_changes()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert len(_incremental_member_edges(root)) == 1


def test_cli_requeues_caller_when_factory_is_added_and_removed(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Health regressiontest_cli_requeues_caller_when_factory_is_added_and_removed()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@oleksii-tumanov

Copy link
Copy Markdown
Contributor Author

Hi @safishamsi, I refreshed this PR onto the current v8 branch and narrowed it to the receiver-typing behavior in #1699.

It now uses method-scoped receiver facts and fails closed for ambiguous or shadowed bindings, including incremental CLI and watch behavior. The current head is green on Python 3.10/3.12, skillgen, security, and the Graphify gate.

Ready for review when convenient. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant