Skip to content

[Diagnostics] [Typechecker] Emit fix-its for witness mismatches #26990

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Sep 4, 2019
Merged
91 changes: 63 additions & 28 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2089,53 +2089,88 @@ diagnoseMatch(ModuleDecl *module, NormalProtocolConformance *conformance,
break;
}

case MatchKind::StaticNonStaticConflict:
// FIXME: Could emit a Fix-It here.
diags.diagnose(match.Witness, diag::protocol_witness_static_conflict,
!req->isInstanceMember());
case MatchKind::StaticNonStaticConflict: {
auto witness = match.Witness;
auto diag = diags.diagnose(witness, diag::protocol_witness_static_conflict,
!req->isInstanceMember());
if (req->isInstanceMember()) {
SourceLoc loc;
if (auto FD = dyn_cast<FuncDecl>(witness)) {
loc = FD->getStaticLoc();
} else if (auto VD = dyn_cast<VarDecl>(witness)) {
loc = VD->getParentPatternBinding()->getStaticLoc();
} else if (auto SD = dyn_cast<SubscriptDecl>(witness)) {
loc = SD->getStaticLoc();
} else {
llvm_unreachable("Unexpected witness");
}
diag.fixItRemove(loc);
} else {
diag.fixItInsert(witness->getAttributeInsertionLoc(true), "static ");
}
break;

case MatchKind::SettableConflict:
diags.diagnose(match.Witness, diag::protocol_witness_settable_conflict);
}

case MatchKind::SettableConflict: {
auto witness = match.Witness;
auto diag =
diags.diagnose(witness, diag::protocol_witness_settable_conflict);
if (auto VD = dyn_cast<VarDecl>(witness)) {
if (VD->hasStorage()) {
auto PBD = VD->getParentPatternBinding();
diag.fixItReplace(PBD->getStartLoc(), getTokenText(tok::kw_var));
}
}
break;
}

case MatchKind::PrefixNonPrefixConflict:
// FIXME: Could emit a Fix-It here.
diags.diagnose(match.Witness,
diag::protocol_witness_prefix_postfix_conflict, false,
match.Witness->getAttrs().hasAttribute<PostfixAttr>() ? 2
: 0);
case MatchKind::PrefixNonPrefixConflict: {
auto witness = match.Witness;
auto diag = diags.diagnose(
witness, diag::protocol_witness_prefix_postfix_conflict, false,
witness->getAttrs().hasAttribute<PostfixAttr>() ? 2 : 0);
// We already emit a fix-it when we're missing the attribute, so only
// emit a fix-it if the attribute is there, but is not correct.
if (auto attr = witness->getAttrs().getAttribute<PostfixAttr>()) {
diag.fixItReplace(attr->getLocation(), "prefix");
}
break;
}

case MatchKind::PostfixNonPostfixConflict:
// FIXME: Could emit a Fix-It here.
diags.diagnose(match.Witness,
diag::protocol_witness_prefix_postfix_conflict, true,
match.Witness->getAttrs().hasAttribute<PrefixAttr>() ? 1
: 0);
case MatchKind::PostfixNonPostfixConflict: {
auto witness = match.Witness;
auto diag = diags.diagnose(
witness, diag::protocol_witness_prefix_postfix_conflict, true,
witness->getAttrs().hasAttribute<PrefixAttr>() ? 1 : 0);
// We already emit a fix-it when we're missing the attribute, so only
// emit a fix-it if the attribute is there, but is not correct.
if (auto attr = witness->getAttrs().getAttribute<PrefixAttr>()) {
diag.fixItReplace(attr->getLocation(), "postfix");
}
break;
}
case MatchKind::MutatingConflict:
// FIXME: Could emit a Fix-It here.
diags.diagnose(match.Witness,
diag::protocol_witness_mutation_modifier_conflict,
SelfAccessKind::Mutating);
break;
case MatchKind::NonMutatingConflict:
// FIXME: Could emit a Fix-It here.
diags.diagnose(match.Witness,
diag::protocol_witness_mutation_modifier_conflict,
SelfAccessKind::NonMutating);
// Don't bother about this, because a non-mutating witness can satisfy
// a mutating requirement.
break;
case MatchKind::ConsumingConflict:
// FIXME: Could emit a Fix-It here.
diags.diagnose(match.Witness,
diag::protocol_witness_mutation_modifier_conflict,
SelfAccessKind::Consuming);
break;
case MatchKind::RethrowsConflict:
// FIXME: Could emit a Fix-It here.
diags.diagnose(match.Witness, diag::protocol_witness_rethrows_conflict);
case MatchKind::RethrowsConflict: {
auto witness = match.Witness;
auto diag =
diags.diagnose(witness, diag::protocol_witness_rethrows_conflict);
auto FD = cast<FuncDecl>(witness);
diag.fixItReplace(FD->getThrowsLoc(), getTokenText(tok::kw_rethrows));
break;
}
case MatchKind::NonObjC:
diags.diagnose(match.Witness, diag::protocol_witness_not_objc);
break;
Expand Down
36 changes: 36 additions & 0 deletions test/decl/protocol/req/witness_fix_its.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// RUN: %target-typecheck-verify-swift

prefix operator ^^^
postfix operator ^^^^

protocol Foo {
var bar1: Int { get set } // expected-note {{protocol requires property 'bar1' with type 'Int'; do you want to add a stub?}}
static var bar2: Int { get set } // expected-note {{protocol requires property 'bar2' with type 'Int'; do you want to add a stub?}}
var bar3: Int { get set } // expected-note {{protocol requires property 'bar3' with type 'Int'; do you want to add a stub?}}
static prefix func ^^^(value: Self) -> Int // expected-note {{protocol requires function '^^^' with type '(ConformsToFoo) -> Int'; do you want to add a stub?}}
static postfix func ^^^^(value: Self) -> Int // expected-note {{protocol requires function '^^^^' with type '(ConformsToFoo) -> Int'; do you want to add a stub?}}
func bar4(closure: () throws -> Int) rethrows // expected-note {{protocol requires function 'bar4(closure:)' with type '(() throws -> Int) throws -> ()'; do you want to add a stub?}}
var bar5: Int { get set } // expected-note {{protocol requires property 'bar5' with type 'Int'; do you want to add a stub?}}
static subscript(_ pos: Int) -> Int { get } // expected-note {{protocol requires subscript with type '(Int) -> Int'; do you want to add a stub?}}
}

struct ConformsToFoo: Foo { // expected-error {{type 'ConformsToFoo' does not conform to protocol 'Foo'}}
let bar1: Int // expected-note {{candidate is not settable, but protocol requires it}}{{3-6=var}}
var bar2: Int // expected-note {{candidate operates on an instance, not a type as required}}{{3-3=static }}
static var bar3: Int = 1 // expected-note {{candidate operates on a type, not an instance as required}}{{3-10=}}
static postfix func ^^^(value: ConformsToFoo) -> Int { return 0 } // expected-error {{operator implementation without matching operator declaration}}
// expected-note@-1 {{candidate is postfix, not prefix as required}}{{10-17=prefix}}
static prefix func ^^^^(value: ConformsToFoo) -> Int { return 0 } // expected-error {{operator implementation without matching operator declaration}}
// expected-note@-1 {{candidate is prefix, not postfix as required}}{{10-16=postfix}}
func bar4(closure: () throws -> Int) throws {} // expected-note {{candidate is not 'rethrows', but protocol requires it}}{{40-46=rethrows}}
var bar5: Int { return 0 } // expected-note {{candidate is not settable, but protocol requires it}}{{none}}
subscript(_ pos: Int) -> Int { return 0 } // expected-note {{candidate operates on an instance, not a type as required}}{{3-3=static }}
}

protocol Foo1 {
subscript(value: Bool) -> Bool { get set } // expected-note {{protocol requires subscript with type '(Bool) -> Bool'; do you want to add a stub?}}
}

struct ConformsToFoo1: Foo1 { // expected-error {{type 'ConformsToFoo1' does not conform to protocol 'Foo1'}}
subscript(value: Bool) -> Bool { return false } // expected-note {{candidate is not settable, but protocol requires it}}{{none}}
}