Skip to content

Commit 6dc0c7c

Browse files
committed
NCGenerics: break cycle with SuperclassTypeRequest
With NoncopyableGenerics, we get a cycle involving `SuperclassTypeRequest` with this program: public struct RawMarkupHeader {} final class RawMarkup: ManagedBuffer<RawMarkupHeader, RawMarkup> { } Because we generally don't support the following kind of relationship: class Base<T: P>: P {} class Derived: Base<Derived> {} This commit works around the root-cause, which is that Derived's synthesized conformance to Copyable gets superceded by the inherited one from Base. Instead of recording conformances in the ConformanceLookup table at all, create builtin conformances on the fly, since classes cannot be conditionally Copyable or Escapable.
1 parent 1c85264 commit 6dc0c7c

File tree

7 files changed

+123
-43
lines changed

7 files changed

+123
-43
lines changed

include/swift/AST/InverseMarking.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ struct InverseMarking {
5757
}
5858
operator bool() const { return isPresent(); }
5959

60+
// Is there any kind of explicit marking?
61+
bool isAnyExplicit() const {
62+
return is(Kind::Explicit) || is(Kind::LegacyExplicit);
63+
}
64+
6065
SourceLoc getLoc() const { return loc; }
6166

6267
void set(Kind k, SourceLoc l = SourceLoc()) {

include/swift/AST/ProtocolConformance.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,6 +1157,19 @@ class BuiltinProtocolConformance final : public RootProtocolConformance {
11571157
return getBuiltinConformanceKind() == BuiltinConformanceKind::Missing;
11581158
}
11591159

1160+
bool isInvalid() const {
1161+
switch (getBuiltinConformanceKind()) {
1162+
case BuiltinConformanceKind::Synthesized:
1163+
return false;
1164+
case BuiltinConformanceKind::Missing:
1165+
return true;
1166+
}
1167+
}
1168+
1169+
SourceLoc getLoc() const {
1170+
return SourceLoc();
1171+
}
1172+
11601173
/// Get any requirements that must be satisfied for this conformance to apply.
11611174
llvm::Optional<ArrayRef<Requirement>>
11621175
getConditionalRequirementsIfAvailable() const {
@@ -1191,6 +1204,10 @@ class BuiltinProtocolConformance final : public RootProtocolConformance {
11911204
llvm_unreachable("builtin-conformances never have associated types");
11921205
}
11931206

1207+
bool hasWitness(ValueDecl *requirement) const {
1208+
llvm_unreachable("builtin-conformances never have requirement witnesses");
1209+
}
1210+
11941211
/// Retrieve the type witness and type decl (if one exists)
11951212
/// for the given associated type.
11961213
TypeWitnessAndDecl
@@ -1199,6 +1216,10 @@ class BuiltinProtocolConformance final : public RootProtocolConformance {
11991216
llvm_unreachable("builtin-conformances never have associated types");
12001217
}
12011218

1219+
Witness getWitness(ValueDecl *requirement) const {
1220+
llvm_unreachable("builtin-conformances never have requirement witnesses");
1221+
}
1222+
12021223
/// Given that the requirement signature of the protocol directly states
12031224
/// that the given dependent type must conform to the given protocol,
12041225
/// return its associated conformance.

lib/AST/ConformanceLookup.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/AST/DiagnosticsSema.h"
3030
#include "swift/AST/ExistentialLayout.h"
3131
#include "swift/AST/GenericEnvironment.h"
32+
#include "swift/AST/InverseMarking.h"
3233
#include "swift/AST/NameLookup.h"
3334
#include "swift/AST/NameLookupRequests.h"
3435
#include "swift/AST/PackConformance.h"
@@ -402,6 +403,41 @@ static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
402403
return ProtocolConformanceRef::forMissingOrInvalid(type, protocol);
403404
}
404405

406+
static ProtocolConformanceRef
407+
getBuiltinInvertibleProtocolConformance(NominalTypeDecl *nominal,
408+
Type type,
409+
ProtocolDecl *protocol) {
410+
assert(isa<ClassDecl>(nominal));
411+
ASTContext &ctx = protocol->getASTContext();
412+
413+
auto ip = protocol->getInvertibleProtocolKind();
414+
switch (*ip) {
415+
case InvertibleProtocolKind::Copyable:
416+
// If move-only classes is enabled, we'll check the markings.
417+
if (ctx.LangOpts.hasFeature(Feature::MoveOnlyClasses)) {
418+
auto marking = nominal->getMarking(*ip);
419+
switch (marking.getInverse().getKind()) {
420+
case InverseMarking::Kind::LegacyExplicit:
421+
case InverseMarking::Kind::Explicit:
422+
// An inverse ~Copyable prevents conformance.
423+
return ProtocolConformanceRef::forInvalid();
424+
425+
case InverseMarking::Kind::Inferred: // ignore "inferred" inverse marking
426+
case InverseMarking::Kind::None:
427+
break;
428+
}
429+
}
430+
break;
431+
case InvertibleProtocolKind::Escapable:
432+
// Always conforms.
433+
break;
434+
}
435+
436+
return ProtocolConformanceRef(
437+
ctx.getBuiltinConformance(type, protocol,
438+
BuiltinConformanceKind::Synthesized));
439+
}
440+
405441
/// Synthesize a builtin type conformance to the given protocol, if
406442
/// appropriate.
407443
static ProtocolConformanceRef
@@ -586,6 +622,13 @@ LookupConformanceInModuleRequest::evaluate(
586622
if (!nominal || isa<ProtocolDecl>(nominal))
587623
return ProtocolConformanceRef::forMissingOrInvalid(type, protocol);
588624

625+
// We specially avoid recording conformances to invertible protocols in a
626+
// class's conformance table. This prevents an evaluator cycle.
627+
if (ctx.LangOpts.hasFeature(Feature::NoncopyableGenerics)
628+
&& isa<ClassDecl>(nominal)
629+
&& protocol->getInvertibleProtocolKind())
630+
return getBuiltinInvertibleProtocolConformance(nominal, type, protocol);
631+
589632
// Expand conformances added by extension macros.
590633
//
591634
// FIXME: This expansion should only be done if the

lib/AST/ProtocolConformance.cpp

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,10 @@ switch (getKind()) { \
102102
return cast<NormalProtocolConformance>(this)->Method Args; \
103103
case ProtocolConformanceKind::Self: \
104104
return cast<SelfProtocolConformance>(this)->Method Args; \
105+
case ProtocolConformanceKind::Builtin: \
106+
return cast<BuiltinProtocolConformance>(this)->Method Args; \
105107
case ProtocolConformanceKind::Specialized: \
106108
case ProtocolConformanceKind::Inherited: \
107-
case ProtocolConformanceKind::Builtin: \
108109
llvm_unreachable("not a root conformance"); \
109110
} \
110111
llvm_unreachable("bad ProtocolConformanceKind");
@@ -1090,19 +1091,21 @@ void NominalTypeDecl::prepareConformanceTable() const {
10901091
// Synthesize the unconditional conformances to invertible protocols.
10911092
// For conditional ones, see findSynthesizedConformances .
10921093
if (ctx.LangOpts.hasFeature(Feature::NoncopyableGenerics)) {
1093-
bool missingOne = false;
1094-
for (auto ip : InvertibleProtocolSet::full()) {
1095-
auto invertible = getMarking(ip);
1096-
if (!invertible.getInverse() || bool(invertible.getPositive()))
1097-
addSynthesized(ctx.getProtocol(getKnownProtocolKind(ip)));
1098-
else
1099-
missingOne = true;
1100-
}
1101-
1102-
// FIXME: rdar://122289155 (NCGenerics: convert Equatable, Hashable, and RawRepresentable to ~Copyable.)
1103-
if (missingOne)
1104-
return;
1094+
// Classes get their conformances during ModuleDecl::lookupConformance.
1095+
if (!isa<ClassDecl>(this)) {
1096+
bool missingOne = false;
1097+
for (auto ip : InvertibleProtocolSet::full()) {
1098+
auto invertible = getMarking(ip);
1099+
if (!invertible.getInverse() || bool(invertible.getPositive()))
1100+
addSynthesized(ctx.getProtocol(getKnownProtocolKind(ip)));
1101+
else
1102+
missingOne = true;
1103+
}
11051104

1105+
// FIXME: rdar://122289155 (NCGenerics: convert Equatable, Hashable, and RawRepresentable to ~Copyable.)
1106+
if (missingOne)
1107+
return;
1108+
}
11061109
} else if (!canBeCopyable()) {
11071110
return; // No synthesized conformances for move-only nominals.
11081111
}

lib/Sema/TypeCheckDeclPrimary.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3183,6 +3183,29 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
31833183
}
31843184
}
31853185

3186+
static void diagnoseInverseOnClass(ClassDecl *decl) {
3187+
auto &ctx = decl->getASTContext();
3188+
3189+
for (auto ip : InvertibleProtocolSet::full()) {
3190+
auto marking = decl->getMarking(ip);
3191+
3192+
// Inferred inverses are already ignored for classes.
3193+
// FIXME: we can also diagnose @_moveOnly here if we use `isAnyExplicit`
3194+
if (!marking.getInverse().is(InverseMarking::Kind::Explicit))
3195+
continue;
3196+
3197+
// Allow ~Copyable when MoveOnlyClasses is enabled
3198+
if (ip == InvertibleProtocolKind::Copyable
3199+
&& ctx.LangOpts.hasFeature(Feature::MoveOnlyClasses))
3200+
continue;
3201+
3202+
3203+
ctx.Diags.diagnose(marking.getInverse().getLoc(),
3204+
diag::inverse_on_class,
3205+
getProtocolName(getKnownProtocolKind(ip)));
3206+
}
3207+
}
3208+
31863209
/// check to see if a move-only type can ever conform to the given type.
31873210
/// \returns true iff a diagnostic was emitted because it was not compatible
31883211
static bool diagnoseIncompatibleWithMoveOnlyType(SourceLoc loc,
@@ -3415,6 +3438,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
34153438

34163439
diagnoseIncompatibleProtocolsForMoveOnlyType(CD);
34173440

3441+
diagnoseInverseOnClass(CD);
34183442
}
34193443

34203444
void visitProtocolDecl(ProtocolDecl *PD) {

lib/Sema/TypeCheckInvertible.cpp

Lines changed: 9 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -171,20 +171,14 @@ static bool checkInvertibleConformanceCommon(ProtocolConformance *conformance,
171171
auto &ctx = nom->getASTContext();
172172
bool conforms = true;
173173

174-
// An explicit `~IP` prevents conformance if any of these are true:
174+
// An explicit `~IP` prevents conformance if it appears on the same
175+
// declaration that also declares the conformance.
175176
//
176-
// 1. It appears on a class.
177-
// 2. Appears on the same declaration that also declares the conformance.
178-
// So, if the nominal has `~Copyable` but this conformance is
179-
// written in an extension, then we do not raise an error.
177+
// So, if the nominal has `~Copyable` but this conformance is
178+
// written in an extension, then we do not raise an error.
180179
auto marking = nom->getMarking(ip);
181-
if (marking.getInverse().getKind() == InverseMarking::Kind::Explicit) {
182-
if (isa<ClassDecl>(nom)) {
183-
ctx.Diags.diagnose(marking.getInverse().getLoc(),
184-
diag::inverse_on_class,
185-
getProtocolName(kp));
186-
conforms &= false;
187-
} else if (conformance->getDeclContext() == nom) {
180+
if (marking.getInverse().isAnyExplicit()) {
181+
if (conformance->getDeclContext() == nom) {
188182
ctx.Diags.diagnose(marking.getInverse().getLoc(),
189183
diag::inverse_but_also_conforms,
190184
nom, getProtocolName(kp));
@@ -198,7 +192,7 @@ static bool checkInvertibleConformanceCommon(ProtocolConformance *conformance,
198192

199193
// Protocols do not directly define any storage.
200194
if (isa<ProtocolDecl, BuiltinTupleDecl>(nom))
201-
llvm_unreachable("unexpected nominal to check Copyable conformance");
195+
llvm_unreachable("unexpected nominal to check invertible's conformance");
202196

203197
// A deinit prevents a struct or enum from conforming to Copyable.
204198
if (ip == InvertibleProtocolKind::Copyable) {
@@ -343,6 +337,7 @@ ProtocolConformance *deriveConformanceForInvertible(Evaluator &evaluator,
343337
if (!ip)
344338
llvm_unreachable("not an invertible protocol");
345339

340+
assert(!isa<ClassDecl>(nominal) && "classes aren't handled here");
346341
auto file = cast<FileUnit>(nominal->getModuleScopeContext());
347342

348343
// Generates a conformance for the nominal to the protocol.
@@ -403,20 +398,6 @@ ProtocolConformance *deriveConformanceForInvertible(Evaluator &evaluator,
403398
return generateConformance(ext);
404399
};
405400

406-
switch (*ip) {
407-
case InvertibleProtocolKind::Copyable:
408-
// If move-only classes is enabled, we'll check the markings.
409-
if (ctx.LangOpts.hasFeature(Feature::MoveOnlyClasses))
410-
break;
411-
412-
LLVM_FALLTHROUGH;
413-
case InvertibleProtocolKind::Escapable:
414-
// Always derive unconditional IP conformance for classes
415-
if (isa<ClassDecl>(nominal))
416-
return generateConformance(nominal);
417-
break;
418-
}
419-
420401
auto marking = nominal->getMarking(*ip);
421402

422403
// Unexpected to have any positive marking for IP if we're deriving it.
@@ -430,10 +411,8 @@ ProtocolConformance *deriveConformanceForInvertible(Evaluator &evaluator,
430411
return nullptr; // No positive IP conformance will be inferred.
431412

432413
case InverseMarking::Kind::Inferred:
433-
if (!isa<ClassDecl>(nominal))
434-
return generateConditionalConformance();
414+
return generateConditionalConformance();
435415

436-
LLVM_FALLTHROUGH;
437416
case InverseMarking::Kind::None:
438417
// All types already start with conformances to the invertible protocols in
439418
// this case, within `NominalTypeDecl::prepareConformanceTable`.

test/Generics/inverse_generics.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,12 @@ protocol NeedsCopyable {}
169169

170170
struct Silly: ~Copyable, Copyable {} // expected-error {{struct 'Silly' required to be 'Copyable' but is marked with '~Copyable'}}
171171
enum Sally: Copyable, ~Copyable, NeedsCopyable {} // expected-error {{enum 'Sally' required to be 'Copyable' but is marked with '~Copyable'}}
172+
172173
class NiceTry: ~Copyable, Copyable {} // expected-error {{classes cannot be '~Copyable'}}
174+
// expected-error@-1 {{class 'NiceTry' required to be 'Copyable' but is marked with '~Copyable}}
175+
176+
@_moveOnly class NiceTry2: Copyable {} // expected-error {{'@_moveOnly' attribute is only valid on structs or enums}}
177+
// expected-error@-1 {{class 'NiceTry2' required to be 'Copyable' but is marked with '~Copyable'}}
173178

174179
struct OopsConformance1: ~Copyable, NeedsCopyable {}
175180
// expected-error@-1 {{type 'OopsConformance1' does not conform to protocol 'NeedsCopyable'}}

0 commit comments

Comments
 (0)