Skip to content

[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

Merged
merged 1 commit into from
Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 95 additions & 74 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

if (conformance && !conformance->isInvalid()) {
if (auto *SF = DC->getParentSourceFile()) {
if (SF->Kind == SourceFileKind::Interface) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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()) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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));
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/Serialization/Deserialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2699,6 +2699,7 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {
return nullptr;
}

theStruct->setAddedImplicitInitializers();
if (isImplicit)
theStruct->setImplicit();
theStruct->setIsObjC(isObjC);
Expand Down Expand Up @@ -3606,6 +3607,7 @@ ModuleFile::getDeclCheckedImpl(DeclID DID) {
return nullptr;
}

theEnum->setAddedImplicitInitializers();
if (isImplicit)
theEnum->setImplicit();
theEnum->setIsObjC(isObjC);
Expand Down
34 changes: 34 additions & 0 deletions test/ModuleInterface/Conformances.swiftinterface
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'
16 changes: 16 additions & 0 deletions test/ModuleInterface/Inputs/ConformancesUser.swift
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)
}