diff --git a/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td b/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td index d12204b991a26..0d2f51532a7d2 100644 --- a/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td +++ b/mlir/include/mlir/Dialect/PDL/IR/PDLOps.td @@ -346,7 +346,8 @@ def PDL_OperationOp : PDL_Op<"operation", [AttrSizedOperandSegments]> { Variadic>:$operandValues, Variadic:$attributeValues, StrArrayAttr:$attributeValueNames, - Variadic>:$typeValues); + Variadic>:$typeValues, + OptionalAttr:$numRegions); let results = (outs PDL_Operation:$op); let assemblyFormat = [{ ($opName^)? (`(` $operandValues^ `:` type($operandValues) `)`)? @@ -361,9 +362,10 @@ def PDL_OperationOp : PDL_Op<"operation", [AttrSizedOperandSegments]> { CArg<"ValueRange", "llvm::None">:$attrValues, CArg<"ValueRange", "llvm::None">:$resultTypes), [{ auto nameAttr = name ? $_builder.getStringAttr(*name) : StringAttr(); + IntegerAttr numRegionsAttr; build($_builder, $_state, $_builder.getType(), nameAttr, operandValues, attrValues, $_builder.getStrArrayAttr(attrNames), - resultTypes); + resultTypes, numRegionsAttr); }]>, ]; let extraClassDeclaration = [{ diff --git a/mlir/include/mlir/Dialect/PDLInterp/IR/PDLInterpOps.td b/mlir/include/mlir/Dialect/PDLInterp/IR/PDLInterpOps.td index 0f4292bf055f9..64d5b533f2780 100644 --- a/mlir/include/mlir/Dialect/PDLInterp/IR/PDLInterpOps.td +++ b/mlir/include/mlir/Dialect/PDLInterp/IR/PDLInterpOps.td @@ -430,15 +430,24 @@ def PDLInterp_CreateOperationOp Variadic:$inputAttributes, StrArrayAttr:$inputAttributeNames, Variadic>:$inputResultTypes, - UnitAttr:$inferredResultTypes); + UnitAttr:$inferredResultTypes, + OptionalAttr:$numRegions); let results = (outs PDL_Operation:$resultOp); let builders = [ OpBuilder<(ins "StringRef":$name, "ValueRange":$types, "bool":$inferredResultTypes, "ValueRange":$operands, "ValueRange":$attributes, "ArrayAttr":$attributeNames), [{ + IntegerAttr numRegionsAttr; build($_builder, $_state, $_builder.getType(), name, - operands, attributes, attributeNames, types, inferredResultTypes); + operands, attributes, attributeNames, types, inferredResultTypes, numRegionsAttr); + }]>, + OpBuilder<(ins "StringRef":$name, "ValueRange":$types, + "bool":$inferredResultTypes, "ValueRange":$operands, + "ValueRange":$attributes, "ArrayAttr":$attributeNames, "uint32_t":$numRegions), [{ + auto numRegionsAttr = $_builder.getUI32IntegerAttr(numRegions); + build($_builder, $_state, $_builder.getType(), name, + operands, attributes, attributeNames, types, inferredResultTypes, numRegionsAttr); }]> ]; let assemblyFormat = [{ diff --git a/mlir/include/mlir/Tools/PDLL/AST/Nodes.h b/mlir/include/mlir/Tools/PDLL/AST/Nodes.h index 2281115dddddb..79b3a2fff0b9f 100644 --- a/mlir/include/mlir/Tools/PDLL/AST/Nodes.h +++ b/mlir/include/mlir/Tools/PDLL/AST/Nodes.h @@ -501,12 +501,11 @@ class OperationExpr final private llvm::TrailingObjects { public: - static OperationExpr *create(Context &ctx, SMRange loc, - const ods::Operation *odsOp, - const OpNameDecl *nameDecl, - ArrayRef operands, - ArrayRef resultTypes, - ArrayRef attributes); + static OperationExpr * + create(Context &ctx, SMRange loc, const ods::Operation *odsOp, + const OpNameDecl *nameDecl, ArrayRef operands, + ArrayRef resultTypes, + ArrayRef attributes, unsigned numRegions); /// Return the name of the operation, or None if there isn't one. Optional getName() const; @@ -542,19 +541,22 @@ class OperationExpr final return const_cast(this)->getAttributes(); } + unsigned getNumRegions() const { return numRegions; } + private: OperationExpr(SMRange loc, Type type, const OpNameDecl *nameDecl, unsigned numOperands, unsigned numResultTypes, - unsigned numAttributes, SMRange nameLoc) + unsigned numAttributes, unsigned numRegions, SMRange nameLoc) : Base(loc, type), nameDecl(nameDecl), numOperands(numOperands), numResultTypes(numResultTypes), numAttributes(numAttributes), - nameLoc(nameLoc) {} + numRegions(numRegions), nameLoc(nameLoc) {} /// The name decl of this expression. const OpNameDecl *nameDecl; - /// The number of operands, result types, and attributes of the operation. - unsigned numOperands, numResultTypes, numAttributes; + /// The number of operands, result types, attributes and regions of the + /// operation. + unsigned numOperands, numResultTypes, numAttributes, numRegions; /// The location of the operation name in the expression if it has a name. SMRange nameLoc; diff --git a/mlir/lib/Conversion/PDLToPDLInterp/PDLToPDLInterp.cpp b/mlir/lib/Conversion/PDLToPDLInterp/PDLToPDLInterp.cpp index 2c08f3483454a..59b4d06f9eca7 100644 --- a/mlir/lib/Conversion/PDLToPDLInterp/PDLToPDLInterp.cpp +++ b/mlir/lib/Conversion/PDLToPDLInterp/PDLToPDLInterp.cpp @@ -767,9 +767,10 @@ void PatternLowering::generateRewriter( // Create the new operation. Location loc = operationOp.getLoc(); + auto numRegions = operationOp.getNumRegions().value_or(0); Value createdOp = builder.create( loc, *operationOp.getOpName(), types, hasInferredResultTypes, operands, - attributes, operationOp.getAttributeValueNames()); + attributes, operationOp.getAttributeValueNames(), numRegions); rewriteValues[operationOp.getOp()] = createdOp; // Generate accesses for any results that have their types constrained. diff --git a/mlir/lib/Dialect/PDL/IR/PDL.cpp b/mlir/lib/Dialect/PDL/IR/PDL.cpp index e33ba7153968e..a6cc84d8ae971 100644 --- a/mlir/lib/Dialect/PDL/IR/PDL.cpp +++ b/mlir/lib/Dialect/PDL/IR/PDL.cpp @@ -141,6 +141,8 @@ LogicalResult OperandsOp::verify() { return verifyHasBindingUse(*this); } // pdl::OperationOp //===----------------------------------------------------------------------===// +/// Handles parsing of OperationOpAttributes, e.g. {"attr" = %attribute}. +/// Also allows empty `{}` static ParseResult parseOperationOpAttributes( OpAsmParser &p, SmallVectorImpl &attrOperands, @@ -148,27 +150,42 @@ static ParseResult parseOperationOpAttributes( Builder &builder = p.getBuilder(); SmallVector attrNames; if (succeeded(p.parseOptionalLBrace())) { - auto parseOperands = [&]() { - StringAttr nameAttr; - OpAsmParser::UnresolvedOperand operand; - if (p.parseAttribute(nameAttr) || p.parseEqual() || - p.parseOperand(operand)) + if (failed(p.parseOptionalRBrace())) { + auto parseOperands = [&]() { + StringAttr nameAttr; + OpAsmParser::UnresolvedOperand operand; + if (p.parseAttribute(nameAttr) || p.parseEqual() || + p.parseOperand(operand)) + return failure(); + attrNames.push_back(nameAttr); + attrOperands.push_back(operand); + return success(); + }; + if (p.parseCommaSeparatedList(parseOperands) || p.parseRBrace()) return failure(); - attrNames.push_back(nameAttr); - attrOperands.push_back(operand); - return success(); - }; - if (p.parseCommaSeparatedList(parseOperands) || p.parseRBrace()) - return failure(); + } } attrNamesAttr = builder.getArrayAttr(attrNames); return success(); } +/// Handles printing of OperationOpAttributes, e.g. {"attr" = %attribute}. +/// Prints empty `{}` when it would not be possible to discern the attr-dict +/// otherwise. static void printOperationOpAttributes(OpAsmPrinter &p, OperationOp op, OperandRange attrArgs, ArrayAttr attrNames) { - if (attrNames.empty()) + /// Only omit printing empty `{}` if there are no other attributes that have + /// to be printed later because otherwise we could not discern the attr dict. + static const SmallVector specialAttrs = { + "operand_segment_sizes", "attributeValueNames", "opName"}; + bool onlySpecialAttrs = + llvm::all_of(op->getAttrs(), [&](const NamedAttribute &attr) { + return llvm::any_of(specialAttrs, [&](const StringRef &predefinedAttr) { + return attr.getName() == predefinedAttr; + }); + }); + if (attrNames.empty() && onlySpecialAttrs) return; p << " {"; interleaveComma(llvm::seq(0, attrNames.size()), p, diff --git a/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp b/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp index e8a61ef4c6a4d..0178e311b5803 100644 --- a/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp +++ b/mlir/lib/Dialect/PDLInterp/IR/PDLInterp.cpp @@ -64,6 +64,8 @@ LogicalResult CreateOperationOp::verify() { return success(); } +/// Handles parsing of OperationOpAttributes, e.g. {"attr" = %attribute}. +/// Also allows empty `{}` static ParseResult parseCreateOperationOpAttributes( OpAsmParser &p, SmallVectorImpl &attrOperands, @@ -71,28 +73,36 @@ static ParseResult parseCreateOperationOpAttributes( Builder &builder = p.getBuilder(); SmallVector attrNames; if (succeeded(p.parseOptionalLBrace())) { - auto parseOperands = [&]() { - StringAttr nameAttr; - OpAsmParser::UnresolvedOperand operand; - if (p.parseAttribute(nameAttr) || p.parseEqual() || - p.parseOperand(operand)) + if (failed(p.parseOptionalRBrace())) { + auto parseOperands = [&]() { + StringAttr nameAttr; + OpAsmParser::UnresolvedOperand operand; + if (p.parseAttribute(nameAttr) || p.parseEqual() || + p.parseOperand(operand)) + return failure(); + attrNames.push_back(nameAttr); + attrOperands.push_back(operand); + return success(); + }; + if (p.parseCommaSeparatedList(parseOperands) || p.parseRBrace()) return failure(); - attrNames.push_back(nameAttr); - attrOperands.push_back(operand); - return success(); - }; - if (p.parseCommaSeparatedList(parseOperands) || p.parseRBrace()) - return failure(); + } } attrNamesAttr = builder.getArrayAttr(attrNames); return success(); } +/// Handles printing of OperationOpAttributes, e.g. {"attr" = %attribute}. +/// Prints empty `{}` when it would not be possible to discern the attr-dict +/// otherwise. static void printCreateOperationOpAttributes(OpAsmPrinter &p, CreateOperationOp op, OperandRange attrArgs, ArrayAttr attrNames) { - if (attrNames.empty()) + /// Only omit printing empty `{}` if we have result types because otherwise we + /// could not discern the attr dict. + unsigned numResultTypes = op.getODSOperandIndexAndLength(2).second; + if (attrNames.empty() && (numResultTypes > 0 || op.getInferredResultTypes())) return; p << " {"; interleaveComma(llvm::seq(0, attrNames.size()), p, diff --git a/mlir/lib/Rewrite/ByteCode.cpp b/mlir/lib/Rewrite/ByteCode.cpp index 03024659fafef..b179841fec0de 100644 --- a/mlir/lib/Rewrite/ByteCode.cpp +++ b/mlir/lib/Rewrite/ByteCode.cpp @@ -885,6 +885,14 @@ void Generator::generate(pdl_interp::CreateOperationOp op, writer.append(kInferTypesMarker); else writer.appendPDLValueList(op.getInputResultTypes()); + + // Add number of regions + if (IntegerAttr attr = op.getNumRegionsAttr()) { + writer.append(ByteCodeField(attr.getUInt())); + } else { + unsigned numRegions = 0; + writer.append(ByteCodeField(numRegions)); + } } void Generator::generate(pdl_interp::CreateRangeOp op, ByteCodeWriter &writer) { // Append the correct opcode for the range type. @@ -1663,6 +1671,12 @@ void ByteCodeExecutor::executeCreateOperation(PatternRewriter &rewriter, } } + // handle regions: + unsigned numRegions = read(); + for (unsigned i = 0; i < numRegions; i++) { + state.addRegion(); + } + Operation *resultOp = rewriter.create(state); memory[memIndex] = resultOp; diff --git a/mlir/lib/Tools/PDLL/AST/NodePrinter.cpp b/mlir/lib/Tools/PDLL/AST/NodePrinter.cpp index cc27bb6cdfceb..14ec633e7c2b1 100644 --- a/mlir/lib/Tools/PDLL/AST/NodePrinter.cpp +++ b/mlir/lib/Tools/PDLL/AST/NodePrinter.cpp @@ -247,7 +247,7 @@ void NodePrinter::printImpl(const MemberAccessExpr *expr) { void NodePrinter::printImpl(const OperationExpr *expr) { os << "OperationExpr " << expr << " Type<"; print(expr->getType()); - os << ">\n"; + os << "> numRegions:" << expr->getNumRegions() << "\n"; printChildren(expr->getNameDecl()); printChildren("Operands", expr->getOperands()); diff --git a/mlir/lib/Tools/PDLL/AST/Nodes.cpp b/mlir/lib/Tools/PDLL/AST/Nodes.cpp index 0129f9fd1d0bc..ab49ab16b486c 100644 --- a/mlir/lib/Tools/PDLL/AST/Nodes.cpp +++ b/mlir/lib/Tools/PDLL/AST/Nodes.cpp @@ -301,11 +301,13 @@ MemberAccessExpr *MemberAccessExpr::create(Context &ctx, SMRange loc, // OperationExpr //===----------------------------------------------------------------------===// -OperationExpr * -OperationExpr::create(Context &ctx, SMRange loc, const ods::Operation *odsOp, - const OpNameDecl *name, ArrayRef operands, - ArrayRef resultTypes, - ArrayRef attributes) { +OperationExpr *OperationExpr::create(Context &ctx, SMRange loc, + const ods::Operation *odsOp, + const OpNameDecl *name, + ArrayRef operands, + ArrayRef resultTypes, + ArrayRef attributes, + unsigned numRegions) { unsigned allocSize = OperationExpr::totalSizeToAlloc( operands.size() + resultTypes.size(), attributes.size()); @@ -315,7 +317,7 @@ OperationExpr::create(Context &ctx, SMRange loc, const ods::Operation *odsOp, Type resultType = OperationType::get(ctx, name->getName(), odsOp); OperationExpr *opExpr = new (rawData) OperationExpr(loc, resultType, name, operands.size(), resultTypes.size(), - attributes.size(), name->getLoc()); + attributes.size(), numRegions, name->getLoc()); std::uninitialized_copy(operands.begin(), operands.end(), opExpr->getOperands().begin()); std::uninitialized_copy(resultTypes.begin(), resultTypes.end(), diff --git a/mlir/lib/Tools/PDLL/CodeGen/MLIRGen.cpp b/mlir/lib/Tools/PDLL/CodeGen/MLIRGen.cpp index 33ede71b987b9..3e67a0eadbce6 100644 --- a/mlir/lib/Tools/PDLL/CodeGen/MLIRGen.cpp +++ b/mlir/lib/Tools/PDLL/CodeGen/MLIRGen.cpp @@ -515,8 +515,14 @@ Value CodeGen::genExprImpl(const ast::OperationExpr *expr) { for (const ast::Expr *result : expr->getResultTypes()) results.push_back(genSingleExpr(result)); - return builder.create(loc, opName, operands, attrNames, - attrValues, results); + auto operationOp = builder.create( + loc, opName, operands, attrNames, attrValues, results); + + // numRegions + if (expr->getNumRegions() > 0) + operationOp.setNumRegions(expr->getNumRegions()); + + return operationOp; } Value CodeGen::genExprImpl(const ast::RangeExpr *expr) { diff --git a/mlir/lib/Tools/PDLL/Parser/Parser.cpp b/mlir/lib/Tools/PDLL/Parser/Parser.cpp index c1a1abb437f4b..bb3cf0d065d34 100644 --- a/mlir/lib/Tools/PDLL/Parser/Parser.cpp +++ b/mlir/lib/Tools/PDLL/Parser/Parser.cpp @@ -421,7 +421,8 @@ class Parser { OpResultTypeContext resultTypeContext, SmallVectorImpl &operands, MutableArrayRef attributes, - SmallVectorImpl &results); + SmallVectorImpl &results, + unsigned numRegions); LogicalResult validateOperationOperands(SMRange loc, Optional name, const ods::Operation *odsOp, @@ -2129,8 +2130,23 @@ Parser::parseOperationExpr(OpResultTypeContext inputResultTypeContext) { resultTypeContext = OpResultTypeContext::Interface; } + // Parse list of regions + unsigned numRegions = 0; + if (consumeIf(Token::l_paren)) { + do { + if (failed(parseToken(Token::l_brace, "expected `{` to open region"))) + return failure(); + if (failed(parseToken(Token::r_brace, "expected `}` to close region"))) + return failure(); + numRegions++; + } while (consumeIf(Token::comma)); + if (failed(parseToken(Token::r_paren, "expected `)` to close region " + "list"))) + return failure(); + } + return createOperationExpr(loc, *opNameDecl, resultTypeContext, operands, - attributes, resultTypes); + attributes, resultTypes, numRegions); } FailureOr Parser::parseTupleExpr() { @@ -2807,7 +2823,7 @@ FailureOr Parser::createOperationExpr( OpResultTypeContext resultTypeContext, SmallVectorImpl &operands, MutableArrayRef attributes, - SmallVectorImpl &results) { + SmallVectorImpl &results, unsigned numRegions) { Optional opNameRef = name->getName(); const ods::Operation *odsOp = lookupODSOperation(opNameRef); @@ -2844,7 +2860,7 @@ FailureOr Parser::createOperationExpr( } return ast::OperationExpr::create(ctx, loc, odsOp, name, operands, results, - attributes); + attributes, numRegions); } LogicalResult diff --git a/mlir/test/Conversion/PDLToPDLInterp/pdl-to-pdl-interp-rewriter.mlir b/mlir/test/Conversion/PDLToPDLInterp/pdl-to-pdl-interp-rewriter.mlir index e5a84d69dcad9..436be348ae6f7 100644 --- a/mlir/test/Conversion/PDLToPDLInterp/pdl-to-pdl-interp-rewriter.mlir +++ b/mlir/test/Conversion/PDLToPDLInterp/pdl-to-pdl-interp-rewriter.mlir @@ -260,3 +260,19 @@ module @range_op { } } } + +// ----- + +// CHECK-LABEL: module @create_empty_region +module @create_empty_region { + // CHECK: module @rewriters + // CHECK: func @pdl_generated_rewriter() + // CHECK: %[[UNUSED:.*]] = pdl_interp.create_operation "bar.op" {} {numRegions = 1 : ui32} + // CHECK: pdl_interp.finalize + pdl.pattern : benefit(1) { + %root = operation "foo.op" + rewrite %root { + %unused = operation "bar.op" {} {"numRegions" = 1 : ui32} + } + } +} diff --git a/mlir/test/Dialect/PDL/ops.mlir b/mlir/test/Dialect/PDL/ops.mlir index 0f6e98fe591b4..194c463a69795 100644 --- a/mlir/test/Dialect/PDL/ops.mlir +++ b/mlir/test/Dialect/PDL/ops.mlir @@ -173,3 +173,39 @@ pdl.pattern @attribute_with_loc : benefit(1) { %root = operation {"attribute" = %attr} rewrite %root with "rewriter" } + +// ----- + +// Check that we can attach a numRegions attribute to pdl.operation + +pdl.pattern @num_regions : benefit(1) { + // CHECK-GENERIC: "pdl.attribute" + // CHECK-GENERIC-NOT: value = loc + %root = operation {} {numRegions = 1 : ui32} + rewrite %root with "rewriter" +} + +// ----- + +pdl.pattern @num_regions_with_attr_vals : benefit(1) { + %attr = attribute + %root = operation {"attribute" = %attr} {numRegions = 1 : ui32} + rewrite %root with "rewriter" +} + +// ----- + +pdl.pattern @num_regions_with_operands : benefit(1) { + %types = types + %operands = operands : %types + %root = operation (%operands : !pdl.range) {} {numRegions = 1 : ui32} + rewrite %root with "rewriter" +} + +// ----- + +pdl.pattern @num_regions_with_results : benefit(1) { + %types = types + %root = operation -> (%types : !pdl.range) {numRegions = 1 : ui32} + rewrite %root with "rewriter" +} diff --git a/mlir/test/Dialect/PDLInterp/ops.mlir b/mlir/test/Dialect/PDLInterp/ops.mlir index ef9cefe813a5b..ebb8c1aaf7e88 100644 --- a/mlir/test/Dialect/PDLInterp/ops.mlir +++ b/mlir/test/Dialect/PDLInterp/ops.mlir @@ -28,6 +28,9 @@ func.func @operations(%attribute: !pdl.attribute, // inferred results %op4 = pdl_interp.create_operation "arith.constant" -> + // region + %op5 = pdl_interp.create_operation "foo.op" {} {"numRegions" = 1 : ui32} + pdl_interp.finalize } diff --git a/mlir/test/Rewrite/pdl-bytecode.mlir b/mlir/test/Rewrite/pdl-bytecode.mlir index 14fd008d1ff42..ff50365797475 100644 --- a/mlir/test/Rewrite/pdl-bytecode.mlir +++ b/mlir/test/Rewrite/pdl-bytecode.mlir @@ -599,6 +599,37 @@ module @ir attributes { test.create_op_infer_results } { // ----- +// Test support for creating an operation with an empty region. +module @patterns { + pdl_interp.func @matcher(%root : !pdl.operation) { + pdl_interp.check_operation_name of %root is "test.op" -> ^pat, ^end + + ^pat: + pdl_interp.record_match @rewriters::@success(%root : !pdl.operation) : benefit(1), loc([%root]) -> ^end + + ^end: + pdl_interp.finalize + } + + module @rewriters { + pdl_interp.func @success(%root : !pdl.operation) { + %op = pdl_interp.create_operation "test.success" {} {"numRegions" = 1 : ui32} + pdl_interp.erase %root + pdl_interp.finalize + } + } +} + +// CHECK-LABEL: test.create_op_with_empty_region +// CHECK: "test.success"() ({ +// CHECK-NEXT: }) : () -> () +module @ir attributes { test.create_op_with_empty_region } { + "test.op"() : () -> () +} + +// ----- + + //===----------------------------------------------------------------------===// // pdl_interp::CreateRangeOp //===----------------------------------------------------------------------===// diff --git a/mlir/test/mlir-pdll/Parser/expr.pdll b/mlir/test/mlir-pdll/Parser/expr.pdll index 0736962dada78..a9e24a7b20b4b 100644 --- a/mlir/test/mlir-pdll/Parser/expr.pdll +++ b/mlir/test/mlir-pdll/Parser/expr.pdll @@ -241,6 +241,29 @@ Pattern { // ----- +// Test parsing of an op with an empty region + +// CHECK: Module +// CHECK: -PatternDecl {{.*}} +// CHECK: -RewriteStmt {{.*}} +// CHECK: -OperationExpr {{.*}} Type> +// CHECK-SAME: numRegions:0 +// CHECK: `Operands` +// CHECK: `Result Types` +// CHECK: `Attributes` +// CHECK: -OperationExpr {{.*}} Type> +// CHECK-SAME: numRegions:1 +// CHECK: `Operands` +// CHECK: `Result Types` +// CHECK: `Attributes` +Pattern { + rewrite op(operand: Value) {attr=attr: Attr} -> (type : Type) with { + op(operand) {attr=attr} -> (type) ({}); + }; +} + +// ----- + //===----------------------------------------------------------------------===// // TupleExpr //===----------------------------------------------------------------------===// diff --git a/mlir/test/mlir-pdll/Parser/stmt-failure.pdll b/mlir/test/mlir-pdll/Parser/stmt-failure.pdll index aaf45a9603352..e9afa29921be2 100644 --- a/mlir/test/mlir-pdll/Parser/stmt-failure.pdll +++ b/mlir/test/mlir-pdll/Parser/stmt-failure.pdll @@ -97,6 +97,27 @@ Pattern { // ----- +Pattern { + // CHECK: expected `)` to close region list + let foo = op<>() ({}; +} + +// ----- + +Pattern { + // CHECK: expected `}` to close region + let foo = op<>() ({; +} + +// ----- + +Pattern { + // CHECK: expected `{` to open region + let foo = op<>() (); +} + +// ----- + Pattern { // CHECK: expected expression let foo: Value<>;