Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Plugins/BridgeJS/Sources/BridgeJSCore/SwiftToSkeleton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ public final class SwiftToSkeleton {

while let parent = currentNode {
if let extensionDecl = parent.as(ExtensionDeclSyntax.self) {
if let extendedDecl = typeDeclResolver.resolve(extensionDecl.extendedType),
if let extendedDecl = typeDeclResolver.resolveExtensionTarget(extensionDecl.extendedType),
visitedExtendedTypes.insert(extendedDecl.id).inserted
{
declarations.append(Syntax(extendedDecl))
Expand Down Expand Up @@ -1996,7 +1996,7 @@ private final class ExportSwiftAPICollector: SyntaxAnyVisitor {

/// Walks extension members under the matching type’s state, returning whether the type was found.
func resolveExtension(_ ext: ExtensionDeclSyntax) -> Bool {
guard let extendedDecl = parent.typeDeclResolver.resolve(ext.extendedType) else {
guard let extendedDecl = parent.typeDeclResolver.resolveExtensionTarget(ext.extendedType) else {
return false
}
let swiftCallName = parent.computeSwiftCallName(for: extendedDecl, itemName: extendedDecl.name.text)
Expand Down
45 changes: 24 additions & 21 deletions Plugins/BridgeJS/Sources/BridgeJSCore/TypeDeclResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class TypeDeclResolver {
}

/// Builds the type name scope for a given type usage
private func buildScope(type: IdentifierTypeSyntax) -> QualifiedName {
private func buildScope(type: TypeSyntax) -> QualifiedName {
var innerToOuter: [String] = []
var context: SyntaxProtocol = type
while let parent = context.parent {
Expand All @@ -108,25 +108,29 @@ class TypeDeclResolver {
return innerToOuter.reversed()
}

/// Looks up a qualified name of a type declaration by its unqualified type usage
/// Looks up a qualified name of a type declaration relative to its lexical scope
/// Returns the qualified name hierarchy of the type declaration
/// If the type declaration is not found, returns the unqualified name
private func tryQualify(type: IdentifierTypeSyntax) -> QualifiedName {
let name = type.name.text
/// If the type declaration is not found, returns the original qualified name
private func tryQualify(type: TypeSyntax) -> QualifiedName? {
guard let components = type.qualifiedComponents else {
return nil
}
let scope = buildScope(type: type)
/// 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])
let qualifiedName = Array(scope.prefix(i)) + components
if typeDeclByQualifiedName[qualifiedName] != nil || typeAliasByQualifiedName[qualifiedName] != nil {
return qualifiedName
}
}
return [name]
return components
}

/// Looks up a type declaration by its unqualified type usage
func lookupType(for type: IdentifierTypeSyntax) -> TypeDecl? {
let qualifiedName = tryQualify(type: type)
guard let qualifiedName = tryQualify(type: TypeSyntax(type)) else {
return nil
}
return typeDeclByQualifiedName[qualifiedName]
}

Expand All @@ -139,36 +143,35 @@ class TypeDeclResolver {
///
/// Supported inputs:
/// - IdentifierTypeSyntax (e.g. `Method`) — resolved relative to the lexical scope, preferring the innermost enclosing type.
/// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved by recursively building the fully qualified name.
/// - MemberTypeSyntax (e.g. `Networking.API.Method`) — resolved relative to the lexical scope before falling back to the fully qualified name.
///
/// 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` and look it up with `lookupType(fullyQualified:)`.
/// Build the qualified name with `qualifiedComponents`, then attempt scope-aware qualification via `tryQualify`.
///
/// - 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 = type.qualifiedComponents {
return lookupType(fullyQualified: components)
if let qualifiedName = tryQualify(type: type) {
return lookupType(fullyQualified: qualifiedName)
}
return nil
}

func resolveExtensionTarget(_ type: TypeSyntax) -> TypeDecl? {
guard let qualifiedName = type.qualifiedComponents else {
return nil
}
return lookupType(fullyQualified: qualifiedName)
}

/// Resolves a type usage node to a type alias declaration
///
/// - Parameter type: The SwiftSyntax node representing a type appearance in source code.
/// - Returns: The type alias declaration if found, otherwise nil.
func resolveTypeAlias(_ type: TypeSyntax) -> TypeAliasDeclSyntax? {
if let id = type.as(IdentifierTypeSyntax.self) {
let qualifiedName = tryQualify(type: id)
if let qualifiedName = tryQualify(type: type) {
return typeAliasByQualifiedName[qualifiedName]
}
if let components = type.qualifiedComponents {
return typeAliasByQualifiedName[components]
}
return nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@JS class Library {
@JS struct Shelf {
@JS struct Divider {
var slot: Int

@JS init(slot: Int) {
self.slot = slot
}
}
}

@JS init() {}
}

extension Library {
@JS func divider(_ value: Shelf.Divider) -> Shelf.Divider {
value
}
}

@JS class Outer {
@JS struct Inner {
@JS struct Outer {
@JS struct Inner {
@JS init() {}
}
}

@JS init() {}
}

@JS init() {}
}

extension Outer.Inner {
@JS func marker() -> Int { 1 }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
{
"exported" : {
"aliases" : [

],
"classes" : [
{
"constructor" : {
"abiName" : "bjs_Library_init",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"parameters" : [

]
},
"methods" : [
{
"abiName" : "bjs_Library_divider",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "divider",
"parameters" : [
{
"label" : "_",
"name" : "value",
"type" : {
"swiftStruct" : {
"_0" : "Library.Shelf.Divider"
}
}
}
],
"returnType" : {
"swiftStruct" : {
"_0" : "Library.Shelf.Divider"
}
}
}
],
"name" : "Library",
"properties" : [

],
"swiftCallName" : "Library"
},
{
"constructor" : {
"abiName" : "bjs_Outer_init",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"parameters" : [

]
},
"methods" : [

],
"name" : "Outer",
"properties" : [

],
"swiftCallName" : "Outer"
}
],
"enums" : [

],
"exposeToGlobal" : false,
"functions" : [

],
"protocols" : [

],
"structs" : [
{
"methods" : [

],
"name" : "Shelf",
"namespace" : [
"Library"
],
"properties" : [

],
"swiftCallName" : "Library.Shelf"
},
{
"constructor" : {
"abiName" : "bjs_Library_Shelf_Divider_init",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"parameters" : [
{
"label" : "slot",
"name" : "slot",
"type" : {
"integer" : {
"_0" : {
"isSigned" : true,
"width" : "word"
}
}
}
}
]
},
"methods" : [

],
"name" : "Divider",
"namespace" : [
"Library",
"Shelf"
],
"properties" : [
{
"isReadonly" : true,
"isStatic" : false,
"name" : "slot",
"namespace" : [
"Library",
"Shelf"
],
"type" : {
"integer" : {
"_0" : {
"isSigned" : true,
"width" : "word"
}
}
}
}
],
"swiftCallName" : "Library.Shelf.Divider"
},
{
"constructor" : {
"abiName" : "bjs_Outer_Inner_init",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"parameters" : [

]
},
"methods" : [
{
"abiName" : "bjs_Outer_Inner_marker",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"name" : "marker",
"parameters" : [

],
"returnType" : {
"integer" : {
"_0" : {
"isSigned" : true,
"width" : "word"
}
}
}
}
],
"name" : "Inner",
"namespace" : [
"Outer"
],
"properties" : [

],
"swiftCallName" : "Outer.Inner"
},
{
"methods" : [

],
"name" : "Outer",
"namespace" : [
"Outer",
"Inner"
],
"properties" : [

],
"swiftCallName" : "Outer.Inner.Outer"
},
{
"constructor" : {
"abiName" : "bjs_Outer_Inner_Outer_Inner_init",
"effects" : {
"isAsync" : false,
"isStatic" : false,
"isThrows" : false
},
"parameters" : [

]
},
"methods" : [

],
"name" : "Inner",
"namespace" : [
"Outer",
"Inner",
"Outer"
],
"properties" : [

],
"swiftCallName" : "Outer.Inner.Outer.Inner"
}
]
},
"moduleName" : "TestModule",
"usedExternalModules" : [

]
}
Loading
Loading