// Copyright 2021 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "gmock/gmock.h"
#include "src/tint/lang/wgsl/resolver/resolve.h"
#include "src/tint/lang/wgsl/resolver/resolver.h"
#include "src/tint/lang/wgsl/resolver/resolver_helper_test.h"

namespace tint::resolver {
namespace {


using ResolverValidationTest = ResolverTest;
using ResolverValidationDeathTest = ResolverValidationTest;

class FakeStmt final : public Castable<FakeStmt, ast::Statement> {
  public:
    FakeStmt(ast::NodeID nid, Source src) : Base(nid, src) {}
};

class FakeExpr final : public Castable<FakeExpr, ast::Expression> {
  public:
    FakeExpr(ast::NodeID nid, Source src) : Base(nid, src) {}
};

TEST_F(ResolverValidationTest, WorkgroupMemoryUsedInVertexStage) {
    EXPECT_ERROR(
        R"(
var<workgroup> wg: vec4<f32>;

@vertex
fn f0() -> @builtin(position) vec4f {
  return wg;
}
)",
        R"(
input.wgsl:6:10 error: var with 'workgroup' address space cannot be used by vertex pipeline stage
  return wg;
         ^^

input.wgsl:2:1 note: variable is declared here
var<workgroup> wg: vec4<f32>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, WorkgroupMemoryUsedInFragmentStage) {
    EXPECT_ERROR(
        R"(
var<workgroup> wg: vec4<f32>;
var<workgroup> dst: vec4<f32>;
fn f2() { dst = wg; }
fn f1() { f2(); }
@fragment
fn f0() {
 f1();
}
)",
        R"(
input.wgsl:4:11 error: var with 'workgroup' address space cannot be used by fragment pipeline stage
fn f2() { dst = wg; }
          ^^^

input.wgsl:3:1 note: variable is declared here
var<workgroup> dst: vec4<f32>;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

input.wgsl:4:1 note: called by function 'f2'
fn f2() { dst = wg; }
^^^^^^^^^^^^^^^^^^^^^

input.wgsl:5:1 note: called by function 'f1'
fn f1() { f2(); }
^^^^^^^^^^^^^^^^^

input.wgsl:7:1 note: called by entry point 'f0'
fn f0() {
^^^^^^^^^
 f1();
^^^^^^
}
^
)");
}

TEST_F(ResolverValidationTest, RWStorageBufferUsedInVertexStage) {
    EXPECT_ERROR(
        R"(
@group(0) @binding(0) var<storage, read_write> v : vec4<f32>;
@vertex
fn main() -> @builtin(position) vec4f {
    return v;
}
 )",
        R"(
input.wgsl:5:12 error: var with 'storage' address space and 'read_write' access mode cannot be used by vertex pipeline stage
    return v;
           ^

input.wgsl:2:23 note: variable is declared here
@group(0) @binding(0) var<storage, read_write> v : vec4<f32>;
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, RWStorageTextureUsedInVertexStage) {
    EXPECT_ERROR(
        R"(
@group(0) @binding(0) var v : texture_storage_2d<r32uint, read_write>;

@vertex
fn main() -> @builtin(position) vec4f {
  _ = v;
  return vec4f();
}
)",
        R"(
input.wgsl:6:7 error: storage texture with 'read_write' access mode cannot be used by vertex pipeline stage
  _ = v;
      ^

input.wgsl:2:23 note: variable is declared here
@group(0) @binding(0) var v : texture_storage_2d<r32uint, read_write>;
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationDeathTest, UnhandledStmt) {
    EXPECT_DEATH_IF_SUPPORTED(
        {
            ProgramBuilder b;
            b.WrapInFunction(b.create<FakeStmt>());
            resolver::Resolve(b);
        },
        testing::HasSubstr(
            "internal compiler error: Switch() matched no cases. Type: tint::resolver::FakeStmt"));
}

TEST_F(ResolverValidationTest, Stmt_If_NonBool) {
    EXPECT_ERROR(
        R"(
fn f() {
  if (1.23f) {}
}
)",
        R"(
input.wgsl:3:7 error: if statement condition must be bool, got f32
  if (1.23f) {}
      ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_ElseIf_NonBool) {
    EXPECT_ERROR(
        R"(
fn f() {
  if (true) {} else if (1.23f) {}
}
)",
        R"(
input.wgsl:3:25 error: if statement condition must be bool, got f32
  if (true) {} else if (1.23f) {}
                        ^^^^^
)");
}

TEST_F(ResolverValidationDeathTest, Expr_ErrUnknownExprType) {
    EXPECT_DEATH_IF_SUPPORTED(
        {
            ProgramBuilder b;
            b.WrapInFunction(b.create<FakeExpr>());
            Resolver(&b, {}).Resolve();
        },
        testing::HasSubstr(
            "internal compiler error: Switch() matched no cases. Type: tint::resolver::FakeExpr"));
}

TEST_F(ResolverValidationTest, UsingUndefinedVariable_Fail) {
    EXPECT_ERROR(
        R"(
fn f() {
  b = 2;
}
)",
        R"(
input.wgsl:3:3 error: unresolved value 'b'
  b = 2;
  ^
)");
}

TEST_F(ResolverValidationTest, UsingUndefinedVariableInBlockStatement_Fail) {
    EXPECT_ERROR(
        R"(
fn f() {
  {
    b = 2;
  }
}
)",
        R"(
input.wgsl:4:5 error: unresolved value 'b'
    b = 2;
    ^
)");
}

TEST_F(ResolverValidationTest, UsingDefinedGlobalVariable_Pass) {
    EXPECT_SUCCESS(
        R"(
var<private> global_var: f32 = 2.1;
fn my_func() {
  global_var = 3.14;
}
    )");
}

TEST_F(ResolverValidationTest, UsingUndefinedVariableInnerScope_Fail) {
    EXPECT_ERROR(
        R"(
fn f() {
  if (true) { var a : f32 = 2.0; }
  a = 3.14;
}
)",
        R"(
input.wgsl:4:3 error: unresolved value 'a'
  a = 3.14;
  ^
)");
}

TEST_F(ResolverValidationTest, UsingDefinedVariableOuterScope_Pass) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  var a : f32 = 2.0;
  if (true) { a = 3.14; }
}
)");
}

TEST_F(ResolverValidationTest, UsingUndefinedVariableDifferentScope_Fail) {
    EXPECT_ERROR(
        R"(
fn f() {
  { var a : f32 = 2.0; }
  { a = 3.14; }
}
)",
        R"(
input.wgsl:4:5 error: unresolved value 'a'
  { a = 3.14; }
    ^
)");
}

TEST_F(ResolverValidationTest, AddressSpace_FunctionVariableWorkgroupClass) {
    EXPECT_ERROR(
        R"(
fn func() {
  var<workgroup> var_name : i32;
}
)",
        R"(
input.wgsl:3:3 error: function-scope 'var' declaration must use 'function' address space
  var<workgroup> var_name : i32;
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, AddressSpace_FunctionVariablePrivateClass) {
    EXPECT_ERROR(
        R"(
fn func() {
  var<private> s : i32;
}
)",
        R"(
input.wgsl:3:3 error: function-scope 'var' declaration must use 'function' address space
  var<private> s : i32;
  ^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, AddressSpace_SamplerExplicitAddressSpace) {
    EXPECT_ERROR(
        R"(
@group(0) @binding(0) var<private> var_name : sampler;
)",
        R"(
input.wgsl:2:23 error: variables of type 'sampler' must not specify an address space
@group(0) @binding(0) var<private> var_name : sampler;
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, AddressSpace_TextureExplicitAddressSpace) {
    EXPECT_ERROR(
        R"(
@group(0) @binding(0) var<function> var_name : texture_1d<f32>;
)",
        R"(
input.wgsl:2:23 error: variables of type 'texture_1d<f32>' must not specify an address space
@group(0) @binding(0) var<function> var_name : texture_1d<f32>;
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadChar) {
    EXPECT_ERROR(
        R"(
var<private> my_vec : vec3<f32>;
fn f() {
  _ = my_vec.xyqz;
}
)",
        R"(
input.wgsl:4:16 error: invalid vector swizzle character
  _ = my_vec.xyqz;
               ^
)");
}

TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_MixedChars) {
    EXPECT_ERROR(
        R"(
var<private> my_vec : vec4<f32>;
fn f() {
  _ = my_vec.rgyw;
}
)",
        R"(
input.wgsl:4:14 error: invalid mixing of vector swizzle characters rgba with xyzw
  _ = my_vec.rgyw;
             ^^^^
)");
}

TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadLength) {
    EXPECT_ERROR(
        R"(
var<private> my_vec : vec3<f32>;
fn f() {
  _ = my_vec.zzzzz;
}
)",
        R"(
input.wgsl:4:14 error: invalid vector swizzle size
  _ = my_vec.zzzzz;
             ^^^^^
)");
}

TEST_F(ResolverValidationTest, Expr_MemberAccessor_VectorSwizzle_BadIndex) {
    EXPECT_ERROR(
        R"(
var<private> my_vec : vec2<f32>;
fn f() {
  _ = my_vec.z;
}
)",
        R"(
input.wgsl:4:14 error: invalid vector swizzle member
  _ = my_vec.z;
             ^
)");
}

TEST_F(ResolverValidationTest,
       Stmt_Loop_ContinueInLoopBodyBeforeDeclAndAfterDecl_UsageInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      continue;
      var z : i32;
      continue;
      continuing {
          z = 2;
      }
  }
}
)",
        R"(
input.wgsl:5:7 warning: code is unreachable
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:4:7 error: continue statement bypasses declaration of 'z'
      continue;
      ^^^^^^^^

input.wgsl:5:7 note: identifier 'z' declared here
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:8:11 note: identifier 'z' referenced in continuing block here
          z = 2;
          ^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing_InBlocks) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop  {
      if (false) { break; }
      var z : i32;
      {{{continue;}}}
      continue;

      continuing {
          z = 2i;
      }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      if (true) {
          continue;
      }
      var z : i32;
      continuing {
          z = 2i;
      }
  }
}
)",
        R"(
input.wgsl:5:11 error: continue statement bypasses declaration of 'z'
          continue;
          ^^^^^^^^

input.wgsl:7:7 note: identifier 'z' declared here
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:9:11 note: identifier 'z' referenced in continuing block here
          z = 2i;
          ^
)");
}

TEST_F(ResolverValidationTest,
       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingSubscope) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      if (true) {
          continue;
      }
      var z : i32;
      continuing {
          if (true) {
              z = 2i;
          }
      }
  }
}
)",
        R"(
input.wgsl:5:11 error: continue statement bypasses declaration of 'z'
          continue;
          ^^^^^^^^

input.wgsl:7:7 note: identifier 'z' declared here
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:10:15 note: identifier 'z' referenced in continuing block here
              z = 2i;
              ^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageOutsideBlock) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      if (true) {
          continue;
      }
      var z : i32;
      continuing {
          if (z < 2i) {
          }
      }
  }
}
)",
        R"(
input.wgsl:5:11 error: continue statement bypasses declaration of 'z'
          continue;
          ^^^^^^^^

input.wgsl:7:7 note: identifier 'z' declared here
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:9:15 note: identifier 'z' referenced in continuing block here
          if (z < 2i) {
              ^
)");
}

TEST_F(ResolverValidationTest,
       Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuingLoop) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      if (true) {
          continue;
      }
      var z : i32;
      continuing {
          loop {
              z = 2i;
          }
      }
  }
}
)",
        R"(
input.wgsl:5:11 error: continue statement bypasses declaration of 'z'
          continue;
          ^^^^^^^^

input.wgsl:7:7 note: identifier 'z' declared here
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:10:15 note: identifier 'z' referenced in continuing block here
              z = 2i;
              ^
)");
}

TEST_F(ResolverValidationTest,
       Stmt_Loop_ContinueInLoopBodyBeforeDecl_UsageInNestedContinuingInBody) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop  {
      continue;
      var z : i32;
      loop {
          continue;
          continuing {
              z = 2i;
              break if true;
          }
      }
      continuing {
          break if true;
      }
  }
}
)");
}

TEST_F(ResolverValidationTest,
       Stmt_Loop_ContinueInLoopBodyBeforeDecl_UsageInNestedContinuingInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      continue;
      var z : i32;
      continuing {
          loop {
              continuing {
                z = 2i;
                break if true;
              }
          }
          break if true;
      }
  }
}
)",
        R"(
input.wgsl:5:7 warning: code is unreachable
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:4:7 error: continue statement bypasses declaration of 'z'
      continue;
      ^^^^^^^^

input.wgsl:5:7 note: identifier 'z' declared here
      var z : i32;
      ^^^^^^^^^^^

input.wgsl:9:17 note: identifier 'z' referenced in continuing block here
                z = 2i;
                ^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuing) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop  {
      loop {
          if (true) { continue; } // OK: not part of the outer loop
          break;
      }
      var z : i32;
      break;
      continuing {
          z = 2i;
      }
  }
}
)");
}

TEST_F(ResolverValidationTest,
       Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingSubscope) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop  {
      loop {
          if (true) { continue; } // OK: not part of the outer loop
          break;
      }
      var z : i32;
      break;
      continuing {
          if (true) {
              z = 2i;
          }
      }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInNestedLoopBodyBeforeDecl_UsageInContinuingLoop) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop  {
      loop {
          if (true) { continue; } // OK: not part of the outer loop
          break;
      }
      var z : i32;
      break;
      continuing {
          loop {
              z = 2i;
              break;
          }
      }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInLoopBodyAfterDecl_UsageInContinuing) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop  {
      var z : i32;
      if (true) { continue; }
      break;
      continuing {
          z = 2i;
      }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ReturnInContinuing_Direct) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
    continuing {
      return;
    }
  }
}
)",
        R"(
input.wgsl:5:7 error: continuing blocks must not contain a return statement
      return;
      ^^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ReturnInContinuing_Indirect) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    if (false) { break; }
    continuing {
      loop {
        return;
      }
    }
  }
}
)",
        R"(
input.wgsl:7:9 error: continuing blocks must not contain a return statement
        return;
        ^^^^^^

input.wgsl:5:16 note: see continuing block here
    continuing {
               ^
      loop {
^^^^^^^^^^^^
        return;
^^^^^^^^^^^^^^^
      }
^^^^^^^
    }
^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_DiscardInContinuing_Direct) {
    EXPECT_SUCCESS(
        R"(
fn my_func() {
  loop  {
    continuing {
      discard;
      break if true;
    }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInContinuing_Direct) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      continuing {
          continue;
      }
  }
}
)",
        R"(
input.wgsl:5:11 error: continuing blocks must not contain a continue statement
          continue;
          ^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInContinuing_Indirect) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop {
    if (false) { break; }
    continuing {
      loop {
        if (false) { break; }
        continue;
      }
    }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop  {
      continuing {
          break if true;
      }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf_Not_Last) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      var z : i32;
      continuing {
          break if true;
          z = 2i;
      }
  }
}
)",
        R"(
input.wgsl:6:11 error: break-if must be the last statement in a continuing block
          break if true;
          ^^^^^

input.wgsl:5:18 note: see continuing block here
      continuing {
                 ^
          break if true;
^^^^^^^^^^^^^^^^^^^^^^^^
          z = 2i;
^^^^^^^^^^^^^^^^^
      }
^^^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf_Duplicate) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      continuing {
          break if true;
          break if false;
      }
  }
}
)",
        R"(
input.wgsl:5:11 error: break-if must be the last statement in a continuing block
          break if true;
          ^^^^^

input.wgsl:4:18 note: see continuing block here
      continuing {
                 ^
          break if true;
^^^^^^^^^^^^^^^^^^^^^^^^
          break if false;
^^^^^^^^^^^^^^^^^^^^^^^^^
      }
^^^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf_NonBool) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop  {
      continuing {
          break if 1i;
      }
  }
}
)",
        R"(
input.wgsl:5:20 error: break-if statement condition must be bool, got i32
          break if 1i;
                   ^^
)");
}

TEST_F(ResolverValidationTest, Stmt_ForLoop_CondIsBoolRef) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  var cond : bool = true;
  for (; cond; ) {}
}
)");
}

TEST_F(ResolverValidationTest, Stmt_ForLoop_CondIsNotBool) {
    EXPECT_ERROR(
        R"(
fn f() {
  for (; 1.0f; ) {}
}
)",
        R"(
input.wgsl:3:10 error: for-loop condition must be bool, got f32
  for (; 1.0f; ) {}
         ^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_While_CondIsBoolRef) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  var cond : bool = false;
  while (cond) {}
}
)");
}

TEST_F(ResolverValidationTest, Stmt_While_CondIsNotBool) {
    EXPECT_ERROR(
        R"(
fn f() {
  while (1.0f) {}
}
)",
        R"(
input.wgsl:3:10 error: while condition must be bool, got f32
  while (1.0f) {}
         ^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_ContinueInLoop) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop {
    if (false) { break; }
    continue;
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_ContinueNotInLoop) {
    EXPECT_ERROR(
        R"(
fn f() {
  continue;
}
)",
        R"(
input.wgsl:3:3 error: continue statement must be in a loop
  continue;
  ^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInLoop) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop {
    break;
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInSwitch) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop {
    switch(1) {
      case 1: { break; }
      default: {}
    }
    break;
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInSwitchInContinuing) {
    EXPECT_SUCCESS(
        R"(
fn f() {
  loop {
    break;
    continuing {
      switch(1) {
        default: {
          break;
        }
      }
    }
  }
}
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {
        break;
      }
    }
  }
}
)",
        R"(
input.wgsl:6:9 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
        break;
        ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfElseInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {} else { break; }
    }
  }
}
)",
        R"(
input.wgsl:5:26 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
      if(true) {} else { break; }
                         ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      break;
    }
  }
}
)",
        R"(
input.wgsl:5:7 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
      break;
      ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfInIfInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {
        if(true) {
          break;
        }
      }
    }
  }
}
)",
        R"(
input.wgsl:7:11 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
          break;
          ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueMultipleStmtsInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {
        _ = 1i;
        break;
      }
    }
  }
}
)",
        R"(
input.wgsl:7:9 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
        break;
        ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfElseMultipleStmtsInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {} else {
        _ = 1i;
        break;
      }
    }
  }
}
)",
        R"(
input.wgsl:7:9 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
        break;
        ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfElseIfInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {} else if (true) {
        break;
      }
    }
  }
}
)",
        R"(
input.wgsl:6:9 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
        break;
        ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfNonEmptyElseInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {
        break;
      } else {
        _ = 1i;
      }
    }
  }
}
)",
        R"(
input.wgsl:6:9 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
        break;
        ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfElseNonEmptyTrueInContinuing) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {
        _ = 1i;
      } else {
        break;
      }
    }
  }
}
)",
        R"(
input.wgsl:8:9 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
        break;
        ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakInIfInContinuingNotLast) {
    EXPECT_ERROR(
        R"(
fn f() {
  loop {
    continuing {
      if(true) {
        break;
      }
      _ = 1i;
    }
  }
}
)",
        R"(
input.wgsl:6:9 error: 'break' must not be used to exit from a continuing block. Use 'break if' instead.
        break;
        ^^^^^
)");
}

TEST_F(ResolverValidationTest, Stmt_BreakNotInLoopOrSwitch) {
    EXPECT_ERROR(
        R"(
fn f() {
  break;
}
)",
        R"(
input.wgsl:3:3 error: break statement must be in a loop or switch case
  break;
  ^^^^^
)");
}

TEST_F(ResolverValidationTest, StructMemberDuplicateName) {
    EXPECT_ERROR(
        R"(
struct S {
  a : i32,
  a : i32,
}
)",
        R"(
input.wgsl:4:3 error: redefinition of 'a'
  a : i32,
  ^

input.wgsl:3:3 note: previous definition is here
  a : i32,
  ^
)");
}
TEST_F(ResolverValidationTest, StructMemberDuplicateNameDifferentTypes) {
    EXPECT_ERROR(
        R"(
struct S {
  a : bool,
  a : vec3<f32>,
}
)",
        R"(
input.wgsl:4:3 error: redefinition of 'a'
  a : vec3<f32>,
  ^

input.wgsl:3:3 note: previous definition is here
  a : bool,
  ^
)");
}
TEST_F(ResolverValidationTest, StructMemberDuplicateNamePass) {
    EXPECT_SUCCESS(
        R"(
struct S {
  a : i32,
  b : f32,
}
struct S1 {
  a : i32,
  b : f32,
}
)");
}

TEST_F(ResolverValidationTest, NegativeStructMemberAlignAttribute) {
    EXPECT_ERROR(
        R"(
struct S {
  @align(-2) a : f32,
}
)",
        R"(
input.wgsl:3:4 error: '@align' value must be a positive, power-of-two integer
  @align(-2) a : f32,
   ^^^^^
)");
}

TEST_F(ResolverValidationTest, NonPOTStructMemberAlignAttribute) {
    EXPECT_ERROR(
        R"(
struct S {
  @align(3) a : f32,
}
)",
        R"(
input.wgsl:3:4 error: '@align' value must be a positive, power-of-two integer
  @align(3) a : f32,
   ^^^^^
)");
}

TEST_F(ResolverValidationTest, ZeroStructMemberAlignAttribute) {
    EXPECT_ERROR(
        R"(
struct S {
  @align(0) a : f32,
}
)",
        R"(
input.wgsl:3:4 error: '@align' value must be a positive, power-of-two integer
  @align(0) a : f32,
   ^^^^^
)");
}

TEST_F(ResolverValidationTest, StructMemberSizeAttributeTooSmall) {
    EXPECT_ERROR(
        R"(
struct S {
  @size(1) a : f32,
}
)",
        R"(
input.wgsl:3:4 error: '@size' must be at least as big as the type's size (4)
  @size(1) a : f32,
   ^^^^
)");
}

TEST_F(ResolverValidationTest, Expr_Initializer_Cast_Pointer) {
    EXPECT_ERROR(
        R"(
fn f() {
  var vf : f32;
  let ip : ptr<function, i32> = ptr<function, i32>(vf);
}
)",
        R"(
input.wgsl:4:33 error: type is not constructible
  let ip : ptr<function, i32> = ptr<function, i32>(vf);
                                ^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, I32_Overflow) {
    EXPECT_ERROR(
        R"(
var<private> v : i32 = 2147483648;
)",
        R"(
input.wgsl:2:24 error: value 2147483648 cannot be represented as 'i32'
var<private> v : i32 = 2147483648;
                       ^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, I32_Underflow) {
    EXPECT_ERROR(
        R"(
var<private> v : i32 = -2147483649;
)",
        R"(
input.wgsl:2:24 error: value -2147483649 cannot be represented as 'i32'
var<private> v : i32 = -2147483649;
                       ^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, U32_Overflow) {
    EXPECT_ERROR(
        R"(
var<private> v : u32 = 4294967296;
)",
        R"(
input.wgsl:2:24 error: value 4294967296 cannot be represented as 'u32'
var<private> v : u32 = 4294967296;
                       ^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, ShiftLeft_I32_PartialEval_Valid) {
    EXPECT_SUCCESS(
        R"(
var<private> v : i32;
fn f() {
  let res = v << 31u;
}
)");
}

TEST_F(ResolverValidationTest, ShiftLeft_I32_PartialEval_Invalid) {
    EXPECT_ERROR(
        R"(
var<private> v : i32;
fn f() {
  let res = v << 32u;
}
)",
        R"(
input.wgsl:4:13 error: shift left value must be less than the bit width of the lhs, which is 32
  let res = v << 32u;
            ^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, ShiftRight_U32_PartialEval_Invalid) {
    EXPECT_ERROR(
        R"(
var<private> v : u32;
fn f() {
  let res = v >> 64u;
}
)",
        R"(
input.wgsl:4:13 error: shift right value must be less than the bit width of the lhs, which is 32
  let res = v >> 64u;
            ^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, ShiftLeft_VecI32_PartialEval_Valid) {
    EXPECT_SUCCESS(
        R"(
var<private> v : vec3<i32>;
fn f() {
  let res = v << vec3<u32>(0u, 1u, 2u);
}
)");
}

TEST_F(ResolverValidationTest, ShiftLeft_VecI32_PartialEval_Invalid) {
    EXPECT_ERROR(
        R"(
var<private> v : vec3<i32>;
fn f() {
  let res = v << vec3<u32>(31u, 32u, 33u);
}
)",
        R"(
input.wgsl:4:13 error: shift left value must be less than the bit width of the lhs, which is 32
  let res = v << vec3<u32>(31u, 32u, 33u);
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, ShiftLeft_I32_CompoundAssign_Valid) {
    EXPECT_SUCCESS(
        R"(
var<private> v : i32;
fn f() {
  v <<= 1u;
}
)");
}

TEST_F(ResolverValidationTest, ShiftLeft_I32_CompoundAssign_Invalid) {
    EXPECT_ERROR(
        R"(
var<private> v : i32;
fn f() {
  v <<= 64u;
}
)",
        R"(
input.wgsl:4:5 error: shift left value must be less than the bit width of the lhs, which is 32
  v <<= 64u;
    ^^^
)");
}

TEST_F(ResolverValidationTest, WorkgroupUniformLoad_ArraySize_NamedOverride) {
    EXPECT_ERROR(
        R"(
override size = 10u;
var<workgroup> a : array<u32, size>;
fn f() {
  _ = workgroupUniformLoad(&a);
}
)",
        R"(
input.wgsl:5:7 error: no matching call to 'workgroupUniformLoad(ptr<workgroup, array<u32, size>, read_write>)'

2 candidate functions:
 • 'workgroupUniformLoad(ptr<workgroup, T, read_write>  ✗ ) -> T' where:
      ✗  'T' is 'any concrete constructible type'
 • 'workgroupUniformLoad(ptr<workgroup, atomic<T>, read_write>  ✗ ) -> T' where:
      ✗  'T' is 'i32' or 'u32'

  _ = workgroupUniformLoad(&a);
      ^^^^^^^^^^^^^^^^^^^^^^^^
)");
}

TEST_F(ResolverValidationTest, WorkgroupUniformLoad_ArraySize_NamedConstant) {
    EXPECT_SUCCESS(
        R"(
const size = 10u;
var<workgroup> a : array<u32, size>;
fn f() {
  _ = workgroupUniformLoad(&a);
}
)");
}

}  // namespace
}  // namespace tint::resolver

TINT_INSTANTIATE_TYPEINFO(tint::resolver::FakeStmt);
TINT_INSTANTIATE_TYPEINFO(tint::resolver::FakeExpr);
