From 75eaac750c982a01f8f31c8cb7b1b1d42318b747 Mon Sep 17 00:00:00 2001 From: Mick Cooney Date: Sun, 11 Oct 2026 19:49:26 +0100 Subject: [PATCH] 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. --- docs/languages/r-provider.md | 2 +- gitnexus/src/core/ingestion/import-resolvers/r.ts | 2 +- .../src/core/ingestion/languages/r/namespace-imports.ts | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/languages/r-provider.md b/docs/languages/r-provider.md index 923831888..40d601a8c 100644 --- a/docs/languages/r-provider.md +++ b/docs/languages/r-provider.md @@ -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: diff --git a/gitnexus/src/core/ingestion/import-resolvers/r.ts b/gitnexus/src/core/ingestion/import-resolvers/r.ts index 73e693e3e..32e33d4f9 100644 --- a/gitnexus/src/core/ingestion/import-resolvers/r.ts +++ b/gitnexus/src/core/ingestion/import-resolvers/r.ts @@ -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, diff --git a/gitnexus/src/core/ingestion/languages/r/namespace-imports.ts b/gitnexus/src/core/ingestion/languages/r/namespace-imports.ts index d404f44a3..ac0d86841 100644 --- a/gitnexus/src/core/ingestion/languages/r/namespace-imports.ts +++ b/gitnexus/src/core/ingestion/languages/r/namespace-imports.ts @@ -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[] = []; 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;