Skip to content

fix(resolver): R3 — conditional self.X assignment + class-level union types#1

Merged
smochan merged 2 commits into
mainfrom
fix/c1-resolver-r3
Apr 27, 2026
Merged

fix(resolver): R3 — conditional self.X assignment + class-level union types#1
smochan merged 2 commits into
mainfrom
fix/c1-resolver-r3

Conversation

@smochan

@smochan smochan commented Apr 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • R3-1: handle if/else branches in init that assign self.X to different types
  • R3-2: regression test for single-branch annotated self.X (R2 territory, kept stable)
  • R3-3: class-level union annotations (Foo | Bar and Union[Foo, Bar])
  • Resolver emits one CALLS edge per candidate type when self.X.tail has multiple declared types

Why

The embed/ module's backend-facade pattern (self._backend: _Json | _Lance) was producing 17 false-positive dead-code findings. R2 only handled single-type class annotations.

Effect on self-graph

Dead code: 24 → 14 (-10). All backend methods now resolved correctly. The remaining 5 EmbeddingStore facade methods need local-variable type inference (R4+).

Tests

  • 8 new fixture-based tests in tests/test_resolve_r3.py
  • Full suite: 333 passed (was 325; +8)
  • ruff + mypy --strict clean

Schema

Node.metadata.attr_types migrated dict[str, str] → dict[str, list[str]] with back-compat tolerance for the old shape.

smochan added 2 commits April 27, 2026 18:19
…s (R3)

Backend-facade pattern was opaque to the resolver: when a class declared
`self._backend` via either `if/else` branches in __init__ or a class-
level union annotation (`_b: Foo | Bar`), `self._backend.method()`
calls had no way to bind to the concrete backend class.

This change extends `_collect_class_attr_types` to:

* parse class-level annotations as a list of types (BinOp `A | B` or
  `Union[A, B]` flatten into operands)
* walk `__init__` for `self.X[: T] = Y(...)` assignments, including
  inside `if/else` (and try/with/for/while) branches, collecting
  annotation-or-constructor types from every branch

The resolver now exposes a `_try_multi_self_attr` helper that emits one
CALLS edge per candidate type when `self.X.tail` resolves to multiple
classes. Single-type cases still go through the existing R2 path.

attr_types schema migrated from dict[str, str] to dict[str, list[str]]
with back-compat handling in the resolver (legacy single-string values
are still accepted).
8 fixture-based tests for resolver R3:
* if/else with two annotated types -> both methods get edges
* if/else with same annotation -> annotation wins over RHS constructor
* class-level `Foo | Bar` -> both methods get edges
* class-level `Union[Foo, Bar]` -> both methods get edges
* if/else without annotation -> falls back to RHS constructor names
* single-branch annotated assignment -> R2 regression
* walrus operator -> silently skipped, no crash
* method on undeclared self attr -> no phantom edge
@smochan smochan merged commit 5f727a0 into main Apr 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant