Skip to content

Commit 0f493a6

Browse files
dingobyejrose-apple
authored andcommitted
[Sema] Improve diagnostics for access level of protocol witness in extension. (#22235)
If the access level of a protocol witness does not satisfies a requirement, the compiler suggests marking it as the required level. This is not suitable when the witness is in an extension whose specified access level is less than the required level, since the fixit fights with other warnings in this case. This patch identifies such case and produces improved diagnostics. Resolves: SR-9793
1 parent 2301bff commit 0f493a6

File tree

5 files changed

+172
-8
lines changed

5 files changed

+172
-8
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,10 @@ ERROR(type_witness_objc_generic_parameter,none,
17261726
NOTE(witness_fix_access,none,
17271727
"mark the %0 as '%select{%error|fileprivate|internal|public|%error}1' to "
17281728
"satisfy the requirement", (DescriptiveDeclKind, AccessLevel))
1729+
NOTE(witness_move_to_another_extension,none,
1730+
"move the %0 to another extension where it can be declared "
1731+
"'%select{%error|%error|internal|public|%error}1' to "
1732+
"satisfy the requirement", (DescriptiveDeclKind, AccessLevel))
17291733
WARNING(assoc_type_default_conformance_failed,none,
17301734
"default type %0 for associated type %1 does not satisfy constraint "
17311735
"%2: %3", (Type, DeclName, Type, Type))

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,6 +2403,45 @@ class DiagnoseUsableFromInline {
24032403
};
24042404
}
24052405

2406+
/// Helper function for diagnostics when a witness needs to be seated at a
2407+
/// required access level.
2408+
static void diagnoseWitnessFixAccessLevel(DiagnosticEngine &diags,
2409+
ValueDecl *decl,
2410+
AccessLevel requiredAccess,
2411+
bool isForSetter = false) {
2412+
bool shouldMoveToAnotherExtension = false;
2413+
bool shouldUseDefaultAccess = false;
2414+
if (auto extDecl = dyn_cast<ExtensionDecl>(decl->getDeclContext())) {
2415+
if (auto attr = extDecl->getAttrs().getAttribute<AccessControlAttr>()) {
2416+
auto extAccess = std::max(attr->getAccess(), AccessLevel::FilePrivate);
2417+
if (extAccess < requiredAccess) {
2418+
shouldMoveToAnotherExtension = true;
2419+
} else if (extAccess == requiredAccess) {
2420+
auto declAttr = decl->getAttrs().getAttribute<AccessControlAttr>();
2421+
assert(declAttr && declAttr->getAccess() < requiredAccess &&
2422+
"expect an explicitly specified access control level which is "
2423+
"less accessible than required.");
2424+
(void)declAttr;
2425+
shouldUseDefaultAccess = true;
2426+
}
2427+
}
2428+
}
2429+
2430+
// If decl lives in an extension that forbids the required level, we should
2431+
// move it to another extension where the required level is possible;
2432+
// otherwise, we simply mark decl as the required level.
2433+
if (shouldMoveToAnotherExtension) {
2434+
diags.diagnose(decl, diag::witness_move_to_another_extension,
2435+
decl->getDescriptiveKind(), requiredAccess);
2436+
} else {
2437+
auto fixItDiag = diags.diagnose(decl, diag::witness_fix_access,
2438+
decl->getDescriptiveKind(),
2439+
requiredAccess);
2440+
fixItAccess(fixItDiag, decl, requiredAccess, isForSetter,
2441+
shouldUseDefaultAccess);
2442+
}
2443+
}
2444+
24062445
void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
24072446
Type type,
24082447
TypeDecl *typeDecl) {
@@ -2444,10 +2483,7 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
24442483
typeDecl->getFullName(),
24452484
requiredAccess,
24462485
proto->getName());
2447-
auto fixItDiag = diags.diagnose(typeDecl, diag::witness_fix_access,
2448-
typeDecl->getDescriptiveKind(),
2449-
requiredAccess);
2450-
fixItAccess(fixItDiag, typeDecl, requiredAccess);
2486+
diagnoseWitnessFixAccessLevel(diags, typeDecl, requiredAccess);
24512487
});
24522488
}
24532489

@@ -3103,10 +3139,8 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
31033139
return;
31043140
}
31053141
}
3106-
auto fixItDiag = diags.diagnose(witness, diag::witness_fix_access,
3107-
witness->getDescriptiveKind(),
3108-
requiredAccess);
3109-
fixItAccess(fixItDiag, witness, requiredAccess, isSetter);
3142+
diagnoseWitnessFixAccessLevel(diags, witness, requiredAccess,
3143+
isSetter);
31103144
});
31113145
break;
31123146
}

test/Compatibility/accessibility.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ private struct PrivateStruct: PublicProto, InternalProto, FilePrivateProto, Priv
5858
public var publicVar = 0
5959
}
6060

61+
public struct PublicStructWithInternalExtension: PublicProto, InternalProto, FilePrivateProto, PrivateProto {}
62+
// expected-error@-1 {{method 'publicReq()' must be declared public because it matches a requirement in public protocol 'PublicProto'}} {{none}}
63+
// expected-error@-2 {{method 'internalReq()' must be declared internal because it matches a requirement in internal protocol 'InternalProto'}} {{none}}
64+
// expected-error@-3 {{method 'filePrivateReq()' must be declared fileprivate because it matches a requirement in fileprivate protocol 'FilePrivateProto'}} {{none}}
65+
// expected-error@-4 {{method 'privateReq()' must be declared fileprivate because it matches a requirement in private protocol 'PrivateProto'}} {{none}}
66+
internal extension PublicStructWithInternalExtension {
67+
private func publicReq() {} // expected-note {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
68+
private func internalReq() {} // expected-note {{mark the instance method as 'internal' to satisfy the requirement}} {{3-11=}}
69+
private func filePrivateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
70+
private func privateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
71+
}
72+
6173
extension PublicStruct {
6274
public init(x: Int) { self.init() }
6375
}
@@ -670,6 +682,22 @@ internal struct EquatablishOuterProblem3 {
670682
private func ==(lhs: EquatablishOuterProblem3.Inner, rhs: EquatablishOuterProblem3.Inner) {}
671683
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{1-8=internal}}
672684

685+
internal struct EquatablishOuterProblem4 {
686+
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
687+
}
688+
internal extension EquatablishOuterProblem4.Inner {
689+
fileprivate static func ==(lhs: EquatablishOuterProblem4.Inner, rhs: EquatablishOuterProblem4.Inner) {}
690+
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{3-15=}}
691+
}
692+
693+
internal struct EquatablishOuterProblem5 {
694+
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
695+
}
696+
private extension EquatablishOuterProblem5.Inner {
697+
static func ==(lhs: EquatablishOuterProblem5.Inner, rhs: EquatablishOuterProblem5.Inner) {}
698+
// expected-note@-1 {{move the operator function to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
699+
}
700+
673701

674702
public protocol AssocTypeProto {
675703
associatedtype Assoc
@@ -693,6 +721,20 @@ internal struct AssocTypeOuterProblem2 {
693721
}
694722
}
695723

724+
internal struct AssocTypeOuterProblem3 {
725+
public struct Inner : AssocTypeProto {} // expected-error {{type alias 'Assoc' must be as accessible as its enclosing type because it matches a requirement in protocol 'AssocTypeProto'}} {{none}}
726+
}
727+
internal extension AssocTypeOuterProblem3.Inner {
728+
fileprivate typealias Assoc = Int // expected-note {{mark the type alias as 'internal' to satisfy the requirement}} {{3-15=}}
729+
}
730+
731+
internal struct AssocTypeOuterProblem4 {
732+
public struct Inner : AssocTypeProto {} // expected-error {{type alias 'Assoc' must be as accessible as its enclosing type because it matches a requirement in protocol 'AssocTypeProto'}} {{none}}
733+
}
734+
private extension AssocTypeOuterProblem4.Inner {
735+
typealias Assoc = Int // expected-note {{move the type alias to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
736+
}
737+
696738
internal typealias InternalComposition = PublicClass & PublicProto // expected-note {{declared here}}
697739
public class DerivedFromInternalComposition : InternalComposition { // expected-error {{class cannot be declared public because its superclass is internal}}
698740
public func publicReq() {}

test/Sema/accessibility.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,18 @@ private struct PrivateStruct: PublicProto, InternalProto, FilePrivateProto, Priv
5757
public var publicVar = 0
5858
}
5959

60+
public struct PublicStructWithInternalExtension: PublicProto, InternalProto, FilePrivateProto, PrivateProto {}
61+
// expected-error@-1 {{method 'publicReq()' must be declared public because it matches a requirement in public protocol 'PublicProto'}} {{none}}
62+
// expected-error@-2 {{method 'internalReq()' must be declared internal because it matches a requirement in internal protocol 'InternalProto'}} {{none}}
63+
// expected-error@-3 {{method 'filePrivateReq()' must be declared fileprivate because it matches a requirement in fileprivate protocol 'FilePrivateProto'}} {{none}}
64+
// expected-error@-4 {{method 'privateReq()' must be declared fileprivate because it matches a requirement in private protocol 'PrivateProto'}} {{none}}
65+
internal extension PublicStructWithInternalExtension {
66+
private func publicReq() {} // expected-note {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
67+
private func internalReq() {} // expected-note {{mark the instance method as 'internal' to satisfy the requirement}} {{3-11=}}
68+
private func filePrivateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
69+
private func privateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
70+
}
71+
6072
extension PublicStruct {
6173
public init(x: Int) { self.init() }
6274
}
@@ -677,6 +689,22 @@ internal struct EquatablishOuterProblem3 {
677689
private func ==(lhs: EquatablishOuterProblem3.Inner, rhs: EquatablishOuterProblem3.Inner) {}
678690
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{1-8=internal}}
679691

692+
internal struct EquatablishOuterProblem4 {
693+
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
694+
}
695+
internal extension EquatablishOuterProblem4.Inner {
696+
fileprivate static func ==(lhs: EquatablishOuterProblem4.Inner, rhs: EquatablishOuterProblem4.Inner) {}
697+
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{3-15=}}
698+
}
699+
700+
internal struct EquatablishOuterProblem5 {
701+
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
702+
}
703+
private extension EquatablishOuterProblem5.Inner {
704+
static func ==(lhs: EquatablishOuterProblem5.Inner, rhs: EquatablishOuterProblem5.Inner) {}
705+
// expected-note@-1 {{move the operator function to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
706+
}
707+
680708

681709
public protocol AssocTypeProto {
682710
associatedtype Assoc
@@ -700,6 +728,20 @@ internal struct AssocTypeOuterProblem2 {
700728
}
701729
}
702730

731+
internal struct AssocTypeOuterProblem3 {
732+
public struct Inner : AssocTypeProto {} // expected-error {{type alias 'Assoc' must be as accessible as its enclosing type because it matches a requirement in protocol 'AssocTypeProto'}} {{none}}
733+
}
734+
internal extension AssocTypeOuterProblem3.Inner {
735+
fileprivate typealias Assoc = Int // expected-note {{mark the type alias as 'internal' to satisfy the requirement}} {{3-15=}}
736+
}
737+
738+
internal struct AssocTypeOuterProblem4 {
739+
public struct Inner : AssocTypeProto {} // expected-error {{type alias 'Assoc' must be as accessible as its enclosing type because it matches a requirement in protocol 'AssocTypeProto'}} {{none}}
740+
}
741+
private extension AssocTypeOuterProblem4.Inner {
742+
typealias Assoc = Int // expected-note {{move the type alias to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
743+
}
744+
703745
internal typealias InternalComposition = PublicClass & PublicProto // expected-note {{declared here}}
704746
public class DerivedFromInternalComposition : InternalComposition { // expected-error {{class cannot be declared public because its superclass is internal}}
705747
public func publicReq() {}

test/attr/accessibility_proto.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,26 @@ extension Adopter {
1616
// expected-note@-1 {{mark the instance method as 'public' to satisfy the requirement}} {{3-3=public }}
1717
}
1818

19+
public struct Adopter2<T> : ProtoWithReqs {}
20+
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
21+
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
22+
public extension Adopter2 {
23+
internal typealias Assoc = Int
24+
// expected-note@-1 {{mark the type alias as 'public' to satisfy the requirement}} {{3-12=}}
25+
fileprivate func foo() {}
26+
// expected-note@-1 {{mark the instance method as 'public' to satisfy the requirement}} {{3-15=}}
27+
}
28+
29+
public struct Adopter3<T> : ProtoWithReqs {}
30+
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
31+
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
32+
internal extension Adopter3 {
33+
typealias Assoc = Int
34+
// expected-note@-1 {{move the type alias to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
35+
func foo() {}
36+
// expected-note@-1 {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
37+
}
38+
1939
public class AnotherAdopterBase {
2040
typealias Assoc = Int
2141
// expected-note@-1 {{mark the type alias as 'public' to satisfy the requirement}} {{3-3=public }}
@@ -26,6 +46,28 @@ public class AnotherAdopterSub : AnotherAdopterBase, ProtoWithReqs {}
2646
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
2747
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
2848

49+
public class AnotherAdopterBase2 {}
50+
public extension AnotherAdopterBase2 {
51+
internal typealias Assoc = Int
52+
// expected-note@-1 {{mark the type alias as 'public' to satisfy the requirement}} {{3-12=}}
53+
fileprivate func foo() {}
54+
// expected-note@-1 {{mark the instance method as 'public' to satisfy the requirement}} {{3-15=}}
55+
}
56+
public class AnotherAdopterSub2 : AnotherAdopterBase2, ProtoWithReqs {}
57+
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
58+
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
59+
60+
public class AnotherAdopterBase3 {}
61+
internal extension AnotherAdopterBase3 {
62+
typealias Assoc = Int
63+
// expected-note@-1 {{move the type alias to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
64+
func foo() {}
65+
// expected-note@-1 {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
66+
}
67+
public class AnotherAdopterSub3 : AnotherAdopterBase3, ProtoWithReqs {}
68+
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
69+
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
70+
2971
public protocol ReqProvider {}
3072
extension ReqProvider {
3173
func foo() {}

0 commit comments

Comments
 (0)