Skip to content

Commit

Permalink
Fix associatedtype generics (#1345)
Browse files Browse the repository at this point in the history
* Preliminary associatedtype support

* Implemented associatedtype support with generic requirements

* Fixed failing test

* Squashed commit of the following:

commit 9d01e6f
Author: Ruslan A <r.alikhamov@gmail.com>
Date:   Fri Jun 14 20:06:41 2024 +0400

    Improved concurrency support in SwiftTemplate caching (#1344)

* Removed test code

* Removed comment

* Updated Linux classes

* update internal boilerplate code.

* Updated generated code

* Removed warnings

* Updated expected file

* Updated expected file

* Adjusted protocol type for Linux

* Removed protocol composition due to Swift compiler crash under Linux
  • Loading branch information
art-divin authored Jun 19, 2024
1 parent 9d01e6f commit 7153768
Show file tree
Hide file tree
Showing 27 changed files with 889 additions and 148 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -246,16 +246,37 @@ class SyntaxTreeCollector: SyntaxVisitor {
let name = node.name.text.trimmed
var typeName: TypeName?
var type: Type?

if let possibleTypeName = node.inheritanceClause?.inheritedTypes.description.trimmed {
var genericRequirements: [GenericRequirement] = []
if let genericWhereClause = node.genericWhereClause {
genericRequirements = genericWhereClause.requirements.compactMap { requirement in
if let sameType = requirement.requirement.as(SameTypeRequirementSyntax.self) {
return GenericRequirement(sameType)
} else if let conformanceType = requirement.requirement.as(ConformanceRequirementSyntax.self) {
return GenericRequirement(conformanceType)
}
return nil
}
}
if let composition = processPossibleProtocolComposition(for: possibleTypeName, localName: "") {
type = composition
} else {
type = Protocol(name: possibleTypeName, genericRequirements: genericRequirements)
}
typeName = TypeName(possibleTypeName)
} else if let possibleTypeName = (node.initializer?.value as? TypeSyntax)?.description.trimmed {
type = processPossibleProtocolComposition(for: possibleTypeName, localName: "")
typeName = TypeName(possibleTypeName)
} else {
type = Type(name: "Any")
typeName = TypeName(name: "Any", actualTypeName: TypeName(name: "Any"))
}

sourceryProtocol.associatedTypes[name] = AssociatedType(name: name, typeName: typeName, type: type)
return .skipChildren
}


public override func visit(_ node: OperatorDeclSyntax) -> SyntaxVisitorContinueKind {
return .skipChildren
}
Expand Down
12 changes: 9 additions & 3 deletions SourceryRuntime/Sources/Common/AST/Actor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import Foundation
@objc(SwiftActor) @objcMembers
#endif
public final class Actor: Type {

// sourcery: skipJSExport
public class var kind: String { return "actor" }

/// Returns "actor"
public override var kind: String { return "actor" }
public override var kind: String { Self.kind }

/// Whether type is final
public var isFinal: Bool {
Expand Down Expand Up @@ -36,7 +40,8 @@ public final class Actor: Type {
annotations: [String: NSObject] = [:],
documentation: [String] = [],
isGeneric: Bool = false,
implements: [String: Type] = [:]) {
implements: [String: Type] = [:],
kind: String = Actor.kind) {
super.init(
name: name,
parent: parent,
Expand All @@ -54,7 +59,8 @@ public final class Actor: Type {
annotations: annotations,
documentation: documentation,
isGeneric: isGeneric,
implements: implements
implements: implements,
kind: kind
)
}

Expand Down
11 changes: 8 additions & 3 deletions SourceryRuntime/Sources/Common/AST/Class.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import Foundation
@objc(SwiftClass) @objcMembers
#endif
public final class Class: Type {
// sourcery: skipJSExport
public class var kind: String { return "class" }

/// Returns "class"
public override var kind: String { return "class" }
public override var kind: String { Self.kind }

/// Whether type is final
public var isFinal: Bool {
Expand All @@ -30,7 +33,8 @@ public final class Class: Type {
annotations: [String: NSObject] = [:],
documentation: [String] = [],
isGeneric: Bool = false,
implements: [String: Type] = [:]) {
implements: [String: Type] = [:],
kind: String = Class.kind) {
super.init(
name: name,
parent: parent,
Expand All @@ -48,7 +52,8 @@ public final class Class: Type {
annotations: annotations,
documentation: documentation,
isGeneric: isGeneric,
implements: implements
implements: implements,
kind: kind
)
}

Expand Down
16 changes: 11 additions & 5 deletions SourceryRuntime/Sources/Common/AST/Protocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@

import Foundation

#if canImport(ObjectiveC)

/// :nodoc:
public typealias SourceryProtocol = Protocol

/// Describes Swift protocol
#if canImport(ObjectiveC)
@objcMembers
#endif
public final class Protocol: Type {

// sourcery: skipJSExport
public class var kind: String { return "protocol" }

/// Returns "protocol"
public override var kind: String { return "protocol" }
public override var kind: String { Self.kind }

/// list of all declared associated types with their names as keys
public var associatedTypes: [String: AssociatedType] {
Expand Down Expand Up @@ -52,7 +55,8 @@ public final class Protocol: Type {
modifiers: [SourceryModifier] = [],
annotations: [String: NSObject] = [:],
documentation: [String] = [],
implements: [String: Type] = [:]) {
implements: [String: Type] = [:],
kind: String = Protocol.kind) {
self.associatedTypes = associatedTypes
super.init(
name: name,
Expand All @@ -71,7 +75,8 @@ public final class Protocol: Type {
annotations: annotations,
documentation: documentation,
isGeneric: !associatedTypes.isEmpty || !genericRequirements.isEmpty,
implements: implements
implements: implements,
kind: kind
)
}

Expand Down Expand Up @@ -132,3 +137,4 @@ public final class Protocol: Type {
}
// sourcery:end
}
#endif
11 changes: 8 additions & 3 deletions SourceryRuntime/Sources/Common/AST/ProtocolComposition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import Foundation
#endif
public final class ProtocolComposition: Type {

// sourcery: skipJSExport
public class var kind: String { return "protocolComposition" }

/// Returns "protocolComposition"
public override var kind: String { return "protocolComposition" }
public override var kind: String { Self.kind }

/// The names of the types composed to form this composition
public let composedTypeNames: [TypeName]
Expand All @@ -35,7 +38,8 @@ public final class ProtocolComposition: Type {
isGeneric: Bool = false,
composedTypeNames: [TypeName] = [],
composedTypes: [Type]? = nil,
implements: [String: Type] = [:]) {
implements: [String: Type] = [:],
kind: String = ProtocolComposition.kind) {
self.composedTypeNames = composedTypeNames
self.composedTypes = composedTypes
super.init(
Expand All @@ -51,7 +55,8 @@ public final class ProtocolComposition: Type {
typealiases: typealiases,
annotations: annotations,
isGeneric: isGeneric,
implements: implements
implements: implements,
kind: kind
)
}

Expand Down
11 changes: 8 additions & 3 deletions SourceryRuntime/Sources/Common/AST/Struct.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import Foundation
#endif
public final class Struct: Type {

// sourcery: skipJSExport
public class var kind: String { return "struct" }

/// Returns "struct"
public override var kind: String { return "struct" }
public override var kind: String { Self.kind }

/// :nodoc:
public override init(name: String = "",
Expand All @@ -35,7 +38,8 @@ public final class Struct: Type {
annotations: [String: NSObject] = [:],
documentation: [String] = [],
isGeneric: Bool = false,
implements: [String: Type] = [:]) {
implements: [String: Type] = [:],
kind: String = Struct.kind) {
super.init(
name: name,
parent: parent,
Expand All @@ -53,7 +57,8 @@ public final class Struct: Type {
annotations: annotations,
documentation: documentation,
isGeneric: isGeneric,
implements: implements
implements: implements,
kind: kind
)
}

Expand Down
12 changes: 7 additions & 5 deletions SourceryRuntime/Sources/Common/Composer/Composer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ public enum Composer {
let composed = ParserResultsComposed(parserResult: parserResult)

let resolveType = { (typeName: TypeName, containingType: Type?) -> Type? in
return composed.resolveType(typeName: typeName, containingType: containingType)
composed.resolveType(typeName: typeName, containingType: containingType)
}

let methodResolveType = { (typeName: TypeName, containingType: Type?, method: Method) -> Type? in
return composed.resolveType(typeName: typeName, containingType: containingType, method: method)
composed.resolveType(typeName: typeName, containingType: containingType, method: method)
}

let processType = { (type: Type) in
Expand All @@ -52,7 +52,7 @@ public enum Composer {
}

if let sourceryProtocol = type as? SourceryProtocol {
resolveProtocolTypes(sourceryProtocol, resolve: resolveType)
resolveAssociatedTypes(sourceryProtocol, resolve: resolveType)
}
}

Expand Down Expand Up @@ -180,11 +180,13 @@ public enum Composer {
protocolComposition.composedTypes = composedTypes
}

private static func resolveProtocolTypes(_ sourceryProtocol: SourceryProtocol, resolve: TypeResolver) {
private static func resolveAssociatedTypes(_ sourceryProtocol: SourceryProtocol, resolve: TypeResolver) {
sourceryProtocol.associatedTypes.forEach { (_, value) in
guard let typeName = value.typeName,
let type = resolve(typeName, sourceryProtocol)
else { return }
else {
return
}
value.type = type
}

Expand Down
54 changes: 43 additions & 11 deletions SourceryRuntime/Sources/Common/Composer/ParserResultsComposed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal struct ParserResultsComposed {
let parsedTypes: [Type]
let functions: [SourceryMethod]
let resolvedTypealiases: [String: Typealias]
let associatedTypes: [String: AssociatedType]
let unresolvedTypealiases: [String: Typealias]

init(parserResult: FileParserResult) {
Expand All @@ -23,6 +24,7 @@ internal struct ParserResultsComposed {
let aliases = Self.typealiases(parserResult)
resolvedTypealiases = aliases.resolved
unresolvedTypealiases = aliases.unresolved
associatedTypes = Self.extractAssociatedTypes(parserResult)
parsedTypes = parserResult.types

// set definedInType for all methods and variables
Expand Down Expand Up @@ -51,6 +53,11 @@ internal struct ParserResultsComposed {
alias.type = resolveType(typeName: alias.typeName, containingType: alias.parent)
}

/// Map associated types
associatedTypes.forEach {
typeMap[$0.key] = $0.value.type
}

types = unifyTypes()
}

Expand Down Expand Up @@ -96,7 +103,7 @@ internal struct ParserResultsComposed {
resolveExtensionOfNestedType(type)
}

if let resolved = resolveGlobalName(for: oldName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases)?.name {
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)."
Expand All @@ -116,7 +123,7 @@ internal struct ParserResultsComposed {
// extend all types with their extensions
parsedTypes.forEach { type in
let inheritedTypes: [[String]] = type.inheritedTypes.compactMap { inheritedName in
if let resolvedGlobalName = resolveGlobalName(for: inheritedName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases)?.name {
if let resolvedGlobalName = resolveGlobalName(for: inheritedName, containingType: type.parent, unique: typeMap, modules: modules, typealiases: resolvedTypealiases, associatedTypes: associatedTypes)?.name {
return [resolvedGlobalName]
}
if let baseType = Composer.findBaseType(for: type, name: inheritedName, typesByName: typeMap) {
Expand Down Expand Up @@ -175,6 +182,14 @@ internal struct ParserResultsComposed {
})
}

// extract associated types from all types and add them to types
private static func extractAssociatedTypes(_ parserResult: FileParserResult) -> [String: AssociatedType] {
parserResult.types
.compactMap { $0 as? SourceryProtocol }
.map { $0.associatedTypes }
.flatMap { $0 }.reduce(into: [:]) { $0[$1.key] = $1.value }
}

/// returns typealiases map to their full names, with `resolved` removing intermediate
/// typealises and `unresolved` including typealiases that reference other typealiases.
private static func typealiases(_ parserResult: FileParserResult) -> (resolved: [String: Typealias], unresolved: [String: Typealias]) {
Expand Down Expand Up @@ -207,11 +222,14 @@ internal struct ParserResultsComposed {
}

/// Resolves type identifier for name
func resolveGlobalName(for type: String,
containingType: Type? = nil,
unique: [String: Type]? = nil,
modules: [String: [String: Type]],
typealiases: [String: Typealias]) -> (name: String, typealias: Typealias?)? {
func resolveGlobalName(
for type: String,
containingType: Type? = nil,
unique: [String: Type]? = nil,
modules: [String: [String: Type]],
typealiases: [String: Typealias],
associatedTypes: [String: AssociatedType]
) -> (name: String, typealias: Typealias?)? {
// if the type exists for this name and isn't an extension just return it's name
// if it's extension we need to check if there aren't other options TODO: verify
if let realType = unique?[type], realType.isExtension == false {
Expand All @@ -222,6 +240,13 @@ internal struct ParserResultsComposed {
return (name: alias.type?.globalName ?? alias.typeName.name, typealias: alias)
}

if let associatedType = associatedTypes[type],
let actualType = associatedType.type
{
let typeName = associatedType.typeName ?? TypeName(name: actualType.name)
return (name: actualType.globalName, typealias: Typealias(aliasName: type, typeName: typeName))
}

if let containingType = containingType {
if type == "Self" {
return (name: containingType.globalName, typealias: nil)
Expand All @@ -231,7 +256,7 @@ internal struct ParserResultsComposed {
while currentContainer != nil, let parentName = currentContainer?.globalName {
/// TODO: no parent for sure?
/// manually walk the containment tree
if let name = resolveGlobalName(for: "\(parentName).\(type)", containingType: nil, unique: unique, modules: modules, typealiases: typealiases) {
if let name = resolveGlobalName(for: "\(parentName).\(type)", containingType: nil, unique: unique, modules: modules, typealiases: typealiases, associatedTypes: associatedTypes) {
return name
}

Expand Down Expand Up @@ -565,20 +590,27 @@ internal struct ParserResultsComposed {
}
}

return unique[resolvedIdentifier]
if let associatedType = associatedTypes[resolvedIdentifier] {
return associatedType.type
}

return unique[resolvedIdentifier] ?? unique[typeName.name]
}

private func actualTypeName(for typeName: TypeName,
containingType: Type? = nil) -> TypeName? {
containingType: Type? = nil) -> TypeName? {
let unique = typeMap
let typealiases = resolvedTypealiases
let associatedTypes = associatedTypes

var unwrapped = typeName.unwrappedTypeName
if let generic = typeName.generic {
unwrapped = generic.name
} else if let type = associatedTypes[unwrapped] {
unwrapped = type.name
}

guard let aliased = resolveGlobalName(for: unwrapped, containingType: containingType, unique: unique, modules: modules, typealiases: typealiases) else {
guard let aliased = resolveGlobalName(for: unwrapped, containingType: containingType, unique: unique, modules: modules, typealiases: typealiases, associatedTypes: associatedTypes) else {
return nil
}

Expand Down
Loading

0 comments on commit 7153768

Please sign in to comment.