[Pitch] Amend SE-0368 to remove prefix `+` operator

SE-0368 included the following operation on StaticBigInt:

/// Returns the given value unchanged.
public static prefix func + (_ rhs: Self) -> Self

This was included so that prefix + could be included as a literal prefix in contexts where values are desired to be type StaticBigInt, rather than literals of another type, for symmetry with -:

let signs: [StaticBigInt] = [-1, 0, +1]

It turns out that this was a source breaking change, because of examples like the following:

let a: Int = 7
let b = +1     // Inferred as `StaticBigInt` because concrete `+` beats
               // the generic one on `AdditiveArithmetic`
let c = a + b  // Error: Cannot convert `b` from `StaticBigInt` to `Int`

(Previously, b was given type Int, and this example compiled correctly.)

I propose amending SE-0368 to remove this operator. This is a minor inconvenience for code that wants to use StaticBigInt values, but:

  • no such code exists today, because the type doesn't exist in 5.7.
  • such code can omit the leading + as a workaround.
  • we can investigate other options to restore the desired syntax at our leisure once the source break is resolved.

There's a PR showing the proposed change here: Remove prefix+ from StaticBigInt by stephentyrone · Pull Request #62733 · apple/swift · GitHub

23 Likes

This makes sense to me!

Doug

1 Like

The right long-term solution here is probably to recognize a leading + on a numeric literal as part of the literal rather than an operator applied to a literal, the same way that it works for leading -. There's certainly a possibility that that would have source-compatibility consequences of its own, though.

28 Likes

It would at least not have this particular consequence, and I agree that it's probably the right thing to do.

8 Likes

A kludgy but not entirely preposterous workaround which would deliver on the goals here without removing functionality would be to put this operator on equal footing by moving it to an extension of an underscored _StaticBigIntProtocol to which only this type conforms.

Agree entirely on the longterm solution and wouldn’t be fussed if we didn’t have this, but worth pointing out that there is a path which would maintain support for the desired syntax. It’s not elegant but it doesn’t actually make anything worse IMO.

2 Likes

Unlike functions, we can’t emit protocols into client, so we’d be stuck with the kludge forever.

3 Likes

if the default IntegerLiteralType is Int, and +(1 as Int) typechecks, why isn’t the 1 inferred to be type Int?

Indeed, that is part of the kludginess.

I wonder, though, if we could add this as an always-emitted-into-client extension method of ExpressibleByIntegerLiteral.

It should lose out to the existing method owing to the protocol hierarchy, and it closely reflects our intention to support this syntax for all integer literals.

If possible, that would be kinda elegant as far as kludges go.

Concrete parameter types win out here over default literal type resolution; right or wrong we couldn’t change this without risking (possibly widespread) unanticipated changes in inferred type.

2 Likes

Yes, that’s plausible. However, for 5.8 my preference is to simply remove the operation, as we’ve already branched.

4 Likes

Entirely fair—am curious to prototype this for the main branch though. Might mean that we can achieve substantially most or all of our desired end goal without mucking around with parsing.

1 Like

I am going to regret this, because I have every expectation that something will go wonky that we don't currently expect, but I'm now convinced after having played around with this idea that we should add the method to ExpressibleByIntegerLiteral (in a near-future version)—for the reason you've outlined here, because it's actually a bit more generally applicable than we'd like to think maybe:

Any well meaning person who's designing an ExpressibleByIntegerLiteral type and who wants to have their users enjoy every convenience that built-in integer literal types can offer, adding this static prefix func + to their own type, will inadvertently mess up default integer literal type inference in the same way we have here. We shouldn't leave this trap for people, penalizing the most conscientious API designers and their users.

There are two three fixes here of varying degrees of finality, which are not mutually exclusive:

  1. We can make prefix + Just Work™ for all integer literal types by adding these extension methods to ExpressibleByIntegerLiteral; users won't need to write their own and so they're unlikely to try unless they're seeking actively to be evil
  2. We can also, or instead, add these to all concrete built-in integer and floating-point types, like we do with homogeneous comparison operators and for the same reason (i.e., to "win" overload resolution): this will work, it's precedented, and we can make it all @_alwaysEmitIntoClient, but it's a lot of code to generate and it's kludgy, and it leaves the responsibility to end users to add this to their own custom types
  3. We can revamp parsing so that + is part of the literal—which could break other things and doesn't entirely make (1) or (2) superfluous (someone might want to write +x (note the non-literal), and we do currently provide this for free for arithmetic types, and there's not really a reason to take this away for non-arithmetic integer literal expressible types)
6 Likes

i feel like i really need to be devils advocate here and ask:

do we really need this?

we have many things in the standard library that have low, but non-zero utility, such as isMultiple(of:) (SE-0225). but prefix + stands out to me because it quite literally does nothing.

my instinct when using something like prefix + is to check the godbolt to make sure that it really is compiling to a no-op, and when i’m going to the trouble of verifying a zero-cost abstraction is really a ZCA it usually needs to meet at least one of the following criteria:

  1. it improves readability, or

  2. it improves type safety.

prefix + has no type safety benefits. and i don’t think prefix + improves readability either, it is just visual noise, and it’s especially jarring if you are using an editor with semantic highlighting, because - is “literal”-colored, but + is “function”-colored.

and even if you are a fan of prefix +, it’s not like StaticBigInt is meant to be a commonly-used type. StaticBigInt is a shim that library authors use to bootstrap actual user-facing numeric types, like KeyValuePairs<Key, Value> is used to bootstrap ordered collections. i think it is a very rare use case that someone would be passing a StaticBigInt to an API.

3 Likes

I would suggest that this is not the correct question to ask: we already have this for all numeric types, and it cannot be removed—the question is, then, only whether it can be made to work consistently.

Yup, I think we can all agree it’s fine that we don’t have this working on StaticBigInt for the moment.

6 Likes