Skip to content

Commit 6b5de49

Browse files
Merge pull request #40980 from AnthonyLatsis/swiftui-warning-hack
Sema: Tighten up SwiftUI conformance hack and add a warning
2 parents 67c2b75 + 0eb7eaa commit 6b5de49

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
@@ -4691,14 +4691,30 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
46914691
continue;
46924692

46934693
// As a narrow fix for a source compatibility issue with SwiftUI's
4694-
// swiftinterface, allow the conformance if the underlying type of
4695-
// the typealias is Never.
4694+
// swiftinterface, allow a 'typealias' type witness with an underlying type
4695+
// of 'Never' if it is declared in a context that does not satisfy the
4696+
// requirements of the conformance context.
46964697
//
4697-
// FIXME: This should be conditionalized on a new language version.
4698+
// FIXME: If SwiftUI redeclares the typealias under the correct constraints,
4699+
// this can be removed.
46984700
bool skipRequirementCheck = false;
46994701
if (auto *typeAliasDecl = dyn_cast<TypeAliasDecl>(typeDecl)) {
4700-
if (typeAliasDecl->getUnderlyingType()->isUninhabited())
4701-
skipRequirementCheck = true;
4702+
if (typeAliasDecl->getUnderlyingType()->isNever()) {
4703+
if (typeAliasDecl->getParentModule()->getName().is("SwiftUI")) {
4704+
if (typeAliasDecl->getDeclContext()->getSelfNominalTypeDecl() ==
4705+
Adoptee->getAnyNominal()) {
4706+
const auto reqs =
4707+
typeAliasDecl->getGenericSignature().requirementsNotSatisfiedBy(
4708+
DC->getGenericSignatureOfContext());
4709+
if (!reqs.empty()) {
4710+
SwiftUIInvalidTyWitness = {assocType, typeAliasDecl,
4711+
reqs.front()};
4712+
4713+
skipRequirementCheck = true;
4714+
}
4715+
}
4716+
}
4717+
}
47024718
}
47034719

47044720
// Skip typealiases with an unbound generic type as their underlying type.
@@ -5162,6 +5178,36 @@ void ConformanceChecker::checkConformance(MissingWitnessDiagnosisKind Kind) {
51625178
}
51635179
}
51645180
}
5181+
5182+
if (Conformance->isInvalid()) {
5183+
return;
5184+
}
5185+
5186+
// As a narrow fix for a source compatibility issue with SwiftUI's
5187+
// swiftinterface, but only if the conformance succeeds, warn about an
5188+
// actually malformed conformance if we recorded a 'typealias' type witness
5189+
// with an underlying type of 'Never', which resides in a context that does
5190+
// not satisfy the requirements of the conformance context.
5191+
//
5192+
// FIXME: If SwiftUI redeclares the typealias under the correct constraints,
5193+
// this can be removed.
5194+
if (SwiftUIInvalidTyWitness) {
5195+
const auto &info = SwiftUIInvalidTyWitness.getValue();
5196+
const auto &failedReq = info.FailedReq;
5197+
5198+
auto &diags = getASTContext().Diags;
5199+
diags.diagnose(Loc, diag::type_does_not_conform_swiftui_warning, Adoptee,
5200+
Proto->getDeclaredInterfaceType());
5201+
diags.diagnose(info.AssocTypeDecl, diag::no_witnesses_type,
5202+
info.AssocTypeDecl->getName());
5203+
5204+
if (failedReq.getKind() != RequirementKind::Layout) {
5205+
diags.diagnose(info.TypeWitnessDecl,
5206+
diag::protocol_type_witness_missing_requirement,
5207+
failedReq.getFirstType(), failedReq.getSecondType(),
5208+
(unsigned)failedReq.getKind());
5209+
}
5210+
}
51655211
}
51665212

51675213
/// 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)