[swift-evolution] [Proposal] Type Narrowing

Callionica (Swift) swift-callionica at callionica.com
Thu Nov 3 12:47:07 CDT 2016


Great. I'd like to see something like this. Couple of comments:

You don't explicitly address overload selection. Do you intend it to
be affected?

Impact on existing code section could be more descriptive.

On Thursday, November 3, 2016, Haravikk via swift-evolution <
swift-evolution at swift.org> wrote:

> To avoid hijacking the guard let x = x thread entirely I've decided to try
> to write up a proposal on type narrowing in Swift.
> Please give your feedback on the functionality proposed, as well as the
> clarity of the proposal/examples themselves; I've tried to keep it
> straightforward, but I do tend towards being overly verbose, I've always
> tried to have the examples build upon one another to show how it all stacks
> up.
>
>
>
> Type Narrowing
>
>    - Proposal: SE-NNNN
>    <https://github.com/Haravikk/swift-evolution/blob/master/proposals/NNNN-type-narrowing.md>
>    - Author: Haravikk <https://github.com/haravikk>
>    - Status: Awaiting review
>    - Review manager: TBD
>
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#introduction>
> Introduction
>
> This proposal is to introduce type-narrowing to Swift, enabling the
> type-checker to automatically infer a narrower type from context such as
> conditionals.
>
> Swift-evolution thread: Discussion thread topic for that proposal
> <http://news.gmane.org/gmane.comp.lang.swift.evolution>
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#motivation>
> Motivation
>
> Currently in Swift there are various pieces of boilerplate required in
> order to manually narrow types. The most obvious is in the case of
> polymorphism:
>
> let foo:A = B() // B extends A
> if foo is B {
>     (foo as B).someMethodSpecificToB()
> }
>
> But also in the case of unwrapping of optionals:
>
> var foo:A? = A()
> if var foo = foo { // foo is now unwrapped and shadowed
>     foo.someMethod()
>     foo!.someMutatingMethod() // Can't be done
> }
>
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#proposed-solution>Proposed
> solution
>
> The proposed solution to the boiler-plate is to introduce type-narrowing,
> essentially a finer grained knowledge of type based upon context. Thus as
> any contextual clue indicating a more or less specific type are
> encountered, the type of the variable will reflect this from that point
> onwards.
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#detailed-design>Detailed
> design
>
> The concept of type-narrowing would essentially treat all variables as
> having not just a single type, but instead as having a stack of
> increasingly specific (narrow) types.
>
> Whenever a contextual clue such as a conditional is encountered, the type
> checker will infer whether this narrows the type, and add the new narrow
> type to the stack from that point onwards. Whenever the type widens again
> narrower types are popped from the stack.
>
> Here are the above examples re-written to take advantage of type-narrowing:
>
> let foo:A = B() // B extends A
> if foo is B { // B is added to foo's type stack
>     foo.someMethodSpecificToB()
> }
> // B is popped from foo's type stack
>
> var foo:A? = A()
> if foo != nil { // Optional<A>.some is added to foo's type stack
>    foo.someMethod()
>    foo.someMutatingMethod() // Can modify mutable original
> }
> // Optional<A>.some is popped from foo's type stack
>
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#enum-types>Enum
> Types
>
> As seen in the simple optional example, to implement optional support each
> case in an enum is considered be a unique sub-type of the enum itself,
> thus allowing narrowing to nil (.none) and non-nil (.some) types.
>
> This behaviour actually enables some other useful behaviours,
> specifically, if a value is known to be either nil or non-nil then the
> need to unwrap or force unwrap the value can be eliminated entirely, with
> the compiler able to produce errors if these are used incorrectly, for
> example:
>
> var foo:A? = A()
> foo.someMethod() // A is non-nil, no operators required!
> foo = nil
> foo!.someMethod() // Error: foo is always nil at this point
>
> However, unwrapping of the value is only possible if the case contains
> either no value at all, or contains a single value able to satisfy the
> variable's original type requirements. In other words, the value stored in
> Optional<A>.some satisfies the type requirements of var foo:A?, thus it
> is implicitly unwrapped for use. For general enums this likely means no
> cases are implicitly unwrapped unless using a type of Any.
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#type-widening>Type
> Widening
>
> In some cases a type may be narrowed, only to be used in a way that makes
> no sense for the narrowed type. In cases such as these the operation is
> tested against each type in the stack to determine whether the type must
> instead be widened. If a widened type is found it is selected (with
> re-narrowing where possible) otherwise an error is produced as normal.
>
> For example:
>
> let foo:A? = A()
> if (foo != nil) { // Type of foo is Optional<A>.some
>     foo.someMethod()
>     foo = nil // Type of foo is widened to Optional<A>, then re-narrowed to Optional<A>.none
> } // Type of foo is Optional<A>.none
> foo.someMethod() // Error: foo is always nil at this point
>
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#multiple-conditions-and-branching>Multiple
> Conditions and Branching
>
> When dealing with complex conditionals or branches, all paths must agree
> on a common type for narrowing to occur. For example:
>
> let foo:A? = B() // B extends A
> let bar:C = C() // C extends B
>
> if (foo != nil) || (foo == bar) { // Optional<A>.some is added to foo's type stack
>     if foo is B { // Optional<B>.some is added to foo's type stack
>         foo.someMethodSpecificToB()
>     } // Optional<B>.some is popped from foo's type stack
>     foo = nil // Type of foo is re-narrowed as Optional<A>.none
> } // Type of foo is Optional<A>.none in all branches
> foo.someMethod() // Error: foo is always nil at this point
>
> Here we can see that the extra condition (foo == bar) does not prevent
> type-narrowing, as the variable bar cannot be nil so both conditions
> require a type of Optional<A>.some as a minimum.
>
> In this example foo is also nil at the end of both branches, thus its
> type can remain narrowed past this point.
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#context-triggers>Context
> Triggers
> TriggerImpact
> as Explicitly narrows a type with as! failing and as? narrowing to Type? instead
> when this is not possible.
> is Anywhere a type is tested will allow the type-checker to infer the new
> type if there was a match (and other conditions agree).
> case Any form of exhaustive test on an enum type allows it to be narrowed
> either to that case or the opposite, e.g- foo != nil eliminates .none,
> leaving only .some as the type, which can then be implicitly unwrapped
> (see Enum Types above).
> = Assigning a value to a type will either narrow it if the new value is a
> sub-type, or will trigger widening to find a new common type, before
> attempting to re-narrow from there.
>
> There may be other triggers that should be considered.
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#impact-on-existing-code>Impact
> on existing code
>
> Although this change is technically additive, it will impact any code in
> which there are currently errors that type-narrowing would have detected;
> for example, attempting to manipulate a predictably nil value.
>
> <https://github.com/Haravikk/swift-evolution/tree/master/proposals#alternatives-considered>Alternatives
> considered
> One of the main advantages of type-narrowing is that it functions as an
> alternative to other features. This includes alternative syntax for
> shadowing/unwrapping of optionals, in which case type-narrowing allows an
> optional to be implicitly unwrapped simply by testing it, and without the
> need to introduce any new syntax.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161103/56f575be/attachment.html>


More information about the swift-evolution mailing list