-
-
Notifications
You must be signed in to change notification settings - Fork 75
BridgeJS: Allow extensions to contain types #803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,8 +16,7 @@ class TypeDeclResolver { | |
|
|
||
| private class TypeDeclCollector: SyntaxVisitor { | ||
| let resolver: TypeDeclResolver | ||
| var scope: [TypeDecl] = [] | ||
| var rootTypeDecls: [TypeDecl] = [] | ||
| var scope: [String] = [] | ||
|
|
||
| init(resolver: TypeDeclResolver) { | ||
| self.resolver = resolver | ||
|
|
@@ -26,17 +25,14 @@ class TypeDeclResolver { | |
|
|
||
| func visitNominalDecl(_ node: TypeDecl) -> SyntaxVisitorContinueKind { | ||
| let name = node.name.text | ||
| let qualifiedName = scope.map(\.name.text) + [name] | ||
| let qualifiedName = scope + [name] | ||
| resolver.typeDeclByQualifiedName[qualifiedName] = node | ||
| scope.append(node) | ||
| scope.append(name) | ||
| return .visitChildren | ||
| } | ||
|
|
||
| func visitPostNominalDecl() { | ||
| let type = scope.removeLast() | ||
| if scope.isEmpty { | ||
| rootTypeDecls.append(type) | ||
| } | ||
| scope.removeLast() | ||
| } | ||
|
|
||
| override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { | ||
|
|
@@ -72,10 +68,21 @@ class TypeDeclResolver { | |
|
|
||
| override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { | ||
| let name = node.name.text | ||
| let qualifiedName = scope.map(\.name.text) + [name] | ||
| let qualifiedName = scope + [name] | ||
| resolver.typeAliasByQualifiedName[qualifiedName] = node | ||
| return .skipChildren | ||
| } | ||
|
|
||
| override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { | ||
| guard let components = node.memberScopeComponents else { | ||
| return .skipChildren | ||
| } | ||
| scope.append(contentsOf: components) | ||
| return .visitChildren | ||
| } | ||
| override func visitPost(_ node: ExtensionDeclSyntax) { | ||
| scope.removeLast(node.memberScopeComponents?.count ?? 0) | ||
| } | ||
| } | ||
|
|
||
| /// Collects type declarations from a parsed Swift source file | ||
|
|
@@ -91,6 +98,10 @@ class TypeDeclResolver { | |
| while let parent = context.parent { | ||
| if let parent = parent.asProtocol(NamedDeclSyntax.self), parent.isProtocol(DeclGroupSyntax.self) { | ||
| innerToOuter.append(parent.name.text) | ||
| } else if let extensionDecl = parent.as(ExtensionDeclSyntax.self), | ||
| let components = extensionDecl.memberScopeComponents | ||
| { | ||
| innerToOuter.append(contentsOf: components.reversed()) | ||
| } | ||
| context = parent | ||
| } | ||
|
|
@@ -106,7 +117,7 @@ class TypeDeclResolver { | |
| /// Search for the type declaration from the innermost scope to the outermost scope | ||
| for i in (0...scope.count).reversed() { | ||
| let qualifiedName = Array(scope[0..<i] + [name]) | ||
| if typeDeclByQualifiedName[qualifiedName] != nil { | ||
| if typeDeclByQualifiedName[qualifiedName] != nil || typeAliasByQualifiedName[qualifiedName] != nil { | ||
| return qualifiedName | ||
| } | ||
| } | ||
|
|
@@ -132,15 +143,15 @@ class TypeDeclResolver { | |
| /// | ||
| /// Resolution strategy: | ||
| /// 1. If the node is IdentifierTypeSyntax, call `lookupType(for:)` which attempts scope-aware qualification via `tryQualify`. | ||
| /// 2. Otherwise, attempt to build a fully qualified name with `qualifiedComponents(from:)` and look it up with `lookupType(fullyQualified:)`. | ||
| /// 2. Otherwise, attempt to build a fully qualified name with `qualifiedComponents` and look it up with `lookupType(fullyQualified:)`. | ||
| /// | ||
| /// - Parameter type: The SwiftSyntax node representing a type appearance in source code. | ||
| /// - Returns: The nominal declaration (enum/class/actor/struct) if found, otherwise nil. | ||
| func resolve(_ type: TypeSyntax) -> TypeDecl? { | ||
| if let id = type.as(IdentifierTypeSyntax.self) { | ||
| return lookupType(for: id) | ||
| } | ||
| if let components = qualifiedComponents(from: type) { | ||
| if let components = type.qualifiedComponents { | ||
| return lookupType(fullyQualified: components) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, extension scoping now handles unqualified names, but relative qualified names such as This behavior already exists before this PR, so I opened #805 to follow it separately rather than treating it as part of this change. |
||
| } | ||
| return nil | ||
|
|
@@ -155,20 +166,29 @@ class TypeDeclResolver { | |
| let qualifiedName = tryQualify(type: id) | ||
| return typeAliasByQualifiedName[qualifiedName] | ||
| } | ||
| if let components = qualifiedComponents(from: type) { | ||
| if let components = type.qualifiedComponents { | ||
| return typeAliasByQualifiedName[components] | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func qualifiedComponents(from type: TypeSyntax) -> QualifiedName? { | ||
| if let m = type.as(MemberTypeSyntax.self) { | ||
| guard let base = qualifiedComponents(from: TypeSyntax(m.baseType)) else { return nil } | ||
| } | ||
|
|
||
| extension TypeSyntax { | ||
| var qualifiedComponents: TypeDeclResolver.QualifiedName? { | ||
| if let m = self.as(MemberTypeSyntax.self) { | ||
| guard let base = TypeSyntax(m.baseType).qualifiedComponents else { return nil } | ||
| return base + [m.name.text] | ||
| } else if let id = type.as(IdentifierTypeSyntax.self) { | ||
| } else if let id = self.as(IdentifierTypeSyntax.self) { | ||
| return [id.name.text] | ||
| } else { | ||
| return nil | ||
| } | ||
| } | ||
| } | ||
|
|
||
| extension ExtensionDeclSyntax { | ||
| var memberScopeComponents: TypeDeclResolver.QualifiedName? { | ||
| extendedType.qualifiedComponents | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deferred extension resolution works well for cross-file declarations, but a single pass leaves it dependent on source order.
I put
extension Library.Shelfbefore theextension Librarythat declaresShelf. The generated struct was present, but the earlier extension's method was missing. Reversing the declarations restored it.Could we retry unresolved extensions until a pass makes no progress? A reversed-order cross-file test would cover the same case across input files.