mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 20:12:11 +08:00
fix(dart): calls to a private name in another part of the same library link again (#2832)
Dart keeps a `_name` to its library: the library file and every file its `part` directives name. isVisibleAcrossFiles still listed Dart in PRIVATE_IS_FILE_LOCAL (added with PR #1745 for Kotlin, Java, C#, Swift, Scala and PHP), and both Dart extractors mark a `_` function, method or class `private`. So once the name-matching pipeline had picked a private candidate in another file, the pick was declined and the reference stayed unresolved. A library split into parts, generated `.g.dart` and `.freezed.dart` parts included, lost the calls and type uses between its files. Fix: - dartPrivateVisible (dart-libraries.ts) says whether a file belongs to the reference's library, read from `part` / `part of` alone. Unlike inSameDartLibrary it needs no pubspec.yaml, since a part names its library by a path or a library name. When the reference's library can't be found, the name stays file-local, as before. - isVisibleAcrossFiles takes Dart out of PRIVATE_IS_FILE_LOCAL and lets a private Dart candidate through when it is in the reference's library. - matchByExactName drops another library's private Dart candidates before the ranking, as it does for C#, PHP, Java and Go names the file can't see. Otherwise another library's namesake can rank first, be declined, and take the library's own declaration down with it. Without a pubspec nothing else filters a top-level `_name`, and a member's class hierarchy is matched by type name, which can reach another library's class of the same name (the SDK's wasm and VM `_ImmutableLinkedHashMapMixin`). Validation: - New __tests__/dart-part-privacy.test.ts, run with and without a pubspec: the issue's library plus a receiver call, a generated part's `_$UserFromJson` and `_$Counter`, another library that imports the one declaring the names, a namesake beside a part, a part named by library name, and a part whose library is missing. 6 of 12 fail on main's resolver, 12 pass. With the pre-ranking filter disabled, 1 fails (the namesake without a pubspec). - A/B vs main58eb57a3, each arm with its own dist and kernel; the kernel and wasm paths give the same changed-site sets. Re-run after the rebases, on5b47bc49(kernel) and8fdc8a1e(wasm), with the same changed sites. Edges added: getx (217 Dart files) 0, bloc (616) +49, riverpod (1,300) +542, localsend (393) +1, protobuf.dart (225) +143, apidash (954) +342, dart-lang/sdk sdk/lib (480, no pubspec) +207. Nothing removed or retargeted, and 1,064 edges changed only their confidence. Every newly resolved ref has its edge at the same site; nodes and files are unchanged. - Triage of all 1,284 added edges: 1,274 right. An independent part/part-of oracle confirms 1,195 (a top-level `_name` unique in its library, a member of the call site's own hierarchy, or a member of the receiver's declared type); 79 were checked by hand. 10 are wrong, all receiver-name guesses (instance-method 0.65-0.8) that pick one implementation of an abstract member, or the first of a tie: the guess public names already get, which the file-local rule had hidden. The 1,064 confidence changes are same-file targets that had another library's namesake beside them (exact-match 0.7 to 0.9). - gin and moshi controls byte-identical, kernel and wasm. Kernel and wasm full-index dumps byte-identical on every Dart repo. kernel-parity --lang dart: 0 diffs on all seven repos. - Index time, median of 3 interleaved runs on the kernel path, CPU with workers, main vs fix: riverpod 55.6 / 50.3 s, sdk/lib 59.4 / 59.6 s, apidash 29.5 / 30.8 s, bloc 18.6 / 18.3 s, protobuf.dart 14.7 / 14.3 s. Within noise. Fixes #2466 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8fdc8a1efb
commit
f7820b584f
@@ -794,3 +794,18 @@ export function inSameDartLibrary(fromFile: string, declFile: string, context: R
|
||||
const scope = scopeOf(fromFile, context, memoFor(context));
|
||||
return scope !== null && scope.own.has(declFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a private `_name` declared in `declFile` can be named in `fromFile`:
|
||||
* both are files of one library, the library file and its parts. Dart keeps a
|
||||
* `_name` to its library, not to its file, so a library split into `part`s
|
||||
* calls its own private helpers across them. Unlike inSameDartLibrary this
|
||||
* needs no pubspec.yaml above the files: a part names its library by a path
|
||||
* or a library name. False when the library of `fromFile` is unknown.
|
||||
*/
|
||||
export function dartPrivateVisible(fromFile: string, declFile: string, context: ResolutionContext): boolean {
|
||||
if (fromFile === declFile) return true;
|
||||
const memo = memoFor(context);
|
||||
const library = libraryOf(fromFile, context, memo);
|
||||
return library !== null && membersOf(library, context, memo).has(declFile);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { UnresolvedRef, ResolvedRef, ResolutionContext, ImportMapping, isSuperty
|
||||
import { blankStringContents, stripCommentsForRegex } from './strip-comments';
|
||||
import { JS_ARRAY_METHODS, JS_BUILT_INS, JS_BUILTIN_METHODS, TS_PRIMITIVE_TYPES } from './js-builtins';
|
||||
import { SWIFT_TYPE_PATH_CALL, resolveSwiftTypePathCall } from './swift-type-visibility';
|
||||
import { dartImportPrefixes, dartLibrarySees, dartPrefixSees, inSameDartLibrary } from './dart-libraries';
|
||||
import { dartImportPrefixes, dartLibrarySees, dartPrefixSees, dartPrivateVisible, inSameDartLibrary } from './dart-libraries';
|
||||
import { isDartLocallyBound } from './dart-local-scope';
|
||||
import { breakVbTie, isVbMemberInScope, isVbNestedTypeInScope, isVbTypeQualifiedBy, matchVbTypedCall, preferVbProject, sameVbProject } from './vbnet-receivers';
|
||||
import { cppAliasedClass, cppAliasedTypeName, cppAliasedTypeWritten, cppDeclaresLocalAlias, cppParentScope, cppScopesWithin, cppTemplateArguments, cppTemplateParameters, cppTypeNamesDeclared, cppTypeSegments, isCppPointerType, resolveCppAliasedType, stripCppTemplateArguments, type CppAliasedType } from './cpp-type-aliases';
|
||||
@@ -2150,9 +2150,10 @@ function luaModuleFile(name: string, decl: Node, context: ResolutionContext): st
|
||||
/**
|
||||
* Languages in which `visibility: 'private'` on a definition means no other
|
||||
* FILE can name it: a Kotlin `private fun` is file- or class-local, and the
|
||||
* same holds for Java, C#, Swift, Scala, Dart and PHP members.
|
||||
* same holds for Java, C#, Swift, Scala and PHP members. (A Dart `_name` is
|
||||
* its library's, which can span several files: see isVisibleAcrossFiles.)
|
||||
*/
|
||||
const PRIVATE_IS_FILE_LOCAL = new Set<string>(['kotlin', 'java', 'csharp', 'swift', 'scala', 'dart', 'php']);
|
||||
const PRIVATE_IS_FILE_LOCAL = new Set<string>(['kotlin', 'java', 'csharp', 'swift', 'scala', 'php']);
|
||||
|
||||
/** Per-context memo: node id → "this C/C++ function is declared `static`". */
|
||||
const C_STATIC_MEMO = new WeakMap<ResolutionContext, Map<string, boolean>>();
|
||||
@@ -3404,9 +3405,12 @@ function isGoNameVisible(candidate: Node, ref: UnresolvedRef, context: Resolutio
|
||||
* - **Objective-C**: a struct, union, enum, typedef or global a `.m` / `.mm`
|
||||
* source file defines is local to that file, as in C; other files reach a
|
||||
* global through its header's `extern` declaration. A class is not.
|
||||
* - **Kotlin, Java, C#, Swift, Scala, Dart, PHP**: `private` is class- or
|
||||
* - **Kotlin, Java, C#, Swift, Scala, PHP**: `private` is class- or
|
||||
* file-local. An Android `editor.apply()` resolved onto an unrelated class's
|
||||
* `private fun apply`.
|
||||
* - **Dart**: a `_name` is private to its library — the library file and the
|
||||
* files its `part` directives name — so another file of the library names
|
||||
* it, and a file of another library never does (#2466).
|
||||
* - **Go**: an unexported (lowercase) identifier is package-local, and a
|
||||
* package is a directory. Judged by the name's case: the extractor's
|
||||
* `isExported` is unset for every Go method.
|
||||
@@ -3496,6 +3500,13 @@ export function isVisibleAcrossFiles(candidate: Node, ref: UnresolvedRef, contex
|
||||
const owner = rustModuleDir(candidate.filePath);
|
||||
return ref.filePath.startsWith(owner + '/');
|
||||
}
|
||||
// A Dart `_name` is its library's, which spans the library file and its
|
||||
// parts: riverpod_generator's `class Counter extends _$Counter` names the
|
||||
// class it wrote into `main.g.dart`, and protobuf.dart's GeneratedMessage
|
||||
// calls `_fieldSet._clear()` on the FieldSet another part declares.
|
||||
if (lang === 'dart') {
|
||||
return candidate.visibility !== 'private' || (ref.language === 'dart' && dartPrivateVisible(ref.filePath, candidate.filePath, context));
|
||||
}
|
||||
if (PRIVATE_IS_FILE_LOCAL.has(lang)) return candidate.visibility !== 'private';
|
||||
// An R test file runs in an environment of its own (testthat): its top-level
|
||||
// `c <- ggplot(…)` is not what the package's 2,843 `c(…)` calls mean. The
|
||||
@@ -4364,6 +4375,21 @@ function isDartTopLevelVisible(candidate: Node, ref: UnresolvedRef, context: Res
|
||||
: dartPrefixSees(ref.filePath, prefix, candidate.filePath, candidate.name, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `n` is a Dart `_name` that another library declares, which nothing
|
||||
* written in the reference's library can mean (#2466). It leaves the
|
||||
* candidates before one is picked, so the library's own declaration wins
|
||||
* where another library's namesake would have ranked first and then been
|
||||
* declined: the Dart SDK's wasm `_ImmutableLinkedHashMapMixin` calls the
|
||||
* `_createIndex` of the `_MapCreateIndexMixin` it is `on`, not the one the
|
||||
* VM's mixin of the same name declares. isVisibleAcrossFiles applies the
|
||||
* same rule to the pipeline's pick.
|
||||
*/
|
||||
function isDartPrivateOfAnotherLibrary(n: Node, ref: UnresolvedRef, context: ResolutionContext): boolean {
|
||||
return n.language === 'dart' && n.visibility === 'private' && n.filePath !== ref.filePath &&
|
||||
!dartPrivateVisible(ref.filePath, n.filePath, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* The import prefix a Dart type name is written through — the `p` of
|
||||
* `p.Report`, `p.Color.red` — or null. A type reference's column is the
|
||||
@@ -11844,6 +11870,7 @@ export function matchByExactName(
|
||||
!(ref.language === 'dart' && isDartConstructor(n, context)) &&
|
||||
!(dartBare && isDartTypeValue(n) && !isDartMethodInScope(n, ref, context)) &&
|
||||
!((dartBare || dartTypeRef) && !isDartTopLevelVisible(n, ref, context, dartPrefix)) &&
|
||||
!(ref.language === 'dart' && isDartPrivateOfAnotherLibrary(n, ref, context)) &&
|
||||
!(phpSelf && (n.kind !== 'method' || !isPhpMethodInScope(n, ref, phpSelf, context))) &&
|
||||
!(pythonShape && !fitsPythonCallShape(n, pythonShape, ref, context)) &&
|
||||
!(rustBare && !isRustNameInScope(n, ref, context)) &&
|
||||
|
||||
Reference in New Issue
Block a user