Skip to main content

wesl/pass/
retarget_idents.rs

1use std::{
2    collections::{HashMap, hash_map::Entry},
3    rc::Rc,
4};
5
6use crate::{
7    Resolver, SyntaxUtil,
8    idents::builtin_ident,
9    pass::{Imports, Module, UsedItems, Visit, imported_item_path},
10};
11use wesl_macros::query_mut;
12
13use wgsl_parse::{SyntaxNode, syntax::*};
14
15/// was that not in the std at some point???
16type BoxedIterator<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
17
18#[allow(dead_code)]
19pub trait IteratorExt: Iterator {
20    fn boxed<'a>(self) -> BoxedIterator<'a, Self::Item>
21    where
22        Self: Sized + 'a;
23}
24
25impl<T: Iterator> IteratorExt for T {
26    fn boxed<'a>(self) -> BoxedIterator<'a, Self::Item>
27    where
28        Self: Sized + 'a,
29    {
30        Box::new(self)
31    }
32}
33
34struct ScopeInner {
35    local: HashMap<String, Ident>,
36    parent: Option<Rc<ScopeInner>>,
37}
38
39struct Scope(Rc<ScopeInner>);
40
41impl ScopeInner {
42    fn iter(&self) -> impl Iterator<Item = (&String, &Ident)> {
43        self.local
44            .iter()
45            .chain(self.parent.iter().flat_map(|parent| parent.iter().boxed()))
46    }
47}
48
49impl Scope {
50    fn new() -> Scope {
51        Scope(Rc::new(ScopeInner {
52            local: HashMap::new(),
53            parent: None,
54        }))
55    }
56
57    fn push(&self) -> Scope {
58        Scope(Rc::new(ScopeInner {
59            local: HashMap::new(),
60            parent: Some(self.0.clone()),
61        }))
62    }
63
64    fn iter(&self) -> impl Iterator<Item = (&String, &Ident)> {
65        self.0.iter()
66    }
67
68    // insert in scope; or if already present, retarget the ident.
69    fn insert(&mut self, ident: &mut Ident) {
70        let inner = Rc::get_mut(&mut self.0).expect("cannot insert: scope use-count > 1");
71        match inner.local.entry(ident.to_string()) {
72            Entry::Occupied(entry) => {
73                *ident = entry.get().clone();
74            }
75            Entry::Vacant(entry) => {
76                entry.insert(ident.clone());
77            }
78        }
79    }
80}
81
82/// Make all identifiers that point to the same declaration refer to the same string.
83///
84/// Retarget local references to the local declaration ident and global
85/// references to the global declaration ident. It does this by keeping track of the
86/// local declarations scope.
87///
88/// Same-scope declarations with the same name will have the same identifier.
89/// Note: this can be valid code only with `@if` conditional declarations.
90pub fn retarget_idents(module: &mut TranslationUnit) {
91    fn flatten_imports(imports: &mut [ImportStatement]) -> impl Iterator<Item = &mut Ident> + '_ {
92        fn rec(content: &mut ImportContent) -> impl Iterator<Item = &mut Ident> + '_ {
93            match content {
94                ImportContent::Item(item) => {
95                    std::iter::once(item.rename.as_mut().unwrap_or(&mut item.ident)).boxed()
96                }
97                ImportContent::Collection(coll) => coll
98                    .iter_mut()
99                    .flat_map(|import| rec(&mut import.content))
100                    .boxed(),
101            }
102        }
103        imports
104            .iter_mut()
105            .flat_map(|import| rec(&mut import.content))
106    }
107
108    fn retarget_ty(ty: &mut TypeExpression, scope: &Scope) {
109        if ty.path.is_none() {
110            if let Some((_, id)) = scope
111                .iter()
112                .find(|(name, _)| name.as_str() == *ty.ident.name())
113            {
114                ty.ident = id.clone();
115            } else {
116                let builtin = builtin_ident(&ty.ident.name()).cloned();
117                if let Some(id) = builtin {
118                    ty.ident = id;
119                }
120            }
121        }
122        query_mut!(ty.template_args.[].[].expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)))
123            .for_each(|ty| retarget_ty(ty, scope));
124    }
125
126    // retarget local references to the local declaration ident and global
127    // references to the global declaration ident. It does this by keeping track of the
128    // local declarations scope.
129    fn retarget_stats<'a>(
130        stats: impl IntoIterator<Item = &'a mut StatementNode>,
131        mut scope: Scope,
132    ) -> Scope {
133        stats.into_iter().for_each(|stmt| match stmt.node_mut() {
134            Statement::Void => (),
135            Statement::Compound(s) => {
136                query_mut!(s.attributes.[].(x => x.visit_mut()))
137                    .for_each(|ty| retarget_ty(ty, &scope));
138                retarget_stats(&mut s.statements, scope.push());
139            }
140            Statement::Assignment(s) => {
141                query_mut!(s.{
142                    attributes.[].(x => x.visit_mut()),
143                    lhs.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
144                    rhs.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
145                })
146                .for_each(|ty| retarget_ty(ty, &scope));
147            }
148            Statement::Increment(s) => {
149                query_mut!(s.{
150                    attributes.[].(x => x.visit_mut()),
151                    expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
152                })
153                .for_each(|ty| retarget_ty(ty, &scope));
154            }
155            Statement::Decrement(s) => {
156                query_mut!(s.{
157                    attributes.[].(x => x.visit_mut()),
158                    expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
159                })
160                .for_each(|ty| retarget_ty(ty, &scope));
161            }
162            Statement::If(s) => {
163                let s2 = &mut *s; // COMBAK: not sure why this is needed?
164                query_mut!(s2.{
165                    attributes.[].(x => x.visit_mut()),
166                    if_clause.{
167                        expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
168                        body.{
169                            attributes.[].(x => x.visit_mut()),
170                        }
171                    },
172                    else_if_clauses.[].{
173                        attributes.[].(x => x.visit_mut()),
174                        expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
175                        body.{
176                            attributes.[].(x => x.visit_mut()),
177                        }
178                    },
179                    else_clause.[].{
180                        attributes.[].(x => x.visit_mut()),
181                        body.{
182                            attributes.[].(x => x.visit_mut()),
183                        },
184                    },
185                })
186                .for_each(|ty| retarget_ty(ty, &scope));
187                retarget_stats(&mut s.if_clause.body.statements, scope.push());
188                for clause in &mut s.else_if_clauses {
189                    retarget_stats(&mut clause.body.statements, scope.push());
190                }
191                if let Some(clause) = &mut s.else_clause {
192                    retarget_stats(&mut clause.body.statements, scope.push());
193                }
194            }
195            Statement::Switch(s) => {
196                let s2 = &mut *s; // COMBAK: not sure why this is needed?
197                query_mut!(s2.{
198                    attributes.[].(x => x.visit_mut()),
199                    expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
200                    body_attributes.[].(x => x.visit_mut()),
201                    clauses.[].{
202                        attributes.[].(x => x.visit_mut()),
203                        case_selectors.[].CaseSelector::Expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
204                        body.{
205                            attributes.[].(x => x.visit_mut()),
206                        }
207                    },
208
209                })
210                .for_each(|ty| retarget_ty(ty, &scope));
211                for clause in &mut s.clauses {
212                    retarget_stats(&mut clause.body.statements, scope.push());
213                }
214            }
215            Statement::Loop(s) => {
216                let s2 = &mut *s; // COMBAK: not sure why this is needed?
217                query_mut!(s2.{
218                    attributes.[].(x => x.visit_mut()),
219                    body.attributes.[].(x => x.visit_mut()),
220                })
221                .for_each(|ty| retarget_ty(ty, &scope));
222                let scope = retarget_stats(&mut s.body.statements, scope.push());
223                // continuing, if present, must be the last statement of the loop body
224                // and therefore has access to the scope at the end of the body.
225                if let Some(s) = &mut s.continuing {
226                    let s2 = &mut *s; // COMBAK: not sure why this is needed?
227                    query_mut!(s2.{
228                        attributes.[].(x => x.visit_mut()),
229                        body.attributes.[].(x => x.visit_mut()),
230                    })
231                    .for_each(|ty| retarget_ty(ty, &scope));
232                    let scope = retarget_stats(&mut s.body.statements, scope.push());
233                    // break-if, if present, must be the last statement of the continuing body
234                    // and therefore has access to the scope at the end of the body.
235                    if let Some(s) = &mut s.break_if {
236                        let s2 = &mut *s; // COMBAK: not sure why this is needed?
237                        query_mut!(s2.{
238                            attributes.[].(x => x.visit_mut()),
239                            expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
240                        })
241                        .for_each(|ty| retarget_ty(ty, &scope));
242                    }
243                }
244            }
245            Statement::For(s) => {
246                query_mut!(s.attributes.[].(x => x.visit_mut()))
247                    .for_each(|ty| retarget_ty(ty, &scope));
248                let scope = if let Some(init) = &mut s.initializer {
249                    retarget_stats([init], scope.push())
250                } else {
251                    scope.push()
252                };
253                query_mut!(s.condition.[].(x => Visit::<TypeExpression>::visit_mut(&mut **x)))
254                    .for_each(|ty| retarget_ty(ty, &scope));
255                query_mut!(s.body.attributes.[].(x => x.visit_mut()))
256                    .for_each(|ty| retarget_ty(ty, &scope));
257                if let Some(update) = &mut s.update {
258                    retarget_stats([update], scope.push());
259                }
260                retarget_stats(&mut s.body.statements, scope);
261            }
262            Statement::While(s) => {
263                let s2 = &mut *s; // COMBAK: not sure why this is needed?
264                query_mut!(s2.{
265                    attributes.[].(x => x.visit_mut()),
266                    condition.(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
267                    body.attributes.[].(x => x.visit_mut()),
268                })
269                .for_each(|ty| retarget_ty(ty, &scope));
270                retarget_stats(&mut s.body.statements, scope.push());
271            }
272            Statement::Break(s) => {
273                query_mut!(s.attributes.[].(x => x.visit_mut()))
274                    .for_each(|ty| retarget_ty(ty, &scope));
275            }
276            Statement::Continue(s) => {
277                query_mut!(s.attributes.[].(x => x.visit_mut()))
278                    .for_each(|ty| retarget_ty(ty, &scope));
279            }
280            Statement::Return(s) => {
281                query_mut!(s.expression.[].(x => Visit::<TypeExpression>::visit_mut(&mut **x)))
282                    .for_each(|ty| retarget_ty(ty, &scope));
283            }
284            Statement::Discard(s) => {
285                query_mut!(s.attributes.[].(x => x.visit_mut()))
286                    .for_each(|ty| retarget_ty(ty, &scope));
287            }
288            Statement::FunctionCall(s) => {
289                query_mut!(s.{
290                    attributes.[].(x => x.visit_mut()),
291                    call.{
292                        ty,
293                        arguments.[].(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
294                    }
295                })
296                .for_each(|ty| retarget_ty(ty, &scope));
297            }
298            Statement::ConstAssert(s) => {
299                query_mut!(s.{
300                    expression.(x => Visit::<TypeExpression>::visit_mut(&mut **x))
301                })
302                .for_each(|ty| retarget_ty(ty, &scope));
303            }
304            Statement::Declaration(s) => {
305                let s2 = &mut *s; // COMBAK: not sure why this is needed?
306                query_mut!(s2.{
307                    attributes.[].(x => x.visit_mut()),
308                    ty.[],
309                    initializer.[].(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
310                })
311                .for_each(|ty| retarget_ty(ty, &scope));
312                scope.insert(&mut s.ident);
313            }
314        });
315        scope
316    }
317
318    let mut scope = Scope::new();
319
320    for ident in flatten_imports(&mut module.imports) {
321        scope.insert(ident);
322    }
323
324    for decl in &mut module.global_declarations {
325        let ident = match decl.node_mut() {
326            GlobalDeclaration::Void => None,
327            GlobalDeclaration::Declaration(decl) => Some(&mut decl.ident),
328            GlobalDeclaration::TypeAlias(decl) => Some(&mut decl.ident),
329            GlobalDeclaration::Struct(decl) => Some(&mut decl.ident),
330            GlobalDeclaration::Function(decl) => Some(&mut decl.ident),
331            GlobalDeclaration::ConstAssert(_) => None,
332            GlobalDeclaration::Compound(_) => None,
333        };
334
335        if let Some(ident) = ident {
336            scope.insert(ident);
337        }
338    }
339
340    for decl in &mut module.global_declarations {
341        match decl.node_mut() {
342            GlobalDeclaration::Void => (),
343            GlobalDeclaration::Declaration(d) => {
344                Visit::<TypeExpression>::visit_mut(d).for_each(|ty| retarget_ty(ty, &scope))
345            }
346            GlobalDeclaration::TypeAlias(d) => {
347                Visit::<TypeExpression>::visit_mut(d).for_each(|ty| retarget_ty(ty, &scope))
348            }
349            GlobalDeclaration::Struct(d) => {
350                Visit::<TypeExpression>::visit_mut(d).for_each(|ty| retarget_ty(ty, &scope))
351            }
352            GlobalDeclaration::Function(d) => {
353                #[cfg(feature = "generics")]
354                let scope = {
355                    let mut scope = scope.push();
356                    d.attributes
357                        .iter_mut()
358                        .filter_map(|attr| match attr.node_mut() {
359                            Attribute::Type(attr) => Some(&mut attr.ident),
360                            _ => None,
361                        })
362                        .for_each(|ident| scope.insert(ident));
363                    scope
364                };
365                let d2 = &mut *d; // COMBAK: not sure why this is needed?
366                query_mut!(d2.{
367                    attributes.[].(x => x.visit_mut()),
368                    parameters.[].{
369                        attributes.[].(x => x.visit_mut()),
370                        ty,
371                    },
372                    return_attributes.[].(x => x.visit_mut()),
373                    return_type.[],
374                    body.{
375                        attributes.[].(x => x.visit_mut()),
376                    }
377                })
378                .for_each(|ty| retarget_ty(ty, &scope));
379                let mut scope = scope.push();
380                d.parameters
381                    .iter_mut()
382                    .for_each(|param| scope.insert(&mut param.ident));
383                retarget_stats(&mut d.body.statements, scope);
384            }
385            GlobalDeclaration::ConstAssert(d) => {
386                Visit::<TypeExpression>::visit_mut(d).for_each(|ty| retarget_ty(ty, &scope))
387            }
388            GlobalDeclaration::Compound(d) => {
389                query_mut!(d.{
390                    attributes.[].(x => x.visit_mut()),
391                    body.[].(x => Visit::<TypeExpression>::visit_mut(&mut **x)),
392                })
393                .for_each(|ty| retarget_ty(ty, &scope));
394            }
395        }
396    }
397}
398
399/// Retarget used identifiers to point at the corresponding declaration.
400///
401/// We call this after usage analysis, because it is mutating the modules, and we want to keep
402/// mutations and lookups separate if possible, to avoid multiple mut borrows.
403///
404/// # Panics
405///
406/// * if an identifier has no corresponding declaration.
407pub fn retarget_modules(modules: &mut [Module], used_items: &UsedItems, resolver: &impl Resolver) {
408    // unfortunately I have to pass 3 module_xxx by ref here because I can't mutably borrow `ty` and immutably borrow a `Module`.
409    // TODO: could we get away with using just used_items instead of other_modules?
410    // in theory it contains all used identifiers, and we wouldn't have to deal with the double borrow
411    // shenanigans. The only concern is re-exports, they would need to be retargeted.
412    fn retarget_ty<'a>(
413        ty: &mut TypeExpression,
414        module_path: &ModulePath,
415        module_imports: &Imports,
416        module_idents: &HashMap<Ident, Visibility>,
417        other_modules: impl IntoIterator<Item = &'a Module> + Clone + 'a,
418        resolver: &impl Resolver,
419    ) {
420        // first the recursive call
421        for ty in Visit::<TypeExpression>::visit_mut(ty) {
422            retarget_ty(
423                ty,
424                module_path,
425                module_imports,
426                module_idents,
427                other_modules.clone(),
428                resolver,
429            );
430        }
431
432        if let Some((import_path, mut import_ident)) =
433            imported_item_path(ty, module_path, module_imports)
434        {
435            let mut import_path = resolver.canonical_path(&import_path);
436            // because of re-exports, we may have to look up the import module in a loop.
437            // TODO: check that there can't be re-export cycles: A exports foo from B, B exports foo from A...
438            loop {
439                // if the import path points to a local decl.
440                // this is a special case but does the same as the code below, because the current
441                // module is not stored in `other_modules`.
442                if import_path == *module_path {
443                    if let Some(ident) = module_idents
444                        .keys()
445                        .find(|ident| *ident.name() == *import_ident.name())
446                        .cloned()
447                    {
448                        // we found a declaration with the right name.
449                        ty.path = None;
450                        ty.ident = ident;
451                        return;
452                    } else if let Some((_, item)) = module_imports
453                        .iter()
454                        .find(|(ident, _)| *ident.name() == *ty.ident.name())
455                    {
456                        // there is no declaration with this name, but there is a re-export.
457                        // we loop again with a new path and ident to look up.
458                        // TODO: check that there can't be re-export cycles: A exports foo from B, B exports foo from A...
459                        import_path = resolver.canonical_path(&item.path);
460                        import_ident = item.ident.clone();
461                    } else {
462                        debug_assert!(false, "no declaration {import_ident} in {import_path}");
463                        return;
464                    }
465                } else {
466                    let Some(import_module) = other_modules
467                        .clone()
468                        .into_iter()
469                        .find(|m| m.path == import_path)
470                    else {
471                        debug_assert!(false, "no importable module {import_path}");
472                        return;
473                    };
474                    if let Some(ident) = import_module.syntax.decl_ident(&import_ident.name()) {
475                        // we found a declaration with the right name.
476                        ty.path = None;
477                        ty.ident = ident;
478                        return;
479                    } else if let Some((_, item)) = import_module
480                        .imports
481                        .iter()
482                        .find(|(ident, _)| *ident.name() == *import_ident.name())
483                    {
484                        // there is no declaration with this name, but there is a re-export.
485                        // we loop again with a new path and ident to look up.
486                        // TODO: check that there can't be re-export cycles: A exports foo from B, B exports foo from A...
487                        import_path = resolver.canonical_path(&item.path);
488                        import_ident = item.ident.clone();
489                    } else {
490                        debug_assert!(false, "no declaration {import_ident} in {import_path}");
491                        return;
492                    }
493                }
494            }
495        }
496    }
497
498    for i in 0..modules.len() {
499        // shenanigans to get both a mutable reference to the current module,
500        // and an immutable reference to the other modules.
501        let (left, right) = modules.split_at_mut(i);
502        let (module, right) =
503            right.split_first_mut().unwrap(/* SAFETY: the 1st element exists at index i */);
504        let other_modules = left.iter().chain(right.iter());
505
506        let Some(module_used_items) = used_items.get_module(&module.path) else {
507            debug_assert!(false, "missing module {} in retarget_idents", module.path);
508            continue;
509        };
510
511        for decl in &mut module.syntax.global_declarations {
512            // we only retarget used declarations. Other declarations are not checked.
513            if let Some(ident) = decl.ident()
514                && !module_used_items.contains_key(&ident)
515            {
516                continue;
517            }
518
519            for ty in Visit::<TypeExpression>::visit_mut(decl.node_mut()) {
520                retarget_ty(
521                    ty,
522                    &module.path,
523                    &module.imports,
524                    module_used_items,
525                    other_modules.clone(),
526                    resolver,
527                );
528            }
529        }
530    }
531}
532
533/// Check that retarget_ident handles shadowing correctly.
534///
535/// Procedure: we strip the ident digits, call retarget_idents and then rename idents
536/// with a unique digit. We should end up back with the test code, assuming the iterator
537/// order is predictable (is should be a depth-first search).
538#[test]
539fn test_retarget_idents() {
540    use std::collections::HashSet;
541
542    let source = r#"
543        import        i0;
544        import        i::{i0, i0, j};
545        const_assert  i0+c1+v2+a3+s4; // due to hoisting, all global decls idents should be visible
546        const         c1: c1 = i0+c1+v2+a3+s4;
547        var<private>  v2: v2 = i0+c1+v2+a3+s4;
548        alias         a3 = a3<i0, c1, v2, a3>;
549        struct        s4 { m: m5 }
550
551        fn f18(p: p6) {
552            let x7 = i0+c1+v2+a3+s4;
553            let x7 = x7;
554
555            // shadowing with local declarations
556            let i8 = i0+c1+v2+a3+s4;
557            let c9 = i8+c1+v2+a3+s4;
558            let v10 = i8+c9+v2+a3+s4;
559            let a11 = i8+c9+v10+a3+s4;
560            let s12 = i8+c9+v10+a11+s4;
561            let x7 = i8+c9+v10+a11+s12; // x is in the same scope as previous x: should have same ident
562
563            {
564                let x13 = i8+c9+v10+a11+s12 +x7; // different x
565                let x13 = x13;
566
567                for (let x14 = x13; x14; x14++) {
568                    let x14: x14 = x14; // in for loops, the initializer cannot be shadowed
569                }
570
571                loop {
572                    var x15 = x13;
573                    const y17 = x15;
574                    continuing {
575                        let x16 = x15;
576                        break if x16 + y17;
577                    }
578                }
579            }
580
581            switch x7 {
582                case x7, f18, g19 { x7 = x7; }
583            }
584        }
585
586        fn f18() {} // f is in the same scope as previous f: should have same ident
587        fn g19() {}
588    "#;
589
590    let module: TranslationUnit = source.parse().expect("parse failure");
591    let source_stripped = source.replace(|c: char| c.is_ascii_digit(), "");
592    let mut module_stripped: TranslationUnit = source_stripped.parse().expect("parse failure");
593    retarget_idents(&mut module_stripped);
594
595    let mut idents = HashSet::new();
596    let mut ordered_idents = Vec::new();
597    // the test assumes that Visit is in depth-first order so the ident order is predictable.
598    for ty in Visit::<TypeExpression>::visit(&module_stripped) {
599        let inserted = idents.insert(ty.ident.clone());
600        if inserted {
601            ordered_idents.push(ty.ident.clone());
602        }
603    }
604
605    for (i, ident) in ordered_idents.iter().enumerate() {
606        println!("ident #{i}: {ident}, count: {}", ident.use_count() - 2);
607        ident.clone().rename(format!("{ident}{i}"));
608    }
609
610    println!("=== expectation ===\n{module}");
611    println!("=== test output ===\n{module_stripped}");
612
613    assert_eq!(module.to_string(), module_stripped.to_string())
614}
615
616#[test]
617fn test_qualified_reference() {
618    let mut module: TranslationUnit = "fn value() -> u32 { return package::helper::value(); }"
619        .parse()
620        .unwrap();
621    retarget_idents(&mut module);
622    assert!(crate::pass::validate_wesl(&module).is_ok());
623}