1use draxl_ast as ast;
4use std::collections::BTreeMap;
5use std::error::Error as StdError;
6use std::fmt;
7use syn::spanned::Spanned;
8
9pub fn import_source(source: &str) -> Result<ast::File, ImportError> {
11 let parsed = syn::parse_file(source).map_err(ImportError::RustParse)?;
12 Importer::default().import_file(&parsed)
13}
14
15#[derive(Debug)]
17pub enum ImportError {
18 RustParse(syn::Error),
20 Unsupported(syn::Error),
22}
23
24impl fmt::Display for ImportError {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 Self::RustParse(error) => write!(f, "Rust parse failed: {error}"),
28 Self::Unsupported(error) => write!(f, "unsupported Rust syntax: {error}"),
29 }
30 }
31}
32
33impl StdError for ImportError {
34 fn source(&self) -> Option<&(dyn StdError + 'static)> {
35 match self {
36 Self::RustParse(error) => Some(error),
37 Self::Unsupported(error) => Some(error),
38 }
39 }
40}
41
42#[derive(Debug, Default)]
43struct Importer {
44 ids: IdAllocator,
45}
46
47#[derive(Debug, Default)]
48struct IdAllocator {
49 counts: BTreeMap<&'static str, usize>,
50}
51
52impl IdAllocator {
53 fn next(&mut self, prefix: &'static str) -> String {
54 let next = self.counts.entry(prefix).or_insert(0);
55 *next += 1;
56 format!("{prefix}{next:04}")
57 }
58}
59
60#[derive(Debug)]
61struct Placement {
62 slot: Option<&'static str>,
63 rank: Option<String>,
64}
65
66impl Placement {
67 fn file_item() -> Self {
68 Self {
69 slot: Some("file_items"),
70 rank: None,
71 }
72 }
73
74 fn ranked(slot: &'static str, index: usize) -> Self {
75 Self {
76 slot: Some(slot),
77 rank: Some(format!("r{:04}", index + 1)),
78 }
79 }
80}
81
82impl Importer {
83 fn import_file(mut self, file: &syn::File) -> Result<ast::File, ImportError> {
84 if file.shebang.is_some() {
85 return Err(unsupported(
86 file,
87 "shebang lines are unsupported in Rust import",
88 ));
89 }
90 self.check_attrs(&file.attrs, "file")?;
91 let items = file
92 .items
93 .iter()
94 .map(|item| self.import_item(item, Placement::file_item()))
95 .collect::<Result<Vec<_>, _>>()?;
96 Ok(ast::File { items })
97 }
98
99 fn import_item(
100 &mut self,
101 item: &syn::Item,
102 placement: Placement,
103 ) -> Result<ast::Item, ImportError> {
104 match item {
105 syn::Item::Mod(node) => self.import_mod(node, placement),
106 syn::Item::Use(node) => self.import_use(node, placement),
107 syn::Item::Struct(node) => self.import_struct(node, placement),
108 syn::Item::Enum(node) => self.import_enum(node, placement),
109 syn::Item::Fn(node) => self.import_fn(node, placement),
110 syn::Item::Macro(node) => Err(unsupported(node, "macro items are unsupported")),
111 _ => Err(unsupported(
112 item,
113 "only `mod`, `use`, `struct`, `enum`, and `fn` items are supported",
114 )),
115 }
116 }
117
118 fn import_mod(
119 &mut self,
120 node: &syn::ItemMod,
121 placement: Placement,
122 ) -> Result<ast::Item, ImportError> {
123 self.check_attrs(&node.attrs, "module")?;
124 self.ensure_inherited_visibility(&node.vis, "module visibility modifiers are unsupported")?;
125 if node.unsafety.is_some() {
126 return Err(unsupported(node, "`unsafe mod` is unsupported"));
127 }
128 let Some((_, items)) = &node.content else {
129 return Err(unsupported(
130 node,
131 "out-of-line `mod foo;` declarations are unsupported",
132 ));
133 };
134 let items = items
135 .iter()
136 .enumerate()
137 .map(|(index, item)| self.import_item(item, Placement::ranked("items", index)))
138 .collect::<Result<Vec<_>, _>>()?;
139 Ok(ast::Item::Mod(ast::ItemMod {
140 meta: self.meta("m", placement),
141 name: node.ident.to_string(),
142 items,
143 }))
144 }
145
146 fn import_use(
147 &mut self,
148 node: &syn::ItemUse,
149 placement: Placement,
150 ) -> Result<ast::Item, ImportError> {
151 self.check_attrs(&node.attrs, "use item")?;
152 self.ensure_inherited_visibility(
153 &node.vis,
154 "use item visibility modifiers are unsupported",
155 )?;
156 if node.leading_colon.is_some() {
157 return Err(unsupported(
158 node,
159 "leading `::` in `use` items is unsupported",
160 ));
161 }
162 Ok(ast::Item::Use(ast::ItemUse {
163 meta: self.meta("u", placement),
164 tree: self.import_use_tree(&node.tree)?,
165 }))
166 }
167
168 fn import_use_tree(&mut self, tree: &syn::UseTree) -> Result<ast::UseTree, ImportError> {
169 match tree {
170 syn::UseTree::Name(node) => Ok(ast::UseTree::Name(ast::UseName {
171 name: node.ident.to_string(),
172 })),
173 syn::UseTree::Path(node) => Ok(ast::UseTree::Path(ast::UsePathTree {
174 prefix: node.ident.to_string(),
175 tree: Box::new(self.import_use_tree(&node.tree)?),
176 })),
177 syn::UseTree::Group(node) => Ok(ast::UseTree::Group(ast::UseGroup {
178 items: node
179 .items
180 .iter()
181 .map(|item| self.import_use_tree(item))
182 .collect::<Result<Vec<_>, _>>()?,
183 })),
184 syn::UseTree::Glob(_) => Ok(ast::UseTree::Glob(ast::UseGlob)),
185 syn::UseTree::Rename(node) => Err(unsupported(
186 node,
187 "`use path as name` renames are unsupported",
188 )),
189 }
190 }
191
192 fn import_struct(
193 &mut self,
194 node: &syn::ItemStruct,
195 placement: Placement,
196 ) -> Result<ast::Item, ImportError> {
197 self.check_attrs(&node.attrs, "struct")?;
198 self.ensure_inherited_visibility(&node.vis, "struct visibility modifiers are unsupported")?;
199 self.ensure_no_generics(&node.generics, "struct generics are unsupported")?;
200 let syn::Fields::Named(fields) = &node.fields else {
201 return Err(unsupported(
202 node,
203 "only named-field structs are supported in Rust import",
204 ));
205 };
206 let fields = fields
207 .named
208 .iter()
209 .enumerate()
210 .map(|(index, field)| self.import_field(field, index))
211 .collect::<Result<Vec<_>, _>>()?;
212 Ok(ast::Item::Struct(ast::ItemStruct {
213 meta: self.meta("st", placement),
214 name: node.ident.to_string(),
215 fields,
216 }))
217 }
218
219 fn import_field(
220 &mut self,
221 field: &syn::Field,
222 index: usize,
223 ) -> Result<ast::Field, ImportError> {
224 self.check_attrs(&field.attrs, "struct field")?;
225 self.ensure_inherited_visibility(&field.vis, "field visibility modifiers are unsupported")?;
226 let Some(ident) = &field.ident else {
227 return Err(unsupported(field, "only named struct fields are supported"));
228 };
229 Ok(ast::Field {
230 meta: self.meta("fd", Placement::ranked("fields", index)),
231 name: ident.to_string(),
232 ty: self.import_type(&field.ty)?,
233 })
234 }
235
236 fn import_enum(
237 &mut self,
238 node: &syn::ItemEnum,
239 placement: Placement,
240 ) -> Result<ast::Item, ImportError> {
241 self.check_attrs(&node.attrs, "enum")?;
242 self.ensure_inherited_visibility(&node.vis, "enum visibility modifiers are unsupported")?;
243 self.ensure_no_generics(&node.generics, "enum generics are unsupported")?;
244 let variants = node
245 .variants
246 .iter()
247 .enumerate()
248 .map(|(index, variant)| self.import_variant(variant, index))
249 .collect::<Result<Vec<_>, _>>()?;
250 Ok(ast::Item::Enum(ast::ItemEnum {
251 meta: self.meta("en", placement),
252 name: node.ident.to_string(),
253 variants,
254 }))
255 }
256
257 fn import_variant(
258 &mut self,
259 variant: &syn::Variant,
260 index: usize,
261 ) -> Result<ast::Variant, ImportError> {
262 self.check_attrs(&variant.attrs, "enum variant")?;
263 if !matches!(variant.fields, syn::Fields::Unit) {
264 return Err(unsupported(
265 variant,
266 "only unit enum variants are supported in Rust import",
267 ));
268 }
269 if variant.discriminant.is_some() {
270 return Err(unsupported(
271 variant,
272 "enum variant discriminants are unsupported",
273 ));
274 }
275 Ok(ast::Variant {
276 meta: self.meta("v", Placement::ranked("variants", index)),
277 name: variant.ident.to_string(),
278 })
279 }
280
281 fn import_fn(
282 &mut self,
283 node: &syn::ItemFn,
284 placement: Placement,
285 ) -> Result<ast::Item, ImportError> {
286 self.check_attrs(&node.attrs, "function")?;
287 self.ensure_inherited_visibility(
288 &node.vis,
289 "function visibility modifiers are unsupported",
290 )?;
291 self.ensure_supported_signature(&node.sig)?;
292 let params = node
293 .sig
294 .inputs
295 .iter()
296 .enumerate()
297 .map(|(index, arg)| self.import_param(arg, index))
298 .collect::<Result<Vec<_>, _>>()?;
299 let ret_ty = match &node.sig.output {
300 syn::ReturnType::Default => None,
301 syn::ReturnType::Type(_, ty) => Some(self.import_type(ty)?),
302 };
303 Ok(ast::Item::Fn(ast::ItemFn {
304 meta: self.meta("f", placement),
305 name: node.sig.ident.to_string(),
306 params,
307 ret_ty,
308 body: self.import_block(&node.block)?,
309 }))
310 }
311
312 fn import_param(&mut self, arg: &syn::FnArg, index: usize) -> Result<ast::Param, ImportError> {
313 let syn::FnArg::Typed(node) = arg else {
314 return Err(unsupported(
315 arg,
316 "method receivers are unsupported; only free functions are supported",
317 ));
318 };
319 self.check_attrs(&node.attrs, "function parameter")?;
320 let name = self.import_param_name(&node.pat)?;
321 Ok(ast::Param {
322 meta: self.meta("p", Placement::ranked("params", index)),
323 name,
324 ty: self.import_type(&node.ty)?,
325 })
326 }
327
328 fn import_param_name(&mut self, pat: &syn::Pat) -> Result<String, ImportError> {
329 match pat {
330 syn::Pat::Ident(node) => {
331 self.check_attrs(&node.attrs, "function parameter pattern")?;
332 if node.by_ref.is_some() || node.mutability.is_some() || node.subpat.is_some() {
333 return Err(unsupported(
334 node,
335 "parameter patterns must be plain identifiers",
336 ));
337 }
338 Ok(node.ident.to_string())
339 }
340 _ => Err(unsupported(
341 pat,
342 "parameter patterns must be plain identifiers",
343 )),
344 }
345 }
346
347 fn import_block(&mut self, block: &syn::Block) -> Result<ast::Block, ImportError> {
348 let stmts = block
349 .stmts
350 .iter()
351 .enumerate()
352 .map(|(index, stmt)| self.import_stmt(stmt, index))
353 .collect::<Result<Vec<_>, _>>()?;
354 Ok(ast::Block { meta: None, stmts })
355 }
356
357 fn import_stmt(&mut self, stmt: &syn::Stmt, index: usize) -> Result<ast::Stmt, ImportError> {
358 match stmt {
359 syn::Stmt::Local(node) => {
360 self.check_attrs(&node.attrs, "let statement")?;
361 let Some(init) = &node.init else {
362 return Err(unsupported(
363 node,
364 "`let` statements without initializers are unsupported",
365 ));
366 };
367 if init.diverge.is_some() {
368 return Err(unsupported(node, "`let ... else` is unsupported"));
369 }
370 Ok(ast::Stmt::Let(ast::StmtLet {
371 meta: self.meta("s", Placement::ranked("body", index)),
372 pat: self.import_pattern(&node.pat)?,
373 value: self.import_expr(&init.expr)?,
374 }))
375 }
376 syn::Stmt::Item(item) => Ok(ast::Stmt::Item(
377 self.import_item(item, Placement::ranked("body", index))?,
378 )),
379 syn::Stmt::Expr(expr, semi) => Ok(ast::Stmt::Expr(ast::StmtExpr {
380 meta: self.meta("s", Placement::ranked("body", index)),
381 expr: self.import_expr(expr)?,
382 has_semi: semi.is_some(),
383 })),
384 syn::Stmt::Macro(node) => Err(unsupported(node, "statement macros are unsupported")),
385 }
386 }
387
388 fn import_pattern(&mut self, pat: &syn::Pat) -> Result<ast::Pattern, ImportError> {
389 match pat {
390 syn::Pat::Ident(node) => {
391 self.check_attrs(&node.attrs, "pattern")?;
392 if node.by_ref.is_some() || node.mutability.is_some() || node.subpat.is_some() {
393 return Err(unsupported(
394 node,
395 "only plain identifier and wildcard patterns are supported",
396 ));
397 }
398 Ok(ast::Pattern::Ident(ast::PatIdent {
399 meta: Some(self.detached_meta("pt")),
400 name: node.ident.to_string(),
401 }))
402 }
403 syn::Pat::Wild(node) => {
404 self.check_attrs(&node.attrs, "pattern")?;
405 Ok(ast::Pattern::Wild(ast::PatWild {
406 meta: Some(self.detached_meta("pt")),
407 }))
408 }
409 _ => Err(unsupported(
410 pat,
411 "only plain identifier and wildcard patterns are supported",
412 )),
413 }
414 }
415
416 fn import_type(&mut self, ty: &syn::Type) -> Result<ast::Type, ImportError> {
417 match ty {
418 syn::Type::Path(node) => Ok(ast::Type::Path(ast::TypePath {
419 meta: self.detached_meta("t"),
420 path: self.import_path(&node.path, node.qself.is_some(), "type paths")?,
421 })),
422 _ => Err(unsupported(ty, "only path types are supported")),
423 }
424 }
425
426 fn import_expr(&mut self, expr: &syn::Expr) -> Result<ast::Expr, ImportError> {
427 self.import_expr_with_root_meta(expr, true)
428 }
429
430 fn import_expr_with_root_meta(
431 &mut self,
432 expr: &syn::Expr,
433 allow_root_meta: bool,
434 ) -> Result<ast::Expr, ImportError> {
435 match expr {
436 syn::Expr::Path(node) => {
437 self.check_attrs(&node.attrs, "expression")?;
438 Ok(ast::Expr::Path(ast::ExprPath {
439 meta: allow_root_meta.then(|| self.detached_meta("e")),
440 path: self.import_path(&node.path, node.qself.is_some(), "expression paths")?,
441 }))
442 }
443 syn::Expr::Lit(node) => {
444 self.check_attrs(&node.attrs, "expression")?;
445 Ok(ast::Expr::Lit(ast::ExprLit {
446 meta: allow_root_meta.then(|| self.detached_meta("l")),
447 value: self.import_lit(&node.lit, node)?,
448 }))
449 }
450 syn::Expr::Paren(node) => {
451 self.check_attrs(&node.attrs, "expression")?;
452 Ok(ast::Expr::Group(ast::ExprGroup {
453 meta: allow_root_meta.then(|| self.detached_meta("e")),
454 expr: Box::new(self.import_expr_with_root_meta(&node.expr, true)?),
455 }))
456 }
457 syn::Expr::Group(node) => {
458 self.check_attrs(&node.attrs, "expression")?;
459 Ok(ast::Expr::Group(ast::ExprGroup {
460 meta: allow_root_meta.then(|| self.detached_meta("e")),
461 expr: Box::new(self.import_expr_with_root_meta(&node.expr, true)?),
462 }))
463 }
464 syn::Expr::Binary(node) => {
465 self.check_attrs(&node.attrs, "expression")?;
466 Ok(ast::Expr::Binary(ast::ExprBinary {
467 meta: allow_root_meta.then(|| self.detached_meta("e")),
468 lhs: Box::new(self.import_expr_with_root_meta(&node.left, false)?),
469 op: self.import_bin_op(&node.op)?,
470 rhs: Box::new(self.import_expr_with_root_meta(&node.right, true)?),
471 }))
472 }
473 syn::Expr::Unary(node) => {
474 self.check_attrs(&node.attrs, "expression")?;
475 let syn::UnOp::Neg(_) = node.op else {
476 return Err(unsupported(
477 node,
478 "only unary minus is supported in expressions",
479 ));
480 };
481 Ok(ast::Expr::Unary(ast::ExprUnary {
482 meta: allow_root_meta.then(|| self.detached_meta("e")),
483 op: ast::UnaryOp::Neg,
484 expr: Box::new(self.import_expr_with_root_meta(&node.expr, true)?),
485 }))
486 }
487 syn::Expr::Call(node) => {
488 self.check_attrs(&node.attrs, "expression")?;
489 Ok(ast::Expr::Call(ast::ExprCall {
490 meta: allow_root_meta.then(|| self.detached_meta("e")),
491 callee: Box::new(self.import_expr_with_root_meta(&node.func, false)?),
492 args: node
493 .args
494 .iter()
495 .map(|arg| self.import_expr_with_root_meta(arg, true))
496 .collect::<Result<Vec<_>, _>>()?,
497 }))
498 }
499 syn::Expr::Match(node) => {
500 self.check_attrs(&node.attrs, "expression")?;
501 Ok(ast::Expr::Match(ast::ExprMatch {
502 meta: allow_root_meta.then(|| self.detached_meta("e")),
503 scrutinee: Box::new(self.import_expr_with_root_meta(&node.expr, true)?),
504 arms: node
505 .arms
506 .iter()
507 .enumerate()
508 .map(|(index, arm)| self.import_arm(arm, index))
509 .collect::<Result<Vec<_>, _>>()?,
510 }))
511 }
512 syn::Expr::Block(node) => {
513 self.check_attrs(&node.attrs, "expression")?;
514 if node.label.is_some() {
515 return Err(unsupported(
516 node,
517 "labeled block expressions are unsupported",
518 ));
519 }
520 let mut block = self.import_block(&node.block)?;
521 block.meta = allow_root_meta.then(|| self.detached_meta("e"));
522 Ok(ast::Expr::Block(block))
523 }
524 _ => Err(unsupported(
525 expr,
526 "expression is outside the currently supported Rust import subset",
527 )),
528 }
529 }
530
531 fn import_lit(
532 &mut self,
533 lit: &syn::Lit,
534 span_node: &impl Spanned,
535 ) -> Result<ast::Literal, ImportError> {
536 match lit {
537 syn::Lit::Int(node) => {
538 if !node.suffix().is_empty() {
539 return Err(unsupported(
540 span_node,
541 "integer literal suffixes are unsupported",
542 ));
543 }
544 node.base10_parse::<i64>()
545 .map(ast::Literal::Int)
546 .map_err(|_| {
547 unsupported(
548 span_node,
549 "only decimal integer literals that fit in `i64` are supported",
550 )
551 })
552 }
553 syn::Lit::Str(node) => Ok(ast::Literal::Str(node.value())),
554 _ => Err(unsupported(
555 span_node,
556 "only integer and string literals are supported",
557 )),
558 }
559 }
560
561 fn import_bin_op(&mut self, op: &syn::BinOp) -> Result<ast::BinaryOp, ImportError> {
562 match op {
563 syn::BinOp::Add(_) => Ok(ast::BinaryOp::Add),
564 syn::BinOp::Sub(_) => Ok(ast::BinaryOp::Sub),
565 syn::BinOp::Lt(_) => Ok(ast::BinaryOp::Lt),
566 _ => Err(unsupported(
567 op,
568 "only `+`, `-`, and `<` binary operators are supported",
569 )),
570 }
571 }
572
573 fn import_arm(&mut self, arm: &syn::Arm, index: usize) -> Result<ast::MatchArm, ImportError> {
574 self.check_attrs(&arm.attrs, "match arm")?;
575 Ok(ast::MatchArm {
576 meta: self.meta("a", Placement::ranked("arms", index)),
577 pat: self.import_pattern(&arm.pat)?,
578 guard: arm
579 .guard
580 .as_ref()
581 .map(|(_, expr)| self.import_expr(expr))
582 .transpose()?,
583 body: self.import_expr(&arm.body)?,
584 })
585 }
586
587 fn import_path(
588 &mut self,
589 path: &syn::Path,
590 has_qself: bool,
591 context: &str,
592 ) -> Result<ast::Path, ImportError> {
593 if has_qself {
594 return Err(unsupported(
595 path,
596 format!("qualified self in {context} is unsupported"),
597 ));
598 }
599 if path.leading_colon.is_some() {
600 return Err(unsupported(
601 path,
602 format!("leading `::` in {context} is unsupported"),
603 ));
604 }
605 let mut segments = Vec::with_capacity(path.segments.len());
606 for segment in &path.segments {
607 if !matches!(segment.arguments, syn::PathArguments::None) {
608 return Err(unsupported(
609 segment,
610 format!("generic arguments in {context} are unsupported"),
611 ));
612 }
613 segments.push(segment.ident.to_string());
614 }
615 Ok(ast::Path { segments })
616 }
617
618 fn ensure_supported_signature(&self, sig: &syn::Signature) -> Result<(), ImportError> {
619 if sig.constness.is_some() {
620 return Err(unsupported(sig, "`const fn` is unsupported"));
621 }
622 if sig.asyncness.is_some() {
623 return Err(unsupported(sig, "`async fn` is unsupported"));
624 }
625 if sig.unsafety.is_some() {
626 return Err(unsupported(sig, "`unsafe fn` is unsupported"));
627 }
628 if sig.abi.is_some() {
629 return Err(unsupported(sig, "extern function ABIs are unsupported"));
630 }
631 self.ensure_no_generics(&sig.generics, "function generics are unsupported")?;
632 if sig.variadic.is_some() {
633 return Err(unsupported(sig, "variadic functions are unsupported"));
634 }
635 Ok(())
636 }
637
638 fn ensure_no_generics(
639 &self,
640 generics: &syn::Generics,
641 message: &str,
642 ) -> Result<(), ImportError> {
643 if !generics.params.is_empty() || generics.where_clause.is_some() {
644 return Err(unsupported(generics, message));
645 }
646 Ok(())
647 }
648
649 fn ensure_inherited_visibility(
650 &self,
651 vis: &syn::Visibility,
652 message: &str,
653 ) -> Result<(), ImportError> {
654 if !matches!(vis, syn::Visibility::Inherited) {
655 return Err(unsupported(vis, message));
656 }
657 Ok(())
658 }
659
660 fn check_attrs(&self, attrs: &[syn::Attribute], context: &str) -> Result<(), ImportError> {
661 for attr in attrs {
662 if attr.path().is_ident("doc") {
663 continue;
664 }
665 return Err(unsupported(
666 attr,
667 format!("unsupported attribute on {context}; only doc comments are ignored"),
668 ));
669 }
670 Ok(())
671 }
672
673 fn meta(&mut self, prefix: &'static str, placement: Placement) -> ast::Meta {
674 ast::Meta {
675 id: self.ids.next(prefix),
676 rank: placement.rank,
677 anchor: None,
678 slot: placement.slot.map(str::to_owned),
679 span: None,
680 }
681 }
682
683 fn detached_meta(&mut self, prefix: &'static str) -> ast::Meta {
684 ast::Meta {
685 id: self.ids.next(prefix),
686 rank: None,
687 anchor: None,
688 slot: None,
689 span: None,
690 }
691 }
692}
693
694fn unsupported<T: Spanned>(node: &T, message: impl Into<String>) -> ImportError {
695 ImportError::Unsupported(syn::Error::new(node.span(), message.into()))
696}
697
698#[cfg(test)]
699mod tests {
700 use super::import_source;
701 use crate::lower_file;
702 use draxl_ast as ast;
703
704 fn lower_imported(imported: &ast::File) -> String {
705 lower_file(imported)
706 }
707
708 #[test]
709 fn imports_a_simple_function_body() {
710 let source = r#"
711mod demo {
712 fn add_one(x: i64) -> i64 {
713 let y = (x + 1);
714 y
715 }
716}
717"#;
718
719 let imported = import_source(source).expect("simple function should import");
720
721 let [ast::Item::Mod(module)] = imported.items.as_slice() else {
722 panic!("expected one imported module, found {:?}", imported.items);
723 };
724 assert_eq!(module.meta.id, "m0001");
725 let [ast::Item::Fn(function)] = module.items.as_slice() else {
726 panic!("expected one imported function, found {:?}", module.items);
727 };
728 assert_eq!(function.meta.id, "f0001");
729 assert_eq!(function.params[0].meta.id, "p0001");
730 assert_eq!(function.body.stmts.len(), 2);
731 assert_eq!(
732 lower_imported(&imported),
733 "mod demo {\n fn add_one(x: i64) -> i64 {\n let y = (x + 1);\n y\n }\n}\n\n"
734 );
735 }
736
737 #[test]
738 fn imports_match_use_and_shapes() {
739 let source = r#"
740mod shapes {
741 use std::cmp::{self, *};
742
743 struct Point {
744 x: i64,
745 y: i64,
746 }
747
748 enum Color {
749 Red,
750 Green,
751 }
752
753 fn abs(x: i64) -> i64 {
754 match x {
755 n if (n < 0) => (-n),
756 _ => x,
757 }
758 }
759}
760"#;
761
762 let imported = import_source(source).expect("supported Rust subset should import");
763
764 let [ast::Item::Mod(module)] = imported.items.as_slice() else {
765 panic!("expected one imported module, found {:?}", imported.items);
766 };
767 assert_eq!(module.items.len(), 4);
768 assert!(matches!(
769 &module.items[0],
770 ast::Item::Use(node) if node.meta.id == "u0001"
771 ));
772 assert!(matches!(
773 &module.items[1],
774 ast::Item::Struct(node) if node.meta.id == "st0001" && node.fields.len() == 2
775 ));
776 assert!(matches!(
777 &module.items[2],
778 ast::Item::Enum(node) if node.meta.id == "en0001" && node.variants.len() == 2
779 ));
780 assert!(matches!(
781 &module.items[3],
782 ast::Item::Fn(node) if node.name == "abs" && node.body.stmts.len() == 1
783 ));
784 assert_eq!(
785 lower_imported(&imported),
786 "mod shapes {\n use std::cmp::{self, *};\n\n struct Point {\n x: i64,\n y: i64,\n }\n\n enum Color {\n Red,\n Green,\n }\n\n fn abs(x: i64) -> i64 {\n match x {\n n if (n < 0) => (-n),\n _ => x,\n }\n }\n}\n\n"
787 );
788 }
789
790 #[test]
791 fn rejects_generics_and_visibility() {
792 let error =
793 import_source("pub fn run<T>(x: T) {}\n").expect_err("unsupported syntax should fail");
794 let message = error.to_string();
795 assert!(
796 message.contains("visibility modifiers") || message.contains("generics"),
797 "unexpected error: {message}"
798 );
799 }
800
801 #[test]
802 fn rejects_macros_and_tuple_structs() {
803 let macro_error = import_source("fn run() { println!(\"hi\"); }\n")
804 .expect_err("statement macro should fail");
805 assert!(
806 macro_error
807 .to_string()
808 .contains("statement macros are unsupported")
809 || macro_error.to_string().contains("expression is outside"),
810 "unexpected error: {macro_error}"
811 );
812
813 let struct_error =
814 import_source("struct Pair(i64, i64);\n").expect_err("tuple struct should fail");
815 assert!(
816 struct_error
817 .to_string()
818 .contains("only named-field structs are supported"),
819 "unexpected error: {struct_error}"
820 );
821 }
822}