diff --git a/agdb/Cargo.toml b/agdb/Cargo.toml index 38c67ae9..c87866db 100644 --- a/agdb/Cargo.toml +++ b/agdb/Cargo.toml @@ -27,3 +27,4 @@ serde = { version = "1", features = ["derive"], optional = true } [dev-dependencies] serde_json = { version = "1" } +tokio = { version = "1", features = ["macros", "rt"] } diff --git a/agdb/src/lib.rs b/agdb/src/lib.rs index 7bac028d..0ac4556e 100644 --- a/agdb/src/lib.rs +++ b/agdb/src/lib.rs @@ -45,7 +45,7 @@ pub use agdb_derive::{DbElement, DbSerialize, DbType, DbTypeMarker, DbValue}; #[cfg(feature = "api")] pub mod type_def; #[cfg(feature = "api")] -pub use agdb_derive::{TypeDef, TypeDefImpl, fn_def, impl_def, trait_def}; +pub use agdb_derive::{TypeDef, TypeDefImpl, fn_def, impl_def, test_def, trait_def}; pub use db::Db; pub use db::DbAny; diff --git a/agdb/src/type_def.rs b/agdb/src/type_def.rs index b65e37c2..15a4fa23 100644 --- a/agdb/src/type_def.rs +++ b/agdb/src/type_def.rs @@ -23,6 +23,7 @@ pub trait TypeDefinition { match Self::type_def() { Type::Enum(e) => e.generics.iter().map(|g| g.name).collect(), Type::Function(f) => f.generics.iter().map(|g| g.name).collect(), + Type::Test(f) => f.generics.iter().map(|g| g.name).collect(), Type::Struct(s) => s.generics.iter().map(|g| g.name).collect(), Type::Trait(t) => t.generics.iter().map(|g| g.name).collect(), Type::Impl(i) => i.generics.iter().map(|g| g.name).collect(), @@ -51,6 +52,7 @@ pub trait ImplDefinition: TypeDefinition { pub enum Type { Enum(Enum), Function(Function), + Test(Function), Generic(Generic), Impl(Impl), Literal(Literal), @@ -71,6 +73,7 @@ impl Type { match self { Type::Enum(e) => e.name, Type::Function(_) => "fn", + Type::Test(_) => "test", Type::Generic(g) => g.name, Type::Impl(i) => i.name, Type::Literal(l) => l.name(), @@ -379,6 +382,7 @@ mod tests { assert_eq!(def.name, "Type"); assert!(def.variants.iter().any(|v| v.name == "Function")); + assert!(def.variants.iter().any(|v| v.name == "Test")); assert!(def.variants.iter().any(|v| v.name == "Trait")); } diff --git a/agdb/src/type_def/expression_def.rs b/agdb/src/type_def/expression_def.rs index 733e11bb..ceab8dbe 100644 --- a/agdb/src/type_def/expression_def.rs +++ b/agdb/src/type_def/expression_def.rs @@ -188,6 +188,7 @@ mod tests { "tuple_expr" => __tuple_expr_type_def(), "format_expr" => __format_expr_type_def(), "vec_macro_expr" => __vec_macro_expr_type_def(), + "assert_macros_expr" => __assert_macros_expr_type_def(), "match_expr" => __match_expr_type_def(), "struct_expr" => __struct_expr_type_def(), "implicit_return" => __implicit_return_type_def(), @@ -1247,6 +1248,71 @@ mod tests { } } + #[agdb::fn_def] + #[allow(unused)] + fn assert_macros_expr() { + let x = 42; + assert!(x > 0); + assert_eq!(x, 42); + let _matched = matches!(Some(x), Some(42)); + } + + #[test] + fn test_assert_macros() { + let body = get_body("assert_macros_expr"); + assert_eq!(body.len(), 4); + + assert!( + matches!( + &body[1], + Expression::Call { + function: Expression::Path { + ident: "assert", + .. + }, + .. + } + ), + "Got: {:?}", + body[1] + ); + + assert!( + matches!( + &body[2], + Expression::Call { + function: Expression::Path { + ident: "assert_eq", + .. + }, + .. + } + ), + "Got: {:?}", + body[2] + ); + + match &body[3] { + Expression::Let { + value: Some(Expression::Call { function, .. }), + .. + } => { + assert!( + matches!( + function, + Expression::Path { + ident: "matches", + .. + } + ), + "Got: {:?}", + function + ); + } + _ => panic!("Got: {:?}", body[3]), + } + } + #[agdb::fn_def] #[allow(unused)] fn match_expr() -> i32 { diff --git a/agdb/src/type_def/function_def.rs b/agdb/src/type_def/function_def.rs index c2ef91d6..68c45eda 100644 --- a/agdb/src/type_def/function_def.rs +++ b/agdb/src/type_def/function_def.rs @@ -15,10 +15,95 @@ pub struct Function { #[cfg(test)] mod tests { + use crate::type_def::Expression; use crate::type_def::GenericKind; use crate::type_def::Literal; use crate::type_def::Type; + #[agdb::test_def] + #[test] + fn plain_test_function() { + let value = 1; + assert!(value == 1); + } + + #[agdb::test_def] + #[tokio::test] + async fn tokio_async_test_function() { + let value = 1; + assert_eq!(value, 1); + assert!(matches!(Some(value), Some(1))); + } + + #[test] + fn plain_test_type_def() { + let Type::Test(def) = __plain_test_function_type_def() else { + panic!("Expected a test type definition"); + }; + + assert_eq!(def.name, "plain_test_function"); + assert!(!def.async_fn); + assert_eq!(def.body.len(), 2); + assert!( + matches!( + &def.body[1], + Expression::Call { + function: Expression::Path { + ident: "assert", + .. + }, + .. + } + ), + "Got: {:?}", + def.body[1] + ); + } + + #[test] + fn tokio_test_type_def() { + let Type::Test(def) = __tokio_async_test_function_type_def() else { + panic!("Expected a test type definition"); + }; + + assert_eq!(def.name, "tokio_async_test_function"); + assert!(def.async_fn); + assert_eq!(def.body.len(), 3); + + assert!( + matches!( + &def.body[1], + Expression::Call { + function: Expression::Path { + ident: "assert_eq", + .. + }, + .. + } + ), + "Got: {:?}", + def.body[1] + ); + + let Expression::Call { args, .. } = &def.body[2] else { + panic!("Expected assert! call in body"); + }; + assert!( + matches!( + &args[0], + Expression::Call { + function: Expression::Path { + ident: "matches", + .. + }, + .. + } + ), + "Got: {:?}", + args[0] + ); + } + #[test] fn empty_function() { #[agdb::fn_def] diff --git a/agdb_derive/src/api_def.rs b/agdb_derive/src/api_def.rs deleted file mode 100644 index 3ef2fc41..00000000 --- a/agdb_derive/src/api_def.rs +++ /dev/null @@ -1,97 +0,0 @@ -pub(crate) mod enum_parser; -pub(crate) mod expression_parser; -pub(crate) mod function_parser; -pub(crate) mod generics_parser; -pub(crate) mod impl_parser; -pub(crate) mod struct_parser; -pub(crate) mod trait_parser; - -use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use syn::DeriveInput; -use syn::Ident; - -pub(crate) fn type_def_impl(item: TokenStream) -> TokenStream { - let input = syn::parse_macro_input!(item as DeriveInput); - - match &input.data { - syn::Data::Struct(s) => struct_parser::parse_struct(&input, s), - syn::Data::Enum(e) => enum_parser::parse_enum(&input, e), - _ => unimplemented!("Only structs and enums are supported for now"), - } - .into() -} - -pub(crate) fn type_def_impl_impl(item: TokenStream) -> TokenStream { - let it = item.clone(); - let def: TokenStream2 = type_def_impl(item).into(); - - let input = syn::parse_macro_input!(it as DeriveInput); - let name = input.ident; - let (impl_generics, ty_generic, where_clause) = input.generics.split_for_impl(); - - quote! { - #def - - impl #impl_generics ::agdb::api_def::ImplDefinition for #name #ty_generic #where_clause {} - } - .into() -} - -pub(crate) fn impl_def_impl(item: TokenStream) -> TokenStream { - let it: TokenStream2 = item.clone().into(); - let def_impl = if let Ok(input) = syn::parse::(item) { - impl_parser::parse_impl(&input) - } else { - unimplemented!("Only impl blocks are supported") - }; - - quote! { - #it - - #def_impl - } - .into() -} - -pub(crate) fn trait_def_impl(item: TokenStream) -> TokenStream { - let it: TokenStream2 = item.clone().into(); - let def_fn = if let Ok(input) = syn::parse::(item) { - trait_parser::parse_trait(&input) - } else { - unimplemented!("Only traits are supported") - }; - - quote! { - #it - - #def_fn - } - .into() -} - -pub(crate) fn fn_def_impl(item: TokenStream) -> TokenStream { - let it: TokenStream2 = item.clone().into(); - let def_fn = if let Ok(input) = syn::parse::(item) { - function_parser::parse_function(&input) - } else { - unimplemented!("Only functions are supported") - }; - - quote! { - #it - - #def_fn - } - .into() -} - -pub(crate) fn type_def_fn(name: &String) -> TokenStream2 { - let bound_fn_name = Ident::new( - &format!("__{name}_type_def"), - proc_macro2::Span::call_site(), - ); - - quote! { #bound_fn_name } -} diff --git a/agdb_derive/src/api_def/expression_parser.rs b/agdb_derive/src/api_def/expression_parser.rs deleted file mode 100644 index 138ae8ed..00000000 --- a/agdb_derive/src/api_def/expression_parser.rs +++ /dev/null @@ -1,1016 +0,0 @@ -use crate::api_def::generics_parser; -use crate::api_def::generics_parser::Generic; -use proc_macro2::TokenStream as TokenStream2; -use quote::ToTokens; -use quote::quote; -use syn::BinOp; -use syn::Block; -use syn::Expr; -use syn::ExprArray; -use syn::ExprBinary; -use syn::ExprCall; -use syn::ExprClosure; -use syn::ExprField; -use syn::ExprIndex; -use syn::ExprMacro; -use syn::ExprMethodCall; -use syn::ExprReference; -use syn::ExprStruct; -use syn::ExprTry; -use syn::ExprUnary; -use syn::FieldValue; -use syn::GenericArgument; -use syn::Lit; -use syn::Member; -use syn::Pat; -use syn::PatOr; -use syn::Path; -use syn::PathArguments; -use syn::PathSegment; -use syn::ReturnType; -use syn::Stmt; - -// --------------------------------------------------------------------------- -// Public entry points -// --------------------------------------------------------------------------- - -/// Parse a block into a list of expression token-streams (one per statement). -pub(crate) fn parse_block_stmts(block: &Block, generics: &[Generic]) -> Vec { - block - .stmts - .iter() - .enumerate() - .map(|(i, stmt)| { - let last = i + 1 == block.stmts.len(); - parse_statement(stmt, generics, last) - }) - .collect() -} - -/// Parse a block into an `::agdb::api_def::Expression::Block(...)`. -pub(crate) fn parse_block(block: &Block, generics: &[Generic]) -> TokenStream2 { - let expressions = parse_block_stmts(block, generics); - quote! { - ::agdb::api_def::Expression::Block(&[#(#expressions),*]) - } -} - -/// Parse a single `syn::Expr` into a `TokenStream2` that constructs an -/// `::agdb::api_def::Expression`. -pub(crate) fn parse_expression(e: &Expr, generics: &[Generic]) -> TokenStream2 { - match e { - Expr::Array(e) => parse_array(e, generics), - Expr::Assign(e) => parse_assign(e, generics), - Expr::Async(e) => parse_block(&e.block, generics), - Expr::Await(e) => parse_await(e, generics), - Expr::Binary(e) => parse_binary(e, generics), - Expr::Block(e) => parse_block(&e.block, generics), - Expr::Break(_) => quote! { ::agdb::api_def::Expression::Break }, - Expr::Call(e) => parse_call(e, generics), - Expr::Cast(e) => parse_expression(&e.expr, generics), - Expr::Closure(e) => parse_closure(e, generics), - Expr::Const(e) => parse_block(&e.block, generics), - Expr::Continue(_) => quote! { ::agdb::api_def::Expression::Continue }, - Expr::Field(e) => parse_field_access(e, generics), - Expr::ForLoop(e) => parse_for_loop(e, generics), - Expr::Group(e) => parse_expression(&e.expr, generics), - Expr::If(e) => parse_if(e, generics), - Expr::Index(e) => parse_index(e, generics), - Expr::Infer(_) => quote! { ::agdb::api_def::Expression::Wild }, - Expr::Let(e) => parse_let_expr(e, generics), - Expr::Lit(e) => parse_literal(&e.lit), - Expr::Loop(e) => parse_loop(e, generics), - Expr::Macro(e) => parse_macro(e, generics), - Expr::Match(e) => parse_match(e, generics), - Expr::MethodCall(e) => parse_method_call(e, generics), - Expr::Paren(e) => parse_expression(&e.expr, generics), - Expr::Path(e) => parse_path(&e.path), - Expr::Reference(e) => parse_reference(e, generics), - Expr::Return(e) => parse_return(e, generics), - Expr::Struct(e) => parse_struct(e, generics), - Expr::Try(e) => parse_try(e, generics), - Expr::Tuple(e) => parse_tuple(e, generics), - Expr::Unary(e) => parse_unary(e, generics), - Expr::While(e) => parse_while(e, generics), - _ => panic!("Unsupported expression: {}", e.to_token_stream()), - } -} - -// --------------------------------------------------------------------------- -// Statements -// --------------------------------------------------------------------------- - -fn parse_statement(stmt: &Stmt, generics: &[Generic], last: bool) -> TokenStream2 { - match stmt { - Stmt::Local(local) => parse_local(local, generics), - Stmt::Expr(expr, semi) => { - let parsed = parse_expression(expr, generics); - if last && semi.is_none() && is_returnable(expr) { - quote! { ::agdb::api_def::Expression::Return(Some(&#parsed)) } - } else { - parsed - } - } - Stmt::Item(_) => quote! { ::agdb::api_def::Expression::Block(&[]) }, - Stmt::Macro(m) => parse_stmt_macro(m, generics), - } -} - -fn parse_local(local: &syn::Local, generics: &[Generic]) -> TokenStream2 { - let (name, ty) = parse_pattern(&local.pat, generics); - let value = if let Some(init) = &local.init { - let expr = parse_expression(&init.expr, generics); - quote! { Some(&#expr) } - } else { - quote! { None } - }; - - quote! { - ::agdb::api_def::Expression::Let { - name: &#name, - ty: #ty, - value: #value, - } - } -} - -fn parse_stmt_macro(m: &syn::StmtMacro, generics: &[Generic]) -> TokenStream2 { - let name = path_to_string(&m.mac.path); - parse_macro_by_name(&name, &m.mac.tokens, generics) -} - -fn is_returnable(e: &Expr) -> bool { - !matches!( - e, - Expr::Return(_) - | Expr::Break(_) - | Expr::Continue(_) - | Expr::ForLoop(_) - | Expr::While(_) - | Expr::Loop(_) - | Expr::Match(_) - | Expr::If(_) - ) -} - -// --------------------------------------------------------------------------- -// Array / Index -// --------------------------------------------------------------------------- - -fn parse_array(e: &ExprArray, generics: &[Generic]) -> TokenStream2 { - let elements = e.elems.iter().map(|elem| parse_expression(elem, generics)); - quote! { - ::agdb::api_def::Expression::Array(&[#(#elements),*]) - } -} - -fn parse_index(e: &ExprIndex, generics: &[Generic]) -> TokenStream2 { - let base = parse_expression(&e.expr, generics); - let index = parse_expression(&e.index, generics); - quote! { - ::agdb::api_def::Expression::Index { - base: &#base, - index: &#index, - } - } -} - -// --------------------------------------------------------------------------- -// Assign -// --------------------------------------------------------------------------- - -fn parse_assign(e: &syn::ExprAssign, generics: &[Generic]) -> TokenStream2 { - let target = parse_expression(&e.left, generics); - let value = parse_expression(&e.right, generics); - quote! { - ::agdb::api_def::Expression::Assign { - target: &#target, - value: &#value, - } - } -} - -// --------------------------------------------------------------------------- -// Await -// --------------------------------------------------------------------------- - -fn parse_await(e: &syn::ExprAwait, generics: &[Generic]) -> TokenStream2 { - let expr = parse_expression(&e.base, generics); - quote! { - ::agdb::api_def::Expression::Await(&#expr) - } -} - -// --------------------------------------------------------------------------- -// Binary / Unary / Op -// --------------------------------------------------------------------------- - -fn parse_binary(e: &ExprBinary, generics: &[Generic]) -> TokenStream2 { - let left = parse_expression(&e.left, generics); - let right = parse_expression(&e.right, generics); - let op = parse_binop(&e.op); - quote! { - ::agdb::api_def::Expression::Binary { - op: #op, - left: &#left, - right: &#right, - } - } -} - -fn parse_unary(e: &ExprUnary, generics: &[Generic]) -> TokenStream2 { - let expr = parse_expression(&e.expr, generics); - let op = match &e.op { - syn::UnOp::Deref(_) => quote! { ::agdb::api_def::Op::Deref }, - syn::UnOp::Not(_) => quote! { ::agdb::api_def::Op::Not }, - syn::UnOp::Neg(_) => quote! { ::agdb::api_def::Op::Neg }, - _ => panic!("Unsupported unary operator: {:?}", e.op), - }; - quote! { - ::agdb::api_def::Expression::Unary { - op: #op, - expr: &#expr, - } - } -} - -fn parse_binop(op: &BinOp) -> TokenStream2 { - match op { - BinOp::Add(_) => quote! { ::agdb::api_def::Op::Add }, - BinOp::Sub(_) => quote! { ::agdb::api_def::Op::Sub }, - BinOp::Mul(_) => quote! { ::agdb::api_def::Op::Mul }, - BinOp::Div(_) => quote! { ::agdb::api_def::Op::Div }, - BinOp::Rem(_) => quote! { ::agdb::api_def::Op::Rem }, - BinOp::BitXor(_) => quote! { ::agdb::api_def::Op::BitXor }, - BinOp::BitAnd(_) => quote! { ::agdb::api_def::Op::BitAnd }, - BinOp::BitOr(_) => quote! { ::agdb::api_def::Op::BitOr }, - BinOp::Lt(_) => quote! { ::agdb::api_def::Op::Lt }, - BinOp::Gt(_) => quote! { ::agdb::api_def::Op::Gt }, - BinOp::And(_) => quote! { ::agdb::api_def::Op::And }, - BinOp::Or(_) => quote! { ::agdb::api_def::Op::Or }, - BinOp::Shl(_) => quote! { ::agdb::api_def::Op::Shl }, - BinOp::Shr(_) => quote! { ::agdb::api_def::Op::Shr }, - BinOp::Eq(_) => quote! { ::agdb::api_def::Op::Eq }, - BinOp::Le(_) => quote! { ::agdb::api_def::Op::Le }, - BinOp::Ne(_) => quote! { ::agdb::api_def::Op::Ne }, - BinOp::Ge(_) => quote! { ::agdb::api_def::Op::Ge }, - BinOp::AddAssign(_) => quote! { ::agdb::api_def::Op::AddAssign }, - BinOp::SubAssign(_) => quote! { ::agdb::api_def::Op::SubAssign }, - BinOp::MulAssign(_) => quote! { ::agdb::api_def::Op::MulAssign }, - BinOp::DivAssign(_) => quote! { ::agdb::api_def::Op::DivAssign }, - BinOp::RemAssign(_) => quote! { ::agdb::api_def::Op::RemAssign }, - BinOp::BitXorAssign(_) => quote! { ::agdb::api_def::Op::BitXorAssign }, - BinOp::BitAndAssign(_) => quote! { ::agdb::api_def::Op::BitAndAssign }, - BinOp::BitOrAssign(_) => quote! { ::agdb::api_def::Op::BitOrAssign }, - BinOp::ShlAssign(_) => quote! { ::agdb::api_def::Op::ShlAssign }, - BinOp::ShrAssign(_) => quote! { ::agdb::api_def::Op::ShrAssign }, - _ => panic!("Unsupported binary operator"), - } -} - -// --------------------------------------------------------------------------- -// Call / MethodCall -// --------------------------------------------------------------------------- - -fn parse_call(e: &ExprCall, generics: &[Generic]) -> TokenStream2 { - let function = parse_expression(&e.func, generics); - let args = e.args.iter().map(|arg| parse_expression(arg, generics)); - quote! { - ::agdb::api_def::Expression::Call { - recipient: None, - function: &#function, - args: &[#(#args),*], - } - } -} - -fn parse_method_call(e: &ExprMethodCall, generics: &[Generic]) -> TokenStream2 { - let recipient = parse_expression(&e.receiver, generics); - let method = &e.method; - let turbofish_generics = e - .turbofish - .as_ref() - .map(|gt| { - gt.args - .iter() - .filter_map(|ga| match ga { - GenericArgument::Type(ty) => Some(quote! { <#ty as ::agdb::api_def::TypeDefinition>::type_def }), - _ => None, - }) - .collect::>() - }) - .unwrap_or_default(); - let args = e.args.iter().map(|arg| parse_expression(arg, generics)); - quote! { - ::agdb::api_def::Expression::Call { - recipient: Some(&#recipient), - function: &::agdb::api_def::Expression::Path { - ident: stringify!(#method), - parent: None, - generics: &[#(#turbofish_generics),*], - }, - args: &[#(#args),*], - } - } -} - -// --------------------------------------------------------------------------- -// Closure -// --------------------------------------------------------------------------- - -fn parse_closure(e: &ExprClosure, generics: &[Generic]) -> TokenStream2 { - let args: Vec = e - .inputs - .iter() - .map(|pat| { - let (name_tokens, ty_tokens) = parse_closure_arg(pat, generics); - quote! { - ::agdb::api_def::NamedType { - name: stringify!(#name_tokens), - ty: Some(#ty_tokens), - } - } - }) - .collect(); - let async_fn = e.asyncness.is_some(); - let ret = parse_return_type(&e.output, generics); - let body = match e.body.as_ref() { - Expr::Block(body) => parse_block_stmts(&body.block, generics), - other => { - let expr = parse_expression(other, generics); - vec![quote! { ::agdb::api_def::Expression::Return(Some(&#expr)) }] - } - }; - - quote! { - ::agdb::api_def::Expression::Closure(::agdb::api_def::Function { - name: "", - generics: &[], - args: &[#(#args),*], - ret: #ret, - async_fn: #async_fn, - body: &[#(#body),*], - }) - } -} - -fn parse_closure_arg(pat: &Pat, generics: &[Generic]) -> (TokenStream2, TokenStream2) { - match pat { - Pat::Type(p) => { - let name = extract_pat_ident(&p.pat); - let ty = generics_parser::parse_type(&p.ty, generics); - (quote! { #name }, ty) - } - Pat::Ident(p) => { - let name = &p.ident; - (quote! { #name }, quote! { <() as ::agdb::api_def::TypeDefinition>::type_def }) - } - _ => panic!( - "Unsupported closure argument pattern: {}", - pat.to_token_stream() - ), - } -} - -fn extract_pat_ident(pat: &Pat) -> &syn::Ident { - match pat { - Pat::Ident(p) => &p.ident, - _ => panic!( - "Expected identifier pattern, got: {}", - pat.to_token_stream() - ), - } -} - -fn parse_return_type(ret: &ReturnType, generics: &[Generic]) -> TokenStream2 { - match ret { - ReturnType::Default => quote! { <() as ::agdb::api_def::TypeDefinition>::type_def }, - ReturnType::Type(_, ty) => generics_parser::parse_type(ty, generics), - } -} - -// --------------------------------------------------------------------------- -// Field access / Tuple access -// --------------------------------------------------------------------------- - -fn parse_field_access(e: &ExprField, generics: &[Generic]) -> TokenStream2 { - let base = parse_expression(&e.base, generics); - match &e.member { - Member::Named(ident) => { - quote! { - ::agdb::api_def::Expression::FieldAccess { - base: &#base, - field: stringify!(#ident), - } - } - } - Member::Unnamed(index) => { - let idx = index.index; - quote! { - ::agdb::api_def::Expression::TupleAccess { - base: &#base, - index: #idx, - } - } - } - } -} - -// --------------------------------------------------------------------------- -// Loops -// --------------------------------------------------------------------------- - -fn parse_for_loop(e: &syn::ExprForLoop, generics: &[Generic]) -> TokenStream2 { - let (pattern, _) = parse_pattern(&e.pat, generics); - let iterable = parse_expression(&e.expr, generics); - let body = parse_block(&e.body, generics); - quote! { - ::agdb::api_def::Expression::For { - pattern: &#pattern, - iterable: &#iterable, - body: &#body, - } - } -} - -fn parse_loop(e: &syn::ExprLoop, generics: &[Generic]) -> TokenStream2 { - let body = parse_block(&e.body, generics); - quote! { - ::agdb::api_def::Expression::While { - condition: &::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::Bool(true)), - body: &#body, - } - } -} - -fn parse_while(e: &syn::ExprWhile, generics: &[Generic]) -> TokenStream2 { - let condition = parse_expression(&e.cond, generics); - let body = parse_block(&e.body, generics); - quote! { - ::agdb::api_def::Expression::While { - condition: &#condition, - body: &#body, - } - } -} - -// --------------------------------------------------------------------------- -// If / Match -// --------------------------------------------------------------------------- - -fn parse_if(e: &syn::ExprIf, generics: &[Generic]) -> TokenStream2 { - let condition = parse_expression(&e.cond, generics); - let then_branch = parse_block(&e.then_branch, generics); - - let else_branch = if let Some((_, else_expr)) = &e.else_branch { - let else_tokens = match else_expr.as_ref() { - Expr::If(else_if) => parse_if(else_if, generics), - Expr::Block(else_block) => parse_block(&else_block.block, generics), - _ => panic!("Unsupported else branch"), - }; - quote! { Some(&#else_tokens) } - } else { - quote! { None } - }; - - quote! { - ::agdb::api_def::Expression::If { - condition: &#condition, - then_branch: &#then_branch, - else_branch: #else_branch, - } - } -} - -fn parse_match(e: &syn::ExprMatch, generics: &[Generic]) -> TokenStream2 { - let subject = parse_expression(&e.expr, generics); - let mut branches = Vec::new(); - let mut else_branch: Option = None; - - for arm in &e.arms { - if matches!(&arm.pat, Pat::Wild(_)) { - else_branch = Some(parse_match_arm_body(&arm.body, generics)); - } else { - let condition = parse_match_condition(&subject, &arm.pat, generics); - let condition_with_guard = if let Some((_, guard)) = &arm.guard { - let guard_expr = parse_expression(guard, generics); - quote! { - ::agdb::api_def::Expression::Binary { - op: ::agdb::api_def::Op::And, - left: &#condition, - right: &#guard_expr, - } - } - } else { - condition - }; - let body = parse_match_arm_body(&arm.body, generics); - branches.push((condition_with_guard, body)); - } - } - - if branches.is_empty() { - else_branch.expect("Match expression must have at least one arm") - } else { - branches - .iter() - .rev() - .fold(else_branch, |else_br, (cond, body)| { - let else_part = if let Some(eb) = else_br { - eb - } else { - quote! { ::agdb::api_def::Expression::Block(&[]) } - }; - Some(quote! { - ::agdb::api_def::Expression::If { - condition: &#cond, - then_branch: &#body, - else_branch: Some(&#else_part), - } - }) - }) - .expect("At least one match arm present") - } -} - -fn parse_match_arm_body(body: &Expr, generics: &[Generic]) -> TokenStream2 { - match body { - Expr::Block(b) => parse_block(&b.block, generics), - Expr::Tuple(t) if t.elems.is_empty() => { - quote! { ::agdb::api_def::Expression::Block(&[]) } - } - expr => { - let inner = parse_expression(expr, generics); - quote! { ::agdb::api_def::Expression::Block(&[#inner]) } - } - } -} - -fn parse_match_condition(subject: &TokenStream2, pat: &Pat, generics: &[Generic]) -> TokenStream2 { - if let Pat::Or(p) = pat { - return parse_match_or(subject, p, generics); - } - - let (rhs, _) = parse_pattern(pat, generics); - quote! { - ::agdb::api_def::Expression::Binary { - op: ::agdb::api_def::Op::Eq, - left: &#subject, - right: &#rhs, - } - } -} - -fn parse_match_or(subject: &TokenStream2, pat_or: &PatOr, generics: &[Generic]) -> TokenStream2 { - let conds: Vec = pat_or - .cases - .iter() - .map(|subpat| parse_match_condition(subject, subpat, generics)) - .collect(); - let mut iter = conds.into_iter(); - let first = iter.next().expect("Or pattern must have cases"); - iter.fold(first, |acc, next| { - quote! { - ::agdb::api_def::Expression::Binary { - op: ::agdb::api_def::Op::Or, - left: &#acc, - right: &#next, - } - } - }) -} - -// --------------------------------------------------------------------------- -// Let (expression form, e.g. `if let`) -// --------------------------------------------------------------------------- - -fn parse_let_expr(e: &syn::ExprLet, generics: &[Generic]) -> TokenStream2 { - let (name, ty) = parse_pattern(&e.pat, generics); - let value = parse_expression(&e.expr, generics); - quote! { - ::agdb::api_def::Expression::Let { - name: &#name, - ty: #ty, - value: Some(&#value), - } - } -} - -// --------------------------------------------------------------------------- -// Literal -// --------------------------------------------------------------------------- - -fn parse_literal(lit: &Lit) -> TokenStream2 { - match lit { - Lit::Str(s) => { - let value = s.value(); - quote! { - ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::Str(#value)) - } - } - Lit::Int(i) => { - let suffix = i.suffix(); - match suffix { - "i8" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::I8(#v)) } - } - "i16" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::I16(#v)) } - } - "i32" | "" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::I32(#v)) } - } - "u8" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::U8(#v)) } - } - "u16" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::U16(#v)) } - } - "u32" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::U32(#v)) } - } - "u64" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::U64(#v)) } - } - "usize" => { - let v = i.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::Usize(#v)) } - } - _ => panic!("Unsupported integer suffix: {suffix}"), - } - } - Lit::Float(f) => { - let suffix = f.suffix(); - match suffix { - "f32" => { - let v = f.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::F32(#v)) } - } - "f64" | "" => { - let v = f.base10_parse::().unwrap(); - quote! { ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::F64(#v)) } - } - _ => panic!("Unsupported float suffix: {suffix}"), - } - } - Lit::Bool(b) => { - let value = b.value; - quote! { - ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::Bool(#value)) - } - } - Lit::Char(c) => { - let value = c.value().to_string(); - quote! { - ::agdb::api_def::Expression::Literal(::agdb::api_def::LiteralValue::Str(#value)) - } - } - _ => panic!("Unsupported literal: {:?}", lit), - } -} - -// --------------------------------------------------------------------------- -// Macro (format!, vec!, etc.) -// --------------------------------------------------------------------------- - -fn parse_macro(e: &ExprMacro, generics: &[Generic]) -> TokenStream2 { - let name = path_to_string(&e.mac.path); - parse_macro_by_name(&name, &e.mac.tokens, generics) -} - -fn parse_macro_by_name( - name: &str, - tokens: &proc_macro2::TokenStream, - generics: &[Generic], -) -> TokenStream2 { - let args: syn::punctuated::Punctuated = syn::parse::Parser::parse2( - syn::punctuated::Punctuated::parse_terminated, - tokens.clone(), - ) - .unwrap_or_default(); - - match name { - "vec" => { - let elements = args.iter().map(|arg| parse_expression(arg, generics)); - quote! { - ::agdb::api_def::Expression::Array(&[#(#elements),*]) - } - } - "format" => { - let mut args_iter = args.into_iter(); - let format_string_expr = args_iter - .next() - .expect("format! requires at least one argument"); - let format_string = extract_format_string(&format_string_expr); - let (fmt_str, fmt_args) = - extract_format_parts(&format_string, &mut args_iter, generics); - quote! { - ::agdb::api_def::Expression::Format { - format_string: #fmt_str, - args: &[#(#fmt_args),*], - } - } - } - // Common macros treated as calls with string arguments - "panic" | "todo" | "unimplemented" | "println" | "eprintln" | "dbg" | "assert" - | "assert_eq" | "assert_ne" | "debug_assert" | "debug_assert_eq" | "debug_assert_ne" - | "unreachable" | "write" | "writeln" => { - let macro_args = args.iter().map(|arg| parse_expression(arg, generics)); - quote! { - ::agdb::api_def::Expression::Call { - recipient: None, - function: &::agdb::api_def::Expression::Path { - ident: #name, - parent: None, - generics: &[], - }, - args: &[#(#macro_args),*], - } - } - } - _ => panic!("Unsupported macro: {name}"), - } -} - -fn extract_format_string(e: &Expr) -> String { - match e { - Expr::Lit(expr_lit) => { - if let Lit::Str(lit_str) = &expr_lit.lit { - lit_str.value() - } else { - panic!("First argument to format! must be a string literal"); - } - } - _ => panic!("First argument to format! must be a string literal"), - } -} - -fn extract_format_parts( - format_string: &str, - args_iter: &mut impl Iterator, - generics: &[Generic], -) -> (TokenStream2, Vec) { - let mut args = Vec::new(); - let mut fmt_str = String::new(); - let mut chars = format_string.chars(); - - while let Some(c) = chars.next() { - fmt_str.push(c); - - if c == '{' - && let Some(next) = chars.next() - { - if next == '{' { - fmt_str.push(next); - continue; // escaped brace - } - - fmt_str.push('}'); - - if next == '}' { - // positional argument - let arg = args_iter - .next() - .expect("not enough arguments for format string"); - args.push(parse_expression(&arg, generics)); - } else { - // named argument - let mut ident = next.to_string(); - for nc in chars.by_ref() { - if nc == '}' { - break; - } - ident.push(nc); - } - args.push(quote! { ::agdb::api_def::Expression::Ident(#ident) }); - } - } - } - - (quote! { #fmt_str }, args) -} - -// --------------------------------------------------------------------------- -// Path -// --------------------------------------------------------------------------- - -fn parse_path(path: &Path) -> TokenStream2 { - let mut iter = path.segments.iter(); - let first = iter.next().expect("path should have at least one segment"); - - // Single-segment path with no generics => Ident - if path.segments.len() == 1 && matches!(first.arguments, PathArguments::None) { - let ident = &first.ident; - return quote! { - ::agdb::api_def::Expression::Ident(stringify!(#ident)) - }; - } - - let first_segment = parse_path_segment(first, quote! { None }); - iter.fold(first_segment, |parent, segment| { - parse_path_segment(segment, quote! { Some(&#parent) }) - }) -} - -fn parse_path_segment(segment: &PathSegment, parent: TokenStream2) -> TokenStream2 { - let ident = &segment.ident; - let generics = match &segment.arguments { - PathArguments::AngleBracketed(args) => args - .args - .iter() - .filter_map(|ga| match ga { - GenericArgument::Type(ty) => Some(quote! { <#ty as ::agdb::api_def::TypeDefinition>::type_def }), - _ => None, - }) - .collect::>(), - PathArguments::Parenthesized(args) => args - .inputs - .iter() - .map(|ty| quote! { <#ty as ::agdb::api_def::TypeDefinition>::type_def }) - .collect::>(), - PathArguments::None => Vec::new(), - }; - - quote! { - ::agdb::api_def::Expression::Path { - ident: stringify!(#ident), - parent: #parent, - generics: &[#(#generics),*], - } - } -} - -fn path_to_string(path: &Path) -> String { - path.segments - .last() - .expect("path should not be empty") - .ident - .to_string() -} - -// --------------------------------------------------------------------------- -// Reference / Return / Try -// --------------------------------------------------------------------------- - -fn parse_reference(e: &ExprReference, generics: &[Generic]) -> TokenStream2 { - let expr = parse_expression(&e.expr, generics); - quote! { - ::agdb::api_def::Expression::Reference(&#expr) - } -} - -fn parse_return(e: &syn::ExprReturn, generics: &[Generic]) -> TokenStream2 { - if let Some(expr) = &e.expr { - let parsed = parse_expression(expr, generics); - quote! { - ::agdb::api_def::Expression::Return(Some(&#parsed)) - } - } else { - quote! { - ::agdb::api_def::Expression::Return(None) - } - } -} - -fn parse_try(e: &ExprTry, generics: &[Generic]) -> TokenStream2 { - let expr = parse_expression(&e.expr, generics); - quote! { - ::agdb::api_def::Expression::Try(&#expr) - } -} - -// --------------------------------------------------------------------------- -// Struct / Tuple -// --------------------------------------------------------------------------- - -fn parse_struct(e: &ExprStruct, generics: &[Generic]) -> TokenStream2 { - let path = parse_path(&e.path); - let fields = e.fields.iter().map(|f| parse_struct_field(f, generics)); - quote! { - ::agdb::api_def::Expression::Struct { - name: &#path, - fields: &[#(#fields),*], - } - } -} - -fn parse_struct_field(field: &FieldValue, generics: &[Generic]) -> TokenStream2 { - let field_name = match &field.member { - Member::Named(ident) => ident, - Member::Unnamed(_) => panic!("Unnamed fields are not supported in struct expressions"), - }; - let field_value = parse_expression(&field.expr, generics); - quote! { - (stringify!(#field_name), #field_value) - } -} - -fn parse_tuple(e: &syn::ExprTuple, generics: &[Generic]) -> TokenStream2 { - let elements = e.elems.iter().map(|elem| parse_expression(elem, generics)); - quote! { - ::agdb::api_def::Expression::Tuple(&[#(#elements),*]) - } -} - -// --------------------------------------------------------------------------- -// Patterns (used in let, for, match, closure, etc.) -// --------------------------------------------------------------------------- - -/// Returns `(name_tokens, type_tokens)` where type_tokens is `None` or -/// `Some(fn_ptr)`. -fn parse_pattern(pat: &Pat, generics: &[Generic]) -> (TokenStream2, TokenStream2) { - match pat { - Pat::Ident(p) => { - let name = &p.ident; - ( - quote! { ::agdb::api_def::Expression::Ident(stringify!(#name)) }, - quote! { None }, - ) - } - Pat::Lit(p) => { - let lit = parse_literal(&p.lit); - (lit, quote! { None }) - } - Pat::Type(p) => { - let (name, _) = parse_pattern(&p.pat, generics); - let ty = generics_parser::parse_type(&p.ty, generics); - (name, quote! { Some(#ty) }) - } - Pat::Or(p) => { - let conds: Vec = p - .cases - .iter() - .map(|subpat| parse_pattern(subpat, generics).0) - .collect(); - let mut iter = conds.into_iter(); - let first = iter.next().expect("Or pattern must have cases"); - ( - iter.fold(first, |acc, next| { - quote! { - ::agdb::api_def::Expression::Binary { - op: ::agdb::api_def::Op::Or, - left: &#acc, - right: &#next, - } - } - }), - quote! { None }, - ) - } - Pat::Paren(p) => parse_pattern(&p.pat, generics), - Pat::Path(p) => { - let path = parse_path(&p.path); - (path, quote! { None }) - } - Pat::Reference(p) => parse_pattern(&p.pat, generics), - Pat::Slice(p) => { - let elems = p.elems.iter().map(|elem| parse_pattern(elem, generics).0); - ( - quote! { ::agdb::api_def::Expression::Array(&[#(#elems),*]) }, - quote! { None }, - ) - } - Pat::Struct(p) => { - let path = parse_path(&p.path); - let fields = p.fields.iter().map(|f| parse_pattern(&f.pat, generics).0); - ( - quote! { - ::agdb::api_def::Expression::StructPattern { - name: &#path, - fields: &[#(#fields),*], - } - }, - quote! { None }, - ) - } - Pat::Tuple(p) => { - let elems = p.elems.iter().map(|elem| parse_pattern(elem, generics).0); - ( - quote! { ::agdb::api_def::Expression::Tuple(&[#(#elems),*]) }, - quote! { None }, - ) - } - Pat::TupleStruct(p) => { - let path = parse_path(&p.path); - let elems = p.elems.iter().map(|elem| parse_pattern(elem, generics).0); - ( - quote! { - ::agdb::api_def::Expression::TupleStruct { - name: &#path, - expressions: &[#(#elems),*], - } - }, - quote! { None }, - ) - } - Pat::Wild(_) => (quote! { ::agdb::api_def::Expression::Wild }, quote! { None }), - _ => panic!("Unsupported pattern: {}", pat.to_token_stream()), - } -} diff --git a/agdb_derive/src/db_serialize.rs b/agdb_derive/src/db_serialize.rs index d741c45c..3f2f46a0 100644 --- a/agdb_derive/src/db_serialize.rs +++ b/agdb_derive/src/db_serialize.rs @@ -29,7 +29,7 @@ pub fn db_serialize(item: TokenStream) -> TokenStream { } else if let syn::Data::Enum(data) = input.data { serialize_enum(name, data) } else { - unimplemented!() + crate::compile_error(&name, "Only structs and enums are supported") }; tokens.into() diff --git a/agdb_derive/src/db_type.rs b/agdb_derive/src/db_type.rs index a570a7ea..45384cfb 100644 --- a/agdb_derive/src/db_type.rs +++ b/agdb_derive/src/db_type.rs @@ -33,7 +33,7 @@ pub fn derive_impl(item: TokenStream, element_id: bool) -> TokenStream { let input = parse_macro_input!(item as DeriveInput); let name = input.ident; let syn::Data::Struct(data) = input.data else { - unimplemented!() + return crate::compile_error(&name, "Only structs are supported").into(); }; let has_option = data.fields.iter().any(|f| { if let Some(ident) = &f.ident diff --git a/agdb_derive/src/lib.rs b/agdb_derive/src/lib.rs index 616dc121..05274abf 100644 --- a/agdb_derive/src/lib.rs +++ b/agdb_derive/src/lib.rs @@ -4,6 +4,12 @@ mod db_value; mod type_def_parser; use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; + +pub(crate) fn compile_error(span: impl ToTokens, message: impl AsRef) -> TokenStream2 { + syn::Error::new_spanned(span, message.as_ref()).to_compile_error() +} /// The derive macro to add `agdb` compatibility /// to user defined types. It implements [`agdb::UserDbType`] @@ -155,3 +161,8 @@ pub fn trait_def(_attr: TokenStream, item: TokenStream) -> TokenStream { pub fn fn_def(_attr: TokenStream, item: TokenStream) -> TokenStream { type_def_parser::fn_def_impl(item) } + +#[proc_macro_attribute] +pub fn test_def(_attr: TokenStream, item: TokenStream) -> TokenStream { + type_def_parser::test_def_impl(item) +} diff --git a/agdb_derive/src/type_def_parser.rs b/agdb_derive/src/type_def_parser.rs index 81919289..4d65478c 100644 --- a/agdb_derive/src/type_def_parser.rs +++ b/agdb_derive/src/type_def_parser.rs @@ -18,7 +18,7 @@ pub(crate) fn type_def_impl(item: TokenStream) -> TokenStream { match &input.data { syn::Data::Struct(s) => struct_parser::parse_struct(&input, s), syn::Data::Enum(e) => enum_parser::parse_enum(&input, e), - _ => unimplemented!("Only structs and enums are supported for now"), + _ => crate::compile_error(&input.ident, "Only structs and enums are supported"), } .into() } @@ -41,10 +41,11 @@ pub(crate) fn type_def_impl_impl(item: TokenStream) -> TokenStream { pub(crate) fn impl_def_impl(item: TokenStream) -> TokenStream { let it: TokenStream2 = item.clone().into(); - let def_impl = if let Ok(input) = syn::parse::(item) { - impl_parser::parse_impl(&input) - } else { - unimplemented!("Only impl blocks are supported") + let def_impl = match syn::parse::(item) { + Ok(input) => impl_parser::parse_impl(&input), + Err(_) => { + return crate::compile_error(it, "Only impl blocks are supported").into(); + } }; quote! { @@ -57,10 +58,11 @@ pub(crate) fn impl_def_impl(item: TokenStream) -> TokenStream { pub(crate) fn trait_def_impl(item: TokenStream) -> TokenStream { let it: TokenStream2 = item.clone().into(); - let def_fn = if let Ok(input) = syn::parse::(item) { - trait_parser::parse_trait(&input) - } else { - unimplemented!("Only traits are supported") + let def_fn = match syn::parse::(item) { + Ok(input) => trait_parser::parse_trait(&input), + Err(_) => { + return crate::compile_error(it, "Only traits are supported").into(); + } }; quote! { @@ -72,11 +74,21 @@ pub(crate) fn trait_def_impl(item: TokenStream) -> TokenStream { } pub(crate) fn fn_def_impl(item: TokenStream) -> TokenStream { + parse_fn_attr_impl(item, quote! { ::agdb::type_def::Type::Function }) +} + +pub(crate) fn test_def_impl(item: TokenStream) -> TokenStream { + parse_fn_attr_impl(item, quote! { ::agdb::type_def::Type::Test }) +} + +fn parse_fn_attr_impl(item: TokenStream, wrapper: TokenStream2) -> TokenStream { let it: TokenStream2 = item.clone().into(); - let def_fn = if let Ok(input) = syn::parse::(item) { - function_parser::parse_function(&input) - } else { - unimplemented!("Only functions are supported") + + let def_fn = match syn::parse::(item) { + Ok(input) => function_parser::parse_function_internal(&input, wrapper), + Err(_) => { + return crate::compile_error(it, "Only functions are supported").into(); + } }; quote! { diff --git a/agdb_derive/src/type_def_parser/expression_parser.rs b/agdb_derive/src/type_def_parser/expression_parser.rs index f71c9cf2..ccec5e64 100644 --- a/agdb_derive/src/type_def_parser/expression_parser.rs +++ b/agdb_derive/src/type_def_parser/expression_parser.rs @@ -92,7 +92,10 @@ pub(crate) fn parse_expression(e: &Expr, generics: &[Generic]) -> TokenStream2 { Expr::Tuple(e) => parse_tuple(e, generics), Expr::Unary(e) => parse_unary(e, generics), Expr::While(e) => parse_while(e, generics), - _ => panic!("Unsupported expression: {}", e.to_token_stream()), + _ => crate::compile_error( + e, + format!("Unsupported expression: {}", e.to_token_stream()), + ), } } @@ -224,7 +227,7 @@ fn parse_unary(e: &ExprUnary, generics: &[Generic]) -> TokenStream2 { syn::UnOp::Deref(_) => quote! { ::agdb::type_def::Op::Deref }, syn::UnOp::Not(_) => quote! { ::agdb::type_def::Op::Not }, syn::UnOp::Neg(_) => quote! { ::agdb::type_def::Op::Neg }, - _ => panic!("Unsupported unary operator: {:?}", e.op), + _ => crate::compile_error(e.op, format!("Unsupported unary operator: {:?}", e.op)), }; quote! { ::agdb::type_def::Expression::Unary { @@ -264,7 +267,7 @@ fn parse_binop(op: &BinOp) -> TokenStream2 { BinOp::BitOrAssign(_) => quote! { ::agdb::type_def::Op::BitOrAssign }, BinOp::ShlAssign(_) => quote! { ::agdb::type_def::Op::ShlAssign }, BinOp::ShrAssign(_) => quote! { ::agdb::type_def::Op::ShrAssign }, - _ => panic!("Unsupported binary operator"), + _ => crate::compile_error(op, "Unsupported binary operator"), } } @@ -328,7 +331,7 @@ fn parse_closure(e: &ExprClosure, generics: &[Generic]) -> TokenStream2 { let (name_tokens, ty_tokens) = parse_closure_arg(pat, generics); quote! { ::agdb::type_def::Variable { - name: stringify!(#name_tokens), + name: #name_tokens, ty: Some(#ty_tokens), } } @@ -359,30 +362,40 @@ fn parse_closure(e: &ExprClosure, generics: &[Generic]) -> TokenStream2 { fn parse_closure_arg(pat: &Pat, generics: &[Generic]) -> (TokenStream2, TokenStream2) { match pat { Pat::Type(p) => { - let name = extract_pat_ident(&p.pat); let ty = generics_parser::parse_type(&p.ty, generics); - (quote! { #name }, ty) + match p.pat.as_ref() { + Pat::Ident(pat_ident) => { + let name = pat_ident.ident.to_string(); + (quote! { #name }, ty) + } + _ => ( + crate::compile_error( + &p.pat, + format!( + "Expected identifier pattern, got: {}", + p.pat.to_token_stream() + ), + ), + ty, + ), + } } Pat::Ident(p) => { - let name = &p.ident; + let name = p.ident.to_string(); ( quote! { #name }, quote! { <() as ::agdb::type_def::TypeDefinition>::type_def }, ) } - _ => panic!( - "Unsupported closure argument pattern: {}", - pat.to_token_stream() - ), - } -} - -fn extract_pat_ident(pat: &Pat) -> &syn::Ident { - match pat { - Pat::Ident(p) => &p.ident, - _ => panic!( - "Expected identifier pattern, got: {}", - pat.to_token_stream() + _ => ( + crate::compile_error( + pat, + format!( + "Unsupported closure argument pattern: {}", + pat.to_token_stream() + ), + ), + quote! { <() as ::agdb::type_def::TypeDefinition>::type_def }, ), } } @@ -471,7 +484,7 @@ fn parse_if(e: &syn::ExprIf, generics: &[Generic]) -> TokenStream2 { let else_tokens = match else_expr.as_ref() { Expr::If(else_if) => parse_if(else_if, generics), Expr::Block(else_block) => parse_block(&else_block.block, generics), - _ => panic!("Unsupported else branch"), + _ => crate::compile_error(else_expr, "Unsupported else branch"), }; quote! { Some(&#else_tokens) } } else { @@ -648,7 +661,7 @@ fn parse_literal(lit: &Lit) -> TokenStream2 { let v = i.base10_parse::().unwrap(); quote! { ::agdb::type_def::Expression::Literal(::agdb::type_def::LiteralValue::Usize(#v)) } } - _ => panic!("Unsupported integer suffix: {suffix}"), + _ => crate::compile_error(i, format!("Unsupported integer suffix: {suffix}")), } } Lit::Float(f) => { @@ -662,7 +675,7 @@ fn parse_literal(lit: &Lit) -> TokenStream2 { let v = f.base10_parse::().unwrap(); quote! { ::agdb::type_def::Expression::Literal(::agdb::type_def::LiteralValue::F64(#v)) } } - _ => panic!("Unsupported float suffix: {suffix}"), + _ => crate::compile_error(f, format!("Unsupported float suffix: {suffix}")), } } Lit::Bool(b) => { @@ -677,7 +690,7 @@ fn parse_literal(lit: &Lit) -> TokenStream2 { ::agdb::type_def::Expression::Literal(::agdb::type_def::LiteralValue::Str(#value)) } } - _ => panic!("Unsupported literal: {:?}", lit), + _ => crate::compile_error(lit, format!("Unsupported literal: {:?}", lit)), } } @@ -713,7 +726,10 @@ fn parse_macro_by_name( let format_string_expr = args_iter .next() .expect("format! requires at least one argument"); - let format_string = extract_format_string(&format_string_expr); + let format_string = match extract_format_string(&format_string_expr) { + Ok(v) => v, + Err(err) => return err, + }; let (fmt_str, fmt_args) = extract_format_parts(&format_string, &mut args_iter, generics); quote! { @@ -723,10 +739,10 @@ fn parse_macro_by_name( } } } - // Common macros treated as calls with string arguments + // Common macros treated as function calls "panic" | "todo" | "unimplemented" | "println" | "eprintln" | "dbg" | "assert" | "assert_eq" | "assert_ne" | "debug_assert" | "debug_assert_eq" | "debug_assert_ne" - | "unreachable" | "write" | "writeln" => { + | "matches" | "unreachable" | "write" | "writeln" => { let macro_args = args.iter().map(|arg| parse_expression(arg, generics)); quote! { ::agdb::type_def::Expression::Call { @@ -740,20 +756,26 @@ fn parse_macro_by_name( } } } - _ => panic!("Unsupported macro: {name}"), + _ => crate::compile_error(tokens, format!("Unsupported macro: {name}")), } } -fn extract_format_string(e: &Expr) -> String { +fn extract_format_string(e: &Expr) -> Result { match e { Expr::Lit(expr_lit) => { if let Lit::Str(lit_str) = &expr_lit.lit { - lit_str.value() + Ok(lit_str.value()) } else { - panic!("First argument to format! must be a string literal"); + Err(crate::compile_error( + expr_lit, + "First argument to format! must be a string literal", + )) } } - _ => panic!("First argument to format! must be a string literal"), + _ => Err(crate::compile_error( + e, + "First argument to format! must be a string literal", + )), } } @@ -911,7 +933,12 @@ fn parse_struct(e: &ExprStruct, generics: &[Generic]) -> TokenStream2 { fn parse_struct_field(field: &FieldValue, generics: &[Generic]) -> TokenStream2 { let field_name = match &field.member { Member::Named(ident) => ident, - Member::Unnamed(_) => panic!("Unnamed fields are not supported in struct expressions"), + Member::Unnamed(_) => { + return crate::compile_error( + &field.member, + "Unnamed fields are not supported in struct expressions", + ); + } }; let field_value = parse_expression(&field.expr, generics); quote! { @@ -1021,6 +1048,12 @@ fn parse_pattern(pat: &Pat, generics: &[Generic]) -> (TokenStream2, TokenStream2 quote! { ::agdb::type_def::Expression::Wild }, quote! { None }, ), - _ => panic!("Unsupported pattern: {}", pat.to_token_stream()), + _ => ( + crate::compile_error( + pat, + format!("Unsupported pattern: {}", pat.to_token_stream()), + ), + quote! { None }, + ), } } diff --git a/agdb_derive/src/type_def_parser/function_parser.rs b/agdb_derive/src/type_def_parser/function_parser.rs index 45c59fc0..2f1cc655 100644 --- a/agdb_derive/src/type_def_parser/function_parser.rs +++ b/agdb_derive/src/type_def_parser/function_parser.rs @@ -9,7 +9,7 @@ use syn::ReturnType; use syn::punctuated::Punctuated; use syn::token::Comma; -pub(crate) fn parse_function(input: &ItemFn) -> TokenStream2 { +pub(crate) fn parse_function_internal(input: &ItemFn, wrapper: TokenStream2) -> TokenStream2 { let name_str = input.sig.ident.to_string(); let fn_name = crate::type_def_parser::type_def_fn(&name_str); let current_generics = generics_parser::extract_generics(&input.sig.generics); @@ -31,7 +31,7 @@ pub(crate) fn parse_function(input: &ItemFn) -> TokenStream2 { quote! { fn #fn_name #lt_generics () -> ::agdb::type_def::Type { - ::agdb::type_def::Type::Function(::agdb::type_def::Function { + #wrapper(::agdb::type_def::Function { name: stringify!(#name), generics: &[#(#generics),*], args: &[#(#args),*], diff --git a/agdb_derive/src/type_def_parser/generics_parser.rs b/agdb_derive/src/type_def_parser/generics_parser.rs index 7a9d7c6b..fa34d5bc 100644 --- a/agdb_derive/src/type_def_parser/generics_parser.rs +++ b/agdb_derive/src/type_def_parser/generics_parser.rs @@ -88,24 +88,28 @@ pub(crate) fn parse_generics(generics: &Generics) -> Vec { } pub(crate) fn parse_type_param_bound(bound: &TypeParamBound) -> TokenStream2 { - let name = extract_type_param_bound(bound); - - quote! { - || ::agdb::type_def::Type::Trait(::agdb::type_def::Trait { - name: #name, - generics: &[], - bounds: &[], - functions: &[], - }) + match extract_type_param_bound(bound) { + Ok(name) => quote! { + || ::agdb::type_def::Type::Trait(::agdb::type_def::Trait { + name: #name, + generics: &[], + bounds: &[], + functions: &[], + }) + }, + Err(e) => e, } } -fn extract_type_param_bound(bound: &TypeParamBound) -> String { +fn extract_type_param_bound(bound: &TypeParamBound) -> Result { match bound { TypeParamBound::Trait(trait_bound) => { - trait_bound.path.segments.last().unwrap().ident.to_string() + Ok(trait_bound.path.segments.last().unwrap().ident.to_string()) } - _ => unimplemented!("Only trait bounds are supported for now"), + _ => Err(crate::compile_error( + proc_macro2::TokenStream::new(), + "Only trait bounds are supported", + )), } } diff --git a/agdb_derive/src/type_def_parser/impl_parser.rs b/agdb_derive/src/type_def_parser/impl_parser.rs index c801071a..94588754 100644 --- a/agdb_derive/src/type_def_parser/impl_parser.rs +++ b/agdb_derive/src/type_def_parser/impl_parser.rs @@ -28,7 +28,7 @@ pub(crate) fn parse_impl(input: &syn::ItemImpl) -> TokenStream2 { .iter() .map(|item| match item { ImplItem::Fn(impl_fn) => function_parser::parse_signature(&impl_fn.sig), - _ => panic!("Only function items are supported in impl blocks"), + _ => crate::compile_error(item, "Only function items are supported in impl blocks"), }) .collect::>();