Skip to content

Sema: Use the availability of the extended nominal as a floor for the availability of extensions #59040

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions include/swift/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ class VersionRange {
return getLowerEndpoint() >= Other.getLowerEndpoint();
}

// Returns true if all the versions in the Other range are versions in this
// range and the ranges are not equal.
bool isSupersetOf(const VersionRange &Other) const {
if (isEmpty() || Other.isAll())
return false;

if (isAll() || Other.isEmpty())
return true;

return getLowerEndpoint() < Other.getLowerEndpoint();
}

/// Mutates this range to be a best-effort underapproximation of
/// the intersection of itself and Other. This is the
/// meet operation (greatest lower bound) in the version range lattice.
Expand Down Expand Up @@ -244,10 +256,17 @@ class AvailabilityContext {
/// Returns true if \p other makes stronger guarantees than this context.
///
/// That is, `a.isContainedIn(b)` implies `a.union(b) == b`.
bool isContainedIn(AvailabilityContext other) const {
bool isContainedIn(const AvailabilityContext &other) const {
return OSVersion.isContainedIn(other.OSVersion);
}

/// Returns true if \p other is a strict subset of this context.
///
/// That is, `a.isSupersetOf(b)` implies `a != b` and `a.union(b) == a`.
bool isSupersetOf(const AvailabilityContext &other) const {
return OSVersion.isSupersetOf(other.OSVersion);
}

/// Returns true if this context has constraints that make it impossible to
/// actually occur.
///
Expand All @@ -272,7 +291,7 @@ class AvailabilityContext {
///
/// As an example, this is used when figuring out the required availability
/// for a type that references multiple nominal decls.
void intersectWith(AvailabilityContext other) {
void intersectWith(const AvailabilityContext &other) {
OSVersion.intersectWith(other.getOSVersion());
}

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

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

Expand Down
29 changes: 17 additions & 12 deletions include/swift/AST/TypeRefinementContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,19 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {
/// The root refinement context.
Root,

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

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

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

Decl *getAsDecl() const {
assert(IntroReason == Reason::Decl || IntroReason == Reason::APIBoundary);
assert(IntroReason == Reason::Decl ||
IntroReason == Reason::DeclImplicit);
return D;
}

Expand Down Expand Up @@ -163,8 +168,8 @@ class TypeRefinementContext : public ASTAllocated<TypeRefinementContext> {

SourceRange SrcRange;

/// A canonical availability info for this context, computed top-down from the root
/// context (compilation deployment target).
/// A canonical availability info for this context, computed top-down from the
/// root context.
AvailabilityContext AvailabilityInfo;

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

/// Create a refinement context for the given declaration.
static TypeRefinementContext *
createForAPIBoundary(ASTContext &Ctx, Decl *D, TypeRefinementContext *Parent,
const AvailabilityContext &Info, SourceRange SrcRange);
createForDeclImplicit(ASTContext &Ctx, Decl *D, TypeRefinementContext *Parent,
const AvailabilityContext &Info, SourceRange SrcRange);

/// Create a refinement context for the Then branch of the given IfStmt.
static TypeRefinementContext *
Expand Down
14 changes: 7 additions & 7 deletions lib/AST/TypeRefinementContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ TypeRefinementContext::createForDecl(ASTContext &Ctx, Decl *D,
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info, ExplicitInfo);
}

TypeRefinementContext *TypeRefinementContext::createForAPIBoundary(
TypeRefinementContext *TypeRefinementContext::createForDeclImplicit(
ASTContext &Ctx, Decl *D, TypeRefinementContext *Parent,
const AvailabilityContext &Info, SourceRange SrcRange) {
assert(D);
assert(Parent);
return new (Ctx) TypeRefinementContext(
Ctx, IntroNode(D, Reason::APIBoundary), Parent, SrcRange, Info,
Ctx, IntroNode(D, Reason::DeclImplicit), Parent, SrcRange, Info,
AvailabilityContext::alwaysAvailable());
}

Expand Down Expand Up @@ -180,7 +180,7 @@ void TypeRefinementContext::dump(raw_ostream &OS, SourceManager &SrcMgr) const {
SourceLoc TypeRefinementContext::getIntroductionLoc() const {
switch (getReason()) {
case Reason::Decl:
case Reason::APIBoundary:
case Reason::DeclImplicit:
return Node.getAsDecl()->getLoc();

case Reason::IfStmtThenBranch:
Expand Down Expand Up @@ -294,7 +294,7 @@ TypeRefinementContext::getAvailabilityConditionVersionSourceRange(
Node.getAsWhileStmt()->getCond(), Platform, Version);

case Reason::Root:
case Reason::APIBoundary:
case Reason::DeclImplicit:
return SourceRange();
}

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

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

if (getReason() == Reason::Decl || getReason() == Reason::APIBoundary) {
if (getReason() == Reason::Decl || getReason() == Reason::DeclImplicit) {
Decl *D = Node.getAsDecl();
OS << " decl=";
if (auto VD = dyn_cast<ValueDecl>(D)) {
Expand Down Expand Up @@ -350,8 +350,8 @@ StringRef TypeRefinementContext::getReasonName(Reason R) {
case Reason::Decl:
return "decl";

case Reason::APIBoundary:
return "api_boundary";
case Reason::DeclImplicit:
return "decl_implicit";

case Reason::IfStmtThenBranch:
return "if_then";
Expand Down
15 changes: 1 addition & 14 deletions lib/Sema/TypeCheckAccess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1850,21 +1850,8 @@ class DeclAvailabilityChecker : public DeclVisitor<DeclAvailabilityChecker> {
});

Where = wasWhere.withExported(hasExportedMembers);

// When diagnosing potential unavailability of the extended type, downgrade
// the diagnostics to warnings when the extension decl has no declared
// availability and the required availability is more than the deployment
// target.
DeclAvailabilityFlags extendedTypeFlags = None;
auto annotatedRange =
AvailabilityInference::annotatedAvailableRange(ED, ED->getASTContext());
if (!annotatedRange.hasValue())
extendedTypeFlags |= DeclAvailabilityFlag::
WarnForPotentialUnavailabilityBeforeDeploymentTarget;

checkType(ED->getExtendedType(), ED->getExtendedTypeRepr(), ED,
ExportabilityReason::ExtensionWithPublicMembers,
extendedTypeFlags);
ExportabilityReason::ExtensionWithPublicMembers);

// 3) If the extension contains exported members or defines conformances,
// the 'where' clause must only name exported types.
Expand Down
131 changes: 79 additions & 52 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,9 @@ class TypeRefinementContextBuilder : private ASTWalker {
// Adds in a TRC that covers the entire declaration.
if (auto DeclTRC = getNewContextForSignatureOfDecl(D)) {
pushContext(DeclTRC, D);

// Possibly use this as an effective parent context later.
recordEffectiveParentContext(D, DeclTRC);
}

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

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

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

// The potential versions in the declaration are constrained by both
// the declared availability of the declaration and the potential versions
// of its lexical context.
AvailabilityContext ExplicitDeclInfo =
swift::AvailabilityInference::availableRange(D, Context);
AvailabilityContext DeclInfo = ExplicitDeclInfo;
DeclInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());

if (ConstrainToDeploymentTarget)
DeclInfo.intersectWith(AvailabilityContext::forDeploymentTarget(Context));

SourceRange Range = refinementSourceRangeForDecl(D);
TypeRefinementContext *NewTRC;
if (HasExplicitAvailability)
NewTRC = TypeRefinementContext::createForDecl(
Context, D, getCurrentTRC(), DeclInfo, ExplicitDeclInfo, Range);
else
NewTRC = TypeRefinementContext::createForAPIBoundary(
Context, D, getCurrentTRC(), DeclInfo, Range);
// Declarations without explicit availability get a TRC if they are
// effectively less available than the surrounding context. For example, an
// internal property in a public struct can be effectively less available
// than the containing struct decl because the internal property will only
// be accessed by code running at the deployment target or later.
AvailabilityContext CurrentAvailability =
getCurrentTRC()->getAvailabilityInfo();
AvailabilityContext EffectiveAvailability =
getEffectiveAvailabilityForDeclSignature(D, CurrentAvailability);
if (CurrentAvailability.isSupersetOf(EffectiveAvailability))
return TypeRefinementContext::createForDeclImplicit(
Context, D, getCurrentTRC(), EffectiveAvailability,
refinementSourceRangeForDecl(D));

return nullptr;
}

// Possibly use this as an effective parent context later.
recordEffectiveParentContext(D, NewTRC);
AvailabilityContext getEffectiveAvailabilityForDeclSignature(
Decl *D, const AvailabilityContext BaseAvailability) {
AvailabilityContext EffectiveAvailability = BaseAvailability;

// As a special case, extension decls are treated as effectively as
// available as the nominal type they extend, up to the deployment target.
// This rule is a convenience for library authors who have written
// extensions without specifying availabilty on the extension itself.
if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
auto *Nominal = ED->getExtendedNominal();
if (Nominal && !hasActiveAvailableAttribute(D, Context)) {
EffectiveAvailability.intersectWith(
swift::AvailabilityInference::availableRange(Nominal, Context));

// We want to require availability to be specified on extensions of
// types that would be potentially unavailable to the module containing
// the extension, so limit the effective availability to the deployment
// target.
EffectiveAvailability.unionWith(
AvailabilityContext::forDeploymentTarget(Context));
}
}

return NewTRC;
EffectiveAvailability.intersectWith(getCurrentTRC()->getAvailabilityInfo());
if (shouldConstrainSignatureToDeploymentTarget(D))
EffectiveAvailability.intersectWith(
AvailabilityContext::forDeploymentTarget(Context));

return EffectiveAvailability;
}

/// Checks whether the entire declaration, including its signature, should be
Expand All @@ -607,6 +627,14 @@ class TypeRefinementContextBuilder : private ASTWalker {
/// provides a convenient place to specify the refined range when it is
/// different than the declaration's source range.
SourceRange refinementSourceRangeForDecl(Decl *D) {
// We require a valid range in order to be able to query for the TRC
// corresponding to a given SourceLoc.
// If this assert fires, it means we have probably synthesized an implicit
// declaration without location information. The appropriate fix is
// probably to gin up a source range for the declaration when synthesizing
// it.
assert(D->getSourceRange().isValid());

if (auto *storageDecl = dyn_cast<AbstractStorageDecl>(D)) {
// Use the declaration's availability for the context when checking
// the bodies of its accessors.
Expand Down Expand Up @@ -642,25 +670,26 @@ class TypeRefinementContextBuilder : private ASTWalker {
return D->getSourceRange();
}

TypeRefinementContext *createAPIBoundaryContext(Decl *D, SourceRange range) {
AvailabilityContext DeploymentTargetInfo =
AvailabilityContext::forDeploymentTarget(Context);
DeploymentTargetInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());

return TypeRefinementContext::createForAPIBoundary(
Context, D, getCurrentTRC(), DeploymentTargetInfo, range);
}

void buildContextsForBodyOfDecl(Decl *D) {
// Are we already constrained by the deployment target? If not, adding
// new contexts won't change availability.
if (isCurrentTRCContainedByDeploymentTarget())
return;

// A lambda that creates an implicit decl TRC specifying the deployment
// target for `range` in decl `D`.
auto createContext = [this](Decl *D, SourceRange range) {
AvailabilityContext Availability =
AvailabilityContext::forDeploymentTarget(Context);
Availability.intersectWith(getCurrentTRC()->getAvailabilityInfo());

return TypeRefinementContext::createForDeclImplicit(
Context, D, getCurrentTRC(), Availability, range);
};

// Top level code always uses the deployment target.
if (auto tlcd = dyn_cast<TopLevelCodeDecl>(D)) {
auto *topLevelTRC =
createAPIBoundaryContext(tlcd, tlcd->getSourceRange());
auto *topLevelTRC = createContext(tlcd, tlcd->getSourceRange());
pushContext(topLevelTRC, D);
return;
}
Expand All @@ -670,8 +699,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
if (!afd->isImplicit() && afd->getBodySourceRange().isValid() &&
afd->getResilienceExpansion() != ResilienceExpansion::Minimal) {
auto *functionBodyTRC =
createAPIBoundaryContext(afd, afd->getBodySourceRange());
auto *functionBodyTRC = createContext(afd, afd->getBodySourceRange());
pushContext(functionBodyTRC, D);
}
return;
Expand All @@ -693,8 +721,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
// Create a TRC for the init written in the source. The ASTWalker
// won't visit these expressions so instead of pushing these onto the
// stack we build them directly.
auto *initTRC =
createAPIBoundaryContext(vd, initExpr->getSourceRange());
auto *initTRC = createContext(vd, initExpr->getSourceRange());
TypeRefinementContextBuilder(initTRC, Context).build(initExpr);
}

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

Expand Down
Loading