Skip to content

Commit f65ad81

Browse files
authored
Check protocol witnesses using access scopes. (#4176)
...rather than relying on the access-as-spelled, which may be greater than the effective access due to parent scopes. (Some of this will get cleaned up with SR-2209.) rdar://problem/27663492
1 parent 84d4685 commit f65ad81

File tree

6 files changed

+155
-78
lines changed

6 files changed

+155
-78
lines changed

lib/Sema/MiscDiagnostics.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3606,6 +3606,20 @@ void swift::performStmtDiagnostics(TypeChecker &TC, const Stmt *S) {
36063606
// Utility functions
36073607
//===----------------------------------------------------------------------===//
36083608

3609+
3610+
Accessibility
3611+
swift::accessibilityFromScopeForDiagnostics(const DeclContext *accessScope) {
3612+
if (!accessScope)
3613+
return Accessibility::Public;
3614+
if (isa<ModuleDecl>(accessScope))
3615+
return Accessibility::Internal;
3616+
if (accessScope->isModuleScopeContext() &&
3617+
accessScope->getASTContext().LangOpts.EnableSwift3Private) {
3618+
return Accessibility::FilePrivate;
3619+
}
3620+
return Accessibility::Private;
3621+
}
3622+
36093623
void swift::fixItAccessibility(InFlightDiagnostic &diag, ValueDecl *VD,
36103624
Accessibility desiredAccess, bool isForSetter) {
36113625
StringRef fixItString;
@@ -3618,7 +3632,7 @@ void swift::fixItAccessibility(InFlightDiagnostic &diag, ValueDecl *VD,
36183632
}
36193633

36203634
DeclAttributes &attrs = VD->getAttrs();
3621-
DeclAttribute *attr;
3635+
AbstractAccessibilityAttr *attr;
36223636
if (isForSetter) {
36233637
attr = attrs.getAttribute<SetterAccessibilityAttr>();
36243638
cast<AbstractStorageDecl>(VD)->overwriteSetterAccessibility(desiredAccess);
@@ -3646,10 +3660,18 @@ void swift::fixItAccessibility(InFlightDiagnostic &diag, ValueDecl *VD,
36463660
diag.fixItRemove(attr->Range);
36473661

36483662
} else if (attr) {
3649-
// This uses getLocation() instead of getRange() because we don't want to
3650-
// replace the "(set)" part of a setter attribute.
3651-
diag.fixItReplace(attr->getLocation(), fixItString.drop_back());
3652-
attr->setInvalid();
3663+
// If the formal access already matches the desired access, the problem
3664+
// must be in a parent scope. Don't emit a fix-it.
3665+
// FIXME: It's also possible for access to already be /broader/ than what's
3666+
// desired, in which case the problem is also in a parent scope. However,
3667+
// this function is sometimes called to make access narrower, so assuming
3668+
// that a broader scope is acceptable breaks some diagnostics.
3669+
if (attr->getAccess() != desiredAccess) {
3670+
// This uses getLocation() instead of getRange() because we don't want to
3671+
// replace the "(set)" part of a setter attribute.
3672+
diag.fixItReplace(attr->getLocation(), fixItString.drop_back());
3673+
attr->setInvalid();
3674+
}
36533675

36543676
} else if (auto var = dyn_cast<VarDecl>(VD)) {
36553677
if (auto PBD = var->getParentPatternBinding())

lib/Sema/MiscDiagnostics.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ namespace swift {
3131
class TypeChecker;
3232
class ValueDecl;
3333

34+
/// Returns the access level associated with \p accessScope, for diagnostic
35+
/// purposes.
36+
///
37+
/// \sa ValueDecl::getFormalAccessScope
38+
Accessibility
39+
accessibilityFromScopeForDiagnostics(const DeclContext *accessScope);
40+
3441
/// \brief Emit diagnostics for syntactic restrictions on a given expression.
3542
void performSyntacticExprDiagnostics(TypeChecker &TC, const Expr *E,
3643
const DeclContext *DC,

lib/Sema/TypeCheckDecl.cpp

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1538,23 +1538,6 @@ static void highlightOffendingType(TypeChecker &TC, InFlightDiagnostic &diag,
15381538
}
15391539
}
15401540

1541-
/// Returns the access level associated with \p accessScope, for diagnostic
1542-
/// purposes.
1543-
///
1544-
/// \sa ValueDecl::getFormalAccessScope
1545-
static Accessibility
1546-
accessibilityFromScopeForDiagnostics(const DeclContext *accessScope) {
1547-
if (!accessScope)
1548-
return Accessibility::Public;
1549-
if (isa<ModuleDecl>(accessScope))
1550-
return Accessibility::Internal;
1551-
if (accessScope->isModuleScopeContext() &&
1552-
accessScope->getASTContext().LangOpts.EnableSwift3Private) {
1553-
return Accessibility::FilePrivate;
1554-
}
1555-
return Accessibility::Private;
1556-
}
1557-
15581541
static void checkGenericParamAccessibility(TypeChecker &TC,
15591542
const GenericParamList *params,
15601543
const Decl *owner,

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 51 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ namespace {
6868
unsigned &bestIdx,
6969
bool &doNotDiagnoseMatches);
7070

71-
bool checkWitnessAccessibility(Accessibility *requiredAccess,
71+
bool checkWitnessAccessibility(const DeclContext *&requiredAccessScope,
7272
ValueDecl *requirement,
7373
ValueDecl *witness,
7474
bool *isSetter);
@@ -77,7 +77,7 @@ namespace {
7777
ValueDecl *witness,
7878
AvailabilityContext *requirementInfo);
7979

80-
RequirementCheck checkWitness(Accessibility requiredAccess,
80+
RequirementCheck checkWitness(const DeclContext *requiredAccessScope,
8181
ValueDecl *requirement,
8282
RequirementMatch match);
8383
};
@@ -392,24 +392,24 @@ namespace {
392392
struct RequirementCheck {
393393
CheckKind Kind;
394394

395-
/// The required accessibility, if the check failed due to the
395+
/// The required access scope, if the check failed due to the
396396
/// witness being less accessible than the requirement.
397-
Accessibility RequiredAccess;
397+
const DeclContext *RequiredAccessScope;
398398

399399
/// The required availability, if the check failed due to the
400400
/// witness being less available than the requirement.
401401
AvailabilityContext RequiredAvailability;
402402

403403
RequirementCheck(CheckKind kind)
404-
: Kind(kind), RequiredAccess(Accessibility::Public),
404+
: Kind(kind), RequiredAccessScope(nullptr),
405405
RequiredAvailability(AvailabilityContext::alwaysAvailable()) { }
406406

407-
RequirementCheck(CheckKind kind, Accessibility requiredAccess)
408-
: Kind(kind), RequiredAccess(requiredAccess),
407+
RequirementCheck(CheckKind kind, const DeclContext *requiredAccessScope)
408+
: Kind(kind), RequiredAccessScope(requiredAccessScope),
409409
RequiredAvailability(AvailabilityContext::alwaysAvailable()) { }
410410

411411
RequirementCheck(CheckKind kind, AvailabilityContext requiredAvailability)
412-
: Kind(kind), RequiredAccess(Accessibility::Public),
412+
: Kind(kind), RequiredAccessScope(nullptr),
413413
RequiredAvailability(requiredAvailability) { }
414414
};
415415
}
@@ -1218,48 +1218,35 @@ bool WitnessChecker::findBestWitness(ValueDecl *requirement,
12181218
}
12191219

12201220
bool WitnessChecker::
1221-
checkWitnessAccessibility(Accessibility *requiredAccess,
1221+
checkWitnessAccessibility(const DeclContext *&requiredAccessScope,
12221222
ValueDecl *requirement,
12231223
ValueDecl *witness,
12241224
bool *isSetter) {
12251225
*isSetter = false;
12261226

1227-
*requiredAccess = std::min(Proto->getFormalAccess(), *requiredAccess);
1228-
if (TC.getLangOpts().EnableSwift3Private)
1229-
*requiredAccess = std::max(*requiredAccess, Accessibility::FilePrivate);
1227+
const DeclContext *protoAccessScope = Proto->getFormalAccessScope(DC);
12301228

1231-
Accessibility witnessAccess = witness->getFormalAccess(DC);
1232-
1233-
// Leave a hole for old-style top-level operators to be declared 'private' for
1234-
// a fileprivate conformance.
1235-
if (witnessAccess == Accessibility::Private &&
1236-
witness->getDeclContext()->isModuleScopeContext()) {
1237-
witnessAccess = Accessibility::FilePrivate;
1229+
// FIXME: This is the same operation as TypeCheckDecl.cpp's
1230+
// TypeAccessScopeChecker::intersectAccess.
1231+
if (!requiredAccessScope) {
1232+
requiredAccessScope = protoAccessScope;
1233+
} else if (protoAccessScope) {
1234+
if (protoAccessScope->isChildContextOf(requiredAccessScope)) {
1235+
requiredAccessScope = protoAccessScope;
1236+
} else {
1237+
assert(requiredAccessScope == protoAccessScope ||
1238+
requiredAccessScope->isChildContextOf(protoAccessScope));
1239+
}
12381240
}
12391241

1240-
if (witnessAccess < *requiredAccess)
1242+
if (!witness->isAccessibleFrom(requiredAccessScope))
12411243
return true;
12421244

12431245
if (requirement->isSettable(DC)) {
12441246
*isSetter = true;
12451247

12461248
auto ASD = cast<AbstractStorageDecl>(witness);
1247-
const DeclContext *accessDC;
1248-
switch (*requiredAccess) {
1249-
case Accessibility::Open:
1250-
case Accessibility::Public:
1251-
accessDC = nullptr;
1252-
break;
1253-
case Accessibility::Internal:
1254-
accessDC = DC->getParentModule();
1255-
break;
1256-
case Accessibility::FilePrivate:
1257-
case Accessibility::Private:
1258-
accessDC = DC->getModuleScopeContext();
1259-
break;
1260-
}
1261-
1262-
if (!ASD->isSetterAccessibleFrom(accessDC))
1249+
if (!ASD->isSetterAccessibleFrom(requiredAccessScope))
12631250
return true;
12641251
}
12651252

@@ -1276,19 +1263,19 @@ checkWitnessAvailability(ValueDecl *requirement,
12761263
}
12771264

12781265
RequirementCheck WitnessChecker::
1279-
checkWitness(Accessibility requiredAccess,
1266+
checkWitness(const DeclContext *requiredAccessScope,
12801267
ValueDecl *requirement,
12811268
RequirementMatch match) {
12821269
if (!match.OptionalAdjustments.empty())
12831270
return CheckKind::OptionalityConflict;
12841271

12851272
bool isSetter = false;
1286-
if (checkWitnessAccessibility(&requiredAccess, requirement,
1273+
if (checkWitnessAccessibility(requiredAccessScope, requirement,
12871274
match.Witness, &isSetter)) {
12881275
CheckKind kind = (isSetter
12891276
? CheckKind::AccessibilityOfSetter
12901277
: CheckKind::Accessibility);
1291-
return RequirementCheck(kind, requiredAccess);
1278+
return RequirementCheck(kind, requiredAccessScope);
12921279
}
12931280

12941281
auto requiredAvailability = AvailabilityContext::alwaysAvailable();
@@ -1914,17 +1901,23 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
19141901

19151902
if (typeDecl) {
19161903
// Check access.
1917-
Accessibility requiredAccess = Adoptee->getAnyNominal()->getFormalAccess();
1904+
const DeclContext *requiredAccessScope =
1905+
Adoptee->getAnyNominal()->getFormalAccessScope(DC);
19181906
bool isSetter = false;
1919-
if (checkWitnessAccessibility(&requiredAccess, assocType, typeDecl,
1907+
if (checkWitnessAccessibility(requiredAccessScope, assocType, typeDecl,
19201908
&isSetter)) {
19211909
assert(!isSetter);
19221910

1911+
// Avoid relying on the lifetime of 'this'.
1912+
const DeclContext *DC = this->DC;
19231913
diagnoseOrDefer(assocType, false,
1924-
[typeDecl, requiredAccess, assocType](
1914+
[DC, typeDecl, requiredAccessScope, assocType](
19251915
TypeChecker &tc, NormalProtocolConformance *conformance) {
1916+
Accessibility requiredAccess =
1917+
accessibilityFromScopeForDiagnostics(requiredAccessScope);
19261918
auto proto = conformance->getProtocol();
1927-
bool protoForcesAccess = (requiredAccess == proto->getFormalAccess());
1919+
bool protoForcesAccess =
1920+
(requiredAccessScope == proto->getFormalAccessScope(DC));
19281921
auto diagKind = protoForcesAccess
19291922
? diag::type_witness_not_accessible_proto
19301923
: diag::type_witness_not_accessible_type;
@@ -2014,6 +2007,8 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
20142007
static void diagnoseNoWitness(ValueDecl *Requirement, Type RequirementType,
20152008
NormalProtocolConformance *Conformance,
20162009
TypeChecker &TC) {
2010+
// FIXME: Try an ignore-access lookup?
2011+
20172012
SourceLoc FixitLocation;
20182013
SourceLoc TypeLoc;
20192014
if (auto Extension = dyn_cast<ExtensionDecl>(Conformance->getDeclContext())) {
@@ -2162,21 +2157,27 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
21622157
});
21632158
}
21642159

2165-
Accessibility requiredAccess = Adoptee->getAnyNominal()->getFormalAccess();
2166-
auto check = checkWitness(requiredAccess, requirement, best);
2160+
const DeclContext *nominalAccessScope =
2161+
Adoptee->getAnyNominal()->getFormalAccessScope(DC);
2162+
auto check = checkWitness(nominalAccessScope, requirement, best);
21672163

21682164
switch (check.Kind) {
21692165
case CheckKind::Success:
21702166
break;
21712167

21722168
case CheckKind::Accessibility:
21732169
case CheckKind::AccessibilityOfSetter: {
2170+
// Avoid relying on the lifetime of 'this'.
2171+
const DeclContext *DC = this->DC;
21742172
diagnoseOrDefer(requirement, false,
2175-
[witness, check, requirement](
2173+
[DC, witness, check, requirement](
21762174
TypeChecker &tc, NormalProtocolConformance *conformance) {
2175+
Accessibility requiredAccess =
2176+
accessibilityFromScopeForDiagnostics(check.RequiredAccessScope);
2177+
21772178
auto proto = conformance->getProtocol();
21782179
bool protoForcesAccess =
2179-
(check.RequiredAccess == proto->getFormalAccess());
2180+
(check.RequiredAccessScope == proto->getFormalAccessScope(DC));
21802181
auto diagKind = protoForcesAccess
21812182
? diag::witness_not_accessible_proto
21822183
: diag::witness_not_accessible_type;
@@ -2186,9 +2187,9 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
21862187
getRequirementKind(requirement),
21872188
witness->getFullName(),
21882189
isSetter,
2189-
check.RequiredAccess,
2190+
requiredAccess,
21902191
proto->getName());
2191-
fixItAccessibility(diag, witness, check.RequiredAccess, isSetter);
2192+
fixItAccessibility(diag, witness, requiredAccess, isSetter);
21922193
});
21932194
break;
21942195
}
@@ -4968,7 +4969,7 @@ DefaultWitnessChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
49684969

49694970
// Perform the same checks as conformance witness matching, but silently
49704971
// ignore the candidate instead of diagnosing anything.
4971-
auto check = checkWitness(Accessibility::Public, requirement, best);
4972+
auto check = checkWitness(/*access: public*/nullptr, requirement, best);
49724973
if (check.Kind != CheckKind::Success)
49734974
return ResolveWitnessResult::ExplicitFailed;
49744975

test/NameBinding/accessibility.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,10 @@ protocol MethodProto {
9696
}
9797

9898
extension OriginallyEmpty : MethodProto {}
99-
extension HiddenMethod : MethodProto {} // expected-error {{type 'HiddenMethod' does not conform to protocol 'MethodProto'}}
10099
// TESTABLE-NOT: :[[@LINE-1]]:{{[^:]+}}:
101100
#if !ACCESS_DISABLED
101+
extension HiddenMethod : MethodProto {} // expected-error {{type 'HiddenMethod' does not conform to protocol 'MethodProto'}}
102+
102103
extension Foo : MethodProto {} // expected-error {{type 'Foo' does not conform to protocol 'MethodProto'}}
103104
#endif
104105

@@ -108,9 +109,10 @@ protocol TypeProto {
108109
}
109110

110111
extension OriginallyEmpty {}
112+
#if !ACCESS_DISABLED
111113
extension HiddenType : TypeProto {} // expected-error {{type 'HiddenType' does not conform to protocol 'TypeProto'}}
112114
// TESTABLE-NOT: :[[@LINE-1]]:{{[^:]+}}:
113-
#if !ACCESS_DISABLED
115+
114116
extension Foo : TypeProto {} // expected-error {{type 'Foo' does not conform to protocol 'TypeProto'}}
115117
#endif
116118

0 commit comments

Comments
 (0)