Skip to content

Commit 63cc926

Browse files
authored
Merge pull request #67563 from hamishknight/complete
[CodeComplete] Properly handle `if`/`switch` expressions
2 parents d26bdd3 + c48b77f commit 63cc926

File tree

9 files changed

+542
-62
lines changed

9 files changed

+542
-62
lines changed

include/swift/AST/TypeCheckRequests.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3868,6 +3868,30 @@ class PreCheckReturnStmtRequest
38683868
bool isCached() const { return true; }
38693869
};
38703870

3871+
/// Performs some pre-checking of a function body, including inserting and
3872+
/// removing implict returns where needed. This request is currently
3873+
/// side-effectful, as it will mutate the body in-place.
3874+
///
3875+
/// Note this request is currently uncached as:
3876+
/// - The result will ultimately be cached by TypeCheckFunctionBodyRequest.
3877+
/// - When re-parsing function bodies in SourceKit, we can set a previously
3878+
/// type-checked function body back to being parsed, so caching the result of
3879+
/// this request would result in incorrectly attempting to restore the
3880+
/// previous body. We either need to eliminate the mutation of the AST, or
3881+
/// implement request cache invalidation through request dependencies.
3882+
class PreCheckFunctionBodyRequest
3883+
: public SimpleRequest<PreCheckFunctionBodyRequest,
3884+
BraceStmt *(AbstractFunctionDecl *),
3885+
RequestFlags::Uncached> {
3886+
public:
3887+
using SimpleRequest::SimpleRequest;
3888+
3889+
private:
3890+
friend SimpleRequest;
3891+
3892+
BraceStmt *evaluate(Evaluator &evaluator, AbstractFunctionDecl *AFD) const;
3893+
};
3894+
38713895
/// The result of the query for whether a statement can produce a single value.
38723896
class IsSingleValueStmtResult {
38733897
public:

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,9 @@ SWIFT_REQUEST(TypeChecker, SynthesizeAccessorRequest,
325325
SeparatelyCached, NoLocationInfo)
326326
SWIFT_REQUEST(TypeChecker, TangentStoredPropertyRequest,
327327
llvm::Expected<VarDecl *>(VarDecl *, CanType), Cached, NoLocationInfo)
328+
SWIFT_REQUEST(TypeChecker, PreCheckFunctionBodyRequest,
329+
BraceStmt *(AbstractFunctionDecl *),
330+
Uncached, NoLocationInfo)
328331
SWIFT_REQUEST(TypeChecker, TypeCheckFunctionBodyRequest,
329332
BraceStmt *(AbstractFunctionDecl *), SeparatelyCached,
330333
NoLocationInfo)

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/IDE/TypeCheckCompletionCallback.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ Type swift::ide::getTypeForCompletion(const constraints::Solution &S,
5252
return nullptr;
5353
}
5454

55-
auto &CS = S.getConstraintSystem();
56-
5755
Type Result;
5856

5957
if (isExpr<CodeCompletionExpr>(Node)) {
@@ -62,9 +60,9 @@ Type swift::ide::getTypeForCompletion(const constraints::Solution &S,
6260
Result = S.getResolvedType(Node);
6361
}
6462

65-
if (!Result || Result->is<UnresolvedType>()) {
66-
Result = CS.getContextualType(Node, /*forConstraint=*/false);
67-
}
63+
if (!Result || Result->is<UnresolvedType>())
64+
Result = S.getContextualType(Node);
65+
6866
if (Result && Result->is<UnresolvedType>()) {
6967
Result = Type();
7068
}

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: 30 additions & 7 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,
@@ -1122,9 +1141,13 @@ class SyntacticElementConstraintGenerator
11221141

11231142
for (auto element : braceStmt->getElements()) {
11241143
if (cs.isForCodeCompletion() &&
1125-
!cs.containsIDEInspectionTarget(element)) {
1144+
!cs.containsIDEInspectionTarget(element) &&
1145+
!(braceStmt->getSingleActiveExpression() &&
1146+
locator->isForSingleValueStmtConjunctionOrBrace())) {
11261147
// To improve performance, skip type checking elements that can't
1127-
// influence the code completion token.
1148+
// influence the code completion token. Note we don't do this for
1149+
// single expression SingleValueStmtExpr branches, as they're needed to
1150+
// infer the type.
11281151
if (element.is<Stmt *>() && !element.isStmt(StmtKind::Guard) && !element.isStmt(StmtKind::Return)) {
11291152
// Statements can't influence the expresion that contains the code
11301153
// completion token.

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: 87 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,6 +2396,13 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
23962396
return false;
23972397
}
23982398
}
2399+
if (auto *AFD = dyn_cast<AbstractFunctionDecl>(DC)) {
2400+
if (AFD->hasBody() && !AFD->isBodyTypeChecked()) {
2401+
// Pre-check the function body if needed.
2402+
(void)evaluateOrDefault(evaluator, PreCheckFunctionBodyRequest{AFD},
2403+
nullptr);
2404+
}
2405+
}
23992406
}
24002407

24012408
// Find innermost ASTNode at Loc from DC. Results the reference to the found
@@ -2519,6 +2526,26 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
25192526
if (isa<TapExpr>(E))
25202527
return Action::SkipChildren(E);
25212528

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+
}
2548+
25222549
if (auto closure = dyn_cast<ClosureExpr>(E)) {
25232550
// NOTE: When a client wants to type check a closure signature, it
25242551
// requests with closure's 'getLoc()' location.
@@ -2599,11 +2626,6 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
25992626
// thus the transform couldn't be applied. Perform code completion
26002627
// pretending there was no result builder to recover.
26012628
}
2602-
} else if (func->hasSingleExpressionBody() &&
2603-
func->getResultInterfaceType()->isVoid()) {
2604-
// The function returns void. We don't need an explicit return, no matter
2605-
// what the type of the expression is. Take the inserted return back out.
2606-
func->getBody()->setLastElement(func->getSingleExpressionBody());
26072629
}
26082630
}
26092631

@@ -2641,7 +2663,60 @@ bool TypeCheckASTNodeAtLocRequest::evaluate(
26412663
}
26422664

26432665
BraceStmt *
2644-
TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator,
2666+
PreCheckFunctionBodyRequest::evaluate(Evaluator &evaluator,
2667+
AbstractFunctionDecl *AFD) const {
2668+
auto &ctx = AFD->getASTContext();
2669+
auto *body = AFD->getBody();
2670+
assert(body && "Expected body");
2671+
assert(!AFD->isBodyTypeChecked() && "Body already type-checked?");
2672+
2673+
if (auto *func = dyn_cast<FuncDecl>(AFD)) {
2674+
// Don't apply this pre-checking to functions with result builders.
2675+
if (getResultBuilderType(func))
2676+
return body;
2677+
2678+
if (func->hasSingleExpressionBody() &&
2679+
func->getResultInterfaceType()->isVoid()) {
2680+
// The function returns void. We don't need an explicit return, no
2681+
// matter what the type of the expression is. Take the inserted return
2682+
// back out.
2683+
body->setLastElement(func->getSingleExpressionBody());
2684+
}
2685+
// If there is a single statement in the body that can be turned into a
2686+
// single expression return, do so now.
2687+
if (!func->getResultInterfaceType()->isVoid()) {
2688+
if (auto *S = body->getSingleActiveStatement()) {
2689+
if (S->mayProduceSingleValue(evaluator)) {
2690+
auto *SVE = SingleValueStmtExpr::createWithWrappedBranches(
2691+
ctx, S, /*DC*/ func, /*mustBeExpr*/ false);
2692+
auto *RS = new (ctx) ReturnStmt(SourceLoc(), SVE);
2693+
body->setLastElement(RS);
2694+
func->setHasSingleExpressionBody();
2695+
func->setSingleExpressionBody(SVE);
2696+
}
2697+
}
2698+
}
2699+
}
2700+
2701+
if (auto *ctor = dyn_cast<ConstructorDecl>(AFD)) {
2702+
if (body->empty() || !isKnownEndOfConstructor(body->getLastElement())) {
2703+
// For constructors, we make sure that the body ends with a "return"
2704+
// stmt, which we either implicitly synthesize, or the user can write.
2705+
// This simplifies SILGen.
2706+
SmallVector<ASTNode, 8> Elts(body->getElements().begin(),
2707+
body->getElements().end());
2708+
Elts.push_back(new (ctx) ReturnStmt(body->getRBraceLoc(),
2709+
/*value*/ nullptr,
2710+
/*implicit*/ true));
2711+
body = BraceStmt::create(ctx, body->getLBraceLoc(), Elts,
2712+
body->getRBraceLoc(), body->isImplicit());
2713+
}
2714+
}
2715+
return body;
2716+
}
2717+
2718+
BraceStmt *
2719+
TypeCheckFunctionBodyRequest::evaluate(Evaluator &eval,
26452720
AbstractFunctionDecl *AFD) const {
26462721
ASTContext &ctx = AFD->getASTContext();
26472722

@@ -2672,6 +2747,12 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator,
26722747
range.End);
26732748
};
26742749

2750+
// First do a pre-check of the body.
2751+
body = evaluateOrDefault(eval, PreCheckFunctionBodyRequest{AFD}, nullptr);
2752+
assert(body);
2753+
2754+
// Then apply a result builder if we have one, which if successful will
2755+
// produce a type-checked body.
26752756
bool alreadyTypeChecked = false;
26762757
if (auto *func = dyn_cast<FuncDecl>(AFD)) {
26772758
if (Type builderType = getResultBuilderType(func)) {
@@ -2686,42 +2767,6 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &evaluator,
26862767

26872768
body->walk(ContextualizeClosuresAndMacros(AFD));
26882769
}
2689-
} else {
2690-
if (func->hasSingleExpressionBody() &&
2691-
func->getResultInterfaceType()->isVoid()) {
2692-
// The function returns void. We don't need an explicit return, no
2693-
// matter what the type of the expression is. Take the inserted return
2694-
// back out.
2695-
body->setLastElement(func->getSingleExpressionBody());
2696-
}
2697-
// If there is a single statement in the body that can be turned into a
2698-
// single expression return, do so now.
2699-
if (!func->getResultInterfaceType()->isVoid()) {
2700-
if (auto *S = body->getSingleActiveStatement()) {
2701-
if (S->mayProduceSingleValue(evaluator)) {
2702-
auto *SVE = SingleValueStmtExpr::createWithWrappedBranches(
2703-
ctx, S, /*DC*/ func, /*mustBeExpr*/ false);
2704-
auto *RS = new (ctx) ReturnStmt(SourceLoc(), SVE);
2705-
body->setLastElement(RS);
2706-
func->setHasSingleExpressionBody();
2707-
func->setSingleExpressionBody(SVE);
2708-
}
2709-
}
2710-
}
2711-
}
2712-
} else if (auto *ctor = dyn_cast<ConstructorDecl>(AFD)) {
2713-
if (body->empty() ||
2714-
!isKnownEndOfConstructor(body->getLastElement())) {
2715-
// For constructors, we make sure that the body ends with a "return" stmt,
2716-
// which we either implicitly synthesize, or the user can write. This
2717-
// simplifies SILGen.
2718-
SmallVector<ASTNode, 8> Elts(body->getElements().begin(),
2719-
body->getElements().end());
2720-
Elts.push_back(new (ctx) ReturnStmt(body->getRBraceLoc(),
2721-
/*value*/nullptr,
2722-
/*implicit*/true));
2723-
body = BraceStmt::create(ctx, body->getLBraceLoc(), Elts,
2724-
body->getRBraceLoc(), body->isImplicit());
27252770
}
27262771
}
27272772

0 commit comments

Comments
 (0)