Skip to content

Commit a817109

Browse files
authored
Merge pull request #9403 from jckarter/mix-and-match-witnesses-4.0
[4.0] Serialization: Recovery for protocol conformances with changed witness or requirement signatures.
2 parents 0a687c6 + baa99c6 commit a817109

File tree

14 files changed

+344
-16
lines changed

14 files changed

+344
-16
lines changed

include/swift/AST/Witness.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ class Witness {
108108
/// not generic (excepting \c Self) and the conforming type is non-generic.
109109
Witness(ValueDecl *witness) : storage(witness) { assert(witness != nullptr); }
110110

111+
/// Create an opaque witness for the given requirement.
112+
///
113+
/// This indicates that a witness exists, but is not visible to the current
114+
/// module.
115+
static Witness forOpaque(ValueDecl *requirement) {
116+
// TODO: It's probably a good idea to have a separate 'opaque' bit.
117+
// Making req == witness is kind of a hack.
118+
return Witness(requirement);
119+
}
120+
111121
/// Create a witness that requires substitutions.
112122
///
113123
/// \param decl The declaration for the witness.

include/swift/Basic/Version.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ class Version {
144144

145145
bool operator>=(const Version &lhs, const Version &rhs);
146146
bool operator==(const Version &lhs, const Version &rhs);
147+
inline bool operator!=(const Version &lhs, const Version &rhs) {
148+
return !(lhs == rhs);
149+
}
147150

148151
raw_ostream &operator<<(raw_ostream &os, const Version &version);
149152

include/swift/Serialization/ModuleFile.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class ModuleFile : public LazyMemberLoader {
7373
StringRef TargetTriple;
7474

7575
/// The Swift compatibility version in use when this module was built.
76-
StringRef CompatibilityVersion;
76+
version::Version CompatibilityVersion;
7777

7878
/// The data blob containing all of the module's identifiers.
7979
StringRef IdentifierData;

include/swift/Serialization/Validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct ValidationInfo {
7171
StringRef name = {};
7272
StringRef targetTriple = {};
7373
StringRef shortVersion = {};
74-
StringRef compatibilityVersion = {};
74+
version::Version compatibilityVersion = {};
7575
size_t bytes = 0;
7676
Status status = Status::Malformed;
7777
};

lib/Basic/Version.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,11 @@ Optional<Version> Version::parseVersionString(StringRef VersionString,
226226
return isValidVersion ? Optional<Version>(TheVersion) : None;
227227
}
228228

229+
Version::Version(StringRef VersionString,
230+
SourceLoc Loc,
231+
DiagnosticEngine *Diags)
232+
: Version(*parseVersionString(VersionString, Loc, Diags))
233+
{}
229234

230235
Version Version::getCurrentCompilerVersion() {
231236
#ifdef SWIFT_COMPILER_VERSION

lib/Serialization/Deserialization.cpp

Lines changed: 89 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,20 @@ void ModuleFile::fatal(llvm::Error error) {
165165
getContext().Diags.diagnose(SourceLoc(), diag::serialization_fatal, Name);
166166

167167
if (!CompatibilityVersion.empty()) {
168-
SmallString<16> buffer;
169-
llvm::raw_svector_ostream out(buffer);
170-
out << getContext().LangOpts.EffectiveLanguageVersion;
171-
if (out.str() != CompatibilityVersion) {
168+
if (getContext().LangOpts.EffectiveLanguageVersion
169+
!= CompatibilityVersion) {
170+
SmallString<16> effectiveVersionBuffer, compatVersionBuffer;
171+
{
172+
llvm::raw_svector_ostream out(effectiveVersionBuffer);
173+
out << getContext().LangOpts.EffectiveLanguageVersion;
174+
}
175+
{
176+
llvm::raw_svector_ostream out(compatVersionBuffer);
177+
out << CompatibilityVersion;
178+
}
172179
getContext().Diags.diagnose(
173180
SourceLoc(), diag::serialization_compatibility_version_mismatch,
174-
out.str(), Name, CompatibilityVersion);
181+
effectiveVersionBuffer, Name, compatVersionBuffer);
175182
}
176183
}
177184
}
@@ -4464,20 +4471,69 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
44644471

44654472
ArrayRef<uint64_t>::iterator rawIDIter = rawIDs.begin();
44664473

4474+
// An imported requirement may have changed type between Swift versions.
4475+
// In this situation we need to do a post-pass to fill in missing
4476+
// requirements with opaque witnesses.
4477+
bool needToFillInOpaqueValueWitnesses = false;
44674478
while (valueCount--) {
4468-
auto req = cast<ValueDecl>(getDecl(*rawIDIter++));
4469-
auto witness = cast_or_null<ValueDecl>(getDecl(*rawIDIter++));
4470-
assert(witness ||
4479+
ValueDecl *req;
4480+
4481+
auto trySetWitness = [&](Witness w) {
4482+
if (req)
4483+
conformance->setWitness(req, w);
4484+
};
4485+
4486+
auto deserializedReq = getDeclChecked(*rawIDIter++);
4487+
if (deserializedReq) {
4488+
req = cast<ValueDecl>(*deserializedReq);
4489+
} else if (getContext().LangOpts.EnableDeserializationRecovery) {
4490+
consumeError(deserializedReq.takeError());
4491+
req = nullptr;
4492+
needToFillInOpaqueValueWitnesses = true;
4493+
} else {
4494+
fatal(deserializedReq.takeError());
4495+
}
4496+
4497+
bool isOpaque = false;
4498+
ValueDecl *witness;
4499+
auto deserializedWitness = getDeclChecked(*rawIDIter++);
4500+
if (deserializedWitness) {
4501+
witness = cast<ValueDecl>(*deserializedWitness);
4502+
// Across language compatibility versions, the witnessing decl may have
4503+
// changed its signature as seen by the current compatibility version.
4504+
// In that case, we want the conformance to still be available, but
4505+
// we can't make use of the relationship to the underlying decl.
4506+
} else if (getContext().LangOpts.EnableDeserializationRecovery) {
4507+
consumeError(deserializedWitness.takeError());
4508+
isOpaque = true;
4509+
witness = nullptr;
4510+
} else {
4511+
fatal(deserializedWitness.takeError());
4512+
}
4513+
4514+
assert(!req || isOpaque || witness ||
44714515
req->getAttrs().hasAttribute<OptionalAttr>() ||
44724516
req->getAttrs().isUnavailable(getContext()));
4473-
if (!witness) {
4474-
conformance->setWitness(req, Witness());
4517+
if (!witness && !isOpaque) {
4518+
trySetWitness(Witness());
44754519
continue;
44764520
}
44774521

44784522
// Generic signature and environment.
44794523
GenericSignature *syntheticSig = nullptr;
44804524
GenericEnvironment *syntheticEnv = nullptr;
4525+
4526+
auto trySetOpaqueWitness = [&]{
4527+
if (!req)
4528+
return;
4529+
4530+
// We shouldn't yet need to worry about generic requirements, since
4531+
// an imported ObjC method should never be generic.
4532+
assert(syntheticSig == nullptr && syntheticEnv == nullptr &&
4533+
"opaque witness shouldn't be generic yet. when this is "
4534+
"possible, it should use forwarding substitutions");
4535+
conformance->setWitness(req, Witness::forOpaque(req));
4536+
};
44814537

44824538
// Requirement -> synthetic map.
44834539
SmallVector<Substitution, 4> reqToSyntheticSubs;
@@ -4518,16 +4574,22 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
45184574
}
45194575
}
45204576

4577+
// Handle opaque witnesses that couldn't be deserialized.
4578+
if (isOpaque) {
4579+
trySetOpaqueWitness();
4580+
continue;
4581+
}
4582+
45214583
// Handle simple witnesses.
45224584
if (witnessSubstitutions.empty() && !syntheticSig && !syntheticEnv &&
45234585
reqToSyntheticSubs.empty()) {
4524-
conformance->setWitness(req, Witness(witness));
4586+
trySetWitness(Witness(witness));
45254587
continue;
45264588
}
45274589

45284590
// Set the witness.
4529-
conformance->setWitness(req, Witness(witness, witnessSubstitutions,
4530-
syntheticEnv, reqToSyntheticSubs));
4591+
trySetWitness(Witness(witness, witnessSubstitutions,
4592+
syntheticEnv, reqToSyntheticSubs));
45314593
}
45324594
assert(rawIDIter <= rawIDs.end() && "read too much");
45334595

@@ -4558,6 +4620,20 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
45584620
conformance->setTypeWitness(typeWitness.first, typeWitness.second.first,
45594621
typeWitness.second.second);
45604622
}
4623+
4624+
// Fill in opaque value witnesses if we need to.
4625+
if (needToFillInOpaqueValueWitnesses) {
4626+
for (auto member : proto->getMembers()) {
4627+
// We only care about non-associated-type requirements.
4628+
auto valueMember = dyn_cast<ValueDecl>(member);
4629+
if (!valueMember || !valueMember->isProtocolRequirement()
4630+
|| isa<AssociatedTypeDecl>(valueMember))
4631+
continue;
4632+
4633+
if (!conformance->hasWitness(valueMember))
4634+
conformance->setWitness(valueMember, Witness::forOpaque(valueMember));
4635+
}
4636+
}
45614637
}
45624638

45634639
GenericEnvironment *ModuleFile::loadGenericEnvironment(const DeclContext *decl,

lib/Serialization/ModuleFile.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ validateControlBlock(llvm::BitstreamCursor &cursor,
200200
default:
201201
// Add new cases here, in descending order.
202202
case 4:
203-
result.compatibilityVersion = blobData.substr(scratch[2]+1, scratch[3]);
203+
result.compatibilityVersion =
204+
version::Version(blobData.substr(scratch[2]+1, scratch[3]),
205+
SourceLoc(), nullptr);
204206
LLVM_FALLTHROUGH;
205207
case 3:
206208
result.shortVersion = blobData.slice(0, scratch[2]);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Name: SomeObjCModule
2+
Classes:
3+
- Name: NSRuncibleSpoon
4+
SwiftBridge: RuncibleSpoon
5+
SwiftVersions:
6+
- Version: 3
7+
Classes:
8+
- Name: NSRuncibleSpoon
9+
SwiftBridge: ""
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Swift 3 sees the ObjC class NSRuncibleSpoon as the class, and uses methods
2+
// with type signatures involving NSRuncibleSpoon to conform to protocols
3+
// across the language boundary. Swift 4 sees the type as bridged to
4+
// a RuncibleSpoon value type, but still needs to be able to use conformances
5+
// declared by Swift 3.
6+
7+
@import Foundation;
8+
9+
@interface NSRuncibleSpoon: NSObject
10+
@end
11+
12+
@interface SomeObjCClass: NSObject
13+
- (instancetype _Nonnull)initWithSomeSwiftInitRequirement:(NSRuncibleSpoon* _Nonnull)s;
14+
- (void)someSwiftMethodRequirement:(NSRuncibleSpoon* _Nonnull)s;
15+
@property NSRuncibleSpoon * _Nonnull someSwiftPropertyRequirement;
16+
@end
17+
18+
@protocol SomeObjCProtocol
19+
- (instancetype _Nonnull)initWithSomeObjCInitRequirement:(NSRuncibleSpoon * _Nonnull)string;
20+
- (void)someObjCMethodRequirement:(NSRuncibleSpoon * _Nonnull)string;
21+
@property NSRuncibleSpoon * _Nonnull someObjCPropertyRequirement;
22+
@end
23+
24+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// NB: This file is not named SomeObjCModule.swift to avoid getting picked up
2+
// by -enable-source-import
3+
4+
@_exported import SomeObjCModule
5+
6+
public struct RuncibleSpoon: _ObjectiveCBridgeable {
7+
public init() {}
8+
9+
public func _bridgeToObjectiveC() -> NSRuncibleSpoon {
10+
fatalError()
11+
}
12+
public static func _forceBridgeFromObjectiveC(_: NSRuncibleSpoon, result: inout RuncibleSpoon?) {
13+
fatalError()
14+
}
15+
public static func _conditionallyBridgeFromObjectiveC(_: NSRuncibleSpoon, result: inout RuncibleSpoon?) -> Bool {
16+
fatalError()
17+
}
18+
public static func _unconditionallyBridgeFromObjectiveC(_: NSRuncibleSpoon?) -> RuncibleSpoon {
19+
fatalError()
20+
}
21+
}
22+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module SomeObjCModule {
2+
header "SomeObjCModule.h"
3+
export *
4+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
// Swift 3 sees the ObjC class NSRuncibleSpoon as the class, and uses methods
3+
// with type signatures involving NSRuncibleSpoon to conform to protocols
4+
// across the language boundary. Swift 4 sees the type as bridged to
5+
// a RuncibleSpoon value type, but still needs to be able to use conformances
6+
// declared by Swift 3.
7+
8+
// Swift 3, importing Swift 3 and Swift 4 code
9+
10+
import SomeObjCModule
11+
import SomeSwift3Module
12+
import SomeSwift4Module
13+
14+
func testMatchAndMix(bridged: RuncibleSpoon, unbridged: NSRuncibleSpoon) {
15+
let objcInstanceViaClass
16+
= SomeObjCClass(someSwiftInitRequirement: unbridged)
17+
18+
let objcClassAsS3Protocol: SomeSwift3Protocol.Type = SomeObjCClass.self
19+
let objcInstanceViaS3Protocol
20+
= objcClassAsS3Protocol.init(someSwiftInitRequirement: unbridged)
21+
22+
let objcClassAsS4Protocol: SomeSwift4Protocol.Type = SomeObjCClass.self
23+
let objcInstanceViaS4Protocol
24+
= objcClassAsS4Protocol.init(someSwiftInitRequirement: bridged)
25+
26+
var bridgedSink: RuncibleSpoon
27+
var unbridgedSink: NSRuncibleSpoon
28+
29+
let swiftPropertyViaClass = objcInstanceViaClass.someSwiftPropertyRequirement
30+
unbridgedSink = swiftPropertyViaClass
31+
let swiftPropertyViaS3Protocol = objcInstanceViaS3Protocol.someSwiftPropertyRequirement
32+
unbridgedSink = swiftPropertyViaS3Protocol
33+
let swiftPropertyViaS4Protocol = objcInstanceViaS4Protocol.someSwiftPropertyRequirement
34+
bridgedSink = swiftPropertyViaS4Protocol
35+
36+
objcInstanceViaClass.someSwiftMethodRequirement(unbridged)
37+
objcInstanceViaS3Protocol.someSwiftMethodRequirement(unbridged)
38+
objcInstanceViaS4Protocol.someSwiftMethodRequirement(bridged)
39+
40+
let swift3InstanceViaClass
41+
= SomeSwift3Class(someObjCInitRequirement: unbridged)
42+
let swift3ClassAsProtocol: SomeObjCProtocol.Type = SomeSwift3Class.self
43+
let swift3InstanceViaProtocol
44+
= swift3ClassAsProtocol.init(someObjCInitRequirement: unbridged)
45+
46+
let objcPropertyViaClassS3 = swift3InstanceViaClass.someObjCPropertyRequirement
47+
unbridgedSink = objcPropertyViaClassS3
48+
let objcPropertyViaProtocolS3 = swift3InstanceViaProtocol.someObjCPropertyRequirement
49+
unbridgedSink = objcPropertyViaProtocolS3
50+
51+
swift3InstanceViaClass.someObjCMethodRequirement(unbridged)
52+
swift3InstanceViaProtocol.someObjCMethodRequirement(unbridged)
53+
54+
let swift4InstanceViaClass
55+
= SomeSwift4Class(someObjCInitRequirement: bridged)
56+
let swift4ClassAsProtocol: SomeObjCProtocol.Type = SomeSwift4Class.self
57+
let swift4InstanceViaProtocol
58+
= swift4ClassAsProtocol.init(someObjCInitRequirement: unbridged)
59+
60+
let objcPropertyViaClassS4 = swift4InstanceViaClass.someObjCPropertyRequirement
61+
bridgedSink = objcPropertyViaClassS4
62+
let objcPropertyViaProtocolS4 = swift4InstanceViaProtocol.someObjCPropertyRequirement
63+
unbridgedSink = objcPropertyViaProtocolS4
64+
65+
swift4InstanceViaClass.someObjCMethodRequirement(bridged)
66+
swift4InstanceViaProtocol.someObjCMethodRequirement(unbridged)
67+
68+
_ = bridgedSink
69+
_ = unbridgedSink
70+
}
71+

0 commit comments

Comments
 (0)