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
Swift: enum cases (case timeout, case server(code: Int, message: String)) never captured as symbols, and indirect enum is silently dropped because indirect is missing from the enum-row modifier slot #319
Two independent Swift extractor gaps in src/CodeIndex/Indexer/SymbolExtractor.cs:247-258:
Enum cases are never captured. There is no row matching the case keyword inside a Swift enum body. Every case name, case name(AssociatedValues), and case name = "raw" declaration is silently dropped. Swift's enums are the idiomatic sum type for modeling domain values (Result<T,E>, NetworkError, UIKit/SwiftUI routing enums with associated values) — missing the entire case surface removes the one thing that makes Swift enums more useful than structs.
indirect enum is silently dropped entirely. The Swift enum row at line 251 has no modifier slot in front of enum:
So indirect enum Tree<T> falls through the entire Swift row list — no symbol, no warning. Recursive algebraic data types (indirect enums with case node(Tree<T>, Tree<T>)) are the canonical Swift idiom for trees, linked lists, expression ASTs, parser combinators — all invisible.
Both are in the same enum row and fix together cleanly. Filing as a single ticket because the test fixture and scope overlap.
Repro
CDIDX=/root/.local/bin/cdidx
mkdir -p /tmp/dogfood/swift-enum && cat > /tmp/dogfood/swift-enum/E.swift <<'EOF'enum NetworkError: Error { case timeout case server(code: Int, message: String) case client(Int) case unknown}enum Shape { case circle(radius: Double) case rectangle(width: Double, height: Double) case triangle(a: Double, b: Double, c: Double)}indirect enum Tree<T> { case leaf(T) case node(Tree<T>, Tree<T>)}enum Status: String, Codable { case active case inactive = "off" case pending}EOF"$CDIDX" index /tmp/dogfood/swift-enum --rebuild
"$CDIDX" symbols --db /tmp/dogfood/swift-enum/.cdidx/codeindex.db
Observed:
enum NetworkError E.swift:1-6
enum Shape E.swift:8-12
enum Status E.swift:19-23
(3 symbols in 1 files)
Missing:
Tree — dropped entirely (bug 2: indirect modifier blocks the enum row).
14 case entries across all four enums — dropped entirely (bug 1: no case row).
Expected: 4 enum type rows + 14 case rows = 18 total symbols. Today: 3.
Suspected root cause
src/CodeIndex/Indexer/SymbolExtractor.cs:247-258:
["swift"]=[new("function",newRegex(@"^\s*(?<visibility>public|private|internal|open|fileprivate)?\s*(?:(?:static|class|nonisolated|mutating|nonmutating)\s+)*(?:override\s+)?func\s+(?<name>\w+)", ...),BodyStyle.Brace,"visibility"),new("struct",newRegex(@"^\s*(?<visibility>public|private|internal|open|fileprivate)?\s*(?:(?:final)\s+)*struct\s+(?<name>\w+)", ...),BodyStyle.Brace,"visibility"),new("enum",newRegex(@"^\s*(?<visibility>public|private|internal|open|fileprivate)?\s*enum\s+(?<name>\w+)", ...),BodyStyle.Brace,"visibility"),// ← no modifier slotnew("interface",newRegex(@"^\s*(?<visibility>public|private|internal|open|fileprivate)?\s*protocol\s+(?<name>\w+)", ...),BodyStyle.Brace,"visibility"),new("class",newRegex(@"^\s*(?<visibility>public|private|internal|open|fileprivate)?\s*(?:(?:final|distributed)\s+)*(?:class|actor)\s+(?<name>\w+)", ...),BodyStyle.Brace,"visibility"),new("class",newRegex(@"^\s*(?<visibility>public|private|internal|open|fileprivate)?\s*typealias\s+(?<name>\w+)", ...),BodyStyle.None,"visibility"),// ← kind=class covered by #206new("import",newRegex(@"^\s*import\s+(?<name>.+)", ...),BodyStyle.None),],
Enum row's modifier slot missing indirect. The class/actor row has (?:(?:final|distributed)\s+)* and the struct row has (?:(?:final)\s+)*, but the enum row has no modifier slot at all. Swift's indirect keyword applies either at the enum level (entire enum is boxed) or at the individual case level (indirect case node(...)).
Suggested direction
Two additive fixes in the same row block:
Fix 1 — add indirect to the enum modifier slot:
// Add modifier slot to enum row for indirect / Swift enum 行に indirect 修飾子スロットを追加。new("enum",newRegex(@"^\s*(?<visibility>public|private|internal|open|fileprivate)?\s*"+@"(?:indirect\s+)?"// ← NEW: indirect modifier+@"enum\s+(?<name>\w+)",RegexOptions.Compiled),BodyStyle.Brace,"visibility"),
indirect can only combine with enum in Swift (not with struct, class, actor), so this slot is enum-specific and doesn't risk widening the other rows.
Fix 2 — add an enum-case row after the enum row:
// Swift enum case — bare, with associated values, or with raw value.// Swift 列挙ケース — bare / 関連値 / 生値いずれにも対応。new("property",newRegex(@"^\s*(?:indirect\s+)?"// indirect case is legal at the case level too+@"case\s+(?<name>\w+)"// case name+@"(?:\s*\(.*\))?"// optional associated-values tuple+@"(?:\s*=\s*(?<returnType>.+?))?"// optional raw-value assignment+@"\s*$",RegexOptions.Compiled),BodyStyle.None,ReturnTypeGroup:"returnType"),
Notes:
Kind choice.property matches the treatment Kotlin uses for enum-like value entries. Alternate option: introduce a new case or enum_member kind, but that requires taxonomy discussion — property lands the visibility/searchability gains immediately without schema debate.
Associated value capture. The (?:\s*\(.*\))? group accepts case server(code: Int, message: String). The captured string is not parsed further here; downstream signature extraction could be improved in a follow-up, but the symbol's existence and name are the high-value gain.
Raw value capture. The = ... suffix only applies to backed enums (enum Status: String). The captured value feeds return_type metadata, matching the treatment suggested for PHP enums in the sibling ticket.
Switch-case false positives. Swift switch arms have the form case .timeout: (with dot-shorthand) or case let .server(code, _): (with binding). Both start with case but are followed by . or let, not a bare identifier. The proposed pattern requires case\s+\w+ without a leading ./let, and the trailing \s*$ anchors the match to the full declaration line. A switch arm ending in : would fail the \s*$ anchor (Swift switch arms require : before the arm body on the same or next line). Verify with a fixture.
Why it matters
Every UIKit/AppKit/SwiftUI codebase models routing, navigation, state, error handling via enum with associated values. cdidx definition timeout --lang swift returns zero today.
indirect enum is the canonical Swift recursive-type idiom. Parser combinators, tree/AST libraries (SwiftSyntax's own data model), linked lists — all invisible. Not captured at all, not even as a class.
unused over-reports.public enum Foo { case bar } with bar never extracted means the value is flagged as unused (because there are no case definitions and Foo is only mentioned as the type, not dereferenced). Even without graph support (Swift's graph column is no per cdidx languages), symbol-level searches fail.
outline / inspect show enum shells with no interior, same as the PHP sibling.
Silent. The enum type shell captures successfully, giving the user a false sense that the file is indexed.
Cross-language note
PHP 8.1+ enum cases — same structural gap, filed as a sibling ticket. The fix regex differs (PHP uses ; terminator; Swift uses newline with optional = value).
Rust enum variants — enum Foo { Bar, Baz(i32) } — worth a spot-check, different syntax shape (comma-separated, multi-line bodies).
Java enums — enum E { A, B; } — already a long-standing pattern; worth verifying with a fixture.
Kotlin enum class — enum entries like FOO, / BAR(42) — worth a spot-check.
C# — comma-separated enum members — Covered correctly today.
indirect modifier — Swift-only, so no cross-language action.
Scope
src/CodeIndex/Indexer/SymbolExtractor.cs:247-258 — two additive changes in the Swift row block: (a) widen enum row's modifier slot to include indirect, (b) add a new enum-case row with optional associated-values tuple and optional raw-value suffix.
tests/CodeIndex.Tests/SymbolExtractorTests.cs — fixtures: (a) bare case case timeout captured as property with name=timeout, (b) associated-values case case server(code: Int, message: String) captured with name server, (c) raw-value case case inactive = "off" captured with backing value in return_type, (d) indirect enum Tree<T> captured as enum, (e) indirect case node(Tree<T>, Tree<T>) captured, (f) regression: Swift switch case .foo: / case let .bar(x, y): arms are NOT captured, (g) regression: enum type enum NetworkError: Error still matches and is not disturbed by the new modifier slot.
DEVELOPER_GUIDE.md — Swift language pattern reference row should list case + indirect support.
Sibling ticket filed for PHP enum cases — shares the property-kind choice and the optional-value-capture pattern.
No existing issue covers Swift enum cases or indirect enum modifier.
Environment
cdidx v1.10.0 at /root/.local/bin/cdidx (trimmed release build installed via install.sh).
Fixture /tmp/dogfood/swift-enum/E.swift as above.
cdidx languages shows swift with yes / no in Symbols/Graph (graph not supported, so reference-side enum-case gaps are out of scope for this ticket — symbol-side only).
Platform: linux-x64 container.
Filed from a Claude Code cloud session per CLOUD_BOOTSTRAP_PROMPT.md.
Summary
Two independent Swift extractor gaps in
src/CodeIndex/Indexer/SymbolExtractor.cs:247-258:Enum cases are never captured. There is no row matching the
casekeyword inside a Swift enum body. Everycase name,case name(AssociatedValues), andcase name = "raw"declaration is silently dropped. Swift's enums are the idiomatic sum type for modeling domain values (Result<T,E>,NetworkError, UIKit/SwiftUI routing enums with associated values) — missing the entire case surface removes the one thing that makes Swift enums more useful than structs.indirect enumis silently dropped entirely. The Swift enum row at line 251 has no modifier slot in front ofenum:So
indirect enum Tree<T>falls through the entire Swift row list — no symbol, no warning. Recursive algebraic data types (indirectenums withcase node(Tree<T>, Tree<T>)) are the canonical Swift idiom for trees, linked lists, expression ASTs, parser combinators — all invisible.Both are in the same enum row and fix together cleanly. Filing as a single ticket because the test fixture and scope overlap.
Repro
Observed:
Missing:
Tree— dropped entirely (bug 2:indirectmodifier blocks the enum row).caseentries across all four enums — dropped entirely (bug 1: no case row).Expected: 4 enum type rows + 14 case rows = 18 total symbols. Today: 3.
Suspected root cause
src/CodeIndex/Indexer/SymbolExtractor.cs:247-258:Enum row has no
casesibling. The closest to enum-value capture is theclass/actorrow, which doesn't help. Thetypealiasrow hasBodyStyle.Noneand is kind-misclassified (separately tracked by Type-alias declarations misclassified asclassacross Rust / TypeScript / Swift / Go / F# (and silently dropped in Scala) #206 for Swift/Rust/TS/Go/F#).Enum row's modifier slot missing
indirect. Theclass/actorrow has(?:(?:final|distributed)\s+)*and thestructrow has(?:(?:final)\s+)*, but theenumrow has no modifier slot at all. Swift'sindirectkeyword applies either at the enum level (entire enum is boxed) or at the individual case level (indirect case node(...)).Suggested direction
Two additive fixes in the same row block:
Fix 1 — add
indirectto the enum modifier slot:indirectcan only combine withenumin Swift (not withstruct,class,actor), so this slot is enum-specific and doesn't risk widening the other rows.Fix 2 — add an enum-case row after the enum row:
Notes:
propertymatches the treatment Kotlin uses for enum-like value entries. Alternate option: introduce a newcaseorenum_memberkind, but that requires taxonomy discussion —propertylands the visibility/searchability gains immediately without schema debate.(?:\s*\(.*\))?group acceptscase server(code: Int, message: String). The captured string is not parsed further here; downstream signature extraction could be improved in a follow-up, but the symbol's existence and name are the high-value gain.= ...suffix only applies to backed enums (enum Status: String). The captured value feedsreturn_typemetadata, matching the treatment suggested for PHP enums in the sibling ticket.case foo, bar, bazon a single line (less idiomatic but legal for bare cases). The proposed row captures only the first name via\w+. Covering comma-lists requires a split similar to the CSS comma-selector handling from CSS: comma-joined selector lists drop every selector after the first on the same line; ID selectors misclassified askind=function;@counter-style/@page/@namespaceat-rules not captured #235 — out of scope for this ticket, can be added later if it shows up in real codebases.case .timeout:(with dot-shorthand) orcase let .server(code, _):(with binding). Both start withcasebut are followed by.orlet, not a bare identifier. The proposed pattern requirescase\s+\w+without a leading./let, and the trailing\s*$anchors the match to the full declaration line. A switch arm ending in:would fail the\s*$anchor (Swift switch arms require:before the arm body on the same or next line). Verify with a fixture.Why it matters
cdidx definition timeout --lang swiftreturns zero today.indirect enumis the canonical Swift recursive-type idiom. Parser combinators, tree/AST libraries (SwiftSyntax's own data model), linked lists — all invisible. Not captured at all, not even as a class.unusedover-reports.public enum Foo { case bar }withbarnever extracted means the value is flagged as unused (because there are no case definitions andFoois only mentioned as the type, not dereferenced). Even without graph support (Swift's graph column isnopercdidx languages), symbol-level searches fail.outline/inspectshow enum shells with no interior, same as the PHP sibling.Cross-language note
;terminator; Swift uses newline with optional= value).enum Foo { Bar, Baz(i32) }— worth a spot-check, different syntax shape (comma-separated, multi-line bodies).enum E { A, B; }— already a long-standing pattern; worth verifying with a fixture.enum class— enum entries likeFOO,/BAR(42)— worth a spot-check.indirectmodifier — Swift-only, so no cross-language action.Scope
src/CodeIndex/Indexer/SymbolExtractor.cs:247-258— two additive changes in the Swift row block: (a) widen enum row's modifier slot to includeindirect, (b) add a new enum-case row with optional associated-values tuple and optional raw-value suffix.tests/CodeIndex.Tests/SymbolExtractorTests.cs— fixtures: (a) bare casecase timeoutcaptured as property withname=timeout, (b) associated-values casecase server(code: Int, message: String)captured with nameserver, (c) raw-value casecase inactive = "off"captured with backing value inreturn_type, (d)indirect enum Tree<T>captured as enum, (e)indirect case node(Tree<T>, Tree<T>)captured, (f) regression: Swift switchcase .foo:/case let .bar(x, y):arms are NOT captured, (g) regression: enum typeenum NetworkError: Errorstill matches and is not disturbed by the new modifier slot.DEVELOPER_GUIDE.md— Swift language pattern reference row should list case + indirect support.Related
classacross Rust / TypeScript / Swift / Go / F# (and silently dropped in Scala) #206 — Swifttypealiasmisclassified asclass. Same file, unrelated construct; orthogonal fix.public int Count;,private readonly List<int> _items;,public static int GlobalCount;) are not captured as symbols — onlyconstandstatic readonlyare indexed #298 — C# plain fields dropped. Same "class/enum member coverage gap" family.property-kind choice and the optional-value-capture pattern.indirectenum modifier.Environment
/root/.local/bin/cdidx(trimmed release build installed viainstall.sh)./tmp/dogfood/swift-enum/E.swiftas above.cdidx languagesshowsswiftwithyes/noin Symbols/Graph (graph not supported, so reference-side enum-case gaps are out of scope for this ticket — symbol-side only).CLOUD_BOOTSTRAP_PROMPT.md.