Skip to main content

wgsl_types/builtin/
ctor.rs

1//! Constructor implementations, including zero-value constructors.
2//!
3//! Functions bear the same name as the WGSL counterpart.
4//! Functions that take template parameters are suffixed with `_t` and take `tplt_*` arguments.
5//!
6//! ### Usage quirks
7//!
8//! * The arguments must be [loaded][Type::loaded].
9//! * The struct constructor is a bit special since it is the only user-defined type.
10//!   Use [`struct_ctor`] and [`typecheck_struct_ctor`] for structs.
11//! * User-defined functions can shadow WGSL built-in functions.
12//! * Type aliases must be resolved: WGSL allows calling functions with the name of the alias.
13
14use itertools::Itertools;
15use num_traits::{One, ToPrimitive, Zero};
16
17use crate::{
18    CallSignature, Error, ShaderStage,
19    conv::{Convert, convert_all, convert_all_inner_to, convert_all_to, convert_all_ty},
20    f16,
21    inst::{
22        ArrayInstance, AtomicInstance, Instance, LiteralInstance, MatInstance, StructInstance,
23        VecInstance,
24    },
25    tplt::{ArrayTemplate, MatTemplate, TpltParam, VecTemplate},
26    ty::{StructType, Ty, Type},
27};
28
29type E = Error;
30
31/// Check if a function name could correspond to a built-in constructor function.
32///
33/// Warning: WGSL allows shadowing built-in functions. Check that a user-defined
34/// function does not shadow the built-in one.
35pub fn is_ctor(name: &str) -> bool {
36    match name {
37        "array" | "bool" | "i32" | "u32" | "f32" | "f16" | "mat2x2" | "mat2x3" | "mat2x4"
38        | "mat3x2" | "mat3x3" | "mat3x4" | "mat4x2" | "mat4x3" | "mat4x4" | "vec2" | "vec3"
39        | "vec4" => true,
40        #[cfg(feature = "naga-ext")]
41        "i64" | "u64" | "f64" => true,
42        _ => false,
43    }
44}
45
46// ------------
47// CONSTRUCTORS
48// ------------
49// reference: <https://www.w3.org/TR/WGSL/#constructor-builtin-function>
50
51/// `array<T,N>()` constructor.
52///
53/// Reference: <https://www.w3.org/TR/WGSL/#array-builtin>
54pub fn array_t(tplt_ty: &Type, tplt_n: usize, args: &[Instance]) -> Result<Instance, E> {
55    let args = args
56        .iter()
57        .map(|a| {
58            a.convert_to(tplt_ty)
59                .ok_or_else(|| E::ParamType(tplt_ty.clone(), a.ty()))
60        })
61        .collect::<Result<Vec<_>, _>>()?;
62
63    if args.len() != tplt_n {
64        return Err(E::ParamCount("array".to_string(), tplt_n, args.len()));
65    }
66
67    Ok(ArrayInstance::new(args, false).into())
68}
69
70/// `array()` constructor.
71///
72/// Reference: <https://www.w3.org/TR/WGSL/#array-builtin>
73pub fn array(args: &[Instance]) -> Result<Instance, E> {
74    if args.is_empty() {
75        return Err(E::Builtin("array constructor expects at least 1 argument"));
76    }
77
78    let args = convert_all(args).ok_or(E::Builtin("array elements are incompatible"))?;
79
80    Ok(ArrayInstance::new(args, false).into())
81}
82
83/// `bool()` constructor.
84///
85/// Reference: <https://www.w3.org/TR/WGSL/#bool-builtin>
86pub fn bool(a1: &Instance) -> Result<Instance, E> {
87    match a1 {
88        Instance::Literal(l) => {
89            let zero = LiteralInstance::zero_value(&l.ty())?;
90            Ok(LiteralInstance::Bool(*l != zero).into())
91        }
92        _ => Err(E::Builtin("bool constructor expects a scalar argument")),
93    }
94}
95
96/// `i32()` constructor.
97///
98/// Reference: <https://www.w3.org/TR/WGSL/#i32-builtin>
99pub fn i32(a1: &Instance) -> Result<Instance, E> {
100    match a1 {
101        Instance::Literal(l) => {
102            let val = match l {
103                LiteralInstance::Bool(n) => Some(n.then_some(1).unwrap_or(0)),
104                LiteralInstance::AbstractInt(n) => n.to_i32(), // identity if representable
105                LiteralInstance::AbstractFloat(n) => Some(*n as i32), // rounding towards 0
106                LiteralInstance::I32(n) => Some(*n),           // identity operation
107                LiteralInstance::U32(n) => Some(*n as i32),    // reinterpretation of bits
108                LiteralInstance::F32(n) => Some((*n as i32).min(2147483520)), // rounding towards 0 AND representable in f32
109                LiteralInstance::F16(n) => Some((f16::to_f32(*n) as i32).clamp(-65504, 65504)), // rounding towards 0 AND representable in f16
110                #[cfg(feature = "naga-ext")]
111                LiteralInstance::I64(n) => n.to_i32(), // identity if representable
112                #[cfg(feature = "naga-ext")]
113                LiteralInstance::U64(n) => n.to_i32(), // identity if representable
114                #[cfg(feature = "naga-ext")]
115                LiteralInstance::F64(n) => Some(*n as i32), // rounding towards 0
116            }
117            .ok_or(E::ConvOverflow(*l, Type::I32))?;
118            Ok(LiteralInstance::I32(val).into())
119        }
120        _ => Err(E::Builtin("i32 constructor expects a scalar argument")),
121    }
122}
123
124/// `u32()` constructor.
125///
126/// Reference: <https://www.w3.org/TR/WGSL/#u32-builtin>
127pub fn u32(a1: &Instance) -> Result<Instance, E> {
128    match a1 {
129        Instance::Literal(l) => {
130            let val = match l {
131                LiteralInstance::Bool(n) => Some(n.then_some(1).unwrap_or(0)),
132                LiteralInstance::AbstractInt(n) => n.to_u32(), // identity if representable
133                LiteralInstance::AbstractFloat(n) => Some(*n as u32), // rounding towards 0
134                LiteralInstance::I32(n) => Some(*n as u32),    // reinterpretation of bits
135                LiteralInstance::U32(n) => Some(*n),           // identity operation
136                LiteralInstance::F32(n) => Some((*n as u32).min(4294967040)), // rounding towards 0 AND representable in f32
137                LiteralInstance::F16(n) => Some((f16::to_f32(*n) as u32).min(65504)), // rounding towards 0 AND representable in f16
138                #[cfg(feature = "naga-ext")]
139                LiteralInstance::I64(n) => n.to_u32(), // identity if representable
140                #[cfg(feature = "naga-ext")]
141                LiteralInstance::U64(n) => n.to_u32(), // identity if representable
142                #[cfg(feature = "naga-ext")]
143                LiteralInstance::F64(n) => Some(*n as u32), // rounding towards 0
144            }
145            .ok_or(E::ConvOverflow(*l, Type::U32))?;
146            Ok(LiteralInstance::U32(val).into())
147        }
148        _ => Err(E::Builtin("u32 constructor expects a scalar argument")),
149    }
150}
151
152/// `f32()` constructor.
153///
154/// Reference: <https://www.w3.org/TR/WGSL/#f32-builtin>
155pub fn f32(a1: &Instance, stage: ShaderStage) -> Result<Instance, E> {
156    match a1 {
157        Instance::Literal(l) => {
158            let val = match l {
159                LiteralInstance::Bool(n) => Some(n.then_some(1f32).unwrap_or(0f32)),
160                LiteralInstance::AbstractInt(n) => Some(*n as f32), // i64 never leaves f32's finite range
161                LiteralInstance::AbstractFloat(n) => {
162                    // shader-creation error if overflows f32's finite range during const-eval.
163                    let range = f32::MIN as f64..=f32::MAX as f64;
164                    if stage == ShaderStage::Const && !range.contains(n) {
165                        None
166                    } else {
167                        Some(*n as f32)
168                    }
169                }
170                LiteralInstance::I32(n) => Some(*n as f32), // i32 never leaves f32's finite range
171                LiteralInstance::U32(n) => Some(*n as f32), // u32 never leaves f32's finite range
172                LiteralInstance::F32(n) => Some(*n),        // identity operation
173                LiteralInstance::F16(n) => Some(f16::to_f32(*n)), // exactly representable
174                #[cfg(feature = "naga-ext")]
175                LiteralInstance::I64(n) => Some(*n as f32), // i64 never leaves f32's finite range
176                #[cfg(feature = "naga-ext")]
177                LiteralInstance::U64(n) => Some(*n as f32), // u64 never leaves f32's finite range
178                #[cfg(feature = "naga-ext")]
179                LiteralInstance::F64(n) => {
180                    // shader-creation error if overflows f32's finite range during const-eval.
181                    let range = f32::MIN as f64..=f32::MAX as f64;
182                    if stage == ShaderStage::Const && !range.contains(n) {
183                        None
184                    } else {
185                        Some(*n as f32)
186                    }
187                }
188            }
189            .ok_or(E::ConvOverflow(*l, Type::F32))?;
190
191            Ok(LiteralInstance::F32(val).into())
192        }
193        _ => Err(E::Builtin("f32 constructor expects a scalar argument")),
194    }
195}
196
197/// `f16()` constructor.
198///
199/// Reference: <https://www.w3.org/TR/WGSL/#f16-builtin>
200pub fn f16(a1: &Instance, stage: ShaderStage) -> Result<Instance, E> {
201    match a1 {
202        Instance::Literal(l) => {
203            let val = match l {
204                LiteralInstance::Bool(n) => Some(n.then_some(f16::one()).unwrap_or(f16::zero())),
205                LiteralInstance::AbstractInt(n) => {
206                    // shader-creation error if overflows f16's finite range during const-eval.
207                    let range = f16::MIN.to_i64().unwrap()..=f16::MAX.to_i64().unwrap();
208                    if stage == ShaderStage::Const && !range.contains(n) {
209                        None
210                    } else {
211                        Some(f16::from_f32(*n as f32))
212                    }
213                }
214                LiteralInstance::AbstractFloat(n) => {
215                    // shader-creation error if overflows f16's finite range during const-eval.
216                    let range = f16::MIN.to_f64()..=f16::MAX.to_f64();
217                    if stage == ShaderStage::Const && !range.contains(n) {
218                        None
219                    } else {
220                        Some(f16::from_f32(*n as f32))
221                    }
222                }
223                LiteralInstance::I32(n) => {
224                    // shader-creation error if overflows f16's finite range during const-eval.
225                    let range = f16::MIN.to_i32().unwrap()..=f16::MAX.to_i32().unwrap();
226                    if stage == ShaderStage::Const && !range.contains(n) {
227                        None
228                    } else {
229                        Some(f16::from_f32(*n as f32))
230                    }
231                }
232                LiteralInstance::U32(n) => {
233                    // shader-creation error if overflows f16's finite range during const-eval.
234                    let range = 0..=f16::MAX.to_u32().unwrap();
235                    if stage == ShaderStage::Const && !range.contains(n) {
236                        None
237                    } else {
238                        Some(f16::from_f32(*n as f32))
239                    }
240                }
241                LiteralInstance::F32(n) => {
242                    // shader-creation error if overflows f16's finite range during const-eval.
243                    let range = f16::MIN.to_f32()..=f16::MAX.to_f32();
244                    if stage == ShaderStage::Const && !range.contains(n) {
245                        None
246                    } else {
247                        Some(f16::from_f32(*n))
248                    }
249                }
250                LiteralInstance::F16(n) => Some(*n), // identity operation
251                #[cfg(feature = "naga-ext")]
252                LiteralInstance::I64(n) => {
253                    // shader-creation error if overflows f16's finite range during const-eval.
254                    let range = f16::MIN.to_i64().unwrap()..=f16::MAX.to_i64().unwrap();
255                    if stage == ShaderStage::Const && !range.contains(n) {
256                        None
257                    } else {
258                        Some(f16::from_f32(*n as f32))
259                    }
260                }
261                #[cfg(feature = "naga-ext")]
262                LiteralInstance::U64(n) => {
263                    // shader-creation error if overflows f16's finite range during const-eval.
264                    let range = 0..=f16::MAX.to_u64().unwrap();
265                    if stage == ShaderStage::Const && !range.contains(n) {
266                        None
267                    } else {
268                        Some(f16::from_f32(*n as f32))
269                    }
270                }
271                #[cfg(feature = "naga-ext")]
272                LiteralInstance::F64(n) => {
273                    // shader-creation error if overflows f16's finite range during const-eval.
274                    let range = f16::MIN.to_f64()..=f16::MAX.to_f64();
275                    if stage == ShaderStage::Const && !range.contains(n) {
276                        None
277                    } else {
278                        Some(f16::from_f32(*n as f32))
279                    }
280                }
281            }
282            .ok_or(E::ConvOverflow(*l, Type::F16))?;
283            Ok(LiteralInstance::F16(val).into())
284        }
285        _ => Err(E::Builtin("f16 constructor expects a scalar argument")),
286    }
287}
288
289/// `i64()` constructor (naga extension).
290///
291/// TODO: This built-in is not implemented!
292#[cfg(feature = "naga-ext")]
293pub fn i64(_a1: &Instance) -> Result<Instance, E> {
294    Err(E::Todo("i64".to_string()))
295}
296
297/// `u64()` constructor (naga extension).
298///
299/// TODO: This built-in is not implemented!
300#[cfg(feature = "naga-ext")]
301pub fn u64(_a1: &Instance) -> Result<Instance, E> {
302    Err(E::Todo("u64".to_string()))
303}
304
305/// `f64()` constructor (naga extension).
306///
307/// TODO: This built-in is not implemented!
308#[cfg(feature = "naga-ext")]
309pub fn f64(_a1: &Instance, _stage: ShaderStage) -> Result<Instance, E> {
310    Err(E::Todo("f64".to_string()))
311}
312
313/// `matCxR<T>()` constructor.
314///
315/// Expects `tplt_ty` to be valid (a float).
316///
317/// Reference: <https://www.w3.org/TR/WGSL/#mat2x2-builtin>
318pub fn mat_t(
319    c: usize,
320    r: usize,
321    tplt_ty: &Type,
322    args: &[Instance],
323    stage: ShaderStage,
324) -> Result<Instance, E> {
325    // overload 1: mat conversion constructor
326    if let [Instance::Mat(m)] = args {
327        if m.c() != c || m.r() != r {
328            return Err(E::Conversion(
329                m.ty(),
330                Type::Mat(c as u8, r as u8, Box::new(tplt_ty.clone())),
331            ));
332        }
333
334        let conv_fn = match tplt_ty {
335            Type::F32 => f32,
336            Type::F16 => f16,
337            _ => return Err(E::Builtin("matrix type must be a f32 or f16")),
338        };
339
340        let comps = m
341            .iter_cols()
342            .map(|v| {
343                v.unwrap_vec_ref()
344                    .iter()
345                    .map(|n| conv_fn(n, stage))
346                    .collect::<Result<Vec<_>, _>>()
347                    .map(|s| Instance::Vec(VecInstance::new(s)))
348            })
349            .collect::<Result<Vec<_>, _>>()?;
350
351        Ok(MatInstance::from_cols(comps).into())
352    } else {
353        let ty = args
354            .first()
355            .ok_or(E::Builtin("matrix constructor expects arguments"))?
356            .ty();
357        let ty = ty
358            .convert_inner_to(tplt_ty)
359            .ok_or(E::Conversion(ty.inner_ty(), tplt_ty.clone()))?;
360        let args =
361            convert_all_to(args, &ty).ok_or(E::Builtin("matrix components are incompatible"))?;
362
363        // overload 2: mat from column vectors
364        if let Type::Vec(n, _) = ty {
365            if n as usize != r {
366                return Err(E::Builtin(
367                    "column vector dimension does not match matrix row dimension",
368                ));
369            }
370            if args.len() != c {
371                return Err(E::ParamCount(format!("mat{c}x{r}"), c, args.len()));
372            }
373
374            Ok(MatInstance::from_cols(args).into())
375        }
376        // overload 3: mat from float values
377        else if ty.is_float() {
378            if args.len() != c * r {
379                return Err(E::ParamCount(format!("mat{c}x{r}"), c * r, args.len()));
380            }
381
382            let args = args
383                .chunks(r)
384                .map(|v| Instance::Vec(VecInstance::new(v.to_vec())))
385                .collect_vec();
386
387            Ok(MatInstance::from_cols(args).into())
388        } else {
389            Err(E::Builtin(
390                "matrix constructor expects float or vector of float arguments",
391            ))
392        }
393    }
394}
395
396/// `matCxR()` constructor.
397///
398/// Reference: <https://www.w3.org/TR/WGSL/#mat2x2-builtin>
399pub fn mat(c: usize, r: usize, args: &[Instance]) -> Result<Instance, E> {
400    // overload 1: mat conversion constructor
401    if let [Instance::Mat(m)] = args {
402        if m.c() != c || m.r() != r {
403            let ty2 = Type::Mat(c as u8, r as u8, m.inner_ty().into());
404            return Err(E::Conversion(m.ty(), ty2));
405        }
406        // note: `matCxR(e: matCxR<S>) -> matCxR<S>` is no-op
407        Ok(m.clone().into())
408    } else {
409        if args.is_empty() {
410            return Err(E::Builtin(
411                "zero-value matrix constructor requires a template",
412            ));
413        }
414        let tys = args.iter().map(|a| a.ty()).collect_vec();
415        let ty = convert_all_ty(&tys).ok_or(E::Builtin("matrix components are incompatible"))?;
416        let mut inner_ty = ty.inner_ty();
417
418        if inner_ty.is_abstract_int() {
419            // force conversion from AbstractInt to a float type
420            inner_ty = Type::AbstractFloat;
421        } else if !inner_ty.is_float() {
422            return Err(E::Builtin(
423                "matrix constructor expects float or vector of float arguments",
424            ));
425        }
426
427        let args = convert_all_inner_to(args, &inner_ty)
428            .ok_or(E::Builtin("matrix components are incompatible"))?;
429
430        // overload 2: mat from column vectors
431        if ty.is_vec() {
432            if args.len() != c {
433                return Err(E::ParamCount(format!("mat{c}x{r}"), c, args.len()));
434            }
435
436            Ok(MatInstance::from_cols(args).into())
437        }
438        // overload 3: mat from float values
439        else if ty.is_float() || ty.is_abstract_int() {
440            if args.len() != c * r {
441                return Err(E::ParamCount(format!("mat{c}x{r}"), c * r, args.len()));
442            }
443            let args = args
444                .chunks(r)
445                .map(|v| Instance::Vec(VecInstance::new(v.to_vec())))
446                .collect_vec();
447
448            Ok(MatInstance::from_cols(args).into())
449        } else {
450            Err(E::Builtin(
451                "matrix constructor expects float or vector of float arguments",
452            ))
453        }
454    }
455}
456
457/// `vecN<T>()` constructor.
458///
459/// Expects `tplt_ty` to be valid (a scalar).
460///
461/// Reference: <https://www.w3.org/TR/WGSL/#vec2-builtin>
462pub fn vec_t(
463    n: usize,
464    tplt_ty: &Type,
465    args: &[Instance],
466    stage: ShaderStage,
467) -> Result<Instance, E> {
468    // overload 1: vec init from single scalar value
469    if let [Instance::Literal(l)] = args {
470        let val = l
471            .convert_to(tplt_ty)
472            .map(Instance::Literal)
473            .ok_or_else(|| E::ParamType(tplt_ty.clone(), l.ty()))?;
474        let comps = (0..n).map(|_| val.clone()).collect_vec();
475        Ok(VecInstance::new(comps).into())
476    }
477    // overload 2: vec conversion constructor
478    else if let [Instance::Vec(v)] = args {
479        let ty = Type::Vec(n as u8, Box::new(tplt_ty.clone()));
480        if v.n() != n {
481            return Err(E::Conversion(v.ty(), ty));
482        }
483
484        let conv_fn = match ty.inner_ty() {
485            Type::Bool => |n, _| bool(n),
486            Type::I32 => |n, _| i32(n),
487            Type::U32 => |n, _| u32(n),
488            Type::F32 => |n, stage| f32(n, stage),
489            Type::F16 => |n, stage| f16(n, stage),
490            #[cfg(feature = "naga-ext")]
491            Type::I64 => |n, _| i64(n),
492            #[cfg(feature = "naga-ext")]
493            Type::U64 => |n, _| u64(n),
494            #[cfg(feature = "naga-ext")]
495            Type::F64 => |n, stage| f64(n, stage),
496            _ => return Err(E::Builtin("vector type must be a scalar")),
497        };
498
499        let comps = v
500            .iter()
501            .map(|n| conv_fn(n, stage))
502            .collect::<Result<Vec<_>, _>>()?;
503
504        Ok(VecInstance::new(comps).into())
505    }
506    // overload 3: vec init from component values
507    else {
508        // flatten vecN args
509        let args = args
510            .iter()
511            .flat_map(|a| -> Box<dyn Iterator<Item = &Instance>> {
512                match a {
513                    Instance::Vec(v) => Box::new(v.iter()),
514                    _ => Box::new(std::iter::once(a)),
515                }
516            })
517            .collect_vec();
518        if args.len() != n {
519            return Err(E::ParamCount(format!("vec{n}"), n, args.len()));
520        }
521
522        let comps = args
523            .iter()
524            .map(|a| {
525                a.convert_to(tplt_ty)
526                    .ok_or_else(|| E::ParamType(tplt_ty.clone(), a.ty()))
527            })
528            .collect::<Result<Vec<_>, _>>()?;
529
530        Ok(VecInstance::new(comps).into())
531    }
532}
533
534/// `vecN()` constructor.
535///
536/// Reference: <https://www.w3.org/TR/WGSL/#vec2-builtin>
537pub fn vec(n: usize, args: &[Instance]) -> Result<Instance, E> {
538    // overload 1: vec init from single scalar value
539    if let [Instance::Literal(l)] = args {
540        let ty = l.ty();
541        if !ty.is_scalar() {
542            return Err(E::Builtin("vec constructor expects scalar arguments"));
543        }
544        let val = Instance::Literal(*l);
545        let comps = (0..n).map(|_| val.clone()).collect_vec();
546        Ok(VecInstance::new(comps).into())
547    }
548    // overload 2: vec conversion constructor
549    else if let [Instance::Vec(v)] = args {
550        if v.n() != n {
551            let ty = v.ty();
552            let ty2 = Type::Vec(n as u8, ty.inner_ty().into());
553            return Err(E::Conversion(ty, ty2));
554        }
555        // note: `vecN(e: vecN<S>) -> vecN<S>` is no-op
556        Ok(v.clone().into())
557    }
558    // overload 3: vec init from component values
559    else if !args.is_empty() {
560        // flatten vecN args
561        let args = args
562            .iter()
563            .flat_map(|a| -> Box<dyn Iterator<Item = &Instance>> {
564                match a {
565                    Instance::Vec(v) => Box::new(v.iter()),
566                    _ => Box::new(std::iter::once(a)),
567                }
568            })
569            .cloned()
570            .collect_vec();
571        if args.len() != n {
572            return Err(E::ParamCount(format!("vec{n}"), n, args.len()));
573        }
574
575        let comps = convert_all(&args).ok_or(E::Builtin("vector components are incompatible"))?;
576
577        if !comps.first().unwrap(/* SAFETY: len() checked above */).ty().is_scalar() {
578            return Err(E::Builtin("vec constructor expects scalar arguments"));
579        }
580        Ok(VecInstance::new(comps).into())
581    }
582    // overload 3: zero-vec
583    else {
584        VecInstance::zero_value(n as u8, &Type::AbstractInt).map(Into::into)
585    }
586}
587
588/// User-defined struct constructor.
589pub fn struct_ctor(struct_ty: &StructType, args: &[Instance]) -> Result<StructInstance, E> {
590    if args.is_empty() {
591        return StructInstance::zero_value(struct_ty);
592    }
593
594    if args.len() != struct_ty.members.len() {
595        return Err(E::ParamCount(
596            struct_ty.name.clone(),
597            struct_ty.members.len(),
598            args.len(),
599        ));
600    }
601
602    let members = struct_ty
603        .members
604        .iter()
605        .zip(args)
606        .map(|(m_ty, inst)| {
607            let inst = inst
608                .convert_to(&m_ty.ty)
609                .ok_or_else(|| E::ParamType(m_ty.ty.clone(), inst.ty()))?;
610            Ok(inst)
611        })
612        .collect::<Result<Vec<_>, E>>()?;
613
614    Ok(StructInstance::new(struct_ty.clone(), members))
615}
616
617/// Check a struct constructor call signature.
618///
619/// Validates the type and number of arguments passed.
620pub fn typecheck_struct_ctor(struct_ty: &StructType, args: &[Type]) -> Result<(), E> {
621    if args.is_empty() {
622        // zero-value constructor
623        return Ok(());
624    }
625
626    if args.len() != struct_ty.members.len() {
627        return Err(E::ParamCount(
628            struct_ty.name.clone(),
629            struct_ty.members.len(),
630            args.len(),
631        ));
632    }
633
634    for (m_ty, a_ty) in struct_ty.members.iter().zip(args) {
635        if !a_ty.is_convertible_to(&m_ty.ty) {
636            return Err(E::ParamType(m_ty.ty.clone(), a_ty.ty()));
637        }
638    }
639
640    Ok(())
641}
642
643// -----------------
644// CONSTRUCTOR TYPES
645// -----------------
646
647/// Return type of `array<T,N>()` constructor.
648///
649/// Expects `tplt_ty` to be valid.
650///
651/// Reference: <https://www.w3.org/TR/WGSL/#array-builtin>
652fn array_ctor_ty_t(tplt_ty: &Type, tplt_n: usize, args: &[Type]) -> Result<Type, E> {
653    if let Some(arg) = args.iter().find(|arg| !arg.is_convertible_to(tplt_ty)) {
654        Err(E::Conversion(arg.clone(), tplt_ty.clone()))
655    } else {
656        Ok(Type::Array(Box::new(tplt_ty.clone()), Some(tplt_n)))
657    }
658}
659
660/// Return type of `array()` constructor.
661///
662/// Reference: <https://www.w3.org/TR/WGSL/#array-builtin>
663fn array_ctor_ty(args: &[Type]) -> Result<Type, E> {
664    let ty = convert_all_ty(args).ok_or(E::Builtin("array elements are incompatible"))?;
665    Ok(Type::Array(Box::new(ty.clone()), Some(args.len())))
666}
667
668/// Return type of `matCxR<T>()` constructor.
669///
670/// Expects `tplt_ty` to be valid (a float).
671///
672/// Reference: <https://www.w3.org/TR/WGSL/#mat2x2-builtin>
673fn mat_ctor_ty_t(c: u8, r: u8, tplt_ty: &Type, args: &[Type]) -> Result<Type, E> {
674    // overload 1: mat conversion constructor
675    if let [ty @ Type::Mat(c2, r2, _)] = args {
676        // note: this is an explicit conversion, not automatic conversion
677        if *c2 != c || *r2 != r {
678            return Err(E::Conversion(
679                ty.clone(),
680                Type::Mat(c, r, Box::new(tplt_ty.clone())),
681            ));
682        }
683    } else {
684        if args.is_empty() {
685            return Err(E::Builtin("matrix constructor expects arguments"));
686        }
687        let ty = convert_all_ty(args).ok_or(E::Builtin("matrix components are incompatible"))?;
688        let ty = ty
689            .convert_inner_to(tplt_ty)
690            .ok_or(E::Conversion(ty.inner_ty(), tplt_ty.clone()))?;
691
692        // overload 2: mat from column vectors
693        if let Type::Vec(n, _) = ty {
694            if n != r {
695                return Err(E::Builtin(
696                    "column vector dimension does not match matrix row dimension",
697                ));
698            }
699            if args.len() != c as usize {
700                return Err(E::ParamCount(format!("mat{c}x{r}"), c as usize, args.len()));
701            }
702        }
703        // overload 3: mat from float values
704        else if ty.is_float() {
705            let n = c as usize * r as usize;
706            if args.len() != n {
707                return Err(E::ParamCount(format!("mat{c}x{r}"), n, args.len()));
708            }
709        } else {
710            return Err(E::Builtin(
711                "matrix constructor expects float or vector of float arguments",
712            ));
713        }
714    }
715
716    Ok(Type::Mat(c, r, Box::new(tplt_ty.clone())))
717}
718
719/// Return type of `matCxR()` constructor.
720///
721/// Reference: <https://www.w3.org/TR/WGSL/#mat2x2-builtin>
722fn mat_ctor_ty(c: u8, r: u8, args: &[Type]) -> Result<Type, E> {
723    // overload 1: mat conversion constructor
724    if let [ty @ Type::Mat(c2, r2, ty2)] = args {
725        // note: this is an explicit conversion, not automatic conversion
726        if *c2 != c || *r2 != r {
727            return Err(E::Conversion(ty.clone(), Type::Mat(c, r, ty2.clone())));
728        }
729        Ok(ty.clone())
730    } else {
731        if args.is_empty() {
732            return Err(E::Builtin(
733                "zero-value matrix constructor requires a template",
734            ));
735        }
736        let ty = convert_all_ty(args).ok_or(E::Builtin("matrix components are incompatible"))?;
737        let mut inner_ty = ty.inner_ty();
738
739        if inner_ty.is_abstract_int() {
740            // force conversion from AbstractInt to a float type
741            inner_ty = Type::AbstractFloat;
742        } else if !inner_ty.is_float() {
743            return Err(E::Builtin(
744                "matrix constructor expects float or vector of float arguments",
745            ));
746        }
747
748        // overload 2: mat from column vectors
749        if ty.is_vec() {
750            if args.len() != c as usize {
751                return Err(E::ParamCount(format!("mat{c}x{r}"), c as usize, args.len()));
752            }
753        }
754        // overload 3: mat from float values
755        else if ty.is_float() || ty.is_abstract_int() {
756            let n = c as usize * r as usize;
757            if args.len() != n {
758                return Err(E::ParamCount(format!("mat{c}x{r}"), n, args.len()));
759            }
760        } else {
761            return Err(E::Builtin(
762                "matrix constructor expects float or vector of float arguments",
763            ));
764        }
765
766        Ok(Type::Mat(c, r, inner_ty.into()))
767    }
768}
769
770/// Return type of `vecN<T>()` constructor.
771///
772/// Expects `tplt_ty` to be valid (a scalar).
773///
774/// Reference: <https://www.w3.org/TR/WGSL/#vec2-builtin>
775fn vec_ctor_ty_t(n: u8, tplt_ty: &Type, args: &[Type]) -> Result<Type, E> {
776    if let [arg] = args {
777        // overload 1: vec init from single scalar value
778        if arg.is_scalar() {
779            if !arg.is_convertible_to(tplt_ty) {
780                return Err(E::Conversion(arg.clone(), tplt_ty.clone()));
781            }
782        }
783        // overload 2: vec conversion constructor
784        else if let Type::Vec(arg_n, _) = arg
785            && n == *arg_n
786        {
787            // note: this is an explicit conversion, not automatic conversion
788        } else {
789            return Err(E::Conversion(arg.clone(), tplt_ty.clone()));
790        }
791    }
792    // overload 3: vec init from component values
793    else {
794        // flatten vecN args
795        let n2 = args
796            .iter()
797            .try_fold(0, |acc, arg| match arg {
798                ty if ty.is_scalar() => ty.is_convertible_to(tplt_ty).then_some(acc + 1),
799                Type::Vec(n, ty) => ty.is_convertible_to(tplt_ty).then_some(acc + n),
800                _ => None,
801            })
802            .ok_or(E::Builtin(
803                "vector constructor expects scalar or vector arguments",
804            ))?;
805        if n2 != n {
806            return Err(E::ParamCount(format!("vec{n}"), n as usize, args.len()));
807        }
808    }
809
810    Ok(Type::Vec(n, Box::new(tplt_ty.clone())))
811}
812
813/// Return type of `vecN()` constructor.
814///
815/// Reference: <https://www.w3.org/TR/WGSL/#vec2-builtin>
816fn vec_ctor_ty(n: u8, args: &[Type]) -> Result<Type, E> {
817    if let [arg] = args {
818        // overload 1: vec init from single scalar value
819        if arg.is_scalar() {
820        }
821        // overload 2: vec conversion constructor
822        else if let Type::Vec(arg_n, _) = arg
823            && n == *arg_n
824        {
825            // note: `vecN(e: vecN<S>) -> vecN<S>` is no-op
826        } else {
827            return Err(E::Builtin(
828                "vector constructor expects scalar or vector arguments",
829            ));
830        }
831        Ok(Type::Vec(n, arg.inner_ty().into()))
832    }
833    // overload 3: vec init from component values
834    else if !args.is_empty() {
835        // flatten vecN args
836        let n2 = args
837            .iter()
838            .try_fold(0, |acc, arg| match arg {
839                ty if ty.is_scalar() => Some(acc + 1),
840                Type::Vec(n, _) => Some(acc + n),
841                _ => None,
842            })
843            .ok_or(E::Builtin(
844                "vector constructor expects scalar or vector arguments",
845            ))?;
846        if n2 != n {
847            return Err(E::ParamCount(format!("vec{n}"), n as usize, args.len()));
848        }
849
850        let tys = args.iter().map(|arg| arg.inner_ty()).collect_vec();
851        let ty = convert_all_ty(&tys).ok_or(E::Builtin("vector components are incompatible"))?;
852
853        Ok(Type::Vec(n, ty.clone().into()))
854    }
855    // overload 3: zero-vec
856    else {
857        Ok(Type::Vec(n, Type::AbstractInt.into()))
858    }
859}
860
861/// Compute the return type of calling a built-in constructor function.
862///
863/// The arguments must be [loaded][Type::loaded].
864///
865/// Includes built-in constructors and zero-value constructors, *but not* the struct
866/// constructors, since they require knowledge of the struct type.
867/// You can type-check a struct constructor call with [`typecheck_struct_ctor`].
868pub fn type_ctor(name: &str, tplt: Option<&[TpltParam]>, args: &[Type]) -> Result<Type, E> {
869    match (name, tplt, args) {
870        ("array", Some(t), []) => {
871            let tplt = ArrayTemplate::parse(t)?;
872            tplt.n()
873                .is_some()
874                .then(|| tplt.ty())
875                .ok_or(E::TemplateArgs("array"))
876        }
877        ("array", Some(t), a) => {
878            let tplt = ArrayTemplate::parse(t)?;
879            array_ctor_ty_t(
880                &tplt.inner_ty(),
881                tplt.n().ok_or(E::TemplateArgs("array"))?,
882                a,
883            )
884        }
885        ("array", None, _) => array_ctor_ty(args),
886        ("bool", None, []) => Ok(Type::Bool),
887        ("bool", None, [a]) if a.is_scalar() => Ok(Type::Bool),
888        ("i32", None, []) => Ok(Type::I32),
889        ("i32", None, [a]) if a.is_scalar() => Ok(Type::I32),
890        ("u32", None, []) => Ok(Type::U32),
891        ("u32", None, [a]) if a.is_scalar() => Ok(Type::U32),
892        ("f32", None, []) => Ok(Type::F32),
893        ("f32", None, [a]) if a.is_scalar() => Ok(Type::F32),
894        ("f16", None, []) => Ok(Type::F16),
895        ("f16", None, [a]) if a.is_scalar() => Ok(Type::F16),
896        ("mat2x2", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(2, 2)),
897        ("mat2x2", Some(t), _) => mat_ctor_ty_t(2, 2, MatTemplate::parse(t)?.inner_ty(), args),
898        ("mat2x2", None, _) => mat_ctor_ty(2, 2, args),
899        ("mat2x3", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(2, 3)),
900        ("mat2x3", Some(t), _) => mat_ctor_ty_t(2, 3, MatTemplate::parse(t)?.inner_ty(), args),
901        ("mat2x3", None, _) => mat_ctor_ty(2, 3, args),
902        ("mat2x4", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(2, 4)),
903        ("mat2x4", Some(t), _) => mat_ctor_ty_t(2, 4, MatTemplate::parse(t)?.inner_ty(), args),
904        ("mat2x4", None, _) => mat_ctor_ty(2, 4, args),
905        ("mat3x2", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(3, 2)),
906        ("mat3x2", Some(t), _) => mat_ctor_ty_t(3, 2, MatTemplate::parse(t)?.inner_ty(), args),
907        ("mat3x2", None, _) => mat_ctor_ty(3, 2, args),
908        ("mat3x3", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(3, 3)),
909        ("mat3x3", Some(t), _) => mat_ctor_ty_t(3, 3, MatTemplate::parse(t)?.inner_ty(), args),
910        ("mat3x3", None, _) => mat_ctor_ty(3, 3, args),
911        ("mat3x4", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(3, 4)),
912        ("mat3x4", Some(t), _) => mat_ctor_ty_t(3, 4, MatTemplate::parse(t)?.inner_ty(), args),
913        ("mat3x4", None, _) => mat_ctor_ty(3, 4, args),
914        ("mat4x2", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(4, 2)),
915        ("mat4x2", Some(t), _) => mat_ctor_ty_t(4, 2, MatTemplate::parse(t)?.inner_ty(), args),
916        ("mat4x2", None, _) => mat_ctor_ty(4, 2, args),
917        ("mat4x3", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(4, 3)),
918        ("mat4x3", Some(t), _) => mat_ctor_ty_t(4, 3, MatTemplate::parse(t)?.inner_ty(), args),
919        ("mat4x3", None, _) => mat_ctor_ty(4, 3, args),
920        ("mat4x4", Some(t), []) => Ok(MatTemplate::parse(t)?.ty(4, 4)),
921        ("mat4x4", Some(t), _) => mat_ctor_ty_t(4, 4, MatTemplate::parse(t)?.inner_ty(), args),
922        ("mat4x4", None, _) => mat_ctor_ty(4, 4, args),
923        ("vec2", Some(t), []) => Ok(VecTemplate::parse(t)?.ty(2)),
924        ("vec2", Some(t), _) => vec_ctor_ty_t(2, VecTemplate::parse(t)?.inner_ty(), args),
925        ("vec2", None, _) => vec_ctor_ty(2, args),
926        ("vec3", Some(t), []) => Ok(VecTemplate::parse(t)?.ty(3)),
927        ("vec3", Some(t), _) => vec_ctor_ty_t(3, VecTemplate::parse(t)?.inner_ty(), args),
928        ("vec3", None, _) => vec_ctor_ty(3, args),
929        ("vec4", Some(t), []) => Ok(VecTemplate::parse(t)?.ty(4)),
930        ("vec4", Some(t), _) => vec_ctor_ty_t(4, VecTemplate::parse(t)?.inner_ty(), args),
931        ("vec4", None, _) => vec_ctor_ty(4, args),
932        #[cfg(feature = "naga-ext")]
933        ("i64", None, []) => Ok(Type::I64),
934        #[cfg(feature = "naga-ext")]
935        ("i64", None, [a]) if a.is_scalar() => Ok(Type::I64),
936        #[cfg(feature = "naga-ext")]
937        ("u64", None, []) => Ok(Type::U64),
938        #[cfg(feature = "naga-ext")]
939        ("u64", None, [a]) if a.is_scalar() => Ok(Type::U64),
940        #[cfg(feature = "naga-ext")]
941        ("f64", None, []) => Ok(Type::F64),
942        #[cfg(feature = "naga-ext")]
943        ("f64", None, [a]) if a.is_scalar() => Ok(Type::F64),
944        _ => Err(E::Signature(CallSignature {
945            name: name.to_string(),
946            tplt: tplt.map(|t| t.to_vec()),
947            args: args.to_vec(),
948        })),
949    }
950}
951
952// -----------
953// ZERO VALUES
954// -----------
955// reference: <https://www.w3.org/TR/WGSL/#zero-value>
956
957impl Instance {
958    /// Zero-value initialize an instance of a given type.
959    pub fn zero_value(ty: &Type) -> Result<Self, E> {
960        match ty {
961            Type::Bool
962            | Type::AbstractInt
963            | Type::AbstractFloat
964            | Type::I32
965            | Type::U32
966            | Type::F32
967            | Type::F16 => LiteralInstance::zero_value(ty).map(Into::into),
968            Type::Struct(s) => StructInstance::zero_value(s).map(Into::into),
969            Type::Array(a_ty, Some(n)) => ArrayInstance::zero_value(a_ty, *n).map(Into::into),
970            Type::Array(_, None) => Err(E::NotConstructible(ty.clone())),
971            Type::Vec(n, v_ty) => VecInstance::zero_value(*n, v_ty).map(Into::into),
972            Type::Mat(c, r, m_ty) => MatInstance::zero_value(*c, *r, m_ty).map(Into::into),
973            Type::Atomic(_)
974            | Type::Ptr(_, _, _)
975            | Type::Ref(_, _, _)
976            | Type::Texture(_)
977            | Type::Sampler(_)
978            | Type::Unknown => Err(E::NotConstructible(ty.clone())),
979
980            #[cfg(feature = "naga-ext")]
981            Type::I64 | Type::U64 | Type::F64 => LiteralInstance::zero_value(ty).map(Into::into),
982            #[cfg(feature = "naga-ext")]
983            Type::BindingArray(_, _) | Type::RayQuery(_) | Type::AccelerationStructure(_) => {
984                Err(E::NotConstructible(ty.clone()))
985            }
986        }
987    }
988
989    /// For types that are not *constructible*, but are *storable*, there is no zero-value,
990    /// but it is possible to initialize a variable storing an instance of the type.
991    ///
992    /// In practice, these types are only atomics and composite of atomics (array and struct)
993    /// in the `workgroup` address space.
994    ///
995    /// There are other non-constructible, storable types, but they live in host-managed
996    /// address spaces:
997    ///
998    /// * textures and samplers in the `handle` address space.
999    /// * runtime-sized arrays in the `storage` address space.
1000    pub fn storable_zero_value(ty: &Type) -> Result<Self, E> {
1001        match ty {
1002            Type::Struct(s) => StructInstance::storable_zero_value(s).map(Into::into),
1003            Type::Array(a_ty, Some(n)) => {
1004                ArrayInstance::storable_zero_value(a_ty, *n).map(Into::into)
1005            }
1006            Type::Atomic(a_ty) => AtomicInstance::storable_zero_value(a_ty).map(Into::into),
1007            _ => Self::zero_value(ty),
1008        }
1009    }
1010}
1011
1012impl LiteralInstance {
1013    /// The zero-value constructor.
1014    pub fn zero_value(ty: &Type) -> Result<Self, E> {
1015        match ty {
1016            Type::Bool => Ok(LiteralInstance::Bool(false)),
1017            Type::AbstractInt => Ok(LiteralInstance::AbstractInt(0)),
1018            Type::AbstractFloat => Ok(LiteralInstance::AbstractFloat(0.0)),
1019            Type::I32 => Ok(LiteralInstance::I32(0)),
1020            Type::U32 => Ok(LiteralInstance::U32(0)),
1021            Type::F32 => Ok(LiteralInstance::F32(0.0)),
1022            Type::F16 => Ok(LiteralInstance::F16(f16::zero())),
1023            #[cfg(feature = "naga-ext")]
1024            Type::I64 => Ok(LiteralInstance::I64(0)),
1025            #[cfg(feature = "naga-ext")]
1026            Type::U64 => Ok(LiteralInstance::U64(0)),
1027            #[cfg(feature = "naga-ext")]
1028            Type::F64 => Ok(LiteralInstance::F64(0.0)),
1029            _ => Err(E::NotScalar(ty.clone())),
1030        }
1031    }
1032}
1033
1034impl StructInstance {
1035    /// Zero-value initialize a `struct` instance.
1036    pub fn zero_value(s: &StructType) -> Result<Self, E> {
1037        let members = s
1038            .members
1039            .iter()
1040            .map(|mem| {
1041                let val = Instance::zero_value(&mem.ty)?;
1042                Ok(val)
1043            })
1044            .collect::<Result<Vec<_>, _>>()?;
1045
1046        Ok(StructInstance::new(s.clone(), members))
1047    }
1048
1049    /// See [`Instance::storable_zero_value`].
1050    pub fn storable_zero_value(s: &StructType) -> Result<Self, E> {
1051        let members = s
1052            .members
1053            .iter()
1054            .map(|mem| {
1055                let val = Instance::storable_zero_value(&mem.ty)?;
1056                Ok(val)
1057            })
1058            .collect::<Result<Vec<_>, _>>()?;
1059
1060        Ok(StructInstance::new(s.clone(), members))
1061    }
1062}
1063
1064impl ArrayInstance {
1065    /// Zero-value initialize an `array` instance.
1066    pub fn zero_value(ty: &Type, n: usize) -> Result<Self, E> {
1067        let zero = Instance::zero_value(ty)?;
1068        let comps = (0..n).map(|_| zero.clone()).collect_vec();
1069        Ok(ArrayInstance::new(comps, false))
1070    }
1071
1072    /// See [`Instance::storable_zero_value`].
1073    pub fn storable_zero_value(ty: &Type, n: usize) -> Result<Self, E> {
1074        let zero = Instance::storable_zero_value(ty)?;
1075        let comps = (0..n).map(|_| zero.clone()).collect_vec();
1076        Ok(ArrayInstance::new(comps, false))
1077    }
1078}
1079
1080impl VecInstance {
1081    /// Zero-value initialize a `vec` instance.
1082    pub fn zero_value(n: u8, ty: &Type) -> Result<Self, E> {
1083        let zero = Instance::Literal(LiteralInstance::zero_value(ty)?);
1084        let comps = (0..n).map(|_| zero.clone()).collect_vec();
1085        Ok(VecInstance::new(comps))
1086    }
1087}
1088
1089impl MatInstance {
1090    /// Zero-value initialize a `mat` instance.
1091    pub fn zero_value(c: u8, r: u8, ty: &Type) -> Result<Self, E> {
1092        let zero = Instance::Literal(LiteralInstance::zero_value(ty)?);
1093        let zero_col = Instance::Vec(VecInstance::new((0..r).map(|_| zero.clone()).collect_vec()));
1094        let comps = (0..c).map(|_| zero_col.clone()).collect_vec();
1095        Ok(MatInstance::from_cols(comps))
1096    }
1097}
1098
1099impl AtomicInstance {
1100    /// See [`Instance::storable_zero_value`].
1101    pub fn storable_zero_value(ty: &Type) -> Result<Self, E> {
1102        let zero = Instance::zero_value(ty)?;
1103        Ok(AtomicInstance::new(zero))
1104    }
1105}