[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!
This commit is contained in:
Jordan Rose
2018-08-23 16:46:06 -07:00
committed by GitHub
parent 04b5eb501d
commit eeb8f330f9
4 changed files with 146 additions and 73 deletions

View File

@@ -1060,6 +1060,23 @@ bool WitnessChecker::findBestWitness(
} }
if (numViable == 0) { if (numViable == 0) {
// Assume any missing value witnesses for a conformance in a textual
// interface can be treated as opaque.
// FIXME: ...but we should do something better about types.
if (conformance && !conformance->isInvalid()) {
if (auto *SF = DC->getParentSourceFile()) {
if (SF->Kind == SourceFileKind::Interface) {
auto match = matchWitness(TC, ReqEnvironmentCache, Proto,
conformance, DC, requirement, requirement);
assert(match.isViable());
numViable = 1;
bestIdx = matches.size();
matches.push_back(std::move(match));
return true;
}
}
}
if (anyFromUnconstrainedExtension && if (anyFromUnconstrainedExtension &&
conformance != nullptr && conformance != nullptr &&
conformance->isInvalid()) { conformance->isInvalid()) {
@@ -4598,8 +4615,9 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
currentDecl = cast<NominalTypeDecl>(dc); currentDecl = cast<NominalTypeDecl>(dc);
} }
SourceFile *SF = dc->getParentSourceFile();
ReferencedNameTracker *tracker = nullptr; ReferencedNameTracker *tracker = nullptr;
if (SourceFile *SF = dc->getParentSourceFile()) if (SF)
tracker = SF->getReferencedNameTracker(); tracker = SF->getReferencedNameTracker();
// Check each of the conformances associated with this context. // Check each of the conformances associated with this context.
@@ -4804,85 +4822,88 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc,
// If there were any unsatisfied requirements, check whether there // If there were any unsatisfied requirements, check whether there
// are any near-matches we should diagnose. // are any near-matches we should diagnose.
if (!unsatisfiedReqs.empty() && !anyInvalid) { if (!unsatisfiedReqs.empty() && !anyInvalid) {
// Find all of the members that aren't used to satisfy if (SF && SF->Kind != SourceFileKind::Interface) {
// requirements, and check whether they are close to an // Find all of the members that aren't used to satisfy
// unsatisfied or defaulted requirement. // requirements, and check whether they are close to an
for (auto member : idc->getMembers()) { // unsatisfied or defaulted requirement.
// Filter out anything that couldn't satisfy one of the for (auto member : idc->getMembers()) {
// requirements or was used to satisfy a different requirement. // Filter out anything that couldn't satisfy one of the
auto value = dyn_cast<ValueDecl>(member); // requirements or was used to satisfy a different requirement.
if (!value) continue; auto value = dyn_cast<ValueDecl>(member);
if (isa<TypeDecl>(value)) continue; if (!value) continue;
if (!value->getFullName()) continue; if (isa<TypeDecl>(value)) continue;
if (!value->getFullName()) continue;
// If this declaration overrides another declaration, the signature is // If this declaration overrides another declaration, the signature is
// fixed; don't complain about near misses. // fixed; don't complain about near misses.
if (value->getOverriddenDecl()) continue; if (value->getOverriddenDecl()) continue;
// If this member is a witness to any @objc requirement, ignore it. // If this member is a witness to any @objc requirement, ignore it.
if (!findWitnessedObjCRequirements(value, /*anySingleRequirement=*/true) if (!findWitnessedObjCRequirements(value, /*anySingleRequirement=*/true)
.empty()) .empty())
continue; continue;
// Find the unsatisfied requirements with the nearest-matching // Find the unsatisfied requirements with the nearest-matching
// names. // names.
SmallVector<ValueDecl *, 4> bestOptionalReqs; SmallVector<ValueDecl *, 4> bestOptionalReqs;
unsigned bestScore = UINT_MAX; unsigned bestScore = UINT_MAX;
for (auto req : unsatisfiedReqs) { for (auto req : unsatisfiedReqs) {
// Skip unavailable requirements. // Skip unavailable requirements.
if (req->getAttrs().isUnavailable(Context)) continue; if (req->getAttrs().isUnavailable(Context)) continue;
// Score this particular optional requirement. // Score this particular optional requirement.
auto score = scorePotentiallyMatching(*this, req, value, bestScore); auto score = scorePotentiallyMatching(*this, req, value, bestScore);
if (!score) continue; if (!score) continue;
// If the score is better than the best we've seen, update the best // If the score is better than the best we've seen, update the best
// and clear out the list. // and clear out the list.
if (*score < bestScore) { if (*score < bestScore) {
bestOptionalReqs.clear(); bestOptionalReqs.clear();
bestScore = *score; bestScore = *score;
}
// If this score matches the (possible new) best score, record it.
if (*score == bestScore)
bestOptionalReqs.push_back(req);
}
// If we found some requirements with nearly-matching names, diagnose
// the first one.
if (bestScore < UINT_MAX) {
bestOptionalReqs.erase(
std::remove_if(
bestOptionalReqs.begin(),
bestOptionalReqs.end(),
[&](ValueDecl *req) {
return !shouldWarnAboutPotentialWitness(groupChecker, req, value,
defaultAccess, bestScore);
}),
bestOptionalReqs.end());
}
// If we have something to complain about, do so.
if (!bestOptionalReqs.empty()) {
auto req = bestOptionalReqs[0];
bool diagnosed = false;
for (auto conformance : conformances) {
if (conformance->getProtocol() == req->getDeclContext()) {
diagnosePotentialWitness(*this,
conformance->getRootNormalConformance(),
req, value, defaultAccess);
diagnosed = true;
break;
} }
}
assert(diagnosed && "Failed to find conformance to diagnose?");
(void)diagnosed;
// Remove this requirement from the list. We don't want to // If this score matches the (possible new) best score, record it.
// complain about it twice. if (*score == bestScore)
unsatisfiedReqs.erase(std::find(unsatisfiedReqs.begin(), bestOptionalReqs.push_back(req);
unsatisfiedReqs.end(), }
req));
// If we found some requirements with nearly-matching names, diagnose
// the first one.
if (bestScore < UINT_MAX) {
bestOptionalReqs.erase(
std::remove_if(
bestOptionalReqs.begin(),
bestOptionalReqs.end(),
[&](ValueDecl *req) {
return !shouldWarnAboutPotentialWitness(groupChecker, req,
value, defaultAccess,
bestScore);
}),
bestOptionalReqs.end());
}
// If we have something to complain about, do so.
if (!bestOptionalReqs.empty()) {
auto req = bestOptionalReqs[0];
bool diagnosed = false;
for (auto conformance : conformances) {
if (conformance->getProtocol() == req->getDeclContext()) {
diagnosePotentialWitness(*this,
conformance->getRootNormalConformance(),
req, value, defaultAccess);
diagnosed = true;
break;
}
}
assert(diagnosed && "Failed to find conformance to diagnose?");
(void)diagnosed;
// Remove this requirement from the list. We don't want to
// complain about it twice.
unsatisfiedReqs.erase(std::find(unsatisfiedReqs.begin(),
unsatisfiedReqs.end(),
req));
}
} }
} }

View File

@@ -2697,6 +2697,7 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {
return nullptr; return nullptr;
} }
theStruct->setAddedImplicitInitializers();
if (isImplicit) if (isImplicit)
theStruct->setImplicit(); theStruct->setImplicit();
theStruct->setIsObjC(isObjC); theStruct->setIsObjC(isObjC);
@@ -3604,6 +3605,7 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {
return nullptr; return nullptr;
} }
theEnum->setAddedImplicitInitializers();
if (isImplicit) if (isImplicit)
theEnum->setImplicit(); theEnum->setImplicit();
theEnum->setIsObjC(isObjC); theEnum->setIsObjC(isObjC);

View File

@@ -0,0 +1,34 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -enable-resilience -o %t/Conformances.swiftmodule %s
// RUN: %target-swift-frontend -emit-sil -I %t %S/Inputs/ConformancesUser.swift -O | %FileCheck %s
public protocol MyProto {
init()
func method()
var prop: Int { get set }
subscript(index: Int) -> Int { get set }
}
@_fixed_layout // allow conformance devirtualization
public struct FullStructImpl: MyProto {
public init()
public func method()
public var prop: Int { get set }
public subscript(index: Int) -> Int { get set }
}
// CHECK-LABEL: sil @$S16ConformancesUser8testFullSiyF
// CHECK: function_ref @$S12Conformances14FullStructImplVACycfC
// CHECK: function_ref @$S12Conformances14FullStructImplV6methodyyF
// CHECK: function_ref @$S12Conformances14FullStructImplV4propSivs
// CHECK: function_ref @$S12Conformances14FullStructImplVyS2icig
// CHECK: end sil function '$S16ConformancesUser8testFullSiyF'
@_fixed_layout // allow conformance devirtualization
public struct OpaqueStructImpl: MyProto {}
// CHECK-LABEL: sil @$S16ConformancesUser10testOpaqueSiyF
// CHECK: function_ref @$S12Conformances7MyProtoPxycfC
// CHECK: function_ref @$S12Conformances7MyProtoP6methodyyF
// CHECK: function_ref @$S12Conformances7MyProtoP4propSivs
// CHECK: function_ref @$S12Conformances7MyProtoPyS2icig
// CHECK: end sil function '$S16ConformancesUser10testOpaqueSiyF'

View File

@@ -0,0 +1,16 @@
import Conformances
func testGeneric<T: MyProto>(_: T.Type) -> Int {
var impl = T.init()
impl.method()
impl.prop = 0
return impl[0]
}
public func testFull() -> Int {
return testGeneric(FullStructImpl.self)
}
public func testOpaque() -> Int {
return testGeneric(OpaqueStructImpl.self)
}