Proposal: 'T(literal)' should construct T using the appropriate literal protocol if possible

See, I've made this mistake as well, and *not* because I thought it casts
the literal. I had always assumed that something like `UInt16(42)` would
initialize using the integer literal init, since it seems logical that
that's the most specific. The proposed change reflects my currently
erroneous mental model.

···

On Thu, Jun 2, 2016 at 16:42 Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

> 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.

I've seen developers do this; in one memorable case, it resulted in Swift
taking a ridiculously long time to typecheck an expression, since the
seemingly pinned-down types of the literals had actually become *more*
ambiguous, not less.

However, it's not difficult to teach developers to use `as`. Usually
what's happening is that their mental model of the language is wrong: they
think of `UInt16(foo)` as a cast to a primitive type, and are surprised to
learn that it's actually an initializer on a struct and they're
initializing an instance. Learning this helps them understand how the
language works, what the difference is between initializers and `as`, and
how they can write the same things they see in the standard library types.

I think *actually* turning this into magic would be counterproductive. The
better solution is to make the compiler replace me in that story, by having
it emit a warning with a fix-it. It keeps initializer calls meaning exactly
what they say. (And it doesn't require an evolution proposal to do, since
you can add a warning with a mere bug.)

        UInt16(42)
        ^~~~~~ ^~
        Use of initializer with integer literal does not cast '42' to
'UInt16'
        Fix-It: Replace with '42 as UInt16'

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Well, I understand that it seems like there is some problem with UIntN() initialization, but can't find any simple code that will demonstrate this..

All below works as expected:

var x1: Int32 = 0
var x2 = Int32(0)

print(x1.dynamicType, x2.dynamicType) // Int32 Int32

// integer overflows when converted from 'Int' to 'UInt16'
//var x = UInt16(100_000)
//var x = UInt16(-10)

// negative integer cannot be converted to unsigned type 'UInt64'
// var x = UInt64(-1)

So, what code will produce some unexpected behavior / error at runtime?

···

On 03.06.2016 0:25, John McCall wrote:

On Jun 2, 2016, at 1:56 PM, Vladimir.S <svabox@gmail.com> wrote:

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.

Seems like I'm very slow today.. Could you present a couple of examples where such initialization(like UInt16(7)) can produce some unexpected behavior / error at runtime?

UIntN has unlabeled initializers taking all of the standard integer types, including itself. The literal type will therefore get defaulted to Int. The legal range of values for Int may not be a superset of the legal range of values for UIntN. If the literal is in the legal range for an Int but not for the target type, this might trap at runtime. Now, for a built-in integer type like UInt16, we will recognize that the coercion always traps and emit an error at compile-time, but this generally won't apply to other types.

John.

On 02.06.2016 19:08, John McCall via swift-evolution 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.

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

What is wrong with your examples?

var x1: Int32 = 0
var x2 = Int32(0)
print(x1.dynamicType, x2.dynamicType) // Int32 Int32

···

On 03.06.2016 0:17, Tony Allevato via swift-evolution wrote:

+1. As someone who thought "var x: Int32 = 0" and "var x = Int32(0)" were
equivalent, this is very good to know (and very good to fix).

I'm starting to wonder now if some of the times I've hit "expression was
too complex" errors with large 64-bit multi-term expressions with literals
were caused by coercions happening that I didn't realize.

On Thu, Jun 2, 2016 at 9:31 AM John McCall via swift-evolution > <swift-evolution@swift.org <mailto:swift-evolution@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.

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I don't know what you're saying here. Literal types already do explicitly conform to a protocol that's specific to the literal; it's not just convention. There's nothing linking those protocols because there's no useful operation in common: there is no useful generic code that you can write that works for an arbitrary type that allows some unknown kind of literal. We intentionally do not add protocols that do not serve some useful purpose in generic programming.

John.

···

On Jun 2, 2016, at 10:31 PM, L. Mihalkovic <laurent.mihalkovic@gmail.com> wrote:
On Jun 2, 2016, at 6:08 PM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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.

Looking transversally at all literal protocols as this proposes to operates reminds me that the knowledge that a protocol has the right semantic is based on a convention, rather than on conformance. Would it be conceibable to look into something like the following, that all others would specialize.

protocol LiteralConvertible {}

This might offer a stronger identification than the name. It might also be interesting to define an associated type, but that would exclude NilLiteralConvertible.

Note: as compiler expert, I would appreciate your thinking on the notion of formally expressing what might otherwise be a known strong semantic relationship. Is there any incentive to pursue, known disavantages, ...

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
<Swift.org - API Design Guidelines.
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.

John.

···

On Jun 5, 2016, at 5:18 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
on Thu Jun 02 2016, John McCall <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

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.

I completely agree that this is a problem that we need to solve. In addition to the trap of using [U]Int64 values on 32-bit targets, it is embarrassing that we reject (on all targets):

  let x = UInt64(0x8000_0000_0000_0000)

and require people to use the less obvious syntax:

  let x = 0x1000_0000_0000_0000 as UInt64

Therefore, I propose that we adopt the following typing rule:

I’m sorry of this has already been covered down-thread (just getting caught up now, and haven’t read it all), but this seems like a LOT of magic in the type checker to solve this problem.

It was somewhat covered elsewhere in the thread, but this particular idea is new, I think.

Can’t we just require that literal convertibles implement an initializer that the type checker will already consider to be more specific than any of the other overloads? This would eliminate the need for magic like this in the type checker. Right now, we have this:

public protocol IntegerLiteralConvertible {
  associatedtype IntegerLiteralType : _BuiltinIntegerLiteralConvertible
  init(integerLiteral value: IntegerLiteralType)
}

Change it to be an unlabeled requirement like this probably isn’t enough to make it privileged in the case of ambiguity:

Right.

public protocol IntegerLiteralConvertible {
  associatedtype IntegerLiteralType : _BuiltinIntegerLiteralConvertible
  init(_ value: IntegerLiteralType)
}

but perhaps we could have:

public protocol IntegerLiteralConvertible {
  associatedtype IntegerLiteralType : _BuiltinIntegerLiteralConvertible
  init(integerLiteral value: IntegerLiteralType)
  init<T : IntegerLiteralConvertible>(_ value: T)
}

and get the type checker to consider the later one to be a“more specific” match than the other overloads, when confronted with a literal?

Er. We definitely don't want to say that every integer-literal-convertible type has to be initializable from an arbitrary value of an arbitrary other integer-literal-convertible type. That's not an implementable requirement, nor should it be.

The idea of making a special unlabelled initializer that's preferred by the type-checker came up earlier in the thread; the more workable proposals included declaring it with @literal or changing the type to some magic Literal<T> type. I just don't think it's a good idea because it's still basically the same type-checker magic on the expression side to recognize that we should favor the case, and then it adds extra complexity on the declaration side; plus the different literal protocols start to interfere with each other if one type implements more than one of them.

If you're worried about the complexity of adding new constraints, we could start with a more modest rule that only applies the special case when the callee is syntactically a type reference rather than e.g. an arbitrary expression of metatype value. But changing the ambiguity-resolution rules is actually a lot more complex than just adding a new constraint.

John.

···

On Jun 4, 2016, at 11:54 AM, Chris Lattner <clattner@apple.com> wrote:
On Jun 2, 2016, at 9:08 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I think we should actually go further.

If this proposal is accepted, disallow "as" as a way of specifying the type to construct from a literal.

I see no reason to restrict "as" like this. We're not going to stop using type context as a way of determining the type of a literal, and "as" is a general feature for providing type context.

If this proposal isn't accepted, disallow using literal values as the argument to one-unlabeled-argument constructors.

I can't imagine accepting this, either; it would be a major regression in the usefulness of unlabeled initializers. At best, this would be appropriate only when the type conforms to an appropriate literal protocol.

In general, Swift gives unlabeled initializers an idiomatic meaning: they coerce the argument to the target type in a nominally value-preserving way. One way of viewing this proposal is that it recognizes that there is an obvious way to do that when the target type supports being directly formed from the given kind of literal. But if the target type doesn't support that, coercing a value of some other appropriate literal type is still a completely reasonable behavior.

John.

···

On Jun 2, 2016, at 11:55 AM, Austin Zheng <austinzheng@gmail.com> wrote:

Austin

On Thu, Jun 2, 2016 at 11:46 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:
+1.

The primary advantage is that it aligns the language semantics with how most programmers expect this common C-language-family idiom to behave and removes a potential source of silently wrong code.

The primary disadvantage is that it introduces special-case behavior to certain types of initializers (although, to be fair, this special-case behavior is easily recognizable: unlabeled one-argument initializer with a literal as the argument).

I think the advantage outweighs the disadvantage.

This problem should be addressed one way or another. I prefer this solution, but if it is rejected for whatever reason we should at least explicitly outlaw A(literal) syntax in favor of "literal as A".

Austin

On Thu, Jun 2, 2016 at 9:08 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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.

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

+1.

The primary advantage is that it aligns the language semantics with how most programmers expect this common C-language-family idiom to behave and removes a potential source of silently wrong code.

The primary disadvantage is that it introduces special-case behavior to certain types of initializers (although, to be fair, this special-case behavior is easily recognizable: unlabeled one-argument initializer with a literal as the argument).

I think the advantage outweighs the disadvantage.

Agree. This change basically means the label isn’t intended to be used by callers, but is only present to distinguish the initializer used by the protocol from any other unlabeled initializer accepting the same type. But conceptually it is treated as the *most specific* unlabelled initializer possible, thus winning the overload resolution.

How important is it that we have the ability to distinguish between literals and non-literals with the same type? If that isn’t important, maybe the literal convertible protocols could be reworked such that the label isn’t necessary. That would eliminate the special-case elision of the label.

There is no way to rework the literal protocols so that this behavior just falls out. It's easy enough to convince yourself of this if you try to work through an actual example.

John.

···

On Jun 2, 2016, at 12:10 PM, Matthew Johnson <matthew@anandabits.com> wrote:

On Jun 2, 2016, at 1:46 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

This problem should be addressed one way or another. I prefer this solution, but if it is rejected for whatever reason we should at least explicitly outlaw A(literal) syntax in favor of "literal as A".

Austin

On Thu, Jun 2, 2016 at 9:08 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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.

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

+1 to the proposal. I can also see the argument for disallowing multiple
ways of doing the same thing, though disallowing the use of `as` in this
way might be introducing another special case.

If the proposal is accepted, I'd also advocate for the suggestion in the
initial proposal to apply the rule regardless of the number of parentheses,
so that `A((B))` behaves the same way as `A(B)`.

···

On Thu, Jun 2, 2016 at 1:55 PM, Austin Zheng via swift-evolution < swift-evolution@swift.org> wrote:

I think we should actually go further.

If this proposal is accepted, disallow "as" as a way of specifying the
type to construct from a literal.

If this proposal isn't accepted, disallow using literal values as the
argument to one-unlabeled-argument constructors.

In either case we should disabuse users of the notion that A(literal) is
an initializer that behaves exactly the same as other initializers.

Austin

On Thu, Jun 2, 2016 at 11:46 AM, Austin Zheng <austinzheng@gmail.com> > wrote:

+1.

The primary advantage is that it aligns the language semantics with how
most programmers expect this common C-language-family idiom to behave and
removes a potential source of silently wrong code.

The primary disadvantage is that it introduces special-case behavior to
certain types of initializers (although, to be fair, this special-case
behavior is easily recognizable: unlabeled one-argument initializer with a
literal as the argument).

I think the advantage outweighs the disadvantage.

This problem should be addressed one way or another. I prefer this
solution, but if it is rejected for whatever reason we should at least
explicitly outlaw A(literal) syntax in favor of "literal as A".

Austin

On Thu, Jun 2, 2016 at 9:08 AM, John McCall via swift-evolution < >> swift-evolution@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.

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I think we should actually go further.

If this proposal is accepted, disallow "as" as a way of specifying the type to construct from a literal.

I’m not sure. I agree it would be bad style to use `as` here. But I’m not sure if it should be banned or not.

If this proposal isn't accepted, disallow using literal values as the argument to one-unlabeled-argument constructors.

I don’t know about this. It says that if you want users to initialize your type with an unlabeled argument that has a type which has a corresponding literal you *must* conform to the corresponding literal convertible protocol. Do we really want to require that? Maybe, but maybe not. We definitely *should not* allow such an initializer to be written if it cannot be called with a literal.

For example, if I have:

`init(_ a: [String])`

users could call the initializer with an array variable but not a array literal. That would be very bad IMO. Either this initializer is banned altogether, or we allow it to be called with a literal.

FWIW, there are types in the standard library with overlapping initializers in this new model:

public init(_ value: UInt8)
public init(integerLiteral value: UInt8)

I’m not sure whether they actually need to do different things or whether they are just provided so you can initialize the type with a variable and also use literals in a context expecting that type.

I think it’s important to understand whether overlap like this is necessary or not. If behavior should always be identical I am in favor of refactoring the literal convertible protocols as part of this change.

···

On Jun 2, 2016, at 1:55 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

In either case we should disabuse users of the notion that A(literal) is an initializer that behaves exactly the same as other initializers.

Austin

On Thu, Jun 2, 2016 at 11:46 AM, Austin Zheng <austinzheng@gmail.com <mailto:austinzheng@gmail.com>> wrote:
+1.

The primary advantage is that it aligns the language semantics with how most programmers expect this common C-language-family idiom to behave and removes a potential source of silently wrong code.

The primary disadvantage is that it introduces special-case behavior to certain types of initializers (although, to be fair, this special-case behavior is easily recognizable: unlabeled one-argument initializer with a literal as the argument).

I think the advantage outweighs the disadvantage.

This problem should be addressed one way or another. I prefer this solution, but if it is rejected for whatever reason we should at least explicitly outlaw A(literal) syntax in favor of "literal as A".

Austin

On Thu, Jun 2, 2016 at 9:08 AM, John McCall via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@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.

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Well, I understand that it seems like there is some problem with UIntN() initialization, but can't find any simple code that will demonstrate this..

All below works as expected:

var x1: Int32 = 0
var x2 = Int32(0)

print(x1.dynamicType, x2.dynamicType) // Int32 Int32

// integer overflows when converted from 'Int' to 'UInt16'
//var x = UInt16(100_000)
//var x = UInt16(-10)

// negative integer cannot be converted to unsigned type 'UInt64'
// var x = UInt64(-1)

So, what code will produce some unexpected behavior / error at runtime?

Like I said, the standard library and compiler conspire to make sure that easy cases like this are caught at compile time, but that would not help non-standard types that conform to IntegerLiteralConvertible.

Also, even for standard types, the syntax only works statically if the literal fits in the range of Int, which may not be a superset of the desired type. For example, UInt64(0x10000000000) would not work on a 32-bit platform. It is diagnosed statically, however.

John.

···

On Jun 2, 2016, at 2:36 PM, Vladimir.S <svabox@gmail.com> wrote:

On 03.06.2016 0:25, John McCall wrote:

On Jun 2, 2016, at 1:56 PM, Vladimir.S <svabox@gmail.com> wrote:

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.

Seems like I'm very slow today.. Could you present a couple of examples where such initialization(like UInt16(7)) can produce some unexpected behavior / error at runtime?

UIntN has unlabeled initializers taking all of the standard integer types, including itself. The literal type will therefore get defaulted to Int. The legal range of values for Int may not be a superset of the legal range of values for UIntN. If the literal is in the legal range for an Int but not for the target type, this might trap at runtime. Now, for a built-in integer type like UInt16, we will recognize that the coercion always traps and emit an error at compile-time, but this generally won't apply to other types.

John.

On 02.06.2016 19:08, John McCall via swift-evolution 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.

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

+1.

The primary advantage is that it aligns the language semantics with how most programmers expect this common C-language-family idiom to behave and removes a potential source of silently wrong code.

The primary disadvantage is that it introduces special-case behavior to certain types of initializers (although, to be fair, this special-case behavior is easily recognizable: unlabeled one-argument initializer with a literal as the argument).

I think the advantage outweighs the disadvantage.

Agree. This change basically means the label isn’t intended to be used by callers, but is only present to distinguish the initializer used by the protocol from any other unlabeled initializer accepting the same type. But conceptually it is treated as the *most specific* unlabelled initializer possible, thus winning the overload resolution.

How important is it that we have the ability to distinguish between literals and non-literals with the same type? If that isn’t important, maybe the literal convertible protocols could be reworked such that the label isn’t necessary. That would eliminate the special-case elision of the label.

There is no way to rework the literal protocols so that this behavior just falls out. It's easy enough to convince yourself of this if you try to work through an actual example.

Ok, I'll trust you on this point. +1 on the idea as you proposed it.

···

Sent from my iPad

On Jun 2, 2016, at 3:41 PM, John McCall <rjmccall@apple.com> wrote:

On Jun 2, 2016, at 12:10 PM, Matthew Johnson <matthew@anandabits.com> wrote:
On Jun 2, 2016, at 1:46 PM, Austin Zheng via swift-evolution <swift-evolution@swift.org> wrote:

John.

This problem should be addressed one way or another. I prefer this solution, but if it is rejected for whatever reason we should at least explicitly outlaw A(literal) syntax in favor of "literal as A".

Austin

On Thu, Jun 2, 2016 at 9:08 AM, John McCall via swift-evolution <swift-evolution@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.

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

So, you think that this syntax is enticing to new developers who naturally think that the feature works the way that I'm proposing it should work, and you think that the right solution is to make the syntax illegal so that you can more conveniently tell them it doesn't work that way? :)

I think the difference between a cast (which merely reinterprets a value as a compatible type) and a fullwidth conversion (which creates a similar instance of an incompatible type) is very important to understanding how to write Swift, and we shouldn't muddy the waters by creating a magic syntax.

You can still tell them that it's a struct and you're calling an initializer on it; it's just that the initializer chosen is the special literal initializer because the argument is a literal.

If you're planning to change `IntegerLiteralConvertible` and friends to require a fullwidth conversion initializer like `init(_ value: IntegerLiteralType)`, then this is simply an overload resolution rule. In that case, I think your proposal is fine.

But if you're going to call `init(integerLiteral:)` like it's `init(_:)`, I don't think that's a good idea. Parameter labels are supposed to be significant; we don't want to lose that.

···

--
Brent Royal-Gordon
Architechies

+1
It has nagged at me that this can happen even though I understood *why* it
happens.

···

On Thu, Jun 2, 2016 at 6:22 PM, John McCall via swift-evolution < swift-evolution@swift.org> wrote:

> On Jun 2, 2016, at 2:43 PM, Brent Royal-Gordon <brent@architechies.com> > wrote:
>> 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.
>
> I've seen developers do this; in one memorable case, it resulted in
Swift taking a ridiculously long time to typecheck an expression, since the
seemingly pinned-down types of the literals had actually become *more*
ambiguous, not less.

Yes, this would also tend to improve compile times, since currently we end
up generating a massively-ambiguous constraint system which must be
resolved by type defaulting. That's not really why I'm proposing this,
though.

> However, it's not difficult to teach developers to use `as`. Usually
what's happening is that their mental model of the language is wrong: they
think of `UInt16(foo)` as a cast to a primitive type, and are surprised to
learn that it's actually an initializer on a struct and they're
initializing an instance. Learning this helps them understand how the
language works, what the difference is between initializers and `as`, and
how they can write the same things they see in the standard library types.

So, you think that this syntax is enticing to new developers who naturally
think that the feature works the way that I'm proposing it should work, and
you think that the right solution is to make the syntax illegal so that you
can more conveniently tell them it doesn't work that way? :)

You can still tell them that it's a struct and you're calling an
initializer on it; it's just that the initializer chosen is the special
literal initializer because the argument is a literal.

John.

>
> I think *actually* turning this into magic would be counterproductive.
The better solution is to make the compiler replace me in that story, by
having it emit a warning with a fix-it. It keeps initializer calls meaning
exactly what they say. (And it doesn't require an evolution proposal to do,
since you can add a warning with a mere bug.)
>
> UInt16(42)
> ^~~~~~ ^~
> Use of initializer with integer literal does not cast '42' to
'UInt16'
> Fix-It: Replace with '42 as UInt16'
>
> --
> Brent Royal-Gordon
> Architechies
>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

What is wrong with your examples?

var x1: Int32 = 0
var x2 = Int32(0)
print(x1.dynamicType, x2.dynamicType) // Int32 Int32

I was referring to the subtle distinction between creating an Int32 from a
literal (the first one) and creating an Int from a literal and then
coercing it to Int32 (the second one). So, I was pondering whether this was
the cause of some complex expressions I've had problems with in the past.
However, looking at the specific code, it looks like I had the *opposite*
problem.

This expression is evaluated quickly in Swift 2.2:

let value = Int64((0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) |
(0x3b << 28) |
      (0x56 << 35) | (0x00 << 42) | (0x05 << 49) | (0x26 << 56) | (0x01 <<
63))

This one errors out with "expression was too complex to be solved in
reasonable time":

let value: Int64 = (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21)

(0x3b << 28) |

      (0x56 << 35) | (0x00 << 42) | (0x05 << 49) | (0x26 << 56) | (0x01 <<
63)

···

On Thu, Jun 2, 2016 at 2:38 PM Vladimir.S <svabox@gmail.com> wrote:

On 03.06.2016 0:17, Tony Allevato via swift-evolution wrote:
> +1. As someone who thought "var x: Int32 = 0" and "var x = Int32(0)" were
> equivalent, this is very good to know (and very good to fix).
>
> I'm starting to wonder now if some of the times I've hit "expression was
> too complex" errors with large 64-bit multi-term expressions with
literals
> were caused by coercions happening that I didn't realize.
>
>
> On Thu, Jun 2, 2016 at 9:31 AM John McCall via swift-evolution > > <swift-evolution@swift.org <mailto:swift-evolution@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.
>
> 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 <mailto:swift-evolution@swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>

Like I said, the standard library and compiler conspire to make sure

that easy cases like this are caught at compile time, but that would not help non-standard types that conform to IntegerLiteralConvertible.
>
> Also, even for standard types, the syntax only works statically if the literal fits in the range of Int, which may not be a superset of the desired type. For example, UInt64(0x10000000000) would not work on a 32-bit platform. It is diagnosed statically, however.

I believe I understand the problem you described, but I really can't figure out how this problem can produce a unexpected behavior and run-time errors, as was stated in your initial message. So, this is why I was asking for any code that can prove this. The example with UInt64(0x10000000000) on 32bit systems will raise error at _compilation_ time. Could someone provide any code to illustrate the possible problems at run-time? I understand that we need to fix this somehow in any case.

For others, who not really understand the issue(probably I'm not the one, I hope ;-) ) : If we'd have Int128 type, we can't create an instance of it in this form:
let x = Int128(92233720368547758070)
(92233720368547758070 == Int.max * 10)
as '92233720368547758070' literal will be treated always as of Int type.

In more general description, the difference between UIntN(xxx) and yyy as UIntN is that xxx will be treated as Int(so it can't be greater than Int.max for example) then this Int will be sent to UIntN(:Int) initializer and then we have UIntN as a result of call to initializer; yyy will be treated as UIntN literal just by its definition, no calling to any initializer, yyy can be of any value allowed for UIntN.

But again, could someone help with examples when this can produce problems at run-time?

···

On 03.06.2016 1:12, John McCall wrote:

On Jun 2, 2016, at 2:36 PM, Vladimir.S <svabox@gmail.com> wrote:
Well, I understand that it seems like there is some problem with UIntN() initialization, but can't find any simple code that will demonstrate this..

All below works as expected:

var x1: Int32 = 0
var x2 = Int32(0)

print(x1.dynamicType, x2.dynamicType) // Int32 Int32

// integer overflows when converted from 'Int' to 'UInt16'
//var x = UInt16(100_000)
//var x = UInt16(-10)

// negative integer cannot be converted to unsigned type 'UInt64'
// var x = UInt64(-1)

So, what code will produce some unexpected behavior / error at runtime?

Like I said, the standard library and compiler conspire to make sure that easy cases like this are caught at compile time, but that would not help non-standard types that conform to IntegerLiteralConvertible.

Also, even for standard types, the syntax only works statically if the literal fits in the range of Int, which may not be a superset of the desired type. For example, UInt64(0x10000000000) would not work on a 32-bit platform. It is diagnosed statically, however.

John.

On 03.06.2016 0:25, John McCall wrote:

On Jun 2, 2016, at 1:56 PM, Vladimir.S <svabox@gmail.com> wrote:

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.

Seems like I'm very slow today.. Could you present a couple of examples where such initialization(like UInt16(7)) can produce some unexpected behavior / error at runtime?

UIntN has unlabeled initializers taking all of the standard integer types, including itself. The literal type will therefore get defaulted to Int. The legal range of values for Int may not be a superset of the legal range of values for UIntN. If the literal is in the legal range for an Int but not for the target type, this might trap at runtime. Now, for a built-in integer type like UInt16, we will recognize that the coercion always traps and emit an error at compile-time, but this generally won't apply to other types.

John.

On 02.06.2016 19:08, John McCall via swift-evolution 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.

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

.

I was referring to the subtle distinction between creating an Int32 from a

> literal (the first one) and creating an Int from a literal and then
> coercing it to Int32 (the second one).

I understand the difference and just trying to find out if that behavior(Int32(x) vs x as Int32) could really produce problems at run-time as was stated in initial message for this proposal.

···

On 03.06.2016 0:57, Tony Allevato wrote:

On Thu, Jun 2, 2016 at 2:38 PM Vladimir.S <svabox@gmail.com > <mailto:svabox@gmail.com>> wrote:

    What is wrong with your examples?

    var x1: Int32 = 0
    var x2 = Int32(0)
    print(x1.dynamicType, x2.dynamicType) // Int32 Int32

I was referring to the subtle distinction between creating an Int32 from a
literal (the first one) and creating an Int from a literal and then
coercing it to Int32 (the second one). So, I was pondering whether this was
the cause of some complex expressions I've had problems with in the past.
However, looking at the specific code, it looks like I had the *opposite*
problem.

This expression is evaluated quickly in Swift 2.2:

let value = Int64((0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) |
(0x3b << 28) |
      (0x56 << 35) | (0x00 << 42) | (0x05 << 49) | (0x26 << 56) | (0x01 << 63))

This one errors out with "expression was too complex to be solved in
reasonable time":

let value: Int64 = (0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21)
> (0x3b << 28) |
      (0x56 << 35) | (0x00 << 42) | (0x05 << 49) | (0x26 << 56) | (0x01 << 63)

    On 03.06.2016 0:17, Tony Allevato via swift-evolution wrote:
    > +1. As someone who thought "var x: Int32 = 0" and "var x = Int32(0)" were
    > equivalent, this is very good to know (and very good to fix).
    >
    > I'm starting to wonder now if some of the times I've hit "expression was
    > too complex" errors with large 64-bit multi-term expressions with
    literals
    > were caused by coercions happening that I didn't realize.
    >
    > On Thu, Jun 2, 2016 at 9:31 AM John McCall via swift-evolution > > <swift-evolution@swift.org <mailto:swift-evolution@swift.org> > <mailto:swift-evolution@swift.org <mailto:swift-evolution@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.
    >
    > 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 <mailto:swift-evolution@swift.org>
    <mailto:swift-evolution@swift.org <mailto:swift-evolution@swift.org>>
    > https://lists.swift.org/mailman/listinfo/swift-evolution
    >
    > _______________________________________________
    > swift-evolution mailing list
    > swift-evolution@swift.org <mailto:swift-evolution@swift.org>
    > https://lists.swift.org/mailman/listinfo/swift-evolution
    >

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.

Looking transversally at all literal protocols as this proposes to operates reminds me that the knowledge that a protocol has the right semantic is based on a convention, rather than on conformance. Would it be conceibable to look into something like the following, that all others would specialize.

protocol LiteralConvertible {}

This might offer a stronger identification than the name. It might also be interesting to define an associated type, but that would exclude NilLiteralConvertible.

I don't know what you're saying here. Literal types already do explicitly conform to a protocol that's specific to the literal; it's not just convention.

Sorry for my lack of clarity. Each literal type (string/int/array...) is independent from the others, forming a 'set' only in appearance. And in the compiler, they must all be declared individually in knownprotocols.def. Which then means that these are the only literals, which the compiler knows and can reason about now and for ever. And then if users write a hypothetical XyzLiteralConvertible, there is no chance for the compiler to ever treat it like the builins.

There's nothing linking those protocols because there's no useful operation in common: there is no useful generic code that you can write that works for an arbitrary type that allows some unknown kind of literal.

I believe I understand. I come from a heavy java/scala background, with lots of reflection/invokedynamic/bytecode work, so it looked like a bit of missed opportunities for some future proofing of the runtime. Particularly being able to discover at compile-time additional protocols than those listed statically in knownprotocols.def

We intentionally do not add protocols that do not serve some useful purpose in generic programming.

I understand and respecfully point to a discrepancy with this logic: the existance of the _builtinxxx underlying layer. To me it says that some protocols (there are others) do carry a compiler oriented semantic, and when i found it i took it as the sign that the team was willing to consider that it was worth using the language constructs to encode some of its semantics, in the name of creating future proofing or simplifying the compiler source. But your answer is clear, and I now understand it as: _builtinxxxliteral is an exceptional case, not necessarily a design principle or future proofing tool.

Note: as compiler expert, I would appreciate your thinking on the notion of formally expressing what might otherwise be a known strong semantic relationship. Is there any incentive to pursue, known disavantages, ...

This part was an extension on future proofing another part of the library where there is IMHO a real incentive for formalizing what exists today, considering it will likely never change and is already a source of questions. I am still trying to find a formalism that would align with how the library and compiler are designed, so it will be its own topic. Thank you for taking the time.

···

On Jun 3, 2016, at 8:51 PM, John McCall <rjmccall@apple.com> wrote:

On Jun 2, 2016, at 10:31 PM, L. Mihalkovic <laurent.mihalkovic@gmail.com> wrote:

On Jun 2, 2016, at 6:08 PM, John McCall via swift-evolution <swift-evolution@swift.org> wrote:

John.

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
<Swift.org - API Design Guidelines.
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.

···

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@swift.org> wrote:
on Thu Jun 02 2016, John McCall <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

--
Dave

+1 to the proposal. I can also see the argument for disallowing multiple
ways of doing the same thing, though disallowing the use of `as` in this
way might be introducing another special case.

The "as", "as?" and "as!" operators in Swift are already surprisingly
overloaded. Joe Groff's proposal lists 9 (!!!) different things "as?" does
here:

.

"as" is also overloaded in this sense. It performs bridging casts (soon to
go away?), upcasts that can never fail (e.g. subclass to superclass), and
defining the concrete type of a literal expression. It wouldn't be a big
loss for "as" to lose the last meaning.

If the proposal is accepted, I'd also advocate for the suggestion in the
initial proposal to apply the rule regardless of the number of parentheses,
so that `A((B))` behaves the same way as `A(B)`.

+1. Yes please.

···

On Thu, Jun 2, 2016 at 12:11 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Thu, Jun 2, 2016 at 1:55 PM, Austin Zheng via swift-evolution < > swift-evolution@swift.org> wrote:

I think we should actually go further.

If this proposal is accepted, disallow "as" as a way of specifying the
type to construct from a literal.

If this proposal isn't accepted, disallow using literal values as the
argument to one-unlabeled-argument constructors.

In either case we should disabuse users of the notion that A(literal) is
an initializer that behaves exactly the same as other initializers.

Austin

On Thu, Jun 2, 2016 at 11:46 AM, Austin Zheng <austinzheng@gmail.com> >> wrote:

+1.

The primary advantage is that it aligns the language semantics with how
most programmers expect this common C-language-family idiom to behave and
removes a potential source of silently wrong code.

The primary disadvantage is that it introduces special-case behavior to
certain types of initializers (although, to be fair, this special-case
behavior is easily recognizable: unlabeled one-argument initializer with a
literal as the argument).

I think the advantage outweighs the disadvantage.

This problem should be addressed one way or another. I prefer this
solution, but if it is rejected for whatever reason we should at least
explicitly outlaw A(literal) syntax in favor of "literal as A".

Austin

On Thu, Jun 2, 2016 at 9:08 AM, John McCall via swift-evolution < >>> swift-evolution@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.

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

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution