Skip to content

Commit 1dd86fc

Browse files
committed
[CodeComplete] More efficient skipping for completions in if/switch exprs
Skip type-checking multi-statement branches if the completion is in a single-expression branch, and skip type-checking the expression as a whole if the completion is in a multi-statement branch.
1 parent d8d8db9 commit 1dd86fc

File tree

6 files changed

+313
-18
lines changed

6 files changed

+313
-18
lines changed

include/swift/Sema/ConstraintLocator.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,10 @@ class ConstraintLocator : public llvm::FoldingSetNode {
310310
/// SingleValueStmtExpr.
311311
bool isForSingleValueStmtConjunction() const;
312312

313+
/// Whether this locator identifies a conjunction for the branches of a
314+
/// SingleValueStmtExpr, or a conjunction for one of the BraceStmts itself.
315+
bool isForSingleValueStmtConjunctionOrBrace() const;
316+
313317
/// Whether this locator identifies a conversion for a SingleValueStmtExpr
314318
/// branch, and if so, the kind of branch.
315319
llvm::Optional<SingleValueStmtBranchKind> isForSingleValueStmtBranch() const;

lib/Sema/CSStep.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,11 +883,15 @@ bool ConjunctionStep::attempt(const ConjunctionElement &element) {
883883
assert(!ModifiedOptions.has_value() &&
884884
"Previously modified options should have been restored in resume");
885885
if (CS.isForCodeCompletion() &&
886-
!element.mightContainCodeCompletionToken(CS)) {
886+
!element.mightContainCodeCompletionToken(CS) &&
887+
!getLocator()->isForSingleValueStmtConjunctionOrBrace()) {
887888
ModifiedOptions.emplace(CS.Options);
888889
// If we know that this conjunction element doesn't contain the code
889890
// completion token, type check it in normal mode without any special
890891
// behavior that is intended for the code completion token.
892+
// Avoid doing this for SingleValueStmtExprs, because we can more eagerly
893+
// prune branches in that case, which requires us to detect the code
894+
// completion option while solving the conjunction.
891895
CS.Options -= ConstraintSystemFlags::ForCodeCompletion;
892896
}
893897

lib/Sema/CSSyntacticElement.cpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,9 @@ class TypeVariableRefFinder : public ASTWalker {
242242
// MARK: Constraint generation
243243

244244
/// Check whether it makes sense to convert this element into a constraint.
245-
static bool isViableElement(ASTNode element) {
245+
static bool isViableElement(ASTNode element,
246+
bool isForSingleValueStmtCompletion,
247+
ConstraintSystem &cs) {
246248
if (auto *decl = element.dyn_cast<Decl *>()) {
247249
// - Ignore variable declarations, they are handled by pattern bindings;
248250
// - Ignore #if, the chosen children should appear in the
@@ -255,10 +257,21 @@ static bool isViableElement(ASTNode element) {
255257
}
256258

257259
if (auto *stmt = element.dyn_cast<Stmt *>()) {
258-
// Empty brace statements are now viable because they do not require
259-
// inference.
260260
if (auto *braceStmt = dyn_cast<BraceStmt>(stmt)) {
261-
return braceStmt->getNumElements() > 0;
261+
// Empty brace statements are not viable because they do not require
262+
// inference.
263+
if (braceStmt->empty())
264+
return false;
265+
266+
// Skip if we're doing completion for a SingleValueStmtExpr, and have a
267+
// brace that doesn't involve a single expression, and doesn't have a
268+
// code completion token, as it won't contribute to the type of the
269+
// SingleValueStmtExpr.
270+
if (isForSingleValueStmtCompletion &&
271+
!braceStmt->getSingleActiveExpression() &&
272+
!cs.containsIDEInspectionTarget(braceStmt)) {
273+
return false;
274+
}
262275
}
263276
}
264277

@@ -324,13 +337,19 @@ static void createConjunction(ConstraintSystem &cs,
324337

325338
VarRefCollector paramCollector(cs);
326339

340+
// Whether we're doing completion, and the conjunction is for a
341+
// SingleValueStmtExpr, or one of its braces.
342+
const auto isForSingleValueStmtCompletion =
343+
cs.isForCodeCompletion() &&
344+
locator->isForSingleValueStmtConjunctionOrBrace();
345+
327346
for (const auto &entry : elements) {
328347
ASTNode element = std::get<0>(entry);
329348
ContextualTypeInfo context = std::get<1>(entry);
330349
bool isDiscarded = std::get<2>(entry);
331350
ConstraintLocator *elementLoc = std::get<3>(entry);
332351

333-
if (!isViableElement(element))
352+
if (!isViableElement(element, isForSingleValueStmtCompletion, cs))
334353
continue;
335354

336355
// If this conjunction going to represent a body of a closure,

lib/Sema/ConstraintLocator.cpp

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -634,22 +634,52 @@ bool ConstraintLocator::isForMacroExpansion() const {
634634
return directlyAt<MacroExpansionExpr>();
635635
}
636636

637-
bool ConstraintLocator::isForSingleValueStmtConjunction() const {
638-
auto *SVE = getAsExpr<SingleValueStmtExpr>(getAnchor());
637+
static bool isForSingleValueStmtConjunction(ASTNode anchor,
638+
ArrayRef<LocatorPathElt> path) {
639+
auto *SVE = getAsExpr<SingleValueStmtExpr>(anchor);
639640
if (!SVE)
640641
return false;
641642

642-
// Ignore a trailing SyntacticElement path element for the statement.
643-
auto path = getPath();
644-
if (auto elt = getLastElementAs<LocatorPathElt::SyntacticElement>()) {
645-
if (elt->getElement() == ASTNode(SVE->getStmt()))
646-
path = path.drop_back();
643+
if (!path.empty()) {
644+
// Ignore a trailing SyntacticElement path element for the statement.
645+
if (auto elt = path.back().getAs<LocatorPathElt::SyntacticElement>()) {
646+
if (elt->getElement() == ASTNode(SVE->getStmt()))
647+
path = path.drop_back();
648+
}
647649
}
648650

649651
// Other than the trailing SyntaticElement, we must be at the anchor.
650652
return path.empty();
651653
}
652654

655+
bool ConstraintLocator::isForSingleValueStmtConjunction() const {
656+
return ::isForSingleValueStmtConjunction(getAnchor(), getPath());
657+
}
658+
659+
bool ConstraintLocator::isForSingleValueStmtConjunctionOrBrace() const {
660+
if (!isExpr<SingleValueStmtExpr>(getAnchor()))
661+
return false;
662+
663+
auto path = getPath();
664+
while (!path.empty()) {
665+
// Ignore a trailing TernaryBranch locator, which is used for if statements.
666+
if (path.back().is<LocatorPathElt::TernaryBranch>()) {
667+
path = path.drop_back();
668+
continue;
669+
}
670+
671+
// Ignore a SyntaticElement path element for a case statement of a switch.
672+
if (auto elt = path.back().getAs<LocatorPathElt::SyntacticElement>()) {
673+
if (elt->getElement().isStmt(StmtKind::Case)) {
674+
path = path.drop_back();
675+
continue;
676+
}
677+
}
678+
break;
679+
}
680+
return ::isForSingleValueStmtConjunction(getAnchor(), path);
681+
}
682+
653683
llvm::Optional<SingleValueStmtBranchKind>
654684
ConstraintLocator::isForSingleValueStmtBranch() const {
655685
// Ignore a trailing ContextualType path element.

lib/Sema/TypeCheckStmt.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2526,10 +2526,25 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
25262526
if (isa<TapExpr>(E))
25272527
return Action::SkipChildren(E);
25282528

2529-
// Don't walk into SingleValueStmtExprs, they should be type-checked as
2530-
// a whole.
2531-
if (isa<SingleValueStmtExpr>(E))
2532-
return Action::SkipChildren(E);
2529+
// If the location is within a single-expression branch of a
2530+
// SingleValueStmtExpr, walk it directly rather than as part of the brace.
2531+
// This ensures we type-check it a part of the whole expression, unless it
2532+
// has an inner closure or SVE, in which case we can still pick up a
2533+
// better node to type-check.
2534+
if (auto *SVE = dyn_cast<SingleValueStmtExpr>(E)) {
2535+
SmallVector<Expr *> scratch;
2536+
for (auto *branch : SVE->getSingleExprBranches(scratch)) {
2537+
auto branchCharRange = Lexer::getCharSourceRangeFromSourceRange(
2538+
SM, branch->getSourceRange());
2539+
if (branchCharRange.contains(Loc)) {
2540+
if (!branch->walk(*this))
2541+
return Action::Stop();
2542+
2543+
return Action::SkipChildren(E);
2544+
}
2545+
}
2546+
return Action::Continue(E);
2547+
}
25332548

25342549
if (auto closure = dyn_cast<ClosureExpr>(E)) {
25352550
// NOTE: When a client wants to type check a closure signature, it

0 commit comments

Comments
 (0)