diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index fc2b17b953d5b..da53d4401b8b4 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -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" @@ -930,6 +931,25 @@ static Optional findMissingGenericRequirementForSolutionFix( return Optional(); } +/// Determine the set of effects on a given declaration. +static PossibleEffects getEffects(ValueDecl *value) { + if (auto func = dyn_cast(value)) { + PossibleEffects result; + if (func->hasThrows()) + result |= EffectKind::Throws; + if (func->hasAsync()) + result |= EffectKind::Async; + return result; + } + + if (auto storage = dyn_cast(value)) { + if (auto accessor = storage->getEffectfulGetAccessor()) + return getEffects(accessor); + } + + return PossibleEffects(); +} + RequirementMatch swift::matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache, ProtocolDecl *proto, ProtocolConformance *conformance, @@ -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); @@ -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; @@ -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 = @@ -6431,7 +6457,7 @@ swift::findWitnessedObjCRequirements(const ValueDecl *witness, if (matchWitness(reqEnvCache, proto, *conformance, witnessToMatch->getDeclContext(), req, const_cast(witnessToMatch)) - .Kind == MatchKind::ExactMatch) { + .isWellFormed()) { if (accessorKind) { auto *storageReq = dyn_cast(req); if (!storageReq) diff --git a/lib/Sema/TypeCheckProtocol.h b/lib/Sema/TypeCheckProtocol.h index e66cdcfc9c9c1..c09d67ad3ac78 100644 --- a/lib/Sema/TypeCheckProtocol.h +++ b/lib/Sema/TypeCheckProtocol.h @@ -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, @@ -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; @@ -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: diff --git a/lib/Sema/TypeCheckProtocolInference.cpp b/lib/Sema/TypeCheckProtocolInference.cpp index b86b6fdcf2d56..6f608aacdb3fe 100644 --- a/lib/Sema/TypeCheckProtocolInference.cpp +++ b/lib/Sema/TypeCheckProtocolInference.cpp @@ -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(); } diff --git a/test/decl/protocol/async_requirements.swift b/test/decl/protocol/async_requirements.swift new file mode 100644 index 0000000000000..b85615985c4b3 --- /dev/null +++ b/test/decl/protocol/async_requirements.swift @@ -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 { } +}