Skip to content

Commit 4dc0db3

Browse files
committed
Sema: Improve the 'protocol method can't impose new requirements on Self' check
This check had two problems. First, it would assert upon encountering a layout requirement, due to an unimplemented code path. A more fundamental issue is that the logic wasn't fully sound, because it would miss certain cases, for example: protocol P { associatedtype A func run<B: Equatable>(_: B) where B == Self.A } Here, the reduced type of `Self.A` is `B`, and at first glance, the requirement `B: Equatable` appears to be fine. However, this is actually a new requirement on `Self`, and the protocol be rejected. Now that we can change the reduction order by assigning weights to generic parameters, this check can be implemented in a better way, by building a new generic signature first, where all generic parameters introduced by the protocol method, like 'B' above, are assigned a non-zero weight. With this reduction order, any type that is equivalent to a member type of `Self` will have a reduced type rooted in `Self`, at which point the previous syntactic check becomes sound. Since this may cause us to reject code we accepted previously, the type checker now performs the check once: first on the original signature, which may miss certain cases, and then again on the new signature built with the weighted reduction order. If the first check fails, we diagnose an error. If the second check fails, we only diagnose a warning. However, this warning will become an error soon, and it really can cause compiler crashes and miscompiles to have a malformed protocol like this. Fixes rdar://116938972.
1 parent de8bf25 commit 4dc0db3

File tree

4 files changed

+174
-31
lines changed

4 files changed

+174
-31
lines changed

include/swift/AST/DiagnosticEngine.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,12 @@ namespace swift {
401401
/// until the next major language version.
402402
InFlightDiagnostic &warnUntilFutureSwiftVersion();
403403

404+
InFlightDiagnostic &warnUntilFutureSwiftVersionIf(bool shouldLimit) {
405+
if (!shouldLimit)
406+
return *this;
407+
return warnUntilFutureSwiftVersion();
408+
}
409+
404410
/// Limit the diagnostic behavior to warning until the specified version.
405411
///
406412
/// This helps stage in fixes for stricter diagnostics as warnings

lib/Sema/TypeCheckGeneric.cpp

Lines changed: 138 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -267,49 +267,157 @@ OpaqueResultTypeRequest::evaluate(Evaluator &evaluator,
267267
return opaqueDecl;
268268
}
269269

270-
/// Determine whether the given type is \c Self, an associated type of \c Self,
271-
/// or a concrete type.
272-
static bool isSelfDerivedOrConcrete(Type protoSelf, Type type) {
273-
// Check for a concrete type.
274-
if (!type->hasTypeParameter())
275-
return true;
270+
static bool checkProtocolSelfRequirementsImpl(
271+
ASTContext &ctx, ProtocolDecl *proto, ValueDecl *decl,
272+
GenericSignature originalSig,
273+
GenericSignature effectiveSig,
274+
bool downgrade,
275+
bool *hasSameTypeRequirement) {
276+
if (hasSameTypeRequirement)
277+
*hasSameTypeRequirement = false;
278+
279+
for (auto req : effectiveSig.getRequirements()) {
280+
if (req.getKind() == RequirementKind::SameType)
281+
if (hasSameTypeRequirement)
282+
*hasSameTypeRequirement = true;
283+
284+
// The conformance of 'Self' to the protocol is okay.
285+
if (req.getKind() == RequirementKind::Conformance &&
286+
req.getProtocolDecl() == proto &&
287+
req.getFirstType()->isEqual(ctx.TheSelfType))
288+
continue;
289+
290+
auto isSelfDerived = [&](Type t) -> bool {
291+
return t->getRootGenericParam()->isEqual(ctx.TheSelfType);
292+
};
293+
294+
// If the requirement's subject type is rooted in an inner generic
295+
// parameter, this requirement is okay.
296+
if (!isSelfDerived(req.getFirstType()))
297+
continue;
298+
299+
Type firstType = req.getFirstType();
300+
Type secondType;
301+
if (req.getKind() == RequirementKind::Layout)
302+
secondType = ctx.getAnyObjectConstraint();
303+
else
304+
secondType = req.getSecondType();
276305

277-
if (type->isTypeParameter() &&
278-
type->getRootGenericParam()->isEqual(protoSelf))
306+
// Self.A.B == <<inner generic parameter>> is OK.
307+
if (req.getKind() == RequirementKind::SameType &&
308+
secondType->isTypeParameter() &&
309+
!isSelfDerived(secondType))
310+
continue;
311+
312+
// Anything else is a new requirement on 'Self', which is not allowed.
313+
314+
// Downgrade even harder in this case, since the old logic always missed it.
315+
if (secondType->hasTypeParameter() && !secondType->isTypeParameter())
316+
downgrade = true;
317+
318+
// Re-sugar the types, since effectiveSig might be canonical.
319+
firstType = originalSig->getSugaredType(firstType);
320+
secondType = originalSig->getSugaredType(secondType);
321+
static_assert((unsigned)RequirementKind::LAST_KIND == 4,
322+
"update %select in diagnostic!");
323+
324+
ctx.Diags.diagnose(decl, diag::requirement_restricts_self, decl,
325+
firstType.getString(),
326+
static_cast<unsigned>(req.getKind()),
327+
secondType.getString())
328+
// FIXME: This should become an unconditional error since violating
329+
// this invariant can introduce compiler and run time crashes.
330+
.warnUntilFutureSwiftVersionIf(downgrade);
279331
return true;
332+
}
280333

281334
return false;
282335
}
283336

284337
// For a generic requirement in a protocol, make sure that the requirement
285338
// set didn't add any requirements to Self or its associated types.
286339
void TypeChecker::checkProtocolSelfRequirements(ValueDecl *decl) {
287-
// For a generic requirement in a protocol, make sure that the requirement
288-
// set didn't add any requirements to Self or its associated types.
340+
// Make sure the generic signature of a protocol method doesn't
341+
// impose any new requirements on Self or one of its member types.
289342
if (auto *proto = dyn_cast<ProtocolDecl>(decl->getDeclContext())) {
343+
auto *genCtx = decl->getAsGenericContext();
344+
ASSERT(genCtx != nullptr);
345+
346+
// If it's not generic and it doesn't have a where clause, there is
347+
// nothing to check.
348+
if (!genCtx->getGenericParams() && !genCtx->getTrailingWhereClause())
349+
return;
350+
290351
auto &ctx = proto->getASTContext();
291-
auto protoSelf = proto->getSelfInterfaceType();
292-
auto sig = decl->getInnermostDeclContext()->getGenericSignatureOfContext();
352+
auto sig = genCtx->getGenericSignature();
353+
354+
bool hasSameTypeRequirement = false;
355+
356+
// Perform the check as it was formerly implemented first, by looking at
357+
// the syntactic requirements of the original generic signature.
358+
if (checkProtocolSelfRequirementsImpl(ctx, proto, decl, sig, sig,
359+
/*downgrade=*/false,
360+
&hasSameTypeRequirement)) {
361+
return;
362+
}
363+
364+
// If the generic signature was sufficiently simple, we're done.
365+
if (!hasSameTypeRequirement)
366+
return;
367+
368+
// The quick check doesn't catch everything when same-type requirements
369+
// are involved, so the correct thing is to build a new signature where
370+
// the innermost generic parameters added by the protocol method now have
371+
// a non-zero weight. In this signature, any type that can be made
372+
// equivalent to a member type of Self will have a reduced type rooted
373+
// in Self.
374+
SmallVector<GenericTypeParamType *, 2> params;
375+
SmallVector<Requirement, 2> reqs;
376+
377+
auto transformParam = [&](GenericTypeParamType *paramTy)
378+
-> GenericTypeParamType * {
379+
if (paramTy->getDepth() == sig->getMaxDepth()) {
380+
return GenericTypeParamType::get(
381+
paramTy->getParamKind(),
382+
paramTy->getDepth(),
383+
paramTy->getIndex(),
384+
/*weight=*/1,
385+
paramTy->getValueType(), ctx);
386+
}
387+
388+
return cast<GenericTypeParamType>(paramTy->getCanonicalType());
389+
};
390+
391+
for (auto *paramTy : sig.getGenericParams())
392+
params.push_back(transformParam(paramTy));
393+
394+
auto transformType = [&](Type t) -> Type {
395+
return t.transformRec([&](TypeBase *t) -> std::optional<Type> {
396+
if (auto *paramTy = dyn_cast<GenericTypeParamType>(t))
397+
return transformParam(paramTy);
398+
399+
return std::nullopt;
400+
})->getCanonicalType();
401+
};
402+
293403
for (auto req : sig.getRequirements()) {
294-
// If one of the types in the requirement is dependent on a non-Self
295-
// type parameter, this requirement is okay.
296-
if (!isSelfDerivedOrConcrete(protoSelf, req.getFirstType()) ||
297-
!isSelfDerivedOrConcrete(protoSelf, req.getSecondType()))
298-
continue;
299-
300-
// The conformance of 'Self' to the protocol is okay.
301-
if (req.getKind() == RequirementKind::Conformance &&
302-
req.getProtocolDecl() == proto &&
303-
req.getFirstType()->is<GenericTypeParamType>())
304-
continue;
305-
306-
static_assert((unsigned)RequirementKind::LAST_KIND == 4,
307-
"update %select in diagnostic!");
308-
ctx.Diags.diagnose(decl, diag::requirement_restricts_self, decl,
309-
req.getFirstType().getString(),
310-
static_cast<unsigned>(req.getKind()),
311-
req.getSecondType().getString());
404+
if (req.getKind() != RequirementKind::Layout) {
405+
reqs.emplace_back(req.getKind(),
406+
transformType(req.getFirstType()),
407+
transformType(req.getSecondType()));
408+
} else {
409+
reqs.emplace_back(req.getKind(),
410+
transformType(req.getFirstType()),
411+
req.getLayoutConstraint());
412+
}
312413
}
414+
415+
auto weightedSig = buildGenericSignature(
416+
ctx, GenericSignature(), params, reqs, /*allowInverses=*/false);
417+
418+
// Repeat the check with the new signature.
419+
checkProtocolSelfRequirementsImpl(ctx, proto, decl, sig, weightedSig,
420+
/*downgrade=*/true, nullptr);
313421
}
314422
}
315423

test/decl/protocol/req/unsatisfiable.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,32 @@ class C7<T>: P7 { // expected-error {{type 'C7<T>' does not conform to protocol
9797
// expected-note@-1 {{add stubs for conformance}}
9898
typealias A = T
9999
}
100+
101+
// This used to just crash.
102+
protocol LayoutConstraint {
103+
associatedtype A
104+
105+
func f<T>(_: T) where A: AnyObject
106+
// expected-error@-1 {{instance method requirement 'f' cannot add constraint 'Self.A: AnyObject' on 'Self'}}
107+
}
108+
109+
protocol Q {
110+
associatedtype A3
111+
}
112+
113+
// We missed these cases originally.
114+
protocol ComplexDerivation {
115+
associatedtype A1
116+
associatedtype A2: Q
117+
118+
func bad1<B: Equatable>(_: B) where B == Self.A1
119+
// expected-warning@-1 {{instance method requirement 'bad1' cannot add constraint 'Self.A1: Equatable' on 'Self'; this will be an error in a future Swift language mode}}
120+
121+
func bad2<B>(_: B) where A1 == [B]
122+
// expected-warning@-1 {{instance method requirement 'bad2' cannot add constraint 'Self.A1 == [B]' on 'Self'; this will be an error in a future Swift language mode}}
123+
124+
func good<B>(_: B) where A2 == B // This is fine
125+
126+
func bad3<B, C>(_: B, _: C) where A2 == B, B.A3 == [C]
127+
// expected-warning@-1 {{instance method requirement 'bad3' cannot add constraint 'Self.A2.A3 == Array<C>' on 'Self'; this will be an error in a future Swift language mode}}
128+
}

validation-test/compiler_crashers_2/16638651735c2c8.swift renamed to validation-test/compiler_crashers_2_fixed/16638651735c2c8.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// {"kind":"typecheck","signature":"swift::TypeChecker::checkProtocolSelfRequirements(swift::ValueDecl*)"}
2-
// RUN: not --crash %target-swift-frontend -typecheck %s
2+
// RUN: not %target-swift-frontend -typecheck %s
33
protocol a {
44
associatedtype b
55
func c () where b : AnyObject

0 commit comments

Comments
 (0)