Skip to content

Commit

Permalink
Implemented Proper Protocol Composition Type Parsing (#1314)
Browse files Browse the repository at this point in the history
* Implemented proper protocol composition parsing

* removed redundant variable
  • Loading branch information
art-divin committed Mar 28, 2024
1 parent 2f9cf2c commit e1993f4
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 34 deletions.
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

0 comments on commit e1993f4

Please sign in to comment.