-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[ModuleInterface] Allow conformances to be missing value witnesses #18932
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1060,6 +1060,23 @@ bool WitnessChecker::findBestWitness( | |
} | ||
|
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ve noticed your patches often switch over the kind or check for an interface file - maybe a convenience predicate would be handy? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doug mentioned this too in #18778, but I'm not sure the rules are consistent enough to be worth it. Sometimes the special case is just for interface files; sometimes it's interfaces or SIL; sometimes it's interface, SIL, or library (i.e. not script mode). |
||
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 && | ||
conformance != nullptr && | ||
conformance->isInvalid()) { | ||
|
@@ -4598,8 +4615,9 @@ void TypeChecker::checkConformancesInContext(DeclContext *dc, | |
currentDecl = cast<NominalTypeDecl>(dc); | ||
} | ||
|
||
SourceFile *SF = dc->getParentSourceFile(); | ||
ReferencedNameTracker *tracker = nullptr; | ||
if (SourceFile *SF = dc->getParentSourceFile()) | ||
if (SF) | ||
tracker = SF->getReferencedNameTracker(); | ||
|
||
// 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 | ||
// are any near-matches we should diagnose. | ||
if (!unsatisfiedReqs.empty() && !anyInvalid) { | ||
// Find all of the members that aren't used to satisfy | ||
// requirements, and check whether they are close to an | ||
// unsatisfied or defaulted requirement. | ||
for (auto member : idc->getMembers()) { | ||
// Filter out anything that couldn't satisfy one of the | ||
// requirements or was used to satisfy a different requirement. | ||
auto value = dyn_cast<ValueDecl>(member); | ||
if (!value) continue; | ||
if (isa<TypeDecl>(value)) continue; | ||
if (!value->getFullName()) continue; | ||
|
||
// If this declaration overrides another declaration, the signature is | ||
// fixed; don't complain about near misses. | ||
if (value->getOverriddenDecl()) continue; | ||
|
||
// If this member is a witness to any @objc requirement, ignore it. | ||
if (!findWitnessedObjCRequirements(value, /*anySingleRequirement=*/true) | ||
.empty()) | ||
continue; | ||
if (SF && SF->Kind != SourceFileKind::Interface) { | ||
// Find all of the members that aren't used to satisfy | ||
// requirements, and check whether they are close to an | ||
// unsatisfied or defaulted requirement. | ||
for (auto member : idc->getMembers()) { | ||
// Filter out anything that couldn't satisfy one of the | ||
// requirements or was used to satisfy a different requirement. | ||
auto value = dyn_cast<ValueDecl>(member); | ||
if (!value) continue; | ||
if (isa<TypeDecl>(value)) continue; | ||
if (!value->getFullName()) continue; | ||
|
||
// Find the unsatisfied requirements with the nearest-matching | ||
// names. | ||
SmallVector<ValueDecl *, 4> bestOptionalReqs; | ||
unsigned bestScore = UINT_MAX; | ||
for (auto req : unsatisfiedReqs) { | ||
// Skip unavailable requirements. | ||
if (req->getAttrs().isUnavailable(Context)) continue; | ||
|
||
// Score this particular optional requirement. | ||
auto score = scorePotentiallyMatching(*this, req, value, bestScore); | ||
if (!score) continue; | ||
|
||
// If the score is better than the best we've seen, update the best | ||
// and clear out the list. | ||
if (*score < bestScore) { | ||
bestOptionalReqs.clear(); | ||
bestScore = *score; | ||
} | ||
// If this declaration overrides another declaration, the signature is | ||
// fixed; don't complain about near misses. | ||
if (value->getOverriddenDecl()) continue; | ||
|
||
// If this score matches the (possible new) best score, record it. | ||
if (*score == bestScore) | ||
bestOptionalReqs.push_back(req); | ||
} | ||
// If this member is a witness to any @objc requirement, ignore it. | ||
if (!findWitnessedObjCRequirements(value, /*anySingleRequirement=*/true) | ||
.empty()) | ||
continue; | ||
|
||
// 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()); | ||
} | ||
// Find the unsatisfied requirements with the nearest-matching | ||
// names. | ||
SmallVector<ValueDecl *, 4> bestOptionalReqs; | ||
unsigned bestScore = UINT_MAX; | ||
for (auto req : unsatisfiedReqs) { | ||
// Skip unavailable requirements. | ||
if (req->getAttrs().isUnavailable(Context)) continue; | ||
|
||
// Score this particular optional requirement. | ||
auto score = scorePotentiallyMatching(*this, req, value, bestScore); | ||
if (!score) continue; | ||
|
||
// If the score is better than the best we've seen, update the best | ||
// and clear out the list. | ||
if (*score < bestScore) { | ||
bestOptionalReqs.clear(); | ||
bestScore = *score; | ||
} | ||
|
||
// 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; | ||
// 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 | ||
// complain about it twice. | ||
unsatisfiedReqs.erase(std::find(unsatisfiedReqs.begin(), | ||
unsatisfiedReqs.end(), | ||
req)); | ||
} | ||
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)); | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don’t expect to generate textual interfaces for Swift 4 modules, we can make it an error in Swift 5 mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We may still want to to help with incremental builds, but yes.