Skip to content

Commit 044c8a9

Browse files
committed
Check protocol witnesses using access scopes. (swiftlang#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 (cherry picked from commit f65ad81)
1 parent 6eca2bf commit 044c8a9

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
@@ -3598,6 +3598,20 @@ void swift::performStmtDiagnostics(TypeChecker &TC, const Stmt *S) {
35983598
// Utility functions
35993599
//===----------------------------------------------------------------------===//
36003600

3601+
3602+
Accessibility
3603+
swift::accessibilityFromScopeForDiagnostics(const DeclContext *accessScope) {
3604+
if (!accessScope)
3605+
return Accessibility::Public;
3606+
if (isa<ModuleDecl>(accessScope))
3607+
return Accessibility::Internal;
3608+
if (accessScope->isModuleScopeContext() &&
3609+
accessScope->getASTContext().LangOpts.EnableSwift3Private) {
3610+
return Accessibility::FilePrivate;
3611+
}
3612+
return Accessibility::Private;
3613+
}
3614+
36013615
void swift::fixItAccessibility(InFlightDiagnostic &diag, ValueDecl *VD,
36023616
Accessibility desiredAccess, bool isForSetter) {
36033617
StringRef fixItString;
@@ -3610,7 +3624,7 @@ void swift::fixItAccessibility(InFlightDiagnostic &diag, ValueDecl *VD,
36103624
}
36113625

36123626
DeclAttributes &attrs = VD->getAttrs();
3613-
DeclAttribute *attr;
3627+
AbstractAccessibilityAttr *attr;
36143628
if (isForSetter) {
36153629
attr = attrs.getAttribute<SetterAccessibilityAttr>();
36163630
cast<AbstractStorageDecl>(VD)->overwriteSetterAccessibility(desiredAccess);
@@ -3638,10 +3652,18 @@ void swift::fixItAccessibility(InFlightDiagnostic &diag, ValueDecl *VD,
36383652
diag.fixItRemove(attr->Range);
36393653

36403654
} else if (attr) {
3641-
// This uses getLocation() instead of getRange() because we don't want to
3642-
// replace the "(set)" part of a setter attribute.
3643-
diag.fixItReplace(attr->getLocation(), fixItString.drop_back());
3644-
attr->setInvalid();
3655+
// If the formal access already matches the desired access, the problem
3656+
// must be in a parent scope. Don't emit a fix-it.
3657+
// FIXME: It's also possible for access to already be /broader/ than what's
3658+
// desired, in which case the problem is also in a parent scope. However,
3659+
// this function is sometimes called to make access narrower, so assuming
3660+
// that a broader scope is acceptable breaks some diagnostics.
3661+
if (attr->getAccess() != desiredAccess) {
3662+
// This uses getLocation() instead of getRange() because we don't want to
3663+
// replace the "(set)" part of a setter attribute.
3664+
diag.fixItReplace(attr->getLocation(), fixItString.drop_back());
3665+
attr->setInvalid();
3666+
}
36453667

36463668
} else if (auto var = dyn_cast<VarDecl>(VD)) {
36473669
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
@@ -1539,23 +1539,6 @@ static void highlightOffendingType(TypeChecker &TC, InFlightDiagnostic &diag,
15391539
}
15401540
}
15411541

1542-
/// Returns the access level associated with \p accessScope, for diagnostic
1543-
/// purposes.
1544-
///
1545-
/// \sa ValueDecl::getFormalAccessScope
1546-
static Accessibility
1547-
accessibilityFromScopeForDiagnostics(const DeclContext *accessScope) {
1548-
if (!accessScope)
1549-
return Accessibility::Public;
1550-
if (isa<ModuleDecl>(accessScope))
1551-
return Accessibility::Internal;
1552-
if (accessScope->isModuleScopeContext() &&
1553-
accessScope->getASTContext().LangOpts.EnableSwift3Private) {
1554-
return Accessibility::FilePrivate;
1555-
}
1556-
return Accessibility::Private;
1557-
}
1558-
15591542
static void checkGenericParamAccessibility(TypeChecker &TC,
15601543
const GenericParamList *params,
15611544
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
}
@@ -1214,48 +1214,35 @@ bool WitnessChecker::findBestWitness(ValueDecl *requirement,
12141214
}
12151215

12161216
bool WitnessChecker::
1217-
checkWitnessAccessibility(Accessibility *requiredAccess,
1217+
checkWitnessAccessibility(const DeclContext *&requiredAccessScope,
12181218
ValueDecl *requirement,
12191219
ValueDecl *witness,
12201220
bool *isSetter) {
12211221
*isSetter = false;
12221222

1223-
*requiredAccess = std::min(Proto->getFormalAccess(), *requiredAccess);
1224-
if (TC.getLangOpts().EnableSwift3Private)
1225-
*requiredAccess = std::max(*requiredAccess, Accessibility::FilePrivate);
1223+
const DeclContext *protoAccessScope = Proto->getFormalAccessScope(DC);
12261224

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

1236-
if (witnessAccess < *requiredAccess)
1238+
if (!witness->isAccessibleFrom(requiredAccessScope))
12371239
return true;
12381240

12391241
if (requirement->isSettable(DC)) {
12401242
*isSetter = true;
12411243

12421244
auto ASD = cast<AbstractStorageDecl>(witness);
1243-
const DeclContext *accessDC;
1244-
switch (*requiredAccess) {
1245-
case Accessibility::Open:
1246-
case Accessibility::Public:
1247-
accessDC = nullptr;
1248-
break;
1249-
case Accessibility::Internal:
1250-
accessDC = DC->getParentModule();
1251-
break;
1252-
case Accessibility::FilePrivate:
1253-
case Accessibility::Private:
1254-
accessDC = DC->getModuleScopeContext();
1255-
break;
1256-
}
1257-
1258-
if (!ASD->isSetterAccessibleFrom(accessDC))
1245+
if (!ASD->isSetterAccessibleFrom(requiredAccessScope))
12591246
return true;
12601247
}
12611248

@@ -1272,19 +1259,19 @@ checkWitnessAvailability(ValueDecl *requirement,
12721259
}
12731260

12741261
RequirementCheck WitnessChecker::
1275-
checkWitness(Accessibility requiredAccess,
1262+
checkWitness(const DeclContext *requiredAccessScope,
12761263
ValueDecl *requirement,
12771264
RequirementMatch match) {
12781265
if (!match.OptionalAdjustments.empty())
12791266
return CheckKind::OptionalityConflict;
12801267

12811268
bool isSetter = false;
1282-
if (checkWitnessAccessibility(&requiredAccess, requirement,
1269+
if (checkWitnessAccessibility(requiredAccessScope, requirement,
12831270
match.Witness, &isSetter)) {
12841271
CheckKind kind = (isSetter
12851272
? CheckKind::AccessibilityOfSetter
12861273
: CheckKind::Accessibility);
1287-
return RequirementCheck(kind, requiredAccess);
1274+
return RequirementCheck(kind, requiredAccessScope);
12881275
}
12891276

12901277
auto requiredAvailability = AvailabilityContext::alwaysAvailable();
@@ -1910,17 +1897,23 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
19101897

19111898
if (typeDecl) {
19121899
// Check access.
1913-
Accessibility requiredAccess = Adoptee->getAnyNominal()->getFormalAccess();
1900+
const DeclContext *requiredAccessScope =
1901+
Adoptee->getAnyNominal()->getFormalAccessScope(DC);
19141902
bool isSetter = false;
1915-
if (checkWitnessAccessibility(&requiredAccess, assocType, typeDecl,
1903+
if (checkWitnessAccessibility(requiredAccessScope, assocType, typeDecl,
19161904
&isSetter)) {
19171905
assert(!isSetter);
19181906

1907+
// Avoid relying on the lifetime of 'this'.
1908+
const DeclContext *DC = this->DC;
19191909
diagnoseOrDefer(assocType, false,
1920-
[typeDecl, requiredAccess, assocType](
1910+
[DC, typeDecl, requiredAccessScope, assocType](
19211911
TypeChecker &tc, NormalProtocolConformance *conformance) {
1912+
Accessibility requiredAccess =
1913+
accessibilityFromScopeForDiagnostics(requiredAccessScope);
19221914
auto proto = conformance->getProtocol();
1923-
bool protoForcesAccess = (requiredAccess == proto->getFormalAccess());
1915+
bool protoForcesAccess =
1916+
(requiredAccessScope == proto->getFormalAccessScope(DC));
19241917
auto diagKind = protoForcesAccess
19251918
? diag::type_witness_not_accessible_proto
19261919
: diag::type_witness_not_accessible_type;
@@ -2010,6 +2003,8 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,
20102003
static void diagnoseNoWitness(ValueDecl *Requirement, Type RequirementType,
20112004
NormalProtocolConformance *Conformance,
20122005
TypeChecker &TC) {
2006+
// FIXME: Try an ignore-access lookup?
2007+
20132008
SourceLoc FixitLocation;
20142009
SourceLoc TypeLoc;
20152010
if (auto Extension = dyn_cast<ExtensionDecl>(Conformance->getDeclContext())) {
@@ -2158,21 +2153,27 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
21582153
});
21592154
}
21602155

2161-
Accessibility requiredAccess = Adoptee->getAnyNominal()->getFormalAccess();
2162-
auto check = checkWitness(requiredAccess, requirement, best);
2156+
const DeclContext *nominalAccessScope =
2157+
Adoptee->getAnyNominal()->getFormalAccessScope(DC);
2158+
auto check = checkWitness(nominalAccessScope, requirement, best);
21632159

21642160
switch (check.Kind) {
21652161
case CheckKind::Success:
21662162
break;
21672163

21682164
case CheckKind::Accessibility:
21692165
case CheckKind::AccessibilityOfSetter: {
2166+
// Avoid relying on the lifetime of 'this'.
2167+
const DeclContext *DC = this->DC;
21702168
diagnoseOrDefer(requirement, false,
2171-
[witness, check, requirement](
2169+
[DC, witness, check, requirement](
21722170
TypeChecker &tc, NormalProtocolConformance *conformance) {
2171+
Accessibility requiredAccess =
2172+
accessibilityFromScopeForDiagnostics(check.RequiredAccessScope);
2173+
21732174
auto proto = conformance->getProtocol();
21742175
bool protoForcesAccess =
2175-
(check.RequiredAccess == proto->getFormalAccess());
2176+
(check.RequiredAccessScope == proto->getFormalAccessScope(DC));
21762177
auto diagKind = protoForcesAccess
21772178
? diag::witness_not_accessible_proto
21782179
: diag::witness_not_accessible_type;
@@ -2182,9 +2183,9 @@ ConformanceChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
21822183
getRequirementKind(requirement),
21832184
witness->getFullName(),
21842185
isSetter,
2185-
check.RequiredAccess,
2186+
requiredAccess,
21862187
proto->getName());
2187-
fixItAccessibility(diag, witness, check.RequiredAccess, isSetter);
2188+
fixItAccessibility(diag, witness, requiredAccess, isSetter);
21882189
});
21892190
break;
21902191
}
@@ -4978,7 +4979,7 @@ DefaultWitnessChecker::resolveWitnessViaLookup(ValueDecl *requirement) {
49784979

49794980
// Perform the same checks as conformance witness matching, but silently
49804981
// ignore the candidate instead of diagnosing anything.
4981-
auto check = checkWitness(Accessibility::Public, requirement, best);
4982+
auto check = checkWitness(/*access: public*/nullptr, requirement, best);
49824983
if (check.Kind != CheckKind::Success)
49834984
return ResolveWitnessResult::ExplicitFailed;
49844985

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)