Skip to content

Commit 874168c

Browse files
committed
Sema: Teach the compiler to refine VarDecl initializer expressions using the deployment target when the init would not be exposed to module clients. Without this, the initializers of public properties in API modules could be misdiagnosed as potentially unavailable to clients of the module, even though the expression will only ever execute on the deployment target or higher.
While developing this fix I discovered a few other bugs that will need to be addressed separately: - The availability of the inferred type of a property is not diagnosed. Previously this mattered less since the initializer expression and the var declaration itself would both be diagnosed with the same availability. - The availability of the property wrapper type of a `VarDecl` appears to not be diagnosed at all. - Members of frozen structs with property wrappers result in broken swiftinterfaces because the synthesized storage is printed explicitly, confusing the diagnostics. Resolves rdar://92713589
1 parent 54a11df commit 874168c

File tree

2 files changed

+245
-179
lines changed

2 files changed

+245
-179
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 91 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "swift/AST/Initializer.h"
2525
#include "swift/AST/NameLookup.h"
2626
#include "swift/AST/Pattern.h"
27+
#include "swift/AST/PrettyStackTrace.h"
2728
#include "swift/AST/ProtocolConformance.h"
2829
#include "swift/AST/SourceFile.h"
2930
#include "swift/AST/TypeDeclFinder.h"
@@ -320,21 +321,6 @@ static bool hasActiveAvailableAttribute(Decl *D,
320321
return getActiveAvailableAttribute(D, AC);
321322
}
322323

323-
static bool shouldConstrainBodyToDeploymentTarget(Decl *D) {
324-
// The declaration contains code...
325-
if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
326-
// And it has a location so we can check it...
327-
if (!afd->isImplicit() && afd->getBodySourceRange().isValid()) {
328-
// And the code is within our resilience domain, so it should be
329-
// compiled with the minimum deployment target, not the minimum inlining
330-
// target.
331-
return afd->getResilienceExpansion() != ResilienceExpansion::Minimal;
332-
}
333-
}
334-
335-
return false;
336-
}
337-
338324
static bool computeContainedByDeploymentTarget(TypeRefinementContext *TRC,
339325
ASTContext &ctx) {
340326
return TRC->getAvailabilityInfo()
@@ -418,6 +404,10 @@ class TypeRefinementContextBuilder : private ASTWalker {
418404
ContextStack.push_back(Info);
419405
}
420406

407+
const char *stackTraceAction() const {
408+
return "building type refinement context for";
409+
}
410+
421411
public:
422412
TypeRefinementContextBuilder(TypeRefinementContext *TRC, ASTContext &Context)
423413
: Context(Context) {
@@ -426,20 +416,23 @@ class TypeRefinementContextBuilder : private ASTWalker {
426416
}
427417

428418
void build(Decl *D) {
419+
PrettyStackTraceDecl trace(stackTraceAction(), D);
429420
unsigned StackHeight = ContextStack.size();
430421
D->walk(*this);
431422
assert(ContextStack.size() == StackHeight);
432423
(void)StackHeight;
433424
}
434425

435426
void build(Stmt *S) {
427+
PrettyStackTraceStmt trace(Context, stackTraceAction(), S);
436428
unsigned StackHeight = ContextStack.size();
437429
S->walk(*this);
438430
assert(ContextStack.size() == StackHeight);
439431
(void)StackHeight;
440432
}
441433

442434
void build(Expr *E) {
435+
PrettyStackTraceExpr trace(Context, stackTraceAction(), E);
443436
unsigned StackHeight = ContextStack.size();
444437
E->walk(*this);
445438
assert(ContextStack.size() == StackHeight);
@@ -448,6 +441,8 @@ class TypeRefinementContextBuilder : private ASTWalker {
448441

449442
private:
450443
bool walkToDeclPre(Decl *D) override {
444+
PrettyStackTraceDecl trace(stackTraceAction(), D);
445+
451446
// Adds in a parent TRC for decls which are syntactically nested but are not
452447
// represented that way in the AST. (Particularly, AbstractStorageDecl
453448
// parents for AccessorDecl children.)
@@ -460,17 +455,12 @@ class TypeRefinementContextBuilder : private ASTWalker {
460455
pushContext(DeclTRC, D);
461456
}
462457

463-
// Adds in a TRC that covers only the body of the declaration.
464-
if (auto BodyTRC = getNewContextForBodyOfDecl(D)) {
465-
pushContext(BodyTRC, D);
466-
}
467-
458+
// Create TRCs that cover only the body of the declaration.
459+
buildContextsForBodyOfDecl(D);
468460
return true;
469461
}
470462

471463
bool walkToDeclPost(Decl *D) override {
472-
// As seen above, we could have up to three TRCs in the stack for a single
473-
// declaration.
474464
while (ContextStack.back().ScopeNode.getAsDecl() == D) {
475465
ContextStack.pop_back();
476466
}
@@ -515,7 +505,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
515505
return nullptr;
516506
}
517507

518-
/// Builds the type refinement hierarchy for the body of the function.
508+
/// Builds the type refinement hierarchy for the signature of the declaration.
519509
TypeRefinementContext *buildDeclarationRefinementContext(Decl *D) {
520510
// We require a valid range in order to be able to query for the TRC
521511
// corresponding to a given SourceLoc.
@@ -611,15 +601,19 @@ class TypeRefinementContextBuilder : private ASTWalker {
611601
if (auto *storageDecl = dyn_cast<AbstractStorageDecl>(D)) {
612602
// Use the declaration's availability for the context when checking
613603
// the bodies of its accessors.
604+
SourceRange Range = storageDecl->getSourceRange();
614605

615-
Decl *locDecl = D;
616606
// For a variable declaration (without accessors) we use the range of the
617607
// containing pattern binding declaration to make sure that we include
618-
// any type annotation in the type refinement context range.
619-
if (auto varDecl = dyn_cast<VarDecl>(storageDecl)) {
620-
auto *PBD = varDecl->getParentPatternBinding();
621-
if (PBD)
622-
locDecl = PBD;
608+
// any type annotation in the type refinement context range. We also
609+
// need to include any attached property wrappers.
610+
if (auto *varDecl = dyn_cast<VarDecl>(storageDecl)) {
611+
if (auto *PBD = varDecl->getParentPatternBinding())
612+
Range = PBD->getSourceRange();
613+
614+
for (auto *propertyWrapper : varDecl->getAttachedPropertyWrappers()) {
615+
Range.widen(propertyWrapper->getRange());
616+
}
623617
}
624618

625619
// HACK: For synthesized trivial accessors we may have not a valid
@@ -628,58 +622,92 @@ class TypeRefinementContextBuilder : private ASTWalker {
628622
// to update AbstractStorageDecl::addTrivialAccessors() to take brace
629623
// locations and have callers of that method provide appropriate source
630624
// locations.
631-
SourceLoc BracesEnd = storageDecl->getBracesRange().End;
632-
if (storageDecl->hasParsedAccessors() && BracesEnd.isValid()) {
633-
return SourceRange(locDecl->getStartLoc(),
634-
BracesEnd);
625+
SourceRange BracesRange = storageDecl->getBracesRange();
626+
if (storageDecl->hasParsedAccessors() && BracesRange.isValid()) {
627+
Range.widen(BracesRange);
635628
}
636-
637-
return locDecl->getSourceRange();
629+
630+
return Range;
638631
}
639632

640633
return D->getSourceRange();
641634
}
642635

643-
TypeRefinementContext *getNewContextForBodyOfDecl(Decl *D) {
644-
if (bodyIntroducesNewContext(D))
645-
return buildBodyRefinementContext(D);
636+
TypeRefinementContext *createAPIBoundaryContext(Decl *D, SourceRange range) {
637+
AvailabilityContext DeploymentTargetInfo =
638+
AvailabilityContext::forDeploymentTarget(Context);
639+
DeploymentTargetInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
646640

647-
return nullptr;
641+
return TypeRefinementContext::createForAPIBoundary(
642+
Context, D, getCurrentTRC(), DeploymentTargetInfo, range);
648643
}
649644

650-
bool bodyIntroducesNewContext(Decl *D) {
651-
// Are we already constrained by the deployment target? If not, adding a
652-
// new context wouldn't change availability.
645+
void buildContextsForBodyOfDecl(Decl *D) {
646+
// Are we already constrained by the deployment target? If not, adding
647+
// new contexts won't change availability.
653648
if (isCurrentTRCContainedByDeploymentTarget())
654-
return false;
655-
656-
// If we're in a function, check if it ought to use the deployment target.
657-
if (auto afd = dyn_cast<AbstractFunctionDecl>(D))
658-
return shouldConstrainBodyToDeploymentTarget(afd);
659-
660-
// The only other case we care about is top-level code.
661-
return isa<TopLevelCodeDecl>(D);
662-
}
649+
return;
663650

664-
TypeRefinementContext *buildBodyRefinementContext(Decl *D) {
665-
SourceRange range;
651+
// Top level code always uses the deployment target.
666652
if (auto tlcd = dyn_cast<TopLevelCodeDecl>(D)) {
667-
range = tlcd->getSourceRange();
668-
} else if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
669-
range = afd->getBodySourceRange();
670-
} else {
671-
llvm_unreachable("unknown decl");
653+
pushContext(createAPIBoundaryContext(tlcd, tlcd->getSourceRange()), D);
654+
return;
672655
}
673656

674-
AvailabilityContext DeploymentTargetInfo =
675-
AvailabilityContext::forDeploymentTarget(Context);
676-
DeploymentTargetInfo.intersectWith(getCurrentTRC()->getAvailabilityInfo());
657+
// Function bodies use the deployment target if they are within the module's
658+
// resilience domain.
659+
if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
660+
if (!afd->isImplicit() && afd->getBodySourceRange().isValid() &&
661+
afd->getResilienceExpansion() != ResilienceExpansion::Minimal) {
662+
pushContext(createAPIBoundaryContext(afd, afd->getBodySourceRange()),
663+
D);
664+
}
665+
return;
666+
}
677667

678-
return TypeRefinementContext::createForAPIBoundary(
679-
Context, D, getCurrentTRC(), DeploymentTargetInfo, range);
668+
// Var decls may have associated pattern binding decls or property wrappers
669+
// with init expressions. Those expressions need to be constrained to the
670+
// deployment target unless they are exposed to clients.
671+
if (auto vd = dyn_cast<VarDecl>(D)) {
672+
if (!vd->hasInitialValue() || vd->isInitExposedToClients())
673+
return;
674+
675+
if (auto *pbd = vd->getParentPatternBinding()) {
676+
int idx = pbd->getPatternEntryIndexForVarDecl(vd);
677+
auto *initExpr = pbd->getInit(idx);
678+
if (initExpr && !initExpr->isImplicit()) {
679+
assert(initExpr->getSourceRange().isValid());
680+
681+
// Create a TRC for the init written in the source. The ASTWalker
682+
// won't visit these expressions so instead of pushing these onto the
683+
// stack we build them directly.
684+
auto *initTRC =
685+
createAPIBoundaryContext(vd, initExpr->getSourceRange());
686+
TypeRefinementContextBuilder(initTRC, Context).build(initExpr);
687+
}
688+
689+
// Ideally any init expression would be returned by `getInit()` above.
690+
// However, for property wrappers it doesn't get populated until
691+
// typechecking completes (which is too late). Instead, we find the
692+
// the property wrapper attribute and use its source range to create a
693+
// TRC for the initializer expression.
694+
//
695+
// FIXME: Since we don't have an expression here, we can't build out its
696+
// TRC. Currently that doesn't matter because expression contents don't
697+
// ever contribute to the TRC, but it could become relevant if we ever
698+
// get control flow expressions.
699+
for (auto *wrapper : vd->getAttachedPropertyWrappers()) {
700+
createAPIBoundaryContext(vd, wrapper->getRange());
701+
}
702+
}
703+
704+
return;
705+
}
680706
}
681707

682708
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
709+
PrettyStackTraceStmt trace(Context, stackTraceAction(), S);
710+
683711
if (auto *IS = dyn_cast<IfStmt>(S)) {
684712
buildIfStmtRefinementContext(IS);
685713
return std::make_pair(false, S);

0 commit comments

Comments
 (0)