Skip to content

Commit 7b0f74b

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 2f30304 commit 7b0f74b

File tree

2 files changed

+290
-179
lines changed

2 files changed

+290
-179
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 95 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,96 @@ 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+
auto *topLevelTRC =
654+
createAPIBoundaryContext(tlcd, tlcd->getSourceRange());
655+
pushContext(topLevelTRC, D);
656+
return;
672657
}
673658

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

678-
return TypeRefinementContext::createForAPIBoundary(
679-
Context, D, getCurrentTRC(), DeploymentTargetInfo, range);
671+
// Var decls may have associated pattern binding decls or property wrappers
672+
// with init expressions. Those expressions need to be constrained to the
673+
// deployment target unless they are exposed to clients.
674+
if (auto vd = dyn_cast<VarDecl>(D)) {
675+
if (!vd->hasInitialValue() || vd->isInitExposedToClients())
676+
return;
677+
678+
if (auto *pbd = vd->getParentPatternBinding()) {
679+
int idx = pbd->getPatternEntryIndexForVarDecl(vd);
680+
auto *initExpr = pbd->getInit(idx);
681+
if (initExpr && !initExpr->isImplicit()) {
682+
assert(initExpr->getSourceRange().isValid());
683+
684+
// Create a TRC for the init written in the source. The ASTWalker
685+
// won't visit these expressions so instead of pushing these onto the
686+
// stack we build them directly.
687+
auto *initTRC =
688+
createAPIBoundaryContext(vd, initExpr->getSourceRange());
689+
TypeRefinementContextBuilder(initTRC, Context).build(initExpr);
690+
}
691+
692+
// Ideally any init expression would be returned by `getInit()` above.
693+
// However, for property wrappers it doesn't get populated until
694+
// typechecking completes (which is too late). Instead, we find the
695+
// the property wrapper attribute and use its source range to create a
696+
// TRC for the initializer expression.
697+
//
698+
// FIXME: Since we don't have an expression here, we can't build out its
699+
// TRC. If the Expr that will eventually be created contains a closure
700+
// expression, then it might have AST nodes that need to be refined. For
701+
// example, property wrapper initializers that takes block arguments
702+
// are not handled correctly because of this (rdar://77841331).
703+
for (auto *wrapper : vd->getAttachedPropertyWrappers()) {
704+
createAPIBoundaryContext(vd, wrapper->getRange());
705+
}
706+
}
707+
708+
return;
709+
}
680710
}
681711

682712
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
713+
PrettyStackTraceStmt trace(Context, stackTraceAction(), S);
714+
683715
if (auto *IS = dyn_cast<IfStmt>(S)) {
684716
buildIfStmtRefinementContext(IS);
685717
return std::make_pair(false, S);

0 commit comments

Comments
 (0)