Move function validation from Validator to Resolver

* Fixed many tests that now failed validation. Most of the time,
functions declared that they returned a type, but with no return
statement.
* ProgramBuilder::WrapInFunction now returns the function is creates,
and std::moves its StatementList.
* ProgramBuilder: Added Return function to create ast::ReturnStatements
more easily.

Bug: tint:642
Change-Id: I3011314e66e264ebd7b89bf9271392391be6a0e5
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/45382
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 324c224..9614ed4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -469,6 +469,7 @@
     program_test.cc
     resolver/assignment_validation_test.cc
     resolver/decoration_validation_test.cc
+    resolver/function_validation_test.cc
     resolver/host_shareable_validation_test.cc
     resolver/intrinsic_test.cc
     resolver/is_host_shareable_test.cc
diff --git a/src/program_builder.cc b/src/program_builder.cc
index c65a044..ac5d0d1 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -97,8 +97,8 @@
   return stmt;
 }
 
-void ProgramBuilder::WrapInFunction(ast::StatementList stmts) {
-  Func("test_function", {}, ty.void_(), stmts, {});
+ast::Function* ProgramBuilder::WrapInFunction(ast::StatementList stmts) {
+  return Func("test_function", {}, ty.void_(), std::move(stmts), {});
 }
 
 }  // namespace tint
diff --git a/src/program_builder.h b/src/program_builder.h
index a8779a8..ffe7476 100644
--- a/src/program_builder.h
+++ b/src/program_builder.h
@@ -28,6 +28,7 @@
 #include "src/ast/loop_statement.h"
 #include "src/ast/member_accessor_expression.h"
 #include "src/ast/module.h"
+#include "src/ast/return_statement.h"
 #include "src/ast/scalar_constructor_expression.h"
 #include "src/ast/sint_literal.h"
 #include "src/ast/stride_decoration.h"
@@ -1031,6 +1032,14 @@
     return func;
   }
 
+  /// Creates an ast::ReturnStatement with the input args
+  /// @param args arguments to construct a return statement with
+  /// @returns the return statement pointer
+  template <typename... Args>
+  ast::ReturnStatement* Return(Args&&... args) {
+    return create<ast::ReturnStatement>(std::forward<Args>(args)...);
+  }
+
   /// Creates a ast::Struct and type::Struct, registering the type::Struct with
   /// the AST().ConstructedTypes().
   /// @param source the source information
@@ -1206,14 +1215,16 @@
   /// Wraps the list of arguments in a simple function so that each is reachable
   /// by the Resolver.
   /// @param args a mix of ast::Expression, ast::Statement, ast::Variables.
+  /// @returns the function
   template <typename... ARGS>
-  void WrapInFunction(ARGS&&... args) {
+  ast::Function* WrapInFunction(ARGS&&... args) {
     ast::StatementList stmts{WrapInStatement(std::forward<ARGS>(args))...};
-    WrapInFunction(stmts);
+    return WrapInFunction(std::move(stmts));
   }
   /// @param stmts a list of ast::Statement that will be wrapped by a function,
   /// so that each statement is reachable by the Resolver.
-  void WrapInFunction(ast::StatementList stmts);
+  /// @returns the function
+  ast::Function* WrapInFunction(ast::StatementList stmts);
 
   /// The builder types
   TypesBuilder const ty{this};
diff --git a/src/resolver/decoration_validation_test.cc b/src/resolver/decoration_validation_test.cc
index b1a45c1..39f8f8d 100644
--- a/src/resolver/decoration_validation_test.cc
+++ b/src/resolver/decoration_validation_test.cc
@@ -14,6 +14,7 @@
 
 #include "src/ast/access_decoration.h"
 #include "src/ast/constant_id_decoration.h"
+#include "src/ast/return_statement.h"
 #include "src/ast/stage_decoration.h"
 #include "src/ast/struct_block_decoration.h"
 #include "src/ast/workgroup_decoration.h"
@@ -84,6 +85,41 @@
   return nullptr;
 }
 
+using FunctionReturnTypeDecorationTest = TestWithParams;
+TEST_P(FunctionReturnTypeDecorationTest, IsValid) {
+  auto params = GetParam();
+
+  Func("main", ast::VariableList{}, ty.f32(),
+       ast::StatementList{create<ast::ReturnStatement>(Expr(1.f))},
+       ast::DecorationList{
+           create<ast::StageDecoration>(ast::PipelineStage::kVertex)},
+       ast::DecorationList{createDecoration({}, *this, params.kind)});
+
+  if (params.should_pass) {
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+  } else {
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(),
+              "error: decoration is not valid for function return types");
+  }
+}
+INSTANTIATE_TEST_SUITE_P(
+    ResolverDecorationValidationTest,
+    FunctionReturnTypeDecorationTest,
+    testing::Values(TestParams{DecorationKind::kAccess, false},
+                    TestParams{DecorationKind::kAlign, false},
+                    TestParams{DecorationKind::kBinding, false},
+                    TestParams{DecorationKind::kBuiltin, true},
+                    TestParams{DecorationKind::kConstantId, false},
+                    TestParams{DecorationKind::kGroup, false},
+                    TestParams{DecorationKind::kLocation, true},
+                    TestParams{DecorationKind::kOffset, false},
+                    TestParams{DecorationKind::kSize, false},
+                    TestParams{DecorationKind::kStage, false},
+                    TestParams{DecorationKind::kStride, false},
+                    TestParams{DecorationKind::kStructBlock, false},
+                    TestParams{DecorationKind::kWorkgroup, false}));
+
 using ArrayDecorationTest = TestWithParams;
 
 TEST_P(ArrayDecorationTest, IsValid) {
diff --git a/src/resolver/function_validation_test.cc b/src/resolver/function_validation_test.cc
new file mode 100644
index 0000000..0da990a
--- /dev/null
+++ b/src/resolver/function_validation_test.cc
@@ -0,0 +1,78 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/ast/return_statement.h"
+#include "src/resolver/resolver.h"
+#include "src/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace {
+
+class ResolverFunctionValidationTest : public resolver::TestHelper,
+                                       public testing::Test {};
+
+TEST_F(ResolverFunctionValidationTest, FunctionNamesMustBeUnique_fail) {
+  // fn func -> i32 { return 2; }
+  // fn func -> i32 { return 2; }
+  Func("func", ast::VariableList{}, ty.i32(),
+       ast::StatementList{
+           create<ast::ReturnStatement>(Expr(2)),
+       },
+       ast::DecorationList{});
+
+  Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
+       ast::StatementList{
+           create<ast::ReturnStatement>(Expr(2)),
+       },
+       ast::DecorationList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error v-0016: function names must be unique 'func'");
+}
+
+TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatement_Fail) {
+  // fn func -> int { var a:i32 = 2; }
+
+  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
+
+  Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
+       ast::StatementList{
+           create<ast::VariableDeclStatement>(var),
+       },
+       ast::DecorationList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error v-0002: non-void function must end with a return statement");
+}
+
+TEST_F(ResolverFunctionValidationTest,
+       FunctionEndWithoutReturnStatementEmptyBody_Fail) {
+  // fn func -> int {}
+
+  Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
+       ast::StatementList{}, ast::DecorationList{});
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error v-0002: non-void function must end with a return statement");
+}
+
+}  // namespace
+}  // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index d4eb5b1..b02f6af 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -222,9 +222,65 @@
   return true;
 }
 
+bool Resolver::ValidateParameter(const ast::Variable* param) {
+  if (auto* r = param->type()->UnwrapAll()->As<type::Array>()) {
+    if (r->IsRuntimeArray()) {
+      diagnostics_.add_error(
+          "v-0015",
+          "runtime arrays may only appear as the last member of a struct",
+          param->source());
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Resolver::ValidateFunction(const ast::Function* func) {
+  if (symbol_to_function_.find(func->symbol()) != symbol_to_function_.end()) {
+    diagnostics_.add_error("v-0016",
+                           "function names must be unique '" +
+                               builder_->Symbols().NameFor(func->symbol()) +
+                               "'",
+                           func->source());
+    return false;
+  }
+
+  for (auto* param : func->params()) {
+    if (!ValidateParameter(param)) {
+      return false;
+    }
+  }
+
+  if (!func->return_type()->Is<type::Void>()) {
+    if (!func->get_last_statement() ||
+        !func->get_last_statement()->Is<ast::ReturnStatement>()) {
+      diagnostics_.add_error(
+          "v-0002", "non-void function must end with a return statement",
+          func->source());
+      return false;
+    }
+
+    for (auto* deco : func->return_type_decorations()) {
+      if (!(deco->Is<ast::BuiltinDecoration>() ||
+            deco->Is<ast::LocationDecoration>())) {
+        diagnostics_.add_error(
+            "decoration is not valid for function return types",
+            deco->source());
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 bool Resolver::Function(ast::Function* func) {
   auto* func_info = function_infos_.Create<FunctionInfo>(func);
 
+  if (!ValidateFunction(func)) {
+    return false;
+  }
+
   ScopedAssignment<FunctionInfo*> sa(current_function_, func_info);
 
   variable_stack_.push_scope();
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
index 00102ee..d6db052 100644
--- a/src/resolver/resolver.h
+++ b/src/resolver/resolver.h
@@ -224,16 +224,15 @@
   // AST and Type validation methods
   // Each return true on success, false on failure.
   bool ValidateBinary(ast::BinaryExpression* expr);
+  bool ValidateParameter(const ast::Variable* param);
+  bool ValidateFunction(const ast::Function* func);
+  bool ValidateStructure(const type::Struct* st);
 
   /// @returns the semantic information for the array `arr`, building it if it
   /// hasn't been constructed already. If an error is raised, nullptr is
   /// returned.
   const semantic::Array* Array(type::Array*);
 
-  /// @returns returns true if input struct is valid
-  /// @param st the struct to validate
-  bool ValidateStructure(const type::Struct* st);
-
   /// @returns the StructInfo for the structure `str`, building it if it hasn't
   /// been constructed already. If an error is raised, nullptr is returned.
   StructInfo* Structure(type::Struct* str);
@@ -286,7 +285,7 @@
   BlockInfo* current_block_ = nullptr;
   ScopeStack<VariableInfo*> variable_stack_;
   std::unordered_map<Symbol, FunctionInfo*> symbol_to_function_;
-  std::unordered_map<ast::Function*, FunctionInfo*> function_to_info_;
+  std::unordered_map<const ast::Function*, FunctionInfo*> function_to_info_;
   std::unordered_map<ast::Variable*, VariableInfo*> variable_to_info_;
   std::unordered_map<ast::CallExpression*, FunctionCallInfo> function_calls_;
   std::unordered_map<ast::Expression*, ExpressionInfo> expr_info_;
diff --git a/src/resolver/resolver_test.cc b/src/resolver/resolver_test.cc
index 664a465..171ef6d 100644
--- a/src/resolver/resolver_test.cc
+++ b/src/resolver/resolver_test.cc
@@ -271,7 +271,7 @@
 
 TEST_F(ResolverTest, Stmt_Call) {
   ast::VariableList params;
-  Func("my_func", params, ty.f32(), ast::StatementList{},
+  Func("my_func", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
        ast::DecorationList{});
 
   auto* expr = Call("my_func");
@@ -325,7 +325,7 @@
 }
 
 TEST_F(ResolverTest, Stmt_VariableDecl_OuterScopeAfterInnerScope) {
-  // fn func_i32() -> i32 {
+  // fn func_i32() -> void {
   //   {
   //     var foo : i32 = 2;
   //     var bar : i32 = foo;
@@ -359,11 +359,11 @@
   auto* bar_f32_init = bar_f32->constructor();
   auto* bar_f32_decl = create<ast::VariableDeclStatement>(bar_f32);
 
-  Func("func", params, ty.f32(),
+  Func("func", params, ty.void_(),
        ast::StatementList{inner, foo_f32_decl, bar_f32_decl},
        ast::DecorationList{});
 
-  EXPECT_TRUE(r()->Resolve());
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
   ASSERT_NE(TypeOf(foo_i32_init), nullptr);
   EXPECT_TRUE(TypeOf(foo_i32_init)->Is<type::I32>());
   ASSERT_NE(TypeOf(foo_f32_init), nullptr);
@@ -381,11 +381,11 @@
 }
 
 TEST_F(ResolverTest, Stmt_VariableDecl_ModuleScopeAfterFunctionScope) {
-  // fn func_i32() -> i32 {
+  // fn func_i32() -> void {
   //   var foo : i32 = 2;
   // }
   // var foo : f32 = 2.0;
-  // fn func_f32() -> f32 {
+  // fn func_f32() -> void {
   //   var bar : f32 = foo;
   // }
 
@@ -395,7 +395,7 @@
   auto* fn_i32 = Var("foo", ty.i32(), ast::StorageClass::kNone, Expr(2));
   auto* fn_i32_init = fn_i32->constructor();
   auto* fn_i32_decl = create<ast::VariableDeclStatement>(fn_i32);
-  Func("func_i32", params, ty.i32(), ast::StatementList{fn_i32_decl},
+  Func("func_i32", params, ty.void_(), ast::StatementList{fn_i32_decl},
        ast::DecorationList{});
 
   // Declare f32 "foo" at module scope
@@ -407,10 +407,10 @@
   auto* fn_f32 = Var("bar", ty.f32(), ast::StorageClass::kNone, Expr("foo"));
   auto* fn_f32_init = fn_f32->constructor();
   auto* fn_f32_decl = create<ast::VariableDeclStatement>(fn_f32);
-  Func("func_f32", params, ty.f32(), ast::StatementList{fn_f32_decl},
+  Func("func_f32", params, ty.void_(), ast::StatementList{fn_f32_decl},
        ast::DecorationList{});
 
-  EXPECT_TRUE(r()->Resolve());
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
   ASSERT_NE(TypeOf(mod_init), nullptr);
   EXPECT_TRUE(TypeOf(mod_init)->Is<type::F32>());
   ASSERT_NE(TypeOf(fn_i32_init), nullptr);
@@ -529,7 +529,7 @@
 
 TEST_F(ResolverTest, Expr_Call) {
   ast::VariableList params;
-  Func("my_func", params, ty.f32(), ast::StatementList{},
+  Func("my_func", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
        ast::DecorationList{});
 
   auto* call = Call("my_func");
@@ -543,7 +543,8 @@
 
 TEST_F(ResolverTest, Expr_Call_InBinaryOp) {
   ast::VariableList params;
-  Func("func", params, ty.f32(), ast::StatementList{}, ast::DecorationList{});
+  Func("func", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
+       ast::DecorationList{});
 
   auto* expr = Add(Call("func"), Call("func"));
   WrapInFunction(expr);
@@ -556,7 +557,7 @@
 
 TEST_F(ResolverTest, Expr_Call_WithParams) {
   ast::VariableList params;
-  Func("my_func", params, ty.f32(), ast::StatementList{},
+  Func("my_func", params, ty.void_(), ast::StatementList{},
        ast::DecorationList{});
 
   auto* param = Expr(2.4f);
@@ -671,7 +672,7 @@
   auto* var = Const("my_var", ty.f32());
   auto* assign = create<ast::AssignmentStatement>(my_var_a, my_var_b);
 
-  Func("my_func", ast::VariableList{}, ty.f32(),
+  Func("my_func", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::VariableDeclStatement>(var),
            assign,
@@ -696,7 +697,7 @@
 
   auto* var = Var("my_var", ty.f32(), ast::StorageClass::kNone);
 
-  Func("my_func", ast::VariableList{}, ty.f32(),
+  Func("my_func", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::VariableDeclStatement>(var),
            assign,
@@ -721,7 +722,7 @@
   auto* my_var_b = Expr("my_var");
   auto* assign = create<ast::AssignmentStatement>(my_var_a, my_var_b);
 
-  Func("my_func", ast::VariableList{}, ty.f32(),
+  Func("my_func", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::VariableDeclStatement>(
                Var("my_var", ty.pointer<f32>(ast::StorageClass::kFunction),
@@ -743,8 +744,8 @@
 }
 
 TEST_F(ResolverTest, Expr_Call_Function) {
-  Func("my_func", ast::VariableList{}, ty.f32(), ast::StatementList{},
-       ast::DecorationList{});
+  Func("my_func", ast::VariableList{}, ty.f32(),
+       ast::StatementList{Return(Expr(0.0f))}, ast::DecorationList{});
 
   auto* call = Call("my_func");
   WrapInFunction(call);
@@ -770,7 +771,7 @@
   auto* priv_var = Global("priv_var", ty.f32(), ast::StorageClass::kPrivate);
 
   auto* func = Func(
-      "my_func", ast::VariableList{}, ty.f32(),
+      "my_func", ast::VariableList{}, ty.void_(),
       ast::StatementList{
           create<ast::AssignmentStatement>(Expr("out_var"), Expr("in_var")),
           create<ast::AssignmentStatement>(Expr("wg_var"), Expr("wg_var")),
@@ -806,11 +807,11 @@
            create<ast::AssignmentStatement>(Expr("wg_var"), Expr("wg_var")),
            create<ast::AssignmentStatement>(Expr("sb_var"), Expr("sb_var")),
            create<ast::AssignmentStatement>(Expr("priv_var"), Expr("priv_var")),
-       },
+           Return(Expr(0.0f))},
        ast::DecorationList{});
 
   auto* func2 = Func(
-      "func", ast::VariableList{}, ty.f32(),
+      "func", ast::VariableList{}, ty.void_(),
       ast::StatementList{
           create<ast::AssignmentStatement>(Expr("out_var"), Call("my_func")),
       },
@@ -834,7 +835,7 @@
   auto* var = Var("in_var", ty.f32(), ast::StorageClass::kFunction);
 
   auto* func =
-      Func("my_func", ast::VariableList{}, ty.f32(),
+      Func("my_func", ast::VariableList{}, ty.void_(),
            ast::StatementList{
                create<ast::VariableDeclStatement>(var),
                create<ast::AssignmentStatement>(Expr("var"), Expr(1.f)),
@@ -1276,7 +1277,7 @@
   auto* var = Var("var", ty.i32(), ast::StorageClass::kNone);
 
   auto* stmt = create<ast::VariableDeclStatement>(var);
-  Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
+  Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
        ast::DecorationList{});
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1287,7 +1288,7 @@
 TEST_F(ResolverTest, StorageClass_DoesNotSetOnConst) {
   auto* var = Const("var", ty.i32());
   auto* stmt = create<ast::VariableDeclStatement>(var);
-  Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
+  Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
        ast::DecorationList{});
 
   EXPECT_TRUE(r()->Resolve()) << r()->error();
@@ -1310,23 +1311,22 @@
 
   ast::VariableList params;
   auto* func_b =
-      Func("b", params, ty.f32(), ast::StatementList{}, ast::DecorationList{});
-  auto* func_c =
-      Func("c", params, ty.f32(),
-           ast::StatementList{
-               create<ast::AssignmentStatement>(Expr("second"), Call("b")),
-           },
+      Func("b", params, ty.f32(), ast::StatementList{Return(Expr(0.0f))},
            ast::DecorationList{});
+  auto* func_c = Func("c", params, ty.f32(),
+                      ast::StatementList{create<ast::AssignmentStatement>(
+                                             Expr("second"), Call("b")),
+                                         Return(Expr(0.0f))},
+                      ast::DecorationList{});
 
-  auto* func_a =
-      Func("a", params, ty.f32(),
-           ast::StatementList{
-               create<ast::AssignmentStatement>(Expr("first"), Call("c")),
-           },
-           ast::DecorationList{});
+  auto* func_a = Func("a", params, ty.f32(),
+                      ast::StatementList{create<ast::AssignmentStatement>(
+                                             Expr("first"), Call("c")),
+                                         Return(Expr(0.0f))},
+                      ast::DecorationList{});
 
   auto* ep_1 =
-      Func("ep_1", params, ty.f32(),
+      Func("ep_1", params, ty.void_(),
            ast::StatementList{
                create<ast::AssignmentStatement>(Expr("call_a"), Call("a")),
                create<ast::AssignmentStatement>(Expr("call_b"), Call("b")),
@@ -1336,7 +1336,7 @@
            });
 
   auto* ep_2 =
-      Func("ep_2", params, ty.f32(),
+      Func("ep_2", params, ty.void_(),
            ast::StatementList{
                create<ast::AssignmentStatement>(Expr("call_c"), Call("c")),
            },
diff --git a/src/resolver/type_validation_test.cc b/src/resolver/type_validation_test.cc
index 23caf52..dc43bcf 100644
--- a/src/resolver/type_validation_test.cc
+++ b/src/resolver/type_validation_test.cc
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "src/ast/return_statement.h"
 #include "src/ast/stage_decoration.h"
 #include "src/ast/struct_block_decoration.h"
 #include "src/resolver/resolver.h"
@@ -96,6 +97,34 @@
             "member of a struct");
 }
 
+TEST_F(ResolverTypeValidationTest, RuntimeArrayAsParameter_Fail) {
+  // fn func(a : array<u32>) {}
+  // [[stage(vertex)]] fn main() {}
+
+  auto* param = Var(Source{Source::Location{12, 34}}, "a", ty.array<i32>(),
+                    ast::StorageClass::kNone);
+
+  Func("func", ast::VariableList{param}, ty.void_(),
+       ast::StatementList{
+           create<ast::ReturnStatement>(),
+       },
+       ast::DecorationList{});
+
+  Func("main", ast::VariableList{}, ty.void_(),
+       ast::StatementList{
+           create<ast::ReturnStatement>(),
+       },
+       ast::DecorationList{
+           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
+       });
+
+  EXPECT_FALSE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(
+      r()->error(),
+      "12:34 error v-0015: runtime arrays may only appear as the last member "
+      "of a struct");
+}
+
 TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
   // [[Block]]
   // type RTArr = array<u32>;
diff --git a/src/resolver/validation_test.cc b/src/resolver/validation_test.cc
index 659eb0f..f9366e3 100644
--- a/src/resolver/validation_test.cc
+++ b/src/resolver/validation_test.cc
@@ -116,7 +116,7 @@
   auto* call_expr = Call("main");
   ast::VariableList params0;
 
-  Func("main", params0, ty.f32(),
+  Func("main", params0, ty.void_(),
        ast::StatementList{
            create<ast::CallStatement>(call_expr),
        },
@@ -245,7 +245,7 @@
   auto* var = Var("var", ty.i32(), ast::StorageClass::kWorkgroup);
 
   auto* stmt = create<ast::VariableDeclStatement>(var);
-  Func("func", ast::VariableList{}, ty.i32(), ast::StatementList{stmt},
+  Func("func", ast::VariableList{}, ty.void_(), ast::StatementList{stmt},
        ast::DecorationList{});
 
   EXPECT_FALSE(r()->Resolve());
diff --git a/src/validator/validator_decoration_test.cc b/src/validator/validator_decoration_test.cc
index 52343f8..a48ff9f 100644
--- a/src/validator/validator_decoration_test.cc
+++ b/src/validator/validator_decoration_test.cc
@@ -121,42 +121,6 @@
                     DecorationTestParams{DecorationKind::kStructBlock, false},
                     DecorationTestParams{DecorationKind::kWorkgroup, true}));
 
-using FunctionReturnTypeDecorationTest = ValidatorDecorationsTestWithParams;
-TEST_P(FunctionReturnTypeDecorationTest, Decoration_IsValid) {
-  auto params = GetParam();
-
-  Func("main", ast::VariableList{}, ty.f32(),
-       ast::StatementList{create<ast::ReturnStatement>(Expr(1.f))},
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex)},
-       ast::DecorationList{createDecoration(*this, params.kind)});
-
-  ValidatorImpl& v = Build();
-
-  if (params.should_pass) {
-    EXPECT_TRUE(v.Validate());
-  } else {
-    EXPECT_FALSE(v.Validate());
-    EXPECT_EQ(v.error(), "decoration is not valid for function return types");
-  }
-}
-INSTANTIATE_TEST_SUITE_P(
-    ValidatorTest,
-    FunctionReturnTypeDecorationTest,
-    testing::Values(DecorationTestParams{DecorationKind::kAccess, false},
-                    DecorationTestParams{DecorationKind::kAlign, false},
-                    DecorationTestParams{DecorationKind::kBinding, false},
-                    DecorationTestParams{DecorationKind::kBuiltin, true},
-                    DecorationTestParams{DecorationKind::kConstantId, false},
-                    DecorationTestParams{DecorationKind::kGroup, false},
-                    DecorationTestParams{DecorationKind::kLocation, true},
-                    DecorationTestParams{DecorationKind::kOffset, false},
-                    DecorationTestParams{DecorationKind::kSize, false},
-                    DecorationTestParams{DecorationKind::kStage, false},
-                    DecorationTestParams{DecorationKind::kStride, false},
-                    DecorationTestParams{DecorationKind::kStructBlock, false},
-                    DecorationTestParams{DecorationKind::kWorkgroup, false}));
-
 using VariableDecorationTest = ValidatorDecorationsTestWithParams;
 TEST_P(VariableDecorationTest, Decoration_IsValid) {
   auto params = GetParam();
diff --git a/src/validator/validator_function_test.cc b/src/validator/validator_function_test.cc
index aa47060..5c217eb 100644
--- a/src/validator/validator_function_test.cc
+++ b/src/validator/validator_function_test.cc
@@ -56,37 +56,6 @@
   EXPECT_TRUE(v.Validate());
 }
 
-TEST_F(ValidateFunctionTest, FunctionEndWithoutReturnStatement_Fail) {
-  // fn func -> int { var a:i32 = 2; }
-
-  auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2));
-
-  Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
-       ast::StatementList{
-           create<ast::VariableDeclStatement>(var),
-       },
-       ast::DecorationList{});
-
-  ValidatorImpl& v = Build();
-
-  EXPECT_FALSE(v.Validate());
-  EXPECT_EQ(v.error(),
-            "12:34 v-0002: non-void function must end with a return statement");
-}
-
-TEST_F(ValidateFunctionTest, FunctionEndWithoutReturnStatementEmptyBody_Fail) {
-  // fn func -> int {}
-
-  Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
-       ast::StatementList{}, ast::DecorationList{});
-
-  ValidatorImpl& v = Build();
-
-  EXPECT_FALSE(v.Validate());
-  EXPECT_EQ(v.error(),
-            "12:34 v-0002: non-void function must end with a return statement");
-}
-
 TEST_F(ValidateFunctionTest, FunctionTypeMustMatchReturnStatementType_Pass) {
   // [[stage(vertex)]]
   // fn func -> void { return; }
@@ -204,26 +173,6 @@
       "return type, returned '__u32', expected '__alias_tint_symbol_1__f32'");
 }
 
-TEST_F(ValidateFunctionTest, FunctionNamesMustBeUnique_fail) {
-  // fn func -> i32 { return 2; }
-  // fn func -> i32 { return 2; }
-  Func("func", ast::VariableList{}, ty.i32(),
-       ast::StatementList{
-           create<ast::ReturnStatement>(Expr(2)),
-       },
-       ast::DecorationList{});
-
-  Func(Source{Source::Location{12, 34}}, "func", ast::VariableList{}, ty.i32(),
-       ast::StatementList{
-           create<ast::ReturnStatement>(Expr(2)),
-       },
-       ast::DecorationList{});
-
-  ValidatorImpl& v = Build();
-
-  EXPECT_FALSE(v.Validate());
-  EXPECT_EQ(v.error(), "12:34 v-0016: function names must be unique 'func'");
-}
 
 TEST_F(ValidateFunctionTest, PipelineStage_MustBeUnique_Fail) {
   // [[stage(fragment)]]
diff --git a/src/validator/validator_impl.cc b/src/validator/validator_impl.cc
index 25d4da0..7f46edb 100644
--- a/src/validator/validator_impl.cc
+++ b/src/validator/validator_impl.cc
@@ -168,57 +168,11 @@
 }
 
 bool ValidatorImpl::ValidateFunction(const ast::Function* func) {
-  if (function_stack_.has(func->symbol())) {
-    add_error(func->source(), "v-0016",
-              "function names must be unique '" +
-                  program_->Symbols().NameFor(func->symbol()) + "'");
-    return false;
-  }
-
-  function_stack_.set(func->symbol(), func);
-
-  variable_stack_.push_scope();
-
-  for (auto* param : func->params()) {
-    variable_stack_.set(param->symbol(), param);
-    if (!ValidateParameter(param)) {
-      return false;
-    }
-  }
+  // TODO(amaiorano): Remove ValidateFunction once we've moved all the statement
+  // validation to Resovler
   if (!ValidateStatements(func->body())) {
     return false;
   }
-  variable_stack_.pop_scope();
-
-  if (!current_function_->return_type()->Is<type::Void>()) {
-    if (!func->get_last_statement() ||
-        !func->get_last_statement()->Is<ast::ReturnStatement>()) {
-      add_error(func->source(), "v-0002",
-                "non-void function must end with a return statement");
-      return false;
-    }
-
-    for (auto* deco : current_function_->return_type_decorations()) {
-      if (!(deco->Is<ast::BuiltinDecoration>() ||
-            deco->Is<ast::LocationDecoration>())) {
-        add_error(deco->source(),
-                  "decoration is not valid for function return types");
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-bool ValidatorImpl::ValidateParameter(const ast::Variable* param) {
-  if (auto* r = param->type()->UnwrapAll()->As<type::Array>()) {
-    if (r->IsRuntimeArray()) {
-      add_error(
-          param->source(), "v-0015",
-          "runtime arrays may only appear as the last member of a struct");
-      return false;
-    }
-  }
   return true;
 }
 
diff --git a/src/validator/validator_impl.h b/src/validator/validator_impl.h
index 1d2338e..93c1d8d 100644
--- a/src/validator/validator_impl.h
+++ b/src/validator/validator_impl.h
@@ -76,10 +76,6 @@
   /// @param func the function to check
   /// @returns true if the validation was successful
   bool ValidateFunction(const ast::Function* func);
-  /// Validates a function parameter
-  /// @param param the function parameter to check
-  /// @returns true if the validation was successful
-  bool ValidateParameter(const ast::Variable* param);
   /// Validates a block of statements
   /// @param block the statements to check
   /// @returns true if the validation was successful
@@ -147,7 +143,6 @@
   const Program* program_;
   diag::List diags_;
   ScopeStack<const ast::Variable*> variable_stack_;
-  ScopeStack<const ast::Function*> function_stack_;
   ast::Function* current_function_ = nullptr;
 };
 
diff --git a/src/validator/validator_type_test.cc b/src/validator/validator_type_test.cc
index dd991ae..de8b6f5 100644
--- a/src/validator/validator_type_test.cc
+++ b/src/validator/validator_type_test.cc
@@ -44,33 +44,5 @@
             "of a struct");
 }
 
-TEST_F(ValidatorTypeTest, RuntimeArrayAsParameter_Fail) {
-  // fn func(a : array<u32>) {}
-  // [[stage(vertex)]] fn main() {}
-
-  auto* param = Var(Source{Source::Location{12, 34}}, "a", ty.array<i32>(),
-                    ast::StorageClass::kNone);
-
-  Func("func", ast::VariableList{param}, ty.void_(),
-       ast::StatementList{
-           create<ast::ReturnStatement>(),
-       },
-       ast::DecorationList{});
-
-  Func("main", ast::VariableList{}, ty.void_(),
-       ast::StatementList{
-           create<ast::ReturnStatement>(),
-       },
-       ast::DecorationList{
-           create<ast::StageDecoration>(ast::PipelineStage::kVertex),
-       });
-
-  ValidatorImpl& v = Build();
-
-  EXPECT_FALSE(v.Validate());
-  EXPECT_EQ(v.error(),
-            "12:34 v-0015: runtime arrays may only appear as the last member "
-            "of a struct");
-}
 }  // namespace
 }  // namespace tint
diff --git a/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc b/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc
index f1c3bb3..2c65dc4 100644
--- a/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc
+++ b/src/writer/hlsl/generator_impl_function_entry_point_data_test.cc
@@ -42,7 +42,7 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("vtx_main", ast::VariableList{}, ty.f32(),
+  Func("vtx_main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
            create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@@ -85,7 +85,7 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("vtx_main", ast::VariableList{}, ty.f32(),
+  Func("vtx_main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
            create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@@ -128,7 +128,7 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("main", ast::VariableList{}, ty.f32(),
+  Func("main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
            create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@@ -171,7 +171,7 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("main", ast::VariableList{}, ty.f32(),
+  Func("main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
            create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@@ -211,7 +211,7 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("main", ast::VariableList{}, ty.f32(),
+  Func("main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
            create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
@@ -247,7 +247,7 @@
              create<ast::LocationDecoration>(1),
          });
 
-  Func("main", ast::VariableList{}, ty.f32(),
+  Func("main", ast::VariableList{}, ty.void_(),
        ast::StatementList{
            create<ast::AssignmentStatement>(Expr("foo"), Expr("foo")),
            create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
diff --git a/src/writer/msl/generator_impl_function_entry_point_data_test.cc b/src/writer/msl/generator_impl_function_entry_point_data_test.cc
index c31b79b..fa22462 100644
--- a/src/writer/msl/generator_impl_function_entry_point_data_test.cc
+++ b/src/writer/msl/generator_impl_function_entry_point_data_test.cc
@@ -42,7 +42,7 @@
       create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
 
-  Func("vtx_main", ast::VariableList{}, ty.f32(), body,
+  Func("vtx_main", ast::VariableList{}, ty.void_(), body,
        ast::DecorationList{
            create<ast::StageDecoration>(ast::PipelineStage::kVertex),
        });
@@ -79,7 +79,7 @@
       create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
 
-  Func("vtx_main", ast::VariableList{}, ty.f32(), body,
+  Func("vtx_main", ast::VariableList{}, ty.void_(), body,
        ast::DecorationList{
            create<ast::StageDecoration>(ast::PipelineStage::kVertex),
        });
@@ -116,7 +116,7 @@
       create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
 
-  Func("main", ast::VariableList{}, ty.f32(), body,
+  Func("main", ast::VariableList{}, ty.void_(), body,
        ast::DecorationList{
            create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
@@ -153,7 +153,7 @@
       create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
 
-  Func("main", ast::VariableList{}, ty.f32(), body,
+  Func("main", ast::VariableList{}, ty.void_(), body,
        ast::DecorationList{
            create<ast::StageDecoration>(ast::PipelineStage::kFragment),
        });
@@ -187,7 +187,7 @@
       create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
 
-  Func("main", ast::VariableList{}, ty.f32(), body,
+  Func("main", ast::VariableList{}, ty.void_(), body,
        ast::DecorationList{
            create<ast::StageDecoration>(ast::PipelineStage::kCompute),
        });
@@ -217,7 +217,7 @@
       create<ast::AssignmentStatement>(Expr("bar"), Expr("bar")),
   };
 
-  Func("main", ast::VariableList{}, ty.f32(), body,
+  Func("main", ast::VariableList{}, ty.void_(), body,
        ast::DecorationList{
            create<ast::StageDecoration>(ast::PipelineStage::kCompute),
        });
diff --git a/src/writer/spirv/builder_intrinsic_test.cc b/src/writer/spirv/builder_intrinsic_test.cc
index 4fe131e..4e2bb84 100644
--- a/src/writer/spirv/builder_intrinsic_test.cc
+++ b/src/writer/spirv/builder_intrinsic_test.cc
@@ -382,8 +382,8 @@
   auto* expr2 = Call("textureSampleCompare", "texture", "sampler",
                      vec2<f32>(1.0f, 2.0f), 2.0f);
 
-  WrapInFunction(expr1);
-  WrapInFunction(expr2);
+  Func("f1", {}, ty.void_(), {create<ast::CallStatement>(expr1)}, {});
+  Func("f2", {}, ty.void_(), {create<ast::CallStatement>(expr2)}, {});
 
   spirv::Builder& b = Build();
 
diff --git a/src/writer/spirv/builder_switch_test.cc b/src/writer/spirv/builder_switch_test.cc
index cff64cc..f9d2d97 100644
--- a/src/writer/spirv/builder_switch_test.cc
+++ b/src/writer/spirv/builder_switch_test.cc
@@ -79,8 +79,8 @@
 
   WrapInFunction(expr);
 
-  auto* func =
-      Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
+  auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{});
 
   spirv::Builder& b = Build();
 
@@ -92,29 +92,30 @@
 
   EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
-OpName %7 "a_func"
+OpName %8 "a_func"
 %3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %5 = OpVariable %2 Private %4
-%6 = OpTypeFunction %3
-%14 = OpConstant %3 1
-%15 = OpConstant %3 2
-%7 = OpFunction %3 None %6
-%8 = OpLabel
-%10 = OpLoad %3 %5
-OpSelectionMerge %9 None
-OpSwitch %10 %11 1 %12 2 %13
-%12 = OpLabel
-OpStore %1 %14
-OpBranch %9
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%15 = OpConstant %3 1
+%16 = OpConstant %3 2
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13 2 %14
 %13 = OpLabel
 OpStore %1 %15
-OpBranch %9
-%11 = OpLabel
-OpBranch %9
-%9 = OpLabel
+OpBranch %10
+%14 = OpLabel
+OpStore %1 %16
+OpBranch %10
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
 OpReturn
 OpFunctionEnd
 )");
@@ -151,8 +152,8 @@
 
   WrapInFunction(expr);
 
-  auto* func =
-      Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
+  auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{});
 
   spirv::Builder& b = Build();
 
@@ -164,7 +165,7 @@
 
   EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
-OpName %10 "a_func"
+OpName %11 "a_func"
 %3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
@@ -173,23 +174,24 @@
 %6 = OpTypePointer Private %7
 %8 = OpConstantNull %7
 %5 = OpVariable %6 Private %8
-%9 = OpTypeFunction %3
-%17 = OpConstant %3 1
-%18 = OpConstant %3 2
-%10 = OpFunction %3 None %9
-%11 = OpLabel
-%13 = OpLoad %7 %5
-OpSelectionMerge %12 None
-OpSwitch %13 %14 1 %15 2 %16
-%15 = OpLabel
-OpStore %1 %17
-OpBranch %12
+%10 = OpTypeVoid
+%9 = OpTypeFunction %10
+%18 = OpConstant %3 1
+%19 = OpConstant %3 2
+%11 = OpFunction %10 None %9
+%12 = OpLabel
+%14 = OpLoad %7 %5
+OpSelectionMerge %13 None
+OpSwitch %14 %15 1 %16 2 %17
 %16 = OpLabel
 OpStore %1 %18
-OpBranch %12
-%14 = OpLabel
-OpBranch %12
-%12 = OpLabel
+OpBranch %13
+%17 = OpLabel
+OpStore %1 %19
+OpBranch %13
+%15 = OpLabel
+OpBranch %13
+%13 = OpLabel
 OpReturn
 OpFunctionEnd
 )");
@@ -215,8 +217,8 @@
 
   WrapInFunction(expr);
 
-  auto* func =
-      Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
+  auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{});
 
   spirv::Builder& b = Build();
 
@@ -228,23 +230,24 @@
 
   EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
-OpName %7 "a_func"
+OpName %8 "a_func"
 %3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %5 = OpVariable %2 Private %4
-%6 = OpTypeFunction %3
-%12 = OpConstant %3 1
-%7 = OpFunction %3 None %6
-%8 = OpLabel
-%10 = OpLoad %3 %5
-OpSelectionMerge %9 None
-OpSwitch %10 %11
-%11 = OpLabel
-OpStore %1 %12
-OpBranch %9
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%13 = OpConstant %3 1
+%8 = OpFunction %7 None %6
 %9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12
+%12 = OpLabel
+OpStore %1 %13
+OpBranch %10
+%10 = OpLabel
 OpReturn
 OpFunctionEnd
 )");
@@ -289,8 +292,8 @@
 
   WrapInFunction(expr);
 
-  auto* func =
-      Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
+  auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{});
 
   spirv::Builder& b = Build();
 
@@ -302,31 +305,32 @@
 
   EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
-OpName %7 "a_func"
+OpName %8 "a_func"
 %3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %5 = OpVariable %2 Private %4
-%6 = OpTypeFunction %3
-%14 = OpConstant %3 1
-%15 = OpConstant %3 2
-%16 = OpConstant %3 3
-%7 = OpFunction %3 None %6
-%8 = OpLabel
-%10 = OpLoad %3 %5
-OpSelectionMerge %9 None
-OpSwitch %10 %11 1 %12 2 %13 3 %13
-%12 = OpLabel
-OpStore %1 %14
-OpBranch %9
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%15 = OpConstant %3 1
+%16 = OpConstant %3 2
+%17 = OpConstant %3 3
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13 2 %14 3 %14
 %13 = OpLabel
 OpStore %1 %15
-OpBranch %9
-%11 = OpLabel
+OpBranch %10
+%14 = OpLabel
 OpStore %1 %16
-OpBranch %9
-%9 = OpLabel
+OpBranch %10
+%12 = OpLabel
+OpStore %1 %17
+OpBranch %10
+%10 = OpLabel
 OpReturn
 OpFunctionEnd
 )");
@@ -372,8 +376,8 @@
 
   WrapInFunction(expr);
 
-  auto* func =
-      Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
+  auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{});
 
   spirv::Builder& b = Build();
 
@@ -385,31 +389,32 @@
 
   EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
-OpName %7 "a_func"
+OpName %8 "a_func"
 %3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %5 = OpVariable %2 Private %4
-%6 = OpTypeFunction %3
-%14 = OpConstant %3 1
-%15 = OpConstant %3 2
-%16 = OpConstant %3 3
-%7 = OpFunction %3 None %6
-%8 = OpLabel
-%10 = OpLoad %3 %5
-OpSelectionMerge %9 None
-OpSwitch %10 %11 1 %12 2 %13
-%12 = OpLabel
-OpStore %1 %14
-OpBranch %13
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%15 = OpConstant %3 1
+%16 = OpConstant %3 2
+%17 = OpConstant %3 3
+%8 = OpFunction %7 None %6
+%9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13 2 %14
 %13 = OpLabel
 OpStore %1 %15
-OpBranch %9
-%11 = OpLabel
+OpBranch %14
+%14 = OpLabel
 OpStore %1 %16
-OpBranch %9
-%9 = OpLabel
+OpBranch %10
+%12 = OpLabel
+OpStore %1 %17
+OpBranch %10
+%10 = OpLabel
 OpReturn
 OpFunctionEnd
 )");
@@ -439,8 +444,8 @@
 
   WrapInFunction(expr);
 
-  auto* func =
-      Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
+  auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{});
 
   spirv::Builder& b = Build();
 
@@ -482,8 +487,8 @@
 
   WrapInFunction(expr);
 
-  auto* func =
-      Func("a_func", {}, ty.i32(), ast::StatementList{}, ast::DecorationList{});
+  auto* func = Func("a_func", {}, ty.void_(), ast::StatementList{},
+                    ast::DecorationList{});
 
   spirv::Builder& b = Build();
 
@@ -495,32 +500,33 @@
 
   EXPECT_EQ(DumpBuilder(b), R"(OpName %1 "v"
 OpName %5 "a"
-OpName %7 "a_func"
+OpName %8 "a_func"
 %3 = OpTypeInt 32 1
 %2 = OpTypePointer Private %3
 %4 = OpConstantNull %3
 %1 = OpVariable %2 Private %4
 %5 = OpVariable %2 Private %4
-%6 = OpTypeFunction %3
-%13 = OpTypeBool
-%14 = OpConstantTrue %13
-%17 = OpConstant %3 1
-%7 = OpFunction %3 None %6
-%8 = OpLabel
-%10 = OpLoad %3 %5
-OpSelectionMerge %9 None
-OpSwitch %10 %11 1 %12
-%12 = OpLabel
-OpSelectionMerge %15 None
-OpBranchConditional %14 %16 %15
-%16 = OpLabel
-OpBranch %9
-%15 = OpLabel
-OpStore %1 %17
-OpBranch %9
-%11 = OpLabel
-OpBranch %9
+%7 = OpTypeVoid
+%6 = OpTypeFunction %7
+%14 = OpTypeBool
+%15 = OpConstantTrue %14
+%18 = OpConstant %3 1
+%8 = OpFunction %7 None %6
 %9 = OpLabel
+%11 = OpLoad %3 %5
+OpSelectionMerge %10 None
+OpSwitch %11 %12 1 %13
+%13 = OpLabel
+OpSelectionMerge %16 None
+OpBranchConditional %15 %17 %16
+%17 = OpLabel
+OpBranch %10
+%16 = OpLabel
+OpStore %1 %18
+OpBranch %10
+%12 = OpLabel
+OpBranch %10
+%10 = OpLabel
 OpReturn
 OpFunctionEnd
 )");
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 15508c4..b49cf59 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -171,6 +171,7 @@
     "../src/program_test.cc",
     "../src/resolver/assignment_validation_test.cc",
     "../src/resolver/decoration_validation_test.cc",
+    "../src/resolver/function_validation_test.cc",
     "../src/resolver/host_shareable_validation_test.cc",
     "../src/resolver/intrinsic_test.cc",
     "../src/resolver/is_host_shareable_test.cc",