> 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