-
Notifications
You must be signed in to change notification settings - Fork 10.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add '@_requiresConcreteImplementation' and use it #30417
Conversation
@DougGregor because I don't actually know what I'm doing while rooting around Sema. @airspeedswift as he's been involved with the motivating issue as regards to the standard library. I'll also tag @jens-bc here as he raised the issue regarding |
SIMPLE_DECL_ATTR(_requiresConcreteImplementation, RequiresConcreteImplementation, | ||
OnFunc | OnAccessor | OnVar | OnSubscript | OnConstructor | | ||
LongAttribute | UserInaccessible | NotSerialized | | ||
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
APIBreakingToAdd
?
@@ -134,7 +134,7 @@ DECL(PRECEDENCE_GROUP) | |||
DECL(ACCESSOR) | |||
|
|||
#ifndef DECL_ATTR | |||
#define DECL_ATTR(NAME, CLASS, OPTIONS, CODE) RECORD_VAL(CLASS##_DECL_ATTR, 100+CODE) | |||
#define DECL_ATTR(NAME, CLASS, OPTIONS, CODE) RECORD_VAL(CLASS##_DECL_ATTR, 90+CODE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not proud of this, but there are 99 existing decl attrs, so with one more we would otherwise crash into FIRST_PATTERN(PAREN, 200)
.
@swift-ci Please test |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
1ca7dc3
to
530dde5
Compare
@swift-ci Please test |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…ttr doesn't clash with 'FIRST_PATTERN(PAREN, 200)'
530dde5
to
5feb517
Compare
Third time's the charm. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Personally, I'm pretty against this as a general-purpose solution. There's no reason (in a perfect world) why every concrete type would have to avoid getting its implementation from a protocol extension. I'd say it's the implementation who bears this responsibility, because it is a valid witness to a requirement in the protocol it is extending. So I'd prefer an attribute on the implementation that says "this implementation must not satisfy a requirement with the same full name in the protocol it's extending" (and have it be a warning, not an error, so as not to break source compatibility). That's effectively similar to the (conservative) infinite recursion checking we already have—"the body of this method may call the requirement in an unguarded way, so they better not be the same"—which makes me feel a lot better about it being a reasonable fix to the problem. None of these solutions solve the general problem of "two requirements and you must implement at least one of them", but that's just gonna be hard regardless. As a side note, it should be fine to renumber all the serialization node numbers as long as you bump |
More like an What swayed me more towards the approach given here, I guess, was considering the case of complex protocol hierarchies: It's quite legitimate for a refining protocol to provide a default implementation for a requirement of a less refined protocol. It's also legitimate to make use of default implementations guaranteed by less refined protocols in building up a more refined one. It would seem to me that authors of protocol extension methods can more easily reason about their code if they don't have to worry about some refining protocol inadvertently throwing a wrench into things in this way, and seems consonant with Swift's general approach of placing such decisions in the hands of those vending the APIs (c.f.
Since we have control over where the attribute is applied, we can still have enforcement be an error; we would only break source compatibility if we applied it somewhere that rendered existing working conformances an error. In this case, no existing code would actually break, because even though conforming |
Unrelated OS X test failure. |
I don't understand this point. Default implementations are not guaranteed, and neither of our designs would change that.
I don't agree. If a superclass wants to invoke a method and be sure a subclass doesn't override it, the other method has to not be
It seems nice to me to design the feature so that it can be applied to other protocols where existing implementations might be only partially broken rather than completely broken. |
I should rephrase that more carefully: an implementation of the less refined protocol's requirements is guaranteed in that the conforming type would have to conform to the less refined protocol as well; this could be the default one.
I think I have described the scenario poorly. Let me do so by way of example; it's going to take a beat to come up with a good one, so perhaps it's not a common enough scenario to be concerned about solving. I'll circle back to this. |
This has been a while and, I think, has been superseded by the cleverly devised workaround of |
This PR adds a new attribute,
@_requiresConcreteImplementation
, and adopts it for the one protocol requirement inRandomNumberGenerator
.Motivation
The motivation for adding this facility is a scenario as follows—
P
has a requirementfunc frobnicate(_: Int)
.P
can offer a protocol extension methodfunc frobnicate<T>(_: T)
which is implemented by callingfrobnicate(_: Int)
.Currently, the type checker regards the protocol extension method as a valid witness for the protocol requirement, causing a recursive default implementation. Users implementing
P
are misled, thinking that they have implemented all necessary protocol requirements, only to be faced with a runtime error.A concrete example of this problem exists in the standard library:
RandomNumberGenerator
has a requirementnext() -> UInt64
which is unwittingly witnessed by a protocol extension methodnext<T: FixedWidthInteger & UnsignedInteger>() -> T
. Conforming types that do not supply a concrete implementation of the non-generic method will compile but cause crashes at runtime.It should not be necessary to name two semantically identical methods differently just to work around this problem. As the core team stated in swiftlang/swift-evolution#912:
Solution
The current solution takes inspiration from existing attributes such as
@_nonoverride
and@_implements
. The new attribute does what it says on the tin. It can be used on any (non-associatedtype
) protocol requirement so that Sema rejects an implementation in a protocol extension as an acceptable witness.Unfortunately, it appears that we are running out of numbers to serialize attributes. I have had to twiddle with
DeclTypeRecordNodes.def
in order to make room; there's now room for a handful more attributes, but this is clearly not sustainable—nor am I certain about the implications of renumbering all decl type record nodes for attributes in this way.There is an additional (aspirational) goal for this solution which is interesting to consider:
It is permitted to add a requirement to a resilient protocol, but only if a default implementation is also provided. One may envision scenarios where a default implementation for such a requirement is possible for all conforming types but greatly suboptimal.
It would be ideal to support scenarios where the default implementation can be made available for all existing clients (i.e., for resiliency purposes), but nonetheless to require all new conformances going forward to provide concrete implementations. The
@_requiresConcreteImplementation
attribute could enable this possibility.I am not nimble enough in the inner workings of the compiler but suspect that the implementation presented here would not be sufficient to enable this use case as-is. It would be necessary to record a default implementation as a valid witness for runtime purposes but invalid at compile time conformance checking. Something like this must already be present in the codebase for availability annotations.