Skip to content

Commit 2e45011

Browse files
authored
Merge pull request #18048 from jrose-apple/finding-ways-to-scope
Limit ValueDecl::getFormalAccess and get rid of adjustAccessLevelForProtocolExtension
2 parents e938fdf + cd22c5d commit 2e45011

16 files changed

+362
-187
lines changed

include/swift/AST/Decl.h

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2350,33 +2350,17 @@ class ValueDecl : public Decl {
23502350
/// Returns the access level specified explicitly by the user, or provided by
23512351
/// default according to language rules.
23522352
///
2353-
/// This is the access used when calculating if access control is being used
2354-
/// consistently. If \p useDC is provided (the location where the value is
2355-
/// being used), features that affect formal access such as \c \@testable are
2356-
/// taken into account.
2357-
///
2358-
/// If \p treatUsableFromInlineAsPublic is true, declarations marked with the
2359-
/// \c @usableFromInline attribute are treated as public. This is normally
2360-
/// false for name lookup and other source language concerns, but true when
2361-
/// computing the linkage of generated functions.
2353+
/// Most of the time this is not the interesting value to check; access is
2354+
/// limited by enclosing scopes per SE-0025. Use #getFormalAccessScope to
2355+
/// check if access control is being used consistently, and to take features
2356+
/// such as \c \@testable and \c \@usableFromInline into account.
23622357
///
23632358
/// \sa getFormalAccessScope
2364-
AccessLevel getFormalAccess(const DeclContext *useDC = nullptr,
2365-
bool treatUsableFromInlineAsPublic = false) const;
2359+
/// \sa hasOpenAccess
2360+
AccessLevel getFormalAccess() const;
23662361

2367-
/// If this declaration is a member of a protocol extension, return the
2368-
/// minimum of the given access level and the protocol's access level.
2369-
///
2370-
/// Otherwise, return the given access level unmodified.
2371-
///
2372-
/// This is used when checking name lookup visibility. Protocol extension
2373-
/// members can be found when performing name lookup on a concrete type;
2374-
/// if the concrete type is visible from the lookup context but the
2375-
/// protocol is not, we do not want the protocol extension members to be
2376-
/// visible.
2377-
AccessLevel adjustAccessLevelForProtocolExtension(AccessLevel access) const;
2378-
2379-
/// Determine whether this Decl has either Private or FilePrivate access.
2362+
/// Determine whether this Decl has either Private or FilePrivate access,
2363+
/// and its DeclContext does not.
23802364
bool isOutermostPrivateOrFilePrivateScope() const;
23812365

23822366
/// Returns the outermost DeclContext from which this declaration can be
@@ -2398,6 +2382,7 @@ class ValueDecl : public Decl {
23982382
///
23992383
/// \sa getFormalAccess
24002384
/// \sa isAccessibleFrom
2385+
/// \sa hasOpenAccess
24012386
AccessScope
24022387
getFormalAccessScope(const DeclContext *useDC = nullptr,
24032388
bool treatUsableFromInlineAsPublic = false) const;
@@ -2451,6 +2436,14 @@ class ValueDecl : public Decl {
24512436
bool isAccessibleFrom(const DeclContext *DC,
24522437
bool forConformance = false) const;
24532438

2439+
/// Returns whether this declaration should be treated as \c open from
2440+
/// \p useDC. This is very similar to #getFormalAccess, but takes
2441+
/// \c \@testable into account.
2442+
///
2443+
/// This is mostly only useful when considering requirements on an override:
2444+
/// if the base declaration is \c open, the override might have to be too.
2445+
bool hasOpenAccess(const DeclContext *useDC) const;
2446+
24542447
/// Retrieve the "interface" type of this value, which uses
24552448
/// GenericTypeParamType if the declaration is generic. For a generic
24562449
/// function, this will have a GenericFunctionType with a

lib/AST/Decl.cpp

Lines changed: 197 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,10 +2297,44 @@ static AccessLevel getTestableAccess(const ValueDecl *decl) {
22972297
return AccessLevel::Public;
22982298
}
22992299

2300+
/// Adjust \p access based on whether \p VD is \@usableFromInline or has been
2301+
/// testably imported from \p useDC.
2302+
///
2303+
/// \p access isn't always just `VD->getFormalAccess()` because this adjustment
2304+
/// may be for a write, in which case the setter's access might be used instead.
2305+
static AccessLevel getAdjustedFormalAccess(const ValueDecl *VD,
2306+
AccessLevel access,
2307+
const DeclContext *useDC,
2308+
bool treatUsableFromInlineAsPublic) {
2309+
if (treatUsableFromInlineAsPublic &&
2310+
access == AccessLevel::Internal &&
2311+
VD->isUsableFromInline()) {
2312+
return AccessLevel::Public;
2313+
}
2314+
2315+
if (useDC && (access == AccessLevel::Internal ||
2316+
access == AccessLevel::Public)) {
2317+
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()))
2318+
if (useSF->hasTestableImport(VD->getModuleContext()))
2319+
return getTestableAccess(VD);
2320+
}
2321+
2322+
return access;
2323+
}
2324+
2325+
/// Convenience overload that uses `VD->getFormalAccess()` as the access to
2326+
/// adjust.
2327+
static AccessLevel
2328+
getAdjustedFormalAccess(const ValueDecl *VD, const DeclContext *useDC,
2329+
bool treatUsableFromInlineAsPublic) {
2330+
return getAdjustedFormalAccess(VD, VD->getFormalAccess(), useDC,
2331+
treatUsableFromInlineAsPublic);
2332+
}
2333+
23002334
AccessLevel ValueDecl::getEffectiveAccess() const {
23012335
auto effectiveAccess =
2302-
getFormalAccess(/*useDC=*/nullptr,
2303-
/*treatUsableFromInlineAsPublic=*/true);
2336+
getAdjustedFormalAccess(this, /*useDC=*/nullptr,
2337+
/*treatUsableFromInlineAsPublic=*/true);
23042338

23052339
// Handle @testable.
23062340
switch (effectiveAccess) {
@@ -2352,48 +2386,54 @@ AccessLevel ValueDecl::getEffectiveAccess() const {
23522386
return effectiveAccess;
23532387
}
23542388

2355-
AccessLevel ValueDecl::getFormalAccess(const DeclContext *useDC,
2356-
bool treatUsableFromInlineAsPublic) const {
2389+
AccessLevel ValueDecl::getFormalAccess() const {
23572390
ASTContext &ctx = getASTContext();
2358-
AccessLevel result = ctx.evaluator(AccessLevelRequest{const_cast<ValueDecl *>(this)});
2359-
if (treatUsableFromInlineAsPublic &&
2360-
result == AccessLevel::Internal &&
2361-
isUsableFromInline()) {
2362-
return AccessLevel::Public;
2363-
}
2364-
if (useDC && (result == AccessLevel::Internal ||
2365-
result == AccessLevel::Public)) {
2366-
if (auto *useSF = dyn_cast<SourceFile>(useDC->getModuleScopeContext()))
2367-
if (useSF->hasTestableImport(getModuleContext()))
2368-
return getTestableAccess(this);
2369-
}
2370-
return result;
2391+
return ctx.evaluator(AccessLevelRequest{const_cast<ValueDecl *>(this)});
23712392
}
23722393

2373-
AccessScope
2374-
ValueDecl::getFormalAccessScope(const DeclContext *useDC,
2375-
bool treatUsableFromInlineAsPublic) const {
2376-
const DeclContext *result = getDeclContext();
2377-
AccessLevel access = getFormalAccess(useDC, treatUsableFromInlineAsPublic);
2394+
bool ValueDecl::hasOpenAccess(const DeclContext *useDC) const {
2395+
assert(isa<ClassDecl>(this) || isa<ConstructorDecl>(this) ||
2396+
isPotentiallyOverridable());
23782397

2379-
while (!result->isModuleScopeContext()) {
2380-
if (result->isLocalContext() || access == AccessLevel::Private)
2381-
return AccessScope(result, true);
2398+
AccessLevel access =
2399+
getAdjustedFormalAccess(this, useDC,
2400+
/*treatUsableFromInlineAsPublic*/false);
2401+
return access == AccessLevel::Open;
2402+
}
23822403

2383-
if (auto enclosingNominal = dyn_cast<NominalTypeDecl>(result)) {
2404+
/// Given the formal access level for using \p VD, compute the scope where
2405+
/// \p VD may be accessed, taking \@usableFromInline, \@testable imports,
2406+
/// and enclosing access levels into account.
2407+
///
2408+
/// \p access isn't always just `VD->getFormalAccess()` because this adjustment
2409+
/// may be for a write, in which case the setter's access might be used instead.
2410+
static AccessScope
2411+
getAccessScopeForFormalAccess(const ValueDecl *VD,
2412+
AccessLevel formalAccess,
2413+
const DeclContext *useDC,
2414+
bool treatUsableFromInlineAsPublic) {
2415+
AccessLevel access = getAdjustedFormalAccess(VD, formalAccess, useDC,
2416+
treatUsableFromInlineAsPublic);
2417+
const DeclContext *resultDC = VD->getDeclContext();
2418+
2419+
while (!resultDC->isModuleScopeContext()) {
2420+
if (resultDC->isLocalContext() || access == AccessLevel::Private)
2421+
return AccessScope(resultDC, /*private*/true);
2422+
2423+
if (auto enclosingNominal = dyn_cast<NominalTypeDecl>(resultDC)) {
23842424
auto enclosingAccess =
2385-
enclosingNominal->getFormalAccess(useDC,
2386-
treatUsableFromInlineAsPublic);
2425+
getAdjustedFormalAccess(enclosingNominal, useDC,
2426+
treatUsableFromInlineAsPublic);
23872427
access = std::min(access, enclosingAccess);
23882428

2389-
} else if (auto enclosingExt = dyn_cast<ExtensionDecl>(result)) {
2429+
} else if (auto enclosingExt = dyn_cast<ExtensionDecl>(resultDC)) {
23902430
// Just check the base type. If it's a constrained extension, Sema should
23912431
// have already enforced access more strictly.
23922432
if (auto extendedTy = enclosingExt->getExtendedType()) {
23932433
if (auto nominal = extendedTy->getAnyNominal()) {
23942434
auto nominalAccess =
2395-
nominal->getFormalAccess(useDC,
2396-
treatUsableFromInlineAsPublic);
2435+
getAdjustedFormalAccess(nominal, useDC,
2436+
treatUsableFromInlineAsPublic);
23972437
access = std::min(access, nominalAccess);
23982438
}
23992439
}
@@ -2402,16 +2442,16 @@ ValueDecl::getFormalAccessScope(const DeclContext *useDC,
24022442
llvm_unreachable("unknown DeclContext kind");
24032443
}
24042444

2405-
result = result->getParent();
2445+
resultDC = resultDC->getParent();
24062446
}
24072447

24082448
switch (access) {
24092449
case AccessLevel::Private:
24102450
case AccessLevel::FilePrivate:
2411-
assert(result->isModuleScopeContext());
2412-
return AccessScope(result, access == AccessLevel::Private);
2451+
assert(resultDC->isModuleScopeContext());
2452+
return AccessScope(resultDC, access == AccessLevel::Private);
24132453
case AccessLevel::Internal:
2414-
return AccessScope(result->getParentModule());
2454+
return AccessScope(resultDC->getParentModule());
24152455
case AccessLevel::Public:
24162456
case AccessLevel::Open:
24172457
return AccessScope::getPublic();
@@ -2420,6 +2460,128 @@ ValueDecl::getFormalAccessScope(const DeclContext *useDC,
24202460
llvm_unreachable("unknown access level");
24212461
}
24222462

2463+
AccessScope
2464+
ValueDecl::getFormalAccessScope(const DeclContext *useDC,
2465+
bool treatUsableFromInlineAsPublic) const {
2466+
return getAccessScopeForFormalAccess(this, getFormalAccess(), useDC,
2467+
treatUsableFromInlineAsPublic);
2468+
}
2469+
2470+
/// Checks if \p VD may be used from \p useDC, taking \@testable imports into
2471+
/// account.
2472+
///
2473+
/// Whenever the enclosing context of \p VD is usable from \p useDC, this
2474+
/// should compute the same result as checkAccess, below, but more slowly.
2475+
///
2476+
/// See ValueDecl::isAccessibleFrom for a description of \p forConformance.
2477+
static bool checkAccessUsingAccessScopes(const DeclContext *useDC,
2478+
const ValueDecl *VD,
2479+
AccessLevel access) {
2480+
AccessScope accessScope =
2481+
getAccessScopeForFormalAccess(VD, access, useDC,
2482+
/*treatUsableFromInlineAsPublic*/false);
2483+
return accessScope.getDeclContext() == useDC ||
2484+
AccessScope(useDC).isChildOf(accessScope);
2485+
}
2486+
2487+
/// Checks if \p VD may be used from \p useDC, taking \@testable imports into
2488+
/// account.
2489+
///
2490+
/// When \p access is the same as `VD->getFormalAccess()` and the enclosing
2491+
/// context of \p VD is usable from \p useDC, this ought to be the same as
2492+
/// getting the AccessScope for `VD` and checking if \p useDC is within it.
2493+
/// However, there's a source compatibility hack around protocol extensions
2494+
/// that makes it not quite the same.
2495+
///
2496+
/// See ValueDecl::isAccessibleFrom for a description of \p forConformance.
2497+
static bool checkAccess(const DeclContext *useDC, const ValueDecl *VD,
2498+
AccessLevel access, bool forConformance) {
2499+
auto *sourceDC = VD->getDeclContext();
2500+
2501+
if (!forConformance) {
2502+
if (auto *proto = sourceDC->getAsProtocolOrProtocolExtensionContext()) {
2503+
// FIXME: Swift 4.1 allowed accessing protocol extension methods that were
2504+
// marked 'public' if the protocol was '@_versioned' (now
2505+
// '@usableFromInline'). Which works at the ABI level, so let's keep
2506+
// supporting that here by explicitly checking for it.
2507+
if (access == AccessLevel::Public) {
2508+
assert(proto->getDeclContext()->isModuleScopeContext() &&
2509+
"if we get nested protocols, this should not apply to them");
2510+
if (proto->getFormalAccess() == AccessLevel::Internal &&
2511+
proto->isUsableFromInline()) {
2512+
return true;
2513+
}
2514+
}
2515+
2516+
// Skip the fast path below and just compare access scopes.
2517+
return checkAccessUsingAccessScopes(useDC, VD, access);
2518+
}
2519+
}
2520+
2521+
// Fast path: assume that the client context already has access to our parent
2522+
// DeclContext, and only check what might be different about this declaration.
2523+
if (!useDC)
2524+
return access >= AccessLevel::Public;
2525+
2526+
switch (access) {
2527+
case AccessLevel::Private:
2528+
return (useDC == sourceDC ||
2529+
AccessScope::allowsPrivateAccess(useDC, sourceDC));
2530+
case AccessLevel::FilePrivate:
2531+
return useDC->getModuleScopeContext() == sourceDC->getModuleScopeContext();
2532+
case AccessLevel::Internal: {
2533+
const ModuleDecl *sourceModule = sourceDC->getParentModule();
2534+
const DeclContext *useFile = useDC->getModuleScopeContext();
2535+
if (useFile->getParentModule() == sourceModule)
2536+
return true;
2537+
if (auto *useSF = dyn_cast<SourceFile>(useFile))
2538+
if (useSF->hasTestableImport(sourceModule))
2539+
return true;
2540+
return false;
2541+
}
2542+
case AccessLevel::Public:
2543+
case AccessLevel::Open:
2544+
return true;
2545+
}
2546+
llvm_unreachable("bad access level");
2547+
}
2548+
2549+
bool ValueDecl::isAccessibleFrom(const DeclContext *useDC,
2550+
bool forConformance) const {
2551+
auto access = getFormalAccess();
2552+
bool result = checkAccess(useDC, this, access, forConformance);
2553+
2554+
// For everything outside of protocols and operators, we should get the same
2555+
// result using either implementation of checkAccess, because useDC must
2556+
// already have access to this declaration's DeclContext.
2557+
// FIXME: Arguably, we're doing the wrong thing for operators here too,
2558+
// because we're finding internal operators within private types. Fortunately
2559+
// we have a requirement that a member operator take the enclosing type as an
2560+
// argument, so it won't ever match.
2561+
assert(getDeclContext()->getAsProtocolOrProtocolExtensionContext() ||
2562+
isOperator() ||
2563+
result == checkAccessUsingAccessScopes(useDC, this, access));
2564+
2565+
return result;
2566+
}
2567+
2568+
bool AbstractStorageDecl::isSetterAccessibleFrom(const DeclContext *DC,
2569+
bool forConformance) const {
2570+
assert(isSettable(DC));
2571+
2572+
// If a stored property does not have a setter, it is still settable from the
2573+
// designated initializer constructor. In this case, don't check setter
2574+
// access; it is not set.
2575+
if (hasStorage() && !isSettable(nullptr))
2576+
return true;
2577+
2578+
if (isa<ParamDecl>(this))
2579+
return true;
2580+
2581+
auto access = getSetterFormalAccess();
2582+
return checkAccess(DC, this, access, forConformance);
2583+
}
2584+
24232585
void ValueDecl::copyFormalAccessFrom(const ValueDecl *source,
24242586
bool sourceIsParentContext) {
24252587
if (!hasAccess()) {

0 commit comments

Comments
 (0)