diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -11,10 +11,12 @@ #include "HeuristicResolver.h" #include "ParsedAST.h" #include "SourceCode.h" +#include "clang/AST/ASTDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/Type.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/SourceManager.h" #include "llvm/ADT/ScopeExit.h" @@ -190,6 +192,52 @@ return Designators; } +// Determines if any part in desugaring steps of QualType QT is a dependent or +// builtin type. Ignore pointer or reference qualifiers. +bool isDependentOrTrivialSugaredType(QualType QT) { + static auto PeelQualifier = [](QualType QT) { + // Neither `PointerType` nor `ReferenceType` is considered as sugared + // type. Peel it. + QualType Last = QT; + for (QT = QT->getPointeeType(); !QT.isNull(); + Last = QT, QT = QT->getPointeeType()) + ; + return Last; + }; + for (QualType Desugared = + PeelQualifier(QT->getLocallyUnqualifiedSingleStepDesugaredType()); + Desugared != QT; QT = Desugared, + Desugared = PeelQualifier( + Desugared + ->getLocallyUnqualifiedSingleStepDesugaredType())) { + if (Desugared->getAs()) + return true; + if (Desugared->getAs()) + return true; + } + return false; +} + +// A simple wrapper for `clang::desugarForDiagnostic` that provides optional +// semantic. +std::optional desugar(ASTContext &AST, QualType QT) { + bool ShouldAKA; + auto Desugared = clang::desugarForDiagnostic(AST, QT, ShouldAKA); + if (!ShouldAKA) + return std::nullopt; + return Desugared; +} + +// 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 +// name). If so, return the desugared type. Otherwise, return the unchanged +// parameter QT. +QualType tryDesugar(ASTContext &AST, QualType QT) { + if (isDependentOrTrivialSugaredType(QT)) + return desugar(AST, QT).value_or(QT); + return QT; +} + class InlayHintVisitor : public RecursiveASTVisitor { public: InlayHintVisitor(std::vector &Results, ParsedAST &AST, @@ -687,9 +735,14 @@ if (!Cfg.InlayHints.DeducedTypes || T.isNull()) return; - std::string TypeName = T.getAsString(Policy); - if (Cfg.InlayHints.TypeNameLimit == 0 || - TypeName.length() < Cfg.InlayHints.TypeNameLimit) + auto Desugared = tryDesugar(AST, T); + std::string TypeName = Desugared.getAsString(Policy); + if (T != Desugared && !shouldPrintTypeHint(TypeName)) { + // If the desugared type is too long to display, fallback to the original + // type. + TypeName = T.getAsString(Policy); + } + if (shouldPrintTypeHint(TypeName)) addInlayHint(R, HintSide::Right, InlayHintKind::Type, Prefix, TypeName, /*Suffix=*/""); } @@ -699,6 +752,11 @@ /*Prefix=*/"", Text, /*Suffix=*/"="); } + bool shouldPrintTypeHint(llvm::StringRef TypeName) const noexcept { + return Cfg.InlayHints.TypeNameLimit == 0 || + TypeName.size() < Cfg.InlayHints.TypeNameLimit; + } + std::vector &Results; ASTContext &AST; const syntax::TokenBuffer &Tokens; diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -1417,6 +1417,81 @@ ExpectedHint{": int", "h"}, ExpectedHint{": int", "i"}); } +TEST(TypeHints, DependentAliases) { + assertTypeHints( + R"cpp( + template struct allocator {}; + + template + struct vector_base { + using pointer = T*; + }; + + template + struct internal_iterator_type_template_we_dont_expect {}; + + struct my_iterator {}; + + template > + struct vector : vector_base { + using base = vector_base; + typedef T value_type; + typedef base::pointer pointer; + using allocator_type = A; + using size_type = int; + using iterator = internal_iterator_type_template_we_dont_expect; + using non_template_iterator = my_iterator; + + value_type& operator[](int index) { return elements[index]; } + const value_type& at(int index) const { return elements[index]; } + pointer data() { return &elements[0]; } + allocator_type get_allocator() { return A(); } + size_type size() const { return 10; } + iterator begin() { return iterator(); } + non_template_iterator end() { return non_template_iterator(); } + + T elements[10]; + }; + + vector array; + + auto $no_modifier[[by_value]] = array[3]; + auto* $ptr_modifier[[ptr]] = &array[3]; + auto& $ref_modifier[[ref]] = array[3]; + auto& $at[[immutable]] = array.at(3); + + auto $data[[data]] = array.data(); + auto $allocator[[alloc]] = array.get_allocator(); + auto $size[[size]] = array.size(); + auto $begin[[begin]] = array.begin(); + auto $end[[end]] = array.end(); + + + // If the type alias is not type-dependent or non-trivial, do not show desugared type. + using VeryLongLongTypeName = my_iterator; + using Short = VeryLongLongTypeName; + + auto $short_name[[my_value]] = Short(); + + // Same applies with templates. + template + using basic_static_vector = vector; + template + using static_vector = basic_static_vector>; + + auto $vector_name[[vec]] = static_vector(); + )cpp", + ExpectedHint{": int", "no_modifier"}, + ExpectedHint{": int *", "ptr_modifier"}, + ExpectedHint{": int &", "ref_modifier"}, + ExpectedHint{": const int &", "at"}, ExpectedHint{": int *", "data"}, + ExpectedHint{": allocator", "allocator"}, + ExpectedHint{": int", "size"}, ExpectedHint{": iterator", "begin"}, + ExpectedHint{": non_template_iterator", "end"}, + ExpectedHint{": Short", "short_name"}, + ExpectedHint{": static_vector", "vector_name"}); +} + TEST(DesignatorHints, Basic) { assertDesignatorHints(R"cpp( struct S { int x, y, z; };