From 2cb2c7fa1f23ed93a61d52cffccf41a43bfa5e88 Mon Sep 17 00:00:00 2001 From: KtorZ Date: Thu, 18 Apr 2024 17:18:57 +0200 Subject: [PATCH] Add dedicated 'Pair' typed and untyped expression Before this commit, we would parse 'Pair' as a user-defined data-types, and thus piggybacking on that whole record system. While perhaps handy for some things, it's also semantically wrong and induces a lot more complexity in codegen which now needs to systematically distinguish every data-type access between pairs, and others. So it's better to have it as a separate expression, and handle it similar to tuples (since it's fundamentally a 2-tuple with a special serialization). --- crates/aiken-lang/src/ast.rs | 1 + crates/aiken-lang/src/builtins.rs | 7 ++- crates/aiken-lang/src/expr.rs | 23 ++++++++ crates/aiken-lang/src/format.rs | 8 +++ crates/aiken-lang/src/gen_uplc.rs | 14 ++++- crates/aiken-lang/src/parser/expr/chained.rs | 27 ++++------ crates/aiken-lang/src/parser/expr/mod.rs | 2 + crates/aiken-lang/src/parser/expr/pair.rs | 53 +++++++++++++++++++ .../src/parser/expr/snapshots/basic_pair.snap | 21 ++++++++ .../expr/snapshots/pair_from_prelude.snap | 21 ++++++++ crates/aiken-lang/src/tipo/expr.rs | 25 ++++++++- ...export__tests__cannot_export_generics.snap | 2 +- ...t__export__tests__recursive_types.snap.new | 53 +++++++++++++++++++ 13 files changed, 235 insertions(+), 22 deletions(-) create mode 100644 crates/aiken-lang/src/parser/expr/pair.rs create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/basic_pair.snap create mode 100644 crates/aiken-lang/src/parser/expr/snapshots/pair_from_prelude.snap create mode 100644 crates/aiken-project/src/snapshots/aiken_project__export__tests__recursive_types.snap.new diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index 1e1fc5c6..622c455f 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -970,6 +970,7 @@ pub enum Annotation { location: Span, elems: Vec, }, + Pair { location: Span, fst: Box, diff --git a/crates/aiken-lang/src/builtins.rs b/crates/aiken-lang/src/builtins.rs index 37a4f05b..c522442b 100644 --- a/crates/aiken-lang/src/builtins.rs +++ b/crates/aiken-lang/src/builtins.rs @@ -15,6 +15,9 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use strum::IntoEnumIterator; use uplc::builtins::DefaultFunction; +pub const PRELUDE: &str = "aiken"; +pub const BUILTIN: &str = "aiken/builtin"; + pub const BYTE_ARRAY: &str = "ByteArray"; pub const BOOL: &str = "Bool"; pub const INT: &str = "Int"; @@ -37,7 +40,7 @@ pub const FUZZER: &str = "Fuzzer"; /// into a compiler pipeline pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { let mut prelude = TypeInfo { - name: "aiken".to_string(), + name: PRELUDE.to_string(), package: "".to_string(), kind: ModuleKind::Lib, types: HashMap::new(), @@ -592,7 +595,7 @@ pub fn prelude(id_gen: &IdGenerator) -> TypeInfo { pub fn plutus(id_gen: &IdGenerator) -> TypeInfo { let mut plutus = TypeInfo { - name: "aiken/builtin".to_string(), + name: BUILTIN.to_string(), package: "".to_string(), kind: ModuleKind::Lib, types: HashMap::new(), diff --git a/crates/aiken-lang/src/expr.rs b/crates/aiken-lang/src/expr.rs index 7655c9ff..a2d5c007 100644 --- a/crates/aiken-lang/src/expr.rs +++ b/crates/aiken-lang/src/expr.rs @@ -161,6 +161,13 @@ pub enum TypedExpr { elems: Vec, }, + Pair { + location: Span, + tipo: Rc, + fst: Box, + snd: Box, + }, + TupleIndex { location: Span, tipo: Rc, @@ -214,6 +221,7 @@ impl TypedExpr { | Self::UnOp { tipo, .. } | Self::BinOp { tipo, .. } | Self::Tuple { tipo, .. } + | Self::Pair { tipo, .. } | Self::String { tipo, .. } | Self::ByteArray { tipo, .. } | Self::TupleIndex { tipo, .. } @@ -256,6 +264,7 @@ impl TypedExpr { | TypedExpr::ErrorTerm { .. } | TypedExpr::BinOp { .. } | TypedExpr::Tuple { .. } + | TypedExpr::Pair { .. } | TypedExpr::UnOp { .. } | TypedExpr::String { .. } | TypedExpr::Sequence { .. } @@ -299,6 +308,7 @@ impl TypedExpr { | Self::List { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } + | Self::Pair { location, .. } | Self::String { location, .. } | Self::UnOp { location, .. } | Self::Pipeline { location, .. } @@ -337,6 +347,7 @@ impl TypedExpr { | Self::List { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } + | Self::Pair { location, .. } | Self::String { location, .. } | Self::UnOp { location, .. } | Self::Sequence { location, .. } @@ -392,6 +403,11 @@ impl TypedExpr { .find_map(|e| e.find_node(byte_index)) .or(Some(Located::Expression(self))), + TypedExpr::Pair { fst, snd, .. } => [fst, snd] + .iter() + .find_map(|e| e.find_node(byte_index)) + .or(Some(Located::Expression(self))), + TypedExpr::List { elements, tail, .. } => elements .iter() .find_map(|e| e.find_node(byte_index)) @@ -578,6 +594,12 @@ pub enum UntypedExpr { elems: Vec, }, + Pair { + location: Span, + fst: Box, + snd: Box, + }, + TupleIndex { location: Span, index: usize, @@ -1272,6 +1294,7 @@ impl UntypedExpr { | Self::ByteArray { location, .. } | Self::BinOp { location, .. } | Self::Tuple { location, .. } + | Self::Pair { location, .. } | Self::String { location, .. } | Self::Assignment { location, .. } | Self::TupleIndex { location, .. } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index 7b77b1df..e1a7b095 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -980,6 +980,14 @@ impl<'comments> Formatter<'comments> { wrap_args(elems.iter().map(|e| (self.wrap_expr(e), false))).group() } + UntypedExpr::Pair { fst, snd, .. } => "Pair" + .to_doc() + .append("(") + .append(self.expr(fst, false)) + .append(",") + .append(self.expr(snd, false)) + .append(")"), + UntypedExpr::TupleIndex { index, tuple, .. } => { let suffix = Ordinal(*index + 1).suffix().to_doc(); self.expr(tuple, false) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index cd7da698..7bfdca9d 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -373,7 +373,13 @@ impl<'a> CodeGenerator<'a> { .. } => { let data_type = lookup_data_type_by_tipo(&self.data_types, tipo) - .expect("Creating a record with no record definition."); + .unwrap_or_else(|| + panic!( + "Creating a record of type {:?} with no record definition. Known definitions: {:?}", + tipo.to_pretty(0), + self.data_types.keys() + ) + ); let (constr_index, _) = data_type .constructors @@ -758,6 +764,12 @@ impl<'a> CodeGenerator<'a> { } }, + TypedExpr::Pair { tipo, fst, snd, .. } => AirTree::pair( + self.build(fst, module_build_name, &[]), + self.build(snd, module_build_name, &[]), + tipo.clone(), + ), + TypedExpr::Tuple { tipo, elems, .. } => AirTree::tuple( elems .iter() diff --git a/crates/aiken-lang/src/parser/expr/chained.rs b/crates/aiken-lang/src/parser/expr/chained.rs index 05b7f893..13ea7940 100644 --- a/crates/aiken-lang/src/parser/expr/chained.rs +++ b/crates/aiken-lang/src/parser/expr/chained.rs @@ -1,20 +1,11 @@ -use chumsky::prelude::*; - -use super::anonymous_function::parser as anonymous_function; -use super::assignment; -use super::block::parser as block; -use super::bytearray::parser as bytearray; -use super::if_else::parser as if_else; -use super::int::parser as int; -use super::list::parser as list; -use super::record::parser as record; -use super::record_update::parser as record_update; -use super::string::parser as string; -use super::tuple::parser as tuple; -use super::var::parser as var; -use super::when::parser as when; -use super::{and_or_chain, anonymous_binop::parser as anonymous_binop}; - +use super::{ + and_or_chain, anonymous_binop::parser as anonymous_binop, + anonymous_function::parser as anonymous_function, assignment, block::parser as block, + bytearray::parser as bytearray, if_else::parser as if_else, int::parser as int, + list::parser as list, pair::parser as pair, record::parser as record, + record_update::parser as record_update, string::parser as string, tuple::parser as tuple, + var::parser as var, when::parser as when, +}; use crate::{ expr::UntypedExpr, parser::{ @@ -23,6 +14,7 @@ use crate::{ token::Token, }, }; +use chumsky::prelude::*; pub fn parser<'a>( sequence: Recursive<'a, Token, UntypedExpr, ParseError>, @@ -58,6 +50,7 @@ pub fn chain_start<'a>( choice(( string(), int(), + pair(expression.clone()), record_update(expression.clone()), record(expression.clone()), field_access::constructor(), diff --git a/crates/aiken-lang/src/parser/expr/mod.rs b/crates/aiken-lang/src/parser/expr/mod.rs index d9f6afb2..b415ddc4 100644 --- a/crates/aiken-lang/src/parser/expr/mod.rs +++ b/crates/aiken-lang/src/parser/expr/mod.rs @@ -12,6 +12,7 @@ mod fail_todo_trace; mod if_else; mod int; mod list; +mod pair; mod record; mod record_update; mod sequence; @@ -31,6 +32,7 @@ pub use fail_todo_trace::parser as fail_todo_trace; pub use if_else::parser as if_else; pub use int::parser as int; pub use list::parser as list; +pub use pair::parser as pair; pub use record::parser as record; pub use record_update::parser as record_update; pub use sequence::parser as sequence; diff --git a/crates/aiken-lang/src/parser/expr/pair.rs b/crates/aiken-lang/src/parser/expr/pair.rs new file mode 100644 index 00000000..5862c2cf --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/pair.rs @@ -0,0 +1,53 @@ +use crate::{ + builtins::{PAIR, PRELUDE}, + expr::UntypedExpr, + parser::{error::ParseError, token::Token}, +}; +use chumsky::prelude::*; + +pub fn parser( + r: Recursive<'_, Token, UntypedExpr, ParseError>, +) -> impl Parser + '_ { + select! {Token::Name { name } if &name == PRELUDE => name} + .then_ignore(just(Token::Dot)) + .or_not() + .then_ignore(select! {Token::UpName { name } if &name == PAIR => name}) + .ignore_then( + r.clone() + .separated_by(just(Token::Comma)) + .exactly(2) + .allow_trailing() + .delimited_by( + choice((just(Token::LeftParen), just(Token::NewLineLeftParen))), + just(Token::RightParen), + ) + .map_with_span(|elems, location| UntypedExpr::Pair { + location, + fst: elems + .first() + .expect("Pair should have exactly 2 elements") + .to_owned() + .into(), + snd: elems + .last() + .expect("Pair should have exactly 2 elements") + .to_owned() + .into(), + }), + ) +} + +#[cfg(test)] +mod tests { + use crate::assert_expr; + + #[test] + fn basic_pair() { + assert_expr!(r#"Pair(1, 2)"#); + } + + #[test] + fn pair_from_prelude() { + assert_expr!(r#"aiken.Pair(1, 2)"#); + } +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/basic_pair.snap b/crates/aiken-lang/src/parser/expr/snapshots/basic_pair.snap new file mode 100644 index 00000000..c352868e --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/basic_pair.snap @@ -0,0 +1,21 @@ +--- +source: crates/aiken-lang/src/parser/expr/pair.rs +description: "Code:\n\nPair(1, 2)" +--- +Pair { + location: 4..10, + fst: UInt { + location: 5..6, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + snd: UInt { + location: 8..9, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, +} diff --git a/crates/aiken-lang/src/parser/expr/snapshots/pair_from_prelude.snap b/crates/aiken-lang/src/parser/expr/snapshots/pair_from_prelude.snap new file mode 100644 index 00000000..a3e3a8d1 --- /dev/null +++ b/crates/aiken-lang/src/parser/expr/snapshots/pair_from_prelude.snap @@ -0,0 +1,21 @@ +--- +source: crates/aiken-lang/src/parser/expr/pair.rs +description: "Code:\n\naiken.Pair(1, 2)" +--- +Pair { + location: 10..16, + fst: UInt { + location: 11..12, + value: "1", + base: Decimal { + numeric_underscore: false, + }, + }, + snd: UInt { + location: 14..15, + value: "2", + base: Decimal { + numeric_underscore: false, + }, + }, +} diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index ec0cb9e5..3d4b930d 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -16,7 +16,7 @@ use crate::{ UntypedClauseGuard, UntypedIfBranch, UntypedPattern, UntypedRecordUpdateArg, }, builtins::{ - bool, byte_array, function, g1_element, g2_element, int, list, string, tuple, void, + bool, byte_array, function, g1_element, g2_element, int, list, pair, string, tuple, void, }, expr::{FnStyle, TypedExpr, UntypedExpr}, format, @@ -226,6 +226,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> { UntypedExpr::Tuple { location, elems } => self.infer_tuple(elems, location), + UntypedExpr::Pair { location, fst, snd } => self.infer_pair(*fst, *snd, location), + UntypedExpr::String { location, value } => Ok(self.infer_string(value, location)), UntypedExpr::LogicalOpChain { @@ -2020,6 +2022,26 @@ impl<'a, 'b> ExprTyper<'a, 'b> { } } + fn infer_pair( + &mut self, + fst: UntypedExpr, + snd: UntypedExpr, + location: Span, + ) -> Result { + let typed_fst = self.infer(fst)?; + ensure_serialisable(false, typed_fst.tipo(), location)?; + + let typed_snd = self.infer(snd)?; + ensure_serialisable(false, typed_snd.tipo(), location)?; + + Ok(TypedExpr::Pair { + location, + tipo: pair(typed_fst.tipo(), typed_snd.tipo()), + fst: typed_fst.into(), + snd: typed_snd.into(), + }) + } + fn infer_tuple(&mut self, elems: Vec, location: Span) -> Result { let mut typed_elems = vec![]; @@ -2343,6 +2365,7 @@ fn assert_no_assignment(expr: &UntypedExpr) -> Result<(), Error> { | UntypedExpr::Sequence { .. } | UntypedExpr::String { .. } | UntypedExpr::Tuple { .. } + | UntypedExpr::Pair { .. } | UntypedExpr::TupleIndex { .. } | UntypedExpr::UnOp { .. } | UntypedExpr::Var { .. } diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap index a94ac91f..d3effaf3 100644 --- a/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__cannot_export_generics.snap @@ -9,7 +9,7 @@ Schema { Var { tipo: RefCell { value: Generic { - id: 33, + id: 37, }, }, alias: None, diff --git a/crates/aiken-project/src/snapshots/aiken_project__export__tests__recursive_types.snap.new b/crates/aiken-project/src/snapshots/aiken_project__export__tests__recursive_types.snap.new new file mode 100644 index 00000000..27bafdb7 --- /dev/null +++ b/crates/aiken-project/src/snapshots/aiken_project__export__tests__recursive_types.snap.new @@ -0,0 +1,53 @@ +--- +source: crates/aiken-project/src/export.rs +assertion_line: 154 +description: "Code:\n\npub type Foo {\n Empty\n Bar(a, Foo)\n}\n\npub fn add(a: Foo, b: Foo) -> Int {\n when (a, b) is {\n (Empty, Empty) -> 0\n (Bar(x, y), Bar(c, d)) -> x + c + add(y, d)\n (Empty, Bar(c, d)) -> c + add(Empty, d)\n (Bar(x, y), Empty) -> x + add(y, Empty)\n }\n}\n" +--- +{ + "name": "test_module.add", + "parameters": [ + { + "title": "a", + "schema": { + "$ref": "#/definitions/test_module~1Foo$Int" + } + }, + { + "title": "b", + "schema": { + "$ref": "#/definitions/test_module~1Foo$Int" + } + } + ], + "compiledCode": "5901d501000032323232323222323232323253330083002300937540062a666010600460126ea801052000001001132323232533300b3004300c375400c2646464a66601c600e601e6ea80284c8cdc019b80003375a60260026600c0046026602800260206ea8028010c044c048008dd6980800098069baa006001132533300b3005300c375400c2a666016600860186ea801c4c8cdc01bad3010001330034c103d879800030103011001300d375400e00200226466e00dd698078009980118079808000a60103d8798000300c375400a600200244464646464a66601e601260206ea800854ccc03cc024c040dd50018a4000002002264a66601e601060206ea80084c8c8c94ccc048c02cc04cdd500309919b80337000066eb4c05c004ccc02c02c008c05cc060004c050dd5003002180a980b0011bad301400130113754004002264a66601e601260206ea800854ccc03cc020c040dd500189919b80375a6028002666010010980103d8798000301430150013011375400600200226466e00dd698098009998038039809980a000a60103d8798000301037540026022004602060220026601c0046601c00297ae0370e90011b8748000c024008c020c024004cc018008cc0180052f5c0ae6955ceaab9e5740ae855d101", + "hash": "dca86b6e092019b67ef310ba8360682d7bf8284cc728c6b525fb0b0d", + "definitions": { + "Int": { + "dataType": "integer" + }, + "test_module/Foo$Int": { + "title": "Foo", + "anyOf": [ + { + "title": "Empty", + "dataType": "constructor", + "index": 0, + "fields": [] + }, + { + "title": "Bar", + "dataType": "constructor", + "index": 1, + "fields": [ + { + "$ref": "#/definitions/Int" + }, + { + "$ref": "#/definitions/test_module~1Foo$Int" + } + ] + } + ] + } + } +}