Skip to content

[Sema] Fix availability checking in inlinable code #33855

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
merged 12 commits into from
Oct 13, 2020
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
6 changes: 6 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2455,10 +2455,16 @@ NOTE(suggest_removing_override, none,
ERROR(override_less_available,none,
"overriding %0 must be as available as declaration it overrides",
(DeclBaseName))
WARNING(override_less_available_warn,none,
"overriding %0 must be as available as declaration it overrides",
(DeclBaseName))

ERROR(override_accessor_less_available,none,
"overriding %0 for %1 must be as available as declaration it overrides",
(DescriptiveDeclKind, DeclBaseName))
WARNING(override_accessor_less_available_warn,none,
"overriding %0 for %1 must be as available as declaration it overrides",
(DescriptiveDeclKind, DeclBaseName))

ERROR(override_let_property,none,
"cannot override immutable 'let' property %0 with the getter of a 'var'",
Expand Down
24 changes: 20 additions & 4 deletions include/swift/AST/TypeRefinementContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,23 @@ class TypeRefinementContext {

SourceRange SrcRange;

/// Runtime availability information for the code in this context.
AvailabilityContext AvailabilityInfo;

/// Runtime availability information as explicitly declared by attributes
/// for the inlinable code in this context. Compared to AvailabilityInfo,
/// this is not bounded to the minimum deployment OS version.
AvailabilityContext AvailabilityInfoExplicit;

std::vector<TypeRefinementContext *> Children;

TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
TypeRefinementContext *Parent, SourceRange SrcRange,
const AvailabilityContext &Info);
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit);

public:

/// Create the root refinement context for the given SourceFile.
static TypeRefinementContext *createRoot(SourceFile *SF,
const AvailabilityContext &Info);
Expand All @@ -172,8 +179,9 @@ class TypeRefinementContext {
static TypeRefinementContext *createForDecl(ASTContext &Ctx, Decl *D,
TypeRefinementContext *Parent,
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit,
SourceRange SrcRange);

/// Create a refinement context for the Then branch of the given IfStmt.
static TypeRefinementContext *
createForIfStmtThen(ASTContext &Ctx, IfStmt *S, TypeRefinementContext *Parent,
Expand Down Expand Up @@ -240,11 +248,19 @@ class TypeRefinementContext {
SourceRange getSourceRange() const { return SrcRange; }

/// Returns the information on what can be assumed present at run time when
/// running code contained in this context.
/// running code contained in this context, taking into account the minimum
/// deployment target.
const AvailabilityContext &getAvailabilityInfo() const {
return AvailabilityInfo;
}

/// Returns the information on what can be assumed present at run time when
/// running code contained in this context if it were to be inlined,
/// without considering the minimum deployment target.
const AvailabilityContext &getAvailabilityInfoExplicit() const {
return AvailabilityInfoExplicit;
}

/// Adds a child refinement context.
void addChild(TypeRefinementContext *Child) {
assert(Child->getSourceRange().isValid());
Expand Down
16 changes: 13 additions & 3 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,13 @@ static bool isBetterThan(const AvailableAttr *newAttr,
return true;

// If they belong to the same platform, the one that introduces later wins.
if (prevAttr->Platform == newAttr->Platform)
if (prevAttr->Platform == newAttr->Platform) {
if (newAttr->isUnconditionallyUnavailable())
return true;
if (prevAttr->isUnconditionallyUnavailable())
return false;
return prevAttr->Introduced.getValue() < newAttr->Introduced.getValue();
}

// If the new attribute's platform inherits from the old one, it wins.
return inheritsAvailabilityFromPlatform(newAttr->Platform,
Expand All @@ -158,10 +163,12 @@ AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {

for (auto Attr : D->getAttrs()) {
auto *AvailAttr = dyn_cast<AvailableAttr>(Attr);
if (AvailAttr == nullptr || !AvailAttr->Introduced.hasValue() ||
if (AvailAttr == nullptr ||
!AvailAttr->isActivePlatform(Ctx) ||
AvailAttr->isLanguageVersionSpecific() ||
AvailAttr->isPackageDescriptionVersionSpecific()) {
AvailAttr->isPackageDescriptionVersionSpecific() ||
(!AvailAttr->Introduced.hasValue() &&
!AvailAttr->isUnconditionallyUnavailable())) {
continue;
}

Expand All @@ -172,6 +179,9 @@ AvailabilityInference::annotatedAvailableRange(const Decl *D, ASTContext &Ctx) {
if (!bestAvailAttr)
return None;

if (bestAvailAttr->isUnconditionallyUnavailable())
return AvailabilityContext(VersionRange::empty());

return AvailabilityContext{
VersionRange::allGTE(bestAvailAttr->Introduced.getValue())};
}
Expand Down
27 changes: 16 additions & 11 deletions lib/AST/TypeRefinementContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ using namespace swift;
TypeRefinementContext::TypeRefinementContext(ASTContext &Ctx, IntroNode Node,
TypeRefinementContext *Parent,
SourceRange SrcRange,
const AvailabilityContext &Info)
: Node(Node), SrcRange(SrcRange), AvailabilityInfo(Info) {
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit)
: Node(Node), SrcRange(SrcRange), AvailabilityInfo(Info),
AvailabilityInfoExplicit(InfoExplicit) {
if (Parent) {
assert(SrcRange.isValid());
Parent->addChild(this);
assert(Info.isContainedIn(Parent->getAvailabilityInfo()));
assert(InfoExplicit.isContainedIn(Parent->getAvailabilityInfoExplicit()));
}
Ctx.addDestructorCleanup(Children);
}
Expand All @@ -46,18 +48,20 @@ TypeRefinementContext::createRoot(SourceFile *SF,
ASTContext &Ctx = SF->getASTContext();
return new (Ctx)
TypeRefinementContext(Ctx, SF,
/*Parent=*/nullptr, SourceRange(), Info);
/*Parent=*/nullptr, SourceRange(), Info,
AvailabilityContext::alwaysAvailable());
}

TypeRefinementContext *
TypeRefinementContext::createForDecl(ASTContext &Ctx, Decl *D,
TypeRefinementContext *Parent,
const AvailabilityContext &Info,
const AvailabilityContext &InfoExplicit,
SourceRange SrcRange) {
assert(D);
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info);
TypeRefinementContext(Ctx, D, Parent, SrcRange, Info, InfoExplicit);
}

TypeRefinementContext *
Expand All @@ -68,7 +72,7 @@ TypeRefinementContext::createForIfStmtThen(ASTContext &Ctx, IfStmt *S,
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, IntroNode(S, /*IsThen=*/true), Parent,
S->getThenStmt()->getSourceRange(), Info);
S->getThenStmt()->getSourceRange(), Info, Info);
}

TypeRefinementContext *
Expand All @@ -79,7 +83,7 @@ TypeRefinementContext::createForIfStmtElse(ASTContext &Ctx, IfStmt *S,
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, IntroNode(S, /*IsThen=*/false), Parent,
S->getElseStmt()->getSourceRange(), Info);
S->getElseStmt()->getSourceRange(), Info, Info);
}

TypeRefinementContext *
Expand All @@ -92,7 +96,7 @@ TypeRefinementContext::createForConditionFollowingQuery(ASTContext &Ctx,
assert(Parent);
SourceRange Range(PAI->getEndLoc(), LastElement.getEndLoc());
return new (Ctx) TypeRefinementContext(Ctx, PAI, Parent, Range,
Info);
Info, Info);
}

TypeRefinementContext *
Expand All @@ -107,7 +111,7 @@ TypeRefinementContext::createForGuardStmtFallthrough(ASTContext &Ctx,
SourceRange Range(RS->getEndLoc(), ContainingBraceStmt->getEndLoc());
return new (Ctx) TypeRefinementContext(Ctx,
IntroNode(RS, /*IsFallthrough=*/true),
Parent, Range, Info);
Parent, Range, Info, Info);
}

TypeRefinementContext *
Expand All @@ -118,7 +122,7 @@ TypeRefinementContext::createForGuardStmtElse(ASTContext &Ctx, GuardStmt *RS,
assert(Parent);
return new (Ctx)
TypeRefinementContext(Ctx, IntroNode(RS, /*IsFallthrough=*/false), Parent,
RS->getBody()->getSourceRange(), Info);
RS->getBody()->getSourceRange(), Info, Info);
}

TypeRefinementContext *
Expand All @@ -128,7 +132,7 @@ TypeRefinementContext::createForWhileStmtBody(ASTContext &Ctx, WhileStmt *S,
assert(S);
assert(Parent);
return new (Ctx) TypeRefinementContext(
Ctx, S, Parent, S->getBody()->getSourceRange(), Info);
Ctx, S, Parent, S->getBody()->getSourceRange(), Info, Info);
}

// Only allow allocation of TypeRefinementContext using the allocator in
Expand Down Expand Up @@ -296,6 +300,7 @@ void TypeRefinementContext::print(raw_ostream &OS, SourceManager &SrcMgr,
OS << "(" << getReasonName(getReason());

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

if (getReason() == Reason::Decl) {
Decl *D = Node.getAsDecl();
Expand Down
13 changes: 11 additions & 2 deletions lib/ClangImporter/ImportDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1817,18 +1817,27 @@ static void applyAvailableAttribute(Decl *decl, AvailabilityContext &info,
if (info.isAlwaysAvailable())
return;

PlatformAgnosticAvailabilityKind platformAgnosticAvailability;
llvm::VersionTuple introducedVersion;
if (info.isKnownUnreachable()) {
platformAgnosticAvailability = PlatformAgnosticAvailabilityKind::Unavailable;
} else {
platformAgnosticAvailability = PlatformAgnosticAvailabilityKind::None;
introducedVersion = info.getOSVersion().getLowerEndpoint();
}

llvm::VersionTuple noVersion;
auto AvAttr = new (C) AvailableAttr(SourceLoc(), SourceRange(),
targetPlatform(C.LangOpts),
/*Message=*/StringRef(),
/*Rename=*/StringRef(),
info.getOSVersion().getLowerEndpoint(),
introducedVersion,
/*IntroducedRange*/SourceRange(),
/*Deprecated=*/noVersion,
/*DeprecatedRange*/SourceRange(),
/*Obsoleted=*/noVersion,
/*ObsoletedRange*/SourceRange(),
PlatformAgnosticAvailabilityKind::None,
platformAgnosticAvailability,
/*Implicit=*/false);

decl->getAttrs().add(AvAttr);
Expand Down
3 changes: 2 additions & 1 deletion lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2638,7 +2638,8 @@ void SILFunction::print(SILPrintContext &PrintCtx) const {
if (isAlwaysWeakImported())
OS << "[weak_imported] ";
auto availability = getAvailabilityForLinkage();
if (!availability.isAlwaysAvailable()) {
if (!availability.isAlwaysAvailable() &&
!availability.isKnownUnreachable()) {
auto version = availability.getOSVersion().getLowerEndpoint();
OS << "[available " << version.getAsString() << "] ";
}
Expand Down
13 changes: 10 additions & 3 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1530,9 +1530,16 @@ void AttributeChecker::visitAvailableAttr(AvailableAttr *attr) {
VersionRange::allGTE(attr->Introduced.getValue())};

if (!AttrRange.isContainedIn(EnclosingAnnotatedRange.getValue())) {
diagnose(attr->getLocation(), diag::availability_decl_more_than_enclosing);
diagnose(EnclosingDecl->getLoc(),
diag::availability_decl_more_than_enclosing_enclosing_here);
// Don't show this error in swiftinterfaces if it is about a context that
// is unavailable, this was in the stdlib at Swift 5.3.
SourceFile *Parent = D->getDeclContext()->getParentSourceFile();
if (!Parent || Parent->Kind != SourceFileKind::Interface ||
!EnclosingAnnotatedRange.getValue().isKnownUnreachable()) {
diagnose(attr->getLocation(),
diag::availability_decl_more_than_enclosing);
diagnose(EnclosingDecl->getLoc(),
diag::availability_decl_more_than_enclosing_enclosing_here);
}
}
}

Expand Down
53 changes: 42 additions & 11 deletions lib/Sema/TypeCheckAvailability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,30 @@ class TypeRefinementContextBuilder : private ASTWalker {
// 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 DeclInfo =
AvailabilityContext ExplicitDeclInfo =
swift::AvailabilityInference::availableRange(D, Context);
DeclInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
ExplicitDeclInfo.intersectWith(
getCurrentTRC()->getAvailabilityInfoExplicit());
AvailabilityContext DeclInfo = ExplicitDeclInfo;

// When the body is inlinable consider only the explicitly declared range
// for checking availability. Otherwise, use the parent range which may
// begin at the minimum deployment target.
//
// Also use the parent range when reading swiftinterfaces for
// retrocompatibility.
bool isInlinable = D->getAttrs().hasAttribute<InlinableAttr>() ||
D->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>();
SourceFile *SF = D->getDeclContext()->getParentSourceFile();
if (!isInlinable || (SF && SF->Kind == SourceFileKind::Interface)) {
DeclInfo.intersectWith(
getCurrentTRC()->getAvailabilityInfo());
}

TypeRefinementContext *NewTRC =
TypeRefinementContext::createForDecl(Context, D, getCurrentTRC(),
DeclInfo,
ExplicitDeclInfo,
refinementSourceRangeForDecl(D));

// Record the TRC for this storage declaration so that
Expand All @@ -190,19 +207,27 @@ class TypeRefinementContextBuilder : private ASTWalker {

return NewTRC;
}

/// Returns true if the declaration should introduce a new refinement context.
bool declarationIntroducesNewContext(Decl *D) {
if (!isa<ValueDecl>(D) && !isa<ExtensionDecl>(D)) {
return false;
}


// Explicit inlinability may to the decl being used on an earlier OS
// version when inlined on the client side. This check assumes that
// implicit decls are handled elsewhere.
bool isExplicitlyInlinable = !D->isImplicit() &&
(D->getAttrs().hasAttribute<InlinableAttr>() ||
D->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>());

// No need to introduce a context if the declaration does not have an
// availability attribute.
if (!hasActiveAvailableAttribute(D, Context)) {
// availability or non-implicit inlinable attribute.
if (!hasActiveAvailableAttribute(D, Context) &&
!isExplicitlyInlinable) {
return false;
}

// Only introduce for an AbstractStorageDecl if it is not local.
// We introduce for the non-local case because these may
// have getters and setters (and these may be synthesized, so they might
Expand All @@ -213,7 +238,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
return false;
}
}

return true;
}

Expand Down Expand Up @@ -674,9 +699,7 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
// refined. For now, this is fine -- but if we ever synthesize #available(),
// this will be a real problem.

// We can assume we are running on at least the minimum deployment target.
auto OverApproximateContext =
AvailabilityContext::forDeploymentTarget(Context);
auto OverApproximateContext = AvailabilityContext::alwaysAvailable();
auto isInvalidLoc = [SF](SourceLoc loc) {
return SF ? loc.isInvalid() : true;
};
Expand Down Expand Up @@ -707,6 +730,14 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
}
}

// If we still don't have an introduction version, use the current deployment
// target. This covers cases where an inlinable function and its parent
// contexts don't have explicit availability attributes.
if (!OverApproximateContext.getOSVersion().hasLowerEndpoint()) {
auto currentOS = AvailabilityContext::forDeploymentTarget(Context);
OverApproximateContext.constrainWith(currentOS);
}

return OverApproximateContext;
}

Expand Down
Loading