[llvm-branch-commits] [llvm] [mlir] [TableGen] Add let append/prepend syntax for field concatenation (PR #182382)

Henrich Lauko via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Feb 19 13:55:59 PST 2026


https://github.com/xlauko created https://github.com/llvm/llvm-project/pull/182382

None

>From 24cc278733d9a8439d6f0daa1ebe400de2b8e089 Mon Sep 17 00:00:00 2001
From: xlauko <xlauko at mail.muni.cz>
Date: Thu, 19 Feb 2026 22:52:42 +0100
Subject: [PATCH] [TableGen] Add let append/prepend syntax for field
 concatenation

---
 llvm/docs/TableGen/ProgRef.rst                |  43 ++++++-
 llvm/lib/TableGen/TGLexer.cpp                 |   2 +
 llvm/lib/TableGen/TGLexer.h                   |   2 +
 llvm/lib/TableGen/TGParser.cpp                |  75 ++++++++++--
 llvm/lib/TableGen/TGParser.h                  |  11 +-
 llvm/test/TableGen/let-append-error.td        |  13 +++
 llvm/test/TableGen/let-append.td              | 110 ++++++++++++++++++
 .../DefiningDialects/AttributesAndTypes.md    |  14 +--
 mlir/docs/DefiningDialects/Operations.md      |  21 ++--
 mlir/include/mlir/IR/AttrTypeBase.td          |   9 --
 mlir/include/mlir/IR/OpBase.td                |   9 --
 mlir/include/mlir/TableGen/AttrOrTypeDef.h    |  14 ---
 mlir/include/mlir/TableGen/Operator.h         |  10 --
 mlir/lib/TableGen/AttrOrTypeDef.cpp           |  42 -------
 mlir/lib/TableGen/Operator.cpp                |  11 --
 mlir/test/mlir-tblgen/attrdefs.td             |  28 ++---
 mlir/test/mlir-tblgen/op-decl-and-defs.td     |  40 +++----
 mlir/test/mlir-tblgen/typedefs.td             |  28 ++---
 mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp   |   4 -
 mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp   |   4 -
 20 files changed, 307 insertions(+), 183 deletions(-)
 create mode 100644 llvm/test/TableGen/let-append-error.td
 create mode 100644 llvm/test/TableGen/let-append.td

diff --git a/llvm/docs/TableGen/ProgRef.rst b/llvm/docs/TableGen/ProgRef.rst
index 2e66778b42ae1..bae1db459b43b 100644
--- a/llvm/docs/TableGen/ProgRef.rst
+++ b/llvm/docs/TableGen/ProgRef.rst
@@ -686,9 +686,10 @@ arguments.
 .. productionlist::
    Body: ";" | "{" `BodyItem`* "}"
    BodyItem: `Type` `TokIdentifier` ["=" `Value`] ";"
-           :| "let" `TokIdentifier` ["{" `RangeList` "}"] "=" `Value` ";"
+           :| "let" [`ConcatMode`] `TokIdentifier` ["{" `RangeList` "}"] "=" `Value` ";"
            :| "defvar" `TokIdentifier` "=" `Value` ";"
            :| `Assert`
+   ConcatMode: "append" | "prepend"
 
 A field definition in the body specifies a field to be included in the class
 or record. If no initial value is specified, then the field's value is
@@ -700,6 +701,34 @@ for fields defined directly in the body or fields inherited from parent
 classes.  A :token:`RangeList` can be specified to reset certain bits in a
 ``bit<n>`` field.
 
+The ``let append`` and ``let prepend`` forms concatenate a value with the
+field's current value instead of replacing it. For ``append``, the new value
+is added after the current value; for ``prepend``, it is added before. The
+supported types and concatenation operators are:
+
+* ``list<T>``: uses ``!listconcat``
+* ``string`` / ``code``: uses ``!strconcat``
+* ``dag``: uses ``!con``
+
+If the field is currently unset (``?``), ``let append`` and ``let prepend``
+simply set the value directly. This is useful for accumulating values across
+a class hierarchy:
+
+.. code-block:: text
+
+  class Base {
+    list<int> items = [1, 2];
+  }
+  class Middle : Base {
+    let append items = [3];       // items = [1, 2, 3]
+  }
+  def Concrete : Middle {
+    let append items = [4];       // items = [1, 2, 3, 4]
+  }
+
+A plain ``let`` (without ``append``/``prepend``) always replaces the current
+value, which can be used to opt out of accumulated values.
+
 The ``defvar`` form defines a variable whose value can be used in other
 value expressions within the body. The variable is not a field: it does not
 become a field of the class or record being defined. Variables are provided
@@ -890,7 +919,7 @@ statements within the scope of the ``let``.
    Let:  "let" `LetList` "in" "{" `Statement`* "}"
       :| "let" `LetList` "in" `Statement`
    LetList: `LetItem` ("," `LetItem`)*
-   LetItem: `TokIdentifier` ["<" `RangeList` ">"] "=" `Value`
+   LetItem: [`ConcatMode`] `TokIdentifier` ["<" `RangeList` ">"] "=" `Value`
 
 The ``let`` statement establishes a scope, which is a sequence of statements
 in braces or a single statement with no braces. The bindings in the
@@ -927,6 +956,16 @@ statements can be nested.
 Note that a top-level ``let`` will not override fields defined in the classes or records
 themselves.
 
+Top-level ``let`` also supports ``append`` and ``prepend`` modes, which
+concatenate the value with the field's current value instead of replacing it.
+See the `Body`_ section for the supported types and semantics.
+
+.. code-block:: text
+
+  let append traits = [NewTrait] in {
+    def MyRecord : Base;
+  }
+
 
 ``multiclass`` --- define multiple records
 ------------------------------------------
diff --git a/llvm/lib/TableGen/TGLexer.cpp b/llvm/lib/TableGen/TGLexer.cpp
index 3c88f107f790a..78fcc9129fcb0 100644
--- a/llvm/lib/TableGen/TGLexer.cpp
+++ b/llvm/lib/TableGen/TGLexer.cpp
@@ -447,6 +447,8 @@ tgtok::TokKind TGLexer::LexIdentifier() {
                             .Case("else", tgtok::ElseKW)
                             .Case("assert", tgtok::Assert)
                             .Case("dump", tgtok::Dump)
+                            .Case("append", tgtok::Append)
+                            .Case("prepend", tgtok::Prepend)
                             .Default(tgtok::Id);
 
   // A couple of tokens require special processing.
diff --git a/llvm/lib/TableGen/TGLexer.h b/llvm/lib/TableGen/TGLexer.h
index a0ade6412024e..a7f934f72c44a 100644
--- a/llvm/lib/TableGen/TGLexer.h
+++ b/llvm/lib/TableGen/TGLexer.h
@@ -86,6 +86,8 @@ enum TokKind {
   List,
   String,
   Then,
+  Append,
+  Prepend,
 
   // Object start tokens.
   OBJECT_START_FIRST,
diff --git a/llvm/lib/TableGen/TGParser.cpp b/llvm/lib/TableGen/TGParser.cpp
index 3d31d8e2717b1..5b3ede96d11dc 100644
--- a/llvm/lib/TableGen/TGParser.cpp
+++ b/llvm/lib/TableGen/TGParser.cpp
@@ -238,7 +238,8 @@ bool TGParser::AddValue(Record *CurRec, SMLoc Loc, const RecordVal &RV) {
 /// Return true on error, false on success.
 bool TGParser::SetValue(Record *CurRec, SMLoc Loc, const Init *ValName,
                         ArrayRef<unsigned> BitList, const Init *V,
-                        bool AllowSelfAssignment, bool OverrideDefLoc) {
+                        bool AllowSelfAssignment, bool OverrideDefLoc,
+                        LetConcatKind ConcatKind) {
   if (!V)
     return false;
 
@@ -250,6 +251,40 @@ bool TGParser::SetValue(Record *CurRec, SMLoc Loc, const Init *ValName,
     return Error(Loc,
                  "Value '" + ValName->getAsUnquotedString() + "' unknown!");
 
+  // Handle append/prepend by concatenating with the current value.
+  if (ConcatKind != LetConcatKind::Replace) {
+    if (!BitList.empty())
+      return Error(Loc, "cannot use append/prepend with bit range");
+
+    const Init *CurVal = RV->getValue();
+    const RecTy *Type = RV->getType();
+
+    // If the current value is unset, just use the new value directly.
+    if (!isa<UnsetInit>(CurVal)) {
+      const Init *First = ConcatKind == LetConcatKind::Append ? CurVal : V;
+      const Init *Second = ConcatKind == LetConcatKind::Append ? V : CurVal;
+
+      if (isa<ListRecTy>(Type))
+        V = BinOpInit::get(BinOpInit::LISTCONCAT, First, Second, Type)
+                ->Fold(CurRec);
+      else if (isa<StringRecTy>(Type))
+        V = BinOpInit::get(BinOpInit::STRCONCAT, First, Second,
+                           StringRecTy::get(Records))
+                ->Fold(CurRec);
+      else if (isa<DagRecTy>(Type))
+        V = BinOpInit::get(BinOpInit::CONCAT, First, Second,
+                           DagRecTy::get(Records))
+                ->Fold(CurRec);
+      else
+        return Error(Loc, "cannot " +
+                              Twine(ConcatKind == LetConcatKind::Append
+                                        ? "append"
+                                        : "prepend") +
+                              " to field '" + ValName->getAsUnquotedString() +
+                              "' of type '" + Type->getAsString() + "'");
+    }
+  }
+
   // Do not allow assignments like 'X = X'. This will just cause infinite loops
   // in the resolution machinery.
   if (BitList.empty())
@@ -3637,8 +3672,20 @@ bool TGParser::ParseBodyItem(Record *CurRec) {
     return false;
   }
 
-  // LET ID OptionalRangeList '=' Value ';'
-  if (Lex.Lex() != tgtok::Id)
+  // LET [append|prepend] ID OptionalBitList '=' Value ';'
+  Lex.Lex(); // eat 'let'.
+
+  // Check for optional 'append' or 'prepend' keyword.
+  LetConcatKind ConcatKind = LetConcatKind::Replace;
+  if (Lex.getCode() == tgtok::Append) {
+    ConcatKind = LetConcatKind::Append;
+    Lex.Lex();
+  } else if (Lex.getCode() == tgtok::Prepend) {
+    ConcatKind = LetConcatKind::Prepend;
+    Lex.Lex();
+  }
+
+  if (Lex.getCode() != tgtok::Id)
     return TokError("expected field identifier after let");
 
   SMLoc IdLoc = Lex.getLoc();
@@ -3671,7 +3718,9 @@ bool TGParser::ParseBodyItem(Record *CurRec) {
   if (!consume(tgtok::semi))
     return TokError("expected ';' after let expression");
 
-  return SetValue(CurRec, IdLoc, FieldName, BitList, Val);
+  return SetValue(CurRec, IdLoc, FieldName, BitList, Val,
+                  /*AllowSelfAssignment=*/false, /*OverrideDefLoc=*/true,
+                  ConcatKind);
 }
 
 /// ParseBody - Read the body of a class or def. Return true on error, false on
@@ -3711,7 +3760,9 @@ bool TGParser::ParseBody(Record *CurRec) {
 bool TGParser::ApplyLetStack(Record *CurRec) {
   for (SmallVectorImpl<LetRecord> &LetInfo : LetStack)
     for (LetRecord &LR : LetInfo)
-      if (SetValue(CurRec, LR.Loc, LR.Name, LR.Bits, LR.Value))
+      if (SetValue(CurRec, LR.Loc, LR.Name, LR.Bits, LR.Value,
+                   /*AllowSelfAssignment=*/false, /*OverrideDefLoc=*/true,
+                   LR.ConcatKind))
         return true;
   return false;
 }
@@ -4187,10 +4238,20 @@ bool TGParser::ParseClass() {
 /// of LetRecords.
 ///
 ///   LetList ::= LetItem (',' LetItem)*
-///   LetItem ::= ID OptionalRangeList '=' Value
+///   LetItem ::= [append|prepend] ID OptionalRangeList '=' Value
 ///
 void TGParser::ParseLetList(SmallVectorImpl<LetRecord> &Result) {
   do {
+    // Check for optional 'append' or 'prepend' keyword.
+    LetConcatKind ConcatKind = LetConcatKind::Replace;
+    if (Lex.getCode() == tgtok::Append) {
+      ConcatKind = LetConcatKind::Append;
+      Lex.Lex();
+    } else if (Lex.getCode() == tgtok::Prepend) {
+      ConcatKind = LetConcatKind::Prepend;
+      Lex.Lex();
+    }
+
     if (Lex.getCode() != tgtok::Id) {
       TokError("expected identifier in let definition");
       Result.clear();
@@ -4222,7 +4283,7 @@ void TGParser::ParseLetList(SmallVectorImpl<LetRecord> &Result) {
     }
 
     // Now that we have everything, add the record.
-    Result.emplace_back(Name, Bits, Val, NameLoc);
+    Result.emplace_back(Name, Bits, Val, NameLoc, ConcatKind);
   } while (consume(tgtok::comma));
 }
 
diff --git a/llvm/lib/TableGen/TGParser.h b/llvm/lib/TableGen/TGParser.h
index 09b7d5380695d..86287c6d16dd5 100644
--- a/llvm/lib/TableGen/TGParser.h
+++ b/llvm/lib/TableGen/TGParser.h
@@ -26,13 +26,17 @@ struct MultiClass;
 struct SubClassReference;
 struct SubMultiClassReference;
 
+enum class LetConcatKind { Replace, Append, Prepend };
+
 struct LetRecord {
   const StringInit *Name;
   std::vector<unsigned> Bits;
   const Init *Value;
   SMLoc Loc;
-  LetRecord(const StringInit *N, ArrayRef<unsigned> B, const Init *V, SMLoc L)
-      : Name(N), Bits(B), Value(V), Loc(L) {}
+  LetConcatKind ConcatKind;
+  LetRecord(const StringInit *N, ArrayRef<unsigned> B, const Init *V, SMLoc L,
+            LetConcatKind CK = LetConcatKind::Replace)
+      : Name(N), Bits(B), Value(V), Loc(L), ConcatKind(CK) {}
 };
 
 /// RecordsEntry - Holds exactly one of a Record, ForeachLoop, or
@@ -226,7 +230,8 @@ class TGParser {
   /// RecordVal.
   bool SetValue(Record *TheRec, SMLoc Loc, const Init *ValName,
                 ArrayRef<unsigned> BitList, const Init *V,
-                bool AllowSelfAssignment = false, bool OverrideDefLoc = true);
+                bool AllowSelfAssignment = false, bool OverrideDefLoc = true,
+                LetConcatKind ConcatKind = LetConcatKind::Replace);
   bool AddSubClass(Record *Rec, SubClassReference &SubClass);
   bool AddSubClass(RecordsEntry &Entry, SubClassReference &SubClass);
   bool AddSubMultiClass(MultiClass *CurMC,
diff --git a/llvm/test/TableGen/let-append-error.td b/llvm/test/TableGen/let-append-error.td
new file mode 100644
index 0000000000000..365d64efd30fb
--- /dev/null
+++ b/llvm/test/TableGen/let-append-error.td
@@ -0,0 +1,13 @@
+// RUN: not llvm-tblgen %s 2>&1 | FileCheck %s
+// XFAIL: vg_leak
+
+// Test that 'let append' on unsupported types produces an error.
+
+class Base {
+  int count = 0;
+}
+
+// CHECK: error: cannot append to field 'count' of type 'int'
+def Bad : Base {
+  let append count = 1;
+}
diff --git a/llvm/test/TableGen/let-append.td b/llvm/test/TableGen/let-append.td
new file mode 100644
index 0000000000000..0cb3651b08ae4
--- /dev/null
+++ b/llvm/test/TableGen/let-append.td
@@ -0,0 +1,110 @@
+// RUN: llvm-tblgen %s | FileCheck %s
+// XFAIL: vg_leak
+
+// Test 'let append' and 'let prepend' syntax.
+
+def op;
+
+class Base {
+  list<int> items = [1, 2];
+  string text = "hello";
+  dag d = (op);
+}
+
+// Defs are printed in alphabetical order.
+
+// Test append with code type.
+class WithCode {
+  code body = [{ int x = 0; }];
+}
+
+// CHECK: def AppendCode
+// CHECK: code body = [{ int x = 0;  int y = 1; }]
+def AppendCode : WithCode {
+  let append body = [{ int y = 1; }];
+}
+
+// CHECK: def AppendDag
+// CHECK: dag d = (op 3:$a);
+def AppendDag : Base {
+  let append d = (op 3:$a);
+}
+
+// CHECK: def AppendList
+// CHECK: list<int> items = [1, 2, 3, 4];
+def AppendList : Base {
+  let append items = [3, 4];
+}
+
+// CHECK: def AppendString
+// CHECK: string text = "hello world";
+def AppendString : Base {
+  let append text = " world";
+}
+
+// Test append on initially-unset field.
+class WithUnset {
+  list<int> vals = ?;
+  string msg = ?;
+}
+
+// CHECK: def AppendUnset
+// CHECK: list<int> vals = [1];
+// CHECK: string msg = "hi";
+def AppendUnset : WithUnset {
+  let append vals = [1];
+  let append msg = "hi";
+}
+
+// Test multi-level inheritance accumulation.
+class Middle : Base {
+  let append items = [3];
+  let append text = " world";
+}
+
+// CHECK: def MultiLevel
+// CHECK: list<int> items = [1, 2, 3, 4];
+// CHECK: string text = "hello world!";
+def MultiLevel : Middle {
+  let append items = [4];
+  let append text = "!";
+}
+
+// CHECK: def OverrideAfterAppend
+// CHECK: list<int> items = [10];
+def OverrideAfterAppend : Base {
+  let append items = [3];
+  let items = [10];
+}
+
+// CHECK: def PrependDag
+// CHECK: dag d = (op 0:$z);
+def PrependDag : Base {
+  let prepend d = (op 0:$z);
+}
+
+// CHECK: def PrependList
+// CHECK: list<int> items = [0, 1, 2];
+def PrependList : Base {
+  let prepend items = [0];
+}
+
+// CHECK: def PrependString
+// CHECK: string text = "say hello";
+def PrependString : Base {
+  let prepend text = "say ";
+}
+
+// Test top-level let with append.
+let append items = [100] in {
+  // CHECK: def TopLevelAppend
+  // CHECK: list<int> items = [1, 2, 100];
+  def TopLevelAppend : Base;
+}
+
+// Test top-level let with prepend.
+let prepend items = [0] in {
+  // CHECK: def TopLevelPrepend
+  // CHECK: list<int> items = [0, 1, 2];
+  def TopLevelPrepend : Base;
+}
diff --git a/mlir/docs/DefiningDialects/AttributesAndTypes.md b/mlir/docs/DefiningDialects/AttributesAndTypes.md
index 28634ae8f1cfa..23caeb0d6b65c 100644
--- a/mlir/docs/DefiningDialects/AttributesAndTypes.md
+++ b/mlir/docs/DefiningDialects/AttributesAndTypes.md
@@ -1200,21 +1200,21 @@ Note that these are mechanisms intended for long-tail cases by power users; for
 not-yet-implemented widely-applicable cases, improving the infrastructure is
 preferable.
 
-### Inheritable extra declarations and definitions
+### Accumulating extra declarations with `let append`
 
-Similar to [operations](Operations.md#inheritable-extra-declarations-and-definitions),
-attribute and type definitions support `inheritableExtraClassDeclaration` and
-`inheritableExtraClassDefinition`. These fields accumulate across the TableGen
+Similar to [operations](Operations.md#accumulating-extra-declarations-with-let-append),
+attribute and type definitions support `let append` on `extraClassDeclaration`
+and `extraClassDefinition`. These values accumulate across the TableGen
 class hierarchy, so base classes can provide shared C++ code that is
 automatically included in all derived attributes or types. A derived class can
-opt out by setting the field to empty (`[{}]`).
+opt out by using a plain `let` to override the accumulated value.
 
 ```tablegen
 class MyBaseType<string name> : TypeDef<MyDialect, name> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     bool isCompatible(Type other);
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     bool $cppClass::isCompatible(Type other) { return other == *this; }
   }];
 }
diff --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md
index fa8e5ba4eeacb..183e9ed519f8d 100644
--- a/mlir/docs/DefiningDialects/Operations.md
+++ b/mlir/docs/DefiningDialects/Operations.md
@@ -1161,26 +1161,20 @@ declaration. In these cases, users can add an `extraClassDefinition` to define
 code that is added to the generated source file inside the op's C++ namespace.
 The substitution `$cppClass` is replaced by the op's C++ class name.
 
-### Inheritable extra declarations and definitions
+### Accumulating extra declarations with `let append`
 
 When defining base op classes in TableGen, `extraClassDeclaration` and
 `extraClassDefinition` follow standard TableGen `let` override semantics: if a
 derived class sets them, the base class values are lost. To provide shared C++
-code that is automatically available to all derived ops, use
-`inheritableExtraClassDeclaration` and `inheritableExtraClassDefinition`.
-
-These fields **accumulate** across the class hierarchy. Each class in the
-inheritance chain that sets a new value contributes its code to all concrete ops
-below it. A derived class can opt out of inherited declarations by setting the
-field to empty (`[{}]`).
+code that **accumulates** across the class hierarchy, use `let append`:
 
 ```tablegen
 class MyDialectOp<string mnemonic, list<Trait> traits = []>
     : Op<MyDialect, mnemonic, traits> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     MyDialect &getDialectInstance();
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     MyDialect &$cppClass::getDialectInstance() {
       return static_cast<MyDialect &>((*this)->getDialect());
     }
@@ -1189,7 +1183,7 @@ class MyDialectOp<string mnemonic, list<Trait> traits = []>
 
 def FooOp : MyDialectOp<"foo"> {
   // FooOp gets both getDialectInstance() and doFoo().
-  let extraClassDeclaration = [{ void doFoo(); }];
+  let append extraClassDeclaration = [{ void doFoo(); }];
 }
 
 def BarOp : MyDialectOp<"bar"> {
@@ -1197,8 +1191,9 @@ def BarOp : MyDialectOp<"bar"> {
 }
 ```
 
-Multiple levels of the hierarchy can set `inheritableExtraClassDeclaration`;
-all their values are concatenated in the generated code.
+Multiple levels of the hierarchy can use `let append extraClassDeclaration`;
+all their values are concatenated in the generated code. A derived class can
+opt out by using a plain `let` to override the accumulated value.
 
 ### Generated C++ code
 
diff --git a/mlir/include/mlir/IR/AttrTypeBase.td b/mlir/include/mlir/IR/AttrTypeBase.td
index eee78eb8c2a94..16f7f8b532521 100644
--- a/mlir/include/mlir/IR/AttrTypeBase.td
+++ b/mlir/include/mlir/IR/AttrTypeBase.td
@@ -250,15 +250,6 @@ class AttrOrTypeDef<string valueType, string name, list<Trait> defTraits,
   // replaced by the class name.
   code extraClassDefinition = [{}];
 
-  // Extra class declarations/definitions that are inherited by all derived
-  // classes. These can be set at any level in the class hierarchy. Unlike
-  // extraClassDeclaration/extraClassDefinition, the inheritable values carry
-  // over to all derived classes — both the inheritable and the regular extra
-  // declarations are concatenated in the generated code. A derived class can
-  // discard inherited declarations by setting these to empty [{}].
-  code inheritableExtraClassDeclaration = [{}];
-  code inheritableExtraClassDefinition = [{}];
-
   // Generate a default 'getAlias' method for OpAsm{Type,Attr}Interface.
   bit genMnemonicAlias = 0;
 }
diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td
index 4d288c6fc09f4..7a667d701ab71 100644
--- a/mlir/include/mlir/IR/OpBase.td
+++ b/mlir/include/mlir/IR/OpBase.td
@@ -440,15 +440,6 @@ class Op<Dialect dialect, string mnemonic, list<Trait> props = []> {
   // generated code is placed inside the op's C++ namespace. `$cppClass` is
   // replaced by the op's C++ class name.
   code extraClassDefinition = ?;
-
-  // Extra class declarations/definitions that are inherited by all derived op
-  // classes. These can be set at any level in the class hierarchy. Unlike
-  // extraClassDeclaration/extraClassDefinition, the inheritable values carry
-  // over to all derived ops — both the inheritable and the regular extra
-  // declarations are concatenated in the generated code. A derived class can
-  // discard inherited declarations by setting these to empty [{}].
-  code inheritableExtraClassDeclaration = [{}];
-  code inheritableExtraClassDefinition = [{}];
 }
 
 // The arguments of an op.
diff --git a/mlir/include/mlir/TableGen/AttrOrTypeDef.h b/mlir/include/mlir/TableGen/AttrOrTypeDef.h
index cd0b066297320..65992f9fef5e9 100644
--- a/mlir/include/mlir/TableGen/AttrOrTypeDef.h
+++ b/mlir/include/mlir/TableGen/AttrOrTypeDef.h
@@ -216,14 +216,6 @@ class AttrOrTypeDef {
   /// Returns the def's extra class definition code.
   std::optional<StringRef> getExtraDefs() const;
 
-  /// Collects inheritable extra class declarations accumulated across the
-  /// class hierarchy into `result`.
-  void getInheritableExtraDecls(SmallVectorImpl<StringRef> &result) const;
-
-  /// Collects inheritable extra class definitions accumulated across the
-  /// class hierarchy into `result`.
-  void getInheritableExtraDefs(SmallVectorImpl<StringRef> &result) const;
-
   /// Returns true if we need to generate a default 'getAlias' implementation
   /// using the mnemonic.
   bool genMnemonicAlias() const;
@@ -301,12 +293,6 @@ class TypeDef : public AttrOrTypeDef {
   StringRef getTypeName() const;
 };
 
-/// Walk the superclass chain of `def` and accumulate distinct values of
-/// `fieldName` into `result`. Used to collect inheritable extra class
-/// declarations/definitions across the TableGen class hierarchy.
-void accumulateInheritableField(const llvm::Record &def, StringRef fieldName,
-                                SmallVectorImpl<StringRef> &result);
-
 } // namespace tblgen
 } // namespace mlir
 
diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h
index 42c5c7a0c9d8f..f0514d8e61748 100644
--- a/mlir/include/mlir/TableGen/Operator.h
+++ b/mlir/include/mlir/TableGen/Operator.h
@@ -303,16 +303,6 @@ class Operator {
   /// Returns this op's extra class definition code.
   StringRef getExtraClassDefinition() const;
 
-  /// Collects inheritable extra class declarations accumulated across the
-  /// class hierarchy into `result`.
-  void getInheritableExtraClassDeclarations(
-      SmallVectorImpl<StringRef> &result) const;
-
-  /// Collects inheritable extra class definitions accumulated across the
-  /// class hierarchy into `result`.
-  void
-  getInheritableExtraClassDefinitions(SmallVectorImpl<StringRef> &result) const;
-
   /// Returns the Tablegen definition this operator was constructed from.
   /// TODO: do not expose the TableGen record, this is a temporary solution to
   /// OpEmitter requiring a Record because Operator does not provide enough
diff --git a/mlir/lib/TableGen/AttrOrTypeDef.cpp b/mlir/lib/TableGen/AttrOrTypeDef.cpp
index 380dea745e105..bf835a860cd5b 100644
--- a/mlir/lib/TableGen/AttrOrTypeDef.cpp
+++ b/mlir/lib/TableGen/AttrOrTypeDef.cpp
@@ -207,48 +207,6 @@ std::optional<StringRef> AttrOrTypeDef::getExtraDefs() const {
   return value.empty() ? std::optional<StringRef>() : value;
 }
 
-void mlir::tblgen::accumulateInheritableField(
-    const Record &def, StringRef fieldName,
-    SmallVectorImpl<StringRef> &result) {
-  auto getFieldValue = [&](const Record *rec) -> StringRef {
-    const auto *val = rec->getValue(fieldName);
-    if (!val)
-      return {};
-    const auto *si = dyn_cast<StringInit>(val->getValue());
-    return si ? si->getValue() : StringRef();
-  };
-
-  // Track the previous value to detect when a class explicitly sets the field
-  // vs. merely inheriting it unchanged from a parent.
-  StringRef prev;
-  auto accumulate = [&](StringRef value) {
-    if (value == prev)
-      return;
-    // An empty value means the class opted out of inherited declarations,
-    // so discard everything accumulated so far.
-    if (value.empty())
-      result.clear();
-    else
-      result.push_back(value);
-    prev = value;
-  };
-
-  for (const Record *rec : def.getSuperClasses())
-    accumulate(getFieldValue(rec));
-  // The concrete def itself may also override the field.
-  accumulate(def.getValueAsString(fieldName));
-}
-
-void AttrOrTypeDef::getInheritableExtraDecls(
-    SmallVectorImpl<StringRef> &result) const {
-  accumulateInheritableField(*def, "inheritableExtraClassDeclaration", result);
-}
-
-void AttrOrTypeDef::getInheritableExtraDefs(
-    SmallVectorImpl<StringRef> &result) const {
-  accumulateInheritableField(*def, "inheritableExtraClassDefinition", result);
-}
-
 bool AttrOrTypeDef::genMnemonicAlias() const {
   return def->getValueAsBit("genMnemonicAlias");
 }
diff --git a/mlir/lib/TableGen/Operator.cpp b/mlir/lib/TableGen/Operator.cpp
index c14d374c889ed..82dfbcbfa4d4f 100644
--- a/mlir/lib/TableGen/Operator.cpp
+++ b/mlir/lib/TableGen/Operator.cpp
@@ -12,7 +12,6 @@
 
 #include "mlir/TableGen/Operator.h"
 #include "mlir/TableGen/Argument.h"
-#include "mlir/TableGen/AttrOrTypeDef.h"
 #include "mlir/TableGen/Predicate.h"
 #include "mlir/TableGen/Trait.h"
 #include "mlir/TableGen/Type.h"
@@ -181,16 +180,6 @@ StringRef Operator::getExtraClassDefinition() const {
   return def.getValueAsString(attr);
 }
 
-void Operator::getInheritableExtraClassDeclarations(
-    SmallVectorImpl<StringRef> &result) const {
-  accumulateInheritableField(def, "inheritableExtraClassDeclaration", result);
-}
-
-void Operator::getInheritableExtraClassDefinitions(
-    SmallVectorImpl<StringRef> &result) const {
-  accumulateInheritableField(def, "inheritableExtraClassDefinition", result);
-}
-
 const Record &Operator::getDef() const { return def; }
 
 bool Operator::skipDefaultBuilders() const {
diff --git a/mlir/test/mlir-tblgen/attrdefs.td b/mlir/test/mlir-tblgen/attrdefs.td
index 25b009f5ec14b..ae171b84a84f8 100644
--- a/mlir/test/mlir-tblgen/attrdefs.td
+++ b/mlir/test/mlir-tblgen/attrdefs.td
@@ -205,24 +205,24 @@ def J_CustomStorageCtorAttr : AttrDef<Test_Dialect, "CustomStorageCtorAttr"> {
 // DEF: static CustomStorageCtorAttrAttrStorage *construct
 // DEF-SAME: (::mlir::AttributeStorageAllocator &allocator, KeyTy &&tblgenKey);
 
-// Test inheritable extra class declarations/definitions for attributes.
+// Test 'let append' for extra class declarations/definitions in attributes.
 
 class InheritableTestAttr<string name> : AttrDef<Test_Dialect, name> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     int getInheritedHelper();
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     int $cppClass::getInheritedHelper() { return 42; }
   }];
 }
 
-// Both inheritable and regular extra declarations should appear.
+// Both appended and regular extra declarations should appear.
 def K_InheritableAttrA : InheritableTestAttr<"InheritableA"> {
   let attrName = "test.inheritable_a";
-  let extraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     void doA();
   }];
-  let extraClassDefinition = [{
+  let append extraClassDefinition = [{
     void $cppClass::doA() {}
   }];
 }
@@ -235,7 +235,7 @@ def K_InheritableAttrA : InheritableTestAttr<"InheritableA"> {
 // DEF: return 42;
 // DEF-LABEL: void InheritableAAttr::doA()
 
-// Only inheritable declarations (no extraClassDeclaration).
+// Only appended declarations from parent (no additional extraClassDeclaration).
 def L_InheritableAttrB : InheritableTestAttr<"InheritableB"> {
   let attrName = "test.inheritable_b";
 }
@@ -243,22 +243,22 @@ def L_InheritableAttrB : InheritableTestAttr<"InheritableB"> {
 // DECL-LABEL: class InheritableBAttr
 // DECL: int getInheritedHelper();
 
-// Discard inheritable declarations by setting to empty.
+// Discard accumulated declarations by using plain 'let' to override.
 def M_InheritableAttrC : InheritableTestAttr<"InheritableC"> {
   let attrName = "test.inheritable_c";
-  let inheritableExtraClassDeclaration = [{}];
-  let inheritableExtraClassDefinition = [{}];
+  let extraClassDeclaration = [{}];
+  let extraClassDefinition = [{}];
 }
 
 // DECL-LABEL: class InheritableCAttr
 // DECL-NOT: int getInheritedHelper();
 
-// Middle-of-stack: accumulates with base inheritable declarations.
+// Middle-of-stack: accumulates with base declarations via 'let append'.
 class InheritableMiddleAttr<string name> : InheritableTestAttr<name> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     int getMiddleHelper();
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     int $cppClass::getMiddleHelper() { return 1; }
   }];
 }
@@ -277,7 +277,7 @@ def N_InheritableAttrD : InheritableMiddleAttr<"InheritableD"> {
 // DEF-LABEL: int InheritableDAttr::getMiddleHelper()
 // DEF: return 1;
 
-// Passthrough: middle class doesn't set inheritable, base value passes through.
+// Passthrough: middle class doesn't append, base value passes through.
 class InheritablePassthroughAttr<string name> : InheritableTestAttr<name> {}
 
 def O_InheritableAttrE : InheritablePassthroughAttr<"InheritableE"> {
diff --git a/mlir/test/mlir-tblgen/op-decl-and-defs.td b/mlir/test/mlir-tblgen/op-decl-and-defs.td
index d9bda6adb7dbb..9449c7b83ea48 100644
--- a/mlir/test/mlir-tblgen/op-decl-and-defs.td
+++ b/mlir/test/mlir-tblgen/op-decl-and-defs.td
@@ -589,63 +589,63 @@ def _TypeInferredPropOp : NS_Op<"type_inferred_prop_op_with_properties", [
   let hasCustomAssemblyFormat = 1;
 }
 
-// Test inheritable extra class declarations/definitions.
+// Test 'let append' for extra class declarations/definitions.
 class NS_InheritableOp<string mnemonic, list<Trait> traits = []>
     : NS_Op<mnemonic, traits> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     int getInheritedHelper();
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     int $cppClass::getInheritedHelper() { return 42; }
   }];
 }
 
-// Both inheritable and regular extra declarations should appear.
+// Both appended and regular extra declarations should appear.
 def NS_InheritableOpA : NS_InheritableOp<"inheritable_op_a"> {
-  let extraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     void doA();
   }];
-  let extraClassDefinition = [{
+  let append extraClassDefinition = [{
     void $cppClass::doA() {}
   }];
 }
 
-// Only inheritable declarations (no extraClassDeclaration).
+// Only appended declarations from parent (no additional extraClassDeclaration).
 def NS_InheritableOpB : NS_InheritableOp<"inheritable_op_b"> {}
 
-// Discard inheritable declarations by setting to empty.
+// Discard accumulated declarations by using plain 'let' to override.
 def NS_InheritableOpC : NS_InheritableOp<"inheritable_op_c"> {
-  let inheritableExtraClassDeclaration = [{}];
-  let inheritableExtraClassDefinition = [{}];
+  let extraClassDeclaration = ?;
+  let extraClassDefinition = ?;
 }
 
 // Middle-of-stack: NS_Op -> NS_InheritableOp -> NS_InheritableMiddleOp
-// The middle class adds its own inheritableExtraClassDeclaration. Concrete ops
-// get both the base and middle inheritable declarations (accumulated).
+// The middle class appends its own extraClassDeclaration. Concrete ops
+// get both the base and middle declarations (accumulated via 'let append').
 class NS_InheritableMiddleOp<string mnemonic, list<Trait> traits = []>
     : NS_InheritableOp<mnemonic, traits> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     int getMiddleHelper();
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     int $cppClass::getMiddleHelper() { return 1; }
   }];
 }
 
-// Concrete op inheriting from middle class gets the middle class's value.
+// Concrete op inheriting from middle class gets both base and middle.
 def NS_InheritableOpD : NS_InheritableMiddleOp<"inheritable_op_d"> {}
 
-// Concrete op inheriting from middle class with its own extraClassDeclaration.
+// Concrete op inheriting from middle class with its own appended declaration.
 def NS_InheritableOpE : NS_InheritableMiddleOp<"inheritable_op_e"> {
-  let extraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     void doE();
   }];
 }
 
-// Middle class that does NOT set inheritableExtraClassDeclaration — inherits
-// the base class value and passes it through to concrete ops.
+// Middle class that does NOT append — inherits the base class value and
+// passes it through to concrete ops.
 class NS_InheritablePassthroughOp<string mnemonic, list<Trait> traits = []>
     : NS_InheritableOp<mnemonic, traits> {}
 
-// Concrete op gets the original base class inheritable declarations.
+// Concrete op gets the original base class declarations.
 def NS_InheritableOpF : NS_InheritablePassthroughOp<"inheritable_op_f"> {}
diff --git a/mlir/test/mlir-tblgen/typedefs.td b/mlir/test/mlir-tblgen/typedefs.td
index 74524e192493f..d899d0bfa47f1 100644
--- a/mlir/test/mlir-tblgen/typedefs.td
+++ b/mlir/test/mlir-tblgen/typedefs.td
@@ -154,24 +154,24 @@ def E_IntegerType : TestType<"Integer"> {
 // DECL-NEXT: bool isUnsigned() const { return getSignedness() == Unsigned; }
 }
 
-// Test inheritable extra class declarations/definitions for types.
+// Test 'let append' for extra class declarations/definitions in types.
 
 class InheritableTestType<string name> : TypeDef<Test_Dialect, name> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     int getInheritedHelper();
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     int $cppClass::getInheritedHelper() { return 42; }
   }];
 }
 
-// Both inheritable and regular extra declarations should appear.
+// Both appended and regular extra declarations should appear.
 def F_InheritableTypeA : InheritableTestType<"InheritableA"> {
   let typeName = "test.inheritable_a";
-  let extraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     void doA();
   }];
-  let extraClassDefinition = [{
+  let append extraClassDefinition = [{
     void $cppClass::doA() {}
   }];
 }
@@ -184,7 +184,7 @@ def F_InheritableTypeA : InheritableTestType<"InheritableA"> {
 // DEF: return 42;
 // DEF-LABEL: void InheritableAType::doA()
 
-// Only inheritable declarations (no extraClassDeclaration).
+// Only appended declarations from parent (no additional extraClassDeclaration).
 def G_InheritableTypeB : InheritableTestType<"InheritableB"> {
   let typeName = "test.inheritable_b";
 }
@@ -192,22 +192,22 @@ def G_InheritableTypeB : InheritableTestType<"InheritableB"> {
 // DECL-LABEL: class InheritableBType
 // DECL: int getInheritedHelper();
 
-// Discard inheritable declarations by setting to empty.
+// Discard accumulated declarations by using plain 'let' to override.
 def H_InheritableTypeC : InheritableTestType<"InheritableC"> {
   let typeName = "test.inheritable_c";
-  let inheritableExtraClassDeclaration = [{}];
-  let inheritableExtraClassDefinition = [{}];
+  let extraClassDeclaration = [{}];
+  let extraClassDefinition = [{}];
 }
 
 // DECL-LABEL: class InheritableCType
 // DECL-NOT: int getInheritedHelper();
 
-// Middle-of-stack: accumulates with base inheritable declarations.
+// Middle-of-stack: accumulates with base declarations via 'let append'.
 class InheritableMiddleType<string name> : InheritableTestType<name> {
-  let inheritableExtraClassDeclaration = [{
+  let append extraClassDeclaration = [{
     int getMiddleHelper();
   }];
-  let inheritableExtraClassDefinition = [{
+  let append extraClassDefinition = [{
     int $cppClass::getMiddleHelper() { return 1; }
   }];
 }
@@ -226,7 +226,7 @@ def I_InheritableTypeD : InheritableMiddleType<"InheritableD"> {
 // DEF-LABEL: int InheritableDType::getMiddleHelper()
 // DEF: return 1;
 
-// Passthrough: middle class doesn't set inheritable, base value passes through.
+// Passthrough: middle class doesn't append, base value passes through.
 class InheritablePassthroughType<string name> : InheritableTestType<name> {}
 
 def J_InheritableTypeE : InheritablePassthroughType<"InheritableE"> {
diff --git a/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp b/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
index de4c3be8f2377..7cd620f612d13 100644
--- a/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
+++ b/mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
@@ -273,8 +273,6 @@ void DefGen::createParentWithTraits() {
 /// Include declarations specified on NativeTrait
 static std::string formatExtraDeclarations(const AttrOrTypeDef &def) {
   SmallVector<StringRef> extraDeclarations;
-  // Include inheritable extra class declarations.
-  def.getInheritableExtraDecls(extraDeclarations);
   // Include extra class declarations from NativeTrait.
   for (const auto &trait : def.getTraits()) {
     if (auto *attrOrTypeTrait = dyn_cast<tblgen::NativeTrait>(&trait)) {
@@ -294,8 +292,6 @@ static std::string formatExtraDeclarations(const AttrOrTypeDef &def) {
 /// replaced by the C++ class name.
 static std::string formatExtraDefinitions(const AttrOrTypeDef &def) {
   SmallVector<StringRef> extraDefinitions;
-  // Include inheritable extra class definitions.
-  def.getInheritableExtraDefs(extraDefinitions);
   // Include extra class definitions from NativeTrait.
   for (const auto &trait : def.getTraits()) {
     if (auto *attrOrTypeTrait = dyn_cast<tblgen::NativeTrait>(&trait)) {
diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index 6c1b4d6cf3137..ebef58324d490 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -1164,8 +1164,6 @@ static void genPropertyVerifier(
 /// Include declarations specified on NativeTrait
 static std::string formatExtraDeclarations(const Operator &op) {
   SmallVector<StringRef> extraDeclarations;
-  // Include inheritable extra class declarations.
-  op.getInheritableExtraClassDeclarations(extraDeclarations);
   // Include extra class declarations from NativeTrait.
   for (const auto &trait : op.getTraits()) {
     if (auto *opTrait = dyn_cast<tblgen::NativeTrait>(&trait)) {
@@ -1184,8 +1182,6 @@ static std::string formatExtraDeclarations(const Operator &op) {
 /// Include declarations specified on NativeTrait
 static std::string formatExtraDefinitions(const Operator &op) {
   SmallVector<StringRef> extraDefinitions;
-  // Include inheritable extra class definitions.
-  op.getInheritableExtraClassDefinitions(extraDefinitions);
   // Include extra class definitions from NativeTrait.
   for (const auto &trait : op.getTraits()) {
     if (auto *opTrait = dyn_cast<tblgen::NativeTrait>(&trait)) {



More information about the llvm-branch-commits mailing list