Skip to content

Commit ffd0072

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 7577496 commit ffd0072

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
@@ -8920,6 +8920,11 @@ bool Solution::hasType(ASTNode node) const {
89208920
return cs.hasType(node);
89218921
}
89228922

8923+
bool Solution::hasType(const KeyPathExpr *KP, unsigned ComponentIndex) const {
8924+
auto &cs = getConstraintSystem();
8925+
return cs.hasType(KP, ComponentIndex);
8926+
}
8927+
89238928
Type Solution::getType(ASTNode node) const {
89248929
auto result = nodeTypes.find(node);
89258930
if (result != nodeTypes.end())

lib/Sema/TypeCheckCodeCompletion.cpp

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

625-
/// The AST node that represents the code completion token, either as an
626-
/// expression or a KeyPath component.
627-
llvm::PointerUnion<CodeCompletionExpr *, const KeyPathExpr::Component *>
628-
CompletionNode;
625+
/// The AST node that represents the code completion token, either as a
626+
/// \c CodeCompletionExpr or a \c KeyPathExpr which contains a code completion
627+
/// component.
628+
llvm::PointerUnion<CodeCompletionExpr *, const KeyPathExpr *> CompletionNode;
629629

630630
Expr *InitialExpr = nullptr;
631631
DeclContext *InitialDC;
@@ -679,10 +679,13 @@ class CompletionContextFinder : public ASTWalker {
679679
for (auto &component : KeyPath->getComponents()) {
680680
if (component.getKind() ==
681681
KeyPathExpr::Component::Kind::CodeCompletion) {
682-
CompletionNode = &component;
682+
CompletionNode = KeyPath;
683683
return std::make_pair(false, nullptr);
684684
}
685685
}
686+
// Code completion in key paths is modelled by a code completion component
687+
// Don't walk the key path's parsed expressions.
688+
return std::make_pair(false, E);
686689
}
687690

688691
return std::make_pair(true, E);
@@ -717,12 +720,33 @@ class CompletionContextFinder : public ASTWalker {
717720
}
718721

719722
bool hasCompletionKeyPathComponent() const {
720-
return CompletionNode.dyn_cast<const KeyPathExpr::Component *>() != nullptr;
723+
return CompletionNode.dyn_cast<const KeyPathExpr *>() != nullptr;
721724
}
722725

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

728752
struct Fallback {
@@ -953,20 +977,27 @@ bool TypeChecker::typeCheckForCodeCompletion(
953977
if (contextAnalyzer.locatedInMultiStmtClosure()) {
954978
auto &solution = solutions.front();
955979

956-
if (solution.hasType(contextAnalyzer.getCompletionExpr())) {
957-
llvm::for_each(solutions, callback);
958-
return CompletionResult::Ok;
980+
bool HasTypeForCompletionNode = false;
981+
if (completionExpr) {
982+
HasTypeForCompletionNode = solution.hasType(completionExpr);
983+
} else {
984+
assert(contextAnalyzer.hasCompletionKeyPathComponent());
985+
HasTypeForCompletionNode = solution.hasType(
986+
contextAnalyzer.getKeyPathContainingCompletionComponent(),
987+
contextAnalyzer.getKeyPathCompletionComponentIndex());
959988
}
960989

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

9721003
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)