NCGenerics: restrict conditional Copyable reqs

It doesn't really make sense for a conditional conformance requirement
for `Copyable` to depend on any other requirement other than other
`Copyable` conformance requirements.

resolves rdar://124967739
This commit is contained in:
Kavon Farvardin
2024-04-03 15:31:16 -07:00
parent 3bc570fd93
commit 215c4e1c25
7 changed files with 92 additions and 6 deletions

View File

@@ -2791,7 +2791,7 @@ ERROR(protocol_has_missing_requirements_versioned,none,
(Type, Type, llvm::VersionTuple, llvm::VersionTuple))
ERROR(requirement_restricts_self,none,
"%kindonly0 requirement %0 cannot add constraint "
"'%1%select{:|:| ==|:}2 %3' on 'Self'",
"'%1%select{:|:| ==|:| has same shape as}2 %3' on 'Self'",
(const ValueDecl *, StringRef, unsigned, StringRef))
ERROR(witness_argument_name_mismatch,none,
"%kind0 has different argument labels "
@@ -7644,6 +7644,10 @@ ERROR(inverse_extension, none,
ERROR(copyable_illegal_deinit, none,
"deinitializer cannot be declared in %kind0 that conforms to 'Copyable'",
(const ValueDecl *))
ERROR(inverse_cannot_be_conditional_on_requirement, none,
"conditional conformance to suppressible %kind0 cannot depend on "
"'%1%select{:|:| ==|:| has same shape as}2 %3'",
(const ProtocolDecl *, StringRef, unsigned, StringRef))
ERROR(inverse_type_member_in_conforming_type,none,
"%select{stored property %2|associated value %2}1 of "
"'%4'-conforming %kind3 has non-%4 type %0",

View File

@@ -36,10 +36,11 @@ enum class RequirementKind : unsigned {
Layout,
/// A same-shape requirement shape(T) == shape(U), where T and U are pack
/// parameters.
SameShape
SameShape,
// Note: there is code that packs this enum in a 3-bit bitfield. Audit users
// when adding enumerators.
LAST_KIND=SameShape
};
} // namespace swift

View File

@@ -302,6 +302,8 @@ void TypeChecker::checkProtocolSelfRequirements(ValueDecl *decl) {
req.getFirstType()->is<GenericTypeParamType>())
continue;
static_assert((unsigned)RequirementKind::LAST_KIND == 4,
"update %select in diagnostic!");
ctx.Diags.diagnose(decl, diag::requirement_restricts_self, decl,
req.getFirstType().getString(),
static_cast<unsigned>(req.getKind()),

View File

@@ -140,10 +140,50 @@ static void checkInvertibleConformanceCommon(DeclContext *dc,
if (conformance.isConcrete()) {
auto concrete = conformance.getConcrete();
if (auto *normalConf = dyn_cast<NormalProtocolConformance>(concrete)) {
hasUnconditionalConformance =
normalConf->getConditionalRequirements().empty();
conformanceLoc = normalConf->getLoc();
assert(conformanceLoc);
auto condReqs = normalConf->getConditionalRequirements();
hasUnconditionalConformance = condReqs.empty();
auto *thisProto = normalConf->getProtocol();
// Ensure that conditional conformance to an invertible protocol IP only
// depends conformance requirements involving IP, and its subject is not
// a dependent member type.
//
// In theory, it could depend on any invertible protocol, but it may be
// confusing if we permitted that and this simplifies the model a bit.
for (auto req : condReqs) {
std::optional<StringRef> illegalSecondTypeStr;
// If we are diagnosing, fill-in the second-type string of this req.
switch (req.getKind()) {
case RequirementKind::Layout:
illegalSecondTypeStr = req.getLayoutConstraint().getString();
break;
case RequirementKind::Conformance:
if (req.getProtocolDecl() == thisProto
&& !req.getFirstType()->is<DependentMemberType>())
break; // permitted, don't fill-in.
LLVM_FALLTHROUGH;
case RequirementKind::Superclass:
case RequirementKind::SameType:
case RequirementKind::SameShape:
illegalSecondTypeStr = req.getSecondType().getString();
break;
}
static_assert((unsigned)RequirementKind::LAST_KIND == 4,
"update %select in diagnostic!");
if (illegalSecondTypeStr) {
ctx.Diags.diagnose(conformanceLoc,
diag::inverse_cannot_be_conditional_on_requirement,
thisProto,
req.getFirstType().getString(),
static_cast<unsigned>(req.getKind()),
*illegalSecondTypeStr);
}
}
}
}
assert(!conformance.isPack() && "not handled");

View File

@@ -0,0 +1,39 @@
// RUN: %target-typecheck-verify-swift \
// RUN: -enable-experimental-feature NoncopyableGenerics \
// RUN: -enable-experimental-feature NonescapableTypes \
// RUN: -enable-experimental-feature SuppressedAssociatedTypes
protocol P {}
protocol Q {}
class DoggoClass {}
struct Blah<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
extension Blah: Copyable {} // expected-error {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'T: Escapable'}}
extension Blah: Escapable {} // expected-error {{conditional conformance to suppressible protocol 'Escapable' cannot depend on 'T: Copyable'}}
struct Yuck<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
extension Yuck: Copyable where T: ~Escapable {}
extension Yuck: Escapable where T: ~Copyable {}
struct TryConformance<Whatever: ~Copyable>: ~Copyable {}
extension TryConformance: Copyable
where Whatever: P, Whatever: Q, Whatever: Sendable {}
// expected-error@-2 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: P'}}
// expected-error@-3 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: Q'}}
// expected-error@-4 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: Sendable'}}
struct TrySameType<Whatever: ~Copyable>: ~Copyable {}
extension TrySameType: Copyable
where Whatever == Int {}
// expected-error@-2 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever == Int'}}
struct TryClassAndLayoutConstraints<Whatever: ~Copyable, Heckin>: ~Copyable {}
extension TryClassAndLayoutConstraints: Copyable
where Heckin: DoggoClass, Whatever: AnyObject {}
// expected-error@-2 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: AnyObject'}}
// expected-error@-3 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Heckin: DoggoClass'}}
protocol Queue: ~Copyable { associatedtype Job: ~Copyable }
struct Scheduler<Q: Queue>: ~Copyable {}
extension Scheduler: Copyable where Q.Job: Copyable {}
// expected-error@-1 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Q.Job: Copyable'}}

View File

@@ -98,7 +98,7 @@ extension Outer: Copyable {}
extension Outer.InnerStruct: Copyable {}
extension Outer.InnerVariation1: Copyable {}
extension Outer.InnerVariation2: Escapable {}
extension Outer.InnerVariation2: Escapable where A: ~Copyable {}
extension Outer.InnerStruct {
public func hello<T: ~Escapable>(_ t: T) {}

View File

@@ -134,7 +134,7 @@ import NoncopyableGenerics_Misc
// CHECK-MISC: #endif
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation2 : Swift.Escapable {
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation2 : Swift.Escapable where A : ~Copyable {
// CHECK-MISC: #endif
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics