Skip to content

[Diagnostics] Generalize the "protocol type cannot conform" error to work for all types that cannot conform to protocols. #27176

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 8 commits into from
Sep 18, 2019
Merged
15 changes: 12 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1665,10 +1665,19 @@ ERROR(cannot_use_nil_with_this_type,none,
ERROR(use_of_equal_instead_of_equality,none,
"use of '=' in a boolean context, did you mean '=='?", ())

ERROR(type_cannot_conform, none,
"%select{|value of protocol }0type %1 cannot conform to %2; "
"only struct/enum/class types can conform to protocols",
(bool, Type, Type))
NOTE(required_by_opaque_return,none,
"required by opaque return type of %0 %1", (DescriptiveDeclKind, DeclName))
NOTE(required_by_decl,none,
"required by %0 %1 where %2 = %3",
(DescriptiveDeclKind, DeclName, Type, Type))
NOTE(required_by_decl_ref,none,
"required by referencing %0 %1 on %2 where %3 = %4",
(DescriptiveDeclKind, DeclName, Type, Type, Type))

ERROR(protocol_does_not_conform_objc,none,
"protocol type %0 cannot conform to %1 because only concrete "
"types can conform to protocols", (Type, Type))
ERROR(protocol_does_not_conform_static,none,
"%0 cannot be used as a type conforming to protocol %1 because %1 "
"has static requirements",
Expand Down
68 changes: 61 additions & 7 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -592,16 +592,18 @@ bool MissingConformanceFailure::diagnoseAsError() {
}
}

if (nonConformingType->isExistentialType()) {
auto diagnostic = diag::protocol_does_not_conform_objc;
if (nonConformingType->isObjCExistentialType())
diagnostic = diag::protocol_does_not_conform_static;

emitDiagnostic(anchor->getLoc(), diagnostic, nonConformingType,
protocolType);
if (nonConformingType->isObjCExistentialType()) {
emitDiagnostic(anchor->getLoc(), diag::protocol_does_not_conform_static,
nonConformingType, protocolType);
return true;
}

if (diagnoseTypeCannotConform((atParameterPos ?
getArgumentAt(Apply, *atParameterPos) : anchor),
nonConformingType, protocolType)) {
return true;
}

if (atParameterPos) {
// Requirement comes from one of the parameter types,
// let's try to point diagnostic to the argument expression.
Expand All @@ -617,6 +619,58 @@ bool MissingConformanceFailure::diagnoseAsError() {
return RequirementFailure::diagnoseAsError();
}

bool MissingConformanceFailure::diagnoseTypeCannotConform(Expr *anchor,
Type nonConformingType, Type protocolType) const {
if (!(nonConformingType->is<AnyFunctionType>() ||
nonConformingType->is<TupleType>() ||
nonConformingType->isExistentialType() ||
nonConformingType->is<AnyMetatypeType>())) {
return false;
}

emitDiagnostic(anchor->getLoc(), diag::type_cannot_conform,
nonConformingType->isExistentialType(), nonConformingType,
protocolType);

if (auto *OTD = dyn_cast<OpaqueTypeDecl>(AffectedDecl)) {
auto *namingDecl = OTD->getNamingDecl();
if (auto *repr = namingDecl->getOpaqueResultTypeRepr()) {
emitDiagnostic(repr->getLoc(), diag::required_by_opaque_return,
namingDecl->getDescriptiveKind(), namingDecl->getFullName())
.highlight(repr->getSourceRange());
}
return true;
}

auto &req = getRequirement();
auto *reqDC = getRequirementDC();
auto *genericCtx = getGenericContext();
auto noteLocation = reqDC->getAsDecl()->getLoc();

if (!noteLocation.isValid())
noteLocation = anchor->getLoc();

if (isConditional()) {
emitDiagnostic(noteLocation, diag::requirement_implied_by_conditional_conformance,
resolveType(Conformance->getType()),
Conformance->getProtocol()->getDeclaredInterfaceType());
} else if (genericCtx != reqDC && (genericCtx->isChildContextOf(reqDC) ||
isStaticOrInstanceMember(AffectedDecl))) {
emitDiagnostic(noteLocation, diag::required_by_decl_ref,
AffectedDecl->getDescriptiveKind(),
AffectedDecl->getFullName(),
reqDC->getSelfNominalTypeDecl()->getDeclaredType(),
req.getFirstType(), nonConformingType);
} else {
emitDiagnostic(noteLocation, diag::required_by_decl,
AffectedDecl->getDescriptiveKind(),
AffectedDecl->getFullName(), req.getFirstType(),
nonConformingType);
}

return true;
}

bool MissingConformanceFailure::diagnoseAsAmbiguousOperatorRef() {
auto *anchor = getRawAnchor();
auto *ODRE = dyn_cast<OverloadedDeclRefExpr>(anchor);
Expand Down
12 changes: 8 additions & 4 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,10 @@ class RequirementFailure : public FailureDiagnostic {
isa<BinaryExpr>(apply);
}

/// Determine whether given declaration represents a static
/// or instance property/method, excluding operators.
static bool isStaticOrInstanceMember(const ValueDecl *decl);

private:
/// Retrieve declaration associated with failing generic requirement.
ValueDecl *getDeclRef() const;
Expand All @@ -299,10 +303,6 @@ class RequirementFailure : public FailureDiagnostic {

void emitRequirementNote(const Decl *anchor, Type lhs, Type rhs) const;

/// Determine whether given declaration represents a static
/// or instance property/method, excluding operators.
static bool isStaticOrInstanceMember(const ValueDecl *decl);

/// If this is a failure in conditional requirement, retrieve
/// conformance information.
ProtocolConformance *
Expand Down Expand Up @@ -346,6 +346,10 @@ class MissingConformanceFailure final : public RequirementFailure {
DiagAsNote getDiagnosticAsNote() const override {
return diag::candidate_types_conformance_requirement;
}

private:
bool diagnoseTypeCannotConform(Expr *anchor, Type nonConformingType,
Type protocolType) const;
};

/// Diagnose failures related to same-type generic requirements, e.g.
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3970,7 +3970,7 @@ static void diagnoseConformanceFailure(Type T,
TypeChecker::containsProtocol(T, Proto, DC, None)) {

if (!T->isObjCExistentialType()) {
diags.diagnose(ComplainLoc, diag::protocol_does_not_conform_objc,
diags.diagnose(ComplainLoc, diag::type_cannot_conform, true,
T, Proto->getDeclaredType());
return;
}
Expand Down
12 changes: 9 additions & 3 deletions test/Constraints/diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ func f3(_: @escaping (_: @escaping (Int) -> Float) -> Int) {}
func f4(_ x: Int) -> Int { }

func f5<T : P2>(_ : T) { }
// expected-note@-1 {{required by global function 'f5' where 'T' = '(Int) -> Int'}}
// expected-note@-2 {{required by global function 'f5' where 'T' = '(Int, String)'}}
// expected-note@-3 {{required by global function 'f5' where 'T' = 'Int.Type'}}

func f6<T : P, U : P>(_ t: T, _ u: U) where T.SomeType == U.SomeType {}

Expand All @@ -46,8 +49,10 @@ f0(i, i,
i) // expected-error{{extra argument in call}}


// Position mismatch
f5(f4) // expected-error {{argument type '(Int) -> Int' does not conform to expected type 'P2'}}
// Cannot conform to protocols.
f5(f4) // expected-error {{type '(Int) -> Int' cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}
f5((1, "hello")) // expected-error {{type '(Int, String)' cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}
f5(Int.self) // expected-error {{type 'Int.Type' cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}

// Tuple element not convertible.
f0(i,
Expand Down Expand Up @@ -94,10 +99,11 @@ func f7() -> (c: Int, v: A) {
}

func f8<T:P2>(_ n: T, _ f: @escaping (T) -> T) {}
// expected-note@-1 {{required by global function 'f8' where 'T' = 'Tup' (aka '(Int, Double)')}}
f8(3, f4) // expected-error {{argument type 'Int' does not conform to expected type 'P2'}}
typealias Tup = (Int, Double)
func f9(_ x: Tup) -> Tup { return x }
f8((1,2.0), f9) // expected-error {{argument type 'Tup' (aka '(Int, Double)') does not conform to expected type 'P2'}}
f8((1,2.0), f9) // expected-error {{type 'Tup' (aka '(Int, Double)') cannot conform to 'P2'; only struct/enum/class types can conform to protocols}}

// <rdar://problem/19658691> QoI: Incorrect diagnostic for calling nonexistent members on literals
1.doesntExist(0) // expected-error {{value of type 'Int' has no member 'doesntExist'}}
Expand Down
4 changes: 2 additions & 2 deletions test/Constraints/function_builder_diags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ struct TupleP<U> : P {

@_functionBuilder
struct Builder {
static func buildBlock<S0, S1>(_ stmt1: S0, _ stmt2: S1) // expected-note {{where 'S1' = 'Label<Any>.Type'}}
static func buildBlock<S0, S1>(_ stmt1: S0, _ stmt2: S1) // expected-note {{required by static method 'buildBlock' where 'S1' = 'Label<Any>.Type'}}
-> TupleP<(S0, S1)> where S0: P, S1: P {
return TupleP((stmt1, stmt2))
}
Expand All @@ -166,7 +166,7 @@ struct Label<L> : P where L : P { // expected-note {{'L' declared as parameter t
}

func test_51167632() -> some P {
AnyP(G { // expected-error {{static method 'buildBlock' requires that 'Label<Any>.Type' conform to 'P'}}
AnyP(G { // expected-error {{type 'Label<Any>.Type' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
Text("hello")
Label // expected-error {{generic parameter 'L' could not be inferred}}
// expected-note@-1 {{explicitly specify the generic arguments to fix this issue}} {{10-10=<<#L: P#>>}}
Expand Down
3 changes: 2 additions & 1 deletion test/Constraints/generics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ func r22459135() {

// <rdar://problem/19710848> QoI: Friendlier error message for "[] as Set"
// <rdar://problem/22326930> QoI: "argument for generic parameter 'Element' could not be inferred" lacks context
_ = [] as Set // expected-error {{protocol type 'Any' cannot conform to 'Hashable' because only concrete types can conform to protocols}}
_ = [] as Set // expected-error {{value of protocol type 'Any' cannot conform to 'Hashable'; only struct/enum/class types can conform to protocols}}
// expected-note@-1 {{required by generic struct 'Set' where 'Element' = 'Any'}}


//<rdar://problem/22509125> QoI: Error when unable to infer generic archetype lacks greatness
Expand Down
3 changes: 2 additions & 1 deletion test/Constraints/operator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ func rdar46459603() {
// expected-error@-1 {{binary operator '==' cannot be applied to operands of type 'Dictionary<String, E>.Values' and '[E]'}}
// expected-note@-2 {{expected an argument list of type '(Self, Self)'}}
_ = [arr.values] == [[e]]
// expected-error@-1 {{protocol type 'Any' cannot conform to 'Equatable' because only concrete types can conform to protocols}}
// expected-error@-1 {{value of protocol type 'Any' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols}}
// expected-note@-2 {{requirement from conditional conformance of '[Any]' to 'Equatable'}}
}

// SR-10843
Expand Down
5 changes: 3 additions & 2 deletions test/Generics/conditional_conformances_literals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extension Array: Conforms where Element: Conforms {}
// expected-note@-1 5 {{requirement from conditional conformance of '[Fails]' to 'Conforms'}}
extension Dictionary: Conforms where Value: Conforms {}
// expected-note@-1 3 {{requirement from conditional conformance of '[Int : Fails]' to 'Conforms'}}
// expected-note@-2 2 {{requirement from conditional conformance of '[Int : Conforms]' to 'Conforms'}}

let works = Works()
let fails = Fails()
Expand Down Expand Up @@ -127,9 +128,9 @@ func combined() {

// Needs self conforming protocols:
let _: Conforms = [[0: [1 : [works]] as Conforms]]
// expected-error@-1 {{protocol type 'Conforms' cannot conform to 'Conforms' because only concrete types can conform to protocols}}
// expected-error@-1 {{value of protocol type 'Conforms' cannot conform to 'Conforms'; only struct/enum/class types can conform to protocols}}

let _: Conforms = [[0: [1 : [fails]] as Conforms]]
// expected-error@-1 {{protocol 'Conforms' requires that 'Fails' conform to 'Conforms'}}
// expected-error@-2 {{protocol type 'Conforms' cannot conform to 'Conforms' because only concrete types can conform to protocols}}
// expected-error@-2 {{value of protocol type 'Conforms' cannot conform to 'Conforms'; only struct/enum/class types can conform to protocols}}
}
16 changes: 10 additions & 6 deletions test/Generics/existential_restrictions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,18 @@ protocol CP : class { }
}

func fP<T : P>(_ t: T) { }
// expected-note@-1 {{required by global function 'fP' where 'T' = 'P'}}
// expected-note@-2 {{required by global function 'fP' where 'T' = 'OP & P'}}
func fOP<T : OP>(_ t: T) { }
// expected-note@-1 {{required by global function 'fOP' where 'T' = 'OP & P'}}
func fOPE(_ t: OP) { }
func fSP<T : SP>(_ t: T) { }
func fAO<T : AnyObject>(_ t: T) { }
func fAOE(_ t: AnyObject) { }
func fT<T>(_ t: T) { }

func testPassExistential(_ p: P, op: OP, opp: OP & P, cp: CP, sp: SP, any: Any, ao: AnyObject) {
fP(p) // expected-error{{protocol type 'P' cannot conform to 'P' because only concrete types can conform to protocols}}
fP(p) // expected-error{{value of protocol type 'P' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
fAO(p) // expected-error{{cannot invoke 'fAO' with an argument list of type '(P)'}} // expected-note{{expected an argument list of type '(T)'}}
fAOE(p) // expected-error{{argument type 'P' does not conform to expected type 'AnyObject'}}
fT(p)
Expand All @@ -31,8 +34,8 @@ func testPassExistential(_ p: P, op: OP, opp: OP & P, cp: CP, sp: SP, any: Any,
fAOE(cp)
fT(cp)

fP(opp) // expected-error{{protocol type 'OP & P' cannot conform to 'P' because only concrete types can conform to protocols}}
fOP(opp) // expected-error{{protocol type 'OP & P' cannot conform to 'OP' because only concrete types can conform to protocols}}
fP(opp) // expected-error{{value of protocol type 'OP & P' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
fOP(opp) // expected-error{{value of protocol type 'OP & P' cannot conform to 'OP'; only struct/enum/class types can conform to protocols}}
fAO(opp) // expected-error{{cannot invoke 'fAO' with an argument list of type '(OP & P)'}} // expected-note{{expected an argument list of type '(T)'}}
fAOE(opp)
fT(opp)
Expand All @@ -58,9 +61,9 @@ class GAO<T : AnyObject> {} // expected-note 2{{requirement specified as 'T' : '
func blackHole(_ t: Any) {}

func testBindExistential() {
blackHole(GP<P>()) // expected-error{{protocol type 'P' cannot conform to 'P' because only concrete types can conform to protocols}}
blackHole(GP<P>()) // expected-error{{value of protocol type 'P' cannot conform to 'P'; only struct/enum/class types can conform to protocols}}
blackHole(GOP<OP>())
blackHole(GCP<CP>()) // expected-error{{protocol type 'CP' cannot conform to 'CP' because only concrete types can conform to protocols}}
blackHole(GCP<CP>()) // expected-error{{value of protocol type 'CP' cannot conform to 'CP'; only struct/enum/class types can conform to protocols}}
blackHole(GAO<P>()) // expected-error{{'GAO' requires that 'P' be a class type}}
blackHole(GAO<OP>())
blackHole(GAO<CP>()) // expected-error{{'GAO' requires that 'CP' be a class type}}
Expand All @@ -73,6 +76,7 @@ protocol Mine {}
class M1: Mine {}
class M2: Mine {}
extension Collection where Iterator.Element : Mine {
// expected-note@-1 {{required by referencing instance method 'takeAll()' on 'Collection'}}
func takeAll() {}
}

Expand All @@ -85,5 +89,5 @@ func foo() {
// generic no overloads error path. The error should actually talk
// about the return type, and this can happen in other contexts as well;
// <rdar://problem/21900971> tracks improving QoI here.
allMine.takeAll() // expected-error{{protocol type 'Mine' cannot conform to 'Mine' because only concrete types can conform to protocols}}
allMine.takeAll() // expected-error{{value of protocol type 'Mine' cannot conform to 'Mine'; only struct/enum/class types can conform to protocols}}
}
3 changes: 2 additions & 1 deletion test/Parse/confusables.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ if (true ꝸꝸꝸ false) {} // expected-note {{identifier 'ꝸꝸꝸ' contains

// expected-error @+3 {{invalid character in source file}}
// expected-error @+2 {{expected ',' separator}}
// expected-error @+1 {{argument type '(Int, Int)' does not conform to expected type 'BinaryInteger'}}
// expected-error @+1 {{type '(Int, Int)' cannot conform to 'BinaryInteger'; only struct/enum/class types can conform to protocols}}
if (5 ‒ 5) == 0 {} // expected-note {{unicode character '‒' looks similar to '-'; did you mean to use '-'?}} {{7-10=-}}
// expected-note @-1 {{required by referencing operator function '==' on 'BinaryInteger' where 'Self' = '(Int, Int)'}}
9 changes: 6 additions & 3 deletions test/decl/protocol/conforms/error_self_conformance.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
// RUN: %target-typecheck-verify-swift

func wantsError<T: Error>(_: T) {}
// expected-note@-1 {{required by global function 'wantsError' where 'T' = 'ErrorRefinement'}}
// expected-note@-2 {{required by global function 'wantsError' where 'T' = 'Error & OtherProtocol'}}
// expected-note@-3 {{required by global function 'wantsError' where 'T' = 'C & Error'}}

func testSimple(error: Error) {
wantsError(error)
}

protocol ErrorRefinement : Error {}
func testErrorRefinment(error: ErrorRefinement) {
wantsError(error) // expected-error {{protocol type 'ErrorRefinement' cannot conform to 'Error' because only concrete types can conform to protocols}}
wantsError(error) // expected-error {{value of protocol type 'ErrorRefinement' cannot conform to 'Error'; only struct/enum/class types can conform to protocols}}
}

protocol OtherProtocol {}
func testErrorComposition(error: Error & OtherProtocol) {
wantsError(error) // expected-error {{protocol type 'Error & OtherProtocol' cannot conform to 'Error' because only concrete types can conform to protocols}}
wantsError(error) // expected-error {{value of protocol type 'Error & OtherProtocol' cannot conform to 'Error'; only struct/enum/class types can conform to protocols}}
}

class C {}
func testErrorCompositionWithClass(error: Error & C) {
wantsError(error) // expected-error {{protocol type 'C & Error' cannot conform to 'Error' because only concrete types can conform to protocols}}
wantsError(error) // expected-error {{value of protocol type 'C & Error' cannot conform to 'Error'; only struct/enum/class types can conform to protocols}}
}
2 changes: 1 addition & 1 deletion test/stmt/foreach.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func testOptionalSequence() {

// Crash with (invalid) for each over an existential
func testExistentialSequence(s: Sequence) { // expected-error {{protocol 'Sequence' can only be used as a generic constraint because it has Self or associated type requirements}}
for x in s { // expected-error {{protocol type 'Sequence' cannot conform to 'Sequence' because only concrete types can conform to protocols}}
for x in s { // expected-error {{value of protocol type 'Sequence' cannot conform to 'Sequence'; only struct/enum/class types can conform to protocols}}
_ = x
}
}
Expand Down
6 changes: 3 additions & 3 deletions test/type/opaque.swift
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,9 @@ protocol P_51641323 {

func rdar_51641323() {
struct Foo: P_51641323 {
var foo: some P_51641323 { {} }
// expected-error@-1 {{return type of property 'foo' requires that '() -> ()' conform to 'P_51641323'}}
// expected-note@-2 {{opaque return type declared here}}
var foo: some P_51641323 { // expected-note {{required by opaque return type of property 'foo'}}
{} // expected-error {{type '() -> ()' cannot conform to 'P_51641323'; only struct/enum/class types can conform to protocols}}
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion test/type/subclass_composition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ func dependentMemberTypes<T : BaseIntAndP2>(

func conformsToAnyObject<T : AnyObject>(_: T) {}
func conformsToP1<T : P1>(_: T) {}
// expected-note@-1 {{required by global function 'conformsToP1' where 'T' = 'P1'}}
func conformsToP2<T : P2>(_: T) {}
func conformsToBaseIntAndP2<T : Base<Int> & P2>(_: T) {}
// expected-note@-1 {{where 'T' = 'FakeDerived'}}
Expand Down Expand Up @@ -412,7 +413,7 @@ func conformsTo<T1 : P2, T2 : Base<Int> & P2>(
// expected-note@-2 {{expected an argument list of type '(T)'}}

conformsToP1(p1)
// expected-error@-1 {{protocol type 'P1' cannot conform to 'P1' because only concrete types can conform to protocols}}
// expected-error@-1 {{value of protocol type 'P1' cannot conform to 'P1'; only struct/enum/class types can conform to protocols}}

// FIXME: Following diagnostics are not great because when
// `conformsTo*` methods are re-typechecked, they loose information
Expand Down
Loading