如何在 clang-tidy 检查中将复制初始化转换为直接列表初始化?

问题描述 投票:0回答:1

我想编写一个

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 方法为我创建这样的代码替换吗?

c++ clang abstract-syntax-tree clang-tidy list-initialization
1个回答
0
投票

这个答案主要关注如何使用 API 进行一般性重构,而不是稳健重构字段初始化的具体任务,因为后者对于 SO 答案来说将是一项相当大的工作。

合成和漂亮打印 AST 节点

我可以使用 clang AST 方法为我创建这样的代码替换吗?

是的。 AST 节点类通常有一个静态

create
方法,该方法接受
ASTContext
,因为它是“拥有”并管理节点对象(分配、释放等)的上下文。 例如,要创建
FieldDecl
,请调用
FieldDecl::Create

然后,一旦创建,AST 节点就可以漂亮地打印为 C++ 代码:

  • 对于
    Decl
    及其子类,请致电
    print
  • 对于
    Stmt
    及其子类,请致电
    printPretty
  • 对于
    QualType
    ,请致电
    print
    。 如果您有
    Type
    ,请包裹起来 首先是
    QualType

但是,这种方法有几个问题:

  • 创建格式正确的 AST 的确切要求有时并不明确,而且通常没有文档记录。 鉴于您只会漂亮地打印它们(而不是生成 LLVM 汇编代码),让事情正常工作可能不会太困难,但需要一些尝试和错误。

  • 漂亮的打印会删除注释和空白,可能会造成严重破坏。

重新排列并插入到现有文本中

相反,重构工具更常见的是检查 AST 以找到您想要保留的节点,提取这些节点的原始源代码(您链接到的答案显示了如何做到这一点),然后包围使用新语法提取的文本,以便生成所需的最终代码。

例如,如果我们给出:

MyStruct m_data = MyStruct(1, 3);

并想把它变成:

MyStruct m_data{1, 3};

然后我们就可以提取文本元素了:

  • MyStruct
    作为类型说明符,
  • m_data
    作为字段名称,
  • 1
    作为原始构造函数调用的第一个参数(同时丢弃调用节点),并且
  • 3
    作为第二个参数。

然后,连接这些片段,在适当的位置插入大括号和逗号以获得所需的结果。

Rewriter
类可以存储特定文件所需的所有更改,以文本编辑的形式表示,然后一次写出所有更改。 在
clang-tidy
检查的上下文中,人们会创建一组包含所需更改的
FixItHint
对象。

这种方法也有一些缺点:

  • 插入文本时,没有自动机制来确保结果满足基本的格式良好条件(例如平衡分隔符),更不用说满足所有 C++ 语法规则了。

  • 根据变换的不同,令人满意地匹配周围的缩进样式可能具有挑战性。

即便如此,在实践中,基于文本的方法通常更容易使工作充分进行。

示例:
UnnecessaryValueParamCheck.cpp

展示其中一些想法的示例是

UnnecessaryValueParamCheck.cpp
clang-tidy
的一部分),其末尾有:

void UnnecessaryValueParamCheck::handleMoveFix(const ParmVarDecl &Param,
                                               const DeclRefExpr &CopyArgument,
                                               ASTContext &Context) {
  auto Diag = diag(CopyArgument.getBeginLoc(),
                   "parameter %0 is passed by value and only copied once; "
                   "consider moving it to avoid unnecessary copies")
              << &Param;
  // Do not propose fixes in macros since we cannot place them correctly.
  if (CopyArgument.getBeginLoc().isMacroID())
    return;
  const auto &SM = Context.getSourceManager();
  auto EndLoc = Lexer::getLocForEndOfToken(CopyArgument.getLocation(), 0, SM,
                                           Context.getLangOpts());
  Diag << FixItHint::CreateInsertion(CopyArgument.getBeginLoc(), "std::move(")
       << FixItHint::CreateInsertion(EndLoc, ")")
       << Inserter.createIncludeInsertion(
              SM.getFileID(CopyArgument.getBeginLoc()), "<utility>");
}

此函数只是在表达式周围插入

std::move(
)
,而不删除任何内容,但这里的要点是保留原始表达式文本,而不是从 AST 重新创建。

clang-tidy
来源还有许多其他示例,可以作为模仿的有用示例。 可以通过在
FixItHint
目录下搜索
clang-tidy
来找到它们。

© www.soinside.com 2019 - 2024. All rights reserved.