Skip to content
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

Fixed parsing of extensions and nested types in swiftinterface files #1113

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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- Adds support for public protocols in AutoMockable template [#1100](https://github.com/krzysztofzablocki/Sourcery/pull/1100)
- Adds support for async and throwing properties to AutoMockable template [#1101](https://github.com/krzysztofzablocki/Sourcery/pull/1101)

## Internal Changes
- Fixed parsing of extensions and nested types in swiftinterface files [#1113](https://github.com/krzysztofzablocki/Sourcery/pull/1113)

## 1.9.0
- Update StencilSwiftKit to fix SPM resolving issue when building as a Command Plugin [#1023](https://github.com/krzysztofzablocki/Sourcery/issues/1023)
- Adds new `--cacheBasePath` option to `SourceryExecutable` to allow for plugins setting a default cache [#1093](https://github.com/krzysztofzablocki/Sourcery/pull/1093)
Expand Down
10 changes: 5 additions & 5 deletions Sourcery/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ public struct Project {
let simulatorSlicePath = frameworkRelativePath.glob("*")
.first(where: { $0.lastComponent.contains("simulator") })
else {
throw Configuration.Error.invalidXCFramework(message: "Framework path invalid. Expected to find simulator slice.")
throw Configuration.Error.invalidXCFramework(path: frameworkRelativePath, message: "Framework path invalid. Expected to find simulator slice.")
}
let modulePath = simulatorSlicePath + Path("\(moduleName).framework/Modules/\(moduleName).swiftmodule/")
guard let interfacePath = modulePath.glob("*.swiftinterface").first(where: { $0.lastComponent.contains("simulator") })
else {
throw Configuration.Error.invalidXCFramework(message: "Framework path invalid. Expected to find .swiftinterface.")
throw Configuration.Error.invalidXCFramework(path: frameworkRelativePath, message: "Framework path invalid. Expected to find .swiftinterface.")
}
self.path = frameworkRelativePath
self.swiftInterfacePath = interfacePath
Expand Down Expand Up @@ -249,7 +249,7 @@ public struct Configuration {
public enum Error: Swift.Error, CustomStringConvertible {
case invalidFormat(message: String)
case invalidSources(message: String)
case invalidXCFramework(message: String)
case invalidXCFramework(path: Path? = nil, message: String)
case invalidTemplates(message: String)
case invalidOutput(message: String)
case invalidCacheBasePath(message: String)
Expand All @@ -261,8 +261,8 @@ public struct Configuration {
return "Invalid config file format. \(message)"
case .invalidSources(let message):
return "Invalid sources. \(message)"
case .invalidXCFramework(let message):
return "Invalid xcframework. \(message)"
case .invalidXCFramework(let path, let message):
return "Invalid xcframework\(path.map { " at path '\($0)'" } ?? "")'. \(message)"
case .invalidTemplates(let message):
return "Invalid templates. \(message)"
case .invalidOutput(let message):
Expand Down
39 changes: 31 additions & 8 deletions SourceryRuntime/Sources/Composer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public enum Composer {

// map all known types to their names
parsedTypes
.filter { $0.isExtension == false }
.forEach {
guard !$0.isExtension else { return }

typeMap[$0.globalName] = $0
if let module = $0.module {
var typesByModules = modules[module, default: [:]]
Expand All @@ -50,17 +51,41 @@ public enum Composer {
}
}

private func resolveExtensionOfNestedType(_ type: Type) {
var components = type.localName.components(separatedBy: ".")
let rootName = components.removeFirst() // Module/parent name
if let moduleTypes = modules[rootName], let baseType = moduleTypes[components.joined(separator: ".")] ?? moduleTypes[type.localName] {
type.localName = baseType.localName
type.module = baseType.module
type.parent = baseType.parent
} else {
for _import in type.imports {
let parentKey = "\(rootName).\(components.joined(separator: "."))"
let parentKeyFull = "\(_import.moduleName).\(parentKey)"
if let moduleTypes = modules[_import.moduleName], let baseType = moduleTypes[parentKey] ?? moduleTypes[parentKeyFull] {
type.localName = baseType.localName
type.module = baseType.module
type.parent = baseType.parent
return
}
}
}
}

func unifyTypes() -> [Type] {
/// Resolve actual names of extensions, as they could have been done on typealias and note updated child names in uniques if needed
parsedTypes
.filter { $0.isExtension == true }
.forEach {
guard $0.isExtension else { return }

let oldName = $0.globalName

if $0.parent == nil, $0.localName.contains(".") {
resolveExtensionOfNestedType($0)
}

if let resolved = resolveGlobalName(for: oldName, containingType: $0.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases)?.name {
$0.localName = resolved.replacingOccurrences(of: "\($0.module != nil ? "\($0.module!)." : "")", with: "")
} else {
return
$0.localName = resolved.components(separatedBy: ".").last!
}

// nothing left to do
Expand Down Expand Up @@ -90,7 +115,7 @@ public enum Composer {
(inferTypeNameFromModules(from: type.localName, containedInType: type.parent, uniqueTypes: typeMap, modules: modules).flatMap { typeMap[$0] })

guard let current = uniqueType else {
assert(type.isExtension)
assert(type.isExtension, "Type \(type.globalName) should be extension")

// for unknown types we still store their extensions but mark them as unknown
type.isUnknownExtension = true
Expand Down Expand Up @@ -337,8 +362,6 @@ public enum Composer {
}

let unique = state.typeMap
let modules = state.modules
let typealiases = state.resolvedTypealiases

if let name = typeName.actualTypeName {
let resolvedIdentifier = name.generic?.name ?? name.unwrappedTypeName
Expand Down
21 changes: 21 additions & 0 deletions SourceryTests/Parsing/ComposerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2089,6 +2089,27 @@ class ParserComposerSpec: QuickSpec {
}
}

context("given nested types") {
it("resolve extensions of nested type properly") {
let types = parseModules(
("Mod1", "enum NS {}; extension Mod1.NS { struct Foo { func f1() } }"),
("Mod2", "import Mod1; extension Mod1.NS.Foo { func f2() }"),
("Mod3", "import Mod1; extension NS.Foo { func f3() }")
).types
expect(types.map { $0.globalName }).to(equal(["Mod1.NS", "Mod1.NS.Foo"]))
expect(types[1].methods.map { $0.name }).to(equal(["f1()", "f2()", "f3()"]))
}

it("resolve extensions with nested types properly") {
let types = parseModules(
("Mod1", "enum NS {}"),
("Mod2", "import Mod1; extension Mod1.NS { struct A {} }"),
("Mod3", "import Mod1; extension NS { struct B {} }")
).types
expect(types.map { $0.globalName }).to(equal(["Mod1.NS", "Mod2.NS.A", "Mod3.NS.B"]))
}
}

context("given protocols of the same name in different modules") {
func parseModules(_ modules: (name: String?, contents: String)...) -> [Type] {
let moduleResults = modules.compactMap {
Expand Down