Skip to content

[Bug] Unnamed parameter generates invalid backing call referencing _ (subscripts and methods) #187

Description

@graycampbell

Swift Version

Any supported toolchain — the defect is in macro expansion logic and is toolchain-independent.

Package Version

main (observed on the feature/subscript-support branch).

Bug Description

A member declared with an unnamed parameter (a wildcard _ with no internal name) generates an invalid backing body. Both macros derive the forwarded argument from parameter.secondName ?? parameter.firstName, which resolves to the wildcard token _ when there is no internal name, so the generated code forwards _ as an expression — which is not legal Swift ('_' can only appear in a pattern or on the left side of an assignment).

This affects both the subscript and method macros (confirmed — see Compiler verification below):

  • @_MockedSubscriptkeyArgumentExpression(from:) in MockedSubscriptMacro+AccessorMacro.swift.
  • @_MockedMethodparameter.secondName ?? parameter.firstName in MockedMethodMacro+BodyMacro.swift (~line 72). The method case emits _ in two places (the recordInput tuple and the _invoke(...) call).

Found during review of the subscript-support feature (#184).

Steps to Reproduce

Mock a protocol with a subscript or method whose parameter has no internal name:

@Mocked
protocol Dependency {
    subscript(_: Int) -> String { get }
    func foo(_: Int) -> String
}

Expected Behavior

The generated backing body should reference a synthesized parameter name (e.g. bind the wildcard to a generated identifier in the signature, or synthesize a name like arg0), producing compilable code such as self.__subscriptIndex.get(arg0) and _invoke(arg0).

Actual Behavior

Both macros reference the wildcard _ directly, which does not compile.

Subscript (subscript(_: Int) -> String, read-only):

subscript(_: Int) -> String {
    get {
        self.__subscriptIndex.get(_)
    }
}

Method (func foo(_: Int) -> String):

func foo(_: Int) -> String {
    self.__foo.recordInput(
        (
            _
        )
    )
    let _invoke = self.__foo.closure()
    let returnValue = _invoke(
        _
    )
    self.__foo.recordOutput(
        returnValue
    )
    return returnValue
}

Stack Trace / Logs

'_' can only appear in a pattern or on the left side of an assignment

Additional Context

  • Sources:
    • Sources/MockingMacros/Macros/MockedSubscriptMacro/MockedSubscriptMacro+AccessorMacro.swiftkeyArgumentExpression(from:).
    • Sources/MockingMacros/Macros/MockedMethodMacro/MockedMethodMacro+BodyMacro.swiftparameter.secondName ?? parameter.firstName (~line 72).
  • Surfaced during the code review for Add subscript support #184. Low real-world frequency (subscripts/methods are usually declared with named parameters), so it is filed as a follow-up rather than blocking the feature.
  • Since both macros share the same secondName ?? firstName forwarding pattern, a fix should cover both — ideally via a shared helper that synthesizes a stable name for wildcard parameters.

Compiler verification

Both halves of the bug were confirmed against the Swift compiler (swiftc -typecheck), not just the macros' (parse-only) test harness:

  1. The inputs are valid Swift. An unnamed parameter typechecks cleanly as both a protocol requirement and a concrete implementation, for subscripts and methods:

    protocol P {
        subscript(_: Int) -> String { get }   // compiles
        func foo(_: Int) -> String            // compiles
    }
    struct S: P {
        subscript(_: Int) -> String { "x" }   // compiles
        func foo(_: Int) -> String { "x" }    // compiles
    }
  2. The generated output is invalid Swift. Forwarding _ as a call argument is a hard compile error:

    func g(_ x: Int) -> String { "y" }
    func h() -> String { g(_) }   // error: '_' can only appear in a pattern or on the left side of an assignment

So a legal protocol declaration produces a mock that fails to compile, for both subscripts and methods.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Fields

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions