diff --git a/Cargo.lock b/Cargo.lock index c4ba9a09..e49dff86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,7 @@ version = "0.0.26" dependencies = [ "chumsky", "indexmap", + "indoc", "itertools", "miette", "pretty_assertions", diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml index 2d45a26b..4eb485ab 100644 --- a/crates/lang/Cargo.toml +++ b/crates/lang/Cargo.toml @@ -21,4 +21,5 @@ uplc = { path = '../uplc', version = "0.0.25" } vec1 = "1.8.0" [dev-dependencies] +indoc = "1.0.7" pretty_assertions = "1.3.0" diff --git a/crates/lang/src/parser.rs b/crates/lang/src/parser.rs index 371c7266..9a4cc547 100644 --- a/crates/lang/src/parser.rs +++ b/crates/lang/src/parser.rs @@ -562,6 +562,168 @@ pub fn expr_parser( } }); + let record_update_parser = select! {Token::Name { name } => name} + .map_with_span(|module, span: Span| (module, span)) + .then_ignore(just(Token::Dot)) + .or_not() + .then(select! {Token::UpName { name } => name}.map_with_span(|name, span| (name, span))) + .then( + just(Token::DotDot) + .ignore_then(r.clone()) + .then( + just(Token::Comma) + .ignore_then( + select! { Token::Name {name} => name } + .then_ignore(just(Token::Colon)) + .then(r.clone()) + .map_with_span(|(label, value), span| { + ast::UntypedRecordUpdateArg { + label, + value, + location: span, + } + }) + .separated_by(just(Token::Comma)) + .allow_trailing(), + ) + .or_not(), + ) + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) + .map_with_span(|a, span: Span| (a, span)), + ) + .map(|((module, (name, n_span)), ((spread, opt_args), span))| { + let constructor = if let Some((module, m_span)) = module { + expr::UntypedExpr::FieldAccess { + location: m_span.union(n_span), + label: name, + container: Box::new(expr::UntypedExpr::Var { + location: m_span, + name: module, + }), + } + } else { + expr::UntypedExpr::Var { + location: n_span, + name, + } + }; + + let spread_span = spread.location(); + + let location = Span::new((), spread_span.start - 2..spread_span.end); + + let spread = ast::RecordUpdateSpread { + base: Box::new(spread), + location, + }; + + expr::UntypedExpr::RecordUpdate { + location: constructor.location().union(span), + constructor: Box::new(constructor), + spread, + arguments: opt_args.unwrap_or_default(), + } + }); + + let record_parser = choice(( + select! {Token::Name { name } => name} + .map_with_span(|module, span| (module, span)) + .then_ignore(just(Token::Dot)) + .or_not() + .then( + select! {Token::UpName { name } => name} + .map_with_span(|name, span| (name, span)), + ) + .then( + select! {Token::Name {name} => name} + .then_ignore(just(Token::Colon)) + .or_not() + .then(r.clone()) + .validate(|(label_opt, value), span, emit| { + dbg!(&label_opt); + let label = if label_opt.is_some() { + label_opt + } else if let expr::UntypedExpr::Var { name, .. } = &value { + Some(name.clone()) + } else { + emit(ParseError::expected_input_found( + value.location(), + None, + Some(error::Pattern::RecordPunning), + )); + + None + }; + + ast::CallArg { + location: span, + value, + label, + } + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)), + ), + select! {Token::Name { name } => name} + .map_with_span(|module, span| (module, span)) + .then_ignore(just(Token::Dot)) + .or_not() + .then( + select! {Token::UpName { name } => name} + .map_with_span(|name, span| (name, span)), + ) + .then( + r.clone() + .map_with_span(|value, span| ast::CallArg { + location: span, + value, + label: None, + }) + .separated_by(just(Token::Comma)) + .allow_trailing() + .delimited_by(just(Token::LeftParen), just(Token::RightParen)), + ), + )) + .map_with_span(|((module, (name, n_span)), arguments), span| { + let fun = if let Some((module, m_span)) = module { + expr::UntypedExpr::FieldAccess { + location: m_span.union(n_span), + label: name, + container: Box::new(expr::UntypedExpr::Var { + location: m_span, + name: module, + }), + } + } else { + expr::UntypedExpr::Var { + location: n_span, + name, + } + }; + + expr::UntypedExpr::Call { + arguments, + fun: Box::new(fun), + location: span, + } + }); + + let field_access_constructor = select! {Token::Name { name } => name} + .map_with_span(|module, span| (module, span)) + .then_ignore(just(Token::Dot)) + .then(select! {Token::UpName { name } => name}) + .map_with_span( + |((module, m_span), name), span| expr::UntypedExpr::FieldAccess { + location: span, + label: name, + container: Box::new(expr::UntypedExpr::Var { + location: m_span, + name: module, + }), + }, + ); + let var_parser = select! { Token::Name { name } => name, Token::UpName { name } => name, @@ -756,23 +918,29 @@ pub fn expr_parser( ); let if_parser = just(Token::If) - .ignore_then(r.clone().then(block_parser.clone()).map_with_span( - |(condition, body), span| ast::IfBranch { - condition, - body, - location: span, - }, - )) + .ignore_then( + r.clone() + .then_ignore(just(Token::Then)) + .then(block_parser.clone()) + .map_with_span(|(condition, body), span| ast::IfBranch { + condition, + body, + location: span, + }), + ) .then( just(Token::Else) .ignore_then(just(Token::If)) - .ignore_then(r.clone().then(block_parser.clone()).map_with_span( - |(condition, body), span| ast::IfBranch { - condition, - body, - location: span, - }, - )) + .ignore_then( + r.clone() + .then_ignore(just(Token::Then)) + .then(block_parser.clone()) + .map_with_span(|(condition, body), span| ast::IfBranch { + condition, + body, + location: span, + }), + ) .repeated(), ) .then_ignore(just(Token::Else)) @@ -792,6 +960,9 @@ pub fn expr_parser( let expr_unit_parser = choice(( string_parser, int_parser, + record_update_parser, + record_parser, + field_access_constructor, var_parser, todo_parser, tuple, @@ -819,42 +990,14 @@ pub fn expr_parser( enum Chain { Call(Vec, Span), FieldAccess(String, Span), - RecordUpdate( - Box<(expr::UntypedExpr, Vec)>, - Span, - ), } let field_access_parser = just(Token::Dot) .ignore_then(select! { Token::Name { name } => name, - Token::UpName { name } => name }) .map_with_span(Chain::FieldAccess); - let record_update_parser = just(Token::DotDot) - .ignore_then(r.clone()) - .then( - just(Token::Comma) - .ignore_then( - select! { Token::Name {name} => name } - .then_ignore(just(Token::Colon)) - .then(r.clone()) - .map_with_span(|(label, value), span| ast::UntypedRecordUpdateArg { - label, - value, - location: span, - }) - .separated_by(just(Token::Comma)) - .allow_trailing(), - ) - .or_not(), - ) - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) - .map_with_span(|(spread, args_opt), span| { - Chain::RecordUpdate(Box::new((spread, args_opt.unwrap_or_default())), span) - }); - let call_parser = choice(( select! { Token::Name { name } => name } .then_ignore(just(Token::Colon)) @@ -881,7 +1024,7 @@ pub fn expr_parser( .delimited_by(just(Token::LeftParen), just(Token::RightParen)) .map_with_span(Chain::Call); - let chain = choice((field_access_parser, record_update_parser, call_parser)); + let chain = choice((field_access_parser, call_parser)); let chained = expr_unit_parser .then(chain.repeated()) @@ -941,24 +1084,6 @@ pub fn expr_parser( label, container: Box::new(e), }, - - Chain::RecordUpdate(data, span) => { - let (spread, arguments) = *data; - - let location = span.union(spread.location()); - - let spread = ast::RecordUpdateSpread { - base: Box::new(spread), - location, - }; - - expr::UntypedExpr::RecordUpdate { - location: e.location().union(span), - constructor: Box::new(e), - spread, - arguments, - } - } }); // Negate diff --git a/crates/lang/src/parser/lexer.rs b/crates/lang/src/parser/lexer.rs index af6aeb9f..43b2efaf 100644 --- a/crates/lang/src/parser/lexer.rs +++ b/crates/lang/src/parser/lexer.rs @@ -69,6 +69,7 @@ pub fn lexer() -> impl Parser, Error = ParseError> { "fn" => Token::Fn, "if" => Token::If, "else" => Token::Else, + "then" => Token::Then, "is" => Token::Is, "let" => Token::Let, "opaque" => Token::Opaque, diff --git a/crates/lang/src/parser/token.rs b/crates/lang/src/parser/token.rs index 8faaa339..542edcae 100644 --- a/crates/lang/src/parser/token.rs +++ b/crates/lang/src/parser/token.rs @@ -72,6 +72,7 @@ pub enum Token { Trace, Type, When, + Then, } impl fmt::Display for Token { @@ -145,6 +146,7 @@ impl fmt::Display for Token { Token::Todo => "todo", Token::Trace => "try", Token::Type => "type", + Token::Then => "then", }; write!(f, "\"{}\"", s) } diff --git a/crates/lang/src/tests/parser.rs b/crates/lang/src/tests/parser.rs index 03a93321..e1ca3845 100644 --- a/crates/lang/src/tests/parser.rs +++ b/crates/lang/src/tests/parser.rs @@ -1,4 +1,5 @@ use chumsky::prelude::*; +use indoc::indoc; use pretty_assertions::assert_eq; use crate::{ @@ -6,106 +7,7 @@ use crate::{ expr, parser, }; -#[test] -fn module() { - let code = r#" - use std/list - use std/address.{Address as A, thing as w} - use std/tx as t - - type Option(a) { - Some(a, Int) - None - Wow { name: Int, age: Int } - } - - pub opaque type User { - name: _w - } - - type Thing = Option(Int) - - pub type Me = Option(String) - - pub fn add_one(a) -> Int { - a + 1 - } - - pub fn thing(thing a: Int) { - a + 2 - |> add_one - |> add_one - } - - pub fn wow(a: Int) { - let x = - a + 2 - |> add_one - |> add_one - - let thing = [ 1, 2, a ] - - let idk = thing - - y - } - - pub fn wow2(a: Int){ - let b = { - let x = 4 - - x + 5 - } - - when a, b is { - 1, 2 -> 3 - 1 | 4, 5 -> { - let amazing = 5 - - amazing - } - 3 -> 9 - _ -> 4 - } - } - - pub fn such() -> Int { - let add_one = fn (a: Int) -> Int { a + 1 } - - 2 |> add_one - } - - fn run() {} - - fn name(user: User) { - user.name - } - - fn calls() { - let x = add_one(3) - - let map_add_x = list.map(_, fn (y) { x + y }) - - map_add_x([ 1, 2, 3 ]) - } - - fn update_name(user: User, name: String) -> User { - User { ..user, name: "Aiken", } - } - - fn ifs() { - if True { - 1 + 1 - } else if a < 4 { - 5 - } else if a || b { - 6 - } else { - 3 - } - } - "#; - +fn assert_definition(code: &str, definition: ast::UntypedDefinition) { let (module, _extra) = parser::module(code, ast::ModuleKind::Validator).unwrap(); assert_eq!( @@ -115,947 +17,1237 @@ fn module() { kind: ast::ModuleKind::Validator, name: "".to_string(), type_info: (), - definitions: vec![ - ast::UntypedDefinition::Use(Use { - location: Span::new((), 13..25), - module: vec!["std".to_string(), "list".to_string()], - as_name: None, - unqualified: vec![], - package: (), - }), - ast::UntypedDefinition::Use(Use { - location: Span::new((), 38..80), - module: vec!["std".to_string(), "address".to_string()], - as_name: None, - unqualified: vec![ - ast::UnqualifiedImport { - as_name: Some("A".to_string()), - location: Span::new((), 55..67), - layer: Default::default(), - name: "Address".to_string() - }, - ast::UnqualifiedImport { - as_name: Some("w".to_string()), - location: Span::new((), 69..79), - layer: Default::default(), - name: "thing".to_string() - } - ], - package: (), - }), - ast::UntypedDefinition::Use(Use { - location: Span::new((), 93..108), - module: vec!["std".to_string(), "tx".to_string()], - as_name: Some("t".to_string()), - unqualified: vec![], - package: (), - }), - ast::UntypedDefinition::DataType(DataType { - location: Span::new((), 122..240), - constructors: vec![ - ast::RecordConstructor { - location: Span::new((), 153..165), - name: "Some".to_string(), - arguments: vec![ - ast::RecordConstructorArg { - label: None, - annotation: ast::Annotation::Var { - location: Span::new((), 158..159), - name: "a".to_string(), - }, - location: Span::new((), 158..159), - tipo: (), - doc: None, - }, - ast::RecordConstructorArg { - label: None, - annotation: ast::Annotation::Constructor { - location: Span::new((), 161..164), - module: None, - name: "Int".to_string(), - arguments: vec![], - }, - location: Span::new((), 161..164), - tipo: (), - doc: None, - }, - ], - documentation: None, - sugar: false, - }, - ast::RecordConstructor { - location: Span::new((), 180..184), - name: "None".to_string(), - arguments: vec![], - documentation: None, - sugar: false, - }, - ast::RecordConstructor { - location: Span::new((), 199..226), - name: "Wow".to_string(), - arguments: vec![ - ast::RecordConstructorArg { - label: Some("name".to_string(),), - annotation: ast::Annotation::Constructor { - location: Span::new((), 211..214), - module: None, - name: "Int".to_string(), - arguments: vec![], - }, - location: Span::new((), 205..214), - tipo: (), - doc: None, - }, - ast::RecordConstructorArg { - label: Some("age".to_string(),), - annotation: ast::Annotation::Constructor { - location: Span::new((), 221..224), - module: None, - name: "Int".to_string(), - arguments: vec![], - }, - location: Span::new((), 216..224), - tipo: (), - doc: None, - }, - ], - documentation: None, - sugar: false, - }, - ], - doc: None, - name: "Option".to_string(), - opaque: false, - parameters: vec!["a".to_string(),], - public: false, - typed_parameters: vec![], - }), - ast::UntypedDefinition::DataType(DataType { - location: Span::new((), 254..313), - constructors: vec![ast::RecordConstructor { - location: Span::new((), 275..313), - name: "User".to_string(), - arguments: vec![ast::RecordConstructorArg { - label: Some("name".to_string()), - annotation: ast::Annotation::Hole { - location: Span::new((), 297..299), - name: "_w".to_string(), + definitions: vec![definition] + } + ) +} + +#[test] +fn import() { + let code = indoc! {r#" + use std/list + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Use(Use { + location: Span::new((), 0..12), + module: vec!["std".to_string(), "list".to_string()], + as_name: None, + unqualified: vec![], + package: (), + }), + ) +} + +#[test] +fn unqualified_imports() { + let code = indoc! {r#" + use std/address.{Address as A, thing as w} + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Use(Use { + location: Span::new((), 0..42), + module: vec!["std".to_string(), "address".to_string()], + as_name: None, + unqualified: vec![ + ast::UnqualifiedImport { + as_name: Some("A".to_string()), + location: Span::new((), 17..29), + layer: Default::default(), + name: "Address".to_string(), + }, + ast::UnqualifiedImport { + as_name: Some("w".to_string()), + location: Span::new((), 31..41), + layer: Default::default(), + name: "thing".to_string(), + }, + ], + package: (), + }), + ) +} + +#[test] +fn import_alias() { + let code = indoc! {r#" + use std/tx as t + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Use(Use { + location: Span::new((), 0..15), + module: vec!["std".to_string(), "tx".to_string()], + as_name: Some("t".to_string()), + unqualified: vec![], + package: (), + }), + ) +} + +#[test] +fn custom_type() { + let code = indoc! {r#" + type Option(a) { + Some(a, Int) + None + Wow { name: Int, age: Int } + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::DataType(DataType { + constructors: vec![ + ast::RecordConstructor { + location: Span::new((), 19..31), + name: "Some".to_string(), + arguments: vec![ + ast::RecordConstructorArg { + label: None, + annotation: ast::Annotation::Var { + location: Span::new((), 24..25), + name: "a".to_string(), }, - location: Span::new((), 291..299), + location: Span::new((), 24..25), tipo: (), doc: None, - },], - documentation: None, - sugar: true, - },], - doc: None, - name: "User".to_string(), - opaque: true, - parameters: vec![], - public: true, - typed_parameters: vec![], - }), - ast::UntypedDefinition::TypeAlias(TypeAlias { - alias: "Thing".to_string(), - annotation: ast::Annotation::Constructor { - location: Span::new((), 340..351), - module: None, - name: "Option".to_string(), - arguments: vec![ast::Annotation::Constructor { - location: Span::new((), 347..350), - module: None, - name: "Int".to_string(), - arguments: vec![], - },], - }, - doc: None, - location: Span::new((), 327..351), - parameters: vec![], - public: false, - tipo: (), - }), - ast::UntypedDefinition::TypeAlias(TypeAlias { - alias: "Me".to_string(), - annotation: ast::Annotation::Constructor { - location: Span::new((), 379..393), - module: None, - name: "Option".to_string(), - arguments: vec![ast::Annotation::Constructor { - location: Span::new((), 386..392), - module: None, - name: "String".to_string(), - arguments: vec![], - },], - }, - doc: None, - location: Span::new((), 365..393), - parameters: vec![], - public: true, - tipo: (), - }), - ast::UntypedDefinition::Fn(Function { - end_position: 466, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "a".to_string(), - location: Span::new((), 422..423), }, - location: Span::new((), 422..423), - annotation: None, - tipo: (), - },], - body: expr::UntypedExpr::BinOp { - location: Span::new((), 448..453), + ast::RecordConstructorArg { + label: None, + annotation: ast::Annotation::Constructor { + location: Span::new((), 27..30), + module: None, + name: "Int".to_string(), + arguments: vec![], + }, + location: Span::new((), 27..30), + tipo: (), + doc: None, + }, + ], + documentation: None, + sugar: false, + }, + ast::RecordConstructor { + location: Span::new((), 34..38), + name: "None".to_string(), + arguments: vec![], + documentation: None, + sugar: false, + }, + ast::RecordConstructor { + location: Span::new((), 41..68), + name: "Wow".to_string(), + arguments: vec![ + ast::RecordConstructorArg { + label: Some("name".to_string()), + annotation: ast::Annotation::Constructor { + location: Span::new((), 53..56), + module: None, + name: "Int".to_string(), + arguments: vec![], + }, + location: Span::new((), 47..56), + tipo: (), + doc: None, + }, + ast::RecordConstructorArg { + label: Some("age".to_string()), + annotation: ast::Annotation::Constructor { + location: Span::new((), 63..66), + module: None, + name: "Int".to_string(), + arguments: vec![], + }, + location: Span::new((), 58..66), + tipo: (), + doc: None, + }, + ], + documentation: None, + sugar: false, + }, + ], + doc: None, + location: Span::new((), 0..70), + name: "Option".to_string(), + opaque: false, + parameters: vec!["a".to_string()], + public: false, + typed_parameters: vec![], + }), + ) +} + +#[test] +fn opaque_type() { + let code = indoc! {r#" + pub opaque type User { + name: _w + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::DataType(DataType { + constructors: vec![ast::RecordConstructor { + location: Span::new((), 21..35), + name: "User".to_string(), + arguments: vec![ast::RecordConstructorArg { + label: Some("name".to_string()), + annotation: ast::Annotation::Hole { + location: Span::new((), 31..33), + name: "_w".to_string(), + }, + location: Span::new((), 25..33), + tipo: (), + doc: None, + }], + documentation: None, + sugar: true, + }], + doc: None, + location: Span::new((), 0..35), + name: "User".to_string(), + opaque: true, + parameters: vec![], + public: true, + typed_parameters: vec![], + }), + ) +} + +#[test] +fn type_alias() { + let code = indoc! {r#" + type Thing = Option(Int) + "#}; + + assert_definition( + code, + ast::UntypedDefinition::TypeAlias(TypeAlias { + alias: "Thing".to_string(), + annotation: ast::Annotation::Constructor { + location: Span::new((), 13..24), + module: None, + name: "Option".to_string(), + arguments: vec![ast::Annotation::Constructor { + location: Span::new((), 20..23), + module: None, + name: "Int".to_string(), + arguments: vec![], + }], + }, + doc: None, + location: Span::new((), 0..24), + parameters: vec![], + public: false, + tipo: (), + }), + ) +} + +#[test] +fn pub_type_alias() { + let code = indoc! {r#" + pub type Me = Option(String) + "#}; + + assert_definition( + code, + ast::UntypedDefinition::TypeAlias(TypeAlias { + alias: "Me".to_string(), + annotation: ast::Annotation::Constructor { + location: Span::new((), 14..28), + module: None, + name: "Option".to_string(), + arguments: vec![ast::Annotation::Constructor { + location: Span::new((), 21..27), + module: None, + name: "String".to_string(), + arguments: vec![], + }], + }, + doc: None, + location: Span::new((), 0..28), + parameters: vec![], + public: true, + tipo: (), + }), + ) +} + +#[test] +fn empty_function() { + let code = indoc! {r#" + pub fn run() {} + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Todo { + kind: ast::TodoKind::EmptyFunction, + location: Span::new((), 0..15), + label: None, + }, + doc: None, + location: Span::new((), 0..12), + name: "run".to_string(), + public: true, + return_annotation: None, + return_type: (), + end_position: 14, + }), + ) +} + +#[test] +fn plus_binop() { + let code = indoc! {r#" + pub fn add_one(a) -> Int { + a + 1 + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "a".to_string(), + location: Span::new((), 15..16), + }, + location: Span::new((), 15..16), + annotation: None, + tipo: (), + }], + body: expr::UntypedExpr::BinOp { + location: Span::new((), 29..34), + name: ast::BinOp::AddInt, + left: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 29..30), + name: "a".to_string(), + }), + right: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 33..34), + value: "1".to_string(), + }), + }, + doc: None, + location: Span::new((), 0..24), + name: "add_one".to_string(), + public: true, + return_annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 21..24), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + return_type: (), + end_position: 35, + }), + ) +} + +#[test] +fn pipeline() { + let code = indoc! {r#" + pub fn thing(thing a: Int) { + a + 2 + |> add_one + |> add_one + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![ast::Arg { + arg_name: ast::ArgName::NamedLabeled { + name: "a".to_string(), + label: "thing".to_string(), + location: Span::new((), 13..20), + }, + location: Span::new((), 13..25), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 22..25), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + tipo: (), + }], + body: expr::UntypedExpr::PipeLine { + expressions: vec1::vec1![ + expr::UntypedExpr::BinOp { + location: Span::new((), 31..36), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 448..449), + location: Span::new((), 31..32), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 452..453), - value: "1".to_string(), + location: Span::new((), 35..36), + value: "2".to_string(), }), }, - doc: None, - location: Span::new((), 407..431), - name: "add_one".to_string(), - public: true, - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 428..431), - module: None, - name: "Int".to_string(), - arguments: vec![], - },), - return_type: (), - }), - ast::UntypedDefinition::Fn(Function { - end_position: 598, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::NamedLabeled { - name: "a".to_string(), - label: "thing".to_string(), - location: Span::new((), 494..501), + expr::UntypedExpr::Var { + location: Span::new((), 42..49), + name: "add_one".to_string(), + }, + expr::UntypedExpr::Var { + location: Span::new((), 55..62), + name: "add_one".to_string(), + }, + ], + }, + doc: None, + location: Span::new((), 0..26), + name: "thing".to_string(), + public: true, + return_annotation: None, + return_type: (), + end_position: 63, + }), + ) +} + +#[test] +fn if_expression() { + let code = indoc! {r#" + fn ifs() { + if True then { + 1 + 1 + } else if a < 4 then { + 5 + } else if a || b then { + 6 + } else { + 3 + } + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::If { + location: Span::new((), 13..121), + branches: vec1::vec1![ + ast::IfBranch { + condition: expr::UntypedExpr::Var { + location: Span::new((), 16..20), + name: "True".to_string(), }, - location: Span::new((), 494..506), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 503..506), - module: None, - name: "Int".to_string(), - arguments: vec![], - },), - tipo: (), - },], - body: expr::UntypedExpr::PipeLine { - expressions: vec1::vec1![ - expr::UntypedExpr::BinOp { - location: Span::new((), 526..531), + body: expr::UntypedExpr::BinOp { + location: Span::new((), 32..37), + name: ast::BinOp::AddInt, + left: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 32..33), + value: "1".to_string(), + }), + right: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 36..37), + value: "1".to_string(), + }), + }, + location: Span::new((), 16..41), + }, + ast::IfBranch { + condition: expr::UntypedExpr::BinOp { + location: Span::new((), 50..55), + name: ast::BinOp::LtInt, + left: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 50..51), + name: "a".to_string(), + }), + right: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 54..55), + value: "4".to_string(), + }), + }, + body: expr::UntypedExpr::Int { + location: Span::new((), 67..68), + value: "5".to_string(), + }, + location: Span::new((), 50..72), + }, + ast::IfBranch { + condition: expr::UntypedExpr::BinOp { + location: Span::new((), 81..87), + name: ast::BinOp::Or, + left: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 81..82), + name: "a".to_string(), + }), + right: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 86..87), + name: "b".to_string(), + }), + }, + body: expr::UntypedExpr::Int { + location: Span::new((), 99..100), + value: "6".to_string(), + }, + location: Span::new((), 81..104), + }, + ], + final_else: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 116..117), + value: "3".to_string(), + }), + }, + doc: None, + location: Span::new((), 0..8), + name: "ifs".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 122, + }), + ) +} + +#[test] +fn let_bindings() { + let code = indoc! {r#" + pub fn wow(a: Int) { + let x = + a + 2 + |> add_one + |> add_one + + let thing = [ 1, 2, a ] + + let idk = thing + + y + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "a".to_string(), + location: Span::new((), 11..12), + }, + location: Span::new((), 11..17), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 14..17), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + tipo: (), + }], + body: expr::UntypedExpr::Sequence { + location: Span::new((), 23..121), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 23..70), + value: Box::new(expr::UntypedExpr::PipeLine { + expressions: vec1::vec1![ + expr::UntypedExpr::BinOp { + location: Span::new((), 35..40), + name: ast::BinOp::AddInt, + left: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 35..36), + name: "a".to_string(), + }), + right: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 39..40), + value: "2".to_string(), + }), + }, + expr::UntypedExpr::Var { + location: Span::new((), 48..55), + name: "add_one".to_string(), + }, + expr::UntypedExpr::Var { + location: Span::new((), 63..70), + name: "add_one".to_string(), + }, + ], + }), + pattern: ast::Pattern::Var { + location: Span::new((), 27..28), + name: "x".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Assignment { + location: Span::new((), 74..97), + value: Box::new(expr::UntypedExpr::List { + location: Span::new((), 86..97), + elements: vec![ + expr::UntypedExpr::Int { + location: Span::new((), 88..89), + value: "1".to_string(), + }, + expr::UntypedExpr::Int { + location: Span::new((), 91..92), + value: "2".to_string(), + }, + expr::UntypedExpr::Var { + location: Span::new((), 94..95), + name: "a".to_string(), + }, + ], + tail: None, + }), + pattern: ast::Pattern::Var { + location: Span::new((), 78..83), + name: "thing".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Assignment { + location: Span::new((), 101..116), + value: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 111..116), + name: "thing".to_string(), + }), + pattern: ast::Pattern::Var { + location: Span::new((), 105..108), + name: "idk".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Var { + location: Span::new((), 120..121), + name: "y".to_string(), + }, + ], + }, + doc: None, + location: Span::new((), 0..18), + name: "wow".to_string(), + public: true, + return_annotation: None, + return_type: (), + end_position: 122, + }), + ) +} + +#[test] +fn block() { + let code = indoc! {r#" + pub fn wow2(a: Int){ + let b = { + let x = 4 + + x + 5 + } + + b + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "a".to_string(), + location: Span::new((), 12..13), + }, + location: Span::new((), 12..18), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 15..18), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + tipo: (), + }], + body: expr::UntypedExpr::Sequence { + location: Span::new((), 23..66), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 23..61), + value: Box::new(expr::UntypedExpr::Sequence { + location: Span::new((), 37..57), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 37..46), + value: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 45..46), + value: "4".to_string(), + }), + pattern: ast::Pattern::Var { + location: Span::new((), 41..42), + name: "x".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::BinOp { + location: Span::new((), 52..57), + name: ast::BinOp::AddInt, + left: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 52..53), + name: "x".to_string(), + }), + right: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 56..57), + value: "5".to_string(), + }), + }, + ], + }), + pattern: ast::Pattern::Var { + location: Span::new((), 27..28), + name: "b".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Var { + location: Span::new((), 65..66), + name: "b".to_string(), + }, + ], + }, + doc: None, + location: Span::new((), 0..19), + name: "wow2".to_string(), + public: true, + return_annotation: None, + return_type: (), + end_position: 67, + }), + ) +} + +#[test] +fn when() { + let code = indoc! {r#" + pub fn wow2(a: Int){ + when a, b is { + 1, 2 -> 3 + 1 | 4, 5 -> { + let amazing = 5 + + amazing + } + 3 -> 9 + _ -> 4 + } + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "a".to_string(), + location: Span::new((), 12..13), + }, + location: Span::new((), 12..18), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 15..18), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + tipo: (), + }], + body: expr::UntypedExpr::When { + location: Span::new((), 23..138), + subjects: vec![ + expr::UntypedExpr::Var { + location: Span::new((), 28..29), + name: "a".to_string(), + }, + expr::UntypedExpr::Var { + location: Span::new((), 31..32), + name: "b".to_string(), + }, + ], + clauses: vec![ + ast::Clause { + location: Span::new((), 42..51), + pattern: vec![ + ast::Pattern::Int { + location: Span::new((), 42..43), + value: "1".to_string(), + }, + ast::Pattern::Int { + location: Span::new((), 45..46), + value: "2".to_string(), + }, + ], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Int { + location: Span::new((), 50..51), + value: "3".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 56..112), + pattern: vec![ast::Pattern::Int { + location: Span::new((), 56..57), + value: "1".to_string(), + }], + alternative_patterns: vec![vec![ + ast::Pattern::Int { + location: Span::new((), 60..61), + value: "4".to_string(), + }, + ast::Pattern::Int { + location: Span::new((), 63..64), + value: "5".to_string(), + }, + ]], + guard: None, + then: expr::UntypedExpr::Sequence { + location: Span::new((), 76..106), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 76..91), + value: Box::new(expr::UntypedExpr::Int { + location: Span::new((), 90..91), + value: "5".to_string(), + }), + pattern: ast::Pattern::Var { + location: Span::new((), 80..87), + name: "amazing".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Var { + location: Span::new((), 99..106), + name: "amazing".to_string(), + }, + ], + }, + }, + ast::Clause { + location: Span::new((), 117..123), + pattern: vec![ast::Pattern::Int { + location: Span::new((), 117..118), + value: "3".to_string(), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Int { + location: Span::new((), 122..123), + value: "9".to_string(), + }, + }, + ast::Clause { + location: Span::new((), 128..134), + pattern: vec![ast::Pattern::Discard { + name: "_".to_string(), + location: Span::new((), 128..129), + }], + alternative_patterns: vec![], + guard: None, + then: expr::UntypedExpr::Int { + location: Span::new((), 133..134), + value: "4".to_string(), + }, + }, + ], + }, + doc: None, + location: Span::new((), 0..19), + name: "wow2".to_string(), + public: true, + return_annotation: None, + return_type: (), + end_position: 139, + }), + ) +} + +#[test] +fn anonymous_function() { + let code = indoc! {r#" + pub fn such() -> Int { + let add_one = fn (a: Int) -> Int { a + 1 } + + 2 |> add_one + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Sequence { + location: Span::new((), 25..83), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 25..67), + value: Box::new(expr::UntypedExpr::Fn { + location: Span::new((), 39..67), + is_capture: false, + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "a".to_string(), + location: Span::new((), 43..44), + }, + location: Span::new((), 43..49), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 46..49), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + tipo: (), + }], + body: Box::new(expr::UntypedExpr::BinOp { + location: Span::new((), 60..65), name: ast::BinOp::AddInt, left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 526..527), + location: Span::new((), 60..61), name: "a".to_string(), }), right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 530..531), - value: "2".to_string(), + location: Span::new((), 64..65), + value: "1".to_string(), }), + }), + return_annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 54..57), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + }), + pattern: ast::Pattern::Var { + location: Span::new((), 29..36), + name: "add_one".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::PipeLine { + expressions: vec1::vec1![ + expr::UntypedExpr::Int { + location: Span::new((), 71..72), + value: "2".to_string(), }, expr::UntypedExpr::Var { - location: Span::new((), 551..558), - name: "add_one".to_string(), - }, - expr::UntypedExpr::Var { - location: Span::new((), 578..585), + location: Span::new((), 76..83), name: "add_one".to_string(), }, ], }, - doc: None, - location: Span::new((), 481..507), - name: "thing".to_string(), - public: true, - return_annotation: None, - return_type: (), - }), - ast::UntypedDefinition::Fn(Function { - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "a".to_string(), - location: Span::new((), 624..625), - }, - location: Span::new((), 624..630), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 627..630), - module: None, - name: "Int".to_string(), - arguments: vec![], - },), - tipo: (), - },], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 648..818), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 648..731), - value: Box::new(expr::UntypedExpr::PipeLine { - expressions: vec1::vec1![ - expr::UntypedExpr::BinOp { - location: Span::new((), 672..677), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 672..673), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 676..677), - value: "2".to_string(), - }), - }, - expr::UntypedExpr::Var { - location: Span::new((), 697..704), - name: "add_one".to_string(), - }, - expr::UntypedExpr::Var { - location: Span::new((), 724..731), - name: "add_one".to_string(), - }, - ], - }), - pattern: ast::Pattern::Var { - location: Span::new((), 652..653), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 747..770), - value: Box::new(expr::UntypedExpr::List { - location: Span::new((), 759..770), - elements: vec![ - expr::UntypedExpr::Int { - location: Span::new((), 761..762), - value: "1".to_string(), - }, - expr::UntypedExpr::Int { - location: Span::new((), 764..765), - value: "2".to_string(), - }, - expr::UntypedExpr::Var { - location: Span::new((), 767..768), - name: "a".to_string(), - }, - ], - tail: None, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 751..756), - name: "thing".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 786..801), - value: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 796..801), - name: "thing".to_string(), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 790..793), - name: "idk".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Var { - location: Span::new((), 817..818), - name: "y".to_string(), - }, - ], - }, - doc: None, - location: Span::new((), 613..631), - name: "wow".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 831, - }), - ast::UntypedDefinition::Fn(Function { - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "a".to_string(), - location: Span::new((), 858..859), - }, - location: Span::new((), 858..864), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 861..864), - module: None, - name: "Int".to_string(), - arguments: vec![], - },), - tipo: (), - },], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 881..1182), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 881..955), - value: Box::new(expr::UntypedExpr::Sequence { - location: Span::new((), 907..939), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 907..916), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 915..916), - value: "4".to_string(), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 911..912), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::BinOp { - location: Span::new((), 934..939), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 934..935), - name: "x".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 938..939), - value: "5".to_string(), - }), - }, - ], - }), - pattern: ast::Pattern::Var { - location: Span::new((), 885..886), - name: "b".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::When { - location: Span::new((), 971..1182), - subjects: vec![ - expr::UntypedExpr::Var { - location: Span::new((), 976..977), - name: "a".to_string(), - }, - expr::UntypedExpr::Var { - location: Span::new((), 979..980), - name: "b".to_string(), - }, - ], - clauses: vec![ - ast::Clause { - location: Span::new((), 1002..1011), - pattern: vec![ - ast::Pattern::Int { - location: Span::new((), 1002..1003), - value: "1".to_string(), - }, - ast::Pattern::Int { - location: Span::new((), 1005..1006), - value: "2".to_string(), - }, - ], - alternative_patterns: vec![], - guard: None, - then: expr::UntypedExpr::Int { - location: Span::new((), 1010..1011), - value: "3".to_string(), - }, - }, - ast::Clause { - location: Span::new((), 1028..1120), - pattern: vec![ast::Pattern::Int { - location: Span::new((), 1028..1029), - value: "1".to_string(), - },], - alternative_patterns: vec![vec![ - ast::Pattern::Int { - location: Span::new((), 1032..1033), - value: "4".to_string(), - }, - ast::Pattern::Int { - location: Span::new((), 1035..1036), - value: "5".to_string(), - }, - ],], - guard: None, - then: expr::UntypedExpr::Sequence { - location: Span::new((), 1060..1102), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 1060..1075), - value: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 1074..1075), - value: "5".to_string(), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 1064..1071), - name: "amazing".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Var { - location: Span::new((), 1095..1102), - name: "amazing".to_string(), - }, - ], - }, - }, - ast::Clause { - location: Span::new((), 1137..1143), - pattern: vec![ast::Pattern::Int { - location: Span::new((), 1137..1138), - value: "3".to_string(), - },], - alternative_patterns: vec![], - guard: None, - then: expr::UntypedExpr::Int { - location: Span::new((), 1142..1143), - value: "9".to_string(), - }, - }, - ast::Clause { - location: Span::new((), 1160..1166), - pattern: vec![ast::Pattern::Discard { - name: "_".to_string(), - location: Span::new((), 1160..1161), - },], - alternative_patterns: vec![], - guard: None, - then: expr::UntypedExpr::Int { - location: Span::new((), 1165..1166), - value: "4".to_string(), - }, - }, - ], - }, - ], - }, - doc: None, - location: Span::new((), 846..865), - name: "wow2".to_string(), - public: true, - return_annotation: None, - return_type: (), - end_position: 1195, - }), - ast::UntypedDefinition::Fn(Function { + ], + }, + doc: None, + location: Span::new((), 0..20), + name: "such".to_string(), + public: true, + return_annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 17..20), + module: None, + name: "Int".to_string(), + arguments: vec![], + }), + return_type: (), + end_position: 84, + }), + ) +} + +#[test] +fn field_access() { + let code = indoc! {r#" + fn name(user: User) { + user.name + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "user".to_string(), + location: Span::new((), 8..12), + }, + location: Span::new((), 8..18), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 14..18), + module: None, + name: "User".to_string(), arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 1249..1321), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 1249..1291), - value: Box::new(expr::UntypedExpr::Fn { - location: Span::new((), 1263..1291), - is_capture: false, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "a".to_string(), - location: Span::new((), 1267..1268), - }, - location: Span::new((), 1267..1273), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 1270..1273), - module: None, - name: "Int".to_string(), - arguments: vec![], - },), - tipo: (), - },], - body: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 1284..1289), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1284..1285), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 1288..1289), - value: "1".to_string(), - }), - }), - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 1278..1281), - module: None, - name: "Int".to_string(), - arguments: vec![], - },), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 1253..1260), - name: "add_one".to_string(), + }), + tipo: (), + }], + body: expr::UntypedExpr::FieldAccess { + location: Span::new((), 24..33), + label: "name".to_string(), + container: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 24..28), + name: "user".to_string(), + }), + }, + doc: None, + location: Span::new((), 0..19), + name: "name".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 34, + }), + ) +} + +#[test] +fn call() { + let code = indoc! {r#" + fn calls() { + let x = add_one(3) + + let map_add_x = list.map(_, fn (y) { x + y }) + + map_add_x([ 1, 2, 3 ]) + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![], + body: expr::UntypedExpr::Sequence { + location: Span::new((), 15..108), + expressions: vec![ + expr::UntypedExpr::Assignment { + location: Span::new((), 15..33), + value: Box::new(expr::UntypedExpr::Call { + arguments: vec![ast::CallArg { + label: None, + location: Span::new((), 31..32), + value: expr::UntypedExpr::Int { + location: Span::new((), 31..32), + value: "3".to_string(), }, - kind: ast::AssignmentKind::Let, + }], + fun: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 23..30), + name: "add_one".to_string(), + }), + location: Span::new((), 30..33), + }), + pattern: ast::Pattern::Var { + location: Span::new((), 19..20), + name: "x".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Assignment { + location: Span::new((), 37..82), + value: Box::new(expr::UntypedExpr::Fn { + location: Span::new((), 61..82), + is_capture: true, + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "_capture__0".to_string(), + location: Span::new((), 0..0), + }, + location: Span::new((), 0..0), annotation: None, - }, - expr::UntypedExpr::PipeLine { - expressions: vec1::vec1![ + tipo: (), + }], + body: Box::new(expr::UntypedExpr::Call { + arguments: vec![ + ast::CallArg { + label: None, + location: Span::new((), 62..63), + value: expr::UntypedExpr::Var { + location: Span::new((), 62..63), + name: "_capture__0".to_string(), + }, + }, + ast::CallArg { + label: None, + location: Span::new((), 65..81), + value: expr::UntypedExpr::Fn { + location: Span::new((), 65..81), + is_capture: false, + arguments: vec![ast::Arg { + arg_name: ast::ArgName::Named { + name: "y".to_string(), + location: Span::new((), 69..70), + }, + location: Span::new((), 69..70), + annotation: None, + tipo: (), + }], + body: Box::new(expr::UntypedExpr::BinOp { + location: Span::new((), 74..79), + name: ast::BinOp::AddInt, + left: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 74..75), + name: "x".to_string(), + }), + right: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 78..79), + name: "y".to_string(), + }), + }), + return_annotation: None, + }, + }, + ], + fun: Box::new(expr::UntypedExpr::FieldAccess { + location: Span::new((), 53..61), + label: "map".to_string(), + container: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 53..57), + name: "list".to_string(), + }), + }), + location: Span::new((), 61..82), + }), + return_annotation: None, + }), + pattern: ast::Pattern::Var { + location: Span::new((), 41..50), + name: "map_add_x".to_string(), + }, + kind: ast::AssignmentKind::Let, + annotation: None, + }, + expr::UntypedExpr::Call { + arguments: vec![ast::CallArg { + label: None, + location: Span::new((), 96..107), + value: expr::UntypedExpr::List { + location: Span::new((), 96..107), + elements: vec![ expr::UntypedExpr::Int { - location: Span::new((), 1309..1310), + location: Span::new((), 98..99), + value: "1".to_string(), + }, + expr::UntypedExpr::Int { + location: Span::new((), 101..102), value: "2".to_string(), }, - expr::UntypedExpr::Var { - location: Span::new((), 1314..1321), - name: "add_one".to_string(), + expr::UntypedExpr::Int { + location: Span::new((), 104..105), + value: "3".to_string(), }, ], + tail: None, }, - ], - }, - doc: None, - location: Span::new((), 1210..1230), - name: "such".to_string(), - public: true, - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 1227..1230), - module: None, - name: "Int".to_string(), - arguments: vec![], - },), - return_type: (), - end_position: 1334, - }), - ast::UntypedDefinition::Fn(Function { - arguments: vec![], - body: expr::UntypedExpr::Todo { - kind: ast::TodoKind::EmptyFunction, - location: Span::new((), 1349..1360), - label: None, - }, - doc: None, - location: Span::new((), 1349..1357), - name: "run".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 1359, - }), - ast::UntypedDefinition::Fn(Function { - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "user".to_string(), - location: Span::new((), 1382..1386), - }, - location: Span::new((), 1382..1392), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 1388..1392), - module: None, - name: "User".to_string(), - arguments: vec![], - },), - tipo: (), - },], - body: expr::UntypedExpr::FieldAccess { - location: Span::new((), 1412..1421), - label: "name".to_string(), - container: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1412..1416), - name: "user".to_string(), + }], + fun: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 86..95), + name: "map_add_x".to_string(), }), + location: Span::new((), 95..108), }, - doc: None, - location: Span::new((), 1374..1393), - name: "name".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 1434, - }), - ast::UntypedDefinition::Fn(Function { - arguments: vec![], - body: expr::UntypedExpr::Sequence { - location: Span::new((), 1478..1599), - expressions: vec![ - expr::UntypedExpr::Assignment { - location: Span::new((), 1478..1496), - value: Box::new(expr::UntypedExpr::Call { - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 1494..1495), - value: expr::UntypedExpr::Int { - location: Span::new((), 1494..1495), - value: "3".to_string(), - }, - }], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1486..1493), - name: "add_one".to_string(), - }), - location: Span::new((), 1493..1496), - }), - pattern: ast::Pattern::Var { - location: Span::new((), 1482..1483), - name: "x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Assignment { - location: Span::new((), 1514..1559), - value: Box::new(expr::UntypedExpr::Fn { - location: Span::new((), 1538..1559), - is_capture: true, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "_capture__0".to_string(), - location: Span::new((), 0..0), - }, - location: Span::new((), 0..0), - annotation: None, - tipo: (), - },], - body: Box::new(expr::UntypedExpr::Call { - arguments: vec![ - ast::CallArg { - label: None, - location: Span::new((), 1539..1540), - value: expr::UntypedExpr::Var { - location: Span::new((), 1539..1540), - name: "_capture__0".to_string(), - }, - }, - ast::CallArg { - label: None, - location: Span::new((), 1542..1558), - value: expr::UntypedExpr::Fn { - location: Span::new((), 1542..1558), - is_capture: false, - arguments: vec![ast::Arg { - arg_name: ast::ArgName::Named { - name: "y".to_string(), - location: Span::new((), 1546..1547), - }, - location: Span::new((), 1546..1547), - annotation: None, - tipo: (), - },], - body: Box::new(expr::UntypedExpr::BinOp { - location: Span::new((), 1551..1556), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1551..1552), - name: "x".to_string(), - }), - right: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1555..1556), - name: "y".to_string(), - }), - }), - return_annotation: None, - }, - }, - ], - fun: Box::new(expr::UntypedExpr::FieldAccess { - location: Span::new((), 1530..1538), - label: "map".to_string(), - container: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1530..1534), - name: "list".to_string(), - }), - }), - location: Span::new((), 1538..1559), - }), - return_annotation: None, - }), - pattern: ast::Pattern::Var { - location: Span::new((), 1518..1527), - name: "map_add_x".to_string(), - }, - kind: ast::AssignmentKind::Let, - annotation: None, - }, - expr::UntypedExpr::Call { - arguments: vec![ast::CallArg { - label: None, - location: Span::new((), 1587..1598), - value: expr::UntypedExpr::List { - location: Span::new((), 1587..1598), - elements: vec![ - expr::UntypedExpr::Int { - location: Span::new((), 1589..1590), - value: "1".to_string(), - }, - expr::UntypedExpr::Int { - location: Span::new((), 1592..1593), - value: "2".to_string(), - }, - expr::UntypedExpr::Int { - location: Span::new((), 1595..1596), - value: "3".to_string(), - }, - ], - tail: None, - }, - }], - fun: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1577..1586), - name: "map_add_x".to_string(), - }), - location: Span::new((), 1586..1599), - }, - ], + ], + }, + doc: None, + location: Span::new((), 0..10), + name: "calls".to_string(), + public: false, + return_annotation: None, + return_type: (), + end_position: 109, + }), + ) +} + +#[test] +fn record_update() { + let code = indoc! {r#" + fn update_name(user: User, name: String) -> User { + User { ..user, name: "Aiken", } + } + "#}; + + assert_definition( + code, + ast::UntypedDefinition::Fn(Function { + arguments: vec![ + ast::Arg { + arg_name: ast::ArgName::Named { + name: "user".to_string(), + location: Span::new((), 15..19), }, - doc: None, - location: Span::new((), 1449..1459), - name: "calls".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 1612, - }), - ast::UntypedDefinition::Fn(Function { - arguments: vec![ - ast::Arg { - arg_name: ast::ArgName::Named { - name: "user".to_string(), - location: Span::new((), 1642..1646), - }, - location: Span::new((), 1642..1652), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 1648..1652), - module: None, - name: "User".to_string(), - arguments: vec![], - },), - tipo: (), - }, - ast::Arg { - arg_name: ast::ArgName::Named { - name: "name".to_string(), - location: Span::new((), 1654..1658), - }, - location: Span::new((), 1654..1666), - annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 1660..1666), - module: None, - name: "String".to_string(), - arguments: vec![], - },), - tipo: (), - }, - ], - body: expr::UntypedExpr::RecordUpdate { - location: Span::new((), 1694..1725), - constructor: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1694..1698), - name: "User".to_string(), - }), - spread: ast::RecordUpdateSpread { - base: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1703..1707), - name: "user".to_string(), - }), - location: Span::new((), 1699..1725), - }, - arguments: vec![ast::UntypedRecordUpdateArg { - label: "name".to_string(), - location: Span::new((), 1709..1722), - value: expr::UntypedExpr::String { - location: Span::new((), 1715..1722), - value: "Aiken".to_string(), - }, - },], - }, - doc: None, - location: Span::new((), 1627..1675), - name: "update_name".to_string(), - public: false, - return_annotation: Some(ast::Annotation::Constructor { - location: Span::new((), 1671..1675), + location: Span::new((), 15..25), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 21..25), module: None, name: "User".to_string(), arguments: vec![], - },), - return_type: (), - end_position: 1738, - }), - ast::UntypedDefinition::Fn(Function { - arguments: vec![], - body: expr::UntypedExpr::If { - location: Span::new((), 1780..1993), - branches: vec1::vec1![ - ast::IfBranch { - condition: expr::UntypedExpr::Var { - location: Span::new((), 1783..1787), - name: "True".to_string(), - }, - body: expr::UntypedExpr::BinOp { - location: Span::new((), 1810..1815), - name: ast::BinOp::AddInt, - left: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 1810..1811), - value: "1".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 1814..1815), - value: "1".to_string(), - }), - }, - location: Span::new((), 1783..1833), - }, - ast::IfBranch { - condition: expr::UntypedExpr::BinOp { - location: Span::new((), 1842..1847), - name: ast::BinOp::LtInt, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1842..1843), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 1846..1847), - value: "4".to_string(), - }), - }, - body: expr::UntypedExpr::Int { - location: Span::new((), 1870..1871), - value: "5".to_string(), - }, - location: Span::new((), 1842..1889), - }, - ast::IfBranch { - condition: expr::UntypedExpr::BinOp { - location: Span::new((), 1898..1904), - name: ast::BinOp::Or, - left: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1898..1899), - name: "a".to_string(), - }), - right: Box::new(expr::UntypedExpr::Var { - location: Span::new((), 1903..1904), - name: "b".to_string(), - }), - }, - body: expr::UntypedExpr::Int { - location: Span::new((), 1927..1928), - value: "6".to_string(), - }, - location: Span::new((), 1898..1946), - }, - ], - final_else: Box::new(expr::UntypedExpr::Int { - location: Span::new((), 1974..1975), - value: "3".to_string(), - }), + }), + tipo: (), + }, + ast::Arg { + arg_name: ast::ArgName::Named { + name: "name".to_string(), + location: Span::new((), 27..31), }, - doc: None, - location: Span::new((), 1753..1761), - name: "ifs".to_string(), - public: false, - return_annotation: None, - return_type: (), - end_position: 2006, + location: Span::new((), 27..39), + annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 33..39), + module: None, + name: "String".to_string(), + arguments: vec![], + }), + tipo: (), + }, + ], + body: expr::UntypedExpr::RecordUpdate { + location: Span::new((), 53..84), + constructor: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 53..57), + name: "User".to_string(), }), - ] - }, - ); + spread: ast::RecordUpdateSpread { + base: Box::new(expr::UntypedExpr::Var { + location: Span::new((), 62..66), + name: "user".to_string(), + }), + location: Span::new((), 60..66), + }, + arguments: vec![ast::UntypedRecordUpdateArg { + label: "name".to_string(), + location: Span::new((), 68..81), + value: expr::UntypedExpr::String { + location: Span::new((), 74..81), + value: "Aiken".to_string(), + }, + }], + }, + doc: None, + location: Span::new((), 0..48), + name: "update_name".to_string(), + public: false, + return_annotation: Some(ast::Annotation::Constructor { + location: Span::new((), 44..48), + module: None, + name: "User".to_string(), + arguments: vec![], + }), + return_type: (), + end_position: 85, + }), + ) } diff --git a/examples/sample/validators/swap.ak b/examples/sample/validators/swap.ak index b18bf996..0e7ad91e 100644 --- a/examples/sample/validators/swap.ak +++ b/examples/sample/validators/swap.ak @@ -32,7 +32,7 @@ pub fn final_check(z: Int) { } pub fn incrementor(counter: Int, target: Int) -> Int { - if counter == target { + if counter == target then { target } else { incrementor(counter + 1, target)