diff --git a/include/AST/Utility.h b/include/AST/Utility.h index 9a812939..e7d29c0b 100644 --- a/include/AST/Utility.h +++ b/include/AST/Utility.h @@ -12,6 +12,9 @@ bool is_definition(const clang::Decl* decl); /// we consider it as a template while clang does not. bool is_templated(const clang::Decl* decl); +/// Check whether the decl is anonymous. +bool is_anonymous(const clang::NamedDecl* decl); + /// Return the decl where it is instantiated from. If could be a template decl /// or a member of a class template. If the decl is a full specialization, return /// itself. @@ -19,64 +22,48 @@ const clang::NamedDecl* instantiated_from(const clang::NamedDecl* decl); const clang::NamedDecl* normalize(const clang::NamedDecl* decl); +llvm::StringRef identifier_of(const clang::NamedDecl& D); + +llvm::StringRef identifier_of(clang::QualType T); + /// Get the name of the decl. std::string name_of(const clang::NamedDecl* decl); +std::string display_name_of(const clang::NamedDecl* decl); + /// To response go-to-type-definition request. Some decls actually have a type /// for example the result of `typeof(var)` is the type of `var`. This function /// returns the type for the decl if any. clang::QualType type_of(const clang::NamedDecl* decl); -const clang::NamedDecl* get_decl_for_type(const clang::Type* T); - /// Get the underlying decl for a type if any. -const clang::NamedDecl* decl_of(clang::QualType type); +auto decl_of(clang::QualType type) -> const clang::NamedDecl*; -/// Check whether the decl is anonymous. -bool is_anonymous(const clang::NamedDecl* decl); +/// Recursively strips all pointers, references, and array extents from +/// a TypeLoc. e.g., for "const int*(&)[3]", the result will be location +/// "for int". +auto unwrap_type(clang::TypeLoc type, bool unwrap_function_type = true) -> clang::TypeLoc; -clang::NestedNameSpecifierLoc get_qualifier_loc(const clang::NamedDecl* decl); +auto get_only_instantiation(clang::NamedDecl* templated_decl) -> clang::NamedDecl*; -auto get_template_specialization_args(const clang::NamedDecl* decl) - -> std::optional>; +auto get_only_instantiation(clang::ParmVarDecl* param) -> clang::ParmVarDecl*; -std::string print_template_specialization_args(const clang::NamedDecl* decl); - -std::string print_name(const clang::NamedDecl* decl); - -clang::TemplateTypeParmTypeLoc get_contained_auto_param_type(clang::TypeLoc TL); - -clang::NamedDecl* get_only_instantiation(clang::NamedDecl* TemplatedDecl); - -/// getSimpleName() returns the plain identifier for an entity, if any. -llvm::StringRef getSimpleName(const clang::DeclarationName& DN); -llvm::StringRef getSimpleName(const clang::NamedDecl& D); -llvm::StringRef getSimpleName(clang::QualType T); - -std::string summarizeExpr(const clang::Expr* E); - -// Returns the template parameter pack type from an instantiated function -// template, if it exists, nullptr otherwise. -const clang::TemplateTypeParmType* getFunctionPackType(const clang::FunctionDecl* Callee); +std::string summarize_expr(const clang::Expr* E); // Returns the template parameter pack type that this parameter was expanded // from (if in the Args... or Args&... or Args&&... form), if this is the case, // nullptr otherwise. -const clang::TemplateTypeParmType* getUnderlyingPackType(const clang::ParmVarDecl* Param); +const clang::TemplateTypeParmType* underlying_pack_type(const clang::ParmVarDecl* param); // Returns the parameters that are forwarded from the template parameters. // For example, `template void foo(Args... args)` will return // the `args` parameters. -llvm::SmallVector - resolveForwardingParameters(const clang::FunctionDecl* D, unsigned MaxDepth = 10); - -// Determines if any intermediate type in desugaring QualType QT is of -// substituted template parameter type. Ignore pointer or reference wrappers. -bool isSugaredTemplateParameter(clang::QualType QT); +auto resolve_forwarding_params(const clang::FunctionDecl* decl, unsigned max_depth = 10) + -> llvm::SmallVector; // A simple wrapper for `clang::desugarForDiagnostic` that provides optional // semantic. -std::optional desugar(clang::ASTContext& AST, clang::QualType QT); +std::optional desugar(clang::ASTContext& context, clang::QualType type); // Apply a series of heuristic methods to determine whether or not a QualType QT // is suitable for desugaring (e.g. getting the real name behind the using-alias @@ -85,12 +72,12 @@ std::optional desugar(clang::ASTContext& AST, clang::QualType Q // // This could be refined further. See // https://github.com/clangd/clangd/issues/1298. -clang::QualType maybeDesugar(clang::ASTContext& AST, clang::QualType QT); +clang::QualType maybe_desugar(clang::ASTContext& context, clang::QualType type); // Given a callee expression `Fn`, if the call is through a function pointer, // try to find the declaration of the corresponding function pointer type, // so that we can recover argument names from it. // FIXME: This function is mostly duplicated in SemaCodeComplete.cpp; unify. -clang::FunctionProtoTypeLoc getPrototypeLoc(clang::Expr* Fn); +clang::FunctionProtoTypeLoc proto_type_loc(clang::Expr* expr); } // namespace clice::ast diff --git a/include/Async/Task.h b/include/Async/Task.h index 87d35391..334d9ca7 100644 --- a/include/Async/Task.h +++ b/include/Async/Task.h @@ -317,10 +317,10 @@ public: void stacktrace() { promise_base* handle = core; while(handle) { - clice::println("{}:{}:{}", - handle->location.file_name(), - handle->location.line(), - handle->location.function_name()); + std::println("{}:{}:{}", + handle->location.file_name(), + handle->location.line(), + handle->location.function_name()); handle = handle->continuation; } } diff --git a/include/Compiler/CompilationUnit.h b/include/Compiler/CompilationUnit.h index 66c8bb08..029e6625 100644 --- a/include/Compiler/CompilationUnit.h +++ b/include/Compiler/CompilationUnit.h @@ -122,21 +122,32 @@ public: /// Create a file location with given file id and offset. auto create_location(clang::FileID fid, std::uint32_t offset) -> clang::SourceLocation; + using TokenRange = llvm::ArrayRef; + /// Get the spelled tokens(raw token) of the file id. - auto spelled_tokens(clang::FileID fid) -> llvm::ArrayRef; + auto spelled_tokens(clang::FileID fid) -> TokenRange; + + /// Return the spelled tokens corresponding to the range. + auto spelled_tokens(clang::SourceRange range) -> TokenRange; /// The spelled tokens that overlap or touch a spelling location Loc. /// This always returns 0-2 tokens. - auto spelled_tokens_touch(clang::SourceLocation location) - -> llvm::ArrayRef; + auto spelled_tokens_touch(clang::SourceLocation location) -> TokenRange; - auto expanded_tokens() -> llvm::ArrayRef; + /// All tokens produced by the preprocessor after all macro replacements, + /// directives, etc. Source locations found in the clang AST will always + /// point to one of these tokens. + /// Tokens are in TU order (per SourceManager::isBeforeInTranslationUnit()). + /// FIXME: figure out how to handle token splitting, e.g. '>>' can be split + /// into two '>' tokens by the parser. However, TokenBuffer currently + /// keeps it as a single '>>' token. + auto expanded_tokens() -> TokenRange; - /// Get the expanded tokens(after preprocessing) of the file id. - auto expanded_tokens(clang::SourceRange range) -> llvm::ArrayRef; + /// Returns the subrange of expandedTokens() corresponding to the closed + /// token range R. + auto expanded_tokens(clang::SourceRange range) -> TokenRange; - auto expansions_overlapping(llvm::ArrayRef) - -> std::vector; + auto expansions_overlapping(TokenRange) -> std::vector; /// Get the token length. auto token_length(clang::SourceLocation location) -> std::uint32_t; diff --git a/include/Feature/InlayHint.h b/include/Feature/InlayHint.h index 78850c71..b1ae5460 100644 --- a/include/Feature/InlayHint.h +++ b/include/Feature/InlayHint.h @@ -50,8 +50,8 @@ struct InlayHint { std::vector parts; }; -using InlayHints = std::vector; - -InlayHints inlay_hints(CompilationUnit& unit, LocalSourceRange target); +auto inlay_hint(CompilationUnit& unit, + LocalSourceRange target, + const config::InlayHintsOptions& options) -> std::vector; } // namespace clice::feature diff --git a/include/Protocol/Feature/InlayHint.h b/include/Protocol/Feature/InlayHint.h index f1eae0d3..ba3e4e20 100644 --- a/include/Protocol/Feature/InlayHint.h +++ b/include/Protocol/Feature/InlayHint.h @@ -4,7 +4,13 @@ namespace clice::proto { -struct InlayHintClientCapabilities {}; +struct InlayHintClientCapabilities { + /// Indicates which properties a client can resolve lazily on an inlay hint. + struct { + /// The properties that a client can resolve lazily. + array properties; + } resolveSupport; +}; struct InlayHintOptions { /// The server provides support to resolve additional @@ -12,4 +18,57 @@ struct InlayHintOptions { bool resolveProvider; }; +struct InlayHintParams { + /// The text document. + TextDocumentIdentifier textDocument; + + /// The visible document range for which inlay hints should be computed. + Range range; +}; + +enum class InlayHintKind { + /// An inlay hint that for a type annotation. + Type = 1, + + /// An inlay hint that is for a parameter. + Parameter = 2, +}; + +struct InlayHintLabelPart { + /// The value of this label part. + string value; + + /// An optional source code location that represents this + /// label part. + /// + /// The editor will use this location for the hover and for code navigation + /// features: This part will become a clickable link that resolves to the + /// definition of the symbol at the given location (not necessarily the + /// location itself), it shows the hover that shows at the given location, + /// and it shows a context menu with further code navigation commands. + /// + /// Depending on the client capability `inlayHint.resolveSupport` clients + /// might resolve this property late using the resolve request. + /// FIXME: Location location; +}; + +struct InlayHint { + /// The position of this hint. + /// + /// If multiple hints have the same position, they will be shown in the order + /// they appear in the response. + Position position; + + /// The label of this hint. A human readable string or an array of + /// InlayHintLabelPart label parts. + /// + /// *Note* that neither the string nor the label part can be empty. + /// TODO: Use label + array label; + + /// The kind of this hint. Can be omitted in which case the client + /// should fall back to a reasonable default. + InlayHintKind kind; +}; + } // namespace clice::proto diff --git a/include/Protocol/Lifecycle.h b/include/Protocol/Lifecycle.h index b4b868af..905a1273 100644 --- a/include/Protocol/Lifecycle.h +++ b/include/Protocol/Lifecycle.h @@ -163,7 +163,7 @@ struct ServerCapabilities { /// FIXME: InlineValueOptions inlineValueProvider; /// The server provides inlay hints. - /// FIXME: InlayHintOptions inlayHintProvider; + InlayHintOptions inlayHintProvider; /// The server has support for pull model diagnostics. /// FIXME: DiagnosticOptions diagnosticProvider; diff --git a/include/Server/Server.h b/include/Server/Server.h index 5b207356..413e7726 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -208,6 +208,8 @@ private: async::Task on_semantic_token(proto::SemanticTokensParams params); + async::Task on_inlay_hint(proto::InlayHintParams params); + private: /// The current request id. std::uint32_t id = 0; diff --git a/include/Support/FileSystem.h b/include/Support/FileSystem.h index c366b4b2..28dfd4cd 100644 --- a/include/Support/FileSystem.h +++ b/include/Support/FileSystem.h @@ -146,7 +146,7 @@ inline std::string toPath(llvm::StringRef uri) { llvm::SmallString<128> result; if(auto err = fs::real_path(decoded, result)) { - clice::print("Failed to get real path: {}, Input is {}\n", err.message(), decoded); + std::println("Failed to get real path: {}, Input is {}\n", err.message(), decoded); std::abort(); } diff --git a/include/Support/Format.h b/include/Support/Format.h index b647805d..12565767 100644 --- a/include/Support/Format.h +++ b/include/Support/Format.h @@ -1,25 +1,11 @@ #pragma once #include - +#include #include "Support/JSON.h" #include "Support/Ranges.h" #include "llvm/Support/Error.h" -namespace clice { - -template -void print(std::format_string fmt, Args&&... args) { - llvm::outs() << std::vformat(fmt.get(), std::make_format_args(args...)); -} - -template -void println(std::format_string fmt, Args&&... args) { - llvm::outs() << std::vformat(fmt.get(), std::make_format_args(args...)) << '\n'; -} - -} // namespace clice - template <> struct std::formatter : std::formatter { using Base = std::formatter; @@ -171,7 +157,7 @@ std::string pretty_dump(const Object& object, std::size_t indent = 2) { std::string repr = dump(object); auto json = json::parse(repr); if(!json) { - clice::println("{} {}", json.takeError(), repr); + std::println("{} {}", json.takeError(), repr); std::abort(); } llvm::SmallString<128> buffer = {std::format("{{0:{}}}", indent)}; diff --git a/include/Test/Test.h b/include/Test/Test.h index 3baca2a0..3b207898 100644 --- a/include/Test/Test.h +++ b/include/Test/Test.h @@ -84,16 +84,22 @@ struct may_failure { bool fatal = false; std::string expression; std::source_location location; + std::string message; + + may_failure& operator<< (std::string message) { + this->message += std::move(message); + return *this; + } ~may_failure() { Runner::instance().fail(*this); } }; -inline struct { +constexpr inline struct { template may_failure operator() (const TExpr& expr, - std::source_location location = std::source_location::current()) { + std::source_location location = std::source_location::current()) const { bool failed = false; std::string expression = "false"; @@ -115,16 +121,18 @@ inline struct { } } expect; -inline struct { - test&& operator/ (test&& test) { +constexpr inline struct { + test&& operator/ (test&& test) const { test.skipped = true; return std::move(test); } } skip; -inline struct { - may_failure&& operator/ (may_failure&& failure) { - failure.fatal = true; +constexpr inline struct { + may_failure&& operator/ (may_failure&& failure) const { + if(failure.failed) { + failure.fatal = true; + } return std::move(failure); } } fatal; diff --git a/include/Test/Tester.h b/include/Test/Tester.h index 17f5c79b..a23897d1 100644 --- a/include/Test/Tester.h +++ b/include/Test/Tester.h @@ -88,7 +88,7 @@ struct Tester { if(!unit) { llvm::outs() << unit.error() << "\n"; for(auto& diag: *params.diagnostics) { - clice::println("{}", diag.message); + std::println("{}", diag.message); } return false; } diff --git a/src/AST/Utility.cpp b/src/AST/Utility.cpp index 37528f67..ed1d3ca3 100644 --- a/src/AST/Utility.cpp +++ b/src/AST/Utility.cpp @@ -1,4 +1,5 @@ #include "AST/Utility.h" +#include "Support/Format.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" @@ -52,6 +53,11 @@ bool is_templated(const clang::Decl* decl) { return false; } +bool is_anonymous(const clang::NamedDecl* decl) { + auto name = decl->getDeclName(); + return name.isIdentifier() && !name.getAsIdentifierInfo(); +} + const static clang::CXXRecordDecl* getDeclContextForTemplateInstationPattern(const clang::Decl* D) { if(const auto* CTSD = dyn_cast(D->getDeclContext())) { return CTSD->getTemplateInstantiationPattern(); @@ -150,6 +156,40 @@ const clang::NamedDecl* normalize(const clang::NamedDecl* decl) { return decl; } +llvm::StringRef simple_name(const clang::DeclarationName& name) { + if(clang::IdentifierInfo* Ident = name.getAsIdentifierInfo()) { + return Ident->getName(); + } + + return ""; +} + +llvm::StringRef identifier_of(const clang::NamedDecl& D) { + if(clang::IdentifierInfo* identifier = D.getIdentifier()) { + return identifier->getName(); + } + + return ""; +} + +llvm::StringRef identifier_of(clang::QualType type) { + if(const auto* ET = llvm::dyn_cast(type)) { + return identifier_of(ET->getNamedType()); + } + + if(const auto* BT = llvm::dyn_cast(type)) { + clang::PrintingPolicy PP(clang::LangOptions{}); + PP.adjustForCPlusPlus(); + return BT->getName(PP); + } + + if(const auto* D = decl_of(type)) { + return identifier_of(*D); + } + + return ""; +} + std::string name_of(const clang::NamedDecl* decl) { llvm::SmallString<128> result; @@ -213,102 +253,66 @@ std::string name_of(const clang::NamedDecl* decl) { } clang::QualType type_of(const clang::NamedDecl* decl) { - if(auto VD = llvm::dyn_cast(decl)) { - return VD->getType(); - } + using namespace clang; - if(auto FD = llvm::dyn_cast(decl)) { - return FD->getType(); - } - - if(auto ECD = llvm::dyn_cast(decl)) { - return ECD->getType(); - } - - if(auto BD = llvm::dyn_cast(decl)) { - return BD->getType(); - } - - if(auto TD = llvm::dyn_cast(decl)) { - return TD->getUnderlyingType(); - } - - if(auto CCD = llvm::dyn_cast(decl)) { - return CCD->getThisType(); - } - - if(auto CDD = llvm::dyn_cast(decl)) { - return CDD->getThisType(); + if(auto value = dyn_cast(decl)) { + if(isa(value)) { + return value->getType(); + } else if(auto ctor = dyn_cast(decl)) { + return ctor->getThisType(); + } else if(auto dtor = dyn_cast(decl)) { + return dtor->getThisType(); + } + } else if(auto type = dyn_cast(decl)) { + return QualType(type->getTypeForDecl(), 0); } return clang::QualType(); } -// getDeclForType() returns the decl responsible for Type's spelling. -// This is the inverse of ASTContext::getTypeDeclType(). template requires requires(Ty* T) { T->getDecl(); } -const clang::NamedDecl* get_decl_for_type_impl(const Ty* T) { +const clang::NamedDecl* decl_of_impl(const Ty* T) { return T->getDecl(); } -const clang::NamedDecl* get_decl_for_type_impl(const void* T) { +const clang::NamedDecl* decl_of_impl(const void* T) { return nullptr; } -const clang::NamedDecl* get_decl_for_type(const clang::Type* T) { - switch(T->getTypeClass()) { +auto decl_of(clang::QualType type) -> const clang::NamedDecl* { + switch(type->getTypeClass()) { #define ABSTRACT_TYPE(TY, BASE) #define TYPE(TY, BASE) \ - case clang::Type::TY: return get_decl_for_type_impl(llvm::cast(T)); + case clang::Type::TY: return decl_of_impl(llvm::cast(type)); #include "clang/AST/TypeNodes.inc" } - llvm_unreachable("Unknown TypeClass enum"); -} -const clang::NamedDecl* decl_of(clang::QualType type) { - if(type.isNull()) { - return nullptr; - } - - if(auto RT = type->getAs()) { - return RT->getDecl(); - } - - if(auto TT = type->getAs()) { - return TT->getDecl(); - } - - /// FIXME: - if(auto TST = type->getAs()) { - - auto decl = TST->getTemplateName().getAsTemplateDecl(); - if(type->isDependentType()) { - return decl; - } - - /// For a template specialization type, the template name is possibly a `ClassTemplateDecl` - /// `TypeAliasTemplateDecl` or `TemplateTemplateParmDecl` and `BuiltinTemplateDecl`. - if(llvm::isa(decl)) { - return decl->getTemplatedDecl(); - } - - if(llvm::isa(decl)) { - return decl; - } - - return instantiated_from(TST->getAsCXXRecordDecl()); - } + /// FIXME: Handle Template Specialization type in the future + /// if(auto TST = type->getAs()) { + /// auto decl = TST->getTemplateName().getAsTemplateDecl(); + /// if(type->isDependentType()) { + /// return decl; + /// } + /// + /// /// For a template specialization type, the template name is possibly a + /// `ClassTemplateDecl` + /// /// `TypeAliasTemplateDecl` or `TemplateTemplateParmDecl` and `BuiltinTemplateDecl`. + /// if(llvm::isa(decl)) { + /// return decl->getTemplatedDecl(); + /// } + /// + /// if(llvm::isa(decl)) { + /// return decl; + /// } + /// + /// return instantiated_from(TST->getAsCXXRecordDecl()); + ///} return nullptr; } -bool is_anonymous(const clang::NamedDecl* decl) { - auto name = decl->getDeclName(); - return name.isIdentifier() && !name.getAsIdentifierInfo(); -} - -clang::NestedNameSpecifierLoc get_qualifier_loc(const clang::NamedDecl* decl) { +auto get_qualifier_loc(const clang::NamedDecl* decl) -> clang::NestedNameSpecifierLoc { if(auto* V = llvm::dyn_cast(decl)) { return V->getQualifierLoc(); } @@ -343,81 +347,93 @@ auto get_template_specialization_args(const clang::NamedDecl* decl) } std::string print_template_specialization_args(const clang::NamedDecl* decl) { - std::string TemplateArgs; - llvm::raw_string_ostream OS(TemplateArgs); - clang::PrintingPolicy Policy(decl->getASTContext().getLangOpts()); - if(auto Args = ast::get_template_specialization_args(decl)) { - printTemplateArgumentList(OS, *Args, Policy); - } else if(auto* Cls = llvm::dyn_cast(decl)) { + std::string template_args; + llvm::raw_string_ostream os(template_args); + clang::PrintingPolicy policy(decl->getASTContext().getLangOpts()); + + if(auto args = get_template_specialization_args(decl)) { + printTemplateArgumentList(os, *args, policy); + } else if(auto* cls = llvm::dyn_cast(decl)) { // FIXME: Fix cases when getTypeAsWritten returns null inside clang AST, // e.g. friend decls. Currently we fallback to Template Arguments without // location information. - printTemplateArgumentList(OS, Cls->getTemplateArgs().asArray(), Policy); + printTemplateArgumentList(os, cls->getTemplateArgs().asArray(), policy); } - return TemplateArgs; + + return template_args; } -std::string print_name(const clang::NamedDecl* decl) { - std::string Name; - llvm::raw_string_ostream Out(Name); - clang::PrintingPolicy PP(decl->getASTContext().getLangOpts()); +std::string display_name_of(const clang::NamedDecl* decl) { + std::string name; + llvm::raw_string_ostream out(name); + clang::PrintingPolicy policy(decl->getASTContext().getLangOpts()); + // We don't consider a class template's args part of the constructor name. - PP.SuppressTemplateArgsInCXXConstructors = true; + policy.SuppressTemplateArgsInCXXConstructors = true; // Handle 'using namespace'. They all have the same name - . if(auto* UD = llvm::dyn_cast(decl)) { - Out << "using namespace "; + out << "using namespace "; if(auto* Qual = UD->getQualifier()) - Qual->print(Out, PP); - UD->getNominatedNamespaceAsWritten()->printName(Out); - return Out.str(); + Qual->print(out, policy); + UD->getNominatedNamespaceAsWritten()->printName(out); + return out.str(); } - if(ast::is_anonymous(decl)) { + if(is_anonymous(decl)) { // Come up with a presentation for an anonymous entity. - if(isa(decl)) + if(llvm::isa(decl)) { return "(anonymous namespace)"; - if(auto* Cls = llvm::dyn_cast(decl)) { - if(Cls->isLambda()) - return "(lambda)"; - return ("(anonymous " + Cls->getKindName() + ")").str(); } - if(isa(decl)) + + if(auto* cls = llvm::dyn_cast(decl)) { + if(cls->isLambda()) { + return "(lambda)"; + } + + return std::format("(anonymous {})", cls->getKindName()); + } + + if(llvm::isa(decl)) { return "(anonymous enum)"; + } + return "(anonymous)"; } // Print nested name qualifier if it was written in the source code. - if(auto* Qualifier = ast::get_qualifier_loc(decl).getNestedNameSpecifier()) - Qualifier->print(Out, PP); - // Print the name itself. - decl->getDeclName().print(Out, PP); - // Print template arguments. - Out << ast::print_template_specialization_args(decl); + if(auto* qualifier = get_qualifier_loc(decl).getNestedNameSpecifier()) { + qualifier->print(out, policy); + } - return Out.str(); + // Print the name itself. + decl->getDeclName().print(out, policy); + // Print template arguments. + out << print_template_specialization_args(decl); + + return out.str(); } -clang::TemplateTypeParmTypeLoc get_contained_auto_param_type(clang::TypeLoc TL) { - if(auto QTL = TL.getAs()) { - return get_contained_auto_param_type(QTL.getUnqualifiedLoc()); - } - - if(llvm::isa(TL.getTypePtr())) { - return get_contained_auto_param_type(TL.getNextTypeLoc()); - } - - if(auto FTL = TL.getAs()) { - return get_contained_auto_param_type(FTL.getReturnLoc()); - } - - if(auto TTPTL = TL.getAs()) { - if(TTPTL.getTypePtr()->getDecl()->isImplicit()) { - return TTPTL; +auto unwrap_type(clang::TypeLoc type, bool unwrap_function_type) -> clang::TypeLoc { + while(true) { + if(auto qualified = type.getAs()) { + type = qualified.getUnqualifiedLoc(); + } else if(auto reference = type.getAs()) { + type = reference.getPointeeLoc(); + } else if(auto pointer = type.getAs()) { + type = pointer.getPointeeLoc(); + } else if(auto paren = type.getAs()) { + type = paren.getInnerLoc(); + } else if(auto array = type.getAs()) { + type = array.getElementLoc(); + } else if(auto proto = type.getAs(); + proto && unwrap_function_type) { + type = proto.getReturnLoc(); + } else { + break; } } - - return {}; + return type; } template @@ -445,31 +461,32 @@ clang::NamedDecl* get_only_instantiation(clang::NamedDecl* TemplatedDecl) { return nullptr; } -// getSimpleName() returns the plain identifier for an entity, if any. -llvm::StringRef getSimpleName(const clang::DeclarationName& DN) { - if(clang::IdentifierInfo* Ident = DN.getAsIdentifierInfo()) - return Ident->getName(); - return ""; -} +auto get_only_instantiation(clang::ParmVarDecl* decl) -> clang::ParmVarDecl* { + auto* TemplateFunction = llvm::dyn_cast(decl->getDeclContext()); + if(!TemplateFunction) + return nullptr; + auto* InstantiatedFunction = + llvm::dyn_cast_or_null(get_only_instantiation(TemplateFunction)); + if(!InstantiatedFunction) + return nullptr; -llvm::StringRef getSimpleName(const clang::NamedDecl& D) { - return getSimpleName(D.getDeclName()); -} - -llvm::StringRef getSimpleName(clang::QualType T) { - if(const auto* ET = llvm::dyn_cast(T)) - return getSimpleName(ET->getNamedType()); - if(const auto* BT = llvm::dyn_cast(T)) { - clang::PrintingPolicy PP(clang::LangOptions{}); - PP.adjustForCPlusPlus(); - return BT->getName(PP); + unsigned ParamIdx = 0; + for(auto* Param: TemplateFunction->parameters()) { + // Can't reason about param indexes in the presence of preceding packs. + // And if this param is a pack, it may expand to multiple params. + if(Param->isParameterPack()) + return nullptr; + if(Param == decl) + break; + ++ParamIdx; } - if(const auto* D = ast::get_decl_for_type(T.getTypePtr())) - return getSimpleName(D->getDeclName()); - return ""; + assert(ParamIdx < TemplateFunction->getNumParams() && "Couldn't find param in list?"); + assert(ParamIdx < InstantiatedFunction->getNumParams() && + "Instantiated function has fewer (non-pack) parameters?"); + return InstantiatedFunction->getParamDecl(ParamIdx); } -std::string summarizeExpr(const clang::Expr* E) { +std::string summarize_expr(const clang::Expr* E) { using namespace clang; struct Namer : ConstStmtVisitor { @@ -481,11 +498,11 @@ std::string summarizeExpr(const clang::Expr* E) { // Any sort of decl reference, we just use the unqualified name. std::string VisitMemberExpr(const MemberExpr* E) { - return ast::getSimpleName(*E->getMemberDecl()).str(); + return identifier_of(*E->getMemberDecl()).str(); } std::string VisitDeclRefExpr(const DeclRefExpr* E) { - return ast::getSimpleName(*E->getFoundDecl()).str(); + return identifier_of(*E->getFoundDecl()).str(); } std::string VisitCallExpr(const CallExpr* E) { @@ -493,19 +510,19 @@ std::string summarizeExpr(const clang::Expr* E) { } std::string VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr* E) { - return ast::getSimpleName(E->getMember()).str(); + return simple_name(E->getMember()).str(); } std::string VisitDependentScopeDeclRefExpr(const DependentScopeDeclRefExpr* E) { - return ast::getSimpleName(E->getDeclName()).str(); + return simple_name(E->getDeclName()).str(); } std::string VisitCXXFunctionalCastExpr(const CXXFunctionalCastExpr* E) { - return ast::getSimpleName(E->getType()).str(); + return identifier_of(E->getType()).str(); } std::string VisitCXXTemporaryObjectExpr(const CXXTemporaryObjectExpr* E) { - return ast::getSimpleName(E->getType()).str(); + return identifier_of(E->getType()).str(); } // Step through implicit nodes that clang doesn't classify as such. @@ -629,39 +646,47 @@ std::string summarizeExpr(const clang::Expr* E) { return Namer{}.Visit(E); } -// returns true for `X` in `template void foo()` -bool isTemplateTypeParameterPack(clang::NamedDecl* D) { - if(const auto* TTPD = llvm::dyn_cast(D)) { - return TTPD->isParameterPack(); - } - return false; -} +// Returns the template parameter pack type from an instantiated function +// template, if it exists, nullptr otherwise. +const clang::TemplateTypeParmType* function_pack_type(const clang::FunctionDecl* callee) { + // returns true for `X` in `template void foo()` + auto is_type_pack = [](clang::NamedDecl* decl) { + if(const auto* TTPD = llvm::dyn_cast(decl)) { + return TTPD->isParameterPack(); + } + return false; + }; -const clang::TemplateTypeParmType* getFunctionPackType(const clang::FunctionDecl* Callee) { - if(const auto* TemplateDecl = Callee->getPrimaryTemplate()) { - auto TemplateParams = TemplateDecl->getTemplateParameters()->asArray(); + if(const auto* decl = callee->getPrimaryTemplate()) { + auto template_params = decl->getTemplateParameters()->asArray(); // find the template parameter pack from the back - const auto It = std::find_if(TemplateParams.rbegin(), - TemplateParams.rend(), - isTemplateTypeParameterPack); - if(It != TemplateParams.rend()) { - const auto* TTPD = llvm::dyn_cast(*It); + const auto it = + std::ranges::find_if(template_params.rbegin(), template_params.rend(), is_type_pack); + if(it != template_params.rend()) { + const auto* TTPD = llvm::dyn_cast(*it); return TTPD->getTypeForDecl()->castAs(); } } + return nullptr; } -const clang::TemplateTypeParmType* getUnderlyingPackType(const clang::ParmVarDecl* Param) { - const auto* PlainType = Param->getType().getTypePtr(); - if(auto* RT = llvm::dyn_cast(PlainType)) - PlainType = RT->getPointeeTypeAsWritten().getTypePtr(); - if(const auto* SubstType = llvm::dyn_cast(PlainType)) { - const auto* ReplacedParameter = SubstType->getReplacedParameter(); - if(ReplacedParameter->isParameterPack()) { - return ReplacedParameter->getTypeForDecl()->castAs(); +// Returns the template parameter pack type that this parameter was expanded +// from (if in the Args... or Args&... or Args&&... form), if this is the case, +// nullptr otherwise. +const clang::TemplateTypeParmType* underlying_pack_type(const clang::ParmVarDecl* param) { + const auto* type = param->getType().getTypePtr(); + if(auto* ref_type = llvm::dyn_cast(type)) { + type = ref_type->getPointeeTypeAsWritten().getTypePtr(); + } + + if(const auto* subst_type = llvm::dyn_cast(type)) { + const auto* decl = subst_type->getReplacedParameter(); + if(decl->isParameterPack()) { + return decl->getTypeForDecl()->castAs(); } } + return nullptr; } @@ -686,7 +711,7 @@ const clang::TemplateTypeParmType* getUnderlyingPackType(const clang::ParmVarDec class ForwardingCallVisitor : public clang::RecursiveASTVisitor { public: ForwardingCallVisitor(llvm::ArrayRef Parameters) : - Parameters{Parameters}, PackType{ast::getUnderlyingPackType(Parameters.front())} {} + Parameters{Parameters}, PackType{underlying_pack_type(Parameters.front())} {} bool VisitCallExpr(clang::CallExpr* E) { auto* Callee = getCalleeDeclOrUniqueOverload(E); @@ -748,10 +773,10 @@ private: Callee->parameters().slice(*PackLocation, Parameters.size()); // Check whether the function has a parameter pack as the last template // parameter - if(const auto* TTPT = ast::getFunctionPackType(Callee)) { + if(const auto* TTPT = function_pack_type(Callee)) { // In this case: Separate the parameters into head, pack and tail auto IsExpandedPack = [&](const clang::ParmVarDecl* P) { - return ast::getUnderlyingPackType(P) == TTPT; + return underlying_pack_type(P) == TTPT; }; ForwardingInfo FI; FI.Head = MatchingParams.take_until(IsExpandedPack); @@ -846,21 +871,22 @@ private: } }; -llvm::SmallVector - resolveForwardingParameters(const clang::FunctionDecl* D, unsigned MaxDepth) { - auto Parameters = D->parameters(); +auto resolve_forwarding_params(const clang::FunctionDecl* D, unsigned MaxDepth) + -> llvm::SmallVector { + auto params = D->parameters(); + // If the function has a template parameter pack - if(const auto* TTPT = ast::getFunctionPackType(D)) { + if(const auto* TTPT = function_pack_type(D)) { // Split the parameters into head, pack and tail auto IsExpandedPack = [TTPT](const clang::ParmVarDecl* P) { - return ast::getUnderlyingPackType(P) == TTPT; + return underlying_pack_type(P) == TTPT; }; - llvm::ArrayRef Head = Parameters.take_until(IsExpandedPack); + llvm::ArrayRef Head = params.take_until(IsExpandedPack); llvm::ArrayRef Pack = - Parameters.drop_front(Head.size()).take_while(IsExpandedPack); + params.drop_front(Head.size()).take_while(IsExpandedPack); llvm::ArrayRef Tail = - Parameters.drop_front(Head.size() + Pack.size()); - llvm::SmallVector Result(Parameters.size()); + params.drop_front(Head.size() + Pack.size()); + llvm::SmallVector Result(params.size()); // Fill in non-pack parameters auto* HeadIt = std::copy(Head.begin(), Head.end(), Result.begin()); auto TailIt = std::copy(Tail.rbegin(), Tail.rend(), Result.rbegin()); @@ -894,7 +920,7 @@ llvm::SmallVector if(const auto* Template = CurrentFunction->getPrimaryTemplate()) { bool NewFunction = SeenTemplates.insert(Template).second; if(!NewFunction) { - return {Parameters.begin(), Parameters.end()}; + return {params.begin(), params.end()}; } } } @@ -905,15 +931,17 @@ llvm::SmallVector assert(TailIt.base() == HeadIt); return Result; } - return {Parameters.begin(), Parameters.end()}; + return {params.begin(), params.end()}; } -bool isSugaredTemplateParameter(clang::QualType QT) { - static auto PeelWrapper = [](clang::QualType QT) { +// Determines if any intermediate type in desugaring QualType QT is of +// substituted template parameter type. Ignore pointer or reference wrappers. +static bool isSugaredTemplateParameter(clang::QualType type) { + static auto peel_wrapper = [](clang::QualType type) { // Neither `PointerType` nor `ReferenceType` is considered as sugared // type. Peel it. - clang::QualType Peeled = QT->getPointeeType(); - return Peeled.isNull() ? QT : Peeled; + clang::QualType peeled = type->getPointeeType(); + return peeled.isNull() ? type : peeled; }; // This is a bit tricky: we traverse the type structure and find whether or @@ -941,80 +969,94 @@ bool isSugaredTemplateParameter(clang::QualType QT) { // As such, we always prefer the desugared over the pointee for next type // in the iteration. It could avoid the getPointeeType's implicit desugaring. while(true) { - if(QT->getAs()) + if(type->getAs()) { return true; - clang::QualType Desugared = QT->getLocallyUnqualifiedSingleStepDesugaredType(); - if(Desugared != QT) - QT = Desugared; - else if(auto Peeled = PeelWrapper(Desugared); Peeled != QT) - QT = Peeled; - else + } + + clang::QualType desugared = type->getLocallyUnqualifiedSingleStepDesugaredType(); + if(desugared != type) { + type = desugared; + } else if(auto peeled = peel_wrapper(desugared); peeled != type) { + type = peeled; + } else { break; + } } return false; } -std::optional desugar(clang::ASTContext& AST, clang::QualType QT) { +std::optional desugar(clang::ASTContext& context, clang::QualType type) { bool ShouldAKA = false; - auto Desugared = clang::desugarForDiagnostic(AST, QT, ShouldAKA); - if(!ShouldAKA) + auto Desugared = clang::desugarForDiagnostic(context, type, ShouldAKA); + if(!ShouldAKA) { return std::nullopt; + } + return Desugared; } -clang::QualType maybeDesugar(clang::ASTContext& AST, clang::QualType QT) { +clang::QualType maybe_desugar(clang::ASTContext& context, clang::QualType type) { // Prefer desugared type for name that aliases the template parameters. // This can prevent things like printing opaque `: type` when accessing std // containers. - if(ast::isSugaredTemplateParameter(QT)) - return desugar(AST, QT).value_or(QT); + if(isSugaredTemplateParameter(type)) { + return desugar(context, type).value_or(type); + } // Prefer desugared type for `decltype(expr)` specifiers. - if(QT->isDecltypeType()) - return QT.getCanonicalType(); + if(type->isDecltypeType()) { + return type.getCanonicalType(); + } - if(const auto* AT = QT->getContainedAutoType()) - if(!AT->getDeducedType().isNull() && AT->getDeducedType()->isDecltypeType()) - return QT.getCanonicalType(); - - return QT; -} - -clang::FunctionProtoTypeLoc getPrototypeLoc(clang::Expr* Fn) { - clang::TypeLoc Target; - clang::Expr* NakedFn = Fn->IgnoreParenCasts(); - if(const auto* T = NakedFn->getType().getTypePtr()->getAs()) { - Target = T->getDecl()->getTypeSourceInfo()->getTypeLoc(); - } else if(const auto* DR = llvm::dyn_cast(NakedFn)) { - const auto* D = DR->getDecl(); - if(const auto* const VD = llvm::dyn_cast(D)) { - Target = VD->getTypeSourceInfo()->getTypeLoc(); + if(const auto* AT = type->getContainedAutoType()) { + if(!AT->getDeducedType().isNull() && AT->getDeducedType()->isDecltypeType()) { + return type.getCanonicalType(); } } - if(!Target) + return type; +} + +clang::FunctionProtoTypeLoc proto_type_loc(clang::Expr* expr) { + clang::TypeLoc target; + clang::Expr* naked_fn = expr->IgnoreParenCasts(); + + if(const auto* T = naked_fn->getType().getTypePtr()->getAs()) { + target = T->getDecl()->getTypeSourceInfo()->getTypeLoc(); + } else if(const auto* DR = llvm::dyn_cast(naked_fn)) { + const auto* D = DR->getDecl(); + if(const auto* const VD = llvm::dyn_cast(D)) { + target = VD->getTypeSourceInfo()->getTypeLoc(); + } + } + + if(!target) { return {}; + } // Unwrap types that may be wrapping the function type while(true) { - if(auto P = Target.getAs()) { - Target = P.getPointeeLoc(); + if(auto p = target.getAs()) { + target = p.getPointeeLoc(); continue; } - if(auto A = Target.getAs()) { - Target = A.getModifiedLoc(); + + if(auto a = target.getAs()) { + target = a.getModifiedLoc(); continue; } - if(auto P = Target.getAs()) { - Target = P.getInnerLoc(); + + if(auto p = target.getAs()) { + target = p.getInnerLoc(); continue; } + break; } - if(auto F = Target.getAs()) { - return F; + if(auto f = target.getAs()) { + return f; } return {}; diff --git a/src/Compiler/CompilationUnit.cpp b/src/Compiler/CompilationUnit.cpp index cb7a6000..fa17d298 100644 --- a/src/Compiler/CompilationUnit.cpp +++ b/src/Compiler/CompilationUnit.cpp @@ -151,25 +151,32 @@ auto CompilationUnit::create_location(clang::FileID fid, std::uint32_t offset) return impl->src_mgr.getComposedLoc(fid, offset); } -auto CompilationUnit::spelled_tokens(clang::FileID fid) -> llvm::ArrayRef { +auto CompilationUnit::spelled_tokens(clang::FileID fid) -> TokenRange { return impl->buffer->spelledTokens(fid); } -auto CompilationUnit::spelled_tokens_touch(clang::SourceLocation location) - -> llvm::ArrayRef { +auto CompilationUnit::spelled_tokens(clang::SourceRange range) -> TokenRange { + auto tokens = impl->buffer->spelledForExpanded(impl->buffer->expandedTokens(range)); + if(!tokens) { + return {}; + } + + return *tokens; +} + +auto CompilationUnit::spelled_tokens_touch(clang::SourceLocation location) -> TokenRange { return clang::syntax::spelledTokensTouching(location, *impl->buffer); } -auto CompilationUnit::expanded_tokens() -> llvm::ArrayRef { +auto CompilationUnit::expanded_tokens() -> TokenRange { return impl->buffer->expandedTokens(); } -auto CompilationUnit::expanded_tokens(clang::SourceRange range) - -> llvm::ArrayRef { +auto CompilationUnit::expanded_tokens(clang::SourceRange range) -> TokenRange { return impl->buffer->expandedTokens(range); } -auto CompilationUnit::expansions_overlapping(llvm::ArrayRef spelled_tokens) +auto CompilationUnit::expansions_overlapping(TokenRange spelled_tokens) -> std::vector { return impl->buffer->expansionsOverlapping(spelled_tokens); } diff --git a/src/Driver/unit_tests.cc b/src/Driver/unit_tests.cc index f0dcd990..cb1c701b 100644 --- a/src/Driver/unit_tests.cc +++ b/src/Driver/unit_tests.cc @@ -112,6 +112,7 @@ void Runner::fail(const may_failure& failure) { failure.location.column(), failure.expression, CLEAR); + std::println("{}", failure.message); } if(failure.fatal) { diff --git a/src/Feature/InlayHint.cpp b/src/Feature/InlayHint.cpp index c8a30927..5e51e6f8 100644 --- a/src/Feature/InlayHint.cpp +++ b/src/Feature/InlayHint.cpp @@ -1,7 +1,8 @@ +#include "AST/Utility.h" #include "AST/FilterASTVisitor.h" #include "Feature/InlayHint.h" #include "Support/Compare.h" -#include "AST/Utility.h" +#include "Support/Format.h" #include "clang/Lex/Lexer.h" #include "llvm/ADT/StringExtras.h" @@ -9,51 +10,21 @@ namespace clice::feature { namespace { -using namespace clang; - // For now, inlay hints are always anchored at the left or right of their range. enum class HintSide { Left, Right }; -void stripLeadingUnderscores(StringRef& Name) { - Name = Name.ltrim('_'); -} - -bool isExpandedFromParameterPack(const ParmVarDecl* D) { - return ast::getUnderlyingPackType(D) != nullptr; -} - -ArrayRef - maybeDropCxxExplicitObjectParameters(ArrayRef Params) { - if(!Params.empty() && Params.front()->isExplicitObjectParameter()) - Params = Params.drop_front(1); - return Params; -} - -template -std::string joinAndTruncate(const R& Range, size_t MaxLength) { - std::string Out; - llvm::raw_string_ostream OS(Out); - llvm::ListSeparator Sep(", "); - for(auto&& Element: Range) { - OS << Sep; - if(Out.size() + Element.size() >= MaxLength) { - OS << "..."; - break; - } - OS << Element; - } - OS.flush(); - return Out; +bool is_expanded_from_param_pack(const clang::ParmVarDecl* param) { + return ast::underlying_pack_type(param) != nullptr; } // for a ParmVarDecl from a function declaration, returns the corresponding // ParmVarDecl from the definition if possible, nullptr otherwise. -const static ParmVarDecl* getParamDefinition(const ParmVarDecl* P) { - if(auto* Callee = dyn_cast(P->getDeclContext())) { - if(auto* Def = Callee->getDefinition()) { - auto I = std::distance(Callee->param_begin(), llvm::find(Callee->parameters(), P)); - if(I < (int)Callee->getNumParams()) { - return Def->getParamDecl(I); +const clang::ParmVarDecl* param_definition(const clang::ParmVarDecl* param) { + if(auto* callee = dyn_cast(param->getDeclContext())) { + if(auto* def = callee->getDefinition()) { + auto i = std::distance(callee->param_begin(), llvm::find(callee->parameters(), param)); + if(i < (int)callee->getNumParams()) { + return def->getParamDecl(i); } } } @@ -62,29 +33,30 @@ const static ParmVarDecl* getParamDefinition(const ParmVarDecl* P) { // If "E" spells a single unqualified identifier, return that name. // Otherwise, return an empty string. -static StringRef getSpelledIdentifier(const Expr* E) { - E = E->IgnoreUnlessSpelledInSource(); +llvm::StringRef spelled_identifier_of(const clang::Expr* expr) { + expr = expr->IgnoreUnlessSpelledInSource(); - if(auto* DRE = dyn_cast(E)) - if(!DRE->getQualifier()) - return ast::getSimpleName(*DRE->getDecl()); + if(auto* dre = dyn_cast(expr)) + if(!dre->getQualifier()) + return ast::identifier_of(*dre->getDecl()); - if(auto* ME = dyn_cast(E)) - if(!ME->getQualifier() && ME->isImplicitAccess()) - return ast::getSimpleName(*ME->getMemberDecl()); + if(auto* me = dyn_cast(expr)) + if(!me->getQualifier() && me->isImplicitAccess()) + return ast::identifier_of(*me->getMemberDecl()); return {}; } -using NameVec = SmallVector; - -static bool isSetter(const FunctionDecl* Callee, const NameVec& ParamNames) { - if(ParamNames.size() != 1) +bool is_setter(const clang::FunctionDecl* callee, + const llvm::SmallVector& names) { + if(names.size() != 1) { return false; + } - StringRef Name = ast::getSimpleName(*Callee); - if(!Name.starts_with_insensitive("set")) + llvm::StringRef name = ast::identifier_of(*callee); + if(!name.starts_with_insensitive("set")) { return false; + } // In addition to checking that the function has one parameter and its // name starts with "set", also check that the part after "set" matches @@ -97,19 +69,18 @@ static bool isSetter(const FunctionDecl* Callee, const NameVec& ParamNames) { // void setExceptionHandler(EHFunc exception_handler); // We could improve this by replacing `equals_insensitive` with some // `sloppy_equals` which ignores case and also skips underscores. - StringRef WhatItIsSetting = Name.substr(3).ltrim("_"); - return WhatItIsSetting.equals_insensitive(ParamNames[0]); + return name.substr(3).ltrim("_").equals_insensitive(names[0]); } // Checks if the callee is one of the builtins // addressof, as_const, forward, move(_if_noexcept) -static bool isSimpleBuiltin(const FunctionDecl* Callee) { - switch(Callee->getBuiltinID()) { - case Builtin::BIaddressof: - case Builtin::BIas_const: - case Builtin::BIforward: - case Builtin::BImove: - case Builtin::BImove_if_noexcept: return true; +bool is_simple_builtin(const clang::FunctionDecl* callee) { + switch(callee->getBuiltinID()) { + case clang::Builtin::BIaddressof: + case clang::Builtin::BIas_const: + case clang::Builtin::BIforward: + case clang::Builtin::BImove: + case clang::Builtin::BImove_if_noexcept: return true; default: return false; } } @@ -117,75 +88,47 @@ static bool isSimpleBuiltin(const FunctionDecl* Callee) { struct Callee { // Only one of Decl or Loc is set. // Loc is for calls through function pointers. - const FunctionDecl* Decl = nullptr; - FunctionProtoTypeLoc Loc; + const clang::FunctionDecl* decl = nullptr; + clang::FunctionProtoTypeLoc loc; }; -class InlayHintVisitor : public FilteredASTVisitor { +class Builder { public: - using Base = FilteredASTVisitor; - - InlayHintVisitor(std::vector& Results, - CompilationUnit& unit, - std::optional restrict_range) : - Base(unit, true), results(Results), unit(unit), AST(unit.context()), - - Tokens(unit.token_buffer()), restrict_range(*std::move(restrict_range)), - MainFileID(AST.getSourceManager().getMainFileID()), - TypeHintPolicy(this->AST.getPrintingPolicy()) { - - MainFileBuf = unit.interested_content(); - - TypeHintPolicy.SuppressScope = true; // keep type names short - TypeHintPolicy.AnonymousTagLocations = false; // do not print lambda locations - + Builder(std::vector& result, + CompilationUnit& unit, + LocalSourceRange restrict_range, + const config::InlayHintsOptions& options) : + result(result), unit(unit), restrict_range(restrict_range), options(options), + policy(unit.context().getPrintingPolicy()) { + // The sugared type is more useful in some cases, and the canonical + // type in other cases. + policy.SuppressScope = true; // keep type names short + policy.AnonymousTagLocations = false; // do not print lambda locations // Not setting PrintCanonicalTypes for "auto" allows // SuppressDefaultTemplateArgs (set by default) to have an effect. } -public: +private: // Get the range of the main file that *exactly* corresponds to R. - std::optional getHintRange(SourceRange R) { - const auto& SM = AST.getSourceManager(); - auto Spelled = Tokens.spelledForExpanded(Tokens.expandedTokens(R)); - // TokenBuffer will return null if e.g. R corresponds to only part of a - // macro expansion. - if(!Spelled || Spelled->empty()) + std::optional hint_range(clang::SourceRange R) { + auto tokens = unit.spelled_tokens(R); + + if(tokens.empty()) { return std::nullopt; + } + + auto begin = tokens.front().location(); + auto end = tokens.back().endLocation(); + + auto [begin_fid, begin_offset] = unit.decompose_location(tokens.front().location()); + auto [end_fid, end_offset] = unit.decompose_location(tokens.back().endLocation()); // Hint must be within the main file, not e.g. a non-preamble include. - if(SM.getFileID(Spelled->front().location()) != SM.getMainFileID() || - SM.getFileID(Spelled->back().location()) != SM.getMainFileID()) + if(begin_fid != end_fid || begin_fid != unit.interested_file()) { return std::nullopt; + } - /// FIXME: - // return LocalSourceRange{sourceLocToPosition(SM, Spelled->front().location()), - // sourceLocToPosition(SM, Spelled->back().endLocation())}; - - return unit - .decompose_range( - clang::SourceRange(Spelled->front().location(), Spelled->back().endLocation())) - .second; - } - - void addBlockEndHint(SourceRange BraceRange, - StringRef DeclPrefix, - StringRef Name, - StringRef OptionalPunctuation) { - auto HintRange = computeBlockEndHintRange(BraceRange, OptionalPunctuation); - if(!HintRange) - return; - - std::string Label = DeclPrefix.str(); - if(!Label.empty() && !Name.empty()) - Label += ' '; - Label += Name; - - constexpr unsigned HintMaxLengthLimit = 60; - if(Label.length() > HintMaxLengthLimit) - return; - - add_inlay_hint(*HintRange, HintSide::Right, InlayHintKind::BlockEnd, " // ", Label, ""); + return LocalSourceRange{begin_offset, end_offset}; } // Compute the LSP range to attach the block end hint to, if any allowed. @@ -194,148 +137,102 @@ public: // 2. After "}", if the trimmed trailing text is exactly // `OptionalPunctuation`, say ";". The range of "} ... ;" is returned. // Otherwise, the hint shouldn't be shown. - std::optional computeBlockEndHintRange(SourceRange BraceRange, - StringRef OptionalPunctuation) { + std::optional compute_block_end_range(clang::SourceRange brace_range, + llvm::StringRef optional_punctuation) { constexpr unsigned HintMinLineLimit = 2; - auto& SM = AST.getSourceManager(); - auto [BlockBeginFileId, BlockBeginOffset] = - SM.getDecomposedLoc(SM.getFileLoc(BraceRange.getBegin())); - auto RBraceLoc = SM.getFileLoc(BraceRange.getEnd()); - auto [RBraceFileId, RBraceOffset] = SM.getDecomposedLoc(RBraceLoc); + auto [block_begin_fid, block_begin_offset] = + unit.decompose_location(unit.file_location(brace_range.getBegin())); + auto rbrace_loc = unit.file_location(brace_range.getEnd()); + auto [rbrace_fid, rbrace_offset] = unit.decompose_location(rbrace_loc); // Because we need to check the block satisfies the minimum line limit, we // require both source location to be in the main file. This prevents hint // to be shown in weird cases like '{' is actually in a "#include", but it's // rare anyway. - if(BlockBeginFileId != MainFileID || RBraceFileId != MainFileID) + if(block_begin_fid != rbrace_fid || block_begin_fid != unit.interested_file()) { return std::nullopt; - - StringRef RestOfLine = MainFileBuf.substr(RBraceOffset).split('\n').first; - if(!RestOfLine.starts_with("}")) - return std::nullopt; - - StringRef TrimmedTrailingText = RestOfLine.drop_front().trim(); - if(!TrimmedTrailingText.empty() && TrimmedTrailingText != OptionalPunctuation) - return std::nullopt; - - auto BlockBeginLine = SM.getLineNumber(BlockBeginFileId, BlockBeginOffset); - auto RBraceLine = SM.getLineNumber(RBraceFileId, RBraceOffset); - - // Don't show hint on trivial blocks like `class X {};` - if(BlockBeginLine + HintMinLineLimit - 1 > RBraceLine) - return std::nullopt; - - // This is what we attach the hint to, usually "}" or "};". - StringRef HintRangeText = - RestOfLine.take_front(TrimmedTrailingText.empty() - ? 1 - : TrimmedTrailingText.bytes_end() - RestOfLine.bytes_begin()); - - /// FIXME: Handle case, if RBraceLoc is from macro expansion. - auto [fid, offset] = unit.decompose_location(RBraceLoc); - return LocalSourceRange(offset, offset + HintRangeText.size()); - } - - // We pass HintSide rather than SourceLocation because we want to ensure - // it is in the same file as the common file range. - void add_inlay_hint(SourceRange R, - HintSide Side, - InlayHintKind Kind, - llvm::StringRef Prefix, - llvm::StringRef Label, - llvm::StringRef Suffix) { - auto LSPRange = getHintRange(R); - if(!LSPRange) - return; - - add_inlay_hint(*LSPRange, Side, Kind, Prefix, Label, Suffix); - } - - void add_inlay_hint(LocalSourceRange LSPRange, - HintSide side, - InlayHintKind kind, - llvm::StringRef prefix, - llvm::StringRef Label, - llvm::StringRef suffix) { - // We shouldn't get as far as adding a hint if the category is disabled. - // We'd like to disable as much of the analysis as possible above instead. - // Assert in debug mode but add a dynamic check in production. - assert(options.enabled && "Shouldn't get here if disabled!"); - - std::uint32_t offset = side == HintSide::Left ? LSPRange.begin : LSPRange.end; - if(restrict_range.valid() && !restrict_range.contains(offset)) - return; - - bool pad_left = prefix.consume_front(" "); - bool pad_right = suffix.consume_back(" "); - - InlayHint hint{offset, kind}; - hint.parts.emplace_back(-1, (prefix + Label + suffix).str()); - results.push_back(std::move(hint)); - } - - void add_type_hint(SourceRange R, QualType T, llvm::StringRef Prefix) { - if(!options.deduced_types || T.isNull()) - return; - - // The sugared type is more useful in some cases, and the canonical - // type in other cases. - auto Desugared = ast::maybeDesugar(AST, T); - std::string TypeName = Desugared.getAsString(TypeHintPolicy); - - auto should_print = [&](llvm::StringRef TypeName) { - return options.type_name_limit == 0 || TypeName.size() < options.type_name_limit; - }; - - if(T != Desugared && !should_print(TypeName)) { - // If the desugared type is too long to display, fallback to the sugared - // type. - TypeName = T.getAsString(TypeHintPolicy); } - if(should_print(TypeName)) - add_inlay_hint(R, - HintSide::Right, - InlayHintKind::Type, - Prefix, - TypeName, - /*Suffix=*/""); + llvm::StringRef rest_of_line = + unit.interested_content().substr(rbrace_offset).split('\n').first; + if(!rest_of_line.starts_with("}")) { + return std::nullopt; + } + + llvm::StringRef trailing_text = rest_of_line.drop_front().trim(); + if(!trailing_text.empty() && trailing_text != optional_punctuation) { + return std::nullopt; + } + + auto& src_mgr = unit.context().getSourceManager(); + auto block_begin_line = src_mgr.getLineNumber(block_begin_fid, block_begin_offset); + auto rbrace_line = src_mgr.getLineNumber(rbrace_fid, rbrace_offset); + + // Don't show hint on trivial blocks like `class X {};` + if(block_begin_line + HintMinLineLimit - 1 > rbrace_line) { + return std::nullopt; + } + + // This is what we attach the hint to, usually "}" or "};". + llvm::StringRef text = rest_of_line.take_front( + trailing_text.empty() ? 1 : trailing_text.bytes_end() - rest_of_line.bytes_begin()); + + /// FIXME: Handle case, if RBraceLoc is from macro expansion. + return LocalSourceRange(rbrace_offset, rbrace_offset + text.size()); } - void addDesignatorHint(SourceRange R, llvm::StringRef Text) { - add_inlay_hint(R, - HintSide::Left, - InlayHintKind::Designator, - /*Prefix=*/"", - Text, - /*Suffix=*/"="); + /// Check whether the expr has a param name comment before it. + /// The typical format is `/*name=*/`. + bool has_param_name_comment(const clang::Expr* expr, llvm::StringRef name) { + auto location = unit.file_location(expr->getBeginLoc()); + auto [fid, offset] = unit.decompose_location(location); + if(fid != unit.interested_file()) { + return false; + } + + llvm::StringRef content = unit.interested_content().substr(0, offset); + + // Allow whitespace between comment and expression. + content = content.rtrim(); + if(!content.consume_back("*/")) { + return false; + } + + // Ignore some punctuation and whitespace around comment. + // In particular this allows designators to match nicely. + llvm::StringLiteral ignore_chars = " =."; + name = name.trim(ignore_chars); + content = content.rtrim(ignore_chars); + + // Other than that, the comment must contain exactly name. + if(!content.consume_back(name)) { + return false; + } + + content = content.rtrim(ignore_chars); + return content.ends_with("/*"); } - void add_return_type_hint(FunctionDecl* D, SourceRange Range) { - auto* AT = D->getReturnType()->getContainedAutoType(); - if(!AT || AT->getDeducedType().isNull()) - return; - add_type_hint(Range, D->getReturnType(), /*Prefix=*/"-> "); - } - - bool shouldHintName(const Expr* Arg, StringRef ParamName) { - if(ParamName.empty()) + bool should_hint_name(const clang::Expr* expr, llvm::StringRef name) { + if(name.empty()) return false; // If the argument expression is a single name and it matches the // parameter name exactly, omit the name hint. - if(ParamName == getSpelledIdentifier(Arg)) + if(name == spelled_identifier_of(expr)) return false; // Exclude argument expressions preceded by a /*paramName*/. - if(isPrecededByParamNameComment(Arg, ParamName)) + if(has_param_name_comment(expr, name)) { return false; + } return true; } - bool shouldHintReference(const ParmVarDecl* Param, const ParmVarDecl* ForwardedParam) { + bool should_hint_reference(const clang::ParmVarDecl* param, + const clang::ParmVarDecl* forwarded_param) { // We add a & hint only when the argument is passed as mutable reference. // For parameters that are not part of an expanded pack, this is // straightforward. For expanded pack parameters, it's likely that they will @@ -358,190 +255,288 @@ public: // Additionally, we should not add a reference hint if the forwarded // parameter was only partially resolved, i.e. points to an expanded pack // parameter, since we do not know how it will be used eventually. - auto Type = Param->getType(); - auto ForwardedType = ForwardedParam->getType(); - return Type->isLValueReferenceType() && ForwardedType->isLValueReferenceType() && - !ForwardedType.getNonReferenceType().isConstQualified() && - !isExpandedFromParameterPack(ForwardedParam); + auto type = param->getType(); + auto forwarded_type = forwarded_param->getType(); + return type->isLValueReferenceType() && forwarded_type->isLValueReferenceType() && + !forwarded_type.getNonReferenceType().isConstQualified() && + !is_expanded_from_param_pack(forwarded_param); } - void markBlockEnd(const Stmt* Body, llvm::StringRef Label, llvm::StringRef Name = "") { - if(const auto* CS = llvm::dyn_cast_or_null(Body)) - addBlockEndHint(CS->getSourceRange(), Label, Name, ""); - } + using NameVec = llvm::SmallVector; - using NameVec = SmallVector; - - void processCall(Callee Callee, - SourceLocation RParenOrBraceLoc, - llvm::ArrayRef Args) { - assert(Callee.Decl || Callee.Loc); - - if((!options.parameters && !options.default_arguments) || Args.size() == 0) - return; - - // The parameter name of a move or copy constructor is not very interesting. - if(Callee.Decl) - if(auto* Ctor = dyn_cast(Callee.Decl)) - if(Ctor->isCopyOrMoveConstructor()) - return; - - SmallVector FormattedDefaultArgs; - bool HasNonDefaultArgs = false; - - ArrayRef Params, ForwardedParams; - // Resolve parameter packs to their forwarded parameter - SmallVector ForwardedParamsStorage; - if(Callee.Decl) { - Params = maybeDropCxxExplicitObjectParameters(Callee.Decl->parameters()); - ForwardedParamsStorage = ast::resolveForwardingParameters(Callee.Decl); - ForwardedParams = maybeDropCxxExplicitObjectParameters(ForwardedParamsStorage); - } else { - Params = maybeDropCxxExplicitObjectParameters(Callee.Loc.getParams()); - ForwardedParams = {Params.begin(), Params.end()}; - } - - NameVec ParameterNames = chooseParameterNames(ForwardedParams); - - // Exclude setters (i.e. functions with one argument whose name begins with - // "set"), and builtins like std::move/forward/... as their parameter name - // is also not likely to be interesting. - if(Callee.Decl && (isSetter(Callee.Decl, ParameterNames) || isSimpleBuiltin(Callee.Decl))) - return; - - for(size_t I = 0; I < ParameterNames.size() && I < Args.size(); ++I) { - // Pack expansion expressions cause the 1:1 mapping between arguments and - // parameters to break down, so we don't add further inlay hints if we - // encounter one. - if(isa(Args[I])) { - break; - } - - StringRef Name = ParameterNames[I]; - const bool NameHint = shouldHintName(Args[I], Name) && options.parameters; - const bool ReferenceHint = - shouldHintReference(Params[I], ForwardedParams[I]) && options.parameters; - - const bool IsDefault = isa(Args[I]); - HasNonDefaultArgs |= !IsDefault; - if(IsDefault) { - if(options.default_arguments) { - const auto SourceText = clang::Lexer::getSourceText( - CharSourceRange::getTokenRange(Params[I]->getDefaultArgRange()), - AST.getSourceManager(), - AST.getLangOpts()); - const auto Abbrev = - (SourceText.size() > options.type_name_limit || SourceText.contains("\n")) - ? "..." - : SourceText; - if(NameHint) - FormattedDefaultArgs.emplace_back(llvm::formatv("{0}: {1}", Name, Abbrev)); - else - FormattedDefaultArgs.emplace_back(llvm::formatv("{0}", Abbrev)); - } - } else if(NameHint || ReferenceHint) { - add_inlay_hint(Args[I]->getSourceRange(), - HintSide::Left, - InlayHintKind::Parameter, - ReferenceHint ? "&" : "", - NameHint ? Name : "", - ": "); - } - } - - if(!FormattedDefaultArgs.empty()) { - std::string Hint = joinAndTruncate(FormattedDefaultArgs, options.type_name_limit); - add_inlay_hint(SourceRange{RParenOrBraceLoc}, - HintSide::Left, - InlayHintKind::DefaultArgument, - HasNonDefaultArgs ? ", " : "", - Hint, - ""); - } - } - - // Checks if "E" is spelled in the main file and preceded by a C-style comment - // whose contents match ParamName (allowing for whitespace and an optional "=" - // at the end. - bool isPrecededByParamNameComment(const Expr* E, StringRef ParamName) { - auto& SM = AST.getSourceManager(); - auto FileLoc = SM.getFileLoc(E->getBeginLoc()); - auto Decomposed = SM.getDecomposedLoc(FileLoc); - if(Decomposed.first != MainFileID) - return false; - - StringRef SourcePrefix = MainFileBuf.substr(0, Decomposed.second); - // Allow whitespace between comment and expression. - SourcePrefix = SourcePrefix.rtrim(); - // Check for comment ending. - if(!SourcePrefix.consume_back("*/")) - return false; - // Ignore some punctuation and whitespace around comment. - // In particular this allows designators to match nicely. - llvm::StringLiteral IgnoreChars = " =."; - SourcePrefix = SourcePrefix.rtrim(IgnoreChars); - ParamName = ParamName.trim(IgnoreChars); - // Other than that, the comment must contain exactly ParamName. - if(!SourcePrefix.consume_back(ParamName)) - return false; - SourcePrefix = SourcePrefix.rtrim(IgnoreChars); - return SourcePrefix.ends_with("/*"); - } - - NameVec chooseParameterNames(ArrayRef Parameters) { - NameVec ParameterNames; - for(const auto* P: Parameters) { - if(isExpandedFromParameterPack(P)) { + NameVec choose_param_names(llvm::ArrayRef params) { + NameVec param_names; + for(const auto* param: params) { + if(is_expanded_from_param_pack(param)) { // If we haven't resolved a pack paramater (e.g. foo(Args... args)) to a // non-pack parameter, then hinting as foo(args: 1, args: 2, args: 3) is // unlikely to be useful. - ParameterNames.emplace_back(); + param_names.emplace_back(); } else { - auto SimpleName = ast::getSimpleName(*P); + auto simple_name = ast::identifier_of(*param); // If the parameter is unnamed in the declaration: // attempt to get its name from the definition - if(SimpleName.empty()) { - if(const auto* PD = getParamDefinition(P)) { - SimpleName = ast::getSimpleName(*PD); + if(simple_name.empty()) { + if(const auto* def = param_definition(param)) { + simple_name = ast::identifier_of(*def); } } - ParameterNames.emplace_back(SimpleName); + param_names.emplace_back(simple_name); } } // Standard library functions often have parameter names that start // with underscores, which makes the hints noisy, so strip them out. - for(auto& Name: ParameterNames) - stripLeadingUnderscores(Name); - - return ParameterNames; - } - - ParmVarDecl* getOnlyParamInstantiation(ParmVarDecl* D) { - auto* TemplateFunction = llvm::dyn_cast(D->getDeclContext()); - if(!TemplateFunction) - return nullptr; - auto* InstantiatedFunction = - llvm::dyn_cast_or_null(ast::get_only_instantiation(TemplateFunction)); - if(!InstantiatedFunction) - return nullptr; - - unsigned ParamIdx = 0; - for(auto* Param: TemplateFunction->parameters()) { - // Can't reason about param indexes in the presence of preceding packs. - // And if this param is a pack, it may expand to multiple params. - if(Param->isParameterPack()) - return nullptr; - if(Param == D) - break; - ++ParamIdx; + for(auto& name: param_names) { + name = name.ltrim('_'); } - assert(ParamIdx < TemplateFunction->getNumParams() && "Couldn't find param in list?"); - assert(ParamIdx < InstantiatedFunction->getNumParams() && - "Instantiated function has fewer (non-pack) parameters?"); - return InstantiatedFunction->getParamDecl(ParamIdx); + + return param_names; } +public: + void add_params(Callee callee, + clang::SourceLocation rpunc_location, + llvm::ArrayRef args) { + assert(callee.decl || callee.loc); + + if((!options.parameters && !options.default_arguments) || args.size() == 0) { + return; + } + + if(callee.decl) { + /// We don't want to hint for copy or move constructors, which may make + /// a lot of noise. + auto ctor = llvm::dyn_cast(callee.decl); + if(ctor && ctor->isCopyOrMoveConstructor()) { + return; + } + } + + llvm::SmallVector formatted_default_args; + bool has_non_default_args = false; + + llvm::ArrayRef params, forwarded_params; + // Resolve parameter packs to their forwarded parameter + llvm::SmallVector forwarded_params_storage; + + auto remove_self_params = [](llvm::ArrayRef params) + -> llvm::ArrayRef { + if(!params.empty() && params.front()->isExplicitObjectParameter()) { + params = params.drop_front(1); + } + return params; + }; + + if(callee.decl) { + params = remove_self_params(callee.decl->parameters()); + forwarded_params_storage = ast::resolve_forwarding_params(callee.decl); + forwarded_params = remove_self_params(forwarded_params_storage); + } else { + params = remove_self_params(callee.loc.getParams()); + forwarded_params = {params.begin(), params.end()}; + } + + NameVec param_names = choose_param_names(forwarded_params); + + // Exclude setters (i.e. functions with one argument whose name begins with + // "set"), and builtins like std::move/forward/... as their parameter name + // is also not likely to be interesting. + if(callee.decl && (is_setter(callee.decl, param_names) || is_simple_builtin(callee.decl))) + return; + + for(size_t i = 0; i < param_names.size() && i < args.size(); ++i) { + // Pack expansion expressions cause the 1:1 mapping between arguments and + // parameters to break down, so we don't add further inlay hints if we + // encounter one. + if(llvm::isa(args[i])) { + break; + } + + llvm::StringRef name = param_names[i]; + const bool name_hint = should_hint_name(args[i], name) && options.parameters; + const bool reference_hint = + should_hint_reference(params[i], forwarded_params[i]) && options.parameters; + + const bool is_default = llvm::isa(args[i]); + has_non_default_args |= !is_default; + if(is_default) { + if(options.default_arguments) { + const auto text = clang::Lexer::getSourceText( + clang::CharSourceRange::getTokenRange(params[i]->getDefaultArgRange()), + unit.context().getSourceManager(), + unit.lang_options()); + const auto abbrev = + (text.size() > options.type_name_limit || text.contains("\n")) ? "..." + : text; + if(name_hint) { + formatted_default_args.emplace_back(std::format("{0}: {1}", name, abbrev)); + } else { + formatted_default_args.emplace_back(std::format("{0}", abbrev)); + } + } + } else if(name_hint || reference_hint) { + add_inlay_hint(args[i]->getSourceRange(), + HintSide::Left, + InlayHintKind::Parameter, + reference_hint ? "&" : "", + name_hint ? name : "", + ": "); + } + } + + if(!formatted_default_args.empty()) { + std::string hint; + llvm::raw_string_ostream os(hint); + llvm::ListSeparator sep(", "); + for(auto&& element: formatted_default_args) { + os << sep; + if(hint.size() + element.size() >= options.type_name_limit) { + os << "..."; + break; + } + os << element; + } + os.flush(); + + add_inlay_hint(clang::SourceRange(rpunc_location), + HintSide::Left, + InlayHintKind::DefaultArgument, + has_non_default_args ? ", " : "", + hint, + ""); + } + } + + void add_block_end_hint(clang::SourceRange brace_range, + llvm::StringRef decl_prefix, + llvm::StringRef name, + llvm::StringRef optional_punctuation) { + auto hint_range = compute_block_end_range(brace_range, optional_punctuation); + if(!hint_range) + return; + + std::string label = decl_prefix.str(); + if(!label.empty() && !name.empty()) { + label += ' '; + } + label += name; + + constexpr unsigned HintMaxLengthLimit = 60; + if(label.length() > HintMaxLengthLimit) { + return; + } + + add_inlay_hint(*hint_range, HintSide::Right, InlayHintKind::BlockEnd, " // ", label, ""); + } + + void mark_block_end(const clang::Stmt* body, llvm::StringRef label, llvm::StringRef name = "") { + if(const auto* cs = llvm::dyn_cast_or_null(body)) { + add_block_end_hint(cs->getSourceRange(), label, name, ""); + } + } + + // We pass HintSide rather than SourceLocation because we want to ensure + // it is in the same file as the common file range. + void add_inlay_hint(clang::SourceRange range, + HintSide side, + InlayHintKind Kind, + llvm::StringRef prefix, + llvm::StringRef label, + llvm::StringRef suffix) { + auto local_range = hint_range(range); + if(!local_range) { + return; + } + + add_inlay_hint(*local_range, side, Kind, prefix, label, suffix); + } + + void add_inlay_hint(LocalSourceRange range, + HintSide side, + InlayHintKind kind, + llvm::StringRef prefix, + llvm::StringRef label, + llvm::StringRef suffix) { + // We shouldn't get as far as adding a hint if the category is disabled. + // We'd like to disable as much of the analysis as possible above instead. + // Assert in debug mode but add a dynamic check in production. + assert(options.enabled && "Shouldn't get here if disabled!"); + + std::uint32_t offset = side == HintSide::Left ? range.begin : range.end; + if(restrict_range.valid() && !restrict_range.contains(offset)) + return; + + bool pad_left = prefix.consume_front(" "); + bool pad_right = suffix.consume_back(" "); + + InlayHint hint{offset, kind}; + hint.parts.emplace_back(-1, (prefix + label + suffix).str()); + result.push_back(std::move(hint)); + } + + void add_type_hint(clang::SourceRange range, clang::QualType type, llvm::StringRef prefix) { + if(!options.deduced_types || type.isNull()) + return; + + auto desugared = ast::maybe_desugar(unit.context(), type); + std::string type_name = desugared.getAsString(policy); + + auto should_print = [&](llvm::StringRef TypeName) { + return options.type_name_limit == 0 || TypeName.size() < options.type_name_limit; + }; + + if(type != desugared && !should_print(type_name)) { + // If the desugared type is too long to display, fallback to the sugared + // type. + type_name = type.getAsString(policy); + } + + if(should_print(type_name)) { + add_inlay_hint(range, + HintSide::Right, + InlayHintKind::Type, + prefix, + type_name, + /*Suffix=*/""); + } + } + + void add_designator_hint(clang::SourceRange range, llvm::StringRef text) { + add_inlay_hint(range, + HintSide::Left, + InlayHintKind::Designator, + /*Prefix=*/"", + text, + /*Suffix=*/"="); + } + + void add_return_type_hint(clang::FunctionDecl* decl, clang::SourceRange range) { + auto* type = decl->getReturnType()->getContainedAutoType(); + if(!type || type->getDeducedType().isNull()) { + return; + } + add_type_hint(range, decl->getReturnType(), /*Prefix=*/"-> "); + } + +private: + std::vector& result; + CompilationUnit& unit; + LocalSourceRange restrict_range; + const config::InlayHintsOptions& options; + clang::PrintingPolicy policy; +}; + +class Visitor : public FilteredASTVisitor { +public: + using Base = FilteredASTVisitor; + + Visitor(Builder& builder, + CompilationUnit& unit, + std::optional restrict_range, + const config::InlayHintsOptions& options) : + Base(unit, true), builder(builder), unit(unit), options(options) {} + public: // Carefully recurse into PseudoObjectExprs, which typically incorporate // a syntactic expression and several semantic expressions. @@ -572,138 +567,168 @@ public: return Base::TraversePseudoObjectExpr(expr); } - bool VisitNamespaceDecl(NamespaceDecl* D) { + bool VisitNamespaceDecl(clang::NamespaceDecl* decl) { if(options.block_end) { // For namespace, the range actually starts at the namespace keyword. But // it should be fine since it's usually very short. - addBlockEndHint(D->getSourceRange(), "namespace", ast::getSimpleName(*D), ""); + builder.add_block_end_hint(decl->getSourceRange(), + "namespace", + ast::identifier_of(*decl), + ""); } return true; } - bool VisitTagDecl(TagDecl* D) { - if(options.block_end && D->isThisDeclarationADefinition()) { - std::string DeclPrefix = D->getKindName().str(); - if(const auto* ED = dyn_cast(D)) { - if(ED->isScoped()) - DeclPrefix += ED->isScopedUsingClassTag() ? " class" : " struct"; + bool VisitTagDecl(clang::TagDecl* decl) { + if(options.block_end && decl->isThisDeclarationADefinition()) { + std::string prefix = decl->getKindName().str(); + + if(const auto* enum_decl = dyn_cast(decl)) { + if(enum_decl->isScoped()) { + prefix += enum_decl->isScopedUsingClassTag() ? " class" : " struct"; + } }; - addBlockEndHint(D->getBraceRange(), DeclPrefix, ast::getSimpleName(*D), ";"); + + builder.add_block_end_hint(decl->getBraceRange(), + prefix, + ast::identifier_of(*decl), + ";"); } return true; } - bool VisitFunctionDecl(FunctionDecl* D) { - if(auto* FPT = llvm::dyn_cast(D->getType().getTypePtr())) { - if(!FPT->hasTrailingReturn()) { - if(auto FTL = D->getFunctionTypeLoc()) - add_return_type_hint(D, FTL.getRParenLoc()); + bool VisitFunctionDecl(clang::FunctionDecl* decl) { + if(auto* proto_type = llvm::dyn_cast(decl->getType())) { + if(!proto_type->hasTrailingReturn()) { + if(auto FTL = decl->getFunctionTypeLoc()) { + builder.add_return_type_hint(decl, FTL.getRParenLoc()); + } } } - if(options.block_end && D->isThisDeclarationADefinition()) { + if(options.block_end && decl->isThisDeclarationADefinition()) { // We use `printName` here to properly print name of ctor/dtor/operator // overload. - if(const Stmt* Body = D->getBody()) - addBlockEndHint(Body->getSourceRange(), "", ast::print_name(D), ""); + if(const clang::Stmt* body = decl->getBody()) { + builder.add_block_end_hint(body->getSourceRange(), + "", + ast::display_name_of(decl), + ""); + } } return true; } - bool VisitVarDecl(VarDecl* D) { + bool VisitVarDecl(clang::VarDecl* var) { // Do not show hints for the aggregate in a structured binding, // but show hints for the individual bindings. - if(auto* DD = dyn_cast(D)) { - for(auto* Binding: DD->bindings()) { + if(auto* decl = dyn_cast(var)) { + for(auto* binding: decl->bindings()) { // For structured bindings, print canonical types. This is important // because for bindings that use the tuple_element protocol, the // non-canonical types would be "tuple_element::type". - if(auto Type = Binding->getType(); !Type.isNull() && !Type->isDependentType()) - add_type_hint(Binding->getLocation(), - Type.getCanonicalType(), - /*Prefix=*/": "); + if(auto type = binding->getType(); !type.isNull() && !type->isDependentType()) { + builder.add_type_hint(binding->getLocation(), + type.getCanonicalType(), + /*Prefix=*/": "); + } } return true; } - if(auto* AT = D->getType()->getContainedAutoType()) { - if(AT->isDeduced() && !D->getType()->isDependentType()) { + auto type = var->getType(); + if(auto* auto_type = type->getContainedAutoType()) { + if(auto_type->isDeduced() && !type->isDependentType()) { // Our current approach is to place the hint on the variable // and accordingly print the full type // (e.g. for `const auto& x = 42`, print `const int&`). // Alternatively, we could place the hint on the `auto` // (and then just print the type deduced for the `auto`). - add_type_hint(D->getLocation(), D->getType(), /*Prefix=*/": "); + builder.add_type_hint(var->getLocation(), var->getType(), /*Prefix=*/": "); } } // Handle templates like `int foo(auto x)` with exactly one instantiation. - if(auto* PVD = llvm::dyn_cast(D)) { - if(D->getIdentifier() && PVD->getType()->isDependentType() && - !ast::get_contained_auto_param_type(D->getTypeSourceInfo()->getTypeLoc()).isNull()) { - if(auto* IPVD = getOnlyParamInstantiation(PVD)) - add_type_hint(D->getLocation(), IPVD->getType(), /*Prefix=*/": "); + if(auto* param = llvm::dyn_cast(var)) { + if(var->getIdentifier() && type->isDependentType()) { + auto unwrapped = ast::unwrap_type(var->getTypeSourceInfo()->getTypeLoc()); + if(auto type = unwrapped.getAs()) { + if(auto decl = type.getDecl(); decl && decl->isImplicit()) { + if(auto* IPVD = ast::get_only_instantiation(param)) { + builder.add_type_hint(var->getLocation(), + IPVD->getType(), + /*Prefix=*/": "); + } + } + } } } return true; } - bool VisitCXXConstructExpr(CXXConstructExpr* E) { + bool VisitCXXConstructExpr(clang::CXXConstructExpr* expr) { // Weed out constructor calls that don't look like a function call with // an argument list, by checking the validity of getParenOrBraceRange(). // Also weed out std::initializer_list constructors as there are no names // for the individual arguments. - if(!E->getParenOrBraceRange().isValid() || E->isStdInitListInitialization()) { + if(!expr->getParenOrBraceRange().isValid() || expr->isStdInitListInitialization()) { return true; } - Callee Callee; - Callee.Decl = E->getConstructor(); - if(!Callee.Decl) + Callee callee; + callee.decl = expr->getConstructor(); + if(!callee.decl) { return true; - processCall(Callee, E->getParenOrBraceRange().getEnd(), {E->getArgs(), E->getNumArgs()}); + } + + builder.add_params(callee, + expr->getParenOrBraceRange().getEnd(), + {expr->getArgs(), expr->getNumArgs()}); return true; } - bool VisitCallExpr(CallExpr* E) { - if(!options.parameters) + bool VisitCallExpr(clang::CallExpr* expr) { + if(!options.parameters) { return true; + } - auto isFunctionObjectCallExpr = [](CallExpr* E) { - if(auto* CallExpr = dyn_cast(E)) { - return CallExpr->getOperator() == OverloadedOperatorKind::OO_Call; + auto isFunctionObjectCallExpr = [](clang::CallExpr* E) { + if(auto* call_expr = dyn_cast(E)) { + return call_expr->getOperator() == clang::OverloadedOperatorKind::OO_Call; } return false; }; - bool IsFunctor = isFunctionObjectCallExpr(E); + bool is_functor = isFunctionObjectCallExpr(expr); // Do not show parameter hints for user-defined literals or // operator calls except for operator(). (Among other reasons, the resulting // hints can look awkward, e.g. the expression can itself be a function // argument and then we'd get two hints side by side). - if((isa(E) && !IsFunctor) || isa(E)) + if((llvm::isa(expr) && !is_functor) || + llvm::isa(expr)) return true; /// FIXME: Use template resolver here. - if(E->isTypeDependent() || E->isValueDependent()) { + if(expr->isTypeDependent() || expr->isValueDependent()) { return true; } - auto CalleeDecl = E->getCalleeDecl(); + auto callee_decl = expr->getCalleeDecl(); - Callee Callee; - if(const auto* FD = dyn_cast(CalleeDecl)) - Callee.Decl = FD; - else if(const auto* FTD = dyn_cast(CalleeDecl)) - Callee.Decl = FTD->getTemplatedDecl(); - else if(FunctionProtoTypeLoc Loc = ast::getPrototypeLoc(E->getCallee())) - Callee.Loc = Loc; - else + Callee callee; + if(const auto* FD = llvm::dyn_cast(callee_decl)) { + callee.decl = FD; + } else if(const auto* FTD = llvm::dyn_cast(callee_decl)) { + callee.decl = FTD->getTemplatedDecl(); + } else if(clang::FunctionProtoTypeLoc loc = ast::proto_type_loc(expr->getCallee())) { + callee.loc = loc; + } else { return true; + } // N4868 [over.call.object]p3 says, // The argument list submitted to overload resolution consists of the @@ -715,78 +740,92 @@ public: // implied object argument ([over.call.func]), the list of provided // arguments is preceded by the implied object argument for the purposes of // this correspondence... - llvm::ArrayRef Args = {E->getArgs(), E->getNumArgs()}; - // We don't have the implied object argument through a function pointer - // either. - if(const CXXMethodDecl* Method = dyn_cast_or_null(Callee.Decl)) - if(IsFunctor || Method->hasCXXExplicitFunctionObjectParameter()) - Args = Args.drop_front(1); - processCall(Callee, E->getRParenLoc(), Args); + llvm::ArrayRef args = {expr->getArgs(), expr->getNumArgs()}; + + // We don't have the implied object argument through a function pointer either. + if(const auto* method = llvm::dyn_cast_or_null(callee.decl)) { + if(is_functor || method->hasCXXExplicitFunctionObjectParameter()) { + args = args.drop_front(1); + } + } + + builder.add_params(callee, expr->getRParenLoc(), args); return true; } - bool VisitForStmt(ForStmt* S) { + bool VisitForStmt(clang::ForStmt* S) { if(options.block_end) { - std::string Name; + std::string name; // Common case: for (int I = 0; I < N; I++). Use "I" as the name. - if(auto* DS = llvm::dyn_cast_or_null(S->getInit()); DS && DS->isSingleDecl()) - Name = ast::getSimpleName(llvm::cast(*DS->getSingleDecl())); - else - Name = ast::summarizeExpr(S->getCond()); - markBlockEnd(S->getBody(), "for", Name); + if(auto* DS = llvm::dyn_cast_or_null(S->getInit()); + DS && DS->isSingleDecl()) { + name = ast::identifier_of(llvm::cast(*DS->getSingleDecl())); + } else { + name = ast::summarize_expr(S->getCond()); + } + builder.mark_block_end(S->getBody(), "for", name); } return true; } - bool VisitCXXForRangeStmt(CXXForRangeStmt* S) { - if(options.block_end) - markBlockEnd(S->getBody(), "for", ast::getSimpleName(*S->getLoopVariable())); - return true; - } - - bool VisitWhileStmt(WhileStmt* S) { - if(options.block_end) - markBlockEnd(S->getBody(), "while", ast::summarizeExpr(S->getCond())); - return true; - } - - bool VisitSwitchStmt(SwitchStmt* S) { - if(options.block_end) - markBlockEnd(S->getBody(), "switch", ast::summarizeExpr(S->getCond())); - return true; - } - - bool VisitIfStmt(IfStmt* S) { + bool VisitCXXForRangeStmt(clang::CXXForRangeStmt* S) { if(options.block_end) { - if(const auto* ElseIf = llvm::dyn_cast_or_null(S->getElse())) - ElseIfs.insert(ElseIf); + builder.mark_block_end(S->getBody(), "for", ast::identifier_of(*S->getLoopVariable())); + } + return true; + } + + bool VisitWhileStmt(clang::WhileStmt* S) { + if(options.block_end) { + builder.mark_block_end(S->getBody(), "while", ast::summarize_expr(S->getCond())); + } + return true; + } + + bool VisitSwitchStmt(clang::SwitchStmt* S) { + if(options.block_end) { + builder.mark_block_end(S->getBody(), "switch", ast::summarize_expr(S->getCond())); + } + return true; + } + + bool VisitIfStmt(clang::IfStmt* S) { + if(options.block_end) { + if(const auto* else_if = llvm::dyn_cast_or_null(S->getElse())) { + else_ifs.insert(else_if); + } + // Don't use markBlockEnd: the relevant range is [then.begin, else.end]. - if(const auto* EndCS = - llvm::dyn_cast(S->getElse() ? S->getElse() : S->getThen())) { - addBlockEndHint({S->getThen()->getBeginLoc(), EndCS->getRBracLoc()}, - "if", - ElseIfs.contains(S) ? "" : ast::summarizeExpr(S->getCond()), - ""); + if(const auto* if_end = llvm::dyn_cast( + S->getElse() ? S->getElse() : S->getThen())) { + builder.add_block_end_hint({S->getThen()->getBeginLoc(), if_end->getRBracLoc()}, + "if", + else_ifs.contains(S) ? "" + : ast::summarize_expr(S->getCond()), + ""); } } return true; } - bool VisitLambdaExpr(LambdaExpr* E) { - FunctionDecl* D = E->getCallOperator(); - if(!E->hasExplicitResultType()) { - SourceLocation TypeHintLoc; - if(!E->hasExplicitParameters()) - TypeHintLoc = E->getIntroducerRange().getEnd(); - else if(auto FTL = D->getFunctionTypeLoc()) - TypeHintLoc = FTL.getRParenLoc(); - if(TypeHintLoc.isValid()) - add_return_type_hint(D, TypeHintLoc); + bool VisitLambdaExpr(clang::LambdaExpr* expr) { + clang::FunctionDecl* decl = expr->getCallOperator(); + if(!expr->hasExplicitResultType()) { + clang::SourceLocation type_hint_loc; + if(!expr->hasExplicitParameters()) { + type_hint_loc = expr->getIntroducerRange().getEnd(); + } else if(auto FTL = decl->getFunctionTypeLoc()) { + type_hint_loc = FTL.getRParenLoc(); + } + + if(type_hint_loc.isValid()) { + builder.add_return_type_hint(decl, type_hint_loc); + } } return true; } - bool VisitInitListExpr(InitListExpr* expr) { + bool VisitInitListExpr(clang::InitListExpr* expr) { // We receive the syntactic form here (shouldVisitImplicitCode() is false). // This is the one we will ultimately attach designators to. // It may have subobject initializers inlined without braces. The *semantic* @@ -794,11 +833,13 @@ public: // getUnwrittenDesignators will look at the semantic form to determine the // labels. assert(expr->isSyntacticForm() && "RAV should not visit implicit code!"); - if(!options.designators) + if(!options.designators) { return true; + } - if(expr->isIdiomaticZeroInitializer(AST.getLangOpts())) + if(expr->isIdiomaticZeroInitializer(unit.lang_options())) { return true; + } /// FIXME: // llvm::DenseMap Designators = @@ -813,25 +854,19 @@ public: return true; } - bool VisitTypeLoc(TypeLoc TL) { - if(const auto* DT = llvm::dyn_cast(TL.getType())) - if(QualType UT = DT->getUnderlyingType(); !UT->isDependentType()) - add_type_hint(TL.getSourceRange(), UT, ": "); + bool VisitTypeLoc(clang::TypeLoc TL) { + if(const auto* DT = llvm::dyn_cast(TL.getType())) + if(clang::QualType UT = DT->getUnderlyingType(); !UT->isDependentType()) + builder.add_type_hint(TL.getSourceRange(), UT, ": "); return true; } // FIXME: Handle RecoveryExpr to try to hint some invalid calls. private: - std::vector& results; + Builder& builder; CompilationUnit& unit; - ASTContext& AST; - const syntax::TokenBuffer& Tokens; - LocalSourceRange restrict_range; - FileID MainFileID; - StringRef MainFileBuf; - PrintingPolicy TypeHintPolicy; - config::InlayHintsOptions options; + const config::InlayHintsOptions& options; // If/else chains are tricky. // if (cond1) { @@ -839,15 +874,24 @@ private: // } // mark as "cond1" or "cond2"? // For now, the answer is neither, just mark as "if". // The ElseIf is a different IfStmt that doesn't know about the outer one. - llvm::DenseSet ElseIfs; // not eligible for names + llvm::DenseSet else_ifs; // not eligible for names }; } // namespace -InlayHints inlay_hints(CompilationUnit& unit, LocalSourceRange target) { +auto inlay_hint(CompilationUnit& unit, + LocalSourceRange target, + const config::InlayHintsOptions& options) -> std::vector { std::vector hints; - InlayHintVisitor visitor(hints, unit, target); + + Builder builder(hints, unit, target, options); + Visitor visitor(builder, unit, target, options); visitor.TraverseDecl(unit.tu()); + + ranges::sort(hints, refl::less); + auto sub_range = ranges::unique(hints, refl::equal); + hints.erase(sub_range.begin(), sub_range.end()); + return hints; } diff --git a/src/Server/Feature.cpp b/src/Server/Feature.cpp index c891a2e0..38fecb99 100644 --- a/src/Server/Feature.cpp +++ b/src/Server/Feature.cpp @@ -1,12 +1,13 @@ -#include "Feature/DocumentSymbol.h" -#include "Feature/FoldingRange.h" #include "Server/Server.h" #include "Server/Convert.h" #include "Compiler/Compilation.h" #include "Feature/CodeCompletion.h" #include "Feature/Hover.h" #include "Feature/DocumentLink.h" +#include "Feature/DocumentSymbol.h" +#include "Feature/FoldingRange.h" #include "Feature/SemanticToken.h" +#include "Feature/InlayHint.h" namespace clice { @@ -189,4 +190,40 @@ async::Task Server::on_semantic_token(proto::SemanticTokensParams p }); } +async::Task Server::on_inlay_hint(proto::InlayHintParams params) { + auto path = mapping.to_path(params.textDocument.uri); + auto opening_file = opening_files.get_or_add(path); + auto guard = co_await opening_file->ast_built_lock.try_lock(); + + auto ast = opening_file->ast; + if(!ast) { + co_return json::Value(nullptr); + } + + co_return co_await async::submit([kind = this->kind, ¶ms, &ast] { + auto content = ast->interested_content(); + + LocalSourceRange range{ + to_offset(kind, content, params.range.start), + to_offset(kind, content, params.range.end), + }; + + auto hints = feature::inlay_hint(*ast, range, {}); + + PositionConverter converter(content, kind); + + std::vector result; + + for(auto& hint: hints) { + auto& back = result.emplace_back(converter.toPosition(hint.offset)); + back.label.emplace_back(std::move(hint.parts[0].name)); + + /// FIXME: Determine the set of possible kinds; for now, we'll use Type. + back.kind = proto::InlayHintKind::Type; + } + + return json::serialize(result); + }); +} + } // namespace clice diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index 6e9221ab..ba8058c3 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -71,6 +71,10 @@ async::Task Server::on_initialize(proto::InitializeParams params) { capabilities.semanticTokensProvider.legend.tokenTypes.emplace_back(std::move(type)); } + /// Inlay hint + /// FIXME: Resolve to make hint clickable. + capabilities.inlayHintProvider.resolveProvider = false; + co_return json::serialize(result); } diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index c00933d7..20e18b92 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -112,6 +112,7 @@ Server::Server() { register_callback<&Server::on_document_link>("textDocument/documentLink"); register_callback<&Server::on_folding_range>("textDocument/foldingRange"); register_callback<&Server::on_semantic_token>("textDocument/semanticTokens/full"); + register_callback<&Server::on_inlay_hint>("textDocument/inlayHint"); } async::Task<> Server::on_receive(json::Value value) { diff --git a/tests/unit/Async/Task.cpp b/tests/unit/Async/Task.cpp index 07adf643..27c91de8 100644 --- a/tests/unit/Async/Task.cpp +++ b/tests/unit/Async/Task.cpp @@ -89,7 +89,7 @@ suite<"Async"> suite = [] { auto task1 = [&]() -> async::Task<> { x = 1; co_await async::sleep(300); - clice::println("Task1 done"); + std::println("Task1 done"); x = 2; }; diff --git a/tests/unit/Compiler/Diagnostic.cpp b/tests/unit/Compiler/Diagnostic.cpp index b0fd0c79..e5b64678 100644 --- a/tests/unit/Compiler/Diagnostic.cpp +++ b/tests/unit/Compiler/Diagnostic.cpp @@ -97,7 +97,7 @@ suite<"Diagnostic"> diagnostic = [] { expect(that % !unit->diagnostics().empty()); /// for(auto& diag: unit->diagnostics()) { - /// clice::println("{}", diag.message); + /// std::println("{}", diag.message); /// } }; diff --git a/tests/unit/Compiler/Module.cpp b/tests/unit/Compiler/Module.cpp index ece93e4d..d57e2955 100644 --- a/tests/unit/Compiler/Module.cpp +++ b/tests/unit/Compiler/Module.cpp @@ -46,7 +46,7 @@ auto scan = [](llvm::StringRef content) { params.add_remapped_file("./test.h", "export module A"); auto info = scanModule(params); if(!info) { - /// clice::println("Fail to scan module: {}", info.error()); + /// std::println("Fail to scan module: {}", info.error()); std::abort(); } return std::move(*info); diff --git a/tests/unit/Feature/InlayHint.cpp b/tests/unit/Feature/InlayHint.cpp index 7be5f053..f577d119 100644 --- a/tests/unit/Feature/InlayHint.cpp +++ b/tests/unit/Feature/InlayHint.cpp @@ -7,39 +7,1523 @@ namespace { suite<"InlayHint"> inlay_hint = [] { Tester tester; - feature::InlayHints hints; + std::vector hints; + llvm::DenseMap hints_map; - auto run = [&](llvm::StringRef code, llvm::StringRef name) { + auto run = [&](llvm::StringRef code, + std::source_location location = std::source_location::current()) { tester.clear(); tester.add_main("main.cpp", code); - tester.compile(); + tester.compile_with_pch("-std=c++23"); LocalSourceRange range = LocalSourceRange(0, tester.unit->interested_content().size()); - hints = feature::inlay_hints(*tester.unit, range); + hints = feature::inlay_hint(*tester.unit, range, {}); - expect(that % tester.nameless_points().size() == 1); + hints_map.clear(); + for(auto& hint: hints) { + hints_map[hint.offset] = hint; + } - /// bool visited = false; - /// for(auto& hint: hints) { - /// if(hint.offset == tester.nameless_points().front() && hint.parts.front().name == - /// name){ - /// visited = true; - /// break; - /// } - /// } - /// expect(that % visited); + if(!tester.unit->diagnostics().empty()) { + for(auto& diagnostic: tester.unit->diagnostics()) { + std::println("{}", diagnostic.message); + } + } + + fatal / expect(tester.unit->diagnostics().empty(), location); + }; + + auto dump_results = [&] { + std::println("{}", pretty_dump(hints)); + }; + + auto expect_size = [&](std::uint32_t size, + std::source_location location = std::source_location::current()) { + fatal / expect(eq(hints.size(), size), location) << pretty_dump(hints); + }; + + auto expect_hint = [&](llvm::StringRef pos, + llvm::StringRef name, + std::source_location location = std::source_location::current()) { + auto offset = tester.point(pos); + auto it = hints_map.find(offset); + fatal / expect(it != hints_map.end(), location) + << std::format("offset is {}\n", offset) << pretty_dump(hints); + + auto& parts = it->second.parts; + /// Currently, we has only one label. + fatal / expect(eq(parts.size(), 1), location); + expect(eq(parts[0].name, name), location); }; test("Parameters") = [&] { - run(R"cpp( -void foo(int param); -void bar() { - foo($42); -} -)cpp", - "param"); + /// Name hint for normal param. + run(R"c( + int foo(int param); + int x = foo($(0)42); + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // No hint for anonymous param. + run(R"c( + int foo(int); + int x = foo(42); + )c"); + expect_size(0); + + /// Reference hint for anonymous lvalue ref param. + run(R"c( + int foo(int&); + int x = 1; + int y = foo($(0)x); + )c"); + expect_size(1); + expect_hint("0", "&:"); + + // No hint for anonymous const lvalue ref param. + run(R"c( + int foo(const int&); + int x = foo(42); + )c"); + expect_size(0); + + run(R"c( + template + int foo(Args&& ...); + int x = foo(42); + )c"); + expect_size(0); + + run(R"c( + namespace std { + template T&& forward(T&); + } + + int foo(int); + template + int bar(Args&&... args) { return foo(std::forward(args)...); } + int x = bar(42); + )c"); + expect_size(0); + + // No hint for anonymous r-value ref parameter + run(R"c( + int foo(int&&); + int x = foo(42); + )c"); + expect_size(0); + + // Parameter name picked up from definition if necessary + run(R"c( + int foo(int); + int x = foo($(0)42); + int foo(int param) { + return 0; + } + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Parameter name picked up from definition if necessary + run(R"c( + int foo(int, int b); + int x = foo($(0)42, $(1)42); + int foo(int a, int) { + return 0; + } + )c"); + expect_size(2); + expect_hint("0", "a:"); + expect_hint("1", "b:"); + + // Parameter name picked up from definition in a resolved forwarded parameter + run(R"c( + int foo(int, int); + template + int bar(Args... args) { + return foo(args...); + } + int x = bar($(0)42, $(1)42); + int foo(int a, int b) { + return 0; + } + )c"); + expect_size(2); + expect_hint("0", "a:"); + expect_hint("1", "b:"); + + // Prefer name from declaration + run(R"c( + int foo(int good); + int x = foo($(0)42); + int foo(int bad) { + return 0; + } + )c"); + expect_size(1); + expect_hint("0", "good:"); + + // Only name hint for const l-value ref parameter + run(R"c( + int foo(const int& param); + int x = foo($(0)42); + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Only name hint for const l-value ref parameter via type alias + run(R"c( + using alias = const int&; + int foo(alias param); + int x = 1; + int y = foo($(0)x); + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Reference and name hint for l-value ref parameter + run(R"c( + int foo(int& param); + int x = 1; + int y = foo($(0)x); + )c"); + expect_size(1); + expect_hint("0", "¶m:"); + + // Reference and name hint for l-value ref parameter via type alias + run(R"c( + using alias = int&; + int foo(alias param); + int x = 1; + int y = foo($(0)x); + )c"); + expect_size(1); + expect_hint("0", "¶m:"); + + // Only name hint for r-value ref parameter + run(R"c( + int foo(int&& param); + int x = foo($(0)42); + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Arg matches param + run(R"c( + void foo(int param); + struct S { + static const int param = 42; + }; + void bar() { + int param = 42; + // Do not show redundant "param: param". + foo(param); + // But show it if the argument is qualified. + foo($(0)S::param); + } + struct A { + int param; + void bar() { + // Do not show "param: param" for member-expr. + foo(param); + } + }; + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Arg matches param reference + run(R"c( + void foo(int& param); + void foo2(const int& param); + void bar() { + int param; + // show reference hint on mutable reference + foo($(0)param); + // but not on const reference + foo2(param); + } + )c"); + expect_size(1); + expect_hint("0", "&:"); + + // Name hint for variadic parameter using std::forward in a constructor call + run(R"c( + namespace std { template T&& forward(T&); } + struct S { S(int a); }; + template + T bar(Args&&... args) { return T{std::forward(args)...}; } + int x = 1; + S y = bar($(0)x); + )c"); + expect_size(1); + expect_hint("0", "a:"); + + // Name hint for variadic parameter in a constructor call + run(R"c( + struct S { S(int a); }; + template + T bar(Args&&... args) { return T{args...}; } + int x = 1; + S y = bar($(0)x); + )c"); + expect_size(1); + expect_hint("0", "a:"); + + // Name for variadic parameter using std::forward + run(R"c( + namespace std { template T&& forward(T&); } + int foo(int a); + template + int bar(Args&&... args) { return foo(std::forward(args)...); } + int x = 1; + int y = bar($(0)x); + )c"); + expect_size(1); + expect_hint("0", "a:"); + + // Name hint for variadic parameter + run(R"c( + int foo(int a); + template + int bar(Args&&... args) { return foo(args...); } + int x = bar($(0)42); + )c"); + expect_size(1); + expect_hint("0", "a:"); + + // Name hint for variadic parameter when the parameter pack is not the last template + // parameter + run(R"c( + int foo(int a); + template + int bar(Arg, Args&&... args) { return foo(args...); } + int x = bar(1, $(0)42); + )c"); + expect_size(1); + expect_hint("0", "a:"); + + // Name for variadic parameter that involves both head and tail parameters + run(R"c( + namespace std { template T&& forward(T&); } + int baz(int, int b, double); + template + int foo(int a, Args&&... args) { + return baz(1, std::forward(args)..., 1.0); + } + template + int bar(Args&&... args) { return foo(std::forward(args)...); } + int x = bar($(0)32, $(1)42); + )c"); + expect_size(2); + expect_hint("0", "a:"); + expect_hint("1", "b:"); + + // No hint for operator call with operator syntax + run(R"c( + struct S {}; + void operator+(S lhs, S rhs); + void bar() { + S a, b; + a + b; + } + )c"); + expect_size(0); + + // Function call operator + run(R"c( + struct W { + void operator()(int x); + }; + + struct S : W { + using W::operator(); + static void operator()(int x, int y); + }; + + void bar() { + auto l1 = [](int x) -> void {}; + auto l2 = [](int x) static -> void {}; + + S s; + s($(0)1); + s.operator()($(1)1); + s.operator()($(2)1, $(3)2); + S::operator()($(4)1, $(5)2); + + l1($(6)1); + l1.operator()($(7)1); + l2($(8)1); + l2.operator()($(9)1); + + void (*ptr)(int a, int b) = &S::operator(); + ptr($(10)1, $(11)2); + } + )c"); + + expect_hint("0", "x:"); + expect_hint("1", "x:"); + expect_hint("2", "x:"); + expect_hint("3", "y:"); + expect_hint("4", "x:"); + expect_hint("5", "y:"); + expect_hint("6", "x:"); + expect_hint("7", "x:"); + expect_hint("8", "x:"); + expect_hint("9", "x:"); + expect_hint("10", "a:"); + expect_hint("11", "b:"); + + // Deducing this + run(R"c( + struct S { + template + int operator()(this This &&Self, int Param) { + return 42; + } + + int function(this auto &Self, int Param) { + return Param; + } + }; + void work() { + S s; + s($(0)42); + s.function($(1)42); + S()($(2)42); + auto lambda = [](this auto &Self, char C) -> void { + return Self(C); + }; + lambda($(3)'A'); + } + )c"); + + expect_hint("0", "Param:"); + expect_hint("1", "Param:"); + expect_hint("2", "Param:"); + expect_hint("3", "C:"); + + // Constructor with parentheses + run(R"c( + struct S { + S(int param); + }; + void bar() { + S obj($(0)42); + } + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Constructor with braces + run(R"c( + struct S { + S(int param); + }; + void bar() { + S obj{$(0)42}; + } + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Member initialization + run(R"c( + struct S { + S(int param); + }; + + struct T { + S member; + T() : member($(0)42) {} + }; + )c"); + expect_size(1); + expect_hint("0", "param:"); + + // Function pointer + run(R"c( + void (*f1)(int param); + void (*f2)(int param) noexcept; + using f3_t = void(*)(int param); + f3_t f3; + using f4_t = void(*)(int param) noexcept; + f4_t f4; + + void bar() { + f1($(0)42); + f2($(1)42); + f3($(2)42); + f4($(3)42); + } + )c"); + expect_size(4); + expect_hint("0", "param:"); + expect_hint("1", "param:"); + expect_hint("2", "param:"); + expect_hint("3", "param:"); + + // Leading underscore + run(R"c( + void foo(int p1, int _p2, int __p3); + void bar() { + foo($(0)41, $(1)42, $(2)43); + } + )c"); + expect_size(3); + expect_hint("0", "p1:"); + expect_hint("1", "p2:"); + expect_hint("2", "p3:"); + + // Variadic function + run(R"c( + template + void foo(int fixed, T... variadic); + + void bar() { + foo($(0)41, 42, 43); + } + )c"); + expect_size(1); + expect_hint("0", "fixed:"); + + // Varargs function + run(R"c( + void foo(int fixed, ...); + + void bar() { + foo($(0)41, 42, 43); + } + )c"); + expect_size(1); + expect_hint("0", "fixed:"); + + // Do not show hint for parameter of copy or move constructor + run(R"c( + struct S { + S(); + S(const S& other); + S(S&& other); + }; + + void bar() { + S a; + S b = S(a); // copy + S c = S(S()); // move + } + )c"); + expect_size(0); + + // Do not hint call to user-defined literal operator + run(R"c( + long double operator ""_w(long double param); + void bar() { + 1.2_w; + } + )c"); + expect_size(0); + + // Parameter name comment + run(R"c( + void foo(int param); + void bar() { + foo(/*param*/42); + foo( /* param = */ 42); + #define X 42 + #define Y X + #define Z(...) Y + foo(/*param=*/Z(a)); + foo($(0)Z(a)); + foo(/* the answer */$(1)42); + } + )c"); + expect_size(2); + expect_hint("0", "param:"); + expect_hint("1", "param:"); + + // Setter functions + run(R"c( + struct S { + void setParent(S* parent); + void set_parent(S* parent); + void setTimeout(int timeoutMillis); + void setTimeoutMillis(int timeout_millis); + }; + void bar() { + S s; + // Parameter name matches setter name - omit hint. + s.setParent(nullptr); + // Support snake_case + s.set_parent(nullptr); + // Parameter name may contain extra info - show hint. + s.setTimeout($(0)120); + // FIXME: Ideally we'd want to omit this. + s.setTimeoutMillis($(1)120); + } + )c"); + expect_size(2); + expect_hint("0", "timeoutMillis:"); + expect_hint("1", "timeout_millis:"); + }; + + test("Types") = [&] { + // Basic type hint + run(R"c( + auto waldo$(0) = 42; + )c"); + expect_size(1); + expect_hint("0", ": int"); + + // Decorations + run(R"c( + int x = 42; + auto* var1$(0) = &x; + auto&& var2$(1) = x; + const auto& var3$(2) = x; + )c"); + expect_size(3); + expect_hint("0", ": int *"); + expect_hint("1", ": int &"); + expect_hint("2", ": const int &"); + + // Decltype auto + run(R"c( + int x = 42; + int& y = x; + decltype(auto) z$(0) = y; + )c"); + expect_size(1); + expect_hint("0", ": int &"); + + // No qualifiers + run(R"c( + namespace A { + namespace B { + struct S1 {}; + S1 foo(); + auto x$(0) = foo(); + + struct S2 { + template + struct Inner {}; + }; + + S2::Inner bar(); + auto y$(1) = bar(); + } + } + )c"); + expect_size(2); + expect_hint("0", ": S1"); + expect_hint("1", ": S2::Inner"); + + // Lambda + run(R"c( + void f() { + int cap = 42; + auto L$(0) = [cap, init$(1) = 1 + 1](int a)$(2) { + return a + cap + init; + }; + } + )c"); + expect_size(3); + expect_hint("0", ": (lambda)"); + expect_hint("1", ": int"); + expect_hint("2", "-> int"); + + // Lambda return hint shown even if no param list + run(R"c( + auto x$(0) = []$(1){return 42;}; + )c"); + expect_size(2); + expect_hint("0", ": (lambda)"); + expect_hint("1", "-> int"); + + // Structured bindings - public struct + run(R"c( + struct Point { + int x; + int y; + }; + Point foo(); + auto [x$(0), y$(1)] = foo(); + )c"); + expect_size(2); + expect_hint("0", ": int"); + expect_hint("1", ": int"); + + // Structured bindings - array + run(R"c( + int arr[2]; + auto [x$(0), y$(1)] = arr; + )c"); + expect_size(2); + expect_hint("0", ": int"); + expect_hint("1", ": int"); + + // Structured bindings - tuple-like + run(R"c( + struct IntPair { + int a; + int b; + }; + + namespace std { + template + struct tuple_size {}; + + template <> + struct tuple_size { + constexpr static unsigned value = 2; + }; + + template + struct tuple_element {}; + + template + struct tuple_element { + using type = int; + }; + } + + template + int get(const IntPair& p) { + if constexpr (I == 0) { + return p.a; + } else if constexpr (I == 1) { + return p.b; + } + } + + IntPair bar(); + auto [x$(0), y$(1)] = bar(); + )c"); + expect_size(2); + expect_hint("0", ": int"); + expect_hint("1", ": int"); + + // Return type deduction + run(R"c( + auto f1(int x)$(0); // Hint forward declaration too + auto f1(int x)$(1) { return x + 1; } + + // Include pointer operators in hint + int s; + auto& f2()$(2) { return s; } + + // Do not hint `auto` for trailing return type. + auto f3() -> int; + + // Do not hint when a trailing return type is specified. + auto f4() -> auto* { return "foo"; } + + auto f5()$(3) {} + + // `auto` conversion operator + struct A { + operator auto()$(4) { return 42; } + }; + )c"); + expect_size(5); + expect_hint("0", "-> int"); + expect_hint("1", "-> int"); + expect_hint("2", "-> int &"); + expect_hint("3", "-> void"); + expect_hint("4", "-> int"); + + // Decltype + run(R"c( + decltype(0)$(0) a; + decltype(a)$(1) b; + const decltype(0)$(2) &c = b; + + decltype(0)$(3) e(); + auto f() -> decltype(0)$(4); + + template struct Foo; + using G = Foo; + + auto h$(6) = decltype(0)$(7){}; + )c"); + expect_size(8); + expect_hint("0", ": int"); + expect_hint("1", ": int"); + expect_hint("2", ": int"); + expect_hint("3", ": int"); + expect_hint("4", ": int"); + expect_hint("5", ": int"); + expect_hint("6", ": int"); + expect_hint("7", ": int"); + + // Long type name + run(R"c( + template + struct A {}; + struct MultipleWords {}; + A foo(); + // Omit type hint past a certain length (currently 32) + auto var = foo(); + )c"); + expect_size(0); + + // Default template args + run(R"c( + template + struct A {}; + A foo(); + auto var$(0) = foo(); + A bar[1]; + auto [binding$(1)] = bar; + )c"); + expect_size(2); + expect_hint("0", ": A"); + expect_hint("1", ": A"); + + // Deduplication + run(R"c( + template + void foo() { + auto var$(0) = 42; + } + + template void foo(); + template void foo(); + )c"); + expect_size(1); + expect_hint("0", ": int"); + + // Singly instantiated template + run(R"c( + auto lambda$(0) = [](auto* param$(1), auto) { return 42; }; + int m = lambda("foo", 3); + )c"); + expect_hint("0", ": (lambda)"); + expect_hint("1", ": const char *"); + + // No hint for packs, or auto params following packs + run(R"c( + int x(auto a$(0), auto... b, auto c) { return 42; } + int m = x(nullptr, 'c', 2.0, 2); + )c"); + expect_hint("0", ": void *"); + }; + + skip / test("Designators") = [&] { + // Basic designator hints + run(R"c( + struct S { int x, y, z; }; + S s {$(0)1, $(1)2+2}; + + int x[] = {$(2)0, $(3)1}; + )c"); + expect_size(4); + expect_hint("0", ".x="); + expect_hint("1", ".y="); + expect_hint("2", "[0]="); + expect_hint("3", "[1]="); + + // Nested designators + run(R"c( + struct Inner { int x, y; }; + struct Outer { Inner a, b; }; + Outer o{ $(0)a{ $(1)1, $(2)2 }, $(3)bx3 }; + )c"); + expect_size(4); + expect_hint("0", ".a="); + expect_hint("1", ".x="); + expect_hint("2", ".y="); + expect_hint("3", ".b.x="); + + // Anonymous record + run(R"c( + struct S { + union { + struct { + struct { + int y; + }; + } x; + }; + }; + S s{$(0)xy42}; + )c"); + expect_size(1); + expect_hint("0", ".x.y="); + + // Suppression + run(R"c( + struct Point { int a, b, c, d, e, f, g, h; }; + Point p{/*a=*/1, .c=2, /* .d = */3, $(0)4}; + )c"); + expect_size(1); + expect_hint("0", ".e="); + + // Std array + run(R"c( + template struct Array { T __elements[N]; }; + Array x = {$(0)0, $(1)1}; + )c"); + expect_size(2); + expect_hint("0", "[0]="); + expect_hint("1", "[1]="); + + // Only aggregate init + run(R"c( + struct Copyable { int x; } c; + Copyable d{c}; + + struct Constructible { Constructible(int x); }; + Constructible x{42}; + )c"); + expect_size(0); + + // No crash + run(R"c( + struct A {}; + struct Foo {int a; int b;}; + void test() { + Foo f{A(), $(0)1}; + } + )c"); + expect_size(1); + expect_hint("0", ".b="); + }; + + skip / test("BlockEnd") = [&] { + // Functions + run(R"c( + int foo() { + return 41; + $(0)} + + template + int bar() { + // No hint for lambda for now + auto f = []() { + return X; + }; + return f(); + $(1)} + + // No hint because this isn't a definition + int buz(); + + struct S{}; + bool operator==(S, S) { + return true; + $(2)} + )c"); + expect_size(3); + expect_hint("0", " // foo"); + expect_hint("1", " // bar"); + expect_hint("2", " // operator=="); + + // Methods + run(R"c( + struct Test { + // No hint because there's no function body + Test() = default; + + ~Test() { + $(0)} + + void method1() { + $(1)} + + // No hint because this isn't a definition + void method2(); + + template + void method3() { + $(2)} + + // No hint because this isn't a definition + template + void method4(); + + Test operator+(int) const { + return *this; + $(3)} + + operator bool() const { + return true; + $(4)} + + // No hint because there's no function body + operator int() const = delete; + } x; + + void Test::method2() { + $(5)} + + template + void Test::method4() { + $(6)} + )c"); + expect_size(7); + expect_hint("0", " // ~Test"); + expect_hint("1", " // method1"); + expect_hint("2", " // method3"); + expect_hint("3", " // operator+"); + expect_hint("4", " // operator bool"); + expect_hint("5", " // Test::method2"); + expect_hint("6", " // Test::method4"); + + // Namespaces + run(R"c( + namespace { + void foo(); + $(0)} + + namespace ns { + void bar(); + $(1)} + )c"); + expect_size(2); + expect_hint("0", " // namespace"); + expect_hint("1", " // namespace ns"); + + // Types + run(R"c( + struct S { + $(0)}; + + class C { + $(1)}; + + union U { + $(2)}; + + enum E1 { + $(3)}; + + enum class E2 { + $(4)}; + )c"); + expect_size(5); + expect_hint("0", " // struct S"); + expect_hint("1", " // class C"); + expect_hint("2", " // union U"); + expect_hint("3", " // enum E1"); + expect_hint("4", " // enum class E2"); + + // If statements + run(R"c( + void foo(bool cond) { + if (cond) + ; + + if (cond) { + $(0)} + + if (cond) { + } else { + $(1)} + + if (cond) { + } else if (!cond) { + $(2)} + + if (cond) { + } else { + if (!cond) { + $(3)} + $(4)} + + if (auto X = cond) { + $(5)} + + if (int i = 0; i > 10) { + $(6)} + } + )c"); + expect_size(7); + expect_hint("0", " // if cond"); + expect_hint("1", " // if cond"); + expect_hint("2", " // if"); + expect_hint("3", " // if !cond"); + expect_hint("4", " // if cond"); + expect_hint("5", " // if X"); + expect_hint("6", " // if i > 10"); + + // Loops + run(R"c( + void foo() { + while (true) + ; + + while (true) { + $(0)} + + do { + } while (true); + + for (;true;) { + $(1)} + + for (int I = 0; I < 10; ++I) { + $(2)} + + int Vs[] = {1,2,3}; + for (auto V : Vs) { + $(3)} + } + )c"); + expect_size(4); + expect_hint("0", " // while true"); + expect_hint("1", " // for true"); + expect_hint("2", " // for I"); + expect_hint("3", " // for V"); + + // Switch + run(R"c( + void foo(int I) { + switch (I) { + case 0: break; + $(0)} + } + )c"); + expect_size(1); + expect_hint("0", " // switch I"); + + // Print literals + run(R"c( + void foo() { + while ("foo") { + $(0)} + + while ("foo but this time it is very long") { + $(1)} + + while (true) { + $(2)} + + while (1) { + $(3)} + + while (1.5) { + $(4)} + } + )c"); + expect_size(5); + expect_hint("0", " // while \"foo\""); + expect_hint("1", " // while \"foo but...\""); + expect_hint("2", " // while true"); + expect_hint("3", " // while 1"); + expect_hint("4", " // while 1.5"); + + // Print refs + run(R"c( + namespace ns { + int Var; + int func(); + struct S { + int Field; + int method() const; + }; + } + void foo() { + while (ns::Var) { + $(0)} + + while (ns::func()) { + $(1)} + + while (ns::S{}.Field) { + $(2)} + + while (ns::S{}.method()) { + $(3)} + } + )c"); + expect_size(4); + expect_hint("0", " // while Var"); + expect_hint("1", " // while func"); + expect_hint("2", " // while Field"); + expect_hint("3", " // while method"); + + // Print conversions + run(R"c( + struct S { + S(int); + S(int, int); + explicit operator bool(); + }; + void foo(int I) { + while (float(I)) { + $(0)} + + while (S(I)) { + $(1)} + + while (S(I, I)) { + $(2)} + } + )c"); + expect_size(3); + expect_hint("0", " // while float"); + expect_hint("1", " // while S"); + expect_hint("2", " // while S"); + + // Print operators + run(R"c( + using Integer = int; + void foo(Integer I) { + while(++I){ + $(0)} + + while(I++){ + $(1)} + + while(+(I + I)){ + $(2)} + + while(I < 0){ + $(3)} + + while((I + I) < I){ + $(4)} + + while(I < (I + I)){ + $(5)} + + while((I + I) < (I + I)){ + $(6)} + } + )c"); + expect_size(7); + expect_hint("0", " // while ++I"); + expect_hint("1", " // while I++"); + expect_hint("2", " // while"); + expect_hint("3", " // while I < 0"); + expect_hint("4", " // while ... < I"); + expect_hint("5", " // while I < ..."); + expect_hint("6", " // while"); + + // Trailing semicolon + run(R"c( + // The hint is placed after the trailing ';' + struct S1 { + $(0)} ; + + // The hint is always placed in the same line with the closing '}'. + // So in this case where ';' is missing, it is attached to '}'. + struct S2 { + $(1)} + + ; + + // No hint because only one trailing ';' is allowed + struct S3 { + };; + + // No hint because trailing ';' is only allowed for class/struct/union/enum + void foo() { + }; + + // Rare case, but yes we'll have a hint here. + struct { + int x; + $(2)} + + s2; + )c"); + expect_size(3); + expect_hint("0", " // struct S1"); + expect_hint("1", " // struct S2"); + expect_hint("2", " // struct"); + + // Trailing text + run(R"c( + struct S1 { + $(0)} ; + + // No hint for S2 because of the trailing comment + struct S2 { + }; /* Put anything here */ + + struct S3 { + // No hint for S4 because of the trailing source code + struct S4 { + };$(1)}; + + // No hint for ns because of the trailing comment + namespace ns { + } // namespace ns + )c"); + expect_size(2); + expect_hint("0", " // struct S1"); + expect_hint("1", " // struct S3"); + + // Macro + run(R"c( + #define DECL_STRUCT(NAME) struct NAME { + #define RBRACE } + + DECL_STRUCT(S1) + $(0)}; + + // No hint because we require a '}' + DECL_STRUCT(S2) + RBRACE; + )c"); + expect_size(1); + expect_hint("0", " // struct S1"); + + // Pointer to member function + run(R"c( + class A {}; + using Predicate = bool(A::*)(); + void foo(A* a, Predicate p) { + if ((a->*p)()) { + $(0)} + } + )c"); + expect_size(1); + expect_hint("0", " // if"); + }; + + skip / test("DefaultArguments") = [&] { + // Smoke test + run(R"c( + int foo(int A = 4) { return A; } + int bar(int A, int B = 1, bool C = foo($(0))) { return A; } + int A = bar($(1)2$(2)); + + void baz(int = 5) { if (false) baz($(3)); }; + )c"); + expect_size(4); + expect_hint("0", "A: 4"); + expect_hint("1", "A: "); + expect_hint("2", ", B: 1, C: foo()"); + expect_hint("3", "5"); + + // Without parameter names + run(R"c( + struct Baz { + Baz(float a = 3 // + + 2); + }; + struct Foo { + Foo(int, Baz baz = // + Baz{$(0)} + + // + ) {} + }; + + int main() { + Foo foo1(1$(1)); + Foo foo2{2$(2)}; + Foo foo3 = {3$(3)}; + auto foo4 = Foo{4$(4)}; + } + )c"); + expect_size(5); + expect_hint("0", "..."); + expect_hint("1", ", Baz{}"); + expect_hint("2", ", Baz{}"); + expect_hint("3", ", Baz{}"); + expect_hint("4", ", Baz{}"); + }; + + test("Special") = [&] { + // Macros + run(R"c( + void foo(int param); + #define ExpandsToCall() foo(42) + void bar() { + ExpandsToCall(); + } + )c"); + expect_size(0); + + run(R"c( + #define PI 3.14 + void foo(double param); + void bar() { + foo($(0)PI); + } + )c"); + expect_size(1); + expect_hint("0", "param:"); + + run(R"c( + void abort(); + #define ASSERT(expr) if (!(expr)) abort() + int foo(int param); + void bar() { + ASSERT(foo($(0)42) == 0); + } + )c"); + expect_size(1); + expect_hint("0", "param:"); + + run(R"c( + void foo(double x, double y); + #define CONSTANTS 3.14, 2.72 + void bar() { + foo(CONSTANTS); + } + )c"); + expect_size(0); + + // Implicit constructor + run(R"c( + struct S { + S(int param); + }; + void bar(S); + S foo() { + // Do not show hint for implicit constructor call in argument. + bar(42); + // Do not show hint for implicit constructor call in return. + return 42; + } + )c"); + expect_size(0); + + // Aggregate init + run(R"c( + struct Point { + int x; + int y; + }; + void bar() { + Point p{41, 42}; + } + )c"); + expect_size(0); + + // Builtin functions + run(R"c( + namespace std { template T&& forward(T&); } + void foo() { + int i; + int&& s = std::forward(i); + } + )c"); + expect_size(0); + + // Pseudo object expression + run(R"c( + struct S { + __declspec(property(get=GetX, put=PutX)) int x[]; + int GetX(int y, int z) { return 42 + y; } + void PutX(int) { } + + // This is a PseudoObjectExpression whose syntactic form is a binary + // operator. + void Work(int y) { x = y; } // Not `x = y: y`. + }; + + int printf(const char *Format, ...); + + int main() { + S s; + __builtin_dump_struct(&s, printf); // Not `Format: __builtin_dump_struct()` + printf($(0)"Hello, %d", 42); // Normal calls are not affected. + // This builds a PseudoObjectExpr, but here it's useful for showing the + // arguments from the semantic form. + return s.x[ $(1)1 ][ $(2)2 ]; // `x[y: 1][z: 2]` + } + )c"); + expect_size(3); + expect_hint("0", "Format:"); + expect_hint("1", "y:"); + expect_hint("2", "z:"); + }; + + test("VariadicTemplate") = [&] { + // Arg packs and constructors + run(R"c( + struct Foo{ Foo(); Foo(int x); }; + + void foo(Foo a, int b); + + template + void bar(Args... args) { + foo(args...); + } + + template + void baz(Args... args) { foo($(0)Foo{args...}, $(1)1); } + + template + void bax(Args... args) { foo($(2){args...}, args...); } + + void foo() { + bar($(3)Foo{}, $(4)42); + bar($(5)42, $(6)42); + baz($(7)42); + bax($(8)42); + } + )c"); + /// FIXME: + /// expect_hint("0", "a:"); + /// expect_hint("1", "b:"); + /// expect_hint("2", "a:"); + expect_hint("3", "a:"); + expect_hint("4", "b:"); + expect_hint("5", "a:"); + expect_hint("6", "b:"); + expect_hint("7", "x:"); + expect_hint("8", "b:"); + + // Doesn't expand all args + run(R"c( + void foo(int x, int y); + int id(int a, int b, int c); + template + void bar(Args... args) { + foo(id($(0)args, $(1)1, $(2)args)...); + } + void foo() { + bar(1, 2); // FIXME: We could have `bar(a: 1, a: 2)` here. + } + )c"); + /// FIXME: + /// expect_size(3); + /// expect_hint("0", "a:"); + /// expect_hint("1", "b:"); + /// expect_hint("2", "c:"); + }; + + skip / test("Dependent") = [&] { + // Dependent calls + run(R"c( + template + void nonmember(T par1); + + template + struct A { + void member(T par2); + static void static_member(T par3); + }; + + void overload(int anInt); + void overload(double aDouble); + + template + struct S { + void bar(A a, T t) { + nonmember($(0)t); + a.member($(1)t); + A::static_member($(2)t); + // We don't want to arbitrarily pick between + // "anInt" or "aDouble", so just show no hint. + overload(T{}); + } + }; + )c"); + expect_size(3); + expect_hint("0", "par1:"); + expect_hint("1", "par2:"); + expect_hint("2", "par3:"); }; }; } // namespace } // namespace clice::testing + diff --git a/tests/unit/Index/HeaderIndex.cpp b/tests/unit/Index/HeaderIndex.cpp index d68f053e..86e8d584 100644 --- a/tests/unit/Index/HeaderIndex.cpp +++ b/tests/unit/Index/HeaderIndex.cpp @@ -16,52 +16,52 @@ struct DumpConfig { auto dump = [](HeaderIndex& index, DumpConfig config) { if(config.enable_total) { - println("\n---------------------------Total Info---------------------------"); - println("file count: {}", index.file_count()); - println("header context count: {}", index.header_context_count()); - println("canonical context count: {}", index.canonical_context_count()); - println("symbol count: {}", index.symbols.size()); - println("occurrence count: {}", index.occurrences.size()); + std::println("\n---------------------------Total Info---------------------------"); + std::println("file count: {}", index.file_count()); + std::println("header context count: {}", index.header_context_count()); + std::println("canonical context count: {}", index.canonical_context_count()); + std::println("symbol count: {}", index.symbols.size()); + std::println("occurrence count: {}", index.occurrences.size()); } if(config.enable_contexts) { - println("\n--------------------------Contexts Info-------------------------"); + std::println("\n--------------------------Contexts Info-------------------------"); for(auto& [path, contents]: index.header_contexts) { - println("{}:", path); + std::println("{}:", path); for(auto& context: contents) { - println(" include: {}, hctx_id: {}, cctx_id: {}", - context.include, - context.hctx_id, - context.cctx_id); + std::println(" include: {}, hctx_id: {}, cctx_id: {}", + context.include, + context.hctx_id, + context.cctx_id); } } } if(config.enable_symbol) { - println("\n-------------------------Symbols Info--------------------------"); + std::println("\n-------------------------Symbols Info--------------------------"); for(auto& [symbol_id, symbol]: index.symbols) { - clice::println("symbol: {}, kind: {}", symbol.name, symbol.kind.name()); + std::println("symbol: {}, kind: {}", symbol.name, symbol.kind.name()); for(auto& relation: symbol.relations) { if(relation.ctx.is_dependent()) { auto context = index.dependent_elem_states[relation.ctx.offset()]; - clice::println(" kind: {}, context: {:#b}", - relation.kind.name(), - context.to_ulong()); + std::println(" kind: {}, context: {:#b}", + relation.kind.name(), + context.to_ulong()); } } } } if(config.enable_occurrence) { - println("\n-------------------------Occurrences Info--------------------------"); + std::println("\n-------------------------Occurrences Info--------------------------"); for(auto& [range, occurrences]: index.occurrences) { - println("occurrence: {} {}", range.begin, range.end); + std::println("occurrence: {} {}", range.begin, range.end); for(auto& occurrence: occurrences) { if(occurrence.ctx.is_dependent()) { auto context = index.dependent_elem_states[occurrence.ctx.offset()]; - println(" target: {}, context: {:#b}", - occurrence.target_symbol, - context.to_ulong()); + std::println(" target: {}, context: {:#b}", + occurrence.target_symbol, + context.to_ulong()); } } } diff --git a/tests/unit/Support/Doxygen.cpp b/tests/unit/Support/Doxygen.cpp index df7a0d81..72cbc74c 100644 --- a/tests/unit/Support/Doxygen.cpp +++ b/tests/unit/Support/Doxygen.cpp @@ -69,27 +69,27 @@ suite<"Doxygen"> doxygen = [] { )"; auto [di, md] = strip_doxygen_info(raw_comment); expect(di.get_block_command_comments().size() == 0); - clice::println("Rest:\n```{}```", md); + std::println("Rest:\n```{}```", md); } // ParamCommandComment { constexpr auto raw_comment = R"( @)"; - clice::println("Processing raw comment: `{}`", raw_comment); + std::println("Processing raw comment: `{}`", raw_comment); auto [di, md] = strip_doxygen_info(raw_comment); - clice::println("Rest:\n```\n{}\n```\n", md); + std::println("Rest:\n```\n{}\n```\n", md); } { constexpr auto raw_comment = R"( @param)"; - clice::println("Processing raw comment: `{}`", raw_comment); + std::println("Processing raw comment: `{}`", raw_comment); auto [di, md] = strip_doxygen_info(raw_comment); - clice::println("Rest:\n```\n{}\n```\n", md); + std::println("Rest:\n```\n{}\n```\n", md); } { constexpr auto raw_comment = R"( @param[in,out] foo doc for foo)"; - clice::println("Processing raw comment: `{}`", raw_comment); + std::println("Processing raw comment: `{}`", raw_comment); auto [di, md] = strip_doxygen_info(raw_comment); expect(md.size() == 0); auto info_foo = di.find_param_info("foo"); @@ -98,7 +98,7 @@ suite<"Doxygen"> doxygen = [] { llvm::StringRef doc = info_foo.value()->content; expect(info_foo.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::InOut); - clice::println("Doc:\n```\n{}\n```\n", doc); + std::println("Doc:\n```\n{}\n```\n", doc); } } @@ -121,7 +121,7 @@ suite<"Doxygen"> doxygen = [] { llvm::StringRef doc = info_foo.value()->content; expect(info_foo.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::Out); - clice::println("Doc:\n```\n{}\n```", doc); + std::println("Doc:\n```\n{}\n```", doc); } auto info_bar = di.find_param_info("bar"); @@ -130,7 +130,7 @@ suite<"Doxygen"> doxygen = [] { llvm::StringRef doc = info_bar.value()->content; expect(info_bar.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::In); - clice::println("Doc:\n```\n{}\n```", doc); + std::println("Doc:\n```\n{}\n```", doc); } auto info_baz = di.find_param_info("baz"); @@ -146,7 +146,7 @@ suite<"Doxygen"> doxygen = [] { test("DoxygenParserIntegrated") = [&] { { - clice::println("##################################################################"); + std::println("##################################################################"); constexpr auto raw_comment = R"( @brief Calculates the area of a rectangle. @@ -171,13 +171,13 @@ suite<"Doxygen"> doxygen = [] { details 2 blah blah... line2 )"; auto [di, md] = strip_doxygen_info(raw_comment); - clice::println("Markdown After Stripping:\n```\n{}\n```", md); + std::println("Markdown After Stripping:\n```\n{}\n```", md); auto info_width = di.find_param_info("width"); expect(info_width.has_value()); if(info_width.has_value()) { expect(info_width.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::In); - clice::println("Doc for `width`:\n```\n{}\n```", info_width.value()->content); + std::println("Doc for `width`:\n```\n{}\n```", info_width.value()->content); } auto info_height = di.find_param_info("height"); @@ -185,28 +185,28 @@ suite<"Doxygen"> doxygen = [] { if(info_height.has_value()) { expect(info_height.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::In); - clice::println("Doc for `height`:\n```\n{}\n```", info_height.value()->content); + std::println("Doc for `height`:\n```\n{}\n```", info_height.value()->content); } auto bcc_list = di.get_block_command_comments(); expect(bcc_list.size() == 3); - clice::println("RegularTags:"); + std::println("RegularTags:"); for(auto& [tag_name, content]: bcc_list) { - clice::println("================================="); - clice::println("Tag name: `{}`", tag_name); + std::println("================================="); + std::println("Tag name: `{}`", tag_name); for(auto& item: content) { - clice::println("Item:\n```\n{}\n```", item.content); + std::println("Item:\n```\n{}\n```", item.content); } - clice::println("================================="); + std::println("================================="); } auto ret_info = di.get_return_info(); expect(ret_info.has_value()); if(ret_info.has_value()) { - clice::println("Doc for return value:\n```\n{}\n```", ret_info.value()); + std::println("Doc for return value:\n```\n{}\n```", ret_info.value()); } - clice::println("##################################################################"); + std::println("##################################################################"); } // Full test @@ -268,13 +268,13 @@ suite<"Doxygen"> doxygen = [] { @return doc for return value )"; auto [di, md] = strip_doxygen_info(raw_comment); - clice::println("Markdown After Stripping:\n```\n{}\n```", md); + std::println("Markdown After Stripping:\n```\n{}\n```", md); auto info_foo = di.find_param_info("foo"); expect(info_foo.has_value()); if(info_foo.has_value()) { expect(info_foo.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::In); - clice::println("Doc for `foo`:\n```\n{}\n```", info_foo.value()->content); + std::println("Doc for `foo`:\n```\n{}\n```", info_foo.value()->content); } auto info_bar = di.find_param_info("bar"); @@ -282,7 +282,7 @@ suite<"Doxygen"> doxygen = [] { if(info_bar.has_value()) { expect(info_bar.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::Out); - clice::println("Doc for `bar`:\n```\n{}\n```", info_bar.value()->content); + std::println("Doc for `bar`:\n```\n{}\n```", info_bar.value()->content); } auto info_baz = di.find_param_info("baz"); @@ -290,7 +290,7 @@ suite<"Doxygen"> doxygen = [] { if(info_baz.has_value()) { expect(info_baz.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::InOut); - clice::println("Doc for `baz`:\n```\n{}\n```", info_baz.value()->content); + std::println("Doc for `baz`:\n```\n{}\n```", info_baz.value()->content); } auto info_awa = di.find_param_info("awa"); @@ -298,28 +298,28 @@ suite<"Doxygen"> doxygen = [] { if(info_awa.has_value()) { expect(info_awa.value()->direction == DoxygenInfo::ParamCommandCommentContent::ParamDirection::Unspecified); - clice::println("Doc for `awa`:\n```\n{}\n```", info_awa.value()->content); + std::println("Doc for `awa`:\n```\n{}\n```", info_awa.value()->content); } auto bcc_list = di.get_block_command_comments(); expect(bcc_list.size() == 4); - clice::println("RegularTags:"); + std::println("RegularTags:"); for(auto& [tag_name, content]: bcc_list) { - clice::println("================================="); - clice::println("Tag name: `{}`", tag_name); + std::println("================================="); + std::println("Tag name: `{}`", tag_name); for(auto& item: content) { - clice::println("Item:\n```\n{}\n```", item.content); + std::println("Item:\n```\n{}\n```", item.content); } - clice::println("================================="); + std::println("================================="); } auto ret_info = di.get_return_info(); expect(ret_info.has_value()); if(ret_info.has_value()) { - clice::println("Doc for return value:\n```\n{}\n```", ret_info.value()); + std::println("Doc for return value:\n```\n{}\n```", ret_info.value()); } - clice::println("##################################################################"); + std::println("##################################################################"); } }; }; diff --git a/tests/unit/Support/StructedText.cpp b/tests/unit/Support/StructedText.cpp index 9dad7dfc..d2c8a832 100644 --- a/tests/unit/Support/StructedText.cpp +++ b/tests/unit/Support/StructedText.cpp @@ -47,7 +47,7 @@ char *longestPalindrome_solv2(const char *s) { st.add_code_block(cb, "c"); auto& para = st.add_paragraph(); para.append_text("para1").append_newline_char(); - clice::print("{}", st.as_markdown()); + std::println("{}", st.as_markdown()); }; test("BulletList") = [&] { @@ -60,7 +60,7 @@ char *longestPalindrome_solv2(const char *s) { Paragraph::Kind::Italic); st.add_bullet_list().add_item().add_paragraph().append_text("Item5", Paragraph::Kind::Strikethough); - clice::print("{}", st.as_markdown()); + std::println("{}", st.as_markdown()); }; test("FullText") = [&] { @@ -112,7 +112,7 @@ This is *Italic* **Bold** ~~Striketough~~, `InlineCode` warnings.add_item().add_paragraph().append_text("warnings3: blah blah..."); st.add_ruler(); st.add_code_block("int test_bar(int foo, char **bar, char **baz);\n", "cpp"); - clice::print("{}", st.as_markdown()); + std::println("{}", st.as_markdown()); }; };