Use of Mirror?

I'm super impressed with ArgumentParser - it seems really well considered. I notice that you're using Mirror in ArgumentSet.init(_ type: ParsableArguments.Type) to find the all of the properties of ArgumentSetProvider type (the ParsedWrappers). Is this just to get the names of the properties to automagically turn these into the command names?

The primary reason I ask is that it would be nice to eliminate the requirement to use ParsableCommand. I should be able to write playgrounds and #! scripts with something as simple as:

import ArgumentParser
@Flag(help: "enable stuff")
var stuff: Bool

parseArguments()
if stuff { ... }

Such a use case works in playgrounds and #! because global variables are strictly initialized in top-down order.

Beyond the convenience and "progressive disclosure" nature of such a change, it would also make things more efficient than using reflection.

WDYT? Is there some other way to enable the simplified use-case?

-Chris

6 Likes

Thanks, Chris! It's using reflection to turn the names of flag and option properties into their command-line names, like --verbose. Resolving commands and subcommands just uses static type info, like the type name and some static properties, not reflection.

If I'm not mistaken, we'd need some more language features to enable that code — the declaration and calling parseArguments() could work, but for stuff to be able to get its value out of the parsed values, it would need to know that it's called stuff, which requires reflection somewhere...

If you'd like a lighter weight approach than defining a command, you can create a type that conforms to ParsableArguments instead, and parse it out as a value — there's an example of that more scripty style here: https://github.com/apple/swift-argument-parser/blob/master/Examples/roll/main.swift

5 Likes

Is there any possibility of being able to provide the name of the flag as a string? Something like:

@Flag(help: "enable stuff", name: "enable-stuff")
var stuff: Bool
2 Likes

This already works

 @Flag(name: [.customShort("s"), .customLong("enable-stuff")])
 var stuff: Bool
1 Like

Thanks Nate!

What I'm looking for is to make the simplest case as simple as possible. Ideally, the roll/main.swift example shouldn't require a struct at all - just defining the @Options as global variables should be sufficient.

I realize that you won't be able to do this today: it would require a small extension to property wrappers would enable this (and would be useful for many other things). We just need a way for the compiler to make the property name available to the property wrapper. For example, this could be done by having the init take a specially known keyword argument like propertyName and having the compiler provide that.

In any case, have you thought about the most minimal usecase like this? One nice thing about progressive disclosure is teachability benefits from making the simplest case really really simple.

In any case, this is all a super nit that I hope you think about before locking the API down. I'm a huge fan of this work - it has surpassed my expectations!

-Chris

5 Likes

This sort of situational awareness would be pretty interesting! It would also be valuable to know the context of the declaration, so that the @Flag property wrapper could alter its behavior depending on whether it was part of a Parsable... type or a global declaration. If we had that capability, we could make your hypothetical example even simpler:

import ArgumentParser

@Flag(help: "enable stuff")
var stuff: Bool

if stuff { ... }
2 Likes

Yep exactly. This is more or less how it works in the command line arg parsing library that I wrote years ago for C++. Of course, in C++ you still need a "main" and it isn't exactly great for scripting, but it was designed for the same convenience and composability. Note how the quick start doesn't require types to be defined.

In any case, I suspect that there are many "meta" features that a property wrapper might want access to beyond its type: the name is a really obviously one that would be useful for lots of things. I wonder how property wrappers could be used to improve Codable synth.

Property wrappers are not necessarily the best long-term solution for this sort of thing to begin with. Although combining property wrappers with mirrors can achieve a lightweight-looking syntax, it isn't the greatest thing for performance. I think we'll ultimately want some other form of custom attribute feature that is geared specifically toward registration, that doesn't require storing all of the attribute information per-instance or fully general reflection to iterate through. It seems like registration could also be used to provide something like llvm::cl::opt that wasn't tied to a type.

6 Likes

Yeah, I'd love to have the "adding an attribute to a protocol makes it possible to use reflection to retrieve all types that conform to the protocol" feature we talked about some time ago.

That said, I think that Nate's design here is good, it would just be nice to untie it from an enclosing struct or class. Requiring that when one defines subcommands seems fine, but it would be great if the simplest case didn't require it.