Skip to content

Running command with overloaded run function with all params having default values prints help instead of running command #670

Closed
@rgoldberg

Description

@rgoldberg

If a run function is defined with parameters, each of which has a default value (e.g., func run(arg: Type = defaultValue)), the function can be called via run().

Each ParsableCommand has a default run() function that outputs its help info, however, so that gets called instead of the overload with its default values.

If run() isn't specifically overridden in a subtype of ParsableCommand, but it is overloaded by another function that has default values for each of its parameters, can you autogenerate a run() that calls the defaulted overload?

This is similar to other open issues (#622 & #538), all of which stem from the default implementation of run() in ParsableCommand (see #622 (comment)).

ArgumentParser version:
1.5.0

Swift version:
swift-driver version: 1.62.15 Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)
Target: x86_64-apple-macosx12.0

Checklist

  • If possible, I've reproduced the issue using the main branch of this package
    I've searched for existing GitHub issues

Steps to Reproduce

@main
struct Command: ParsableCommand {
    func run(param: String = "default") {
        print("Ran defaulted overload")
    }
}

Expected behavior

Prints "Ran defaulted overload"

Actual behavior

Prints help info for command

Activity

natecook1000

natecook1000 commented on Feb 13, 2025

@natecook1000
Member

Unfortunately this is a result of how types satisfy requirements when conforming to protocols in Swift. The ParsableCommand protocol has mutating func run() throws as a requirement, which can be satisfied by any of these:

func run() { ... }
mutating func run() { ... }
mutating func run() throws { ... }
func run() throws { ... }

...but not by a function with default parameters. Here's a stripped down example:

protocol Command {
  func run()
}

extension Command {
  func run() {
    print("Default implementation")
  }
}

struct Example: Command {
  func run(extraFast: Bool = true) {
    print("Example implementation")
  }
}

func runSomeCommand(_ command: some Command) {
  command.run()
}

func runExample(_ command: Example) {
  command.run()
}

let example = Example()
runExample(example)
runSomeCommand(example)
// Prints:
// Example implementation
// Default implementation

While the default-parametered Example.run() is callable on concrete instances of Example, it doesn't actually satisfy the Command.run() protocol requirement, so when .run() is called on an instance where the only thing known is its Command conformance (as in runSomeCommand(_:)), then the protocol's version is called. You can see this if you comment out extension Command { ... } -- without that, the compiler says:

- error: type 'Example' does not conform to protocol 'Command'

because Example doesn't provide a method that satisfies the protocol requirment.

In this case, my recommendation is to go the other way – leave the defaults off your custom method and provide them within your run() implementation, or use another method name:

struct FixedExample: Command {
  func fixedRun(extraFast: Bool = true) {
    print("FixedExample implementation")
  }

  func run() {
    fixedRun()
  }
}

runSomeCommand(FixedExample())
// Prints "FixedExample implementation"
rgoldberg

rgoldberg commented on Feb 14, 2025

@rgoldberg
ContributorAuthor

@natecook1000 Thanks for the info. I haven't had a chance to look into the following, but could @_disfavoredOverload be useful at all for this circumstance? I'm not sure exactly how that interacts (or other possible similar attributes interact) with function selection…

FYI, when I dealt with this back then, I used your first suggestion: I didn't have defaults in my custom function, instead supplying the default values in my run() function implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Running command with overloaded `run` function with all params having default values prints help instead of running command · Issue #670 · apple/swift-argument-parser