From 4f9f00f34a4c16662c9cb97a0d2f3da2e42dc366 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 7 Sep 2024 14:27:32 +1000 Subject: [PATCH 01/18] feat: union types --- src/modules/types.rs | 77 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index 02a7eb92..960810fb 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -1,19 +1,58 @@ use std::fmt::Display; use heraclitus_compiler::prelude::*; +use itertools::Itertools; use crate::utils::ParserMetadata; -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Eq, Default)] pub enum Type { #[default] Null, Text, Bool, Num, + Union(Vec>), Array(Box), Failable(Box), Generic } +impl Type { + pub fn is_union(&self) -> bool { + match self { + Type::Union(_) => true, + _ => false + } + } + + fn eq_union_normal(one: &Vec>, other: &Type) -> bool { + one.iter().find(|x| (***x).to_string() == other.to_string()).is_some() + } + + fn eq_unions(one: &Vec>, other: &Vec>) -> bool { + one.iter().find(|x| { + Self::eq_union_normal(other, x) + }).is_some() + } +} + +impl PartialEq for Type { + fn eq(&self, other: &Self) -> bool { + if let Type::Union(union) = self { + if let Type::Union(other) = other { + return Type::eq_unions(union, other); + } else { + return Type::eq_union_normal(union, other); + } + } + + if let Type::Union(other) = other { + Type::eq_union_normal(other, self) + } else { + self.to_string() == other.to_string() + } + } +} + impl Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -21,6 +60,7 @@ impl Display for Type { Type::Bool => write!(f, "Bool"), Type::Num => write!(f, "Num"), Type::Null => write!(f, "Null"), + Type::Union(types) => write!(f, "{}", types.iter().map(|x| format!("{x}")).join(" | ")), Type::Array(t) => write!(f, "[{}]", t), Type::Failable(t) => write!(f, "{}?", t), Type::Generic => write!(f, "Generic") @@ -39,10 +79,8 @@ pub fn parse_type(meta: &mut ParserMetadata) -> Result { .map_err(|_| Failure::Loud(Message::new_err_at_token(meta, tok).message("Expected a data type"))) } -// Tries to parse the type - if it fails, it fails quietly -pub fn try_parse_type(meta: &mut ParserMetadata) -> Result { - let tok = meta.get_current_token(); - let res = match tok.clone() { +fn parse_type_tok(meta: &mut ParserMetadata, tok: Option) -> Result { + match tok.clone() { Some(matched_token) => { match matched_token.word.as_ref() { "Text" => { @@ -99,10 +137,35 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result { None => { Err(Failure::Quiet(PositionInfo::at_eof(meta))) } - }; + } +} +fn parse_one_type(meta: &mut ParserMetadata, tok: Option) -> Result { + let res = parse_type_tok(meta, tok)?; if token(meta, "?").is_ok() { - return res.map(|t| Type::Failable(Box::new(t))) + return Ok(Type::Failable(Box::new(res))) + } + Ok(res) +} + +// Tries to parse the type - if it fails, it fails quietly +pub fn try_parse_type(meta: &mut ParserMetadata) -> Result { + let tok = meta.get_current_token(); + let res = parse_one_type(meta, tok); + + if token(meta, "|").is_ok() { + // is union type + let mut unioned = vec![ Box::new(res?) ]; + loop { + match parse_one_type(meta, meta.get_current_token()) { + Err(err) => return Err(err), + Ok(t) => unioned.push(Box::new(t)) + }; + if token(meta, "|").is_err() { + break; + } + } + return Ok(Type::Union(unioned)) } res From a742b635e4f8e8ddde8fd8ea35e673f4c4481392 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 7 Sep 2024 14:38:40 +1000 Subject: [PATCH 02/18] tests: unions --- src/tests/errors.rs | 2 ++ src/tests/errors/unions.rs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 src/tests/errors/unions.rs diff --git a/src/tests/errors.rs b/src/tests/errors.rs index dadb73d3..404890a9 100644 --- a/src/tests/errors.rs +++ b/src/tests/errors.rs @@ -1,5 +1,7 @@ use super::test_amber; +mod unions; + #[test] #[should_panic(expected = "ERROR: Return type does not match function return type")] fn function_with_wrong_typed_return() { diff --git a/src/tests/errors/unions.rs b/src/tests/errors/unions.rs new file mode 100644 index 00000000..19639ffb --- /dev/null +++ b/src/tests/errors/unions.rs @@ -0,0 +1,32 @@ +use crate::tests::test_amber; + +#[test] +#[should_panic(expected = "ERROR: 1st argument 'param' of function 'abc' expects type 'Text | Null', but 'Num' was given")] +fn invalid_union_type_eq_normal_type() { + let code = r#" + fun abc(param: Text | Null) {} + abc("") + abc(123) + "#; + test_amber(code, ""); +} + +#[test] +#[should_panic(expected = "ERROR: 1st argument 'param' of function 'abc' expects type 'Text | Null', but 'Num | [Text]' was given")] +fn invalid_two_unions() { + let code = r#" + fun abc(param: Text | Null) {} + abc(123 as Num | [Text]) + "#; + test_amber(code, ""); +} + +#[test] +#[should_panic(expected = "ERROR: 1st argument 'param' of function 'abc' expects type 'Text | Num | Text? | Num? | [Null]', but 'Null' was given")] +fn big_union() { + let code = r#" + fun abc(param: Text | Num | Text? | Num? | [Null]) {} + abc(null) + "#; + test_amber(code, ""); +} \ No newline at end of file From 888ea7a2c32d35ec0676cb496a639d2c07ce094f Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 7 Sep 2024 15:03:25 +1000 Subject: [PATCH 03/18] tests: validity tests for union types --- .../validity/function_with_union_types.ab | 10 ++++++++++ src/tests/validity/union_types.ab | 7 +++++++ src/tests/validity/union_types_if.ab | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 src/tests/validity/function_with_union_types.ab create mode 100644 src/tests/validity/union_types.ab create mode 100644 src/tests/validity/union_types_if.ab diff --git a/src/tests/validity/function_with_union_types.ab b/src/tests/validity/function_with_union_types.ab new file mode 100644 index 00000000..d0797888 --- /dev/null +++ b/src/tests/validity/function_with_union_types.ab @@ -0,0 +1,10 @@ +// Output +// abc +// 123 + +fun check(thing: Text | Num): Null { + echo thing +} + +check("abc") +check(123) \ No newline at end of file diff --git a/src/tests/validity/union_types.ab b/src/tests/validity/union_types.ab new file mode 100644 index 00000000..387c0d27 --- /dev/null +++ b/src/tests/validity/union_types.ab @@ -0,0 +1,7 @@ +// Output +// 123 + +let thingy = "abc" as Text | Num; +thingy = 123; + +echo thingy; \ No newline at end of file diff --git a/src/tests/validity/union_types_if.ab b/src/tests/validity/union_types_if.ab new file mode 100644 index 00000000..5a913a5a --- /dev/null +++ b/src/tests/validity/union_types_if.ab @@ -0,0 +1,19 @@ +// Output +// is text +// abc +// is num +// 123 + +fun check(thing: Text | Num): Null { + if thing is Text { + echo "is text" + echo thing + } + if thing is Num { + echo "is num" + echo thing + } +} + +check("abc") +check(123) \ No newline at end of file From 5a67306da47ef1d5a5d744091d53ab4cf4034249 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 7 Sep 2024 15:55:14 +1000 Subject: [PATCH 04/18] fix: dereferencing error --- src/modules/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index 960810fb..a3c1899d 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -25,7 +25,7 @@ impl Type { } fn eq_union_normal(one: &Vec>, other: &Type) -> bool { - one.iter().find(|x| (***x).to_string() == other.to_string()).is_some() + one.iter().find(|x| (**x).to_string() == other.to_string()).is_some() } fn eq_unions(one: &Vec>, other: &Vec>) -> bool { From 2e459d1c0155e1bc921383801dd7311388764c7e Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 7 Sep 2024 15:56:00 +1000 Subject: [PATCH 05/18] clippy --fix --- src/modules/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index a3c1899d..eb065bf2 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -25,7 +25,7 @@ impl Type { } fn eq_union_normal(one: &Vec>, other: &Type) -> bool { - one.iter().find(|x| (**x).to_string() == other.to_string()).is_some() + one.iter().any(|x| (**x).to_string() == other.to_string()) } fn eq_unions(one: &Vec>, other: &Vec>) -> bool { From fa78fa51f1e0bb8a03372b22051c00efbbf25bcf Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 7 Sep 2024 15:58:56 +1000 Subject: [PATCH 06/18] fix: make clippy happy --- src/modules/types.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index eb065bf2..ef2d68c8 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -10,7 +10,7 @@ pub enum Type { Text, Bool, Num, - Union(Vec>), + Union(Vec), Array(Box), Failable(Box), Generic @@ -18,20 +18,17 @@ pub enum Type { impl Type { pub fn is_union(&self) -> bool { - match self { - Type::Union(_) => true, - _ => false - } + matches!(self, Type::Union(_)) } - fn eq_union_normal(one: &Vec>, other: &Type) -> bool { - one.iter().any(|x| (**x).to_string() == other.to_string()) + fn eq_union_normal(one: &[Type], other: &Type) -> bool { + one.iter().any(|x| (*x).to_string() == other.to_string()) } - fn eq_unions(one: &Vec>, other: &Vec>) -> bool { - one.iter().find(|x| { + fn eq_unions(one: &[Type], other: &[Type]) -> bool { + one.iter().any(|x| { Self::eq_union_normal(other, x) - }).is_some() + }) } } @@ -155,11 +152,11 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result { if token(meta, "|").is_ok() { // is union type - let mut unioned = vec![ Box::new(res?) ]; + let mut unioned = vec![ res? ]; loop { match parse_one_type(meta, meta.get_current_token()) { Err(err) => return Err(err), - Ok(t) => unioned.push(Box::new(t)) + Ok(t) => unioned.push(t) }; if token(meta, "|").is_err() { break; From 62bcb5b144a28b968ee249867173e202295bc9db Mon Sep 17 00:00:00 2001 From: b1ek Date: Sat, 7 Sep 2024 16:01:14 +1000 Subject: [PATCH 07/18] fix: remove unused method --- src/modules/types.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index ef2d68c8..614496bd 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -17,10 +17,6 @@ pub enum Type { } impl Type { - pub fn is_union(&self) -> bool { - matches!(self, Type::Union(_)) - } - fn eq_union_normal(one: &[Type], other: &Type) -> bool { one.iter().any(|x| (*x).to_string() == other.to_string()) } From 3adcf2a352fa16e934f9a70e1f1a211927fd749e Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 15:13:01 +1000 Subject: [PATCH 08/18] fix: unions eq --- src/modules/types.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index 614496bd..abde7031 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -22,8 +22,12 @@ impl Type { } fn eq_unions(one: &[Type], other: &[Type]) -> bool { - one.iter().any(|x| { - Self::eq_union_normal(other, x) + let bigger; + let smaller = if one.len() < other.len() { bigger = other; one } else { bigger = one; other }; + + smaller.iter().all(|x| { + let ok = Self::eq_union_normal(bigger, x); + ok }) } } From f189e32f0d931f1f0af3bb9653a2c3af888d97a3 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 15:13:10 +1000 Subject: [PATCH 09/18] tests: types eq --- src/tests/mod.rs | 1 + src/tests/types/eq.rs | 16 +++++++++ src/tests/types/mod.rs | 2 ++ src/tests/types/union.rs | 73 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+) create mode 100644 src/tests/types/eq.rs create mode 100644 src/tests/types/mod.rs create mode 100644 src/tests/types/union.rs diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 1536b56b..ef774c84 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -9,6 +9,7 @@ pub mod cli; pub mod errors; pub mod extra; pub mod formatter; +pub mod types; mod stdlib; mod validity; diff --git a/src/tests/types/eq.rs b/src/tests/types/eq.rs new file mode 100644 index 00000000..19d95577 --- /dev/null +++ b/src/tests/types/eq.rs @@ -0,0 +1,16 @@ +use crate::modules::types::Type; + +#[test] +fn two_normal_types() { + assert_eq!(Type::Bool, Type::Bool); +} + +#[test] +fn two_different_normal_types() { + assert_ne!(Type::Null, Type::Bool); +} + +#[test] +fn normal_and_failable_type() { + assert_ne!(Type::Failable(Box::new(Type::Text)), Type::Text, "Text? and Text must not be equal!") +} \ No newline at end of file diff --git a/src/tests/types/mod.rs b/src/tests/types/mod.rs new file mode 100644 index 00000000..40991751 --- /dev/null +++ b/src/tests/types/mod.rs @@ -0,0 +1,2 @@ +pub mod union; +pub mod eq; diff --git a/src/tests/types/union.rs b/src/tests/types/union.rs new file mode 100644 index 00000000..5c961d3f --- /dev/null +++ b/src/tests/types/union.rs @@ -0,0 +1,73 @@ +use crate::modules::types::Type; + +#[test] +fn partially_overlapping_types() { + let one = Type::Union(vec![Type::Text, Type::Num]); + let two = Type::Union(vec![Type::Num, Type::Null]); + + assert_ne!(one, two, "Text | Num must not be equal to Num | Null!") +} + +#[test] +fn overlapping_types() { + let one = Type::Union(vec![Type::Text, Type::Num]); + let two = Type::Union(vec![Type::Text, Type::Num, Type::Null]); + + assert_eq!(one, two, "Text | Num must be equal to Text | Num | Null!") +} + +#[test] +fn same_union() { + let one = Type::Union(vec![Type::Text, Type::Num]); + let two = Type::Union(vec![Type::Text, Type::Num]); + + assert_eq!(one, two, "Text | Num must be equal to Text | Num!") +} + +#[test] +fn empty_union() { + let one = Type::Union(vec![]); + let two = Type::Union(vec![]); + + assert_eq!(one, two, "If one of unions is empty, it must always be equal to another") +} + +#[test] +fn empty_and_normal_union() { + let one = Type::Union(vec![Type::Text, Type::Num]); + let two = Type::Union(vec![]); + + assert_eq!(one, two, "If one of unions is empty, it must always be equal to another") +} + +#[test] +fn empty_union_and_normal_type() { + let one = Type::Union(vec![]); + let two = Type::Text; + + assert_ne!(one, two, "An empty union and one type are not equal") +} + +#[test] +fn big_union() { + let one = Type::Union(vec![Type::Text, Type::Text, Type::Text, Type::Text, Type::Text, Type::Text, Type::Text, Type::Num]); + let two = Type::Union(vec![Type::Text, Type::Num]); + + assert_eq!(one, two, "Text | Text | ... | Text | Num and Text | Num must be equal!") +} + +#[test] +fn normal_and_union() { + let one = Type::Text; + let two = Type::Union(vec![Type::Text, Type::Null]); + + assert_eq!(one, two, "Text and Text | Null must be equal!"); +} + +#[test] +fn normal_not_in_union() { + let one = Type::Text; + let two = Type::Union(vec![Type::Num, Type::Null]); + + assert_ne!(one, two, "Text and Num | Null must not be equal!"); +} From 65e1e4a03a77549740d6aa244cb70288d08a73ee Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 15:40:24 +1000 Subject: [PATCH 10/18] feat: low cost type hash for the PartialEq trait --- src/modules/types.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index abde7031..58725b29 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -30,6 +30,23 @@ impl Type { ok }) } + + fn type_hash(&self) -> u32 { + match self { + // normal types + Type::Null => 0x001, + Type::Text => 0x002, + Type::Bool => 0x003, + Type::Num => 0x004, + Type::Generic => 0x005, + + // special types + Type::Array(t) => t.type_hash() + 0x100, + Type::Failable(t) => t.type_hash() + 0x200, + + Type::Union(_) => unreachable!("Type hash is not available for union types! Use the PartialEq trait instead"), + } + } } impl PartialEq for Type { @@ -45,7 +62,7 @@ impl PartialEq for Type { if let Type::Union(other) = other { Type::eq_union_normal(other, self) } else { - self.to_string() == other.to_string() + self.type_hash() == other.type_hash() } } } From 50d212027e6b4ae6fcf12ebfa7feaf1438ff92cc Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 15:40:40 +1000 Subject: [PATCH 11/18] test: cover some more type equals --- src/tests/types/eq.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/tests/types/eq.rs b/src/tests/types/eq.rs index 19d95577..cc1947d9 100644 --- a/src/tests/types/eq.rs +++ b/src/tests/types/eq.rs @@ -1,8 +1,11 @@ use crate::modules::types::Type; #[test] -fn two_normal_types() { - assert_eq!(Type::Bool, Type::Bool); +fn normal_types_eq() { + let types = vec![Type::Null, Type::Text, Type::Bool, Type::Num, Type::Generic]; + for typ in types { + assert_eq!(typ, typ, "{typ} and {typ} must be equal!"); + } } #[test] @@ -13,4 +16,14 @@ fn two_different_normal_types() { #[test] fn normal_and_failable_type() { assert_ne!(Type::Failable(Box::new(Type::Text)), Type::Text, "Text? and Text must not be equal!") +} + +#[test] +fn array_and_normal_type() { + assert_ne!(Type::Array(Box::new(Type::Bool)), Type::Bool); +} + +#[test] +fn array_and_array_of_failables() { + assert_ne!(Type::Array(Box::new(Type::Bool)), Type::Array(Box::new(Type::Failable(Box::new(Type::Bool))))); } \ No newline at end of file From 87dfe4f4d24b94bb74ba6ef30271e53761c6e07e Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 16:01:30 +1000 Subject: [PATCH 12/18] fix: make hashes non-collidable --- src/modules/types.rs | 43 ++++++++++++++++++++++++++++++++++++------- src/tests/types/eq.rs | 5 +++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index 58725b29..574ec8f7 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -31,18 +31,46 @@ impl Type { }) } + /** + Hash is calculated in a hex number with 8 digits. Each digit has its own meaning. + + Last two digits are reserved for "singular" types, like text and/or bool. + + # Important + This hash is not supposed to be readed. + It is generated in a way that it can't collide, but it is not supposed to be used for representing a type. + + ``` + 0x00 00 00 00 + ^^ ^^ ^^ ^^ -- singular type indicator + || -- -- number of nested arrays, such as [Num] will be 1, and [[[Num]]] will be 3. + || -- -- modifier + || -- -- reserved for future use + ``` + + ## Modifiers + These modifiers are valid: + + `00` - no modifier + `01` - failable type + */ fn type_hash(&self) -> u32 { match self { // normal types - Type::Null => 0x001, - Type::Text => 0x002, - Type::Bool => 0x003, - Type::Num => 0x004, - Type::Generic => 0x005, + Type::Null => 0x00000001, + Type::Text => 0x00000002, + Type::Bool => 0x00000003, + Type::Num => 0x00000004, + Type::Generic => 0x00000005, // special types - Type::Array(t) => t.type_hash() + 0x100, - Type::Failable(t) => t.type_hash() + 0x200, + Type::Array(t) => t.type_hash() + 0x00000100, + Type::Failable(t) => { + if let Type::Failable(_) = **t { + panic!("Failable types can't be nested!"); + } + t.type_hash() + 0x00010000 + }, Type::Union(_) => unreachable!("Type hash is not available for union types! Use the PartialEq trait instead"), } @@ -62,6 +90,7 @@ impl PartialEq for Type { if let Type::Union(other) = other { Type::eq_union_normal(other, self) } else { + println!("{:#03x}, {:#03x}", self.type_hash(), other.type_hash()); self.type_hash() == other.type_hash() } } diff --git a/src/tests/types/eq.rs b/src/tests/types/eq.rs index cc1947d9..a19cb1ea 100644 --- a/src/tests/types/eq.rs +++ b/src/tests/types/eq.rs @@ -26,4 +26,9 @@ fn array_and_normal_type() { #[test] fn array_and_array_of_failables() { assert_ne!(Type::Array(Box::new(Type::Bool)), Type::Array(Box::new(Type::Failable(Box::new(Type::Bool))))); +} + +#[test] +fn nested_array_normal_array_with_failable() { + assert_ne!(Type::Array(Box::new(Type::Array(Box::new(Type::Bool)))), Type::Failable(Box::new(Type::Bool))); } \ No newline at end of file From 0e4241069143316d879a435d92f28e0663612c1b Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 16:02:52 +1000 Subject: [PATCH 13/18] docs: format markdown to be more prettier --- src/modules/types.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index 574ec8f7..a6a92353 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -51,20 +51,21 @@ impl Type { ## Modifiers These modifiers are valid: - `00` - no modifier - `01` - failable type + | code | desc | + | ---- | ------------- | + | `00` | no modifier | + | `01` | failable type | */ fn type_hash(&self) -> u32 { match self { - // normal types Type::Null => 0x00000001, Type::Text => 0x00000002, Type::Bool => 0x00000003, Type::Num => 0x00000004, Type::Generic => 0x00000005, - // special types Type::Array(t) => t.type_hash() + 0x00000100, + Type::Failable(t) => { if let Type::Failable(_) = **t { panic!("Failable types can't be nested!"); From 6bf7b3720a4bcaf9d5d1d4dfb281cb60de84f620 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 16:10:02 +1000 Subject: [PATCH 14/18] refactor: remove leftover debug code --- src/modules/types.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index a6a92353..e7d50344 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -26,8 +26,7 @@ impl Type { let smaller = if one.len() < other.len() { bigger = other; one } else { bigger = one; other }; smaller.iter().all(|x| { - let ok = Self::eq_union_normal(bigger, x); - ok + Self::eq_union_normal(bigger, x) }) } @@ -91,7 +90,6 @@ impl PartialEq for Type { if let Type::Union(other) = other { Type::eq_union_normal(other, self) } else { - println!("{:#03x}, {:#03x}", self.type_hash(), other.type_hash()); self.type_hash() == other.type_hash() } } From d77b30e72d23e035ae26410f2e4b25bd4cc374a7 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 8 Sep 2024 17:50:50 +1000 Subject: [PATCH 15/18] refactor: simplify impl PartialEq for Type --- src/modules/types.rs | 72 +++++++++----------------------------------- 1 file changed, 14 insertions(+), 58 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index e7d50344..e0aeadce 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -29,68 +29,24 @@ impl Type { Self::eq_union_normal(bigger, x) }) } - - /** - Hash is calculated in a hex number with 8 digits. Each digit has its own meaning. - - Last two digits are reserved for "singular" types, like text and/or bool. - - # Important - This hash is not supposed to be readed. - It is generated in a way that it can't collide, but it is not supposed to be used for representing a type. - - ``` - 0x00 00 00 00 - ^^ ^^ ^^ ^^ -- singular type indicator - || -- -- number of nested arrays, such as [Num] will be 1, and [[[Num]]] will be 3. - || -- -- modifier - || -- -- reserved for future use - ``` - - ## Modifiers - These modifiers are valid: - - | code | desc | - | ---- | ------------- | - | `00` | no modifier | - | `01` | failable type | - */ - fn type_hash(&self) -> u32 { - match self { - Type::Null => 0x00000001, - Type::Text => 0x00000002, - Type::Bool => 0x00000003, - Type::Num => 0x00000004, - Type::Generic => 0x00000005, - - Type::Array(t) => t.type_hash() + 0x00000100, - - Type::Failable(t) => { - if let Type::Failable(_) = **t { - panic!("Failable types can't be nested!"); - } - t.type_hash() + 0x00010000 - }, - - Type::Union(_) => unreachable!("Type hash is not available for union types! Use the PartialEq trait instead"), - } - } } impl PartialEq for Type { fn eq(&self, other: &Self) -> bool { - if let Type::Union(union) = self { - if let Type::Union(other) = other { - return Type::eq_unions(union, other); - } else { - return Type::eq_union_normal(union, other); - } - } - - if let Type::Union(other) = other { - Type::eq_union_normal(other, self) - } else { - self.type_hash() == other.type_hash() + match (self, other) { + (Type::Null, Type::Null) => true, + (Type::Text, Type::Text) => true, + (Type::Bool, Type::Bool) => true, + (Type::Num, Type::Num) => true, + (Type::Generic, Type::Generic) => true, + + (Type::Array(ref a), Type::Array(ref b)) => a == b, + (Type::Failable(ref a), Type::Failable(ref b)) => a == b, + + (Type::Union(one), Type::Union(other)) => Type::eq_unions(one, other), + (Type::Union(one), other) => Type::eq_union_normal(one, other), + (other, Type::Union(one)) => Type::eq_union_normal(one, other), + (_, _) => false } } } From 103c2ef729543b5715b1a325540f83d6198ad4f8 Mon Sep 17 00:00:00 2001 From: blek! Date: Sun, 15 Sep 2024 09:26:20 +1000 Subject: [PATCH 16/18] Update src/modules/types.rs Co-authored-by: Phoenix Himself --- src/modules/types.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/modules/types.rs b/src/modules/types.rs index e0aeadce..066f9dd3 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -22,8 +22,11 @@ impl Type { } fn eq_unions(one: &[Type], other: &[Type]) -> bool { - let bigger; - let smaller = if one.len() < other.len() { bigger = other; one } else { bigger = one; other }; + let (smaller, bigger) = if one.len() < other.len() { + (one, other) + } else { + (other, one) + }; smaller.iter().all(|x| { Self::eq_union_normal(bigger, x) From b000459c16298afbbf5cef3e24f2580567b63057 Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 15 Sep 2024 13:26:00 +1000 Subject: [PATCH 17/18] refactor: rewrite to not implement PartialEq manually --- src/modules/types.rs | 55 +++++++++----------------------------- src/modules/types/union.rs | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+), 43 deletions(-) create mode 100644 src/modules/types/union.rs diff --git a/src/modules/types.rs b/src/modules/types.rs index 066f9dd3..7f35db74 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -2,58 +2,23 @@ use std::fmt::Display; use heraclitus_compiler::prelude::*; use itertools::Itertools; +use union::UnionType; use crate::utils::ParserMetadata; -#[derive(Debug, Clone, Eq, Default)] +mod union; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum Type { #[default] Null, Text, Bool, Num, - Union(Vec), + Union(UnionType), Array(Box), Failable(Box), Generic } -impl Type { - fn eq_union_normal(one: &[Type], other: &Type) -> bool { - one.iter().any(|x| (*x).to_string() == other.to_string()) - } - - fn eq_unions(one: &[Type], other: &[Type]) -> bool { - let (smaller, bigger) = if one.len() < other.len() { - (one, other) - } else { - (other, one) - }; - - smaller.iter().all(|x| { - Self::eq_union_normal(bigger, x) - }) - } -} - -impl PartialEq for Type { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Type::Null, Type::Null) => true, - (Type::Text, Type::Text) => true, - (Type::Bool, Type::Bool) => true, - (Type::Num, Type::Num) => true, - (Type::Generic, Type::Generic) => true, - - (Type::Array(ref a), Type::Array(ref b)) => a == b, - (Type::Failable(ref a), Type::Failable(ref b)) => a == b, - - (Type::Union(one), Type::Union(other)) => Type::eq_unions(one, other), - (Type::Union(one), other) => Type::eq_union_normal(one, other), - (other, Type::Union(one)) => Type::eq_union_normal(one, other), - (_, _) => false - } - } -} - impl Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -61,10 +26,14 @@ impl Display for Type { Type::Bool => write!(f, "Bool"), Type::Num => write!(f, "Num"), Type::Null => write!(f, "Null"), - Type::Union(types) => write!(f, "{}", types.iter().map(|x| format!("{x}")).join(" | ")), Type::Array(t) => write!(f, "[{}]", t), Type::Failable(t) => write!(f, "{}?", t), - Type::Generic => write!(f, "Generic") + Type::Generic => write!(f, "Generic"), + + Type::Union(types) => { + let types: &Vec = types.into(); + write!(f, "{}", types.iter().map(|x| format!("{x}")).join(" | ")) + } } } } @@ -166,7 +135,7 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result { break; } } - return Ok(Type::Union(unioned)) + return Ok(Type::Union(unioned.into())) } res diff --git a/src/modules/types/union.rs b/src/modules/types/union.rs new file mode 100644 index 00000000..cb8bc5d7 --- /dev/null +++ b/src/modules/types/union.rs @@ -0,0 +1,28 @@ +use super::Type; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct UnionType(Vec); + +impl Into> for UnionType { + fn into(self) -> Vec { + self.0 + } +} + +impl <'a> Into<&'a Vec> for &'a UnionType { + fn into(self) -> &'a Vec { + &self.0 + } +} + +impl From> for UnionType { + fn from(value: Vec) -> Self { + let mut value = value; + value.sort(); + if value.len() < 2 { + unreachable!("A union type must have at least two elements") + } + + Self(value) + } +} From cc6d0ea22076a1a60d1fb769c5de4ded67db63ae Mon Sep 17 00:00:00 2001 From: b1ek Date: Sun, 24 Nov 2024 23:48:09 +1000 Subject: [PATCH 18/18] treat union types in arguments the same way as limited Generic types --- src/modules/function/invocation_utils.rs | 11 +++++++++++ src/modules/types/union.rs | 13 ++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/modules/function/invocation_utils.rs b/src/modules/function/invocation_utils.rs index 769ccce4..ed8dbdc7 100644 --- a/src/modules/function/invocation_utils.rs +++ b/src/modules/function/invocation_utils.rs @@ -39,6 +39,17 @@ fn run_function_with_args(meta: &mut ParserMetadata, mut fun: FunctionDecl, args // Check if the function argument types match if fun.is_args_typed { for (index, (arg_name, arg_type, given_type)) in izip!(fun.arg_names.iter(), fun.arg_types.iter(), args.iter()).enumerate() { + + // union type matching works differently in functions + if let Type::Union(union) = &arg_type { + if ! union.has(given_type) { + let fun_name = &fun.name; + let ordinal = ordinal_number(index); + return error!(meta, tok, format!("{ordinal} argument of function '{fun_name}' does not allow '{given_type}' in '{arg_type}'")) + } + continue; + } + if !given_type.is_allowed_in(arg_type) { let fun_name = &fun.name; let ordinal = ordinal_number(index); diff --git a/src/modules/types/union.rs b/src/modules/types/union.rs index cb8bc5d7..cd2b22e6 100644 --- a/src/modules/types/union.rs +++ b/src/modules/types/union.rs @@ -1,7 +1,13 @@ use super::Type; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] -pub struct UnionType(Vec); +pub struct UnionType(pub Vec); + +impl UnionType { + pub fn has(&self, other: &Type) -> bool { + self.0.iter().find(|x| **x == *other).is_some() + } +} impl Into> for UnionType { fn into(self) -> Vec { @@ -22,6 +28,11 @@ impl From> for UnionType { if value.len() < 2 { unreachable!("A union type must have at least two elements") } + for typ in &value { + if let Type::Union(_) = typ { + unreachable!("Union types cannot be nested") + } + } Self(value) }