diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index dafb7ce381..3cf2389ac8 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -502,9 +502,8 @@ FunctionEmitter::StatementBlock::~StatementBlock() = default;
 void FunctionEmitter::PushNewStatementBlock(const Construct* construct,
                                             uint32_t end_id,
                                             CompletionAction action) {
-  statements_stack_.emplace_back(
-      StatementBlock{construct, end_id, action,
-                     std::make_unique<ast::BlockStatement>(), nullptr});
+  statements_stack_.emplace_back(StatementBlock{
+      construct, end_id, action, create<ast::BlockStatement>(), nullptr});
 }
 
 void FunctionEmitter::PushGuard(const std::string& guard_name,
@@ -515,11 +514,11 @@ void FunctionEmitter::PushGuard(const std::string& guard_name,
   // if-selection with a then-clause ending at the same block
   // as the statement block at the top of the stack.
   const auto& top = statements_stack_.back();
-  auto cond = std::make_unique<ast::IdentifierExpression>(guard_name);
-  auto body = std::make_unique<ast::BlockStatement>();
-  auto* const guard_stmt = AddStatement(std::make_unique<ast::IfStatement>(
-                                            std::move(cond), std::move(body)))
-                               ->AsIf();
+  auto cond = create<ast::IdentifierExpression>(guard_name);
+  auto body = create<ast::BlockStatement>();
+  auto* const guard_stmt =
+      AddStatement(create<ast::IfStatement>(std::move(cond), std::move(body)))
+          ->AsIf();
   PushNewStatementBlock(top.construct_, end_id,
                         [guard_stmt](StatementBlock* s) {
                           guard_stmt->set_body(std::move(s->statements_));
@@ -530,10 +529,10 @@ void FunctionEmitter::PushTrueGuard(uint32_t end_id) {
   assert(!statements_stack_.empty());
   const auto& top = statements_stack_.back();
   auto cond = MakeTrue();
-  auto body = std::make_unique<ast::BlockStatement>();
-  auto* const guard_stmt = AddStatement(std::make_unique<ast::IfStatement>(
-                                            std::move(cond), std::move(body)))
-                               ->AsIf();
+  auto body = create<ast::BlockStatement>();
+  auto* const guard_stmt =
+      AddStatement(create<ast::IfStatement>(std::move(cond), std::move(body)))
+          ->AsIf();
   guard_stmt->set_condition(MakeTrue());
   PushNewStatementBlock(top.construct_, end_id,
                         [guard_stmt](StatementBlock* s) {
@@ -649,13 +648,12 @@ bool FunctionEmitter::EmitFunctionDeclaration() {
     return false;
   }
 
-  auto ast_fn =
-      std::make_unique<ast::Function>(name, std::move(ast_params), ret_ty,
-                                      std::make_unique<ast::BlockStatement>());
+  auto ast_fn = create<ast::Function>(name, std::move(ast_params), ret_ty,
+                                      create<ast::BlockStatement>());
 
   if (ep_info_ != nullptr) {
     ast_fn->add_decoration(
-        std::make_unique<ast::StageDecoration>(ep_info_->stage, Source{}));
+        create<ast::StageDecoration>(ep_info_->stage, Source{}));
   }
 
   ast_module_.AddFunction(std::move(ast_fn));
@@ -1707,8 +1705,7 @@ bool FunctionEmitter::EmitFunctionVariables() {
           parser_impl_.MakeConstantExpression(inst.GetSingleWordInOperand(1))
               .expr);
     }
-    auto var_decl_stmt =
-        std::make_unique<ast::VariableDeclStatement>(std::move(var));
+    auto var_decl_stmt = create<ast::VariableDeclStatement>(std::move(var));
     AddStatementForInstruction(std::move(var_decl_stmt), inst);
     // Save this as an already-named value.
     identifier_values_.insert(inst.result_id());
@@ -1723,7 +1720,7 @@ TypedExpression FunctionEmitter::MakeExpression(uint32_t id) {
   if (identifier_values_.count(id) || parser_impl_.IsScalarSpecConstant(id)) {
     return TypedExpression(
         parser_impl_.ConvertType(def_use_mgr_->GetDef(id)->type_id()),
-        std::make_unique<ast::IdentifierExpression>(namer_.Name(id)));
+        create<ast::IdentifierExpression>(namer_.Name(id)));
   }
   if (singly_used_values_.count(id)) {
     auto expr = std::move(singly_used_values_[id]);
@@ -1742,9 +1739,9 @@ TypedExpression FunctionEmitter::MakeExpression(uint32_t id) {
   switch (inst->opcode()) {
     case SpvOpVariable:
       // This occurs for module-scope variables.
-      return TypedExpression(parser_impl_.ConvertType(inst->type_id()),
-                             std::make_unique<ast::IdentifierExpression>(
-                                 namer_.Name(inst->result_id())));
+      return TypedExpression(
+          parser_impl_.ConvertType(inst->type_id()),
+          create<ast::IdentifierExpression>(namer_.Name(inst->result_id())));
     default:
       break;
   }
@@ -1973,21 +1970,20 @@ bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
   const std::string guard_name = block_info.flow_guard_name;
   if (!guard_name.empty()) {
     // Declare the guard variable just before the "if", initialized to true.
-    auto guard_var = std::make_unique<ast::Variable>(
+    auto guard_var = create<ast::Variable>(
         guard_name, ast::StorageClass::kFunction, parser_impl_.BoolType());
     guard_var->set_constructor(MakeTrue());
-    auto guard_decl =
-        std::make_unique<ast::VariableDeclStatement>(std::move(guard_var));
+    auto guard_decl = create<ast::VariableDeclStatement>(std::move(guard_var));
     AddStatement(std::move(guard_decl));
   }
 
   const auto condition_id =
       block_info.basic_block->terminator()->GetSingleWordInOperand(0);
   auto cond = MakeExpression(condition_id).expr;
-  auto body = std::make_unique<ast::BlockStatement>();
-  auto* const if_stmt = AddStatement(std::make_unique<ast::IfStatement>(
-                                         std::move(cond), std::move(body)))
-                            ->AsIf();
+  auto body = create<ast::BlockStatement>();
+  auto* const if_stmt =
+      AddStatement(create<ast::IfStatement>(std::move(cond), std::move(body)))
+          ->AsIf();
 
   // Generate the code for the condition.
 
@@ -2040,17 +2036,18 @@ bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
 
   auto push_else = [this, if_stmt, else_end, construct]() {
     // Push the else clause onto the stack first.
-    PushNewStatementBlock(construct, else_end, [if_stmt](StatementBlock* s) {
-      // Only set the else-clause if there are statements to fill it.
-      if (!s->statements_->empty()) {
-        // The "else" consists of the statement list from the top of statments
-        // stack, without an elseif condition.
-        ast::ElseStatementList else_stmts;
-        else_stmts.emplace_back(std::make_unique<ast::ElseStatement>(
-            nullptr, std::move(s->statements_)));
-        if_stmt->set_else_statements(std::move(else_stmts));
-      }
-    });
+    PushNewStatementBlock(
+        construct, else_end, [this, if_stmt](StatementBlock* s) {
+          // Only set the else-clause if there are statements to fill it.
+          if (!s->statements_->empty()) {
+            // The "else" consists of the statement list from the top of
+            // statments stack, without an elseif condition.
+            ast::ElseStatementList else_stmts;
+            else_stmts.emplace_back(
+                create<ast::ElseStatement>(nullptr, std::move(s->statements_)));
+            if_stmt->set_else_statements(std::move(else_stmts));
+          }
+        });
   };
 
   if (GetBlockInfo(else_end)->pos < GetBlockInfo(then_end)->pos) {
@@ -2099,7 +2096,7 @@ bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
   const auto* branch = block_info.basic_block->terminator();
 
   auto* const switch_stmt =
-      AddStatement(std::make_unique<ast::SwitchStatement>())->AsSwitch();
+      AddStatement(create<ast::SwitchStatement>())->AsSwitch();
   const auto selector_id = branch->GetSingleWordInOperand(0);
   // Generate the code for the selector.
   auto selector = MakeExpression(selector_id);
@@ -2111,7 +2108,7 @@ bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
       construct, construct->end_id, [switch_stmt](StatementBlock* s) {
         switch_stmt->set_body(std::move(*std::move(s->cases_)));
       });
-  statements_stack_.back().cases_ = std::make_unique<ast::CaseStatementList>();
+  statements_stack_.back().cases_ = create<ast::CaseStatementList>();
   // Grab a pointer to the case list.  It will get buried in the statement block
   // stack.
   auto* cases = statements_stack_.back().cases_.get();
@@ -2157,8 +2154,8 @@ bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
   for (size_t i = last_clause_index;; --i) {
     // Create the case clause.  Temporarily put it in the wrong order
     // on the case statement list.
-    cases->emplace_back(std::make_unique<ast::CaseStatement>(
-        std::make_unique<ast::BlockStatement>()));
+    cases->emplace_back(
+        create<ast::CaseStatement>(create<ast::BlockStatement>()));
     auto* clause = cases->back().get();
 
     // Create a list of integer literals for the selector values leading to
@@ -2175,10 +2172,10 @@ bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
         const uint32_t value32 = uint32_t(value & 0xFFFFFFFF);
         if (selector.type->is_unsigned_scalar_or_vector()) {
           selectors.emplace_back(
-              std::make_unique<ast::UintLiteral>(selector.type, value32));
+              create<ast::UintLiteral>(selector.type, value32));
         } else {
           selectors.emplace_back(
-              std::make_unique<ast::SintLiteral>(selector.type, value32));
+              create<ast::SintLiteral>(selector.type, value32));
         }
       }
       clause->set_selectors(std::move(selectors));
@@ -2195,9 +2192,9 @@ bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
     if ((default_info == clause_heads[i]) && has_selectors &&
         construct->ContainsPos(default_info->pos)) {
       // Generate a default clause with a just fallthrough.
-      auto stmts = std::make_unique<ast::BlockStatement>();
-      stmts->append(std::make_unique<ast::FallthroughStatement>());
-      auto case_stmt = std::make_unique<ast::CaseStatement>(std::move(stmts));
+      auto stmts = create<ast::BlockStatement>();
+      stmts->append(create<ast::FallthroughStatement>());
+      auto case_stmt = create<ast::CaseStatement>(std::move(stmts));
       cases->emplace_back(std::move(case_stmt));
     }
 
@@ -2214,10 +2211,10 @@ bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
 }
 
 bool FunctionEmitter::EmitLoopStart(const Construct* construct) {
-  auto* loop = AddStatement(std::make_unique<ast::LoopStatement>(
-                                std::make_unique<ast::BlockStatement>(),
-                                std::make_unique<ast::BlockStatement>()))
-                   ->AsLoop();
+  auto* loop =
+      AddStatement(create<ast::LoopStatement>(create<ast::BlockStatement>(),
+                                              create<ast::BlockStatement>()))
+          ->AsLoop();
   PushNewStatementBlock(
       construct, construct->end_id,
       [loop](StatementBlock* s) { loop->set_body(std::move(s->statements_)); });
@@ -2244,18 +2241,17 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
   const auto& terminator = *(block_info.basic_block->terminator());
   switch (terminator.opcode()) {
     case SpvOpReturn:
-      AddStatement(std::make_unique<ast::ReturnStatement>());
+      AddStatement(create<ast::ReturnStatement>());
       return true;
     case SpvOpReturnValue: {
       auto value = MakeExpression(terminator.GetSingleWordInOperand(0));
-      AddStatement(
-          std::make_unique<ast::ReturnStatement>(std::move(value.expr)));
+      AddStatement(create<ast::ReturnStatement>(std::move(value.expr)));
     }
       return true;
     case SpvOpKill:
       // For now, assume SPIR-V OpKill has same semantics as WGSL discard.
       // TODO(dneto): https://github.com/gpuweb/gpuweb/issues/676
-      AddStatement(std::make_unique<ast::DiscardStatement>());
+      AddStatement(create<ast::DiscardStatement>());
       return true;
     case SpvOpUnreachable:
       // Translate as if it's a return. This avoids the problem where WGSL
@@ -2263,10 +2259,10 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
       {
         const auto* result_type = type_mgr_->GetType(function_.type_id());
         if (result_type->AsVoid() != nullptr) {
-          AddStatement(std::make_unique<ast::ReturnStatement>());
+          AddStatement(create<ast::ReturnStatement>());
         } else {
           auto* ast_type = parser_impl_.ConvertType(function_.type_id());
-          AddStatement(std::make_unique<ast::ReturnStatement>(
+          AddStatement(create<ast::ReturnStatement>(
               parser_impl_.MakeNullValue(ast_type)));
         }
       }
@@ -2350,7 +2346,7 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchDetailed(
       break;
     case EdgeKind::kSwitchBreak: {
       if (forced) {
-        return std::make_unique<ast::BreakStatement>();
+        return create<ast::BreakStatement>();
       }
       // Unless forced, don't bother with a break at the end of a case/default
       // clause.
@@ -2375,10 +2371,10 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchDetailed(
         }
       }
       // We need a break.
-      return std::make_unique<ast::BreakStatement>();
+      return create<ast::BreakStatement>();
     }
     case EdgeKind::kLoopBreak:
-      return std::make_unique<ast::BreakStatement>();
+      return create<ast::BreakStatement>();
     case EdgeKind::kLoopContinue:
       // An unconditional continue to the next block is redundant and ugly.
       // Skip it in that case.
@@ -2386,7 +2382,7 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchDetailed(
         break;
       }
       // Otherwise, emit a regular continue statement.
-      return std::make_unique<ast::ContinueStatement>();
+      return create<ast::ContinueStatement>();
     case EdgeKind::kIfBreak: {
       const auto& flow_guard =
           GetBlockInfo(dest_info.header_for_merge)->flow_guard_name;
@@ -2395,9 +2391,8 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchDetailed(
           *flow_guard_name_ptr = flow_guard;
         }
         // Signal an exit from the branch.
-        return std::make_unique<ast::AssignmentStatement>(
-            std::make_unique<ast::IdentifierExpression>(flow_guard),
-            MakeFalse());
+        return create<ast::AssignmentStatement>(
+            create<ast::IdentifierExpression>(flow_guard), MakeFalse());
       }
 
       // For an unconditional branch, the break out to an if-selection
@@ -2405,7 +2400,7 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranchDetailed(
       break;
     }
     case EdgeKind::kCaseFallThrough:
-      return std::make_unique<ast::FallthroughStatement>();
+      return create<ast::FallthroughStatement>();
     case EdgeKind::kForward:
       // Unconditional forward branch is implicit.
       break;
@@ -2420,19 +2415,19 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeSimpleIf(
   if ((then_stmt == nullptr) && (else_stmt == nullptr)) {
     return nullptr;
   }
-  auto if_stmt = std::make_unique<ast::IfStatement>(
-      std::move(condition), std::make_unique<ast::BlockStatement>());
+  auto if_stmt = create<ast::IfStatement>(std::move(condition),
+                                          create<ast::BlockStatement>());
   if (then_stmt != nullptr) {
-    auto stmts = std::make_unique<ast::BlockStatement>();
+    auto stmts = create<ast::BlockStatement>();
     stmts->append(std::move(then_stmt));
     if_stmt->set_body(std::move(stmts));
   }
   if (else_stmt != nullptr) {
-    auto stmts = std::make_unique<ast::BlockStatement>();
+    auto stmts = create<ast::BlockStatement>();
     stmts->append(std::move(else_stmt));
     ast::ElseStatementList else_stmts;
     else_stmts.emplace_back(
-        std::make_unique<ast::ElseStatement>(nullptr, std::move(stmts)));
+        create<ast::ElseStatement>(nullptr, std::move(stmts)));
     if_stmt->set_else_statements(std::move(else_stmts));
   }
   return if_stmt;
@@ -2479,7 +2474,7 @@ bool FunctionEmitter::EmitConditionalCaseFallThrough(
     AddStatement(
         MakeSimpleIf(std::move(cond), std::move(other_branch), nullptr));
   }
-  AddStatement(std::make_unique<ast::FallthroughStatement>());
+  AddStatement(create<ast::FallthroughStatement>());
 
   return success();
 }
@@ -2506,7 +2501,7 @@ bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
     assert(def_inst);
     auto* ast_type =
         RemapStorageClass(parser_impl_.ConvertType(def_inst->type_id()), id);
-    AddStatement(std::make_unique<ast::VariableDeclStatement>(
+    AddStatement(create<ast::VariableDeclStatement>(
         parser_impl_.MakeVariable(id, ast::StorageClass::kFunction, ast_type)));
     // Save this as an already-named value.
     identifier_values_.insert(id);
@@ -2517,10 +2512,10 @@ bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
     assert(def_inst);
     const auto phi_var_name = GetDefInfo(id)->phi_var;
     assert(!phi_var_name.empty());
-    auto var = std::make_unique<ast::Variable>(
-        phi_var_name, ast::StorageClass::kFunction,
-        parser_impl_.ConvertType(def_inst->type_id()));
-    AddStatement(std::make_unique<ast::VariableDeclStatement>(std::move(var)));
+    auto var =
+        create<ast::Variable>(phi_var_name, ast::StorageClass::kFunction,
+                              parser_impl_.ConvertType(def_inst->type_id()));
+    AddStatement(create<ast::VariableDeclStatement>(std::move(var)));
   }
 
   // Emit regular statements.
@@ -2550,9 +2545,8 @@ bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
     for (auto assignment : block_info.phi_assignments) {
       const auto var_name = GetDefInfo(assignment.phi_id)->phi_var;
       auto expr = MakeExpression(assignment.value);
-      AddStatement(std::make_unique<ast::AssignmentStatement>(
-          std::make_unique<ast::IdentifierExpression>(var_name),
-          std::move(expr.expr)));
+      AddStatement(create<ast::AssignmentStatement>(
+          create<ast::IdentifierExpression>(var_name), std::move(expr.expr)));
     }
   }
 
@@ -2574,7 +2568,7 @@ bool FunctionEmitter::EmitConstDefinition(
   ast_const->set_constructor(std::move(ast_expr.expr));
   ast_const->set_is_const(true);
   AddStatementForInstruction(
-      std::make_unique<ast::VariableDeclStatement>(std::move(ast_const)), inst);
+      create<ast::VariableDeclStatement>(std::move(ast_const)), inst);
   // Save this as an already-named value.
   identifier_values_.insert(inst.result_id());
   return success();
@@ -2588,8 +2582,8 @@ bool FunctionEmitter::EmitConstDefOrWriteToHoistedVar(
   if (def_info && def_info->requires_hoisted_def) {
     // Emit an assignment of the expression to the hoisted variable.
     AddStatementForInstruction(
-        std::make_unique<ast::AssignmentStatement>(
-            std::make_unique<ast::IdentifierExpression>(namer_.Name(result_id)),
+        create<ast::AssignmentStatement>(
+            create<ast::IdentifierExpression>(namer_.Name(result_id)),
             std::move(ast_expr.expr)),
         inst);
     return true;
@@ -2654,7 +2648,7 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
       // TODO(dneto): Order of evaluation?
       auto lhs = MakeExpression(ptr_id);
       auto rhs = MakeExpression(value_id);
-      AddStatementForInstruction(std::make_unique<ast::AssignmentStatement>(
+      AddStatementForInstruction(create<ast::AssignmentStatement>(
                                      std::move(lhs.expr), std::move(rhs.expr)),
                                  inst);
       return success();
@@ -2678,9 +2672,9 @@ bool FunctionEmitter::EmitStatement(const spvtools::opt::Instruction& inst) {
     }
     case SpvOpPhi: {
       // Emit a read from the associated state variable.
-      auto expr = TypedExpression(
-          parser_impl_.ConvertType(inst.type_id()),
-          std::make_unique<ast::IdentifierExpression>(def_info->phi_var));
+      auto expr =
+          TypedExpression(parser_impl_.ConvertType(inst.type_id()),
+                          create<ast::IdentifierExpression>(def_info->phi_var));
       return EmitConstDefOrWriteToHoistedVar(inst, std::move(expr));
     }
     case SpvOpFunctionCall:
@@ -2714,7 +2708,7 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
   if (binary_op != ast::BinaryOp::kNone) {
     auto arg0 = MakeOperand(inst, 0);
     auto arg1 = MakeOperand(inst, 1);
-    auto binary_expr = std::make_unique<ast::BinaryExpression>(
+    auto binary_expr = create<ast::BinaryExpression>(
         binary_op, std::move(arg0.expr), std::move(arg1.expr));
     TypedExpression result(ast_type, std::move(binary_expr));
     return parser_impl_.RectifyForcedResultType(std::move(result), opcode,
@@ -2724,8 +2718,8 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
   auto unary_op = ast::UnaryOp::kNegation;
   if (GetUnaryOp(opcode, &unary_op)) {
     auto arg0 = MakeOperand(inst, 0);
-    auto unary_expr = std::make_unique<ast::UnaryOpExpression>(
-        unary_op, std::move(arg0.expr));
+    auto unary_expr =
+        create<ast::UnaryOpExpression>(unary_op, std::move(arg0.expr));
     TypedExpression result(ast_type, std::move(unary_expr));
     return parser_impl_.RectifyForcedResultType(std::move(result), opcode,
                                                 arg0.type);
@@ -2735,10 +2729,9 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
   if (unary_builtin_name != nullptr) {
     ast::ExpressionList params;
     params.emplace_back(MakeOperand(inst, 0).expr);
-    return {ast_type,
-            std::make_unique<ast::CallExpression>(
-                std::make_unique<ast::IdentifierExpression>(unary_builtin_name),
-                std::move(params))};
+    return {ast_type, create<ast::CallExpression>(
+                          create<ast::IdentifierExpression>(unary_builtin_name),
+                          std::move(params))};
   }
 
   const auto intrinsic = GetIntrinsic(opcode);
@@ -2751,7 +2744,7 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
   }
 
   if (opcode == SpvOpBitcast) {
-    return {ast_type, std::make_unique<ast::BitcastExpression>(
+    return {ast_type, create<ast::BitcastExpression>(
                           ast_type, MakeOperand(inst, 0).expr)};
   }
 
@@ -2759,10 +2752,10 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
   if (negated_op != ast::BinaryOp::kNone) {
     auto arg0 = MakeOperand(inst, 0);
     auto arg1 = MakeOperand(inst, 1);
-    auto binary_expr = std::make_unique<ast::BinaryExpression>(
+    auto binary_expr = create<ast::BinaryExpression>(
         negated_op, std::move(arg0.expr), std::move(arg1.expr));
-    auto negated_expr = std::make_unique<ast::UnaryOpExpression>(
-        ast::UnaryOp::kNot, std::move(binary_expr));
+    auto negated_expr = create<ast::UnaryOpExpression>(ast::UnaryOp::kNot,
+                                                       std::move(binary_expr));
     return {ast_type, std::move(negated_expr)};
   }
 
@@ -2780,7 +2773,7 @@ TypedExpression FunctionEmitter::MaybeEmitCombinatorialValue(
     for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
       operands.emplace_back(MakeOperand(inst, iarg).expr);
     }
-    return {ast_type, std::make_unique<ast::TypeConstructorExpression>(
+    return {ast_type, create<ast::TypeConstructorExpression>(
                           ast_type, std::move(operands))};
   }
 
@@ -2838,15 +2831,14 @@ TypedExpression FunctionEmitter::EmitGlslStd450ExtInst(
     return {};
   }
 
-  auto func = std::make_unique<ast::IdentifierExpression>(name);
+  auto func = create<ast::IdentifierExpression>(name);
   ast::ExpressionList operands;
   // All parameters to GLSL.std.450 extended instructions are IDs.
   for (uint32_t iarg = 2; iarg < inst.NumInOperands(); ++iarg) {
     operands.emplace_back(MakeOperand(inst, iarg).expr);
   }
   auto* ast_type = parser_impl_.ConvertType(inst.type_id());
-  auto call = std::make_unique<ast::CallExpression>(std::move(func),
-                                                    std::move(operands));
+  auto call = create<ast::CallExpression>(std::move(func), std::move(operands));
   return {ast_type, std::move(call)};
 }
 
@@ -2913,7 +2905,7 @@ TypedExpression FunctionEmitter::MakeAccessChain(
       // Replace the gl_PerVertex reference with the gl_Position reference
       ptr_ty_id = builtin_position_info.member_pointer_type_id;
       current_expr.expr =
-          std::make_unique<ast::IdentifierExpression>(namer_.Name(base_id));
+          create<ast::IdentifierExpression>(namer_.Name(base_id));
       current_expr.type = parser_impl_.ConvertType(ptr_ty_id);
     }
   }
@@ -2963,13 +2955,13 @@ TypedExpression FunctionEmitter::MakeAccessChain(
                    << " is too big. Max handled index is "
                    << ((sizeof(swizzles) / sizeof(swizzles[0])) - 1);
           }
-          auto letter_index = std::make_unique<ast::IdentifierExpression>(
-              swizzles[index_const_val]);
-          next_expr = std::make_unique<ast::MemberAccessorExpression>(
+          auto letter_index =
+              create<ast::IdentifierExpression>(swizzles[index_const_val]);
+          next_expr = create<ast::MemberAccessorExpression>(
               std::move(current_expr.expr), std::move(letter_index));
         } else {
           // Non-constant index. Use array syntax
-          next_expr = std::make_unique<ast::ArrayAccessorExpression>(
+          next_expr = create<ast::ArrayAccessorExpression>(
               std::move(current_expr.expr),
               std::move(MakeOperand(inst, index).expr));
         }
@@ -2978,20 +2970,20 @@ TypedExpression FunctionEmitter::MakeAccessChain(
         break;
       case SpvOpTypeMatrix:
         // Use array syntax.
-        next_expr = std::make_unique<ast::ArrayAccessorExpression>(
+        next_expr = create<ast::ArrayAccessorExpression>(
             std::move(current_expr.expr),
             std::move(MakeOperand(inst, index).expr));
         // All matrix components are the same type.
         pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
         break;
       case SpvOpTypeArray:
-        next_expr = std::make_unique<ast::ArrayAccessorExpression>(
+        next_expr = create<ast::ArrayAccessorExpression>(
             std::move(current_expr.expr),
             std::move(MakeOperand(inst, index).expr));
         pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
         break;
       case SpvOpTypeRuntimeArray:
-        next_expr = std::make_unique<ast::ArrayAccessorExpression>(
+        next_expr = create<ast::ArrayAccessorExpression>(
             std::move(current_expr.expr),
             std::move(MakeOperand(inst, index).expr));
         pointee_type_id = pointee_type_inst->GetSingleWordInOperand(0);
@@ -3011,10 +3003,10 @@ TypedExpression FunctionEmitter::MakeAccessChain(
                  << pointee_type_id << " having " << num_members << " members";
           return {};
         }
-        auto member_access = std::make_unique<ast::IdentifierExpression>(
+        auto member_access = create<ast::IdentifierExpression>(
             namer_.GetMemberName(pointee_type_id, uint32_t(index_const_val)));
 
-        next_expr = std::make_unique<ast::MemberAccessorExpression>(
+        next_expr = create<ast::MemberAccessorExpression>(
             std::move(current_expr.expr), std::move(member_access));
         pointee_type_id = pointee_type_inst->GetSingleWordInOperand(
             static_cast<uint32_t>(index_const_val));
@@ -3047,10 +3039,10 @@ TypedExpression FunctionEmitter::MakeCompositeExtract(
   // expressions.
   TypedExpression current_expr(MakeOperand(inst, 0));
 
-  auto make_index = [](uint32_t literal) {
+  auto make_index = [this](uint32_t literal) {
     ast::type::U32Type u32;
-    return std::make_unique<ast::ScalarConstructorExpression>(
-        std::make_unique<ast::UintLiteral>(&u32, literal));
+    return create<ast::ScalarConstructorExpression>(
+        create<ast::UintLiteral>(&u32, literal));
   };
   static const char* swizzles[] = {"x", "y", "z", "w"};
 
@@ -3088,8 +3080,8 @@ TypedExpression FunctionEmitter::MakeCompositeExtract(
                  << ((sizeof(swizzles) / sizeof(swizzles[0])) - 1);
         }
         auto letter_index =
-            std::make_unique<ast::IdentifierExpression>(swizzles[index_val]);
-        next_expr = std::make_unique<ast::MemberAccessorExpression>(
+            create<ast::IdentifierExpression>(swizzles[index_val]);
+        next_expr = create<ast::MemberAccessorExpression>(
             std::move(current_expr.expr), std::move(letter_index));
         // All vector components are the same type.
         current_type_id = current_type_inst->GetSingleWordInOperand(0);
@@ -3110,7 +3102,7 @@ TypedExpression FunctionEmitter::MakeCompositeExtract(
                  << ((sizeof(swizzles) / sizeof(swizzles[0])) - 1);
         }
         // Use array syntax.
-        next_expr = std::make_unique<ast::ArrayAccessorExpression>(
+        next_expr = create<ast::ArrayAccessorExpression>(
             std::move(current_expr.expr), make_index(index_val));
         // All matrix components are the same type.
         current_type_id = current_type_inst->GetSingleWordInOperand(0);
@@ -3120,7 +3112,7 @@ TypedExpression FunctionEmitter::MakeCompositeExtract(
         // The array size could be a spec constant, and so it's not always
         // statically checkable.  Instead, rely on a runtime index clamp
         // or runtime check to keep this safe.
-        next_expr = std::make_unique<ast::ArrayAccessorExpression>(
+        next_expr = create<ast::ArrayAccessorExpression>(
             std::move(current_expr.expr), make_index(index_val));
         current_type_id = current_type_inst->GetSingleWordInOperand(0);
         break;
@@ -3135,10 +3127,10 @@ TypedExpression FunctionEmitter::MakeCompositeExtract(
                  << current_type_id << " having " << num_members << " members";
           return {};
         }
-        auto member_access = std::make_unique<ast::IdentifierExpression>(
+        auto member_access = create<ast::IdentifierExpression>(
             namer_.GetMemberName(current_type_id, uint32_t(index_val)));
 
-        next_expr = std::make_unique<ast::MemberAccessorExpression>(
+        next_expr = create<ast::MemberAccessorExpression>(
             std::move(current_expr.expr), std::move(member_access));
         current_type_id = current_type_inst->GetSingleWordInOperand(index_val);
         break;
@@ -3155,14 +3147,14 @@ TypedExpression FunctionEmitter::MakeCompositeExtract(
 }
 
 std::unique_ptr<ast::Expression> FunctionEmitter::MakeTrue() const {
-  return std::make_unique<ast::ScalarConstructorExpression>(
-      std::make_unique<ast::BoolLiteral>(parser_impl_.BoolType(), true));
+  return create<ast::ScalarConstructorExpression>(
+      create<ast::BoolLiteral>(parser_impl_.BoolType(), true));
 }
 
 std::unique_ptr<ast::Expression> FunctionEmitter::MakeFalse() const {
   ast::type::BoolType bool_type;
-  return std::make_unique<ast::ScalarConstructorExpression>(
-      std::make_unique<ast::BoolLiteral>(parser_impl_.BoolType(), false));
+  return create<ast::ScalarConstructorExpression>(
+      create<ast::BoolLiteral>(parser_impl_.BoolType(), false));
 }
 
 TypedExpression FunctionEmitter::MakeVectorShuffle(
@@ -3188,15 +3180,15 @@ TypedExpression FunctionEmitter::MakeVectorShuffle(
     const auto index = inst.GetSingleWordInOperand(i);
     if (index < vec0_len) {
       assert(index < sizeof(swizzles) / sizeof(swizzles[0]));
-      values.emplace_back(std::make_unique<ast::MemberAccessorExpression>(
+      values.emplace_back(create<ast::MemberAccessorExpression>(
           MakeExpression(vec0_id).expr,
-          std::make_unique<ast::IdentifierExpression>(swizzles[index])));
+          create<ast::IdentifierExpression>(swizzles[index])));
     } else if (index < vec0_len + vec1_len) {
       const auto sub_index = index - vec0_len;
       assert(sub_index < sizeof(swizzles) / sizeof(swizzles[0]));
-      values.emplace_back(std::make_unique<ast::MemberAccessorExpression>(
+      values.emplace_back(create<ast::MemberAccessorExpression>(
           MakeExpression(vec1_id).expr,
-          std::make_unique<ast::IdentifierExpression>(swizzles[sub_index])));
+          create<ast::IdentifierExpression>(swizzles[sub_index])));
     } else if (index == 0xFFFFFFFF) {
       // By rule, this maps to OpUndef.  Instead, make it zero.
       values.emplace_back(parser_impl_.MakeNullValue(result_type->type()));
@@ -3206,7 +3198,7 @@ TypedExpression FunctionEmitter::MakeVectorShuffle(
       return {};
     }
   }
-  return {result_type, std::make_unique<ast::TypeConstructorExpression>(
+  return {result_type, create<ast::TypeConstructorExpression>(
                            result_type, std::move(values))};
 }
 
@@ -3497,28 +3489,27 @@ TypedExpression FunctionEmitter::MakeNumericConversion(
 
   ast::ExpressionList params;
   params.push_back(std::move(arg_expr.expr));
-  TypedExpression result(expr_type,
-                         std::make_unique<ast::TypeConstructorExpression>(
-                             expr_type, std::move(params)));
+  TypedExpression result(expr_type, create<ast::TypeConstructorExpression>(
+                                        expr_type, std::move(params)));
 
   if (requested_type == expr_type) {
     return result;
   }
-  return {requested_type, std::make_unique<ast::BitcastExpression>(
+  return {requested_type, create<ast::BitcastExpression>(
                               requested_type, std::move(result.expr))};
 }
 
 bool FunctionEmitter::EmitFunctionCall(const spvtools::opt::Instruction& inst) {
   // We ignore function attributes such as Inline, DontInline, Pure, Const.
-  auto function = std::make_unique<ast::IdentifierExpression>(
+  auto function = create<ast::IdentifierExpression>(
       namer_.Name(inst.GetSingleWordInOperand(0)));
 
   ast::ExpressionList params;
   for (uint32_t iarg = 1; iarg < inst.NumInOperands(); ++iarg) {
     params.emplace_back(MakeOperand(inst, iarg).expr);
   }
-  auto call_expr = std::make_unique<ast::CallExpression>(std::move(function),
-                                                         std::move(params));
+  auto call_expr =
+      create<ast::CallExpression>(std::move(function), std::move(params));
   auto* result_type = parser_impl_.ConvertType(inst.type_id());
   if (!result_type) {
     return Fail() << "internal error: no mapped type result of call: "
@@ -3528,8 +3519,7 @@ bool FunctionEmitter::EmitFunctionCall(const spvtools::opt::Instruction& inst) {
   if (result_type->IsVoid()) {
     return nullptr !=
            AddStatementForInstruction(
-               std::make_unique<ast::CallStatement>(std::move(call_expr)),
-               inst);
+               create<ast::CallStatement>(std::move(call_expr)), inst);
   }
 
   return EmitConstDefOrWriteToHoistedVar(inst,
@@ -3541,15 +3531,15 @@ TypedExpression FunctionEmitter::MakeIntrinsicCall(
   const auto intrinsic = GetIntrinsic(inst.opcode());
   std::ostringstream ss;
   ss << intrinsic;
-  auto ident = std::make_unique<ast::IdentifierExpression>(ss.str());
+  auto ident = create<ast::IdentifierExpression>(ss.str());
   ident->set_intrinsic(intrinsic);
 
   ast::ExpressionList params;
   for (uint32_t iarg = 0; iarg < inst.NumInOperands(); ++iarg) {
     params.emplace_back(MakeOperand(inst, iarg).expr);
   }
-  auto call_expr = std::make_unique<ast::CallExpression>(std::move(ident),
-                                                         std::move(params));
+  auto call_expr =
+      create<ast::CallExpression>(std::move(ident), std::move(params));
   auto* result_type = parser_impl_.ConvertType(inst.type_id());
   if (!result_type) {
     Fail() << "internal error: no mapped type result of call: "
@@ -3578,10 +3568,9 @@ TypedExpression FunctionEmitter::MakeSimpleSelect(
     params.push_back(std::move(operand2.expr));
     // The condition goes last.
     params.push_back(std::move(condition.expr));
-    return {operand1.type,
-            std::make_unique<ast::CallExpression>(
-                std::make_unique<ast::IdentifierExpression>("select"),
-                std::move(params))};
+    return {operand1.type, create<ast::CallExpression>(
+                               create<ast::IdentifierExpression>("select"),
+                               std::move(params))};
   }
   return {};
 }
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index c9df72aaa1..19323888e8 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -21,6 +21,7 @@
 #include <string>
 #include <unordered_map>
 #include <unordered_set>
+#include <utility>
 #include <vector>
 
 #include "source/opt/basic_block.h"
@@ -799,6 +800,13 @@ class FunctionEmitter {
   /// @returns a boolean false expression.
   std::unique_ptr<ast::Expression> MakeFalse() const;
 
+  /// @return a `std::unique_ptr` to a new `T` constructed with `args`
+  /// @param args the arguments to forward to the constructor for `T`
+  template <typename T, typename... ARGS>
+  std::unique_ptr<T> create(ARGS&&... args) const {
+    return std::make_unique<T>(std::forward<ARGS>(args)...);
+  }
+
   ParserImpl& parser_impl_;
   ast::Module& ast_module_;
   spvtools::opt::IRContext& ir_context_;
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index c36ec78834..63cc7c7834 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -392,8 +392,7 @@ ParserImpl::ConvertMemberDecoration(uint32_t struct_type_id,
             << ShowType(struct_type_id);
         return nullptr;
       }
-      return std::make_unique<ast::StructMemberOffsetDecoration>(decoration[1],
-                                                                 Source{});
+      return create<ast::StructMemberOffsetDecoration>(decoration[1], Source{});
     case SpvDecorationNonReadable:
       // WGSL doesn't have a member decoration for this.  Silently drop it.
       return nullptr;
@@ -782,8 +781,7 @@ bool ParserImpl::ApplyArrayDecorations(
                       << ": multiple ArrayStride decorations";
       }
       ast::ArrayDecorationList decos;
-      decos.push_back(
-          std::make_unique<ast::StrideDecoration>(stride, Source{}));
+      decos.push_back(create<ast::StrideDecoration>(stride, Source{}));
       ast_type->set_decorations(std::move(decos));
     } else {
       return Fail() << "invalid array type ID " << type_id
@@ -806,10 +804,10 @@ ast::type::Type* ParserImpl::ConvertType(
     const auto decoration = struct_decorations[0][0];
     if (decoration == SpvDecorationBlock) {
       ast_struct_decorations.push_back(
-          std::make_unique<ast::StructBlockDecoration>(Source{}));
+          create<ast::StructBlockDecoration>(Source{}));
     } else if (decoration == SpvDecorationBufferBlock) {
       ast_struct_decorations.push_back(
-          std::make_unique<ast::StructBlockDecoration>(Source{}));
+          create<ast::StructBlockDecoration>(Source{}));
       remap_buffer_block_type_.insert(type_id);
     } else {
       Fail() << "struct with ID " << type_id
@@ -879,14 +877,14 @@ ast::type::Type* ParserImpl::ConvertType(
       ++num_non_writable_members;
     }
     const auto member_name = namer_.GetMemberName(type_id, member_index);
-    auto ast_struct_member = std::make_unique<ast::StructMember>(
+    auto ast_struct_member = create<ast::StructMember>(
         member_name, ast_member_ty, std::move(ast_member_decorations));
     ast_members.push_back(std::move(ast_struct_member));
   }
 
   // Now make the struct.
-  auto ast_struct = std::make_unique<ast::Struct>(
-      std::move(ast_struct_decorations), std::move(ast_members));
+  auto ast_struct = create<ast::Struct>(std::move(ast_struct_decorations),
+                                        std::move(ast_members));
 
   namer_.SuggestSanitizedName(type_id, "S");
   auto ast_struct_type = std::make_unique<ast::type::StructType>(
@@ -971,8 +969,8 @@ bool ParserImpl::EmitScalarSpecConstants() {
       case SpvOpSpecConstantTrue:
       case SpvOpSpecConstantFalse: {
         ast_type = ConvertType(inst.type_id());
-        ast_expr = std::make_unique<ast::ScalarConstructorExpression>(
-            std::make_unique<ast::BoolLiteral>(
+        ast_expr =
+            create<ast::ScalarConstructorExpression>(create<ast::BoolLiteral>(
                 ast_type, inst.opcode() == SpvOpSpecConstantTrue));
         break;
       }
@@ -980,19 +978,19 @@ bool ParserImpl::EmitScalarSpecConstants() {
         ast_type = ConvertType(inst.type_id());
         const uint32_t literal_value = inst.GetSingleWordInOperand(0);
         if (ast_type->IsI32()) {
-          ast_expr = std::make_unique<ast::ScalarConstructorExpression>(
-              std::make_unique<ast::SintLiteral>(
+          ast_expr =
+              create<ast::ScalarConstructorExpression>(create<ast::SintLiteral>(
                   ast_type, static_cast<int32_t>(literal_value)));
         } else if (ast_type->IsU32()) {
-          ast_expr = std::make_unique<ast::ScalarConstructorExpression>(
-              std::make_unique<ast::UintLiteral>(
+          ast_expr =
+              create<ast::ScalarConstructorExpression>(create<ast::UintLiteral>(
                   ast_type, static_cast<uint32_t>(literal_value)));
         } else if (ast_type->IsF32()) {
           float float_value;
           // Copy the bits so we can read them as a float.
           std::memcpy(&float_value, &literal_value, sizeof(float_value));
-          ast_expr = std::make_unique<ast::ScalarConstructorExpression>(
-              std::make_unique<ast::FloatLiteral>(ast_type, float_value));
+          ast_expr = create<ast::ScalarConstructorExpression>(
+              create<ast::FloatLiteral>(ast_type, float_value));
         } else {
           return Fail() << " invalid result type for OpSpecConstant "
                         << inst.PrettyPrint();
@@ -1008,8 +1006,7 @@ bool ParserImpl::EmitScalarSpecConstants() {
       ast::VariableDecorationList spec_id_decos;
       for (const auto& deco : GetDecorationsFor(inst.result_id())) {
         if ((deco.size() == 2) && (deco[0] == SpvDecorationSpecId)) {
-          auto cid =
-              std::make_unique<ast::ConstantIdDecoration>(deco[1], Source{});
+          auto cid = create<ast::ConstantIdDecoration>(deco[1], Source{});
           spec_id_decos.push_back(std::move(cid));
           break;
         }
@@ -1020,8 +1017,7 @@ bool ParserImpl::EmitScalarSpecConstants() {
         ast_var->set_constructor(std::move(ast_expr));
         ast_module_.AddGlobalVariable(std::move(ast_var));
       } else {
-        auto ast_deco_var =
-            std::make_unique<ast::DecoratedVariable>(std::move(ast_var));
+        auto ast_deco_var = create<ast::DecoratedVariable>(std::move(ast_var));
         ast_deco_var->set_is_const(true);
         ast_deco_var->set_constructor(std::move(ast_expr));
         ast_deco_var->set_decorations(std::move(spec_id_decos));
@@ -1140,13 +1136,13 @@ bool ParserImpl::EmitModuleScopeVariables() {
     // Make sure the variable has a name.
     namer_.SuggestSanitizedName(builtin_position_.per_vertex_var_id,
                                 "gl_Position");
-    auto var = std::make_unique<ast::DecoratedVariable>(MakeVariable(
+    auto var = create<ast::DecoratedVariable>(MakeVariable(
         builtin_position_.per_vertex_var_id,
         enum_converter_.ToStorageClass(builtin_position_.storage_class),
         ConvertType(builtin_position_.member_type_id)));
     ast::VariableDecorationList decos;
-    decos.push_back(std::make_unique<ast::BuiltinDecoration>(
-        ast::Builtin::kPosition, Source{}));
+    decos.push_back(
+        create<ast::BuiltinDecoration>(ast::Builtin::kPosition, Source{}));
     var->set_decorations(std::move(decos));
 
     ast_module_.AddGlobalVariable(std::move(var));
@@ -1171,7 +1167,7 @@ std::unique_ptr<ast::Variable> ParserImpl::MakeVariable(uint32_t id,
         std::make_unique<ast::type::AccessControlType>(access, type));
   }
 
-  auto ast_var = std::make_unique<ast::Variable>(namer_.Name(id), sc, type);
+  auto ast_var = create<ast::Variable>(namer_.Name(id), sc, type);
 
   ast::VariableDecorationList ast_decorations;
   for (auto& deco : GetDecorationsFor(id)) {
@@ -1191,7 +1187,7 @@ std::unique_ptr<ast::Variable> ParserImpl::MakeVariable(uint32_t id,
         return nullptr;
       }
       ast_decorations.emplace_back(
-          std::make_unique<ast::BuiltinDecoration>(ast_builtin, Source{}));
+          create<ast::BuiltinDecoration>(ast_builtin, Source{}));
     }
     if (deco[0] == SpvDecorationLocation) {
       if (deco.size() != 2) {
@@ -1200,7 +1196,7 @@ std::unique_ptr<ast::Variable> ParserImpl::MakeVariable(uint32_t id,
         return nullptr;
       }
       ast_decorations.emplace_back(
-          std::make_unique<ast::LocationDecoration>(deco[1], Source{}));
+          create<ast::LocationDecoration>(deco[1], Source{}));
     }
     if (deco[0] == SpvDecorationDescriptorSet) {
       if (deco.size() == 1) {
@@ -1209,7 +1205,7 @@ std::unique_ptr<ast::Variable> ParserImpl::MakeVariable(uint32_t id,
         return nullptr;
       }
       ast_decorations.emplace_back(
-          std::make_unique<ast::SetDecoration>(deco[1], Source{}));
+          create<ast::SetDecoration>(deco[1], Source{}));
     }
     if (deco[0] == SpvDecorationBinding) {
       if (deco.size() == 1) {
@@ -1218,12 +1214,11 @@ std::unique_ptr<ast::Variable> ParserImpl::MakeVariable(uint32_t id,
         return nullptr;
       }
       ast_decorations.emplace_back(
-          std::make_unique<ast::BindingDecoration>(deco[1], Source{}));
+          create<ast::BindingDecoration>(deco[1], Source{}));
     }
   }
   if (!ast_decorations.empty()) {
-    auto decorated_var =
-        std::make_unique<ast::DecoratedVariable>(std::move(ast_var));
+    auto decorated_var = create<ast::DecoratedVariable>(std::move(ast_var));
     decorated_var->set_decorations(std::move(ast_decorations));
     ast_var = std::move(decorated_var);
   }
@@ -1263,26 +1258,26 @@ TypedExpression ParserImpl::MakeConstantExpression(uint32_t id) {
   // Currently "null<type>" is missing from the WGSL parser.
   // See https://bugs.chromium.org/p/tint/issues/detail?id=34
   if (ast_type->IsU32()) {
-    return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
-                          std::make_unique<ast::UintLiteral>(
-                              ast_type, spirv_const->GetU32()))};
+    return {ast_type,
+            create<ast::ScalarConstructorExpression>(
+                create<ast::UintLiteral>(ast_type, spirv_const->GetU32()))};
   }
   if (ast_type->IsI32()) {
-    return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
-                          std::make_unique<ast::SintLiteral>(
-                              ast_type, spirv_const->GetS32()))};
+    return {ast_type,
+            create<ast::ScalarConstructorExpression>(
+                create<ast::SintLiteral>(ast_type, spirv_const->GetS32()))};
   }
   if (ast_type->IsF32()) {
-    return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
-                          std::make_unique<ast::FloatLiteral>(
-                              ast_type, spirv_const->GetFloat()))};
+    return {ast_type,
+            create<ast::ScalarConstructorExpression>(
+                create<ast::FloatLiteral>(ast_type, spirv_const->GetFloat()))};
   }
   if (ast_type->IsBool()) {
     const bool value = spirv_const->AsNullConstant()
                            ? false
                            : spirv_const->AsBoolConstant()->value();
-    return {ast_type, std::make_unique<ast::ScalarConstructorExpression>(
-                          std::make_unique<ast::BoolLiteral>(ast_type, value))};
+    return {ast_type, create<ast::ScalarConstructorExpression>(
+                          create<ast::BoolLiteral>(ast_type, value))};
   }
   auto* spirv_composite_const = spirv_const->AsCompositeConstant();
   if (spirv_composite_const != nullptr) {
@@ -1308,8 +1303,8 @@ TypedExpression ParserImpl::MakeConstantExpression(uint32_t id) {
       ast_components.emplace_back(std::move(ast_component.expr));
     }
     return {original_ast_type,
-            std::make_unique<ast::TypeConstructorExpression>(
-                original_ast_type, std::move(ast_components))};
+            create<ast::TypeConstructorExpression>(original_ast_type,
+                                                   std::move(ast_components))};
   }
   auto* spirv_null_const = spirv_const->AsNullConstant();
   if (spirv_null_const != nullptr) {
@@ -1336,20 +1331,20 @@ std::unique_ptr<ast::Expression> ParserImpl::MakeNullValue(
   type = type->UnwrapIfNeeded();
 
   if (type->IsBool()) {
-    return std::make_unique<ast::ScalarConstructorExpression>(
-        std::make_unique<ast::BoolLiteral>(type, false));
+    return create<ast::ScalarConstructorExpression>(
+        create<ast::BoolLiteral>(type, false));
   }
   if (type->IsU32()) {
-    return std::make_unique<ast::ScalarConstructorExpression>(
-        std::make_unique<ast::UintLiteral>(type, 0u));
+    return create<ast::ScalarConstructorExpression>(
+        create<ast::UintLiteral>(type, 0u));
   }
   if (type->IsI32()) {
-    return std::make_unique<ast::ScalarConstructorExpression>(
-        std::make_unique<ast::SintLiteral>(type, 0));
+    return create<ast::ScalarConstructorExpression>(
+        create<ast::SintLiteral>(type, 0));
   }
   if (type->IsF32()) {
-    return std::make_unique<ast::ScalarConstructorExpression>(
-        std::make_unique<ast::FloatLiteral>(type, 0.0f));
+    return create<ast::ScalarConstructorExpression>(
+        create<ast::FloatLiteral>(type, 0.0f));
   }
   if (type->IsVector()) {
     const auto* vec_ty = type->AsVector();
@@ -1357,8 +1352,8 @@ std::unique_ptr<ast::Expression> ParserImpl::MakeNullValue(
     for (size_t i = 0; i < vec_ty->size(); ++i) {
       ast_components.emplace_back(MakeNullValue(vec_ty->type()));
     }
-    return std::make_unique<ast::TypeConstructorExpression>(
-        type, std::move(ast_components));
+    return create<ast::TypeConstructorExpression>(type,
+                                                  std::move(ast_components));
   }
   if (type->IsMatrix()) {
     const auto* mat_ty = type->AsMatrix();
@@ -1370,8 +1365,8 @@ std::unique_ptr<ast::Expression> ParserImpl::MakeNullValue(
     for (size_t i = 0; i < mat_ty->columns(); ++i) {
       ast_components.emplace_back(MakeNullValue(column_ty));
     }
-    return std::make_unique<ast::TypeConstructorExpression>(
-        type, std::move(ast_components));
+    return create<ast::TypeConstructorExpression>(type,
+                                                  std::move(ast_components));
   }
   if (type->IsArray()) {
     auto* arr_ty = type->AsArray();
@@ -1379,8 +1374,8 @@ std::unique_ptr<ast::Expression> ParserImpl::MakeNullValue(
     for (size_t i = 0; i < arr_ty->size(); ++i) {
       ast_components.emplace_back(MakeNullValue(arr_ty->type()));
     }
-    return std::make_unique<ast::TypeConstructorExpression>(
-        original_type, std::move(ast_components));
+    return create<ast::TypeConstructorExpression>(original_type,
+                                                  std::move(ast_components));
   }
   if (type->IsStruct()) {
     auto* struct_ty = type->AsStruct();
@@ -1388,8 +1383,8 @@ std::unique_ptr<ast::Expression> ParserImpl::MakeNullValue(
     for (auto& member : struct_ty->impl()->members()) {
       ast_components.emplace_back(MakeNullValue(member->type()));
     }
-    return std::make_unique<ast::TypeConstructorExpression>(
-        original_type, std::move(ast_components));
+    return create<ast::TypeConstructorExpression>(original_type,
+                                                  std::move(ast_components));
   }
   Fail() << "can't make null value for type: " << type->type_name();
   return nullptr;
@@ -1416,15 +1411,15 @@ TypedExpression ParserImpl::RectifyOperandSignedness(SpvOp op,
     auto* unsigned_ty = unsigned_type_for_[type];
     if (unsigned_ty != nullptr) {
       // Conversion is required.
-      return {unsigned_ty, std::make_unique<ast::BitcastExpression>(
+      return {unsigned_ty, create<ast::BitcastExpression>(
                                unsigned_ty, std::move(expr.expr))};
     }
   } else if (requires_signed) {
     auto* signed_ty = signed_type_for_[type];
     if (signed_ty != nullptr) {
       // Conversion is required.
-      return {signed_ty, std::make_unique<ast::BitcastExpression>(
-                             signed_ty, std::move(expr.expr))};
+      return {signed_ty,
+              create<ast::BitcastExpression>(signed_ty, std::move(expr.expr))};
     }
   }
   // We should not reach here.
@@ -1487,8 +1482,8 @@ TypedExpression ParserImpl::RectifyForcedResultType(
   if ((forced_result_ty == nullptr) || (forced_result_ty == expr.type)) {
     return expr;
   }
-  return {expr.type, std::make_unique<ast::BitcastExpression>(
-                         expr.type, std::move(expr.expr))};
+  return {expr.type,
+          create<ast::BitcastExpression>(expr.type, std::move(expr.expr))};
 }
 
 bool ParserImpl::EmitFunctions() {
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 70373972aa..5eaaf08e35 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -432,6 +432,13 @@ class ParserImpl : Reader {
   bool ApplyArrayDecorations(const spvtools::opt::analysis::Type* spv_type,
                              ast::type::ArrayType* ast_type);
 
+  /// @return a `std::unique_ptr` to a new `T` constructed with `args`
+  /// @param args the arguments to forward to the constructor for `T`
+  template <typename T, typename... ARGS>
+  std::unique_ptr<T> create(ARGS&&... args) const {
+    return std::make_unique<T>(std::forward<ARGS>(args)...);
+  }
+
   // The SPIR-V binary we're parsing
   std::vector<uint32_t> spv_binary_;