SE-0422 proposal. Potential bugs discovered 🚨

SE-0422: "Expression macro as caller-side default argument".

TL;DR: Freestanding macros in a default argument position expend at a caller side (not in-place, as usual).

Two potential bugs are discovered:

- In a case of generic macro in specific cases the type couldn't be inferred by a compiler for some reason.

- There is a situation where an regular expression macro expends in-place, not at a collar side (as it should).

Please, refer to the source code of the minimal reproducible project for concrete examples.

You probably want to report this to GitHub - swiftlang/swift: The Swift Programming Language.

Looking at your project, those don't appear to be bugs in the specification or implementation of SE-0422, just general constraints on type-checking.

Given that your #identity macros expand to { value }() (where value is an identifier but is not defined by the macro itself), and given the implementation of printNumeric() as:

func printNumeric(_ value: some Numeric = #identity1) {
    print("The numeric value is", value)
}

In the first example:

// ❌ Compilation error: "Generic parameter 'some Numeric' could not be inferred"
printNumeric()

There is no type information the compiler can use to infer the type of value, and thus the program is ill-formed.

In the second example:

// ❌ Compilation error: "Cannot find 'value' in scope" (because it extends in-place)
func printNumeric(_ value: some Numeric = #identity2) {
    print("The numeric value is", value)
}

If you replace #identity2 with its expansion ({ value }()), the compiler emits the same error. This error is expected because default arguments cannot be self-referential. The compiler would also emit this error for your first example if it could get past the "could not be inferred" issue. For example:

func printNumeric<T: Numeric>(_ type: T.Type, _ value: T = #identity1) {
  print("The numeric value is", value)
}
printNumeric(Int.self) // ❌ Compilation error: "Cannot find 'value' in scope"

If we expand the macros to 1 / 0 instead of { value }(), we can see that the expression is not callee-evaluated because the compiler does not emit a division-by-zero error until/unless the function is called.

1 Like

Another really unfortunate bug with SE-0422: expression macros are invoked with an empty lexicalContext when expanded as a default argument (GH issue):

func test(_ loc: SourceLocation = #here) {
    print(loc)
}

#Playground {
    test() // lexicalContext is empty
    test(#here) // lexicalContext works as expected
}
1 Like

You should report this at GitHub - swiftlang/swift: The Swift Programming Language :slightly_smiling_face:

I think you missed one crucial thing: according to SE-0422 the macro will not be expended “in-place“. It will be expended at a call-side. That’s the whole point.

So the code in your very last snippet will do compile and work! You can just try to run it by yourself :slightly_smiling_face:

func printNumeric<T: Numeric>(_ type: T.Type, _ value: T = #identity1) {
  print("The numeric value is", value)
}

func start(with value: Int) {
    printNumeric(Int.self) // ✅ Compiles & Works
}
// Or in a more general form
func start(with value: some Numeric) {
    printNumeric(type(of: value)) // ✅ Compiles & Works
}

At the same time this example of yours really helps to understand that the “issue1“ is indeed not a bug. It illustrates very clearly that the type information is indeed not being passed in this scenario.

But as for “issue2“ it still looks like a potential bug for me. Namely: the macro expends “in-place“, while it should expends at the call-side, according to SE-0422.

The expansion still ultimately happens at the call site, however the compiler first type-checks default argument expressions (which as I noted it does for non-macro-based values too.) I'll let the code owners confirm this is by design, but the behaviour is observably correct in other macros like #file or Swift Testing's #_sourceLocation.

1 Like

I’ve run into this bug too! And I really need this feature to work :persevering_face:
Any workarounds? :face_holding_back_tears: Because we’ll probably reach AGI before Apple fixes the bug…