Skip to content

Commit a7dd04e

Browse files
authored
Merge pull request #84403 from a7medev/refactor/signature-help-to-ide
[IDE] Move signature help formatting to IDE instead of SourceKit
2 parents de51b2e + 456a40a commit a7dd04e

38 files changed

+825
-1483
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//===--- SignatureHelpFormatter.h --- ---------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#ifndef SWIFT_IDE_SIGNATURE_HELP_FORMATTER_H
14+
#define SWIFT_IDE_SIGNATURE_HELP_FORMATTER_H
15+
16+
#include "swift/IDE/SignatureHelp.h"
17+
#include "llvm/ADT/ArrayRef.h"
18+
#include "llvm/ADT/StringRef.h"
19+
#include "llvm/Support/Allocator.h"
20+
21+
namespace swift {
22+
23+
class DeclContext;
24+
25+
namespace ide {
26+
27+
class CodeCompletionString;
28+
29+
struct FormattedSignatureHelp {
30+
struct Parameter {
31+
/// The offset of the parameter text in the signature text.
32+
unsigned Offset;
33+
34+
/// The length of the parameter text in the signature text.
35+
unsigned Length;
36+
37+
/// The internal parameter name.
38+
llvm::StringRef Name;
39+
40+
Parameter() {}
41+
};
42+
43+
struct Signature {
44+
llvm::StringRef Text;
45+
llvm::StringRef DocComment;
46+
std::optional<unsigned> ActiveParam;
47+
llvm::ArrayRef<Parameter> Params;
48+
49+
Signature(llvm::StringRef Text, llvm::StringRef DocComment,
50+
std::optional<unsigned> ActiveParam,
51+
llvm::ArrayRef<Parameter> Params)
52+
: Text(Text), DocComment(DocComment), ActiveParam(ActiveParam),
53+
Params(Params) {}
54+
};
55+
56+
llvm::ArrayRef<Signature> Signatures;
57+
unsigned ActiveSignature;
58+
59+
FormattedSignatureHelp(llvm::ArrayRef<Signature> Signatures,
60+
unsigned ActiveSignature)
61+
: Signatures(Signatures), ActiveSignature(ActiveSignature) {}
62+
};
63+
64+
class SignatureHelpFormatter {
65+
private:
66+
llvm::BumpPtrAllocator &Allocator;
67+
68+
public:
69+
SignatureHelpFormatter(llvm::BumpPtrAllocator &Allocator)
70+
: Allocator(Allocator) {}
71+
72+
FormattedSignatureHelp format(ide::SignatureHelpResult Result);
73+
74+
private:
75+
FormattedSignatureHelp::Signature
76+
formatSignature(const DeclContext *DC, const ide::Signature &Signature);
77+
78+
CodeCompletionString *createSignatureString(const ide::Signature &Signature,
79+
const DeclContext *DC);
80+
};
81+
82+
} // namespace ide
83+
} // namespace swift
84+
85+
#endif // SWIFT_IDE_SIGNATURE_HELP_FORMATTER_H

lib/IDE/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_swift_host_library(swiftIDE STATIC
1919
CompletionOverrideLookup.cpp
2020
ConformingMethodList.cpp
2121
SignatureHelp.cpp
22+
SignatureHelpFormatter.cpp
2223
CursorInfo.cpp
2324
ExprCompletion.cpp
2425
ExprContextAnalysis.cpp

lib/IDE/CodeCompletionResultBuilder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
#ifndef SWIFT_LIB_IDE_CODE_COMPLETION_RESULT_BUILDER_H
1414
#define SWIFT_LIB_IDE_CODE_COMPLETION_RESULT_BUILDER_H
1515

16+
#include "CodeCompletionStringBuilder.h"
1617
#include "swift/AST/Types.h"
1718
#include "swift/Basic/LLVM.h"
1819
#include "swift/IDE/CodeCompletionResult.h"
1920
#include "swift/IDE/CodeCompletionResultSink.h"
20-
#include "swift/IDE/CodeCompletionStringBuilder.h"
2121

2222
namespace clang {
2323
class Module;

lib/IDE/CodeCompletionStringBuilder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
#include "swift/IDE/CodeCompletionStringBuilder.h"
13+
#include "CodeCompletionStringBuilder.h"
1414
#include "swift/AST/ASTContext.h"
1515
#include "swift/AST/Decl.h"
1616
#include "swift/AST/GenericEnvironment.h"
File renamed without changes.

lib/IDE/CodeCompletionStringPrinter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/IDE/CodeCompletionStringPrinter.h"
14+
#include "CodeCompletionStringBuilder.h"
1415
#include "swift/AST/Module.h"
1516
#include "swift/Basic/Assertions.h"
16-
#include "swift/IDE/CodeCompletionStringBuilder.h"
1717

1818
using namespace swift;
1919
using namespace swift::ide;

lib/IDE/SignatureHelpFormatter.cpp

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
//===--- SignatureHelpFormatter.cpp --- -------------------------*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "swift/IDE/SignatureHelpFormatter.h"
14+
#include "CodeCompletionStringBuilder.h"
15+
#include "swift/AST/ParameterList.h"
16+
#include "swift/IDE/CommentConversion.h"
17+
18+
using namespace swift;
19+
using namespace swift::ide;
20+
21+
using ChunkKind = CodeCompletionString::Chunk::ChunkKind;
22+
23+
/// \returns Array of parameters of \p VD accounting for implicitly curried
24+
/// instance methods.
25+
static ArrayRef<const ParamDecl *>
26+
getParameterArray(const ValueDecl *VD, bool IsImplicitlyCurried,
27+
const ParamDecl *&Scratch) {
28+
if (!VD)
29+
return {};
30+
31+
if (IsImplicitlyCurried) {
32+
auto *FD = dyn_cast<AbstractFunctionDecl>(VD);
33+
assert(FD && FD->hasImplicitSelfDecl());
34+
35+
Scratch = FD->getImplicitSelfDecl();
36+
return ArrayRef(&Scratch, 1);
37+
}
38+
39+
if (auto *ParamList = VD->getParameterList())
40+
return ParamList->getArray();
41+
42+
return {};
43+
}
44+
45+
static StringRef copyAndClearString(llvm::BumpPtrAllocator &Allocator,
46+
SmallVectorImpl<char> &Str) {
47+
auto Ref = StringRef(Str.data(), Str.size()).copy(Allocator);
48+
Str.clear();
49+
return Ref;
50+
}
51+
52+
CodeCompletionString *
53+
SignatureHelpFormatter::createSignatureString(const ide::Signature &Signature,
54+
const DeclContext *DC) {
55+
ValueDecl *FD = Signature.FuncD;
56+
AnyFunctionType *AFT = Signature.FuncTy;
57+
58+
GenericSignature GenericSig;
59+
if (FD) {
60+
if (auto *GC = FD->getAsGenericContext())
61+
GenericSig = GC->getGenericSignature();
62+
}
63+
64+
CodeCompletionStringBuilder StringBuilder(
65+
Allocator, /*AnnotateResults=*/false,
66+
/*UnderscoreEmptyArgumentLabel=*/!Signature.IsSubscript,
67+
/*FullParameterFlags=*/true);
68+
69+
DeclBaseName BaseName;
70+
71+
if (!Signature.IsSecondApply && FD) {
72+
BaseName = FD->getBaseName();
73+
} else if (Signature.IsSubscript) {
74+
BaseName = DeclBaseName::createSubscript();
75+
}
76+
77+
if (!BaseName.empty())
78+
StringBuilder.addValueBaseName(BaseName,
79+
/*IsMember=*/bool(Signature.BaseType));
80+
81+
StringBuilder.addLeftParen();
82+
83+
const ParamDecl *ParamScratch;
84+
StringBuilder.addCallArgumentPatterns(
85+
AFT->getParams(),
86+
getParameterArray(FD, Signature.IsImplicitlyCurried, ParamScratch), DC,
87+
GenericSig, DefaultArgumentOutputMode::All,
88+
/*includeDefaultValues=*/true);
89+
90+
StringBuilder.addRightParen();
91+
92+
if (!Signature.IsImplicitlyCurried) {
93+
if (Signature.IsSecondApply) {
94+
// For a second apply, we don't pass the declaration to avoid adding
95+
// incorrect rethrows and reasync which are only usable in a single apply.
96+
StringBuilder.addEffectsSpecifiers(AFT, /*AFD=*/nullptr);
97+
} else {
98+
StringBuilder.addEffectsSpecifiers(
99+
AFT, dyn_cast_or_null<AbstractFunctionDecl>(FD));
100+
}
101+
}
102+
103+
if (FD && FD->isImplicitlyUnwrappedOptional()) {
104+
StringBuilder.addTypeAnnotationForImplicitlyUnwrappedOptional(
105+
AFT->getResult(), DC, GenericSig);
106+
} else {
107+
StringBuilder.addTypeAnnotation(AFT->getResult(), DC, GenericSig);
108+
}
109+
110+
return StringBuilder.createCompletionString();
111+
}
112+
113+
FormattedSignatureHelp::Signature
114+
SignatureHelpFormatter::formatSignature(const DeclContext *DC,
115+
const ide::Signature &Signature) {
116+
auto *FD = Signature.FuncD;
117+
auto *AFT = Signature.FuncTy;
118+
119+
bool IsConstructor = isa_and_nonnull<ConstructorDecl>(FD);
120+
121+
auto *SignatureString = createSignatureString(Signature, DC);
122+
123+
llvm::SmallString<512> SS;
124+
llvm::raw_svector_ostream OS(SS);
125+
126+
bool SkipResult = AFT->getResult()->isVoid() || IsConstructor;
127+
128+
SmallVector<FormattedSignatureHelp::Parameter, 8> FormattedParams;
129+
130+
auto Chunks = SignatureString->getChunks();
131+
auto C = Chunks.begin();
132+
while (C != Chunks.end()) {
133+
if (C->is(ChunkKind::TypeAnnotation) && SkipResult) {
134+
++C;
135+
continue;
136+
}
137+
138+
if (C->is(ChunkKind::TypeAnnotation))
139+
OS << " -> ";
140+
141+
if (C->is(ChunkKind::CallArgumentBegin)) {
142+
unsigned NestingLevel = C->getNestingLevel();
143+
++C;
144+
145+
auto &P = FormattedParams.emplace_back();
146+
P.Offset = SS.size();
147+
148+
do {
149+
if (!C->is(ChunkKind::CallArgumentClosureType) && C->hasText())
150+
OS << C->getText();
151+
152+
++C;
153+
} while (C != Chunks.end() && !C->endsPreviousNestedGroup(NestingLevel));
154+
155+
P.Length = SS.size() - P.Offset;
156+
continue;
157+
}
158+
159+
if (C->hasText())
160+
OS << C->getText();
161+
162+
++C;
163+
}
164+
165+
StringRef SignatureText = copyAndClearString(Allocator, SS);
166+
167+
// Parameter names.
168+
const ParamDecl *ParamScratch;
169+
auto ParamDecls =
170+
getParameterArray(FD, Signature.IsImplicitlyCurried, ParamScratch);
171+
172+
if (!ParamDecls.empty()) {
173+
for (unsigned i = 0; i < FormattedParams.size(); ++i) {
174+
FormattedParams[i].Name = ParamDecls[i]->getParameterName().str();
175+
}
176+
}
177+
178+
// Documentation.
179+
StringRef DocComment;
180+
if (FD) {
181+
ide::getRawDocumentationComment(FD, OS);
182+
DocComment = copyAndClearString(Allocator, SS);
183+
}
184+
185+
return FormattedSignatureHelp::Signature(
186+
SignatureText, DocComment, Signature.ParamIdx,
187+
ArrayRef(FormattedParams).copy(Allocator));
188+
}
189+
190+
FormattedSignatureHelp
191+
SignatureHelpFormatter::format(SignatureHelpResult Result) {
192+
SmallVector<FormattedSignatureHelp::Signature, 8> FormattedSignatures;
193+
FormattedSignatures.reserve(Result.Signatures.size());
194+
195+
for (auto &Signature : Result.Signatures) {
196+
FormattedSignatures.push_back(formatSignature(Result.DC, Signature));
197+
}
198+
199+
// FIXME: Ideally we should select an active signature based on the context.
200+
unsigned ActiveSignature = 0;
201+
202+
return FormattedSignatureHelp(ArrayRef(FormattedSignatures).copy(Allocator),
203+
ActiveSignature);
204+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// RUN: %target-swift-ide-test -signature-help -code-completion-token=CLOSURE_PARAM -source-filename=%s | %FileCheck %s --check-prefix=CLOSURE_PARAM
2+
3+
func apply<Value, Result>(value: Value, body: (Value) -> Result) -> Result {
4+
return body(#^CLOSURE_PARAM^#)
5+
// CLOSURE_PARAM: Begin signatures, 1 items
6+
// CLOSURE_PARAM-DAG: Signature[Active]: body(<param active>Value</param>) -> Result
7+
}

0 commit comments

Comments
 (0)