Index: clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -21,6 +21,7 @@ ExpandMacro.cpp ExtractFunction.cpp ExtractVariable.cpp + FillStructDesignatedInit.cpp MemberwiseConstructor.cpp ObjCLocalizeStringLiteral.cpp ObjCMemberwiseInitializer.cpp Index: clang-tools-extra/clangd/refactor/tweaks/FillStructDesignatedInit.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clangd/refactor/tweaks/FillStructDesignatedInit.cpp @@ -0,0 +1,128 @@ +//===--- FillStructDesignatedInit.cpp ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "AST.h" +#include "Selection.h" +#include "refactor/Tweak.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclBase.h" +#include "clang/AST/DeclCXX.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { +namespace clangd { +namespace { + +// A tweak that fills an aggregate without parents with designated initializers +// +// Given: +// struct S { int x; unique_ptr y; char* s; }; +// the tweak initializes struct with designated initializers: +// void bar() { S s{.x = int{}, .y = unique_ptr{}, .s = {}}; } +// +class FillStructDesignatedInit : public Tweak { +public: + const char *id() const final; + llvm::StringLiteral kind() const override { + return CodeAction::REFACTOR_KIND; + } + bool prepare(const Selection &Inputs) override; + Expected apply(const Selection &Inputs) override; + std::string title() const override; + +private: + CXXRecordDecl const *Class; + SourceRange InitRange; + std::string buildInitCode() const; + std::string determineTypeName(FieldDecl const &FD, + DeclContext const &Ctx) const; + std::string processRecord(CXXRecordDecl const *RD, DeclContext const &) const; +}; + +REGISTER_TWEAK(FillStructDesignatedInit) + +std::string FillStructDesignatedInit::title() const { + return "Fill struct with designated initializers"; +} + +bool FillStructDesignatedInit::prepare(const Selection &Inputs) { + if (!Inputs.AST->getLangOpts().CPlusPlus20) { + return false; + } + if (auto *N = Inputs.ASTSelection.commonAncestor()) { + if (InitListExpr const *ILE = N->ASTNode.get()) { + Class = ILE->getType()->getAsCXXRecordDecl(); + if (Class && Class->isAggregate() && Class->bases().empty()) { + InitRange = ILE->getSourceRange(); + return true; + } + } + } + return false; +} + +std::string FillStructDesignatedInit::buildInitCode() const { + std::string Res; + llvm::raw_string_ostream OS(Res); + + OS << processRecord(Class, *Class); + + return Res; +} + +std::string +FillStructDesignatedInit::determineTypeName(FieldDecl const &FD, + DeclContext const &Ctx) const { + std::string TypeName; + if (!FD.getType()->isPointerType()) { + TypeName = printType(FD.getType().getLocalUnqualifiedType(), Ctx); + } + return TypeName; +} + +std::string +FillStructDesignatedInit::processRecord(CXXRecordDecl const *RD, + DeclContext const &Ctx) const { + std::string Res; + llvm::raw_string_ostream OS(Res); + + auto ProcessWithoutParents = [&]() { + char const *Sep = " "; + for (auto It = RD->field_begin(); It != RD->field_end();) { + FieldDecl const *FD = *It; + std::string TypeName(determineTypeName(*FD, Ctx)); + OS << llvm::formatv(".{0} = {1}{{}", (*It)->getName(), TypeName); + if (++It == RD->field_end()) { + break; + } + OS << "," << Sep; + } + }; + + OS << "{"; + ProcessWithoutParents(); + OS << "}"; + + return Res; +} + +Expected +FillStructDesignatedInit::apply(const Selection &Inputs) { + assert(Class); + std::string Code = buildInitCode(); + auto &SrcMgr = Inputs.AST->getSourceManager(); + tooling::Replacement Init(SrcMgr, CharSourceRange(InitRange, true), Code); + return Effect::mainFileEdit(SrcMgr, tooling::Replacements(Init)); +} +} // namespace +} // namespace clangd +} // namespace clang Index: clang-tools-extra/clangd/unittests/CMakeLists.txt =================================================================== --- clang-tools-extra/clangd/unittests/CMakeLists.txt +++ clang-tools-extra/clangd/unittests/CMakeLists.txt @@ -118,6 +118,7 @@ tweaks/ExpandMacroTests.cpp tweaks/ExtractFunctionTests.cpp tweaks/ExtractVariableTests.cpp + tweaks/FillStructDesignatedInitTests.cpp tweaks/MemberwiseConstructorTests.cpp tweaks/ObjCLocalizeStringLiteralTests.cpp tweaks/ObjCMemberwiseInitializerTests.cpp Index: clang-tools-extra/clangd/unittests/tweaks/FillStructDesignatedInitTests.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clangd/unittests/tweaks/FillStructDesignatedInitTests.cpp @@ -0,0 +1,126 @@ +//===-- FillStructDesignatedInitTests.cpp -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "TweakTesting.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace clang { +namespace clangd { +namespace { + +TWEAK_TEST(FillStructDesignatedInit); + +TEST_F(FillStructDesignatedInitTest, AvailableUnavailable) { + Header = R"cpp( + namespace ns1 { + struct Foo { + Foo() {} + }; + } + )cpp"; + EXPECT_UNAVAILABLE(R"cpp( + namespace ns1 { + struct Bar { + int x; + int y; + }; + void bar(Bar b) {} + void foo() { + Bar b[[{}]]; + } + } + )cpp"); + ExtraArgs.push_back("-std=c++20"); + EXPECT_UNAVAILABLE(R"cpp( + namespace ns1 { + struct Bar : Foo { + int x; + int y; + }; + void bar(Bar b) {} + void foo() { + Bar b[[{}]]; + } + } + )cpp"); + EXPECT_AVAILABLE(R"cpp( + namespace ns1 { + struct Bar { + int x; + int y; + }; + void bar(Bar b) {} + void foo() { + Bar b[[{}]]; + bar([[{}]]); + } + } + )cpp"); +} + +TEST_F(FillStructDesignatedInitTest, Apply1) { + ExtraArgs.push_back("-std=c++20"); + EXPECT_EQ(apply(R"cpp( + namespace ns1 { + struct D1 { + int x; + int* y; + }; + void foo() { + D1 b[[{}]]; + } + } + )cpp"), + R"cpp( + namespace ns1 { + struct D1 { + int x; + int* y; + }; + void foo() { + D1 b{.x = int{}, .y = {}}; + } + } + )cpp"); +} + +TEST_F(FillStructDesignatedInitTest, Apply2) { + ExtraArgs.push_back("-std=c++20"); + Header = R"cpp( + namespace ns { + struct B1 { + int b1; + }; + } + namespace ns1 { + struct D1 { + int* x; + ns::B1 b1; + }; + } + )cpp"; + EXPECT_EQ(apply(R"cpp( + namespace ns1 { + void foo() { + D1 b[[{}]]; + } + } + )cpp"), + R"cpp( + namespace ns1 { + void foo() { + D1 b{.x = {}, .b1 = ns::B1{}}; + } + } + )cpp"); +} + +} // namespace +} // namespace clangd +} // namespace clang