Skip to content

Commit 0c75e3b

Browse files
committed
[CodeCompletion] Support type checking attributes even if they are not part of the AST
The code completion might occur inside an attriubte that isn’t part of the AST because it’s missing a `VarDecl` that it could be attached to. In these cases, record the `CustomAttr` and type check it standalone, pretending it was part of a `DeclContext`.
1 parent 11b7116 commit 0c75e3b

16 files changed

+284
-124
lines changed

include/swift/AST/ASTNode.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ namespace swift {
8484
V.Val = decltype(V.Val)::getFromOpaqueValue(VP);
8585
return V;
8686
}
87+
88+
friend llvm::hash_code hash_value(ASTNode N) {
89+
return llvm::hash_value(N.getOpaqueValue());
90+
}
8791
};
8892
} // namespace swift
8993

include/swift/AST/TypeCheckRequests.h

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "swift/AST/ActorIsolation.h"
2020
#include "swift/AST/AnyFunctionRef.h"
21+
#include "swift/AST/ASTNode.h"
2122
#include "swift/AST/ASTTypeIDs.h"
2223
#include "swift/AST/Effects.h"
2324
#include "swift/AST/GenericParamList.h"
@@ -1544,12 +1545,78 @@ class TypeCheckFunctionBodyRequest
15441545
readDependencySource(const evaluator::DependencyRecorder &) const;
15451546
};
15461547

1548+
/// Describes the context in which the AST node to type check in a
1549+
/// \c TypeCheckASTNodeAtLocRequest should be searched. This can be either of
1550+
/// two cases:
1551+
/// 1. A \c DeclContext that contains the node representing the location to
1552+
/// type check
1553+
/// 2. If the node that should be type checked that might not be part of the
1554+
/// AST (e.g. because it is a dangling property attribute), an \c ASTNode
1555+
/// that contains the location to type check in together with a DeclContext
1556+
/// in which we should pretend that node occurs.
1557+
class TypeCheckASTNodeAtLocContext {
1558+
DeclContext *DC;
1559+
ASTNode Node;
1560+
1561+
/// Memberwise initializer
1562+
TypeCheckASTNodeAtLocContext(DeclContext *DC, ASTNode Node)
1563+
: DC(DC), Node(Node) {
1564+
assert(DC != nullptr);
1565+
}
1566+
1567+
public:
1568+
static TypeCheckASTNodeAtLocContext declContext(DeclContext *DC) {
1569+
return TypeCheckASTNodeAtLocContext(DC, /*Node=*/nullptr);
1570+
}
1571+
1572+
static TypeCheckASTNodeAtLocContext node(DeclContext *DC, ASTNode Node) {
1573+
assert(!Node.isNull());
1574+
return TypeCheckASTNodeAtLocContext(DC, Node);
1575+
}
1576+
1577+
DeclContext *getDeclContext() const { return DC; }
1578+
1579+
bool isForUnattachedNode() const { return !Node.isNull(); }
1580+
1581+
ASTNode getUnattachedNode() const {
1582+
assert(isForUnattachedNode());
1583+
return Node;
1584+
}
1585+
1586+
ASTNode &getUnattachedNode() {
1587+
assert(isForUnattachedNode());
1588+
return Node;
1589+
}
1590+
1591+
friend llvm::hash_code hash_value(const TypeCheckASTNodeAtLocContext &ctx) {
1592+
return llvm::hash_combine(ctx.DC, ctx.Node);
1593+
}
1594+
1595+
friend bool operator==(const TypeCheckASTNodeAtLocContext &lhs,
1596+
const TypeCheckASTNodeAtLocContext &rhs) {
1597+
return lhs.DC == rhs.DC && lhs.Node == rhs.Node;
1598+
}
1599+
1600+
friend bool operator!=(const TypeCheckASTNodeAtLocContext &lhs,
1601+
const TypeCheckASTNodeAtLocContext &rhs) {
1602+
return !(lhs == rhs);
1603+
}
1604+
1605+
friend SourceLoc
1606+
extractNearestSourceLoc(const TypeCheckASTNodeAtLocContext &ctx) {
1607+
return extractNearestSourceLoc(ctx.DC);
1608+
}
1609+
};
1610+
1611+
void simple_display(llvm::raw_ostream &out,
1612+
const TypeCheckASTNodeAtLocContext &ctx);
1613+
15471614
/// Request to typecheck a function body element at the given source location.
15481615
///
15491616
/// Produces true if an error occurred, false otherwise.
15501617
class TypeCheckASTNodeAtLocRequest
15511618
: public SimpleRequest<TypeCheckASTNodeAtLocRequest,
1552-
bool(DeclContext *, SourceLoc),
1619+
bool(TypeCheckASTNodeAtLocContext, SourceLoc),
15531620
RequestFlags::Uncached> {
15541621
public:
15551622
using SimpleRequest::SimpleRequest;
@@ -1558,7 +1625,8 @@ class TypeCheckASTNodeAtLocRequest
15581625
friend SimpleRequest;
15591626

15601627
// Evaluation.
1561-
bool evaluate(Evaluator &evaluator, DeclContext *DC, SourceLoc Loc) const;
1628+
bool evaluate(Evaluator &evaluator, TypeCheckASTNodeAtLocContext,
1629+
SourceLoc Loc) const;
15621630
};
15631631

15641632
/// Request to obtain a list of stored properties in a nominal type.
@@ -3605,6 +3673,7 @@ class GetSourceFileAsyncNode
36053673
bool isCached() const { return true; }
36063674
};
36073675

3676+
void simple_display(llvm::raw_ostream &out, ASTNode node);
36083677
void simple_display(llvm::raw_ostream &out, Type value);
36093678
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
36103679
void simple_display(llvm::raw_ostream &out, ImplicitMemberAction action);

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ SWIFT_REQUEST(TypeChecker, TypeCheckFunctionBodyRequest,
345345
BraceStmt *(AbstractFunctionDecl *), SeparatelyCached,
346346
NoLocationInfo)
347347
SWIFT_REQUEST(TypeChecker, TypeCheckASTNodeAtLocRequest,
348-
bool(DeclContext *, SourceLoc),
348+
bool(TypeCheckASTNodeAtLocContext, SourceLoc),
349349
Uncached, NoLocationInfo)
350350
SWIFT_REQUEST(TypeChecker, UnderlyingTypeRequest, Type(TypeAliasDecl *),
351351
SeparatelyCached, NoLocationInfo)

include/swift/Parse/CodeCompletionCallbacks.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ class CodeCompletionCallbacks {
118118
/// Set target decl for attribute if the CC token is in attribute of the decl.
119119
virtual void setAttrTargetDeclKind(Optional<DeclKind> DK) {}
120120

121+
/// Set that the code completion token occurred in a custom attribute. This
122+
/// allows us to type check the custom attribute even if it is not attached to
123+
/// the AST, e.g. because there is no var declaration following it.
124+
virtual void setCompletingInAttribute(CustomAttr *Attr){};
125+
121126
/// Complete expr-dot after we have consumed the dot.
122127
virtual void completeDotExpr(CodeCompletionExpr *E, SourceLoc DotLoc) {};
123128

include/swift/Sema/IDETypeChecking.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
#ifndef SWIFT_SEMA_IDETYPECHECKING_H
2020
#define SWIFT_SEMA_IDETYPECHECKING_H
2121

22+
#include "swift/AST/ASTNode.h"
2223
#include "swift/AST/Identifier.h"
23-
#include "swift/Basic/SourceLoc.h"
24+
#include "swift/AST/TypeCheckRequests.h"
2425
#include "swift/Basic/OptionSet.h"
26+
#include "swift/Basic/SourceLoc.h"
2527
#include <memory>
2628
#include <tuple>
2729

@@ -144,8 +146,9 @@ namespace swift {
144146
/// Typecheck the given expression.
145147
bool typeCheckExpression(DeclContext *DC, Expr *&parsedExpr);
146148

147-
/// Type check a function body element which is at \p TagetLoc .
148-
bool typeCheckASTNodeAtLoc(DeclContext *DC, SourceLoc TargetLoc);
149+
/// Type check a function body element which is at \p TagetLoc.
150+
bool typeCheckASTNodeAtLoc(TypeCheckASTNodeAtLocContext TypeCheckCtx,
151+
SourceLoc TargetLoc);
149152

150153
/// Thunk around \c TypeChecker::typeCheckForCodeCompletion to make it
151154
/// available to \c swift::ide.
@@ -163,6 +166,11 @@ namespace swift {
163166
constraints::SolutionApplicationTarget &target, bool needsPrecheck,
164167
llvm::function_ref<void(const constraints::Solution &)> callback);
165168

169+
/// Thunk around \c TypeChecker::typeCheckASTNode to make it available to
170+
/// \c swift::ide.
171+
void typeCheckASTNode(ASTNode &node, DeclContext *DC,
172+
bool LeaveBodyUnchecked = false);
173+
166174
LookupResult
167175
lookupSemanticMember(DeclContext *DC, Type ty, DeclName name);
168176

lib/AST/TypeCheckRequests.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ void swift::simple_display(llvm::raw_ostream &out,
6262
}
6363
}
6464

65+
void swift::simple_display(llvm::raw_ostream &out, ASTNode node) {
66+
if (node) {
67+
node.dump(out);
68+
} else {
69+
out << "null";
70+
}
71+
}
72+
6573
void swift::simple_display(llvm::raw_ostream &out, Type type) {
6674
if (type)
6775
type.print(out);

lib/IDE/CodeCompletion.cpp

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,12 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
115115
DeclContext *CurDeclContext = nullptr;
116116
DeclAttrKind AttrKind;
117117

118+
/// When the code completion token occurs in a custom attribute, the attribute
119+
/// it occurs in. Used so we can complete inside the attribute even if it's
120+
/// not attached to the AST, e.g. because there is no var decl it could be
121+
/// attached to.
122+
CustomAttr *AttrWithCompletion = nullptr;
123+
118124
/// In situations when \c SyntaxKind hints or determines
119125
/// completions, i.e. a precedence group attribute, this
120126
/// can be set and used to control the code completion scenario.
@@ -227,6 +233,11 @@ class CodeCompletionCallbacksImpl : public CodeCompletionCallbacks {
227233
AttTargetDK = DK;
228234
}
229235

236+
void setCompletingInAttribute(CustomAttr *Attr) override {
237+
AttrWithCompletion = Attr;
238+
CurDeclContext = P.CurDeclContext;
239+
}
240+
230241
void completeDotExpr(CodeCompletionExpr *E, SourceLoc DotLoc) override;
231242
void completeStmtOrExpr(CodeCompletionExpr *E) override;
232243
void completePostfixExprBeginning(CodeCompletionExpr *E) override;
@@ -1330,16 +1341,24 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13301341
? ParsedExpr->getLoc()
13311342
: CurDeclContext->getASTContext().SourceMgr.getCodeCompletionLoc();
13321343

1333-
switch (Kind) {
1334-
case CompletionKind::DotExpr: {
1335-
assert(CodeCompleteTokenExpr);
1336-
assert(CurDeclContext);
1337-
1338-
DotExprTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr,
1339-
CurDeclContext);
1344+
auto typeCheckWithLookup = [this, &CompletionLoc](
1345+
TypeCheckCompletionCallback &Lookup) {
13401346
llvm::SaveAndRestore<TypeCheckCompletionCallback*>
13411347
CompletionCollector(Context.CompletionCallback, &Lookup);
1342-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1348+
if (AttrWithCompletion) {
1349+
/// The attribute might not be attached to the AST if there is no var decl
1350+
/// it could be attached to. Type check it standalone.
1351+
ASTNode Call = CallExpr::create(
1352+
CurDeclContext->getASTContext(), AttrWithCompletion->getTypeExpr(),
1353+
AttrWithCompletion->getArgs(), /*implicit=*/true);
1354+
typeCheckContextAt(
1355+
TypeCheckASTNodeAtLocContext::node(CurDeclContext, Call),
1356+
CompletionLoc);
1357+
} else {
1358+
typeCheckContextAt(
1359+
TypeCheckASTNodeAtLocContext::declContext(CurDeclContext),
1360+
CompletionLoc);
1361+
}
13431362

13441363
// This (hopefully) only happens in cases where the expression isn't
13451364
// typechecked during normal compilation either (e.g. member completion in a
@@ -1348,6 +1367,16 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13481367
// tooling in general though.
13491368
if (!Lookup.gotCallback())
13501369
Lookup.fallbackTypeCheck(CurDeclContext);
1370+
};
1371+
1372+
switch (Kind) {
1373+
case CompletionKind::DotExpr: {
1374+
assert(CodeCompleteTokenExpr);
1375+
assert(CurDeclContext);
1376+
1377+
DotExprTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr,
1378+
CurDeclContext);
1379+
typeCheckWithLookup(Lookup);
13511380

13521381
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody);
13531382

@@ -1362,12 +1391,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13621391

13631392
UnresolvedMemberTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr,
13641393
CurDeclContext);
1365-
llvm::SaveAndRestore<TypeCheckCompletionCallback*>
1366-
CompletionCollector(Context.CompletionCallback, &Lookup);
1367-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1368-
1369-
if (!Lookup.gotCallback())
1370-
Lookup.fallbackTypeCheck(CurDeclContext);
1394+
typeCheckWithLookup(Lookup);
13711395

13721396
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody);
13731397
Lookup.deliverResults(CurDeclContext, DotLoc, CompletionContext, Consumer);
@@ -1382,7 +1406,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13821406
KeyPathTypeCheckCompletionCallback Lookup(KeyPath);
13831407
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
13841408
Context.CompletionCallback, &Lookup);
1385-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1409+
typeCheckWithLookup(Lookup);
13861410

13871411
Lookup.deliverResults(CurDeclContext, DotLoc, CompletionContext, Consumer);
13881412
return true;
@@ -1395,11 +1419,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13951419
CurDeclContext);
13961420
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
13971421
Context.CompletionCallback, &Lookup);
1398-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1399-
1400-
if (!Lookup.gotCallback()) {
1401-
Lookup.fallbackTypeCheck(CurDeclContext);
1402-
}
1422+
typeCheckWithLookup(Lookup);
14031423

14041424
Lookup.deliverResults(ShouldCompleteCallPatternAfterParen, CompletionLoc,
14051425
CurDeclContext, CompletionContext, Consumer);
@@ -1426,13 +1446,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
14261446
// need to have a TypeCheckCompletionCallback so we can call
14271447
// deliverResults on it to deliver the keyword results from the completion
14281448
// context's result sink to the consumer.
1429-
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
1430-
Context.CompletionCallback, &Lookup);
1431-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1432-
1433-
if (!Lookup.gotCallback()) {
1434-
Lookup.fallbackTypeCheck(CurDeclContext);
1435-
}
1449+
typeCheckWithLookup(Lookup);
14361450
}
14371451

14381452
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody);
@@ -1447,13 +1461,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
14471461

14481462
AfterPoundExprCompletion Lookup(CodeCompleteTokenExpr, CurDeclContext,
14491463
ParentStmtKind);
1450-
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
1451-
Context.CompletionCallback, &Lookup);
1452-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1453-
1454-
if (!Lookup.gotCallback()) {
1455-
Lookup.fallbackTypeCheck(CurDeclContext);
1456-
}
1464+
typeCheckWithLookup(Lookup);
14571465

14581466
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody);
14591467

@@ -1523,7 +1531,7 @@ void CodeCompletionCallbacksImpl::doneParsing() {
15231531

15241532
undoSingleExpressionReturn(CurDeclContext);
15251533
typeCheckContextAt(
1526-
CurDeclContext,
1534+
TypeCheckASTNodeAtLocContext::declContext(CurDeclContext),
15271535
ParsedExpr
15281536
? ParsedExpr->getLoc()
15291537
: CurDeclContext->getASTContext().SourceMgr.getCodeCompletionLoc());

lib/IDE/ConformingMethodList.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ void ConformingMethodListCallbacks::doneParsing() {
6868
if (!ParsedExpr)
6969
return;
7070

71-
typeCheckContextAt(CurDeclContext, ParsedExpr->getLoc());
71+
typeCheckContextAt(TypeCheckASTNodeAtLocContext::declContext(CurDeclContext),
72+
ParsedExpr->getLoc());
7273

7374
Type T = ParsedExpr->getType();
7475

0 commit comments

Comments
 (0)