Skip to content
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
85 changes: 85 additions & 0 deletions include/swift/IDE/SignatureHelpFormatter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//===--- SignatureHelpFormatter.h --- ---------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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_SIGNATURE_HELP_FORMATTER_H
#define SWIFT_IDE_SIGNATURE_HELP_FORMATTER_H

#include "swift/IDE/SignatureHelp.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Allocator.h"

namespace swift {

class DeclContext;

namespace ide {

class CodeCompletionString;

struct FormattedSignatureHelp {
struct Parameter {
/// The offset of the parameter text in the signature text.
unsigned Offset;

/// The length of the parameter text in the signature text.
unsigned Length;

/// The internal parameter name.
llvm::StringRef Name;

Parameter() {}
Comment on lines +30 to +40
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 removed the DocComment field since we now rely on SourceKit-LSP to parse the signature's documentation and extract the parameter documentation.

};

struct Signature {
llvm::StringRef Text;
llvm::StringRef DocComment;
std::optional<unsigned> ActiveParam;
llvm::ArrayRef<Parameter> Params;

Signature(llvm::StringRef Text, llvm::StringRef DocComment,
std::optional<unsigned> ActiveParam,
llvm::ArrayRef<Parameter> Params)
: Text(Text), DocComment(DocComment), ActiveParam(ActiveParam),
Params(Params) {}
};

llvm::ArrayRef<Signature> Signatures;
unsigned ActiveSignature;

FormattedSignatureHelp(llvm::ArrayRef<Signature> Signatures,
unsigned ActiveSignature)
: Signatures(Signatures), ActiveSignature(ActiveSignature) {}
};

class SignatureHelpFormatter {
private:
llvm::BumpPtrAllocator &Allocator;
Copy link
Member Author

Choose a reason for hiding this comment

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

SignatureHelpFormatter doesn't own its allocator to make the lifetime of the results it returns more explicit to the caller.


public:
SignatureHelpFormatter(llvm::BumpPtrAllocator &Allocator)
: Allocator(Allocator) {}

FormattedSignatureHelp format(ide::SignatureHelpResult Result);

private:
FormattedSignatureHelp::Signature
formatSignature(const DeclContext *DC, const ide::Signature &Signature);

CodeCompletionString *createSignatureString(const ide::Signature &Signature,
const DeclContext *DC);
};

} // namespace ide
} // namespace swift

#endif // SWIFT_IDE_SIGNATURE_HELP_FORMATTER_H
1 change: 1 addition & 0 deletions lib/IDE/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ add_swift_host_library(swiftIDE STATIC
CompletionOverrideLookup.cpp
ConformingMethodList.cpp
SignatureHelp.cpp
SignatureHelpFormatter.cpp
CursorInfo.cpp
ExprCompletion.cpp
ExprContextAnalysis.cpp
Expand Down
2 changes: 1 addition & 1 deletion lib/IDE/CodeCompletionResultBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
#ifndef SWIFT_LIB_IDE_CODE_COMPLETION_RESULT_BUILDER_H
#define SWIFT_LIB_IDE_CODE_COMPLETION_RESULT_BUILDER_H

#include "CodeCompletionStringBuilder.h"
#include "swift/AST/Types.h"
#include "swift/Basic/LLVM.h"
#include "swift/IDE/CodeCompletionResult.h"
#include "swift/IDE/CodeCompletionResultSink.h"
#include "swift/IDE/CodeCompletionStringBuilder.h"

namespace clang {
class Module;
Expand Down
2 changes: 1 addition & 1 deletion lib/IDE/CodeCompletionStringBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "swift/IDE/CodeCompletionStringBuilder.h"
#include "CodeCompletionStringBuilder.h"
#include "swift/AST/ASTContext.h"
#include "swift/AST/Decl.h"
#include "swift/AST/GenericEnvironment.h"
Expand Down
2 changes: 1 addition & 1 deletion lib/IDE/CodeCompletionStringPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
//===----------------------------------------------------------------------===//

#include "swift/IDE/CodeCompletionStringPrinter.h"
#include "CodeCompletionStringBuilder.h"
#include "swift/AST/Module.h"
#include "swift/Basic/Assertions.h"
#include "swift/IDE/CodeCompletionStringBuilder.h"

using namespace swift;
using namespace swift::ide;
Expand Down
204 changes: 204 additions & 0 deletions lib/IDE/SignatureHelpFormatter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
//===--- SignatureHelpFormatter.cpp --- -------------------------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2025 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/SignatureHelpFormatter.h"
#include "CodeCompletionStringBuilder.h"
#include "swift/AST/ParameterList.h"
#include "swift/IDE/CommentConversion.h"

using namespace swift;
using namespace swift::ide;

using ChunkKind = CodeCompletionString::Chunk::ChunkKind;

/// \returns Array of parameters of \p VD accounting for implicitly curried
/// instance methods.
static ArrayRef<const ParamDecl *>
getParameterArray(const ValueDecl *VD, bool IsImplicitlyCurried,
const ParamDecl *&Scratch) {
if (!VD)
return {};

if (IsImplicitlyCurried) {
auto *FD = dyn_cast<AbstractFunctionDecl>(VD);
assert(FD && FD->hasImplicitSelfDecl());

Scratch = FD->getImplicitSelfDecl();
return ArrayRef(&Scratch, 1);
}

if (auto *ParamList = VD->getParameterList())
return ParamList->getArray();

return {};
}

static StringRef copyAndClearString(llvm::BumpPtrAllocator &Allocator,
SmallVectorImpl<char> &Str) {
auto Ref = StringRef(Str.data(), Str.size()).copy(Allocator);
Str.clear();
return Ref;
}

CodeCompletionString *
SignatureHelpFormatter::createSignatureString(const ide::Signature &Signature,
const DeclContext *DC) {
ValueDecl *FD = Signature.FuncD;
AnyFunctionType *AFT = Signature.FuncTy;

GenericSignature GenericSig;
if (FD) {
if (auto *GC = FD->getAsGenericContext())
GenericSig = GC->getGenericSignature();
}

CodeCompletionStringBuilder StringBuilder(
Allocator, /*AnnotateResults=*/false,
/*UnderscoreEmptyArgumentLabel=*/!Signature.IsSubscript,
/*FullParameterFlags=*/true);

DeclBaseName BaseName;

if (!Signature.IsSecondApply && FD) {
BaseName = FD->getBaseName();
} else if (Signature.IsSubscript) {
BaseName = DeclBaseName::createSubscript();
}

if (!BaseName.empty())
StringBuilder.addValueBaseName(BaseName,
/*IsMember=*/bool(Signature.BaseType));

StringBuilder.addLeftParen();

const ParamDecl *ParamScratch;
StringBuilder.addCallArgumentPatterns(
AFT->getParams(),
getParameterArray(FD, Signature.IsImplicitlyCurried, ParamScratch), DC,
GenericSig, DefaultArgumentOutputMode::All,
/*includeDefaultValues=*/true);

StringBuilder.addRightParen();

if (!Signature.IsImplicitlyCurried) {
if (Signature.IsSecondApply) {
// For a second apply, we don't pass the declaration to avoid adding
// incorrect rethrows and reasync which are only usable in a single apply.
StringBuilder.addEffectsSpecifiers(AFT, /*AFD=*/nullptr);
} else {
StringBuilder.addEffectsSpecifiers(
AFT, dyn_cast_or_null<AbstractFunctionDecl>(FD));
}
}

if (FD && FD->isImplicitlyUnwrappedOptional()) {
StringBuilder.addTypeAnnotationForImplicitlyUnwrappedOptional(
AFT->getResult(), DC, GenericSig);
} else {
StringBuilder.addTypeAnnotation(AFT->getResult(), DC, GenericSig);
}

return StringBuilder.createCompletionString();
}

FormattedSignatureHelp::Signature
SignatureHelpFormatter::formatSignature(const DeclContext *DC,
const ide::Signature &Signature) {
auto *FD = Signature.FuncD;
auto *AFT = Signature.FuncTy;

bool IsConstructor = isa_and_nonnull<ConstructorDecl>(FD);

auto *SignatureString = createSignatureString(Signature, DC);

llvm::SmallString<512> SS;
llvm::raw_svector_ostream OS(SS);

bool SkipResult = AFT->getResult()->isVoid() || IsConstructor;

SmallVector<FormattedSignatureHelp::Parameter, 8> FormattedParams;

auto Chunks = SignatureString->getChunks();
auto C = Chunks.begin();
while (C != Chunks.end()) {
if (C->is(ChunkKind::TypeAnnotation) && SkipResult) {
++C;
continue;
}

if (C->is(ChunkKind::TypeAnnotation))
OS << " -> ";

if (C->is(ChunkKind::CallArgumentBegin)) {
unsigned NestingLevel = C->getNestingLevel();
++C;

auto &P = FormattedParams.emplace_back();
P.Offset = SS.size();

do {
if (!C->is(ChunkKind::CallArgumentClosureType) && C->hasText())
OS << C->getText();

++C;
} while (C != Chunks.end() && !C->endsPreviousNestedGroup(NestingLevel));

P.Length = SS.size() - P.Offset;
continue;
}

if (C->hasText())
OS << C->getText();

++C;
}

StringRef SignatureText = copyAndClearString(Allocator, SS);

// Parameter names.
const ParamDecl *ParamScratch;
auto ParamDecls =
getParameterArray(FD, Signature.IsImplicitlyCurried, ParamScratch);

if (!ParamDecls.empty()) {
for (unsigned i = 0; i < FormattedParams.size(); ++i) {
FormattedParams[i].Name = ParamDecls[i]->getParameterName().str();
}
}

// Documentation.
StringRef DocComment;
if (FD) {
ide::getRawDocumentationComment(FD, OS);
DocComment = copyAndClearString(Allocator, SS);
}

return FormattedSignatureHelp::Signature(
SignatureText, DocComment, Signature.ParamIdx,
ArrayRef(FormattedParams).copy(Allocator));
}

FormattedSignatureHelp
SignatureHelpFormatter::format(SignatureHelpResult Result) {
SmallVector<FormattedSignatureHelp::Signature, 8> FormattedSignatures;
FormattedSignatures.reserve(Result.Signatures.size());

for (auto &Signature : Result.Signatures) {
FormattedSignatures.push_back(formatSignature(Result.DC, Signature));
}

// FIXME: Ideally we should select an active signature based on the context.
unsigned ActiveSignature = 0;

return FormattedSignatureHelp(ArrayRef(FormattedSignatures).copy(Allocator),
ActiveSignature);
}
7 changes: 7 additions & 0 deletions test/IDE/signature_help_closure_param.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// RUN: %target-swift-ide-test -signature-help -code-completion-token=CLOSURE_PARAM -source-filename=%s | %FileCheck %s --check-prefix=CLOSURE_PARAM

func apply<Value, Result>(value: Value, body: (Value) -> Result) -> Result {
return body(#^CLOSURE_PARAM^#)
// CLOSURE_PARAM: Begin signatures, 1 items
// CLOSURE_PARAM-DAG: Signature[Active]: body(<param active>Value</param>) -> Result
}
Loading