diff --git a/include/swift/IDE/ArgumentCompletion.h b/include/swift/IDE/ArgumentCompletion.h index cc1150d372385..8b32fe1fd1208 100644 --- a/include/swift/IDE/ArgumentCompletion.h +++ b/include/swift/IDE/ArgumentCompletion.h @@ -16,7 +16,9 @@ #include "swift/IDE/CodeCompletionConsumer.h" #include "swift/IDE/CodeCompletionContext.h" #include "swift/IDE/PossibleParamInfo.h" +#include "swift/IDE/SignatureHelp.h" #include "swift/IDE/TypeCheckCompletionCallback.h" +#include "swift/Sema/ConstraintSystem.h" namespace swift { namespace ide { @@ -87,6 +89,8 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback { /// this result. This in particular includes parameters of closures that /// were type-checked with the code completion expression. llvm::SmallDenseMap SolutionSpecificVarTypes; + + constraints::Score FixedScore; }; CodeCompletionExpr *CompletionExpr; @@ -120,6 +124,9 @@ class ArgumentTypeCheckCompletionCallback : public TypeCheckCompletionCallback { void collectResults(bool IsLabeledTrailingClosure, SourceLoc Loc, DeclContext *DC, CodeCompletionContext &CompletionCtx); + + // TODO(a7medev): add doc comment. + SignatureHelpResult getSignatures(SourceLoc Loc, DeclContext *DC); }; } // end namespace ide diff --git a/include/swift/IDE/CommentConversion.h b/include/swift/IDE/CommentConversion.h index f9451daaf8af4..75b0c5a3e4959 100644 --- a/include/swift/IDE/CommentConversion.h +++ b/include/swift/IDE/CommentConversion.h @@ -27,9 +27,11 @@ namespace ide { /// If the declaration has a documentation comment, prints the comment to \p OS /// in Clang-like XML format. /// +/// \param IncludeParameters if true, includes the parameters part of the documentation comment and ignores it otherwise. +/// /// \returns true if the declaration has a documentation comment. bool getDocumentationCommentAsXML( - const Decl *D, raw_ostream &OS, + const Decl *D, raw_ostream &OS, bool IncludeParameters, TypeOrExtensionDecl SynthesizedTarget = TypeOrExtensionDecl()); /// If the declaration has a documentation comment, prints the comment to \p OS diff --git a/include/swift/IDE/SignatureHelp.h b/include/swift/IDE/SignatureHelp.h new file mode 100644 index 0000000000000..08cefd14e859f --- /dev/null +++ b/include/swift/IDE/SignatureHelp.h @@ -0,0 +1,78 @@ +//===--- 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 SignatureHelpResult { + struct Signature { + /// True if this is a subscript rather than a function call. + bool IsSubscript; + + /// The FuncDecl or SubscriptDecl associated with the call. + ValueDecl *FuncD; + + /// The type of the function being called. + AnyFunctionType *FuncTy; + + /// The index of the argument containing the completion location + unsigned ArgIdx; + + /// The index of the parameter corresponding to the completion argument. + std::optional ParamIdx; + + /// True if the completion is a noninitial term in a variadic argument. + bool IsNoninitialVariadic; + + /// The base type of the call/subscript (null for free functions). + Type BaseType; + + /// The resolved type of the expression. + Type ExprType; + }; + + /// The decl context of the parsed expression. + DeclContext *DC; + + /// The active signature. + std::optional ActiveSignature; + + /// Suggested signatures. + SmallVector 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 diff --git a/include/swift/IDETool/IDEInspectionInstance.h b/include/swift/IDETool/IDEInspectionInstance.h index 0f3ab25f7f09b..fbe72c7624834 100644 --- a/include/swift/IDETool/IDEInspectionInstance.h +++ b/include/swift/IDETool/IDEInspectionInstance.h @@ -19,6 +19,7 @@ #include "swift/IDE/CodeCompletionResult.h" #include "swift/IDE/CodeCompletionResultSink.h" #include "swift/IDE/ConformingMethodList.h" +#include "swift/IDE/SignatureHelp.h" #include "swift/IDE/CursorInfo.h" #include "swift/IDE/ImportDepth.h" #include "swift/IDE/SwiftCompletionInfo.h" @@ -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. @@ -205,6 +214,15 @@ class IDEInspectionInstance { std::shared_ptr> CancellationFlag, llvm::function_ref)> Callback); + + void signatureHelp( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *ideInspectionTargetBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, + std::shared_ptr> CancellationFlag, + llvm::function_ref)> + Callback); void cursorInfo( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, diff --git a/lib/IDE/ArgumentCompletion.cpp b/lib/IDE/ArgumentCompletion.cpp index 77828265f7b9b..1fe33261f72ae 100644 --- a/lib/IDE/ArgumentCompletion.cpp +++ b/lib/IDE/ArgumentCompletion.cpp @@ -282,12 +282,16 @@ void ArgumentTypeCheckCompletionCallback::sawSolutionImpl(const Solution &S) { IncludeSignature = true; } } + + SmallString<512> ToPrint; + llvm::raw_svector_ostream OS(ToPrint); + S.getFixedScore().print(OS); Results.push_back( {ExpectedTy, ExpectedCallType, isa(ParentCall), Info.getValue(), FuncTy, ArgIdx, ParamIdx, std::move(ClaimedParams), IsNoninitialVariadic, IncludeSignature, Info.BaseTy, HasLabel, FirstTrailingClosureIndex, - IsAsync, DeclParamIsOptional, SolutionSpecificVarTypes}); + IsAsync, DeclParamIsOptional, SolutionSpecificVarTypes, S.getFixedScore()}); } void ArgumentTypeCheckCompletionCallback::computeShadowedDecls( @@ -430,3 +434,42 @@ void ArgumentTypeCheckCompletionCallback::collectResults( *Lookup.getExpectedTypeContext(), Lookup.canCurrDeclContextHandleAsync()); } + + +SignatureHelpResult ArgumentTypeCheckCompletionCallback::getSignatures( + SourceLoc Loc, DeclContext *DC) { + SmallPtrSet ShadowedDecls; + computeShadowedDecls(ShadowedDecls); + + SignatureHelpResult result(DC); + + if (Results.empty()) + return result; + + // The active signature is the signature with the lowest solution score + // TODO(a7medev): Is that the most suitable active signature? + std::optional MinScore; + + for (size_t i = 0; i < Results.size(); ++i) { + auto &Result = Results[i]; + + // TODO(a7medev): Use the same result output mechanism in code completion + // Only show call pattern completions if the function isn't + // overridden. + if (Result.FuncD && ShadowedDecls.count(Result.FuncD) == 0) { + // TODO(a7medev): Probably avoid using a new type altogether. + result.Signatures.push_back({ + Result.IsSubscript, Result.FuncD, Result.FuncTy, Result.ArgIdx, + Result.ParamIdx, Result.IsNoninitialVariadic, Result.BaseType, + Result.ExpectedType + }); + + if (!MinScore || Result.FixedScore < MinScore) { + result.ActiveSignature = i; + MinScore = Result.FixedScore; + } + } + } + + return result; +} diff --git a/lib/IDE/CMakeLists.txt b/lib/IDE/CMakeLists.txt index 7b03bb48cc2a4..a78f45e63114e 100644 --- a/lib/IDE/CMakeLists.txt +++ b/lib/IDE/CMakeLists.txt @@ -17,6 +17,7 @@ add_swift_host_library(swiftIDE STATIC CompletionLookup.cpp CompletionOverrideLookup.cpp ConformingMethodList.cpp + SignatureHelp.cpp CursorInfo.cpp ExprCompletion.cpp ExprContextAnalysis.cpp diff --git a/lib/IDE/CommentConversion.cpp b/lib/IDE/CommentConversion.cpp index 500d8c4831e40..60467b392f241 100644 --- a/lib/IDE/CommentConversion.cpp +++ b/lib/IDE/CommentConversion.cpp @@ -38,8 +38,10 @@ using namespace swift; namespace { struct CommentToXMLConverter { raw_ostream &OS; + bool IncludeParameters; - CommentToXMLConverter(raw_ostream &OS) : OS(OS) {} + CommentToXMLConverter(raw_ostream &OS, bool IncludeParameters = true) + : OS(OS), IncludeParameters(IncludeParameters) {} void printRawHTML(StringRef Tag) { OS << ""; @@ -224,6 +226,8 @@ struct CommentToXMLConverter { } void printParamField(const ParamField *PF) { + assert(IncludeParameters); + OS << ""; OS << ""; OS << PF->getName(); @@ -283,7 +287,7 @@ void CommentToXMLConverter::visitCommentParts(const swift::markup::CommentParts OS << ""; } - if (!Parts.ParamFields.empty()) { + if (!Parts.ParamFields.empty() && IncludeParameters) { OS << ""; for (const auto *PF : Parts.ParamFields) printParamField(PF); @@ -496,12 +500,14 @@ static DocComment *getCascadingDocComment(swift::markup::MarkupContext &MC, } bool ide::getDocumentationCommentAsXML(const Decl *D, raw_ostream &OS, + bool IncludeParameters, TypeOrExtensionDecl SynthesizedTarget) { auto MaybeClangNode = D->getClangNode(); if (MaybeClangNode) { if (auto *CD = MaybeClangNode.getAsDecl()) { std::string S; llvm::raw_string_ostream SS(S); + // TODO(refaey): respect IncludeParameters for Clang. if (getClangDocumentationCommentAsXML(CD, SS)) { replaceObjcDeclarationsWithSwiftOnes(D, SS.str(), OS, SynthesizedTarget); @@ -516,7 +522,7 @@ bool ide::getDocumentationCommentAsXML(const Decl *D, raw_ostream &OS, if (!DC) return false; - CommentToXMLConverter Converter(OS); + CommentToXMLConverter Converter(OS, IncludeParameters); Converter.visitDocComment(DC, SynthesizedTarget); OS.flush(); diff --git a/lib/IDE/SignatureHelp.cpp b/lib/IDE/SignatureHelp.cpp new file mode 100644 index 0000000000000..1da53e16d2156 --- /dev/null +++ b/lib/IDE/SignatureHelp.cpp @@ -0,0 +1,119 @@ +//===--- SignatureHelp.cpp ------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "swift/IDE/SignatureHelp.h" +#include "ExprContextAnalysis.h" +#include "swift/AST/ASTDemangler.h" +#include "swift/AST/ConformanceLookup.h" +#include "swift/IDE/ArgumentCompletion.h" +#include "swift/AST/GenericEnvironment.h" +#include "swift/AST/NameLookup.h" +#include "swift/AST/USRGeneration.h" +#include "swift/Basic/Assertions.h" +#include "swift/IDE/TypeCheckCompletionCallback.h" +#include "swift/Parse/IDEInspectionCallbacks.h" +#include "swift/Sema/IDETypeChecking.h" +#include "swift/Sema/ConstraintSystem.h" +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "swift/IDE/SelectedOverloadInfo.h" + +using namespace swift; +using namespace swift::ide; +using namespace swift::constraints; + +namespace { +class SignatureHelpCallbacks : public CodeCompletionCallbacks, + public DoneParsingCallback { + SignatureHelpConsumer &Consumer; + SourceLoc Loc; + CodeCompletionExpr *CCExpr = nullptr; + DeclContext *CurDeclContext = nullptr; + + + void typeCheckWithLookup(TypeCheckCompletionCallback &Lookup, + SourceLoc CompletionLoc); + +public: + SignatureHelpCallbacks(Parser &P, SignatureHelpConsumer &Consumer) + : CodeCompletionCallbacks(P), DoneParsingCallback(), Consumer(Consumer) {} + + // Only handle callbacks for argument completions. + // { + void completeCallArg(CodeCompletionExpr *E) override; + // } + + void doneParsing(SourceFile *SrcFile) override; +}; + +void SignatureHelpCallbacks::completeCallArg(CodeCompletionExpr *E) { + CurDeclContext = P.CurDeclContext; + CCExpr = E; +} + +void SignatureHelpCallbacks::doneParsing(SourceFile *SrcFile) { + if (!CCExpr) + return; + + ArgumentTypeCheckCompletionCallback Lookup(CCExpr, CurDeclContext); + typeCheckWithLookup(Lookup, CCExpr->getLoc()); + + SignatureHelpResult Result = Lookup.getSignatures(CCExpr->getLoc(), + CurDeclContext); + + Consumer.handleResult(Result); +} + +// TODO(a7medev): Share it with CodeCompletion or just simplify to typeCheckContextAt if possible. +void SignatureHelpCallbacks::typeCheckWithLookup( + TypeCheckCompletionCallback &Lookup, SourceLoc CompletionLoc) { + llvm::SaveAndRestore CompletionCollector( + Context.CompletionCallback, &Lookup); + typeCheckContextAt( + TypeCheckASTNodeAtLocContext::declContext(CurDeclContext), + CompletionLoc); + + // This (hopefully) only happens in cases where the expression isn't + // typechecked during normal compilation either (e.g. member completion in a + // switch case where there control expression is invalid). Having normal + // typechecking still resolve even these cases would be beneficial for + // tooling in general though. + if (!Lookup.gotCallback()) { + if (Context.TypeCheckerOpts.DebugConstraintSolver) { + llvm::errs() << "--- Fallback typecheck for code completion ---\n"; + } + Lookup.fallbackTypeCheck(CurDeclContext); + } +} + +} // anonymous namespace. + +IDEInspectionCallbacksFactory * +swift::ide::makeSignatureHelpCallbacksFactory(SignatureHelpConsumer &Consumer) { + + // CC callback factory which produces 'SignatureCallbacks'. + class SignatureHelpCallbacksFactoryImpl + : public IDEInspectionCallbacksFactory { + SignatureHelpConsumer &Consumer; + + public: + SignatureHelpCallbacksFactoryImpl( + SignatureHelpConsumer &Consumer) : Consumer(Consumer) {} + + Callbacks createCallbacks(Parser &P) override { + auto Callback = std::make_shared(P, Consumer); + return {Callback, Callback}; + } + }; + + return new SignatureHelpCallbacksFactoryImpl(Consumer); +} diff --git a/lib/IDETool/IDEInspectionInstance.cpp b/lib/IDETool/IDEInspectionInstance.cpp index ebb0e086983f1..177e1164875f9 100644 --- a/lib/IDETool/IDEInspectionInstance.cpp +++ b/lib/IDETool/IDEInspectionInstance.cpp @@ -812,6 +812,73 @@ void swift::ide::IDEInspectionInstance::conformingMethodList( }); } +void swift::ide::IDEInspectionInstance::signatureHelp( + swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, + llvm::IntrusiveRefCntPtr FileSystem, + llvm::MemoryBuffer *ideInspectionTargetBuffer, unsigned int Offset, + DiagnosticConsumer *DiagC, + std::shared_ptr> CancellationFlag, + llvm::function_ref)> + Callback) { + using ResultType = CancellableResult; + + struct ConsumerToCallbackAdapter + : public swift::ide::SignatureHelpConsumer { + bool ReusingASTContext; + std::shared_ptr> CancellationFlag; + llvm::function_ref Callback; + bool HandleResultsCalled = false; + + ConsumerToCallbackAdapter( + bool ReusingASTContext, + std::shared_ptr> CancellationFlag, + llvm::function_ref Callback) + : ReusingASTContext(ReusingASTContext), + CancellationFlag(CancellationFlag), Callback(Callback) {} + + void handleResult(const SignatureHelpResult &result) override { + HandleResultsCalled = true; + if (CancellationFlag && + CancellationFlag->load(std::memory_order_relaxed)) { + Callback(ResultType::cancelled()); + } else { + Callback(ResultType::success({&result, ReusingASTContext})); + } + } + }; + + performOperation( + Invocation, Args, FileSystem, ideInspectionTargetBuffer, Offset, DiagC, + CancellationFlag, + [&](CancellableResult CIResult) { + CIResult.mapAsync( + [&CancellationFlag](auto &Result, auto DeliverTransformed) { + ConsumerToCallbackAdapter Consumer( + Result.DidReuseAST, CancellationFlag, DeliverTransformed); + std::unique_ptr callbacksFactory( + ide::makeSignatureHelpCallbacksFactory(Consumer)); + + if (!Result.DidFindIDEInspectionTarget) { + DeliverTransformed( + ResultType::success({/*Results=*/{}, Result.DidReuseAST})); + } + + performIDEInspectionSecondPass( + *Result.CI->getIDEInspectionFile(), *callbacksFactory); + if (!Consumer.HandleResultsCalled) { + // If we didn't receive a handleResult call from the second + // pass, we didn't receive any results. To make sure Callback + // gets called exactly once, call it manually with no results + // here. + DeliverTransformed( + ResultType::success({/*Results=*/{}, Result.DidReuseAST})); + } + }, + Callback); + }); +} + + void swift::ide::IDEInspectionInstance::cursorInfo( swift::CompilerInvocation &Invocation, llvm::ArrayRef Args, llvm::IntrusiveRefCntPtr FileSystem, diff --git a/tools/SourceKit/include/SourceKit/Core/LangSupport.h b/tools/SourceKit/include/SourceKit/Core/LangSupport.h index 4b143b0f8ea8c..d84199efb162d 100644 --- a/tools/SourceKit/include/SourceKit/Core/LangSupport.h +++ b/tools/SourceKit/include/SourceKit/Core/LangSupport.h @@ -1010,6 +1010,36 @@ class ConformingMethodListConsumer { virtual void cancelled() = 0; }; +struct SignatureHelpResult { + struct Parameter { + unsigned LabelBegin; + unsigned LabelLength; + StringRef DocComment; + }; + + struct Signature { + StringRef Label; + StringRef Doc; + std::optional ActiveParam; + ArrayRef Params; + }; + + std::optional ActiveSignature; + ArrayRef Signatures; +}; + +class SignatureHelpConsumer { + virtual void anchor(); + +public: + virtual ~SignatureHelpConsumer() {} + + virtual void handleResult(const SignatureHelpResult &Result) = 0; + virtual void setReusingASTContext(bool flag) = 0; + virtual void failed(StringRef ErrDescription) = 0; + virtual void cancelled() = 0; +}; + struct CompilationResult { unsigned int ResultStatus; llvm::ArrayRef Diagnostics; @@ -1262,6 +1292,12 @@ class LangSupport { ConformingMethodListConsumer &Consumer, std::optional vfsOptions) = 0; + virtual void getSignatureHelp(llvm::MemoryBuffer *inputBuf, unsigned Offset, + ArrayRef Args, + SourceKitCancellationToken CancellationToken, + SignatureHelpConsumer &Consumer, + std::optional vfsOptions) = 0; + virtual void expandMacroSyntactically(llvm::MemoryBuffer *inputBuf, ArrayRef args, ArrayRef expansions, diff --git a/tools/SourceKit/lib/Core/LangSupport.cpp b/tools/SourceKit/lib/Core/LangSupport.cpp index 1d3a0df0e74c5..bee552e77df32 100644 --- a/tools/SourceKit/lib/Core/LangSupport.cpp +++ b/tools/SourceKit/lib/Core/LangSupport.cpp @@ -21,4 +21,5 @@ void OptionsDictionary::anchor() {} void DocInfoConsumer::anchor() { } void TypeContextInfoConsumer::anchor() { } void ConformingMethodListConsumer::anchor() { } +void SignatureHelpConsumer::anchor() { } void LangSupport::anchor() { } diff --git a/tools/SourceKit/lib/SwiftLang/CMakeLists.txt b/tools/SourceKit/lib/SwiftLang/CMakeLists.txt index 0037e5a040392..3cd81d50ec1a1 100644 --- a/tools/SourceKit/lib/SwiftLang/CMakeLists.txt +++ b/tools/SourceKit/lib/SwiftLang/CMakeLists.txt @@ -4,6 +4,7 @@ add_sourcekit_library(SourceKitSwiftLang SwiftCompile.cpp SwiftCompletion.cpp SwiftConformingMethodList.cpp + SwiftSignatureHelp.cpp SwiftDocSupport.cpp SwiftEditor.cpp SwiftEditorInterfaceGen.cpp diff --git a/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp index cda9216374062..48327ab34e897 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftDocSupport.cpp @@ -465,7 +465,8 @@ static bool initDocEntityInfo(const Decl *D, llvm::SmallString<128> DocBuffer; { llvm::raw_svector_ostream OSS(DocBuffer); - ide::getDocumentationCommentAsXML(D, OSS, SynthesizedTarget); + ide::getDocumentationCommentAsXML(D, OSS, /*IncludeParameters=*/true, + SynthesizedTarget); } OS << DocBuffer; } diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp index 8926be20789e2..1a1caca590a27 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.cpp @@ -919,15 +919,20 @@ bool SwiftLangSupport::printAccessorUSR(const AbstractStorageDecl *D, void SwiftLangSupport::printMemberDeclDescription(const swift::ValueDecl *VD, swift::Type baseTy, bool usePlaceholder, - llvm::raw_ostream &OS) { + llvm::raw_ostream &OS, + llvm::function_ref beforePrintParam, + llvm::function_ref afterPrintParam) { // Base name. OS << VD->getBaseName().userFacingName(); // Parameters. auto substMap = baseTy->getMemberSubstitutionMap(VD); auto printSingleParam = [&](ParamDecl *param) { + beforePrintParam(param); + auto paramTy = param->getInterfaceType(); + // TODO(a7medev): add _: in place of an argument with no name for signature help // Label. if (!param->getArgumentName().empty()) OS << param->getArgumentName() << ": "; @@ -952,6 +957,8 @@ void SwiftLangSupport::printMemberDeclDescription(const swift::ValueDecl *VD, if (usePlaceholder) OS << "#>"; + + afterPrintParam(param); }; auto printParams = [&](const ParameterList *params) { OS << '('; diff --git a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h index aced4a4059c47..be698042ae4d8 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h +++ b/tools/SourceKit/lib/SwiftLang/SwiftLangSupport.h @@ -48,6 +48,7 @@ namespace swift { class SourceFile; class SILOptions; class ValueDecl; + class ParamDecl; class GenericSignature; enum class AccessorKind; @@ -502,7 +503,9 @@ class SwiftLangSupport : public LangSupport { /// a typed editor placeholders which is suitable for 'sourcetext'. static void printMemberDeclDescription(const swift::ValueDecl *VD, swift::Type baseTy, - bool usePlaceholder, llvm::raw_ostream &OS); + bool usePlaceholder, llvm::raw_ostream &OS, + llvm::function_ref beforePrintParam = {}, + llvm::function_ref afterPrintParam = {}); /// Tries to resolve the path to the real file-system path. If it fails it /// returns the original path; @@ -758,6 +761,12 @@ class SwiftLangSupport : public LangSupport { SourceKitCancellationToken CancellationToken, ConformingMethodListConsumer &Consumer, std::optional vfsOptions) override; + + void getSignatureHelp(llvm::MemoryBuffer *inputBuf, unsigned Offset, + ArrayRef Args, + SourceKitCancellationToken CancellationToken, + SignatureHelpConsumer &Consumer, + std::optional vfsOptions) override; void expandMacroSyntactically(llvm::MemoryBuffer *inputBuf, ArrayRef args, diff --git a/tools/SourceKit/lib/SwiftLang/SwiftSignatureHelp.cpp b/tools/SourceKit/lib/SwiftLang/SwiftSignatureHelp.cpp new file mode 100644 index 0000000000000..1109162cd66a0 --- /dev/null +++ b/tools/SourceKit/lib/SwiftLang/SwiftSignatureHelp.cpp @@ -0,0 +1,141 @@ +//===--- SwiftSignatureHelp.cpp -------------------------------------------===// +// +// 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 "SwiftASTManager.h" +#include "SwiftEditorDiagConsumer.h" +#include "SwiftLangSupport.h" +#include "swift/Frontend/Frontend.h" +#include "swift/Frontend/PrintingDiagnosticConsumer.h" +#include "swift/IDE/SignatureHelp.h" +#include "swift/IDE/CommentConversion.h" +#include "swift/IDETool/IDEInspectionInstance.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Comment.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Type.h" + +using namespace SourceKit; +using namespace swift; +using namespace ide; + +static void +deliverResults(SourceKit::SignatureHelpConsumer &SKConsumer, + CancellableResult Result) { + switch (Result.getKind()) { + case CancellableResultKind::Success: { + SKConsumer.setReusingASTContext(Result->DidReuseAST); + + if (!Result->Result) { + // If we have no results, don't call SKConsumer.handleResult which causes + // empty results to be delivered. + break; + } + + SmallString<512> SS; + llvm::raw_svector_ostream OS(SS); + + struct SignatureInfo { + size_t LabelBegin; + size_t LabelLength; + StringRef DocComment; + std::optional ActiveParam; + SmallVector Params; + + SignatureInfo() {} + }; + + SmallVector Signatures; + + for (auto signature : Result->Result->Signatures) { + Signatures.emplace_back(); + auto &signatureElem = Signatures.back(); + + // Label. + signatureElem.LabelBegin = SS.size(); + // TODO(a7medev): Add parameter documentation. + auto &Params = signatureElem.Params; + // TODO(a7medev): Replace `printMemberDeclDescription` with logic similar to code completion. + // for benefits like: handling subscripts, implicit subscripts (e.g. [keyPath:]), generics, etc. + SwiftLangSupport::printMemberDeclDescription( + signature.FuncD, signature.ExprType, /*usePlaceholder=*/false, OS, + /*beforePrintParam=*/[&](ParamDecl *Param) { + Params.emplace_back(); + Params.back().LabelBegin = SS.size() - signatureElem.LabelBegin; + }, + /*afterPrintParam=*/[&](ParamDecl *Param) { + Params.back().LabelLength = + SS.size() - Params.back().LabelBegin - signatureElem.LabelBegin; + }); + signatureElem.LabelLength = SS.size() - signatureElem.LabelBegin; + signatureElem.ActiveParam = signature.ParamIdx; + + // Documentation. + unsigned DocCommentBegin = SS.size(); + ide::getDocumentationCommentAsXML(signature.FuncD, OS, + /*IncludeParameters=*/false); + unsigned DocCommentLength = SS.size() - DocCommentBegin; + + StringRef DocComment(SS.begin() + DocCommentBegin, DocCommentLength); + signatureElem.DocComment = DocComment; + } + + SourceKit::SignatureHelpResult SKResult; + SmallVector SKSignatures; + + for (auto &info : Signatures) { + StringRef Label(SS.begin() + info.LabelBegin, info.LabelLength); + SKSignatures.push_back({Label, info.DocComment, info.ActiveParam, info.Params}); + } + + SKResult.Signatures = SKSignatures; + SKResult.ActiveSignature = Result->Result->ActiveSignature; + + SKConsumer.handleResult(SKResult); + break; + } + case CancellableResultKind::Failure: + SKConsumer.failed(Result.getError()); + break; + case CancellableResultKind::Cancelled: + SKConsumer.cancelled(); + break; + } +} + +void SwiftLangSupport::getSignatureHelp( + llvm::MemoryBuffer *UnresolvedInputFile, unsigned Offset, + ArrayRef Args, SourceKitCancellationToken CancellationToken, + SignatureHelpConsumer &SKConsumer, std::optional vfsOptions) { + std::string error; + + // FIXME: the use of None as primary file is to match the fact we do not read + // the document contents using the editor documents infrastructure. + auto fileSystem = + getFileSystem(vfsOptions, /*primaryFile=*/std::nullopt, error); + if (!fileSystem) { + return SKConsumer.failed(error); + } + + performWithParamsToCompletionLikeOperation( + UnresolvedInputFile, Offset, /*InsertCodeCompletionToken=*/true, Args, + fileSystem, CancellationToken, + [&](CancellableResult ParmsResult) { + ParmsResult.mapAsync( + [&](auto &Params, auto DeliverTransformed) { + getIDEInspectionInstance()->signatureHelp( + Params.Invocation, Args, fileSystem, Params.completionBuffer, + Offset, Params.DiagC, Params.CancellationFlag, + DeliverTransformed); + }, + [&](auto Result) { deliverResults(SKConsumer, Result); }); + }); +} diff --git a/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp b/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp index 86c93d40e2850..b9d6e51caefe4 100644 --- a/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp +++ b/tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp @@ -1008,7 +1008,8 @@ fillSymbolInfo(CursorSymbolInfo &Symbol, const DeclInfo &DInfo, ide::getRawDocumentationComment(DInfo.OriginalProperty, OS); Symbol.DocComment = copyAndClearString(Allocator, Buffer); - ide::getDocumentationCommentAsXML(DInfo.OriginalProperty, OS); + ide::getDocumentationCommentAsXML(DInfo.OriginalProperty, OS, + /*IncludeParameters=*/true); Symbol.DocCommentAsXML = copyAndClearString(Allocator, Buffer); { diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp index 14345dfbb40f6..59fcdca5af705 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.cpp @@ -116,6 +116,7 @@ bool TestOptions::parseArgs(llvm::ArrayRef Args) { .Case("complete.setpopularapi", SourceKitRequest::CodeCompleteSetPopularAPI) .Case("typecontextinfo", SourceKitRequest::TypeContextInfo) .Case("conformingmethods", SourceKitRequest::ConformingMethodList) + .Case("signaturehelp", SourceKitRequest::SignatureHelp) .Case("cursor", SourceKitRequest::CursorInfo) .Case("related-idents", SourceKitRequest::RelatedIdents) .Case("active-regions", SourceKitRequest::ActiveRegions) diff --git a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h index a962e3128253c..ae641fbe24f3b 100644 --- a/tools/SourceKit/tools/sourcekitd-test/TestOptions.h +++ b/tools/SourceKit/tools/sourcekitd-test/TestOptions.h @@ -35,6 +35,7 @@ enum class SourceKitRequest { CodeCompleteSetPopularAPI, TypeContextInfo, ConformingMethodList, + SignatureHelp, ActiveRegions, CursorInfo, RangeInfo, diff --git a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp index c79a8f297c03e..4918acf20480e 100644 --- a/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp +++ b/tools/SourceKit/tools/sourcekitd-test/sourcekitd-test.cpp @@ -816,6 +816,11 @@ static int handleTestInvocation(TestOptions Opts, TestOptions &InitOpts) { sourcekitd_request_dictionary_set_int64(Req, KeyOffset, ByteOffset); addRequestOptionsDirect(Req, Opts); break; + + case SourceKitRequest::SignatureHelp: + sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestSignatureHelp); + sourcekitd_request_dictionary_set_int64(Req, KeyOffset, ByteOffset); + break; case SourceKitRequest::CursorInfo: sourcekitd_request_dictionary_set_uid(Req, KeyRequest, RequestCursorInfo); @@ -1416,6 +1421,7 @@ static bool handleResponse(sourcekitd_response_t Resp, const TestOptions &Opts, case SourceKitRequest::CodeCompleteSetPopularAPI: case SourceKitRequest::TypeContextInfo: case SourceKitRequest::ConformingMethodList: + case SourceKitRequest::SignatureHelp: case SourceKitRequest::DependencyUpdated: case SourceKitRequest::Diagnostics: case SourceKitRequest::SemanticTokens: diff --git a/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp b/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp index c3e802a5c056f..f7bfee14f0924 100644 --- a/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp +++ b/tools/SourceKit/tools/sourcekitd/lib/Service/Requests.cpp @@ -312,6 +312,11 @@ static sourcekitd_response_t conformingMethodList( ArrayRef ExpectedTypes, std::optional vfsOptions, SourceKitCancellationToken CancellationToken); +static sourcekitd_response_t signatureHelp( + llvm::MemoryBuffer *InputBuf, int64_t Offset, ArrayRef Args, + std::optional vfsOptions, + SourceKitCancellationToken CancellationToken); + static sourcekitd_response_t editorOpen(StringRef Name, llvm::MemoryBuffer *Buf, SKEditorConsumerOptions Opts, ArrayRef Args, @@ -1467,6 +1472,27 @@ handleRequestConformingMethodList(const RequestDict &Req, }); } +static void +handleRequestSignatureHelp(const RequestDict &Req, + SourceKitCancellationToken CancellationToken, + ResponseReceiver Rec) { + handleSemanticRequest(Req, Rec, [Req, CancellationToken, Rec]() { + std::optional vfsOptions = getVFSOptions(Req); + std::unique_ptr InputBuf = + getInputBufForRequestOrEmitError(Req, vfsOptions, Rec); + if (!InputBuf) + return; + SmallVector Args; + if (getCompilerArgumentsForRequestOrEmitError(Req, Args, Rec)) + return; + int64_t Offset; + if (Req.getInt64(KeyOffset, Offset, /*isOptional=*/false)) + return Rec(createErrorRequestInvalid("missing 'key.offset'")); + return Rec(signatureHelp(InputBuf.get(), Offset, Args, std::move(vfsOptions), + CancellationToken)); + }); +} + static void handleRequestIndex(const RequestDict &Req, SourceKitCancellationToken CancellationToken, ResponseReceiver Rec) { @@ -2208,6 +2234,7 @@ void handleRequestImpl(sourcekitd_object_t ReqObj, HANDLE_REQUEST(RequestCodeCompleteUpdate, handleRequestCodeCompleteUpdate) HANDLE_REQUEST(RequestTypeContextInfo, handleRequestTypeContextInfo) HANDLE_REQUEST(RequestConformingMethodList, handleRequestConformingMethodList) + HANDLE_REQUEST(RequestSignatureHelp, handleRequestSignatureHelp) HANDLE_REQUEST(RequestIndex, handleRequestIndex) HANDLE_REQUEST(RequestIndexToStore, handleRequestIndexToStore) @@ -3564,6 +3591,86 @@ static sourcekitd_response_t conformingMethodList( } } +//===----------------------------------------------------------------------===// +// Signature Help +//===----------------------------------------------------------------------===// + +static sourcekitd_response_t signatureHelp( + llvm::MemoryBuffer *InputBuf, int64_t Offset, ArrayRef Args, + std::optional vfsOptions, + SourceKitCancellationToken CancellationToken) { + ResponseBuilder RespBuilder; + + class Consumer : public SignatureHelpConsumer { + ResponseBuilder::Dictionary SKResult; + std::optional ErrorDescription; + bool WasCancelled = false; + + public: + Consumer(ResponseBuilder Builder) : SKResult(Builder.getDictionary()) {} + + void handleResult(const SignatureHelpResult &Result) override { + if (Result.ActiveSignature.has_value()) + SKResult.set(KeyActiveSignature, Result.ActiveSignature.value()); + + auto Signatures = SKResult.setArray(KeyMembers); + + for (auto Signature : Result.Signatures) { + auto SignatureElem = Signatures.appendDictionary(); + + SignatureElem.set(KeyName, Signature.Label); + + if (Signature.ActiveParam.has_value()) + SignatureElem.set(KeyActiveParameter, Signature.ActiveParam.value()); + + if (!Signature.Doc.empty()) + SignatureElem.set(KeyDocComment, Signature.Doc); + + auto Params = SignatureElem.setArray(KeyParameters); + + for (auto Param : Signature.Params) { + auto ParamElem = Params.appendDictionary(); + + ParamElem.set(KeyNameOffset, Param.LabelBegin); + ParamElem.set(KeyNameLength, Param.LabelLength); + + if (!Param.DocComment.empty()) + ParamElem.set(KeyDocComment, Param.DocComment); + } + } + } + + void setReusingASTContext(bool flag) override { + if (flag) + SKResult.setBool(KeyReusingASTContext, flag); + } + + void failed(StringRef ErrDescription) override { + ErrorDescription = ErrDescription.str(); + } + + void cancelled() override { WasCancelled = true; } + + bool wasCancelled() const { return WasCancelled; } + bool isError() const { return ErrorDescription.has_value(); } + const char *getErrorDescription() const { + return ErrorDescription->c_str(); + } + } Consumer(RespBuilder); + + LangSupport &Lang = getGlobalContext().getSwiftLangSupport(); + Lang.getSignatureHelp(InputBuf, Offset, Args, CancellationToken, Consumer, + std::move(vfsOptions)); + + if (Consumer.wasCancelled()) { + return createErrorRequestCancelled(); + } else if (Consumer.isError()) { + return createErrorRequestFailed(Consumer.getErrorDescription()); + } else { + return RespBuilder.createResponse(); + } +} + //===----------------------------------------------------------------------===// // Editor //===----------------------------------------------------------------------===// diff --git a/tools/swift-ide-test/swift-ide-test.cpp b/tools/swift-ide-test/swift-ide-test.cpp index 109373e4357ab..f69eb208b68a8 100644 --- a/tools/swift-ide-test/swift-ide-test.cpp +++ b/tools/swift-ide-test/swift-ide-test.cpp @@ -3497,7 +3497,7 @@ class ASTCommentPrinter : public ASTWalker { std::string XML; { llvm::raw_string_ostream OS(XML); - getDocumentationCommentAsXML(D, OS); + getDocumentationCommentAsXML(D, OS, /*IncludeParameters=*/true); } OS << "DocCommentAsXML="; if (XML.empty()) { diff --git a/utils/gyb_sourcekit_support/UIDs.py b/utils/gyb_sourcekit_support/UIDs.py index eed16a62ab4d8..fb66767d30164 100644 --- a/utils/gyb_sourcekit_support/UIDs.py +++ b/utils/gyb_sourcekit_support/UIDs.py @@ -61,6 +61,9 @@ def __init__(self, internal_name, external_name): KEY('FullyAnnotatedDecl', 'key.fully_annotated_decl'), KEY('FullyAnnotatedGenericSignature', 'key.fully_annotated_generic_signature'), + KEY('Parameters', 'key.parameters'), + KEY('ActiveParameter', 'key.active_parameter'), + KEY('ActiveSignature', 'key.active_signature'), KEY('DocBrief', 'key.doc.brief'), KEY('Context', 'key.context'), KEY('TypeRelation', 'key.typerelation'), @@ -253,6 +256,7 @@ def __init__(self, internal_name, external_name): REQUEST('CodeCompleteSetPopularAPI', 'source.request.codecomplete.setpopularapi'), REQUEST('CodeCompleteSetCustom', 'source.request.codecomplete.setcustom'), + REQUEST('SignatureHelp', 'source.request.signaturehelp'), REQUEST('TypeContextInfo', 'source.request.typecontextinfo'), REQUEST('ConformingMethodList', 'source.request.conformingmethods'), REQUEST('ActiveRegions', 'source.request.activeregions'),