Skip to content

Commit fc1fe4d

Browse files
committed
WIP rdar://93630782
1 parent de47a77 commit fc1fe4d

File tree

6 files changed

+284
-157
lines changed

6 files changed

+284
-157
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+
if (!hasActiveAvailableAttribute(D, Context)) {
586+
EffectiveAvailability.intersectWith(
587+
swift::AvailabilityInference::availableRange(
588+
ED->getExtendedNominal(), 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)