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

Implemented Proper Protocol Composition Type Parsing #1314

Merged
merged 2 commits into from
Mar 28, 2024
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
64 changes: 43 additions & 21 deletions SourceryRuntime/Sources/Common/Composer/Composer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public enum Composer {
}
}

private static func findBaseType(for type: Type, name: String, typesByName: [String: Type]) -> Type? {
internal static func findBaseType(for type: Type, name: String, typesByName: [String: Type]) -> Type? {
// special case to check if the type is based on one of the recognized types
// and the superclass has a generic constraint in `name` part of the `TypeName`
var name = name
Expand All @@ -229,7 +229,16 @@ public enum Composer {
return baseType
}
}
return nil
guard name.contains("&") else { return nil }
// this can happen for a type which consists of mutliple types composed together (i.e. (A & B))
let nameComponents = name.components(separatedBy: "&").map { $0.trimmingCharacters(in: .whitespaces) }
let types: [Type] = nameComponents.compactMap {
typesByName[$0]
}
let typeNames = types.map {
TypeName(name: $0.name)
}
return ProtocolComposition(name: name, inheritedTypes: types.map { $0.globalName }, composedTypeNames: typeNames, composedTypes: types)
}

private static func updateTypeRelationship(for type: Type, typesByName: [String: Type], processed: inout [String: Bool]) {
Expand All @@ -240,29 +249,42 @@ public enum Composer {
processed[globalName] = true
updateTypeRelationship(for: baseType, typesByName: typesByName, processed: &processed)
}
copyTypeRelationships(from: baseType, to: type)
if let composedType = baseType as? ProtocolComposition {
let implements = composedType.composedTypes?.filter({ $0 is SourceryProtocol })
implements?.forEach { updateInheritsAndImplements(from: $0, to: type) }
if implements?.count == composedType.composedTypes?.count
|| composedType.composedTypes == nil
|| composedType.composedTypes?.isEmpty == true
{
type.implements[globalName] = baseType
}
} else {
updateInheritsAndImplements(from: baseType, to: type)
}
type.basedTypes[globalName] = baseType
}
}

baseType.based.keys.forEach { type.based[$0] = $0 }
baseType.basedTypes.forEach { type.basedTypes[$0.key] = $0.value }
baseType.inherits.forEach { type.inherits[$0.key] = $0.value }
baseType.implements.forEach { type.implements[$0.key] = $0.value }

if baseType is Class {
type.inherits[globalName] = baseType
} else if let baseProtocol = baseType as? SourceryProtocol {
type.implements[globalName] = baseProtocol
if let extendingProtocol = type as? SourceryProtocol {
baseProtocol.associatedTypes.forEach {
if extendingProtocol.associatedTypes[$0.key] == nil {
extendingProtocol.associatedTypes[$0.key] = $0.value
}
private static func updateInheritsAndImplements(from baseType: Type, to type: Type) {
if baseType is Class {
type.inherits[baseType.name] = baseType
} else if let `protocol` = baseType as? SourceryProtocol {
type.implements[baseType.name] = baseType
if let extendingProtocol = type as? SourceryProtocol {
`protocol`.associatedTypes.forEach {
if extendingProtocol.associatedTypes[$0.key] == nil {
extendingProtocol.associatedTypes[$0.key] = $0.value
}
}
} else if baseType is ProtocolComposition {
// TODO: associated types?
type.implements[globalName] = baseType
}

type.basedTypes[globalName] = baseType
}
}

private static func copyTypeRelationships(from baseType: Type, to type: Type) {
baseType.based.keys.forEach { type.based[$0] = $0 }
baseType.basedTypes.forEach { type.basedTypes[$0.key] = $0.value }
baseType.inherits.forEach { type.inherits[$0.key] = $0.value }
baseType.implements.forEach { type.implements[$0.key] = $0.value }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,31 @@ internal struct ParserResultsComposed {

// extend all types with their extensions
parsedTypes.forEach { type in
type.inheritedTypes = type.inheritedTypes.map { inheritedName in
resolveGlobalName(for: inheritedName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases)?.name ?? inheritedName
let inheritedTypes: [[String]] = type.inheritedTypes.compactMap { inheritedName in
if let resolvedGlobalName = resolveGlobalName(for: inheritedName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases)?.name {
return [resolvedGlobalName]
}
if let baseType = Composer.findBaseType(for: type, name: inheritedName, typesByName: typeMap) {
if let composed = baseType as? ProtocolComposition, let composedTypes = composed.composedTypes {
// ignore inheritedTypes when it is a `ProtocolComposition` and composedType is a protocol
var combinedTypes = composedTypes.map { $0.globalName }
combinedTypes.append(baseType.globalName)
return combinedTypes
}
}
return [inheritedName]
}
type.inheritedTypes = inheritedTypes.flatMap { $0 }
let uniqueType: Type?
if let mappedType = typeMap[type.globalName] {
// this check will only fail on an extension?
uniqueType = mappedType
} else if let composedNameType = typeFromComposedName(type.name, modules: modules) {
// this can happen for an extension on unknown type, this case should probably be handled by the inferTypeNameFromModules
uniqueType = composedNameType
} else {
uniqueType = inferTypeNameFromModules(from: type.localName, containedInType: type.parent, uniqueTypes: typeMap, modules: modules).flatMap { typeMap[$0] }
}

let uniqueType = typeMap[type.globalName] ?? // this check will only fail on an extension?
typeFromComposedName(type.name, modules: modules) ?? // this can happen for an extension on unknown type, this case should probably be handled by the inferTypeNameFromModules
(inferTypeNameFromModules(from: type.localName, containedInType: type.parent, uniqueTypes: typeMap, modules: modules).flatMap { typeMap[$0] })

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

Expand Down
2 changes: 1 addition & 1 deletion SourceryRuntime/Sources/Linux/AST/Type_Linux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ public class Type: NSObject, SourceryModel, Annotated, Documented, Diffable, Sou
public var inherits = [String: Type]()

// sourcery: skipEquality, skipDescription
/// Protocols this type implements
/// Protocols this type implements. Does not contain classes in case where composition (`&`) is used in the declaration
public var implements = [String: Type]()

/// Contained types
Expand Down
2 changes: 1 addition & 1 deletion SourceryRuntime/Sources/macOS/AST/Type.swift
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ public class Type: NSObject, SourceryModel, Annotated, Documented, Diffable {
public var inherits = [String: Type]()

// sourcery: skipEquality, skipDescription
/// Protocols this type implements
/// Protocols this type implements. Does not contain classes in case where composition (`&`) is used in the declaration
public var implements: [String: Type] = [:]

/// Contained types
Expand Down
38 changes: 34 additions & 4 deletions SourceryTests/Parsing/ComposerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1590,17 +1590,47 @@ class ParserComposerSpec: QuickSpec {
]))
}

it("should deconstruct compositions of class and protocol for implements") {
let expectedProtocol = Protocol(name: "Foo")
let type = parse("protocol Foo {}; class Bar {}; class Implements: Bar & Foo {}").last as? Class

expect(type?.implements).to(equal([
expectedProtocol.name: expectedProtocol
]))
}

it("should deconstruct compositions of class and protocol for based") {
let expectedProtocol = Protocol(name: "Foo")
let expectedClass = Class(name: "Bar")
let expectedProtocolComposition = ProtocolComposition(name: "Bar & Foo", inheritedTypes: ["Bar", "Foo"], composedTypeNames: [TypeName(name: "Bar"), TypeName(name: "Foo")], composedTypes: [expectedClass, expectedProtocol])

let type = parse("protocol Foo {}; class Bar {}; class Implements: Bar & Foo {}").last as? Class

expect(type?.based).to(equal([
expectedClass.name: expectedClass.name,
expectedProtocol.name: expectedProtocol.name,
expectedProtocolComposition.name: expectedProtocolComposition.name
]))
}

it("should deconstruct compositions of class and protocol for inherits") {
let expectedClass = Class(name: "Bar")

let type = parse("protocol Foo {}; class Bar {}; class Implements: Bar & Foo {}").last as? Class

expect(type?.inherits).to(equal([
expectedClass.name: expectedClass
]))
}

it("should deconstruct compositions of protocols and classes for implements and inherits") {
let expectedProtocol = Protocol(name: "Foo")
let expectedClass = Class(name: "Bar")
let expectedProtocolComposition = ProtocolComposition(name: "GlobalComposition", inheritedTypes: ["Foo", "Bar"], composedTypeNames: [TypeName(name: "Foo"), TypeName(name: "Bar")])
expectedProtocolComposition.inherits = ["Bar": expectedClass]

let type = parse("typealias GlobalComposition = Foo & Bar; protocol Foo {}; class Bar {}; class Implements: GlobalComposition {}").last as? Class

expect(type?.implements).to(equal([
expectedProtocol.name: expectedProtocol,
expectedProtocolComposition.name: expectedProtocolComposition
expectedProtocol.name: expectedProtocol
]))

expect(type?.inherits).to(equal([
Expand Down
Loading