Как преобразовать инициализацию копии в прямую инициализацию списка в проверке clang-tidy?

Вопрос или проблема

Я хотел бы написать проверку для clang-tidy, которая находит объявления полей с копированием инициализации (ICIS_CopyInit) и может изменить их на прямую инициализацию списком (ICIS_ListInit).

Существует способ точно найти объявления полей с копированием инициализации:

  • найти FieldDecl
  • пропустить, если !hasInClassInitializer()
  • пропустить, если ICIS_CopyInit != getInClassInitStyle()

Затем я могу получить объявление в виде строки согласно тому ответу: https://stackoverflow.com/a/61510968/2492801 и заменить знак = на открывающую фигурную скобку, добавив закрывающую фигурную скобку в конце.

Но это выглядит неуклюже. Это было бы нормально для int test = 3;. Но такой подход, например, превратит копированную инициализацию MyStruct m_data = MyStruct(1, 3); в MyStruct m_data{MyStruct(1, 3)};, что не лучше, так как это все равно позволяет узкие преобразования аргументов конструктора MyStruct.

Вместо этого я хотел бы получить MyStruct m_data{1, 3};. Является ли создание строки замены кода для FixItHint::CreateReplacement(...) единственным, что я могу сделать, выполняя поиск и замену в строке объявления? Или я могу использовать методы clang AST для создания такой замены кода сам?

Ответ или решение

Создание проверки clang-tidy для преобразования инициализации копированием в инициализацию списком – это интересная задача, которую можно решить, используя AST (Abstract Syntax Tree) в Clang. Давайте рассмотрим, как можно реализовать такую проверку шаг за шагом.

1. Настройка clang-tidy

Первое, что вам нужно сделать, это создать новый clang-tidy проверочный инструмент. Для этого создайте новый класс, который наследует от ClangTidyCheck.

#include "ClangTidyCheck.h"
#include "ClangTidyContext.h"
#include "llvm/Support/raw_ostream.h"

using namespace clang;
using namespace clang::tidy;

class CopyInitToListInitCheck : public ClangTidyCheck {
public:
    CopyInitToListInitCheck(StringRef Name, ClangTidyContext *Context)
        : ClangTidyCheck(Name, Context) {}

    void registerMatchers(MatchFinder *Finder) override {
        // Находим FieldDecl с инициализацией копированием
        Finder->addMatcher(
            fieldDecl(hasInClassInitializer(), 
                      isEqualTo(ICIS_CopyInit)).bind("field"),
            this);
    }

    void check(const MatchFinder::MatchResult &Result) override {
        const auto *Field = Result.Nodes.getNodeAs<FieldDecl>("field");

        // Получаем информацию об инициализации
        if (const auto *InitExpr = Field->getInClassInitializer()) {
            if (const auto *ConstructExpr = dyn_cast<ConstructExpr>(InitExpr)) {
                // Получаем аргументы конструктора
                SmallVector<Expr *, 4> Args;
                for (auto Arg : ConstructExpr->arguments()) {
                    Args.push_back(Arg);
                }

                // Создаем новый AST узел для Инициализации списком
                auto *ListInit = new (Result.Context) InitListExpr(
                    Result.Context, SourceLocation(), Args, SourceLocation());

                // Здесь необходимо создать замену
                auto FixitHint = FixItHint::CreateReplacement(
                    Field->getSourceRange(), 
                    printNewListInit(Field, ListInit));

                // Выдаем предупреждение с возможностью исправления
                diagnoseAndSuggest(Field, FixitHint);
            }
        }
    }

private:
    std::string printNewListInit(const FieldDecl *Field, InitListExpr *ListInit) {
        // Конструируем строку из нового инициализатора
        std::string FieldName = Field->getName().str();
        std::string ArgsStr;
        llvm::raw_string_ostream Stream(ArgsStr);

        // Добавляем аргументы
        for (size_t i = 0; i < ListInit->getNumInits(); ++i) {
            if (i > 0)
                Stream << ", ";
            ListInit->getInit(i)->printPretty(Stream, nullptr, PrintingPolicy());
        }

        return FieldName + " {" + Stream.str() + "};";
    }

    void diagnoseAndSuggest(const FieldDecl *Field, const FixItHint &Hint) {
        if (getLangOpts().CPlusPlus) {
            diag(Field->getBeginLoc(), "замените инициализацию копированием на инициализацию списком")
                << Hint;
        }
    }
};

// Регистрация проверки
static RegisterCheck<CopyInitToListInitCheck>
    X("copy-init-to-list-init", "Преобразует инициализацию копированием в инициализацию списком.");

2. Важные моменты

  • Мы используем hasInClassInitializer() и isEqualTo(ICIS_CopyInit) для поиска полей, у которых есть инициализатор копированием.
  • Мы захватываем инициализатор и извлекаем аргументы конструктора, а затем создаем новый InitListExpr с полученными аргументами.
  • Метод printNewListInit формирует строку для нового инициализатора списка и использует FixItHint::CreateReplacement для создания замены.
  • Наконец, мы выдаем предупреждение и предлагаем исправление.

Заключение

Таким образом, мы создаем clang-tidy проверку, которая находит и преобразует поля, инициализируемые копированием, в инициализацию списком. Это решение использует возможности AST, чтобы избежать неаккуратных строковых манипуляций и гарантировать, что генерация кода аккуратная и отражает намерения программиста.

Оцените материал
Добавить комментарий

Капча загружается...