Skip to content

Commit 8be4ec3

Browse files
committed
Protocol requirement overrides must match in mutating-ness.
Without this change, SILGen will crash when compiling a use of the derived protocol's requirement: it will instead attempt to use the base protocol's requirement, but the code will have been type-checked incorrectly for that. This has a potential for source-compatibility impact if anyone's using explicit override checking for their protocol requirements: reasonable idioms like overriding a mutating requirement with a non-mutating one will no longer count as an override. However, this is arguably a bug-fix, because the current designed intent of protocol override checking is to not allow any differences in type, even "covariant" changes like making a mutating requirement non-mutating. Moreover, we believe explicit override checking in protocols is quite uncommon, so the overall compatibility impact will be low. This also has a potential for ABI impact whenever something that was once an override becomes a non-override and thus requires a new entry. It might require a contrived test case to demonstrate that while using the derived entry, but it's quite possible to imagine a situation where the derived entry is not used directly but nonetheless has ABI impact. Furthermore, as part of developing this patch (earlier versions of which used stricter rules in places), I discovered a number of places where the standard library was unintentionally introducing a new requirement in a derived protocol when it intended only to guide associated type deduction. Fixing that (as I have in this patch) *definitely* has ABI impact.
1 parent d29b545 commit 8be4ec3

9 files changed

+164
-3
lines changed

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,37 @@ Type swift::getMemberTypeForComparison(ASTContext &ctx, ValueDecl *member,
120120
return memberType;
121121
}
122122

123+
static bool areAccessorsOverrideCompatible(AbstractStorageDecl *storage,
124+
AbstractStorageDecl *parentStorage) {
125+
// It's okay for the storage to disagree about whether to use a getter or
126+
// a read accessor; we'll patch up any differences when setting overrides
127+
// for the accessors. We don't want to diagnose anything involving
128+
// `@_borrowed` because it is not yet part of the language.
129+
130+
// All the other checks are for non-static storage only.
131+
if (storage->isStatic())
132+
return true;
133+
134+
// The storage must agree on whether reads are mutating. For accessors,
135+
// this is sufficient to imply that they use the same SelfAccessKind
136+
// because we do not allow accessors to be consuming.
137+
if (storage->isGetterMutating() != parentStorage->isGetterMutating())
138+
return false;
139+
140+
// We allow covariance about whether the storage itself is mutable, so we
141+
// can only check mutating-ness of setters if both have one.
142+
if (storage->supportsMutation() && parentStorage->supportsMutation()) {
143+
// The storage must agree on whether writes are mutating.
144+
if (storage->isSetterMutating() != parentStorage->isSetterMutating())
145+
return false;
146+
147+
// Those together should imply that read-write accesses have the same
148+
// mutability.
149+
}
150+
151+
return true;
152+
}
153+
123154
bool swift::isOverrideBasedOnType(ValueDecl *decl, Type declTy,
124155
ValueDecl *parentDecl, Type parentDeclTy) {
125156
auto *genericSig =
@@ -149,6 +180,23 @@ bool swift::isOverrideBasedOnType(ValueDecl *decl, Type declTy,
149180
auto fnType2 = parentDeclTy->castTo<AnyFunctionType>();
150181
return AnyFunctionType::equalParams(fnType1->getParams(),
151182
fnType2->getParams());
183+
184+
// In a non-static protocol requirement, verify that the self access kind
185+
// matches.
186+
} else if (auto func = dyn_cast<FuncDecl>(decl)) {
187+
// We only compare `isMutating()` rather than `getSelfAccessKind()`
188+
// because we don't want to complain about `nonmutating` vs. `__consuming`
189+
// conflicts at this time, especially since `__consuming` is not yet
190+
// officially part of the language.
191+
if (!func->isStatic() &&
192+
func->isMutating() != cast<FuncDecl>(parentDecl)->isMutating())
193+
return false;
194+
195+
// In abstract storage, verify that the accessor mutating-ness matches.
196+
} else if (auto storage = dyn_cast<AbstractStorageDecl>(decl)) {
197+
auto parentStorage = cast<AbstractStorageDecl>(parentDecl);
198+
if (!areAccessorsOverrideCompatible(storage, parentStorage))
199+
return false;
152200
}
153201

154202
return canDeclTy == canParentDeclTy;

stdlib/public/core/BidirectionalCollection.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ where SubSequence: BidirectionalCollection, Indices: BidirectionalCollection {
211211
override subscript(bounds: Range<Index>) -> SubSequence { get }
212212

213213
// FIXME: Only needed for associated type inference.
214+
@_borrowed
214215
override subscript(position: Index) -> Element { get }
215216
override var startIndex: Index { get }
216217
override var endIndex: Index { get }

stdlib/public/core/Collection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ public protocol Collection: Sequence {
382382
// we get an `IndexingIterator` rather than properly deducing the
383383
// Iterator type from makeIterator(). <rdar://problem/21539115>
384384
/// Returns an iterator over the elements of the collection.
385-
override func makeIterator() -> Iterator
385+
override __consuming func makeIterator() -> Iterator
386386

387387
/// A sequence that represents a contiguous subrange of the collection's
388388
/// elements.

stdlib/public/core/CollectionOfOne.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ extension CollectionOfOne: RandomAccessCollection, MutableCollection {
127127
/// valid position in a `CollectionOfOne` instance is `0`.
128128
@inlinable // trivial-implementation
129129
public subscript(position: Int) -> Element {
130-
get {
130+
_read {
131131
_precondition(position == 0, "Index out of range")
132-
return _element
132+
yield _element
133133
}
134134
_modify {
135135
_precondition(position == 0, "Index out of range")

stdlib/public/core/RandomAccessCollection.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ where SubSequence: RandomAccessCollection, Indices: RandomAccessCollection
8484
override subscript(bounds: Range<Index>) -> SubSequence { get }
8585

8686
// FIXME: Associated type inference requires these.
87+
@_borrowed
8788
override subscript(position: Index) -> Element { get }
8889
override var startIndex: Index { get }
8990
override var endIndex: Index { get }

stdlib/public/core/RangeReplaceableCollection.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ public protocol RangeReplaceableCollection : Collection
363363
where shouldBeRemoved: (Element) throws -> Bool) rethrows
364364

365365
// FIXME: Associated type inference requires these.
366+
@_borrowed
366367
override subscript(bounds: Index) -> Element { get }
367368
override subscript(bounds: Range<Index>) -> SubSequence { get }
368369
}

test/SILGen/protocol_overrides.swift

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// RUN: %target-swift-emit-silgen -module-name main %s | %FileCheck %s
2+
3+
// rdar://47470420
4+
5+
/*****************************************************************************/
6+
/* Methods *******************************************************************/
7+
/*****************************************************************************/
8+
9+
protocol HasMutatingMethod {
10+
mutating func foo()
11+
}
12+
13+
protocol HasMutatingMethodClone: HasMutatingMethod {
14+
mutating func foo()
15+
}
16+
extension HasMutatingMethodClone {
17+
// CHECK-LABEL: sil hidden [ossa] @$s4main22HasMutatingMethodClonePAAE10performFooyyF :
18+
// CHECK: witness_method $Self, #HasMutatingMethod.foo!1
19+
mutating func performFoo() {
20+
foo()
21+
}
22+
}
23+
24+
protocol HasNonMutatingMethod: HasMutatingMethod {
25+
func foo()
26+
}
27+
extension HasNonMutatingMethod {
28+
// CHECK-LABEL: sil hidden [ossa] @$s4main20HasNonMutatingMethodPAAE10performFooyyF :
29+
// CHECK: witness_method $Self, #HasNonMutatingMethod.foo!1 :
30+
func performFoo() {
31+
foo()
32+
}
33+
}
34+
35+
/*****************************************************************************/
36+
/* Getters *******************************************************************/
37+
/*****************************************************************************/
38+
39+
protocol HasMutatingGetter {
40+
var foo: Int { mutating get }
41+
}
42+
43+
protocol HasMutatingGetterClone: HasMutatingGetter {
44+
var foo: Int { mutating get }
45+
}
46+
extension HasMutatingGetterClone {
47+
// CHECK-LABEL: sil hidden [ossa] @$s4main22HasMutatingGetterClonePAAE11readFromFooSiyF :
48+
// CHECK: witness_method $Self, #HasMutatingGetter.foo!getter.1 :
49+
mutating func readFromFoo() -> Int {
50+
return foo
51+
}
52+
}
53+
54+
protocol HasNonMutatingGetter: HasMutatingGetter {
55+
var foo: Int { get }
56+
}
57+
extension HasNonMutatingGetter {
58+
// CHECK-LABEL: sil hidden [ossa] @$s4main20HasNonMutatingGetterPAAE11readFromFooSiyF :
59+
// CHECK: witness_method $Self, #HasNonMutatingGetter.foo!getter.1 :
60+
func readFromFoo() -> Int {
61+
return foo
62+
}
63+
}
64+
65+
/*****************************************************************************/
66+
/* Setters *******************************************************************/
67+
/*****************************************************************************/
68+
69+
protocol HasMutatingSetter {
70+
var foo: Int { get set }
71+
}
72+
73+
protocol HasMutatingSetterClone: HasMutatingSetter {
74+
var foo: Int { get set }
75+
}
76+
extension HasMutatingSetterClone {
77+
// CHECK-LABEL: sil hidden [ossa] @$s4main22HasMutatingSetterClonePAAE11readFromFooSiyF :
78+
// CHECK: witness_method $Self, #HasMutatingSetter.foo!getter.1 :
79+
func readFromFoo() -> Int {
80+
return foo
81+
}
82+
83+
// CHECK-LABEL: sil hidden [ossa] @$s4main22HasMutatingSetterClonePAAE10writeToFooyySiF :
84+
// CHECK: witness_method $Self, #HasMutatingSetter.foo!setter.1 :
85+
mutating func writeToFoo(_ x: Int) {
86+
foo = x
87+
}
88+
}
89+
90+
protocol HasNonMutatingSetter: HasMutatingSetter {
91+
var foo: Int { get nonmutating set }
92+
}
93+
extension HasNonMutatingSetter {
94+
// It's unfortunate that this uses a new witness table entry,
95+
// but we can live with it.
96+
// CHECK-LABEL: sil hidden [ossa] @$s4main20HasNonMutatingSetterPAAE11readFromFooSiyF :
97+
// CHECK: witness_method $Self, #HasNonMutatingSetter.foo!getter.1 :
98+
func readFromFoo() -> Int {
99+
return foo
100+
}
101+
102+
// CHECK-LABEL: sil hidden [ossa] @$s4main20HasNonMutatingSetterPAAE10writeToFooyySiF :
103+
// CHECK: witness_method $Self, #HasNonMutatingSetter.foo!setter.1 :
104+
func writeToFoo(_ x: Int) {
105+
foo = x
106+
}
107+
}

test/api-digester/Outputs/stability-stdlib-abi.swift.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,5 @@ Protocol SIMDStorage has generic signature change from <τ_0_0.Scalar : Hashable
8282
Func Sequence.flatMap(_:) has been removed
8383
Subscript String.UnicodeScalarView.subscript(_:) has been removed
8484
Subscript Substring.subscript(_:) has been removed
85+
86+
Func Collection.makeIterator() has self access kind changing from NonMutating to __Consuming

test/api-digester/Outputs/stability-stdlib-source.swift.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
Func Collection.makeIterator() has self access kind changing from NonMutating to __Consuming
12
Func tryReallocateUniquelyReferenced(buffer:newMinimumCapacity:) has been removed
23
Protocol SIMD has added inherited protocol Decodable
34
Protocol SIMD has added inherited protocol Encodable

0 commit comments

Comments
 (0)