blob: 2abf591f69890dad1a75b61adf8cc1a58339e4fe [file]
// 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 ::testing::HasSubstr;
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
R"(
fn f() {
b = 2;
}
)",
R"(
input.wgsl:3:3 error: unresolved value 'b'
b = 2;
^
)");
}
TEST_F(ResolverValidationTest, UsingUndefinedVariableInBlockStatement_Fail) {
ExpectError(
R"(
fn f() {
{
b = 2;
}
}
)",
R"(
input.wgsl:4:5 error: unresolved value 'b'
b = 2;
^
)");
}
TEST_F(ResolverValidationTest, UsingDefinedGlobalVariable_Pass) {
ExpectSuccess(
R"(
var<private> global_var: f32 = 2.1;
fn my_func() {
global_var = 3.14;
}
)");
}
TEST_F(ResolverValidationTest, UsingUndefinedVariableInnerScope_Fail) {
ExpectError(
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) {
ExpectSuccess(
R"(
fn f() {
var a : f32 = 2.0;
if (true) { a = 3.14; }
}
)");
}
TEST_F(ResolverValidationTest, UsingUndefinedVariableDifferentScope_Fail) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
R"(
fn f() {
loop {
if (false) { break; }
var z : i32;
{{{continue;}}}
continue;
continuing {
z = 2i;
}
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInLoopBodySubscopeBeforeDecl_UsageInContinuing) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
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) {
ExpectError(
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) {
ExpectSuccess(
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) {
ExpectSuccess(
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) {
ExpectSuccess(
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) {
ExpectSuccess(
R"(
fn f() {
loop {
var z : i32;
if (true) { continue; }
break;
continuing {
z = 2i;
}
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_Loop_ReturnInContinuing_Direct) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
R"(
fn my_func() {
loop {
continuing {
discard;
break if true;
}
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_Loop_ContinueInContinuing_Direct) {
ExpectError(
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) {
ExpectSuccess(
R"(
fn f() {
loop {
if (false) { break; }
continuing {
loop {
if (false) { break; }
continue;
}
}
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf) {
ExpectSuccess(
R"(
fn f() {
loop {
continuing {
break if true;
}
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_Loop_Continuing_BreakIf_Not_Last) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
R"(
fn f() {
var cond : bool = true;
for (; cond; ) {}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_ForLoop_CondIsNotBool) {
ExpectError(
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) {
ExpectSuccess(
R"(
fn f() {
var cond : bool = false;
while (cond) {}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_While_CondIsNotBool) {
ExpectError(
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) {
ExpectSuccess(
R"(
fn f() {
loop {
if (false) { break; }
continue;
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_ContinueNotInLoop) {
ExpectError(
R"(
fn f() {
continue;
}
)",
R"(
input.wgsl:3:3 error: continue statement must be in a loop
continue;
^^^^^^^^
)");
}
TEST_F(ResolverValidationTest, Stmt_BreakInLoop) {
ExpectSuccess(
R"(
fn f() {
loop {
break;
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_BreakInSwitch) {
ExpectSuccess(
R"(
fn f() {
loop {
switch(1) {
case 1: { break; }
default: {}
}
break;
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_BreakInSwitchInContinuing) {
ExpectSuccess(
R"(
fn f() {
loop {
break;
continuing {
switch(1) {
default: {
break;
}
}
}
}
}
)");
}
TEST_F(ResolverValidationTest, Stmt_BreakInIfTrueInContinuing) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
R"(
struct S {
a : i32,
b : f32,
}
struct S1 {
a : i32,
b : f32,
}
)");
}
TEST_F(ResolverValidationTest, NegativeStructMemberAlignAttribute) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
R"(
var<private> v : i32;
fn f() {
let res = v << 31u;
}
)");
}
TEST_F(ResolverValidationTest, ShiftLeft_I32_PartialEval_Invalid) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
R"(
var<private> v : vec3<i32>;
fn f() {
let res = v << vec3<u32>(0u, 1u, 2u);
}
)");
}
TEST_F(ResolverValidationTest, ShiftLeft_VecI32_PartialEval_Invalid) {
ExpectError(
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) {
ExpectSuccess(
R"(
var<private> v : i32;
fn f() {
v <<= 1u;
}
)");
}
TEST_F(ResolverValidationTest, ShiftLeft_I32_CompoundAssign_Invalid) {
ExpectError(
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) {
ExpectError(
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) {
ExpectSuccess(
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);