Skip to content

Commit 5cbfeb7

Browse files
authored
Merge pull request swiftlang#27026 from theblixguy/fix/SR--11412
[CSDiagnostics] Offer protocol conformance fix-it in ContextualFailure
2 parents 160f25b + f16d0e2 commit 5cbfeb7

File tree

4 files changed

+163
-0
lines changed

4 files changed

+163
-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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "TypoCorrection.h"
2121
#include "swift/AST/ASTContext.h"
2222
#include "swift/AST/Decl.h"
23+
#include "swift/AST/ExistentialLayout.h"
2324
#include "swift/AST/Expr.h"
2425
#include "swift/AST/GenericSignature.h"
2526
#include "swift/AST/Initializer.h"
@@ -2006,6 +2007,9 @@ void ContextualFailure::tryFixIts(InFlightDiagnostic &diagnostic) const {
20062007
if (tryIntegerCastFixIts(diagnostic))
20072008
return;
20082009

2010+
if (tryProtocolConformanceFixIt(diagnostic))
2011+
return;
2012+
20092013
if (tryTypeCoercionFixIt(diagnostic))
20102014
return;
20112015
}
@@ -2430,6 +2434,90 @@ bool ContextualFailure::tryTypeCoercionFixIt(
24302434
return false;
24312435
}
24322436

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