diff --git a/agdb/src/type_def/expression_def.rs b/agdb/src/type_def/expression_def.rs index ceab8dbe..6ea615b6 100644 --- a/agdb/src/type_def/expression_def.rs +++ b/agdb/src/type_def/expression_def.rs @@ -57,6 +57,11 @@ pub enum Expression { parent: Option<&'static Expression>, generics: &'static [fn() -> Type], }, + Range { + start: Option<&'static Expression>, + end: Option<&'static Expression>, + inclusive: bool, + }, Reference(&'static Expression), Return(Option<&'static Expression>), Struct { @@ -197,6 +202,12 @@ mod tests { "path_expr" => __path_expr_type_def(), "let_pattern_tuple" => __let_pattern_tuple_type_def(), "for_pattern" => __for_pattern_type_def(), + "range_half_open" => __range_half_open_type_def(), + "range_inclusive" => __range_inclusive_type_def(), + "range_from" => __range_from_type_def(), + "range_to" => __range_to_type_def(), + "range_to_inclusive" => __range_to_inclusive_type_def(), + "range_full" => __range_full_type_def(), _ => panic!("Unknown test function: {name}"), }; let Type::Function(def) = func_type else { @@ -1504,4 +1515,189 @@ mod tests { _ => panic!("Got: {:?}", body[1]), } } + + #[agdb::fn_def] + #[allow(unused)] + fn range_half_open() { + let _r = 0..10; + } + + #[test] + fn test_range_half_open() { + let body = get_body("range_half_open"); + assert_eq!(body.len(), 1); + match &body[0] { + Expression::Let { + value: + Some(Expression::Range { + start: Some(start), + end: Some(end), + inclusive: false, + }), + .. + } => { + assert!( + matches!(start, Expression::Literal(LiteralValue::I32(0))), + "Got start: {:?}", + start + ); + assert!( + matches!(end, Expression::Literal(LiteralValue::I32(10))), + "Got end: {:?}", + end + ); + } + _ => panic!("Got: {:?}", body[0]), + } + } + + #[agdb::fn_def] + #[allow(unused)] + fn range_inclusive() { + let _r = 1..=5; + } + + #[test] + fn test_range_inclusive() { + let body = get_body("range_inclusive"); + assert_eq!(body.len(), 1); + match &body[0] { + Expression::Let { + value: + Some(Expression::Range { + start: Some(start), + end: Some(end), + inclusive: true, + }), + .. + } => { + assert!( + matches!(start, Expression::Literal(LiteralValue::I32(1))), + "Got start: {:?}", + start + ); + assert!( + matches!(end, Expression::Literal(LiteralValue::I32(5))), + "Got end: {:?}", + end + ); + } + _ => panic!("Got: {:?}", body[0]), + } + } + + #[agdb::fn_def] + #[allow(unused)] + fn range_from() { + let start = 3; + let _r = start..; + } + + #[test] + fn test_range_from() { + let body = get_body("range_from"); + assert_eq!(body.len(), 2); + match &body[1] { + Expression::Let { + value: + Some(Expression::Range { + start: Some(start), + end: None, + inclusive: false, + }), + .. + } => { + assert!( + matches!(start, Expression::Ident("start")), + "Got start: {:?}", + start + ); + } + _ => panic!("Got: {:?}", body[1]), + } + } + + #[agdb::fn_def] + #[allow(unused)] + fn range_to() { + let _r = ..10; + } + + #[test] + fn test_range_to() { + let body = get_body("range_to"); + assert_eq!(body.len(), 1); + match &body[0] { + Expression::Let { + value: + Some(Expression::Range { + start: None, + end: Some(end), + inclusive: false, + }), + .. + } => { + assert!( + matches!(end, Expression::Literal(LiteralValue::I32(10))), + "Got end: {:?}", + end + ); + } + _ => panic!("Got: {:?}", body[0]), + } + } + + #[agdb::fn_def] + #[allow(unused)] + fn range_to_inclusive() { + let _r = ..=10; + } + + #[test] + fn test_range_to_inclusive() { + let body = get_body("range_to_inclusive"); + assert_eq!(body.len(), 1); + match &body[0] { + Expression::Let { + value: + Some(Expression::Range { + start: None, + end: Some(end), + inclusive: true, + }), + .. + } => { + assert!( + matches!(end, Expression::Literal(LiteralValue::I32(10))), + "Got end: {:?}", + end + ); + } + _ => panic!("Got: {:?}", body[0]), + } + } + + #[agdb::fn_def] + #[allow(unused)] + fn range_full() { + let _r = ..; + } + + #[test] + fn test_range_full() { + let body = get_body("range_full"); + assert_eq!(body.len(), 1); + match &body[0] { + Expression::Let { + value: + Some(Expression::Range { + start: None, + end: None, + inclusive: false, + }), + .. + } => {} + _ => panic!("Got: {:?}", body[0]), + } + } } diff --git a/agdb_derive/src/type_def_parser/expression_parser.rs b/agdb_derive/src/type_def_parser/expression_parser.rs index ccec5e64..2f27ed2c 100644 --- a/agdb_derive/src/type_def_parser/expression_parser.rs +++ b/agdb_derive/src/type_def_parser/expression_parser.rs @@ -14,6 +14,7 @@ use syn::ExprField; use syn::ExprIndex; use syn::ExprMacro; use syn::ExprMethodCall; +use syn::ExprRange; use syn::ExprReference; use syn::ExprStruct; use syn::ExprTry; @@ -85,6 +86,7 @@ pub(crate) fn parse_expression(e: &Expr, generics: &[Generic]) -> TokenStream2 { Expr::MethodCall(e) => parse_method_call(e, generics), Expr::Paren(e) => parse_expression(&e.expr, generics), Expr::Path(e) => parse_path(&e.path), + Expr::Range(e) => parse_range(e, generics), Expr::Reference(e) => parse_reference(e, generics), Expr::Return(e) => parse_return(e, generics), Expr::Struct(e) => parse_struct(e, generics), @@ -884,6 +886,33 @@ fn path_to_string(path: &Path) -> String { .to_string() } +// --------------------------------------------------------------------------- +// Range +// --------------------------------------------------------------------------- + +fn parse_range(e: &ExprRange, generics: &[Generic]) -> TokenStream2 { + let start = if let Some(start) = &e.start { + let s = parse_expression(start, generics); + quote! { Some(&#s) } + } else { + quote! { None } + }; + let end = if let Some(end) = &e.end { + let v = parse_expression(end, generics); + quote! { Some(&#v) } + } else { + quote! { None } + }; + let inclusive = matches!(e.limits, syn::RangeLimits::Closed(_)); + quote! { + ::agdb::type_def::Expression::Range { + start: #start, + end: #end, + inclusive: #inclusive, + } + } +} + // --------------------------------------------------------------------------- // Reference / Return / Try // ---------------------------------------------------------------------------