Implement type inference

Bug: tint:672
Change-Id: I3f586ee867f75427c4e8c309f72fb468643c23c0
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/53182
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 580c171..268b037 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -580,6 +580,7 @@
     resolver/entry_point_validation_test.cc
     resolver/function_validation_test.cc
     resolver/host_shareable_validation_test.cc
+    resolver/inferred_type_test.cc
     resolver/intrinsic_test.cc
     resolver/intrinsic_validation_test.cc
     resolver/is_host_shareable_test.cc
diff --git a/src/ast/module_test.cc b/src/ast/module_test.cc
index e53b9d8..7007b4b 100644
--- a/src/ast/module_test.cc
+++ b/src/ast/module_test.cc
@@ -55,15 +55,6 @@
       "internal compiler error");
 }
 
-TEST_F(ModuleTest, Assert_Invalid_GlobalVariable) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder builder;
-        builder.Global("var", nullptr, StorageClass::kInput);
-      },
-      "internal compiler error");
-}
-
 TEST_F(ModuleTest, Assert_Null_ConstructedType) {
   EXPECT_FATAL_FAILURE(
       {
diff --git a/src/ast/variable.cc b/src/ast/variable.cc
index 974607c..d1f3395 100644
--- a/src/ast/variable.cc
+++ b/src/ast/variable.cc
@@ -40,8 +40,6 @@
       declared_storage_class_(declared_storage_class) {
   TINT_ASSERT(symbol_.IsValid());
   TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(symbol_, program_id);
-  // no type means we must have a constructor to infer it
-  TINT_ASSERT(type_ || constructor);
   TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(constructor, program_id);
 }
 
diff --git a/src/ast/variable_test.cc b/src/ast/variable_test.cc
index 9967a20..5f7ce60 100644
--- a/src/ast/variable_test.cc
+++ b/src/ast/variable_test.cc
@@ -71,15 +71,6 @@
       "internal compiler error");
 }
 
-TEST_F(VariableTest, Assert_Null_Type) {
-  EXPECT_FATAL_FAILURE(
-      {
-        ProgramBuilder b;
-        b.Var("x", nullptr, StorageClass::kNone);
-      },
-      "internal compiler error");
-}
-
 TEST_F(VariableTest, Assert_DifferentProgramID_Symbol) {
   EXPECT_FATAL_FAILURE(
       {
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 224d228..67bd0a3 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -496,7 +496,7 @@
 
   const char* use = "let declaration";
 
-  auto decl = expect_variable_ident_decl(use);
+  auto decl = expect_variable_ident_decl(use, /* allow_inferred = */ true);
   if (decl.errored)
     return Failure::kErrored;
 
@@ -520,7 +520,7 @@
 
 // variable_decl
 //   : VAR variable_storage_decoration? variable_ident_decl
-Maybe<ParserImpl::VarDeclInfo> ParserImpl::variable_decl() {
+Maybe<ParserImpl::VarDeclInfo> ParserImpl::variable_decl(bool allow_inferred) {
   if (!match(Token::Type::kVar))
     return Failure::kNoMatch;
 
@@ -544,7 +544,8 @@
     }
   }
 
-  auto decl = expect_variable_ident_decl("variable declaration");
+  auto decl =
+      expect_variable_ident_decl("variable declaration", allow_inferred);
   if (decl.errored)
     return Failure::kErrored;
 
@@ -898,11 +899,16 @@
 // variable_ident_decl
 //   : IDENT COLON variable_decoration_list* type_decl
 Expect<ParserImpl::TypedIdentifier> ParserImpl::expect_variable_ident_decl(
-    const std::string& use) {
+    const std::string& use,
+    bool allow_inferred) {
   auto ident = expect_ident(use);
   if (ident.errored)
     return Failure::kErrored;
 
+  if (allow_inferred && !peek().Is(Token::Type::kColon)) {
+    return TypedIdentifier{nullptr, ident.value, ident.source};
+  }
+
   if (!expect(use, Token::Type::kColon))
     return Failure::kErrored;
 
@@ -1689,7 +1695,8 @@
 //   | CONST variable_ident_decl EQUAL logical_or_expression
 Maybe<ast::VariableDeclStatement*> ParserImpl::variable_stmt() {
   if (match(Token::Type::kLet)) {
-    auto decl = expect_variable_ident_decl("let declaration");
+    auto decl = expect_variable_ident_decl("let declaration",
+                                           /*allow_inferred = */ true);
     if (decl.errored)
       return Failure::kErrored;
 
@@ -1714,7 +1721,7 @@
     return create<ast::VariableDeclStatement>(decl->source, var);
   }
 
-  auto decl = variable_decl();
+  auto decl = variable_decl(/*allow_inferred = */ true);
   if (decl.errored)
     return Failure::kErrored;
   if (!decl.matched)
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 1fa4f31..5a58359 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -214,8 +214,8 @@
     /// Destructor
     ~TypedIdentifier();
 
-    /// Parsed type.
-    ast::Type* type;
+    /// Parsed type. May be nullptr for inferred types.
+    ast::Type* type = nullptr;
     /// Parsed identifier.
     std::string name;
     /// Source to the identifier.
@@ -387,13 +387,19 @@
   /// @param decos the list of decorations for the constant declaration.
   Maybe<ast::Variable*> global_constant_decl(ast::DecorationList& decos);
   /// Parses a `variable_decl` grammar element
+  /// @param allow_inferred if true, do not fail if variable decl does not
+  /// specify type
   /// @returns the parsed variable declaration info
-  Maybe<VarDeclInfo> variable_decl();
+  Maybe<VarDeclInfo> variable_decl(bool allow_inferred = false);
   /// Parses a `variable_ident_decl` grammar element, erroring on parse
   /// failure.
   /// @param use a description of what was being parsed if an error was raised.
+  /// @param allow_inferred if true, do not fail if variable decl does not
+  /// specify type
   /// @returns the identifier and type parsed or empty otherwise
-  Expect<TypedIdentifier> expect_variable_ident_decl(const std::string& use);
+  Expect<TypedIdentifier> expect_variable_ident_decl(
+      const std::string& use,
+      bool allow_inferred = false);
   /// Parses a `variable_storage_decoration` grammar element
   /// @returns the storage class or StorageClass::kNone if none matched
   Maybe<ast::StorageClass> variable_storage_decoration();
diff --git a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
index 992883e..8892df7 100644
--- a/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_constant_decl_test.cc
@@ -48,6 +48,33 @@
       ast::HasDecoration<ast::OverrideDecoration>(e.value->decorations()));
 }
 
+TEST_F(ParserImplTest, GlobalConstantDecl_Inferred) {
+  auto p = parser("let a = 1.");
+  auto decos = p->decoration_list();
+  EXPECT_FALSE(decos.errored);
+  EXPECT_FALSE(decos.matched);
+  auto e = p->global_constant_decl(decos.value);
+  EXPECT_FALSE(p->has_error()) << p->error();
+  EXPECT_TRUE(e.matched);
+  EXPECT_FALSE(e.errored);
+  ASSERT_NE(e.value, nullptr);
+
+  EXPECT_TRUE(e->is_const());
+  EXPECT_EQ(e->symbol(), p->builder().Symbols().Get("a"));
+  EXPECT_EQ(e->type(), nullptr);
+
+  EXPECT_EQ(e->source().range.begin.line, 1u);
+  EXPECT_EQ(e->source().range.begin.column, 5u);
+  EXPECT_EQ(e->source().range.end.line, 1u);
+  EXPECT_EQ(e->source().range.end.column, 6u);
+
+  ASSERT_NE(e->constructor(), nullptr);
+  EXPECT_TRUE(e->constructor()->Is<ast::ConstructorExpression>());
+
+  EXPECT_FALSE(
+      ast::HasDecoration<ast::OverrideDecoration>(e.value->decorations()));
+}
+
 TEST_F(ParserImplTest, GlobalConstantDecl_InvalidVariable) {
   auto p = parser("let a : invalid = 1.");
   auto decos = p->decoration_list();
diff --git a/src/reader/wgsl/parser_impl_global_decl_test.cc b/src/reader/wgsl/parser_impl_global_decl_test.cc
index 79e81a3..d9421db 100644
--- a/src/reader/wgsl/parser_impl_global_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_global_decl_test.cc
@@ -37,6 +37,13 @@
   EXPECT_EQ(v->symbol(), program.Symbols().Get("a"));
 }
 
+TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Inferred_Invalid) {
+  auto p = parser("var<private> a = vec2<i32>(1, 2);");
+  p->expect_global_decl();
+  ASSERT_TRUE(p->has_error());
+  EXPECT_EQ(p->error(), "1:16: expected ':' for variable declaration");
+}
+
 TEST_F(ParserImplTest, GlobalDecl_GlobalVariable_Invalid) {
   auto p = parser("var<private> a : vec2<invalid>;");
   p->expect_global_decl();
diff --git a/src/reader/wgsl/parser_impl_variable_decl_test.cc b/src/reader/wgsl/parser_impl_variable_decl_test.cc
index bb5a659..367b3d4 100644
--- a/src/reader/wgsl/parser_impl_variable_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_decl_test.cc
@@ -33,6 +33,18 @@
   EXPECT_EQ(v->type->source().range, (Source::Range{{1u, 14u}, {1u, 17u}}));
 }
 
+TEST_F(ParserImplTest, VariableDecl_Inferred_Parses) {
+  auto p = parser("var my_var = 1.0");
+  auto v = p->variable_decl(/*allow_inferred = */ true);
+  EXPECT_FALSE(p->has_error());
+  EXPECT_TRUE(v.matched);
+  EXPECT_FALSE(v.errored);
+  EXPECT_EQ(v->name, "my_var");
+  EXPECT_EQ(v->type, nullptr);
+
+  EXPECT_EQ(v->source.range, (Source::Range{{1u, 5u}, {1u, 11u}}));
+}
+
 TEST_F(ParserImplTest, VariableDecl_MissingVar) {
   auto p = parser("my_var : f32");
   auto v = p->variable_decl();
diff --git a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
index 696991d..35cd419 100644
--- a/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
+++ b/src/reader/wgsl/parser_impl_variable_ident_decl_test.cc
@@ -33,6 +33,17 @@
   EXPECT_EQ(decl->type->source().range, (Source::Range{{1u, 10u}, {1u, 13u}}));
 }
 
+TEST_F(ParserImplTest, VariableIdentDecl_Inferred_Parses) {
+  auto p = parser("my_var = 1.0");
+  auto decl = p->expect_variable_ident_decl("test", /*allow_inferred = */ true);
+  ASSERT_FALSE(p->has_error()) << p->error();
+  ASSERT_FALSE(decl.errored);
+  ASSERT_EQ(decl->name, "my_var");
+  ASSERT_EQ(decl->type, nullptr);
+
+  EXPECT_EQ(decl->source.range, (Source::Range{{1u, 1u}, {1u, 7u}}));
+}
+
 TEST_F(ParserImplTest, VariableIdentDecl_MissingIdent) {
   auto p = parser(": f32");
   auto decl = p->expect_variable_ident_decl("test");
diff --git a/src/resolver/inferred_type_test.cc b/src/resolver/inferred_type_test.cc
new file mode 100644
index 0000000..caceb43
--- /dev/null
+++ b/src/resolver/inferred_type_test.cc
@@ -0,0 +1,165 @@
+// 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/struct_block_decoration.h"
+#include "src/resolver/resolver.h"
+#include "src/resolver/resolver_test_helper.h"
+
+#include "gmock/gmock.h"
+
+namespace tint {
+namespace resolver {
+namespace {
+
+// Helpers and typedefs
+using i32 = ProgramBuilder::i32;
+using u32 = ProgramBuilder::u32;
+using f32 = ProgramBuilder::f32;
+
+struct ResolverInferredTypeTest : public resolver::TestHelper,
+                                  public testing::Test {};
+
+struct Params {
+  create_ast_type_func_ptr create_type;
+  create_sem_type_func_ptr create_expected_type;
+};
+
+Params all_cases[] = {
+    {ast_bool, sem_bool},
+    {ast_u32, sem_u32},
+    {ast_i32, sem_i32},
+    {ast_f32, sem_f32},
+    {ast_vec3<bool>, sem_vec3<sem_bool>},
+    {ast_vec3<i32>, sem_vec3<sem_i32>},
+    {ast_vec3<u32>, sem_vec3<sem_u32>},
+    {ast_vec3<f32>, sem_vec3<sem_f32>},
+    {ast_mat3x3<i32>, sem_mat3x3<sem_i32>},
+    {ast_mat3x3<u32>, sem_mat3x3<sem_u32>},
+    {ast_mat3x3<f32>, sem_mat3x3<sem_f32>},
+
+    {ast_alias<ast_bool>, sem_bool},
+    {ast_alias<ast_u32>, sem_u32},
+    {ast_alias<ast_i32>, sem_i32},
+    {ast_alias<ast_f32>, sem_f32},
+    {ast_alias<ast_vec3<bool>>, sem_vec3<sem_bool>},
+    {ast_alias<ast_vec3<i32>>, sem_vec3<sem_i32>},
+    {ast_alias<ast_vec3<u32>>, sem_vec3<sem_u32>},
+    {ast_alias<ast_vec3<f32>>, sem_vec3<sem_f32>},
+    {ast_alias<ast_mat3x3<i32>>, sem_mat3x3<sem_i32>},
+    {ast_alias<ast_mat3x3<u32>>, sem_mat3x3<sem_u32>},
+    {ast_alias<ast_mat3x3<f32>>, sem_mat3x3<sem_f32>},
+};
+
+using ResolverInferredTypeParamTest = ResolverTestWithParam<Params>;
+
+TEST_P(ResolverInferredTypeParamTest, GlobalLet_Pass) {
+  auto& params = GetParam();
+
+  auto* type = params.create_type(ty);
+  auto* expected_type = params.create_expected_type(ty);
+
+  // let a = <type constructor>;
+  auto* ctor_expr = ConstructValueFilledWith(type);
+  auto* var = GlobalConst("a", nullptr, ctor_expr);
+  WrapInFunction();
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var), expected_type);
+}
+
+TEST_P(ResolverInferredTypeParamTest, GlobalVar_Fail) {
+  auto& params = GetParam();
+
+  auto* type = params.create_type(ty);
+
+  // var a = <type constructor>;
+  auto* ctor_expr = ConstructValueFilledWith(type);
+  Global(Source{{12, 34}}, "a", nullptr, ast::StorageClass::kPrivate,
+         ctor_expr);
+  WrapInFunction();
+
+  EXPECT_FALSE(r()->Resolve());
+  EXPECT_EQ(r()->error(),
+            "12:34 error: global var declaration must specify a type");
+}
+
+TEST_P(ResolverInferredTypeParamTest, LocalLet_Pass) {
+  auto& params = GetParam();
+
+  auto* type = params.create_type(ty);
+  auto* expected_type = params.create_expected_type(ty);
+
+  // let a = <type constructor>;
+  auto* ctor_expr = ConstructValueFilledWith(type);
+  auto* var = Const("a", nullptr, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var), expected_type);
+}
+
+TEST_P(ResolverInferredTypeParamTest, LocalVar_Pass) {
+  auto& params = GetParam();
+
+  auto* type = params.create_type(ty);
+  auto* expected_type = params.create_expected_type(ty);
+
+  // var a = <type constructor>;
+  auto* ctor_expr = ConstructValueFilledWith(type);
+  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
+}
+
+INSTANTIATE_TEST_SUITE_P(ResolverTest,
+                         ResolverInferredTypeParamTest,
+                         testing::ValuesIn(all_cases));
+
+TEST_F(ResolverInferredTypeTest, InferArray_Pass) {
+  auto* type = ty.array(ty.u32(), 10);
+  auto* expected_type =
+      create<sem::Array>(create<sem::U32>(), 10, 4, 4 * 10, 4, true);
+
+  auto* ctor_expr = Construct(type);
+  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
+}
+
+TEST_F(ResolverInferredTypeTest, InferStruct_Pass) {
+  auto* member = Member("x", ty.i32());
+  auto* type = Structure("S", {member}, {create<ast::StructBlockDecoration>()});
+
+  auto* expected_type =
+      create<sem::Struct>(type,
+                          sem::StructMemberList{create<sem::StructMember>(
+                              member, create<sem::I32>(), 0, 0, 0, 4)},
+                          0, 4, 4);
+
+  auto* ctor_expr = Construct(type);
+
+  auto* var = Var("a", nullptr, ast::StorageClass::kFunction, ctor_expr);
+  WrapInFunction(var);
+
+  EXPECT_TRUE(r()->Resolve()) << r()->error();
+  EXPECT_EQ(TypeOf(var)->UnwrapRef(), expected_type);
+}
+
+}  // namespace
+}  // namespace resolver
+}  // namespace tint
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index 7b59942..9f81e32 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -417,6 +417,12 @@
 
     // If the variable has no declared type, infer it from the RHS
     if (!storage_type) {
+      if (!var->is_const() && kind == VariableKind::kGlobal) {
+        diagnostics_.add_error("global var declaration must specify a type",
+                               var->source());
+        return nullptr;
+      }
+
       type_name = rhs_type_name;
       storage_type = rhs_type->UnwrapRef();  // Implicit load of RHS
     }
diff --git a/test/var/inferred/function-let.wgsl b/test/var/inferred/function-let.wgsl
new file mode 100644
index 0000000..15fc9fa
--- /dev/null
+++ b/test/var/inferred/function-let.wgsl
@@ -0,0 +1,39 @@
+struct MyStruct {

+    f1 : f32;

+};

+

+type MyArray = array<f32, 10>;

+

+fn ret_i32() -> i32 { return 1; }

+fn ret_u32() -> u32 { return 1u; }

+fn ret_f32() -> f32 { return 1.0; }

+fn ret_MyStruct() -> MyStruct { return MyStruct(); }

+fn ret_MyArray() -> MyArray { return MyArray(); }

+

+// Local lets

+fn let_decls() {

+    let v1 = 1;

+    let v2 = 1u;

+    let v3 = 1.0;

+

+    let v4 = vec3<i32>(1, 1, 1);

+    let v5 = vec3<u32>(1u, 1u, 1u);

+    let v6 = vec3<f32>(1.0, 1.0, 1.0);

+

+    let v7 = mat3x3<f32>(v6, v6, v6);

+

+    let v8 = MyStruct(1.0);

+    let v9 = MyArray();

+

+    let v10 = ret_i32();

+    let v11 = ret_u32();

+    let v12 = ret_f32();

+    let v13 = ret_MyStruct();

+    let v14 = ret_MyStruct();

+    let v15 = ret_MyArray();

+}

+

+[[stage(fragment)]]

+fn main() -> [[location(0)]] vec4<f32> {

+    return vec4<f32>(0.0,0.0,0.0,0.0);

+}

diff --git a/test/var/inferred/function-let.wgsl.expected.hlsl b/test/var/inferred/function-let.wgsl.expected.hlsl
new file mode 100644
index 0000000..8f0accb
--- /dev/null
+++ b/test/var/inferred/function-let.wgsl.expected.hlsl
@@ -0,0 +1,63 @@
+SKIP: FAILED
+
+
+
+Validation Failure:
+struct MyStruct {
+  float f1;
+};
+struct tint_symbol {
+  float4 value : SV_Target0;
+};
+
+int ret_i32() {
+  return 1;
+}
+
+uint ret_u32() {
+  return 1u;
+}
+
+float ret_f32() {
+  return 1.0f;
+}
+
+MyStruct ret_MyStruct() {
+  const MyStruct tint_symbol_1 = {0.0f};
+  return tint_symbol_1;
+}
+
+float[10] ret_MyArray() {
+  const float tint_symbol_2[10] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+  return tint_symbol_2;
+}
+
+void let_decls() {
+  const int v1 = 1;
+  const uint v2 = 1u;
+  const float v3 = 1.0f;
+  const int3 v4 = int3(1, 1, 1);
+  const uint3 v5 = uint3(1u, 1u, 1u);
+  const float3 v6 = float3(1.0f, 1.0f, 1.0f);
+  const float3x3 v7 = float3x3(v6, v6, v6);
+  const MyStruct v8 = {1.0f};
+  const float v9[10] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+  const int v10 = ret_i32();
+  const uint v11 = ret_u32();
+  const float v12 = ret_f32();
+  const MyStruct v13 = ret_MyStruct();
+  const MyStruct v14 = ret_MyStruct();
+  const float v15[10] = ret_MyArray();
+}
+
+tint_symbol main() {
+  const tint_symbol tint_symbol_3 = {float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_3;
+}
+
+
+C:\src\temp\ut9k.0:25:24: error: brackets are not allowed here; to declare an array, place the brackets after the name

+float[10] ret_MyArray() {

+     ~~~~              ^

+                       [10]

+

diff --git a/test/var/inferred/function-let.wgsl.expected.msl b/test/var/inferred/function-let.wgsl.expected.msl
new file mode 100644
index 0000000..a4595d8
--- /dev/null
+++ b/test/var/inferred/function-let.wgsl.expected.msl
@@ -0,0 +1,58 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct MyStruct {
+  float f1;
+};
+struct tint_symbol_1 {
+  float4 value [[color(0)]];
+};
+struct tint_array_wrapper_0 {
+  float array[10];
+};
+
+int ret_i32() {
+  return 1;
+}
+
+uint ret_u32() {
+  return 1u;
+}
+
+float ret_f32() {
+  return 1.0f;
+}
+
+MyStruct ret_MyStruct() {
+  MyStruct const tint_symbol_2 = {};
+  return tint_symbol_2;
+}
+
+tint_array_wrapper_0 ret_MyArray() {
+  tint_array_wrapper_0 const tint_symbol_3 = {};
+  return tint_symbol_3;
+}
+
+void let_decls() {
+  int const v1 = 1;
+  uint const v2 = 1u;
+  float const v3 = 1.0f;
+  int3 const v4 = int3(1, 1, 1);
+  uint3 const v5 = uint3(1u, 1u, 1u);
+  float3 const v6 = float3(1.0f, 1.0f, 1.0f);
+  float3x3 const v7 = float3x3(v6, v6, v6);
+  MyStruct const v8 = {.f1=1.0f};
+  tint_array_wrapper_0 const v9 = {};
+  int const v10 = ret_i32();
+  uint const v11 = ret_u32();
+  float const v12 = ret_f32();
+  MyStruct const v13 = ret_MyStruct();
+  MyStruct const v14 = ret_MyStruct();
+  tint_array_wrapper_0 const v15 = ret_MyArray();
+}
+
+fragment tint_symbol_1 tint_symbol() {
+  tint_symbol_1 const tint_symbol_4 = {.value=float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_4;
+}
+
diff --git a/test/var/inferred/function-let.wgsl.expected.spvasm b/test/var/inferred/function-let.wgsl.expected.spvasm
new file mode 100644
index 0000000..4b590e8
--- /dev/null
+++ b/test/var/inferred/function-let.wgsl.expected.spvasm
@@ -0,0 +1,99 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 59
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %tint_symbol_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %tint_symbol_1 "tint_symbol_1"
+               OpName %ret_i32 "ret_i32"
+               OpName %ret_u32 "ret_u32"
+               OpName %ret_f32 "ret_f32"
+               OpName %MyStruct "MyStruct"
+               OpMemberName %MyStruct 0 "f1"
+               OpName %ret_MyStruct "ret_MyStruct"
+               OpName %ret_MyArray "ret_MyArray"
+               OpName %let_decls "let_decls"
+               OpName %tint_symbol_2 "tint_symbol_2"
+               OpName %tint_symbol "tint_symbol"
+               OpName %main "main"
+               OpDecorate %tint_symbol_1 Location 0
+               OpMemberDecorate %MyStruct 0 Offset 0
+               OpDecorate %_arr_float_uint_10 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+          %5 = OpConstantNull %v4float
+%tint_symbol_1 = OpVariable %_ptr_Output_v4float Output %5
+        %int = OpTypeInt 32 1
+          %6 = OpTypeFunction %int
+      %int_1 = OpConstant %int 1
+       %uint = OpTypeInt 32 0
+         %11 = OpTypeFunction %uint
+     %uint_1 = OpConstant %uint 1
+         %16 = OpTypeFunction %float
+    %float_1 = OpConstant %float 1
+   %MyStruct = OpTypeStruct %float
+         %20 = OpTypeFunction %MyStruct
+         %24 = OpConstantNull %MyStruct
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+         %25 = OpTypeFunction %_arr_float_uint_10
+         %30 = OpConstantNull %_arr_float_uint_10
+       %void = OpTypeVoid
+         %31 = OpTypeFunction %void
+      %v3int = OpTypeVector %int 3
+         %36 = OpConstantComposite %v3int %int_1 %int_1 %int_1
+     %v3uint = OpTypeVector %uint 3
+         %38 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
+    %v3float = OpTypeVector %float 3
+         %40 = OpConstantComposite %v3float %float_1 %float_1 %float_1
+%mat3v3float = OpTypeMatrix %v3float 3
+         %43 = OpConstantComposite %MyStruct %float_1
+         %50 = OpTypeFunction %void %v4float
+    %float_0 = OpConstant %float 0
+         %58 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+    %ret_i32 = OpFunction %int None %6
+          %9 = OpLabel
+               OpReturnValue %int_1
+               OpFunctionEnd
+    %ret_u32 = OpFunction %uint None %11
+         %14 = OpLabel
+               OpReturnValue %uint_1
+               OpFunctionEnd
+    %ret_f32 = OpFunction %float None %16
+         %18 = OpLabel
+               OpReturnValue %float_1
+               OpFunctionEnd
+%ret_MyStruct = OpFunction %MyStruct None %20
+         %23 = OpLabel
+               OpReturnValue %24
+               OpFunctionEnd
+%ret_MyArray = OpFunction %_arr_float_uint_10 None %25
+         %29 = OpLabel
+               OpReturnValue %30
+               OpFunctionEnd
+  %let_decls = OpFunction %void None %31
+         %34 = OpLabel
+         %42 = OpCompositeConstruct %mat3v3float %40 %40 %40
+         %44 = OpFunctionCall %int %ret_i32
+         %45 = OpFunctionCall %uint %ret_u32
+         %46 = OpFunctionCall %float %ret_f32
+         %47 = OpFunctionCall %MyStruct %ret_MyStruct
+         %48 = OpFunctionCall %MyStruct %ret_MyStruct
+         %49 = OpFunctionCall %_arr_float_uint_10 %ret_MyArray
+               OpReturn
+               OpFunctionEnd
+%tint_symbol_2 = OpFunction %void None %50
+%tint_symbol = OpFunctionParameter %v4float
+         %53 = OpLabel
+               OpStore %tint_symbol_1 %tint_symbol
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %31
+         %55 = OpLabel
+         %56 = OpFunctionCall %void %tint_symbol_2 %58
+               OpReturn
+               OpFunctionEnd
diff --git a/test/var/inferred/function-let.wgsl.expected.wgsl b/test/var/inferred/function-let.wgsl.expected.wgsl
new file mode 100644
index 0000000..03d68ef
--- /dev/null
+++ b/test/var/inferred/function-let.wgsl.expected.wgsl
@@ -0,0 +1,48 @@
+struct MyStruct {
+  f1 : f32;
+};
+
+type MyArray = array<f32, 10>;
+
+fn ret_i32() -> i32 {
+  return 1;
+}
+
+fn ret_u32() -> u32 {
+  return 1u;
+}
+
+fn ret_f32() -> f32 {
+  return 1.0;
+}
+
+fn ret_MyStruct() -> MyStruct {
+  return MyStruct();
+}
+
+fn ret_MyArray() -> MyArray {
+  return MyArray();
+}
+
+fn let_decls() {
+  let v1 = 1;
+  let v2 = 1u;
+  let v3 = 1.0;
+  let v4 = vec3<i32>(1, 1, 1);
+  let v5 = vec3<u32>(1u, 1u, 1u);
+  let v6 = vec3<f32>(1.0, 1.0, 1.0);
+  let v7 = mat3x3<f32>(v6, v6, v6);
+  let v8 = MyStruct(1.0);
+  let v9 = MyArray();
+  let v10 = ret_i32();
+  let v11 = ret_u32();
+  let v12 = ret_f32();
+  let v13 = ret_MyStruct();
+  let v14 = ret_MyStruct();
+  let v15 = ret_MyArray();
+}
+
+[[stage(fragment)]]
+fn main() -> [[location(0)]] vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
diff --git a/test/var/inferred/function-var.wgsl b/test/var/inferred/function-var.wgsl
new file mode 100644
index 0000000..0d39fe0
--- /dev/null
+++ b/test/var/inferred/function-var.wgsl
@@ -0,0 +1,39 @@
+struct MyStruct {

+    f1 : f32;

+};

+

+type MyArray = array<f32, 10>;

+

+fn ret_i32() -> i32 { return 1; }

+fn ret_u32() -> u32 { return 1u; }

+fn ret_f32() -> f32 { return 1.0; }

+fn ret_MyStruct() -> MyStruct { return MyStruct(); }

+fn ret_MyArray() -> MyArray { return MyArray(); }

+

+// Local variables

+fn var_decls() {

+    var v1 = 1;

+    var v2 = 1u;

+    var v3 = 1.0;

+

+    var v4 = vec3<i32>(1, 1, 1);

+    var v5 = vec3<u32>(1u, 1u, 1u);

+    var v6 = vec3<f32>(1.0, 1.0, 1.0);

+

+    var v7 = mat3x3<f32>(v6, v6, v6);

+

+    var v8 = MyStruct(1.0);

+    var v9 = MyArray();

+

+    var v10 = ret_i32();

+    var v11 = ret_u32();

+    var v12 = ret_f32();

+    var v13 = ret_MyStruct();

+    var v14 = ret_MyStruct();

+    var v15 = ret_MyArray();

+}

+

+[[stage(fragment)]]

+fn main() -> [[location(0)]] vec4<f32> {

+    return vec4<f32>(0.0,0.0,0.0,0.0);

+}

diff --git a/test/var/inferred/function-var.wgsl.expected.hlsl b/test/var/inferred/function-var.wgsl.expected.hlsl
new file mode 100644
index 0000000..253e39d
--- /dev/null
+++ b/test/var/inferred/function-var.wgsl.expected.hlsl
@@ -0,0 +1,63 @@
+SKIP: FAILED
+
+
+
+Validation Failure:
+struct MyStruct {
+  float f1;
+};
+struct tint_symbol {
+  float4 value : SV_Target0;
+};
+
+int ret_i32() {
+  return 1;
+}
+
+uint ret_u32() {
+  return 1u;
+}
+
+float ret_f32() {
+  return 1.0f;
+}
+
+MyStruct ret_MyStruct() {
+  const MyStruct tint_symbol_1 = {0.0f};
+  return tint_symbol_1;
+}
+
+float[10] ret_MyArray() {
+  const float tint_symbol_2[10] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+  return tint_symbol_2;
+}
+
+void var_decls() {
+  int v1 = 1;
+  uint v2 = 1u;
+  float v3 = 1.0f;
+  int3 v4 = int3(1, 1, 1);
+  uint3 v5 = uint3(1u, 1u, 1u);
+  float3 v6 = float3(1.0f, 1.0f, 1.0f);
+  float3x3 v7 = float3x3(v6, v6, v6);
+  MyStruct v8 = {1.0f};
+  float v9[10] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+  int v10 = ret_i32();
+  uint v11 = ret_u32();
+  float v12 = ret_f32();
+  MyStruct v13 = ret_MyStruct();
+  MyStruct v14 = ret_MyStruct();
+  float v15[10] = ret_MyArray();
+}
+
+tint_symbol main() {
+  const tint_symbol tint_symbol_3 = {float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_3;
+}
+
+
+C:\src\temp\u1efk.0:25:24: error: brackets are not allowed here; to declare an array, place the brackets after the name

+float[10] ret_MyArray() {

+     ~~~~              ^

+                       [10]

+

diff --git a/test/var/inferred/function-var.wgsl.expected.msl b/test/var/inferred/function-var.wgsl.expected.msl
new file mode 100644
index 0000000..66122e7
--- /dev/null
+++ b/test/var/inferred/function-var.wgsl.expected.msl
@@ -0,0 +1,58 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct MyStruct {
+  float f1;
+};
+struct tint_symbol_1 {
+  float4 value [[color(0)]];
+};
+struct tint_array_wrapper_0 {
+  float array[10];
+};
+
+int ret_i32() {
+  return 1;
+}
+
+uint ret_u32() {
+  return 1u;
+}
+
+float ret_f32() {
+  return 1.0f;
+}
+
+MyStruct ret_MyStruct() {
+  MyStruct const tint_symbol_2 = {};
+  return tint_symbol_2;
+}
+
+tint_array_wrapper_0 ret_MyArray() {
+  tint_array_wrapper_0 const tint_symbol_3 = {};
+  return tint_symbol_3;
+}
+
+void var_decls() {
+  int v1 = 1;
+  uint v2 = 1u;
+  float v3 = 1.0f;
+  int3 v4 = int3(1, 1, 1);
+  uint3 v5 = uint3(1u, 1u, 1u);
+  float3 v6 = float3(1.0f, 1.0f, 1.0f);
+  float3x3 v7 = float3x3(v6, v6, v6);
+  MyStruct v8 = {.f1=1.0f};
+  tint_array_wrapper_0 v9 = {};
+  int v10 = ret_i32();
+  uint v11 = ret_u32();
+  float v12 = ret_f32();
+  MyStruct v13 = ret_MyStruct();
+  MyStruct v14 = ret_MyStruct();
+  tint_array_wrapper_0 v15 = ret_MyArray();
+}
+
+fragment tint_symbol_1 tint_symbol() {
+  tint_symbol_1 const tint_symbol_4 = {.value=float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_4;
+}
+
diff --git a/test/var/inferred/function-var.wgsl.expected.spvasm b/test/var/inferred/function-var.wgsl.expected.spvasm
new file mode 100644
index 0000000..94534db
--- /dev/null
+++ b/test/var/inferred/function-var.wgsl.expected.spvasm
@@ -0,0 +1,163 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 93
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %tint_symbol_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %tint_symbol_1 "tint_symbol_1"
+               OpName %ret_i32 "ret_i32"
+               OpName %ret_u32 "ret_u32"
+               OpName %ret_f32 "ret_f32"
+               OpName %MyStruct "MyStruct"
+               OpMemberName %MyStruct 0 "f1"
+               OpName %ret_MyStruct "ret_MyStruct"
+               OpName %ret_MyArray "ret_MyArray"
+               OpName %var_decls "var_decls"
+               OpName %v1 "v1"
+               OpName %v2 "v2"
+               OpName %v3 "v3"
+               OpName %v4 "v4"
+               OpName %v5 "v5"
+               OpName %v6 "v6"
+               OpName %v7 "v7"
+               OpName %v8 "v8"
+               OpName %v9 "v9"
+               OpName %v10 "v10"
+               OpName %v11 "v11"
+               OpName %v12 "v12"
+               OpName %v13 "v13"
+               OpName %v14 "v14"
+               OpName %v15 "v15"
+               OpName %tint_symbol_2 "tint_symbol_2"
+               OpName %tint_symbol "tint_symbol"
+               OpName %main "main"
+               OpDecorate %tint_symbol_1 Location 0
+               OpMemberDecorate %MyStruct 0 Offset 0
+               OpDecorate %_arr_float_uint_10 ArrayStride 4
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+          %5 = OpConstantNull %v4float
+%tint_symbol_1 = OpVariable %_ptr_Output_v4float Output %5
+        %int = OpTypeInt 32 1
+          %6 = OpTypeFunction %int
+      %int_1 = OpConstant %int 1
+       %uint = OpTypeInt 32 0
+         %11 = OpTypeFunction %uint
+     %uint_1 = OpConstant %uint 1
+         %16 = OpTypeFunction %float
+    %float_1 = OpConstant %float 1
+   %MyStruct = OpTypeStruct %float
+         %20 = OpTypeFunction %MyStruct
+         %24 = OpConstantNull %MyStruct
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+         %25 = OpTypeFunction %_arr_float_uint_10
+         %30 = OpConstantNull %_arr_float_uint_10
+       %void = OpTypeVoid
+         %31 = OpTypeFunction %void
+%_ptr_Function_int = OpTypePointer Function %int
+         %37 = OpConstantNull %int
+%_ptr_Function_uint = OpTypePointer Function %uint
+         %40 = OpConstantNull %uint
+%_ptr_Function_float = OpTypePointer Function %float
+         %43 = OpConstantNull %float
+      %v3int = OpTypeVector %int 3
+         %45 = OpConstantComposite %v3int %int_1 %int_1 %int_1
+%_ptr_Function_v3int = OpTypePointer Function %v3int
+         %48 = OpConstantNull %v3int
+     %v3uint = OpTypeVector %uint 3
+         %50 = OpConstantComposite %v3uint %uint_1 %uint_1 %uint_1
+%_ptr_Function_v3uint = OpTypePointer Function %v3uint
+         %53 = OpConstantNull %v3uint
+    %v3float = OpTypeVector %float 3
+         %55 = OpConstantComposite %v3float %float_1 %float_1 %float_1
+%_ptr_Function_v3float = OpTypePointer Function %v3float
+         %58 = OpConstantNull %v3float
+%mat3v3float = OpTypeMatrix %v3float 3
+%_ptr_Function_mat3v3float = OpTypePointer Function %mat3v3float
+         %66 = OpConstantNull %mat3v3float
+         %67 = OpConstantComposite %MyStruct %float_1
+%_ptr_Function_MyStruct = OpTypePointer Function %MyStruct
+%_ptr_Function__arr_float_uint_10 = OpTypePointer Function %_arr_float_uint_10
+         %84 = OpTypeFunction %void %v4float
+    %float_0 = OpConstant %float 0
+         %92 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+    %ret_i32 = OpFunction %int None %6
+          %9 = OpLabel
+               OpReturnValue %int_1
+               OpFunctionEnd
+    %ret_u32 = OpFunction %uint None %11
+         %14 = OpLabel
+               OpReturnValue %uint_1
+               OpFunctionEnd
+    %ret_f32 = OpFunction %float None %16
+         %18 = OpLabel
+               OpReturnValue %float_1
+               OpFunctionEnd
+%ret_MyStruct = OpFunction %MyStruct None %20
+         %23 = OpLabel
+               OpReturnValue %24
+               OpFunctionEnd
+%ret_MyArray = OpFunction %_arr_float_uint_10 None %25
+         %29 = OpLabel
+               OpReturnValue %30
+               OpFunctionEnd
+  %var_decls = OpFunction %void None %31
+         %34 = OpLabel
+         %v1 = OpVariable %_ptr_Function_int Function %37
+         %v2 = OpVariable %_ptr_Function_uint Function %40
+         %v3 = OpVariable %_ptr_Function_float Function %43
+         %v4 = OpVariable %_ptr_Function_v3int Function %48
+         %v5 = OpVariable %_ptr_Function_v3uint Function %53
+         %v6 = OpVariable %_ptr_Function_v3float Function %58
+         %v7 = OpVariable %_ptr_Function_mat3v3float Function %66
+         %v8 = OpVariable %_ptr_Function_MyStruct Function %24
+         %v9 = OpVariable %_ptr_Function__arr_float_uint_10 Function %30
+        %v10 = OpVariable %_ptr_Function_int Function %37
+        %v11 = OpVariable %_ptr_Function_uint Function %40
+        %v12 = OpVariable %_ptr_Function_float Function %43
+        %v13 = OpVariable %_ptr_Function_MyStruct Function %24
+        %v14 = OpVariable %_ptr_Function_MyStruct Function %24
+        %v15 = OpVariable %_ptr_Function__arr_float_uint_10 Function %30
+               OpStore %v1 %int_1
+               OpStore %v2 %uint_1
+               OpStore %v3 %float_1
+               OpStore %v4 %45
+               OpStore %v5 %50
+               OpStore %v6 %55
+         %60 = OpLoad %v3float %v6
+         %61 = OpLoad %v3float %v6
+         %62 = OpLoad %v3float %v6
+         %63 = OpCompositeConstruct %mat3v3float %60 %61 %62
+               OpStore %v7 %63
+               OpStore %v8 %67
+               OpStore %v9 %30
+         %72 = OpFunctionCall %int %ret_i32
+               OpStore %v10 %72
+         %74 = OpFunctionCall %uint %ret_u32
+               OpStore %v11 %74
+         %76 = OpFunctionCall %float %ret_f32
+               OpStore %v12 %76
+         %78 = OpFunctionCall %MyStruct %ret_MyStruct
+               OpStore %v13 %78
+         %80 = OpFunctionCall %MyStruct %ret_MyStruct
+               OpStore %v14 %80
+         %82 = OpFunctionCall %_arr_float_uint_10 %ret_MyArray
+               OpStore %v15 %82
+               OpReturn
+               OpFunctionEnd
+%tint_symbol_2 = OpFunction %void None %84
+%tint_symbol = OpFunctionParameter %v4float
+         %87 = OpLabel
+               OpStore %tint_symbol_1 %tint_symbol
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %31
+         %89 = OpLabel
+         %90 = OpFunctionCall %void %tint_symbol_2 %92
+               OpReturn
+               OpFunctionEnd
diff --git a/test/var/inferred/function-var.wgsl.expected.wgsl b/test/var/inferred/function-var.wgsl.expected.wgsl
new file mode 100644
index 0000000..4614108
--- /dev/null
+++ b/test/var/inferred/function-var.wgsl.expected.wgsl
@@ -0,0 +1,48 @@
+struct MyStruct {
+  f1 : f32;
+};
+
+type MyArray = array<f32, 10>;
+
+fn ret_i32() -> i32 {
+  return 1;
+}
+
+fn ret_u32() -> u32 {
+  return 1u;
+}
+
+fn ret_f32() -> f32 {
+  return 1.0;
+}
+
+fn ret_MyStruct() -> MyStruct {
+  return MyStruct();
+}
+
+fn ret_MyArray() -> MyArray {
+  return MyArray();
+}
+
+fn var_decls() {
+  var v1 = 1;
+  var v2 = 1u;
+  var v3 = 1.0;
+  var v4 = vec3<i32>(1, 1, 1);
+  var v5 = vec3<u32>(1u, 1u, 1u);
+  var v6 = vec3<f32>(1.0, 1.0, 1.0);
+  var v7 = mat3x3<f32>(v6, v6, v6);
+  var v8 = MyStruct(1.0);
+  var v9 = MyArray();
+  var v10 = ret_i32();
+  var v11 = ret_u32();
+  var v12 = ret_f32();
+  var v13 = ret_MyStruct();
+  var v14 = ret_MyStruct();
+  var v15 = ret_MyArray();
+}
+
+[[stage(fragment)]]
+fn main() -> [[location(0)]] vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}
diff --git a/test/var/inferred/global-let.wgsl b/test/var/inferred/global-let.wgsl
new file mode 100644
index 0000000..7928f9b
--- /dev/null
+++ b/test/var/inferred/global-let.wgsl
@@ -0,0 +1,24 @@
+struct MyStruct {

+    f1 : f32;

+};

+

+type MyArray = array<f32, 10>;

+

+// Global lets

+let v1 = 1;

+let v2 = 1u;

+let v3 = 1.0;

+

+let v4 = vec3<i32>(1, 1, 1);

+let v5 = vec3<u32>(1u, 1u, 1u);

+let v6 = vec3<f32>(1.0, 1.0, 1.0);

+

+let v7 = mat3x3<f32>(vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(1.0, 1.0, 1.0));

+

+let v8 = MyStruct();

+let v9 = MyArray();

+

+[[stage(fragment)]]

+fn main() -> [[location(0)]] vec4<f32> {

+    return vec4<f32>(0.0,0.0,0.0,0.0);

+}

diff --git a/test/var/inferred/global-let.wgsl.expected.hlsl b/test/var/inferred/global-let.wgsl.expected.hlsl
new file mode 100644
index 0000000..182e4b0
--- /dev/null
+++ b/test/var/inferred/global-let.wgsl.expected.hlsl
@@ -0,0 +1,21 @@
+struct MyStruct {
+  float f1;
+};
+struct tint_symbol {
+  float4 value : SV_Target0;
+};
+
+static const int v1 = 1;
+static const uint v2 = 1u;
+static const float v3 = 1.0f;
+static const int3 v4 = int3(1, 1, 1);
+static const uint3 v5 = uint3(1u, 1u, 1u);
+static const float3 v6 = float3(1.0f, 1.0f, 1.0f);
+static const float3x3 v7 = float3x3(float3(1.0f, 1.0f, 1.0f), float3(1.0f, 1.0f, 1.0f), float3(1.0f, 1.0f, 1.0f));
+static const MyStruct v8 = {0.0f};
+static const float v9[10] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+tint_symbol main() {
+  const tint_symbol tint_symbol_1 = {float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_1;
+}
+
diff --git a/test/var/inferred/global-let.wgsl.expected.msl b/test/var/inferred/global-let.wgsl.expected.msl
new file mode 100644
index 0000000..65f9d52
--- /dev/null
+++ b/test/var/inferred/global-let.wgsl.expected.msl
@@ -0,0 +1,27 @@
+#include <metal_stdlib>
+
+using namespace metal;
+struct MyStruct {
+  float f1;
+};
+struct tint_symbol_1 {
+  float4 value [[color(0)]];
+};
+struct tint_array_wrapper_0 {
+  float array[10];
+};
+
+constant int v1 = 1;
+constant uint v2 = 1u;
+constant float v3 = 1.0f;
+constant int3 v4 = int3(1, 1, 1);
+constant uint3 v5 = uint3(1u, 1u, 1u);
+constant float3 v6 = float3(1.0f, 1.0f, 1.0f);
+constant float3x3 v7 = float3x3(float3(1.0f, 1.0f, 1.0f), float3(1.0f, 1.0f, 1.0f), float3(1.0f, 1.0f, 1.0f));
+constant MyStruct v8 = {};
+constant tint_array_wrapper_0 v9 = {};
+fragment tint_symbol_1 tint_symbol() {
+  tint_symbol_1 const tint_symbol_2 = {.value=float4(0.0f, 0.0f, 0.0f, 0.0f)};
+  return tint_symbol_2;
+}
+
diff --git a/test/var/inferred/global-let.wgsl.expected.spvasm b/test/var/inferred/global-let.wgsl.expected.spvasm
new file mode 100644
index 0000000..ecf6016
--- /dev/null
+++ b/test/var/inferred/global-let.wgsl.expected.spvasm
@@ -0,0 +1,66 @@
+; SPIR-V
+; Version: 1.3
+; Generator: Google Tint Compiler; 0
+; Bound: 35
+; Schema: 0
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %tint_symbol_1
+               OpExecutionMode %main OriginUpperLeft
+               OpName %v1 "v1"
+               OpName %v2 "v2"
+               OpName %v3 "v3"
+               OpName %v4 "v4"
+               OpName %v5 "v5"
+               OpName %v6 "v6"
+               OpName %v7 "v7"
+               OpName %MyStruct "MyStruct"
+               OpMemberName %MyStruct 0 "f1"
+               OpName %v8 "v8"
+               OpName %v9 "v9"
+               OpName %tint_symbol_1 "tint_symbol_1"
+               OpName %tint_symbol_2 "tint_symbol_2"
+               OpName %tint_symbol "tint_symbol"
+               OpName %main "main"
+               OpMemberDecorate %MyStruct 0 Offset 0
+               OpDecorate %_arr_float_uint_10 ArrayStride 4
+               OpDecorate %tint_symbol_1 Location 0
+        %int = OpTypeInt 32 1
+         %v1 = OpConstant %int 1
+       %uint = OpTypeInt 32 0
+         %v2 = OpConstant %uint 1
+      %float = OpTypeFloat 32
+         %v3 = OpConstant %float 1
+      %v3int = OpTypeVector %int 3
+         %v4 = OpConstantComposite %v3int %v1 %v1 %v1
+     %v3uint = OpTypeVector %uint 3
+         %v5 = OpConstantComposite %v3uint %v2 %v2 %v2
+    %v3float = OpTypeVector %float 3
+         %v6 = OpConstantComposite %v3float %v3 %v3 %v3
+%mat3v3float = OpTypeMatrix %v3float 3
+         %v7 = OpConstantComposite %mat3v3float %v6 %v6 %v6
+   %MyStruct = OpTypeStruct %float
+         %v8 = OpConstantNull %MyStruct
+    %uint_10 = OpConstant %uint 10
+%_arr_float_uint_10 = OpTypeArray %float %uint_10
+         %v9 = OpConstantNull %_arr_float_uint_10
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+         %23 = OpConstantNull %v4float
+%tint_symbol_1 = OpVariable %_ptr_Output_v4float Output %23
+       %void = OpTypeVoid
+         %24 = OpTypeFunction %void %v4float
+         %29 = OpTypeFunction %void
+    %float_0 = OpConstant %float 0
+         %34 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+%tint_symbol_2 = OpFunction %void None %24
+%tint_symbol = OpFunctionParameter %v4float
+         %28 = OpLabel
+               OpStore %tint_symbol_1 %tint_symbol
+               OpReturn
+               OpFunctionEnd
+       %main = OpFunction %void None %29
+         %31 = OpLabel
+         %32 = OpFunctionCall %void %tint_symbol_2 %34
+               OpReturn
+               OpFunctionEnd
diff --git a/test/var/inferred/global-let.wgsl.expected.wgsl b/test/var/inferred/global-let.wgsl.expected.wgsl
new file mode 100644
index 0000000..126f25c
--- /dev/null
+++ b/test/var/inferred/global-let.wgsl.expected.wgsl
@@ -0,0 +1,28 @@
+struct MyStruct {
+  f1 : f32;
+};
+
+type MyArray = array<f32, 10>;
+
+let v1 = 1;
+
+let v2 = 1u;
+
+let v3 = 1.0;
+
+let v4 = vec3<i32>(1, 1, 1);
+
+let v5 = vec3<u32>(1u, 1u, 1u);
+
+let v6 = vec3<f32>(1.0, 1.0, 1.0);
+
+let v7 = mat3x3<f32>(vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(1.0, 1.0, 1.0), vec3<f32>(1.0, 1.0, 1.0));
+
+let v8 = MyStruct();
+
+let v9 = MyArray();
+
+[[stage(fragment)]]
+fn main() -> [[location(0)]] vec4<f32> {
+  return vec4<f32>(0.0, 0.0, 0.0, 0.0);
+}