From 80c4b443f6441980594940e90b334e41b25f1da8 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 23 Jul 2026 13:43:13 +0000 Subject: [PATCH] unified: Emit base types from inheritance clauses Map a nominal type's inheritance clause (`class C: Base, Proto`, and likewise for enum/struct/protocol/extension) to `base_type` children on the `class_like_declaration`, one per inherited type. The tree-sitter path dropped these (no corpus target had a `base_type`) and the mapping matched that for parity; swift-syntax exposes the clause cleanly. Adds a focused `class-with-multiple-base-types` corpus case and updates `class-inheritance` to witness the restored base types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../extractor/src/languages/swift/swift.rs | 51 +++++++++++++++---- .../swift/types/class-inheritance.output | 5 ++ .../class-with-multiple-base-types.output | 51 +++++++++++++++++++ .../class-with-multiple-base-types.swift | 1 + 4 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.output create mode 100644 unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.swift diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 467d136a5b2..e2f9010d70d 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -1028,50 +1028,73 @@ fn translation_rules() -> Vec> { // (swift-syntax represents `#selector`/`#keyPath` and other macro // expansions uniformly as a `macroExpansionExpr`). rule!((macroExpansionExpr) => (unsupported_node)), - // PARITY(tree-sitter): a nominal type's `inheritanceClause` (`: Base, - // Proto`) is not emitted as a `base_type` — the tree-sitter path drops - // it (no corpus target has a `base_type`). swift-syntax exposes it - // cleanly, so emitting `base_type` is a correctness improvement to make - // once tree-sitter is retired. Each declaration keyword gets its own - // rule; the bodies are identical but for the keyword. + // A nominal type's `inheritanceClause` (`: Base, Proto`) becomes a list + // of `base_type`s, one per inherited type. The tree-sitter path dropped + // it (no corpus target had a `base_type`) and the mapping matched that + // for parity; swift-syntax exposes it cleanly. Each declaration keyword + // gets its own rule; the bodies are identical but for the keyword. // Class declaration with body containing members rule!( - (classDecl classKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) + (classDecl + classKeyword: @kind + modifiers: _* @mods + name: @name + inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), // Enum class declaration: same as a regular class but with an enum body. rule!( - (enumDecl enumKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) + (enumDecl + enumKeyword: @kind + modifiers: _* @mods + name: @name + inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), // A `struct` declaration. rule!( - (structDecl structKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) + (structDecl + structKeyword: @kind + modifiers: _* @mods + name: @name + inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), // Protocol declaration rule!( - (protocolDecl protocolKeyword: @kind modifiers: _* @mods name: @name memberBlock: (memberBlock members: _* @members)) + (protocolDecl + protocolKeyword: @kind + modifiers: _* @mods + name: @name + inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), // An `extension Foo { … }` is likewise a `class_like_declaration`, named @@ -1080,12 +1103,18 @@ fn translation_rules() -> Vec> { // a `memberType`) name the declaration just like simple ones, matching the // old tree-sitter `user_type` behaviour. rule!( - (extensionDecl extensionKeyword: @kind modifiers: _* @mods extendedType: @@name memberBlock: (memberBlock members: _* @members)) + (extensionDecl + extensionKeyword: @kind + modifiers: _* @mods + extendedType: @@name + inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? + memberBlock: (memberBlock members: _* @members)) => (class_like_declaration modifier: (modifier #{kind}) modifier: {mods} name: (identifier #{name}) + base_type: {bases.into_iter().map(|ty| tree!((base_type type: {ty})))} member: {members}) ), // A member of a type declaration unwraps to the contained declaration. diff --git a/unified/extractor/tests/corpus/swift/types/class-inheritance.output b/unified/extractor/tests/corpus/swift/types/class-inheritance.output index 62a0a43414d..12328f4acb0 100644 --- a/unified/extractor/tests/corpus/swift/types/class-inheritance.output +++ b/unified/extractor/tests/corpus/swift/types/class-inheritance.output @@ -35,3 +35,8 @@ top_level class_like_declaration modifier: modifier "class" name: identifier "Dog" + base_type: + base_type + type: + named_type_expr + name: identifier "Animal" diff --git a/unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.output b/unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.output new file mode 100644 index 00000000000..e66b88e9be3 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.output @@ -0,0 +1,51 @@ +class Button: Control, Drawable {} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + name: identifier "Button" + inheritanceClause: + inheritanceClause + colon: : + inheritedTypes: + inheritedType + trailingComma: , + type: + identifierType + name: identifier "Control" + inheritedType + type: + identifierType + name: identifier "Drawable" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + modifiers: + classKeyword: class + +--- + +top_level + body: + block + stmt: + class_like_declaration + modifier: modifier "class" + name: identifier "Button" + base_type: + base_type + type: + named_type_expr + name: identifier "Control" + base_type + type: + named_type_expr + name: identifier "Drawable" diff --git a/unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.swift b/unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.swift new file mode 100644 index 00000000000..b4f45e5c7a0 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/types/class-with-multiple-base-types.swift @@ -0,0 +1 @@ +class Button: Control, Drawable {}