From 0e67c89a495841b2c191fcf92c92f3230df54c6a Mon Sep 17 00:00:00 2001 From: Maksym Hazevych Date: Wed, 2 Oct 2024 12:31:50 +0300 Subject: [PATCH] Introduce Generic Arrays (#472) * Introduce generic Array type * Simplify handling of Failable types --- src/modules/function/invocation_utils.rs | 4 +- src/modules/function/ret.rs | 9 +- src/modules/types.rs | 104 ++++++++++++++++-- .../validity/generic_array_in_argument.ab | 10 ++ .../generic_array_in_argument_and_return.ab | 10 ++ .../generic_array_in_failable_return.ab | 9 ++ src/tests/validity/generic_array_in_return.ab | 8 ++ .../validity/generic_array_type_check.ab | 26 +++++ 8 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 src/tests/validity/generic_array_in_argument.ab create mode 100644 src/tests/validity/generic_array_in_argument_and_return.ab create mode 100644 src/tests/validity/generic_array_in_failable_return.ab create mode 100644 src/tests/validity/generic_array_in_return.ab create mode 100644 src/tests/validity/generic_array_type_check.ab diff --git a/src/modules/function/invocation_utils.rs b/src/modules/function/invocation_utils.rs index 5cffbc32..a23ccaf9 100644 --- a/src/modules/function/invocation_utils.rs +++ b/src/modules/function/invocation_utils.rs @@ -40,7 +40,7 @@ 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() { - if arg_type != given_type { + if !given_type.is_allowed_in(arg_type) { let fun_name = &fun.name; let ordinal = ordinal_number(index); return error!(meta, tok, format!("{ordinal} argument '{arg_name}' of function '{fun_name}' expects type '{arg_type}', but '{given_type}' was given")) @@ -116,4 +116,4 @@ fn handle_similar_function(meta: &ParserMetadata, name: &str) -> Option let vars = Vec::from_iter(meta.get_fun_names()); find_best_similarity(name, &vars) .and_then(|(match_name, score)| (score >= 0.75).then(|| format!("Did you mean '{match_name}'?"))) -} \ No newline at end of file +} diff --git a/src/modules/function/ret.rs b/src/modules/function/ret.rs index a6418adf..f321028c 100644 --- a/src/modules/function/ret.rs +++ b/src/modules/function/ret.rs @@ -37,16 +37,9 @@ impl SyntaxModule for Return { syntax(meta, &mut self.expr)?; let ret_type = meta.context.fun_ret_type.as_ref(); let expr_type = &self.expr.get_type(); - // Unpacking Failable types - let (ret_type, expr_type) = match (ret_type, expr_type) { - types @ (Some(Type::Failable(_)), Type::Failable(_)) => types, - (Some(Type::Failable(ret_type)), expr_type) => (Some(ret_type.as_ref()), expr_type), - (Some(ret_type), Type::Failable(expr_type)) => (Some(ret_type), expr_type.as_ref()), - types => types - }; match ret_type { Some(ret_type) => { - if ret_type != expr_type { + if !expr_type.is_allowed_in(ret_type) { return error!(meta, tok => { message: "Return type does not match function return type", comment: format!("Given type: {}, expected type: {}", expr_type, ret_type) diff --git a/src/modules/types.rs b/src/modules/types.rs index 02a7eb92..4d476072 100644 --- a/src/modules/types.rs +++ b/src/modules/types.rs @@ -14,6 +14,24 @@ pub enum Type { Generic } +impl Type { + pub fn is_subset_of(&self, other: &Type) -> bool { + match (self, other) { + (Type::Array(current), Type::Array(other)) => { + **current != Type::Generic && **other == Type::Generic + } + (current, Type::Failable(other)) if !matches!(current, Type::Failable(_)) => { + current.is_allowed_in(other) + }, + _ => false + } + } + + pub fn is_allowed_in(&self, other: &Type) -> bool { + self == other || self.is_subset_of(other) + } +} + impl Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -21,7 +39,11 @@ impl Display for Type { Type::Bool => write!(f, "Bool"), Type::Num => write!(f, "Num"), Type::Null => write!(f, "Null"), - Type::Array(t) => write!(f, "[{}]", t), + Type::Array(t) => if **t == Type::Generic { + write!(f, "[]") + } else { + write!(f, "[{}]", t) + }, Type::Failable(t) => write!(f, "{}?", t), Type::Generic => write!(f, "Generic") } @@ -64,15 +86,19 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result { "[" => { let index = meta.get_index(); meta.increment_index(); - match try_parse_type(meta) { - Ok(Type::Array(_)) => error!(meta, tok, "Arrays cannot be nested due to the Bash limitations"), - Ok(result_type) => { - token(meta, "]")?; - Ok(Type::Array(Box::new(result_type))) - }, - Err(_) => { - meta.set_index(index); - Err(Failure::Quiet(PositionInfo::at_eof(meta))) + if token(meta, "]").is_ok() { + Ok(Type::Array(Box::new(Type::Generic))) + } else { + match try_parse_type(meta) { + Ok(Type::Array(_)) => error!(meta, tok, "Arrays cannot be nested due to the Bash limitations"), + Ok(result_type) => { + token(meta, "]")?; + Ok(Type::Array(Box::new(result_type))) + }, + Err(_) => { + meta.set_index(index); + Err(Failure::Quiet(PositionInfo::at_eof(meta))) + } } } }, @@ -107,3 +133,61 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result { res } + +#[cfg(test)] +mod tests { + use super::Type; + + #[test] + fn concrete_array_is_a_subset_of_generic_array() { + let a = Type::Array(Box::new(Type::Text)); + let b = Type::Array(Box::new(Type::Generic)); + + assert!(a.is_subset_of(&b)); + } + + #[test] + fn generic_array_is_not_a_subset_of_concrete_array() { + let a = Type::Array(Box::new(Type::Text)); + let b = Type::Array(Box::new(Type::Generic)); + + assert!(!b.is_subset_of(&a)); + } + + #[test] + fn concrete_array_is_not_a_subset_of_itself() { + let a = Type::Array(Box::new(Type::Text)); + + assert!(!a.is_subset_of(&a)); + } + + #[test] + fn generic_array_is_not_a_subset_of_itself() { + let a = Type::Array(Box::new(Type::Generic)); + + assert!(!a.is_subset_of(&a)); + } + + #[test] + fn non_failable_is_a_subset_of_failable() { + let a = Type::Text; + let b = Type::Failable(Box::new(Type::Text)); + + assert!(a.is_subset_of(&b)); + } + + #[test] + fn failable_is_not_a_subset_of_non_failable() { + let a = Type::Text; + let b = Type::Failable(Box::new(Type::Text)); + + assert!(!b.is_subset_of(&a)); + } + + #[test] + fn failable_is_not_a_subset_of_itself() { + let a = Type::Failable(Box::new(Type::Text)); + + assert!(!a.is_subset_of(&a)); + } +} diff --git a/src/tests/validity/generic_array_in_argument.ab b/src/tests/validity/generic_array_in_argument.ab new file mode 100644 index 00000000..cc438c93 --- /dev/null +++ b/src/tests/validity/generic_array_in_argument.ab @@ -0,0 +1,10 @@ +// Output +// 1 2 3 +// Hello Hi + +fun test(arr: []): Null { + echo arr +} + +test([1, 2, 3]) +test(["Hello", "Hi"]) diff --git a/src/tests/validity/generic_array_in_argument_and_return.ab b/src/tests/validity/generic_array_in_argument_and_return.ab new file mode 100644 index 00000000..e8de98ae --- /dev/null +++ b/src/tests/validity/generic_array_in_argument_and_return.ab @@ -0,0 +1,10 @@ +// Output +// 1 2 3 +// Hello Hi + +fun test(arr: []): [] { + return arr +} + +echo test([1, 2, 3]) +echo test(["Hello", "Hi"]) diff --git a/src/tests/validity/generic_array_in_failable_return.ab b/src/tests/validity/generic_array_in_failable_return.ab new file mode 100644 index 00000000..b42f00eb --- /dev/null +++ b/src/tests/validity/generic_array_in_failable_return.ab @@ -0,0 +1,9 @@ +// Output +// 1 2 3 + +fun test(arr: [Num]): []? { + if false { fail 1 } + return arr +} + +echo unsafe test([1, 2, 3]) diff --git a/src/tests/validity/generic_array_in_return.ab b/src/tests/validity/generic_array_in_return.ab new file mode 100644 index 00000000..ed9b8640 --- /dev/null +++ b/src/tests/validity/generic_array_in_return.ab @@ -0,0 +1,8 @@ +// Output +// 1 2 3 + +fun test(arr: [Num]): [] { + return arr +} + +echo test([1, 2, 3]) diff --git a/src/tests/validity/generic_array_type_check.ab b/src/tests/validity/generic_array_type_check.ab new file mode 100644 index 00000000..42063687 --- /dev/null +++ b/src/tests/validity/generic_array_type_check.ab @@ -0,0 +1,26 @@ +// Output +// Generic +// Number +// Text + +fun return_generic(): [] { + return ["the great unknown"] +} + +fun test(arr: []) { + if arr is [] { + echo "Generic" + } + + if arr is [Num] { + echo "Number" + } + + if arr is [Text] { + echo "Text" + } +} + +test(return_generic()) +test([42, 69]) +test(["Hello", "Hi"])