# `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 {
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).