Skip to content

Commit 4c60b96

Browse files
authored
Merge pull request swiftlang#36171 from slavapestov/fix-rethrows-conformance-hole
Sema: Fix conformance checking for 'rethrows' protocol requirements
2 parents b007800 + 0f30887 commit 4c60b96

File tree

5 files changed

+110
-110
lines changed

5 files changed

+110
-110
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2232,6 +2232,9 @@ NOTE(protocol_witness_settable_conflict,none,
22322232
"candidate is not settable, but protocol requires it", ())
22332233
NOTE(protocol_witness_rethrows_conflict,none,
22342234
"candidate is not 'rethrows', but protocol requires it", ())
2235+
NOTE(protocol_witness_rethrows_by_conformance_conflict,none,
2236+
"candidate is 'rethrows' via a conformance, "
2237+
"but the protocol requirement is not from a '@rethrows' protocol", ())
22352238
NOTE(protocol_witness_throws_conflict,none,
22362239
"candidate throws, but protocol does not allow it", ())
22372240
NOTE(protocol_witness_not_objc,none,

lib/Sema/TypeCheckEffects.cpp

Lines changed: 53 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -239,69 +239,51 @@ class AbstractFunction {
239239
ParamDecl *TheParameter;
240240
Expr *TheExpr;
241241
};
242-
unsigned TheKind : 2;
243-
unsigned ParamCount : 2;
242+
Kind TheKind;
244243
PolymorphicEffectKind RethrowingKind;
245244
SubstitutionMap Substitutions;
246245

247246
public:
248247
explicit AbstractFunction(Kind kind, Expr *fn)
249248
: TheKind(kind),
250-
ParamCount(1),
251249
RethrowingKind(PolymorphicEffectKind::None) {
252250
TheExpr = fn;
253251
}
254252

255253
explicit AbstractFunction(AbstractFunctionDecl *fn, SubstitutionMap subs)
256254
: TheKind(Kind::Function),
257-
ParamCount(fn->getNumCurryLevels()),
258255
RethrowingKind(fn->getPolymorphicEffectKind(EffectKind::Throws)),
259256
Substitutions(subs) {
260257
TheFunction = fn;
261258
}
262259

263260
explicit AbstractFunction(AbstractClosureExpr *closure)
264261
: TheKind(Kind::Closure),
265-
ParamCount(1),
266262
RethrowingKind(PolymorphicEffectKind::None) {
267263
TheClosure = closure;
268264
}
269265

270266
explicit AbstractFunction(ParamDecl *parameter)
271267
: TheKind(Kind::Parameter),
272-
ParamCount(1),
273268
RethrowingKind(PolymorphicEffectKind::None) {
274269
TheParameter = parameter;
275270
}
276271

277-
Kind getKind() const { return Kind(TheKind); }
278-
279-
/// Whether the function is marked 'rethrows'.
280-
bool isBodyRethrows() const {
281-
switch (RethrowingKind) {
282-
case PolymorphicEffectKind::None:
283-
case PolymorphicEffectKind::Always:
284-
return false;
285-
286-
case PolymorphicEffectKind::ByClosure:
287-
case PolymorphicEffectKind::ByConformance:
288-
case PolymorphicEffectKind::Invalid:
289-
return true;
290-
}
291-
}
272+
Kind getKind() const { return TheKind; }
292273

293274
PolymorphicEffectKind getRethrowingKind() const {
294275
return RethrowingKind;
295276
}
296277

297-
unsigned getNumArgumentsForFullApply() const {
298-
return ParamCount;
299-
}
300-
301278
Type getType() const {
302279
switch (getKind()) {
303280
case Kind::Opaque: return getOpaqueFunction()->getType();
304-
case Kind::Function: return getFunction()->getInterfaceType();
281+
case Kind::Function: {
282+
auto *AFD = getFunction();
283+
if (AFD->hasImplicitSelfDecl())
284+
return AFD->getMethodInterfaceType();
285+
return AFD->getInterfaceType();
286+
}
305287
case Kind::Closure: return getClosure()->getType();
306288
case Kind::Parameter: return getParameter()->getType();
307289
}
@@ -336,23 +318,20 @@ class AbstractFunction {
336318
}
337319

338320
static AbstractFunction decomposeApply(ApplyExpr *apply,
339-
SmallVectorImpl<SmallVector<Expr *, 2>> &argLists) {
340-
Expr *fn;
341-
do {
342-
auto *argExpr = apply->getArg();
343-
if (auto *tupleExpr = dyn_cast<TupleExpr>(argExpr)) {
344-
auto args = tupleExpr->getElements();
345-
argLists.emplace_back(args.begin(), args.end());
346-
} else if (auto *parenExpr = dyn_cast<ParenExpr>(argExpr)) {
347-
argLists.emplace_back();
348-
argLists.back().push_back(parenExpr->getSubExpr());
349-
} else {
350-
argLists.emplace_back();
351-
argLists.back().push_back(argExpr);
352-
}
321+
SmallVectorImpl<Expr *> &args) {
322+
auto *argExpr = apply->getArg();
323+
if (auto *tupleExpr = dyn_cast<TupleExpr>(argExpr)) {
324+
auto elts = tupleExpr->getElements();
325+
args.append(elts.begin(), elts.end());
326+
} else {
327+
auto *parenExpr = cast<ParenExpr>(argExpr);
328+
args.push_back(parenExpr->getSubExpr());
329+
}
330+
331+
Expr *fn = apply->getFn()->getValueProvidingExpr();
353332

354-
fn = apply->getFn()->getValueProvidingExpr();
355-
} while ((apply = dyn_cast<ApplyExpr>(fn)));
333+
if (auto *selfCall = dyn_cast<SelfApplyExpr>(fn))
334+
fn = selfCall->getFn()->getValueProvidingExpr();
356335

357336
return decomposeFunction(fn);
358337
}
@@ -702,6 +681,9 @@ class ApplyClassifier {
702681

703682
/// Check to see if the given function application throws or is async.
704683
Classification classifyApply(ApplyExpr *E) {
684+
if (isa<SelfApplyExpr>(E))
685+
return Classification();
686+
705687
// An apply expression is a potential throw site if the function throws.
706688
// But if the expression didn't type-check, suppress diagnostics.
707689
if (!E->getType() || E->getType()->hasError())
@@ -713,88 +695,66 @@ class ApplyClassifier {
713695
if (!fnType) return Classification::forInvalidCode();
714696

715697
bool isAsync = fnType->isAsync() || E->implicitlyAsync();
698+
716699
// Decompose the application.
717-
SmallVector<SmallVector<Expr *, 2>, 2> argLists;
718-
auto fnRef = AbstractFunction::decomposeApply(E, argLists);
700+
SmallVector<Expr *, 2> args;
701+
auto fnRef = AbstractFunction::decomposeApply(E, args);
719702

720703
// If any of the arguments didn't type check, fail.
721-
for (auto argList : argLists) {
722-
for (auto *arg : argList) {
723-
if (!arg->getType() || arg->getType()->hasError())
724-
return Classification::forInvalidCode();
725-
}
704+
for (auto *arg : args) {
705+
if (!arg->getType() || arg->getType()->hasError())
706+
return Classification::forInvalidCode();
726707
}
727708

728709
// If the function doesn't throw at all, we're done here.
729710
if (!fnType->isThrowing()) {
730711
return isAsync ? Classification::forAsync() : Classification();
731712
}
732713

733-
// If we're applying more arguments than the natural argument
734-
// count, then this is a call to the opaque value returned from
735-
// the function.
736-
if (argLists.size() != fnRef.getNumArgumentsForFullApply()) {
737-
// Special case: a reference to an operator within a type might be
738-
// missing 'self'.
739-
// FIXME: The issue here is that this is an ill-formed expression, but
740-
// we don't know it from the structure of the expression.
741-
if (argLists.size() == 1 && fnRef.getKind() == AbstractFunction::Function &&
742-
isa<FuncDecl>(fnRef.getFunction()) &&
743-
cast<FuncDecl>(fnRef.getFunction())->isOperator() &&
744-
fnRef.getNumArgumentsForFullApply() == 2 &&
745-
fnRef.getFunction()->getDeclContext()->isTypeContext()) {
746-
// Can only happen with invalid code.
747-
assert(fnRef.getFunction()->getASTContext().Diags.hadAnyError());
748-
return Classification::forInvalidCode();
749-
}
750-
751-
assert(argLists.size() > fnRef.getNumArgumentsForFullApply() &&
752-
"partial application was throwing?");
753-
return Classification::forThrow(PotentialThrowReason::forThrowingApply(),
754-
isAsync);
755-
}
756-
757-
if (fnRef.getRethrowingKind() == PolymorphicEffectKind::ByConformance) {
714+
// Handle rethrowing functions.
715+
switch (fnRef.getRethrowingKind()) {
716+
case PolymorphicEffectKind::ByConformance: {
758717
auto substitutions = fnRef.getSubstitutions();
759718
for (auto conformanceRef : substitutions.getConformances()) {
760719
if (conformanceRef.hasEffect(EffectKind::Throws)) {
761720
return Classification::forRethrowingOnly(
762721
PotentialThrowReason::forRethrowsConformance(E), isAsync);
763722
}
764723
}
724+
725+
// 'ByConformance' is a superset of 'ByClosure', so check for
726+
// closure arguments too.
727+
LLVM_FALLTHROUGH;
765728
}
766729

767-
// If the function's body is 'rethrows' for the number of
768-
// arguments we gave it, apply the rethrows logic.
769-
if (fnRef.isBodyRethrows()) {
730+
case PolymorphicEffectKind::ByClosure: {
770731
// We need to walk the original parameter types in parallel
771732
// because it only counts for 'rethrows' purposes if it lines up
772733
// with a throwing function parameter in the original type.
773-
Type type = fnRef.getType();
774-
if (!type) return Classification::forInvalidCode();
734+
auto *origType = fnRef.getType()->getAs<AnyFunctionType>();
735+
if (!origType)
736+
return Classification::forInvalidCode();
775737

776738
// Use the most significant result from the arguments.
777739
Classification result = isAsync ? Classification::forAsync()
778740
: Classification();
779-
for (auto argList : llvm::reverse(argLists)) {
780-
auto fnType = type->getAs<AnyFunctionType>();
781-
if (!fnType)
782-
return Classification::forInvalidCode();
783-
784-
auto params = fnType->getParams();
785-
if (params.size() != argList.size())
786-
return Classification::forInvalidCode();
787-
788-
for (unsigned i = 0, e = params.size(); i < e; ++i) {
789-
result.merge(classifyRethrowsArgument(argList[i],
790-
params[i].getParameterType()));
791-
}
792741

793-
type = fnType->getResult();
742+
auto params = origType->getParams();
743+
if (params.size() != args.size())
744+
return Classification::forInvalidCode();
745+
746+
for (unsigned i = 0, e = params.size(); i < e; ++i) {
747+
result.merge(classifyRethrowsArgument(args[i],
748+
params[i].getParameterType()));
794749
}
750+
795751
return result;
796752
}
797753

754+
default:
755+
break;
756+
}
757+
798758
// Try to classify the implementation of functions that we have
799759
// local knowledge of.
800760
Classification result =

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -558,25 +558,39 @@ swift::matchWitness(
558558
if (!funcReq->isMutating() && funcWitness->isMutating())
559559
return RequirementMatch(witness, MatchKind::MutatingConflict);
560560

561-
// If the requirement is rethrows, the witness must either be
562-
// rethrows or be non-throwing if the requirement is not by conformance
563-
// else the witness can be by conformance, throwing or non throwing
564-
if (reqAttrs.hasAttribute<RethrowsAttr>() &&
565-
!witnessAttrs.hasAttribute<RethrowsAttr>()) {
561+
// If the requirement has an explicit 'rethrows' argument, the witness
562+
// must be 'rethrows', too.
563+
if (reqAttrs.hasAttribute<RethrowsAttr>()) {
566564
auto reqRethrowingKind =
567565
funcReq->getPolymorphicEffectKind(EffectKind::Throws);
568566
auto witnessRethrowingKind =
569567
funcWitness->getPolymorphicEffectKind(EffectKind::Throws);
570-
if (reqRethrowingKind == PolymorphicEffectKind::ByConformance) {
571-
switch (witnessRethrowingKind) {
572-
case PolymorphicEffectKind::ByConformance:
573-
case PolymorphicEffectKind::Always:
574-
case PolymorphicEffectKind::None:
568+
569+
assert(reqRethrowingKind != PolymorphicEffectKind::Always &&
570+
reqRethrowingKind != PolymorphicEffectKind::None);
571+
572+
switch (witnessRethrowingKind) {
573+
case PolymorphicEffectKind::None:
574+
case PolymorphicEffectKind::Invalid:
575+
case PolymorphicEffectKind::ByClosure:
576+
break;
577+
578+
case PolymorphicEffectKind::ByConformance: {
579+
// A by-conformance `rethrows` witness cannot witness a
580+
// by-conformance `rethrows` requirement unless the protocol
581+
// is @rethrows. Otherwise, we don't have enough information
582+
// at the call site to assess if the conformance actually
583+
// throws or not.
584+
auto *proto = cast<ProtocolDecl>(req->getDeclContext());
585+
if (reqRethrowingKind == PolymorphicEffectKind::ByConformance &&
586+
proto->hasPolymorphicEffect(EffectKind::Throws))
575587
break;
576-
default:
577-
return RequirementMatch(witness, MatchKind::RethrowsConflict);
578-
}
579-
} else if (cast<AbstractFunctionDecl>(witness)->hasThrows()) {
588+
589+
return RequirementMatch(witness,
590+
MatchKind::RethrowsByConformanceConflict);
591+
}
592+
593+
case PolymorphicEffectKind::Always:
580594
return RequirementMatch(witness, MatchKind::RethrowsConflict);
581595
}
582596
}
@@ -2524,6 +2538,13 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
25242538
diag.fixItReplace(FD->getThrowsLoc(), getTokenText(tok::kw_rethrows));
25252539
break;
25262540
}
2541+
case MatchKind::RethrowsByConformanceConflict: {
2542+
auto witness = match.Witness;
2543+
auto diag =
2544+
diags.diagnose(witness,
2545+
diag::protocol_witness_rethrows_by_conformance_conflict);
2546+
break;
2547+
}
25272548
case MatchKind::NonObjC:
25282549
diags.diagnose(match.Witness, diag::protocol_witness_not_objc);
25292550
break;

lib/Sema/TypeCheckProtocol.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,13 @@ enum class MatchKind : uint8_t {
263263
/// The witness did not match because of __consuming conflicts.
264264
ConsumingConflict,
265265

266-
/// The witness is not rethrows, but the requirement is.
266+
/// The witness throws unconditionally, but the requirement rethrows.
267267
RethrowsConflict,
268268

269+
/// The witness rethrows via conformance, but the requirement rethrows
270+
/// via closure and is not in a '@rethrows' protocol.
271+
RethrowsByConformanceConflict,
272+
269273
/// The witness is explicitly @nonobjc but the requirement is @objc.
270274
NonObjC,
271275

@@ -498,6 +502,7 @@ struct RequirementMatch {
498502
case MatchKind::NonMutatingConflict:
499503
case MatchKind::ConsumingConflict:
500504
case MatchKind::RethrowsConflict:
505+
case MatchKind::RethrowsByConformanceConflict:
501506
case MatchKind::AsyncConflict:
502507
case MatchKind::ThrowsConflict:
503508
case MatchKind::NonObjC:
@@ -530,6 +535,7 @@ struct RequirementMatch {
530535
case MatchKind::NonMutatingConflict:
531536
case MatchKind::ConsumingConflict:
532537
case MatchKind::RethrowsConflict:
538+
case MatchKind::RethrowsByConformanceConflict:
533539
case MatchKind::AsyncConflict:
534540
case MatchKind::ThrowsConflict:
535541
case MatchKind::NonObjC:

test/attr/attr_rethrows_protocol.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,22 +139,32 @@ func takesEmpty<T : Empty>(_: T) rethrows {}
139139

140140
takesEmpty(EmptyWitness())
141141

142-
// FIXME: Fix this soundness hole
142+
// Rethrows kinds need to line up when a 'rethrows' function witnesses a
143+
// 'rethrows' protocol requirement
143144

144-
// Note: SimpleThrowsClosure is not @rethrows
145+
// Note: the SimpleThrowsClosure protocol is not @rethrows
145146
protocol SimpleThrowsClosure {
146147
func doIt(_: () throws -> ()) rethrows
148+
// expected-note@-1 {{protocol requires function 'doIt' with type '(() throws -> ()) throws -> ()'; do you want to add a stub?}}
149+
150+
func doIt2<T : Empty>(_: T) rethrows
151+
// expected-note@-1 {{protocol requires function 'doIt2' with type '<T> (T) throws -> ()'; do you want to add a stub?}}
147152
}
148153

149154
struct ConformsToSimpleThrowsClosure<T : RethrowingProtocol> : SimpleThrowsClosure {
155+
// expected-error@-1 {{type 'ConformsToSimpleThrowsClosure<T>' does not conform to protocol 'SimpleThrowsClosure'}}
150156
let t: T
151157

152158
// This cannot witness SimpleThrowsClosure.doIt(), because the
153159
// T : RethrowingProtocol conformance is a source here, but that
154160
// is not captured in the protocol's requirement signature.
155161
func doIt(_: () throws -> ()) rethrows {
162+
// expected-note@-1 {{candidate is 'rethrows' via a conformance, but the protocol requirement is not from a '@rethrows' protocol}}
156163
try t.source()
157164
}
165+
166+
func doIt2<T : Empty>(_: T) rethrows {}
167+
// expected-note@-1 {{candidate is 'rethrows' via a conformance, but the protocol requirement is not from a '@rethrows' protocol}}
158168
}
159169

160170
func soundnessHole<T : SimpleThrowsClosure>(_ t: T) {

0 commit comments

Comments
 (0)