Skip to content

[IDE] Implement completion-like cursor info for ValueDecls #62362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions include/swift/IDE/CursorInfo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===--- CursorInfo.h --- ---------------------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_IDE_CURSORINFO_H
#define SWIFT_IDE_CURSORINFO_H

#include "swift/AST/Type.h"
#include "swift/Basic/LLVM.h"
#include "swift/IDE/Utils.h"

namespace swift {
class CodeCompletionCallbacksFactory;

namespace ide {

/// An abstract base class for consumers of context info results.
class CursorInfoConsumer {
public:
virtual ~CursorInfoConsumer() {}
virtual void handleResults(const ResolvedCursorInfo &) = 0;
};

/// Create a factory for code completion callbacks.
CodeCompletionCallbacksFactory *
makeCursorInfoCallbacksFactory(CursorInfoConsumer &Consumer,
SourceLoc RequestedLoc);

} // namespace ide
} // namespace swift

#endif // SWIFT_IDE_CURSORINFO_H
19 changes: 18 additions & 1 deletion include/swift/IDETool/CompletionInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "swift/IDE/CodeCompletionResult.h"
#include "swift/IDE/CodeCompletionResultSink.h"
#include "swift/IDE/ConformingMethodList.h"
#include "swift/IDE/CursorInfo.h"
#include "swift/IDE/ImportDepth.h"
#include "swift/IDE/SwiftCompletionInfo.h"
#include "swift/IDE/TypeContextInfo.h"
Expand Down Expand Up @@ -78,6 +79,14 @@ struct ConformingMethodListResults {
bool DidReuseAST;
};

/// The results returned from \c CompletionInstance::conformingMethodList.
struct CursorInfoResults {
/// The actual results. If \c nullptr, no results were found.
const ResolvedCursorInfo *Result;
/// Whether an AST was reused for the completion.
bool DidReuseAST;
};

/// Manages \c CompilerInstance for completion like operations.
class CompletionInstance {
struct Options {
Expand Down Expand Up @@ -152,7 +161,7 @@ class CompletionInstance {
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
DiagnosticConsumer *DiagC,
DiagnosticConsumer *DiagC, bool IgnoreSwiftSourceInfo,
std::shared_ptr<std::atomic<bool>> CancellationFlag,
llvm::function_ref<void(CancellableResult<CompletionInstanceResult>)>
Callback);
Expand Down Expand Up @@ -192,6 +201,14 @@ class CompletionInstance {
std::shared_ptr<std::atomic<bool>> CancellationFlag,
llvm::function_ref<void(CancellableResult<ConformingMethodListResults>)>
Callback);

void cursorInfo(
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
llvm::MemoryBuffer *completionBuffer, unsigned int Offset,
DiagnosticConsumer *DiagC,
std::shared_ptr<std::atomic<bool>> CancellationFlag,
llvm::function_ref<void(CancellableResult<CursorInfoResults>)> Callback);
};

} // namespace ide
Expand Down
2 changes: 1 addition & 1 deletion include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class Parser {
}

bool isCodeCompletionFirstPass() const {
return L->isCodeCompletion() && !CodeCompletion;
return SourceMgr.hasCodeCompletionBuffer() && !CodeCompletion;
}

bool allowTopLevelCode() const;
Expand Down
1 change: 1 addition & 0 deletions lib/IDE/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ add_swift_host_library(swiftIDE STATIC
CompletionLookup.cpp
CompletionOverrideLookup.cpp
ConformingMethodList.cpp
CursorInfo.cpp
ExprCompletion.cpp
ExprContextAnalysis.cpp
Formatting.cpp
Expand Down
278 changes: 278 additions & 0 deletions lib/IDE/CursorInfo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
//===--- CursorInfo.cpp ---------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "swift/IDE/CursorInfo.h"
#include "ExprContextAnalysis.h"
#include "swift/AST/ASTDemangler.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/USRGeneration.h"
#include "swift/IDE/TypeCheckCompletionCallback.h"
#include "swift/Parse/CodeCompletionCallbacks.h"
#include "swift/Sema/ConstraintSystem.h"
#include "swift/Sema/IDETypeChecking.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/Module.h"

using namespace swift;
using namespace swift::constraints;
using namespace ide;

namespace {

// MARK: - Utilities

void typeCheckDeclAndParentClosures(ValueDecl *VD) {
// We need to type check any parent closures because their types are
// encoded in the USR of ParentContexts in the cursor info response.
auto DC = VD->getDeclContext();
while (DC->getParent()) {
if (auto Closure = dyn_cast<AbstractClosureExpr>(DC)) {
if (Closure->getType().isNull()) {
typeCheckASTNodeAtLoc(
TypeCheckASTNodeAtLocContext::declContext(DC->getParent()),
Closure->getLoc());
}
}
DC = DC->getParent();
}

typeCheckASTNodeAtLoc(
TypeCheckASTNodeAtLocContext::declContext(VD->getDeclContext()),
VD->getLoc());
}

// MARK: - NodeFinderResults

enum class NodeFinderResultKind { Decl };

class NodeFinderResult {
NodeFinderResultKind Kind;

protected:
NodeFinderResult(NodeFinderResultKind Kind) : Kind(Kind) {}

public:
NodeFinderResultKind getKind() const { return Kind; }
};

class NodeFinderDeclResult : public NodeFinderResult {
ValueDecl *ValueD;

public:
NodeFinderDeclResult(ValueDecl *ValueD)
: NodeFinderResult(NodeFinderResultKind::Decl), ValueD(ValueD) {}

ValueDecl *getDecl() const { return ValueD; }

static bool classof(const NodeFinderResult *Res) {
return Res->getKind() == NodeFinderResultKind::Decl;
}
};

// MARK: - NodeFinder

/// Walks the AST, looking for a node at \c LocToResolve. While walking the
/// AST, also gathers information about shorthand shadows.
class NodeFinder : ASTWalker {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel we have too many similar "finder" like this. E.g. ContextFinder in Refactoring.cpp. It'd be great we can consolidate them. But that doesn't block this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll see what I can do in a follow-up PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, IMO ideally we'd do what Clang does here - ie. build up a hierarchy to the deepest node and pass that around everywhere we need it (rather than continually performing the same ASTWalk skipping outside-of-loc ranges in basically every refactoring + cursor info).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I filed rdar://103178329 so we don’t forget about it and hope that I’ll get to it at some point.

SourceFile &SrcFile;
SourceLoc LocToResolve;

/// As we are walking the tree, this variable is updated to the last seen
/// DeclContext.
SmallVector<DeclContext *> DeclContextStack;

/// The found node.
std::unique_ptr<NodeFinderResult> Result;

/// If a decl shadows another decl using shorthand syntax (`[foo]` or
/// `if let foo {`), this maps the re-declared variable to the one that is
/// being shadowed.
/// The transitive closure of shorthand shadowed decls should be reported as
/// additional results in cursor info.
llvm::DenseMap<ValueDecl *, ValueDecl *> ShorthandShadowedDecls;

public:
NodeFinder(SourceFile &SrcFile, SourceLoc LocToResolve)
: SrcFile(SrcFile), LocToResolve(LocToResolve),
DeclContextStack({&SrcFile}) {}

void resolve() { SrcFile.walk(*this); }

std::unique_ptr<NodeFinderResult> takeResult() { return std::move(Result); }

/// Get the declarations that \p ShadowingDecl shadows using shorthand shadow
/// syntax.
SmallVector<ValueDecl *, 2>
getShorthandShadowedDecls(ValueDecl *ShadowingDecl) {
SmallVector<ValueDecl *, 2> Result;
auto ShorthandShadowedDecl = ShorthandShadowedDecls[ShadowingDecl];
while (ShorthandShadowedDecl) {
Result.push_back(ShorthandShadowedDecl);
ShorthandShadowedDecl = ShorthandShadowedDecls[ShorthandShadowedDecl];
}
return Result;
}

private:
SourceManager &getSourceMgr() const {
return SrcFile.getASTContext().SourceMgr;
}

/// The decl context that is currently being walked.
DeclContext *getCurrentDeclContext() { return DeclContextStack.back(); }

bool rangeContainsLocToResolve(SourceRange Range) const {
return Range.contains(LocToResolve);
}

PreWalkAction walkToDeclPre(Decl *D) override {
if (!rangeContainsLocToResolve(D->getSourceRangeIncludingAttrs())) {
return PreWalkAction::SkipChildren;
}

if (auto *newDC = dyn_cast<DeclContext>(D)) {
DeclContextStack.push_back(newDC);
}

if (D->getLoc() != LocToResolve) {
return Action::Continue();
}

if (auto VD = dyn_cast<ValueDecl>(D)) {
if (VD->hasName()) {
assert(Result == nullptr);
Result = std::make_unique<NodeFinderDeclResult>(VD);
return Action::Stop();
}
}

return Action::Continue();
}

PostWalkAction walkToDeclPost(Decl *D) override {
if (auto *newDC = dyn_cast<DeclContext>(D)) {
assert(DeclContextStack.back() == newDC);
DeclContextStack.pop_back();
}
return Action::Continue();
}

PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
if (auto closure = dyn_cast<ClosureExpr>(E)) {
DeclContextStack.push_back(closure);
}

if (auto CaptureList = dyn_cast<CaptureListExpr>(E)) {
for (auto ShorthandShadows :
getShorthandShadows(CaptureList, getCurrentDeclContext())) {
assert(ShorthandShadowedDecls.count(ShorthandShadows.first) == 0);
ShorthandShadowedDecls[ShorthandShadows.first] =
ShorthandShadows.second;
}
}

return Action::Continue(E);
}

PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
if (auto *closure = dyn_cast<ClosureExpr>(E)) {
assert(DeclContextStack.back() == closure);
DeclContextStack.pop_back();
}
return Action::Continue(E);
}

PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
if (auto CondStmt = dyn_cast<LabeledConditionalStmt>(S)) {
for (auto ShorthandShadow :
getShorthandShadows(CondStmt, getCurrentDeclContext())) {
assert(ShorthandShadowedDecls.count(ShorthandShadow.first) == 0);
ShorthandShadowedDecls[ShorthandShadow.first] = ShorthandShadow.second;
}
}
return Action::Continue(S);
}
};

// MARK: - CursorInfoDoneParsingCallback

class CursorInfoDoneParsingCallback : public CodeCompletionCallbacks {
CursorInfoConsumer &Consumer;
SourceLoc RequestedLoc;

public:
CursorInfoDoneParsingCallback(Parser &P, CursorInfoConsumer &Consumer,
SourceLoc RequestedLoc)
: CodeCompletionCallbacks(P), Consumer(Consumer),
RequestedLoc(RequestedLoc) {}

std::unique_ptr<ResolvedCursorInfo>
getDeclResult(NodeFinderDeclResult *DeclResult, SourceFile *SrcFile,
NodeFinder &Finder) const {
typeCheckDeclAndParentClosures(DeclResult->getDecl());
auto CursorInfo = std::make_unique<ResolvedValueRefCursorInfo>(
ResolvedCursorInfo(SrcFile), DeclResult->getDecl(),
/*CtorTyRef=*/nullptr,
/*ExtTyRef=*/nullptr, /*IsRef=*/false, /*Ty=*/Type(),
/*ContainerType=*/Type());
CursorInfo->setLoc(RequestedLoc);
CursorInfo->setShorthandShadowedDecls(
Finder.getShorthandShadowedDecls(DeclResult->getDecl()));
return CursorInfo;
}

void doneParsing(SourceFile *SrcFile) override {
if (!SrcFile) {
return;
}
NodeFinder Finder(*SrcFile, RequestedLoc);
Finder.resolve();
auto Result = Finder.takeResult();
if (!Result) {
return;
}
std::unique_ptr<ResolvedCursorInfo> CursorInfo;
switch (Result->getKind()) {
case NodeFinderResultKind::Decl:
CursorInfo = getDeclResult(cast<NodeFinderDeclResult>(Result.get()),
SrcFile, Finder);
break;
}
if (Result) {
Consumer.handleResults(*CursorInfo);
}
}
};

} // anonymous namespace.

CodeCompletionCallbacksFactory *
swift::ide::makeCursorInfoCallbacksFactory(CursorInfoConsumer &Consumer,
SourceLoc RequestedLoc) {
class CursorInfoCallbacksFactoryImpl : public CodeCompletionCallbacksFactory {
CursorInfoConsumer &Consumer;
SourceLoc RequestedLoc;

public:
CursorInfoCallbacksFactoryImpl(CursorInfoConsumer &Consumer,
SourceLoc RequestedLoc)
: Consumer(Consumer), RequestedLoc(RequestedLoc) {}

CodeCompletionCallbacks *createCodeCompletionCallbacks(Parser &P) override {
return new CursorInfoDoneParsingCallback(P, Consumer, RequestedLoc);
}
};

return new CursorInfoCallbacksFactoryImpl(Consumer, RequestedLoc);
}
Loading