Skip to content

C#: #496 suffix guard still drops real members whose return type is contextual-keyword type names like await / yield #785

Description

@Widthdom

Summary

The current #496 / #782 suffix-guard fix in SymbolExtractor still drops real C# method declarations when the return type is a legal contextual-keyword type name such as await or yield.

Roslyn accepts these declarations (with warning CS8981 only), but cdidx drops the methods entirely from symbols, definition, and outline.

Repro

CDIDX='dotnet ./src/CodeIndex/bin/Debug/net8.0/cdidx.dll'
TMPDIR=$(mktemp -d /tmp/cdidx-cs-contextual-return.XXXXXX)
cat > "$TMPDIR/Class1.cs" <<'EOF'
namespace Demo;

public class await {}
public class yield {}
public class Uses
{
    public await MakeAwait() => new await();
    public yield MakeYield() => new yield();
}
EOF

# Control: Roslyn accepts the code.
(
  cd "$TMPDIR"
  dotnet new classlib --framework net8.0 >/dev/null
  dotnet build -nologo
)

"$CDIDX" index "$TMPDIR" --json >/dev/null
"$CDIDX" outline Class1.cs --db "$TMPDIR/.cdidx/codeindex.db" --json

Observed outline payload (abridged):

{
  "symbols": [
    {"kind":"namespace","name":"Demo"},
    {"kind":"class","name":"await"},
    {"kind":"class","name":"yield"},
    {"kind":"class","name":"Uses"}
  ]
}

Missing:

{"kind":"function","name":"MakeAwait","return_type":"await",...}
{"kind":"function","name":"MakeYield","return_type":"yield",...}

Root cause

HasInvalidCSharpReturnTypeSuffix(...) rejects any captured return-type tail whose final token is one of:

  • as
  • is
  • await
  • return
  • throw
  • yield
  • new

That is correct for phantom call-site fragments like elapsed < TimeSpan.FromSeconds(...), but it is too broad for legal contextual-keyword identifiers used as type names. Unlike new, await and yield do not need @ here; Roslyn accepts them as identifiers.

The current #782 follow-up only exempted verbatim identifiers such as @new, so the non-verbatim contextual-keyword case is still broken.

Suggested direction

Make the suffix guard distinguish between:

  • real declaration return types whose final token is a contextual-keyword identifier (await, yield, possibly other legal contextual identifiers)
  • operator / statement fragments from call-site false positives

A few possible directions:

  1. shrink the deny-list to truly impossible declaration tails and rely on the surrounding regex / declaration context for the rest
  2. keep the deny-list but exempt contextual-keyword identifiers that Roslyn accepts as type names
  3. move the guard closer to the specific phantom-call-site pattern(s) that motivated #496, instead of applying it to every C# row with ReturnTypeGroup

Tests to add

Please add focused regressions in tests/CodeIndex.Tests/SymbolExtractorTests.cs for at least:

public class await {}
public class yield {}
public class Uses
{
    public await MakeAwait() => new await();
    public yield MakeYield() => new yield();
}

Assert that:

  • class await and class yield are present
  • function MakeAwait and function MakeYield are present
  • their ReturnType values are await / yield

Environment

  • Reproduced on macOS arm64
  • Binary: local build from current branch fix-issue496-782 via dotnet ./src/CodeIndex/bin/Debug/net8.0/cdidx.dll
  • Discovered during adversarial review of origin/main..HEAD

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions