Skip to content

Declare ArgValues arrays in type position - #360

Merged
sidepelican merged 1 commit into
uber:masterfrom
farkasseb:fix/args-history-type-annotation
Sep 8, 2026
Merged

sidepelican merged 1 commit into
uber:masterfrom
farkasseb:fix/args-history-type-annotation

Conversation

@farkasseb

Copy link
Copy Markdown
Contributor

Summary

With --enable-args-history, the generated capture array is declared as var xArgValues = [(a: A, b: B)](). In that form Swift has to guess whether [...] is a type or an array literal, and it decides by resolving each name the way it would in an expression. A property or method named Config then hides the Config type, and a () element is already a value, so Swift settles on the array literal and the mock fails to compile. With var xArgValues: [(a: A, b: B)] = [] the annotation is unambiguously a type, so the guess never happens.

Example

struct Config {}

/// @mockable
protocol Foo {
    var Config: Int { get }
    func apply(config: Config?, force: Bool)
    func log(event: (), verbose: Bool)
}

Before: the stored Config property hides the type. Swift reads Config? as optional chaining on an Int and Bool as a value representing the type. () is already an expression and fails on its own:

class FooMock: Foo {
    init() { }
    init(Config: Int = 0) {
        self.Config = Config
    }

    var Config: Int = 0

    private(set) var applyCallCount = 0
    var applyArgValues = [(config: Config?, force: Bool)]()
    var applyHandler: ((Config?, Bool) -> ())?
    func apply(config: Config?, force: Bool) { ... }

    private(set) var logCallCount = 0
    var logArgValues = [(event: (), verbose: Bool)]()
    var logHandler: (((), Bool) -> ())?
    func log(event: (), verbose: Bool) { ... }
}
error: cannot call value of non-function type '[(config: Int?, force: Bool.Type)]'
    var applyArgValues = [(config: Config?, force: Bool)]()
                         ^
error: cannot use instance member 'Config' within property initializer; property initializers run before 'self' is available
    var applyArgValues = [(config: Config?, force: Bool)]()
                                   ^
error: cannot use optional chaining on non-optional value of type 'Int'
    var applyArgValues = [(config: Config?, force: Bool)]()
                                         ^
error: cannot call value of non-function type '[(event: (), verbose: Bool.Type)]'
    var logArgValues = [(event: (), verbose: Bool)]()
                       ^

After: the same mock uses explicit type annotations:

    var applyArgValues: [(config: Config?, force: Bool)] = []
    ...
    var logArgValues: [(event: (), verbose: Bool)] = []

Tuple shape alone does not trigger the failure: optional, dictionary, existential, generic and nested element types compile in either form. The concurrency-safe branch of the same template already used xArgValues: [T], so both paths now render the same way.

Test plan

Added compiled @Fixture argumentsHistoryMemberShadowsTypeName using the protocol above. The expected mock compiles as part of the test target with the shadowing var Config: Int member and the ()-typed parameter. Compilation verifies the fix: the previous = [...]() form fails there. Updated 23 existing fixture lines to use annotations, with no other output changes. swift build && swift test passes.

🤖 Generated with Claude Code

With `--enable-args-history` the generated capture array was declared as
`var xArgValues = [(a: A, b: B)]()`. In that form Swift has to guess whether
`[...]` is a type or an array literal, and it decides by resolving each name
the way it would in an expression. A property or method named `Config` then
hides the `Config` type, and a `()` element is already a value, so Swift
settles on the array literal and the mock fails to compile:

  error: cannot call value of non-function type '[(config: Int?, force: Bool.Type)]'
  error: cannot use instance member 'Config' within property initializer
  error: cannot use optional chaining on non-optional value of type 'Int'
  error: cannot call value of non-function type '[(event: (), verbose: Bool.Type)]'

With `var xArgValues: [(a: A, b: B)] = []` the annotation is unambiguously a
type, so the guess never happens. Tuple shape alone does not trigger the
failure: optional, dictionary, existential, generic and nested element types
compile in either form. The concurrency-safe branch of the same template
already used `xArgValues: [T]`, so both paths now render the same way.

- Template: emit `var xArgValues: [T] = []` instead of `var xArgValues = [T]()`.
- 23 existing fixture lines rewritten to the annotated form; no other output
  changes.
- New compiled fixture `argumentsHistoryMemberShadowsTypeName` proving the
  annotated form compiles next to a `var Config: Int` member that shadows the
  `Config` type, and next to a `()`-typed parameter.
- README arguments-history example updated to the emitted form.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@sidepelican sidepelican left a comment

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.

Looks good. Nice catch! Thank you.

@sidepelican
sidepelican merged commit 2c5bda2 into uber:master Sep 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants