Out of curiosity, I conducted some experiments about ExpressibleByIntegerLiteral and type inference.
Given the following code:

struct MyUInt: ExpressibleByIntegerLiteral {
  // Not conforms to `AdditiveArithmetic`
  typealias IntegerLiteralType = UInt
  init(integerLiteral: IntegerLiteralType) {}
}

struct MyPlusInt {}
extension MyUInt {
  static prefix func +(_: MyUInt) -> MyPlusInt { .init() }
}

struct MyMinusInt {}
extension MyUInt {
  static prefix func -(_: MyUInt) -> MyMinusInt { .init() }
}

func f(_: MyUInt) { print("Unsigned") }
func f(_: MyPlusInt) { print("+") }
func f(_: MyMinusInt) { print("-") }

@freestanding(expression) macro m(_: MyUInt) -> UInt = #externalMacro(module: "MyMacros", type: "MyWonderfulMacro")
@freestanding(expression) macro m(_: MyPlusInt) -> UInt = #externalMacro(module: "MyMacros", type: "MyWonderfulMacro")
@freestanding(expression) macro m(_: MyMinusInt) -> Int = #externalMacro(module: "MyMacros", type: "MyWonderfulMacro")

/* (0) */ let _: MyUInt = -1 // ❌ error: negative integer '-1' overflows when stored into unsigned type 'MyUInt'
/* (1) */ let _: MyUInt = 1 // ✅ OK
/* (2) */ let _: MyPlusInt = +1 // ✅ OK
/* (3) */ let _: MyMinusInt = -1 // ❌ error: cannot convert value of type 'Int' to specified type 'MyMinusInt'
/* (4) */ let _: MyMinusInt = -(1) // ✅ OK

/* (i)   */ f(1)    // ✅ Prints "Unsigned"
/* (ii)  */ f(+1)   // ✅ Prints "+"
/* (iii) */ f(-1)   // ❌ error: negative integer '-1' overflows when stored into unsigned type 'MyUInt'
/* (iv)  */ f(-(1)) // ✅ Prints "-"

/* (I)   */ let _ = #m(1)    // ✅ `m(_: MyUInt) -> UInt` is chosen.
/* (II)  */ let _ = #m(+1)   // ✅ `m(_: MyPlusInt) -> UInt` is chosen.
/* (III) */ let _ = #m(-1)   // 🤯 `m(_: MyUInt) -> UInt` is chosen. 🐛❓
/* (IV)  */ let _ = #m(-(1)) // ✅ `m(_: MyMinusInt) -> Int` is chosen.

Results and thoughts.

Results are described as the comments in the code.
My consideration is below:

  • (0): It is an error as I expected.
  • (3): As left side is explicit about the type, I want the compiler to infer the type like (4).
  • (iii): Same as above.
  • (III): The weirdest thing happened! While MyUInt must not be negative as the compiler said in (0), the compiler chose m(_: MyUInt) -> UInt macro that takes an argument with type MyUInt. I'm not sure why.

Do you expect these results?

From my (slightly old) notes

Negative values can be represented by prepending the hyphen-minus character (-). This is considered to be part of the integer literal. In other words, the expression -42 is lexed as a single value, not as a call to the prefix operator - with 42 as its operand.

By contrast, the expression -(42) is lexed as a call to the prefix operator -. [...]

Note that the Swift standard library defines the prefix operator + for symmetry but does not consider a prepended + to be part of an integer literal. Therefore, +42 is lexed as a call to the prefix operator + with 42 as its operand.

2 Likes

Your prefix + will also break type inference, as previously discussed.

let b = +1
// expected `b: Int`
// actually `b: MyPlusInt`

(Although this doesn't occur in the Swift 6.0 REPL, for whatever reason.)

1 Like

@xwu Thank you for sharing your interesting article.
What confused me was that AST generated by swift-syntax is PrefixOperatorExpr '-' followed by IntegerLiteralExpr '1'.
Also I thought that the parser could theoretically re-parse it and the type-checker could re-consider it as generally expected even if the lexer recognize it as an integer literal.
Now I understand negative numeric literals have special treatment (which I doubt is good idea).
I completely forgot that I had already read the related thread...:sweat_smile: (thanks @benrimmington )


Nevertheless macro overload solution seems to have potentially some bugs.

1 Like

That seems worthy of filing a bug, maybe?

Just in case, I filed a bug: swift-syntax doesn't give negative integer literals special treatment while the Swift compiler does. · Issue #2787 · swiftlang/swift-syntax · GitHub


Follow-up: @Alex_Hoppen1 confirmed that this swift-syntax behavior is intentional.