refactor(scope-resolution): narrow handles + tighten required params

Two small hygiene fixes that fell out of the unified-model work:

  * Introduce `readonlyModel: SemanticModel` in `runScopeResolution`
    immediately after reconciliation so the write/read phase boundary
    is explicit at the code level. Downstream passes (receiver-bound,
    free-call) receive the narrowed `SemanticModel` rather than the
    `MutableSemanticModel` that only the reconciliation pass needs.
    The type system now rejects accidental writes in the read phase.

  * Make `emitFreeCallFallback`'s `workspaceIndex` parameter required.
    It's now always passed (every caller threads it through), and the
    `workspaceIndex?` guard was dead code. Also drops the `| undefined`
    branch from `pickConstructorOrClass` which no caller can hit.

No behavior change.
This commit is contained in:
Gergo Magyar 2026-04-22 16:59:27 +01:00
parent 2f6defe4d7
commit e3f4d8b1eb
2 changed files with 14 additions and 8 deletions

View file

@ -34,7 +34,7 @@ export function emitFreeCallFallback(
_referenceIndex: { readonly bySourceScope: ReadonlyMap<ScopeId, readonly Reference[]> },
handledSites: Set<string>,
model: SemanticModel,
workspaceIndex?: WorkspaceResolutionIndex,
workspaceIndex: WorkspaceResolutionIndex,
): number {
let emitted = 0;
const seen = new Set<string>();
@ -60,7 +60,7 @@ export function emitFreeCallFallback(
// enclosing class. When the workspace has multiple methods of
// the same name in a single class, choose the best match by
// arity + argument types.
if (fnDef === undefined && workspaceIndex !== undefined) {
if (fnDef === undefined) {
fnDef = pickImplicitThisOverload(site, scopes, workspaceIndex, model);
}
if (fnDef === undefined) {
@ -101,9 +101,8 @@ export function emitFreeCallFallback(
* ctors and targetLabel === 'Constructor' for explicit ones. */
function pickConstructorOrClass(
classDef: SymbolDefinition,
workspaceIndex: WorkspaceResolutionIndex | undefined,
workspaceIndex: WorkspaceResolutionIndex,
): SymbolDefinition {
if (workspaceIndex === undefined) return classDef;
const classScope = workspaceIndex.classScopeByDefId.get(classDef.nodeId);
if (classScope === undefined) return classDef;
for (const def of classScope.ownedDefs) {

View file

@ -25,7 +25,7 @@
import type { ParsedFile, RegistryProviders } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { MutableSemanticModel } from '../../model/semantic-model.js';
import type { MutableSemanticModel, SemanticModel } from '../../model/semantic-model.js';
import { reconcileOwnership, validateOwnershipParity } from './reconcile-ownership.js';
import { extractParsedFile } from '../../scope-extractor-bridge.js';
import { finalizeScopeModel } from '../../finalize-orchestrator.js';
@ -77,7 +77,7 @@ export function runScopeResolution(
input: RunScopeResolutionInput,
provider: ScopeResolver,
): RunScopeResolutionStats {
const { graph, files, model } = input;
const { graph, files } = input;
const onWarn = input.onWarn ?? (() => {});
const PROF = process.env.PROF_SCOPE_RESOLUTION === '1';
const tStart = PROF ? process.hrtime.bigint() : 0n;
@ -107,8 +107,15 @@ export function runScopeResolution(
// See `reconcile-ownership.ts` for the full rationale (Contract
// Invariant I9). Debug-mode validator runs immediately after to
// catch drift between `parsed.localDefs` and the registries.
//
// PHASE BOUNDARY: `input.model` is `MutableSemanticModel` up to this
// point (write phase: reconciliation). After this line no further
// writes are expected — downstream passes consume `readonlyModel`
// (narrowed to `SemanticModel`) so accidental writes would surface
// as type errors.
reconcileOwnership(parsedFiles, input.model);
validateOwnershipParity(parsedFiles, input.model, onWarn);
const readonlyModel: SemanticModel = input.model;
if (parsedFiles.length === 0) {
return {
@ -194,7 +201,7 @@ export function runScopeResolution(
handledSites,
provider,
workspaceIndex,
model,
readonlyModel,
);
const freeCallExtras = emitFreeCallFallback(
graph,
@ -203,7 +210,7 @@ export function runScopeResolution(
nodeLookup,
referenceIndex,
handledSites,
model,
readonlyModel,
workspaceIndex,
);
const { emitted, skipped } = emitReferencesViaLookup(