Skip to content

Commit 5e51773

Browse files
committed
NCGenerics: find conflicts in PCT's
We weren't diagnosing conflicts in PCT's like `Copyable & ~Copyable`, instead deferring until that PCT was constrained to something like the existential Self or a generic parameter, which then we'd diagnose. But we should canonicalize PCT's such as `Copyable & Copyable` into `Any`, which represents the empty composition. That's what the assert in PCT::build is about.
1 parent c8bf35a commit 5e51773

File tree

6 files changed

+153
-67
lines changed

6 files changed

+153
-67
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7567,6 +7567,9 @@ ERROR(inverse_with_class_constraint, none,
75677567
"composition involving %select{class requirement %2|'AnyObject'}0 "
75687568
"cannot contain '~%1'",
75697569
(bool, StringRef, Type))
7570+
ERROR(inverse_conflicts_explicit_composition, none,
7571+
"composition cannot contain '~%0' when another member requires '%0'",
7572+
(StringRef))
75707573
ERROR(inverse_extension, none,
75717574
"cannot apply inverse %0 to extension",
75727575
(Type))

include/swift/AST/Requirement.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,15 +255,26 @@ struct InverseRequirement {
255255
static void enumerateDefaultedParams(GenericContext *decl,
256256
SmallVectorImpl<Type> &result);
257257

258-
/// \returns the protocols that are required by default for the given type
259-
/// parameter. These are not inverses themselves.
260-
static InvertibleProtocolSet expandDefault(Type gp);
261-
262258
/// Appends additional requirements corresponding to defaults for the given
263259
/// generic parameters.
264260
static void expandDefaults(ASTContext &ctx,
265261
ArrayRef<Type> gps,
266262
SmallVectorImpl<StructuralRequirement> &result);
263+
264+
/// Adds the inferred default protocols for an assumed generic parameter with
265+
/// respect to that parameter's inverses and existing required protocols.
266+
/// For example, if an inverse ~P exists, then P will not be added to the
267+
/// protocols list.
268+
///
269+
/// Similarly, if the protocols list has a protocol Q that already implies
270+
/// Copyable, then we will not add `Copyable` to the protocols list.
271+
///
272+
/// \param inverses the inverses '& ~P' that are applied to the generic param.
273+
/// \param protocols the existing required protocols, to which defaults will
274+
/// be appended.
275+
static void expandDefaults(ASTContext &ctx,
276+
InvertibleProtocolSet inverses,
277+
SmallVectorImpl<ProtocolDecl*> &protocols);
267278
};
268279

269280
} // end namespace swift

lib/AST/Requirement.cpp

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -342,27 +342,64 @@ void InverseRequirement::enumerateDefaultedParams(
342342
add(gtpd->getDeclaredInterfaceType());
343343
}
344344

345-
InvertibleProtocolSet InverseRequirement::expandDefault(Type gp) {
346-
assert(gp->isTypeParameter());
347-
return InvertibleProtocolSet::full();
348-
}
349-
350345
void InverseRequirement::expandDefaults(
351346
ASTContext &ctx,
352347
ArrayRef<Type> gps,
353348
SmallVectorImpl<StructuralRequirement> &result) {
354-
if (!ctx.LangOpts.hasFeature(Feature::NoncopyableGenerics))
349+
350+
SmallVector<ProtocolDecl*, NumInvertibleProtocols> defaults;
351+
expandDefaults(ctx, /*inverses=*/{}, defaults);
352+
353+
// Fast-path.
354+
if (defaults.empty())
355355
return;
356356

357357
for (auto gp : gps) {
358-
auto protos = InverseRequirement::expandDefault(gp);
359-
for (auto ip : protos) {
360-
auto proto = ctx.getProtocol(getKnownProtocolKind(ip));
361-
assert(proto && "missing Copyable/Escapable from stdlib!");
362-
358+
for (auto *proto : defaults) {
363359
auto protoTy = proto->getDeclaredInterfaceType();
364360
result.push_back({{RequirementKind::Conformance, gp, protoTy},
365361
SourceLoc()});
366362
}
367363
}
368364
}
365+
366+
void InverseRequirement::expandDefaults(
367+
ASTContext &ctx,
368+
InvertibleProtocolSet inverses,
369+
SmallVectorImpl<ProtocolDecl*> &protocols) {
370+
371+
// Skip unless noncopyable generics is enabled
372+
if (!ctx.LangOpts.hasFeature(swift::Feature::NoncopyableGenerics))
373+
return;
374+
375+
// Try to add all invertible protocols, unless:
376+
// - an inverse was provided
377+
// - an existing protocol already requires it
378+
for (auto ip : InvertibleProtocolSet::full()) {
379+
// This matches with `lookupExistentialConformance`'s use of 'inheritsFrom'.
380+
bool alreadyRequired = false;
381+
for (auto proto : protocols) {
382+
if (proto->isSpecificProtocol(getKnownProtocolKind(ip))
383+
|| proto->requiresInvertible(ip)) {
384+
alreadyRequired = true;
385+
break;
386+
}
387+
}
388+
389+
// If some protocol member already implies P, then we don't need to
390+
// add a requirement for P.
391+
if (alreadyRequired) {
392+
assert(!inverses.contains(ip) && "cannot require P and ~P");
393+
continue;
394+
}
395+
396+
// Nothing implies P, so unless there's an inverse ~P, add the requirement.
397+
if (inverses.contains(ip))
398+
continue;
399+
400+
auto proto = ctx.getProtocol(getKnownProtocolKind(ip));
401+
assert(proto && "missing Copyable/Escapable from stdlib!");
402+
403+
protocols.push_back(proto);
404+
}
405+
}

lib/AST/Type.cpp

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -278,50 +278,7 @@ bool TypeBase::allowsOwnership(const GenericSignatureImpl *sig) {
278278
return getCanonicalType().allowsOwnership(sig);
279279
}
280280

281-
/// Adds the inferred default protocols for an ExistentialLayout with respect
282-
/// to that existential's inverses and existing protocols. For example, if an
283-
/// inverse ~P exists, then P will not be added to the protocols list.
284-
///
285-
/// Similarly, if the protocols list has a protocol Q that already implies
286-
/// Copyable, then we will not add `Copyable` to the protocols list.
287-
///
288-
/// \param inverses the inverses '& ~P' that are in the existential's type.
289-
/// \param protocols the output vector of protocols for an ExistentialLayout
290-
/// to be modified.
291-
static void expandDefaultProtocols(
292-
ASTContext &ctx,
293-
InvertibleProtocolSet inverses,
294-
SmallVectorImpl<ProtocolDecl*> &protocols) {
295-
296-
// Skip unless noncopyable generics is enabled
297-
if (!ctx.LangOpts.hasFeature(swift::Feature::NoncopyableGenerics))
298-
return;
299281

300-
// Try to add all invertible protocols, unless:
301-
// - an inverse was provided
302-
// - an existing protocol already requires it
303-
for (auto ip : InvertibleProtocolSet::full()) {
304-
if (inverses.contains(ip))
305-
continue;
306-
307-
// This matches with `lookupExistentialConformance`'s use of 'inheritsFrom'.
308-
bool alreadyRequired = false;
309-
for (auto proto : protocols) {
310-
if (proto->requiresInvertible(ip)) {
311-
alreadyRequired = true;
312-
break;
313-
}
314-
}
315-
316-
if (alreadyRequired)
317-
continue;
318-
319-
auto proto = ctx.getProtocol(getKnownProtocolKind(ip));
320-
assert(proto && "missing Copyable/Escapable from stdlib!");
321-
322-
protocols.push_back(proto);
323-
}
324-
}
325282

326283
ExistentialLayout::ExistentialLayout(CanProtocolType type) {
327284
auto *protoDecl = type->getDecl();
@@ -336,7 +293,7 @@ ExistentialLayout::ExistentialLayout(CanProtocolType type) {
336293
protocols.push_back(protoDecl);
337294

338295
// NOTE: all the invertible protocols are usable from ObjC.
339-
expandDefaultProtocols(type->getASTContext(), {}, protocols);
296+
InverseRequirement::expandDefaults(type->getASTContext(), {}, protocols);
340297
}
341298

342299
ExistentialLayout::ExistentialLayout(CanProtocolCompositionType type) {
@@ -373,7 +330,9 @@ ExistentialLayout::ExistentialLayout(CanProtocolCompositionType type) {
373330
hasExplicitAnyObject && !explicitSuperclass && getProtocols().empty();
374331

375332
// NOTE: all the invertible protocols are usable from ObjC.
376-
expandDefaultProtocols(type->getASTContext(), type->getInverses(), protocols);
333+
InverseRequirement::expandDefaults(type->getASTContext(),
334+
type->getInverses(),
335+
protocols);
377336
}
378337

379338
ExistentialLayout::ExistentialLayout(CanParameterizedProtocolType type)
@@ -3939,7 +3898,7 @@ Type ProtocolCompositionType::get(const ASTContext &C,
39393898
ArrayRef<Type> Members,
39403899
InvertibleProtocolSet Inverses,
39413900
bool HasExplicitAnyObject) {
3942-
// Fast path for 'AnyObject' and 'Any'.
3901+
// Fast path for 'AnyObject', 'Any', and '~Copyable'.
39433902
if (Members.empty()) {
39443903
return build(C, Members, Inverses, HasExplicitAnyObject);
39453904
}
@@ -3994,6 +3953,13 @@ Type ProtocolCompositionType::get(const ASTContext &C,
39943953
}
39953954
}
39963955

3956+
// Drop any explicitly provided invertible protocols.
3957+
if (auto ip = proto->getInvertibleProtocolKind()) {
3958+
// We diagnose '~Copyable & Copyable' before forming the PCT.
3959+
assert(!Inverses.contains(*ip) && "opposing invertible constraints!");
3960+
continue;
3961+
}
3962+
39973963
CanTypes.push_back(proto->getDeclaredInterfaceType());
39983964
}
39993965

lib/Sema/TypeCheckType.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5367,6 +5367,46 @@ NeverNullType TypeResolver::resolveTupleType(TupleTypeRepr *repr,
53675367
return TupleType::get(elements, ctx);
53685368
}
53695369

5370+
/// \returns the inverse ~P that is conflicted if a protocol in \c tys
5371+
/// requires P. For example, `Q & ~Copyable` is a conflict, if `Q` requires
5372+
/// or is equal to `Copyable`
5373+
static std::optional<InvertibleProtocolKind>
5374+
hasConflictedInverse(ArrayRef<Type> tys, InvertibleProtocolSet inverses) {
5375+
// Fast-path: no inverses that could be conflicted!
5376+
if (inverses.empty())
5377+
return std::nullopt;
5378+
5379+
for (auto ty : tys) {
5380+
// Handle nested PCT's recursively since we haven't flattened them away yet.
5381+
if (auto pct = dyn_cast<ProtocolCompositionType>(ty)) {
5382+
if (auto conflict = hasConflictedInverse(pct->getMembers(), inverses))
5383+
return conflict;
5384+
continue;
5385+
}
5386+
5387+
// Dig out a protocol.
5388+
ProtocolDecl *decl = nullptr;
5389+
if (auto protoTy = dyn_cast<ProtocolType>(ty))
5390+
decl = protoTy->getDecl();
5391+
else if (auto paramProtoTy = dyn_cast<ParameterizedProtocolType>(ty))
5392+
decl = paramProtoTy->getProtocol();
5393+
5394+
if (!decl)
5395+
continue;
5396+
5397+
// If an inverse ~I exists for this protocol member of the PCT that
5398+
// requires I, then it's a conflict.
5399+
for (auto inverse : inverses) {
5400+
if (decl->isSpecificProtocol(getKnownProtocolKind(inverse))
5401+
|| decl->requiresInvertible(inverse)) {
5402+
return inverse;
5403+
}
5404+
}
5405+
}
5406+
5407+
return std::nullopt;
5408+
}
5409+
53705410
NeverNullType
53715411
TypeResolver::resolveCompositionType(CompositionTypeRepr *repr,
53725412
TypeResolutionOptions options) {
@@ -5462,6 +5502,13 @@ TypeResolver::resolveCompositionType(CompositionTypeRepr *repr,
54625502
IsInvalid = true;
54635503
}
54645504

5505+
// Cannot provide an inverse in the same composition requiring the protocol.
5506+
if (auto conflict = hasConflictedInverse(Members, Inverses)) {
5507+
diagnose(repr->getLoc(),
5508+
diag::inverse_conflicts_explicit_composition,
5509+
getProtocolName(getKnownProtocolKind(*conflict)));
5510+
IsInvalid = true;
5511+
}
54655512

54665513
if (IsInvalid) {
54675514
repr->setInvalid();

test/Generics/inverse_generics.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ struct ConditionalContainment<T: ~Copyable> {
8080

8181
func chk(_ T: RequireCopyable<ConditionalContainment<Int>>) {}
8282

83+
// expected-note@+2 3{{add}}
84+
// expected-error@+1 {{parameter of noncopyable type 'some Escapable & ~Copyable' must specify ownership}}
85+
func dogDays(_ t: some Escapable & ~Copyable) {}
8386

8487
/// ----------------
8588

@@ -289,11 +292,14 @@ func conflict1<T>(_ t: T) where T: NeedsCopyable, T: ~Copyable {}
289292
func conflict2<T: ~Copyable>(_ t: AlwaysCopyable<T>) {}
290293
// expected-error@-1 {{'T' required to be 'Copyable' but is marked with '~Copyable'}}
291294

292-
func conflict3<T: NeedsCopyable & ~Copyable>(_ t: T) {}
295+
func conflict3a<T: NeedsCopyable & ~Copyable>(_ t: T) {}
296+
// expected-error@-1 {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
297+
298+
func conflict3b<T>(_ t: T) where T: NeedsCopyable, T: ~Copyable {}
293299
// expected-error@-1 {{'T' required to be 'Copyable' but is marked with '~Copyable'}}
294300

295-
func conflict4(_ t: some NeedsCopyable & ~Copyable) {}
296-
// expected-error@-1 {{'some NeedsCopyable & ~Copyable' required to be 'Copyable' but is marked with '~Copyable'}}
301+
func conflict4a(_ t: some NeedsCopyable & ~Copyable) {}
302+
// expected-error@-1 {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
297303

298304
protocol Conflict5: ~Copyable {
299305
borrowing func whatever() -> AlwaysCopyable<Self> // expected-error {{type 'Self' does not conform to protocol 'Copyable'}}
@@ -322,7 +328,7 @@ func conflict9<U: ~Copyable>(_ u: Conflict9<U>) {}
322328
// expected-error@-1 {{'U' required to be 'Copyable' but is marked with '~Copyable'}}
323329

324330
func conflict10<T>(_ t: T, _ u: some ~Copyable & Copyable)
325-
// expected-error@-1 {{'some ~Copyable & Copyable' required to be 'Copyable' but is marked with '~Copyable'}}
331+
// expected-error@-1 {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
326332
where T: Copyable,
327333
T: ~Copyable {}
328334
// expected-error@-1 {{'T' required to be 'Copyable' but is marked with '~Copyable'}}
@@ -378,6 +384,10 @@ func checkClassBound2<T>(_ t: T) where T: ~Escapable, T: AnyObject, T: ~Copyable
378384
func checkClassBound3<T>(_ t: T) where T: Soup & ~Copyable & ~Escapable {}
379385
// expected-error@-1 {{composition involving class requirement 'Soup' cannot contain '~Copyable'}}
380386

387+
func checkClassBound4<T>(_ t: T) where T: Soup, T: ~Copyable & ~Escapable {}
388+
// expected-error@-1 {{'T' required to be 'Copyable' but is marked with '~Copyable'}}
389+
// expected-error@-2 {{'T' required to be 'Escapable' but is marked with '~Escapable'}}
390+
381391
public func checkAnyObjInv1<Result: AnyObject>(_ t: borrowing Result) where Result: ~Copyable {}
382392
// expected-error@-1 {{'Result' required to be 'Copyable' but is marked with '~Copyable'}}
383393

@@ -390,8 +400,10 @@ public func checkAnyObject<Result>(_ t: Result) where Result: AnyObject {
390400

391401
func checkExistentialAndClasses(
392402
_ a: any AnyObject & ~Copyable, // expected-error {{composition involving 'AnyObject' cannot contain '~Copyable'}}
393-
_ b: any Soup & Copyable & ~Escapable & ~Copyable, // expected-error {{composition involving class requirement 'Soup' cannot contain '~Copyable'}}
394-
_ c: some (~Escapable & Removed) & Soup // expected-error {{composition involving class requirement 'Soup' cannot contain '~Escapable'}}
403+
_ b: any Soup & Copyable & ~Escapable & ~Copyable,
404+
// expected-error@-1 {{composition involving class requirement 'Soup' cannot contain '~Copyable'}}
405+
// expected-error@-2 {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
406+
_ c: some (~Escapable & Removed) & Soup // expected-error {{composition cannot contain '~Escapable' when another member requires 'Escapable'}}
395407
) {}
396408

397409
protocol HasNCBuddy: ~Copyable {
@@ -435,3 +447,13 @@ protocol Q: Copyable {}
435447
// expected-error@+1 {{parameter of noncopyable type 'Blahaj<T>' must specify ownership}}
436448
func testBlahaj<T, U: Q>(_ x: Blahaj<T>,
437449
_ y: Blahaj<U>) {}
450+
451+
extension Int: NeedsCopyable {}
452+
453+
func checkExistentials() {
454+
let _: any ~Escapable & (NeedsCopyable & ~Copyable) // expected-error {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
455+
let _: any NeedsCopyable & ~Copyable = 1 // expected-error {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
456+
let _: any NeedsCopyable & ~Escapable = 1 // expected-error {{composition cannot contain '~Escapable' when another member requires 'Escapable'}}
457+
let _: any Copyable & ~Copyable = 1 // expected-error {{composition cannot contain '~Copyable' when another member requires 'Copyable'}}
458+
let _: any Escapable & ~Escapable = 1 // expected-error {{composition cannot contain '~Escapable' when another member requires 'Escapable'}}
459+
}

0 commit comments

Comments
 (0)