> On Sep 13, 2017, at 05:21, Brent Royal-Gordon <[email protected]> wrote:
>
>> On Sep 12, 2017, at 6:30 PM, Jordan Rose <[email protected]
>> <mailto:[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.
Thanks for working this out. This matches the intuitions I was having, and also
finds a point that’s pretty concerning:
> * 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.
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:
override func process(_ transaction: @testable Transaction) {
switch transaction {
case .deposit(let amount):
// …
case .withdrawal(let amount):
// …
default:
super.process(transaction) // hmm…
}
}
So just making it to the ‘default’ case doesn’t guarantee that it’s testable in
practice.
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”.
Jordan_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution