[Mlir-commits] [mlir] [MLIR][TableGen] Make optional enum parser not consume the token when it is not matched (PR #188008)
Robert Konicar
llvmlistbot at llvm.org
Mon Mar 23 03:18:13 PDT 2026
https://github.com/Jezurko updated https://github.com/llvm/llvm-project/pull/188008
>From 0e8c5d3663543374f83d2c0670f2d4d83da15160 Mon Sep 17 00:00:00 2001
From: Robert Konicar <rkonicar at mail.muni.cz>
Date: Mon, 23 Mar 2026 10:05:24 +0100
Subject: [PATCH] [MLIR][TableGen] Make optional enum parser not consume the
token when it is not matched
Previously the optional parser would consume the token even when it
failed to match a value of the enum and prevented parsers later in the op
syntax from having an attempt. This PR changes that so that the token is
consumed only when the parsing succeeds. This change is made to the
emitted `FieldParser<std::optional<T>>` for enums.
This, for example, allows having a simple list of `DefaultValuedEnumProps`
in the assembly format without needing decorations around them. This
mimics the behaviour that is emitted for `DefaultValuedAttribute` when
it is used with `EnumAttr`.
This PR also adds `parseOptionalString` variant with an allow-list argument
as `parseOptionalKeyword` has and adds `parseOptionalKeywordOrString`
allow-list variant which combines these two into a single utility
wrapper.
---
mlir/include/mlir/IR/OpImplementation.h | 12 ++++++
mlir/lib/AsmParser/AsmParserImpl.h | 33 ++++++++++++++++
mlir/test/IR/enum-attr-roundtrip.mlir | 44 +++++++++++++++++++++
mlir/test/lib/Dialect/Test/TestEnumDefs.td | 10 +++++
mlir/test/lib/Dialect/Test/TestOpsSyntax.td | 28 +++++++++++++
mlir/tools/mlir-tblgen/EnumsGen.cpp | 19 ++++++---
6 files changed, 141 insertions(+), 5 deletions(-)
diff --git a/mlir/include/mlir/IR/OpImplementation.h b/mlir/include/mlir/IR/OpImplementation.h
index 9ce92cc7e5d37..66c08e5e8176f 100644
--- a/mlir/include/mlir/IR/OpImplementation.h
+++ b/mlir/include/mlir/IR/OpImplementation.h
@@ -950,6 +950,12 @@ class AsmParser {
parseOptionalKeyword(StringRef *keyword,
ArrayRef<StringRef> allowedValues) = 0;
+ /// Parse a string, if present, and if one of the 'allowedValues',
+ /// into 'result'
+ virtual ParseResult
+ parseOptionalString(std::string *string,
+ ArrayRef<StringRef> allowedValues) = 0;
+
/// Parse a keyword or a quoted string.
ParseResult parseKeywordOrString(std::string *result) {
if (failed(parseOptionalKeywordOrString(result)))
@@ -961,6 +967,12 @@ class AsmParser {
/// Parse an optional keyword or string.
virtual ParseResult parseOptionalKeywordOrString(std::string *result) = 0;
+ /// Parse an optional keyword or string, if present, and if one of the
+ /// 'allowedValues', into 'result'
+ virtual ParseResult
+ parseOptionalKeywordOrString(std::string *result,
+ ArrayRef<StringRef> allowedValues) = 0;
+
//===--------------------------------------------------------------------===//
// Attribute/Type Parsing
//===--------------------------------------------------------------------===//
diff --git a/mlir/lib/AsmParser/AsmParserImpl.h b/mlir/lib/AsmParser/AsmParserImpl.h
index eec2702cba343..3c60287a09a0f 100644
--- a/mlir/lib/AsmParser/AsmParserImpl.h
+++ b/mlir/lib/AsmParser/AsmParserImpl.h
@@ -383,11 +383,44 @@ class AsmParserImpl : public BaseT {
return failure();
}
+ /// Parse a string if it is one of the 'allowedKeywords'.
+ ParseResult
+ parseOptionalString(std::string *result,
+ ArrayRef<StringRef> allowedKeywords) override {
+ // Check that the current token is a keyword.
+ if (!parser.getToken().is(Token::string))
+ return failure();
+
+ std::string string{};
+ string = parser.getToken().getStringValue();
+
+ if (llvm::is_contained(allowedKeywords, string)) {
+ parser.consumeToken();
+ if (result)
+ *result = std::move(string);
+ return success();
+ }
+
+ return failure();
+ }
+
/// Parse an optional keyword or string and set instance into 'result'.`
ParseResult parseOptionalKeywordOrString(std::string *result) override {
return parser.parseOptionalKeywordOrString(result);
}
+ ParseResult
+ parseOptionalKeywordOrString(std::string *result,
+ ArrayRef<StringRef> allowedValues) override {
+ StringRef keyword;
+ if (succeeded(parseOptionalKeyword(&keyword, allowedValues))) {
+ *result = keyword.str();
+ return success();
+ }
+
+ return parseOptionalString(result, allowedValues);
+ }
+
//===--------------------------------------------------------------------===//
// Attribute Parsing
//===--------------------------------------------------------------------===//
diff --git a/mlir/test/IR/enum-attr-roundtrip.mlir b/mlir/test/IR/enum-attr-roundtrip.mlir
index f1f09f977b7d9..d02776e226c80 100644
--- a/mlir/test/IR/enum-attr-roundtrip.mlir
+++ b/mlir/test/IR/enum-attr-roundtrip.mlir
@@ -57,6 +57,50 @@ func.func @test_enum_prop() -> () {
return
}
+// CHECK-LABEL: @test_two_default_enum_props
+func.func @test_two_default_enum_props() -> () {
+ // Both default: both elided.
+ // CHECK: test.op_with_two_default_enum_props{{$}}
+ test.op_with_two_default_enum_props
+
+ // value1 non-default, value2 default (dog): only value1 printed.
+ // CHECK: test.op_with_two_default_enum_props second{{$}}
+ test.op_with_two_default_enum_props second
+
+ // value1 default (first), value2 non-default: only value2 printed.
+ // Key test: value1 parser must not consume "cat" (not a TestEnum keyword).
+ // CHECK: test.op_with_two_default_enum_props cat{{$}}
+ test.op_with_two_default_enum_props cat
+
+ // Both non-default.
+ // CHECK: test.op_with_two_default_enum_props second cat{{$}}
+ test.op_with_two_default_enum_props second cat
+
+ return
+}
+
+// CHECK-LABEL: @test_two_default_bit_enum_props
+func.func @test_two_default_bit_enum_props() -> () {
+ // Both default: both elided.
+ // CHECK: test.op_with_two_default_bit_enum_props{{$}}
+ test.op_with_two_default_bit_enum_props
+
+ // value1 non-default (write), value2 default (user): only value1 printed.
+ // CHECK: test.op_with_two_default_bit_enum_props write{{$}}
+ test.op_with_two_default_bit_enum_props write
+
+ // value1 default (read), value2 non-default: only value2 printed.
+ // Key test: value1 parser must not consume "group" (not a TestBitEnum keyword).
+ // CHECK: test.op_with_two_default_bit_enum_props group{{$}}
+ test.op_with_two_default_bit_enum_props group
+
+ // Both non-default.
+ // CHECK: test.op_with_two_default_bit_enum_props write group{{$}}
+ test.op_with_two_default_bit_enum_props write group
+
+ return
+}
+
// CHECK-LABEL @test_bit_enum_prop()
func.func @test_bit_enum_prop() -> () {
// CHECK: test.op_with_bit_enum_prop read : ()
diff --git a/mlir/test/lib/Dialect/Test/TestEnumDefs.td b/mlir/test/lib/Dialect/Test/TestEnumDefs.td
index 70cf94e3458df..e745d345403cc 100644
--- a/mlir/test/lib/Dialect/Test/TestEnumDefs.td
+++ b/mlir/test/lib/Dialect/Test/TestEnumDefs.td
@@ -47,6 +47,16 @@ def TestEnum
let cppNamespace = "test";
}
+def TestOtherEnum
+ : I32EnumAttr<"TestOtherEnum", "another test enum", [
+ I32EnumAttrCase<"Dog", 0, "dog">,
+ I32EnumAttrCase<"Cat", 1, "cat">,
+ I32EnumAttrCase<"Fish", 2, "fish">,
+ ]> {
+ let genSpecializedAttr = 0;
+ let cppNamespace = "test";
+}
+
def TestSimpleEnum : I32Enum<"SimpleEnum", "", [
I32EnumCase<"a", 0>,
I32EnumCase<"b", 1>,
diff --git a/mlir/test/lib/Dialect/Test/TestOpsSyntax.td b/mlir/test/lib/Dialect/Test/TestOpsSyntax.td
index 6b10ec6173a50..35a34b493035a 100644
--- a/mlir/test/lib/Dialect/Test/TestOpsSyntax.td
+++ b/mlir/test/lib/Dialect/Test/TestOpsSyntax.td
@@ -437,6 +437,34 @@ def FormatOptionalDefaultEnumAttrs : TEST_Op<"format_optional_default_enum_attr"
let assemblyFormat = "($e^)? attr-dict";
}
+// Two adjacent optional enum props with different enum types, to exercise
+// constrained keyword parsing (parseOptionalKeyword with keyword list).
+def TestEnumPropDefault : EnumProp<TestEnum> {
+ let defaultValue = TestEnum.cppType # "::First";
+}
+def TestOtherEnumPropDefault : EnumProp<TestOtherEnum> {
+ let defaultValue = TestOtherEnum.cppType # "::Dog";
+}
+def OpWithTwoDefaultEnumProps : TEST_Op<"op_with_two_default_enum_props"> {
+ let arguments = (ins
+ TestEnumPropDefault:$value1,
+ TestOtherEnumPropDefault:$value2);
+ let assemblyFormat = "($value1^)? ($value2^)? attr-dict";
+}
+
+def TestBitEnumPropDefault : EnumProp<TestBitEnum> {
+ let defaultValue = TestBitEnum.cppType # "::Read";
+}
+def TestBitEnumVerticalBarPropDefault : EnumProp<TestBitEnumVerticalBar> {
+ let defaultValue = TestBitEnumVerticalBar.cppType # "::User";
+}
+def OpWithTwoDefaultBitEnumProps : TEST_Op<"op_with_two_default_bit_enum_props"> {
+ let arguments = (ins
+ TestBitEnumPropDefault:$value1,
+ TestBitEnumVerticalBarPropDefault:$value2);
+ let assemblyFormat = "($value1^)? ($value2^)? attr-dict";
+}
+
def FormatOptionalWithElse : TEST_Op<"format_optional_else"> {
let arguments = (ins UnitAttr:$isFirstBranchPresent);
let assemblyFormat = "(`then` $isFirstBranchPresent^):(`else`)? attr-dict";
diff --git a/mlir/tools/mlir-tblgen/EnumsGen.cpp b/mlir/tools/mlir-tblgen/EnumsGen.cpp
index 8c7f9f7b4bc49..abf3fd9505f25 100644
--- a/mlir/tools/mlir-tblgen/EnumsGen.cpp
+++ b/mlir/tools/mlir-tblgen/EnumsGen.cpp
@@ -89,6 +89,15 @@ static void emitParserPrinter(const EnumInfo &enumInfo, StringRef qualName,
caseListOs << name;
});
caseListOs << "]";
+ std::string casesInitList;
+ llvm::raw_string_ostream casesInitListOs(casesInitList);
+ casesInitListOs << "{";
+ llvm::interleaveComma(llvm::enumerate(cases), casesInitListOs,
+ [&](auto enumerant) {
+ StringRef name = enumerant.value().getStr();
+ casesInitListOs << "\"" << name << "\"";
+ });
+ casesInitListOs << "}";
// Generate the parser and the start of the printer for the enum, excluding
// non-quoted bit enums.
@@ -125,7 +134,7 @@ struct FieldParser<std::optional<{0}>, std::optional<{0}>> {{
// Parse the keyword/string containing the enum.
std::string enumKeyword;
auto loc = parser.getCurrentLocation();
- if (failed(parser.parseOptionalKeywordOrString(&enumKeyword)))
+ if (failed(parser.parseOptionalKeywordOrString(&enumKeyword, {4})))
return std::optional<{0}>{{};
// Symbolize the keyword.
@@ -184,7 +193,7 @@ inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p, {0} value) {{
// Parse the keyword containing a part of the enum.
::llvm::StringRef enumKeyword;
auto loc = parser.getCurrentLocation();
- if (failed(parser.parseOptionalKeyword(&enumKeyword))) {{
+ if (failed(parser.parseOptionalKeyword(&enumKeyword, {6}))) {{
if (firstIter)
return std::optional<{0}>{{};
return parser.emitError(loc, "expected keyword for {2} after '{4}'");
@@ -223,11 +232,11 @@ inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p, {0} value) {{
.Case(",", "parseOptionalComma")
.Default("error, enum separator must be '|' or ','");
os << formatv(parsedAndPrinterStartUnquotedBitEnum, qualName, cppNamespace,
- enumInfo.getSummary(), casesList, separator,
- parseSeparatorFn);
+ enumInfo.getSummary(), casesList, separator, parseSeparatorFn,
+ casesInitList);
} else {
os << formatv(parsedAndPrinterStart, qualName, cppNamespace,
- enumInfo.getSummary(), casesList);
+ enumInfo.getSummary(), casesList, casesInitList);
}
// If all cases require a string, always wrap.
More information about the Mlir-commits
mailing list