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 @@ -199,18 +199,45 @@ // Neither `PointerType` nor `ReferenceType` is considered as sugared // type. Peel it. QualType Next; - while (!(Next = QT->getPointeeType()).isNull()) + if (!(Next = QT->getPointeeType()).isNull()) QT = Next; return QT; }; + + // This is a bit tricky: we traverse the type structure and find whether or + // not a type in the desugaring process is of SubstTemplateTypeParmType. + // During the process, we may encounter pointer or reference types that are + // not marked as sugared; therefore, the desugar function won't apply. To + // move forward the traversal, we retrieve the pointees using + // QualType::getPointeeType(). + // + // However, getPointeeType could leap over our interests: The QT::getAs() + // invoked would implicitly desugar the type. Consequently, if the + // SubstTemplateTypeParmType is encompassed within a TypedefType, we may lose + // the chance to visit it. + // For example, given a QT that represents `std::vector::value_type`: + // `-ElaboratedType 'value_type' sugar + // `-TypedefType 'vector::value_type' sugar + // |-Typedef 'value_type' + // `-SubstTemplateTypeParmType 'int *' sugar class depth 0 index 0 T + // |-ClassTemplateSpecialization 'vector' + // `-PointerType 'int *' + // `-BuiltinType 'int' + // Applying `getPointeeType` to QT results in 'int', a child of our target + // node SubstTemplateTypeParmType. + // + // 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) { - QualType Desugared = - PeelWrappers(QT->getLocallyUnqualifiedSingleStepDesugaredType()); - if (Desugared == QT) - break; - if (Desugared->getAs()) + if (QT->getAs()) return true; - QT = Desugared; + QualType Desugared = QT->getLocallyUnqualifiedSingleStepDesugaredType(); + if (Desugared != QT) + QT = Desugared; + else if (auto Peeled = PeelWrappers(Desugared); Peeled != QT) + QT = Peeled; + else + break; } return false; } 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 @@ -81,11 +81,13 @@ } template -void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource, - ExpectedHints... Expected) { +void assertHintsWithHeader(InlayHintKind Kind, llvm::StringRef AnnotatedSource, + llvm::StringRef HeaderContent, + ExpectedHints... Expected) { Annotations Source(AnnotatedSource); TestTU TU = TestTU::withCode(Source.code()); TU.ExtraArgs.push_back("-std=c++20"); + TU.HeaderCode = HeaderContent; auto AST = TU.build(); EXPECT_THAT(hintsOfKind(AST, Kind), @@ -96,6 +98,12 @@ EXPECT_THAT(inlayHints(AST, std::nullopt), IsEmpty()); } +template +void assertHints(InlayHintKind Kind, llvm::StringRef AnnotatedSource, + ExpectedHints... Expected) { + return assertHintsWithHeader(Kind, AnnotatedSource, "", std::move(Expected)...); +} + // Hack to allow expression-statements operating on parameter packs in C++14. template void ignore(T &&...) {} @@ -1421,8 +1429,7 @@ } TEST(TypeHints, SubstTemplateParameterAliases) { - assertTypeHints( - R"cpp( + llvm::StringRef Header = R"cpp( template struct allocator {}; template @@ -1455,9 +1462,34 @@ T elements[10]; }; + )cpp"; + + llvm::StringRef VectorIntPtr = R"cpp( + vector array; + auto $no_modifier[[x]] = 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(); + )cpp"; + + assertHintsWithHeader( + InlayHintKind::Type, VectorIntPtr, Header, + ExpectedHint{": int *", "no_modifier"}, + ExpectedHint{": int **", "ptr_modifier"}, + ExpectedHint{": int *&", "ref_modifier"}, + ExpectedHint{": int *const &", "at"}, ExpectedHint{": int **", "data"}, + ExpectedHint{": allocator", "allocator"}, + ExpectedHint{": size_type", "size"}, ExpectedHint{": iterator", "begin"}, + ExpectedHint{": non_template_iterator", "end"}); + llvm::StringRef VectorInt = R"cpp( vector array; - auto $no_modifier[[by_value]] = array[3]; auto* $ptr_modifier[[ptr]] = &array[3]; auto& $ref_modifier[[ref]] = array[3]; @@ -1468,8 +1500,19 @@ auto $size[[size]] = array.size(); auto $begin[[begin]] = array.begin(); auto $end[[end]] = array.end(); + )cpp"; + assertHintsWithHeader( + InlayHintKind::Type, VectorInt, Header, + ExpectedHint{": int", "no_modifier"}, + ExpectedHint{": int *", "ptr_modifier"}, + ExpectedHint{": int &", "ref_modifier"}, + ExpectedHint{": const int &", "at"}, ExpectedHint{": int *", "data"}, + ExpectedHint{": allocator", "allocator"}, + ExpectedHint{": size_type", "size"}, ExpectedHint{": iterator", "begin"}, + ExpectedHint{": non_template_iterator", "end"}); + llvm::StringRef TypeAlias = R"cpp( // If the type alias is not of substituted template parameter type, // do not show desugared type. using VeryLongLongTypeName = my_iterator; @@ -1484,16 +1527,11 @@ 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{": size_type", "size"}, ExpectedHint{": iterator", "begin"}, - ExpectedHint{": non_template_iterator", "end"}, - ExpectedHint{": Short", "short_name"}, - ExpectedHint{": static_vector", "vector_name"}); + )cpp"; + + assertHintsWithHeader(InlayHintKind::Type, TypeAlias, Header, + ExpectedHint{": Short", "short_name"}, + ExpectedHint{": static_vector", "vector_name"}); } TEST(DesignatorHints, Basic) {