Skip to content

Commit 179bc4b

Browse files
authored
Merge pull request #72820 from kavon/conditional-copyable-restriction
NCGenerics: restrict conditional Copyable reqs
2 parents 7d4f62f + 0ce97b8 commit 179bc4b

File tree

8 files changed

+104
-11
lines changed

8 files changed

+104
-11
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2807,7 +2807,7 @@ ERROR(protocol_has_missing_requirements_versioned,none,
28072807
(Type, Type, llvm::VersionTuple, llvm::VersionTuple))
28082808
ERROR(requirement_restricts_self,none,
28092809
"%kindonly0 requirement %0 cannot add constraint "
2810-
"'%1%select{:|:| ==|:}2 %3' on 'Self'",
2810+
"'%1%select{:|:| ==|:| has same shape as}2 %3' on 'Self'",
28112811
(const ValueDecl *, StringRef, unsigned, StringRef))
28122812
ERROR(witness_argument_name_mismatch,none,
28132813
"%kind0 has different argument labels "
@@ -7665,6 +7665,10 @@ ERROR(inverse_extension, none,
76657665
ERROR(copyable_illegal_deinit, none,
76667666
"deinitializer cannot be declared in %kind0 that conforms to 'Copyable'",
76677667
(const ValueDecl *))
7668+
ERROR(inverse_cannot_be_conditional_on_requirement, none,
7669+
"conditional conformance to suppressible %kind0 cannot depend on "
7670+
"'%noformat1%select{:|:| ==|:| has same shape as}2 %noformat3'",
7671+
(const ProtocolDecl *, Type, unsigned, Type))
76687672
ERROR(inverse_type_member_in_conforming_type,none,
76697673
"%select{stored property %2|associated value %2}1 of "
76707674
"'%4'-conforming %kind3 has non-%4 type %0",

include/swift/AST/RequirementKind.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ enum class RequirementKind : unsigned {
3636
Layout,
3737
/// A same-shape requirement shape(T) == shape(U), where T and U are pack
3838
/// parameters.
39-
SameShape
39+
SameShape,
4040

4141
// Note: there is code that packs this enum in a 3-bit bitfield. Audit users
4242
// when adding enumerators.
43+
LAST_KIND=SameShape
4344
};
4445

4546
} // namespace swift

lib/AST/DiagnosticEngine.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,13 @@ static void formatDiagnosticArgument(StringRef Modifier,
757757
case DiagnosticArgumentKind::FullyQualifiedType:
758758
case DiagnosticArgumentKind::Type:
759759
case DiagnosticArgumentKind::WitnessType: {
760-
assert(Modifier.empty() && "Improper modifier for Type argument");
760+
std::optional<DiagnosticFormatOptions> TypeFormatOpts;
761+
if (Modifier == "noformat") {
762+
TypeFormatOpts.emplace(DiagnosticFormatOptions::formatForFixIts());
763+
} else {
764+
assert(Modifier.empty() && "Improper modifier for Type argument");
765+
TypeFormatOpts.emplace(FormatOpts);
766+
}
761767

762768
// Strip extraneous parentheses; they add no value.
763769
Type type;
@@ -829,7 +835,7 @@ static void formatDiagnosticArgument(StringRef Modifier,
829835

830836
auto descriptiveKind = opaqueTypeDecl->getDescriptiveKind();
831837

832-
Out << llvm::format(FormatOpts.OpaqueResultFormatString.c_str(),
838+
Out << llvm::format(TypeFormatOpts->OpaqueResultFormatString.c_str(),
833839
type->getString(printOptions).c_str(),
834840
Decl::getDescriptiveKindName(descriptiveKind).data(),
835841
NamingDeclText.c_str());
@@ -843,11 +849,11 @@ static void formatDiagnosticArgument(StringRef Modifier,
843849
llvm::raw_svector_ostream OutAka(AkaText);
844850

845851
getAkaTypeForDisplay(type)->print(OutAka, printOptions);
846-
Out << llvm::format(FormatOpts.AKAFormatString.c_str(),
852+
Out << llvm::format(TypeFormatOpts->AKAFormatString.c_str(),
847853
typeName.c_str(), AkaText.c_str());
848854
} else {
849-
Out << FormatOpts.OpeningQuotationMark << typeName
850-
<< FormatOpts.ClosingQuotationMark;
855+
Out << TypeFormatOpts->OpeningQuotationMark << typeName
856+
<< TypeFormatOpts->ClosingQuotationMark;
851857
}
852858
}
853859
break;

lib/Sema/TypeCheckGeneric.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ void TypeChecker::checkProtocolSelfRequirements(ValueDecl *decl) {
302302
req.getFirstType()->is<GenericTypeParamType>())
303303
continue;
304304

305+
static_assert((unsigned)RequirementKind::LAST_KIND == 4,
306+
"update %select in diagnostic!");
305307
ctx.Diags.diagnose(decl, diag::requirement_restricts_self, decl,
306308
req.getFirstType().getString(),
307309
static_cast<unsigned>(req.getKind()),

lib/Sema/TypeCheckInvertible.cpp

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,51 @@ static void checkInvertibleConformanceCommon(DeclContext *dc,
140140
if (conformance.isConcrete()) {
141141
auto concrete = conformance.getConcrete();
142142
if (auto *normalConf = dyn_cast<NormalProtocolConformance>(concrete)) {
143-
hasUnconditionalConformance =
144-
normalConf->getConditionalRequirements().empty();
145143
conformanceLoc = normalConf->getLoc();
146144
assert(conformanceLoc);
145+
146+
auto condReqs = normalConf->getConditionalRequirements();
147+
hasUnconditionalConformance = condReqs.empty();
148+
auto *thisProto = normalConf->getProtocol();
149+
150+
// Ensure that conditional conformance to an invertible protocol IP only
151+
// depends conformance requirements involving IP, and its subject is not
152+
// a dependent member type.
153+
//
154+
// In theory, it could depend on any invertible protocol, but it may be
155+
// confusing if we permitted that and this simplifies the model a bit.
156+
for (auto req : condReqs) {
157+
Type illegalSecondType;
158+
159+
// If we are diagnosing, fill-in the second-type string of this req.
160+
switch (req.getKind()) {
161+
case RequirementKind::Layout:
162+
assert(req.getLayoutConstraint()->isClass());
163+
illegalSecondType = ctx.getAnyObjectType();
164+
break;
165+
case RequirementKind::Conformance:
166+
if (req.getProtocolDecl() == thisProto
167+
&& !req.getFirstType()->is<DependentMemberType>())
168+
break; // permitted, don't fill-in.
169+
LLVM_FALLTHROUGH;
170+
case RequirementKind::Superclass:
171+
case RequirementKind::SameType:
172+
case RequirementKind::SameShape:
173+
illegalSecondType = req.getSecondType();
174+
break;
175+
}
176+
177+
static_assert((unsigned)RequirementKind::LAST_KIND == 4,
178+
"update %select in diagnostic!");
179+
if (illegalSecondType) {
180+
auto t = ctx.Diags.diagnose(conformanceLoc,
181+
diag::inverse_cannot_be_conditional_on_requirement,
182+
thisProto,
183+
req.getFirstType(),
184+
static_cast<unsigned>(req.getKind()),
185+
illegalSecondType);
186+
}
187+
}
147188
}
148189
}
149190
assert(!conformance.isPack() && "not handled");
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %target-typecheck-verify-swift \
2+
// RUN: -enable-experimental-feature NoncopyableGenerics \
3+
// RUN: -enable-experimental-feature NonescapableTypes \
4+
// RUN: -enable-experimental-feature SuppressedAssociatedTypes
5+
6+
protocol P {}
7+
protocol Q {}
8+
class DoggoClass {}
9+
10+
struct Blah<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
11+
extension Blah: Copyable {} // expected-error {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'T: Escapable'}}
12+
extension Blah: Escapable {} // expected-error {{conditional conformance to suppressible protocol 'Escapable' cannot depend on 'T: Copyable'}}
13+
14+
struct Yuck<T: ~Copyable & ~Escapable>: ~Copyable, ~Escapable {}
15+
extension Yuck: Copyable where T: ~Escapable {}
16+
extension Yuck: Escapable where T: ~Copyable {}
17+
18+
struct TryConformance<Whatever: ~Copyable>: ~Copyable {}
19+
extension TryConformance: Copyable
20+
where Whatever: P, Whatever: Q, Whatever: Sendable {}
21+
// expected-error@-2 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: P'}}
22+
// expected-error@-3 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: Q'}}
23+
// expected-error@-4 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: Sendable'}}
24+
25+
struct TrySameType<Whatever: ~Copyable>: ~Copyable {}
26+
extension TrySameType: Copyable
27+
where Whatever == Int {}
28+
// expected-error@-2 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever == Int'}}
29+
30+
struct TryClassAndLayoutConstraints<Whatever: ~Copyable, Heckin>: ~Copyable {}
31+
extension TryClassAndLayoutConstraints: Copyable
32+
where Heckin: DoggoClass, Whatever: AnyObject {}
33+
// expected-error@-2 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Whatever: AnyObject'}}
34+
// expected-error@-3 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Heckin: DoggoClass'}}
35+
36+
protocol Queue: ~Copyable { associatedtype Job: ~Copyable }
37+
struct Scheduler<Q: Queue>: ~Copyable {}
38+
extension Scheduler: Copyable where Q.Job: Copyable {}
39+
// expected-error@-1 {{conditional conformance to suppressible protocol 'Copyable' cannot depend on 'Q.Job: Copyable'}}

test/ModuleInterface/Inputs/NoncopyableGenerics_Misc.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ extension Outer: Copyable {}
9898
extension Outer.InnerStruct: Copyable {}
9999

100100
extension Outer.InnerVariation1: Copyable {}
101-
extension Outer.InnerVariation2: Escapable {}
101+
extension Outer.InnerVariation2: Escapable where A: ~Copyable {}
102102

103103
extension Outer.InnerStruct {
104104
public func hello<T: ~Escapable>(_ t: T) {}

test/ModuleInterface/noncopyable_generics.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ import NoncopyableGenerics_Misc
139139
// CHECK-MISC: #endif
140140

141141
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics
142-
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation2 : Swift.Escapable {
142+
// CHECK-MISC-NEXT: extension {{.*}}.Outer.InnerVariation2 : Swift.Escapable where A : ~Copyable {
143143
// CHECK-MISC: #endif
144144

145145
// CHECK-MISC: #if compiler(>=5.3) && $NoncopyableGenerics

0 commit comments

Comments
 (0)