Skip to content

[clang-tidy] Add MLIR check for old op builder usage. #149148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_clang_library(clangTidyLLVMModule STATIC
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
MLIROpBuilderCheck.cpp
PreferIsaOrDynCastInConditionalsCheck.cpp
PreferRegisterOverUnsignedCheck.cpp
PreferStaticOverAnonymousNamespaceCheck.cpp
Expand All @@ -16,6 +17,7 @@ add_clang_library(clangTidyLLVMModule STATIC
clangTidy
clangTidyReadabilityModule
clangTidyUtils
clangTransformer

DEPENDS
omp_gen
Expand Down
2 changes: 2 additions & 0 deletions clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "../readability/QualifiedAutoCheck.h"
#include "HeaderGuardCheck.h"
#include "IncludeOrderCheck.h"
#include "MLIROpBuilderCheck.h"
#include "PreferIsaOrDynCastInConditionalsCheck.h"
#include "PreferRegisterOverUnsignedCheck.h"
#include "PreferStaticOverAnonymousNamespaceCheck.h"
Expand All @@ -40,6 +41,7 @@ class LLVMModule : public ClangTidyModule {
CheckFactories.registerCheck<readability::QualifiedAutoCheck>(
"llvm-qualified-auto");
CheckFactories.registerCheck<TwineLocalCheck>("llvm-twine-local");
CheckFactories.registerCheck<MlirOpBuilderCheck>("llvm-mlir-op-builder");
}

ClangTidyOptions getModuleOptions() override {
Expand Down
103 changes: 103 additions & 0 deletions clang-tools-extra/clang-tidy/llvm/MLIROpBuilderCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
//===--- MLIROpBuilderCheck.cpp - clang-tidy ------------------------------===//
//
// 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 "MLIROpBuilderCheck.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/LLVM.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/Transformer/RangeSelector.h"
#include "clang/Tooling/Transformer/RewriteRule.h"
#include "clang/Tooling/Transformer/SourceCode.h"
#include "clang/Tooling/Transformer/Stencil.h"
#include "llvm/Support/Error.h"

namespace clang::tidy::llvm_check {
namespace {

using namespace ::clang::ast_matchers; // NOLINT: Too many names.
using namespace ::clang::transformer; // NOLINT: Too many names.

class TypeAsWrittenStencil : public StencilInterface {
public:
explicit TypeAsWrittenStencil(std::string S) : id(std::move(S)) {}
std::string toString() const override {
return (llvm::Twine("TypeAsWritten(\"") + id + "\")").str();
}

llvm::Error eval(const MatchFinder::MatchResult &match,
std::string *result) const override {
llvm::Expected<CharSourceRange> n = node(id)(match);
if (!n)
return n.takeError();
SourceRange srcRange = n->getAsRange();
if (srcRange.isInvalid()) {
return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument,
"srcRange is invalid");
}
const CharSourceRange range = n->getTokenRange(srcRange);
auto nextToken = [&](std::optional<Token> token) {
if (!token)
return token;
return clang::Lexer::findNextToken(token->getLocation(),
*match.SourceManager,
match.Context->getLangOpts());
};
std::optional<Token> lessToken = clang::Lexer::findNextToken(
range.getBegin(), *match.SourceManager, match.Context->getLangOpts());
while (lessToken && lessToken->getKind() != clang::tok::less) {
lessToken = nextToken(lessToken);
}
if (!lessToken) {
return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument,
"missing '<' token");
}
std::optional<Token> endToken = nextToken(lessToken);
for (std::optional<Token> greaterToken = nextToken(endToken);
greaterToken && greaterToken->getKind() != clang::tok::greater;
greaterToken = nextToken(greaterToken)) {
endToken = greaterToken;
}
if (!endToken) {
return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument,
"missing '>' token");
}
*result += clang::tooling::getText(
CharSourceRange::getTokenRange(lessToken->getEndLoc(),
endToken->getLastLoc()),
*match.Context);
return llvm::Error::success();
}
std::string id;
};

Stencil typeAsWritten(StringRef Id) {
// Using this instead of `describe` so that we get the exact same spelling.
return std::make_shared<TypeAsWrittenStencil>(std::string(Id));
}

RewriteRuleWith<std::string> MlirOpBuilderCheckRule() {
return makeRule(
cxxMemberCallExpr(
on(expr(hasType(
cxxRecordDecl(isSameOrDerivedFrom("::mlir::OpBuilder"))))
.bind("builder")),
callee(cxxMethodDecl(hasTemplateArgument(0, templateArgument()))),
callee(cxxMethodDecl(hasName("create"))))
.bind("call"),
changeTo(cat(typeAsWritten("call"), "::create(", node("builder"), ", ",
callArgs("call"), ")")),
cat("Use OpType::create(builder, ...) instead of "
"builder.create<OpType>(...)"));
}
} // namespace

MlirOpBuilderCheck::MlirOpBuilderCheck(StringRef Name,
ClangTidyContext *Context)
: TransformerClangTidyCheck(MlirOpBuilderCheckRule(), Name, Context) {}

} // namespace clang::tidy::llvm_check
29 changes: 29 additions & 0 deletions clang-tools-extra/clang-tidy/llvm/MLIROpBuilderCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===--- MLIROpBuilderCheck.h - clang-tidy ----------------------*- 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
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_MLIROPBUILDERCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_MLIROPBUILDERCHECK_H

#include "../utils/TransformerClangTidyCheck.h"

namespace clang::tidy::llvm_check {

/// Checks for uses of MLIR's `OpBuilder::create<T>` and suggests using
/// `T::create` instead.
class MlirOpBuilderCheck : public utils::TransformerClangTidyCheck {
public:
MlirOpBuilderCheck(StringRef Name, ClangTidyContext *Context);

bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return getLangOpts().CPlusPlus;
}
};

} // namespace clang::tidy::llvm_check

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_MLIROPBUILDERCHECK_H
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^

- New :doc:`llvm-mlir-op-builder
<clang-tidy/checks/llvm/mlir-op-builder>` check.

Flags usage of old form of invoking create on MLIR's ``OpBuilder`` and
suggests new form.

New check aliases
^^^^^^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ Clang-Tidy Checks
:doc:`linuxkernel-must-check-errs <linuxkernel/must-check-errs>`,
:doc:`llvm-header-guard <llvm/header-guard>`,
:doc:`llvm-include-order <llvm/include-order>`, "Yes"
:doc:`llvm-mlir-op-builder <llvm/mlir-op-builder>`, "Yes"
:doc:`llvm-namespace-comment <llvm/namespace-comment>`,
:doc:`llvm-prefer-isa-or-dyn-cast-in-conditionals <llvm/prefer-isa-or-dyn-cast-in-conditionals>`, "Yes"
:doc:`llvm-prefer-register-over-unsigned <llvm/prefer-register-over-unsigned>`, "Yes"
Expand Down
21 changes: 21 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/llvm/mlir-op-builder.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.. title:: clang-tidy - llvm-mlir-op-builder

llvm-mlir-op-builder
====================

Flags usage of old form of invoking create on MLIR's ``OpBuilder`` and suggests
new form.

Example
-------

.. code-block:: c++

builder.create<FooOp>(builder.getUnknownLoc(), "baz");


Transforms to:

.. code-block:: c++

FooOp::create(builder, builder.getUnknownLoc(), "baz");
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// RUN: %check_clang_tidy --match-partial-fixes %s llvm-mlir-op-builder %t

namespace mlir {
class Location {};
class OpBuilder {
public:
template <typename OpTy, typename... Args>
OpTy create(Location location, Args &&...args) {
return OpTy(args...);
}
Location getUnknownLoc() { return Location(); }
};
class ImplicitLocOpBuilder : public OpBuilder {
public:
template <typename OpTy, typename... Args>
OpTy create(Args &&...args) {
return OpTy(args...);
}
};
struct ModuleOp {
ModuleOp() {}
static ModuleOp create(OpBuilder &builder, Location location) {
return ModuleOp();
}
};
struct NamedOp {
NamedOp(const char* name) {}
static NamedOp create(OpBuilder &builder, Location location, const char* name) {
return NamedOp(name);
}
};
} // namespace mlir

void f() {
mlir::OpBuilder builder;
// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: Use OpType::create(builder, ...) instead of builder.create<OpType>(...) [llvm-mlir-op-builder]
// CHECK-FIXES: mlir:: ModuleOp::create(builder, builder.getUnknownLoc())
builder.create<mlir:: ModuleOp>(builder.getUnknownLoc());

using mlir::NamedOp;
// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: Use OpType::create(builder, ...) instead of builder.create<OpType>(...) [llvm-mlir-op-builder]
// CHECK-FIXES: NamedOp::create(builder, builder.getUnknownLoc(), "baz")
builder.create<NamedOp>(builder.getUnknownLoc(), "baz");

mlir::ImplicitLocOpBuilder ib;
// Note: extra space in the case where there is no other arguments. Could be
// improved, but also clang-format will do that just post.
// CHECK-MESSAGES: :[[@LINE+2]]:3: warning: Use OpType::create(builder, ...) instead of builder.create<OpType>(...) [llvm-mlir-op-builder]
// CHECK-FIXES: mlir::ModuleOp::create(ib )
ib.create<mlir::ModuleOp>();
}