mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
feat(shared,ingestion): extend LanguageProvider with scope-resolution hooks (#911, RFC #909 Ring 1) (#950)
Adds the 14 optional scope-resolution hooks from RFC #909 §5.2 to
`LanguageProviderConfig` plus the supporting input/output types in
`gitnexus-shared`. Contract-only; no runtime behavior changes.
Review-driven refinements (addresses two non-blocking review comments on #950):
1. `ParsedImport` is now a 5-variant discriminated union, not a flat
record. Each variant carries only its legal fields so invalid shapes
are compile errors:
- 'named', 'alias', 'namespace', 'reexport', 'dynamic-unresolved'
'wildcard-expanded' is deliberately excluded — finalize materializes
that kind; a provider must never emit it at parse time.
'reexport' is a first-class parse-phase variant so syntactically-
detectable re-exports (TS `export { X } from './y'`, Rust
`pub use foo::bar`) keep their parse-time signal through to finalize
rather than being re-derived by the SCC pass.
`namespace` gains an `importedName` field so `import numpy as np`
can carry both `localName: 'np'` and `importedName: 'numpy'`.
`dynamic-unresolved.targetRaw` is `string | null` (was mandatory
null) so providers can emit the unresolvable expression text for
diagnostics when available.
2. `bindingScopeFor` and `importOwningScope` return type changed from
`ScopeId` to `ScopeId | null`, aligning with the X | null convention
used by the 12 sibling optional hooks (receiverBinding,
resolveScopeKind, interpretTypeBinding, …). `null` = delegate to the
central default. Enables partial overrides — a JS provider can
return a hoisted scope for `var` and `null` for `let`/`const`
without re-implementing the default lookup.
Both hooks also gain a purity JSDoc contract: same inputs yield the
same ScopeId (or null) across invocations; no closure over mutable
state. Required to keep scope-tree construction deterministic.
A richer callable-defaults pattern (typed BindingScopeDefaults /
ImportOwningDefaults helper interfaces on a `defaults` parameter)
was considered and deferred to Ring 2 PKG #919, where the concrete
ScopeExtractor will exist to inform the helper shape. Designing that
pattern before the first consumer would set cross-hook precedent
based on a single motivating example.
Supporting types added to gitnexus-shared/src/scope-resolution/types.ts:
- CaptureMatch, ParsedImport, ParsedTypeBinding
- WorkspaceIndex, ScopeTree (opaque placeholders until Ring 2)
- Callsite
14 hooks added to LanguageProviderConfig (all optional):
Parse phase: emitScopeCaptures, interpretImport, receiverBinding,
interpretTypeBinding, resolveScopeKind, shouldCreateScope,
bindingScopeFor
Finalize phase: resolveImportTarget, expandsWildcardTo,
importOwningScope, mergeBindings
Reference-extraction phase: classifyCallForm
Resolution phase: shouldShadow, arityCompatibility
Verification:
- gitnexus-shared builds clean (tsc)
- gitnexus builds clean (scripts/build.js)
- test/unit/model: 84/84 pass — no regressions
- No provider needs updating (all hooks optional)
- No BindingScopeDefaults/ImportOwningDefaults/defaults parameter
introduced (deferred to #919)
Stacked on #910 (merged as afc0a8b6); rebased on main.
Tracking: #909 (meta). Unblocks Ring 2 PKG (#919 ScopeExtractor,
#922 import adapters) and all Ring 3 per-language migrations.
Plan: docs/plans/2026-04-18-001-refactor-911-senior-hooks-redesign-plan.md
This commit is contained in:
parent
afc0a8b6c5
commit
af1d278a7e
3 changed files with 394 additions and 1 deletions
|
|
@ -33,6 +33,7 @@ export type {
|
|||
ScopeKind,
|
||||
Range,
|
||||
Capture,
|
||||
CaptureMatch,
|
||||
BindingRef,
|
||||
ImportEdge,
|
||||
TypeRef,
|
||||
|
|
@ -43,6 +44,11 @@ export type {
|
|||
ReferenceIndex,
|
||||
LookupParams,
|
||||
RegistryContributor,
|
||||
ParsedImport,
|
||||
ParsedTypeBinding,
|
||||
WorkspaceIndex,
|
||||
ScopeTree,
|
||||
Callsite,
|
||||
} from './scope-resolution/types.js';
|
||||
|
||||
// Evidence + tie-break constants (RFC Appendix A, Appendix B)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,150 @@ export interface Capture {
|
|||
readonly text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A grouping of `Capture`s that came from a single query match (e.g., one
|
||||
* `@import.statement` match carries `@import.source`, `@import.name`,
|
||||
* `@import.alias?` as child captures). Keyed by capture name for O(1)
|
||||
* child access.
|
||||
*/
|
||||
export type CaptureMatch = Readonly<Record<string, Capture>>;
|
||||
|
||||
// ─── Hook input/output types (RFC §5.2) ─────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Provider-interpreted raw import, consumed by finalize (Phase 2) to produce
|
||||
* linked `ImportEdge[]`. The provider's `interpretImport` hook turns a
|
||||
* `CaptureMatch` for an `@import.statement` into one of these; the central
|
||||
* finalize algorithm resolves `targetRaw` to a concrete file via
|
||||
* `resolveImportTarget` and materializes the final `ImportEdge`.
|
||||
*
|
||||
* Discriminated union — each variant carries only the fields that make sense
|
||||
* for its kind. Invalid shapes (e.g., a `namespace` import with an alias-like
|
||||
* `importedName` mismatch) are compile errors, not latent bugs. `'wildcard-
|
||||
* expanded'` is deliberately NOT a variant: that kind is finalize output only,
|
||||
* produced when `expandsWildcardTo` materializes a wildcard against target
|
||||
* exports — a provider must never emit it at parse time.
|
||||
*/
|
||||
export type ParsedImport =
|
||||
/**
|
||||
* Per-name import without rename.
|
||||
*
|
||||
* Examples:
|
||||
* - Python `from foo import X` → `{ kind: 'named', localName: 'X', importedName: 'X', targetRaw: 'foo' }`
|
||||
* - TS `import { X } from './foo'` → `{ kind: 'named', localName: 'X', importedName: 'X', targetRaw: './foo' }`
|
||||
* - Java `import foo.bar.X` → `{ kind: 'named', localName: 'X', importedName: 'X', targetRaw: 'foo.bar' }`
|
||||
*/
|
||||
| {
|
||||
readonly kind: 'named';
|
||||
readonly localName: string;
|
||||
readonly importedName: string;
|
||||
readonly targetRaw: string;
|
||||
}
|
||||
/**
|
||||
* Per-name import with rename.
|
||||
*
|
||||
* Examples:
|
||||
* - Python `from foo import X as Y` → `{ kind: 'alias', localName: 'Y', importedName: 'X', alias: 'Y', targetRaw: 'foo' }`
|
||||
* - TS `import { X as Y } from './foo'` → `{ kind: 'alias', localName: 'Y', importedName: 'X', alias: 'Y', targetRaw: './foo' }`
|
||||
*/
|
||||
| {
|
||||
readonly kind: 'alias';
|
||||
readonly localName: string;
|
||||
readonly importedName: string;
|
||||
readonly alias: string;
|
||||
readonly targetRaw: string;
|
||||
}
|
||||
/**
|
||||
* Qualified module handle, with or without rename. `importedName` is the
|
||||
* module being aliased; `localName` is the scope-visible handle (often the
|
||||
* same unless renamed).
|
||||
*
|
||||
* Examples:
|
||||
* - Python `import numpy` → `{ kind: 'namespace', localName: 'numpy', importedName: 'numpy', targetRaw: 'numpy' }`
|
||||
* - Python `import numpy as np` → `{ kind: 'namespace', localName: 'np', importedName: 'numpy', targetRaw: 'numpy' }`
|
||||
* - TS `import * as np from 'numpy'` → `{ kind: 'namespace', localName: 'np', importedName: 'numpy', targetRaw: 'numpy' }`
|
||||
* - Go `import foo "pkg/bar"` → `{ kind: 'namespace', localName: 'foo', importedName: 'bar', targetRaw: 'pkg/bar' }`
|
||||
*/
|
||||
| {
|
||||
readonly kind: 'namespace';
|
||||
/** Scope-visible handle (e.g. `np` in `import numpy as np`; `numpy` when unaliased). */
|
||||
readonly localName: string;
|
||||
/** Module being aliased (e.g. `numpy` in `import numpy as np`). */
|
||||
readonly importedName: string;
|
||||
readonly targetRaw: string;
|
||||
}
|
||||
/**
|
||||
* Syntactically-detectable parse-time re-export. Finalize may still produce
|
||||
* `ImportEdge { kind: 'reexport', transitiveVia }` when flattening chains;
|
||||
* this variant preserves the *parse-time* signal so finalize doesn't have
|
||||
* to re-derive it from scratch.
|
||||
*
|
||||
* Examples:
|
||||
* - TS `export { X } from './y'` → `{ kind: 'reexport', localName: 'X', importedName: 'X', targetRaw: './y' }`
|
||||
* - TS `export { X as Y } from './y'` → `{ kind: 'reexport', localName: 'Y', importedName: 'X', alias: 'Y', targetRaw: './y' }`
|
||||
* - Rust `pub use foo::bar` → `{ kind: 'reexport', localName: 'bar', importedName: 'bar', targetRaw: 'foo' }`
|
||||
*/
|
||||
| {
|
||||
readonly kind: 'reexport';
|
||||
/** Name as re-exported in the current module. */
|
||||
readonly localName: string;
|
||||
/** Name in the source module. */
|
||||
readonly importedName: string;
|
||||
readonly targetRaw: string;
|
||||
/** Set when the re-export renames the symbol (e.g. `export { X as Y } from './y'`). */
|
||||
readonly alias?: string;
|
||||
}
|
||||
/**
|
||||
* Runtime-computed target — the import path is not a static literal at
|
||||
* parse time. Providers SHOULD emit the unresolvable expression's source
|
||||
* text as `targetRaw` to aid diagnostics; `null` only when no string form
|
||||
* exists.
|
||||
*
|
||||
* Examples:
|
||||
* - JS `await import(expr)` → `{ kind: 'dynamic-unresolved', localName: '', targetRaw: 'expr' }`
|
||||
* - Python `importlib.import_module(f'pkg.{name}')` → `{ kind: 'dynamic-unresolved', localName: '', targetRaw: "f'pkg.{name}'" }`
|
||||
*/
|
||||
| {
|
||||
readonly kind: 'dynamic-unresolved';
|
||||
readonly localName: string;
|
||||
/** Source text of the unresolved expression when available; `null` otherwise. */
|
||||
readonly targetRaw: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Provider-interpreted type binding. The provider's `interpretTypeBinding`
|
||||
* hook turns a `CaptureMatch` (e.g., `@type-binding.parameter`) into one of
|
||||
* these; the central extractor attaches the resulting `TypeRef` to the
|
||||
* appropriate scope's `typeBindings` map.
|
||||
*/
|
||||
export interface ParsedTypeBinding {
|
||||
/** The name being bound (parameter name, `self`, assignment LHS, …). */
|
||||
readonly boundName: string;
|
||||
/** The raw type name as written in source (`'User'`, `'models.User'`, …). */
|
||||
readonly rawTypeName: string;
|
||||
readonly source: TypeRef['source'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cross-file workspace index consumed by finalize-phase hooks
|
||||
* (`resolveImportTarget`, `expandsWildcardTo`). Opaque placeholder in Ring 1;
|
||||
* concretely typed in Ring 2 SHARED (#915).
|
||||
*/
|
||||
export type WorkspaceIndex = unknown;
|
||||
|
||||
/**
|
||||
* Scope tree handle consumed by parse-phase hooks (`bindingScopeFor`,
|
||||
* `importOwningScope`) to navigate the in-progress scope tree. Opaque
|
||||
* placeholder in Ring 1; concretely typed in Ring 2 SHARED (#912).
|
||||
*/
|
||||
export type ScopeTree = unknown;
|
||||
|
||||
/** Call-site description passed to `arityCompatibility`. */
|
||||
export interface Callsite {
|
||||
/** Number of arguments at the call site. */
|
||||
readonly arity: number;
|
||||
}
|
||||
|
||||
// ─── §2.4 ImportEdge ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,7 +9,23 @@
|
|||
* so adding a language to the enum without creating a provider is a compiler error.
|
||||
*/
|
||||
|
||||
import type { SupportedLanguages, MroStrategy } from 'gitnexus-shared';
|
||||
import type {
|
||||
SupportedLanguages,
|
||||
MroStrategy,
|
||||
Capture,
|
||||
CaptureMatch,
|
||||
BindingRef,
|
||||
TypeRef,
|
||||
Scope,
|
||||
ScopeId,
|
||||
ScopeKind,
|
||||
ScopeTree,
|
||||
ParsedImport,
|
||||
ParsedTypeBinding,
|
||||
SymbolDefinition,
|
||||
Callsite,
|
||||
WorkspaceIndex,
|
||||
} from 'gitnexus-shared';
|
||||
import type { LanguageTypeConfig } from './type-extractors/types.js';
|
||||
import type { CallRouter } from './call-routing.js';
|
||||
import type {
|
||||
|
|
@ -272,6 +288,233 @@ interface LanguageProviderConfig {
|
|||
/** Built-in/stdlib names that should be filtered from the call graph for this language.
|
||||
* Default: undefined (no language-specific filtering). */
|
||||
readonly builtInNames?: ReadonlySet<string>;
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
// Scope-based resolution hooks (RFC #909 — Ring 1 #911)
|
||||
//
|
||||
// All hooks below are OPTIONAL with safe defaults so existing providers
|
||||
// continue to compile unchanged. Ring 2 (#919–#925) wires these into the
|
||||
// central `ScopeExtractor` + finalize pipeline; Ring 3 per-language
|
||||
// tickets implement the ones each language needs.
|
||||
//
|
||||
// See: https://www.notion.so/346dc50b6ed281cfaacbe480bf231d50 §5.2
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ── Parse phase (per-capture interpretation) ───────────────────────
|
||||
|
||||
/**
|
||||
* Emit scope captures from raw source. Tree-sitter-based providers run a
|
||||
* `scopes.scm` query; standalone providers (COBOL) emit captures from a
|
||||
* regex tagger. The return shape is parser-agnostic: the central
|
||||
* `ScopeExtractor` consumes `Capture[]` without knowing which parser
|
||||
* produced them.
|
||||
*
|
||||
* Required for any provider participating in scope-based resolution.
|
||||
* Providers that have not yet migrated continue to run through the legacy
|
||||
* DAG path (feature-flagged per `REGISTRY_PRIMARY_<LANG>`).
|
||||
*
|
||||
* Default: undefined (language continues to use legacy DAG).
|
||||
*/
|
||||
readonly emitScopeCaptures?: (
|
||||
sourceText: string,
|
||||
filePath: string,
|
||||
) => Promise<readonly Capture[]>;
|
||||
|
||||
/**
|
||||
* Interpret a raw `@import.statement` capture group into a `ParsedImport`.
|
||||
* The central finalize algorithm resolves `ParsedImport.targetRaw` to a
|
||||
* concrete file via `resolveImportTarget` and materializes the final
|
||||
* `ImportEdge` with `targetModuleScope` / `targetDefId` filled in.
|
||||
*
|
||||
* Required when `emitScopeCaptures` is implemented.
|
||||
*/
|
||||
readonly interpretImport?: (captures: CaptureMatch) => ParsedImport | null;
|
||||
|
||||
/**
|
||||
* What is the implicit receiver on a Function scope? For instance methods
|
||||
* this is `self`/`this`; for standalone functions it is `null`. Consulted
|
||||
* by `Registry.lookup` Step 2 via the `resolveTypeRef` helper.
|
||||
*
|
||||
* Required for any language with method dispatch (OO semantics).
|
||||
*
|
||||
* Default: undefined (treated as `null` — no implicit receiver).
|
||||
*/
|
||||
readonly receiverBinding?: (functionScope: Scope) => TypeRef | null;
|
||||
|
||||
/**
|
||||
* Interpret a raw type-binding capture (parameter annotation, `self`,
|
||||
* assignment with constructor RHS, …) into a `ParsedTypeBinding`. The
|
||||
* central extractor attaches the resulting `TypeRef` to the appropriate
|
||||
* scope's `typeBindings` map.
|
||||
*
|
||||
* Default: undefined (falls back to `{ boundName: captures.name, rawTypeName: captures.type, source: 'annotation' }`).
|
||||
*/
|
||||
readonly interpretTypeBinding?: (captures: CaptureMatch) => ParsedTypeBinding | null;
|
||||
|
||||
/**
|
||||
* Override the `ScopeKind` assigned to a scope capture. Use when the
|
||||
* capture name alone can't resolve the kind (e.g., tree-sitter captures
|
||||
* a `block` that is semantically an `Expression` in this language).
|
||||
*
|
||||
* Default: undefined (the central extractor uses the capture name's
|
||||
* suffix — `@scope.function` → `'Function'`, etc.).
|
||||
*/
|
||||
readonly resolveScopeKind?: (captures: CaptureMatch) => ScopeKind | null;
|
||||
|
||||
/**
|
||||
* Should this scope capture materialize as a real `Scope` node? Return
|
||||
* `false` to skip scope creation while still emitting declarations that
|
||||
* would have gone inside (they attach to the enclosing real scope).
|
||||
*
|
||||
* Example: Python `if`/`for`/`while` bodies capture as `@scope.block` but
|
||||
* Python has no block scope — hook returns `false` and child declarations
|
||||
* lift to the enclosing function/module.
|
||||
*
|
||||
* Default: undefined (treated as `true` — always create).
|
||||
*/
|
||||
readonly shouldCreateScope?: (captures: CaptureMatch) => boolean;
|
||||
|
||||
/**
|
||||
* Override where a declaration's name becomes visible. By default the name
|
||||
* is bound in the innermost enclosing scope; return a different `ScopeId`
|
||||
* to hoist it (JS `var` → enclosing function scope; Ruby `def` inside
|
||||
* `begin` → enclosing class scope).
|
||||
*
|
||||
* Return `null` to delegate to the central default (innermost enclosing
|
||||
* scope). This matches the `X | null` convention used by the other optional
|
||||
* hooks and supports partial overrides — e.g., a JS provider can return a
|
||||
* hoisted scope for `var` declarations and `null` for `let`/`const`, without
|
||||
* re-implementing the default lookup.
|
||||
*
|
||||
* **Purity:** must be a pure function of its inputs — same parameters must
|
||||
* yield the same `ScopeId` (or `null`) across invocations. No closure over
|
||||
* mutable state. Required so scope-tree construction stays deterministic
|
||||
* across re-parses.
|
||||
*
|
||||
* Default: undefined (the central extractor uses `innermostScope.id`).
|
||||
*/
|
||||
readonly bindingScopeFor?: (
|
||||
declCapture: CaptureMatch,
|
||||
innermostScope: Scope,
|
||||
scopeTree: ScopeTree,
|
||||
) => ScopeId | null;
|
||||
|
||||
// ── Finalize phase (cross-file + materialization) ──────────────────
|
||||
|
||||
/**
|
||||
* Resolve a `ParsedImport.targetRaw` expression to a concrete file path in
|
||||
* the workspace. Language-specific resolution: Python relative imports,
|
||||
* JS package.json + node_modules, Go module paths, Java classpath,
|
||||
* COBOL COPY paths. Ports today's per-language import resolver.
|
||||
*
|
||||
* Required when `emitScopeCaptures` is implemented. Ring 2 PKG #922
|
||||
* provides the adapter that bridges today's resolver shape to this hook.
|
||||
*/
|
||||
readonly resolveImportTarget?: (
|
||||
parsedImport: ParsedImport,
|
||||
workspaceIndex: WorkspaceIndex,
|
||||
) => string | null;
|
||||
|
||||
/**
|
||||
* Enumerate the exported names of a file — used by the finalize algorithm
|
||||
* to expand `import * from M` into individual `BindingRef`s with
|
||||
* `origin: 'wildcard'`.
|
||||
*
|
||||
* Default: undefined (central finalize walks the target file's
|
||||
* `ExportMap.keys()`).
|
||||
*/
|
||||
readonly expandsWildcardTo?: (
|
||||
targetFile: string,
|
||||
workspaceIndex: WorkspaceIndex,
|
||||
) => readonly string[];
|
||||
|
||||
/**
|
||||
* Decide the scope to which a `ParsedImport` attaches. Most languages
|
||||
* attach imports to the nearest enclosing `Module`/`Namespace` scope
|
||||
* (the default); some languages allow local imports (Python function-local
|
||||
* `from x import Y`, Rust fn-local `use`, TS dynamic `import()`) — return
|
||||
* a `Function`/`Block` scope id instead.
|
||||
*
|
||||
* Return `null` to delegate to the central default (nearest enclosing
|
||||
* `Module`/`Namespace`). This matches the `X | null` convention used by
|
||||
* the other optional hooks and supports partial overrides — a provider
|
||||
* that handles only specific import forms non-standardly can `return null`
|
||||
* for the common cases and let the central walk handle them.
|
||||
*
|
||||
* **Purity:** must be a pure function of its inputs — same parameters must
|
||||
* yield the same `ScopeId` (or `null`) across invocations. No closure over
|
||||
* mutable state. Required so scope-tree construction stays deterministic
|
||||
* across re-parses.
|
||||
*
|
||||
* Default: undefined (central finalize walks to the nearest enclosing
|
||||
* `Module` or `Namespace` scope).
|
||||
*/
|
||||
readonly importOwningScope?: (
|
||||
parsedImport: ParsedImport,
|
||||
innermostScope: Scope,
|
||||
scopeTree: ScopeTree,
|
||||
) => ScopeId | null;
|
||||
|
||||
/**
|
||||
* Merge local declarations and imported bindings for a single (scope, name)
|
||||
* during finalize materialization of a scope's binding table. Language-
|
||||
* specific precedence: Python local hides import; TypeScript namespace
|
||||
* merging keeps both; Ruby constant resolution has its own rules.
|
||||
*
|
||||
* Default: undefined (central finalize uses local-first-then-imports,
|
||||
* deduping by `DefId`).
|
||||
*/
|
||||
readonly mergeBindings?: (scope: Scope, bindings: readonly BindingRef[]) => readonly BindingRef[];
|
||||
|
||||
// ── Reference-extraction phase ─────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Classify a `@reference.call` capture as free / member / constructor /
|
||||
* index. Preferred path is declarative via capture sub-tags
|
||||
* (`@reference.call.free`, etc.); this hook handles the languages where
|
||||
* call form can't be decided statically (Ruby bare `foo(x)` is free-or-
|
||||
* member until resolved).
|
||||
*
|
||||
* Default: undefined (central extractor reads capture sub-tag if present;
|
||||
* else treats as `'free'`).
|
||||
*/
|
||||
readonly classifyCallForm?: (
|
||||
captures: CaptureMatch,
|
||||
enclosingScope: Scope,
|
||||
) => 'free' | 'member' | 'constructor' | 'index';
|
||||
|
||||
// ── Resolution phase (RFC §4v2) ────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Does a binding at this scope shadow bindings of the same name in outer
|
||||
* scopes? Default: any binding shadows (standard lexical scoping). Return
|
||||
* `false` for transparent-scope edge cases (Python `from x import *`
|
||||
* contexts, JS `var` hoisting quirks, COBOL PARAGRAPH transparency).
|
||||
*
|
||||
* Consulted by `Registry.lookup` Step 1 and by `resolveTypeRef` for
|
||||
* shadowing decisions during the lexical chain walk.
|
||||
*
|
||||
* Default: undefined (treated as `true` — any binding shadows).
|
||||
*/
|
||||
readonly shouldShadow?: (scope: Scope, bindings: readonly BindingRef[]) => boolean;
|
||||
|
||||
/**
|
||||
* Is this callable definition compatible with the given call-site arity?
|
||||
* Language-specific rules: Python `*args`/`**kwargs`/defaults, JS default
|
||||
* params + rest, Kotlin vararg + defaults, Ruby optional/splat/block, Go
|
||||
* straight counts, Rust no-variadic-no-defaults.
|
||||
*
|
||||
* `'incompatible'` is a soft penalty (−0.15 per EvidenceWeights) and is
|
||||
* filtered only when at least one `'compatible'` candidate exists;
|
||||
* otherwise the incompatible candidate is kept with the penalty so the
|
||||
* call-site still links to a best-guess target.
|
||||
*
|
||||
* Default: undefined (treated as `'unknown'` — no signal either way).
|
||||
*/
|
||||
readonly arityCompatibility?: (
|
||||
def: SymbolDefinition,
|
||||
callsite: Callsite,
|
||||
) => 'compatible' | 'unknown' | 'incompatible';
|
||||
}
|
||||
|
||||
/** Runtime type — same as LanguageProviderConfig but with defaults guaranteed present. */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue