|
| 1 | +//! Source-preserving AST desugarings applied by [`super::parse_source`] (but not |
| 2 | +//! [`super::parse_source_raw`], so the formatter and symbol index still see the |
| 3 | +//! written surface). |
| 4 | +//! |
| 5 | +//! ## Associated constants |
| 6 | +//! |
| 7 | +//! `const Device.DEFAULT: String = "cpu"` declares a *type-associated* constant, |
| 8 | +//! referenced as `Device.DEFAULT`. The reference parses as a field access |
| 9 | +//! (`Field { base: Ident("Device"), name: "DEFAULT" }`), which no value defines. |
| 10 | +//! Rather than teach the checker and both backends to resolve such field |
| 11 | +//! accesses, this pass rewrites associated constants into ordinary ones: |
| 12 | +//! |
| 13 | +//! * the declaration `const Device.DEFAULT` becomes `const Device_DEFAULT`, and |
| 14 | +//! * every reference `Device.DEFAULT` becomes the ident `Device_DEFAULT`. |
| 15 | +//! |
| 16 | +//! After this, all existing const machinery (resolution, type inference, VM, Rust |
| 17 | +//! lowering) handles them with no further changes. The pass is a no-op for |
| 18 | +//! programs without dotted const names. |
| 19 | +
|
| 20 | +use std::collections::HashMap; |
| 21 | + |
| 22 | +use super::ast::{Block, Callee, Expr, Item, Program, Stmt}; |
| 23 | + |
| 24 | +/// Flatten the associated name `Device.DEFAULT` to an ordinary constant |
| 25 | +/// identifier. Constants follow Rust's `SCREAMING_SNAKE_CASE` (the Rust backend |
| 26 | +/// upper-cases const declaration names), so the flattened name is upper-cased too |
| 27 | +/// — keeping the declaration, every reference, and the VM all in agreement. |
| 28 | +fn flatten(name: &str) -> String { |
| 29 | + name.replace('.', "_").to_uppercase() |
| 30 | +} |
| 31 | + |
| 32 | +pub(crate) fn desugar_associated_consts(program: &mut Program) { |
| 33 | + // Map each associated (dotted) const name to its flattened form. |
| 34 | + let assoc: HashMap<String, String> = program |
| 35 | + .items |
| 36 | + .iter() |
| 37 | + .filter_map(|item| match item { |
| 38 | + Item::Const(decl) if decl.name.contains('.') => { |
| 39 | + Some((decl.name.clone(), flatten(&decl.name))) |
| 40 | + } |
| 41 | + _ => None, |
| 42 | + }) |
| 43 | + .collect(); |
| 44 | + if assoc.is_empty() { |
| 45 | + return; |
| 46 | + } |
| 47 | + |
| 48 | + for item in &mut program.items { |
| 49 | + match item { |
| 50 | + Item::Const(decl) => { |
| 51 | + if let Some(flat) = assoc.get(&decl.name) { |
| 52 | + decl.name = flat.clone(); |
| 53 | + } |
| 54 | + rewrite_expr(&mut decl.value, &assoc); |
| 55 | + } |
| 56 | + Item::Function(function) => rewrite_block(&mut function.body, &assoc), |
| 57 | + _ => {} |
| 58 | + } |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +fn rewrite_block(block: &mut Block, assoc: &HashMap<String, String>) { |
| 63 | + for statement in &mut block.statements { |
| 64 | + rewrite_stmt(statement, assoc); |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +fn rewrite_stmt(statement: &mut Stmt, assoc: &HashMap<String, String>) { |
| 69 | + match statement { |
| 70 | + Stmt::Let(stmt) => { |
| 71 | + if let Some(value) = &mut stmt.value { |
| 72 | + rewrite_expr(value, assoc); |
| 73 | + } |
| 74 | + } |
| 75 | + Stmt::Return(stmt) => { |
| 76 | + if let Some(value) = &mut stmt.value { |
| 77 | + rewrite_expr(value, assoc); |
| 78 | + } |
| 79 | + } |
| 80 | + Stmt::Expr(expr) => rewrite_expr(expr, assoc), |
| 81 | + Stmt::Assign(stmt) => { |
| 82 | + rewrite_expr(&mut stmt.target, assoc); |
| 83 | + rewrite_expr(&mut stmt.value, assoc); |
| 84 | + } |
| 85 | + Stmt::With(stmt) => { |
| 86 | + rewrite_expr(&mut stmt.resource, assoc); |
| 87 | + rewrite_block(&mut stmt.body, assoc); |
| 88 | + } |
| 89 | + Stmt::If(stmt) => { |
| 90 | + rewrite_expr(&mut stmt.condition, assoc); |
| 91 | + rewrite_block(&mut stmt.then_body, assoc); |
| 92 | + if let Some(else_body) = &mut stmt.else_body { |
| 93 | + rewrite_block(else_body, assoc); |
| 94 | + } |
| 95 | + } |
| 96 | + Stmt::Loop(stmt) => { |
| 97 | + if let Some(condition) = &mut stmt.condition { |
| 98 | + rewrite_expr(condition, assoc); |
| 99 | + } |
| 100 | + rewrite_block(&mut stmt.body, assoc); |
| 101 | + } |
| 102 | + Stmt::For(stmt) => { |
| 103 | + rewrite_expr(&mut stmt.iterable, assoc); |
| 104 | + rewrite_block(&mut stmt.body, assoc); |
| 105 | + } |
| 106 | + Stmt::LetElse(stmt) => { |
| 107 | + rewrite_expr(&mut stmt.value, assoc); |
| 108 | + rewrite_block(&mut stmt.else_body, assoc); |
| 109 | + } |
| 110 | + Stmt::TaskGroup(stmt) => rewrite_block(&mut stmt.body, assoc), |
| 111 | + Stmt::Match(stmt) => { |
| 112 | + rewrite_expr(&mut stmt.value, assoc); |
| 113 | + for arm in &mut stmt.arms { |
| 114 | + if let Some(guard) = &mut arm.guard { |
| 115 | + rewrite_expr(guard, assoc); |
| 116 | + } |
| 117 | + rewrite_block(&mut arm.body, assoc); |
| 118 | + } |
| 119 | + } |
| 120 | + Stmt::Select(stmt) => { |
| 121 | + for arm in &mut stmt.arms { |
| 122 | + rewrite_expr(&mut arm.operation, assoc); |
| 123 | + rewrite_block(&mut arm.body, assoc); |
| 124 | + } |
| 125 | + } |
| 126 | + Stmt::MalformedWith(_) |
| 127 | + | Stmt::MalformedIf(_) |
| 128 | + | Stmt::MalformedLoop(_) |
| 129 | + | Stmt::MalformedFor(_) |
| 130 | + | Stmt::MalformedMatch(_) |
| 131 | + | Stmt::Break(_) |
| 132 | + | Stmt::Continue(_) |
| 133 | + | Stmt::Unknown(_) => {} |
| 134 | + } |
| 135 | +} |
| 136 | + |
| 137 | +fn rewrite_expr(expr: &mut Expr, assoc: &HashMap<String, String>) { |
| 138 | + // An associated-const reference is a field access on a type name: rewrite the |
| 139 | + // whole expression to the flattened ident and stop (the "base" is the type, |
| 140 | + // not a value to recurse into). |
| 141 | + if let Expr::Field { base, name, span } = expr |
| 142 | + && let Expr::Ident(type_name, _) = base.as_ref() |
| 143 | + && let Some(flat) = assoc.get(&format!("{type_name}.{name}")) |
| 144 | + { |
| 145 | + *expr = Expr::Ident(flat.clone(), span.clone()); |
| 146 | + return; |
| 147 | + } |
| 148 | + |
| 149 | + match expr { |
| 150 | + Expr::Field { base, .. } => rewrite_expr(base, assoc), |
| 151 | + Expr::Index { base, index, .. } => { |
| 152 | + rewrite_expr(base, assoc); |
| 153 | + rewrite_expr(index, assoc); |
| 154 | + } |
| 155 | + Expr::Binary { left, right, .. } => { |
| 156 | + rewrite_expr(left, assoc); |
| 157 | + rewrite_expr(right, assoc); |
| 158 | + } |
| 159 | + Expr::Effect { value, .. } |
| 160 | + | Expr::Manage { value, .. } |
| 161 | + | Expr::Spawn { value, .. } |
| 162 | + | Expr::Await { value, .. } |
| 163 | + | Expr::Try { value, .. } => rewrite_expr(value, assoc), |
| 164 | + Expr::Call { callee, args, .. } => { |
| 165 | + if let Callee::ReceiverCall { receiver, .. } = callee { |
| 166 | + rewrite_expr(receiver, assoc); |
| 167 | + } |
| 168 | + for arg in args { |
| 169 | + rewrite_expr(&mut arg.value, assoc); |
| 170 | + } |
| 171 | + } |
| 172 | + Expr::Closure { body, .. } => rewrite_block(body, assoc), |
| 173 | + Expr::Match { value, arms, .. } => { |
| 174 | + rewrite_expr(value, assoc); |
| 175 | + for arm in arms { |
| 176 | + if let Some(guard) = &mut arm.guard { |
| 177 | + rewrite_expr(guard, assoc); |
| 178 | + } |
| 179 | + rewrite_block(&mut arm.body, assoc); |
| 180 | + } |
| 181 | + } |
| 182 | + Expr::ObjectLiteral { fields, .. } => { |
| 183 | + for field in fields { |
| 184 | + rewrite_expr(&mut field.value, assoc); |
| 185 | + } |
| 186 | + } |
| 187 | + Expr::MapLiteral { entries, .. } => { |
| 188 | + for entry in entries { |
| 189 | + rewrite_expr(&mut entry.key, assoc); |
| 190 | + rewrite_expr(&mut entry.value, assoc); |
| 191 | + } |
| 192 | + } |
| 193 | + Expr::ArrayLiteral { items, .. } => { |
| 194 | + for item in items { |
| 195 | + rewrite_expr(item, assoc); |
| 196 | + } |
| 197 | + } |
| 198 | + Expr::Ident(..) |
| 199 | + | Expr::Number(..) |
| 200 | + | Expr::String(..) |
| 201 | + | Expr::MultilineString(..) |
| 202 | + | Expr::Unknown(_) => {} |
| 203 | + } |
| 204 | +} |
0 commit comments