Skip to content

Commit 3126396

Browse files
authored
Merge pull request #41886 from ahoppen/pr/migrate-foreachsequence-postfixexprbeginning-solver-based
[CodeCompletion] Migrate ForEachSequence and PostfixExprBeginning to solver-based
2 parents e48cd33 + 95d3984 commit 3126396

18 files changed

+251
-137
lines changed

include/swift/IDE/ExprCompletion.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ namespace ide {
2323
class ExprTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
2424
public:
2525
struct Result {
26-
/// The contextual type that the code completion expression should produce.
27-
Type ExpectedType;
28-
2926
/// If the code completion expression is an implicit return in a
3027
/// single-expression closure.
3128
bool IsImplicitSingleExpressionReturn;
@@ -38,14 +35,37 @@ class ExprTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
3835
/// this result. This in particular includes parameters of closures that
3936
/// were type-checked with the code completion expression.
4037
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes;
38+
39+
bool operator==(const Result &Other) const;
4140
};
4241

4342
private:
4443
CodeCompletionExpr *CompletionExpr;
4544
DeclContext *DC;
4645

46+
/// The contextual types to which the code completion results should be
47+
/// convertible.
48+
/// Technically, each result should have its own expected type because some
49+
/// expected types may only be available e.g. for certain
50+
/// \c SolutionSpecificVarTypes. But that means that we need to do a separate
51+
/// completion lookup for each expected type and de-duplicate the results,
52+
/// which can have huge performance implications (>5mins instead of <2secs).
53+
/// In practice sharing ExpectedTypes between results yields identical results
54+
/// in almost all cases and acceptable results in the other cases.
55+
SmallVector<Type, 4> ExpectedTypes;
56+
4757
SmallVector<Result, 4> Results;
4858

59+
/// Adds the given type to \c ExpectedTypes unless \c ExpectedTypes already
60+
/// contains the type.
61+
void addExpectedType(Type ExpectedType);
62+
63+
/// Adds the result with the given parameters to \c Results unless \c Results
64+
/// already contains an entry with exactly the same values.
65+
void addResult(
66+
bool IsImplicitSingleExpressionReturn, bool IsInAsyncContext,
67+
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes);
68+
4969
void sawSolutionImpl(const constraints::Solution &solution) override;
5070

5171
public:

include/swift/IDE/TypeCheckCompletionCallback.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,13 @@ class TypeCheckCompletionCallback {
6767

6868
// MARK: - Utility functions for subclasses of TypeCheckCompletionCallback
6969

70-
Type getTypeForCompletion(const constraints::Solution &S, Expr *E);
70+
Type getTypeForCompletion(const constraints::Solution &S, ASTNode Node);
71+
72+
/// If \p E occurs in a pattern matching position, returns the type that it is
73+
/// being pattern-matched against.
74+
/// If that type is an enum, it allows us to suggest the enum cases for the code
75+
/// completion expression \p E.
76+
Type getPatternMatchType(const constraints::Solution &S, Expr *E);
7177

7278
/// Whether the given completion expression is the only expression in its
7379
/// containing closure or function body and its value is implicitly returned.
@@ -84,6 +90,9 @@ bool isImplicitSingleExpressionReturn(constraints::ConstraintSystem &CS,
8490
/// Returns \c true iff the decl context \p DC allows calling async functions.
8591
bool isContextAsync(const constraints::Solution &S, DeclContext *DC);
8692

93+
/// Returns true if both types are null or if they are equal.
94+
bool nullableTypesEqual(Type LHS, Type RHS);
95+
8796
} // namespace ide
8897
} // namespace swift
8998

lib/AST/Type.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4308,11 +4308,11 @@ operator()(CanType dependentType, Type conformingReplacementType,
43084308
ProtocolConformanceRef MakeAbstractConformanceForGenericType::
43094309
operator()(CanType dependentType, Type conformingReplacementType,
43104310
ProtocolDecl *conformedProtocol) const {
4311-
assert((conformingReplacementType->is<ErrorType>()
4312-
|| conformingReplacementType->is<SubstitutableType>()
4313-
|| conformingReplacementType->is<DependentMemberType>()
4314-
|| conformingReplacementType->is<TypeVariableType>())
4315-
&& "replacement requires looking up a concrete conformance");
4311+
assert((conformingReplacementType->is<ErrorType>() ||
4312+
conformingReplacementType->is<SubstitutableType>() ||
4313+
conformingReplacementType->is<DependentMemberType>() ||
4314+
conformingReplacementType->hasTypeVariable()) &&
4315+
"replacement requires looking up a concrete conformance");
43164316
// A class-constrained archetype might conform to the protocol
43174317
// concretely.
43184318
if (auto *archetypeType = conformingReplacementType->getAs<ArchetypeType>()) {

lib/IDE/ArgumentCompletion.cpp

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,6 @@ using namespace swift;
2020
using namespace swift::ide;
2121
using namespace swift::constraints;
2222

23-
/// Returns true if both types are null or if they are equal.
24-
static bool nullableTypesEqual(Type LHS, Type RHS) {
25-
if (LHS.isNull() && RHS.isNull()) {
26-
return true;
27-
} else if (LHS.isNull() || RHS.isNull()) {
28-
// One type is null but the other is not.
29-
return false;
30-
} else {
31-
return LHS->isEqual(RHS);
32-
}
33-
}
34-
3523
bool ArgumentTypeCheckCompletionCallback::addPossibleParams(
3624
const ArgumentTypeCheckCompletionCallback::Result &Res,
3725
SmallVectorImpl<PossibleParamInfo> &Params, SmallVectorImpl<Type> &Types) {

lib/IDE/CodeCompletion.cpp

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,7 +1396,9 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13961396
CurDeclContext, CompletionContext, Consumer);
13971397
return true;
13981398
}
1399-
case CompletionKind::StmtOrExpr: {
1399+
case CompletionKind::StmtOrExpr:
1400+
case CompletionKind::ForEachSequence:
1401+
case CompletionKind::PostfixExprBeginning: {
14001402
assert(CodeCompleteTokenExpr);
14011403
assert(CurDeclContext);
14021404

@@ -1539,18 +1541,11 @@ void CodeCompletionCallbacksImpl::doneParsing() {
15391541
case CompletionKind::KeyPathExprSwift:
15401542
case CompletionKind::CallArg:
15411543
case CompletionKind::StmtOrExpr:
1544+
case CompletionKind::ForEachSequence:
1545+
case CompletionKind::PostfixExprBeginning:
15421546
llvm_unreachable("should be already handled");
15431547
return;
15441548

1545-
case CompletionKind::ForEachSequence:
1546-
case CompletionKind::PostfixExprBeginning: {
1547-
ExprContextInfo ContextInfo(CurDeclContext, CodeCompleteTokenExpr);
1548-
Lookup.setExpectedTypes(ContextInfo.getPossibleTypes(),
1549-
ContextInfo.isImplicitSingleExpressionReturn());
1550-
DoPostfixExprBeginning();
1551-
break;
1552-
}
1553-
15541549
case CompletionKind::PostfixExpr: {
15551550
Lookup.setHaveLeadingSpace(HasSpace);
15561551
if (isDynamicLookup(*ExprType))

lib/IDE/ExprCompletion.cpp

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,54 @@ using namespace swift;
1919
using namespace swift::ide;
2020
using namespace swift::constraints;
2121

22+
static bool solutionSpecificVarTypesEqual(
23+
const llvm::SmallDenseMap<const VarDecl *, Type> &LHS,
24+
const llvm::SmallDenseMap<const VarDecl *, Type> &RHS) {
25+
if (LHS.size() != RHS.size()) {
26+
return false;
27+
}
28+
for (auto LHSEntry : LHS) {
29+
auto RHSEntry = RHS.find(LHSEntry.first);
30+
if (RHSEntry == RHS.end()) {
31+
// Entry of the LHS doesn't exist in RHS
32+
return false;
33+
} else if (!nullableTypesEqual(LHSEntry.second, RHSEntry->second)) {
34+
return false;
35+
}
36+
}
37+
return true;
38+
}
39+
40+
bool ExprTypeCheckCompletionCallback::Result::operator==(
41+
const Result &Other) const {
42+
return IsImplicitSingleExpressionReturn ==
43+
Other.IsImplicitSingleExpressionReturn &&
44+
IsInAsyncContext == Other.IsInAsyncContext &&
45+
solutionSpecificVarTypesEqual(SolutionSpecificVarTypes,
46+
Other.SolutionSpecificVarTypes);
47+
}
48+
49+
void ExprTypeCheckCompletionCallback::addExpectedType(Type ExpectedType) {
50+
auto IsEqual = [&ExpectedType](Type Other) {
51+
return nullableTypesEqual(ExpectedType, Other);
52+
};
53+
if (llvm::any_of(ExpectedTypes, IsEqual)) {
54+
return;
55+
}
56+
ExpectedTypes.push_back(ExpectedType);
57+
}
58+
59+
void ExprTypeCheckCompletionCallback::addResult(
60+
bool IsImplicitSingleExpressionReturn, bool IsInAsyncContext,
61+
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes) {
62+
Result NewResult = {IsImplicitSingleExpressionReturn, IsInAsyncContext,
63+
SolutionSpecificVarTypes};
64+
if (llvm::is_contained(Results, NewResult)) {
65+
return;
66+
}
67+
Results.push_back(NewResult);
68+
}
69+
2270
void ExprTypeCheckCompletionCallback::sawSolutionImpl(
2371
const constraints::Solution &S) {
2472
auto &CS = S.getConstraintSystem();
@@ -36,8 +84,12 @@ void ExprTypeCheckCompletionCallback::sawSolutionImpl(
3684
}
3785
}
3886

39-
Results.push_back(
40-
{ExpectedTy, ImplicitReturn, IsAsync, SolutionSpecificVarTypes});
87+
addResult(ImplicitReturn, IsAsync, SolutionSpecificVarTypes);
88+
addExpectedType(ExpectedTy);
89+
90+
if (auto PatternMatchType = getPatternMatchType(S, CompletionExpr)) {
91+
addExpectedType(PatternMatchType);
92+
}
4193
}
4294

4395
void ExprTypeCheckCompletionCallback::deliverResults(
@@ -46,9 +98,10 @@ void ExprTypeCheckCompletionCallback::deliverResults(
4698
ASTContext &Ctx = DC->getASTContext();
4799
CompletionLookup Lookup(CompletionCtx.getResultSink(), Ctx, DC,
48100
&CompletionCtx);
101+
Lookup.shouldCheckForDuplicates(Results.size() > 1);
49102

50103
for (auto &Result : Results) {
51-
Lookup.setExpectedTypes(Result.ExpectedType,
104+
Lookup.setExpectedTypes(ExpectedTypes,
52105
Result.IsImplicitSingleExpressionReturn);
53106
Lookup.setCanCurrDeclContextHandleAsync(Result.IsInAsyncContext);
54107
Lookup.setSolutionSpecificVarTypes(Result.SolutionSpecificVarTypes);

lib/IDE/ExprContextAnalysis.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ void swift::ide::typeCheckContextAt(DeclContext *DC, SourceLoc Loc) {
107107
auto *Param = AFD->getParameters()->get(defaultArg->getIndex());
108108
(void)Param->getTypeCheckedDefaultExpr();
109109
}
110+
if (auto *SD = dyn_cast<SubscriptDecl>(defaultArg->getParent())) {
111+
auto *Param = SD->getIndices()->get(defaultArg->getIndex());
112+
(void)Param->getTypeCheckedDefaultExpr();
113+
}
110114
}
111115
break;
112116

lib/IDE/TypeCheckCompletionCallback.cpp

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ void TypeCheckCompletionCallback::fallbackTypeCheck(DeclContext *DC) {
4040

4141
// MARK: - Utility functions for subclasses of TypeCheckCompletionCallback
4242

43-
Type swift::ide::getTypeForCompletion(const constraints::Solution &S, Expr *E) {
44-
if (!S.hasType(E)) {
43+
Type swift::ide::getTypeForCompletion(const constraints::Solution &S,
44+
ASTNode Node) {
45+
if (!S.hasType(Node)) {
4546
assert(false && "Expression wasn't type checked?");
4647
return nullptr;
4748
}
@@ -66,8 +67,8 @@ Type swift::ide::getTypeForCompletion(const constraints::Solution &S, Expr *E) {
6667
// expression should still have a type of `[T]` instead of `[<<hole>>]`
6768
// because it helps to produce correct contextual member list based on
6869
// a conformance requirement associated with generic parameter `T`.
69-
if (isa<CodeCompletionExpr>(E)) {
70-
auto completionTy = S.getType(E).transform([&](Type type) -> Type {
70+
if (isExpr<CodeCompletionExpr>(Node)) {
71+
auto completionTy = S.getType(Node).transform([&](Type type) -> Type {
7172
if (auto *typeVar = type->getAs<TypeVariableType>())
7273
return S.getFixedType(typeVar);
7374
return type;
@@ -92,18 +93,77 @@ Type swift::ide::getTypeForCompletion(const constraints::Solution &S, Expr *E) {
9293
return type;
9394
}));
9495
} else {
95-
Result = S.getResolvedType(E);
96+
Result = S.getResolvedType(Node);
9697
}
9798

9899
if (!Result || Result->is<UnresolvedType>()) {
99-
Result = CS.getContextualType(E, /*forConstraint=*/false);
100+
Result = CS.getContextualType(Node, /*forConstraint=*/false);
100101
}
101102
if (Result && Result->is<UnresolvedType>()) {
102103
Result = Type();
103104
}
104105
return Result;
105106
}
106107

108+
/// If the code completion expression \p E occurs in a pattern matching
109+
/// position, we have an AST that looks like this.
110+
/// \code
111+
/// (binary_expr implicit type='$T3'
112+
/// (overloaded_decl_ref_expr function_ref=compound decls=[
113+
/// Swift.(file).~=,
114+
/// Swift.(file).Optional extension.~=])
115+
/// (argument_list implicit
116+
/// (argument
117+
/// (code_completion_expr implicit type='$T1'))
118+
/// (argument
119+
/// (declref_expr implicit decl=swift_ide_test.(file).foo(x:).$match))))
120+
/// \endcode
121+
/// If the code completion expression occurs in such an AST, return the
122+
/// declaration of the \c $match variable, otherwise return \c nullptr.
123+
static VarDecl *getMatchVarIfInPatternMatch(Expr *E, ConstraintSystem &CS) {
124+
auto &Context = CS.getASTContext();
125+
126+
auto *Binary = dyn_cast_or_null<BinaryExpr>(CS.getParentExpr(E));
127+
if (!Binary || !Binary->isImplicit() || Binary->getLHS() != E) {
128+
return nullptr;
129+
}
130+
131+
auto CalledOperator = Binary->getFn();
132+
if (!isPatternMatchingOperator(CalledOperator)) {
133+
return nullptr;
134+
}
135+
136+
auto MatchArg = dyn_cast_or_null<DeclRefExpr>(Binary->getRHS());
137+
if (!MatchArg || !MatchArg->isImplicit()) {
138+
return nullptr;
139+
}
140+
141+
auto MatchVar = MatchArg->getDecl();
142+
if (MatchVar && MatchVar->isImplicit() &&
143+
MatchVar->getBaseName() == Context.Id_PatternMatchVar) {
144+
return dyn_cast<VarDecl>(MatchVar);
145+
} else {
146+
return nullptr;
147+
}
148+
}
149+
150+
Type swift::ide::getPatternMatchType(const constraints::Solution &S, Expr *E) {
151+
if (auto MatchVar = getMatchVarIfInPatternMatch(E, S.getConstraintSystem())) {
152+
Type MatchVarType;
153+
// If the MatchVar has an explicit type, it's not part of the solution. But
154+
// we can look it up in the constraint system directly.
155+
if (auto T = S.getConstraintSystem().getVarType(MatchVar)) {
156+
MatchVarType = T;
157+
} else {
158+
MatchVarType = getTypeForCompletion(S, MatchVar);
159+
}
160+
if (MatchVarType) {
161+
return MatchVarType;
162+
}
163+
}
164+
return nullptr;
165+
}
166+
107167
bool swift::ide::isImplicitSingleExpressionReturn(ConstraintSystem &CS,
108168
Expr *CompletionExpr) {
109169
Expr *ParentExpr = CS.getParentExpr(CompletionExpr);
@@ -148,3 +208,14 @@ bool swift::ide::isContextAsync(const constraints::Solution &S,
148208
// async.
149209
return canDeclContextHandleAsync(DC);
150210
}
211+
212+
bool swift::ide::nullableTypesEqual(Type LHS, Type RHS) {
213+
if (LHS.isNull() && RHS.isNull()) {
214+
return true;
215+
} else if (LHS.isNull() || RHS.isNull()) {
216+
// One type is null but the other is not.
217+
return false;
218+
} else {
219+
return LHS->isEqual(RHS);
220+
}
221+
}

0 commit comments

Comments
 (0)