Files
swift-mirror/lib/IDE/CodeCompletionResultBuilder.cpp
Ahmed Elrefaey 1bc96857a8 Merge pull request #82464 from a7medev/feat/full-documentation-in-code-completion
[IDE] Add full documentation to code completion result
2025-09-04 10:06:21 +01:00

303 lines
10 KiB
C++

//===--- CodeCompletionResultBuilder.cpp ----------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 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 "CodeCompletionResultBuilder.h"
#include "CodeCompletionDiagnostics.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTDemangler.h"
#include "swift/AST/USRGeneration.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/LLVM.h"
#include "swift/IDE/CodeCompletionStringPrinter.h"
#include "swift/IDE/CommentConversion.h"
#include "swift/IDE/Utils.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Comment.h"
#include "clang/Basic/Module.h"
#include "clang/Index/USRGeneration.h"
using namespace swift;
using namespace swift::ide;
static bool shouldCopyAssociatedUSRForDecl(const ValueDecl *VD) {
// Avoid trying to generate a USR for some declaration types.
if (isa<GenericTypeParamDecl>(VD))
return false;
if (isa<ParamDecl>(VD))
return false;
if (isa<ModuleDecl>(VD))
return false;
if (VD->hasClangNode() && !VD->getClangDecl())
return false;
// Avoid generating USRs for decls in local contexts, we cannot guarantee
// any parent closures will be type-checked, which is needed for mangling.
if (VD->getDeclContext()->getLocalContext())
return false;
return true;
}
template <typename FnTy>
static void walkValueDeclAndOverriddenDecls(const Decl *D, const FnTy &Fn) {
if (auto *VD = dyn_cast<ValueDecl>(D)) {
Fn(VD);
walkOverriddenDecls(VD, Fn);
}
}
static ArrayRef<NullTerminatedStringRef>
copyAssociatedUSRs(llvm::BumpPtrAllocator &Allocator, const Decl *D) {
llvm::SmallVector<NullTerminatedStringRef, 4> USRs;
walkValueDeclAndOverriddenDecls(
D,
[&](llvm::PointerUnion<const ValueDecl *, const clang::NamedDecl *> OD) {
llvm::SmallString<128> SS;
bool Ignored = true;
if (auto *OVD = OD.dyn_cast<const ValueDecl *>()) {
if (shouldCopyAssociatedUSRForDecl(OVD)) {
llvm::raw_svector_ostream OS(SS);
Ignored = printValueDeclUSR(OVD, OS);
}
} else if (auto *OND = OD.dyn_cast<const clang::NamedDecl *>()) {
Ignored = clang::index::generateUSRForDecl(OND, SS);
}
if (!Ignored)
USRs.emplace_back(SS, Allocator);
});
if (!USRs.empty())
return llvm::ArrayRef(USRs).copy(Allocator);
return {};
}
static NullTerminatedStringRef copySwiftUSR(llvm::BumpPtrAllocator &Allocator,
const Decl *D) {
auto *VD = dyn_cast<ValueDecl>(D);
if (!VD || !shouldCopyAssociatedUSRForDecl(VD))
return NullTerminatedStringRef();
SmallString<128> SS;
llvm::raw_svector_ostream OS(SS);
if (!ide::printValueDeclSwiftUSR(VD, OS))
return NullTerminatedStringRef(SS, Allocator);
return NullTerminatedStringRef();
}
/// Tries to reconstruct the provided \p D declaration using \c
/// Demangle::getDeclForUSR and verifies that the declarations match. This only
/// works if \p D is a \c ValueDecl and \c shouldCopyAssociatedUSRForDecl is
/// true.
///
/// This is intended for testing only.
static void verifyUSRToDeclReconstruction(const Decl *D) {
auto *VD = dyn_cast<ValueDecl>(D);
if (!VD)
return;
if (!shouldCopyAssociatedUSRForDecl(VD))
return;
SmallString<128> SwiftUSR;
llvm::raw_svector_ostream OS(SwiftUSR);
if (ide::printValueDeclSwiftUSR(VD, OS)) {
ABORT([&](auto &out) {
out << "Declaration should have a Swift USR:\n";
VD->dump(out);
});
}
auto &Ctx = VD->getASTContext();
auto *Reconstructed = Demangle::getDeclForUSR(Ctx, SwiftUSR);
if (!Reconstructed) {
ABORT([&](auto &out) {
out << "Reconstructed declaration shouldn't be null\n"
<< "Swift USR: " << SwiftUSR << ", original declaration:\n";
VD->dump(out);
});
}
if (Reconstructed != VD) {
ABORT([&](auto &out) {
out << "Reconstructed declaration should match the original one\n"
<< "Swift USR: " << SwiftUSR << "\n"
<< "Original declaration:\n";
VD->dump(out);
out << "Reconstructed declaration:\n";
Reconstructed->dump(out);
});
}
}
CodeCompletionResult *CodeCompletionResultBuilder::takeResult() {
auto &Allocator = *Sink.Allocator;
auto *CCS = createCompletionString();
if (Sink.verifyUSRToDecl && AssociatedDecl) {
verifyUSRToDeclReconstruction(AssociatedDecl);
}
CodeCompletionDiagnosticSeverity ContextFreeDiagnosticSeverity =
CodeCompletionDiagnosticSeverity::None;
NullTerminatedStringRef ContextFreeDiagnosticMessage;
if (ContextFreeNotRecReason != ContextFreeNotRecommendedReason::None) {
assert(AssociatedDecl != nullptr &&
"There should be no case where ContextFreeNotRecReason != None && "
"AssociatedDecl == nulptr");
// FIXME: We should generate the message lazily.
if (const auto *VD = dyn_cast_or_null<ValueDecl>(AssociatedDecl)) {
CodeCompletionDiagnosticSeverity severity;
SmallString<256> message;
llvm::raw_svector_ostream messageOS(message);
if (!getContextFreeCompletionDiagnostics(ContextFreeNotRecReason, VD,
severity, messageOS)) {
ContextFreeDiagnosticSeverity = severity;
ContextFreeDiagnosticMessage =
NullTerminatedStringRef(message, Allocator);
}
}
}
/// This variable should be initialized by all the switch cases below.
ContextFreeCodeCompletionResult *ContextFreeResult = nullptr;
switch (Kind) {
case CodeCompletionResultKind::Declaration: {
NullTerminatedStringRef ModuleName;
if (CurrentModule) {
if (Sink.LastModule.first == CurrentModule.getOpaqueValue()) {
ModuleName = Sink.LastModule.second;
} else {
if (auto *C = CurrentModule.dyn_cast<const clang::Module *>()) {
ModuleName =
NullTerminatedStringRef(C->getFullModuleName(), Allocator);
} else {
ModuleName = NullTerminatedStringRef(
cast<const swift::ModuleDecl *>(CurrentModule)->getName().str(),
Allocator);
}
Sink.LastModule.first = CurrentModule.getOpaqueValue();
Sink.LastModule.second = ModuleName;
}
}
ContextFreeResult = ContextFreeCodeCompletionResult::createDeclResult(
Sink, CCS, AssociatedDecl, HasAsyncAlternative, ModuleName,
NullTerminatedStringRef(BriefDocComment, Allocator),
copyAssociatedUSRs(Allocator, AssociatedDecl),
copySwiftUSR(Allocator, AssociatedDecl), ResultType,
ContextFreeNotRecReason, ContextFreeDiagnosticSeverity,
ContextFreeDiagnosticMessage);
break;
}
case CodeCompletionResultKind::Keyword:
ContextFreeResult = ContextFreeCodeCompletionResult::createKeywordResult(
Sink, KeywordKind, CCS,
NullTerminatedStringRef(BriefDocComment, Allocator), ResultType);
break;
case CodeCompletionResultKind::BuiltinOperator:
case CodeCompletionResultKind::Pattern:
ContextFreeResult =
ContextFreeCodeCompletionResult::createPatternOrBuiltInOperatorResult(
Sink, Kind, CCS, CodeCompletionOperatorKind::None,
NullTerminatedStringRef(BriefDocComment, Allocator), ResultType,
ContextFreeNotRecReason, ContextFreeDiagnosticSeverity,
ContextFreeDiagnosticMessage);
break;
case CodeCompletionResultKind::Literal:
assert(LiteralKind.has_value());
ContextFreeResult = ContextFreeCodeCompletionResult::createLiteralResult(
Sink, *LiteralKind, CCS, ResultType);
break;
}
if (Sink.shouldProduceContextFreeResults()) {
// If the sink only intends to store the context free results in the cache,
// we don't need to compute any contextual properties.
return new (Allocator) CodeCompletionResult(
*ContextFreeResult, AssociatedDecl, SemanticContextKind::None,
CodeCompletionFlair(), /*NumBytesToErase=*/0,
CodeCompletionResultTypeRelation::Unrelated,
ContextualNotRecommendedReason::None);
} else {
assert(
ContextFreeResult != nullptr &&
"ContextFreeResult should have been constructed by the switch above");
// We know that the ContextFreeResult has an AST-based type because it was
// just computed and not read from the cache and
// Sink.shouldProduceContextFreeResults() is false. So we can pass nullptr
// for USRTypeContext.
CodeCompletionResultTypeRelation typeRelation =
ContextFreeResult->calculateContextualTypeRelation(
DC, TypeContext, /*usrTypeContext=*/nullptr);
ContextualNotRecommendedReason notRecommendedReason =
ContextFreeResult->calculateContextualNotRecommendedReason(
ContextualNotRecReason, CanCurrDeclContextHandleAsync);
return new (Allocator) CodeCompletionResult(
*ContextFreeResult, AssociatedDecl, SemanticContext, Flair,
NumBytesToErase, typeRelation, notRecommendedReason);
}
}
void CodeCompletionResultBuilder::finishResult() {
if (!Cancelled)
Sink.Results.push_back(takeResult());
}
void CodeCompletionResultBuilder::setAssociatedDecl(const Decl *D) {
assert(Kind == CodeCompletionResultKind::Declaration);
AssociatedDecl = D;
if (auto *ClangD = D->getClangDecl())
CurrentModule = ClangD->getImportedOwningModule();
// FIXME: macros
// FIXME: imported header module
if (!CurrentModule) {
ModuleDecl *MD = D->getModuleContext();
// If this is an underscored cross-import overlay, map it to the underlying
// module that declares it instead.
if (ModuleDecl *Declaring = MD->getDeclaringModuleIfCrossImportOverlay())
MD = Declaring;
CurrentModule = MD;
}
if (D->isDeprecated())
setContextFreeNotRecommended(ContextFreeNotRecommendedReason::Deprecated);
else if (D->getSoftDeprecatedAttr())
setContextFreeNotRecommended(
ContextFreeNotRecommendedReason::SoftDeprecated);
if (D->getClangNode()) {
if (auto *ClangD = D->getClangDecl()) {
const auto &ClangContext = ClangD->getASTContext();
if (const clang::RawComment *RC =
ClangContext.getRawCommentForAnyRedecl(ClangD)) {
setBriefDocComment(RC->getBriefText(ClangContext));
}
}
} else {
setBriefDocComment(AssociatedDecl->getSemanticBriefComment());
}
}