mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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:
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
34
test/ModuleInterface/Conformances.swiftinterface
Normal file
34
test/ModuleInterface/Conformances.swiftinterface
Normal 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'
|
||||||
16
test/ModuleInterface/Inputs/ConformancesUser.swift
Normal file
16
test/ModuleInterface/Inputs/ConformancesUser.swift
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user