Skip to content

Prefer witnesses with no difference effects to ones that have fewer effects #40088

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
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
38 changes: 32 additions & 6 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "swift/AST/AccessScope.h"
#include "swift/AST/ClangModuleLoader.h"
#include "swift/AST/Decl.h"
#include "swift/AST/Effects.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/GenericSignature.h"
Expand Down Expand Up @@ -930,6 +931,25 @@ static Optional<RequirementMatch> findMissingGenericRequirementForSolutionFix(
return Optional<RequirementMatch>();
}

/// Determine the set of effects on a given declaration.
static PossibleEffects getEffects(ValueDecl *value) {
if (auto func = dyn_cast<AbstractFunctionDecl>(value)) {
PossibleEffects result;
if (func->hasThrows())
result |= EffectKind::Throws;
if (func->hasAsync())
result |= EffectKind::Async;
return result;
}

if (auto storage = dyn_cast<AbstractStorageDecl>(value)) {
if (auto accessor = storage->getEffectfulGetAccessor())
return getEffects(accessor);
}

return PossibleEffects();
}

RequirementMatch
swift::matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache,
ProtocolDecl *proto, ProtocolConformance *conformance,
Expand Down Expand Up @@ -1107,12 +1127,17 @@ swift::matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache,
return RequirementMatch(witness, MatchKind::TypeConflict,
witnessType);

MatchKind matchKind = MatchKind::ExactMatch;
if (hasAnyError(optionalAdjustments))
matchKind = MatchKind::OptionalityConflict;
else if (anyRenaming)
matchKind = MatchKind::RenamedMatch;
else if (getEffects(witness).containsOnly(getEffects(req)))
matchKind = MatchKind::FewerEffects;

// Success. Form the match result.
RequirementMatch result(witness,
hasAnyError(optionalAdjustments)
? MatchKind::OptionalityConflict
: anyRenaming ? MatchKind::RenamedMatch
: MatchKind::ExactMatch,
matchKind,
witnessType,
reqEnvironment,
optionalAdjustments);
Expand Down Expand Up @@ -2461,6 +2486,7 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
auto &diags = module->getASTContext().Diags;
switch (match.Kind) {
case MatchKind::ExactMatch:
case MatchKind::FewerEffects:
diags.diagnose(match.Witness, diag::protocol_witness_exact_match,
withAssocTypes);
break;
Expand Down Expand Up @@ -5746,7 +5772,7 @@ static void diagnosePotentialWitness(NormalProtocolConformance *conformance,
auto dc = conformance->getDeclContext();
auto match = matchWitness(oneUseCache, conformance->getProtocol(),
conformance, dc, req, witness);
if (match.Kind == MatchKind::ExactMatch &&
if (match.isWellFormed() &&
req->isObjC() && !witness->isObjC()) {
// Special case: note to add @objc.
auto diag =
Expand Down Expand Up @@ -6431,7 +6457,7 @@ swift::findWitnessedObjCRequirements(const ValueDecl *witness,
if (matchWitness(reqEnvCache, proto, *conformance,
witnessToMatch->getDeclContext(), req,
const_cast<ValueDecl *>(witnessToMatch))
.Kind == MatchKind::ExactMatch) {
.isWellFormed()) {
if (accessorKind) {
auto *storageReq = dyn_cast<AbstractStorageDecl>(req);
if (!storageReq)
Expand Down
43 changes: 42 additions & 1 deletion lib/Sema/TypeCheckProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ enum class MatchKind : uint8_t {
/// The witness matched the requirement exactly.
ExactMatch,

/// The witness has fewer effects than the requirement, which is okay.
FewerEffects,

/// There is a difference in optionality.
OptionalityConflict,

Expand Down Expand Up @@ -488,10 +491,47 @@ struct RequirementMatch {
/// The matched derivative generic signature.
GenericSignature DerivativeGenSig;

/// Determine whether this match is viable.
/// Determine whether this match is well-formed, meaning that it is any
/// difference determined by requirement matching is acceptable.
bool isWellFormed() const {
switch(Kind) {
case MatchKind::ExactMatch:
case MatchKind::FewerEffects:
return true;

case MatchKind::OptionalityConflict:
case MatchKind::RenamedMatch:
case MatchKind::WitnessInvalid:
case MatchKind::Circularity:
case MatchKind::KindConflict:
case MatchKind::TypeConflict:
case MatchKind::MissingRequirement:
case MatchKind::StaticNonStaticConflict:
case MatchKind::SettableConflict:
case MatchKind::PrefixNonPrefixConflict:
case MatchKind::PostfixNonPostfixConflict:
case MatchKind::MutatingConflict:
case MatchKind::NonMutatingConflict:
case MatchKind::ConsumingConflict:
case MatchKind::RethrowsConflict:
case MatchKind::RethrowsByConformanceConflict:
case MatchKind::AsyncConflict:
case MatchKind::ThrowsConflict:
case MatchKind::NonObjC:
case MatchKind::MissingDifferentiableAttr:
case MatchKind::EnumCaseWithAssociatedValues:
return false;
}

llvm_unreachable("Unhandled MatchKind in switch.");
}

/// Determine whether this match is viable, meaning that we could generate
/// a witness for it, even though there might be semantic errors.
bool isViable() const {
switch(Kind) {
case MatchKind::ExactMatch:
case MatchKind::FewerEffects:
case MatchKind::OptionalityConflict:
case MatchKind::RenamedMatch:
return true;
Expand Down Expand Up @@ -525,6 +565,7 @@ struct RequirementMatch {
bool hasWitnessType() const {
switch(Kind) {
case MatchKind::ExactMatch:
case MatchKind::FewerEffects:
case MatchKind::RenamedMatch:
case MatchKind::TypeConflict:
case MatchKind::MissingRequirement:
Expand Down
4 changes: 2 additions & 2 deletions lib/Sema/TypeCheckProtocolInference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,8 @@ AssociatedTypeInference::inferTypeWitnessesViaValueWitness(ValueDecl *req,
// Match the witness. If we don't succeed, throw away the inference
// information.
// FIXME: A renamed match might be useful to retain for the failure case.
if (matchWitness(dc, req, witness, setup, matchTypes, finalize)
.Kind != MatchKind::ExactMatch) {
if (!matchWitness(dc, req, witness, setup, matchTypes, finalize)
.isWellFormed()) {
inferred.Inferred.clear();
}

Expand Down
37 changes: 37 additions & 0 deletions test/decl/protocol/async_requirements.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// RUN: %target-typecheck-verify-swift -disable-availability-checking

// Ensure that a protocol with async requirements can be conformed to by
// non-async requirements, and that overloading works.
protocol A {
func foo()
func foo() async

init()
init() async

var property: Int { get async }

func bar() throws
func bar() async throws
}

struct A1: A {
func foo() { }

var property: Int = 17

func bar() { }
}

struct A2: A {
func foo() { }
func foo() async { }

init() { }
init() async { }

var property: Int { get async { 5 } }

func bar() throws { }
func bar() async throws { }
}