Skip to content

[Sema] Improve diagnostics for access level of protocol witness in extension. #22235

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 1 commit into from
Feb 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1726,6 +1726,10 @@ ERROR(type_witness_objc_generic_parameter,none,
NOTE(witness_fix_access,none,
"mark the %0 as '%select{%error|fileprivate|internal|public|%error}1' to "
"satisfy the requirement", (DescriptiveDeclKind, AccessLevel))
NOTE(witness_move_to_another_extension,none,
"move the %0 to another extension where it can be declared "
"'%select{%error|%error|internal|public|%error}1' to "
"satisfy the requirement", (DescriptiveDeclKind, AccessLevel))
WARNING(assoc_type_default_conformance_failed,none,
"default type %0 for associated type %1 does not satisfy constraint "
"%2: %3", (Type, DeclName, Type, Type))
Expand Down
50 changes: 42 additions & 8 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2405,6 +2405,45 @@ class DiagnoseUsableFromInline {
};
}

/// Helper function for diagnostics when a witness needs to be seated at a
/// required access level.
static void diagnoseWitnessFixAccessLevel(DiagnosticEngine &diags,
ValueDecl *decl,
AccessLevel requiredAccess,
bool isForSetter = false) {
bool shouldMoveToAnotherExtension = false;
bool shouldUseDefaultAccess = false;
if (auto extDecl = dyn_cast<ExtensionDecl>(decl->getDeclContext())) {
if (auto attr = extDecl->getAttrs().getAttribute<AccessControlAttr>()) {
auto extAccess = std::max(attr->getAccess(), AccessLevel::FilePrivate);
if (extAccess < requiredAccess) {
shouldMoveToAnotherExtension = true;
} else if (extAccess == requiredAccess) {
auto declAttr = decl->getAttrs().getAttribute<AccessControlAttr>();
assert(declAttr && declAttr->getAccess() < requiredAccess &&
"expect an explicitly specified access control level which is "
"less accessible than required.");
(void)declAttr;
shouldUseDefaultAccess = true;
}
}
}

// If decl lives in an extension that forbids the required level, we should
// move it to another extension where the required level is possible;
// otherwise, we simply mark decl as the required level.
if (shouldMoveToAnotherExtension) {
diags.diagnose(decl, diag::witness_move_to_another_extension,
decl->getDescriptiveKind(), requiredAccess);
} else {
auto fixItDiag = diags.diagnose(decl, diag::witness_fix_access,
decl->getDescriptiveKind(),
requiredAccess);
fixItAccess(fixItDiag, decl, requiredAccess, isForSetter,
shouldUseDefaultAccess);
}
}

void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
Type type,
TypeDecl *typeDecl) {
Expand Down Expand Up @@ -2446,10 +2485,7 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
typeDecl->getFullName(),
requiredAccess,
proto->getName());
auto fixItDiag = diags.diagnose(typeDecl, diag::witness_fix_access,
typeDecl->getDescriptiveKind(),
requiredAccess);
fixItAccess(fixItDiag, typeDecl, requiredAccess);
diagnoseWitnessFixAccessLevel(diags, typeDecl, requiredAccess);
});
}

Expand Down Expand Up @@ -3105,10 +3141,8 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
return;
}
}
auto fixItDiag = diags.diagnose(witness, diag::witness_fix_access,
witness->getDescriptiveKind(),
requiredAccess);
fixItAccess(fixItDiag, witness, requiredAccess, isSetter);
diagnoseWitnessFixAccessLevel(diags, witness, requiredAccess,
isSetter);
});
break;
}
Expand Down
42 changes: 42 additions & 0 deletions test/Compatibility/accessibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ private struct PrivateStruct: PublicProto, InternalProto, FilePrivateProto, Priv
public var publicVar = 0
}

public struct PublicStructWithInternalExtension: PublicProto, InternalProto, FilePrivateProto, PrivateProto {}
// expected-error@-1 {{method 'publicReq()' must be declared public because it matches a requirement in public protocol 'PublicProto'}} {{none}}
// expected-error@-2 {{method 'internalReq()' must be declared internal because it matches a requirement in internal protocol 'InternalProto'}} {{none}}
// expected-error@-3 {{method 'filePrivateReq()' must be declared fileprivate because it matches a requirement in fileprivate protocol 'FilePrivateProto'}} {{none}}
// expected-error@-4 {{method 'privateReq()' must be declared fileprivate because it matches a requirement in private protocol 'PrivateProto'}} {{none}}
internal extension PublicStructWithInternalExtension {
private func publicReq() {} // expected-note {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
private func internalReq() {} // expected-note {{mark the instance method as 'internal' to satisfy the requirement}} {{3-11=}}
private func filePrivateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
private func privateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
}

extension PublicStruct {
public init(x: Int) { self.init() }
}
Expand Down Expand Up @@ -670,6 +682,22 @@ internal struct EquatablishOuterProblem3 {
private func ==(lhs: EquatablishOuterProblem3.Inner, rhs: EquatablishOuterProblem3.Inner) {}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{1-8=internal}}

internal struct EquatablishOuterProblem4 {
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
}
internal extension EquatablishOuterProblem4.Inner {
fileprivate static func ==(lhs: EquatablishOuterProblem4.Inner, rhs: EquatablishOuterProblem4.Inner) {}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{3-15=}}
}

internal struct EquatablishOuterProblem5 {
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
}
private extension EquatablishOuterProblem5.Inner {
static func ==(lhs: EquatablishOuterProblem5.Inner, rhs: EquatablishOuterProblem5.Inner) {}
// expected-note@-1 {{move the operator function to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
}


public protocol AssocTypeProto {
associatedtype Assoc
Expand All @@ -693,6 +721,20 @@ internal struct AssocTypeOuterProblem2 {
}
}

internal struct AssocTypeOuterProblem3 {
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}}
}
internal extension AssocTypeOuterProblem3.Inner {
fileprivate typealias Assoc = Int // expected-note {{mark the type alias as 'internal' to satisfy the requirement}} {{3-15=}}
}

internal struct AssocTypeOuterProblem4 {
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}}
}
private extension AssocTypeOuterProblem4.Inner {
typealias Assoc = Int // expected-note {{move the type alias to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
}

internal typealias InternalComposition = PublicClass & PublicProto // expected-note {{declared here}}
public class DerivedFromInternalComposition : InternalComposition { // expected-error {{class cannot be declared public because its superclass is internal}}
public func publicReq() {}
Expand Down
42 changes: 42 additions & 0 deletions test/Sema/accessibility.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@ private struct PrivateStruct: PublicProto, InternalProto, FilePrivateProto, Priv
public var publicVar = 0
}

public struct PublicStructWithInternalExtension: PublicProto, InternalProto, FilePrivateProto, PrivateProto {}
// expected-error@-1 {{method 'publicReq()' must be declared public because it matches a requirement in public protocol 'PublicProto'}} {{none}}
// expected-error@-2 {{method 'internalReq()' must be declared internal because it matches a requirement in internal protocol 'InternalProto'}} {{none}}
// expected-error@-3 {{method 'filePrivateReq()' must be declared fileprivate because it matches a requirement in fileprivate protocol 'FilePrivateProto'}} {{none}}
// expected-error@-4 {{method 'privateReq()' must be declared fileprivate because it matches a requirement in private protocol 'PrivateProto'}} {{none}}
internal extension PublicStructWithInternalExtension {
private func publicReq() {} // expected-note {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
private func internalReq() {} // expected-note {{mark the instance method as 'internal' to satisfy the requirement}} {{3-11=}}
private func filePrivateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
private func privateReq() {} // expected-note {{mark the instance method as 'fileprivate' to satisfy the requirement}} {{3-10=fileprivate}}
}

extension PublicStruct {
public init(x: Int) { self.init() }
}
Expand Down Expand Up @@ -677,6 +689,22 @@ internal struct EquatablishOuterProblem3 {
private func ==(lhs: EquatablishOuterProblem3.Inner, rhs: EquatablishOuterProblem3.Inner) {}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{1-8=internal}}

internal struct EquatablishOuterProblem4 {
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
}
internal extension EquatablishOuterProblem4.Inner {
fileprivate static func ==(lhs: EquatablishOuterProblem4.Inner, rhs: EquatablishOuterProblem4.Inner) {}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{3-15=}}
}

internal struct EquatablishOuterProblem5 {
public struct Inner : Equatablish {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
}
private extension EquatablishOuterProblem5.Inner {
static func ==(lhs: EquatablishOuterProblem5.Inner, rhs: EquatablishOuterProblem5.Inner) {}
// expected-note@-1 {{move the operator function to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
}


public protocol AssocTypeProto {
associatedtype Assoc
Expand All @@ -700,6 +728,20 @@ internal struct AssocTypeOuterProblem2 {
}
}

internal struct AssocTypeOuterProblem3 {
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}}
}
internal extension AssocTypeOuterProblem3.Inner {
fileprivate typealias Assoc = Int // expected-note {{mark the type alias as 'internal' to satisfy the requirement}} {{3-15=}}
}

internal struct AssocTypeOuterProblem4 {
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}}
}
private extension AssocTypeOuterProblem4.Inner {
typealias Assoc = Int // expected-note {{move the type alias to another extension where it can be declared 'internal' to satisfy the requirement}} {{none}}
}

internal typealias InternalComposition = PublicClass & PublicProto // expected-note {{declared here}}
public class DerivedFromInternalComposition : InternalComposition { // expected-error {{class cannot be declared public because its superclass is internal}}
public func publicReq() {}
Expand Down
42 changes: 42 additions & 0 deletions test/attr/accessibility_proto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ extension Adopter {
// expected-note@-1 {{mark the instance method as 'public' to satisfy the requirement}} {{3-3=public }}
}

public struct Adopter2<T> : ProtoWithReqs {}
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
public extension Adopter2 {
internal typealias Assoc = Int
// expected-note@-1 {{mark the type alias as 'public' to satisfy the requirement}} {{3-12=}}
fileprivate func foo() {}
// expected-note@-1 {{mark the instance method as 'public' to satisfy the requirement}} {{3-15=}}
}

public struct Adopter3<T> : ProtoWithReqs {}
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
internal extension Adopter3 {
typealias Assoc = Int
// expected-note@-1 {{move the type alias to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
func foo() {}
// expected-note@-1 {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
}

public class AnotherAdopterBase {
typealias Assoc = Int
// expected-note@-1 {{mark the type alias as 'public' to satisfy the requirement}} {{3-3=public }}
Expand All @@ -26,6 +46,28 @@ public class AnotherAdopterSub : AnotherAdopterBase, ProtoWithReqs {}
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}

public class AnotherAdopterBase2 {}
public extension AnotherAdopterBase2 {
internal typealias Assoc = Int
// expected-note@-1 {{mark the type alias as 'public' to satisfy the requirement}} {{3-12=}}
fileprivate func foo() {}
// expected-note@-1 {{mark the instance method as 'public' to satisfy the requirement}} {{3-15=}}
}
public class AnotherAdopterSub2 : AnotherAdopterBase2, ProtoWithReqs {}
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}

public class AnotherAdopterBase3 {}
internal extension AnotherAdopterBase3 {
typealias Assoc = Int
// expected-note@-1 {{move the type alias to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
func foo() {}
// expected-note@-1 {{move the instance method to another extension where it can be declared 'public' to satisfy the requirement}} {{none}}
}
public class AnotherAdopterSub3 : AnotherAdopterBase3, ProtoWithReqs {}
// expected-error@-1 {{method 'foo()' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}
// expected-error@-2 {{type alias 'Assoc' must be declared public because it matches a requirement in public protocol 'ProtoWithReqs'}} {{none}}

public protocol ReqProvider {}
extension ReqProvider {
func foo() {}
Expand Down