<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Sep 13, 2017, at 05:21, Brent Royal-Gordon &lt;<a href="mailto:brent@architechies.com" class="">brent@architechies.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class="">On Sep 12, 2017, at 6:30 PM, Jordan Rose &lt;<a href="mailto:jordan_rose@apple.com" class="">jordan_rose@apple.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><span style="font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; word-spacing: 0px; -webkit-text-stroke-width: 0px; float: none; display: inline !important;" class="">It gets a little tricky if layout matters—Optional&lt;AnyObject&gt; fits exactly in a single word-sized value, but Optional&lt;Optional&lt;AnyObject&gt;&gt; does not on Apple platforms—but that just means it should be opt-in or tied to -enable-testing in some way.</span></div></blockquote></div><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class="">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):</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>• `@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.</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>• `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.</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>• 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`.</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>• 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`.</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>• 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.</div><div class=""><br class=""></div><div class="">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.</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">* 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.</div><div class=""><br class=""></div><div class="">** 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.</div></div></div></blockquote><br class=""></div><div>Thanks for working this out. This matches the intuitions I was having, and also finds a point that’s pretty concerning:</div><div><br class=""></div><div><blockquote type="cite" class=""><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;"><div class="">* 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.</div></div></blockquote><div><br class=""></div>It’s going to be very common to have a future value and hand it right back to the framework without looking at it, for example:</div><div><br class=""></div><blockquote style="margin: 0 0 0 40px; border: none; padding: 0px;" class=""><div>override func process(_ transaction: @testable Transaction) {</div><div>&nbsp; switch transaction {</div><div>&nbsp; case .deposit(let amount):</div><div>&nbsp; &nbsp; // …</div><div>&nbsp; case .withdrawal(let amount):</div><div>&nbsp; &nbsp; // …</div><div>&nbsp; default:</div><div>&nbsp; &nbsp; super.process(transaction) // hmm…</div><div>&nbsp; }</div><div>}</div></blockquote><div><div class="" style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;"><div class=""><br class=""></div><div class="">So just making it to the ‘default’ case doesn’t guarantee that it’s testable in practice.</div><div class=""><br class=""></div></div></div>In any case, a model like this can be added later without breaking source or binary compatibility, so I think I’m going to leave it out of the proposal for now. I’ll mention it in “Alternatives considered”.<div class=""><br class=""></div><div class="">Jordan</div></body></html>