diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -139,7 +139,7 @@
     ```
   }];
 
-  let arguments = (ins TypedAttrInterface:$value);
+  let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
   let results = (outs AnyType);
 
   let hasFolder = 1;
@@ -212,7 +212,7 @@
     ```
   }];
 
-  let arguments = (ins TypedAttrInterface:$value);
+  let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
   let results = (outs AnyType);
 
   let hasVerifier = 1;
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitCAttributes.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitCAttributes.td
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCAttributes.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCAttributes.td
@@ -26,7 +26,7 @@
   let mnemonic = attrMnemonic;
 }
 
-def EmitC_OpaqueAttr : EmitC_Attr<"Opaque", "opaque", [TypedAttrInterface]> {
+def EmitC_OpaqueAttr : EmitC_Attr<"Opaque", "opaque"> {
   let summary = "An opaque attribute";
 
   let description = [{
@@ -41,10 +41,11 @@
     ```
   }];
 
-  let parameters = (ins "Type":$type,
-                        StringRefParameter<"the opaque value">:$value);
+  let parameters = (ins StringRefParameter<"the opaque value">:$value);
 
   let hasCustomAssemblyFormat = 1;
 }
 
+def EmitC_OpaqueOrTypedAttr : AnyAttrOf<[EmitC_OpaqueAttr, TypedAttrInterface]>;
+
 #endif // MLIR_DIALECT_EMITC_IR_EMITCATTRIBUTES
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -118,6 +118,9 @@
 
 /// The constant op requires that the attribute's type matches the return type.
 LogicalResult emitc::ConstantOp::verify() {
+  if (getValueAttr().isa<emitc::OpaqueAttr>())
+    return success();
+
   TypedAttr value = getValueAttr();
   Type type = getType();
   if (!value.getType().isa<NoneType>() && type != value.getType())
@@ -172,6 +175,9 @@
 
 /// The variable op requires that the attribute's type matches the return type.
 LogicalResult emitc::VariableOp::verify() {
+  if (getValueAttr().isa<emitc::OpaqueAttr>())
+    return success();
+
   TypedAttr value = getValueAttr();
   Type type = getType();
   if (!value.getType().isa<NoneType>() && type != value.getType())
@@ -206,8 +212,7 @@
   if (parser.parseGreater())
     return Attribute();
 
-  return get(parser.getContext(),
-             type ? type : NoneType::get(parser.getContext()), value);
+  return get(parser.getContext(), value);
 }
 
 void emitc::OpaqueAttr::print(AsmPrinter &printer) const {
diff --git a/mlir/test/Target/Cpp/const.mlir b/mlir/test/Target/Cpp/const.mlir
--- a/mlir/test/Target/Cpp/const.mlir
+++ b/mlir/test/Target/Cpp/const.mlir
@@ -7,7 +7,7 @@
   %c2 = "emitc.constant"(){value = -1 : i32} : () -> i32
   %c3 = "emitc.constant"(){value = -1 : si8} : () -> si8
   %c4 = "emitc.constant"(){value = 255 : ui8} : () -> ui8
-  %c5 = "emitc.constant"(){value = #emitc.opaque<"CHAR_MIN"> : !emitc.opaque<"char">} : () -> !emitc.opaque<"char">
+  %c5 = "emitc.constant"(){value = #emitc.opaque<"CHAR_MIN">} : () -> !emitc.opaque<"char">
   return
 }
 // CPP-DEFAULT: void emitc_constant() {
diff --git a/mlir/test/Target/Cpp/variable.mlir b/mlir/test/Target/Cpp/variable.mlir
--- a/mlir/test/Target/Cpp/variable.mlir
+++ b/mlir/test/Target/Cpp/variable.mlir
@@ -7,8 +7,8 @@
   %c2 = "emitc.variable"(){value = -1 : i32} : () -> i32
   %c3 = "emitc.variable"(){value = -1 : si8} : () -> si8
   %c4 = "emitc.variable"(){value = 255 : ui8} : () -> ui8
-  %c5 = "emitc.variable"(){value = #emitc.opaque<""> : !emitc.ptr<i32>} : () -> !emitc.ptr<i32>
-  %c6 = "emitc.variable"(){value = #emitc.opaque<"NULL"> : !emitc.ptr<i32>} : () -> !emitc.ptr<i32>
+  %c5 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.ptr<i32>
+  %c6 = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.ptr<i32>
   return
 }
 // CPP-DEFAULT: void emitc_variable() {