mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 03:49:59 +08:00
ada14454585cbb2a3ef337302806af37863a7717
1585
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ada1445458 |
fix(ts): a qualified type's last name is a member of what its qualifier names, not any declaration of that name (#2739)
Both extractors record the last name of a qualified type alone, so
`React.ReactNode` reached name matching as `ReactNode` and bound by word:
outline's 197 `React.ReactNode` annotations linked to its own editor
`class ReactNode`, excalidraw's `TChat.ChatMessage` to a `ChatMessage`
component, signoz's `EQueryType.CLICKHOUSE` to another enum's member, and
outline's `UnfurlResourceType.Group` to the `Group` model the file imports.
PR #2736 left that name to name matching on purpose.
A TypeScript `references` ref written after a qualifier now resolves
through the qualifier, ahead of the framework, import and name strategies:
- an import: the module's export (`import * as T`, through barrels and the
repository's own `declare module` blocks), or the member of the enum,
class or namespace a named or default import binds;
- a declaration of the file's own, in the innermost namespace around the
reference that declares it, else at the top level: an enum's or a
class's `::` member, or a member of a namespace block;
- otherwise a namespace the project declares global (`Express.User` in a
`declare global` block).
A package from outside the repository, which no manifest of the importing
file's declares as its own and no `declare module` block declares, and a
global the project doesn't declare, link nothing. An import this resolver
can't follow but no manifest declares as a dependency (zod's own tests'
`import * as z from "zod/v3"`, through the package's `exports`), an
`import X = ns.Y` alias, and a name written after an expression
(`import('./x').T`) keep the old strategies.
The index holds no node for a TypeScript namespace, and its members are
indexed by their bare names, so ambient-modules gains scanNamespaceBlocks:
every `namespace A.B { … }` block with its full name and whether it is
global, read from the same tokens as the module blocks.
Validated on 18 TypeScript repos (13 React apps plus zod, vben, mealie,
prometheus's web UI and fastapi-template), main
|
||
|
|
2c4a16d091 |
fix(react): a wrapper component renders the component it wraps, and owns the calls in its styles (#2738)
A component declared through a wrapper with no render function of its own
(`styled.div`…``, `styled(ActionButton).attrs(…)<Props>`…``, MUI's
`styled(Box)(({ theme }) => …)`, `memo(Button)`) was a bodyless component
node: nothing walked its initializer, so the calls in its styles went
nowhere, and nothing linked it to the component it wraps, so a render flow
stopped at it. On outline, explore printed no Flow for
"OverflowMenuButton NudeButton ActionButton performAction".
Extraction (TS and the native kernel) now walks the initializer with the
component on the stack: the template's interpolations, the .attrs() /
.withConfig() / options arguments, a style function and a memo comparator
are the component's code. The wrapper's own chain (styled, styled.div,
.attrs, memo) names the library and is not recorded; a typed tag's type
operands are not walked; and the wrapped component is no function-as-value
reference, from the component or from the file (the declaration-wide scan
skips the walked initializer). A component is also a value-ref reader, as
a function is.
The jsx-render synthesizer links a wrapper to the component it wraps
(`wrappedComponentName` in frameworks/react.ts, beside `wrappedName`),
bound as a tag of that name in the file would be, but only where the file
binds the name by a declaration or an import of a project module, so a
package's styled(Link), a Meteor /imports/… path and a destructured name
render nothing rather than a namesake. Files that declare such wrappers are
synthesis inputs, so a sync redraws the link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
7b9c356dd7 |
fix(react): a type annotation in a .tsx file names a type, never a same-named component (#2737)
React's Pattern 1 ran its component lookup on every PascalCase name in a
.tsx or .jsx file, type annotations included, and its candidate could
outrank name matching, so an annotation linked to a component: outline's
InputSelect.tsx declares `type Option`, and its annotations went to the
`Option` component of Export/ExportDialog.tsx; mastodon's `<Item>` type
parameters went to the `Item` class of media_gallery.jsx, and bulletproof's
`TableProps<Entry>` to another app's `function Entry`.
A type annotation is now name matching's, which scopes a type as TypeScript
does since PR #2736: the file's own, an import's or a global one. Pattern 1
decides only a type alias or interface the file declares beside a value or
a component of the same name, both of them the file's own: the annotation is
the type. That covers bigbluebutton's `type ModalRegistration` beside its
render-prop component, mastodon's `type ShortNumberRenderer` and `interface
AccountList` beside their functions, and a type beside a value that PR #2420
reads through its wrapper or loader. Every other reference keeps PR #2420's
order: a value's loaded or wrapped component, then a function, class or
component the file declares, then name matching for any other declaration
of the file's, and the component lookup only for a name the file doesn't
declare.
Validated on 13 React repos, main
|
||
|
|
458ad51921 |
fix(ts): a type a module doesn't import is never another module's (#2736)
A TypeScript module reaches another module's type only through an import, so a type it names without importing it is its own or a global one: a script's top-level declaration or one in a declare global block. #2726 held a supertype to that. A type annotation, a type argument and a type alias's value went to the name matcher like any other name, which handed them a project type of that name: vscode's Omit<...> annotations reached quickInput.ts's export type Omit instead of TypeScript's own, and type parameters and mapped keys, like excalidraw's <Point extends ...> and frontend's <T>, reached other modules' types and enum members. Both name-match strategies now leave out another module's non-global type for a TS or JS module's type reference to a name the file doesn't import, ahead of the ranking, so a declare global type still wins over a nearer module's namesake. Left as they were: the last name of a qualified type, vscode.TextDocument, written after a dot; a name inside a declare module 'x' block, which is module x's own; and a name an import X = ns.X alias binds, which the file's import list doesn't hold. Globals come from declaration files and declare global blocks. A script .ts's top level is a sample's or a fixture's, global to its own program only (vscode's colorize fixtures declare type T), and a test's or fixture's globals reach its tests only, as its declare module blocks do. That holds for supertypes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0c28480576 |
fix(cpp): -> on a value of one of the project's classes follows its operator->, like rocksdb's FileSystemPtr and MultiGetContext::Range::Iterator (#2735)
C++ reads `x->m` on a class value as `x.operator->()->m`, and applies `->` again to a class it returns. Only a library holder's `->` and a container iterator's (`std::vector<T>::iterator`) were read. A call through a project iterator or smart-pointer wrapper was guessed by the method's name: - rocksdb's `fs_->NewWritableFile(…)` on a `FileSystemPtr`, whose `operator->` returns a `std::shared_ptr<FileSystem>`, reached `FaultInjectionTestFS`; - `writable_file_->Truncate(…)` on an `FSWritableFilePtr` reached the legacy `WritableFile::Truncate`; - `iter->lkey->user_key()` through `MultiGetContext::Range::Iterator`, whose `KeyContext* operator->()` is a `KeyContext*`, reached `BlobCountingIterator::user_key`. Both a member chain's `->` and a `->` call on a declared receiver end in cppIteratorTarget. For a type that is no container's iterator, it now falls back to cppOperatorTarget: - what the class's `operator->` returns, through the pointer it is, or with `->` applied again to a class it is: a library holder, or another of these; - else what its `operator*` returns; - the return type is read where the class declares it, and the class's own template parameters are bound to the type's written arguments (cppTemplateBound): `UnownedPtr<DB>`'s `T* operator->()` is a `DB*`; - a parameter the type doesn't bind names no particular class. A log-only census on main |
||
|
|
4e35e0cb78 |
test(python): the module-resolution tests remove their temp dir after indexing (#2734)
python-module-resolution.test.ts removed a test's temp dir only when the test never opened a CodeGraph (`if (cg) cg.destroy(); else if ...`), and destroy() closes the database without deleting anything. So every test that indexed left its codegraph-pymod-* dir behind, 47 per full run, passing or not, since PR #1921 added the file. The dev box's %TEMP% held 1,739 of them by 2026-10-21. The dir is now removed after destroy(), as the other test files do. Checked with a private TEMP: main's file leaves 47 codegraph-pymod-* dirs, this one none, and its 50 tests pass both ways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bdf288a76f |
fix(cpp): a pimpl's Rep, which a header names and its source file defines, is found for the code in that file (#2733)
A C++ name is looked up from where it is written, and a class that a source file defines belongs to that file's translation unit, so only that file sees it (#2538). A header that declares `struct Rep;` and `std::unique_ptr<Rep> rep_;` writes the name for a `Table::Rep` that its source file defines, and code in that file reads the member through it. The lookup ran from the header, saw no source file's classes and found nothing. The call was then guessed by the method's name, often onto the calling class's own method: - leveldb's `rep_->index_block->NewIterator(…)` reached `Table::NewIterator`; - rocksdb's `rep->write_batch.GetDataSize()` reached `WriteBatchWithIndex::GetDataSize`; - rocksdb's `level_iters_[i]->status()` reached `ForwardIterator::status`. cppClassWritten's lookup now also sees the classes of the file the reference is in, the translation unit that includes the header (visibleFrom(file, unit)). classNode filters by that same visibility instead of recomputing the header's. A member-chain diagnostic on main |
||
|
|
73f68e188c |
fix(ts): a call to a parameter of any type links nothing elsewhere (#2732)
A JS/TS name the calling function binds as a parameter is that parameter, never a same-named function in another file, as the file's own were made in #2226. The parameter reader took a type or default to be anything up to a comma or a parenthesis, so a parameter typed as a function, defaulted to one, or after one was never seen: bitwarden's isAvailable(f) on a callback typed (f: string) => boolean linked to the napi isAvailable, and vscode's merge editor's compare(a, b) on a comparator parameter to strings.ts's. A type or default may now hold balanced parentheses, braces, brackets and generics, a parameter property's modifiers are read, and an earlier destructured parameter is passed over; each alternative still has one parse. A name ends where no identifier character follows, so a const syncState$ no longer declares syncState. The parameter also shadows the file's own import of the name, which the import resolver answered before the check ran, but only where it is in scope: in the body of the function, arrow or catch clause that binds it, or the block or for statement of a const, let, var, function or class, read from the function's text up to the reference. A lone arrow parameter counts, and an object type a function returns is no body. A Promise executor's resolve is the calls inside it, never a resolve(file) after it, and a const X = require('./x').default is the import itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5994edb34e |
fix(cpp): a member declared in an anonymous union, like protobuf's union { Impl_ _impl_; };, is found as a member of the class around it (#2731)
A class's data members are read from its own lines, skipping those of
the functions and classes nested in it. The index keeps an anonymous
union or struct as a nested `<anonymous>` node, so its lines were skipped
too, though C++ looks its members up as the enclosing class's
([class.union.anon]).
Every generated protobuf message keeps its fields in `union { Impl_
_impl_; };`, so a call through `_impl_` was guessed by the method's name:
- `this_._impl_.type_url_.Destroy()` reached `Arena::Destroy`;
- `_impl_._cached_size_.Get()` reached `FieldArenaRep::Get`;
- `_impl_._has_bits_.Clear()` reached the message's own `Clear`.
fmt's `value` keeps its argument in a union of `string`, `custom` and the
rest in the same way.
cppNestedSpans now leaves out an anonymous union or struct with no name
and no declarator (isCppAnonymousMembers):
- a bare `union {` or `struct {` head;
- `};` after the closing brace.
A union with a declarator, `union { … } u;`, keeps its members to itself,
and so does a partial specialization, which the index also keeps as
`<anonymous>` but whose head names its template.
A member-chain diagnostic on main
|
||
|
|
81fd007850 |
fix(cpp): a call through a namespace links no class's method by its name (#2730)
A C++ call written through a namespace, `x::m()`, reached matchMethodCall's name strategies when no declaration of `x::m` was found by qualified name. They can only return a method of some class, which a namespace never holds: the capitalized-receiver class (`internal` → a class `Internal`), the one method of the name in the project, or the best receiver-word guess. So protocolbuffers/protobuf's `absl::StripPrefix(name, …)` linked to its Rust generator's `MultiCasePrefixStripper::StripPrefix` and `absl::Status(…)` to `upb::Status`'s constructor. fmtlib/fmt's `fmt::ptr(p)` linked to a test scanner's `ptr()` method, and `fmt::day(…)` to a test's `date::day`, ahead of the step that reads the namespaces a macro opens and finds fmt's own `ptr` function and `day` alias in `FMT_BEGIN_NAMESPACE`. Those strategies now return nothing when the qualifier is a namespace (isCppNamespaceQualifier): a lowercase name that no C or C++ type or alias node has, and that the calling file declares as neither a template parameter nor an alias (cppNamesBroughtIn, now split into type names and namespace names, since a namespace the file opens confirms it is one). rocksdb's `hash_table::Insert(…)` through `typedef HashTable<…> hash_table;` keeps its edge. So does the constructor of a class the call names in that namespace (isCppConstructorThrough): protobuf's `internal::SourceWrapper<absl::Cord>(&data)` constructs `struct SourceWrapper<absl::Cord>`, a specialization the index holds without its name, whose template is only declared. A method of another template's specialization is no constructor: fmt's `fmt::scan<int>(…)` is fmt's `scan` function, not `scanner<num>::scan`. The check runs only where one of those strategies would return. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
009e4377ff |
fix(cpp): a call through a class the index doesn't hold gets no guess by a shared word (#2729)
A C++ call written through a class, `X::m()`, whose class has no node in the index fell through to the last strategy of matchMethodCall, which scores every method named `m` by the words its class shares with `X` and picks one. protocolbuffers/protobuf declares its descriptor messages in descriptor.pb.h, which is over the indexer's size cap, so `FeatureSet::default_instance()` linked to `UnknownFieldSet::default_instance`, `FeatureSetDefaults::GetDescriptor()` to `JavaFeatures_NestInFileClassFeature::GetDescriptor`, and the options messages' `default_instance()` to `pb::enumvalue::JsonEnumValueOptions`'. A class whose name only shares a word with `X` is another class: one named `X` was looked for first, by qualified name and by class name. That guess is now dropped when `X` is a capitalized name (isUndeclaredTypeName) that no C or C++ class, struct, union, enum, alias or namespace node has, and that the calling file makes nothing of itself (cppNamesBroughtIn): not a template parameter of any `template <…>` head in it, not an alias it declares (a function-local `using FD = FieldDescriptor;`, a typedef, or one a macro it expands declares, like rocksdb's IMPORT_RIBBON_IMPL_TYPES), not a namespace it opens, aliases or uses, and not googletest's TypeParam or TestFixture in a typed test. The one method of a name in the project is still taken as before. The scans read the file once (cppTypeNamesDeclared in cpp-type-aliases.ts, shared with the template-head reader) and the project's headers once for macro bodies, only when a guess would otherwise be returned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
accf600e47 |
fix(ts): a declaration in a declare module block is reached through its module (#2728)
A declaration file's declare module 'x' { ... } declares the members of
x, which no other file names by a bare name: an import of x reaches them.
The functions of such a block have been held to that since #2718, but its
other declarations, the types, classes, constants and enums, still took
bare-name matches from any file. On vscode, Node's
import { readFileSync } from 'fs' linked to the fs that vscode.d.ts's
workspace namespace declares, electron's clipboard and shell to env's, a
ProcessEnv to the proposed API's, and an LSP TextDocument re-exported
through a project barrel to the API's own.
Every block-level declaration now takes a match from another file only
when the reference is written in a block declaring the same module (the
proposed API files merge into vscode.d.ts), is a type position in a file
that imports the module (vscode.TextDocument under a namespace import,
which the extractor records by its last name), or names what the file
imports from a project file that imports the module and re-exports it
(copilot's vscodeTypes.ts).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
f66d064279 |
fix(cpp): a function is named by its declarator, never by its noexcept, throw or requires clause (#2727)
Both C++ extractors name a function by the first qualified identifier found breadth-first in its declarator, which is how an out-of-line `Foo::bar` is named. The walk skipped only the parameter list and the trailing return type, so a qualified name in the clauses after them became the name of a function whose own name is plain. nlohmann/json's constructor `basic_json(CompatibleType&& val) noexcept(noexcept( JSONSerializer<U>::to_json(…)))` was indexed as a method `JSONSerializer::to_json` that doesn't exist, with no node for the constructor; `throw(std::bad_alloc)` named a method `std::bad_alloc`, and a trailing `requires std::integral<T>` a function `std::integral<T>`. The walk now also skips `noexcept`, `throw_specifier` and `requires_clause`, in the TS extractor and the kernel alike; the parity fixture covers all three. Measured on main |
||
|
|
e97012eb31 |
fix(ts): an interface links the interfaces it extends (#2726)
tree-sitter-typescript holds an interface's supertypes in an extends_type_clause, which neither extractInheritance (wasm) nor the kernel's extract_inheritance read: a class's extends and implements became edges, an interface's extends nothing. So an interface had no place in the type hierarchy, a change to it reached none of the interfaces built on it, and a call through a composite interface whose head the receiver typing could not read fell to name matching (home-assistant/frontend's hass.formatEntityName linked to a namesake in an unrelated file). Both engines now emit one extends reference per type of the clause: a name, a qualified name as written (shapes.Shape), or a generic type by its name field (Repo for Repo<User>). They resolve as a class's do, so a package's type stays unresolved, and a namespace import or a module the repository declares resolves. A supertype a TS/JS module names without importing it is now its own declaration or a global one, at a script's top level or in declare global, never another module's or a namespace's: vscode's interfaces extending Omit<...> linked to quickInput.ts's export type Omit, and extends Function to terminal-suggest's Fig.Function. A call on a TS/JS type whose declaration is known now takes the method from that declaration's own supertypes. The walk by name, which unions the supertypes of every same-named type and looks the method up by name again, follows only classes' supertypes, as before interfaces had any. Otherwise vscode's receiver.dispose() on an ILocalOtlpHttpReceiver extends IDisposable reached copilot's own IDisposable, one of eleven, and stream.on() on a NodeJS.ReadableStream the project's ReadableStreamEvents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
226ac930de |
fix(cpp): a call through a written class template specialization, like formatter<int>::format(…), never reaches another specialization's member (#2725)
The index names a class template specialization's out-of-line member
after the template: fmt's test-only `auto
fmt::formatter<incomplete_type>::format(…)` is `fmt::formatter::format`.
So `formatter<int>::format(42, ctx)` in fmt's tests reached that member,
defined in another file, and `formatter<std::tm, Char>::parse(ctx)`,
where the template declares no such member, fell through to a guess by
the receiver's name: `native_formatter::parse`.
- A C++ call whose qualifier is written with template arguments keeps a
candidate defined out of line for a full specialization of its class
only when that specialization's arguments are the call's. A template's
own members, which have a `template <…>` header with parameters,
partial specializations' members and members defined in their class
stay. This applies beside the existing specialization pool in the exact
and partial qualified-name matches and in cppMethodOf's own members.
- When the class strategy finds no member for a call through a project
class template's written specialization, matchMethodCall stops: a
method picked by the receiver's name would be another class's.
Measured on main
|
||
|
|
f6d8f19b29 |
fix(rust): a call through a trait-bounded generic, like Key::get(…) with Key: MapKey, reaches the bound trait's method (#2724)
A Rust call through a generic type parameter is a call of the method its
bounds bring in: protobuf's `Key::get(…)` in `fn map_get<Key: MapKey>`,
with `trait MapKey: … + FfiMapKey`, is `FfiMapKey::get`. No strategy
read the bounds for such a path. These calls linked only when a
qualified name happened to end with the written text, which the scope
boundary of the previous fix rightly stopped, and a parameter bounded
only by an outside trait (`T::default()` under `T: Default`) could reach
a project type's unrelated `default` by name.
matchRustGenericPath, right after the `Self::x` branch of
matchMethodCall, reads the bounds the function and the impl or trait
around the call put on the parameter (in `<…>` or a `where` clause) and
resolves the method among those traits and the traits they extend,
through the helpers typed receivers already use. Outside-only bounds,
markers, and two traits declaring the method give no edge. The ref is
recorded as typed, so a trait method's missing `pub` doesn't hide it
from another module.
Measured on main
|
||
|
|
ac9f96f8a3 |
fix(cpp,rust): a qualified call like Status::OK() links the class it names, not one whose name ends the same way (#2723)
The partial qualified-name match took any declaration whose qualified name ended with the written text, with no scope boundary. In facebook/rocksdb about 4,100 calls to `Status::OK()`, `Status::InvalidArgument(…)` and their siblings went to `IOStatus`'s methods of those names; base-class calls from an override, like `return FileSystemWrapper::NewWritableFile(…)` or leveldb's `return DB::Put(…)`, went to a namesake subclass or to the calling method itself; and ripgrep's `Path::new(…)` on std's `Path` went to its own `PrinterPath::new`. - The written name must start a scope of the candidate's: the candidate's qualified name is the name, or ends with it right after `::` or `.`. - Among those candidates, the ones in the call's own language come first. protobuf's C++ `DescriptorPool::generated_pool()` met a Ruby namesake first, which the language gate then dropped. - A C++ name qualified by a template parameter, like `CacheShard::ComputeHash` in `template <class CacheShard> class ShardedCache`, gets no edge from any strategy, except to a member of the parameter's default (`typename Parser = TcParser`). The boundary alone moved rocksdb's call to the receiver-word guess, with the same wrong target. - The C++ template-head reader names each parameter by its last word, `int N`, `detail::fixed_string S`, `const PrefetchOpts& kOpts`, and keeps its default. A `<…>` after a C++ declaration's name is a specialization's arguments, not parameters. Before, `WireFormatLite:: FieldType kKeyFieldType` made `WireFormatLite` a type parameter. Measured on main |
||
|
|
17f1453a6c |
fix(rust): a value's declared type is its binding's in scope at the call (#2722)
The type a Rust value is written with, read for `reader.as_mut().m()` and calls through a `Pin`, came from a backward line scan for `reader:` in the calling function. That took a `let reader: …` in a block the call is outside of, or a struct literal's `reader: …` field, for the declaration; it also stopped at a `let entry = entry.as_ref()…` on the call's own line, whose new binding is not yet in scope there. It is now the binding rustBindingAt finds in scope at the call, through the block-scoped reader the typed-receiver path already uses. A/B against main with fresh indexes: clap, dioxus, axum, serde, ripgrep and gin are byte-identical; tokio has one call read more precisely, the same target at a higher confidence (`entry.as_ref().handle()` on the parameter `entry: NonNull<TimerShared>`). Two regression tests, both failing on main. Full suite on the tested tree: 8,039 passed, 0 failed. Follow-up 3 of the Rust receiver series (PR 2719's open list). Follow-up 2, an impl's type parameter in a return type read from the receiver's type arguments, was measured and dropped: 84 value reads across six Rust repos reach the case, most substituting a generic for a generic, an associated type, or a head type already named, so about 10 to 20 calls would gain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bd62d9c061 |
fix(rust): a turbofish call reaches the function the turbofish wraps (#2720)
A Rust call written with a turbofish (`s.get::<u32>()`, `helper::<u8>()`, `Pool::<u8>::new()`) kept its type arguments in the callee's name (`s.get::<u32>`), which no resolution strategy reads, so every such call was left unresolved. Both engines now name the function the turbofish wraps and drop a path's type arguments (`Pool::<u8>::new` is `Pool::new`), so the call resolves as it would without the turbofish: on a typed receiver, on `self` or a field of `self`, and through an associated-function chain. The kernel and wasm extractors emit the same bytes; the parity fixture covers nested type arguments and a `->` inside them. A/B against main with fresh indexes, sites matched by position: clap 197 calls linked, dioxus 251 linked and 1 removed, axum 69, tokio 143, serde 48, ripgrep 3; gin and express are byte-identical. About 680 of the new links are right: typed method calls (clap's `matches.get_one::<String>()` to `ArgMatches::get_one`, dioxus' `self.query.new_query::<T>()` to `QueryEngine::new_query`) and generic free functions (tokio's `channel::<T>()`, dioxus' `consume_context::<T>()`, serde's `assert::<T>()`). About 22 are weak guesses at confidence 0.5 or below, where an instance chain's method becomes a bare name, as it is for the same call without a turbofish (dioxus' `.collect::<Vec<_>>()` to `ServerFunction::collect`; main already makes 23 of those for `.collect()`). The one removed link was wrong on main: `serde_json::from_str::<T>(...).context(...)` is no longer guessed as `DiffState::context`. The 54 sites reported as retargeted are positions a chain's inner and outer calls share, where an edge was added beside the existing one. Full suite on the tested tree: 8,037 passed, 0 failed. Follow-up 1 of the Rust receiver series (PR 2719's open list). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7324faf69f |
fix(rust): a call on a field of self reaches the method of the field's declared type (#2719)
A Rust `self.field.m()` call was resolved by the last segment of the field's type name alone. `inner: multer::Field` linked to axum's own `Field::name`, `std: std::time::Instant` to tokio's `Instant` (the calling method itself), `inner: std::sync::Mutex<…>` to tokio's loom mock or async `Mutex::lock`, and a field generic over the impl's `S: Store` to nothing. The field's type is now read the way field values already are: from the struct's declaration, through the wrappers, by the `use`s of the struct's file. A struct's own parameter is known by the bounds that the impl around the call gives the parameter it is instantiated with (`struct Worker<S>` under `impl<S: Store> Worker<S>`). The method is the one the typed receiver path picks. The plain-name reading stays only where the field's type can't be read. A call on a std or dependency value reaches a project trait's impl for its type only when the impl is for that type. tokio's `impl Kill for Child` in process/windows.rs is for its own `struct Child`, not `std::process::Child`. The impl's type is read as the impl writes it: gleam's `impl LowestVersion for pubgrub::Range<Version>` is for pubgrub's `Range`. A/B with fresh indexes against main on axum, serde, ripgrep, clap, dioxus, tokio and gleam, every changed edge triaged: 361 calls linked that were not (fields bounded by the impl's traits, dioxus's signal fields, trait objects), 104 removed (calls on std and dependency fields, and 5 on tokio values whose methods sit inside `cfg_*!` macros), 59 moved to the right type of a shared name, 2,844 kept at a higher confidence; 62 fewer self-loops and none added. gin, serilog, flask and express are byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a33040f786 |
fix(js,ts): an import of a package the repository declares itself links to that declaration (#2718)
A `declare module 'mammoth' { ... }` in a script .d.ts declares the module
outright, and TypeScript takes it before any file the specifier resolves to.
Nothing mapped a package name to a file in the repository, so an import of
such a package linked nothing, or, where the out-of-repo guard did not fire
(VS Code's extensions list no `vscode` dependency), a project namesake. A
bodiless function in an ambient context produced no node either, so
`convertToHtml` had nothing to link to.
- ambient-modules.ts: a token scanner for `declare module` blocks, a
per-context index of the project's declaration files, and the lookup the
import resolver makes: named, default (`export default`, `export =`),
export-clause and namespace-member imports, by position in the block.
- The import resolver consults a script declaration before a file a path
alias maps the specifier to (copilot's `paths` shim), and the import's
module reference and namespace or default binding link the declaring file.
- Extraction, both engines: a function_signature under `declare`, in
`declare global`, at a declaration file's top level or directly in its
`declare module` body is a function; never in a namespace, nor in an
ordinary module file's `declare module`, an augmentation.
- A module's function is reached from another file only through an import.
- A type merged across the files declaring one module is no single
declaration when a receiver is typed.
- Sync re-opens the importers of a package a changed declaration file
declares.
Augmentations, wildcard patterns, shorthands and test-fixture declarations
are left alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
642c7bb3a5 |
fix(python): attribute calls follow the type the class body, a factory or a base gives the attribute (#1926)
Python calls through an attribute reach the method the code gives that attribute: `self.sink.stop()` follows `self.sink = Sink()`, a `sink: Sink` annotation, a typed `__init__` parameter, a factory's return type or what every `return` in it builds, and a base class's `__init__` in another file. Calls straight on a factory's result (`Client.from_env().send()`) resolve the same way, and a sync re-opens these answers when the factory or base they were read from changes. The native Python extractor now keeps a call's receiver text as the wasm one does, so `self.data.append(1)` is never a bare `append` that matches an unrelated project function. This includes all of PR 1704. Reworked from evidence-or-silence, which dropped about 3,000 correct Django links (`self.client.get()`, `self.engine.render_to_string()`). The reader now has three answers. A project class that has the method: that method. Evidence that rules project methods out: no edge, for a builtin, a class from outside the project built in a method or named by an annotation, an attribute only an outside base can give (Django's `TestCase` in an app), or a class without the method whose bases are all known. Nothing typed: the call is guessed by the name of its last link, read as written, as any receiver is, but only for an owner whose name holds the link's head noun (`self.session` for `SessionBase`, not `database.group_shopping_lists` for `GroupRepositoryAIProvider`) and never as the one method of a name; the guess records the files the reader looked at, so a sync re-opens it too. A class-body field an outside class builds (`author = models.ForeignKey(Author)`) may be a descriptor, so it is no evidence. A chain through an imported module (`os.path.exists()`) is the module's. In the shared scoring, a receiver that spells an owner's whole name (`client` for `Client`) outscores one that only shares its word (`RedisCacheClient`). A call through an imported value's attribute chain (`app.jinja_env.filters.keys()`) no longer links to the value's `jinja_env`. A/B against main with fresh indexes, every changed edge triaged, sites matched by position since the extractor now names these calls by their whole chain. Django: 3,317 calls linked that were not (`self.client.force_login()` to `ClientMixin`, `User.objects.create_user()` to `UserManager`, `self.stdout.write()` to `OutputWrapper`), 127 moved, nearly all to a more precise target (`self.site.register()` to `AdminSite.register` rather than the `register` decorator), and 134 removed: main's bare-name links on builtin values (`value.lower()` to the template filter, `.encode()` to a test password hasher) and calls on expressions such as `(q1 | q2).order_by()`, which now go unresolved as they do in the wasm engine. flask: 32 linked, 4 moved to `JSONProvider`. fastapi: 58 linked. mealie: 740 linked. Guesses still wrong, as main makes them for a plain receiver of the same name: fastapi's `request.headers.get()` to a telemetry `_HeadersGetter`, and about 20 of mealie's repository attributes to a controller. Go, C#, Ruby, Swift, Kotlin, Java and TS repositories are byte-identical, except excalidraw's 5 `props.onChange()` calls, which move between two unrelated `Props` interfaces. Indexing CPU: Django +7%, mealie +5%, fastapi flat; wall time unchanged within noise. Full suite on the merged tree: 7,999 passed, 0 failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4286083a84 |
fix(rust): a for-loop variable or a tuple pattern's name has the type it is bound from (#2717)
A Rust `for` loop's variable, and a name a tuple pattern binds (`for (k, v) in &map`, `let (tx, rx) = mpsc::channel(8)`), had no type the typed receiver path could read, so calls on them fell to the older receiver inference and the name strategies. tokio's 198 `tx.send(…)` calls on channel senders went to the internal `chan::Tx::send`, and 100 calls on the receivers to `chan::Rx`. The scope reader now records what a `for` loop iterates, and gives the plain names of a top-level tuple pattern their place in it. A loop's variable is an item of what it iterates: a std sequence's element, a std map's `(K, V)`, a slice's, a std iterator's or an `impl Iterator<Item = T>`'s item, through `&` and the pointers that deref to what they hold. The std adapters that keep the items (`.iter()`, `.rev()`, `.skip(n)`) or project them (`.keys()`, `.values()`, `.enumerate()`) are looked through only where what they are called on is read, so a project method of one of those names is read from its own signature. A tuple pattern's name is the matched tuple's field at its place. A field access (`self.items`, `opts.config`, `pair.0`) has the type the receiver's struct declares for the field. A free function called through a module that a `use … as` renames was looked up by the alias: tokio's loom tests' `oneshot::channel()` under `use crate::runtime::tests::loom_oneshot as oneshot` read the return type of `sync::oneshot::channel`. The path's head now goes through its `use`, and the project's modules are walked along it. A/B with fresh indexes against main on axum, serde, ripgrep, clap, dioxus, tokio and gleam, every changed edge triaged: 394 calls linked that were not, 86 removed (calls on std or dependency values, tokio values whose read and write methods come from macro-declared extension traits, derived `Clone`s, fields that hold functions), 404 moved to the declared type's method, 625 kept at a higher confidence. gin, serilog, flask and express are byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e043efbc84 |
fix(resolution): a call through a class name reaches that class, not one whose name contains it (#1935)
A call through a class name, `Logger.log()`, or through a receiver named after one, `logger.log()`, now binds to that class itself: its own method, else the nearest one a declared supertype has. Before, the method was any one in the class's file whose qualified name contained the class's name, so a `FileLogger` declared first took every `Logger.log()`, and Kotlin's `Foo.create()` went to `FooBar`'s. Reworked from the exact-owner rule alone, which lost inherited methods (Django's `form.is_valid()` on `Form(BaseForm)`, Alamofire's protocol methods). Also in this change: - A class declared inside a function is named only there: a call from anywhere else no longer reaches a class a test declares in its body, through the class name, the receiver's name, or the method-name fallback. Those methods were never in the running, and the one method such a filter leaves is still only a guess by the receiver's name. - Production code never names a test suite's class, as the method-name fallback already held: `manager.check()` in Django's `Model._check_managers` is no test app's `Manager` model. - A Python base built by a class's factory, Django's `class Manager(BaseManager.from_queryset(QuerySet))`, names that class as the supertype, besides the factory's arguments. A/B against main with fresh indexes, every changed edge triaged. Django: 67 links added (model instances reaching `Model.save`, inherited lookups and expressions), 40 removed (links into test-local classes from outside their function, and one polymorphic manager call), 263 retargeted: about 170 to the right class (`user.has_perm` to `PermissionsMixin` rather than `AnonymousUser`, `parser.parse` to the template `Parser` rather than `MultiPartParser`, `field.get_lookup` to `RegisterLookupMixin`), about 90 from one guess to another, and about 20 worse, where a receiver named after a model holds something else (`session.save()` on a session store now guesses the `Session` model). flask: one fix (`app.get` to `Scaffold.get`), 3 wrong links removed. Alamofire: one fix and 3 protocol-typed locals that now guess the `Adapter` class. zod, excalidraw and spring-petclinic: higher confidence on the same targets. gin, cobra, express, eShopOnWeb and jekyll are byte-identical. Full suite on the merged tree: 7,908 passed, 0 failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f76021db22 |
fix(rust): a local holding a method call's value has the type the method returns (#2716)
A Rust local bound to a method call (`let server = b.port(80).build()`, `let s = cfg.load()?`, `let conn = c.connect().await`, `if let Some(c) = arg.get::<Completer>()`) had no type the typed receiver path could read, so calls on it fell to the name strategies. Those strategies did not read `?` on a returned `Result`, a `self` method's value or a turbofish, and picked a same-named method of another type. The value is now the return type of the method the call reaches on its receiver's type, read from that method's signature. A receiver is a local, `self`, or another such call. `.await` hands out an `async fn`'s return type (read off the source: the extractor's async flag is never set for Rust) or an `impl Future`'s `Output`, and an `async fn` not awaited is a future, left unread. A method's own type parameter is the call's turbofish argument. A parameter the call doesn't write stays opaque: `Standard<W>` is a `Standard`, but a value that is `W` itself is unread rather than its bound. A trait's provided method returning `Self` gives the receiver's own type. A type with two impls of one trait, `Encoder<Bytes>` and `Encoder<&[u8]>` or `Service<IncomingStream>` and `Service<Request<B>>`, was ambiguous for a typed call. The impl whose trait argument is the call's first argument's type now wins. A/B with fresh indexes against main on axum, serde, ripgrep, clap, dioxus, tokio and gleam, every changed edge triaged: 536 calls linked that were not, 69 removed (calls on std or dependency values a method returns, and tokio values whose read and write methods come from macro-declared extension traits), 50 moved to the declared type's method, 565 kept at a higher confidence. gin, serilog, flask and express are byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
23b5ca1b43 |
fix(rust): a call on a local reaches the method of the type it is declared with (#2715)
A Rust method call on a local variable or a parameter (`opts.build()`) was matched by the method's name whenever the receiver's declaration wasn't one of two unscoped line patterns. dioxus's `Commands::Build(opts) => opts.build()` lost its link once the build module's `BuildRequest::build` shared the name, and calls on std or dependency values linked to project namesakes: `cx.waker()` on a `Context` to tokio's `CachedParkThread::waker`. The receiver is now typed by the binding the call sees. The new src/resolution/rust-scopes.ts reads each file's parameters, closure parameters, lets, if-let / while-let / let-else, for and match-arm bindings with block scope. A binding's type is its written one, the value it is built from, or the variant or struct field its pattern takes. The method is the one Rust picks on that type: inherent first, then a trait impl whose trait is in scope, then a trait's provided method (a blanket impl only where its bounds are met), then Deref. `&`, Box, Rc, Arc, Pin, Cow and the lock guards are looked through, their own methods kept (one list with the self-call path's), and a project impl on the wrapper itself answers before what the wrapper holds, as it does for `self`. `impl Trait`, `dyn Trait` and bounded generics reach the trait's method or a supertrait's. Among types sharing a name, the one its path names through the project's modules wins, the path written or bound by a block-scoped `use`, else the one that has the method. A std, dependency or primitive type reaches only a project trait impl in scope, and not one that hands the call on to the type's own method of that name. A binding that writes no type leaves the call to the name strategies, which no longer take a project type from another declaration of the name. A trait's own methods carry no `pub`: isVisibleAcrossFiles lets them through for the calls this path resolved, and name guesses stay out as before. A/B with fresh indexes against main on axum, serde, ripgrep, clap, dioxus, tokio and gleam, every changed edge triaged: 4,645 calls linked that were not, 887 removed (calls on std or dependency values, project types without the method, a wrapper's own methods), 459 moved to the declared type's method, 3,669 kept at a higher confidence. gin, serilog, flask and express are byte-identical. The new code costs 0.6 to 0.9 s of CPU per full index of tokio, dioxus or gleam. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f5158fac02 |
fix(rust): a name a glob use brings in is an item the globbed module passes on (#2714)
dioxus's examples write `use dioxus::prelude::*;` and then `Element` or
`use_signal(...)`, which the prelude re-exports from dioxus-core and
dioxus-hooks: `pub use dioxus_core::{Element, ...}`, and `pub use
crate::hooks::*` through `pub use dioxus_hooks as hooks;`. The glob rule
(rustGlobCovers) only took an item declared in a file named like the glob's
last segment, so all of those went unlinked, while `use super::*` took
anything in the parent's directory and a glob over another crate's `types`
or `error` module took this crate's file of that name.
A name only globs bring in now goes through the module walk of #2702:
rustGlobItems walks each of the file's globs from the module it is written
in and looks the name up there. Two additions to the walk:
- a step through a module its parent passes on under that name
(`pub use dioxus_hooks as hooks;`, `pub use dioxus_signals::{self, *}`);
- Rust's visibility for globs: seen from inside a module (`use super::*`) a
glob takes everything; from anywhere else only what the module declares
`pub`, `pub use`s or `pub` globs. dioxus-hooks' `pub use use_effect::*`
does not pass on that module's own `use dioxus_core::*`, so the prelude
never brings in dioxus-core's `Ok` function; without this rule 146 std
`Ok(...)` calls in dioxus's examples linked to it.
A glob that brings the name in settles it, so one the walk can't follow
(a macro's module, a #[path] module) matters only when none does; then the
old module-name rule answers as before.
A/B with fresh indexes, main
|
||
|
|
e2aa5d205d |
fix(rust): a same-file path's inline module is read from its braces, not its indentation (#2713)
PR #2711 and PR #2702 landed a way each to tell which inline `mod` a
place in a Rust file is in. #2711's isInRustInlineModule scanned lines
upward for `mod <name> {` and trusted rustfmt's indentation to find where
the block closes. #2702's rustModBlocks scans the braces, skipping
strings, raw strings, char literals and comments, and keeps the result in
rustUsesOf. isInRustInlineModule now asks rustBlockAt for the innermost
block around the candidate, as rustDeclaredItems does, so there is one
answer to that question.
What changes:
- An item written without indentation inside `mod flat { … }` is found
through `flat::Flat`. The indentation rule rejected it.
- `outer::Shell` is only `outer`'s own `Shell`, not one declared in a
`mod deep` inside it. The upward scan accepted any item below the
`mod outer {` line.
- A file whose braces don't all close says nothing, as it does for the
`use` walk of PR #2702.
The A/B (main
|
||
|
|
818e780336 |
fix(rust): a trait's associated type named <T as Trait>::X or bound as Trait<X = …> links only to what it names (#2712)
A trait's associated type is named in two more ways than `Self::X`, and
neither names a module item:
- Through a qualified self, `<T as Trait>::X`. isRustNameInScope took any
associated item of the name for it. axum's `<L as Listener>::Io`, with
`L` a generic, went to `ReadyListener`'s `type Io`. A trait's own
`fn call(…) -> <Self as HandlerCallWithExtractors<T, S>>::Future;`
went to an impl's `type Future` further down the file.
- As a binding in the trait's arguments, `Iterator<Item = &str>` or
`Readable<Storage = UnsyncStorage>`. The name was matched like a bare
type. axum's `impl Iterator<Item = &str>` went to a same-file
`enum Item`, and dioxus's `Storage = …` bindings to the `Storage` trait.
Fix, in isRustNameInScope:
- isRustAssocBinding: a name followed by `=` or `:` inside an unclosed
`<` on its line is a binding or bound, and links nothing. The trait's
own associated type is no node.
- rustQualifiedSelf reads `<T as Trait>::X` (and `<T>::X`) as its self
type and trait, each as the last segment of its path:
- `Self` means `Self::X`: the enclosing impl's own item, nothing in a
trait's body;
- a generic means no impl's;
- a type means its impl of that trait (the candidate's
`Owner::Name` must match, and its impl header's trait too).
Tests: rust-assoc-qualified-self.test.ts, 3 tests, on both engines; all
3 fail before the fix, and switching off either rule fails its tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
6fe9dc1266 |
fix(python): resolve imports from the package roots the project has (#1921)
Python imports now resolve the way the interpreter finds modules: from the package roots the project has, not by any file whose path merely ends in the module's name. A project root is the repository, each service with its own pyproject.toml, setup.cfg or setup.py, its src/, and the package directories its build config declares; a local root answers only importers within its reach, so a test fixture's copy of a package never becomes the application's. Relative imports climb from the importer's own package. Before, `import json` or `from datetime import date` could link to a project file or function of that name, and a src-layout package like flask's was found through a test fixture named flask.py. On flask, fastapi, mealie and django, relative imports now resolve (about 2,200 links added), stdlib and third-party names stop linking to project namesakes (about 1,300 wrong links removed), and imports of a module's function land on the function rather than its file. A sync re-opens what an added or removed module, an edited __init__.py or a changed package root can move. Indexing CPU time is unchanged. Merged with main: a function-local `import a.b as X` (PR 2607) now uses the root-aware lookup, the outside-module receiver check (PR 2606) passes the importer, and plain imports are read at module level only, since the mappings apply to every function of the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
505c355be3 |
fix(rust): a name written through a path is what the path names, not a same-file namesake (#2711)
A Rust name written through a path is an item of what the path names: a
module (`ast::Pattern`, `io::Error`), a type (`Source::Record { … }`) or a
crate (`mio::Interest`, `std::ffi::OsStr`). isRustNameInScope accepted any
candidate in the file the path is written in, whatever the path said, and
a std path only when the candidate was in another file:
- clap's `std::ffi::OsStr` linked to its own `struct OsStr`, and
`io::Error`, `fmt::Error` and `fmt::Result` in `error/mod.rs` to its own
`Error` and `Result`;
- tokio's `mio::Interest` linked to its own `Interest`, and
`std::time::Instant` to its own `Instant`;
- serde's tests' `Token::Struct { … }` literals linked to their own
`Struct`s, and serde_derive's `syn::Path { … }` to a `Default::Path`
variant;
- gleam's `ast::Pattern` linked to the `enum Pattern` beside it, and
`BinaryBuilderPosition::RecordAttribute { … }` to another enum's
variant of that name.
Fix, in the path branch of isRustNameInScope:
- A path from std, core, alloc or a crate the manifests depend on names
nothing of the project's, in any file, unless the project is that crate
or a `use` in the file binds the root.
- The same-file shortcut is gone. A candidate is the path's when its
file is the path's module, its qualified name holds the segment (a
type's variant or method), or it sits in an inline `mod` of that name
in the file (isInRustInlineModule; rustfmt closes the block at the
indentation it opens at).
- Through a module a `use` binds, a candidate must be in a crate that
`use`'s path can reach (rustUseCanReach, as for a bare name): tokio's
`io::Handle` under `use crate::runtime::io` is tokio's own, not
tokio-test's `io.rs` `Handle` (a dev-dependency).
Tests: rust-path-same-file.test.ts, 6 tests, on both engines; 5 fail
on main (the sixth is the inline-module guard), and the crate-reach one
also fails with only that rule switched off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5e72a43b3c |
fix(rust): a name a use brings in is the item of the module its path walks (#2702)
tokio's signal/mod.rs writes `use crate::sync::watch::Receiver;` and
tokio-stream `use tokio::sync::watch::Receiver;`, and both linked their
`Receiver<()>` to the `Receiver` of net/unix/pipe.rs. Every `Receiver` in
the crate read as in scope once the file imported one, and the first won.
isRustNameInScope now walks the modules a `use` path names, from the module
the `use` is written in: crate::, self::, super::, a module declared there
(2018 paths), the crate's own name, or a project crate its manifest depends
on. It looks the item up there as Rust does: declared in that module (not
inside a function or an inline module), else brought in by the module's own
`use` of that name, macro-wrapped ones included (tokio's `cfg_sync! { pub use
mutex::{Mutex, ...}; }`), else by its globs. A module declared inline, like
`pub mod futures { ... }`, is found by its braces. A candidate the path does
not name is out of scope, so every strategy, the framework name rule
included, lands on the item the path names.
What the walk can't follow says nothing, and name matching answers as
before: a module with no file, one a path attribute puts elsewhere, a glob
over an enum, a file whose braces don't balance. A module that holds no
such item the index knows, because a macro declares it or another crate
does, leaves nothing in scope. A `use` of the name that yields no item
doesn't hide the globs: clap's `pub use clap_derive::ValueEnum` is a derive
macro, and the trait comes in through `pub use clap_builder::*`.
A/B with fresh indexes, main
|
||
|
|
5a7a4347a6 |
fix(rust): a call on what as_mut() hands back reaches the receiver's declared type (#2710)
A Rust call written x.as_mut().m(..) or x.as_ref().m(..) reaches the resolver by its bare name, so it was matched by name alone. tokio's self.delay.as_mut().reset(now) on a Pin<Box<Sleep>> field linked to the calling Interval::reset, its this.timer.as_mut().as_pin_mut() on an Option to a stream's as_pin_mut, reader.as_mut().poll_read(..) on a generic R to BufReader::poll_read, and dioxus's self.child.as_mut().map(..) and self.server.as_mut().filter(..) on Options to its own GenerationalRefMut::map and Store::filter. Such a call is now read from the call's own chain and resolved on the receiver's declared type as as_mut passes it on: a Pin<P> re-pins what it points at (Rust's lookup through the Pin from the previous change), a Box, NonNull, Rc or Arc lends what it holds, an Option stays an Option, whose methods are std's, unless ? takes the value back out (self.client.as_mut()?.next()), and a type parameter or another std or dependency type gives no project method. A field is read from its struct's declaration, also through pin-project's let me = self.project() (a #[pin] field projects to Pin<&mut T>), even when a pin_project! macro wraps the struct; a local or parameter from its own declaration, which now also reads let x: T = .. annotations. A receiver whose type isn't written where it can be read, a pin! local or a call's result, is left to the name strategies as before. A/B on tokio, axum, clap, serde, ripgrep, gleam and dioxus: 33 wrong links removed, 7 moved to the right method, 13 right links added, none lost, and 28 same-target links now resolved from the declared type instead of by name. axum, clap, serde and the controls express, gin, flask and leveldb are byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a31bf2f476 |
fix(c): a macro alone below an #include <…> no longer hides the next declaration (#2709)
blankLoneMacroLines blanks a macro alone on its line, like FMT_BEGIN_NAMESPACE, before C and C++ parse, unless the line above ends in an operator it could be an operand of. A directive like `#include <glib/gtypes.h>` ends in `>`, so GLib's G_BEGIN_DECLS below it stayed, and the parser read `G_BEGIN_DECLS typedef struct _GBytes GBytes;` as one broken declaration. The first declaration of nearly every GLib header went missing (GBytes, GError, GHashTable, GList, GQuark, the GIOCondition enum), a few headers grew junk functions (one named `enum` swallowing a whole header), and the parse error kept the file off the native kernel. Objective-C already read a directive as ending at its line (#2691, directivesEnd). C and C++ now do too, so the flag goes: a directive continues only through a trailing backslash. The kernel takes C and C++ input from the hoisted preParse, so it needs no change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
90e2456b43 |
fix(cpp): a bare call on a parameter, local lambda or functor links nothing, not a function elsewhere of its name (#2708)
A C++ call written by its bare name on a name the calling function binds
itself (a parameter, a lambda's parameter, a local lambda or functor, a
range-for variable) calls that local: C++ lookup finds it before any function
or method of that name. The name strategies linked such calls by name:
protocolbuffers/protobuf's code generators' `format(...)` on their
`Formatter& format` parameter went to the conformance suite's
`TestResult::format`, and facebook/rocksdb's tests' local
`auto key = [](int i) {...};` calls to `ArenaWrappedDBIter::key`.
isCppLocalCallableCall, checked in matchReference beside the JS/TS rule,
reads the caller's declarations of the name: its parameters, a declaration
that starts a statement (`auto x = ...`, `T x = ...`, `T x(args)`, a later
name in a list), a range-for variable or a lambda's parameter, the nearest one
in a block still open at the call deciding. A local type or a function the
body declares is not a local callable (the call constructs it or calls the
function), and an expression that reads like a declaration (`a && f(x)`)
declares nothing. A local's type may carry a function type in its template
arguments (`std::function<void()> done, failed;`), so the first name of such
a list counts as the later ones do, and the #2698 test of that list now
expects no edge for either. The looser isCppLocalName used by implicit-this
is unchanged.
A/B on main
|
||
|
|
01cd287786 |
fix(express): only the handler argument decides whether a route is inline (#1957)
An Express route keeps its named handler when a middleware before it
holds an arrow function, like rateLimit({ keyGenerator: (req) => req.ip })
or (req, res, next) => next(). Only the last argument decides whether
the handler is inline, and strings, regex literals and type arguments
no longer end an argument or a body early. The Express, route and
framework suites pass (757 tests); on express itself three named
handlers gain their link.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
9dc3bc3adc |
fix(resolution): honour TypeScript type modifiers on imports and re-exports (#1955)
Import and re-export lists are now read with TypeScript's `type`
modifiers (`export type { A } from`, `export { type A } from`,
`export type * from`), comments inside the list, ES2022 string names
and JSDoc @import tags. A type re-exported from a package, like vben's
`export type { VxeGridProps } from 'vxe-table'`, no longer links to a
project type of the same name, and type imports link through the
import rather than by name. The import-related suites pass (609 tests)
and indexing CPU time on vben is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
8afb970a23 |
fix(ts): an awaited call with type arguments links to its callee (#2595)
tree-sitter-typescript parses `await f<T>(x)` as a call whose callee is `await f`, so the reference was named `await f` and matched nothing. Both engines now peel the await, skipping a comment after it, and the reference sits where the callee starts, as for `await f(x)`: on the call node, which starts at await, the checks that read the source at a call's position missed the callee, and a package-imported `await get<T>()` linked to a project class's static get. A/B: excalidraw +2 (compressData, decompressData), vben +6, zod and bulletproof-react unchanged. Fixes issue 2586. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
63476df6a6 |
fix(react): JSX at module scope, in a top-level const or in a test callback renders from where its calls are attributed (#2611)
JSX outside every function now renders from what a plain call there is attributed to: a top-level value, or the file. Ownership is by line, so no tag counts twice. Merged with main's destructuring rules (PRs 2676 and 2677): an owning parent takes a name as its local by its own lines or by the file's top-level destructuring, and function parents keep main's scope rules. The viewer's shared-chrome count no longer counts a file as a render parent. A/B: excalidraw +132 and bulletproof-react +38 render sites (tests to the components they render, entries to their root apps, stories to their components), none removed. Fixes issue 2587. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b95b316860 |
fix(sync): git sync hooks refresh the project they were installed for (#1965)
Git runs hooks from the top of the work tree, so a hook running a bare `codegraph sync` never reached a sub-project's index. Each hook block now names the project root it syncs, shell-quoted, one block per root, placed right after the shebang so a user hook that ends in exit cannot skip it. A hook in another language is left untouched and reported, and a block from an older install counts as the top-level project's. A project path with a line break is refused, since the root is written on a comment line in the hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e3f23cc4bc |
fix(go): a local's scope ends at its case clause, and a func-typed result's parameters are not locals (#2597)
Main's gate for a Go call to a local func value or parameter that shadows a package function (PR 2658) reads scopes through the Go scope reader, and two of its readings were too wide. A name declared in one case of a switch or select stayed in scope in the clauses after it, and the parameter names inside a func-typed result, like func() func(run string), *func(...) or chan<- func(...), were read as the function's own. Either made a later call to the package function look shadowed and drop its link. The PR's own earlier shadowing check is left out: the gate already covers every case its tests pin. Go A/B on gin, cobra, etcd, prometheus and hugo is byte-identical. Fixes issue 2580. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
23ff26be68 |
fix(python): an aliased module import inside a function links its calls (#2607)
`import a.b as X` written inside a function binds X there, ahead of any module-level binding of X, so X.f() reaches a/b.py's f, as the same import at module level already did. An undotted alias is left alone, since an optional dependency's name can match any project file. mealie's lifespan_fn now links init_db.main; the other repos are unchanged. Fixes issue 2600. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4f222a3d65 |
fix(python): the module of a from-import no longer links a same-named variable (#2603)
The module of a Python `from X import ...` names a module or package, never a symbol, so `from app import email` no longer links the importing file to a project variable named app, like a FastAPI or Flask app = FastAPI(). A from-import module that resolves to anything but a file or module links nothing. On five Python repos this removes 12 wrong import links and nothing else. Fixes issue 2584. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e48565067e |
fix(python): a call through a module-level instance links its class's method (#2608)
A Python call through a module-level instance of the same file, like _svc.delete(obj) after _svc = SceneService(), now links to the method its class has or inherits, as a method value through that global already did. On django and fastapi: 50 correct links added, 42 moved to the right class (DEBUG_ENGINE's Engine, the dispatch tests' Signal), confidence raised on about 400 more. CPU time on django is unchanged. Fixes issue 2582. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
cc1eb1f942 |
fix(python): a call on an imported outside module no longer links a same-named project method (#2606)
A Python call on a module the file imports from outside the project, like subprocess.run(...) after import subprocess or sp.run(...) after import subprocess as sp, is that module's function, never a project class's method of the same name. The receiver must be bound only by such imports: an assignment, loop or tuple target, a global write in a function, or a fallback class or def of the name keeps the old lookup. On django, mealie, fastapi and flask this removes 168 wrong links (mock.call, time.sleep, sa.text, unicodedata.normalize, click.command) and adds none. CPU time on django is unchanged. Fixes issue 2599. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c8f154b9f4 |
fix(explore): pin the dominant-file aggregation to an edges-first plan (#2575)
The dominant-file aggregation behind codegraph_explore could let SQLite drive the query from the nodes table, which on a large index took seconds to a minute. The query now pins an edges-first plan. On four indexes it returns the same rows, 4 to 12 times faster. Fixes issue 2571. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b5553e238a |
fix(rust): an impl's associated type or const no longer takes a bare name or a module path (#2707)
A `type` or `const` written inside an `impl` belongs to the type: Rust
reaches it only through a path like `Self::Err`, `<T as FromStr>::Err` or
`T::Err`, never by its name alone, and no module path or `use` names it.
Both extractors put it at file scope under its bare name, so the resolver
could not tell it from a module's own alias:
- a same-file `Err(…)` call linked to a `FromStr` impl's `type Err`, and
serde's `Ok(…)` calls to a serializer's `type Ok`;
- axum's routing tests' `Response` linked to the `type Response` of
`routing/mod.rs`'s `impl Service`, a private item the modules inside
it can see;
- `io::Error` and `std::error::Error` linked to the `type Error` of an
impl in the same file, and serde's `use crate::de::Error` to one in
`de/mod.rs`.
Fix:
- Both extractors (rustImplItemOwner, and the kernel's impl_item_owner)
qualify an item written directly in an impl by the impl's type, as they
already qualify its methods: `ValueHint::Err`, `Limits::MAX`. A type no
single name stands for keeps the impl's spelling, whitespace collapsed:
`(A, B)::First`, `[u8]::Out`, `u64::WIDTH`.
- isRustAssociatedItem: a Rust type alias or const with a qualified name.
Nothing else of those kinds gets one: an inline module adds no prefix,
a function body's items aren't indexed, and a trait's default `type`
is already its trait's.
- isRustNameInScope: such an item never matches a bare name, and matches a
path only as `Self::X` in its own impl or as `<…>::X` (unchanged).
- resolveRustPathReference never picks one for a `crate::m::X` path.
Tests: rust-associated-item-scope.test.ts, 6 tests, on both engines; 5
fail on origin/main. The kernel parity torture fixture gains associated
items on a tuple, a slice reference and a primitive.
A/B on fresh indexes, origin/main
|
||
|
|
6c49bfa667 |
fix(go): a call through a variable declared with another package's type reaches that type's method (#2706)
Go receiver inference read a parameter's type only when the line patterns could: a lowercase name of the caller's own package (`s *server`) or a capitalized one (`lg Logger`). A parameter declared through a package, like `client rest.Interface`, `c clock.PassiveClock` or `t *testing.T`, had no type, so a call on it was matched by name: kubernetes' `client.Patch()` reached `RESTClient::Patch` instead of the interface, `c.Now()` an unrelated `Clock`, and every `t.Errorf(…)` a test helper's `T` interface. Only standard method names (PR 2664) and receivers named like an import (PR 2448) asked the scope reader. inferLocalReceiverType now asks the scope reader first for every Go receiver. The declaration in scope (a parameter, a result, a name grouped with others, a `var (…)` spec, a function literal's parameter) gives the named type it writes, as GoReceiverDecl.declared, ahead of the patterns, which scan back with no block scope. matchMethodCall, matchMemberFunctionRef and matchGoFieldChainCall all use it: - a project type resolves in its own package, through embedding and aliases; - a type from outside the project, a predeclared one or an alias of either (goHasNoProjectMethods) reaches none of the project's methods, and neither does a type literal; - a qualifier none of the file's imports names keeps the old lookup by name. Standard method names keep the rules PR 2664 gave them. Any other name the declared project type lacks goes on to the name strategies, as on a receiver of unread type. The block that typed only receivers named like an import is subsumed and removed, with the field it read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
70783f31f1 |
fix(objc): a message to an ivar or a property reaches the class it is declared as (#2703)
An Objective-C message to a local or a parameter was typed by its
declaration (`FMResultSet *rs` makes `[rs close]` FMResultSet's). One to
an instance variable or a property was not, since that declaration sits
outside the calling method: in a header's @interface, an @implementation's
ivar block, a superclass, or synthesized for a property. The receiver's
name decided instead. ObjFW's tar tool's `[_archive nextEntry]` reached
OFLHAArchive::nextEntry, and React Native's `[_displayLink invalidate]` on
a CADisplayLink reached RN's own RCTDisplayLink::invalidate.
- objcFileMembers reads what each class declares, per file: the ivar
block right after an @interface head (seen by subclasses; the head may
go on over several lines, a protocol list on a line of its own), a
class extension's or an @implementation's ivar block (the class's own),
@property declarations, including ObjFW's two-line ones and IBOutlet
ones, and @synthesize lines. Types take protocol lists, type arguments
and ObjFW's OF_GENERIC(...).
- objcMemberReceiverType, after the local inferrer, follows the language.
`self.name` is a property of the class or a superclass. A bare name is
an ivar: the class's own, one its own property synthesizes (`_name`,
never a category property's), one @synthesize names, or a superclass's
@interface ivar. A bare name is never a property's: `arcs` in
`initWithArcs: (...)arcs` is the parameter. A capitalized bare name is a
class, and `self.name` needs a property node, which skips most lookups.
The type then takes the typed path (resolveMethodOnType at 0.9), and a
class from outside the project has no project method, as for locals.
- objcClassFiles: when more than one file declares `@interface Name`
itself, those are namesakes in separate targets, and only the ones in
the calling file's directory count. SDWebImage's TV demo ViewController
was otherwise typed by the macOS demo's `NSImageView *imageView1`.
- localReceiverTypePatterns('objc'): a local's or a parameter's type may
carry OF_GENERIC(...), like `OFMutableArray OF_GENERIC(OFNumber *) *arcs`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
f2f5dee044 |
fix(rust): a call through a Pin, Rc or Box self reaches the method Rust picks, inherent first (#2701)
A Rust method called through a self declared Pin<&mut Self>, Box<Self>,
Rc<Self> or Arc<Self> was typed by its wrapper. tokio's
self.poll_flush(cx) in impl AsyncWrite for File went to its blanket impl
AsyncWrite for Pin<P>, whose poll_flush would need a Pin<&mut Pin<...>>,
and nearly every other such call linked to nothing: tokio's
self.poll_write_priv(..) and self.get_pin_mut(), dioxus's
self.in_scope(..) through self: &Rc<Self>.
Such a call is now looked up as Rust does: the type's own method taking
self through that wrapper first, then the wrapper's own (Pin::get_mut,
Rc's clone: std's, which the index doesn't hold, or a project impl on the
wrapper taking &self), then the type's other methods by deref. A value
declared Pin<&mut T> is looked up the same way on T, so
read.poll_read(..) on a generic R no longer reaches the Pin<P> impl.
A type's inherent method comes before a trait's of the same name, as in
Rust: on a path (tokio's Arc::from_raw(ptr) is std's, not its impl Link
for Arc<ScheduledIo>'s), on a typed value, and through self, where each
of several trait impls for one type calls its own block's method. In a
trait impl, fn wake(self) { self.wake() } hands the call on to the
type's own method, never back to itself, and (self.f)(x) calls what a
field holds, no method. A method called on a tuple, serde's
(self.ip(), self.port()).serialize(s), is no named type's.
A/B on tokio, axum, clap, serde, ripgrep, gleam and dioxus: 30 wrong
links removed, 13 moved to the right target, 74 right links added, none
lost. express, gin, flask and leveldb are byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|