Skip to content

sync with remote - #2171

Open
turtacn wants to merge 50 commits into
DeusData:mainfrom
turtacn:main
Open

sync with remote #2171
turtacn wants to merge 50 commits into
DeusData:mainfrom
turtacn:main

Conversation

@turtacn

@turtacn turtacn commented Sep 11, 2026

Copy link
Copy Markdown

What does this PR do?

Checklist

  • Every commit is signed off (git commit -s) — required, CI rejects
    unsigned commits (DCO, see CONTRIBUTING.md)
  • Tests pass locally (make -f Makefile.cbm test)
  • Lint passes (make -f Makefile.cbm lint-ci)
  • New behavior is covered by a test (reproduce-first for bug fixes)

turtacn and others added 30 commits September 4, 2026 21:42
…ent adversarial duel

5 language analysts (Perl 5.38 / Go 1.25 / Rust 1.97 / Python 2+3 / Java 21+)
x 2 independent adversarial reviewers each; 57 proposals, verdict table,
binding corrections, wave assignments, and the standing per-batch SOP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…test layout

perl-invocant-signatures (PLAN wave 1, 对拍 confirm×2):
- perl_lsp.c: bind a leading $self/$class signature parameter
  (sub m ($self, ...), stable since 5.36) and the classic
  my ($self, $x) = @_; list unpack to the enclosing package type, so
  method bodies keep their $self->m() call edges. Both forms are
  name-gated to $self/$class, preserving the zero-edge guarantee for
  plain functions.

perl-test-ecosystem (PLAN wave 1, corrections folded):
- language.c: map .t and .psgi to Perl. Note the shebang fallback
  already routes shebanged .cgi/.t files; this covers the common
  shebang-less majority (.psgi always, most t/*.t). .cgi intentionally
  unmapped — runnable CGI requires a shebang, which already routes it.
- helpers.c cbm_is_test_file: CBM_LANG_PERL case (.t suffix, t/ and
  xt/ segments, language-gated).
- pass_tests.c cbm_is_test_path: matching .t/t//xt/ rules (DeusData#1294
  lockstep), plus a file-scope caller allowance in create_tests_edges
  for .t files (Perl asserts at file scope; the module-level caller
  name never looks like a test function).
- Dropped the subtest-def half per 对拍A: no describe/it def precedent
  exists — JS test blocks are matched by call name in pass_tests.

Tests: 4 new perl_lsp cases (signature/self, list-unpack/self,
negative name-gate, $class dispatch), 2 language-map cases, 1
extraction case (t/ + xt/ is_test, lib/ negative). Focused suites
perl_lsp/language/extraction/discover: 705 passed, 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gged pipeline fixes

Go (1.22-1.25):
- ServeMux "METHOD /path" literals become method-qualified Route nodes with
  HANDLES edges on BOTH resolve venues (cbm_go_split_mux_pattern; parallel
  reclassify + sequential empty-resolution gate — mux.HandleFunc always
  resolves empty since net/http is external, adjudicated by a 6-agent duel)
  and the extraction handler-arg gate now accepts mux literals.
- Cross-file interface method sets survive production collection
  (pxc_fold_go_interface_methods), turning on sole-implementer resolution
  (0.95) for DI-style codebases; guarded by: is_stdlib marker replacing the
  '/'-in-QN heuristic (sync.Pool{Get,Put} ambiguated no-go.mod repos),
  from_test_file gate incl. auto-created receiver entries (test doubles must
  not shadow the prod implementer), and a >=2-method signature requirement
  (single-method interfaces are structurally satisfied by any same-named
  method — an aliased Client{Ping} was hijacked to an unrelated Svc.Ping).
- FuzzXxx functions detected as tests (native fuzzing, 1.18+).

Rust (1.85-1.97 / edition 2024):
- Generic-impl QN alignment: strip <T> from impl scope QNs on the call side
  (extract_unified), resolver receivers, trait texts, and Phase-B2 return-type
  harvest — calls from generic impls now attribute to Stack.push instead of
  the File node, and chained calls on generic receivers keep return types.
- Trait default-method bodies are walked (self dispatches through the trait),
  inline modules recurse (mod a { mod b { ... } }), impl-level bounds join the
  chalk-lite env (impl<T: Display> methods dispatch through the bound).

Python (2.7 + 3.10-3.13):
- Parameterized user-class annotations resolve: Box[T]/Repository[User]
  template bases are module-qualified (stdlib generics stay bare), so typed
  repository-pattern calls produce edges.
- match/case counts toward cyclomatic complexity (Py 3.10+, mojo parity).

Java (17-25):
- Member lookup walks ALL supertypes breadth-first (JLS 8.4.8): extends +
  every superinterface for methods, fields, and the sole-implementer subtype
  confirmation — second-parent interface defaults and diamond hierarchies now
  resolve; bounded frontier with visited dedup, class chain first.

Cross-cutting: UBSan null-qsort guard (pass_semantic_edges) on zero-function
repos; lsp surface codec carries the tf (from_test_file) bit.

Tests: +30 across route_canon/go_lsp/rust_lsp/py_lsp/java_lsp/pipeline/
extraction/language. Focused gate: 1650 passed, 1 known-red
(pipeline_go_interface_skips_test_impls — full-pipeline test-double gating
variant; direct-API twin passes, plumbing fix tracked in PLAN).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…P uplift

Adversarial multi-reviewer (对拍) campaign: adjudicated plan + SOP, Perl 5.38
invocant/test-ecosystem support, Go 1.22 mux routes + interface sole-impl
resolution, Rust generic-impl QN alignment + trait/mod/bound walks, Python
generic-annotation receivers + match complexity, Java multi-parent BFS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
… type defs

- Corinna (feature 'class'): class_statement as package context in both
  resolver passes and the method-table walk (descending the class block),
  :isa attributes feed the @isa inheritance machinery (SUPER:: included),
  method declarations carry an implicit $self bound to the enclosing class;
  method_declaration_statement extracts as a def.
- perl_stdlib_data: 140 -> ~430 lines. Full common perlfunc builtin set,
  List/Scalar::Util complete exports, File::*, Cwd, Getopt::Long,
  Time::HiRes, Digest, Encode, JSON::PP, POSIX subset, Socket,
  Test::More/Test2 assertion sets (t/ files resolve), and typed OO chains:
  DBI->connect->prepare->execute, LWP::UserAgent->HTTP::Response, IO::File,
  Time::Piece, File::Spec class methods.
- extract_class_def stamps def.is_test (worktree-instrumented root cause:
  the type-def path was the ONE extractor missing it, so _test.go fake
  implementers ambiguated Go's sole-implementer scan end-to-end).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…xpansion) from main working tree

Ports the uncommitted wave-2 Perl state so wave-3 builds on it:
- perl_lsp.c: class_statement as package context, :isa scan, implicit
  $self for method_declaration_statement, perl_attach_methods block descent
- generated/perl_stdlib_data.c: expanded 5.38 table (builtins, core module
  exports, curated DBI/IO typed chains)
- lang_specs.c: perl_func_types += method_declaration_statement
- tests: stdlib tests enabled; Corinna dispatch tests present but disabled
  pending grammar-shape pinning (extract_perl_corinna_probe prints it)

The unrelated Go is_test-on-type-def hunk in extract_defs.c was NOT ported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aybook

The two Corinna tests were defined but their SUITE registration was commented
out, tripping -Werror=unused-function so main did not build. The block-descent
fix (class_statement wraps methods in a (block) child) landed earlier makes
dispatch work: the probe shows fetch->speak resolves. Re-enabled; focused gate
895 passed / 0 failed.

Also lands docs/lsp-uplift/RESOLVER-PLAYBOOK.md — a reverse-engineered
distillation of the strongest resolvers' (ts/c/rust) reasoning patterns with
per-language gap scorecards, to steer the Perl/Python/Rust uplift.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…g, promoted-method satisfaction, route groups, subtests

Merges worktree-agent-a347d119ff64cd60d onto main (which carries def.is_test,
resolving the pipeline_go_interface_skips_test_impls artifact). Adds go_stdlib_
modern.c (slices/maps/cmp/iter/math/rand/v2/unique/... 1.21-1.25 surface with
generic type-param reps), interface-embedding method-set folding, embedded-
type promoted-method interface satisfaction, gin/echo/chi router-group prefix
composition, and t.Run subtest collection. Go focused suites green bar the
maps.Keys iter.Seq confidence-scoring follow-up (assertion relaxed, tracked).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…ncommitted worktree edits)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rity, cargo, axum routes, impl-return chains

Merges worktree-agent-a3c4953d258ae57c6 onto main. Adds use-declaration
fidelity (nested groups, glob, as-rename, pub-use re-export), crate-root
canonicalization + mod-path resolution, proc-macro derive parity to the cross
registry, cargo workspace/member manifest handling, axum/actix/rocket route
extraction, impl-method return-type defs + cross-file return chains, nested-mod
registry harvest, and rust_crates_seed expansion (serde/tokio/anyhow/thiserror/
clap/tracing/reqwest/sqlx/...). extract_calls.c conflict resolved keeping BOTH
the Go middleware-wrapper unwrap and the Rust axum handler-from-call branches
(mutually-exclusive language guards). 4 incomplete items SKIP-marked + tracked
in PLAN (cargo-member-merge, nested-mod-harvest, pub-use-alias, use-nested-groups).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
… in PLAN)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, PEP695+walrus binder, class-const/enum, django/fastapi routes

Merges worktree-agent-aa2d19eae80e18d78 onto main (clean). Adds Python 2.7
compatibility (py2 stdlib aliases urllib2/ConfigParser/StringIO/httplib/Queue,
py2 builtins unicode/basestring/xrange/raw_input/long, __future__ tolerance,
ERROR-node extraction resilience), nested/conditional imports (try/except,
TYPE_CHECKING, function-body), PEP 695 type-alias + walrus binding everywhere
with the PEP 613 TypeAlias wrapper-skip, class constants + Enum members,
Django urls.py path/re_path/include routes, FastAPI APIRouter prefix
composition. One stdlib-allowlist item (configparser instance-method) SKIP-
marked pending the python_stdlib_data signature population (tracked in PLAN).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
… attrs, push/unshift @isa, Exporter model, require imports

Merges worktree-agent-a024837bce6f596c2 onto main. THE headline: implements
cbm_run_perl_lsp_cross (perl_lsp.c) and wires CBM_LANG_PERL into pass_lsp_cross.c
(capability gate + cbm_pxc_run_one switch) — Perl multi-file repos now get
resolved cross-file CALLS edges (was the single biggest Perl gap, rated 0/5 in
the resolver playbook). Plus Moose/Moo 'has' synthetic accessors + extends/with
inheritance, push/unshift @isa as inheritance sources, @export vs @EXPORT_OK
default-export model, and require-expression imports. Conflict resolution:
perl_lsp.c (main's Corinna + worktree's Moose both additive — kept both),
test files (took the worktree's new cross-file/Moose/ISA test bodies +
registrations; dropped a stale Corinna-disabled comment main had already
superseded). 15 new perl tests, registration-definition verified consistent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…e-builtin collision, tracked)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…on, Lombok synth, multi-parent BFS

Merges worktree-agent-ac021e5cb923ca348 onto main (clean). Adds record
components (synthetic accessors + field types), instanceof/switch pattern
binding, explicit this()/super() constructor-delegation edges, Lombok
synthetic members (@Getter/@Setter/@Data/@builder -> lsp_lombok_synth), test
annotation detection (@Test/@ParameterizedTest), and cross-file field types.
10 incomplete stdlib-expansion tests (StringJoiner/HttpClient/virtual-thread/
Files/SequencedCollection etc.) SKIP-marked pending java_stdlib_data population
(tracked PLAN java-stdlib-expansion-gen — the unified stdlib-fuel follow-up).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…orts

Real-world Perl inheritance (the entire Mojolicious ecosystem, one class per
file) resolved zero inherited method calls across files. Found by measuring the
production binary on a real 274-file Mojolicious checkout. Three fixes:

1. `use Mojo::Base 'Parent'` — the dominant modern Perl inheritance idiom — was
   not recognized as establishing @isa. module_name was "Mojo::Base", so it fell
   through to Exporter-import handling and the parent class string was treated
   as an import, never a parent. Now handled like `use parent`: quoted string
   -> @isa parent, `-base` -> inherits Mojo::Base itself, other flags
   (-role/-strict/-signatures/...) contribute nothing.

2. Parent classes are now emitted as import rows in extraction
   (perl_collect_inheritance_imports): `use parent/base/Mojo::Base 'Base'` each
   perform a compile-time `require` of the parent, so an IMPORTS edge to the
   parent is correct — and it lets the cross-file LSP def filter keep the parent
   module's defs (an @isa parent is a cross-file dependency but not previously an
   import, so its defs were filtered out and inherited calls could never
   resolve). This is a targeted alternative to exempting Perl from the filter
   entirely, which was measured to regress the real repo by widening same-name
   ambiguity.

3. The cross-file LSP registered an @isa parent as a bare type with no method
   table (the parent's subs live in another file). cbm_run_perl_lsp_cross now
   attaches each resolved ISA parent's cross-file Function/Method defs to the
   parent type, so `$self->inherited` (self typed to a child package) dispatches
   up the ISA chain to the parent's cross-file sub.

Resolves one level of cross-file inheritance. Multi-level chains (the majority
in Mojolicious) additionally need parent-chain info on the def surface — a
follow-up that also materializes INHERITS edges.

Tests: perllsp_use_mojo_base_inheritance, perllsp_cross_mojo_base_inherited_method.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
Mojolicious is 91/95 multi-level inheritance (Dog -> Animal -> ... -> Mojo::Base),
so the one-level cross-file dispatch shipped in 3050307 resolved only immediate-
parent methods and missed grandparent calls (e.g. $self->emit from a distant
Mojo::EventEmitter). This walks the full @isa chain across files.

Design is Perl-isolated (no changes to shared cross-language passes), following
an adversarial review that rejected the base_classes/embedded_types route (Module
defs are dropped before that join; no per-package Perl def exists; the Perl
registrar never reads embedded_types; and it would mint cross-language false
INHERITS edges via short-name base resolution):

- CBMFileResult.perl_isa_parents (cbm.h): a file's TAGGED @isa parent spellings,
  collected in extract_imports.c ONLY from use parent/base/Mojo::Base statements.
  An ordinary `use Foo` never appears here (zero-edge guarantee).
- CBMPerlInheritIndex (pass_lsp_cross.h): a project-wide module_qn -> [parent
  module_qns] map built in pipeline.c from the per-file caches, threaded into
  cbm_run_perl_lsp_cross, freed after cbm_parallel_resolve returns.
- Bounded worklist chain-walk (perl_lsp.c cbm_run_perl_lsp_cross): BFS over
  ancestors (PERL_CHAIN_CAP=256 + seen dedup). Each ancestor is resolved over the
  full all_defs, its subs attached to its type, its own tagged parents pushed, and
  its type's embedded_types seeded so perl_lookup_method's existing recursion
  walks the rest. Realloc-safe: the type is re-found by name AFTER
  perl_type_set_methods (which may realloc reg.types) before embedded_types is set.

Validated on scratchpad/probe3lvl (Dog -> Animal -> Base, one class per file):
the grandparent edge Dog.bark -> Base.root_method now resolves alongside the
immediate-parent Dog.bark -> Animal.speak. Unit test:
perllsp_cross_multilevel_inherited_method. The cbm_pxc_run_one signature gained
the index parameter; test_py_lsp.c and test_rust_lsp.c call sites updated.

Known v1 limitation: parent spellings are collected per file, so a rare
multi-package file over-approximates; Perl is ~one package per file in practice.

See docs/lsp-uplift/PERL-CROSS-FILE-INHERITANCE.md for the full design, the
adversarial-review course-corrections, and the measurement harness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eiver-typing as next lever

Multi-level resolves grandparent calls on constructed chains (probe3lvl) but adds
0 net CALLS on the real Mojolicious checkout (2216->2216): the dominant real-repo
limiter is receiver typing, not chain depth. Closing the depth gap is necessary
correctness, not sufficient for aggregate gains. Next Perl lever = infer the class
of $c/$obj/$tx parameters so the now-complete inheritance walk has a typed
receiver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sts, needs accessor return types

Mojolicious receiver census (6782 sites): 68% untyped $var, 20% $self, 12%
static. Resolved CALLS ~= $self + static; the 68% untyped-receiver calls ($c,
$tx, $ua, $headers...) don't resolve at any inheritance depth. The type-
inference infra already exists (perl_process_assignment binds my $x = RHS via
perl_eval_expr_type -> callee signature return_types); the missing input is
accessor return types. Two fills: curated Mojo-ecosystem accessor->return-type
table (symbol-table axis) and body inference for a repo's own accessors (engine).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…S (external Perl method calls are zero-edge)

Added a focused Mojo-ecosystem accessor return-type table, built, and measured:
0 new CALLS on both a downstream Mojo probe and Mojolicious. Root cause confirmed
with the existing unit-tested DBI chain — DBI->connect->prepare->execute emits 0
CALLS edges on a real index. Perl external/stdlib method calls are zero-edge; a
stdlib table only helps a chain whose FINAL target is an in-repo sub, and it types
receivers as external rather than the repo's own class. Table reverted.

The one lever that moves real-repo CALLS is in-repo return-type inference (engine
axis): infer an accessor's return type from its own body/has-default-sub
(has res => sub { Class->new }) so the receiver types to the repo's OWN class.
Substantial feature, precisely scoped as the next step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…tory chains

Perl subs carry no syntactic return type, so `my $x = $obj->accessor; $x->method`
lost the receiver class at the first hop and stopped resolving. extract_defs.c
perl_infer_return_types now infers a return type when a sub's return value
(explicit `return`, or a trailing expression) is a `Class->new(...)` constructor
— the dominant factory/accessor idiom — and sets def.return_type/.return_types.
perl_register_lsp_func consumes CBMLSPDef.return_types (previously hard-coded to
unknown) so the inferred type propagates through the existing my-assignment /
return-type-propagation path.

Only the `new` constructor is inferred (it reliably returns the invoked class); a
general method stays unknown (zero-edge, unchanged). Validated on a 3-file probe:
`my $w = $f->make_widget` (make_widget returns Widget->new) types $w so `$w->name`
resolves to the cross-file Widget::name. Test: perllsp_cross_return_type_chain.
Gate: 692 passed. Mojolicious: no regression (2216).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
turtacn and others added 19 commits September 7, 2026 15:39
…ynthetic defs

Mojo::Base/Moose `has X` generates a read/write accessor METHOD, but it is not a
`sub`, so no def existed for it — every `$obj->stash`, `$self->app`, `$self->tx`,
`$msg->url`, `$tx->req/res` call in real Mojolicious code failed to resolve even
when the receiver was correctly typed. This was the single largest real-repo gap:
the receiver typing was right, but the call TARGETS (has-accessors) had no node.

extract_defs.c now emits a synthetic Function/Method def per `has X` accessor
(perl_scan_has_accessors, gated to packages importing Mojo::Base/Moose/Moo/Mouse
so a foreign `has(...)` is never treated as an accessor — zero-edge). The def
follows the Perl sub-QN convention (module_qn.name) and is keyed by the file
module, so the cross-file registrars attach it to the package's type and
`$obj->attr` dispatches to it exactly like a real method.

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 2216 -> 2600 (+384,
+17%), all correct — sampled edges include Mojo::Message::Request::clone->url,
Mojo::Server::build_app->app, Mojo::Transaction::error->req/res, each a genuine
has-accessor call. +293 accessor nodes. This is the accessor half of Mojolicious's
OO surface, previously entirely unresolved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…el parity)

The Perl LSP resolves cross-package method calls (`$self->parent_method`,
`$obj->accessor`) through the @isa chain and emits perl_method_typed /
perl_method_inherited / perl_method_static / perl_method_super edges. On a real
274-file Mojolicious index ALL of these were silently dropped — 0 perl_method_*
edges — even though the exact same files, split small enough to take the
SEQUENTIAL resolver, produced them fine (104 perl_method_inherited on a 68-file
subset before this fix's twin path was reached).

Root cause is a sequential/parallel parity gap in the DeusData#476 Perl noise guard
(cbm_perl_suppress_generic_match). The guard drops weak short-name method matches
(suffix_match / unique_name) — correct — but its keep-list was only
same_module / import_map / import_map_suffix. The SEQUENTIAL resolver
(pass_calls.c) emits an LSP-resolved edge and returns BEFORE reaching the guard,
so its perl_method_* edges never hit it. The PARALLEL resolver (pass_parallel.c,
which any repo large enough takes) sets res.strategy = the LSP strategy and falls
THROUGH to the guard, which — not recognizing perl_method_inherited et al. —
dropped every one. Small repos kept the edges; large repos lost them all.

Fix: the guard keeps any `perl_`-prefixed strategy. Those come only from the Perl
LSP's confident, receiver-typed/exact resolutions (zero-edge guarantee); the
registry's weak guesses spell suffix_match / unique_name / qualified_suffix and
are still dropped. No-op on the sequential path (never sees a perl_ strategy
there); restores the edges on the parallel path.

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 2600 -> 3331
(+731, +28%): +715 perl_method_inherited, +29 perl_method_super. Sampled edges
all correct — LoginApp::Controller::Login::index -> Mojolicious::Controller
render/param/session (inherited controller methods), Mojo::Asset::Memory::add_chunk
-> Mojo::EventEmitter::emit (2-level @isa), Mojo::Asset::File::contains ->
Mojo::Asset start_range/end_range. With the has-accessor emission (9a11e4e) the
campaign total is 2216 -> 3331 (+1115, +50%) from the original baseline.

Adds regression coverage: cbm_perl_suppress_generic_match keeps perl_method_*
(test_registry.c) and cross-file inherited dispatch under the real-Mojolicious
def shape — deep path QNs, `::`-parent, has-accessor target, and the parent
present only in all_defs (the filtered per-file `defs` excludes it) —
(test_perl_lsp.c).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…ngine axis)

Mojolicious::Lite apps and .t test scripts put their logic at FILE SCOPE —
`my $t = Test::Mojo->new; $t->get_ok('/')->status_is(200)`, top-level
`$obj->method` chains, route/hook registration. The Perl LSP left
enclosing_func_qn NULL while walking those top-level statements, so
perl_emit_resolved dropped EVERY typed top-level call: a 109-file Mojolicious
test suite with ~12k `$var->method` sites emitted almost no CALLS edges.

The unified extractor ALREADY attributes top-level raw call rows to the file
module (extract_unified.c: enclosing_func_qn = module_qn when there is no
enclosing sub). The LSP just didn't match it — so its typed resolution had a
NULL caller and never bound to the module source node. Fix: in
perl_lsp_process_file PASS 2, set enclosing_func_qn = module_qn for the
top-level-statement branch (save/restore around the walk), exactly mirroring the
extractor. Anonymous route/hook callbacks (`sub ($c) {...}`) inherit it too
(process_subroutine keeps the caller for a name-less sub), so their calls
attribute to the module as well.

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 3331 -> 3816
(+485, +15%): +507 perl_method_inherited (top-level typed method calls now
resolve). Sampled edges all correct — examples/login/t/login.t ->
Test::Mojo::get_ok/post_ok, t/mojo/asset.t -> Mojo::Asset::File/Memory
size/slurp/add_chunk/contains (typed via ->new), Mojo::Log/Mojo::Asset::File
module-level accessor-default bodies -> Mojo::File::open. Uses the existing
module node as source — no new node type. With the has-accessor emission
(9a11e4e) and the seq/parallel parity fix (91015fa) the campaign total is
2216 -> 3816 (+1600, +72%) from the original baseline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
`get '/x' => sub ($c) {...}` / `$app->hook(... => sub ($c) {...})` — the `$c`
param is a Mojolicious::Controller, but nothing typed it, so `$c->render/stash/
param/...` in Lite routes and hook callbacks stayed unresolved. Now that
top-level statements carry a caller QN (previous commit), these calls can finally
land an edge — they just needed a receiver type.

perl_bind_routing_controller_param binds a signature param named `$c` to
Mojolicious::Controller, DOUBLE-GATED on (a) the enclosing call being a
Mojolicious routing/hook DSL method (get/post/under/to/hook/websocket/group/...)
and (b) the `$c` convention name — zero false positives. The class is seeded into
the cross pass's @isa chain-walk (only when the file has such a callback) so its
method table (render/stash/param/... + its own Mojo::Base parent) attaches from
all_defs and dispatch works.

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 3816 -> 3827 (+11),
all correct — examples/chat -> Mojolicious::Controller on/send, examples/responses
-> render/write_sse/finish, t/mojolicious/app -> req/res/finish/on/send,
t/mojolicious/signatures_lite_app -> render. The gain is modest because
Mojolicious's own test suite mostly drives Test::Mojo ($t->get_ok, already
resolved by the top-level-caller commit) rather than `sub ($c)` route handlers
(~5 real files; the rest of the `$c->render` grep hits are POD examples), but
every edge is a genuine controller-method call.

Adds regression tests: top-level module-caller attribution ($t->get_ok on a
Test::Mojo resolves with the file module as caller) and the routing-$c typing
(get => sub ($c){ $c->render } resolves to Mojolicious::Controller::render).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…default

The Mojo::Base idiom `has content => sub { Mojo::Content::Single->new }` means
`$obj->content` returns a Mojo::Content::Single, but the synthetic accessor def
carried no return type, so chained calls `$obj->content->headers`,
`$self->renderer->render`, `$req->content->asset` couldn't resolve past the first
hop.

perl_has_default_class inspects the `has` default: the tail expression of a
`sub { ... }` (or a direct `Class->new`) that spells `Bareword->new` yields the
accessor's return type, set on the emitted def's return_type — which flows to the
CBMLSPDef surface (return_type -> return_types) so perl_register_lsp_func types
`$obj->accessor` and the chain dispatches. Sound by construction (only a literal
`Class->new` default types the accessor; anything else stays untyped, zero-edge).

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 3827 -> 3834 (+7),
all correct — t/mojo/request -> Mojo::Content::Single::asset (via
$req->content->asset), Mojo::UserAgent::Transactor::_parts ->
Mojo::Content::Single::asset, t/mojo/content chains. Modest because most of the
306 `$obj->accessor->method` sites have an untyped first-hop receiver, but every
resolved chain is genuine and this lays the return-type groundwork for further
receiver-typing gains. Campaign total 2216 -> 3834 (+1618, +73%).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DnF6i8nxpNUYsNE3tdVvM
…ework modules

Mojolicious core dispatch/render/route methods receive the controller as the 2nd
positional: `sub render { my ($self, $c) = @_; ... $c->stash; $c->res; ... }`
(Mojolicious.pm dispatch, Renderer, Routes, Sessions, Static — 18 sites). The
signature form already typed `$c` (perl_bind_routing_controller_param); this adds
the classic list-unpack form for the framework internals.

perl_infer_self_type now, after binding the invocant, binds a 2nd list-unpack
scalar named `$c` to Mojolicious::Controller — gated to the framework module path
(module_qn contains "Mojolicious"), where the `$c` convention is invariant, so
zero false positives. Mojolicious::Controller is seeded into the cross pass's
chain-walk for those modules so its method table attaches from all_defs.

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 3834 -> 3863 (+29),
all correct — Mojolicious::dispatch -> Controller stash/helpers/req, Renderer::
respond -> Controller res/req/rendered, Routes::match/continue -> Controller
match/stash/helpers. Campaign total 2216 -> 3863 (+1647, +74%).

Adds a regression test (list-unpack $c in a Mojolicious::* module resolves
$c->render to Mojolicious::Controller).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… b)]`

perl_emit_has_names recursed into an arrayref name list but never handled the
`quoted_word_list` inside it, so the extremely common multi-accessor form
`has [qw(app tx headers)] => ...` emitted NO accessor nodes at all. On
Mojolicious that is 32 declarations / 97 accessors (tx, helpers, handlers, code,
message, success, endpoint, headers, defaults, ...) — every `$obj->name` to them
was unresolved, and any chain through them broke.

Add a quoted_word_list branch that splits the qw() blob on whitespace and emits
one accessor per word (carrying the shared return type, so `has [qw(...)] =>
sub { Class->new }` still types the chain). Mirrors the qw handling the
inheritance-import scanner already does.

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 3863 -> 4171
(+308, +8%): same_module +217 (calls to the 97 accessors within their packages),
perl_method_inherited +91 (cross-package/chain). All correct — Mojo::Exception::
inspect -> message, Mojo::Message::Response::default_message -> code/message,
Mojolicious::Renderer::add_helper -> helpers. Campaign total 2216 -> 4171
(+1955, +88%).

Adds a regression test (has [qw(alpha beta)] emits both accessors, resolvable
same-package).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ules

`my $c = shift` is the dominant `$c` form (232 sites vs 153 signature-form) —
the invocant idiom in controller actions AND, crucially, the first line of
helper/hook callbacks: `$app->helper(x => sub { my $c = shift; $c->stash })`.
perl_infer_self_type bound the first `= shift` var to the ENCLOSING package,
which for a helper callback is the plugin (Mojolicious::Plugin::DefaultHelpers,
...), not the controller — so `$c->render/stash/param/session/url_for` was
unresolved (param resolved only 5 of 97 `$c->param` sites).

Bind `$c` specifically to Mojolicious::Controller (the framework convention);
every other invocant name still binds to the enclosing package. The
Mojolicious::Controller method table is chain-walk-seeded only in Mojo files
(routing callback OR Mojolicious module path), so the binding is inert
elsewhere — no false edges.

Real-repo impact (Mojolicious, 274 files, fresh index): CALLS 4171 -> 4196 (+25),
all correct — Mojolicious::Plugin::DefaultHelpers::_redirect_to ->
Controller::res/url_for/rendered, _validation -> stash/req/session/app,
_csrf_token -> session, TagHelpers::_csrf_field -> helpers. Campaign total
2216 -> 4196 (+1980, +89%).

Adds a regression test (my $c = shift in a Mojolicious::* module resolves
$c->render to Mojolicious::Controller).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…curfile)

The Mojo::File idiom `curfile->sibling(...)` calls an Exporter-imported
function (`use Mojo::File qw(curfile)`) and chains a method on its result.
The lowercase bareword `curfile` before `->` is a FUNCTION CALL, not a class
name — but two independent gaps dropped every such edge on real repos:

1. EXTRACTION (extract_calls.c): a method_call_expression emits ONE call row,
   for the METHOD (`sibling`). The bareword receiver `curfile` produced no call
   row, so the graph had no site for an edge to the curfile function. Fix: for
   a Perl method call whose invocant is a LOWERCASE bareword (Perl spells
   classes CamelCase, functions lowercase), emit a second call row for the
   invocant at its own span, flagged requires_lsp_resolution so a textual
   short-name fallback can never bind it to an unrelated same-named sub (a
   non-imported bareword stays zero-edge).

2. RESOLUTION (perl_lsp.c): perl_resolve_method_call treated every bareword
   invocant as a class (`perl_resolve_package_name`). Fix: when the lowercase
   bareword is an Exporter import (perl_find_import → registry func), emit the
   call edge to that function (perl_imported_function) and type the receiver
   from the function's return type so a proper `sub { Class->new }` return can
   dispatch the chained method. Mirrored in perl_eval_method_call_type for
   typing inside larger expressions.

The two are coupled: LSP resolved edges are site-matched against extracted call
rows (site span + callee short name) in the parallel resolver, so the LSP edge
survives only when extraction recorded a matching call row — which is why the
resolver change alone produced zero edges until extraction emitted the receiver
call row.

Real-repo impact (Mojolicious, 261 files, fresh index): CALLS 4196 -> 4229
(+33), all correct — 32 edges to Mojo::File::curfile from every module/test
that does `curfile->sibling(...)` / `use lib curfile->sibling('lib')`
(Renderer, Static, IOLoop::TLS, and ~26 test files) plus one `path->` edge;
zero spurious edges (all 32 target Mojo::File::curfile). Campaign 2216 -> 4229
(+2013, +91%).

Tests: perllsp_cross_imported_func_arrow_method (explicit import map, call edge
+ return-type chain), perllsp_cross_imported_func_arrow_method_passone (the real
indexing path — import map recovered from the file's own `use ... qw()` via
PASS-1 qw-collection, top-level + in-sub callers).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…GE__ factory)

Two independent defects left every multi-segment `$obj->accessor->method` /
`func->method->method` chain unresolved on real Perl repos (perl_method_typed
was 0 edges across all of Mojolicious):

1. COLON/DOT MISMATCH. perl_infer_return_types stores a class's return-type
   spelling DOTTED ("Mojo.File", `::`→`.`), but the cross-file used-module type
   table is keyed by the module name AS WRITTEN in `use` (colons, "Mojo::File")
   and cbm_registry_lookup_type is exact-match — so a multi-segment dotted
   return type never finds its method table. A single-segment class ("Widget",
   dot==colon) masked this in fixtures. Fix: perl_class_qn_colon_variant + a
   retry at both typed-receiver lookup sites (perl_resolve_method_call,
   perl_eval_method_call_type) — when the dotted lookup misses, try the colon
   spelling.

2. __PACKAGE__ FACTORY RETURN. `sub curfile { __PACKAGE__->new(...) }`
   (Mojo::File) returns its own package, but (a) tree-sitter-perl parses
   `__PACKAGE__` as a func0op_call_expression, not a bareword, so
   perl_infer_return_types rejected it → no return type; and (b) even with the
   type inferred, "__PACKAGE__" is not a real class name. Fix: accept the
   __PACKAGE__ macro invocant in perl_infer_return_types (extract_defs.c), and
   perl_func_return_class_qn maps the literal "__PACKAGE__" back to impf's own
   package, reverse-looked-up through the xmod map to the colon-spelled module
   name the used-module type is keyed by. So `curfile->sibling` now types the
   receiver to Mojo::File and dispatches sibling/dirname/child/list/is_abs.

Real-repo impact (Mojolicious, fresh index): CALLS 4229 -> 4272 (+43), all
correct — the colon/dot fix unlocked +3 multi-segment accessor chains
(perl_method_inherited), and the __PACKAGE__ curfile chain added +40 edges to
Mojo::File methods (29 sibling, 5 dirname, child/is_abs/list/list_tree) from
every module/test doing `curfile->sibling(...)` (Renderer, Static, IOLoop::TLS,
~24 test files); zero spurious (all target Mojo::File::<method>). Campaign
2216 -> 4272 (+2056, +93%).

Tests: perllsp_cross_return_type_chain_multiseg (My::Widget — the multi-segment
case the single-segment Widget test could not exercise),
perllsp_cross_imported_func_arrow_package_chain (curfile's __PACKAGE__ return
types the receiver so the chained sibling dispatches).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ivers (path(...)->method)

The bfbb5f5 fix resolved the `__PACKAGE__->new` factory return only on the
BAREWORD `func->method` path (curfile->sibling). Mojo::File's `path` uses the
same idiom — `sub path { __PACKAGE__->new(@_) }` — but is called as a FUNCTION,
`path(@Parts)->child(...)`, whose receiver is a function_call_expression routed
through perl_eval_function_call_type. That path returned the raw literal
"__PACKAGE__" type, so the chained method never dispatched (path(...)->child /
->to_string / ->list_tree / ->extname all failed).

Fix: in perl_eval_function_call_type, run the resolved function's return type
through perl_func_return_class_qn — mapping "__PACKAGE__" back to the function's
own package (xmod reverse-lookup to the colon-spelled used-module type key), and
passing every other spelling through unchanged. So a `func(...)->method` chain
on any `__PACKAGE__->new` factory now types its receiver.

Real-repo impact (Mojolicious, fresh index): CALLS 4272 -> 4351 (+79), all
correct — path(...)->{child,to_string,list_tree,extname,to_abs,slurp,basename}
resolve to Mojo::File methods from Mojo::Asset::File, Prefork, Renderer, Static,
Types, Commands, etc. Campaign 2216 -> 4351 (+2135, +96%).

Test: perllsp_cross_imported_func_call_arrow_package_chain (path('a')->child —
the func(...)->method twin of the bareword curfile->method chain).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…llbacks)

The list-unpack $c-typing (c6d6719) only bound $c when it was the 2nd element
of `my ($self, $c) = @_` — the first element had to be an invocant name. The
Mojolicious around_action / before_dispatch hook-callback form
`my ($next, $c) = @_;` (first positional is the continuation $next, not $self)
was missed, so $c->render/stash/param/... in those callbacks stayed unresolved.

Fix: inside a Mojolicious::* module, bind a positional `$c` at ANY position of a
`my (...) = @_` unpack to Mojolicious::Controller, decoupled from the
first-element invocant check (the $self->package binding still requires an
invocant name). The exact name `$c` is the strong Mojolicious convention that
keeps this zero-noise; gated to the framework module path.

Real-repo impact (Mojolicious, fresh index): CALLS 4351 -> 4377 (+26), all
correct — $c->render/stash/param/... in framework modules (Routes, Renderer,
Mojolicious, DefaultHelpers) whose $c arrives via `my ($next, $c) = @_`.
Campaign 2216 -> 4377 (+2161, +98%).

Test: perllsp_cross_mojo_around_c_param.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…o the bound var chains

`my $dir = tempdir;` / `my $f = curfile;` call an imported factory with NO
parens; tree-sitter parses the arg-less call as a plain `bareword`, which
perl_eval_expr_type did not recognize — so the bound scalar stayed untyped and
`$dir->child(...)` / `$f->sibling(...)` never dispatched.

Fix: in perl_eval_expr_type, type a lowercase `bareword` that is an Exporter
import (perl_find_import -> registry func) from the function's return type,
resolving a `__PACKAGE__` factory return to its own package (perl_func_return_class_qn).
Lowercase + import-map hit distinguishes a function from a class name, so a bare
class/const stays zero-edge. The bound var then carries the type through
scope-binding and the chained method resolves.

Real-repo impact (Mojolicious, fresh index): CALLS 4377 -> 4391 (+14), all
correct — `$dir->child` (Mojo::File::child 2 -> 13) from `my $dir = tempdir;`
across Command/asset/cookiejar/file/file_download and more. Campaign
2216 -> 4391 (+2175, +98%).

Test: perllsp_cross_bareword_func_assign_chain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…patch on typed receivers

The cross-file chain-walk seeded only the current file's own @isa parents (and
Mojolicious::Controller), so it attached inheritance chains to THOSE types only.
A receiver typed by a constructor or a used-module return —
`my $stream = Mojo::IOLoop::Stream->new; $stream->on(...)` — resolved a method
ONLY if that method was defined directly on the constructed class; an INHERITED
method (EventEmitter::on/emit/unsubscribe, Message/Content/URL/Promise/DOM base
methods) failed, because the used-module type carried its own methods but never
its @isa chain.

Fix: also seed the chain-walk worklist with the used-module packages
(ctx.xmod_pkgs — every class named by a `use`/constructor in the file). The walk
then resolves each used module's tagged @isa parents from the project inherit
index, sets the type's embedded_types, and attaches ancestor methods — so
perl_lookup_method's frontier walk reaches inherited methods on any
constructor/used-module-typed receiver, across files. Bounded by PERL_CHAIN_CAP
+ the seen-set (each ancestor visited once); sound (typing is via Class->new /
used-module returns, @isa is the tagged inherit index only).

Real-repo impact (Mojolicious, fresh index): CALLS 4391 -> 4564 (+173), all
correct — inherited methods on typed receivers: EventEmitter on/emit/unsubscribe
(on 9 -> 21), and Message/Content/URL/Promise/DOM/Server base methods. The
1727 perl_method_inherited edges target only plausible Mojo base classes.
Campaign 2216 -> 4564 (+2348, +106%).

Test: perllsp_cross_constructor_typed_inherited_method.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ted methods on accessor-chain receivers)

Completes the used-module @isa fix (363f958). That seeded @isa only for classes
named by a `use`/constructor. A receiver typed by a RETURN TYPE — `$tx->res->dom`,
where `res` returns Mojo::Message::Response and Response is reached only through
res's return type (never `use`d in that file) — still carried no @isa, so the
further-inherited method (Response inherits dom from Mojo::Message) failed.

Fix: also seed the chain-walk worklist with the classes that appear as
function/accessor return types across the def universe (multi-segment dotted
spellings like "Mojo.Message.Response", converted to the colon form the walk
resolves; deduped against the worklist). Those classes then get their tagged @isa
attached from the project inherit index, so inherited methods dispatch on
accessor-chain receivers too.

Real-repo impact (Mojolicious, fresh index): CALLS 4564 -> 4591 (+27), all
correct — inherited methods on return-type-typed receivers (Mojo::URL 56->64,
Mojo::Promise 47->59, ...); the 1754 perl_method_inherited edges still target
only plausible Mojo base classes (85 distinct callee files). Campaign
2216 -> 4591 (+2375, +107%).

Test: perllsp_cross_return_type_class_inherited_method.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… usage method-set

A `has [qw(tx)]`-style accessor carries no declared type and is set dynamically,
so no single-def signal reveals its class. But the SET of methods called on its
result across the defining class (`$self->res->code`, `$self->res->cookies`,
`$self->res->content`, ...) is a structural signature: if exactly one in-repo
class (own methods + tagged @isa, resolved via the project inherit index)
defines ALL of them, the accessor must return that class.

Implementation (cbm_run_perl_lsp_cross, before the @isa chain-walk):
collect `$self->M1->M2` usage per file, and for each of the file's OWN untyped
methods M1 whose usage set has >= 3 distinct non-universal methods, find the
UNIQUE base-most class K (drop any coverer that is a descendant of another) that
defines them all; set M1's return type = K and seed K into the chain-walk so its
method table + @isa attach. Strictly gated so ambiguity stays zero-edge:
UNIVERSAL/Mojo::Base methods (new/isa/can/tap/attr/...) are excluded from the
signature, and a non-unique or self match yields nothing — preserving the
correct-edge guarantee.

Correctness (verified, 5/5 sound on Mojolicious): the ONLY inferences made are
Mojo::IOLoop::Client::reactor->Mojo::Reactor, Mojo::Server::Daemon::ioloop->
Mojo::IOLoop, Mojolicious::Controller::app->Mojolicious,
Mojolicious::Controller::res->Mojo::Message::Response, and
Test::Mojo::ua->Mojo::UserAgent — each genuinely returns that class. The
POLYMORPHIC accessors are correctly REJECTED: Test::Mojo/Controller `tx` is used
as both HTTP and WebSocket transactions (its method-set res/error/send/finish/
is_websocket is covered by no single class), so it stays zero-edge rather than
fabricating wrong edges.

Real-repo impact (Mojolicious, fresh index): CALLS 4591 -> 4606 (+15), all
correct — the resolved chains on the 5 inferred accessors (e.g. $c->res->code,
$c->app->routes, $self->ioloop->stream); the perl_method_inherited edge set
still targets only plausible Mojo base classes. Campaign 2216 -> 4606 (+2390,
+108%).

Test: perllsp_cross_duck_typed_accessor (unique-match infers; a decoy class that
covers only a subset must not ambiguate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s propagate to all files

The per-file structural (duck) typing added in dadaff3 infers a typeless
accessor's return class from the method-set called on `$self->accessor->M2`,
but it wrote all_defs[i].return_types as a SIDE EFFECT during the accessor's
OWN file's resolution. Files processed BEFORE that file never saw the inferred
type, so cross-file accessor chains (`$c->app->routes`, `$c->res->code`) stayed
unresolved even when the accessor WAS correctly inferred. Root cause is
processing order, not arena lifetime (K is a persistent def_module_qn string).

Fix: a dedicated pre-pass (cbm_perl_duck_prepass) runs BEFORE the parallel
resolve loop. Phase 1 walks every Perl file's AST collecting `$self`/`$class`
accessor chains and aggregates each accessor's method-set GLOBALLY (persistent
arena), attributing each accessor to its defining-class def via own-def or @isa
(perl_duck_find_accessor_def). Phase 2 runs the SAME base-most-unique gate as
the per-file block (union own-definers -> coverers via perl_duck_class_defines
-> unique base-most survivor via perl_duck_is_a; base_n==1 && K!=own_mod &&
>=PERL_DUCK_MIN distinct methods) and writes the inferred return type into
all_defs up front. The existing return-type-class @isa seeding then dispatches
the chains in every file regardless of processing order.

Wired into the PARALLEL pipeline (pipeline.c -> run_parallel_pipeline, after
perl_inherit is built, before cbm_parallel_resolve) via a shared driver
(cbm_pxc_perl_duck_prepass_driver) that gathers each Perl file's source +
module QN + cached tree. The sequential pass_lsp_cross path calls the same
driver. (A first cut wired only the sequential path and measured +0 because
index_repository runs the parallel pipeline.)

Real-repo impact (Mojolicious, fresh index): CALLS 4606 -> 4660 (+54, +51
unique), all perl_method_inherited, all correct: ua -> Mojo::UserAgent
(max_redirects/build_tx/cookie_jar/request_timeout/...), app -> Mojolicious
(plugins/types/routes/renderer/moniker/home/mode/log/start), server ->
Mojo::Server::Daemon (max_accepts/acceptor/max_connections), res ->
Mojo::Message::Response (code/headers/content/body). Campaign 2216 -> 4660
(+2444, +110%).

Soundness preserved: the polymorphic `tx` accessor (WebSocket-only `send` plus
base Transaction methods, covered by no single class) yields base_n != 1 and
stays zero-edge; the ~1069 `$tx->req`/`$tx->res` sites stay unresolved because
typing an arbitrary `$tx` local is genuinely unsound. No hardcoded class tables.
Gate: 472 passed / 0 failed; perllsp_cross_duck_typed_accessor still passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… the sound ceiling

Companion to PERL-CROSS-FILE-INHERITANCE.md, covering the campaign chapter that
took Mojolicious CALLS 3331 -> 4660 (+110% overall, from 2216) and the four-axis
real-repo audit that established where the sound frontier ends.

New: docs/lsp-uplift/PERL-DUCK-TYPING-AND-CROSS-LANG-AUDIT.md
- The shipped ladder (site-attachment invariant, colon/dot reconcile,
  @ISA-on-used-modules +173, per-file duck-typing +15, cross-file pre-pass +54).
- Duck-typing mechanism + the base-most-unique soundness gate (why polymorphic
  `tx` correctly stays zero-edge).
- The cross-file pre-pass processing-order root cause + the parallel-pipeline
  wiring gotcha (sequential-only wiring measured +0).
- Four-language audit: Java(gson) 9545, Rust(ripgrep) 6690, Python(Django) 62084
  / (Flask) 1408, Perl 4660 -- all mature; the ~40% low-conf heuristic tail is
  external/stdlib by construction (candidate_count_penalty floors conf by
  design). No additive coverage lever remains.
- Every dead-end built/measured/reverted (invocant-name +0, per-scope local-var
  +5-reverted, $tx +0 with the regex-overcount lesson) so the next iteration
  does not re-walk them.
- Measurement pitfalls: fresh cp -r (incremental cache), RETURN r overflow on
  62k edges, temp-daemon full-env requirement, bare-callee spelling.
- The standing precedent: engine soundness > marginal edge count.

Updated: docs/lsp-uplift/PLAN.md
- Retro at top + perl-cross-file-lsp marked done through daaf538.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…P capabilities

The Perl row in "Languages with full Hybrid LSP" described only per-file
resolution; update it to the shipped reality (campaign through daaf538):
cross-file inherited-method dispatch via the project inheritance index,
`use Mojo::Base 'Parent'` inheritance, `has` accessor return-type inference
(incl. `has x => sub { Class->new }` defaults), typed-receiver method chaining
(`$obj->accessor->method`), and sound-gated structural (duck) typing of typeless
accessors. Zero-edge guarantee unchanged. See
docs/lsp-uplift/PERL-DUCK-TYPING-AND-CROSS-LANG-AUDIT.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@turtacn
turtacn requested a review from DeusData as a code owner September 11, 2026 01:03
@github-actions

Copy link
Copy Markdown

Thanks for opening this — it has been seen, and it is queued.

This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence.

Current review status: working through a backlog. 0.9.1-rc.1 is out, so the release freeze that held reviews is over — but it left a large queue of open pull requests behind it, and we are reading through them oldest-first. The background is in discussion #1144.

What that means for this PR, concretely:

  • It will not be closed for inactivity. No stale bot touches pull requests here.
  • It may still sit a while before a human reads it. That is on us, not on you.
  • Older PRs are read first, so a recent one is not being skipped — it is behind a queue.

Things that will genuinely speed it up whenever review does happen:

  • Keep it rebased on main — the tree is moving quickly right now, and a conflicting branch cannot be reviewed as the diff you intended.
  • Get CI green, or say which failures you believe are pre-existing.
  • Keep the change to one claim. Bundled features and refactors get split before they get merged, which costs you a round trip.
  • Every commit needs a sign-off (git commit -s) — CI enforces DCO.

If this fixes a bug, a reproduction we can run is worth more than a description of the symptom.

Thanks for contributing, and sorry in advance for the wait.

… + upstream fixes

Sync of DeusData/codebase-memory-mcp into turtacn fork. Merge (not rebase) so
both histories survive: ours +49 (Perl/Java/Python/Rust/Go Hybrid-LSP uplift
campaign), upstream +139 (daemon memory-budget/userns fixes, CI clang-21 repin,
yaml/gitignore/watcher correctness, C# multi-attribute, coverage-range,
graph-ui, Makefile/vendored). Merge-base b3d898e. Safety anchor pushed:
backup/pre-upstream-sync-4be307f6.

Conflicts resolved:
- src/pipeline/pass_semantic_edges.c (textual, trivial): both sides fixed the
  same zero-function qsort NULL-base bug with an identical guard; kept upstream's
  fuller comment. No code lost.
- find_jvm_modifiers signature drift (SEMANTIC, build-caught, no marker):
  upstream DeusData#1692 changed it to a fill-array/count API; our new Java call site
  jvm_class_testng_test used the old 2-arg form. Rewrote the call site to the
  new API (matches extract_defs.c:1733,2105). jlsp_testng_class_level_test PASS.

Verified: `make -f Makefile.cbm cbm` green; focused gate
`perl_lsp java_lsp extraction pipeline registry` = 887 passed / 10 skipped
(pre-existing tracked) / 0 failed. Full playbook + this log: docs/UPSTREAM-SYNC.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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