Skip to content

Commit 0eb7eaa

Browse files
committed
Sema: Tighten up SwiftUI conformance hack and add a warning
1 parent f64135a commit 0eb7eaa

File tree

6 files changed

+113
-18
lines changed

6 files changed

+113
-18
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,11 @@ NOTE(invalid_nominal_extension_rewrite,none,
19251925
// Protocols
19261926
ERROR(type_does_not_conform,none,
19271927
"type %0 does not conform to protocol %1", (Type, Type))
1928+
1929+
// FIXME: SwiftUI source compatibility hack; see usage for details.
1930+
WARNING(type_does_not_conform_swiftui_warning,none,
1931+
"type %0 does not conform to protocol %1", (Type, Type))
1932+
19281933
ERROR(cannot_use_nil_with_this_type,none,
19291934
"'nil' cannot be used in context expecting type %0", (Type))
19301935

@@ -2300,6 +2305,10 @@ NOTE(protocol_witness_type_conflict,none,
23002305
NOTE(protocol_witness_missing_requirement,none,
23012306
"candidate would match if %0 %select{conformed to|subclassed|"
23022307
"was the same type as}2 %1", (Type, Type, unsigned))
2308+
NOTE(protocol_type_witness_missing_requirement,none,
2309+
"candidate would match if the conformance required that %0 "
2310+
"%select{conformed to|subclassed|was the same type as}2 %1",
2311+
(Type, Type, unsigned))
23032312

23042313
NOTE(protocol_witness_optionality_conflict,none,
23052314
"candidate %select{type has|result type has|parameter type has|"

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4745,14 +4745,30 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
47454745
continue;
47464746

47474747
// As a narrow fix for a source compatibility issue with SwiftUI's
4748-
// swiftinterface, allow the conformance if the underlying type of
4749-
// the typealias is Never.
4748+
// swiftinterface, allow a 'typealias' type witness with an underlying type
4749+
// of 'Never' if it is declared in a context that does not satisfy the
4750+
// requirements of the conformance context.
47504751
//
4751-
// FIXME: This should be conditionalized on a new language version.
4752+
// FIXME: If SwiftUI redeclares the typealias under the correct constraints,
4753+
// this can be removed.
47524754
bool skipRequirementCheck = false;
47534755
if (auto *typeAliasDecl = dyn_cast<TypeAliasDecl>(typeDecl)) {
4754-
if (typeAliasDecl->getUnderlyingType()->isUninhabited())
4755-
skipRequirementCheck = true;
4756+
if (typeAliasDecl->getUnderlyingType()->isNever()) {
4757+
if (typeAliasDecl->getParentModule()->getName().is("SwiftUI")) {
4758+
if (typeAliasDecl->getDeclContext()->getSelfNominalTypeDecl() ==
4759+
Adoptee->getAnyNominal()) {
4760+
const auto reqs =
4761+
typeAliasDecl->getGenericSignature().requirementsNotSatisfiedBy(
4762+
DC->getGenericSignatureOfContext());
4763+
if (!reqs.empty()) {
4764+
SwiftUIInvalidTyWitness = {assocType, typeAliasDecl,
4765+
reqs.front()};
4766+
4767+
skipRequirementCheck = true;
4768+
}
4769+
}
4770+
}
4771+
}
47564772
}
47574773

47584774
// Skip typealiases with an unbound generic type as their underlying type.
@@ -5216,6 +5232,36 @@ void ConformanceChecker::checkConformance(MissingWitnessDiagnosisKind Kind) {
52165232
}
52175233
}
52185234
}
5235+
5236+
if (Conformance->isInvalid()) {
5237+
return;
5238+
}
5239+
5240+
// As a narrow fix for a source compatibility issue with SwiftUI's
5241+
// swiftinterface, but only if the conformance succeeds, warn about an
5242+
// actually malformed conformance if we recorded a 'typealias' type witness
5243+
// with an underlying type of 'Never', which resides in a context that does
5244+
// not satisfy the requirements of the conformance context.
5245+
//
5246+
// FIXME: If SwiftUI redeclares the typealias under the correct constraints,
5247+
// this can be removed.
5248+
if (SwiftUIInvalidTyWitness) {
5249+
const auto &info = SwiftUIInvalidTyWitness.getValue();
5250+
const auto &failedReq = info.FailedReq;
5251+
5252+
auto &diags = getASTContext().Diags;
5253+
diags.diagnose(Loc, diag::type_does_not_conform_swiftui_warning, Adoptee,
5254+
Proto->getDeclaredInterfaceType());
5255+
diags.diagnose(info.AssocTypeDecl, diag::no_witnesses_type,
5256+
info.AssocTypeDecl->getName());
5257+
5258+
if (failedReq.getKind() != RequirementKind::Layout) {
5259+
diags.diagnose(info.TypeWitnessDecl,
5260+
diag::protocol_type_witness_missing_requirement,
5261+
failedReq.getFirstType(), failedReq.getSecondType(),
5262+
(unsigned)failedReq.getKind());
5263+
}
5264+
}
52195265
}
52205266

52215267
/// Retrieve the Objective-C method key from the given function.

lib/Sema/TypeCheckProtocol.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,14 @@ class ConformanceChecker : public WitnessChecker {
782782
/// Whether objcMethodRequirements has been computed.
783783
bool computedObjCMethodRequirements = false;
784784

785+
// FIXME: SwiftUI source compatibility hack; see usage for details.
786+
struct SwiftUIInvalidTypeWitness final {
787+
AssociatedTypeDecl *AssocTypeDecl;
788+
TypeAliasDecl *TypeWitnessDecl;
789+
Requirement FailedReq;
790+
};
791+
llvm::Optional<SwiftUIInvalidTypeWitness> SwiftUIInvalidTyWitness = None;
792+
785793
/// Retrieve the associated types that are referenced by the given
786794
/// requirement with a base of 'Self'.
787795
ArrayRef<AssociatedTypeDecl *> getReferencedAssociatedTypes(ValueDecl *req);

test/Generics/constrained_type_witnesses.swift

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
protocol P {
44
associatedtype A
5-
// expected-note@-1 3{{protocol requires nested type 'A'; do you want to add it?}}
5+
// expected-note@-1 5{{protocol requires nested type 'A'; do you want to add it?}}
66
}
77

88
struct S1<T> {}
@@ -20,11 +20,7 @@ struct S2<T> {}
2020
extension S2 where T : P {
2121
typealias A = Never
2222
}
23-
24-
// Hack: This is OK to make SwiftUI work, which accidentally relies on the
25-
// incorrect behavior with a typealias whose underlying type is 'Never'
26-
// (so it didn't hit the compiler crash).
27-
extension S2 : P {}
23+
extension S2 : P {} // expected-error {{type 'S2<T>' does not conform to protocol 'P'}}
2824

2925
// Here we have a suitable witness
3026
struct S3<T> {}
@@ -48,7 +44,7 @@ struct S5<T> {
4844
typealias A = Never where T : P
4945
}
5046

51-
extension S5 : P {}
47+
extension S5 : P {} // expected-error {{type 'S5<T>' does not conform to protocol 'P'}}
5248

5349
struct S6<T> {
5450
typealias A = Int where T == Int
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %target-typecheck-verify-swift -module-name SwiftUI
2+
3+
class Class {}
4+
protocol P {
5+
associatedtype A
6+
// expected-note@-1 5{{protocol requires nested type 'A'; do you want to add it?}}
7+
}
8+
9+
struct S1<T> {}
10+
extension S1 where T : P {
11+
typealias A = Never // expected-note {{candidate would match if the conformance required that 'T' conformed to 'P'}}
12+
}
13+
extension S1 : P {} // expected-warning {{type 'S1<T>' does not conform to protocol 'P'}}
14+
15+
struct S2<T> {
16+
typealias A = Never where T : P // expected-note {{candidate would match if the conformance required that 'T' conformed to 'P'}}
17+
}
18+
extension S2 : P {} // expected-warning {{type 'S2<T>' does not conform to protocol 'P'}}
19+
20+
struct S3<T> {}
21+
extension S3 where T : Class {
22+
typealias A = Never // expected-note {{candidate would match if the conformance required that 'T' subclassed 'Class'}}
23+
}
24+
extension S3 : P {} // expected-warning {{type 'S3<T>' does not conform to protocol 'P'}}
25+
26+
struct S4<T> {}
27+
extension S4 where T == Bool {
28+
typealias A = Never // expected-note {{candidate would match if the conformance required that 'T' was the same type as 'Bool'}}
29+
}
30+
extension S4 : P {} // expected-warning {{type 'S4<T>' does not conform to protocol 'P'}}
31+
32+
struct S5<T> {}
33+
extension S5 where T: AnyObject {
34+
typealias A = Never
35+
}
36+
extension S5 : P {} // expected-warning {{type 'S5<T>' does not conform to protocol 'P'}}

test/decl/protocol/req/associated_type_inference.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -537,14 +537,14 @@ protocol P32 {
537537
var bar: B { get }
538538
}
539539
protocol P33 {
540-
associatedtype A
540+
associatedtype A // expected-note {{protocol requires nested type 'A'; do you want to add it?}}
541541

542-
var baz: A { get } // expected-note {{protocol requires property 'baz' with type 'S31<T>.A' (aka 'Never'); do you want to add a stub?}}
542+
var baz: A { get }
543543
}
544544
protocol P34 {
545-
associatedtype A
545+
associatedtype A // expected-note {{protocol requires nested type 'A'; do you want to add it?}}
546546

547-
func boo() -> A // expected-note {{protocol requires function 'boo()' with type '() -> S31<T>.A' (aka '() -> Never'); do you want to add a stub?}}
547+
func boo() -> A
548548
}
549549
struct S31<T> {}
550550
extension S31: P32 where T == Int {} // OK
@@ -556,11 +556,11 @@ extension S31 where T: Equatable {
556556
}
557557
extension S31: P33 where T == Never {} // expected-error {{type 'S31<T>' does not conform to protocol 'P33'}}
558558
extension S31 where T == String {
559-
var baz: Bool { true } // expected-note {{candidate has non-matching type 'Bool' [with A = S31<T>.A]}}
559+
var baz: Bool { true }
560560
}
561561
extension S31: P34 {} // expected-error {{type 'S31<T>' does not conform to protocol 'P34'}}
562562
extension S31 where T: P32 {
563-
func boo() -> Void {} // expected-note {{candidate has non-matching type '<T> () -> Void' [with A = S31<T>.A]}}
563+
func boo() -> Void {}
564564
}
565565

566566
// SR-12707

0 commit comments

Comments
 (0)