[swift-evolution] Proposal: 'T(literal)' should construct T using the appropriate literal protocol if possible

John McCall rjmccall at apple.com
Wed Jun 8 12:23:04 CDT 2016

> On Jun 7, 2016, at 4:25 PM, Dave Abrahams <dabrahams at apple.com> wrote:
> on Tue Jun 07 2016, John McCall <rjmccall-AT-apple.com> wrote:
>>> On Jun 5, 2016, at 5:18 PM, Dave Abrahams via swift-evolution <swift-evolution at swift.org> wrote:
>>> on Thu Jun 02 2016, John McCall <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>>> The official way to build a literal of a specific type is to write the
>>>> literal in an explicitly-typed context, like so:
>>>>   let x: UInt16 = 7
>>>> or
>>>>   let x = 7 as UInt16
>>>> Nonetheless, programmers often try the following:
>>>>   UInt16(7)
>>>> Unfortunately, this does not attempt to construct the value using the
>>>> appropriate literal protocol; it instead performs overload resolution
>>>> using the standard rules, i.e. considering only single-argument
>>>> unlabelled initializers of a type which conforms to
>>>> IntegerLiteralConvertible.  Often this leads to static ambiguities or,
>>>> worse, causes the literal to be built using a default type (such as
>>>> Int); this may have semantically very different results which are only
>>>> caught at runtime.
>>>> In my opinion, using this initializer-call syntax to build an
>>>> explicitly-typed literal is an obvious and natural choice with several
>>>> advantages over the "as" syntax.  However, even if you disagree, it's
>>>> clear that programmers are going to continue to independently try to
>>>> use it, so it's really unfortunate for it to be subtly wrong.
>>>> Therefore, I propose that we adopt the following typing rule:
>>>> Given a function call expression of the form A(B) (that is, an
>>>> expr-call with a single, unlabelled argument) where B is an
>>>> expr-literal or expr-collection, if A has type T.Type for some type T
>>>> and there is a declared conformance of T to an appropriate literal
>>>> protocol for B, then the expression is always resolves as a literal
>>>> construction of type T (as if the expression were written "B as A")
>>>> rather than as a general initializer call.
>>>> Formally, this would be a special form of the argument conversion
>>>> constraint, since the type of the expression A may not be immediately
>>>> known.
>>> I realize this is somewhat tangential, but... IMO this may not be entirely
>>> about literals.
>>> We have a standard that full-width type conversions are written as a
>>> label-free initializer
>>> <https://swift.org/documentation/api-design-guidelines/#type-conversion <https://swift.org/documentation/api-design-guidelines/#type-conversion>>.
>>> I believe that is partly responsible for setting up the expectation that
>>> Int(42) works as one would expect.  It gets ultra-weird when you can
>>> convert from type A to type B using B(someA) but you can't write
>>> B(someB).  We should automatically generate a label-free “copy
>>> initializer” for value types, to complete implementation of the expected
>>> mental model.
>> That may also be a good idea, but it won't magically be preferred for
>> literal construction if the type has any other constructors of
>> literal-convertible type.
> I know.  I'm saying, fixing this for literals without giving value types
> copy initializers leaves us with only a partial realization of a larger
> mental model to which I believe people are programming.

That's fair.  Would you like me to incorporate that into my proposal, then?  I see the relation, but it's a pretty significant jump in scope.


>>>> Note that, as specified, it is possible to suppress this typing rule
>>>> by wrapping the literal in parentheses.  This might seem distasteful;
>>>> it would be easy enough to allow the form of B to include extra
>>>> parentheses.  It's potentially useful to have a way to suppress this
>>>> rule and get a normal construction, but there are several other ways
>>>> of getting that effect, such as explicitly typing the literal argument
>>>> (e.g. writing "A(Int(B))").
>>>> A conditional conformance counts as a declared conformance even if the
>>>> generic arguments are known to not satisfy the conditional
>>>> conformance.  This permits the applicability of the rule to be decided
>>>> without having to first decide the type arguments, which greatly
>>>> simplifies the type-checking problem (and may be necessary for
>>>> soundness; I didn't explore this in depth, but it certainly feels like
>>>> a very nasty sort of dependence).  We could potentially weaken this
>>>> for cases where A is a direct type reference with bound parameters,
>>>> e.g. Foo<Int>([]) or the same with a typealias, but I think there's
>>>> some benefit from having a simpler specification, both for the
>>>> implementation and for the explicability of the model.
>>>> John.
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>> -- 
>>> -Dave
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
> -- 
> Dave

More information about the swift-evolution mailing list