Skip to content

Commit ab11842

Browse files
committed
[CSDiagnostics] Offer a fix-it to conform the decl context to the missing protocols during contextual failure
1 parent 6b9133b commit ab11842

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,9 @@ ERROR(cannot_convert_coerce_nil,none,
459459
ERROR(cannot_convert_assign,none,
460460
"cannot assign value of type %0 to type %1",
461461
(Type,Type))
462+
NOTE(assign_protocol_conformance_fix_it,none,
463+
"add missing conformance to %0 to %1 %2",
464+
(Type, DescriptiveDeclKind, Type))
462465
ERROR(cannot_convert_assign_protocol,none,
463466
"value of type %0 does not conform to %1 in assignment",
464467
(Type, Type))

lib/Sema/CSDiagnostics.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,6 +2006,9 @@ void ContextualFailure::tryFixIts(InFlightDiagnostic &diagnostic) const {
20062006
if (tryIntegerCastFixIts(diagnostic))
20072007
return;
20082008

2009+
if (tryProtocolConformanceFixIt(diagnostic))
2010+
return;
2011+
20092012
if (tryTypeCoercionFixIt(diagnostic))
20102013
return;
20112014
}
@@ -2430,6 +2433,101 @@ bool ContextualFailure::tryTypeCoercionFixIt(
24302433
return false;
24312434
}
24322435

2436+
bool ContextualFailure::tryProtocolConformanceFixIt(
2437+
InFlightDiagnostic &diagnostic) const {
2438+
auto innermostTyCtx = getDC()->getInnermostTypeContext();
2439+
if (!innermostTyCtx)
2440+
return false;
2441+
2442+
auto nominal = innermostTyCtx->getSelfNominalTypeDecl();
2443+
if (!nominal)
2444+
return false;
2445+
2446+
// We need to get rid of optionals and parens as it's not relevant when
2447+
// printing the diagnostic and the fix-it.
2448+
auto unwrappedToType =
2449+
ToType->lookThroughAllOptionalTypes()->getWithoutParens();
2450+
2451+
// If the protocol requires a class & we don't have one (maybe the context
2452+
// is a struct), then bail out instead of offering a broken fix-it later on.
2453+
auto requiresClass = false;
2454+
if (unwrappedToType->isExistentialType()) {
2455+
if (auto protocolTy = unwrappedToType->getAs<ProtocolType>()) {
2456+
requiresClass = protocolTy->requiresClass();
2457+
} else if (auto compositionTy =
2458+
unwrappedToType->getAs<ProtocolCompositionType>()) {
2459+
requiresClass = compositionTy->requiresClass();
2460+
}
2461+
}
2462+
if (requiresClass && !FromType->is<ClassType>()) {
2463+
return false;
2464+
}
2465+
2466+
// We can only offer a fix-it if we're assigning to a protocol type and
2467+
// the type we're assigning is the same as the innermost type context.
2468+
bool shouldOfferFixIt = nominal->getSelfTypeInContext()->isEqual(FromType) &&
2469+
unwrappedToType->isExistentialType();
2470+
if (!shouldOfferFixIt)
2471+
return false;
2472+
2473+
diagnostic.flush();
2474+
2475+
// Let's build a list of protocols that the contextual type does not
2476+
// conform to. We will start by first checking if we have a protocol
2477+
// composition type and add all the individual types that the context
2478+
// does not conform to.
2479+
SmallVector<std::string, 8> missingProtoTypeStrings;
2480+
if (auto compositionTy = unwrappedToType->getAs<ProtocolCompositionType>()) {
2481+
for (auto memberTy : compositionTy->getMembers()) {
2482+
auto protocol = memberTy->getAnyNominal()->getSelfProtocolDecl();
2483+
if (!getTypeChecker().conformsToProtocol(
2484+
FromType, protocol, getDC(),
2485+
ConformanceCheckFlags::InExpression)) {
2486+
missingProtoTypeStrings.push_back(memberTy->getString());
2487+
}
2488+
}
2489+
2490+
// If we don't conform to all of the protocols in the composition, then
2491+
// store the composition type only. This is because we need to append
2492+
// 'Foo & Bar' instead of 'Foo, Bar' in order to match the written type.
2493+
if (missingProtoTypeStrings.size() == compositionTy->getMembers().size()) {
2494+
missingProtoTypeStrings = {compositionTy->getString()};
2495+
}
2496+
}
2497+
2498+
// If we didn't have a protocol composition type, it means we only have a
2499+
// single protocol, so just use it directly. Otherwise, construct a comma
2500+
// separated list of missing types.
2501+
std::string protoString;
2502+
if (missingProtoTypeStrings.empty()) {
2503+
protoString = unwrappedToType->getString();
2504+
} else if (missingProtoTypeStrings.size() == 1) {
2505+
protoString = missingProtoTypeStrings.front();
2506+
} else {
2507+
protoString = llvm::join(missingProtoTypeStrings, ", ");
2508+
}
2509+
2510+
// Emit a diagnostic to inform the user that they need to conform to the
2511+
// missing protocols.
2512+
//
2513+
// TODO: Maybe also insert the requirement stubs?
2514+
auto conformanceDiag = emitDiagnostic(
2515+
getAnchor()->getLoc(), diag::assign_protocol_conformance_fix_it,
2516+
unwrappedToType, nominal->getDescriptiveKind(), FromType);
2517+
if (nominal->getInherited().size() > 0) {
2518+
auto lastInherited = nominal->getInherited().back().getLoc();
2519+
auto lastInheritedEndLoc =
2520+
Lexer::getLocForEndOfToken(getASTContext().SourceMgr, lastInherited);
2521+
conformanceDiag.fixItInsert(lastInheritedEndLoc, ", " + protoString);
2522+
} else {
2523+
auto nameEndLoc = Lexer::getLocForEndOfToken(getASTContext().SourceMgr,
2524+
nominal->getNameLoc());
2525+
conformanceDiag.fixItInsert(nameEndLoc, ": " + protoString);
2526+
}
2527+
2528+
return true;
2529+
}
2530+
24332531
void ContextualFailure::tryComputedPropertyFixIts(Expr *expr) const {
24342532
if (!isa<ClosureExpr>(expr))
24352533
return;

lib/Sema/CSDiagnostics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,10 @@ class ContextualFailure : public FailureDiagnostic {
681681
/// conversion is possible.
682682
bool tryTypeCoercionFixIt(InFlightDiagnostic &diagnostic) const;
683683

684+
/// Try to add a fix-it to conform the decl context (if it's a type) to the
685+
/// protocol
686+
bool tryProtocolConformanceFixIt(InFlightDiagnostic &diagnostic) const;
687+
684688
/// Check whether this contextual failure represents an invalid
685689
/// conversion from array literal to dictionary.
686690
static bool isInvalidDictionaryConversion(ConstraintSystem &cs, Expr *anchor,

test/decl/protocol/protocols.swift

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,3 +511,71 @@ extension LetThereBeCrash {
511511
init() { x = 1 }
512512
// expected-error@-1 {{'let' property 'x' may not be initialized directly; use "self.init(...)" or "self = ..." instead}}
513513
}
514+
515+
// SR-11412
516+
// Offer fix-it to conform type of context to the missing protocols
517+
518+
protocol SR_11412_P1 {}
519+
protocol SR_11412_P2 {}
520+
protocol SR_11412_P3 {}
521+
protocol SR_11412_P4: AnyObject {}
522+
523+
class SR_11412_C0 {
524+
var foo1: SR_11412_P1?
525+
var foo2: (SR_11412_P1 & SR_11412_P2)?
526+
weak var foo3: SR_11412_P4?
527+
}
528+
529+
// Context has no inherited types and does not conform to protocol //
530+
531+
class SR_11412_C1 {
532+
let c0 = SR_11412_C0()
533+
534+
func conform() {
535+
c0.foo1 = self // expected-error {{cannot assign value of type 'SR_11412_C1' to type 'SR_11412_P1?'}}
536+
// expected-note@-1 {{add missing conformance to 'SR_11412_P1' to class 'SR_11412_C1'}}{{18-18=: SR_11412_P1}}
537+
}
538+
}
539+
540+
// Context has no inherited types and does not conform to protocol composition //
541+
542+
class SR_11412_C2 {
543+
let c0 = SR_11412_C0()
544+
545+
func conform() {
546+
c0.foo2 = self // expected-error {{cannot assign value of type 'SR_11412_C2' to type '(SR_11412_P1 & SR_11412_P2)?'}}
547+
// expected-note@-1 {{add missing conformance to 'SR_11412_P1 & SR_11412_P2' to class 'SR_11412_C2'}}{{18-18=: SR_11412_P1 & SR_11412_P2}}
548+
}
549+
}
550+
551+
// Context already has an inherited type, but does not conform to protocol //
552+
553+
class SR_11412_C3: SR_11412_P3 {
554+
let c0 = SR_11412_C0()
555+
556+
func conform() {
557+
c0.foo1 = self // expected-error {{cannot assign value of type 'SR_11412_C3' to type 'SR_11412_P1?'}}
558+
// expected-note@-1 {{add missing conformance to 'SR_11412_P1' to class 'SR_11412_C3'}}{{31-31=, SR_11412_P1}}
559+
}
560+
}
561+
562+
// Context conforms to only one protocol in the protocol composition //
563+
564+
class SR_11412_C4: SR_11412_P1 {
565+
let c0 = SR_11412_C0()
566+
567+
func conform() {
568+
c0.foo2 = self // expected-error {{cannot assign value of type 'SR_11412_C4' to type '(SR_11412_P1 & SR_11412_P2)?'}}
569+
// expected-note@-1 {{add missing conformance to 'SR_11412_P1 & SR_11412_P2' to class 'SR_11412_C4'}}{{31-31=, SR_11412_P2}}
570+
}
571+
}
572+
573+
// Context is a value type, but protocol requires class //
574+
575+
struct SR_11412_S0 {
576+
let c0 = SR_11412_C0()
577+
578+
func conform() {
579+
c0.foo3 = self // expected-error {{cannot assign value of type 'SR_11412_S0' to type 'SR_11412_P4?'}}
580+
}
581+
}

0 commit comments

Comments
 (0)