`ExpressibleByIntegerLiteral` and type inference (and macro)

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.