fix(r): skip importFrom() imports that can never bind

populateRNamespaceImports synthesised a named import for every
importFrom(localpkg, name). Names the provider does not export, does not
define at top level, spells with a dot, or defines beside a dotted S3
method of the same tail never resolve, yet the unresolved import counted as
an explicit name claim and suppressed the documented 0.5-confidence
global-name-fallback edge.

Skip the import when resolveRNamedImport returns null, so such names create
no phantom claim and their calls keep the fallback.
This commit is contained in:
Mick Cooney 2026-10-11 19:49:26 +01:00
parent 5d682c2101
commit 75eaac750c
3 changed files with 8 additions and 2 deletions

View file

@ -28,7 +28,7 @@ Types:
Imports:
- `library()` and `require()` resolve to local packages, and `source()` to local files. The argument is `package=` / `file=` wherever it sits, else the first unnamed argument after any named one; named-first arguments such as `lib.loc=` and `local=` are not imports.
- NAMESPACE `importFrom()` names bind to definitions in local packages.
- NAMESPACE `importFrom()` names bind to definitions in local packages. A name that can never bind (not exported, not defined at top level, or dotted) creates no import, so its calls keep the `global-name-fallback` guess.
Calls:

View file

@ -158,7 +158,7 @@ const stripBackticks = (s: string): string => s.replace(/^`+|`+$/g, '');
* layout where the bare def comes first). `null` degrades the call site to the
* baseline `global-name-fallback` guess. Never builds a workspace file index.
*/
function resolveRNamedImport(
export function resolveRNamedImport(
rConfig: RPackageConfig | null,
targetRaw: string,
importedName: string,

View file

@ -42,6 +42,7 @@
import type { ParsedFile, ParsedImport } from 'gitnexus-shared';
import type { RPackageConfig } from './package-config.js';
import { unescapeRString } from './export-pattern.js';
import { resolveRNamedImport } from '../../import-resolvers/r.js';
/** One `importFrom(pkg, name)` pair, in NAMESPACE file order. */
export interface RNamespaceImportFromEntry {
@ -622,6 +623,11 @@ export function populateRNamespaceImports(
const synthesised: Extract<ParsedImport, { kind: 'named' }>[] = [];
for (const [name, pkg] of plan.bindable) {
if (ownNames?.has(name) === true) continue;
// `null` means the name can never bind. A synthesised import for it would stay unresolved
// yet count as an explicit name claim, suppressing the documented `global-name-fallback`.
// (Importing the resolver forms a cycle with import-resolvers/r.ts; every use is deferred
// to call time, so it is safe at runtime.)
if (resolveRNamedImport(cfg, pkg, name, parsedFiles) === null) continue;
synthesised.push({ kind: 'named', localName: name, importedName: name, targetRaw: pkg });
}
if (synthesised.length === 0) continue;