tint/resolver: Clean up 'let' / 'override' resolving

Split the logic out of Resolver::Variable().

Primes the resolver for the introduction of 'const'

Bug: tint:1580
Change-Id: Id67280ed5c8c73a69c62728fb5a81a08f13628a8
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/93785
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 8d3cfdd..dcd49fb 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -312,6 +312,20 @@
 }
 
 sem::Variable* Resolver::Variable(const ast::Variable* v, bool is_global) {
+    return Switch(
+        v,  //
+        [&](const ast::Var* var) { return Var(var, is_global); },
+        [&](const ast::Let* let) { return Let(let, is_global); },
+        [&](const ast::Override* override) { return Override(override); },
+        [&](Default) {
+            TINT_ICE(Resolver, diagnostics_)
+                << "Resolver::GlobalVariable() called with a unknown variable type: "
+                << v->TypeInfo().name;
+            return nullptr;
+        });
+}
+
+sem::Variable* Resolver::Let(const ast::Let* v, bool is_global) {
     const sem::Type* ty = nullptr;
 
     // If the variable has a declared type, resolve it.
@@ -322,8 +336,58 @@
         }
     }
 
-    auto* as_let = v->As<ast::Let>();
-    auto* as_override = v->As<ast::Override>();
+    if (!v->constructor) {
+        AddError("'let' declaration must have an initializer", v->source);
+        return nullptr;
+    }
+
+    auto* rhs = Materialize(Expression(v->constructor), ty);
+    if (!rhs) {
+        return nullptr;
+    }
+
+    // If the variable has no declared type, infer it from the RHS
+    if (!ty) {
+        ty = rhs->Type()->UnwrapRef();  // Implicit load of RHS
+    }
+
+    if (rhs &&
+        !validator_.VariableConstructorOrCast(v, ast::StorageClass::kNone, ty, rhs->Type())) {
+        return nullptr;
+    }
+
+    if (!ApplyStorageClassUsageToType(ast::StorageClass::kNone, const_cast<sem::Type*>(ty),
+                                      v->source)) {
+        AddNote("while instantiating 'let' " + builder_->Symbols().NameFor(v->symbol), v->source);
+        return nullptr;
+    }
+
+    sem::Variable* sem = nullptr;
+    if (is_global) {
+        sem = builder_->create<sem::GlobalVariable>(
+            v, ty, ast::StorageClass::kNone, ast::Access::kUndefined,
+            rhs ? rhs->ConstantValue() : sem::Constant{}, sem::BindingPoint{});
+    } else {
+        sem = builder_->create<sem::LocalVariable>(v, ty, ast::StorageClass::kNone,
+                                                   ast::Access::kUndefined, current_statement_,
+                                                   rhs ? rhs->ConstantValue() : sem::Constant{});
+    }
+
+    sem->SetConstructor(rhs);
+    builder_->Sem().Add(v, sem);
+    return sem;
+}
+
+sem::Variable* Resolver::Override(const ast::Override* v) {
+    const sem::Type* ty = nullptr;
+
+    // If the variable has a declared type, resolve it.
+    if (v->type) {
+        ty = Type(v->type);
+        if (!ty) {
+            return nullptr;
+        }
+    }
 
     const sem::Expression* rhs = nullptr;
 
@@ -338,9 +402,6 @@
         if (!ty) {
             ty = rhs->Type()->UnwrapRef();  // Implicit load of RHS
         }
-    } else if (as_let) {
-        AddError("'let' declaration must have an initializer", v->source);
-        return nullptr;
     } else if (!ty) {
         AddError("'override' declaration requires a type or initializer", v->source);
         return nullptr;
@@ -353,28 +414,17 @@
 
     if (!ApplyStorageClassUsageToType(ast::StorageClass::kNone, const_cast<sem::Type*>(ty),
                                       v->source)) {
-        AddNote("while instantiating variable " + builder_->Symbols().NameFor(v->symbol),
+        AddNote("while instantiating 'override' " + builder_->Symbols().NameFor(v->symbol),
                 v->source);
         return nullptr;
     }
 
-    sem::Variable* sem = nullptr;
-    if (is_global) {
-        bool has_const_val = rhs && !as_override;
-        auto* global = builder_->create<sem::GlobalVariable>(
-            v, ty, ast::StorageClass::kNone, ast::Access::kUndefined,
-            has_const_val ? rhs->ConstantValue() : sem::Constant{}, sem::BindingPoint{});
+    auto* sem = builder_->create<sem::GlobalVariable>(v, ty, ast::StorageClass::kNone,
+                                                      ast::Access::kUndefined, sem::Constant{},
+                                                      sem::BindingPoint{});
 
-        if (as_override) {
-            if (auto* id = ast::GetAttribute<ast::IdAttribute>(v->attributes)) {
-                global->SetConstantId(static_cast<uint16_t>(id->value));
-            }
-        }
-        sem = global;
-    } else {
-        sem = builder_->create<sem::LocalVariable>(
-            v, ty, ast::StorageClass::kNone, ast::Access::kUndefined, current_statement_,
-            (rhs && as_let) ? rhs->ConstantValue() : sem::Constant{});
+    if (auto* id = ast::GetAttribute<ast::IdAttribute>(v->attributes)) {
+        sem->SetConstantId(static_cast<uint16_t>(id->value));
     }
 
     sem->SetConstructor(rhs);
@@ -567,10 +617,7 @@
 }
 
 sem::GlobalVariable* Resolver::GlobalVariable(const ast::Variable* v) {
-    auto* as_var = v->As<ast::Var>();
-
-    auto* sem = As<sem::GlobalVariable>(as_var ? Var(as_var, /* is_global */ true)
-                                               : Variable(v, /* is_global */ true));
+    auto* sem = As<sem::GlobalVariable>(Variable(v, /* is_global */ true));
     if (!sem) {
         return nullptr;
     }
@@ -2477,10 +2524,7 @@
     return StatementScope(stmt, sem, [&] {
         Mark(stmt->variable);
 
-        auto* variable = Switch(
-            stmt->variable,  //
-            [&](const ast::Var* var) { return Var(var, /* is_global */ false); },
-            [&](Default) { return Variable(stmt->variable, /* is_global */ false); });
+        auto* variable = Variable(stmt->variable, /* is_global */ false);
         if (!variable) {
             return false;
         }
@@ -2501,10 +2545,7 @@
             sem->Behaviors() = ctor->Behaviors();
         }
 
-        return Switch(
-            stmt->variable,  //
-            [&](const ast::Var*) { return validator_.Var(variable); },
-            [&](Default) { return validator_.Variable(variable); });
+        return validator_.Variable(variable);
     });
 }
 
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index faaa2d4..5b73e9d 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -298,6 +298,21 @@
     /// @param is_global true if this is module scope, otherwise function scope
     sem::Variable* Variable(const ast::Variable* var, bool is_global);
 
+    /// @returns the semantic info for the `ast::Let` `v`. If an error is raised, nullptr is
+    /// returned.
+    /// @note this method does not resolve the attributes as these are context-dependent (global,
+    /// local)
+    /// @param var the variable
+    /// @param is_global true if this is module scope, otherwise function scope
+    sem::Variable* Let(const ast::Let* var, bool is_global);
+
+    /// @returns the semantic info for the module-scope `ast::Override` `v`. If an error is raised,
+    /// nullptr is returned.
+    /// @note this method does not resolve the attributes as these are context-dependent (global,
+    /// local)
+    /// @param override the variable
+    sem::Variable* Override(const ast::Override* override);
+
     /// @returns the semantic info for the `ast::Var` `var`. If an error is raised, nullptr is
     /// returned.
     /// @note this method does not resolve the attributes as these are context-dependent (global,
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 53ce696..b12143f 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -558,7 +558,7 @@
                     return false;
                 }
             }
-            return Variable(global);
+            return Override(global);
         },
         [&](const ast::Let*) {
             if (!decl->attributes.empty()) {
@@ -566,7 +566,7 @@
                          decl->attributes[0]->source);
                 return false;
             }
-            return Variable(global);
+            return Let(global);
         },
         [&](const ast::Var* var) {
             if (global->StorageClass() == ast::StorageClass::kNone) {
@@ -684,21 +684,53 @@
 
 bool Validator::Variable(const sem::Variable* v) const {
     auto* decl = v->Declaration();
-    auto* storage_ty = v->Type()->UnwrapRef();
+    return Switch(
+        decl,                                               //
+        [&](const ast::Var*) { return Var(v); },            //
+        [&](const ast::Let*) { return Let(v); },            //
+        [&](const ast::Override*) { return Override(v); },  //
+        [&](Default) {
+            TINT_ICE(Resolver, diagnostics_)
+                << "Validator::Variable() called with a unknown variable type: "
+                << decl->TypeInfo().name;
+            return false;
+        });
+}
 
-    auto* kind = decl->Is<ast::Override>() ? "'override'" : "'let'";
+bool Validator::Let(const sem::Variable* v) const {
+    auto* decl = v->Declaration();
+    auto* storage_ty = v->Type()->UnwrapRef();
 
     if (v->Is<sem::GlobalVariable>()) {
         auto name = symbols_.NameFor(decl->symbol);
         if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
-            AddError("'" + name + "' is a builtin and cannot be redeclared as a " + kind,
+            AddError("'" + name + "' is a builtin and cannot be redeclared as a 'let'",
                      decl->source);
             return false;
         }
     }
 
     if (!(storage_ty->IsConstructible() || storage_ty->Is<sem::Pointer>())) {
-        AddError(sem_.TypeNameOf(storage_ty) + " cannot be used as the type of a " + kind,
+        AddError(sem_.TypeNameOf(storage_ty) + " cannot be used as the type of a 'let'",
+                 decl->source);
+        return false;
+    }
+    return true;
+}
+
+bool Validator::Override(const sem::Variable* v) const {
+    auto* decl = v->Declaration();
+    auto* storage_ty = v->Type()->UnwrapRef();
+
+    auto name = symbols_.NameFor(decl->symbol);
+    if (sem::ParseBuiltinType(name) != sem::BuiltinType::kNone) {
+        AddError("'" + name + "' is a builtin and cannot be redeclared as a 'override'",
+                 decl->source);
+        return false;
+    }
+
+    if (!storage_ty->is_scalar()) {
+        AddError(sem_.TypeNameOf(storage_ty) + " cannot be used as the type of a 'override'",
                  decl->source);
         return false;
     }
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index f417557..8437752 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -356,6 +356,16 @@
     /// @returns true on success, false otherwise.
     bool Variable(const sem::Variable* v) const;
 
+    /// Validates a 'let' variable declaration
+    /// @param v the variable to validate
+    /// @returns true on success, false otherwise.
+    bool Let(const sem::Variable* v) const;
+
+    /// Validates a 'override' variable declaration
+    /// @param v the variable to validate
+    /// @returns true on success, false otherwise.
+    bool Override(const sem::Variable* v) const;
+
     /// Validates a 'var' variable declaration
     /// @param v the variable to validate
     /// @returns true on success, false otherwise.
diff --git a/src/tint/resolver/var_let_validation_test.cc b/src/tint/resolver/var_let_validation_test.cc
index 6b9df5d..5d05c9a 100644
--- a/src/tint/resolver/var_let_validation_test.cc
+++ b/src/tint/resolver/var_let_validation_test.cc
@@ -74,6 +74,22 @@
     EXPECT_EQ(r()->error(), "56:78 error: texture_2d<f32> cannot be used as the type of a 'let'");
 }
 
+TEST_F(ResolverVarLetValidationTest, OverrideExplicitTypeNotScalar) {
+    // override o : vec3<f32>;
+    Override(Source{{56, 78}}, "o", ty.vec3<f32>(), nullptr);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "56:78 error: vec3<f32> cannot be used as the type of a 'override'");
+}
+
+TEST_F(ResolverVarLetValidationTest, OverrideInferedTypeNotScalar) {
+    // override o = vec3(1.0f);
+    Override(Source{{56, 78}}, "o", nullptr, vec3<f32>(1.0_f));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "56:78 error: vec3<f32> cannot be used as the type of a 'override'");
+}
+
 TEST_F(ResolverVarLetValidationTest, LetConstructorWrongType) {
     // var v : i32 = 2u
     WrapInFunction(Let(Source{{3, 3}}, "v", ty.i32(), Expr(2_u)));