[swift-evolution] Proposal: String literal suffixes for defining types

Kevin Ballard kevin at sb.org
Fri Dec 11 15:50:52 CST 2015

On Thu, Dec 10, 2015, at 09:52 PM, Chris Lattner wrote:
> If you’re interested in pursuing this, I’d ask you to think about a few different things:
> 1) How does this generalize to integer, FP and other literals we have?

I believe this same proposal should work for decimal integral and decimal FP literals, as those cannot end in a letter. It cannot work for hexadecimal numeric literals as those may end in a letter, unless we come up with an alternative syntax. My first thought for an alternative syntax is to put a # after the literal, e.g. 12#i32 or 0xABC#i32, but I'm not sure if that's actually a good idea. Or we could just say this doesn't work for hexadecimal literals. The only other literals we have are true, false, and nil, and I'm not convinced we need to support literal suffixes for those (although the #i32 suggestion would actually work there too).

> 2) How do we avoid baking these into the compiler?  We would want to allow the suffixes to be user extensible, and have the standard ones defined by the standard library.  For example, “a”c could be sugar for c(“a”) or something.

We'd re-use the existing FooLiteralConvertible protocols. As suggested in my original email, we can add a way to declare these literals, which could look like

literal operator c {
    type Character

Then when the compiler encounters the literal suffix c, e.g. "a"c, it would treat it the same as the literal "a" constrained to the type Character. Note that "literal" here is a declaration specifier, and not a full keyword.

This syntax does not distinguish between literal type. I think this is ok, because if you say `12c` you'll get the same error you would if you typed `let c: Character = 12`, although we could enhance this instead to say something like "error: type Character does not conform to IntegerLiteralConvertible or FloatLiteralConvertible" instead.

The only real reason to actually distinguish between literal types in this declaration is if we want to be able to reuse the same suffix for different types. My inclination is to say we don't expect there to be enough literal suffixes to bother doing that, and none of the suffixes the standard library wants to define will conflict. We could also explicitly allow this, as long as the declared types do not both conform to the same FooLiteralConvertible protocol (and if they do we'd emit an ambiguity error when used with that literal type, just as we will if two different modules declare the same suffix with different types and a module that imports both uses that suffix). I don't know if it's worth the complication though, as it means we can't know what type the literal "12"x resolves to without knowing the protocol conformances of the set of possible types.

> 3) There are C extensions that support things like _128 as well, should we support numbers?  Keep in mind that integer literals in swift support 123_456_789 separators.

I think we should require that all literal suffixes begin with a letter, and then may use letters and numbers (but not underscores). Or more generally, it should be the same rules used for identifiers except with underscore removed.

We might also establish a convention of using a trailing _ on numeric literals when using literal suffixes, because `12_i32` looks slightly nicer than `12i32`, but I'm not proposing that as a rule (and it won't be legal for string literals because those can't end in _).

> 4) Exactly which ones would we add to Swift out of the box?  Do we go down the C path of only having them for large ones (but ignores “short”)?

I'd propose the following:

Character : c
UnicodeScalar : us
// skip String, an un-suffixed string literal will default to String already
Int : i // or int, or omitted
UInt : u // or uint
Int16 : i16
Int32 : i32
Int64 : i64
UInt16 : u16
UInt32 : u32
UInt64 : u64
Float : f
Float32 : f32
Double : f64 // or maybe d
Float80 : f80

I've included Int but not String, because it feels a little weird to omit Int when all the other numeric types are included. String feels less weird to skip. But we could skip Int because integral literals default to Int if not otherwise constrained.

I don't think we need any more, but I'll list the following for discussion anyway:

Set : s // because array literals default to Array otherwise
// if we allow non-overlapping conflicts we can also do the following:
NSDictionary : ns
NSArray : ns
NSString : ns
// Possibly NSSet : nsset as well, but I feel like that's pushing it a bit

-Kevin Ballard

More information about the swift-evolution mailing list