Skip to content
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 Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ let package = Package(
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "Algorithms", package: "swift-algorithms"),
],
swiftSettings: [
.enableUpcomingFeature("BareSlashRegexLiterals"),
]
),
.macro(
Expand Down
35 changes: 29 additions & 6 deletions Sources/MockoloFramework/Templates/NominalTemplate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,39 @@ extension NominalModel {
renderedModelNames: Set<String>
) {
let addAcl = declKindOfMockAnnotatedBaseType == .protocol ? acl : ""
let allWhereConstraints = genericWhereConstraints + models.flatMap { ($1 as? AssociatedTypeModel)?.whereConstraints ?? [] }
let hasWhereConstraints = !allWhereConstraints.isEmpty
var allWhereConstraints = genericWhereConstraints + models.flatMap { ($1 as? AssociatedTypeModel)?.whereConstraints ?? [] }

let aliasModels = [String: [TypealiasRenderableModel]](
grouping: models.compactMap { $1 as? TypealiasRenderableModel },
by: \.name
).sorted(path: \.key)

// If there is a where, do not output typealias as it may not satisfy the conditions
if hasWhereConstraints {
let aliasItems = aliasModels.compactMap { (name, candidates) in
if !allWhereConstraints.isEmpty {
// Find associated type names directly bound to concrete types by same-type constraints
// (e.g. Value == Int).
let allAssociatedTypeNames = Set(aliasModels.map(\.key))
var sameTypeBoundNames: Set<String> = []

let eqConstraintRegex = /(.+)==(.+)/
allWhereConstraints.removeAll { constraint in
if let match = constraint.firstMatch(of: eqConstraintRegex) {
let lhs = match.output.1.trimmingCharacters(in: .whitespacesAndNewlines)
if allAssociatedTypeNames.contains(lhs) {
sameTypeBoundNames.insert(lhs)
return true
}
let rhs = match.output.2.trimmingCharacters(in: .whitespacesAndNewlines)
if allAssociatedTypeNames.contains(rhs) {
sameTypeBoundNames.insert(rhs)
return true
}
}
return false
}

let aliasItems = aliasModels.compactMap { (name, candidates) -> String? in
if sameTypeBoundNames.contains(name) { return nil }
if let defaultType = candidates.firstNonNil(\.defaultType) {
return """
\(1.tab)// Unavailable due to the presence of generic constraints
Expand All @@ -292,8 +314,9 @@ extension NominalModel {
}
return nil
}.joined(separator: "\n")
let typeparameters = aliasModels.map { (name, candidates) in
mergeAssociatedTypes(
let typeparameters = aliasModels.compactMap { (name, candidates) -> String? in
if sameTypeBoundNames.contains(name) { return nil }
return mergeAssociatedTypes(
name: name,
models: candidates.compactMap { $0 as? AssociatedTypeModel }
)
Expand Down
39 changes: 39 additions & 0 deletions Tests/TestPATs/FixturePAT.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,45 @@ import MockoloFramework
}
}
}

@Fixture enum patWithSameTypeConstraint {
public protocol Foo {
associatedtype Value
}

/// @mockable
public protocol NarrowFoo: Foo where Value == Int {}

/// @mockable
public protocol NarrowFooSwapped: Foo where Int == Value {}

@Fixture enum expected {
public class NarrowFooMock: NarrowFoo {
public init() { }
}

public class NarrowFooSwappedMock: NarrowFooSwapped {
public init() { }
}
}
}

@Fixture enum patWithMixedConstraints {
public protocol Foo {
associatedtype T
associatedtype U
}

/// @mockable
public protocol Bar: Foo where T == Int, U: Equatable {}

@Fixture enum expected {
public class BarMock<U>: Bar where U: Equatable {
public init() { }
}
}
}

#if compiler(>=6.0)
@Fixture enum patWithParentCondition {
/// @mockable
Expand Down
11 changes: 11 additions & 0 deletions Tests/TestPATs/ProtocolAssociatedTypeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ class ProtocolAssociatedTypeTests: MockoloTestCase {
verify(srcContent: patWithConditions._source,
dstContent: patWithConditions.expected._source)
}

func testPATWithSameTypeConstraint() {
verify(srcContent: patWithSameTypeConstraint._source,
dstContent: patWithSameTypeConstraint.expected._source)
}

func testPATWithMixedConstraints() {
verify(srcContent: patWithMixedConstraints._source,
dstContent: patWithMixedConstraints.expected._source)
}

#if compiler(>=6.0)
func testPATWithParentCondition() {
verify(srcContent: patWithParentCondition._source,
Expand Down