diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp index 3da047f954213..b540c273cbd59 100644 --- a/clang-tools-extra/clangd/InlayHints.cpp +++ b/clang-tools-extra/clangd/InlayHints.cpp @@ -589,6 +589,31 @@ class InlayHintVisitor : public RecursiveASTVisitor { return true; } + // Carefully recurse into PseudoObjectExprs, which typically incorporate + // a syntactic expression and several semantic expressions. + bool TraversePseudoObjectExpr(PseudoObjectExpr *E) { + Expr *SyntacticExpr = E->getSyntacticForm(); + if (isa(SyntacticExpr)) + // Since the counterpart semantics usually get the identical source + // locations as the syntactic one, visiting those would end up presenting + // confusing hints e.g., __builtin_dump_struct. + // Thus, only traverse the syntactic forms if this is written as a + // CallExpr. This leaves the door open in case the arguments in the + // syntactic form could possibly get parameter names. + return RecursiveASTVisitor::TraverseStmt(SyntacticExpr); + // We don't want the hints for some of the MS property extensions. + // e.g. + // struct S { + // __declspec(property(get=GetX, put=PutX)) int x[]; + // void PutX(int y); + // void Work(int y) { x = y; } // Bad: `x = y: y`. + // }; + if (isa(SyntacticExpr)) + return true; + // FIXME: Handle other forms of a pseudo object expression. + return RecursiveASTVisitor::TraversePseudoObjectExpr(E); + } + bool VisitCallExpr(CallExpr *E) { if (!Cfg.InlayHints.Parameters) return true; diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp index 20c1cdd985dbc..6e91053632e00 100644 --- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp +++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp @@ -1709,7 +1709,8 @@ TEST(DesignatorHints, NoCrash) { void test() { Foo f{A(), $b[[1]]}; } - )cpp", ExpectedHint{".b=", "b"}); + )cpp", + ExpectedHint{".b=", "b"}); } TEST(InlayHints, RestrictRange) { @@ -1724,6 +1725,38 @@ TEST(InlayHints, RestrictRange) { ElementsAre(labelIs(": int"), labelIs(": char"))); } +TEST(ParameterHints, PseudoObjectExpr) { + Annotations Code(R"cpp( + 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($Param[["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[ $one[[1]] ][ $two[[2]] ]; // `x[y: 1][z: 2]` + } + )cpp"); + auto TU = TestTU::withCode(Code.code()); + TU.ExtraArgs.push_back("-fms-extensions"); + auto AST = TU.build(); + EXPECT_THAT(inlayHints(AST, std::nullopt), + ElementsAre(HintMatcher(ExpectedHint{"Format: ", "Param"}, Code), + HintMatcher(ExpectedHint{"y: ", "one"}, Code), + HintMatcher(ExpectedHint{"z: ", "two"}, Code))); +} + TEST(ParameterHints, ArgPacksAndConstructors) { assertParameterHints( R"cpp(