Literal initialization via coercion

I see a problem here: sometimes the “wanted” behavior isn’t actually wanted at all. like:

extension Int8 
{
    func asOffset<I>(from base:I) -> I where I:BinaryInteger 
    {
        return I(self) + base
    }
}

let field:Int8 = 4
print(field.asOffset(from: 0x400000))

The 0x400000 is probably a hardcoded pointer and the intent here was really

field.asOffset(from: 0x400000 as Int)

but with Xiaodi’s proposal it becomes an error.

Yes, it's certainly possible for such a scenario to arise, but I think in real-world usage this particular example is unlikely to be such a case: the user is likely to actually use the result for something other than printing, in which scenario the type is no longer determined by literal type inference rules.

extension Int8 
{
    func asOffset<I>(from base:I) -> UnsafeRawPointer? where I:BinaryInteger 
    {
        return UnsafeRawPointer(bitPattern: UInt(base)).flatMap{ $0 + Int(self) }
    }
}

let field:Int8 = 4
let pointer:UnsafeRawPointer? = field.asOffset(from: 0x400000)

Yes, again, possible. But isn't this API best declared with a parameter UInt? (The desired default inferred type is, in any case, UInt and not today's Int, meaning today's Swift doesn't behave ideally in the general case of hardcoded memory addresses either.) Is this real-world code? What's the use case for supporting differently typed memory addresses?

no but i thought the point was to brainstorm possible counterexamples so problems get caught before they affect real-world code

Yes, I think this is the behavior people expect from the syntax Int(5), and we've seen enough confusion with the current semantics to warrant a change.

Doug

Slightly ot:
Isn't anyone else a little bit unsatisfied that even with the proposed changes Swift can't deal with literals of really big numbers?
Thanks to operator overloading, you can easily create your own numeric types, but there is a fundamental restriction on their initializer.
I know that this isn't an issue for serious applications, but wouldn't it be nice if pupils could just query the Swift REPL for sqrt(10000000000000000000000000000000)without having to think about the limitations of Int64?

3 Likes

Wasn’t some work on something like this done by @xwu and others with the DoubleWidth types? Or are you saying that we should automatically create those types to fit the size of the literal?

We can move this discussion to another thread though, since it’s not directly related.

Very few languages use an arbitrary-precision integer as the default integer-literal type. Haskell does, and from what I remember, it's a bit of a mess there because of course a lot of libraries use the fixed-precision Int type for performance; the use of Integer as a default really undermines the currency of Int.

I think Swift should provide an arbitrary-precision integer type, but that's definitely a question for a separate topic.

why does every discussion about integers in Swift always end up at bigints

1 Like

I think giving parenthesis a meaning in this case would be worse than requiring to spell init or adding as X since we are currently trying to fix all of the logic related to parens in the type-checker to make them insignificant.

1 Like

I may have been guilty of suggesting that we could many parentheses meaningful here... regardless, I agree with you: we already have explicit spellings in the language for both cases (.init or as), so we don't need a third, less-obvious way to change the behavior.

I think this is a great proposal, +1 from me.

Doug

Well, no, it’s understood that parentheses are meaningful in calls already. foo((1,2)) is not the same as foo(1,2).

But we could certainly have a language rule that includes looking through parentheses in order to recognize this as a special case.

Definitely when it concerns tuples but not with a single argument e.g. foo(1) and foo((1)) based on your example.

3 Likes

My argument is not that parens are a great thing to not look through; my argument is that it would be better to have a very bright line about how this works. We’re going to have limits to what we look through to find a literal here. We’re not going to look through T((1,2).0), for example. This is not a type-propagation analysis.

Right, I think the idea is to only handle literals in argument positions anything else would not be considered. Also I'm just trying to point out that there are alternatives available and we have been struggling for some time to deal with fallout of parens having different meaning in different contexts e.g. SE-0110 so we'd be great if we didn't have one more special case related to parens to consider...

I’m just saying that you’re adding the special case here. :) At least from the type-checker’s perspective!

I guess you need to define what exactly you’re going to look through. Presumably not T(id(1)) or T(2+3) or T(opt ?? 4) or T({5}()) or T([6][0]) but maybe T(b ? 7 : 8) and definitely T(((((((((9)))))))))).

I understand! :) I think it should be singular literal number of string, no sub-expressions or anything which isn't modeled as subtype of LiteralExpr with known default protocol.

Nice idea. Would like Int((1)), Int.init(1), etc. to behave like Int(1) and hence 1 as Int though.

2 Likes