Skip to main content

naga_rust_back/
writer.rs

1use alloc::borrow::Cow;
2use alloc::boxed::Box;
3use alloc::format;
4use alloc::string::{String, ToString};
5use alloc::vec;
6use alloc::vec::Vec;
7use arrayvec::ArrayVec;
8use core::fmt::Write;
9
10use naga::{
11    Expression, Handle, Module, ShaderStage, TypeInner, back,
12    proc::{self, NameKey},
13    valid::ModuleInfo,
14};
15
16use crate::conv::{self, BinOpClassified};
17use crate::ra::{self, PrintAst as _};
18use crate::util::Gensym;
19use crate::{Config, Error};
20use crate::{config::WriterFlags, util::GlobalKind};
21
22// -------------------------------------------------------------------------------------------------
23
24/// Shorthand result used internally by the backend
25type BackendResult = Result<(), Error>;
26
27/// The Rust form that [`Writer::expr_ast_with_indirection`] should use to render a Naga
28/// expression.
29///
30/// Sometimes a Naga `Expression` alone doesn't provide enough information to
31/// choose the right rendering for it in Rust.
32/// This is because the Naga IR does not have the Rust concept of “place expressions”
33/// (or WGSL “references”); everything that might be read or written separately
34/// from evaluating the expression itself is expressed via expressions whose Naga IR
35/// types are pointers. But in Rust, we need to know whether to borrow (take a
36/// reference or pointer to) a place, and if so, *how* to borrow it (`&`, `&mut`,
37/// or `&raw`) to satisfy type and borrow checking.
38///
39/// The caller of `expr_ast_with_indirection` must therefore provide this parameter
40/// to say what kind of Rust expression it wants, relative to the type of the Naga IR
41/// expression.
42#[derive(Clone, Copy, Debug)]
43enum Indirection {
44    /// The Naga expression must have a pointer type, and
45    /// the Rust expression will be a place expression for the referent of that pointer.
46    Place,
47
48    /// The Rust expression has the same corresponding type as the Naga expression.
49    /// The Rust expression is not necessarily a mutable place; it may be borrowed
50    /// immutably but not mutably.
51    Ordinary,
52
53    /// The Naga expression has a value type, but the Rust expression is a reference type.
54    /// This is currently used only for texture (image) handles.
55    Ref,
56}
57
58/// Modifier for how scalars in Naga types are translated to Rust based on context.
59///
60/// In order to support translation to SIMD execution (not yet implemented as of this writing),
61/// we need to convert scalars into SIMD vectors.
62/// However, that conversion should apply only to things which are getting vectorized — that is,
63/// function local variables, private global variables, function inputs, and function outputs —
64/// but not to uniforms, struct members, workgroup variables, or the arguments of public function
65/// shims.
66/// This enum captures that distinction, in a way similar to [`naga::AddressSpace`] but more
67/// precisely fitted to our concerns.
68#[derive(Clone, Copy, Debug)]
69pub(crate) enum TypeTranslation {
70    /// Scalar types are translated to standard Rust types, e.g. `[f32; 10]`.
71    RustScalar,
72
73    /// Scalar types are translated to shader-behavior types, e.g.
74    /// `[rt::Scalar<f32>; 10]`, without SIMD.
75    ///
76    /// This is not yet implemented and currently behaves identically to `RustScalar`.
77    ShaderScalar,
78
79    /// Scalar types are translated to SIMD types which contain values for an entire workgroup.
80    ///
81    /// This is not yet implemented and currently behaves identically to `ShaderScalar`.
82    Simd,
83}
84impl From<naga::AddressSpace> for TypeTranslation {
85    fn from(value: naga::AddressSpace) -> Self {
86        match value {
87            // Everything that is stored separately per invocation gets the Simd form.
88            naga::AddressSpace::Function | naga::AddressSpace::Private => Self::Simd,
89
90            // Everything that is not stored separately, and originates from Naga, gets the
91            // ShaderScalar form.
92            naga::AddressSpace::Uniform
93            | naga::AddressSpace::Handle
94            | naga::AddressSpace::WorkGroup
95            | naga::AddressSpace::Immediate
96            | naga::AddressSpace::Storage { .. } => Self::ShaderScalar,
97            naga::AddressSpace::TaskPayload
98            | naga::AddressSpace::RayPayload
99            | naga::AddressSpace::IncomingRayPayload => {
100                unimplemented!("mesh and raytracing shaders are not supported")
101            }
102        }
103    }
104}
105
106/// Reserved prefix for the functions that use types chosen for the convenience of
107/// execution of the shader rather than for convenient public API.
108const FN_INTERNAL_TYPES_PREFIX: &str = "v_";
109
110// -------------------------------------------------------------------------------------------------
111
112/// [`naga`] backend allowing you to translate shader code in any language supported by Naga
113/// to Rust code.
114///
115/// A `Writer` stores a [`Config`] and data structures that can be reused for writing multiple
116/// modules.
117#[allow(missing_debug_implementations, reason = "TODO")]
118pub struct Writer {
119    config: Config,
120    names: naga::FastHashMap<NameKey, String>,
121    namer: proc::Namer,
122    named_expressions: naga::FastIndexMap<Handle<Expression>, String>,
123}
124
125enum ExpressionCtx<'a> {
126    Global {
127        module: &'a Module,
128        module_info: &'a ModuleInfo,
129        expressions: &'a naga::Arena<Expression>,
130    },
131    Function {
132        module: &'a Module,
133        //module_info: &'a ModuleInfo,
134        func_ctx: &'a back::FunctionCtx<'a>,
135    },
136}
137
138impl<'a> ExpressionCtx<'a> {
139    #[track_caller]
140    fn expect_func_ctx(&self) -> &'a back::FunctionCtx<'a> {
141        match self {
142            ExpressionCtx::Function { func_ctx, .. } => func_ctx,
143            ExpressionCtx::Global { .. } => {
144                unreachable!("attempting to access the function context outside of a function")
145            }
146        }
147    }
148
149    fn module(&self) -> &'a Module {
150        match self {
151            ExpressionCtx::Global { module, .. } => module,
152            ExpressionCtx::Function { module, .. } => module,
153        }
154    }
155
156    fn expressions(&self) -> &'a naga::Arena<Expression> {
157        match self {
158            ExpressionCtx::Global { expressions, .. } => expressions,
159            ExpressionCtx::Function { func_ctx, .. } => func_ctx.expressions,
160        }
161    }
162
163    fn types(&self) -> &'a naga::UniqueArena<naga::Type> {
164        &self.module().types
165    }
166
167    fn resolve_type(&self, handle: Handle<Expression>) -> &'a TypeInner {
168        match self {
169            ExpressionCtx::Global { module_info, .. } => &module_info[handle],
170            ExpressionCtx::Function { func_ctx, .. } => &func_ctx.info[handle].ty,
171        }
172        .inner_with(self.types())
173    }
174}
175
176impl Writer {
177    /// Creates a new [`Writer`].
178    #[must_use]
179    pub fn new(config: Config) -> Self {
180        Writer {
181            config,
182            names: naga::FastHashMap::default(),
183            namer: proc::Namer::default(),
184            named_expressions: naga::FastIndexMap::default(),
185        }
186    }
187
188    fn reset(&mut self, module: &Module) {
189        let Self {
190            config,
191            names,
192            namer,
193            named_expressions,
194        } = self;
195        names.clear();
196        namer.reset(
197            module,
198            conv::keywords_2024(),
199            proc::KeywordSet::empty(),
200            proc::CaseInsensitiveKeywordSet::empty(),
201            &[FN_INTERNAL_TYPES_PREFIX],
202            &mut self.names,
203        );
204
205        // TODO: We actually want to say “treat this as reserved but do not rename it”,
206        // but Namer doesn’t have that option
207        if let Some(g) = &config.global_struct {
208            namer.call(g);
209        }
210        if let Some(r) = &config.resource_struct {
211            namer.call(r);
212        }
213        named_expressions.clear();
214    }
215
216    /// Converts `module` to a string of Rust code.
217    ///
218    /// This function’s behavior is independent of prior uses of this [`Writer`].
219    ///
220    /// # Errors
221    ///
222    /// Returns an error if the module cannot be represented as Rust
223    /// or if `out` returns an error.
224    pub fn write(
225        &mut self,
226        out: &mut dyn Write,
227        module: &Module,
228        info: &ModuleInfo,
229    ) -> BackendResult {
230        let top_level_items = self.translate_module(module, info)?;
231
232        let print_ctx = ra::PrintCtx {
233            config: &self.config,
234            indent: back::Level(0),
235        };
236        for item in top_level_items {
237            item.write(out, print_ctx)?;
238        }
239
240        Ok(())
241    }
242
243    /// Converts `module` to a Rust AST.
244    ///
245    /// This function’s behavior is independent of prior uses of this [`Writer`].
246    ///
247    /// (It is not public because the AST is not public)
248    pub(crate) fn translate_module(
249        &mut self,
250        module: &Module,
251        info: &ModuleInfo,
252    ) -> Result<Vec<ra::Item>, Error> {
253        if !module.overrides.is_empty() {
254            return Err(Error::Unimplemented("pipeline constants".into()));
255        }
256
257        self.reset(module);
258
259        let visibility = self.visibility();
260        let mut top_level_items: Vec<ra::Item> = Vec::new();
261
262        // Translate all structs
263        for (handle, ty) in module.types.iter() {
264            if let TypeInner::Struct { ref members, .. } = ty.inner {
265                top_level_items.extend(self.translate_struct_definition(module, handle, members)?);
266            }
267        }
268
269        // Translate all named constants
270        for (handle, _) in module.constants.iter().filter(|&(_, c)| c.name.is_some()) {
271            top_level_items.push(ra::Item::Const(
272                self.translate_global_constant(module, info, handle)?,
273            ));
274        }
275
276        // If we are using resources, write the `struct` that contains them.
277        let any_resource_requires_lifetime = GlobalKind::Resource
278            .filter(&module.global_variables)
279            .any(|(_, global)| matches!(module.types[global.ty].inner, TypeInner::Image { .. }));
280        let resource_lifetime_generics = if any_resource_requires_lifetime {
281            ra::Generics::LtG
282        } else {
283            ra::Generics::None
284        };
285        {
286            let mut resource_iter = GlobalKind::Resource.filter(&module.global_variables);
287            if let Some(ref resource_struct_name) = self.config.resource_struct {
288                let resource_struct_ast = ra::StructItem {
289                    attributes: vec![],
290                    visibility: ra::Visibility::Private, // TODO: wrong visibility
291                    name: resource_struct_name.clone(),
292                    generics: resource_lifetime_generics,
293                    fields: resource_iter
294                        .map(|(handle, global)| {
295                            self.translate_global_variable_to_struct_field(module, global, handle)
296                        })
297                        .collect::<Result<Vec<_>, _>>()?,
298                };
299
300                top_level_items.push(ra::Item::Struct(resource_struct_ast));
301            } else if let Some((_, example)) = resource_iter.next() {
302                return Err(Error::ResourcesNotEnabled {
303                    example: example.name.clone().unwrap_or_default(),
304                });
305            }
306        }
307
308        let ref_to_resources_ty = self.config.resource_struct.as_ref().map(|name| {
309            ra::Type::Ptr(
310                ra::PtrKind::Shared(Some("g")),
311                Box::new(ra::Type::User(name.clone(), resource_lifetime_generics)),
312            )
313        });
314
315        // If we are using global variables, write the `struct` that contains them.
316        let global_lifetime_generics =
317            if self.config.global_struct.is_some() && self.config.resource_struct.is_some() {
318                ra::Generics::LtG
319            } else {
320                // We use the resource struct like it was the global struct if we have only the
321                // former, so the `impl` must use those generics.
322                resource_lifetime_generics
323            };
324        {
325            let mut global_variable_iter = GlobalKind::Variable.filter(&module.global_variables);
326            if let Some(ref global_struct_name) = self.config.global_struct {
327                let mut global_struct_fields = Vec::new();
328                if let Some(ref ref_to_resources_ty) = ref_to_resources_ty {
329                    global_struct_fields.push(ra::Field {
330                        attributes: vec![],
331                        visibility,
332                        name: "resources".into(),
333                        ty: ref_to_resources_ty.clone(),
334                    });
335                }
336                for (handle, global) in global_variable_iter {
337                    global_struct_fields.push(
338                        self.translate_global_variable_to_struct_field(module, global, handle)?,
339                    );
340                }
341
342                let global_struct_item = ra::StructItem {
343                    attributes: vec![],
344                    visibility: ra::Visibility::Private, // TODO: existing behavior but is it right?
345                    name: global_struct_name.clone(),
346                    generics: global_lifetime_generics,
347                    fields: global_struct_fields,
348                };
349                top_level_items.push(ra::Item::Struct(global_struct_item));
350
351                let global_struct_constructor_fn = ra::FunctionItem {
352                    attributes: vec![],
353                    visibility,
354                    const_: true,
355                    name: "new".into(),
356                    // Define new() function with parameter list depending on whether the resource
357                    // struct is needed.
358                    parameters: if let Some(ref_to_resources_ty) = ref_to_resources_ty {
359                        vec![(
360                            ra::Pattern::Binding("resources".into()),
361                            ref_to_resources_ty,
362                        )]
363                    } else {
364                        vec![]
365                    },
366                    self_param: None,
367                    return_type: ra::Type::Self_,
368                    body: ra::Block::expr(ra::Expr::Struct(ra::Type::Self_, {
369                        let mut fields = Vec::new();
370                        if self.config.resource_struct.is_some() {
371                            // Note that we reserve the name “resources” using the keyword set.
372                            fields.push(("resources".into(), ra::Expr::Ident("resources".into())));
373                        }
374                        for (handle, global) in
375                            GlobalKind::Variable.filter(&module.global_variables)
376                        {
377                            fields.push((
378                                self.names[&NameKey::GlobalVariable(handle)].clone(),
379                                self.global_variable_as_field_initializer_expr(
380                                    module, info, global,
381                                )?,
382                            ));
383                        }
384                        fields
385                    })),
386                };
387
388                top_level_items.push(ra::Item::Impl(
389                    global_lifetime_generics,
390                    None,
391                    ra::Type::User(global_struct_name.clone(), global_lifetime_generics),
392                    vec![ra::Item::Function(global_struct_constructor_fn)],
393                ));
394
395                if self.config.resource_struct.is_none() {
396                    // If the global struct doesn’t need a resource struct,
397                    // then it can implement Default.
398                    top_level_items.push(ra::Item::Impl(
399                        global_lifetime_generics,
400                        Some(ra::Trait::Default),
401                        ra::Type::User(global_struct_name.clone(), global_lifetime_generics),
402                        vec![ra::Item::Function(ra::FunctionItem {
403                            attributes: Vec::new(),
404                            visibility: ra::Visibility::Private, // actually implicit-public
405                            const_: false,
406                            name: "default".into(),
407                            self_param: None,
408                            parameters: vec![],
409                            return_type: ra::Type::Self_,
410                            body: ra::Block::expr(ra::Expr::Call(
411                                Box::new(ra::Expr::QualifiedPath(ra::Type::Self_, "new")),
412                                vec![],
413                            )),
414                        })],
415                    ));
416                }
417            } else if let Some((_, example)) = global_variable_iter.next() {
418                return Err(Error::GlobalVariablesNotEnabled {
419                    example: example.name.clone().unwrap_or_default(),
420                });
421            }
422        }
423
424        // Collects all items that go in the `impl Globals` if there is one.
425        let mut maybe_impl_items: Vec<ra::Item> = Vec::new();
426
427        // Translate all regular functions (which may or may not go in an `impl` block).
428        for (handle, function) in module.functions.iter() {
429            let fun_info = &info[handle];
430
431            let func_ctx = back::FunctionCtx {
432                ty: back::FunctionType::Function(handle),
433                info: fun_info,
434                expressions: &function.expressions,
435                named_expressions: &function.named_expressions,
436            };
437
438            // Translate the function
439            maybe_impl_items.extend(self.translate_function(
440                module,
441                function,
442                &func_ctx,
443                vec![],
444            )?);
445        }
446
447        // Translate all entry points
448        for (index, ep) in module.entry_points.iter().enumerate() {
449            let entry_point_attributes = match ep.stage {
450                ShaderStage::Vertex
451                | ShaderStage::Fragment
452                | ShaderStage::Task
453                | ShaderStage::Mesh
454                | ShaderStage::RayGeneration
455                | ShaderStage::Miss
456                | ShaderStage::AnyHit
457                | ShaderStage::ClosestHit => vec![ra::Attribute::Stage(ep.stage)],
458                ShaderStage::Compute => vec![
459                    ra::Attribute::Stage(ShaderStage::Compute),
460                    ra::Attribute::WorkGroupSize(ep.workgroup_size),
461                ],
462            };
463
464            let func_ctx = back::FunctionCtx {
465                ty: back::FunctionType::EntryPoint(index.try_into().unwrap()),
466                info: info.get_entry_point(index),
467                expressions: &ep.function.expressions,
468                named_expressions: &ep.function.named_expressions,
469            };
470            maybe_impl_items.extend(self.translate_function(
471                module,
472                &ep.function,
473                &func_ctx,
474                entry_point_attributes,
475            )?);
476        }
477
478        // If we are making methods rather than free functions, start the `impl` block
479        if let Some(name) = self.config.impl_type() {
480            top_level_items.push(ra::Item::Impl(
481                global_lifetime_generics,
482                None,
483                ra::Type::User(name.to_string(), global_lifetime_generics),
484                maybe_impl_items,
485            ))
486        } else {
487            top_level_items.extend(maybe_impl_items);
488        }
489
490        Ok(top_level_items)
491    }
492
493    /// Translates a shader function to a pair of Rust functions.
494    /// The shader function may be an entry point or not.
495    /// Depending on the configuration it may be written as a method or a free function.
496    fn translate_function(
497        &mut self,
498        module: &Module,
499        func: &naga::Function,
500        func_ctx: &back::FunctionCtx<'_>,
501        extra_attributes: Vec<ra::Attribute>,
502    ) -> Result<[ra::Item; 2], Error> {
503        Ok([
504            self.translate_function_inner(module, func, func_ctx, true, extra_attributes)?,
505            self.translate_function_inner(module, func, func_ctx, false, vec![])?,
506        ])
507    }
508
509    fn translate_function_inner(
510        &mut self,
511        module: &Module,
512        func: &naga::Function,
513        func_ctx: &back::FunctionCtx<'_>,
514        is_public_shim: bool,
515        mut attributes: Vec<ra::Attribute>,
516    ) -> Result<ra::Item, Error> {
517        let signature_type_translation = if is_public_shim {
518            TypeTranslation::RustScalar
519        } else {
520            TypeTranslation::Simd
521        };
522
523        if !is_public_shim {
524            // Don’t lint extra parentheses and such that we might emit.
525            attributes.push(ra::Attribute::AllowFunctionBody);
526        }
527
528        let shader_func_name = match func_ctx.ty {
529            back::FunctionType::EntryPoint(index) => &self.names[&NameKey::EntryPoint(index)],
530            back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)],
531        };
532        let name_prefix = if is_public_shim {
533            ""
534        } else {
535            FN_INTERNAL_TYPES_PREFIX
536        };
537        let rust_func_name = format!("{name_prefix}{shader_func_name}");
538        let visibility = if is_public_shim {
539            self.visibility()
540        } else {
541            ra::Visibility::Private
542        };
543
544        let self_param = if self.config.functions_are_methods() {
545            // TODO: need to figure out whether &mut is needed
546            Some(ra::PtrKind::Shared(None))
547        } else if func_ctx.info.global_variable_count() > 0 {
548            unreachable!(
549                "function has globals but globals are not enabled; \
550                should have been rejected earlier"
551            )
552        } else {
553            None
554        };
555
556        let use_into_for_arg = |arg: &naga::FunctionArgument| {
557            matches!(
558                module.types[arg.ty].inner,
559                TypeInner::Scalar { .. } | TypeInner::Vector { .. }
560            )
561        };
562
563        // Translate function arguments
564        let mut rust_params: Vec<(ra::Pattern, ra::Type)> =
565            Vec::with_capacity(func.arguments.len());
566        for (index, arg) in func.arguments.iter().enumerate() {
567            let argument_name = &self.names[&func_ctx.argument_key(index.try_into().unwrap())];
568
569            // TODO: When `TypeTranslation` actually does things, this and the return value
570            // processing will need to be tweaked.
571            let rust_type = if is_public_shim && use_into_for_arg(arg) {
572                // Allow vectors and scalars to be converted.
573                ra::Type::ImplInto(Box::new(self.type_ast(
574                    module,
575                    arg.ty,
576                    TypeTranslation::ShaderScalar,
577                )?))
578            } else {
579                self.type_ast(module, arg.ty, signature_type_translation)?
580            };
581
582            rust_params.push((ra::Pattern::Binding(argument_name.clone()), rust_type));
583        }
584
585        let return_type = if let Some(ref result) = func.result {
586            // if let Some(ref binding) = result.binding {
587            //     self.write_attributes(&map_binding_to_attribute(binding))?;
588            // }
589            self.type_ast(module, result.ty, signature_type_translation)?
590        } else {
591            ra::Type::Unit
592        };
593
594        let body = if is_public_shim {
595            // Translate function call to the inner, internally-typed function.
596
597            let mut call_args = Vec::new();
598            for (index, arg) in func.arguments.iter().enumerate() {
599                let argument_name = &self.names[&func_ctx.argument_key(index.try_into().unwrap())];
600                let argument_expr = if use_into_for_arg(arg) {
601                    ra::Expr::call_rt(ra::RtItem::IntoFn, [ra::Expr::Ident(argument_name.clone())])
602                } else {
603                    ra::Expr::Ident(argument_name.clone())
604                };
605                call_args.push(argument_expr);
606            }
607
608            // The final into() converts from the internal `TypeTranslation::Simd`
609            // type to the public `TypeTranslation::RustScalar` type.
610            // TODO: calling into as a trait method can be ambiguous
611            ra::Block::expr(ra::Expr::call_rt(
612                ra::RtItem::IntoFn,
613                [ra::Expr::call_maybe_self(
614                    self.config.functions_are_methods(),
615                    format!("{FN_INTERNAL_TYPES_PREFIX}{shader_func_name}"),
616                    call_args,
617                )],
618            ))
619        } else {
620            let mut body_statements = Vec::new();
621
622            // Define function local variables
623            for (handle, local) in func.local_variables.iter() {
624                let local_name = self.names[&func_ctx.name_key(handle)].clone();
625                let init_expression = if let Some(init) = local.init {
626                    Some(self.translate_expr(
627                        init,
628                        &ExpressionCtx::Function {
629                            module,
630                            func_ctx,
631                            //module_info: info,
632                        },
633                    )?)
634                } else {
635                    None
636                };
637
638                body_statements.push(ra::Statement::Let(
639                    ra::Pattern::BindingMut(local_name),
640                    Some(self.type_ast(module, local.ty, TypeTranslation::Simd)?),
641                    init_expression,
642                ));
643            }
644            if !func.local_variables.is_empty() {
645                body_statements.push(ra::Statement::BlankLine);
646            }
647
648            // Translate the function body (statement list)
649            for sta in func.body.iter() {
650                body_statements.extend(self.translate_statement(module, sta, func_ctx)?);
651            }
652
653            self.named_expressions.clear();
654
655            ra::Block(body_statements, None)
656        };
657
658        Ok(ra::Item::Function(ra::FunctionItem {
659            attributes,
660            visibility,
661            const_: false,
662            name: rust_func_name,
663            self_param,
664            parameters: rust_params,
665            return_type,
666            body,
667        }))
668    }
669
670    /// Translates the definition of the struct type referred to by `handle` in `module`.
671    /// Generates a `struct` item, and a constructor function if the struct type is not
672    /// dynamically sized.
673    ///
674    /// Use `members` as the list of `handle`'s members. (This
675    /// function is usually called after matching a `TypeInner`, so
676    /// the callers already have the members at hand.)
677    fn translate_struct_definition(
678        &self,
679        module: &Module,
680        struct_handle: Handle<naga::Type>,
681        members: &[naga::StructMember],
682    ) -> Result<Vec<ra::Item>, Error> {
683        // TODO: we will need to do custom dummy fields to ensure that vec3s have correct alignment.
684        let visibility = self.visibility();
685        let name = &self.names[&NameKey::Type(struct_handle)];
686        let struct_attributes = vec![ra::Attribute::DeriveStructTraits, ra::Attribute::ReprC];
687        let self_ty = ra::Type::User(name.clone(), ra::Generics::None);
688
689        let mut fields: Vec<ra::Field> = Vec::with_capacity(members.len());
690        let mut dyn_sized = false;
691        for (member_name, member) in self.iter_struct_members(struct_handle, members) {
692            // TODO: add bindings as doc-comments ?
693            // if let Some(ref binding) = member.binding {
694            //     map_binding_to_attribute(binding);
695            // }
696
697            fields.push(ra::Field {
698                attributes: vec![],
699                visibility,
700                name: member_name.to_string(),
701                ty: self.type_ast(module, member.ty, TypeTranslation::RustScalar)?,
702            });
703
704            if module.types[member.ty]
705                .inner
706                .is_dynamically_sized(&module.types)
707            {
708                dyn_sized = true;
709            }
710        }
711
712        let mut items = vec![ra::Item::Struct(ra::StructItem {
713            attributes: struct_attributes,
714            visibility,
715            name: name.clone(),
716            generics: ra::Generics::None,
717            fields,
718        })];
719
720        // Constructor (if not dynamically sized)
721        if !dyn_sized {
722            let mut constructor_parameters = Vec::new();
723            let mut constructor_fields = Vec::new();
724            for (member_name, member) in self.iter_struct_members(struct_handle, members) {
725                constructor_parameters.push((
726                    ra::Pattern::Binding(member_name.to_string()),
727                    ra::Type::ImplInto(Box::new(self.type_ast(
728                        module,
729                        member.ty,
730                        TypeTranslation::RustScalar,
731                    )?)),
732                ));
733                constructor_fields.push((
734                    member_name.to_string(),
735                    ra::Expr::call_rt(
736                        ra::RtItem::IntoFn,
737                        [ra::Expr::Ident(member_name.to_string())],
738                    ),
739                ));
740            }
741
742            let constructor_fn = ra::FunctionItem {
743                attributes: vec![],
744                visibility,
745                const_: false,
746                name: "new".into(),
747                self_param: None,
748                parameters: constructor_parameters,
749                return_type: ra::Type::Self_,
750                body: ra::Block::expr(ra::Expr::Struct(ra::Type::Self_, constructor_fields)),
751            };
752
753            items.push(ra::Item::Impl(
754                ra::Generics::None,
755                None,
756                self_ty,
757                vec![ra::Item::Function(constructor_fn)],
758            ));
759        }
760
761        Ok(items)
762    }
763
764    fn iter_struct_members<'mem, 'name>(
765        &'name self,
766        struct_handle: Handle<naga::Type>,
767        members: &'mem [naga::StructMember],
768    ) -> impl Iterator<Item = (&'name str, &'mem naga::StructMember)> {
769        members.iter().enumerate().map(move |(index, member)| {
770            let name =
771                &*self.names[&NameKey::StructMember(struct_handle, index.try_into().unwrap())];
772            (name, member)
773        })
774    }
775
776    fn translate_block(
777        &mut self,
778        module: &Module,
779        stmts: &[naga::Statement],
780        func_ctx: &back::FunctionCtx<'_>,
781    ) -> Result<ra::Block, Error> {
782        Ok(ra::Block(
783            self.translate_statements(module, stmts, func_ctx)?,
784            None,
785        ))
786    }
787
788    fn translate_statements(
789        &mut self,
790        module: &Module,
791        stmts: &[naga::Statement],
792        func_ctx: &back::FunctionCtx<'_>,
793    ) -> Result<Vec<ra::Statement>, Error> {
794        let mut output: Vec<ra::Statement> = Vec::with_capacity(stmts.len());
795        for s in stmts {
796            output.append(&mut self.translate_statement(module, s, func_ctx)?);
797        }
798        Ok(output)
799    }
800
801    fn translate_statement(
802        &mut self,
803        module: &Module,
804        stmt: &naga::Statement,
805        func_ctx: &back::FunctionCtx<'_>,
806    ) -> Result<Vec<ra::Statement>, Error> {
807        use naga::{Expression, Statement};
808
809        let expr_ctx = &ExpressionCtx::Function {
810            module,
811            func_ctx,
812            //module_info: info,
813        };
814
815        Ok(match *stmt {
816            Statement::Emit(ref range) => {
817                let mut output = Vec::new();
818                for handle in range.clone() {
819                    let expr_info = &func_ctx.info[handle];
820                    // TODO: this naming logic originated as a copy from the WGSL backend and it is
821                    // unclear what the rationale is (original comments were unclear).
822                    let expr_name = if let Some(name) = func_ctx.named_expressions.get(&handle) {
823                        Some(self.namer.call(name))
824                    } else {
825                        let expr = &func_ctx.expressions[handle];
826                        let min_ref_count = expr.bake_ref_count();
827                        // Forcefully creating baking expressions in some cases to help with readability
828                        let required_baking_expr = matches!(
829                            *expr,
830                            Expression::ImageLoad { .. }
831                                | Expression::ImageQuery { .. }
832                                | Expression::ImageSample { .. }
833                        );
834                        if min_ref_count <= expr_info.ref_count || required_baking_expr {
835                            Some(Gensym(handle).to_string())
836                        } else {
837                            None
838                        }
839                    };
840
841                    if let Some(name) = expr_name {
842                        output.push(self.let_ast(
843                            module,
844                            handle,
845                            func_ctx,
846                            name.clone(),
847                            self.translate_expr(handle, expr_ctx)?,
848                        )?);
849                        self.named_expressions.insert(handle, name);
850                    }
851                }
852                output
853            }
854            Statement::If {
855                condition,
856                ref accept,
857                ref reject,
858            } => {
859                vec![ra::Statement::If(
860                    Box::new(ra::Expr::call_rt(
861                        ra::RtItem::ScalarIntoBranchCondition,
862                        [self.translate_expr(condition, expr_ctx)?],
863                    )),
864                    self.translate_block(module, accept, func_ctx)?,
865                    self.translate_block(module, reject, func_ctx)?,
866                )]
867            }
868            Statement::Return { value } => {
869                vec![ra::Statement::Return(
870                    value
871                        .map(|v| self.translate_expr(v, expr_ctx))
872                        .transpose()?,
873                )]
874            }
875            Statement::Kill => vec![ra::Statement::Expr(ra::Expr::call_rt(
876                ra::RtItem::DiscardFn,
877                [],
878            ))],
879            Statement::Store { pointer, value } => {
880                vec![self.translate_store_statement(expr_ctx, pointer, value)?]
881            }
882            Statement::Call {
883                function,
884                ref arguments,
885                result,
886            } => {
887                let func_name = &self.names[&NameKey::Function(function)];
888                let rust_func_name = format!("{FN_INTERNAL_TYPES_PREFIX}{func_name}");
889                let rust_args = self.translate_exprs(expr_ctx, arguments.iter().copied())?;
890
891                let translated_call_expr = ra::Expr::call_maybe_self(
892                    self.config.functions_are_methods(),
893                    Cow::Owned(rust_func_name),
894                    rust_args,
895                );
896
897                // If the result is used, give it a name (`let _e10 = `).
898                if let Some(result_expr) = result {
899                    let name = Gensym(result_expr).to_string();
900                    self.named_expressions.insert(result_expr, name.clone());
901                    vec![self.let_ast(module, result_expr, func_ctx, name, translated_call_expr)?]
902                } else {
903                    vec![ra::Statement::Expr(translated_call_expr)]
904                }
905            }
906            Statement::Atomic { .. } => vec![self.unimplemented_stmt("atomic operations")?],
907            Statement::ImageAtomic { .. } => {
908                vec![self.unimplemented_stmt("atomic texture operations")?]
909            }
910            Statement::WorkGroupUniformLoad { .. } => {
911                todo!("Statement::WorkGroupUniformLoad");
912            }
913            Statement::ImageStore { .. } => vec![self.unimplemented_stmt("textureStore")?],
914            Statement::Block(ref block) => {
915                vec![ra::Statement::Block(
916                    None, // no label
917                    self.translate_block(module, block, func_ctx)?,
918                )]
919            }
920            Statement::Switch {
921                selector,
922                ref cases,
923            } => {
924                // Generate each arm, collapsing empty fall-through into a single arm.
925                let mut arms: Vec<ra::Arm> = Vec::with_capacity(cases.len());
926                let mut previous_case_was_fall_through = false;
927                for case in cases {
928                    let &naga::SwitchCase {
929                        value,
930                        ref body,
931                        fall_through,
932                    } = case;
933                    if fall_through && !body.is_empty() {
934                        // TODO
935                        return Ok(vec![self.unimplemented_stmt(
936                            "switch case with statements and fall-through",
937                        )?]);
938                    }
939
940                    let pattern = match value {
941                        naga::SwitchValue::I32(value) => ra::Pattern::LitI32(value),
942                        naga::SwitchValue::U32(value) => ra::Pattern::LitU32(value),
943                        naga::SwitchValue::Default => ra::Pattern::Wildcard,
944                    };
945
946                    let translated_body = self.translate_block(module, body, func_ctx)?;
947
948                    if previous_case_was_fall_through {
949                        let existing_arm = arms.last_mut().unwrap();
950                        existing_arm.pattern_alternatives.push(pattern);
951                        existing_arm.body.0.extend(translated_body.0);
952                    } else {
953                        arms.push(ra::Arm {
954                            pattern_alternatives: vec![pattern],
955                            body: translated_body,
956                        });
957                    }
958
959                    previous_case_was_fall_through = case.fall_through;
960                }
961
962                vec![ra::Statement::Match(
963                    Box::new(ra::Expr::call_rt(
964                        ra::RtItem::ScalarIntoInner,
965                        [self.translate_expr(selector, expr_ctx)?],
966                    )),
967                    arms,
968                )]
969            }
970            Statement::Loop {
971                ref body,
972                ref continuing,
973                break_if,
974            } => {
975                let mut rust_loop_body = Vec::with_capacity(3);
976
977                rust_loop_body.push(ra::Statement::Block(
978                    Some("naga_continue"),
979                    self.translate_block(module, body, func_ctx)?,
980                ));
981
982                // continuing block is the target of a Naga "continue", so it is outside the
983                // labeled block `'naga_continue: { ...body... }`.
984                rust_loop_body
985                    .append(&mut self.translate_statements(module, continuing, func_ctx)?);
986
987                if let Some(break_if) = break_if {
988                    rust_loop_body.push(ra::Statement::If(
989                        Box::new(ra::Expr::call_rt(
990                            ra::RtItem::ScalarIntoBranchCondition,
991                            [self.translate_expr(break_if, expr_ctx)?],
992                        )),
993                        ra::Block(vec![ra::Statement::Break(Some("naga_break"))], None),
994                        ra::Block(vec![], None),
995                    ));
996                }
997
998                vec![ra::Statement::Loop(
999                    "naga_break",
1000                    ra::Block(rust_loop_body, None),
1001                )]
1002            }
1003            Statement::Break => vec![ra::Statement::Break(Some("naga_break"))],
1004            Statement::Continue => vec![ra::Statement::Break(Some("naga_continue"))],
1005
1006            Statement::ControlBarrier(_) | Statement::MemoryBarrier(_) => {
1007                vec![self.unimplemented_stmt("barriers")?]
1008            }
1009            Statement::RayQuery { .. } | Statement::RayPipelineFunction(_) => {
1010                vec![self.unimplemented_stmt("raytracing")?]
1011            }
1012            Statement::SubgroupBallot { .. }
1013            | Statement::SubgroupCollectiveOperation { .. }
1014            | Statement::SubgroupGather { .. } => {
1015                vec![self.unimplemented_stmt("workgroup operations")?]
1016            }
1017            Statement::CooperativeStore { .. } => {
1018                vec![self.unimplemented_stmt("cooperative store")?]
1019            }
1020        })
1021    }
1022
1023    /// Translate a statement which assigns `value_expr` to `*pointer`.
1024    ///
1025    /// This is a helper for [`Self::translate_statement()`], broken out because not all pointers
1026    /// will correspond to single Rust places of the correct type; sometimes we need to use setter
1027    /// functions, so this becomes potentially very complex.
1028    fn translate_store_statement(
1029        &mut self,
1030        expr_ctx: &ExpressionCtx<'_>,
1031        pointer: Handle<Expression>,
1032        value_expr: Handle<Expression>,
1033    ) -> Result<ra::Statement, Error> {
1034        let pointer_type: &TypeInner = expr_ctx.resolve_type(pointer);
1035        let pointer_base_type = pointer_type
1036            .pointer_base_type()
1037            .expect("Store statement’s pointer's type not a pointer type");
1038
1039        // Note: `pointer_expr` is an expression that *in the Naga IR* has a pointer type,
1040        // but does not necessarily translate to a Rust pointer.
1041        let pointer_expr = &expr_ctx.expressions()[pointer];
1042
1043        if let TypeInner::Atomic(_) = pointer_base_type.inner_with(expr_ctx.types()) {
1044            // Atomic operations are currently unsupported.
1045            // When they *are* supported, they will be distinct because per Rust mutability rules,
1046            // we don’t need to obtain a mutable place, so it will suffice to just evaluate the
1047            // pointer expression and call an atomic operation function on it.
1048            return self.unimplemented_stmt("atomic operations");
1049        }
1050
1051        if let Expression::AccessIndex { base, index } = *pointer_expr {
1052            let access_base_type = expr_ctx.resolve_type(base);
1053            let access_pointer_base_type = access_base_type
1054                .pointer_base_type()
1055                .expect("Store statement’s access expression's base type not a pointer type");
1056
1057            // Decide whether to use an accessor function instead of an assignment.
1058            if let TypeInner::Vector { .. } = access_pointer_base_type.inner_with(expr_ctx.types())
1059            {
1060                return Ok(ra::Statement::Expr(ra::Expr::Method(
1061                    Box::new(self.expr_ast_with_indirection(base, expr_ctx, Indirection::Place)?),
1062                    Cow::Owned(format!("set_{}", back::COMPONENTS[index as usize])),
1063                    vec![self.translate_expr(value_expr, expr_ctx)?],
1064                )));
1065            }
1066        }
1067
1068        // Fallthrough: Use Rust assignment.
1069
1070        // The fields of aggregates are (currently) translated as `TypeTranslation::RustScalar`.
1071        // Therefore, if we are storing to a member of a struct, we need to insert a conversion.
1072        // TODO: this should be factored out into a general function for converting
1073        // between TypeTranslations.
1074        let value_expr = match TypeTranslation::from(pointer_type.pointer_space().unwrap()) {
1075            TypeTranslation::RustScalar => ra::Expr::call_rt(
1076                ra::RtItem::ScalarIntoInner,
1077                [self.translate_expr(value_expr, expr_ctx)?],
1078            ),
1079            TypeTranslation::ShaderScalar | TypeTranslation::Simd => {
1080                // No unwrapping
1081                self.translate_expr(value_expr, expr_ctx)?
1082            }
1083        };
1084
1085        Ok(ra::Statement::Assign(
1086            self.expr_ast_with_indirection(pointer, expr_ctx, Indirection::Place)?,
1087            value_expr,
1088        ))
1089    }
1090
1091    /// Build a `let` statement. Helper for statement processing.
1092    fn let_ast(
1093        &self,
1094        module: &Module,
1095        handle: Handle<Expression>,
1096        func_ctx: &back::FunctionCtx<'_>,
1097        name: String,
1098        expr: ra::Expr,
1099    ) -> Result<ra::Statement, Error> {
1100        let rust_ty = if self.config.flags.contains(WriterFlags::EXPLICIT_TYPES) {
1101            Some(match func_ctx.info[handle].ty {
1102                proc::TypeResolution::Handle(ty_handle) => {
1103                    self.type_ast(module, ty_handle, TypeTranslation::Simd)?
1104                }
1105                proc::TypeResolution::Value(ref inner) => {
1106                    self.type_ast_inner(module, inner, TypeTranslation::Simd)?
1107                }
1108            })
1109        } else {
1110            None
1111        };
1112
1113        Ok(ra::Statement::Let(
1114            ra::Pattern::Binding(name),
1115            rust_ty,
1116            Some(expr),
1117        ))
1118    }
1119
1120    /// Translate `expr` to Rust.
1121    ///
1122    /// See `expr_ast_with_indirection` for details.
1123    fn translate_expr(
1124        &self,
1125        expr: Handle<Expression>,
1126        expr_ctx: &ExpressionCtx<'_>,
1127    ) -> Result<ra::Expr, Error> {
1128        self.expr_ast_with_indirection(expr, expr_ctx, Indirection::Ordinary)
1129    }
1130
1131    /// Translate multiple expressions.
1132    fn translate_exprs(
1133        &self,
1134        expr_ctx: &ExpressionCtx<'_>,
1135        exprs: impl IntoIterator<Item = Handle<Expression>>,
1136    ) -> Result<Vec<ra::Expr>, Error> {
1137        exprs
1138            .into_iter()
1139            .map(|expr| self.translate_expr(expr, expr_ctx))
1140            .collect()
1141    }
1142
1143    /// Translate `expr` to Rust with the requested indirection.
1144    ///
1145    /// The `requested` argument indicates how the produced Rust expression’s type should relate
1146    /// to the Naga type of the input expression. See [`Indirection`]’s documentation for details.
1147    fn expr_ast_with_indirection(
1148        &self,
1149        expr: Handle<Expression>,
1150        expr_ctx: &ExpressionCtx<'_>,
1151        requested: Indirection,
1152    ) -> Result<ra::Expr, Error> {
1153        // If the plain form of the expression is not what we need, emit the
1154        // operator necessary to correct that.
1155        let (plain_expr, plain): (ra::Expr, Indirection) =
1156            self.expr_ast_plain_form(expr, expr_ctx)?;
1157        Ok(match (requested, plain) {
1158            // The plain form expression will be a place.
1159            // Convert it to a reference to match the Naga pointer type.
1160            //
1161            // Or, the plain form expression will be a Rust value that we want to take by
1162            // reference (currently, a texture handle; in the future, buffers too).
1163            //
1164            // TODO: We need to choose which borrow operator to use.
1165            (Indirection::Ordinary, Indirection::Place)
1166            | (Indirection::Ref, Indirection::Ordinary) => {
1167                ra::Expr::Borrow(ra::PtrKind::Shared(None), Box::new(plain_expr))
1168            }
1169
1170            // The plain form expression will be a pointer, but the caller wants its pointee.
1171            // Insert a dereference operator.
1172            (Indirection::Place, Indirection::Ordinary)
1173            | (Indirection::Ordinary, Indirection::Ref) => ra::Expr::Deref(Box::new(plain_expr)),
1174
1175            // Matches.
1176            (Indirection::Place, Indirection::Place)
1177            | (Indirection::Ordinary, Indirection::Ordinary)
1178            | (Indirection::Ref, Indirection::Ref) => plain_expr,
1179
1180            (Indirection::Ref, Indirection::Place) | (Indirection::Place, Indirection::Ref) => {
1181                unreachable!("multi-level ref/deref")
1182            }
1183        })
1184    }
1185
1186    /// Translates to the 'plain form' of `expr`.
1187    ///
1188    /// An expression's 'plain form' is the shortest rendition of that
1189    /// expression's meaning into Rust, lacking `&` or `*` operators.
1190    /// Therefore, it may not have a type which matches the Naga IR expression
1191    /// type (because Naga does not have places, only pointers and non-pointer values).
1192    ///
1193    /// When it does not match, this is indicated by the second part of the return value.
1194    /// It is the caller’s responsibility to adapt as needed, usually via
1195    /// [`Self::expr_ast_with_indirection()`].
1196    ///
1197    /// The return type of the written expression always follows [`TypeTranslation::Simd`] form.
1198    /// (We will need to refine that later.)
1199    fn expr_ast_plain_form(
1200        &self,
1201        expr: Handle<Expression>,
1202        expr_ctx: &ExpressionCtx<'_>,
1203    ) -> Result<(ra::Expr, Indirection), Error> {
1204        if let Some(name) = self.named_expressions.get(&expr) {
1205            return Ok((ra::Expr::Ident(name.clone()), Indirection::Ordinary));
1206        }
1207
1208        let expression = &expr_ctx.expressions()[expr];
1209        let module = expr_ctx.module();
1210
1211        // How the indirection of the expression we produce relates to the indirection expected
1212        // in the Naga IR. In particular, the below `match` will reset this to `Indirection::Place`
1213        // whenever we produce a Rust expression that is a Rust place, in a case where the Naga IR
1214        // is expecting the expression to yield a pointer value.
1215        let mut indirection = Indirection::Ordinary;
1216
1217        let rust_expr = match *expression {
1218            Expression::Literal(literal) => {
1219                let rust_literal = match literal {
1220                    // TODO: Should we use the `half` library for f16 support at run time
1221                    // instead of only allowing it as a Rust unstable feature?
1222                    naga::Literal::F16(value) => ra::Expr::LitF16(value),
1223                    naga::Literal::F32(value) => ra::Expr::LitF32(value),
1224                    naga::Literal::U32(value) => ra::Expr::LitU32(value),
1225                    naga::Literal::I32(value) => ra::Expr::LitI32(value),
1226                    naga::Literal::Bool(value) => ra::Expr::LitBool(value),
1227                    naga::Literal::F64(value) => ra::Expr::LitF64(value),
1228                    naga::Literal::I64(value) => ra::Expr::LitI64(value),
1229                    naga::Literal::U64(value) => ra::Expr::LitU64(value),
1230                    naga::Literal::AbstractInt(_) | naga::Literal::AbstractFloat(_) => {
1231                        unreachable!(
1232                            "abstract types should not appear in IR presented to backends"
1233                        );
1234                    }
1235                };
1236                ra::Expr::call_rt(ra::RtItem::Scalar, [rust_literal])
1237            }
1238            Expression::Constant(handle) => {
1239                let constant = &module.constants[handle];
1240                if constant.name.is_some() {
1241                    ra::Expr::Ident(self.names[&NameKey::Constant(handle)].clone())
1242                } else {
1243                    self.translate_expr(constant.init, expr_ctx)?
1244                }
1245            }
1246            Expression::ZeroValue(_ty) => {
1247                // TODO: need to translate type
1248                ra::Expr::call_rt(ra::RtItem::ZeroFn, [])
1249            }
1250            Expression::Compose { ty, ref components } => {
1251                self.constructor_expression_ast(ty, components, expr_ctx)?
1252            }
1253            Expression::Splat { size, value } => ra::Expr::call_rt(
1254                ra::RtItem::SplatFromScalar(size),
1255                [self.translate_expr(value, expr_ctx)?],
1256            ),
1257            Expression::Override(_) => unreachable!(),
1258            Expression::FunctionArgument(pos) => {
1259                let name_key = expr_ctx.expect_func_ctx().argument_key(pos);
1260                let name = &self.names[&name_key];
1261                ra::Expr::Ident(name.clone())
1262            }
1263            Expression::Binary { op, left, right } => match BinOpClassified::from(op) {
1264                BinOpClassified::Vectorizable(_) => ra::Expr::BinOp(
1265                    Box::new(self.translate_expr(left, expr_ctx)?),
1266                    op,
1267                    Box::new(self.translate_expr(right, expr_ctx)?),
1268                ),
1269                BinOpClassified::ScalarBool(bop) => ra::Expr::Method(
1270                    Box::new(self.translate_expr(left, expr_ctx)?),
1271                    Cow::Borrowed(bop.to_vector_method()),
1272                    vec![self.translate_expr(right, expr_ctx)?],
1273                ),
1274                BinOpClassified::ShortCircuit(_bop) => {
1275                    // The ".0"s are for unwrapping the input `Scalar`s
1276                    // TODO: when we support SIMD this will need to change completely
1277                    ra::Expr::call_rt(
1278                        ra::RtItem::Scalar,
1279                        vec![ra::Expr::BinOp(
1280                            Box::new(ra::Expr::TupleField(
1281                                Box::new(self.translate_expr(left, expr_ctx)?),
1282                                0,
1283                            )),
1284                            op,
1285                            Box::new(ra::Expr::TupleField(
1286                                Box::new(self.translate_expr(right, expr_ctx)?),
1287                                0,
1288                            )),
1289                        )],
1290                    )
1291                }
1292            },
1293            Expression::Access { base, index } => {
1294                // In the Naga IR, `Access` and `AccessIndex` pass through the pointer-ness of
1295                // their `base` value, but in Rust, the result of `container[index]` is always
1296                // a place, so we need to report `Indirection::Place` to counteract that.
1297                let base_ty = expr_ctx.resolve_type(base);
1298                if let TypeInner::Pointer { .. } | TypeInner::ValuePointer { .. } = base_ty {
1299                    indirection = Indirection::Place;
1300                }
1301
1302                // TODO: when we support SIMD, this will need to change to not be a single indexing
1303                // expression but a scatter/gather operation which isn’t a Rust place.
1304                ra::Expr::Index(
1305                    Box::new(self.expr_ast_with_indirection(base, expr_ctx, indirection)?),
1306                    Box::new(ra::Expr::call_rt(
1307                        ra::RtItem::ScalarIntoArrayIndex,
1308                        [self.translate_expr(index, expr_ctx)?],
1309                    )),
1310                )
1311            }
1312            Expression::AccessIndex { base, index } => {
1313                let result_ty = expr_ctx.resolve_type(expr);
1314
1315                let base_ty_res = &expr_ctx.expect_func_ctx().info[base].ty;
1316                let mut base_ty_resolved = base_ty_res.inner_with(&module.types);
1317
1318                // In the Naga IR, `Access` and `AccessIndex` pass through the pointer-ness of
1319                // their `base` value, but in Rust, the result of `container[index]` is always
1320                // a place, so we need to report `Indirection::Place` to counteract that.
1321                if let TypeInner::Pointer { .. } | TypeInner::ValuePointer { .. } = base_ty_resolved
1322                {
1323                    indirection = Indirection::Place;
1324                }
1325
1326                // Find the type of container we are accessing, looking past the pointer if there
1327                // is one.
1328                let (base_container_ty_handle, base_is_pointer) = match *base_ty_resolved {
1329                    TypeInner::Pointer { base, space: _ } => {
1330                        base_ty_resolved = &module.types[base].inner;
1331                        (Some(base), true)
1332                    }
1333                    _ => (base_ty_res.handle(), false),
1334                };
1335
1336                match *base_ty_resolved {
1337                    TypeInner::Vector { .. } => ra::Expr::Method(
1338                        Box::new(self.expr_ast_with_indirection(base, expr_ctx, indirection)?),
1339                        Cow::Borrowed(["x", "y", "z", "w"][index as usize]),
1340                        vec![],
1341                    ),
1342                    TypeInner::Matrix { .. }
1343                    | TypeInner::Array { .. }
1344                    | TypeInner::BindingArray { .. }
1345                    | TypeInner::ValuePointer { .. } => ra::Expr::Index(
1346                        Box::new(self.expr_ast_with_indirection(base, expr_ctx, indirection)?),
1347                        Box::new(ra::Expr::LitUsize(index)),
1348                    ),
1349
1350                    TypeInner::Struct { .. } => {
1351                        // TODO: This is a horrible "make the tests pass" kludge which should be
1352                        // replaced with more general implementation of conversion between different
1353                        // `TypeTranslation`s (struct contents are `TypeTranslation::RustScalar`
1354                        // and our result needs to be `TypeTranslation::Simd`).
1355                        let element_type_is_scalar = if base_is_pointer {
1356                            // In Naga IR, if the base is a pointer type then so is the result.
1357                            result_ty.pointer_base_type().is_some_and(|res| {
1358                                matches!(res.inner_with(&module.types), TypeInner::Scalar(_))
1359                            })
1360                        } else {
1361                            matches!(result_ty, TypeInner::Scalar(_))
1362                        };
1363                        // This will never panic in case the type is a `Struct`; this is not so
1364                        // for other types, so we can only check while inside this match arm
1365                        let ty = base_container_ty_handle.unwrap();
1366                        if element_type_is_scalar {
1367                            ra::Expr::call_rt(
1368                                ra::RtItem::Scalar,
1369                                [ra::Expr::NamedField(
1370                                    Box::new(self.expr_ast_with_indirection(
1371                                        base,
1372                                        expr_ctx,
1373                                        indirection,
1374                                    )?),
1375                                    self.names[&NameKey::StructMember(ty, index)].clone(),
1376                                )],
1377                            )
1378                        } else {
1379                            ra::Expr::NamedField(
1380                                Box::new(self.expr_ast_with_indirection(
1381                                    base,
1382                                    expr_ctx,
1383                                    indirection,
1384                                )?),
1385                                self.names[&NameKey::StructMember(ty, index)].clone(),
1386                            )
1387                        }
1388                    }
1389                    ref other => unreachable!("cannot index into a {other:?}"),
1390                }
1391            }
1392            Expression::ImageSample { .. } => {
1393                self.unimplemented_expr("texture sampling (other than textureLoad)")?
1394            }
1395            Expression::ImageQuery { image, query } => {
1396                let image_expr =
1397                    self.expr_ast_with_indirection(image, expr_ctx, Indirection::Ref)?;
1398                match query {
1399                    naga::ImageQuery::Size { level } => ra::Expr::call_rt(
1400                        ra::RtItem::TextureDimensions,
1401                        [
1402                            image_expr,
1403                            if let Some(level) = level {
1404                                ra::Expr::call_rt(
1405                                    ra::RtItem::ScalarIntoInner,
1406                                    [self.translate_expr(level, expr_ctx)?],
1407                                )
1408                            } else {
1409                                ra::Expr::LitI32(0)
1410                            },
1411                        ],
1412                    ),
1413                    naga::ImageQuery::NumLevels => ra::Expr::call_rt(
1414                        ra::RtItem::TextureNzToScalar,
1415                        [ra::Expr::call_rt(
1416                            ra::RtItem::TextureNumLevels,
1417                            [image_expr],
1418                        )],
1419                    ),
1420                    naga::ImageQuery::NumLayers => ra::Expr::call_rt(
1421                        ra::RtItem::TextureNzToScalar,
1422                        [ra::Expr::call_rt(
1423                            ra::RtItem::TextureNumLayers,
1424                            [image_expr],
1425                        )],
1426                    ),
1427                    naga::ImageQuery::NumSamples => ra::Expr::call_rt(
1428                        ra::RtItem::TextureNzToScalar,
1429                        [ra::Expr::call_rt(
1430                            ra::RtItem::TextureNumSamples,
1431                            [image_expr],
1432                        )],
1433                    ),
1434                }
1435            }
1436            Expression::ImageLoad {
1437                image,
1438                coordinate,
1439                array_index,
1440                sample,
1441                level,
1442            } => ra::Expr::call_rt(
1443                ra::RtItem::TextureLoad,
1444                [
1445                    self.expr_ast_with_indirection(image, expr_ctx, Indirection::Ref)?,
1446                    self.translate_expr(coordinate, expr_ctx)?,
1447                    if let Some(array_index) = array_index {
1448                        ra::Expr::call_rt(
1449                            ra::RtItem::ScalarIntoInner,
1450                            [self.translate_expr(array_index, expr_ctx)?],
1451                        )
1452                    } else {
1453                        ra::Expr::LitI32(0)
1454                    },
1455                    if let Some(sample) = sample {
1456                        ra::Expr::call_rt(
1457                            ra::RtItem::ScalarIntoInner,
1458                            [self.translate_expr(sample, expr_ctx)?],
1459                        )
1460                    } else {
1461                        ra::Expr::LitI32(0)
1462                    },
1463                    if let Some(level) = level {
1464                        ra::Expr::call_rt(
1465                            ra::RtItem::ScalarIntoInner,
1466                            [self.translate_expr(level, expr_ctx)?],
1467                        )
1468                    } else {
1469                        ra::Expr::LitI32(0)
1470                    },
1471                ],
1472            ),
1473            Expression::GlobalVariable(handle) => {
1474                let global = &module.global_variables[handle];
1475
1476                // The plain form of `GlobalVariable(g)` is `self.g`, which is a
1477                // Rust place. However, globals in the `Handle` address space are immutable,
1478                // and `GlobalVariable` expressions for those produce the value directly,
1479                // not a pointer to it. Therefore, such expressions have `Indirection::Place`.
1480                // (Note that the exception for Handle is a fact about Naga IR, not this backend.)
1481                indirection = match global.space {
1482                    naga::AddressSpace::Handle => Indirection::Ordinary,
1483                    _ => Indirection::Place,
1484                };
1485
1486                ra::Expr::NamedField(
1487                    Box::new(self.config.global_field_access_expr(global)),
1488                    self.names[&NameKey::GlobalVariable(handle)].clone(),
1489                )
1490            }
1491
1492            Expression::As {
1493                expr,
1494                kind: to_kind,
1495                convert: to_width,
1496            } => {
1497                use naga::ScalarKind as Sk;
1498                use naga::TypeInner as Ti;
1499
1500                let input_type = expr_ctx.resolve_type(expr);
1501                let rust_expr = self.translate_expr(expr, expr_ctx)?;
1502
1503                match (input_type, to_kind, to_width) {
1504                    (
1505                        Ti::Vector { size: _, scalar: _ } | Ti::Scalar(_),
1506                        to_kind,
1507                        Some(to_width),
1508                    ) => ra::Expr::Method(
1509                        Box::new(rust_expr),
1510                        Cow::Borrowed(match (to_kind, to_width) {
1511                            (Sk::Sint, 4) => "cast_elem_as_i32",
1512                            (Sk::Sint, 8) => "cast_elem_as_i64",
1513                            (Sk::Uint, 4) => "cast_elem_as_u32",
1514                            (Sk::Uint, 8) => "cast_elem_as_u64",
1515                            (Sk::Float, 4) => "cast_elem_as_f32",
1516                            (Sk::Float, 8) => "cast_elem_as_f64",
1517                            _ => panic!(
1518                                "unimplemented cast of vector to kind {to_kind:?} width {to_width:?}"
1519                            ),
1520                        }),
1521                        vec![],
1522                    ),
1523                    _ => panic!(
1524                        "unimplemented cast {input_type:?} to kind {to_kind:?} width {to_width:?}"
1525                    ),
1526                }
1527            }
1528            Expression::Load { pointer } => {
1529                self.expr_ast_with_indirection(pointer, expr_ctx, Indirection::Place)?
1530            }
1531            Expression::LocalVariable(handle) => {
1532                // In Naga, a `LocalVariable(x)` expression produces a pointer,
1533                // but our plain form is a variable name `x`,
1534                // which means the caller must reference it if desired.
1535                indirection = Indirection::Place;
1536
1537                ra::Expr::Ident(self.names[&expr_ctx.expect_func_ctx().name_key(handle)].clone())
1538            }
1539            Expression::ArrayLength(expr) => ra::Expr::Method(
1540                Box::new(self.translate_expr(expr, expr_ctx)?),
1541                Cow::Borrowed("len"),
1542                vec![],
1543            ),
1544
1545            Expression::Math {
1546                fun,
1547                arg: first_arg,
1548                arg1,
1549                arg2,
1550                arg3,
1551            } => ra::Expr::Method(
1552                Box::new(self.translate_expr(first_arg, expr_ctx)?),
1553                Cow::Borrowed(conv::math_function_to_method(fun)),
1554                self.translate_exprs(
1555                    expr_ctx,
1556                    [arg1, arg2, arg3].into_iter().flatten(), // flatten options into nonexistence
1557                )?,
1558            ),
1559
1560            Expression::Swizzle {
1561                size,
1562                vector,
1563                pattern,
1564            } => {
1565                let swizzle_method = String::from_iter(
1566                    pattern[..size as usize]
1567                        .iter()
1568                        .map(|&component| back::COMPONENTS[component as usize]),
1569                );
1570                ra::Expr::Method(
1571                    Box::new(self.translate_expr(vector, expr_ctx)?),
1572                    Cow::Owned(swizzle_method),
1573                    vec![],
1574                )
1575            }
1576            Expression::Unary { op, expr } => {
1577                let ctor = match op {
1578                    naga::UnaryOperator::Negate => ra::Expr::Negate,
1579                    naga::UnaryOperator::LogicalNot => ra::Expr::Not,
1580                    naga::UnaryOperator::BitwiseNot => ra::Expr::Not,
1581                };
1582                ctor(Box::new(self.translate_expr(expr, expr_ctx)?))
1583            }
1584
1585            Expression::Select {
1586                condition,
1587                accept,
1588                reject,
1589            } => {
1590                // Calls {vector type}::select() method
1591                ra::Expr::Method(
1592                    Box::new(self.translate_expr(reject, expr_ctx)?),
1593                    Cow::Borrowed("select"),
1594                    vec![
1595                        self.translate_expr(accept, expr_ctx)?,
1596                        self.translate_expr(condition, expr_ctx)?,
1597                    ],
1598                )
1599            }
1600            Expression::Derivative { .. } => self.unimplemented_expr("derivatives")?,
1601            Expression::Relational { fun, argument } => {
1602                use naga::RelationalFunction as Rf;
1603
1604                match fun {
1605                    Rf::All => ra::Expr::Method(
1606                        Box::new(self.translate_expr(argument, expr_ctx)?),
1607                        Cow::Borrowed("all"),
1608                        vec![],
1609                    ),
1610                    Rf::Any => ra::Expr::Method(
1611                        Box::new(self.translate_expr(argument, expr_ctx)?),
1612                        Cow::Borrowed("any"),
1613                        vec![],
1614                    ),
1615                    Rf::IsNan => self.unimplemented_expr("IsNan")?,
1616                    Rf::IsInf => self.unimplemented_expr("IsInf")?,
1617                }
1618            }
1619            Expression::RayQueryGetIntersection { .. }
1620            | Expression::RayQueryVertexPositions { .. }
1621            | Expression::CooperativeLoad { .. }
1622            | Expression::CooperativeMultiplyAdd { .. } => unreachable!("unsupported feature"),
1623            //
1624            Expression::CallResult(_)
1625            | Expression::AtomicResult { .. }
1626            | Expression::RayQueryProceedResult
1627            | Expression::SubgroupBallotResult
1628            | Expression::SubgroupOperationResult { .. }
1629            | Expression::WorkGroupUniformLoadResult { .. } => {
1630                unreachable!("nothing to do here, since call expression already cached")
1631            }
1632        };
1633
1634        Ok((rust_expr, indirection))
1635    }
1636
1637    /// Translates [`Expression::Compose`].
1638    /// Examines the type to write an appropriate constructor or literal expression for it.
1639    ///
1640    /// We do not delegate to a library trait for this because the construction
1641    /// must be const-compatible.
1642    fn constructor_expression_ast(
1643        &self,
1644        ty: Handle<naga::Type>,
1645        components: &[Handle<Expression>],
1646        expr_ctx: &ExpressionCtx<'_>,
1647    ) -> Result<ra::Expr, Error> {
1648        use naga::VectorSize::{Bi, Quad, Tri};
1649
1650        let translated_components = components
1651            .iter()
1652            .map(|&component| self.translate_expr(component, expr_ctx))
1653            .collect::<Result<Vec<ra::Expr>, Error>>()?;
1654
1655        let ctor_name: &'static str = match expr_ctx.types()[ty].inner {
1656            TypeInner::Vector { size, scalar: _ } => {
1657                // Vectors may be constructed by a collection of scalars and vectors which in
1658                // total have the required component count.
1659
1660                let arg_sizes: ArrayVec<u8, 4> = components
1661                    .iter()
1662                    .map(|&component_expr| match *expr_ctx.resolve_type(component_expr) {
1663                        TypeInner::Scalar(_) => 1,
1664                        TypeInner::Vector { size, .. } => size as u8,
1665                        ref t => unreachable!(
1666                            "vector constructor argument should be a scalar or vector, not {t:?}"
1667                        ),
1668                    })
1669                    .collect();
1670
1671                match (size, &*arg_sizes) {
1672                    (Bi, [1, 1]) => "from_scalars",
1673                    (Bi, [2]) => "from",
1674                    (Tri, [1, 1, 1]) => "from_scalars",
1675                    (Tri, [1, 2]) => "new_12",
1676                    (Tri, [2, 1]) => "new_21",
1677                    (Quad, [1, 1, 1, 1]) => "from_scalars",
1678                    (Quad, [1, 1, 2]) => "new_112",
1679                    (Quad, [1, 2, 1]) => "new_121",
1680                    (Quad, [2, 1, 1]) => "new_211",
1681                    (Quad, [2, 2]) => "new_22",
1682                    (Quad, [1, 3]) => "new_13",
1683                    (Quad, [3, 1]) => "new_31",
1684                    (Quad, [4]) => "from",
1685                    _ => unreachable!("vector constructor given too many components {arg_sizes:?}"),
1686                }
1687            }
1688
1689            TypeInner::Array {
1690                base: _,
1691                size,
1692                stride: _,
1693            } => {
1694                assert!(matches!(size, naga::ArraySize::Constant(_)));
1695                return Ok(ra::Expr::Array(translated_components));
1696            }
1697
1698            // Fallback: Assume that a suitable `T::new()` associated function
1699            // exists.
1700            _ => "new",
1701        };
1702
1703        Ok(ra::Expr::Call(
1704            Box::new(ra::Expr::QualifiedPath(
1705                self.type_ast(expr_ctx.module(), ty, TypeTranslation::Simd)?,
1706                ctor_name,
1707            )),
1708            translated_components,
1709        ))
1710    }
1711
1712    /// Translate the gtiven `type_handle` to [`ra::Type`] format.
1713    ///
1714    /// The form a type takes depends on the address space in which the value of that type lives.
1715    pub(super) fn type_ast(
1716        &self,
1717        module: &Module,
1718        type_handle: Handle<naga::Type>,
1719        type_translation: TypeTranslation,
1720    ) -> Result<ra::Type, Error> {
1721        match module.types[type_handle].inner {
1722            TypeInner::Struct { .. } => Ok(ra::Type::User(
1723                self.names[&NameKey::Type(type_handle)].clone(),
1724                ra::Generics::None,
1725            )),
1726            ref other => self.type_ast_inner(module, other, type_translation),
1727        }
1728    }
1729
1730    fn type_ast_inner(
1731        &self,
1732        module: &Module,
1733        inner: &TypeInner,
1734        type_translation: TypeTranslation,
1735    ) -> Result<ra::Type, Error> {
1736        Ok(match *inner {
1737            TypeInner::Vector { size, scalar } => {
1738                ra::Type::RtGen(ra::RtGen::vector(size), scalar.try_into()?)
1739            }
1740            TypeInner::Matrix {
1741                columns,
1742                rows,
1743                scalar,
1744            } => ra::Type::RtGen(ra::RtGen::matrix(columns, rows), scalar.try_into()?),
1745            TypeInner::Scalar(scalar) => match type_translation {
1746                TypeTranslation::RustScalar => ra::Type::BareScalar(scalar.try_into()?),
1747                TypeTranslation::ShaderScalar | TypeTranslation::Simd => {
1748                    ra::Type::RtGen(ra::RtGen::Scalar, scalar.try_into()?)
1749                }
1750            },
1751
1752            TypeInner::Sampler { comparison: false } => ra::Type::Sampler,
1753            TypeInner::Sampler { comparison: true } => ra::Type::SamplerComparison,
1754            TypeInner::Image {
1755                dim,
1756                arrayed,
1757                class,
1758            } => {
1759                let (scalar_type, multisampled): (ra::Scalar, bool) = match class {
1760                    naga::ImageClass::Sampled { kind, multi } => (
1761                        match kind {
1762                            naga::ScalarKind::Sint => ra::Scalar::I32,
1763                            naga::ScalarKind::Uint => ra::Scalar::U32,
1764                            naga::ScalarKind::Float => ra::Scalar::F32,
1765                            naga::ScalarKind::Bool => ra::Scalar::Bool,
1766                            naga::ScalarKind::AbstractInt | naga::ScalarKind::AbstractFloat => {
1767                                unreachable!(
1768                                    "abstract types should not appear in IR presented to backends"
1769                                )
1770                            }
1771                        },
1772                        multi,
1773                    ),
1774                    naga::ImageClass::Depth { multi } => (ra::Scalar::F32, multi),
1775                    naga::ImageClass::External => (ra::Scalar::F32, false),
1776                    naga::ImageClass::Storage { .. } => {
1777                        return Err(Error::Unimplemented("storage texture types".into()));
1778                    }
1779                };
1780                ra::Type::Texture {
1781                    dim,
1782                    multisampled,
1783                    arrayed,
1784                    storage_type: Box::new(ra::Type::Ptr(
1785                        ra::PtrKind::Shared(Some("g")),
1786                        // TODO: we want to support fully statically dispatched texture access,
1787                        // but that will require more work to either:
1788                        //
1789                        // * allow the user to specify a concrete type for the texture storage,
1790                        // * make the resource struct generic,
1791                        // * or allow the user to provide their own resource struct (possibly
1792                        //   corresponding to a single bind group) and adapt to its types.
1793                        //
1794                        // `dyn` is a placeholder for further work, and it’s not great to go through
1795                        // a dynamic dispatch for every texel load.
1796                        Box::new(ra::Type::DynTextureRead {
1797                            dim,
1798                            scalar: scalar_type,
1799                        }),
1800                    )),
1801                }
1802            }
1803            TypeInner::Atomic(scalar) => ra::Type::Atomic(scalar.try_into()?),
1804            TypeInner::Array {
1805                base,
1806                size,
1807                stride: _,
1808            } => {
1809                let element_type = Box::new(self.type_ast(module, base, type_translation)?);
1810                match size {
1811                    naga::ArraySize::Constant(size) => ra::Type::Array(element_type, size.get()),
1812                    naga::ArraySize::Pending(_handle) => {
1813                        return Err(Error::Unimplemented("override array size".into()));
1814                    }
1815                    naga::ArraySize::Dynamic => ra::Type::Slice(element_type),
1816                }
1817            }
1818            TypeInner::BindingArray { .. } => {
1819                return Err(Error::Unimplemented("binding array".into()));
1820            }
1821            TypeInner::Pointer {
1822                base,
1823                space: pointee_space,
1824            } => ra::Type::Ptr(
1825                if self.config.flags.contains(WriterFlags::RAW_POINTERS) {
1826                    ra::PtrKind::RawMut
1827                } else {
1828                    ra::PtrKind::Exclusive(None)
1829                },
1830                Box::new(self.type_ast(module, base, TypeTranslation::from(pointee_space))?),
1831            ),
1832            TypeInner::ValuePointer {
1833                size: _,
1834                scalar: _,
1835                space: _,
1836            } => todo!(),
1837            TypeInner::Struct { .. } => {
1838                unreachable!("should only see a struct by name");
1839            }
1840            TypeInner::AccelerationStructure { .. } => {
1841                return Err(Error::Unimplemented("type AccelerationStructure".into()));
1842            }
1843            TypeInner::RayQuery { .. } => {
1844                return Err(Error::Unimplemented("type RayQuery".into()));
1845            }
1846            TypeInner::CooperativeMatrix { .. } => {
1847                return Err(Error::Unimplemented("type CooperativeMatrix".into()));
1848            }
1849        })
1850    }
1851
1852    fn translate_global_variable_to_struct_field(
1853        &self,
1854        module: &Module,
1855        global: &naga::GlobalVariable,
1856        handle: Handle<naga::GlobalVariable>,
1857    ) -> Result<ra::Field, Error> {
1858        let &naga::GlobalVariable {
1859            name: _, // renamed instead
1860            space,
1861            binding: _, // don't (yet) expose numeric binding locations
1862            ty,
1863            init: _,
1864            memory_decorations: _, // TODO: probably need to do things with this
1865        } = global;
1866
1867        // TODO: reenable this
1868        // // Note bindings.
1869        // // These are not emitted as attributes because Rust does not allow macro attributes to be
1870        // // placed on struct fields.
1871        // if let Some(naga::ResourceBinding { group, binding }) = global.binding {
1872        //     writeln!(out, "{INDENT}// group({group}) binding({binding})")?;
1873        // }
1874
1875        Ok(ra::Field {
1876            attributes: if let Some(naga::ResourceBinding { group, binding }) = global.binding {
1877                vec![ra::Attribute::Doc(format!(
1878                    "group({group}) binding({binding})"
1879                ))]
1880            } else {
1881                vec![]
1882            },
1883            visibility: self.visibility(),
1884            name: self.names[&NameKey::GlobalVariable(handle)].clone(),
1885            ty: self.type_ast(module, ty, TypeTranslation::from(space))?,
1886        })
1887    }
1888    fn global_variable_as_field_initializer_expr(
1889        &self,
1890        module: &Module,
1891        info: &ModuleInfo,
1892        global: &naga::GlobalVariable,
1893    ) -> Result<ra::Expr, Error> {
1894        if let Some(init) = global.init {
1895            self.translate_expr(
1896                init,
1897                &ExpressionCtx::Global {
1898                    expressions: &module.global_expressions,
1899                    module,
1900                    module_info: info,
1901                },
1902            )
1903        } else {
1904            Ok(ra::Expr::call_rt(ra::RtItem::ZeroFn, []))
1905        }
1906    }
1907
1908    /// Translates a [`naga::Constant`] to a Rust `const` item.
1909    fn translate_global_constant(
1910        &self,
1911        module: &Module,
1912        info: &ModuleInfo,
1913        handle: Handle<naga::Constant>,
1914    ) -> Result<ra::ConstItem, Error> {
1915        let name = self.names[&NameKey::Constant(handle)].clone();
1916        let visibility = self.visibility();
1917        let ty = self.type_ast(
1918            module,
1919            module.constants[handle].ty,
1920            TypeTranslation::ShaderScalar,
1921        )?;
1922        let init = module.constants[handle].init;
1923
1924        Ok(ra::ConstItem {
1925            attributes: vec![ra::Attribute::AllowNonUpperCaseGlobals],
1926            visibility,
1927            name,
1928            ty,
1929            value: self.translate_expr(
1930                init,
1931                &ExpressionCtx::Global {
1932                    expressions: &module.global_expressions,
1933                    module,
1934                    module_info: info,
1935                },
1936            )?,
1937        })
1938    }
1939
1940    /// For a feature that naga-rust does not support, either return an immediate conversion error, or emit
1941    /// an expression that panics when executed.
1942    fn unimplemented_expr(&self, unimplemented_feature: &'static str) -> Result<ra::Expr, Error> {
1943        assert!(
1944            !unimplemented_feature.contains(['{', '}']),
1945            "escaping format strings not supported yet"
1946        );
1947
1948        if self.config.flags.contains(WriterFlags::ALLOW_UNIMPLEMENTED) {
1949            Ok(ra::Expr::FormatLikeMacro(
1950                // TODO: path needs to be qualified
1951                "unimplemented",
1952                alloc::format!(
1953                    "this shader function contains a feature which \
1954                    cannot yet be translated to Rust, {unimplemented_feature}"
1955                ),
1956            ))
1957        } else {
1958            Err(Error::Unimplemented(unimplemented_feature.into()))
1959        }
1960    }
1961
1962    fn unimplemented_stmt(
1963        &self,
1964        unimplemented_feature: &'static str,
1965    ) -> Result<ra::Statement, Error> {
1966        Ok(ra::Statement::Expr(
1967            self.unimplemented_expr(unimplemented_feature)?,
1968        ))
1969    }
1970
1971    fn visibility(&self) -> ra::Visibility {
1972        if self.config.flags.contains(WriterFlags::PUBLIC) {
1973            ra::Visibility::Public
1974        } else {
1975            ra::Visibility::Private
1976        }
1977    }
1978}