> On Sep 12, 2017, at 6:30 PM, Jordan Rose <[email protected]> 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
Architechies

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to