[Mlir-commits] [mlir] [MLIR][ODS] Add enum operation format directive (PR #220412)

Mehdi Amini llvmlistbot at llvm.org
Tue Sep 1 15:51:47 PDT 2026


https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/220412

>From 28963df93841d9219a9d63e1be6bd117637a7f93 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Tue, 1 Sep 2026 10:12:27 -0700
Subject: [PATCH] [MLIR][TableGen] Add enum operation format directive

Add enum($attr) to declarative operation assembly formats. The directive
parses and prints the symbolic enum value independently of the attribute's
custom assembly format. This lets standalone attributes retain delimiters
while operations use a bare value.

Validate directive operands during TableGen processing and retain keyword
and quoted-string handling. Add separator-aware parsing and unquoted
printing for comma- and vertical-bar-separated bit enums.

Document the directive and add generated-code, diagnostic, compatibility,
and runtime round-trip coverage.

Assisted-by: Codex
---
 mlir/docs/DefiningDialects/Operations.md   |   9 ++
 mlir/test/IR/enum-attr-roundtrip.mlir      |  18 +++
 mlir/test/lib/Dialect/Test/TestEnumDefs.td |  10 ++
 mlir/test/lib/Dialect/Test/TestOps.td      |  23 ++++
 mlir/test/mlir-tblgen/op-format-invalid.td |  25 ++++
 mlir/test/mlir-tblgen/op-format.td         |  54 +++++++++
 mlir/tools/mlir-tblgen/FormatGen.cpp       |  19 +--
 mlir/tools/mlir-tblgen/FormatGen.h         |   1 +
 mlir/tools/mlir-tblgen/OpFormatGen.cpp     | 133 +++++++++++++++++++--
 9 files changed, 277 insertions(+), 15 deletions(-)

diff --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md
index f8a8d5fe6f0f2..de32b2aa388db 100644
--- a/mlir/docs/DefiningDialects/Operations.md
+++ b/mlir/docs/DefiningDialects/Operations.md
@@ -846,6 +846,15 @@ The available directives are as follows:
         declarative assembly format will print it instead as:
         `vector.multi_reduction #vector.kind<minf>, ...`.
 
+*   `enum ( attribute )`
+
+    -   Represents the symbolic value of an enum-backed attribute without the
+        attribute's dialect prefix, mnemonic, or custom assembly format.
+    -   The argument must be an enum attribute. For example, if `$kind` has the
+        complete form `#vector.kind<minf>`, `enum($kind)` prints `minf`, `$kind`
+        prints the attribute's assembly-format body `<minf>`, and
+        `qualified($kind)` prints the complete attribute `#vector.kind<minf>`.
+
 #### Literals
 
 A literal is either a keyword or punctuation surrounded by \`\`.
diff --git a/mlir/test/IR/enum-attr-roundtrip.mlir b/mlir/test/IR/enum-attr-roundtrip.mlir
index 113b5f07ee6f3..ddb84daa8e7cc 100644
--- a/mlir/test/IR/enum-attr-roundtrip.mlir
+++ b/mlir/test/IR/enum-attr-roundtrip.mlir
@@ -18,6 +18,15 @@ func.func @test_op_with_enum() -> () {
   return
 }
 
+// CHECK-LABEL: @test_op_with_enum_directive
+func.func @test_op_with_enum_directive() -> () {
+  // CHECK: test.op_with_enum_directive third
+  test.op_with_enum_directive third
+  // CHECK: test.op_with_enum_directive "non-keyword"
+  test.op_with_enum_directive "non-keyword"
+  return
+}
+
 // CHECK-LABEL: @test_match_op_with_enum
 func.func @test_match_op_with_enum() -> () {
   // CHECK: test.op_with_enum third tag 0 : i32
@@ -36,6 +45,15 @@ func.func @test_match_op_with_bit_enum() -> () {
   return
 }
 
+// CHECK-LABEL: @test_op_with_bit_enum_directive
+func.func @test_op_with_bit_enum_directive() -> () {
+  // CHECK: test.op_with_bit_enum_directive read, execute
+  test.op_with_bit_enum_directive read, execute
+  // CHECK: test.op_with_bit_enum_vbar_directive user | other
+  test.op_with_bit_enum_vbar_directive user | other
+  return
+}
+
 // CHECK-LABEL: @test_enum_prop
 func.func @test_enum_prop() -> () {
   // CHECK: test.op_with_enum_prop first
diff --git a/mlir/test/lib/Dialect/Test/TestEnumDefs.td b/mlir/test/lib/Dialect/Test/TestEnumDefs.td
index 5d7c60d53c2c0..6479eaebf6243 100644
--- a/mlir/test/lib/Dialect/Test/TestEnumDefs.td
+++ b/mlir/test/lib/Dialect/Test/TestEnumDefs.td
@@ -56,6 +56,16 @@ def TestOtherEnum
   let cppNamespace = "test";
 }
 
+def TestPrettyEnum
+    : I32Enum<"TestPrettyEnum", "an enum with bracketed attribute syntax", [
+        I32EnumCase<"First", 0, "first">,
+        I32EnumCase<"Second", 1, "second">,
+        I32EnumCase<"Third", 2, "third">,
+        I32EnumCase<"NonKeyword", 3, "non-keyword">,
+      ]> {
+  let cppNamespace = "test";
+}
+
 def TestSimpleEnum : I32Enum<"SimpleEnum", "", [
     I32EnumCase<"a", 0>,
     I32EnumCase<"b", 1>,
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 29f11b8cc4ec7..ccdddbc3e3a0b 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -489,12 +489,24 @@ def PromisedInterfacesOp : TEST_Op<"promised_interfaces"> {
 // Define the enum attribute.
 def TestEnumAttr : EnumAttr<Test_Dialect, TestEnum, "enum">;
 
+def TestPrettyEnumAttr
+    : EnumAttr<Test_Dialect, TestPrettyEnum, "pretty_enum"> {
+  let assemblyFormat = "`<` $value `>`";
+}
+
 // Define an op that contains the enum attribute.
 def OpWithEnum : TEST_Op<"op_with_enum"> {
   let arguments = (ins TestEnumAttr:$value, OptionalAttr<AnyAttr>:$tag);
   let assemblyFormat = "$value (`tag` $tag^)? attr-dict";
 }
 
+// Define an op that prints just the symbolic value of an enum attribute whose
+// own assembly format includes angle brackets.
+def OpWithEnumDirective : TEST_Op<"op_with_enum_directive"> {
+  let arguments = (ins TestPrettyEnumAttr:$value);
+  let assemblyFormat = "enum($value) attr-dict";
+}
+
 // Define a pattern that matches and creates an enum attribute.
 def : Pat<(OpWithEnum ConstantEnumCase<TestEnumAttr, "first">:$value,
                       ConstantAttr<I32Attr, "0">:$tag),
@@ -594,6 +606,17 @@ def OpWithBitEnumVerticalBar : TEST_Op<"op_with_bit_enum_vbar"> {
   let assemblyFormat = "$value (`tag` $tag^)? attr-dict";
 }
 
+def OpWithBitEnumDirective : TEST_Op<"op_with_bit_enum_directive"> {
+  let arguments = (ins TestBitEnumAttr:$value);
+  let assemblyFormat = "enum($value) attr-dict";
+}
+
+def OpWithBitEnumVerticalBarDirective
+    : TEST_Op<"op_with_bit_enum_vbar_directive"> {
+  let arguments = (ins TestBitEnumVerticalBarAttr:$value);
+  let assemblyFormat = "enum($value) attr-dict";
+}
+
 // Define a pattern that matches and creates a bit enum attribute.
 def : Pat<(OpWithBitEnum ConstantEnumCase<TestBitEnumAttr, "write|execute">,
                          ConstantAttr<I32Attr, "0">),
diff --git a/mlir/test/mlir-tblgen/op-format-invalid.td b/mlir/test/mlir-tblgen/op-format-invalid.td
index 38c8d57a342b6..3e71b3c876d00 100644
--- a/mlir/test/mlir-tblgen/op-format-invalid.td
+++ b/mlir/test/mlir-tblgen/op-format-invalid.td
@@ -3,6 +3,7 @@
 // This file contains tests for the specification of the declarative op format.
 
 include "mlir/IR/OpBase.td"
+include "mlir/IR/EnumAttr.td"
 include "mlir/Interfaces/InferTypeOpInterface.td"
 
 def TestDialect : Dialect {
@@ -25,6 +26,30 @@ class TestStrictPropertiesFormat_Op<string fmt, list<Trait> traits = []>
 // Directives
 //===----------------------------------------------------------------------===//
 
+//===----------------------------------------------------------------------===//
+// enum
+//===----------------------------------------------------------------------===//
+
+def DirectiveEnumCase : I32EnumCase<"Case", 0>;
+def DirectiveEnum : I32Enum<"DirectiveEnum", "test enum",
+                            [DirectiveEnumCase]>;
+def DirectiveEnumAttr : EnumAttr<TestDialect, DirectiveEnum, "enum">;
+
+// CHECK: error: 'enum' directive expects an enum attribute
+def DirectiveAEnumInvalidA : TestFormat_Op<[{
+  enum($attr) attr-dict
+}]>, Arguments<(ins I64Attr:$attr)>;
+
+// CHECK: error: 'enum' directive expects an enum attribute
+def DirectiveAEnumInvalidB : TestFormat_Op<[{
+  enum($operand) attr-dict
+}]>, Arguments<(ins I64:$operand)>;
+
+// CHECK: error: 'enum' and 'qualified' directives cannot be combined
+def DirectiveAEnumInvalidC : TestFormat_Op<[{
+  qualified(enum($attr)) attr-dict
+}]>, Arguments<(ins DirectiveEnumAttr:$attr)>;
+
 //===----------------------------------------------------------------------===//
 // attr-dict
 //===----------------------------------------------------------------------===//
diff --git a/mlir/test/mlir-tblgen/op-format.td b/mlir/test/mlir-tblgen/op-format.td
index 530005ca4973f..eb75f0662e8b8 100644
--- a/mlir/test/mlir-tblgen/op-format.td
+++ b/mlir/test/mlir-tblgen/op-format.td
@@ -109,6 +109,11 @@ def CustomStringLiteralD : TestFormat_Op<[{
   custom<Foo>(prop-dict) attr-dict
 }]>;
 
+// A directive keyword can still name a custom directive.
+def CustomEnumKeyword : TestFormat_Op<[{
+  custom<enum>($attr) attr-dict
+}]>, Arguments<(ins I64Attr:$attr)>;
+
 //===----------------------------------------------------------------------===//
 // EnumAttr formatting
 //===----------------------------------------------------------------------===//
@@ -125,6 +130,21 @@ def TestEnum : I32Enum<"TestEnum", "a test enum", [TestEnumCase0, TestEnumCase1]
 
 def TestEnumAttr : EnumAttr<TestDialect, TestEnum, "enum">;
 
+def TestPrettyEnumAttr : EnumAttr<TestDialect, TestEnum, "pretty_enum"> {
+  let assemblyFormat = "`<` $value `>`";
+}
+
+def TestNonKeywordEnumCase : I32EnumCase<"NonKeyword", 0, "non-keyword">;
+def TestNonKeywordEnum
+    : I32Enum<"TestNonKeywordEnum", "a non-keyword enum",
+              [TestNonKeywordEnumCase]> {
+  let cppNamespace = "::test";
+}
+def TestNonKeywordEnumAttr
+    : EnumAttr<TestDialect, TestNonKeywordEnum, "non_keyword_enum"> {
+  let assemblyFormat = "`<` $value `>`";
+}
+
 // Default-valued optional attributes have a non-optional getter.
 
 // CHECK-LABEL: DefaultOptionalEnumAttrOp::print
@@ -164,6 +184,40 @@ def TestBitEnumAttr : EnumAttr<TestDialect, TestBitEnum, "bit_enum">;
 def EnumAttrUnquotedBitOp : TestFormat_Op<"$attr attr-dict">,
     Arguments<(ins TestBitEnumAttr:$attr)>;
 
+// Non-keyword enum values use the quoted-string fallback.
+
+// CHECK-LABEL: EnumDirectiveNonKeywordOp::parse
+// CHECK: parser.parseOptionalKeyword(&attrStr, {})
+// CHECK: parser.parseOptionalAttribute
+// CHECK-LABEL: EnumDirectiveNonKeywordOp::print
+// CHECK: _odsPrinter << '"' << caseValueStr << '"'
+def EnumDirectiveNonKeywordOp : TestFormat_Op<"enum($attr) attr-dict">,
+    Arguments<(ins TestNonKeywordEnumAttr:$attr)>;
+
+// The enum directive formats the symbolic enum value independently of the
+// attribute's custom assembly format.
+
+// CHECK-LABEL: EnumDirectiveOp::parse
+// CHECK: symbolizeTestEnum
+// CHECK-NOT: parseCustomAttributeWithFallback
+// CHECK-LABEL: EnumDirectiveOp::print
+// CHECK: stringifyTestEnum
+// CHECK-NOT: printStrippedAttrOrType
+def EnumDirectiveOp : TestFormat_Op<"enum($attr) attr-dict">,
+    Arguments<(ins TestPrettyEnumAttr:$attr)>;
+
+// The enum directive preserves the separator-aware parser and unquoted printer
+// for an unquoted bit enum.
+
+// CHECK-LABEL: EnumDirectiveUnquotedBitOp::parse
+// CHECK: symbolizeTestBitEnum
+// CHECK: parseOptionalVerticalBar
+// CHECK-LABEL: EnumDirectiveUnquotedBitOp::print
+// CHECK: stringifyTestBitEnum
+// CHECK: _odsPrinter << caseValueStr
+def EnumDirectiveUnquotedBitOp : TestFormat_Op<"enum($attr) attr-dict">,
+    Arguments<(ins TestBitEnumAttr:$attr)>;
+
 // Test that legacy EnumAttrInfo-based attributes (I32EnumAttr) also use the
 // enum-keyword format path.
 
diff --git a/mlir/tools/mlir-tblgen/FormatGen.cpp b/mlir/tools/mlir-tblgen/FormatGen.cpp
index 04d3ed1f3b70d..31843fabd5f4d 100644
--- a/mlir/tools/mlir-tblgen/FormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/FormatGen.cpp
@@ -180,6 +180,7 @@ FormatToken FormatLexer::lexIdentifier(const char *tokStart) {
           .Case("attr-dict-with-keyword", FormatToken::kw_attr_dict_w_keyword)
           .Case("prop-dict", FormatToken::kw_prop_dict)
           .Case("custom", FormatToken::kw_custom)
+          .Case("enum", FormatToken::kw_enum)
           .Case("functional-type", FormatToken::kw_functional_type)
           .Case("oilist", FormatToken::kw_oilist)
           .Case("operands", FormatToken::kw_operands)
@@ -405,13 +406,17 @@ FailureOr<FormatElement *> FormatParser::parseCustomDirective(SMLoc loc,
                           "or within a `struct` directive");
   }
 
-  FailureOr<FormatToken> nameTok;
   if (failed(parseToken(FormatToken::less,
-                        "expected '<' before custom directive name")) ||
-      failed(nameTok =
-                 parseToken(FormatToken::identifier,
-                            "expected custom directive name identifier")) ||
-      failed(parseToken(FormatToken::greater,
+                        "expected '<' before custom directive name")))
+    return failure();
+  // Preserve `custom<enum>` now that `enum` is also a directive keyword.
+  if (!curToken.is(FormatToken::identifier) &&
+      !curToken.is(FormatToken::kw_enum))
+    return emitError(curToken.getLoc(),
+                     "expected custom directive name identifier");
+  FormatToken nameTok = curToken;
+  consumeToken();
+  if (failed(parseToken(FormatToken::greater,
                         "expected '>' after custom directive name")) ||
       failed(parseToken(FormatToken::l_paren,
                         "expected '(' before custom directive parameters")))
@@ -435,7 +440,7 @@ FailureOr<FormatElement *> FormatParser::parseCustomDirective(SMLoc loc,
 
   if (failed(verifyCustomDirectiveArguments(loc, arguments)))
     return failure();
-  return create<CustomDirective>(nameTok->getSpelling(), std::move(arguments));
+  return create<CustomDirective>(nameTok.getSpelling(), std::move(arguments));
 }
 
 FailureOr<FormatElement *> FormatParser::parseRefDirective(SMLoc loc,
diff --git a/mlir/tools/mlir-tblgen/FormatGen.h b/mlir/tools/mlir-tblgen/FormatGen.h
index 8e7d49bb37e71..b9600b9761992 100644
--- a/mlir/tools/mlir-tblgen/FormatGen.h
+++ b/mlir/tools/mlir-tblgen/FormatGen.h
@@ -61,6 +61,7 @@ class FormatToken {
     kw_attr_dict_w_keyword,
     kw_prop_dict,
     kw_custom,
+    kw_enum,
     kw_functional_type,
     kw_oilist,
     kw_operands,
diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index 9c849955ac5fd..8e86f64e51f9b 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -77,8 +77,13 @@ struct AttributeVariable
     shouldBeQualifiedFlag = qualified;
   }
 
+  /// Indicate if this attribute is formatted using its underlying enum value.
+  bool shouldFormatAsEnum() const { return shouldFormatAsEnumFlag; }
+  void setShouldFormatAsEnum() { shouldFormatAsEnumFlag = true; }
+
 private:
   bool shouldBeQualifiedFlag = false;
+  bool shouldFormatAsEnumFlag = false;
 };
 
 /// This class represents a variable that refers to an operand argument.
@@ -587,6 +592,44 @@ const char *const enumAttrParserCode = R"(
   }
 )";
 
+/// The code snippet used to generate a parser call for an unquoted bit enum
+/// attribute.
+///
+/// {0}: The name of the attribute.
+/// {1}: The C++ namespace for the enum symbolize functions.
+/// {2}: The function to symbolize a string of the enum.
+/// {3}: The constant builder call to create an attribute of the enum type.
+/// {4}: The set of allowed enum keywords.
+/// {5}: The error message on failure when the enum isn't present.
+/// {6}: The attribute assignment expression.
+/// {7}: The C++ enum type.
+/// {8}: The optional parser for the enum separator.
+const char *const unquotedBitEnumAttrParserCode = R"(
+  {
+    ::llvm::StringRef attrStr;
+    auto loc = parser.getCurrentLocation();
+    if (parser.parseOptionalKeyword(&attrStr, {4})) {
+      {5}
+    } else {
+      {7} flags = {{};
+      while (true) {
+        auto flag = {1}::{2}(attrStr);
+        if (!flag)
+          return parser.emitError(loc, "invalid ")
+                 << "{0} attribute specification: \"" << attrStr << '"';;
+        flags = flags | *flag;
+        if (failed(parser.{8}()))
+          break;
+        if (parser.parseKeyword(&attrStr))
+          return ::mlir::failure();
+      }
+      auto attrOptional = ::std::optional<{7}>(flags);
+      {0}Attr = {3};
+      {6}
+    }
+  }
+)";
+
 /// The code snippet used to generate a parser call for a property.
 /// {0}: The name of the property
 /// {1}: The property access expression
@@ -1230,12 +1273,18 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body,
   }
 
   // Build a string containing the cases that can be formatted as a keyword.
-  std::string validCaseKeywordsStr = "{";
-  llvm::raw_string_ostream validCaseKeywordsOS(validCaseKeywordsStr);
-  for (const EnumCase &attrCase : cases)
+  SmallVector<StringRef> validCaseKeywords;
+  for (const EnumCase &attrCase : cases) {
     if (canFormatStringAsKeyword(attrCase.getStr()))
-      validCaseKeywordsOS << '"' << attrCase.getStr() << "\",";
-  validCaseKeywordsOS.str().back() = '}';
+      validCaseKeywords.push_back(attrCase.getStr());
+  }
+  std::string validCaseKeywordsStr;
+  llvm::raw_string_ostream validCaseKeywordsOS(validCaseKeywordsStr);
+  validCaseKeywordsOS << '{';
+  llvm::interleaveComma(
+      validCaseKeywords, validCaseKeywordsOS,
+      [&](StringRef keyword) { validCaseKeywordsOS << '"' << keyword << '"'; });
+  validCaseKeywordsOS << '}';
 
   // If the attribute is not optional, build an error message for the missing
   // attribute.
@@ -1260,6 +1309,22 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body,
         formatv("result.addAttribute(\"{0}\", {0}Attr);", var->name);
   }
 
+  if (enumInfo.isBitEnum() && !enumInfo.printBitEnumQuoted()) {
+    StringRef separator = enumInfo.getDef().getValueAsString("separator");
+    StringRef parseSeparatorFn = llvm::StringSwitch<StringRef>(separator.trim())
+                                     .Case("|", "parseOptionalVerticalBar")
+                                     .Case(",", "parseOptionalComma")
+                                     .Default("invalid bit enum separator");
+    std::string enumType =
+        (enumInfo.getCppNamespace() + "::" + enumInfo.getEnumClassName()).str();
+    body << formatv(unquotedBitEnumAttrParserCode, var->name,
+                    enumInfo.getCppNamespace(),
+                    enumInfo.getStringToSymbolFnName(), attrBuilderStr,
+                    validCaseKeywordsStr, errorMessage, attrAssignment,
+                    enumType, parseSeparatorFn);
+    return;
+  }
+
   body << formatv(enumAttrParserCode, var->name, enumInfo.getCppNamespace(),
                   enumInfo.getStringToSymbolFnName(), attrBuilderStr,
                   validCaseKeywordsStr, errorMessage, attrAssignment);
@@ -1296,7 +1361,7 @@ static void genAttrParser(AttributeVariable *attr, MethodBody &body,
   const NamedAttribute *var = attr->getVar();
 
   // Check to see if we can parse this as an enum attribute.
-  if (canFormatEnumAttr(var))
+  if (attr->shouldFormatAsEnum() || canFormatEnumAttr(var))
     return genEnumAttrParser(var, body, attrTypeCtx, parseAsOptional,
                              useProperties, opCppClassName);
 
@@ -2852,6 +2917,12 @@ static void genEnumAttrPrinter(const NamedAttribute *var, const Operator &op,
   body << formatv(enumAttrBeginPrinterCode, caseValue,
                   enumInfo.getSymbolToStringFnName());
 
+  if (enumInfo.isBitEnum() && !enumInfo.printBitEnumQuoted()) {
+    body << "    _odsPrinter << caseValueStr;\n"
+            "  }\n";
+    return;
+  }
+
   // Get a string containing all of the cases that can't be represented with a
   // keyword.
   BitVector nonKeywordCases(cases.size());
@@ -3130,7 +3201,7 @@ void OperationFormat::genElementPrinter(FormatElement *element,
     const NamedAttribute *var = attr->getVar();
 
     // If we are formatting as an enum, symbolize the attribute as a string.
-    if (canFormatEnumAttr(var))
+    if (attr->shouldFormatAsEnum() || canFormatEnumAttr(var))
       return genEnumAttrPrinter(var, op, body);
 
     // If we are formatting as a symbol name, handle it as a symbol name.
@@ -3375,6 +3446,7 @@ class OpFormatParser : public FormatParser {
   FailureOr<FormatElement *> parsePropDictDirective(SMLoc loc, Context context);
   FailureOr<FormatElement *> parseAttrDictDirective(SMLoc loc, Context context,
                                                     bool withKeyword);
+  FailureOr<FormatElement *> parseEnumDirective(SMLoc loc, Context context);
   FailureOr<FormatElement *> parseFunctionalTypeDirective(SMLoc loc,
                                                           Context context);
   FailureOr<FormatElement *> parseOIListDirective(SMLoc loc, Context context);
@@ -4057,6 +4129,8 @@ OpFormatParser::parseDirectiveImpl(SMLoc loc, FormatToken::Kind kind,
   case FormatToken::kw_attr_dict_w_keyword:
     return parseAttrDictDirective(loc, ctx,
                                   /*withKeyword=*/true);
+  case FormatToken::kw_enum:
+    return parseEnumDirective(loc, ctx);
   case FormatToken::kw_functional_type:
     return parseFunctionalTypeDirective(loc, ctx);
   case FormatToken::kw_operands:
@@ -4077,6 +4151,42 @@ OpFormatParser::parseDirectiveImpl(SMLoc loc, FormatToken::Kind kind,
   }
 }
 
+FailureOr<FormatElement *> OpFormatParser::parseEnumDirective(SMLoc loc,
+                                                              Context context) {
+  if (context != TopLevelContext)
+    return emitError(loc, "'enum' directive can only be used at the top level");
+
+  FailureOr<FormatElement *> element;
+  if (failed(parseToken(FormatToken::l_paren,
+                        "expected '(' before argument list")) ||
+      failed(element = parseElement(context)) ||
+      failed(
+          parseToken(FormatToken::r_paren, "expected ')' after argument list")))
+    return failure();
+
+  auto *attr = dyn_cast<AttributeVariable>(*element);
+  if (!attr)
+    return emitError(loc, "'enum' directive expects an enum attribute");
+
+  Attribute baseAttr = attr->getVar()->attr.getBaseAttr();
+  if (!baseAttr.isEnumAttr())
+    return emitError(loc, "'enum' directive expects an enum attribute");
+
+  EnumInfo enumInfo(getEnumInfoRecord(baseAttr));
+  if (enumInfo.getUnderlyingType().empty() ||
+      baseAttr.getConstBuilderTemplate().empty()) {
+    return emitError(
+        loc, "'enum' directive requires an enum attribute with an underlying "
+             "type and a constant builder");
+  }
+
+  if (attr->shouldBeQualified())
+    return emitError(loc,
+                     "'enum' and 'qualified' directives cannot be combined");
+  attr->setShouldFormatAsEnum();
+  return attr;
+}
+
 FailureOr<FormatElement *>
 OpFormatParser::parseAttrDictDirective(SMLoc loc, Context context,
                                        bool withKeyword) {
@@ -4338,7 +4448,14 @@ FailureOr<FormatElement *> OpFormatParser::parseTypeDirective(SMLoc loc,
 
 LogicalResult OpFormatParser::markQualified(SMLoc loc, FormatElement *element) {
   return TypeSwitch<FormatElement *, LogicalResult>(element)
-      .Case<AttributeVariable, TypeDirective>([](auto *element) {
+      .Case([&](AttributeVariable *element) {
+        if (element->shouldFormatAsEnum())
+          return emitError(
+              loc, "'enum' and 'qualified' directives cannot be combined");
+        element->setShouldBeQualified();
+        return success();
+      })
+      .Case<TypeDirective>([](auto *element) {
         element->setShouldBeQualified();
         return success();
       })



More information about the Mlir-commits mailing list