From cd424f0462b2d396df69e62f95f745804ecd193e Mon Sep 17 00:00:00 2001 From: Sergey Erokhin Date: Tue, 12 Nov 2024 13:31:37 +0300 Subject: [PATCH] Fixed nested type resolution in various cases --- .../Composer/ParserResultsComposed.swift | 59 +++++++++----- .../Generated/JSExport.generated.swift | 1 - .../SourceryRuntime.content.generated.swift | 80 ++++++++++++++----- ...rceryRuntime_Linux.content.generated.swift | 80 ++++++++++++++----- SourceryTests/Parsing/ComposerSpec.swift | 30 ++++++- 5 files changed, 188 insertions(+), 62 deletions(-) diff --git a/SourceryRuntime/Sources/Common/Composer/ParserResultsComposed.swift b/SourceryRuntime/Sources/Common/Composer/ParserResultsComposed.swift index ef6cde026..a76465323 100644 --- a/SourceryRuntime/Sources/Common/Composer/ParserResultsComposed.swift +++ b/SourceryRuntime/Sources/Common/Composer/ParserResultsComposed.swift @@ -27,6 +27,14 @@ internal struct ParserResultsComposed { associatedTypes = Self.extractAssociatedTypes(parserResult) parsedTypes = parserResult.types + var moduleAndTypeNameCollisions: Set = [] + + for type in parsedTypes where !type.isExtension && type.parent == nil { + if let module = type.module, type.localName == module { + moduleAndTypeNameCollisions.insert(module) + } + } + // set definedInType for all methods and variables parsedTypes .forEach { type in @@ -36,16 +44,23 @@ internal struct ParserResultsComposed { } // map all known types to their names - parsedTypes - .filter { !$0.isExtension } - .forEach { - typeMap[$0.globalName] = $0 - if let module = $0.module { - var typesByModules = modules[module, default: [:]] - typesByModules[$0.name] = $0 - modules[module] = typesByModules - } + + for type in parsedTypes where !type.isExtension && type.parent == nil { + let name = type.name + // If a type name has the `.` prefix, and the type `.` is undefined, we can safely remove the `.` prefix + if let module = type.module, name.hasPrefix(module), name.dropFirst(module.count).hasPrefix("."), !moduleAndTypeNameCollisions.contains(module) { + type.localName.removeFirst(module.count + 1) } + } + + for type in parsedTypes where !type.isExtension { + typeMap[type.globalName] = type + if let module = type.module { + var typesByModules = modules[module, default: [:]] + typesByModules[type.name] = type + modules[module] = typesByModules + } + } /// Resolve typealiases let typealiases = Array(unresolvedTypealiases.values) @@ -66,9 +81,14 @@ internal struct ParserResultsComposed { types = unifyTypes() } - private func resolveExtensionOfNestedType(_ type: Type) { + mutating private func resolveExtensionOfNestedType(_ type: Type) { var components = type.localName.components(separatedBy: ".") - let rootName = type.module ?? components.removeFirst() // Module/parent name + let rootName: String + if type.parent != nil, let module = type.module { + rootName = module + } else { + rootName = components.removeFirst() + } if let moduleTypes = modules[rootName], let baseType = moduleTypes[components.joined(separator: ".")] ?? moduleTypes[type.localName] { type.localName = baseType.localName type.module = baseType.module @@ -85,6 +105,14 @@ internal struct ParserResultsComposed { } } } + // Parent extensions should always be processed before `type`, as this affects the globalName of `type`. + for parent in type.parentTypes where parent.isExtension && parent.localName.contains(".") { + let oldName = parent.globalName + resolveExtensionOfNestedType(parent) + if oldName != parent.globalName { + rewriteChildren(of: parent) + } + } } // if it had contained types, they might have been fully defined and so their name has to be noted in uniques @@ -103,19 +131,14 @@ internal struct ParserResultsComposed { .forEach { (type: Type) in let oldName = type.globalName - let hasDotInLocalName = type.localName.contains(".") as Bool - if let _ = type.parent, hasDotInLocalName { + if type.localName.contains(".") { resolveExtensionOfNestedType(type) - } - - if let resolved = resolveGlobalName(for: oldName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases, associatedTypes: associatedTypes)?.name { + } else if let resolved = resolveGlobalName(for: oldName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases, associatedTypes: associatedTypes)?.name { var moduleName: String = "" if let module = type.module { moduleName = "\(module)." } type.localName = resolved.replacingOccurrences(of: moduleName, with: "") - } else { - return } // nothing left to do diff --git a/SourceryRuntime/Sources/Generated/JSExport.generated.swift b/SourceryRuntime/Sources/Generated/JSExport.generated.swift index dbdba7776..a9209f03a 100644 --- a/SourceryRuntime/Sources/Generated/JSExport.generated.swift +++ b/SourceryRuntime/Sources/Generated/JSExport.generated.swift @@ -673,7 +673,6 @@ extension Type: TypeAutoJSExport {} var set: SetType? { get } var asSource: String { get } var description: String { get } - var hash: Int { get } var debugDescription: String { get } } diff --git a/SourcerySwift/Sources/SourceryRuntime.content.generated.swift b/SourcerySwift/Sources/SourceryRuntime.content.generated.swift index 28f02a73a..529254eae 100644 --- a/SourcerySwift/Sources/SourceryRuntime.content.generated.swift +++ b/SourcerySwift/Sources/SourceryRuntime.content.generated.swift @@ -2246,6 +2246,14 @@ internal struct ParserResultsComposed { associatedTypes = Self.extractAssociatedTypes(parserResult) parsedTypes = parserResult.types + var moduleAndTypeNameCollisions: Set = [] + + for type in parsedTypes where !type.isExtension && type.parent == nil { + if let module = type.module, type.localName == module { + moduleAndTypeNameCollisions.insert(module) + } + } + // set definedInType for all methods and variables parsedTypes .forEach { type in @@ -2255,16 +2263,23 @@ internal struct ParserResultsComposed { } // map all known types to their names - parsedTypes - .filter { !$0.isExtension } - .forEach { - typeMap[$0.globalName] = $0 - if let module = $0.module { - var typesByModules = modules[module, default: [:]] - typesByModules[$0.name] = $0 - modules[module] = typesByModules - } + + for type in parsedTypes where !type.isExtension && type.parent == nil { + let name = type.name + // If a type name has the `.` prefix, and the type `.` is undefined, we can safely remove the `.` prefix + if let module = type.module, name.hasPrefix(module), name.dropFirst(module.count).hasPrefix("."), !moduleAndTypeNameCollisions.contains(module) { + type.localName.removeFirst(module.count + 1) + } + } + + for type in parsedTypes where !type.isExtension { + typeMap[type.globalName] = type + if let module = type.module { + var typesByModules = modules[module, default: [:]] + typesByModules[type.name] = type + modules[module] = typesByModules } + } /// Resolve typealiases let typealiases = Array(unresolvedTypealiases.values) @@ -2274,15 +2289,25 @@ internal struct ParserResultsComposed { /// Map associated types associatedTypes.forEach { - typeMap[$0.key] = $0.value.type + if let globalName = $0.value.type?.globalName, + let type = typeMap[globalName] { + typeMap[$0.key] = type + } else { + typeMap[$0.key] = $0.value.type + } } types = unifyTypes() } - private func resolveExtensionOfNestedType(_ type: Type) { + mutating private func resolveExtensionOfNestedType(_ type: Type) { var components = type.localName.components(separatedBy: ".") - let rootName = type.module ?? components.removeFirst() // Module/parent name + let rootName: String + if type.parent != nil, let module = type.module { + rootName = module + } else { + rootName = components.removeFirst() + } if let moduleTypes = modules[rootName], let baseType = moduleTypes[components.joined(separator: ".")] ?? moduleTypes[type.localName] { type.localName = baseType.localName type.module = baseType.module @@ -2299,6 +2324,14 @@ internal struct ParserResultsComposed { } } } + // Parent extensions should always be processed before `type`, as this affects the globalName of `type`. + for parent in type.parentTypes where parent.isExtension && parent.localName.contains(".") { + let oldName = parent.globalName + resolveExtensionOfNestedType(parent) + if oldName != parent.globalName { + rewriteChildren(of: parent) + } + } } // if it had contained types, they might have been fully defined and so their name has to be noted in uniques @@ -2317,19 +2350,14 @@ internal struct ParserResultsComposed { .forEach { (type: Type) in let oldName = type.globalName - let hasDotInLocalName = type.localName.contains(".") as Bool - if let _ = type.parent, hasDotInLocalName { + if type.localName.contains(".") { resolveExtensionOfNestedType(type) - } - - if let resolved = resolveGlobalName(for: oldName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases, associatedTypes: associatedTypes)?.name { + } else if let resolved = resolveGlobalName(for: oldName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases, associatedTypes: associatedTypes)?.name { var moduleName: String = "" if let module = type.module { moduleName = "\\(module)." } type.localName = resolved.replacingOccurrences(of: moduleName, with: "") - } else { - return } // nothing left to do @@ -3509,7 +3537,8 @@ public final class TemplateContext: NSObject, SourceryModel, NSCoding, Diffable "based": types.based, "inheriting": types.inheriting, "implementing": types.implementing, - "protocolCompositions": types.protocolCompositions + "protocolCompositions": types.protocolCompositions, + "typealiases": types.typealiases ] as [String : Any], "functions": functions, "type": types.typesByName, @@ -3547,6 +3576,9 @@ public final class Typealias: NSObject, Typed, SourceryModel, Diffable { /// module in which this typealias was declared public var module: String? + + /// Imports that existed in the file that contained this typealias declaration + public var imports: [Import] = [] /// typealias annotations public var annotations: Annotations = [:] @@ -3661,6 +3693,12 @@ public final class Typealias: NSObject, Typed, SourceryModel, Diffable { }; self.typeName = typeName self.type = aDecoder.decode(forKey: "type") self.module = aDecoder.decode(forKey: "module") + guard let imports: [Import] = aDecoder.decode(forKey: "imports") else { + withVaList(["imports"]) { arguments in + NSException.raise(NSExceptionName.parseErrorException, format: "Key '%@' not found.", arguments: arguments) + } + fatalError() + }; self.imports = imports guard let annotations: Annotations = aDecoder.decode(forKey: "annotations") else { withVaList(["annotations"]) { arguments in NSException.raise(NSExceptionName.parseErrorException, format: "Key '%@' not found.", arguments: arguments) @@ -3689,6 +3727,7 @@ public final class Typealias: NSObject, Typed, SourceryModel, Diffable { aCoder.encode(self.typeName, forKey: "typeName") aCoder.encode(self.type, forKey: "type") aCoder.encode(self.module, forKey: "module") + aCoder.encode(self.imports, forKey: "imports") aCoder.encode(self.annotations, forKey: "annotations") aCoder.encode(self.documentation, forKey: "documentation") aCoder.encode(self.parent, forKey: "parent") @@ -8506,6 +8545,7 @@ extension TypeName: TypeNameAutoJSExport {} var typeName: TypeName { get } var type: Type? { get } var module: String? { get } + var imports: [Import] { get } var annotations: Annotations { get } var documentation: Documentation { get } var parent: Type? { get } diff --git a/SourcerySwift/Sources/SourceryRuntime_Linux.content.generated.swift b/SourcerySwift/Sources/SourceryRuntime_Linux.content.generated.swift index 90e789b06..b6e78070f 100644 --- a/SourcerySwift/Sources/SourceryRuntime_Linux.content.generated.swift +++ b/SourcerySwift/Sources/SourceryRuntime_Linux.content.generated.swift @@ -2246,6 +2246,14 @@ internal struct ParserResultsComposed { associatedTypes = Self.extractAssociatedTypes(parserResult) parsedTypes = parserResult.types + var moduleAndTypeNameCollisions: Set = [] + + for type in parsedTypes where !type.isExtension && type.parent == nil { + if let module = type.module, type.localName == module { + moduleAndTypeNameCollisions.insert(module) + } + } + // set definedInType for all methods and variables parsedTypes .forEach { type in @@ -2255,16 +2263,23 @@ internal struct ParserResultsComposed { } // map all known types to their names - parsedTypes - .filter { !$0.isExtension } - .forEach { - typeMap[$0.globalName] = $0 - if let module = $0.module { - var typesByModules = modules[module, default: [:]] - typesByModules[$0.name] = $0 - modules[module] = typesByModules - } + + for type in parsedTypes where !type.isExtension && type.parent == nil { + let name = type.name + // If a type name has the `.` prefix, and the type `.` is undefined, we can safely remove the `.` prefix + if let module = type.module, name.hasPrefix(module), name.dropFirst(module.count).hasPrefix("."), !moduleAndTypeNameCollisions.contains(module) { + type.localName.removeFirst(module.count + 1) + } + } + + for type in parsedTypes where !type.isExtension { + typeMap[type.globalName] = type + if let module = type.module { + var typesByModules = modules[module, default: [:]] + typesByModules[type.name] = type + modules[module] = typesByModules } + } /// Resolve typealiases let typealiases = Array(unresolvedTypealiases.values) @@ -2274,15 +2289,25 @@ internal struct ParserResultsComposed { /// Map associated types associatedTypes.forEach { - typeMap[$0.key] = $0.value.type + if let globalName = $0.value.type?.globalName, + let type = typeMap[globalName] { + typeMap[$0.key] = type + } else { + typeMap[$0.key] = $0.value.type + } } types = unifyTypes() } - private func resolveExtensionOfNestedType(_ type: Type) { + mutating private func resolveExtensionOfNestedType(_ type: Type) { var components = type.localName.components(separatedBy: ".") - let rootName = type.module ?? components.removeFirst() // Module/parent name + let rootName: String + if type.parent != nil, let module = type.module { + rootName = module + } else { + rootName = components.removeFirst() + } if let moduleTypes = modules[rootName], let baseType = moduleTypes[components.joined(separator: ".")] ?? moduleTypes[type.localName] { type.localName = baseType.localName type.module = baseType.module @@ -2299,6 +2324,14 @@ internal struct ParserResultsComposed { } } } + // Parent extensions should always be processed before `type`, as this affects the globalName of `type`. + for parent in type.parentTypes where parent.isExtension && parent.localName.contains(".") { + let oldName = parent.globalName + resolveExtensionOfNestedType(parent) + if oldName != parent.globalName { + rewriteChildren(of: parent) + } + } } // if it had contained types, they might have been fully defined and so their name has to be noted in uniques @@ -2317,19 +2350,14 @@ internal struct ParserResultsComposed { .forEach { (type: Type) in let oldName = type.globalName - let hasDotInLocalName = type.localName.contains(".") as Bool - if let _ = type.parent, hasDotInLocalName { + if type.localName.contains(".") { resolveExtensionOfNestedType(type) - } - - if let resolved = resolveGlobalName(for: oldName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases, associatedTypes: associatedTypes)?.name { + } else if let resolved = resolveGlobalName(for: oldName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases, associatedTypes: associatedTypes)?.name { var moduleName: String = "" if let module = type.module { moduleName = "\\(module)." } type.localName = resolved.replacingOccurrences(of: moduleName, with: "") - } else { - return } // nothing left to do @@ -3509,7 +3537,8 @@ public final class TemplateContext: NSObject, SourceryModel, NSCoding, Diffable "based": types.based, "inheriting": types.inheriting, "implementing": types.implementing, - "protocolCompositions": types.protocolCompositions + "protocolCompositions": types.protocolCompositions, + "typealiases": types.typealiases ] as [String : Any], "functions": functions, "type": types.typesByName, @@ -3547,6 +3576,9 @@ public final class Typealias: NSObject, Typed, SourceryModel, Diffable { /// module in which this typealias was declared public var module: String? + + /// Imports that existed in the file that contained this typealias declaration + public var imports: [Import] = [] /// typealias annotations public var annotations: Annotations = [:] @@ -3661,6 +3693,12 @@ public final class Typealias: NSObject, Typed, SourceryModel, Diffable { }; self.typeName = typeName self.type = aDecoder.decode(forKey: "type") self.module = aDecoder.decode(forKey: "module") + guard let imports: [Import] = aDecoder.decode(forKey: "imports") else { + withVaList(["imports"]) { arguments in + NSException.raise(NSExceptionName.parseErrorException, format: "Key '%@' not found.", arguments: arguments) + } + fatalError() + }; self.imports = imports guard let annotations: Annotations = aDecoder.decode(forKey: "annotations") else { withVaList(["annotations"]) { arguments in NSException.raise(NSExceptionName.parseErrorException, format: "Key '%@' not found.", arguments: arguments) @@ -3689,6 +3727,7 @@ public final class Typealias: NSObject, Typed, SourceryModel, Diffable { aCoder.encode(self.typeName, forKey: "typeName") aCoder.encode(self.type, forKey: "type") aCoder.encode(self.module, forKey: "module") + aCoder.encode(self.imports, forKey: "imports") aCoder.encode(self.annotations, forKey: "annotations") aCoder.encode(self.documentation, forKey: "documentation") aCoder.encode(self.parent, forKey: "parent") @@ -9089,6 +9128,7 @@ extension TypeName: TypeNameAutoJSExport {} var typeName: TypeName { get } var type: Type? { get } var module: String? { get } + var imports: [Import] { get } var annotations: Annotations { get } var documentation: Documentation { get } var parent: Type? { get } diff --git a/SourceryTests/Parsing/ComposerSpec.swift b/SourceryTests/Parsing/ComposerSpec.swift index 2bae5b5bb..0c60a4a38 100644 --- a/SourceryTests/Parsing/ComposerSpec.swift +++ b/SourceryTests/Parsing/ComposerSpec.swift @@ -2398,10 +2398,34 @@ class ParserComposerSpec: QuickSpec { it("resolve extensions with nested types properly") { let types = parseModules( ("Mod1", "enum NS {}"), - ("Mod2", "import Mod1; extension NS { struct A {} }"), - ("Mod3", "import Mod1; extension NS { struct B {} }") + ("Mod2", "import Mod1; extension NS { struct A { struct D {} } }"), + ("Mod3", "import Mod1; extension Mod1.NS { struct B { struct C {} } }") ).types - expect(types.map { $0.globalName }).to(equal(["Mod1.NS", "Mod2.NS.A", "Mod3.NS.B"])) + expect(types.map(\.globalName).sorted()).to(equal(["Mod1.NS", "Mod2.NS.A", "Mod2.NS.A.D", "Mod3.NS.B", "Mod3.NS.B.C"])) + } + + it("resolve extensions with global names declared before nested type") { + let types = parseModules( + ("Mod1", "enum A {}; extension Mod1.A.B.C { enum D {} }; extension Mod1.A { enum B { enum C {} } }") + ).types + expect(types.map(\.globalName).sorted()).to(equal(["Mod1.A", "Mod1.A.B", "Mod1.A.B.C", "Mod1.A.B.C.D"])) + } + + it("resolve extensions declared before nested type") { + let types = parseModules( + ("Mod1", "enum A {}; extension A.B.C { enum D {} }; extension A { enum B { enum C {} } }") + ).types + expect(types.map(\.globalName).sorted()).to(equal(["Mod1.A", "Mod1.A.B", "Mod1.A.B.C", "Mod1.A.B.C.D"])) + } + + it("resolve extensions with nested types properly") { + let types = parseModules(("Mod1", "enum NS {}; extension Mod1.NS.A.B { struct D {} } extension Mod1.NS { struct A { struct B {} } }")).types + expect(types.map(\.globalName).sorted()).to(equal(["Mod1.NS", "Mod1.NS.A", "Mod1.NS.A.B", "Mod1.NS.A.B.D"])) + } + + it("resolves extension of type with global parent name in same module") { + let types = parseModules(("Mod1", "enum NS {}; extension Mod1.NS { struct A {} }; extension Mod1.NS.A { struct B {} }; extension Mod1.NS.A.B { struct C {} }")).types + expect(types.map(\.globalName).sorted()).to(equal(["Mod1.NS", "Mod1.NS.A", "Mod1.NS.A.B", "Mod1.NS.A.B.C"])) } it("resolves extensions of nested types properly") {