[swift-evolution] Enums and Source Compatibility

Brent Royal-Gordon brent at architechies.com
Wed Sep 13 07:21:46 CDT 2017

> On Sep 12, 2017, at 6:30 PM, Jordan Rose <jordan_rose at apple.com> wrote:
> It gets a little tricky if layout matters—Optional<AnyObject> fits exactly in a single word-sized value, but Optional<Optional<AnyObject>> does not on Apple platforms—but that just means it should be opt-in or tied to -enable-testing in some way.

I forgot to state this explicitly, but I agree—unless the module was compiled with -enable-testing, the generated code should not permit #invalid values and would be identical to a version without any @testable parameters/types.

Here's a more explicit sketch of a design for this feature (albeit one that has some impact on the type system and a couple weird corners):

	• `@testable T` is a supertype of `T` which, when the module is compiled with `-enable-testing`, has an additional `#invalid` inhabitant. (We can bikeshed `@testable` and `#invalid` some other time.) Notionally, `@testable` is sort of like an enum which has one case (`valid(Wrapped)`) in a non-`-enable-testing` build, and an additional case (`invalid`) in an `-enable-testing` build.

	• `T` implicitly converts to `@testable T`; `@testable T` can be explicitly downcast to `T`.* When `-enable-testing` is *not* provided, these downcasts will always succeed, and the trap in `as!` or the code for a `nil` result from `as?` are unreachable. We should ignore and potentially optimize away this unreachable code without warning about it.

	• Any pattern that matches against `T` can also match against `@testable T` with no alteration. Only `_` or a capture can match `#invalid`.** Otherwise, `#invalid` values will be handled by the `default` case of a `switch` or the `else` block of an `if` or `guard`.

	• A given `@testable T` value (i.e. property, variable, subscript, parameter, return value, etc.) may only be assigned `#invalid` if it is either in the current module or is in a module imported with `@testable import`.

	• When `-enable-testing` is *not* provided, all code which creates an `#invalid` value must be unreachable. This is even true in `default:` cases and other constructs which could be reached by unknown future values of a type. Only constructs like `guard let t = testableT as? T else { return #invalid }` can be successfully compiled with `-enable-testing` disabled.

The memory representation of `#invalid` does not have to be the same for all types, so it could try to find a spare bit or bit pattern that's unused in the original type (as long as, for non-exhaustive enums, it also avoids using any bit pattern a future version of the type *might* use). Or, for simplicity, we could just add a tag byte unconditionally. This tag byte would only be needed when built with `-enable-testing`, so basically only debug builds would pay this price, and only in places where the author explicitly asked to be able to test with `#invalid` values.

* There's an argument to be made for an IUO-style implicit conversion from `@testable Foo` to `Foo` which traps on `#invalid`. This seems dangerous to me, but on the other hand, you should only ever encounter it in testing or development, never in production.

** I'm not sure captures can work here—wouldn't they still be the `@testable` type?—so I'm actually wondering if we should introduce a subtle distinction between `case _`/`case let x` and `default`: the former cannot match `#invalid`, while the latter can. That would be a little bit…odd, though.

Brent Royal-Gordon

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170913/81acfd75/attachment.html>

More information about the swift-evolution mailing list