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.

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@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to