YOCKOW
(YOCKOW)
1
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?
xwu
(Xiaodi Wu)
2
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
YOCKOW
(YOCKOW)
4
@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...
(thanks @benrimmington )
Nevertheless macro overload solution seems to have potentially some bugs.
1 Like
xwu
(Xiaodi Wu)
5
That seems worthy of filing a bug, maybe?
YOCKOW
(YOCKOW)
6