mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
4a591aeee84b18a9cb1409663745aa312de2c9af
1464 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4a591aeee8 |
fix(cpp): a call to an overloaded free function, like protobuf's ParseWithLimit(limit, data, result), reaches the overload its arguments fit (#2612)
A C++ call to a function outside a class took whichever same-named
function the name strategies ranked first, by file and line proximity,
never by its arguments. Methods already picked their overload by argument
count, string literals and written argument types (cppOverloadFor);
functions never did. protobuf's `ParseWithLimit(limit, data, result)` on an
`int32_t result` reached the `int64_t&` overload beside the call, and
rocksdb's `NewLRUCache(cache_opts)` the overload taking a `size_t
capacity`.
Exact, qualified (global, exact, partial) and macro-namespace matching now
keep their choice of function, then let the call's arguments pick among its
overloads, the functions of its qualified name the call's file can see
(cppFunctionOverload, with cppOverloadFor's count-then-type fit; the
macro-namespace scorer breaks its ties by type). The choice stays on a tie.
- An overload only another source file sees (another `.cc` file's
`static`, an unnamed namespace's) never competes. When the name strategy
had picked such a function, which was then rejected, the call now links
to the overload its file sees: leveldb's `GetLengthPrefixedSlice(&input,
&key)` reached memtable.cc's static and linked nothing.
- A function hides a class of its name, so a call to a name both have
reaches the function.
- Only C++ calls: protobuf's Rust FFI calls lost their `extern` targets to
the C definitions when every language went through it.
Argument types read better too: a parameter named like the function it
belongs to, protobuf's `std::string u64(uint64_t u64)`, was read as the
function's own declarator, so `u64` looked like a `std::string`.
Validated on the cgstd kit against main (
|
||
|
|
627018eb25 |
fix(cpp): an explicit specialization, like protobuf's WireFormatLite::ReadPrimitive<int32_t, WireFormatLite::TYPE_INT32>, is named after its template (#2609)
An explicit specialization of a function or member function template kept
its template arguments in its name: protobuf's `template <> bool
WireFormatLite::ReadPrimitive<int32_t, WireFormatLite::TYPE_INT32>(…)` was
named `TYPE_INT32>` and qualified `…::ReadPrimitive::TYPE_INT32>`, the
qualified id split at the `::` inside its arguments; rocksdb's `template <>
int64_t Deserialize<int64_t>(…)` was `Deserialize<int64_t>`. A call writes
the bare name, so nothing linked to them.
Both extractors (wasm and the native kernel) now name a specialization
after its template, from the template-id's own name, and take its receiver
from the text before the declarator's name. That text must end in a real
`::`: tree-sitter recovers Metal's `vertex VertexOut vertexShader(…)` with
a missing, zero-width one.
A specialization now meets its template among a name's candidates, so the
resolver chooses between them wherever it picks among C++ candidates (a
class's methods, the implicit-this walk and its bases, exact, qualified,
global, fuzzy and macro-namespace matching):
- a call that writes template arguments reaches the specialization
written with the same ones (spacing aside, or qualified further on one
side), else the template itself or an ordinary overload, else nothing:
fmt's `num_bits<unsigned short>()` stays on `num_bits<T>()` instead of
the nearer `num_bits<uint128>()`; protobuf's `Arena::Create<T>` keeps
calling `DefaultConstruct<T>`, not the tests' `DefaultConstruct<
DispatcherTestProto>` hook; a dependent `ReadPrimitive<CType,
DeclaredType>(…)` links nowhere rather than to the first specialization.
- a call that writes none, or `<>`, reaches a specialization only when
nothing else has the name.
- a class's own member template, specialized but none for these
arguments, ends the lookup there instead of falling to a base's
namesake.
The `cpp-override` pass leaves specializations out on both sides: a member
template is never virtual.
Validated on the cgstd kit against main (
|
||
|
|
f7951a13db |
fix(cpp): a method a macro of its file defines, like protobuf's PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, number, int), is indexed where the macro is invoked (#2605)
protobuf declares its descriptor accessors in their classes and defines
them below with macros, like `PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor,
number, int)`; its reflection pastes `Reflection::Get##TYPENAME` in
another. The parser never sees those definitions (the preParse blanks the
invocations) and a declaration mints no node, so the methods had none and
every call to them linked to nothing.
- After either extractor (the kernel or the wasm walker), a step over the
original source expands the file's own function-like macros whose body
defines `Class::method(...) ... {`, at each invocation at namespace
scope, and mints a method node per definition: named and qualified like
an out-of-line definition, contained by the file and the same-file
class, with the expanded head as its signature. Constructors,
destructors and variadic macros are left out.
- The parse worker leaves the raw kernel fast path only for a file that
may define one (a cheap text test), so the step sees it.
- The C++ resolver reads such a method's return type and parameters from
its signature, and never takes it for a deleted overload.
A/B vs main on the kernel path, every changed site triaged: leveldb and
redis identical; fmt +13 nodes and rocksdb +11 (gtest's TestEventRepeater
overrides); protobuf +257 nodes, 6,203 calls and 2 references added, 252
calls retargeted, 11 metadata-only, none removed. Kernel and wasm dumps
are byte-identical, protobuf's one pre-existing Swift line aside.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
4727a068e4 |
fix(cpp): a call on an element read by subscript, statuses[i].IsNotFound(), calls the element's method (#2602)
The extractor records a call on a subscripted receiver, like `statuses[i].IsNotFound()`, `buffers[key].Flush()` or `iters[i]->Valid()`, by its bare name, which went to whichever class had a method of that name, and a local an element initializes (`auto& s = statuses[i]`) had no type. - C++ expression typing reads a subscript: an array's element, what a pointer points at, a library map's mapped value, a sequence's element, or what a class's `operator[]` returns. - Declarations record their array dimensions (locals, parameters and data members, also through an alias), so a subscript, `*arr`, `arr->` and a range-for over an array read the type the array is declared with. - The bare-name receiver reader takes subscripts after a name or `(*name)`, reading the line with its literals blanked. - Overload arguments use the same subscript typing: a map's `values[key]` is its value type, no longer a `std::pair`. A/B vs main, every changed site triaged: leveldb 12 removed, 14 added, 1 retargeted, 11 metadata-only; fmt 6/4/10/2; redis identical; rocksdb 513/292/843/807; protobuf 58/5/34/66. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8f951e597b |
fix(cpp): a call on the member of the pair a map's iterator points at, it->second.Flush(), calls the value's method (#2601)
The extractor records `it->second.Flush()`, `kv.second.Flush()` and `(*it).first.Print()` by their bare names, which the name strategies gave to whichever class had a method of that name — 45 `std::string::c_str()` calls went to googletest's `FilePath::c_str` — and a local such a member initializes (`auto& v = it->second`) had no type. `x.first` / `x.second` (and `->`) on a `std::pair` now type as the pair's elements in C++ expression typing (cppPairMember), so a map's iterator, range-for element or declared pair reaches its key and value, and #2598's bare-name receiver reader also takes `name->second`, `name.first` and `(*name).second`. A member typed by another class template's parameter (`CoreLocalArray<T>::AccessElementAndIndex()`'s `std::pair<T*, size_t>`) goes on as before: only the calling function's own template parameter names no particular class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
18efc8c95f |
fix(cpp): a call on a dereferenced iterator or pointer, (*it)->Visit(), calls the method of what it points at (#2598)
The extractor records `(*it)->Visit()` and `(*node).Weight()` by their bare names, which the name strategies then gave to whichever class had a method of that name (0.4-0.7 guesses), and a local a dereference sets, `auto first = *nodes.begin()`, had no type. `*x` and `x->` in C++ expression typing now dereference a container's iterator to its element as well as pointers and holders (cppDereferenced, used by cppExpressionType, cppMemberObject and cppChainInnerType), and a bare-name call written on `(*name)` (read at the call's column, where the receiver starts) is resolved on the dereferenced value's class by C++ lookup before the name strategies run: the method, or no project method for a library type, a template parameter's, or a class that lacks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c29caf2ae8 |
fix(cpp): an auto local holding a library container's iterator calls the element's method through -> (#2579)
`auto it = logs_.begin(); it->IsSyncing()` had no type for `it`: a call on a library container (`std::deque<LogWriterNumber>`) returned nothing the auto-local reader could use, so `->` calls on it were matched by the method's name. A standard or abseil container's `begin`/`end`/`find`/ `lower_bound`/`rbegin`/… now types as `<container>::iterator`, and a standard search (`std::lower_bound`, `std::find_if`, `std::next`, …) as the iterator it is given first; #2576's cppArrowTarget then reaches the container's element. The element is looked up where the container's type is written (the `auto` local's declared site, a data member's class), not where the call is: an element nested in another class (`vs->obsolete_files_.begin()`) finds that class's type, not a namesake visible at the call. A holder's type stays looked up at the call: from a pimpl member's header the lookup finds a namesake (protobuf's other `DescriptorIndex`) or nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8fe3156992 |
fix(cpp): a -> call on a declared container iterator calls the element's method (#2576)
`std::deque<Slice>::const_iterator it; it->data()` was matched by the method's name alone, so it could reach any class with a `data`, and `itr->c_str()` on a `std::vector<std::string>::const_iterator` reached gtest's `FilePath::c_str`. A `->` on a value of an iterator type now reaches the container's element, as `->` on a smart pointer reaches what it holds: the element is read from the container's template arguments (cppRangeElementOf), before the container's own `iterator` alias would lose them (protobuf's `RepeatedPtrField<T>::iterator`), and looked up as C++ does. A library element (`std::string`) or the caller's template parameter calls no project method. An element only a class template's own `begin()`/`operator*` writes with its parameter (googletest's `ParamGenerator<T>::iterator`) goes on as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c71a3d0373 |
fix(cpp): a receiver declared as a class template's member type, like SkipList<Key, Comparator>::Iterator, is that template's member (#2573)
The declarator regex read a type as a name plus one template argument list, so `SkipList<Key, Comparator>::Iterator iter(&list);` was read from its last name alone and looked up from the calling function: leveldb's skip-list tests linked `iter.Valid()` to `leveldb::Iterator::Valid`, rocksdb's to `IteratorBase::Valid`, and rocksdb's SkipListRep iterators linked their `iter_` member's calls (`InlineSkipList<…>::Iterator iter_;`) to their own methods. The type is now read whole, `Outer<Args>::Member`, and C++ lookup finds the member in the class template. A library template's member read this way is a library type only as an iterator (`std::vector<int>::iterator`, never a project class named `iterator`). Others name another type that may be the project's (`std::vector<Slice>::value_type`, gmock's `typename std::tuple_element<N - 1, MatcherTuple>::type matcher`), so their calls go on as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8f1f842b5b |
fix(cpp): a receiver type written from the global scope is looked up there, and a far lowercase outside type reaches no namesake (#2569)
The two follow-ups #2567 and #2568 left open. A receiver declared with a leading `::` lost it: the declarator regex started a type at a letter, so `::upb::Arena& a` inside `namespace hpb` was looked up from `hpb` and found a namespace-local `hpb::upb::Arena`. The declared type now keeps a leading `::` where one can stand (not after a name, a template's arguments or a `)`, so `SkipList<Key, Comparator>::Iterator iter` is not read as `::Iterator`), and resolveCppAliasedType no longer takes a function-local `using Foo = Bar;` for `::Foo`. A lowercase outside type declared outside the calling function still used the last-name typed match: #2413's library rule skips such declarations, and #2567's namesake gate took capitalized types only. So a far `absl::flat_hash_set<int> seen` linked `seen.insert(v)` to the project's `util::flat_hash_set::insert`, and a far `std::string text` linked `text.clear()` to a global project `string` class. The gate now covers lowercase types, which are decided as when no class shares their name, and cppFindsOnlyNamesakes reads a `std::` name exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
759acbbccb |
fix(cpp): an alias of a type written from the global scope names that global type (#2568)
protobuf's hpb declares `class Arena { … backend::Arena arena_; }`, where
`namespace backend = hpb::internal::backend::upb;` and that namespace says
`using Arena = ::upb::Arena;`. cppTypeName dropped the leading `::`, so the
alias's target was looked up as `upb::Arena` from the alias's own scope,
whose nearest `upb` is that very namespace: the alias named itself, the
lookup ended on no class, and `arena_.Fuse(other.arena_)` fell back to the
type's last name, linking `hpb::Arena::Fuse` (and `IsFused`, `RefArena`) to
itself instead of `upb::Arena::Fuse`.
TypeName now records a leading `::` (`global`), and a global name is looked
up from the global scope alone: when an alias's target is followed (follow,
and the scope expand looks the target up from and hands on), in a class's
base clause (baseClassesOf), and in cppAliasedTypeWritten, which #2532's
`auto` locals and chains walk alias by alias.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
00d7430ebb |
fix(cpp): a qualified receiver type never reaches a class that only shares its last name, and rocksdb::DB is RocksDB's own (#2567)
A C++ receiver declared with a namespace-qualified type is from outside the project only when no class of the project can be what that name names. Three cases read that wrong on main: - A namespace a macro stands for. RocksDB declares its classes in `namespace ROCKSDB_NAMESPACE` under `#define ROCKSDB_NAMESPACE rocksdb`, so the index names them `ROCKSDB_NAMESPACE::DB`, and #2532's outside rule (cppProjectScopeNames) never saw `rocksdb`: `rocksdb::DB* db; db->Put(…)` got no edge. cppProjectScopeNames now adds the name a single-name macro stands for, in both directions (cppNameMacros, the map cppNamespaceMacros already builds). - A lowercase outside type beside a project namesake. isCppLibraryType (#2413) took a non-std lowercase type as the library's only when no project type had its name at all, so `absl::flat_hash_set<int> seen; seen.insert(…)` reached a project's own `util::flat_hash_set::insert`. It is now also the library's when every project type of that name is a namesake (cppFindsOnlyNamesakes). - A capitalized type whose last name finds only namesakes, on the paths #2493's call-site rules leave to the last-name match: a declaration read outside the calling function (protobuf's `absl::Status _status` in its RETURN_IF_ERROR macro body reached `upb::Status::ok`; `auto bytes = ::hpb::Serialize(…); bytes.value()` reached `hpb::StatusOr::value`), and a namespace the project uses (its generated `DescriptorProto::ExtensionRange`, which the index doesn't hold, reached `Descriptor::ExtensionRange::options`). That call now gets no edge, as isUndeclaredTypeName rules where no class shares the name; `->` on a value still goes to the name strategies, and a template parameter's member is left alone. cppFindsOnlyNamesakes: every type of the last name, and every owner of a method of the called name (the index can hold a class's methods without the class), has a qualified name that ends unlike the written one, neither the other's tail; namespace aliases are expanded and macro-named namespaces read both ways. A name a project type qualifies, an unqualified name and an unbalanced `<>` are never judged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
eec9288a56 |
fix(react): a tag written in a comment or a string renders no JSX child (#2564)
The jsx-render pass read tag names off each parent's raw lines, so text
that only mentions a tag rendered it: react-router's hooks rendered the
`Router`, `Routes` and `Route` their JSDoc, comments and invariant
messages name ("You cannot render a <Router> inside another <Router>"),
shadcn's `useCarousel` the `Carousel` its error names, and layouts the
markup commented out of them (`{/* <Progress /> */}`).
The pass now scans a code-only view of each file (`jsxCode`, new in
src/resolution/jsx-code.ts): comments and the contents of string,
template and regex literals and of JSX attribute strings are blanked,
offsets and line breaks kept. The lexer follows JSX elements, so a quote,
slash or backtick in JSX text (`Don't`, `https://…`) stays text, and it
re-reads a `<` that opens a type parameter list (`<T,>(x: T) =>`) as an
operator. A file it can't read as one program (an unbalanced brace, a
string open at a line's end, a mismatched closing tag) is scanned as it
is, as before. TypeScript `.ts` files are skipped outright: they can't
hold JSX. The lexer's state lives in a class whose methods every file
shares: closures made per file sent V8 back to recompile it for file
after file.
Checked against TypeScript's parser on 36 React repos (7,649 files,
62,377 `<Name` matches): no real tag blanked, no file given up on, 614
non-JSX matches removed. Graph A/B on 12 repos: -36 jsx-render edges,
+0, nothing else changed; the TypeScript oracle grades all 36 as tags in
comments or strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
3f714bcc48 |
fix(jsx): a .jsx component links to the file-scope values it reads, as in .js and .tsx (#2561)
Value-reference edges (a reader symbol -> the file-scope const/var it reads, which impact follows) ran for typescript, tsx and javascript but not jsx. A .jsx file is its own language, parsed with the JavaScript grammar, so the same component had no edge to the lazy page it renders (`<LoginPage />` under `const LoginPage = Loadable(lazy(...))`), the route table or the config it maps over as .jsx, and all of them as .js or .tsx. Every other JS-family gate in the extractor lists jsx, and nothing in the history says this one was left out on purpose: the kernel mirrored the gap with an explicit "NOT jsx" check for parity. Add jsx to VALUE_REF_LANGS and drop the kernel's jsx exclusion. On mantis, matx, berry, mastodon and bigbluebutton-html5 the index only gains edges (35, 56, 15, 28 and 266): no node, ref or file changes and no edge removed. Every added edge is a same-file read of a file-scope value that is the only binding of its name in the file. A repo with no .jsx files indexes byte-identically, and kernel-parity.mjs shows 0 diffs over those repos with the same deferrals as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8206c8c271 |
fix(react): a JSX tag imported from the project renders what its module exports (#2563)
The JSX render pass matched a tag by name and used the file's imports only
to break ties, so an index that forwards another module's component
declared no candidate and the tag took a namesake elsewhere: outline's
Sidebar/index.ts is `import Sidebar from "./App"; export default Sidebar`,
and <Sidebar> rendered Aside.tsx's styled Sidebar, not the AppSidebar that
App.tsx exports as observer(AppSidebar).
A tag imported from a module of the project now renders what that module
exports under the imported name, read by exportedComponent, the reader
lazy routes use: through an index or barrel, past a wrapper call. Two rules
join that reader, for routes too: a const holding another binding is that
binding (`const Avatar = AvatarWithHoverCard; export { Avatar }`; a let may
be reassigned, so it stays), and a memo wrapper's bodyless component node is
the component it wraps (`const MemoizedBlurhash = memo(Blurhash)`). A module
whose export is no component node (`export default () => ...`) links nothing
rather than a namesake. Sync records every file a tag's import is read
through as a synthesis input, so rewiring only the index redraws the tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d0bb9be51e |
fix(react): a component a file declares itself as a value is what its JSX renders (#2420)
A PascalCase name that a .tsx/.jsx file declares as a module-level value is
that declaration wherever the file uses it: a JSX tag, a route's element, a
layout or a TSX type. Its initializer decides what it renders, in order:
- the component of the module its lazy loader loads, or the export the
loader picks, in either branch of a conditional;
- the function it wraps (`observer(function X() {…})`,
`forwardRef(Inner) as {…}`);
- else the declaration itself.
Before, the lookup by kind skipped the value and took a lone same-named
component anywhere, often in another app of the repository.
- Pattern 1 asks declaredComponent about every PascalCase reference, not
only route references. layoutComponent and the jsx-render synthesizer's
jsxChild ask it too.
- valueLoader reads a value's loader with loaderValue (#2480).
- defaultExportedName reads `export default observer(Login)`.
- lazyValueModules records the modules a file's lazy values load as
synthesis inputs, so a sync that changes one of them redraws the tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
9b237093a3 |
fix(sync): a lazy route relinks a page that appears below its barrel (#2560)
A lazy React Router route whose module is a barrel, and whose page sits in a folder below the barrel's, stayed unlinked after `codegraph sync` added the page or gave it its export, though a fresh index linked it. In DigiShop.Frontend, `@/pages` is `src/pages/index.ts`, which imports `./Home`, `src/pages/Home/index.tsx`, and exports it again. While the page is missing, the route's reference fails and waits under the key of the module it names, `module:pages` (#2422). The retry looks failed references up by the keys of the changed files, and a file in `pages/Home/` gives only `module:Home` and its own stem. The limit, which the `export *` barrels of #2436 share, was recorded with #2558. After the failed-ref retry, which makes the barrel's import edge into an added file, sync now walks up the import edges from each file it added, or gave a definition, to the barrels that pass it on: `export … from`, or an import exported again (`import { X } from './x'; export { X }`, `export default X`), as deep as the resolver chases a name. It re-opens, for the sweep, the failed references waiting under those barrels' keys. Only keys a failed reference waits under are looked up, in one DISTINCT over the failed rows parked under module keys, so a sync with none reads nothing, and a body-only edit seeds nothing. The ImportReader that the added-files reopen (#2512) uses is shared, and gains exportedBindings, behind a regex pre-check, since most importers export nothing. defaultExportBinding moves to alias-binding.ts for this walk and for react.ts's exportsBinding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e30a2c164d |
fix(react-router): a lazy route follows a barrel that re-exports an import (#2558)
A lazily loaded folder whose index passes on a page it imports linked
nothing: DigiShop.Frontend's `src/layout/index.ts` does `import { UserLayout }
from './UserLayout'; export { UserLayout }`, and its routes load
`await import('@/layout')`. exportedComponent, the barrel walk behind lazy
routes, React.lazy values and lazy layouts (#2436, #2452, #2480), read the
export clause as naming a declaration in the barrel, which has none: the
name is an import binding. `import Login from './Login'; export default
Login` and `export { Page as Component }` of an imported Page failed the
same way, so all 27 lazy route refs in DigiShop's App.tsx stayed failed.
A binding the module exports but does not declare is now followed through
the module's import of it, a default import as `default` and a named one by
the name it imports, with each file read added to `reads` so lazyModules
still redraws the route. A namespace import or a package's binding links
nothing. `export default withAuth(Page)` hands over a call, whose first
identifier ownExport reads; that was harmless while only same-file
declarations counted, so a default is followed only when the statement is
the binding itself (exportsBinding), or the route would link the HOC.
#2451 (open) adds the same step to the general import walk; #2420 (open)
carried it in a lazyRouteComponent rewrite that exportedComponent superseded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
5da4a41d92 |
fix(explore): tests and examples the engine knows stay out of explore's answer (#2557)
codegraph_explore's low-value check (the hard filter, the rank penalty and the comparator key) and the dominant-file, route-file and routing-manifest filter in db/queries.ts each kept a regex list of their own. They missed every test convention isTestFile learned since: C++ `foo_test.cc` and `FooTest.cpp`, Google's `foo_unittest.cc`, LLVM's `unittests/`, `testing/`, `e2e/`, `__mocks__/`, Gradle `foo-test/` modules, Kotlin source sets, and the examples, samples, fixtures and benchmarks that explore's gather already dampens through isTestFile. Both now build on isTestFile, keeping icons/i18n (explore) and generated files (queries), plus the singular `__test__/` the old lists matched. Dropping their lowercase suffix patterns also ends false positives such as `Latest.java` and `contest.cs`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1753472a3f |
fix(cpp): a call of a member template with explicit template arguments has the type the template returns (#2556)
A member call was read only when its callee ended in the method's name, so `reflection->GetRaw<RepeatedPtrField<T>>(…)` and `factory->GetOptions<BlockBasedTableOptions>()` gave the `auto` locals they initialize no type. Such a call now reaches the class's template overloads, and when each is declared to return the same one of its own template parameters (`template <typename Type> const Type& GetRaw(…)`), it returns the call's argument for it. A macro body's `\`-spliced lines are read as one statement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e03ff6730a |
fix(cpp): a receiver declared over more than one line has its type read (#2555)
Receiver inference read a declaration a line at a time, so a receiver whose type began on a line above (protobuf's `const google::protobuf::internal::ExtensionIdentifier<` over `FeatureSet, TypeTraitsT, field_type, is_packed>& extension`) or sat alone on the line above its name (rocksdb's `autovector<…>` over `cf_sv_pairs;`) declared nothing it could read, and the call fell to name matching. Such a line is now read with the lines it began on: joined until the template brackets pair, or with the type line above, as class members already were. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8cba7e5370 |
fix(cpp): a nullptr argument picks the std::nullptr_t overload, and no call links to a deleted overload (#2554)
A `nullptr` argument fitted any pointer parameter better than a
`std::nullptr_t` one, which counted as a class, so protobuf's
`ptr.reset(nullptr)` on an `Arena::UniquePtr` took `reset(T*)` over
`reset(std::nullptr_t)`. That `reset(T*)` is declared `= delete`, as is
`RepeatedPtrField::Add(const Element&)`, which `field.Add("foo")` reached.
`nullptr` now fits `std::nullptr_t` exactly and a pointer by conversion, and
an overload declared deleted is taken only when every overload is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
242862182b |
fix(cpp): a comparison or shift in a call's arguments opens no template, and a declared type's template arguments pair their brackets (#2553)
Splitting a call's arguments counted every `<` as a template's, so protobuf's
`p->Emit({{"declare", has_bit_word_index < 0 ? … : ""}, …}, "…")` and
`WriteVarint(num << 3, s)` read as one argument and took the wrong overloads.
A `<` now opens template arguments only right after a name and when a `>`
closes it before the bracket around it does, and the splitter closes the list
at exactly that `>`. A declared type's template arguments now pair their
brackets and may be empty, so `const std::vector<A*>& a, std::vector<B>& b`
and fmt's `parse_context<>& ctx` are read. A line that ends a declaration
begun above, like `std::unique_ptr<Blob>>>& reqs`, still declares the
receiver there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d4b9c2b4e9 |
fix(cpp): a line starting with * is read as code, and block comments and string literals are blanked before declarations are read (#2552)
The line-at-a-time C++ declaration scans took any line starting with `*` for
a comment continuation, skipping rocksdb's `const auto& el =` /
`*reinterpret_cast<std::shared_ptr<EventListener>*>(jhandle);`, and read code
commented out in `/* … */` and the text of string literals (protobuf's
`[[deprecated("Please use SpaceUsedLong() instead")]]`) as declarations.
Block comments are now found in the per-file pass that finds raw strings, and
every scan blanks them, with string contents, before reading a line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d94a92ea89 |
fix(cpp): a gtest TEST_F or TEST_P body finds its fixture's types, and a function's local class is seen only from that function (#2551)
A TEST_F / TEST_P body is a member function of a class derived from its fixture, so its unqualified names find the fixture's nested types (its bases' included) before the enclosing namespaces'. And since the index names a function's local class after the function, every TEST_F body's local `FakeWF` or `MyManager` shared one qualified name: a class local to a function is now visible only from inside that function, and a namesake class's methods are its own body's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
754cc45ad7 |
fix(cpp): an alias naming a class through a derived class leads to the class it inherits (#2549)
protobuf's `using ConformanceRequestSetting = BinaryAndJsonConformanceSuite::ConformanceRequestSetting;` names a class the suite inherits from `ConformanceTestSuite`. An alias was followed only to a class declared under exactly the name it spells, so constructions through it stopped at the alias. Its target is now looked up a segment at a time through each class's bases, as C++ does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6df4827625 |
fix(cpp): a call through a class reaches that class's own or inherited method, never a nested or namesake class's (#2548)
The class-name and capitalized-receiver strategies took any method of the call's name whose qualified name contained the class's name, so protobuf's `descriptor->file()` reached `Descriptor::ExtensionRange::file` and rocksdb's `CompositeEnv::IsInstanceOf(name)` reached `CompositeEnvWrapper::IsInstanceOf`, while an explicit base call like `PessimisticTransaction::PopSavePoint()` never found the method the class inherits. In C and C++ a method is now the class's only when its qualified name is the class's plus the method's, and a call written through a class asks C++ lookup, bases included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
34dd3d2fb4 |
fix(cpp): a nested class defined out of line is indexed under its name, and a construction makes the class C++ finds (#2542)
A class, struct or union whose declaration writes its name in a scope —
leveldb's `struct SkipList<Key, Comparator>::Node { … }`, rocksdb's
`struct BlockBasedTable::Rep { … }` — was indexed as `<anonymous>`, so its
members belonged to no class anyone names and a call on a `Node*` or `Rep*`
was guessed by the method's name. Such a class now takes its own name,
qualified under the written scope the way an out-of-line method is, and its
members are qualified under it, in the TS extractor and the kernel alike. A
specialization (`struct fmt::formatter<Answer>`) stays unnamed: named after
its template, it stood in for the template and every other specialization.
Constructions are recorded by the type's last name, so with `DBImpl::Writer`
named, leveldb's `new log::Writer(…)` in `DBImpl` methods went to it. A C++
construction now makes the class C++ lookup finds for the type as written at
the construction (matchCppConstruction): read with its template arguments
(`new Outer<int>::Inner(…)` makes an `Outer::Inner`), at 0.9, or 0.7 when it
is only the class of that name the file can see; an enum lookup finds; through
an alias the calling function declares, the class it names; and nothing for a
template parameter, a member of a type from outside the project or a local
alias of one. Of several classes or aliases the file declares under one
qualified name (the local classes of separate gtest TEST_F bodies, the members
of unnamed specializations), the one declared around the construction. A `<`
or `>` in parentheses inside template arguments is now read as a comparison.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
a6ceb0472c |
fix(cpp): a call to an overloaded method links to the overload its arguments fit (#2541)
Overloads of a C++ class's method were told apart by argument count and string literals only, for a call without a receiver; a call on a typed receiver always took the first one declared. rocksdb's `db->Get(ro, key, &value)` reached the five-parameter `Get(…, std::string* timestamp)`, and `Insert(ch)` on a `char ch` reached `Insert(const std::string& key, …)`. - cppOverloadFor takes the overloads the count fits best, then the one the arguments' types fit (cppArgumentTypeFit): a char, number, bool, null or zero literal; the written type of a name the calling function declares, of `&name` and of `name[i]`; or an expression's type (cppExpressionType). The same type scores, as does the same kind of type; a character, number or pointer for a string, a string or pointer for a number, and a character or number for a pointer count against. - cppMethodOf picks among a typed receiver's overloads the same way, of the class C++ found when namesake classes share its name (leveldb's two `RandomGenerator`s); a chained call `inner(…).name(…)` keeps the first, since the arguments at its position are the inner call's. - cppOverloadParameters reads an out-of-line method's defaults from its declaration in the class (protobuf's `Printer::Annotate`, whose `semantic` defaults only there). - cppOverloadFit: a string literal, raw ones too, counts against a pointer to anything but characters, a number and a character; comments are dropped from argument and parameter lists (cppWithoutComments). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
61cb4188a7 |
fix(cpp): a call or type name never links to what only another source file can see (#2538)
Name matching linked C and C++ names to declarations no other
translation unit can name: a class or alias a `.cc` file declares for
itself, its members, and what a source file's unnamed namespace
declares. In rocksdb, other test files' bare `Put(…)`, `Flush()` and
`dbfull()` reached column_family_test.cc's `ColumnFamilyTestBase`
thousands of times, and inlineskiplist.h's `Node*` receivers reached the
`Node` hash_linklist_rep.cc declares in `namespace { … }`.
- isVisibleAcrossFiles rejects such a candidate for a C or C++ ref
(isCppTranslationUnitLocal), as it does a `static` C function: a type
only source files declare; a member written inside such a class; an
out-of-line member of a type the member's own file sees only source
files declare (a scope its file sees no type of may be a namespace:
fmt's posix-mock-test.cc defines posix-mock.h's `test::close` beside
ostream-test.cc's own `struct test`); and, for the rest, what an
unnamed namespace of a source file holds. An alias of the scope keeps
the member, since the class may be declared anywhere.
- A source file's declarations are still seen by the file itself, by a
file that includes it, and by a header it includes below them
(rocksdb's version_set.cc includes version_set_sync_and_async.h after
its `FilePicker` classes).
- The unnamed-namespace spans come from a scanner that skips comments,
preprocessor lines (continued past a CRLF too) and string, character
and raw string literals.
- The typed lookups (cppMethodOf, resolveMethodOnType) take the
candidates the call's file can see first (cppVisibleFirst): rocksdb's
db_stress_shared_state.h and two benchmark `.cc` files each declare a
`SharedState`, and a call in db_stress_common.cc is the header's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
0cea43e9ba |
fix(cpp): -> on an absl::StatusOr reaches the class it holds; a using-declared member of a dependent base is found (#2539)
Two gaps in C++ member lookup. In protobuf, `absl::StatusOr<FeatureResolver> resolver = …; resolver->MergeFeatures(…)` was guessed by the method's name: the declared type's last name, `StatusOr`, is no class of the project's, and only std's smart pointers and optional were followed to what they hold. In rocksdb's typed_cache.h, `using BasicTypedCacheHelperFns<TValue>::DownCastValue;` makes a bare `DownCastValue(v)` the base's, but a bare name skipped every base that depends on the class's template parameters. - cppHeldType (was cppStdHeldType) also reads `absl::StatusOr`, `absl::optional` and `absl::NoDestructor` as holders, and matchCppHeldTypeCall takes one whose name no project type has (isCppLibraryHolder) — std's holders as before. - cppUsingDeclares reads the `using Base::name;` declarations a class body writes. cppScopeMethod and cppInheritedDataMember look a bare name up in a dependent base when the class brings it in from that base. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8d918067f8 |
fix(cpp): a bare call in a gtest TEST_F or TEST_P body reaches the fixture's method (#2537)
The index holds a gtest `TEST_F(Fixture, Name)` / `TEST_P` body as a function named `TEST_F`; gtest makes it a method of a class derived from `Fixture`. #2533 gave such a body the fixture's data members (cppTestFixture), not its methods. rocksdb's tests call `DBTestBase`'s `Put(…)`, `Flush()`, `Key(i)` and `NewIterator(…)` bare, and those calls were guessed by name, often reaching a namesake in another class at 0.4 (`ColumnFamilyTestBase::Put`); `auto iter = NewIterator(…)` gave `iter` no type, so `iter->SeekToFirst()` reached the nested `MemTableRep::Iterator`. - cppImplicitThisMethod is #2479's lookup, taken out of matchCppImplicitThisCall: the caller's class, its bases, the classes around it, with cppTestFixture as the owner of a gtest body. matchCppImplicitThisCall keeps the local-shadowing check for the link. - cppExpressionType types a bare call by the same lookup, as on `this`, instead of the class's first declaration. Of two classes sharing a name in different test files it reads the caller's own, and in a template a dependent base a using-declaration names still counts, as before (protobuf's `using RepeatedFieldWrapper<T>::MutableRepeatedField;`). `this` in a gtest body points to the fixture. - A `->` call on a value of a type from outside the project no longer takes the type's last name for the class it is in (isCppArrowThroughOutsideType); the name strategies decide. matchCppDeclaredTypeCall steps aside for `->` on a value before #2532's outside-type check, so typing JsonTest's `auto m = ToProto<Any>(…)` (an `absl::StatusOr<Any>`) had sent `m->value()` to protobuf's own `hpb::StatusOr::value` at 0.9. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bb817d4538 |
fix(cpp): structured bindings and function wrappers give their types to calls on them (#2540)
Receiver inference had no type for a name a structured binding declares,
for what a `std::function` returns, or for a class member whose type is
written on the line above its name. rocksdb's MultiCfIteratorImpl writes
`for (auto& [cfh, iter] : cfh_iter_pairs_) { iter->Valid(); … }` over a
member declared across two lines as a
`std::vector<std::pair<ColumnFamilyHandle*, std::unique_ptr<Iterator>>>`;
the calls were guessed by the method's name.
- cppStructuredBindingType reads a binding's type at the name's place: an
element of the range a range-for over the binding iterates, when the
call is in its body, or of a declaration's initializer the calling
function makes, a `std::pair` or `std::tuple` (cppBindingElements). A
map's range elements are pairs of its key and value (CPP_LIBRARY_MAPS).
- cppStatementFrom reads a statement on to its `;`, for a binding's
initializer and an `auto` local's.
- The declarator regex reads `std::function`, `std::move_only_function`,
`std::copyable_function`, `absl::FunctionRef` and `absl::AnyInvocable`
types, whose template argument is a function type, and
cppExpressionType gives a call on such a variable what the function
type returns (cppWrapperReturnType).
- cppDataMemberIn reads a member whose type is the whole line above its
name (CPP_TYPE_LINE), so a default member initializer continued on the
next line (`bool ready_ = flag_ &&` above `it_;`) isn't taken for one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
fedda29790 |
fix(cpp): a declaration with a qualifier or annotation around its name gives its type to calls on it (#2536)
Receiver inference reads a C++ declaration as a type followed by the name. What may stand between them was read as the type: in rocksdb's `Iterator* const iter = iter_guard.get();` the type was `const`, which names nothing, so the scan went on to an earlier function's `iter`, and in protobuf's `io::EpsCopyOutputStream* PROTOBUF_NONNULL stream` the type was a class named `PROTOBUF_NONNULL`. A name an annotation follows (leveldb's `MemTable* imm_ GUARDED_BY(mutex_);`) was not read at all. - The declarator regex (and the range-for type tail) reads past a cv- qualifier or `__restrict` before or after a `*` or `&` (`T* const`, `T const&`), the nullability annotations after one (abseil's and clang's, and the ALL-CAPS macros c-cpp.ts already blanks for the parser), and an ALL-CAPS annotation macro's call after the name. - matchCppDeclarator hands the readers the type without those annotations (cppDeclaratorType); cv-qualifiers stay, since what reads a type reads past them. An ALL-CAPS word counts as an annotation only after a `*` or `&`, so a type's own name stays (`OS_HANDLE* const h`), and template arguments are kept as written. - The call's own file, its same-named header, a class's members and a range-for's variable are read that way; the local-name check for bare calls reads past the qualifiers too. - #2533's test that a method's parameter is no member used `T* const` locals, which the scan couldn't read; it now declares no local, since one the scan reads decides first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
df3ce972f7 |
fix(cpp): a call on a data member of the caller's class reaches its type's method, wherever the class is declared (#2533)
Receiver inference read back through the call's file and the header of the same name only, so a member of a class declared elsewhere — rocksdb's DBImpl is declared in db_impl.h, its methods in db_impl_write.cc, db_impl_compaction_flush.cc, db_impl_open.cc and more — was never read, and calls on it (`versions_->GetColumnFamilySet()`, `mutex_.Lock()`) were guessed by the receiver's name. - Where the scan leaves the calling function, the name is looked up as a data member of the function's class before anything outside it (cppCallersMember): the class's own declaration lines wherever it is declared (the declaration in the caller's own translation unit, of classes sharing a name), then the classes it derives from through their base edges, skipping a base that depends on the template's parameters. A gtest `TEST_F` / `TEST_P` body is a method of a class derived from its fixture, so `db_` there is the fixture's. - The member goes into the declaration record as the caller's own, with its class as the lookup site, so #2493's lookup, the library-type gate and #2440's held-type path apply. A member of a template parameter's type, or of an alias of one, and a name the scan saw an unreadable declaration of, stay as before. (isCppTemplateDependentType now also counts a std trait of a parameter: protobuf's `using MapFieldBase = std::conditional_t<kIsMutable, …>`.) - cppDataMemberIn reads only declarations outside parentheses and not followed by an argument list (method declarations have no nodes, so their parameters are in the class's lines), skips nested functions and classes, follows block comments across lines, and blanks string literals. - A member declared below an inline method that uses it, and a member of the class itself rather than of a class nested in it, are found the same way; cppExpressionType's own member fallback became redundant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
79cd737bec |
fix(cpp): a call on an auto variable or a chained call reaches the class C++ finds, not a namesake (#2532)
#2493 resolved calls on declared pointers, values and references by C++ lookup. Calls whose receiver type came from an initializer or a return type still went by the type's last name: - An `auto` local now has the type its initializer has, as written where that is declared: a cast or smart pointer written out (`std::unique_ptr<Iterator>(…)`, `static_cast<InstrumentedMutex*>(arg)`), `new` / `std::make_unique` / a construction of a class, or what a function or method is declared to return (`txn->GetIterator(…)`), with the initializer read to its `;` across lines. Its calls then go through matchCppDeclaredTypeCall from that site. A type that depends on a template parameter (`T*`, `std::shared_ptr<T>`) is left to the old path. - A chained call `a->b()->c()` looks `c` up on the class C++ finds for the written return type of `b`, through a smart pointer too (matchCppChainOnWrittenType), before the last-name path. - A type from outside the project gets no project method: a std/abseil library type (#2413's gate, now applied before the last-name match, so a namesake class like fmt's own `ostream` no longer wins), and a capitalized type under a namespace the project declares no type in (`absl::Status`, which reached `upb::Status::ok`). - A bare call in a method types an `auto` local from the class's own or inherited method; a `template <…>` header followed by macros or specifiers still names its parameters; a trailing annotation macro (`T* PROTOBUF_NONNULL`) is no part of a return type; and memoized `auto` types are worked out with a fresh depth budget, so a deep chain that reads a local first no longer leaves it untyped for its other calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c19f0284f8 |
fix(cpp): a declaration inside a string literal is not a receiver's declaration (#2531)
C++ receiver inference reads back from a call for a line that declares the receiver, blanking comments (cppCodeOf) but not string literals. Text inside one was taken for the declaration: - protobuf's upb generator emits C code from raw strings: `GenerateRepeatedSetters(upb::FieldDefPtr field, …)` writes `upb_MiniTableField field = $field_init$;` inside an `R"cc( … )cc"`, then calls `field.ctype()` on its own parameter. The scan found the template's line first, and the call got no edge (a C struct has no methods). - rocksdb's log messages read as declarations: `"… in WriteBatch handler; …"` made `handler` a `WriteBatch`, and `"… remote compaction (…"` gave `compaction` the type `remote`, so calls fell to guesses by the method's name. New cpp-raw-strings.ts reads a file once, when it holds `R"`, as the compiler does (comments, string and character literals, raw strings with their delimiters), and keeps the column ranges each raw string takes per line. The receiver scan, in the file and its header, blanks those and the contents of the line's ordinary string and character literals before cppCodeOf. A character literal holding a quote (`'"'`), a digit separator (`1'000`) and an `R"(` inside a comment don't hide the code after them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
85201e95b3 |
fix(cpp): a smart pointer's held class lacking a method gets no edge only when its bases are known (#2530)
matchCppHeldTypeCall (#2440) returned null (no edge) for a `->` call on a `std::unique_ptr`/`std::shared_ptr`/`std::optional` whose held class lacks the method, unless that class is a class template. #2493 found the gap in the same rule for raw pointers and values: a base the index can't link may declare the method. protobuf's `class FlatAllocator : public decltype(ApplyTypeList<FlatAllocatorImpl>(…))` has no extends edge to `FlatAllocatorImpl`, and rocksdb's `WritePreparedTxnDB` derives from an alias each `#if` arm declares differently. Both paths now share one verdict, cppLacksMethod: no class template, and cppBasesKnown (every base on the way is linked to a class, or is a library type). Otherwise the call goes on as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
80e4b459ff |
fix(sync): an import moves to an added file that outranks the one it found (#2529)
An import written without an extension that resolved to one file, `./button` to `button.vue` while `button.ts` was missing, kept that edge and what it binds when `button.ts` was added beside it, though the resolver now finds `button.ts` first: sync only retried imports that had failed. A fresh index links the added file. When a sync adds a file, the module edges into the files an import of the same name finds instead (`dir/name.*` under another extension, the folder's own `dir/name/index.*` or `dir/name/__init__.py`) whose specifier now resolves to the added file go back to the sweep, and their importers join the added module's importers with that statement, so the names it binds are re-resolved too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4f3eaf33ae |
fix(resolution): component and scoped-alias imports link the module file (#2528)
An import statement's own module reference linked nothing in two cases, even when its bindings resolved through the file: a Vue, Svelte or Astro component's import (`isJsPathImportRef` covered only the script languages), and a bare scoped package that a `tsconfig` path or a workspace package maps to a project file (`isJsPathSpecifier` excluded `@scope/pkg`). So a barrel or a side-effect module showed no dependency from those files, and sync's importer discovery, which follows the module edge into a changed file, never found them when a barrel they import through was deleted or added. Both now resolve through `resolveImportPath`, as a script's path import does; an outside scoped package still names no file. Component imports written with an extension, which a basename match linked before, now link through the resolver too. When a sync adds a file, it also retries the failed imports that now resolve to it but wait under no key its path gives: a scoped package parked under its last segment (`common` for `@excalidraw/common`, mapped to `packages/common/src/index.ts`, whose keys are `src` and `index`), under the module ceiling, and an import of the importer's own folder or the one above (`.`, `..`), parked under no tail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
041db10146 |
fix(ts): a re-export imports its module, and sync follows barrels of barrels (#2526)
An `export … from './x'` statement emitted one `imports` reference per re-exported name and nothing for the module itself; `export * from` emitted nothing at all. So a barrel made only of `export *` lines had no dependency on what it re-exports (no dependent for `affected`), and sync could not find an `index.ts` that re-exports another barrel: when the inner one was deleted or added, the outer one's importers kept their stale or name-matched edges. A re-export now imports its module the way `import … from` does, in both extractors (TS and the Rust kernel): an import node named by the specifier and a module reference that links the barrel to the file it names. Sync's importer discovery follows such modules to their own importers, carrying the names each passes on (`export *`, a named or renamed re-export, an `export * as ns`), as deep as the resolver chases a re-export; an ES namespace import reads whatever its module passes on. A re-export's import node does not count as a symbol for the "parse produced no symbols" warning, so a barrel with an unparseable line still reports it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7ba8eee4a7 |
fix(sync): imports through a re-exporting module follow it when it is deleted or added (#2512)
Deleting a module that re-exports what it imports (a package's `__init__.py`, an `index.ts` barrel) left every file importing through it linked to the symbols it re-exported: those edges point into files that stay, so the removal cascaded none of them. When such a module was added or came back, its importers kept the edges a name lookup had made in its absence, or stayed failed; and in a large Python project the failed import of the module itself stayed failed, because the failed-import retry skips a stem with more than 500 imports waiting (Django's `test`, `models`). Sync now re-opens, as pending references for its sweep, what the files importing a removed or added module bind through those imports: before the removed files are deleted, and after the retries for the added ones (with their failed references and the module's own failed import). Importers are found by the import edge into the file, by Python import nodes naming the module or its package, and through what the module passes on (an import inside a function has no node); in Python, a package or star import re-exporting a changed module leads on to its own importers, as deep as the resolver chases a name. All changed files are handled in one pass, with importer edges and failed references filtered by the bound names in SQL. Python and the ES-module family only; no schema or extraction change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a3fdb920a8 |
fix(sync): a Python import of a name a package re-exports links when the package appears later (#2507)
`from fastapi import FastAPI` reaches `fastapi/applications.py` through `fastapi/__init__.py`, which re-exports it and declares no node named `FastAPI`. While that `__init__.py` was missing, or before it gained the re-export, the calls through the name failed and were parked under the name, which the package file that then appears or changes never carries. A fresh index linked them; a sync never did. An unaliased from-import of a module in the project's own folders is now parked under the module's key, as an aliased one is (#2501): a relative import, or an absolute one whose dotted path, or the package holding it, is a project folder at any depth. An outside package's names (`from django.db.models import Q`) keep their tails, since on netbox they would crowd 'module:models', 'module:urls' and others past the retry ceiling. #2450's by-name lookup still finds every parked ref by its own name. Two limits made the restored package's importers wait anyway: - A module key gets a retry ceiling of its own, 5,000 rather than the 500 a name tail has. A large population under a module key is every importer of a package that was missing, not noise: fastapi's 1,069 `from fastapi import ...` refs, Django's 2,257 under 'module:urls'. - The CG-33 rebind's per-name ceiling counts only the edges that existed before the retry. Relinking every `from django.urls import path` pushed `path` over it, so the `path` edges the sync's new definitions moved were never rebound; the retry's own edges are still rebound under a name that stays below it, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
5171a6aa9c |
fix(sync): a Python import alias links its module when the module appears later (#2501)
`from .models import User as UserModel` and `from . import helpers as h` bind a name their module declares under another: `User`, `helpers`. Every reference through the alias (the import, a call, a base class, the class or function passed as a value) was parked under the alias, which no file a sync adds or changes carries. So a module added after its importers were indexed, restored after a delete, or given the name in a later edit never relinked them, while a fresh index did. #2450 fixed the same gap for ES modules. Such a reference is now parked under its module's key. A Python import names its module by a dotted path, counted from the importing file's own package when it starts with dots: `.models` in app/main.py is 'module:models', `.` is 'module:app', `..pkg` the package above's 'module:pkg'. #2450's lookup still finds it by its own name, which is how a fresh index links an alias its module never declares. A package's `__init__.py` also carries the key of the package above it, as a module file beside it does through its folder, so `from app.pkg import sub as s` relinks when the subpackage appears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fc0da38f0f |
fix(cpp): a call on a declared pointer, value or reference reaches the class its type names, not a namesake (#2493)
Receiver typing resolved a C++ call on a variable, parameter or member by
the declared type's last name: `Iterator* iter; iter->Valid()` became
resolveMethodOnType('Iterator', 'Valid'), which takes any class named
`Iterator` that declares `Valid`. facebook/rocksdb has 22 such classes,
and the `Iterator` the code means inherits `Valid`, `Next` and `key` from
`IteratorBase`, so nested namesakes like `MemTableRep::Iterator` took the
calls (google/leveldb's went to `SkipList::Iterator`).
A declaration the calling function or its class writes, called with the
operator it takes (`->` on a pointer, `.` on a value or reference), now
goes through the same C++ lookup #2440 uses for smart pointers'
held types (cppClassWritten from the caller's scopes, then the file's
`using`s, aliases followed) and cppMethodOf (the class's own method, else
one through its own base edges):
- An alias #2399 followed from the caller (function-local typedefs
included) is the class it names, by qualified name. An `auto` loop's
element type (#2492) is looked up where its range's type is written.
- A class found only as the one class of that name the file can see, a
template parameter's type, a library type, or a type nothing visible
declares: the call goes on as before.
- A class with no such method gets no edge, unless it is a class template
or a base on the way is one the index doesn't link (a `decltype(…)`
base, an alias each `#if` arm declares differently), which may declare
it: then as before.
- Declarations read from elsewhere, or past one the scan couldn't read,
are unchanged.
cppClassWritten now tries the file's `using`s before the global scope:
protobuf's upb generator writes `using google::protobuf::Descriptor;`
inside its namespace, and the PHP extension declares a global C struct
`Descriptor` in a header.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
01ab45d068 |
fix(cpp): a range-based for loop over auto elements has the element type of its range (#2492)
#2439 typed a range-for variable when the loop names its type. A loop
over `auto` elements names none, so receiver inference kept scanning as
for an `auto` local with an unreadable initializer: it reached an earlier
declaration of the same name, often another variable's, or fell through
to the guess by the receiver's name. facebook/rocksdb's db_impl_write.cc
loops `for (auto cfd : cfds)` over a `const autovector<ColumnFamilyData*>&`.
The loop's range is now read from its header and typed as written,
template arguments and all, since the index keeps only a return type's
last name:
- a name the calling function declares, or a data member of its class
read from the class's own lines (`versions_` from db_impl_write.cc);
- an `auto` local's initializer;
- `*x`, `x.m`, `x->m`, `this`, and calls whose declaration writes their
return type, free or on the object's class (own or inherited);
- aliases followed with their template arguments (cppAliasedTypeWritten);
- the element: a standard or abseil sequence container's first template
argument, protobuf's RepeatedPtrField and rocksdb's autovector's, or what
a project class's begin() returns, dereferenced (ColumnFamilySet's
iterator's operator* gives ColumnFamilyData*). Nullability annotations
are dropped.
The element goes through the same declaration record as any declared
receiver, so the typed path, #2413's gate for a standard-library element
and #2440's held-type lookup for `->` on a smart-pointer element apply
unchanged. Classes are looked up with #2440's cppClassWritten, methods
through its cppMethodOf, and matchCppHeldTypeCall now reads its held type
through the shared cppStdHeldType.
A/B on main
|
||
|
|
8a5ba3d02c |
fix(go): a call through a parameter or local named like a standard-library package reaches the project's method (#2489)
isBuiltInOrExternal dropped every dotted Go ref whose first segment is in
GO_STDLIB_PACKAGES before any resolution, so a call through a parameter or
local of that name linked to nothing: gin's `context.AbortWithError(…)` in
`func(context *Context)`, harbor's `context.IsAuthenticated()` after
`context := NewSecurityContext(…)` and `log.Errorf(…)` after
`log := log.G(ctx)`, etcd's `printer.DBHashKV(…)` and `trace.Step(…)`.
Such a call now gets past the package list when #2448's scope reader finds
the name declared in the function around it, unless the declaration says it
holds no project method: a type from outside the project or a predeclared
one, or what an outside package's function hands out
(`scanner := bufio.NewScanner(r)`, `url, err := url.Parse(raw)`). #2478's
exemption for locals bound from a type assertion stays beside it.
matchMethodCall resolves these calls by the declaration alone, never by the
receiver's name, which fits dozens of a big tree's types (kubernetes'
`printer := NewTablePrinter(…)` is no kubeadm `Printer`):
- a written or asserted project type (goWrittenType) calls its own method,
an interface's or a promoted one, or nothing;
- a value a project function hands out calls a method of that function's
package: the type named like the variable, or the only one of the name;
- anything else calls only a method no other type declares, never one with
a standard-library method name.
The scope reader also records `x := T{…}` / `new(T)` types and the
own-package function a `:=` calls. GO_STDLIB_PACKAGES moves to name-matcher.
A/B against main
|
||
|
|
1d3619d657 |
fix(c,cpp): a file in a unittests/ or foo_unittest/ folder is a test (#2484)
isTestPath knew `tests/`, `test/`, `__tests__/`, CamelCase source sets and `foo-test/` modules, but not the folders LLVM, Clang, clangd, lldb, offload and Breakpad keep their unit tests in (`unittests/`), nor glog's `dcheck_unittest/`-style test programs: `/unittests/` holds `tests/` only after a `t`, and the `foo_test/` form wants a separator right before `test`. So a helper there with no test-like name (LLVM's CountCopyAndMove.h, SelectionDAGTestBase.h, Breakpad's dump_analysis.cc) read as production code. isTestPath now also counts a `unittests/` folder and a `foo_unittest/` or `foo-unittests/` one. A bare `unittest/` stays production: CPython's `Lib/unittest/` is the framework, and LLVM vendors googletest under `third-party/unittest/`. isTestSuitePath counts the same folders as suites, as it counts `tests/`: their files build into test binaries and never link into the program, and glog builds each `foo_unittest/` folder as a test program of its own (a `foo-test/` module stays out, as shipped test support). That matters for C/C++ because the suite rule's CamelCase file names skip `.cpp`, so on main none of LLVM's `*Test.cpp` files were suites and production locals named `Ctx`, `KV`, `V` or `OpInfo` linked to what a unit test declares. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1ce1167e6f |
test(js,ts): pin require().default and abstract classes to the declared default (#2481)
#2432 made a default import read the module's own `export default` statement, and its merge pointed #2433's `require('./x').default` fallback (esmDefaultExport) at the same reading. Nothing in the suite pinned that path with a guessable helper above the default: #2433's fixture keeps its helper unexported. Two cases: - React Native's AnimatedColor shape, an exported helper above `export default class AnimatedColor`, loaded with `require("./AnimatedColor").default` and `const { default: Color } = require("./AnimatedColor")`. With the first-exported guess both `new` calls instantiate the helper. - `export default abstract class Repository` below an exported `connect()`, extended by `class Users extends Repository`. With the guess the `extends` edge is missing. Both fail with e47cb258's resolver (before #2432). On main, putting the guess back on the require path fails only the first, and dropping `abstract` from the declaration pattern fails only the second. The CHANGELOG's default-import bullet now says `require('./x').default` is read the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a9781b0e05 |
fix(react-router): a lazy route links the page its loader picks (#2480)
A data-router `lazy` loader resolves to the route's properties, and many
pick their page from the module they load: luci-go milo/ui's
`lazy: async () => { const { TestTab } = await import('…/tabs'); return
{ Component: TestTab }; }`, `const { default: Component } = await
import('./pages/Login')`, `import('./x').then((m) => ({ Component: m.Page }))`,
`({ Component: (await import('./x')).Page })`, React Router 7's
`lazy: { Component: async () => (await import('./x')).Page }`. scanRoutes
recorded only the first `import('…')` as `lazy-import:<spec>`, and
resolution linked the module's default export, else its `Component`: a
different page, or nothing (milo/ui's tabs barrel has neither).
lazyRouteReference reads the loader: its `Component`, or the page its
`element` shows, followed through the loader's bindings (destructured
`await import`, a module binding, `.then` callbacks, `Promise.all`, the
v7.5 object form), and the one lazily imported export a guard shows
(`<AgeGate><FireworksPage /></AgeGate>`). A loader that returns the module
keeps `lazy-import:<spec>`; one this does not read (a helper's, `.then(convert)`)
keeps its first import, as before; one that hands over only a `loader`
renders nothing. `async lazy() { … }` methods, which scanRoutes skipped as
entries, are read too, and comments no longer hide an import's specifier.
The picked export is named the way Vue Router and Angular name a lazy
component, `import:<path>#<export>`, so sync's module-tail retry (#2422)
parks and retries it unchanged, and it resolves through #2436's
exportedComponent, barrels included. React is registered before Vue Router
and Angular, so it answers only its own routes' references (tsx or jsx,
which theirs never are). lazyModules (#2452) names the files a picked
export is read from, so a sync that moves it redraws the route.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|