Skip to content

Commit 2aed784

Browse files
committed
Sema: Expand TypeRefinementContexts lazily for unparsed function bodies.
When building the TypeRefinementContext subtree for a function declaration, postpone creation of the subtree if the function body is unparsed. This allows the compiler to completely avoid parsing function bodies that have been skipped (e.g. with -experimental-skip-non-inlinable-function-bodies) while still ensuring that the TRCs for functions are built lazily later if needed. When lazily generating SIL for a function with -experimental-lazy-typecheck, the TRCs must be built out while typechecking the function in order to emit correct diagnostics and SIL for `if #available` queries. Resolves rdar://117448323
1 parent c8e63ff commit 2aed784

File tree

5 files changed

+85
-52
lines changed

5 files changed

+85
-52
lines changed

lib/Sema/TypeCheckAvailability.cpp

Lines changed: 51 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -512,35 +512,55 @@ class TypeRefinementContextBuilder : private ASTWalker {
512512
return Action::Continue();
513513
}
514514

515-
/// Constructs a placeholder TRC node that should be expanded later if the AST
516-
/// associated with the given declaration is not ready to be traversed yet.
517-
/// Returns true if a node was created.
518-
bool buildLazyContextForDecl(Decl *D) {
519-
if (!isa<PatternBindingDecl>(D))
520-
return false;
521-
522-
// Check whether the current TRC is already a lazy placeholder. If it is,
523-
// we should try to expand it rather than creating a new placeholder.
524-
auto currentTRC = getCurrentTRC();
525-
if (currentTRC->getNeedsExpansion() && currentTRC->getDeclOrNull() == D)
526-
return false;
515+
bool shouldBuildLazyContextForDecl(Decl *D) {
516+
// Skip functions that have unparsed bodies on an initial descent to avoid
517+
// eagerly parsing bodies unnecessarily.
518+
if (auto *afd = dyn_cast<AbstractFunctionDecl>(D)) {
519+
if (afd->hasBody() && !afd->isBodySkipped() &&
520+
!afd->getBody(/*canSynthesize=*/false))
521+
return true;
522+
}
527523

528524
// Pattern binding declarations may have attached property wrappers that
529525
// get expanded from macros attached to the parent declaration. We must
530-
// not eagerly expand the attached property wrappers to avoid a request
531-
// cycle.
526+
// not eagerly expand the attached property wrappers to avoid request
527+
// cycles.
532528
if (auto *pattern = dyn_cast<PatternBindingDecl>(D)) {
533529
if (auto firstVar = pattern->getAnchoringVarDecl(0)) {
534-
// If there's no initial value, or the init is exposed to clients, then
535-
// we don't need to create any implicit TRCs for the init bodies.
536-
if (!firstVar->hasInitialValue() || firstVar->isInitExposedToClients())
537-
return false;
538-
539530
// FIXME: We could narrow this further by detecting whether there are
540531
// any macro expansions required to visit the CustomAttrs of the var.
532+
if (firstVar->hasInitialValue() && !firstVar->isInitExposedToClients())
533+
return true;
541534
}
542535
}
543536

537+
return false;
538+
}
539+
540+
/// For declarations that were previously skipped prepare the AST before
541+
/// building out TRCs.
542+
void prepareDeclForLazyExpansion(Decl *D) {
543+
if (auto AFD = dyn_cast<AbstractFunctionDecl>(D)) {
544+
(void)AFD->getBody(/*canSynthesize*/ true);
545+
}
546+
}
547+
548+
/// Constructs a placeholder TRC node that should be expanded later. This is
549+
/// useful for postponing unnecessary work (and request triggers) when
550+
/// initally building out the TRC subtree under a declaration. Lazy nodes
551+
/// constructed here will be expanded by
552+
/// ExpandChildTypeRefinementContextsRequest. Returns true if a node was
553+
/// created.
554+
bool buildLazyContextForDecl(Decl *D) {
555+
// Check whether the current TRC is already a lazy placeholder. If it is,
556+
// we should try to expand it rather than creating a new placeholder.
557+
auto currentTRC = getCurrentTRC();
558+
if (currentTRC->getNeedsExpansion() && currentTRC->getDeclOrNull() == D)
559+
return false;
560+
561+
if (!shouldBuildLazyContextForDecl(D))
562+
return false;
563+
544564
// If we've made it this far then we've identified a declaration that
545565
// requires lazy expansion later.
546566
auto lazyTRC = TypeRefinementContext::createForDeclImplicit(
@@ -1223,6 +1243,7 @@ class TypeRefinementContextBuilder : private ASTWalker {
12231243
void TypeChecker::buildTypeRefinementContextHierarchy(SourceFile &SF) {
12241244
TypeRefinementContext *RootTRC = SF.getTypeRefinementContext();
12251245
ASTContext &Context = SF.getASTContext();
1246+
assert(!Context.LangOpts.DisableAvailabilityChecking);
12261247

12271248
if (!RootTRC) {
12281249
// The root type refinement context reflects the fact that all parts of
@@ -1246,28 +1267,11 @@ void TypeChecker::buildTypeRefinementContextHierarchy(SourceFile &SF) {
12461267
}
12471268
}
12481269

1249-
void TypeChecker::buildTypeRefinementContextHierarchyDelayed(SourceFile &SF, AbstractFunctionDecl *AFD) {
1250-
// If there's no TRC for the file, we likely don't want this one either.
1251-
// RootTRC is not set when availability checking is disabled.
1252-
TypeRefinementContext *RootTRC = SF.getTypeRefinementContext();
1253-
if(!RootTRC)
1254-
return;
1255-
1256-
if (AFD->getBodyKind() != AbstractFunctionDecl::BodyKind::Unparsed)
1257-
return;
1258-
1259-
// Parse the function body.
1260-
AFD->getBody(/*canSynthesize=*/true);
1261-
1262-
// Build the refinement context for the function body.
1263-
ASTContext &Context = SF.getASTContext();
1264-
auto LocalTRC = RootTRC->findMostRefinedSubContext(AFD->getLoc(), Context);
1265-
TypeRefinementContextBuilder Builder(LocalTRC, Context);
1266-
Builder.build(AFD);
1267-
}
1268-
12691270
TypeRefinementContext *
12701271
TypeChecker::getOrBuildTypeRefinementContext(SourceFile *SF) {
1272+
if (SF->getASTContext().LangOpts.DisableAvailabilityChecking)
1273+
return nullptr;
1274+
12711275
TypeRefinementContext *TRC = SF->getTypeRefinementContext();
12721276
if (!TRC) {
12731277
buildTypeRefinementContextHierarchy(*SF);
@@ -1284,6 +1288,7 @@ ExpandChildTypeRefinementContextsRequest::evaluate(
12841288
if (auto decl = parentTRC->getDeclOrNull()) {
12851289
ASTContext &ctx = decl->getASTContext();
12861290
TypeRefinementContextBuilder builder(parentTRC, ctx);
1291+
builder.prepareDeclForLazyExpansion(decl);
12871292
builder.build(decl);
12881293
}
12891294
return parentTRC->Children;
@@ -1338,11 +1343,13 @@ TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
13381343

13391344
if (SF && loc.isValid()) {
13401345
TypeRefinementContext *rootTRC = getOrBuildTypeRefinementContext(SF);
1341-
TypeRefinementContext *TRC =
1342-
rootTRC->findMostRefinedSubContext(loc, Context);
1343-
OverApproximateContext.constrainWith(TRC->getAvailabilityInfo());
1344-
if (MostRefined) {
1345-
*MostRefined = TRC;
1346+
if (rootTRC) {
1347+
TypeRefinementContext *TRC =
1348+
rootTRC->findMostRefinedSubContext(loc, Context);
1349+
OverApproximateContext.constrainWith(TRC->getAvailabilityInfo());
1350+
if (MostRefined) {
1351+
*MostRefined = TRC;
1352+
}
13461353
}
13471354
}
13481355

lib/Sema/TypeCheckStmt.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,10 +2729,6 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &eval,
27292729
if (tyOpts.DebugTimeFunctionBodies || tyOpts.WarnLongFunctionBodies)
27302730
timer.emplace(AFD);
27312731

2732-
auto SF = AFD->getParentSourceFile();
2733-
if (SF)
2734-
TypeChecker::buildTypeRefinementContextHierarchyDelayed(*SF, AFD);
2735-
27362732
BraceStmt *body = AFD->getBody();
27372733
assert(body && "Expected body to type-check");
27382734

lib/Sema/TypeChecker.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,10 +1056,6 @@ AvailabilityContext overApproximateAvailabilityAtLocation(
10561056
/// Walk the AST to build the hierarchy of TypeRefinementContexts
10571057
void buildTypeRefinementContextHierarchy(SourceFile &SF);
10581058

1059-
/// Walk the AST to complete the hierarchy of TypeRefinementContexts for
1060-
/// the delayed function body of \p AFD.
1061-
void buildTypeRefinementContextHierarchyDelayed(SourceFile &SF, AbstractFunctionDecl *AFD);
1062-
10631059
/// Build the hierarchy of TypeRefinementContexts for the entire
10641060
/// source file, if it has not already been built. Returns the root
10651061
/// TypeRefinementContext for the source file.

test/Interpreter/lazy/deferred_syntax_errors.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
// -enable-experimental-feature requires an asserts build
66
// REQUIRES: asserts
7+
// REQUIRES: rdar118189728
78

89
// Tests that parsing of function bodies is deferred
910
// to runtime when the interpreter is invoked using
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// RUN: %target-swift-frontend -emit-silgen %s -parse-as-library -module-name Test -experimental-lazy-typecheck -experimental-skip-non-inlinable-function-bodies | %FileCheck %s
2+
3+
// Note: This test has been carefully constructed to create the preconditions of
4+
// a lazy typechecking bug. First, foo() is parsed and typechecked lazily in
5+
// order to generate SIL for it. Type checking the body of foo() causes the
6+
// TypeRefinementContext tree to be built for the file, but bar() has not been
7+
// parsed yet so it gets skipped during construction of the tree. Therefore
8+
// when generating the SIL for bar() its TRC must be created on-demand in order
9+
// to emit the correct SIL for the if #available condition.
10+
11+
// REQUIRES: OS=macosx
12+
13+
// CHECK: sil{{.*}} @$s4Test3fooyS2iF : $@convention(thin) (Int) -> Int {
14+
// CHECK: } // end sil function '$s4Test3fooyS2iF'
15+
@inlinable
16+
public func foo(_ x: Int) -> Int {
17+
_ = x
18+
}
19+
20+
// CHECK: sil{{.*}} @$s4Test3barSiyF : $@convention(thin) () -> Int {
21+
// CHECK: {{.*}} = integer_literal $Builtin.Word, 11
22+
// CHECK: {{.*}} = integer_literal $Builtin.Word, 0
23+
// CHECK: {{.*}} = integer_literal $Builtin.Word, 0
24+
// CHECK: {{.*}} = function_ref @$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF
25+
// CHECK: } // end sil function '$s4Test3barSiyF'
26+
@inlinable
27+
public func bar() -> Int {
28+
if #available(macOS 11.0, *) {
29+
return 1
30+
} else {
31+
return 2
32+
}
33+
}

0 commit comments

Comments
 (0)