Skip to content

Commit 917fdc9

Browse files
committed
[Sema] Check completion key path component to determine if completion node was type checked in multi expression closure
To check whether the code completion node was type checked in a multi statement closure, we were assuming that we had a code completion *expression* and not considering that we could also complete in a key path component. When applicable, check if the completion key path component has a type to determine if the completion node was type checked as part of a multi-statement closure. Resolves rdar://80271598 [SR-14891]
1 parent 78a7fe8 commit 917fdc9

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1293,6 +1293,10 @@ class Solution {
12931293

12941294
bool hasType(ASTNode node) const;
12951295

1296+
/// Returns \c true if the \p ComponentIndex-th component in \p KP has a type
1297+
/// associated with it.
1298+
bool hasType(const KeyPathExpr *KP, unsigned ComponentIndex) const;
1299+
12961300
/// Retrieve the type of the given node, as recorded in this solution.
12971301
Type getType(ASTNode node) const;
12981302

lib/Sema/CSApply.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8899,6 +8899,11 @@ bool Solution::hasType(ASTNode node) const {
88998899
return cs.hasType(node);
89008900
}
89018901

8902+
bool Solution::hasType(const KeyPathExpr *KP, unsigned ComponentIndex) const {
8903+
auto &cs = getConstraintSystem();
8904+
return cs.hasType(KP, ComponentIndex);
8905+
}
8906+
89028907
Type Solution::getType(ASTNode node) const {
89038908
auto result = nodeTypes.find(node);
89048909
if (result != nodeTypes.end())

lib/Sema/TypeCheckCodeCompletion.cpp

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -618,10 +618,10 @@ class CompletionContextFinder : public ASTWalker {
618618
/// If we are completing inside an expression, the \c CodeCompletionExpr that
619619
/// represents the code completion token.
620620

621-
/// The AST node that represents the code completion token, either as an
622-
/// expression or a KeyPath component.
623-
llvm::PointerUnion<CodeCompletionExpr *, const KeyPathExpr::Component *>
624-
CompletionNode;
621+
/// The AST node that represents the code completion token, either as a
622+
/// \c CodeCompletionExpr or a \c KeyPathExpr which contains a code completion
623+
/// component.
624+
llvm::PointerUnion<CodeCompletionExpr *, const KeyPathExpr *> CompletionNode;
625625

626626
Expr *InitialExpr = nullptr;
627627
DeclContext *InitialDC;
@@ -675,10 +675,13 @@ class CompletionContextFinder : public ASTWalker {
675675
for (auto &component : KeyPath->getComponents()) {
676676
if (component.getKind() ==
677677
KeyPathExpr::Component::Kind::CodeCompletion) {
678-
CompletionNode = &component;
678+
CompletionNode = KeyPath;
679679
return std::make_pair(false, nullptr);
680680
}
681681
}
682+
// Code completion in key paths is modelled by a code completion component
683+
// Don't walk the key path's parsed expressions.
684+
return std::make_pair(false, E);
682685
}
683686

684687
return std::make_pair(true, E);
@@ -713,12 +716,33 @@ class CompletionContextFinder : public ASTWalker {
713716
}
714717

715718
bool hasCompletionKeyPathComponent() const {
716-
return CompletionNode.dyn_cast<const KeyPathExpr::Component *>() != nullptr;
719+
return CompletionNode.dyn_cast<const KeyPathExpr *>() != nullptr;
717720
}
718721

719-
const KeyPathExpr::Component *getCompletionKeyPathComponent() const {
722+
/// If we are completing in a key path, returns the \c KeyPath that contains
723+
/// the code completion component.
724+
const KeyPathExpr *getKeyPathContainingCompletionComponent() const {
720725
assert(hasCompletionKeyPathComponent());
721-
return CompletionNode.get<const KeyPathExpr::Component *>();
726+
return CompletionNode.get<const KeyPathExpr *>();
727+
}
728+
729+
/// If we are completing in a key path, returns the index at which the key
730+
/// path has the code completion component.
731+
size_t getKeyPathCompletionComponentIndex() const {
732+
assert(hasCompletionKeyPathComponent());
733+
size_t ComponentIndex = 0;
734+
auto Components =
735+
getKeyPathContainingCompletionComponent()->getComponents();
736+
for (auto &Component : Components) {
737+
if (Component.getKind() == KeyPathExpr::Component::Kind::CodeCompletion) {
738+
break;
739+
} else {
740+
ComponentIndex++;
741+
}
742+
}
743+
assert(ComponentIndex < Components.size() &&
744+
"No completion component in the key path?");
745+
return ComponentIndex;
722746
}
723747

724748
struct Fallback {
@@ -949,20 +973,27 @@ bool TypeChecker::typeCheckForCodeCompletion(
949973
if (contextAnalyzer.locatedInMultiStmtClosure()) {
950974
auto &solution = solutions.front();
951975

952-
if (solution.hasType(contextAnalyzer.getCompletionExpr())) {
953-
llvm::for_each(solutions, callback);
954-
return CompletionResult::Ok;
976+
bool HasTypeForCompletionNode = false;
977+
if (completionExpr) {
978+
HasTypeForCompletionNode = solution.hasType(completionExpr);
979+
} else {
980+
assert(contextAnalyzer.hasCompletionKeyPathComponent());
981+
HasTypeForCompletionNode = solution.hasType(
982+
contextAnalyzer.getKeyPathContainingCompletionComponent(),
983+
contextAnalyzer.getKeyPathCompletionComponentIndex());
955984
}
956985

957-
// At this point we know the code completion expression wasn't checked
958-
// with the closure's surrounding context, so can defer to regular type-
959-
// checking for the current call to typeCheckExpression. If that succeeds
960-
// we will get a second call to typeCheckExpression for the body of the
961-
// closure later and can gather completions then. If it doesn't we rely
962-
// on the fallback typechecking in the subclasses of
963-
// TypeCheckCompletionCallback that considers in isolation a
964-
// sub-expression of the closure that contains the completion location.
965-
return CompletionResult::NotApplicable;
986+
if (!HasTypeForCompletionNode) {
987+
// At this point we know the code completion node wasn't checked with
988+
// the closure's surrounding context, so can defer to regular
989+
// type-checking for the current call to typeCheckExpression. If that
990+
// succeeds we will get a second call to typeCheckExpression for the
991+
// body of the closure later and can gather completions then. If it
992+
// doesn't we rely on the fallback typechecking in the subclasses of
993+
// TypeCheckCompletionCallback that considers in isolation a
994+
// sub-expression of the closure that contains the completion location.
995+
return CompletionResult::NotApplicable;
996+
}
966997
}
967998

968999
llvm::for_each(solutions, callback);

test/IDE/complete_swift_key_path.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=GENERIC_KEY_PATH_RESULT | %FileCheck %s -check-prefix=PERSONTYPE-DOT
3737

3838
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=COMPLETE_AFTER_SELF | %FileCheck %s -check-prefix=OBJ-NODOT
39+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_RESULT_BUILDER | %FileCheck %s -check-prefix=PERSONTYPE-DOT
40+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=IN_MULTI_STMT_CLOSURE | %FileCheck %s -check-prefix=PERSONTYPE-DOT
41+
3942
class Person {
4043
var name: String
4144
var friends: [Person] = []
@@ -196,3 +199,32 @@ func genericKeyPathResult<KeyPathResult>(id: KeyPath<Person, KeyPathResult>) {
196199
func completeAfterSelf(people: [Person]) {
197200
people.map(\.self#^COMPLETE_AFTER_SELF^#)
198201
}
202+
203+
func inResultBuilder() {
204+
protocol View2 {}
205+
206+
@resultBuilder public struct ViewBuilder2 {
207+
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View2 { fatalError() }
208+
public static func buildIf<Content>(_ content: Content?) -> Content? where Content : View2 { fatalError() }
209+
}
210+
211+
struct VStack2<Content>: View2 {
212+
init(@ViewBuilder2 view: () -> Content) {}
213+
}
214+
215+
@ViewBuilder2 var body: some View2 {
216+
VStack2 {
217+
if true {
218+
var people: [Person] = []
219+
people.map(\.#^IN_RESULT_BUILDER^#)
220+
}
221+
}
222+
}
223+
}
224+
225+
func inMultiStmtClosure(closure: () -> Void) {
226+
inMultiStmtClosure {
227+
var people: [Person] = []
228+
people.map(\.#^IN_MULTI_STMT_CLOSURE^#)
229+
}
230+
}

0 commit comments

Comments
 (0)