Skip to main content

meta_language/translation/
rust.rs

1//! The Rust frontend: reads a Rust program into the surface AST.
2//!
3//! It reads the subset of Rust the core can represent faithfully: modules
4//! with `use` of crate items, enums with unit and tuple variants (`Box<T>`
5//! fields are the recursive occurrences of a value type), free functions over
6//! machine integers, `bool` and `String`, expression bodies with `let`, `if`,
7//! `match` and `format!`, and a `main` made of `println!`, `let` and
8//! `assert!`/`assert_eq!` statements. Everything else is rejected with a
9//! precise obligation.
10//!
11//! Mirrors `js/src/translation/rust.js`.
12
13use std::collections::HashMap;
14
15use super::aborts::rust_macro_message;
16use super::diagnostics::{Result, TranslationError, type_error, unsupported};
17use super::lexer::{Token, TokenCursor, TokenKind, describe, tokenize};
18use super::surface::{
19    BinaryOp, Flavor, Rounding, SComparison, SCtor, SData, SEffect, SExpr, SField, SFn, SItem,
20    SMain, SModule, SNode, SParam, SPattern, SPatternNode, SProgram, SProp, SPropNode, SRow,
21    ShowStyle, UnaryOp,
22};
23use super::types::{BOOL, STRING, Type, UNIT, rust_fixed_type};
24use super::{Language, Span};
25
26mod expressions;
27mod items;
28
29const ACCEPTED_DERIVES: [&str; 8] = [
30    "Clone",
31    "Copy",
32    "Debug",
33    "PartialEq",
34    "Eq",
35    "Hash",
36    "PartialOrd",
37    "Ord",
38];
39const ACCEPTED_LINTS: [&str; 6] = ["allow", "warn", "deny", "expect", "must_use", "inline"];
40const RESERVED_ITEMS: [&str; 10] = [
41    "impl",
42    "trait",
43    "struct",
44    "union",
45    "static",
46    "type",
47    "extern",
48    "macro_rules",
49    "async",
50    "unsafe",
51];
52const UNSUPPORTED_TYPES: [&str; 11] = [
53    "str", "char", "f32", "f64", "Vec", "Option", "Result", "Rc", "Arc", "HashMap", "Self",
54];
55
56fn comparison_op(value: &str) -> Option<BinaryOp> {
57    Some(match value {
58        "==" => BinaryOp::Eq,
59        "!=" => BinaryOp::Ne,
60        "<" => BinaryOp::Lt,
61        "<=" => BinaryOp::Le,
62        ">" => BinaryOp::Gt,
63        ">=" => BinaryOp::Ge,
64        _ => return None,
65    })
66}
67
68fn additive_op(value: &str) -> Option<BinaryOp> {
69    Some(match value {
70        "+" => BinaryOp::Add,
71        "-" => BinaryOp::Sub,
72        _ => return None,
73    })
74}
75
76fn multiplicative_op(value: &str) -> Option<BinaryOp> {
77    Some(match value {
78        "*" => BinaryOp::Mul,
79        "/" => BinaryOp::Div,
80        "%" => BinaryOp::Rem,
81        _ => return None,
82    })
83}
84
85/// Reads a Rust program.
86///
87/// # Errors
88/// On syntax errors and on constructs outside the portable core.
89pub fn parse_rust(source: &str) -> Result<SProgram> {
90    let tokens = tokenize(source, Language::Rust)?.tokens;
91    RustParser::new(tokens).file()
92}
93
94/// Where a sequence of items ends: at the end of the file or at a module's `}`.
95#[derive(Clone, Copy)]
96enum ItemsEnd {
97    File,
98    Module,
99}
100
101/// Parsing context an expression inherits from its position.
102#[derive(Clone, Copy, Default)]
103struct Options {
104    /// The expression starts a statement, so a leading `if`/`match` ends there.
105    statement: bool,
106    /// `{` opens a block, not a struct literal (`if`/`match` heads).
107    no_struct: bool,
108}
109
110/// A `name @ pattern` binding, bound around the arm's body.
111struct Alias {
112    name: String,
113    value: SExpr,
114}
115
116/// A parsed `let` statement.
117struct Binding {
118    name: String,
119    ty: Option<Type>,
120    value: SExpr,
121    span: Option<Span>,
122}
123
124struct RustParser {
125    cursor: TokenCursor,
126    /// `use` aliases, per module path.
127    aliases: HashMap<String, HashMap<String, Vec<String>>>,
128    module_path: Vec<String>,
129    main: Option<SMain>,
130}
131
132impl RustParser {
133    fn new(tokens: Vec<Token>) -> Self {
134        Self {
135            cursor: TokenCursor::new(tokens, Language::Rust),
136            aliases: HashMap::new(),
137            module_path: Vec::new(),
138            main: None,
139        }
140    }
141
142    fn peek(&self) -> Token {
143        self.cursor.peek().clone()
144    }
145
146    fn fail(message: &str, token: &Token) -> TranslationError {
147        TranslationError::syntax(
148            format!("{message} but found {}", describe(token)),
149            Some(token.span()),
150        )
151    }
152}
153
154/// `format!("{} and {name}", a)`: `{}` takes the next argument, `{name}` a
155/// variable in scope, `{{`/`}}` are braces; every value is rendered with
156/// Display, which for integers is its decimal text.
157fn format(args: Vec<SExpr>, token: &Token) -> Result<SExpr> {
158    let mut args = args.into_iter();
159    let template = args.next();
160    let values: Vec<SExpr> = args.collect();
161    let Some(SExpr {
162        node: SNode::Str { value: source },
163        span: template_span,
164        ..
165    }) = template
166    else {
167        return Err(unsupported(
168            &format!("{}!", token.value),
169            "the format string must be a literal",
170            span(token, token),
171        ));
172    };
173    let chars: Vec<char> = source.chars().collect();
174    let mut pieces: Vec<SExpr> = Vec::new();
175    let mut text = String::new();
176    let mut next = 0;
177    let mut index = 0;
178    while index < chars.len() {
179        let char = chars[index];
180        let following = chars.get(index + 1).copied();
181        if char == '{' && following == Some('{') {
182            text.push('{');
183            index += 2;
184            continue;
185        }
186        if char == '}' && following == Some('}') {
187            text.push('}');
188            index += 2;
189            continue;
190        }
191        if char == '}' {
192            return Err(TranslationError::syntax(
193                "unmatched } in format string",
194                template_span,
195            ));
196        }
197        if char != '{' {
198            text.push(char);
199            index += 1;
200            continue;
201        }
202        let Some(end) = chars[index..]
203            .iter()
204            .position(|&candidate| candidate == '}')
205            .map(|offset| index + offset)
206        else {
207            return Err(TranslationError::syntax(
208                "unclosed { in format string",
209                template_span,
210            ));
211        };
212        let spec: String = chars[index + 1..end].iter().collect();
213        if spec.contains(':') {
214            return Err(unsupported(
215                &format!("format spec {{{spec}}}"),
216                "only plain {} Display formatting is portable",
217                template_span,
218            ));
219        }
220        let value = if spec.is_empty() {
221            let value = values.get(next).cloned();
222            next += 1;
223            value.ok_or_else(|| {
224                TranslationError::syntax("format string has more {} than arguments", template_span)
225            })?
226        } else if spec.bytes().all(|byte| byte.is_ascii_digit()) {
227            // A position too large for an index names no argument either.
228            let position = spec.parse::<usize>().ok();
229            let value = position.and_then(|position| values.get(position).cloned());
230            let (Some(position), Some(value)) = (position, value) else {
231                return Err(TranslationError::syntax(
232                    format!("format argument {spec} is missing"),
233                    template_span,
234                ));
235            };
236            next = next.max(position + 1);
237            value
238        } else if is_identifier(&spec) {
239            SExpr::new(SNode::Name { path: vec![spec] }, template_span)
240        } else {
241            return Err(unsupported(
242                &format!("format argument {{{spec}}}"),
243                "outside the portable core",
244                template_span,
245            ));
246        };
247        if !text.is_empty() {
248            pieces.push(SExpr::new(
249                SNode::Str {
250                    value: std::mem::take(&mut text),
251                },
252                template_span,
253            ));
254        }
255        let value_span = value.span;
256        pieces.push(SExpr::new(
257            SNode::Show {
258                arg: Box::new(value),
259                style: ShowStyle::Rust,
260            },
261            value_span,
262        ));
263        index = end + 1;
264    }
265    if next < values.len() {
266        return Err(TranslationError::syntax(
267            "format string has fewer {} than arguments",
268            template_span,
269        ));
270    }
271    if !text.is_empty() || pieces.is_empty() {
272        pieces.push(SExpr::new(SNode::Str { value: text }, template_span));
273    }
274    let mut pieces = pieces.into_iter();
275    let first = pieces
276        .next()
277        .unwrap_or_else(|| unreachable!("a piece was pushed"));
278    Ok(pieces.fold(first, |left, right| {
279        SExpr::new(
280            SNode::Binary {
281                op: BinaryOp::Concat,
282                left: Box::new(left),
283                right: Box::new(right),
284                rounding: None,
285            },
286            template_span,
287        )
288    }))
289}
290
291/// `/^[A-Za-z_][A-Za-z0-9_]*$/`.
292fn is_identifier(text: &str) -> bool {
293    let mut bytes = text.bytes();
294    bytes
295        .next()
296        .is_some_and(|first| first.is_ascii_alphabetic() || first == b'_')
297        && bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
298}
299
300fn method_call(
301    receiver: SExpr,
302    method: &Token,
303    args: Vec<SExpr>,
304    range: Option<Span>,
305) -> Result<SExpr> {
306    let expect = |count: usize| {
307        if args.len() == count {
308            Ok(())
309        } else {
310            Err(type_error(
311                format!(
312                    "{} expects {count} arguments but got {}",
313                    method.value,
314                    args.len()
315                ),
316                range,
317            ))
318        }
319    };
320    match method.value.as_str() {
321        "clone" | "to_owned" => {
322            expect(0)?;
323            Ok(receiver)
324        }
325        "to_string" => {
326            expect(0)?;
327            Ok(SExpr::new(
328                SNode::ToString {
329                    arg: Box::new(receiver),
330                },
331                range,
332            ))
333        }
334        "div_euclid" | "rem_euclid" => {
335            expect(1)?;
336            let op = if method.value == "div_euclid" {
337                BinaryOp::Div
338            } else {
339                BinaryOp::Rem
340            };
341            let right = args
342                .into_iter()
343                .next()
344                .unwrap_or_else(|| unreachable!("one argument"));
345            Ok(SExpr::new(
346                SNode::Binary {
347                    op,
348                    left: Box::new(receiver),
349                    right: Box::new(right),
350                    rounding: Some(Rounding::Euclid),
351                },
352                range,
353            ))
354        }
355        _ => Err(unsupported(
356            &format!("method {}", method.value),
357            "outside the portable core",
358            range,
359        )),
360    }
361}
362
363/// An imported path from the crate root, so it means the same wherever it is used.
364fn absolute(segments: &[String], path: &[String], range: Option<Span>) -> Result<Vec<String>> {
365    let crate_root = || vec!["crate".to_owned()];
366    if segments[0] == "crate" {
367        return Ok(segments.to_vec());
368    }
369    if segments[0] == "self" {
370        let mut result = crate_root();
371        result.extend_from_slice(path);
372        result.extend_from_slice(&segments[1..]);
373        return Ok(result);
374    }
375    let up = segments
376        .iter()
377        .take_while(|segment| *segment == "super")
378        .count();
379    if up > path.len() {
380        return Err(type_error("super beyond crate root", range));
381    }
382    let mut result = crate_root();
383    result.extend_from_slice(&path[..path.len() - up]);
384    result.extend_from_slice(&segments[up..]);
385    Ok(result)
386}
387
388fn prop_of(expr: SExpr) -> SProp {
389    let node = match expr.node {
390        SNode::Binary {
391            op:
392                op @ (BinaryOp::Eq
393                | BinaryOp::Ne
394                | BinaryOp::Lt
395                | BinaryOp::Le
396                | BinaryOp::Gt
397                | BinaryOp::Ge),
398            left,
399            right,
400            ..
401        } => {
402            let comparison = SComparison {
403                left: *left,
404                right: *right,
405                reference: false,
406                same_value: false,
407            };
408            match op {
409                BinaryOp::Eq => SPropNode::Eq(comparison),
410                BinaryOp::Ne => SPropNode::Ne(comparison),
411                BinaryOp::Lt => SPropNode::Lt(comparison),
412                BinaryOp::Le => SPropNode::Le(comparison),
413                BinaryOp::Gt => SPropNode::Gt(comparison),
414                _ => SPropNode::Ge(comparison),
415            }
416        }
417        SNode::Binary {
418            op: BinaryOp::And,
419            left,
420            right,
421            ..
422        } => SPropNode::And {
423            left: Box::new(prop_of(*left)),
424            right: Box::new(prop_of(*right)),
425        },
426        SNode::Binary {
427            op: BinaryOp::Or,
428            left,
429            right,
430            ..
431        } => SPropNode::Or {
432            left: Box::new(prop_of(*left)),
433            right: Box::new(prop_of(*right)),
434        },
435        SNode::Unary {
436            op: UnaryOp::Not,
437            arg,
438        } => SPropNode::Not {
439            arg: Box::new(prop_of(*arg)),
440        },
441        node => SPropNode::Bool {
442            expr: SExpr { node, ..expr },
443        },
444    };
445    SProp { node, span: None }
446}
447
448/// The value a pattern matched, for `name @ p`.
449fn pattern_value(pattern: &SPattern, range: Option<Span>) -> Result<SExpr> {
450    let node = match &pattern.node {
451        SPatternNode::BindOrCtor { name } => SNode::Name {
452            path: vec![name.clone()],
453        },
454        SPatternNode::NumLit { value, negative } => SNode::Num {
455            value: value.clone(),
456            ty: None,
457            negative: *negative,
458            unit: false,
459        },
460        SPatternNode::BoolLit { value, .. } => SNode::Bool { value: *value },
461        SPatternNode::Ctor { path, args } if args.is_empty() => SNode::Name { path: path.clone() },
462        SPatternNode::Ctor { path, args } => SNode::App {
463            func: Box::new(SExpr::new(SNode::Name { path: path.clone() }, range)),
464            args: args
465                .iter()
466                .map(|arg| pattern_value(arg, range))
467                .collect::<Result<_>>()?,
468        },
469        _ => {
470            return Err(unsupported(
471                "@ binding",
472                "the aliased pattern must bind every field",
473                range,
474            ));
475        }
476    };
477    Ok(SExpr::new(node, range))
478}
479
480const fn is_if_or_match(expr: &SExpr) -> bool {
481    matches!(expr.node, SNode::If { .. } | SNode::Match { .. })
482}
483
484fn operator(token: &Token, table: fn(&str) -> Option<BinaryOp>) -> Option<BinaryOp> {
485    if token.kind == TokenKind::Punct {
486        table(&token.value)
487    } else {
488        None
489    }
490}
491
492fn binary(op: BinaryOp, left: SExpr, right: SExpr, token: &Token) -> SExpr {
493    let range = joined(left.span, right.span, token);
494    SExpr::new(
495        SNode::Binary {
496            op,
497            left: Box::new(left),
498            right: Box::new(right),
499            rounding: None,
500        },
501        range,
502    )
503}
504
505#[allow(clippy::unnecessary_wraps)] // nodes and errors take an optional span
506fn joined(left: Option<Span>, right: Option<Span>, token: &Token) -> Option<Span> {
507    Some(Span::new(
508        left.map_or(token.start, |left| left.start),
509        right.map_or(token.end, |right| right.end),
510    ))
511}
512
513#[allow(clippy::unnecessary_wraps)] // nodes and errors take an optional span
514fn span(from: &Token, to: &Token) -> Option<Span> {
515    let to_start = if to.kind == TokenKind::Eof {
516        from.end
517    } else {
518        to.start
519    };
520    Some(Span::new(from.start, from.end.max(to_start)))
521}