You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A C# const field whose type is a tuple — public const (int, int) Pair = (1, 2);, public const (int a, int b) Named = ..., public const (int, int)? Maybe = null; — causes SymbolExtractor to (a) drop the field from the symbol index and (b) emit a phantom function const row pointing at the field line. Same backtracking mechanism as #336 (readonly-field-tuple → phantom function readonly), but on a different regex row and with a different visible phantom name (const instead of readonly), so searches for "phantom function const" don't surface #336.
#336 explicitly dismisses the const row in its root-cause section (":69 (const field) requires const. N/A."). Its proposed fix (negative lookahead on :94) would also eliminate this phantom, but the const row at :69 would still fail to capture the const-tuple field — the capture side needs a separate change.
Repro
CDIDX=/root/.local/bin/cdidx
mkdir -p /tmp/dogfood/cs-const-tuple
cat > /tmp/dogfood/cs-const-tuple/C.cs <<'EOF'namespace ConstTuple;public class Cfg{ // const tuple field — PHANTOM `function const` public const (int, int) Pair = (1, 2); // const named tuple — PHANTOM `function const` public const (int a, int b) NamedPair = (1, 2); // Control — captured correctly as `function Plain` public const int Plain = 42; // const nullable tuple — PHANTOM `function const` public const (int, int)? MaybePair = null; // Static readonly generic-over-tuple — DROPPED (same family, #336 scope) public static readonly System.Collections.Generic.List<(int, int)> Pairs = new();}EOF"$CDIDX" index /tmp/dogfood/cs-const-tuple --rebuild
"$CDIDX" symbols --db /tmp/dogfood/cs-const-tuple/.cdidx/codeindex.db
Actual:
class Cfg C.cs:3-19
function Plain C.cs:12
namespace ConstTuple C.cs:1
function const C.cs:6
function const C.cs:9
function const C.cs:15
Three phantom function const rows at L6, L9, L15. Pair, NamedPair, MaybePair, and Pairs are all missing from the symbol table. definition Pair --exact returns zero hits. definition const returns three rows of nonsense at field lines, none of which are actual callable methods.
The returnType char class [\w?.<>\[\],:]+ has no (, ), \s, and no \([^)]+\) tuple alternative. For public const (int, int) Pair = (1, 2);:
Visibility matches public. Modifier slot (new|static) is empty (const itself is consumed next, literally).
Literal const matches const.
returnType char class tries to consume the next token — the next char is ( from (int, int). ( is not in the char class. returnType fails to consume anything meaningful (and the char class is + so it must match at least one char). Const row fails.
Then the method row at :94 runs. Same backtracking trace as #336, but the phantom comes out with a different name:
With visibility=public, modifier={}: returnType tuple alt \([^)]+\) matches (int, int). \s+\w+ matches Pair. Then \s*(?:<[^>]+>\s*)?\( requires (, next is = (1, 2); — whitespace then =, not (. Fails.
Visibility group backs off (empty). Modifier slot: const is not in the list (static|sealed|partial|readonly|unsafe|extern|virtual|override|abstract|async|new|file), so it stays empty.
returnType char class matches public. \s+\w+ matches const. Then \s*(?:<[^>]+>\s*)?\( matches ( — the opening paren of the tuple type is consumed as the start of a parameter list.
Match succeeds with returnType=public, name=const. Phantom emitted.
The nullable-tuple variant ((int, int)?) goes through the same path — the ? sits outside the \( the method regex consumes, so it never enters the match.
L18 (public static readonly List<(int, int)> Pairs) silently drops without a phantom because the char class in :71 can't cross the embedded (, and the method regex's own char class backtracking never reaches a valid \(. This drop is arguably a follow-up to #336's "defense in depth" note on :71, not a new bug.
Suggested direction
Two independent changes — either alone only addresses part of the symptom.
1. Extend the const row :69 to accept tuple / nullable-tuple / named-tuple return types.
The \?? suffix handles nullable-tuple ((int, int)?). With this change, public const (int, int) Pair = ... is captured as a const with returnType (int, int) and name Pair.
2. Add the same visibility-keyword negative lookahead to the method row :94 proposed in #336.
After change (1), the method row at :94 no longer gets reached for const-tuple lines — but the #336 fix is still required to also eliminate the readonly-tuple phantom, tuple-return-delegate phantom (#340), tuple-return-operator phantom (#342), and this const-tuple phantom in older indexes or edge lines the extended const row still rejects.
Tests worth adding:
Every shape in the repro (plain const, const tuple, named const tuple, nullable const tuple, generic-over-tuple static readonly).
Assertion: no function const phantom ever appears.
Assertion: const-tuple fields are captured with returnType = (int, int) and name = Pair.
Regression: public const int X = 1; still captured as function X with returnType int.
Why it matters
public const (int, int) values are common in math / geometry / pinned-point code (unit vectors, corner indices, board dimensions). Each one pollutes the index with a phantom function const row and hides the real Pair / NamedPair name.
cdidx definition 'Pair' misses the const field; an AI agent asked "what's the value of Pair?" sees nothing, while cdidx definition 'const' returns meaningless phantoms.
hotspots --kind function over-counts phantom const rows. unused --kind function lists them as unused functions.
Cross-language note
C#-specific. The const-with-tuple shape is unique to C# syntax. The fix is C#-scoped to rows :69 and :94.
Scope
src/CodeIndex/Indexer/SymbolExtractor.cs:69 — add tuple alternative (with optional ? suffix) to returnType.
Summary
A C#
constfield whose type is a tuple —public const (int, int) Pair = (1, 2);,public const (int a, int b) Named = ...,public const (int, int)? Maybe = null;— causesSymbolExtractorto (a) drop the field from the symbol index and (b) emit a phantomfunction constrow pointing at the field line. Same backtracking mechanism as #336 (readonly-field-tuple → phantomfunction readonly), but on a different regex row and with a different visible phantom name (constinstead ofreadonly), so searches for "phantomfunction const" don't surface #336.#336 explicitly dismisses the const row in its root-cause section ("
:69(const field) requiresconst. N/A."). Its proposed fix (negative lookahead on:94) would also eliminate this phantom, but the const row at:69would still fail to capture the const-tuple field — the capture side needs a separate change.Repro
Actual:
Three phantom
function constrows at L6, L9, L15.Pair,NamedPair,MaybePair, andPairsare all missing from the symbol table.definition Pair --exactreturns zero hits.definition constreturns three rows of nonsense at field lines, none of which are actual callable methods.Suspected root cause (from reading the source)
src/CodeIndex/Indexer/SymbolExtractor.cs:69— const row:The returnType char class
[\w?.<>\[\],:]+has no(,),\s, and no\([^)]+\)tuple alternative. Forpublic const (int, int) Pair = (1, 2);:public. Modifier slot (new|static) is empty (const itself is consumed next, literally).constmatchesconst.(from(int, int).(is not in the char class. returnType fails to consume anything meaningful (and the char class is+so it must match at least one char). Const row fails.Then the method row at
:94runs. Same backtracking trace as #336, but the phantom comes out with a different name:visibility=public, modifier={}: returnType tuple alt\([^)]+\)matches(int, int).\s+\w+matchesPair. Then\s*(?:<[^>]+>\s*)?\(requires(, next is= (1, 2);— whitespace then=, not(. Fails.constis not in the list (static|sealed|partial|readonly|unsafe|extern|virtual|override|abstract|async|new|file), so it stays empty.public.\s+\w+matchesconst. Then\s*(?:<[^>]+>\s*)?\(matches(— the opening paren of the tuple type is consumed as the start of a parameter list.returnType=public, name=const. Phantom emitted.The nullable-tuple variant (
(int, int)?) goes through the same path — the?sits outside the\(the method regex consumes, so it never enters the match.L18 (
public static readonly List<(int, int)> Pairs) silently drops without a phantom because the char class in :71 can't cross the embedded(, and the method regex's own char class backtracking never reaches a valid\(. This drop is arguably a follow-up to #336's "defense in depth" note on:71, not a new bug.Suggested direction
Two independent changes — either alone only addresses part of the symptom.
1. Extend the const row
:69to accept tuple / nullable-tuple / named-tuple return types.Mirror the method row's returnType alternation:
The
\??suffix handles nullable-tuple ((int, int)?). With this change,public const (int, int) Pair = ...is captured as a const with returnType(int, int)and namePair.2. Add the same visibility-keyword negative lookahead to the method row
:94proposed in #336.After change (1), the method row at
:94no longer gets reached for const-tuple lines — but the #336 fix is still required to also eliminate the readonly-tuple phantom, tuple-return-delegate phantom (#340), tuple-return-operator phantom (#342), and this const-tuple phantom in older indexes or edge lines the extended const row still rejects.Tests worth adding:
function constphantom ever appears.returnType = (int, int)andname = Pair.public const int X = 1;still captured asfunction Xwith returnTypeint.Why it matters
public const (int, int)values are common in math / geometry / pinned-point code (unit vectors, corner indices, board dimensions). Each one pollutes the index with a phantomfunction constrow and hides the realPair/NamedPairname.cdidx definition 'Pair'misses the const field; an AI agent asked "what's the value ofPair?" sees nothing, whilecdidx definition 'const'returns meaningless phantoms.hotspots --kind functionover-counts phantom const rows.unused --kind functionlists them as unused functions.Cross-language note
C#-specific. The const-with-tuple shape is unique to C# syntax. The fix is C#-scoped to rows
:69and:94.Scope
src/CodeIndex/Indexer/SymbolExtractor.cs:69— add tuple alternative (with optional?suffix) to returnType.src/CodeIndex/Indexer/SymbolExtractor.cs:94— add visibility-keyword negative lookahead on returnType (shared with C# readonly fields with tuple types (public readonly (int, int) X;) emit phantomfunction readonlyrows — method regex backtrackspublicinto returnType #336 / C# delegates with tuple return types (public delegate (int, int) MakePair();) are dropped AND emit phantomfunction delegaterows #340 / C# operators with a tuple / spaced-generic return type emit phantomfunction staticrows (and the real operator is silently dropped) — operator regex\S+can't cross tuple whitespace, method regex backtracks intopublic→returnType /static→name #342).tests/CodeIndex.Tests/SymbolExtractorTests.cs— fixtures for each shape; assert no phantomfunction constrow, and assert const-tuple capture.Related
public readonly (int, int) X;) emit phantomfunction readonlyrows — method regex backtrackspublicinto returnType #336 — readonly field tuple → phantomfunction readonly(distinct row:71, same mechanism).public delegate (int, int) MakePair();) are dropped AND emit phantomfunction delegaterows #340 — delegate tuple → phantomfunction delegate(distinct row:105).function staticrows (and the real operator is silently dropped) — operator regex\S+can't cross tuple whitespace, method regex backtracks intopublic→returnType /static→name #342 — operator tuple → phantomfunction static(distinct row:87).Func<int, int, int>) or tuple types ((int, int)) are silently dropped — returnType char class on property regexes lacks\sand(/)#338 — property tuple / spaced-generics drop (distinct row:100/:103).this[...]) with tuple / named-tuple / nullable-tuple / generic-over-tuple return types is silently dropped — indexer regex returnType lacks\s,(,), and has no tuple alternative #343 — indexer tuple drop.\sin its returnType class #344 — explicit-interface-method tuple drop.Task<(int, string)>,Dictionary<string, (int x, int y)>) are dropped from the symbol index #241 / C#: tuple return types with a trailing suffix ((int, int)[],(int, int)?,(int, int)[][]) silently dropped — returnType tuple branch has no trailing-suffix slot; distinct from #241 #328 — method tuple-return variants.Environment
/root/.local/bin/cdidx).CLOUD_BOOTSTRAP_PROMPT.md.