Skip to content

Commit 9dbd582

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`. This also fixes a few issues where code completion previously wouldn’t find the attribute constructor call and thus wasn’t providing code completion inside the property wrapper. rdar://92842803
1 parent 2f0ae44 commit 9dbd582

16 files changed

+285
-122
lines changed

include/swift/AST/ASTNode.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ namespace swift {
7878

7979
/// Whether the AST node is implicit.
8080
bool isImplicit() const;
81+
82+
friend llvm::hash_code hash_value(ASTNode N) {
83+
return llvm::hash_value(N.getOpaqueValue());
84+
}
8185
};
8286
} // namespace swift
8387

include/swift/AST/TypeCheckRequests.h

Lines changed: 75 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,82 @@ 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+
if (!ctx.Node.isNull()) {
1608+
return ctx.Node.getStartLoc();
1609+
} else {
1610+
return extractNearestSourceLoc(ctx.DC);
1611+
}
1612+
}
1613+
};
1614+
1615+
void simple_display(llvm::raw_ostream &out,
1616+
const TypeCheckASTNodeAtLocContext &ctx);
1617+
15471618
/// Request to typecheck a function body element at the given source location.
15481619
///
15491620
/// Produces true if an error occurred, false otherwise.
15501621
class TypeCheckASTNodeAtLocRequest
15511622
: public SimpleRequest<TypeCheckASTNodeAtLocRequest,
1552-
bool(DeclContext *, SourceLoc),
1623+
bool(TypeCheckASTNodeAtLocContext, SourceLoc),
15531624
RequestFlags::Uncached> {
15541625
public:
15551626
using SimpleRequest::SimpleRequest;
@@ -1558,7 +1629,8 @@ class TypeCheckASTNodeAtLocRequest
15581629
friend SimpleRequest;
15591630

15601631
// Evaluation.
1561-
bool evaluate(Evaluator &evaluator, DeclContext *DC, SourceLoc Loc) const;
1632+
bool evaluate(Evaluator &evaluator, TypeCheckASTNodeAtLocContext,
1633+
SourceLoc Loc) const;
15621634
};
15631635

15641636
/// Request to obtain a list of stored properties in a nominal type.
@@ -3605,6 +3677,7 @@ class GetSourceFileAsyncNode
36053677
bool isCached() const { return true; }
36063678
};
36073679

3680+
void simple_display(llvm::raw_ostream &out, ASTNode node);
36083681
void simple_display(llvm::raw_ostream &out, Type value);
36093682
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
36103683
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: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
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"
24+
#include "swift/AST/TypeCheckRequests.h"
2325
#include "swift/Basic/SourceLoc.h"
2426
#include <memory>
2527
#include <tuple>
@@ -140,8 +142,9 @@ namespace swift {
140142
/// Typecheck the given expression.
141143
bool typeCheckExpression(DeclContext *DC, Expr *&parsedExpr);
142144

143-
/// Type check a function body element which is at \p TargetLoc .
144-
bool typeCheckASTNodeAtLoc(DeclContext *DC, SourceLoc TargetLoc);
145+
/// Type check a function body element which is at \p TagetLoc.
146+
bool typeCheckASTNodeAtLoc(TypeCheckASTNodeAtLocContext TypeCheckCtx,
147+
SourceLoc TargetLoc);
145148

146149
/// Thunk around \c TypeChecker::typeCheckForCodeCompletion to make it
147150
/// available to \c swift::ide.

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 & 39 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);
@@ -1380,9 +1404,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13801404
// so we can safely cast the \c ParsedExpr back to a \c KeyPathExpr.
13811405
auto KeyPath = cast<KeyPathExpr>(ParsedExpr);
13821406
KeyPathTypeCheckCompletionCallback Lookup(KeyPath);
1383-
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
1384-
Context.CompletionCallback, &Lookup);
1385-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1407+
typeCheckWithLookup(Lookup);
13861408

13871409
Lookup.deliverResults(CurDeclContext, DotLoc, CompletionContext, Consumer);
13881410
return true;
@@ -1392,13 +1414,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
13921414
assert(CurDeclContext);
13931415
ArgumentTypeCheckCompletionCallback Lookup(CodeCompleteTokenExpr,
13941416
CurDeclContext);
1395-
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
1396-
Context.CompletionCallback, &Lookup);
1397-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1398-
1399-
if (!Lookup.gotCallback()) {
1400-
Lookup.fallbackTypeCheck(CurDeclContext);
1401-
}
1417+
typeCheckWithLookup(Lookup);
14021418

14031419
Lookup.deliverResults(ShouldCompleteCallPatternAfterParen, CompletionLoc,
14041420
CurDeclContext, CompletionContext, Consumer);
@@ -1425,13 +1441,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
14251441
// need to have a TypeCheckCompletionCallback so we can call
14261442
// deliverResults on it to deliver the keyword results from the completion
14271443
// context's result sink to the consumer.
1428-
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
1429-
Context.CompletionCallback, &Lookup);
1430-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1431-
1432-
if (!Lookup.gotCallback()) {
1433-
Lookup.fallbackTypeCheck(CurDeclContext);
1434-
}
1444+
typeCheckWithLookup(Lookup);
14351445
}
14361446

14371447
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody);
@@ -1446,13 +1456,7 @@ bool CodeCompletionCallbacksImpl::trySolverCompletion(bool MaybeFuncBody) {
14461456

14471457
AfterPoundExprCompletion Lookup(CodeCompleteTokenExpr, CurDeclContext,
14481458
ParentStmtKind);
1449-
llvm::SaveAndRestore<TypeCheckCompletionCallback *> CompletionCollector(
1450-
Context.CompletionCallback, &Lookup);
1451-
typeCheckContextAt(CurDeclContext, CompletionLoc);
1452-
1453-
if (!Lookup.gotCallback()) {
1454-
Lookup.fallbackTypeCheck(CurDeclContext);
1455-
}
1459+
typeCheckWithLookup(Lookup);
14561460

14571461
addKeywords(CompletionContext.getResultSink(), MaybeFuncBody);
14581462

@@ -1522,7 +1526,7 @@ void CodeCompletionCallbacksImpl::doneParsing() {
15221526

15231527
undoSingleExpressionReturn(CurDeclContext);
15241528
typeCheckContextAt(
1525-
CurDeclContext,
1529+
TypeCheckASTNodeAtLocContext::declContext(CurDeclContext),
15261530
ParsedExpr
15271531
? ParsedExpr->getLoc()
15281532
: 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)