[swift-evolution] [Proposal] Explicit Synthetic Behaviour

Haravikk swift-evolution at haravikk.me
Wed Sep 6 03:32:16 CDT 2017

> On 6 Sep 2017, at 01:36, Robert Bennett <rltbennett at icloud.com <mailto:rltbennett at icloud.com>> wrote:
> I take issue with the fact that this problem is no different from accidentally gaining the default inheritance of *any* member required by a protocol and implemented in an extension of that protocol. The fact that in this case conformance is synthesized by the compiler instead of written in source code somewhere is immaterial; in principle, nothing is (was?) stopping the same default implementation from being implemented with a Mirror instead of the current approach.

This is why I'm proposing that Mirrors of `self` should likewise require the new attribute; while I realise it may be possible to sneak self-reflection in somehow regardless by tricking the compiler, this should at least pick up the most obvious cases. A more complete solution could possibly just consider all use of reflection to be synthetic, requiring methods callable from protocol extensions (global functions, static methods etc.) to use the attribute as well, so that the compiler can detect any function call with `self` that could potentially result in reflective behaviour.

The issue here isn't that there might be other ways to do it (these can be addressed), it's that all methods of doing this should require developers to explicitly opt-in, otherwise it leads to potential bugs and/or unwanted behaviour. It's also IMO a gross overreach for protocols to begin with, and sets a dangerous precedent for a language that's supposed to be about safety and prevention of bugs, especially when in the case of Equatable and Hashable it will actually hide bugs that are currently caught.

As a general rule I would argue that Mirrors should almost never be used for any purpose, except perhaps debugging; in production code they can lead to subtle and misleading problems, not to mention any performance impacts. Even for things like serialising types, it is not a desirable way to do it, and should only be used as a last resort because of missing features in Swift.

> I still think that the role keyword proposal is the best solution to this problem proposed so far. https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170612/037484.html <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170612/037484.html>
While I support the idea of these role keywords, they don't solve all of the same problems. Consider for example a missing protocol requirement; currently it is caught if a protocol cannot offer a default implementation for it, however, in the presence of synthetic behaviour it is possible that the requirement is met, but by a method that will not work as desired.

The main issue here is that we're talking about methods that appear like default implementations, but go well beyond what the protocol itself defines; when you start delving into concrete types from within a protocol you are making assumptions about that concrete type that simply cannot be guaranteed. Any mistake or omission by a developer could lead to behaviour they do not want, and that may not be easy to debug, and in the case of Equatable and Hashable is a potential bug that is currently impossible.

>> On Sep 5, 2017, at 4:02 PM, Haravikk via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>> Some of you will have seen my impassioned pleas on the synthesised Equatable/Hashable thread against implementing implicit synthesised behaviour on protocols that must, by necessity, make assumptions about a concrete type that could be incorrect.
>> For those that haven't, the concept of synthetic behaviour in discussion here is essentially any kind of default behaviour for a protocol that is automatically generated based upon the concrete type itself (rather than just what the protocol defines). Currently this refers to compiler magic as proposed for Codable, Equatable and Hashable, but also includes the reflection API and any future native macro support for Swift. Using any of these to implement default methods for protocols should IMO be made explicit to developers using any such protocol so that they can specifically opt-in to the behaviour only if they want to and understand what it does.
>> This proposal idea is essentially for a new attribute @synthetic (name is up for debate). This attribute is required for any default implementation that includes reflective type compiler magic, use of the reflection API against `self` or, in future, any native Swift macros within the method (possibly limited to specific features, will depend on the macro language and its capabilities). If a default method does not have this attribute, then the compiler will produce an error with the appropriate fix-it. For convenience this attribute can be applied to any extension block or even a protocol definition in order to mark all methods in that block/type as synthetic, though it's worth noting that doing so will prevent these default implementations from being provided if they don't actually need this attribute.
>> Basically the intention is that any protocol default implementation that requires more knowledge about the concrete type than merely what the protocol (and its parents) provide direct access to, must be marked as synthetic.
>> To use the synthetic behaviour of a protocol, developers must then use the @synthetic keyword when conforming to it, explicitly indicating that they want the extra behaviours rather than implementing the method(s) for themselves. To ignore the synthetic behaviour (and thus require some kind of manual implementation of methods as normal), simply omit the keyword:
>> 	struct Foo : @synthetic Equatable { var someData:String }
>> 		// Type fully conforms to Equatable using synthetic behaviour (equatable properties must be equal)
>> 	struct Foo : Equatable { var someData:String }
>> 		// Error due to unimplemented methods, but offers @synthetic as a fix-it if all unimplemented methods are @synthetic
>> It is possible that the attribute could be expanded to have parameters, allowing for synthetic conformance only on specific methods, but I'm unsure if that'd be the best way to do it, or how likely that is to be needed.
>> With this kind of explicit declaration it becomes obvious within code when a developer is specifically choosing to benefit from synthetic behaviour; this hopefully makes it more likely that a developer will fully consider what the implications of this may be, rather than doing it accidentally. The idea in part is to distinguish such types as having separate protocol and synthesised behaviour, where conformance without the @synthetic attribute specifically requires that all protocol requirements be met in full, and that any default behaviour is implemented only on the basis of the protocol itself, while adding @synthetic identifies that more invasive automated behaviour is permitted/requested.
>> At this stage I don't think there should be much of an impact for existing code; as far as I can tell it should only affect any protocols that happen to be using Mirror(reflecting: self) for some reason within a default implementation, which I can't imagine represents a huge subsection of existing code, and the fix is the simple addition of an attribute.
>> Anyway, this is basically just a rough dump of the ideas for how the synthesised Codable, Equatable and Hashable behaviours (and anything else anyone can think of) should be changed before Swift 4 is released. I'm hoping for feedback before I make a formal proposal as I don't have a lot of time at the moment, so am looking to do the more structured document once stuff has been hammered out a bit.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170906/006a0be1/attachment.html>

More information about the swift-evolution mailing list