Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7a4a974
[IDE] Add basic signature help request support
a7medev Mar 22, 2025
0e0c2ec
[Test] Add signature help to sourcekitd-test
a7medev Mar 22, 2025
1ce8e59
[IDE] Show signatures without a declaration
a7medev Jul 26, 2025
cdad5ff
[Test] Add basic signature help label tests
a7medev Jul 27, 2025
f9280f2
NFC: Apply clang-format to signature help changes
a7medev Jul 27, 2025
a15f45a
[IDE] Resolve function type for curried function calls in ArgumentTyp…
a7medev Aug 6, 2025
c986188
[IDE] Return whether a func is implicitly curried instance method in …
a7medev Aug 6, 2025
e21f4aa
[IDE] Customize CodeCompletionStringBuilder for signature help
a7medev Aug 6, 2025
76918ee
[IDE] Use CodeCompletionStringBuilder in signature help
a7medev Aug 6, 2025
cd96add
[Test] Split signature help tests for currying
a7medev Aug 9, 2025
026e696
[IDE] NFC: Extract createSignatureLabel function & remove unused incl…
a7medev Aug 9, 2025
d00565b
[IDE] Output all parameters in signature help
a7medev Aug 9, 2025
86b3118
[IDE] Use FunctionRefInfo::ApplyLevel for curried functions in Argume…
a7medev Aug 11, 2025
07d4f23
[IDE] NFC: Forward declare Signature in ArgumentCompletion.h
a7medev Aug 11, 2025
154ca9d
[IDE] Use raw documentation in signature help
a7medev Aug 21, 2025
6945dc1
[IDE] Use editor documents infrastructure for signature help
a7medev Aug 21, 2025
5f42c37
[IDE] Signature help: Keep strings in Allocator instead of SmallString
a7medev Aug 21, 2025
b2a61d3
[IDE] Use keys.signatures instead of keys.members for signature help
a7medev Aug 21, 2025
5297c23
[Test] Add signature help doc comment test
a7medev Aug 22, 2025
bd2fdc0
[Test] Change signature help tests to use split-file
a7medev Aug 22, 2025
aabab5b
[IDE] Misc enhancements to signature help
a7medev Aug 26, 2025
6315e9a
[IDE] NFC: Wrap if-else in curly braces
a7medev Sep 3, 2025
e248a22
[Test] Avoid diff -B in signature help tests for Windows support
a7medev Sep 3, 2025
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
17 changes: 14 additions & 3 deletions include/swift/IDE/ArgumentCompletion.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
namespace swift {
namespace ide {

struct Signature;

class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
struct Result {
/// The type associated with the code completion expression itself.
Expand Down Expand Up @@ -75,6 +77,12 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
/// functions is supported.
bool IsInAsyncContext;

/// True if the function is an implicitly curried instance method.
bool IsImplicitlyCurried;

/// True if the call is the second apply of a double-applied function.
bool IsSecondApply;

/// A bitfield to mark whether the parameter at a given index is optional.
/// Parameters can be optional if they have a default argument or belong to
/// a parameter pack.
Expand Down Expand Up @@ -117,9 +125,12 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback {
/// \param IsLabeledTrailingClosure Whether we are completing the label of a
/// labeled trailing closure, ie. if the code completion location is outside
/// the call after the first trailing closure of the call.
void collectResults(bool IsLabeledTrailingClosure,
SourceLoc Loc, DeclContext *DC,
CodeCompletionContext &CompletionCtx);
void collectResults(bool IsLabeledTrailingClosure, SourceLoc Loc,
DeclContext *DC, CodeCompletionContext &CompletionCtx);

/// Collects non-shadowed signature results into \p Signatures
void getSignatures(SourceLoc Loc, DeclContext *DC,
SmallVectorImpl<Signature> &Signatures);
};

} // end namespace ide
Expand Down
35 changes: 30 additions & 5 deletions include/swift/IDE/CodeCompletionStringBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ Type eraseArchetypes(Type type, GenericSignature genericSig);

bool hasInterestingDefaultValue(const ParamDecl *param);

/// Modes for printing arguments with default values in
/// \c CodeCompletionStringBuilder::addCallArgumentPatterns
enum class DefaultArgumentOutputMode {
/// Don't output any argument that has a default value.
None,
/// Output arguments with interesting default values only.
/// \sa hasInterestingDefaultValue
Interesting,
/// Output all arguments with default values (even non-interesting ones).
All
};

class CodeCompletionStringBuilder {
friend CodeCompletionStringPrinter;

Expand All @@ -39,11 +51,17 @@ class CodeCompletionStringBuilder {
SmallVector<CodeCompletionString::Chunk, 4> Chunks;

bool AnnotateResults;
bool UnderscoreEmptyArgumentLabel;
bool FullParameterFlags;

public:
CodeCompletionStringBuilder(llvm::BumpPtrAllocator &Allocator,
bool AnnotateResults)
: Allocator(Allocator), AnnotateResults(AnnotateResults) {}
bool AnnotateResults = false,
bool UnderscoreEmptyArgumentLabel = false,
bool FullParameterFlags = false)
: Allocator(Allocator), AnnotateResults(AnnotateResults),
UnderscoreEmptyArgumentLabel(UnderscoreEmptyArgumentLabel),
FullParameterFlags(FullParameterFlags) {}

private:
void addChunkWithText(CodeCompletionString::Chunk::ChunkKind Kind,
Expand Down Expand Up @@ -316,7 +334,8 @@ class CodeCompletionStringBuilder {
void addCallArgument(Identifier Name, Identifier LocalName, Type Ty,
Type ContextTy, bool IsVarArg, bool IsInOut, bool IsIUO,
bool IsAutoClosure, bool IsLabeledTrailingClosure,
bool IsForOperator, bool HasDefault);
bool IsForOperator, bool HasDefault,
StringRef DefaultValue = {});

void addCallArgument(Identifier Name, Type Ty, Type ContextTy = Type(),
bool IsForOperator = false) {
Expand Down Expand Up @@ -383,15 +402,19 @@ class CodeCompletionStringBuilder {
ArrayRef<const ParamDecl *> declParams,
const DeclContext *DC,
GenericSignature genericSig,
bool includeDefaultArgs = true);
DefaultArgumentOutputMode defaultArgsMode =
DefaultArgumentOutputMode::Interesting,
bool includeDefaultValues = false);

/// Build argument patterns for calling. Returns \c true if any content was
/// added to \p Builder. If \p Params is non-nullptr, \F
bool addCallArgumentPatterns(const AnyFunctionType *AFT,
const ParameterList *Params,
const DeclContext *DC,
GenericSignature genericSig,
bool includeDefaultArgs = true);
DefaultArgumentOutputMode defaultArgsMode =
DefaultArgumentOutputMode::Interesting,
bool includeDefaultValues = false);

void addTypeAnnotation(Type T, const DeclContext *DC,
GenericSignature genericSig = GenericSignature());
Expand All @@ -408,6 +431,8 @@ class CodeCompletionStringBuilder {
CodeCompletionString *createCompletionString() {
return CodeCompletionString::create(Allocator, Chunks);
}

ArrayRef<CodeCompletionString::Chunk> getChunks() { return Chunks; }
};

} // end namespace ide
Expand Down
72 changes: 72 additions & 0 deletions include/swift/IDE/SignatureHelp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//===--- SignatureHelp.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_H
#define SWIFT_IDE_SIGNATURE_HELP_H

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

namespace swift {
class IDEInspectionCallbacksFactory;

namespace ide {

struct Signature {
/// True if this is a subscript rather than a function call.
bool IsSubscript;

/// True if the function is an implicitly curried instance method.
bool IsImplicitlyCurried;

/// True if the call is the second apply of a double-applied function.
bool IsSecondApply;

/// The FuncDecl or SubscriptDecl associated with the call.
ValueDecl *FuncD;

/// The type of the function being called.
AnyFunctionType *FuncTy;

/// The base type of the call/subscript (null for free functions).
Type BaseType;

/// The index of the parameter corresponding to the completion argument.
std::optional<unsigned> ParamIdx;
};

struct SignatureHelpResult {
/// The decl context of the parsed expression.
DeclContext *DC;

/// Suggested signatures.
SmallVector<Signature, 2> Signatures;

SignatureHelpResult(DeclContext *DC) : DC(DC) {}
};

/// An abstract base class for consumers of signatures results.
class SignatureHelpConsumer {
public:
virtual ~SignatureHelpConsumer() {}
virtual void handleResult(const SignatureHelpResult &result) = 0;
};

/// Create a factory for code completion callbacks.
IDEInspectionCallbacksFactory *
makeSignatureHelpCallbacksFactory(SignatureHelpConsumer &Consumer);

} // namespace ide
} // namespace swift

#endif // SWIFT_IDE_SIGNATURE_HELP_H
18 changes: 18 additions & 0 deletions include/swift/IDETool/IDEInspectionInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "swift/IDE/ConformingMethodList.h"
#include "swift/IDE/CursorInfo.h"
#include "swift/IDE/ImportDepth.h"
#include "swift/IDE/SignatureHelp.h"
#include "swift/IDE/SwiftCompletionInfo.h"
#include "swift/IDE/TypeContextInfo.h"
#include "llvm/ADT/Hashing.h"
Expand Down Expand Up @@ -80,6 +81,14 @@ struct ConformingMethodListResults {
bool DidReuseAST;
};

/// The results returned from \c IDEInspectionInstance::signatures.
struct SignatureHelpResults {
/// The actual results. If \c nullptr, no results were found.
const SignatureHelpResult *Result;
/// Whether an AST was reused to produce the results.
bool DidReuseAST;
};

/// The results returned from \c IDEInspectionInstance::cursorInfo.
struct CursorInfoResults {
/// The actual results.
Expand Down Expand Up @@ -206,6 +215,15 @@ class IDEInspectionInstance {
llvm::function_ref<void(CancellableResult<ConformingMethodListResults>)>
Callback);

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

void cursorInfo(
swift::CompilerInvocation &Invocation, llvm::ArrayRef<const char *> Args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
Expand Down
104 changes: 91 additions & 13 deletions lib/IDE/ArgumentCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
//
//===----------------------------------------------------------------------===//

#include "swift/Basic/Assertions.h"
#include "swift/IDE/ArgumentCompletion.h"
#include "swift/AST/Types.h"
#include "swift/Basic/Assertions.h"
#include "swift/IDE/CodeCompletion.h"
#include "swift/IDE/CompletionLookup.h"
#include "swift/IDE/SelectedOverloadInfo.h"
#include "swift/IDE/SignatureHelp.h"
#include "swift/Sema/ConstraintSystem.h"
#include "swift/Sema/IDETypeChecking.h"

Expand Down Expand Up @@ -60,9 +62,9 @@ bool ArgumentTypeCheckCompletionCallback::addPossibleParams(
// a multitple trailing closure label but the parameter is not a function
// type. Since we only allow labeled trailing closures after the first
// trailing closure, we cannot pass an argument for this parameter.
// If the parameter is required, stop here since we cannot pass an argument
// for the parameter. If it's optional, keep looking for more trailing
// closures that can be passed.
// If the parameter is required, stop here since we cannot pass an
// argument for the parameter. If it's optional, keep looking for more
// trailing closures that can be passed.
if (Required) {
break;
} else {
Expand Down Expand Up @@ -95,14 +97,62 @@ bool ArgumentTypeCheckCompletionCallback::addPossibleParams(
static bool hasParentCallLikeExpr(Expr *E, ConstraintSystem &CS) {
E = CS.getParentExpr(E);
while (E) {
if (E->getArgs() || isa<ParenExpr>(E) || isa<TupleExpr>(E) || isa<CollectionExpr>(E)) {
if (E->getArgs() || isa<ParenExpr>(E) || isa<TupleExpr>(E) ||
isa<CollectionExpr>(E)) {
return true;
}
E = CS.getParentExpr(E);
}
return false;
}

/// The callee can be a double-applied function in the second apply (e.g.
/// `f()(|)`). In that case, the normal callee locator will not be able to find
/// a selected overload since an overload has been selected for the first apply
/// but not the second. We try to find the function's declaration and type
/// if it turns out to be a double apply.
static std::optional<std::pair<ValueDecl *, AnyFunctionType *>>
tryResolveDoubleAppliedFunction(CallExpr *OuterCall, const Solution &S) {
if (!OuterCall)
return std::nullopt;

auto *InnerCall = dyn_cast<CallExpr>(OuterCall->getSemanticFn());
if (!InnerCall)
return std::nullopt;

auto &CS = S.getConstraintSystem();
auto *InnerCallLocator = CS.getConstraintLocator(InnerCall);
auto Overload = S.getCalleeOverloadChoiceIfAvailable(InnerCallLocator);
if (!Overload)
return std::nullopt;

if (!Overload->choice.isDecl())
return std::nullopt;

auto FuncRefInfo = Overload->choice.getFunctionRefInfo();
if (!FuncRefInfo.isDoubleApply())
return std::nullopt;

auto CalleeTy = Overload->adjustedOpenedType->getAs<AnyFunctionType>();
auto ResultTy = S.simplifyTypeForCodeCompletion(CalleeTy->getResult());

auto *FuncTy = ResultTy->getAs<AnyFunctionType>();
if (!FuncTy)
return std::nullopt;

auto *VD = Overload->choice.getDecl();
auto BaseTy = Overload->choice.getBaseType();
bool IsOuterCallImplicitlyCurried =
VD->isInstanceMember() && !doesMemberRefApplyCurriedSelf(BaseTy, VD);

// The function declaration is only relevant if the function is an implicitly
// curried instance method.
if (IsOuterCallImplicitlyCurried)
return std::make_pair(Overload->choice.getDecl(), FuncTy);

return std::make_pair(nullptr, FuncTy);
}

void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
Type ExpectedTy = getTypeForCompletion(S, CompletionExpr);

Expand Down Expand Up @@ -230,11 +280,24 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
llvm::SmallDenseMap<const VarDecl *, Type> SolutionSpecificVarTypes;
getSolutionSpecificVarTypes(S, SolutionSpecificVarTypes);

ValueDecl *FuncD = nullptr;
AnyFunctionType *FuncTy = nullptr;
bool IsSecondApply = false;
if (Info.ValueTy) {
FuncTy = Info.ValueTy->lookThroughAllOptionalTypes()->getAs<AnyFunctionType>();
FuncD = Info.getValue();
FuncTy =
Info.ValueTy->lookThroughAllOptionalTypes()->getAs<AnyFunctionType>();
} else if (auto Result = tryResolveDoubleAppliedFunction(
dyn_cast<CallExpr>(ParentCall), S)) {
FuncD = Result->first;
FuncTy = Result->second;
IsSecondApply = true;
}

bool IsImplicitlyCurried =
Info.ValueRef && Info.ValueRef.getDecl()->isInstanceMember() &&
!doesMemberRefApplyCurriedSelf(Info.BaseTy, Info.ValueRef.getDecl());

// Determine which parameters are optional. We need to do this in
// `sawSolutionImpl` because it accesses the substitution map in
// `Info.ValueRef`. This substitution map might contain type variables that
Expand All @@ -246,9 +309,7 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
for (auto Idx : range(0, ParamsToPass.size())) {
bool Optional = false;
if (Info.ValueRef) {
if (Info.ValueRef.getDecl()->isInstanceMember() &&
!doesMemberRefApplyCurriedSelf(Info.BaseTy,
Info.ValueRef.getDecl())) {
if (IsImplicitlyCurried) {
// We are completing in an unapplied instance function, eg.
// struct TestStatic {
// func method() -> Void {}
Expand Down Expand Up @@ -286,10 +347,11 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) {
}

Results.push_back(
{ExpectedTy, ExpectedCallType, isa<SubscriptExpr>(ParentCall),
Info.getValue(), FuncTy, ArgIdx, ParamIdx, std::move(ClaimedParams),
IsNoninitialVariadic, IncludeSignature, Info.BaseTy, HasLabel, FirstTrailingClosureIndex,
IsAsync, DeclParamIsOptional, SolutionSpecificVarTypes});
{ExpectedTy, ExpectedCallType, isa<SubscriptExpr>(ParentCall), FuncD,
FuncTy, ArgIdx, ParamIdx, std::move(ClaimedParams), IsNoninitialVariadic,
IncludeSignature, Info.BaseTy, HasLabel, FirstTrailingClosureIndex,
IsAsync, IsImplicitlyCurried, IsSecondApply, DeclParamIsOptional,
SolutionSpecificVarTypes});
}

void ArgumentTypeCheckCompletionCallback::computeShadowedDecls(
Expand Down Expand Up @@ -432,3 +494,19 @@ void ArgumentTypeCheckCompletionCallback::collectResults(
*Lookup.getExpectedTypeContext(),
Lookup.canCurrDeclContextHandleAsync());
}

void ArgumentTypeCheckCompletionCallback::getSignatures(
SourceLoc Loc, DeclContext *DC, SmallVectorImpl<Signature> &Signatures) {
SmallPtrSet<ValueDecl *, 4> ShadowedDecls;
computeShadowedDecls(ShadowedDecls);

for (auto &Result : Results) {
// Only show signature if the function isn't overridden.
if (!Result.FuncTy || ShadowedDecls.contains(Result.FuncD))
continue;

Signatures.push_back({Result.IsSubscript, Result.IsImplicitlyCurried,
Result.IsSecondApply, Result.FuncD, Result.FuncTy,
Result.ExpectedType, Result.ParamIdx});
}
}
Loading