Skip to content

Commit eeb8f33

Browse files
authored
[ModuleInterface] Allow conformances to be missing value witnesses (#18932)
It's not clear whether we'll actually need this feature in the long run, but we certainly need it now because non-@usableFromInline members can (currently) satisfy public requirements when a @usableFromInline internal type conforms to a public protocol. In these cases, we'll treat the witnesses as present but opaque, and clients will perform dynamic dispatch when using them even when a generic function gets specialized. With this, we're able to generate a textual interface for the standard library, compile it back to a swiftmodule, and use it to build a Hello World program!
1 parent 04b5eb5 commit eeb8f33

File tree

4 files changed

+147
-74
lines changed

4 files changed

+147
-74
lines changed

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 95 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,23 @@ bool WitnessChecker::findBestWitness(
10601060
}
10611061

10621062
if (numViable == 0) {
1063+
// Assume any missing value witnesses for a conformance in a textual
1064+
// interface can be treated as opaque.
1065+
// FIXME: ...but we should do something better about types.
1066+
if (conformance && !conformance->isInvalid()) {
1067+
if (auto *SF = DC->getParentSourceFile()) {
1068+
if (SF->Kind == SourceFileKind::Interface) {
1069+
auto match = matchWitness(TC, ReqEnvironmentCache, Proto,
1070+
conformance, DC, requirement, requirement);
1071+
assert(match.isViable());
1072+
numViable = 1;
1073+
bestIdx = matches.size();
1074+
matches.push_back(std::move(match));
1075+
return true;
1076+
}
1077+
}
1078+
}
1079+
10631080
if (anyFromUnconstrainedExtension &&
10641081
conformance != nullptr &&
10651082
conformance->isInvalid()) {
@@ -4598,8 +4615,9 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
45984615
currentDecl = cast<NominalTypeDecl>(dc);
45994616
}
46004617

4618+
SourceFile *SF = dc->getParentSourceFile();
46014619
ReferencedNameTracker *tracker = nullptr;
4602-
if (SourceFile *SF = dc->getParentSourceFile())
4620+
if (SF)
46034621
tracker = SF->getReferencedNameTracker();
46044622

46054623
// Check each of the conformances associated with this context.
@@ -4804,85 +4822,88 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
48044822
// If there were any unsatisfied requirements, check whether there
48054823
// are any near-matches we should diagnose.
48064824
if (!unsatisfiedReqs.empty() && !anyInvalid) {
4807-
// Find all of the members that aren't used to satisfy
4808-
// requirements, and check whether they are close to an
4809-
// unsatisfied or defaulted requirement.
4810-
for (auto member : idc->getMembers()) {
4811-
// Filter out anything that couldn't satisfy one of the
4812-
// requirements or was used to satisfy a different requirement.
4813-
auto value = dyn_cast<ValueDecl>(member);
4814-
if (!value) continue;
4815-
if (isa<TypeDecl>(value)) continue;
4816-
if (!value->getFullName()) continue;
4817-
4818-
// If this declaration overrides another declaration, the signature is
4819-
// fixed; don't complain about near misses.
4820-
if (value->getOverriddenDecl()) continue;
4821-
4822-
// If this member is a witness to any @objc requirement, ignore it.
4823-
if (!findWitnessedObjCRequirements(value, /*anySingleRequirement=*/true)
4824-
.empty())
4825-
continue;
4825+
if (SF && SF->Kind != SourceFileKind::Interface) {
4826+
// Find all of the members that aren't used to satisfy
4827+
// requirements, and check whether they are close to an
4828+
// unsatisfied or defaulted requirement.
4829+
for (auto member : idc->getMembers()) {
4830+
// Filter out anything that couldn't satisfy one of the
4831+
// requirements or was used to satisfy a different requirement.
4832+
auto value = dyn_cast<ValueDecl>(member);
4833+
if (!value) continue;
4834+
if (isa<TypeDecl>(value)) continue;
4835+
if (!value->getFullName()) continue;
48264836

4827-
// Find the unsatisfied requirements with the nearest-matching
4828-
// names.
4829-
SmallVector<ValueDecl *, 4> bestOptionalReqs;
4830-
unsigned bestScore = UINT_MAX;
4831-
for (auto req : unsatisfiedReqs) {
4832-
// Skip unavailable requirements.
4833-
if (req->getAttrs().isUnavailable(Context)) continue;
4834-
4835-
// Score this particular optional requirement.
4836-
auto score = scorePotentiallyMatching(*this, req, value, bestScore);
4837-
if (!score) continue;
4838-
4839-
// If the score is better than the best we've seen, update the best
4840-
// and clear out the list.
4841-
if (*score < bestScore) {
4842-
bestOptionalReqs.clear();
4843-
bestScore = *score;
4844-
}
4837+
// If this declaration overrides another declaration, the signature is
4838+
// fixed; don't complain about near misses.
4839+
if (value->getOverriddenDecl()) continue;
48454840

4846-
// If this score matches the (possible new) best score, record it.
4847-
if (*score == bestScore)
4848-
bestOptionalReqs.push_back(req);
4849-
}
4841+
// If this member is a witness to any @objc requirement, ignore it.
4842+
if (!findWitnessedObjCRequirements(value, /*anySingleRequirement=*/true)
4843+
.empty())
4844+
continue;
48504845

4851-
// If we found some requirements with nearly-matching names, diagnose
4852-
// the first one.
4853-
if (bestScore < UINT_MAX) {
4854-
bestOptionalReqs.erase(
4855-
std::remove_if(
4856-
bestOptionalReqs.begin(),
4857-
bestOptionalReqs.end(),
4858-
[&](ValueDecl *req) {
4859-
return !shouldWarnAboutPotentialWitness(groupChecker, req, value,
4860-
defaultAccess, bestScore);
4861-
}),
4862-
bestOptionalReqs.end());
4863-
}
4846+
// Find the unsatisfied requirements with the nearest-matching
4847+
// names.
4848+
SmallVector<ValueDecl *, 4> bestOptionalReqs;
4849+
unsigned bestScore = UINT_MAX;
4850+
for (auto req : unsatisfiedReqs) {
4851+
// Skip unavailable requirements.
4852+
if (req->getAttrs().isUnavailable(Context)) continue;
4853+
4854+
// Score this particular optional requirement.
4855+
auto score = scorePotentiallyMatching(*this, req, value, bestScore);
4856+
if (!score) continue;
4857+
4858+
// If the score is better than the best we've seen, update the best
4859+
// and clear out the list.
4860+
if (*score < bestScore) {
4861+
bestOptionalReqs.clear();
4862+
bestScore = *score;
4863+
}
48644864

4865-
// If we have something to complain about, do so.
4866-
if (!bestOptionalReqs.empty()) {
4867-
auto req = bestOptionalReqs[0];
4868-
bool diagnosed = false;
4869-
for (auto conformance : conformances) {
4870-
if (conformance->getProtocol() == req->getDeclContext()) {
4871-
diagnosePotentialWitness(*this,
4872-
conformance->getRootNormalConformance(),
4873-
req, value, defaultAccess);
4874-
diagnosed = true;
4875-
break;
4865+
// If this score matches the (possible new) best score, record it.
4866+
if (*score == bestScore)
4867+
bestOptionalReqs.push_back(req);
4868+
}
4869+
4870+
// If we found some requirements with nearly-matching names, diagnose
4871+
// the first one.
4872+
if (bestScore < UINT_MAX) {
4873+
bestOptionalReqs.erase(
4874+
std::remove_if(
4875+
bestOptionalReqs.begin(),
4876+
bestOptionalReqs.end(),
4877+
[&](ValueDecl *req) {
4878+
return !shouldWarnAboutPotentialWitness(groupChecker, req,
4879+
value, defaultAccess,
4880+
bestScore);
4881+
}),
4882+
bestOptionalReqs.end());
4883+
}
4884+
4885+
// If we have something to complain about, do so.
4886+
if (!bestOptionalReqs.empty()) {
4887+
auto req = bestOptionalReqs[0];
4888+
bool diagnosed = false;
4889+
for (auto conformance : conformances) {
4890+
if (conformance->getProtocol() == req->getDeclContext()) {
4891+
diagnosePotentialWitness(*this,
4892+
conformance->getRootNormalConformance(),
4893+
req, value, defaultAccess);
4894+
diagnosed = true;
4895+
break;
4896+
}
48764897
}
4898+
assert(diagnosed && "Failed to find conformance to diagnose?");
4899+
(void)diagnosed;
4900+
4901+
// Remove this requirement from the list. We don't want to
4902+
// complain about it twice.
4903+
unsatisfiedReqs.erase(std::find(unsatisfiedReqs.begin(),
4904+
unsatisfiedReqs.end(),
4905+
req));
48774906
}
4878-
assert(diagnosed && "Failed to find conformance to diagnose?");
4879-
(void)diagnosed;
4880-
4881-
// Remove this requirement from the list. We don't want to
4882-
// complain about it twice.
4883-
unsatisfiedReqs.erase(std::find(unsatisfiedReqs.begin(),
4884-
unsatisfiedReqs.end(),
4885-
req));
48864907
}
48874908
}
48884909

lib/Serialization/Deserialization.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2697,6 +2697,7 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {
26972697
return nullptr;
26982698
}
26992699

2700+
theStruct->setAddedImplicitInitializers();
27002701
if (isImplicit)
27012702
theStruct->setImplicit();
27022703
theStruct->setIsObjC(isObjC);
@@ -3604,6 +3605,7 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {
36043605
return nullptr;
36053606
}
36063607

3608+
theEnum->setAddedImplicitInitializers();
36073609
if (isImplicit)
36083610
theEnum->setImplicit();
36093611
theEnum->setIsObjC(isObjC);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module -enable-resilience -o %t/Conformances.swiftmodule %s
3+
// RUN: %target-swift-frontend -emit-sil -I %t %S/Inputs/ConformancesUser.swift -O | %FileCheck %s
4+
5+
public protocol MyProto {
6+
init()
7+
func method()
8+
var prop: Int { get set }
9+
subscript(index: Int) -> Int { get set }
10+
}
11+
12+
@_fixed_layout // allow conformance devirtualization
13+
public struct FullStructImpl: MyProto {
14+
public init()
15+
public func method()
16+
public var prop: Int { get set }
17+
public subscript(index: Int) -> Int { get set }
18+
}
19+
// CHECK-LABEL: sil @$S16ConformancesUser8testFullSiyF
20+
// CHECK: function_ref @$S12Conformances14FullStructImplVACycfC
21+
// CHECK: function_ref @$S12Conformances14FullStructImplV6methodyyF
22+
// CHECK: function_ref @$S12Conformances14FullStructImplV4propSivs
23+
// CHECK: function_ref @$S12Conformances14FullStructImplVyS2icig
24+
// CHECK: end sil function '$S16ConformancesUser8testFullSiyF'
25+
26+
@_fixed_layout // allow conformance devirtualization
27+
public struct OpaqueStructImpl: MyProto {}
28+
29+
// CHECK-LABEL: sil @$S16ConformancesUser10testOpaqueSiyF
30+
// CHECK: function_ref @$S12Conformances7MyProtoPxycfC
31+
// CHECK: function_ref @$S12Conformances7MyProtoP6methodyyF
32+
// CHECK: function_ref @$S12Conformances7MyProtoP4propSivs
33+
// CHECK: function_ref @$S12Conformances7MyProtoPyS2icig
34+
// CHECK: end sil function '$S16ConformancesUser10testOpaqueSiyF'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Conformances
2+
3+
func testGeneric<T: MyProto>(_: T.Type) -> Int {
4+
var impl = T.init()
5+
impl.method()
6+
impl.prop = 0
7+
return impl[0]
8+
}
9+
10+
public func testFull() -> Int {
11+
return testGeneric(FullStructImpl.self)
12+
}
13+
14+
public func testOpaque() -> Int {
15+
return testGeneric(OpaqueStructImpl.self)
16+
}

0 commit comments

Comments
 (0)