Skip to content

C# @-prefixed verbatim identifiers (@class, @int, @return) silently dropped — all name groups use \w+ which excludes @ #367

Description

@Widthdom

Summary

C# allows @-prefixed identifiers to use reserved keywords as names (e.g. @class, @int, @return, @void). This is a standard feature used in interop scenarios, generated code (EF Core DbContext generators, source generators), and library code wrapping keyword-named constructs from other languages. All C# symbol regexes in SymbolExtractor.cs use (?<name>\w+) for the name group, which does NOT include @ (a \w character matches letters, digits, and underscore, but not @). Any member declaration with an @-prefixed name is silently dropped.

Unicode identifiers (ギリシャ文字, 日本語, Russian) work correctly because they ARE matched by \w in .NET's regex engine, which is Unicode-aware.

Repro

CDIDX=/root/.local/bin/cdidx
mkdir -p /tmp/dogfood/cs-verbatim-ident
cat > /tmp/dogfood/cs-verbatim-ident/V.cs <<'EOF'
namespace CsVerbatimIdent;

public class Svc
{
    // @-prefixed property names — DROPPED
    public int @int { get; set; }
    public string @class { get; set; } = "";

    // @-prefixed method — DROPPED
    public void @return() { }

    // Method with @-prefixed parameters — captured (params don't affect method name regex)
    public void Use(int @int, string @class) { }

    // Unicode identifier — captured (\w matches Unicode letters)
    public int 数量 { get; set; }
    public void МетодРусский() { }
}
EOF
"$CDIDX" index /tmp/dogfood/cs-verbatim-ident --rebuild
"$CDIDX" symbols --db /tmp/dogfood/cs-verbatim-ident/.cdidx/codeindex.db

Expected: @int, @class, @return, Use, 数量, МетодРусский + Svc + namespace = 8 symbols.

Actual:

class      Svc                       V.cs:3-17
function   Use                       V.cs:12
function   МетодРусский              V.cs:16
property   数量                       V.cs:15
namespace  CsVerbatimIdent          V.cs:1

Missing: @int, @class (properties at lines 6-7), @return (method at line 10). All symbols declared with @-prefixed names are silently dropped.

Suspected root cause

src/CodeIndex/Indexer/SymbolExtractor.cs:94, 100, 103, 116, 118, 127 — every C# name group uses (?<name>\w+):

// :94 — method
...\s+(?<name>\w+)\s*(?:<[^>]+>\s*)?\(

// :100 — property with get/set/init
...\s+(?<name>\w+)\s*\{\s*(?:get|set|init)

// :103 — expression-bodied property
...\s+(?<name>\w+)\s*=>\s*

// :107 — event
...event\s+\S+\s+(?<name>\w+)

// :127 — enum member
^\s{2,}(?<name>[A-Z]\w*)\s*...

\w in .NET regex (default mode) matches [A-Za-z0-9_] plus Unicode letter categories (\p{L}, \p{Mn}, \p{Nd}, \p{Pc}). It does NOT match @.

For public int @int { get; set; }:

  • Property regex at :100 tries to match. Visibility public, modifier empty, returnType int. Then \s+(?<name>\w+)\s+ matches space. \w+ tries to match @int — but @ is not a \w character. \w+ requires at least one match, so it matches nothing at @ and immediately fails.
  • Backtrack the returnType: in (partial), then \s+ needs whitespace after in — but next char is t. Fails.
  • No other alternative exists.

Suggested fix

Change the name group to accept an optional leading @:

(?<name>@?\w+)

Applied uniformly to rows :94, :97, :100, :103, :105, :107, :116, :118, :120, :122, :127. Each is a single-character addition.

Decision point: should the captured name include or exclude the @ prefix? The C# semantics: @class and class are different identifiers at the declaration site, but equivalent at the usage site (both refer to the same symbol). Two options:

  1. Include @ in the captured name — preserves the exact declaration. definition @class works. definition class falls back to the keyword-named type if any.
  2. Strip @ after capture — normalize to the identifier name. definition class works for both @class and plain class (but class is reserved so can only be a keyword-identifier in practice). Requires post-capture trim.

Option 2 better matches the semantic model (the identifier is "class", @ is syntactic only). Option 1 is simpler and matches the source text exactly.

Either way, the reference extractor's CallRegex ((?<name>[A-Za-z_]\w*)) already doesn't handle @, so a full fix requires updating ReferenceExtractor.cs too — otherwise @foo() calls wouldn't reference the symbol.

Why it matters

  • Generated code frequently uses @-prefixes. EF Core DbContext uses @if, @else columns in databases keyword-tolerant; source generators generate methods with @-prefixed names when the input is a keyword. ASP.NET action parameters named @from, @to are common in route binding.
  • Interop with other languages (VB.NET, F#, Java via language interop, Python via Python.NET) may produce C# wrappers where the original identifier collides with a C# keyword, requiring @ prefix.
  • Silent drop. The symbol is invisible to definition, symbols, outline, inspect, callers, etc.
  • Unicode identifiers work correctly — demonstrating that the regex is Unicode-aware. The @ gap is specifically about the ASCII verbatim-identifier prefix.

Cross-language note

  • C#-specific. The @ verbatim identifier prefix is C# syntax (Roslyn ecosystem).
  • VB.NET uses [keyword] brackets for the same purpose — not applicable here.
  • F# uses `keyword` backticks — not applicable.
  • Kotlin uses backticks too — the Kotlin extractor should be checked separately.

Scope

  • src/CodeIndex/Indexer/SymbolExtractor.cs:94, 97, 100, 103, 105, 107, 116, 118, 120, 122, 127 — add @? before \w+ in the name group of each C# row.
  • src/CodeIndex/Indexer/ReferenceExtractor.cs:75-76ConstructorCallRegex and CallRegex name groups should also accept @ prefix so calls to @foo() resolve.
  • tests/CodeIndex.Tests/SymbolExtractorTests.cs — fixtures for @int, @class, @return, @void as method/property/field names.
  • tests/CodeIndex.Tests/ReferenceExtractorTests.cs — fixtures for calls to @foo().

Related

Environment

  • cdidx: v1.10.0 (/root/.local/bin/cdidx)
  • Platform: Linux 4.4.0
  • Fixture inline in Repro section.

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