Skip to content

Commit 7687a63

Browse files
committed
Sema: Use the availability of the extended nominal as a floor for the availability of extensions. The primary motivation for this change is to reduce unnecessary availability diagnostics for API library authors. Many API libraries contain existing extension decls that lack declared availability where the extension introduces additional members to the extended type in the same release that the extended type was declared. Others contain extensions where the extension itself does not have declared availability but each of the members do. In both cases, the code is safe as written so the extra diagnostics would be a nuisance.
Resolves rdar://93630782
1 parent de47a77 commit 7687a63

8 files changed

+289
-162
lines changed

include/swift/AST/Availability.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,18 @@ class VersionRange {
9393
return getLowerEndpoint() >= Other.getLowerEndpoint();
9494
}
9595

96+
// Returns true if all the versions in the Other range are versions in this
97+
// range and the ranges are not equal.
98+
bool isSupersetOf(const VersionRange &Other) const {
99+
if (isEmpty() || Other.isAll())
100+
return false;
101+
102+
if (isAll() || Other.isEmpty())
103+
return true;
104+
105+
return getLowerEndpoint() < Other.getLowerEndpoint();
106+
}
107+
96108
/// Mutates this range to be a best-effort underapproximation of
97109
/// the intersection of itself and Other. This is the
98110
/// meet operation (greatest lower bound) in the version range lattice.
@@ -244,10 +256,17 @@ class AvailabilityContext {
244256
/// Returns true if \p other makes stronger guarantees than this context.
245257
///
246258
/// That is, `a.isContainedIn(b)` implies `a.union(b) == b`.
247-
bool isContainedIn(AvailabilityContext other) const {
259+
bool isContainedIn(const AvailabilityContext &other) const {
248260
return OSVersion.isContainedIn(other.OSVersion);
249261
}
250262

263+
/// Returns true if \p other is a strict subset of this context.
264+
///
265+
/// That is, `a.isSupersetOf(b)` implies `a != b` and `a.union(b) == a`.
266+
bool isSupersetOf(const AvailabilityContext &other) const {
267+
return OSVersion.isSupersetOf(other.OSVersion);
268+
}
269+
251270
/// Returns true if this context has constraints that make it impossible to
252271
/// actually occur.
253272
///
@@ -272,7 +291,7 @@ class AvailabilityContext {
272291
///
273292
/// As an example, this is used when figuring out the required availability
274293
/// for a type that references multiple nominal decls.
275-
void intersectWith(AvailabilityContext other) {
294+
void intersectWith(const AvailabilityContext &other) {
276295
OSVersion.intersectWith(other.getOSVersion());
277296
}
278297

@@ -283,7 +302,7 @@ class AvailabilityContext {
283302
/// treating some invalid deployment environments as available.
284303
///
285304
/// As an example, this is used for the true branch of `#available`.
286-
void constrainWith(AvailabilityContext other) {
305+
void constrainWith(const AvailabilityContext &other) {
287306
OSVersion.constrainWith(other.getOSVersion());
288307
}
289308

@@ -295,7 +314,7 @@ class AvailabilityContext {
295314
///
296315
/// As an example, this is used for the else branch of a conditional with
297316
/// multiple `#available` checks.
298-
void unionWith(AvailabilityContext other) {
317+
void unionWith(const AvailabilityContext &other) {
299318
OSVersion.unionWith(other.getOSVersion());
300319
}
301320

include/swift/AST/TypeRefinementContext.h

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,19 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
5454
/// The root refinement context.
5555
Root,
5656

57-
/// The context was introduced by a declaration (e.g., the body of a
58-
/// function declaration or the contents of a class declaration).
57+
/// The context was introduced by a declaration with an explicit
58+
/// availability attribute. The context contains both the signature and the
59+
/// body of the declaration.
5960
Decl,
6061

61-
/// The context was introduced by an API boundary; that is, we are in
62-
/// a module with library evolution enabled and the parent context's
63-
/// contents can be visible to the module's clients, but this context's
64-
/// contents are not.
65-
APIBoundary,
62+
/// The context was introduced implicitly by a declaration. The context may
63+
/// cover the entire declaration or it may cover a subset of it. For
64+
/// example, a public, non-inlinable function declaration in an API module
65+
/// will have at least two associated contexts: one for the entire
66+
/// declaration at the declared availability of the API and a nested
67+
/// implicit context for the body of the function, which will always run at
68+
/// the deployment target of the library.
69+
DeclImplicit,
6670

6771
/// The context was introduced for the Then branch of an IfStmt.
6872
IfStmtThenBranch,
@@ -131,7 +135,8 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
131135
}
132136

133137
Decl *getAsDecl() const {
134-
assert(IntroReason == Reason::Decl || IntroReason == Reason::APIBoundary);
138+
assert(IntroReason == Reason::Decl ||
139+
IntroReason == Reason::DeclImplicit);
135140
return D;
136141
}
137142

@@ -163,8 +168,8 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
163168

164169
SourceRange SrcRange;
165170

166-
/// A canonical availability info for this context, computed top-down from the root
167-
/// context (compilation deployment target).
171+
/// A canonical availability info for this context, computed top-down from the
172+
/// root context.
168173
AvailabilityContext AvailabilityInfo;
169174

170175
/// If this context was annotated with an availability attribute, this property captures that.
@@ -194,8 +199,8 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
194199

195200
/// Create a refinement context for the given declaration.
196201
static TypeRefinementContext *
197-
createForAPIBoundary(ASTContext &Ctx, Decl *D, TypeRefinementContext *Parent,
198-
const AvailabilityContext &Info, SourceRange SrcRange);
202+
createForDeclImplicit(ASTContext &Ctx, Decl *D, TypeRefinementContext *Parent,
203+
const AvailabilityContext &Info, SourceRange SrcRange);
199204

200205
/// Create a refinement context for the Then branch of the given IfStmt.
201206
static TypeRefinementContext *

lib/AST/TypeRefinementContext.cpp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ TypeRefinementContext::createForDecl(ASTContext &Ctx, Decl *D,
6464
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info, ExplicitInfo);
6565
}
6666

67-
TypeRefinementContext *TypeRefinementContext::createForAPIBoundary(
67+
TypeRefinementContext *TypeRefinementContext::createForDeclImplicit(
6868
ASTContext &Ctx, Decl *D, TypeRefinementContext *Parent,
6969
const AvailabilityContext &Info, SourceRange SrcRange) {
7070
assert(D);
7171
assert(Parent);
7272
return new (Ctx) TypeRefinementContext(
73-
Ctx, IntroNode(D, Reason::APIBoundary), Parent, SrcRange, Info,
73+
Ctx, IntroNode(D, Reason::DeclImplicit), Parent, SrcRange, Info,
7474
AvailabilityContext::alwaysAvailable());
7575
}
7676

@@ -180,7 +180,7 @@ void TypeRefinementContext::dump(raw_ostream &OS, SourceManager &SrcMgr) const {
180180
SourceLoc TypeRefinementContext::getIntroductionLoc() const {
181181
switch (getReason()) {
182182
case Reason::Decl:
183-
case Reason::APIBoundary:
183+
case Reason::DeclImplicit:
184184
return Node.getAsDecl()->getLoc();
185185

186186
case Reason::IfStmtThenBranch:
@@ -294,7 +294,7 @@ TypeRefinementContext::getAvailabilityConditionVersionSourceRange(
294294
Node.getAsWhileStmt()->getCond(), Platform, Version);
295295

296296
case Reason::Root:
297-
case Reason::APIBoundary:
297+
case Reason::DeclImplicit:
298298
return SourceRange();
299299
}
300300

@@ -308,7 +308,7 @@ void TypeRefinementContext::print(raw_ostream &OS, SourceManager &SrcMgr,
308308

309309
OS << " versions=" << AvailabilityInfo.getOSVersion().getAsString();
310310

311-
if (getReason() == Reason::Decl || getReason() == Reason::APIBoundary) {
311+
if (getReason() == Reason::Decl || getReason() == Reason::DeclImplicit) {
312312
Decl *D = Node.getAsDecl();
313313
OS << " decl=";
314314
if (auto VD = dyn_cast<ValueDecl>(D)) {
@@ -350,8 +350,8 @@ StringRef TypeRefinementContext::getReasonName(Reason R) {
350350
case Reason::Decl:
351351
return "decl";
352352

353-
case Reason::APIBoundary:
354-
return "api_boundary";
353+
case Reason::DeclImplicit:
354+
return "decl_implicit";
355355

356356
case Reason::IfStmtThenBranch:
357357
return "if_then";

lib/Sema/TypeCheckAccess.cpp

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,21 +1850,8 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
18501850
});
18511851

18521852
Where = wasWhere.withExported(hasExportedMembers);
1853-
1854-
// When diagnosing potential unavailability of the extended type, downgrade
1855-
// the diagnostics to warnings when the extension decl has no declared
1856-
// availability and the required availability is more than the deployment
1857-
// target.
1858-
DeclAvailabilityFlags extendedTypeFlags = None;
1859-
auto annotatedRange =
1860-
AvailabilityInference::annotatedAvailableRange(ED, ED->getASTContext());
1861-
if (!annotatedRange.hasValue())
1862-
extendedTypeFlags |= DeclAvailabilityFlag::
1863-
WarnForPotentialUnavailabilityBeforeDeploymentTarget;
1864-
18651853
checkType(ED->getExtendedType(), ED->getExtendedTypeRepr(), ED,
1866-
ExportabilityReason::ExtensionWithPublicMembers,
1867-
extendedTypeFlags);
1854+
ExportabilityReason::ExtensionWithPublicMembers);
18681855

18691856
// 3) If the extension contains exported members or defines conformances,
18701857
// the 'where' clause must only name exported types.

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,9 @@ class TypeRefinementContextBuilder : private ASTWalker {
460460
// Adds in a TRC that covers the entire declaration.
461461
if (auto DeclTRC = getNewContextForSignatureOfDecl(D)) {
462462
pushContext(DeclTRC, D);
463+
464+
// Possibly use this as an effective parent context later.
465+
recordEffectiveParentContext(D, DeclTRC);
463466
}
464467

465468
// Create TRCs that cover only the body of the declaration.
@@ -542,46 +545,63 @@ class TypeRefinementContextBuilder : private ASTWalker {
542545
return nullptr;
543546
}
544547

545-
// A decl only introduces a new context when it either has explicit
546-
// availability or requires the deployment target.
547-
bool HasExplicitAvailability = hasActiveAvailableAttribute(D, Context);
548-
bool ConstrainToDeploymentTarget =
549-
shouldConstrainSignatureToDeploymentTarget(D);
550-
if (!HasExplicitAvailability && !ConstrainToDeploymentTarget)
551-
return nullptr;
548+
// Declarations with an explicit availability attribute always get a TRC.
549+
if (hasActiveAvailableAttribute(D, Context)) {
550+
AvailabilityContext DeclaredAvailability =
551+
swift::AvailabilityInference::availableRange(D, Context);
552552

553-
// We require a valid range in order to be able to query for the TRC
554-
// corresponding to a given SourceLoc.
555-
// If this assert fires, it means we have probably synthesized an implicit
556-
// declaration without location information. The appropriate fix is
557-
// probably to gin up a source range for the declaration when synthesizing
558-
// it.
559-
assert(D->getSourceRange().isValid());
553+
return TypeRefinementContext::createForDecl(
554+
Context, D, getCurrentTRC(),
555+
getEffectiveAvailabilityForDeclSignature(D, DeclaredAvailability),
556+
DeclaredAvailability, refinementSourceRangeForDecl(D));
557+
}
560558

561-
// The potential versions in the declaration are constrained by both
562-
// the declared availability of the declaration and the potential versions
563-
// of its lexical context.
564-
AvailabilityContext ExplicitDeclInfo =
565-
swift::AvailabilityInference::availableRange(D, Context);
566-
AvailabilityContext DeclInfo = ExplicitDeclInfo;
567-
DeclInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
568-
569-
if (ConstrainToDeploymentTarget)
570-
DeclInfo.intersectWith(AvailabilityContext::forDeploymentTarget(Context));
571-
572-
SourceRange Range = refinementSourceRangeForDecl(D);
573-
TypeRefinementContext *NewTRC;
574-
if (HasExplicitAvailability)
575-
NewTRC = TypeRefinementContext::createForDecl(
576-
Context, D, getCurrentTRC(), DeclInfo, ExplicitDeclInfo, Range);
577-
else
578-
NewTRC = TypeRefinementContext::createForAPIBoundary(
579-
Context, D, getCurrentTRC(), DeclInfo, Range);
559+
// Declarations without explicit availability get a TRC if they are
560+
// effectively less available than the surrounding context. For example, an
561+
// internal property in a public struct can be effectively less available
562+
// than the containing struct decl because the internal property will only
563+
// be accessed by code running at the deployment target or later.
564+
AvailabilityContext CurrentAvailability =
565+
getCurrentTRC()->getAvailabilityInfo();
566+
AvailabilityContext EffectiveAvailability =
567+
getEffectiveAvailabilityForDeclSignature(D, CurrentAvailability);
568+
if (CurrentAvailability.isSupersetOf(EffectiveAvailability))
569+
return TypeRefinementContext::createForDeclImplicit(
570+
Context, D, getCurrentTRC(), EffectiveAvailability,
571+
refinementSourceRangeForDecl(D));
572+
573+
return nullptr;
574+
}
580575

581-
// Possibly use this as an effective parent context later.
582-
recordEffectiveParentContext(D, NewTRC);
576+
AvailabilityContext getEffectiveAvailabilityForDeclSignature(
577+
Decl *D, const AvailabilityContext BaseAvailability) {
578+
AvailabilityContext EffectiveAvailability = BaseAvailability;
579+
580+
// As a special case, extension decls are treated as effectively as
581+
// available as the nominal type they extend, up to the deployment target.
582+
// This rule is a convenience for library authors who have written
583+
// extensions without specifying availabilty on the extension itself.
584+
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
585+
auto *Nominal = ED->getExtendedNominal();
586+
if (Nominal && !hasActiveAvailableAttribute(D, Context)) {
587+
EffectiveAvailability.intersectWith(
588+
swift::AvailabilityInference::availableRange(Nominal, Context));
589+
590+
// We want to require availability to be specified on extensions of
591+
// types that would be potentially unavailable to the module containing
592+
// the extension, so limit the effective availability to the deployment
593+
// target.
594+
EffectiveAvailability.unionWith(
595+
AvailabilityContext::forDeploymentTarget(Context));
596+
}
597+
}
583598

584-
return NewTRC;
599+
EffectiveAvailability.intersectWith(getCurrentTRC()->getAvailabilityInfo());
600+
if (shouldConstrainSignatureToDeploymentTarget(D))
601+
EffectiveAvailability.intersectWith(
602+
AvailabilityContext::forDeploymentTarget(Context));
603+
604+
return EffectiveAvailability;
585605
}
586606

587607
/// Checks whether the entire declaration, including its signature, should be
@@ -607,6 +627,14 @@ class TypeRefinementContextBuilder : private ASTWalker {
607627
/// provides a convenient place to specify the refined range when it is
608628
/// different than the declaration's source range.
609629
SourceRange refinementSourceRangeForDecl(Decl *D) {
630+
// We require a valid range in order to be able to query for the TRC
631+
// corresponding to a given SourceLoc.
632+
// If this assert fires, it means we have probably synthesized an implicit
633+
// declaration without location information. The appropriate fix is
634+
// probably to gin up a source range for the declaration when synthesizing
635+
// it.
636+
assert(D->getSourceRange().isValid());
637+
610638
if (auto *storageDecl = dyn_cast<AbstractStorageDecl>(D)) {
611639
// Use the declaration's availability for the context when checking
612640
// the bodies of its accessors.
@@ -642,25 +670,26 @@ class TypeRefinementContextBuilder : private ASTWalker {
642670
return D->getSourceRange();
643671
}
644672

645-
TypeRefinementContext *createAPIBoundaryContext(Decl *D, SourceRange range) {
646-
AvailabilityContext DeploymentTargetInfo =
647-
AvailabilityContext::forDeploymentTarget(Context);
648-
DeploymentTargetInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
649-
650-
return TypeRefinementContext::createForAPIBoundary(
651-
Context, D, getCurrentTRC(), DeploymentTargetInfo, range);
652-
}
653-
654673
void buildContextsForBodyOfDecl(Decl *D) {
655674
// Are we already constrained by the deployment target? If not, adding
656675
// new contexts won't change availability.
657676
if (isCurrentTRCContainedByDeploymentTarget())
658677
return;
659678

679+
// A lambda that creates an implicit decl TRC specifying the deployment
680+
// target for `range` in decl `D`.
681+
auto createContext = [this](Decl *D, SourceRange range) {
682+
AvailabilityContext Availability =
683+
AvailabilityContext::forDeploymentTarget(Context);
684+
Availability.intersectWith(getCurrentTRC()->getAvailabilityInfo());
685+
686+
return TypeRefinementContext::createForDeclImplicit(
687+
Context, D, getCurrentTRC(), Availability, range);
688+
};
689+
660690
// Top level code always uses the deployment target.
661691
if (auto tlcd = dyn_cast<TopLevelCodeDecl>(D)) {
662-
auto *topLevelTRC =
663-
createAPIBoundaryContext(tlcd, tlcd->getSourceRange());
692+
auto *topLevelTRC = createContext(tlcd, tlcd->getSourceRange());
664693
pushContext(topLevelTRC, D);
665694
return;
666695
}
@@ -670,8 +699,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
670699
if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
671700
if (!afd->isImplicit() && afd->getBodySourceRange().isValid() &&
672701
afd->getResilienceExpansion() != ResilienceExpansion::Minimal) {
673-
auto *functionBodyTRC =
674-
createAPIBoundaryContext(afd, afd->getBodySourceRange());
702+
auto *functionBodyTRC = createContext(afd, afd->getBodySourceRange());
675703
pushContext(functionBodyTRC, D);
676704
}
677705
return;
@@ -693,8 +721,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
693721
// Create a TRC for the init written in the source. The ASTWalker
694722
// won't visit these expressions so instead of pushing these onto the
695723
// stack we build them directly.
696-
auto *initTRC =
697-
createAPIBoundaryContext(vd, initExpr->getSourceRange());
724+
auto *initTRC = createContext(vd, initExpr->getSourceRange());
698725
TypeRefinementContextBuilder(initTRC, Context).build(initExpr);
699726
}
700727

@@ -710,7 +737,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
710737
// example, property wrapper initializers that takes block arguments
711738
// are not handled correctly because of this (rdar://77841331).
712739
for (auto *wrapper : vd->getAttachedPropertyWrappers()) {
713-
createAPIBoundaryContext(vd, wrapper->getRange());
740+
createContext(vd, wrapper->getRange());
714741
}
715742
}
716743

0 commit comments

Comments
 (0)