Summary
In C# (and analogously Java's T.class / Class.forName("T")), the keywords nameof, typeof, sizeof, and default accept a type or member name as their argument. These arguments are first-class compile-time references: nameof(Target.Alpha) breaks at compile time if Target.Alpha is renamed, and refactoring tools treat the argument as a rename target. Yet cdidx's reference extractor drops them entirely because:
nameof, typeof, sizeof are in IgnoredCallNames (ReferenceExtractor.cs:27), so the name( call match is correctly suppressed — but that leaves the inner identifier(s) to be matched only via CallRegex, which requires a trailing (. Target.Alpha) has no ( after Alpha, so it never matches.
- There is no special-case handling for these keyword-like operators to peek at their argument list.
Net effect: references Target, references Alpha, references Beta on code that references those members via nameof/typeof/default return zero hits.
Repro
CDIDX=/root/.local/bin/cdidx
mkdir -p /tmp/dogfood/cs-nameof && cat > /tmp/dogfood/cs-nameof/N2.cs << 'EOF'
namespace Demo;
public class Target
{
public int Alpha() => 1;
public static int Beta() => 2;
}
public class Caller
{
public void Work()
{
var n1 = nameof(Target.Alpha); // → should be a reference to Target and Alpha
var n2 = nameof(Target); // → should be a reference to Target
var n3 = nameof(Beta); // → should be a reference to Beta
var tp = typeof(Target); // → should be a reference to Target
Target? def = default(Target); // → should be a reference to Target
}
}
EOF
"$CDIDX" index /tmp/dogfood/cs-nameof --rebuild
"$CDIDX" references Target --db /tmp/dogfood/cs-nameof/.cdidx/codeindex.db --exact
"$CDIDX" references Alpha --db /tmp/dogfood/cs-nameof/.cdidx/codeindex.db --exact
"$CDIDX" references Beta --db /tmp/dogfood/cs-nameof/.cdidx/codeindex.db --exact
Observed:
No references found. ← Target (5 occurrences)
No references found. ← Alpha (1 occurrence)
No references found. ← Beta (1 occurrence)
Expected: at minimum one reference row per argument of nameof/typeof/default.
Suspected root cause (from reading the source)
src/CodeIndex/Indexer/ReferenceExtractor.cs:
- Line 27:
IgnoredCallNames includes "sizeof", "typeof", "return", "throw", "nameof", "await", "using", "new".
- Line 76:
CallRegex = (?<![\w$])(?<name>[A-Za-z_]\w*)(?:<[^>\n]+>)?\s*\( — requires trailing (.
- Line 155-164: only
CallRegex.Matches(preparedLine) is consulted for regular references.
So the scanner walks each call-like token. For nameof(Target.Alpha) it sees:
nameof( — matches CallRegex, but nameof is in IgnoredCallNames → suppressed. ✓ correct for the keyword itself.
Target.Alpha) — no trailing ( anywhere, so CallRegex finds nothing. The argument is never scanned for identifiers.
Analogously for typeof(Target) and default(Target) (default is in the set at line 34).
Suggested fix
Add a targeted argument-capture pass that, for each (nameof|typeof|sizeof|default)\s*\(\s*([\w.]+)\s*\) match on the prepared line, emits one reference row per dotted segment:
private static readonly Regex NameofTypeofArgRegex = new(
@"\b(?:nameof|typeof|sizeof|default)\s*\(\s*(?<arg>[\w.]+)(?:\[\])?\s*\)",
RegexOptions.Compiled);
// Inside the csharp branch of the scan:
if (language is "csharp")
{
foreach (Match arg in NameofTypeofArgRegex.Matches(preparedLine))
{
var raw = arg.Groups["arg"].Value;
// Emit a reference for each dot-segment: `Target.Alpha` → Target + Alpha.
foreach (var segment in raw.Split('.'))
{
if (IgnoredCallNames.Contains(segment)) continue;
AddReference(references, seen, fileId, /* name: */ segment,
/* column: */ arg.Index + raw.IndexOf(segment),
"type_reference", context, lineNumber, container);
}
}
}
Use a dedicated reference_kind (e.g. "type_reference" or "nameof_arg") so downstream tools can distinguish these from ordinary calls. callers/callees should continue to ignore them; references and impact (impact already reads reference_kind IN (...) filtered) should include them.
Java has a parallel case with SomeType.class, MyType[].class, and Class.forName("Foo") — a similar regex (\b(?<arg>[\w.]+)\s*\.class\b) would cover the common compile-time-reference style.
Why it matters
- Refactoring safety. The whole point of
nameof(Foo) over "Foo" is that renaming Foo breaks at compile time. Users who search references Foo to see "what will this rename break?" miss every nameof caller today.
unused false positives. A member only referenced via nameof(MyMember) (e.g. for property-change notifications, serialization attribute keys, logging) looks unused.
impact chains truncate early. If a public API is only reached through nameof for attribute-based wiring (e.g. [BindProperty(Name = nameof(Foo))]), the impact analysis drops the edge.
Related
Scope
src/CodeIndex/Indexer/ReferenceExtractor.cs — add argument-capture pass for C# (and parallel Java .class).
tests/CodeIndex.Tests/ReferenceExtractorTests.cs — fixtures above.
- Consider a dedicated
reference_kind label so existing callers/callees semantics are preserved.
Environment
- cdidx v1.10.0 (installed via
install.sh to /root/.local/bin/cdidx).
- Platform: linux-x64 container.
- Filed from a cloud Claude Code session per
CLOUD_BOOTSTRAP_PROMPT.md.
Summary
In C# (and analogously Java's
T.class/Class.forName("T")), the keywordsnameof,typeof,sizeof, anddefaultaccept a type or member name as their argument. These arguments are first-class compile-time references:nameof(Target.Alpha)breaks at compile time ifTarget.Alphais renamed, and refactoring tools treat the argument as a rename target. Yet cdidx's reference extractor drops them entirely because:nameof,typeof,sizeofare inIgnoredCallNames(ReferenceExtractor.cs:27), so thename(call match is correctly suppressed — but that leaves the inner identifier(s) to be matched only viaCallRegex, which requires a trailing(.Target.Alpha)has no(afterAlpha, so it never matches.Net effect:
references Target,references Alpha,references Betaon code that references those members vianameof/typeof/defaultreturn zero hits.Repro
Observed:
Expected: at minimum one reference row per argument of
nameof/typeof/default.Suspected root cause (from reading the source)
src/CodeIndex/Indexer/ReferenceExtractor.cs:IgnoredCallNamesincludes"sizeof", "typeof", "return", "throw", "nameof", "await", "using", "new".CallRegex = (?<![\w$])(?<name>[A-Za-z_]\w*)(?:<[^>\n]+>)?\s*\(— requires trailing(.CallRegex.Matches(preparedLine)is consulted for regular references.So the scanner walks each call-like token. For
nameof(Target.Alpha)it sees:nameof(— matchesCallRegex, butnameofis inIgnoredCallNames→ suppressed. ✓ correct for the keyword itself.Target.Alpha)— no trailing(anywhere, soCallRegexfinds nothing. The argument is never scanned for identifiers.Analogously for
typeof(Target)anddefault(Target)(defaultis in the set at line 34).Suggested fix
Add a targeted argument-capture pass that, for each
(nameof|typeof|sizeof|default)\s*\(\s*([\w.]+)\s*\)match on the prepared line, emits one reference row per dotted segment:Use a dedicated
reference_kind(e.g."type_reference"or"nameof_arg") so downstream tools can distinguish these from ordinary calls.callers/calleesshould continue to ignore them;referencesandimpact(impact already readsreference_kind IN (...)filtered) should include them.Java has a parallel case with
SomeType.class,MyType[].class, andClass.forName("Foo")— a similar regex (\b(?<arg>[\w.]+)\s*\.class\b) would cover the common compile-time-reference style.Why it matters
nameof(Foo)over"Foo"is that renamingFoobreaks at compile time. Users who searchreferences Footo see "what will this rename break?" miss everynameofcaller today.unusedfalse positives. A member only referenced vianameof(MyMember)(e.g. for property-change notifications, serialization attribute keys, logging) looks unused.impactchains truncate early. If a public API is only reached throughnameoffor attribute-based wiring (e.g.[BindProperty(Name = nameof(Foo))]), the impact analysis drops the edge.Related
IgnoredCallNamesis language-global. This issue is orthogonal: even with the keyword-level suppression correct, the argument needs extra capture logic.[assembly: Attr(args)]/[module: Attr(args)]lines falsely indexed as method definitions #219 / C#: target-specified attributes ([return:,[assembly:,[module:…) become phantomfunctionsymbols, which also silently suppresses their own call references #251 — attribute target cascade (also a C# phantom-symbol path that touchesReferenceExtractorfiltering).ReferenceExtractor.cs:155-164).Scope
src/CodeIndex/Indexer/ReferenceExtractor.cs— add argument-capture pass for C# (and parallel Java.class).tests/CodeIndex.Tests/ReferenceExtractorTests.cs— fixtures above.reference_kindlabel so existingcallers/calleessemantics are preserved.Environment
install.shto/root/.local/bin/cdidx).CLOUD_BOOTSTRAP_PROMPT.md.