Skip to content
5 changes: 3 additions & 2 deletions Sources/MockoloFramework/Models/ParsedEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ struct ResolvedEntity {

func model() -> Model {
let metadata = entity.metadata
let combinedAttributes = entity.entityNode.attributeDescriptions + attributes
return NominalModel(selfType: .init(name: metadata?.nameOverride ?? (key + "Mock")),
namespaces: entity.entityNode.namespaces,
acl: entity.entityNode.accessLevel,
declKindOfMockAnnotatedBaseType: entity.entityNode.declKind,
declKind: inheritsActorProtocol ? .actor : .class,
attributes: attributes,
attributes: combinedAttributes,
offset: entity.entityNode.offset,
inheritedTypeName: (entity.metadata?.module?.withDot ?? "") + key,
genericWhereConstraints: entity.entityNode.genericWhereConstraints,
Expand All @@ -91,7 +92,7 @@ protocol EntityNode {
var nameText: String { get }
var mayHaveGlobalActor: Bool { get }
var accessLevel: String { get }
var attributesDescription: String { get }
var attributeDescriptions: [String] { get }
var declKind: NominalTypeDeclKind { get }
var inheritedTypes: [String] { get }
var genericWhereConstraints: [String] { get }
Expand Down
17 changes: 13 additions & 4 deletions Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ extension ProtocolDeclSyntax: EntityNode {
return genericWhereClause?.requirements.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? []
}

var attributesDescription: String {
self.attributes.trimmedDescription
var attributeDescriptions: [String] {
return attributes.descriptions
}

func annotationMetadata(with annotation: String) -> AnnotationMetadata? {
Expand Down Expand Up @@ -354,8 +354,8 @@ extension ClassDeclSyntax: EntityNode {
return genericWhereClause?.requirements.map { $0.with(\.trailingComma, nil).trimmedDescription } ?? []
}

var attributesDescription: String {
self.attributes.trimmedDescription
var attributeDescriptions: [String] {
return attributes.descriptions
}

var isFinal: Bool {
Expand Down Expand Up @@ -412,6 +412,15 @@ fileprivate func findNamespaces(parent: Syntax?) -> [String] {
}

extension AttributeListSyntax {
fileprivate var descriptions: [String] {
return compactMap { element in
guard case .attribute(let attribute) = element else {
return nil
}
return attribute.trimmedDescription
}
}

fileprivate var mayHaveGlobalActor: Bool {
let wellKnownGlobalActor: Set<String> = [.mainActor]
return self.contains { element in
Expand Down
1 change: 1 addition & 0 deletions Tests/TestActor/FixtureActor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
init() { }
}

@available(iOS 18.0, *)
class P1Mock: P1 {
init() { }
}
Expand Down
72 changes: 72 additions & 0 deletions Tests/TestSendable/FixtureSendable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,4 +208,76 @@
}
}
}

@Fixture enum availableSendableProtocol {
@available(macOS 99.0, *)
struct Bar {}

/// @mockable
@available(macOS 99.0, *)
protocol Foo: Sendable {
func bar() -> Bar
}
Comment on lines +216 to +220
Copy link
Copy Markdown
Collaborator

@sidepelican sidepelican Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to enforce a compilation error when @available isn't applied to a mock.

Suggested change
/// @mockable
@available(iOS 18.0, *)
public protocol Foo: Sendable {
func bar() -> String
}
@available(macOS 99.0, *)
struct Bar {}
/// @mockable
@available(macOS 99.0, *)
protocol Foo: Sendable {
func bar() -> Bar
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2fa6c3d.
I updated the fixture to use an availability-gated return type (Bar) so the generated mock must inherit @available to compile.


@Fixture(includesConcurrencyHelpers: true)
enum expected {
@available(macOS 99.0, *)
final class FooMock: Foo, @unchecked Sendable {
init() { }


private let barState = MockoloMutex(MockoloHandlerState<Never, @Sendable () -> Bar>())
var barCallCount: Int {
return barState.withLock(\.callCount)
}
var barHandler: (@Sendable () -> Bar)? {
get { barState.withLock(\.handler) }
set { barState.withLock { $0.handler = newValue } }
}
func bar() -> Bar {
let barHandler = barState.withLock { state in
state.callCount += 1
return state.handler
}
if let barHandler = barHandler {
return barHandler()
}
fatalError("barHandler returns can't have a default value thus its handler must be set")
}
}
}
}

@Fixture enum availableInheritedProtocol {
@available(macOS 100.0, *)
struct Bar {}

@available(macOS 90.0, *)
protocol Foo {
}

/// @mockable
@available(macOS 100.0, *)
protocol Foo2: Foo {
var bar: Bar { get set }
}

@Fixture enum expected {
@available(macOS 100.0, *)
class Foo2Mock: Foo2 {
init() { }
init(bar: Bar) {
self._bar = bar
}


private(set) var barSetCallCount = 0
private var _bar: Bar! { didSet { barSetCallCount += 1 } }
var bar: Bar {
get { return _bar }
set { _bar = newValue }
}
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need a test case for protocol inheritance scenarios:

@available(macOS 100.0, *)
struct Bar {}

@available(macOS 90.0, *)
protocol Foo {
}

/// @mockable
@available(macOS 100.0, *)
protocol Foo2: Foo {
  var bar: Bar { get set }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 32dffb1.

}
#endif
10 changes: 10 additions & 0 deletions Tests/TestSendable/SendableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,15 @@ class SendableTests: MockoloTestCase {
verify(srcContent: confirmedSendableProtocol._source,
dstContent: confirmedSendableProtocol.expected._source)
}

func testAvailableSendableProtocol() {
verify(srcContent: availableSendableProtocol._source,
dstContent: availableSendableProtocol.expected._source)
}

func testAvailableInheritedProtocol() {
verify(srcContent: availableInheritedProtocol._source,
dstContent: availableInheritedProtocol.expected._source)
}
}
#endif