Skip to content

Resolve C# local functions and shadowing parameters by lexical scope #5188

Description

@Widthdom

Summary

C# same-name local functions are resolved as a file-wide overload family instead of by lexical scope.

In ConsoleUi.Help.cs, two enclosing methods each declare a local WriteHelpLine. Calls inside each enclosing method are marked ambiguous between both definitions. A parameter named WriteHelpLine in PrintFlagReference shadows either local function, yet calls through that delegate parameter are also linked to both local definitions.

All 19 observed references are ambiguous and both local-function definitions report zero identity-scoped callers.

Environment

  • origin/main: c642bc4c3dda4ef659df8e6b699e10f61da3c836
  • cdidx v1.44.3, locally built
  • fresh, complete graph with no cap hits
  • full net8.0 and net9.0 suites pass

Reproduction

dotnet ./src/CodeIndex/bin/Debug/net8.0/cdidx.dll definition \
  WriteHelpLine --lang csharp --exact-name \
  --path 'src/CodeIndex/Cli/ConsoleUi.Help.cs' \
  --fields path,line,kind,name,container_kind,container_name,signature \
  --db .cdidx/codeindex.db --json --limit 10

dotnet ./src/CodeIndex/bin/Debug/net8.0/cdidx.dll references \
  WriteHelpLine --lang csharp --exact-name \
  --path 'src/CodeIndex/Cli/ConsoleUi.Help.cs' \
  --fields path,line,column,container_name,reference_kind,target_symbol_id,resolution_state,resolution_candidate_count \
  --db .cdidx/codeindex.db --json --limit 30

Actual behavior

Definitions are extracted correctly:

line 48: WriteHelpLine, container PrintUsageFull
line 85: WriteHelpLine, container PrintFlagUsage

Lexically unique calls inside PrintUsageFull and PrintFlagUsage have resolution_state: ambiguous and resolution_candidate_count: 2.

PrintFlagReference(Action<string> WriteHelpLine) declares a delegate parameter with the same name. Calls through that parameter at lines 167-193 are also linked to both unrelated local functions.

Expected behavior

  • Calls inside PrintUsageFull resolve only to the line-48 local function.
  • Calls inside PrintFlagUsage resolve only to the line-85 local function.
  • Calls through the PrintFlagReference parameter do not target either local function.
  • Method-group conversions resolve to the local function in their enclosing scope.
  • References, callers, inspect bundles, impact roots, and selectors agree.

Root-cause evidence

Rows already contain useful containment: each local definition and call has its actual enclosing callable. Candidate resolution still groups by leaf name/signature without applying lexical visibility or shadowing first.

Relevant areas include C# candidate generation and scoring in src/CodeIndex/Database/DbWriter.References.cs, callable containment/ranges from the symbol extractor, and graph identity projection.

Focused PR scope

One PR should add deterministic lexical-scope filtering for:

  1. local-function definitions;
  2. calls and method-group references inside their containing callable;
  3. nested local functions;
  4. parameters and local variables that shadow callable names.

Non-goals

  • Do not implement full C# semantic binding.
  • Do not change constructor arity or global overload identity.
  • Do not use declaration order as a visibility gate: local functions may be called before declaration.
  • Do not guess when scope/range evidence is incomplete.

Implementation guidance and cautions

  • Use stable enclosing symbol IDs and source ranges, not display container names alone.
  • Respect nested and sibling block scopes and choose the innermost legal binding.
  • Apply parameter/local shadowing before file- or type-wide name-family fallback.
  • Distinguish invocation and method-group conversion while preserving the same lexical target.
  • Filter lexically first, then keep overload resolution conservative.
  • Prevent collisions between identical enclosing method names in unrelated types/files.
  • Share scope selection across persistence and query/LSP paths.

Required tests

  • Two methods with identical local-function names and calls before/after declaration.
  • Nested local functions and local-function overloads.
  • Delegate parameter and local-variable shadowing.
  • Method-group conversions.
  • Nested blocks, sibling blocks, and identical enclosing names in unrelated types.
  • Lambdas, expression-bodied members, and incomplete ranges remaining ambiguous.

Assert references, callers, inspect, selectors, impact roots, and LSP position parity.

Acceptance criteria

  • Calls in PrintUsageFull and PrintFlagUsage bind only to their own local function.
  • Calls through the delegate parameter bind to neither local function.
  • Both local functions receive correct caller/reference evidence.
  • No cross-method local-function edge remains.
  • True overload ambiguity remains explicitly ambiguous.

Regression history

This is a focused follow-up to #4840, which established innermost callable containment for C# calls, local functions, and lambdas. Current rows carry that containment, but target resolution does not enforce it. Regression tests and the bilingual changelog should reference #4840.

No exact prior issue for same-name local-function target resolution and delegate-parameter shadowing was found.

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .NET codebugSomething isn't workingtests

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions