Repository navigation
Description
Version
Built from source, #2361 head (8116eaaa, on main @ 2278498, v0.11.0-186)
Platform / Install channel / Binary variant
Linux (x64) / Built from source / standard
What happened, and what did you expect?
Follow-up to #2154 / #2361, as agreed in the review there. #2361 gives a Scala companion object the Foo$ QN, and the import resolver / registry accept Foo$ for Foo — but only for the top-level owner of a member path. When the companion is an inner owner, import a.Outer.Inner.member and Outer.Inner.member(...) fail closed (per #2360's member rule) instead of binding Inner$.member.
Expected: import pkg.Outer.Inner.member → IMPORTS edge to pkg.Outer.Inner$.member, and Outer.Inner.member() → CALLS edge to the same node, i.e. the $ tolerance applied at every segment of the owner path, not just the first.
Measured on twitter/finagle (ca472de) in #2361: exactly two IMPORTS edges are lost to this, import Types.Name.Unnamed (ClientDispatcherSpec.scala → postgresql.Types.Name$.Unnamed) and import Mux.Server.SessionF (NonNegotiatingServer.scala → Mux.Server$.SessionF). Both were bound before #2361 only because the object's members collided into the class QN. Small on that corpus, but any codebase that nests object inside object and imports through the pair hits it.
Reproduction
- Dummy snippet, three files in one package:
// t/Shapes.scala
package geo
object Shapes {
class Circle(val r: Double)
object Circle { def unit(): Circle = new Circle(1.0) }
}// t/Use.scala
package geo.app
import geo.Shapes.Circle.unit
object Use { def go(): Unit = unit() }// t/Use2.scala
package geo.app
import geo.Shapes
object Use2 { def go(): Unit = Shapes.Circle.unit() }-
Command:
codebase-memory-mcp cli index_repository --repo-path /tmp/repro, then read theIMPORTS/CALLSedges of the project (search_graphforunit, or the SQLite file directly). -
Result on the fix(scala): keep companion objects distinct from their classes (#2154) #2361 head (
8116eaaa): the nodet.Shapes.Shapes.Circle$.unitexists (the companion QN is right), butUse.scalahas no IMPORTS edge forimport geo.Shapes.Circle.unit(on the feat(scala): parse import selectors and resolve package-aware imports (#2153) #2360 head it boundShapes.Circle.unit, i.e. the pre-$collided node);Use.go → Circle$.unitandUse2.go → Circle$.unitstill exist, but only throughunique_name— on the feat(scala): parse import selectors and resolve package-aware imports (#2153) #2360 head both wereimport_map. They survive becauseunitis unique in this three-file repo; in a corpus with severalunitmethods the same calls are either lost or bound to the wrong one (and the fix(scala): suppress weak short-name matches for receiver calls #2366 guard, correctly, does not rescue a weak match forShapes.Circle.unit()).
Expected: IMPORTS
Use.scala → Shapes.Circle$.unit, and both CALLS atimport_mapconfidence toShapes.Circle$.unit.
Where it is
pass_pkgmap.c, member-import lookup (import pkg.Owner.rest): the top-levelOwnercomes from the index, then<Owner QN>$.<rest>and<Owner QN>.<rest>are tried, with$only on that top-level segment; forShapes.Circle.unitthe node that exists isShapes.Circle$.unit, and neither candidate names it.registry.c,resolve_same_module/resolve_import_map: the companion probe suffixes the owner prefix once (companion_known(owner)→<owner>$.<rest>);qn_seg_matchesalready compares segment by segment and would acceptInner$forInner, but the two exact-lookup probes above do not enumerate the 2^k owner-segment combinations (in practice k ≤ 2–3, and only segments that are registered companions need the$variant, so the enumeration is small and gated).
Proposed fix
In both places, walk the owner segments left to right and at each segment take the $ form when companion_known() says the prefix so far names a registered Scala companion (one hash probe per segment); the non-$ form otherwise. That keeps the gate from #2361 (nothing changes for Java/JS/TS) and adds no work for paths without companions. Tests: an edge_imports case for the snippet above (IMPORTS + CALLS), and a registry case for Outer.Inner.member with Outer.Inner$ registered from a .scala file.
I can send this as a small PR stacked on #2361 if wanted.
Confirmations
- I searched existing issues and this is not a duplicate.
- My reproduction uses shareable code (a dummy snippet or a public OSS repository), not proprietary code.
Activity
DeusData commentedon Oct 19, 2026
Thank you for isolating nested companion ownership and showing the weaker unique-name fallback separately from proper import resolution. The proposed segment-by-segment handling needs review before I can give direction on a stacked PR.
DeusData commentedon Oct 25, 2026
Thank you for your patience. Nested companion segment handling remains the review question before giving direction on a stacked PR. Sorry for the delay; I’ll follow up soon.