diff --git a/CHANGELOG.md b/CHANGELOG.md index c4dc55f9a61..5bcfba1cef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,8 @@ Top level categories: Bottom level categories: -- naga - General +- naga - DX12 - Vulkan - Metal @@ -58,17 +58,16 @@ Bottom level categories: - The validator checks that override-sized arrays have a positive size, if overrides have been resolved. By @andyleiserson in [#8822](https://github.com/gfx-rs/wgpu/pull/8822). - Fix some cases where f16 constants were not working. By @andyleiserson in [#8816](https://github.com/gfx-rs/wgpu/pull/8816). +- Reject zero-value construction of a runtime-sized array with a validation error. Previously it would crash in the HLSL backend. By @mooori in [#8741](https://github.com/gfx-rs/wgpu/pull/8741). +- Reject splat vector construction if the argument type does not match the type of the vector's scalar. Previously it would succeed. By @mooori in [#8829](https://github.com/gfx-rs/wgpu/pull/8829). +- Fixed `workgroupUniformLoad` incorrectly returning an atomic when called on an atomic, it now returns the inner `T` as per the spec. By @cryvosh in [#8791](https://github.com/gfx-rs/wgpu/pull/8791). +- Reject non-constructible types (runtime- and override-sized arrays, and structs containing non-constructible types) in more places where they should not be allowed. By @andyleiserson in [#8873](https://github.com/gfx-rs/wgpu/pull/8873). #### GLES - `DisplayHandle` should now be passed to `InstanceDescriptor` for correct EGL initialization on Wayland. By @MarijnS95 in [#8012](https://github.com/gfx-rs/wgpu/pull/8012) Note that the existing workaround to create surfaces before the adapter is no longer valid. -#### naga - -- Reject zero-value construction of a runtime-sized array with a validation error. Previously it would crash in the HLSL backend. By @mooori in [#8741](https://github.com/gfx-rs/wgpu/pull/8741). -- Reject splat vector construction if the argument type does not match the type of the vector's scalar. Previously it would succeed. By @mooori in [#8829](https://github.com/gfx-rs/wgpu/pull/8829). -- Fixed `workgroupUniformLoad` incorrectly returning an atomic when called on an atomic, it now returns the inner `T` as per the spec. By @cryvosh in [#8791](https://github.com/gfx-rs/wgpu/pull/8791). ### Documentation diff --git a/cts_runner/test.lst b/cts_runner/test.lst index e6527d2a5d8..7183671e612 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -246,12 +246,17 @@ webgpu:shader,validation,expression,binary,short_circuiting_and_or:invalid_types webgpu:shader,validation,expression,binary,short_circuiting_and_or:scalar_vector:op="%26%26";lhs="bool";rhs="bool" webgpu:shader,validation,expression,call,builtin,all:arguments:test="ptr_deref" webgpu:shader,validation,expression,call,builtin,max:values:* -// FAIL: others in `value_constructor` due to https://github.com/gfx-rs/wgpu/issues/4720, possibly more webgpu:shader,validation,expression,call,builtin,value_constructor:array_value:* +webgpu:shader,validation,expression,call,builtin,value_constructor:array_zero_value:* webgpu:shader,validation,expression,call,builtin,value_constructor:matrix_zero_value:* -webgpu:shader,validation,expression,call,builtin,value_constructor:scalar_zero_value:* +webgpu:shader,validation,expression,call,builtin,value_constructor:partial_eval:* webgpu:shader,validation,expression,call,builtin,value_constructor:scalar_value:* +webgpu:shader,validation,expression,call,builtin,value_constructor:scalar_zero_value:* webgpu:shader,validation,expression,call,builtin,value_constructor:struct_value:* +webgpu:shader,validation,expression,call,builtin,value_constructor:struct_zero_value:* +webgpu:shader,validation,expression,call,builtin,value_constructor:vector_copy:* +webgpu:shader,validation,expression,call,builtin,value_constructor:vector_elementwise:* +webgpu:shader,validation,expression,call,builtin,value_constructor:vector_mixed:* webgpu:shader,validation,expression,call,builtin,value_constructor:vector_splat:* webgpu:shader,validation,expression,call,builtin,value_constructor:vector_zero_value:* // NOTE: This is supposed to be an exhaustive listing underneath diff --git a/naga/src/front/wgsl/lower/construction.rs b/naga/src/front/wgsl/lower/construction.rs index 22a53dbd810..45e6148a1a5 100644 --- a/naga/src/front/wgsl/lower/construction.rs +++ b/naga/src/front/wgsl/lower/construction.rs @@ -164,33 +164,38 @@ impl<'source> Lowerer<'source, '_> { let expr; match (components, constructor) { - // Empty constructor - (Components::None, dst_ty) => match dst_ty { - Constructor::Type((result_ty, _)) => { - expr = crate::Expression::ZeroValue(result_ty); - } - Constructor::PartialVector { size } => { - // vec2(), vec3(), vec4() return vectors of abstractInts; the same - // is not true of the similar constructors for matrices or arrays. - // See https://www.w3.org/TR/WGSL/#vec2-builtin et seq. - let result_ty = ctx.module.types.insert( - crate::Type { - name: None, - inner: crate::TypeInner::Vector { - size, - scalar: crate::Scalar::ABSTRACT_INT, - }, + // Zero-value constructor with explicit type. Not everything this + // matches is constructible, but the non-constructible types not + // excluded by `!is_dynamically_sized` are rejected by the + // parser. + (Components::None, Constructor::Type((result_ty, inner))) + if !inner.is_dynamically_sized(&ctx.module.types) => + { + expr = crate::Expression::ZeroValue(result_ty); + } + // Zero-value constructor, vector with type inference + (Components::None, Constructor::PartialVector { size }) => { + // vec2(), vec3(), vec4() return vectors of abstractInts; the same + // is not true of the similar constructors for matrices or arrays. + // See https://www.w3.org/TR/WGSL/#vec2-builtin et seq. + let result_ty = ctx.module.types.insert( + crate::Type { + name: None, + inner: crate::TypeInner::Vector { + size, + scalar: crate::Scalar::ABSTRACT_INT, }, - span, - ); - expr = crate::Expression::ZeroValue(result_ty); - } - Constructor::PartialMatrix { .. } | Constructor::PartialArray => { - // We have no arguments from which to infer the result type, so - // partial constructors aren't acceptable here. - return Err(Box::new(Error::TypeNotInferable(ty_span))); - } - }, + }, + span, + ); + expr = crate::Expression::ZeroValue(result_ty); + } + // Zero-value constructor, matrix or array with type inference + (Components::None, Constructor::PartialMatrix { .. } | Constructor::PartialArray) => { + // We have no arguments from which to infer the result type, so + // partial constructors aren't acceptable here. + return Err(Box::new(Error::TypeNotInferable(ty_span))); + } // Scalar constructor & conversion (scalar -> scalar) ( @@ -539,8 +544,11 @@ impl<'source> Lowerer<'source, '_> { expr = crate::Expression::Compose { ty, components }; } - // Array constructor, explicit type - (components, Constructor::Type((ty, &crate::TypeInner::Array { base, .. }))) => { + // Array constructor, explicit type. Override- and runtime-sized arrays are not constructible. + ( + components, + Constructor::Type((ty, inner @ &crate::TypeInner::Array { base, .. })), + ) if !inner.is_dynamically_sized(&ctx.module.types) => { let mut components = components.into_components_vec(); ctx.try_automatic_conversions_slice(&mut components, &Tr::Handle(base), ty_span)?; expr = crate::Expression::Compose { ty, components }; @@ -549,8 +557,8 @@ impl<'source> Lowerer<'source, '_> { // Struct constructor ( components, - Constructor::Type((ty, &crate::TypeInner::Struct { ref members, .. })), - ) => { + Constructor::Type((ty, inner @ &crate::TypeInner::Struct { ref members, .. })), + ) if !inner.is_dynamically_sized(&ctx.module.types) => { let mut components = components.into_components_vec(); let struct_ty_span = ctx.module.types.get_span(ty); diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index 690e6d50756..e8365ef32d3 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -1717,7 +1717,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { let mut ectx = ctx.as_expression(block, &mut emitter); - let (_ty, initializer) = self.type_and_init( + let (ty, initializer) = self.type_and_init( l.name, Some(l.init), explicit_ty, @@ -1725,6 +1725,20 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { &mut ectx, )?; + // We have this special check here for `let` declarations because the + // validator doesn't check them (they are comingled with other things in + // `named_expressions`; see ). + // The check could go in `type_and_init`, but then we'd have to + // distinguish whether override-sized is allowed. The error ought to use + // the type's span, but `module.types.get_span(ty)` is `Span::UNDEFINED` + // (see ). + if ctx.module.types[ty] + .inner + .is_dynamically_sized(&ctx.module.types) + { + return Err(Box::new(Error::TypeNotConstructible(l.name.span))); + } + // We passed `Some()` to `type_and_init`, so we // will get a lowered initializer expression back. let initializer = diff --git a/naga/src/proc/type_methods.rs b/naga/src/proc/type_methods.rs index 94ce58b0a23..b5b1c730550 100644 --- a/naga/src/proc/type_methods.rs +++ b/naga/src/proc/type_methods.rs @@ -344,10 +344,18 @@ impl crate::TypeInner { left.as_ref().unwrap_or(self) == right.as_ref().unwrap_or(rhs) } + /// Returns true if `self` is runtime- or override-sized. pub fn is_dynamically_sized(&self, types: &crate::UniqueArena) -> bool { use crate::TypeInner as Ti; match *self { - Ti::Array { size, .. } => size == crate::ArraySize::Dynamic, + Ti::Array { + size: crate::ArraySize::Constant(_), + .. + } => false, + Ti::Array { + size: crate::ArraySize::Pending(_) | crate::ArraySize::Dynamic, + .. + } => true, Ti::Struct { ref members, .. } => members .last() .map(|last| types[last.ty].inner.is_dynamically_sized(types)) diff --git a/naga/src/valid/expression.rs b/naga/src/valid/expression.rs index 2437ba622a8..dcd121190a1 100644 --- a/naga/src/valid/expression.rs +++ b/naga/src/valid/expression.rs @@ -1,14 +1,11 @@ use super::{compose::validate_compose, FunctionInfo, ModuleInfo, ShaderStages, TypeFlags}; use crate::arena::UniqueArena; -use crate::valid::expression::builtin::validate_zero_value; use crate::{ arena::Handle, proc::OverloadSet as _, proc::{IndexableLengthError, ResolveError}, }; -pub mod builtin; - #[derive(Clone, Debug, thiserror::Error)] #[cfg_attr(test, derive(PartialEq))] pub enum ExpressionError { @@ -38,8 +35,8 @@ pub enum ExpressionError { InvalidSwizzleComponent(crate::SwizzleComponent, crate::VectorSize), #[error(transparent)] Compose(#[from] super::ComposeError), - #[error(transparent)] - ZeroValue(#[from] super::ZeroValueError), + #[error("Cannot construct zero value of {0:?} because it is not a constructible type")] + InvalidZeroValue(Handle), #[error(transparent)] IndexableLength(#[from] IndexableLengthError), #[error("Operation {0:?} can't work with {1:?}")] @@ -387,7 +384,9 @@ impl super::Validator { } E::Constant(_) | E::Override(_) => ShaderStages::all(), E::ZeroValue(ty) => { - validate_zero_value(ty, module.to_ctx())?; + if !mod_info[ty].contains(TypeFlags::CONSTRUCTIBLE) { + return Err(ExpressionError::InvalidZeroValue(ty)); + } ShaderStages::all() } E::Compose { ref components, ty } => { diff --git a/naga/src/valid/expression/builtin.rs b/naga/src/valid/expression/builtin.rs deleted file mode 100644 index d570bbccd0e..00000000000 --- a/naga/src/valid/expression/builtin.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::arena::Handle; - -#[derive(Clone, Debug, thiserror::Error)] -#[cfg_attr(test, derive(PartialEq))] -pub enum ZeroValueError { - #[error("ZeroValue construction of runtime-sized array is not allowed")] - RuntimeSizedArray, -} - -pub fn validate_zero_value( - self_ty_handle: Handle, - gctx: crate::proc::GlobalCtx, -) -> Result<(), ZeroValueError> { - use crate::TypeInner as Ti; - match gctx.types[self_ty_handle].inner { - Ti::Array { - base: _, - size: crate::ArraySize::Dynamic, - stride: _, - } => { - log::error!("Constructing zero value of runtime-sized array"); - Err(ZeroValueError::RuntimeSizedArray) - } - _ => Ok(()), - } -} diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index 984a47262b5..8bfc215014a 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -27,7 +27,6 @@ use crate::{ use crate::span::{AddSpan as _, WithSpan}; pub use analyzer::{ExpressionInfo, FunctionInfo, GlobalUse, Uniformity, UniformityRequirements}; pub use compose::ComposeError; -pub use expression::builtin::ZeroValueError; pub use expression::{check_literal_value, LiteralError}; pub use expression::{ConstExpressionError, ExpressionError}; pub use function::{CallError, FunctionError, LocalVariableError, SubgroupError}; diff --git a/naga/tests/naga/validation.rs b/naga/tests/naga/validation.rs index 789c3a1ec20..6b40f08db73 100644 --- a/naga/tests/naga/validation.rs +++ b/naga/tests/naga/validation.rs @@ -727,32 +727,408 @@ fn bad_texture_dimensions_level() { assert!(validate("1").is_ok()); } +// Adds IR for `override len: u32` and the type `array`. +fn make_override_array(module: &mut ir::Module) -> naga::Handle { + let span = naga::Span::default(); + + let ty_u32 = module.types.insert( + ir::Type { + name: Some("u32".into()), + inner: ir::TypeInner::Scalar(Scalar::U32), + }, + span, + ); + + let len = module.overrides.append( + ir::Override { + name: Some("len".into()), + id: None, + ty: ty_u32, + init: None, + }, + span, + ); + + module.types.insert( + ir::Type { + name: Some("array".into()), + inner: ir::TypeInner::Array { + base: ty_u32, + size: ir::ArraySize::Pending(len), + stride: 4, + }, + }, + span, + ) +} + +// Adds IR for type `array`. +fn make_runtime_array(module: &mut ir::Module) -> naga::Handle { + let span = naga::Span::default(); + + let ty_u32 = module.types.insert( + ir::Type { + name: Some("u32".into()), + inner: ir::TypeInner::Scalar(Scalar::U32), + }, + span, + ); + + module.types.insert( + ir::Type { + name: Some("array".into()), + inner: ir::TypeInner::Array { + base: ty_u32, + size: ir::ArraySize::Dynamic, + stride: 4, + }, + }, + span, + ) +} + +// Adds a local variable `var x: ty = ty();`. +fn make_zero_value_local_variable(fun: &mut Function, ty: naga::Handle) { + let span = naga::Span::default(); + + let ex_zero = fun.expressions.append(Expression::ZeroValue(ty), span); + + fun.local_variables.append( + naga::LocalVariable { + name: Some("x".into()), + ty, + init: Some(ex_zero), + }, + span, + ); +} + #[test] -fn zero_value_dyn_array_error() { - let source = r#" - @compute @workgroup_size(1) - fn main() { - let a = array(); - } - "#; - let module = naga::front::wgsl::parse_str(source).expect("module should parse"); +fn invalid_local_var_override_sized_array() { + // Similar to a test in wgsl_errors::invalid_local_vars. + // ``` + // override len: u32; + // var arr: array; + // fn f() { + // var x: array = arr; + // } + // ``` + let span = naga::Span::default(); + let mut module = ir::Module::default(); + + let ty_array = make_override_array(&mut module); + + let var_arr = module.global_variables.append( + naga::GlobalVariable { + name: Some("arr".into()), + space: naga::AddressSpace::WorkGroup, + binding: None, + ty: ty_array, + init: None, + }, + span, + ); + + let mut fun = Function { + name: Some("f".into()), + ..Default::default() + }; + + let ex_global = fun + .expressions + .append(Expression::GlobalVariable(var_arr), span); + let ex_load = fun + .expressions + .append(Expression::Load { pointer: ex_global }, span); + + fun.local_variables.append( + naga::LocalVariable { + name: Some("x".into()), + ty: ty_array, + init: Some(ex_load), + }, + span, + ); + + module.functions.append(fun, span); + let err = valid::Validator::new(Default::default(), valid::Capabilities::all()) .validate(&module) - .map_err(|err| err.into_inner()); // discard spans + .expect_err("module should be invalid") + .into_inner(); + assert!(matches!( err, - Err(naga::valid::ValidationError::EntryPoint { - stage: _, - name: _, - source: naga::valid::EntryPointError::Function( - naga::valid::FunctionError::Expression { - handle: _, - source: naga::valid::ExpressionError::ZeroValue( - naga::valid::ZeroValueError::RuntimeSizedArray - ) - } - ) - }) + valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { + name: local_var_name, + source: valid::LocalVariableError::InvalidType(_), + .. + }, + .. + } if local_var_name == "x" + )); +} + +#[test] +fn invalid_zero_value_runtime_array() { + // Similar to a test in wgsl_errors::invalid_zero_value_constructors. + // ``` + // fn main() { + // var x = array(); + // } + // ``` + let span = naga::Span::default(); + let mut module = ir::Module::default(); + + let ty_array = make_runtime_array(&mut module); + + let mut fun = Function { + name: Some("f".into()), + ..Default::default() + }; + + make_zero_value_local_variable(&mut fun, ty_array); + + module.functions.append(fun, span); + + let err = valid::Validator::new(Default::default(), valid::Capabilities::all()) + .validate(&module) + .expect_err("module should be invalid") + .into_inner(); + + assert!(matches!( + err, + valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { + name: local_var_name, + source: valid::LocalVariableError::InvalidType(_), + .. + }, + .. + } if local_var_name == "x" + )); +} + +#[test] +fn invalid_zero_value_override_array() { + // Similar to a test in wgsl_errors::invalid_zero_value_constructors. + // ``` + // override len: u32; + // fn main() { + // var x = array(); + // } + // ``` + let span = naga::Span::default(); + let mut module = ir::Module::default(); + + let ty_array = make_override_array(&mut module); + + let mut fun = Function { + name: Some("f".into()), + ..Default::default() + }; + + make_zero_value_local_variable(&mut fun, ty_array); + + module.functions.append(fun, span); + + let err = valid::Validator::new(Default::default(), valid::Capabilities::all()) + .validate(&module) + .expect_err("module should be invalid") + .into_inner(); + + assert!(matches!( + err, + valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { + name: local_var_name, + source: valid::LocalVariableError::InvalidType(_), + .. + }, + .. + } if local_var_name == "x" + )); +} + +#[test] +fn invalid_zero_value_texture() { + // Similar to a test in wgsl_errors::invalid_zero_value_constructors. + // ``` + // fn main() { + // var x = texture_2d(); + // } + // ``` + use naga::{ImageClass, ImageDimension, Module, Type, TypeInner}; + + let span = naga::Span::default(); + let mut module = Module::default(); + + let ty_texture = module.types.insert( + Type { + name: Some("texture_2d".into()), + inner: TypeInner::Image { + dim: ImageDimension::D2, + arrayed: false, + class: ImageClass::Sampled { + kind: naga::ScalarKind::Float, + multi: false, + }, + }, + }, + span, + ); + + let mut fun = Function { + name: Some("f".into()), + ..Default::default() + }; + + make_zero_value_local_variable(&mut fun, ty_texture); + + module.functions.append(fun, span); + + let err = valid::Validator::new(Default::default(), valid::Capabilities::all()) + .validate(&module) + .expect_err("module should be invalid") + .into_inner(); + + assert!(matches!( + err, + valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { + name: local_var_name, + source: valid::LocalVariableError::InvalidType(_), + .. + }, + .. + } if local_var_name == "x" + )); +} + +/// Test for non-zero-value runtime-sized array constructor. +#[test] +fn invalid_constructor_runtime_array() { + // Similar to a test in wgsl_errors::invalid_zero_value_constructors. + // ``` + // fn main() { + // var x = array(0, 1, 2); + // } + // ``` + let span = naga::Span::default(); + let mut module = ir::Module::default(); + + let ty_array = make_runtime_array(&mut module); + + let mut fun = Function { + name: Some("f".into()), + ..Default::default() + }; + + // Create component expressions + let ex_0 = fun + .expressions + .append(Expression::Literal(naga::Literal::U32(0)), span); + let ex_1 = fun + .expressions + .append(Expression::Literal(naga::Literal::U32(1)), span); + let ex_2 = fun + .expressions + .append(Expression::Literal(naga::Literal::U32(2)), span); + + // Create a Compose expression to construct the array + let ex_compose = fun.expressions.append( + Expression::Compose { + ty: ty_array, + components: vec![ex_0, ex_1, ex_2], + }, + span, + ); + + fun.local_variables.append( + naga::LocalVariable { + name: Some("x".into()), + ty: ty_array, + init: Some(ex_compose), + }, + span, + ); + + module.functions.append(fun, span); + + let err = valid::Validator::new(Default::default(), valid::Capabilities::all()) + .validate(&module) + .expect_err("module should be invalid") + .into_inner(); + + assert!(matches!( + err, + valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { + name: local_var_name, + source: valid::LocalVariableError::InvalidType(_), + .. + }, + .. + } if local_var_name == "x" + )); +} + +#[test] +fn invalid_constructor_unsized_struct() { + // Similar to a test in wgsl_errors::invalid_zero_value_constructors: + // ``` + // struct Unsized { data: array } + // fn main() { + // var x: Unsized = Unsized(); + // } + // ``` + use naga::{Module, StructMember, Type, TypeInner}; + + let span = naga::Span::default(); + let mut module = Module::default(); + + let ty_array = make_runtime_array(&mut module); + + let ty_unsized = module.types.insert( + Type { + name: Some("Unsized".into()), + inner: TypeInner::Struct { + members: vec![StructMember { + name: Some("data".into()), + ty: ty_array, + binding: None, + offset: 0, + }], + span: 4, + }, + }, + span, + ); + + let mut fun = Function { + name: Some("main".into()), + ..Default::default() + }; + + make_zero_value_local_variable(&mut fun, ty_unsized); + + module.functions.append(fun, span); + + let err = valid::Validator::new(Default::default(), valid::Capabilities::all()) + .validate(&module) + .expect_err("module should be invalid") + .into_inner(); + + assert!(matches!( + err, + valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { + source: valid::LocalVariableError::InvalidType(_), + .. + }, + .. + }, )); } diff --git a/naga/tests/naga/wgsl_errors.rs b/naga/tests/naga/wgsl_errors.rs index 743889aeb05..6f49bd1f5c5 100644 --- a/naga/tests/naga/wgsl_errors.rs +++ b/naga/tests/naga/wgsl_errors.rs @@ -2179,10 +2179,10 @@ fn invalid_local_vars() { var not_okay: ptr> = &(*okay).data; } ": - Err(naga::valid::ValidationError::Function { - source: naga::valid::FunctionError::LocalVariable { + Err(valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { name: local_var_name, - source: naga::valid::LocalVariableError::InvalidType(_), + source: valid::LocalVariableError::InvalidType(_), .. }, .. @@ -2196,16 +2196,122 @@ fn invalid_local_vars() { var x: atomic; } ": - Err(naga::valid::ValidationError::Function { - source: naga::valid::FunctionError::LocalVariable { + Err(valid::ValidationError::Function { + source: valid::FunctionError::LocalVariable { name: local_var_name, - source: naga::valid::LocalVariableError::InvalidType(_), + source: valid::LocalVariableError::InvalidType(_), .. }, .. }) if local_var_name == "x" } + + // Rejected in statement lowering + // There is a similar validator test in `validation.rs`. + check( + " + override len: u32; + var arr: array; + fn f() { + let x: array = arr; + } + ", + r#"error: type `x` is not constructible + ┌─ wgsl:5:17 + │ +5 │ let x: array = arr; + │ ^ type is not constructible + +"#, + ); +} + +#[test] +fn invalid_zero_value_constructors() { + // There are similar validator tests in `validation.rs`. + + // Rejected in constructor lowering + check( + " + fn f() { + let x = array(); + } + ", + r#"error: type `array` is not constructible + ┌─ wgsl:3:21 + │ +3 │ let x = array(); + │ ^^^^^^^^^^ type is not constructible + +"#, + ); + + // Rejected in constructor lowering + check( + " + override len: u32; + fn f() { + let x = array(); + } + ", + r#"error: type `array` is not constructible + ┌─ wgsl:4:21 + │ +4 │ let x = array(); + │ ^^^^^^^^^^^^^^^ type is not constructible + +"#, + ); + + // Rejected in parser (but may change with template-list discovery) + check( + " + fn f() { + let x = texture_2d(); + } + ", + r#"error: type `texture_2d` is not constructible + ┌─ wgsl:3:21 + │ +3 │ let x = texture_2d(); + │ ^^^^^^^^^^ type is not constructible + +"#, + ); + + // Rejected in constructor lowering + check( + " + fn f() { + let x = array(0, 1, 2); + } + ", + r#"error: type `array` is not constructible + ┌─ wgsl:3:21 + │ +3 │ let x = array(0, 1, 2); + │ ^^^^^^^^^^ type is not constructible + +"#, + ); + + // Rejected in constructor lowering + check( + " + struct Unsized { data: array } + fn main() { + var not_okay: Unsized = Unsized(); + } + ", + r#"error: type `Unsized` is not constructible + ┌─ wgsl:4:37 + │ +4 │ var not_okay: Unsized = Unsized(); + │ ^^^^^^^ type is not constructible + +"#, + ); } #[test]