Skip to content

Commit

Permalink
Introduce Generic Arrays (#472)
Browse files Browse the repository at this point in the history
* Introduce generic Array type

* Simplify handling of Failable types
  • Loading branch information
mks-h authored Oct 2, 2024
1 parent bdb2bcd commit 0e67c89
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/modules/function/invocation_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down Expand Up @@ -116,4 +116,4 @@ fn handle_similar_function(meta: &ParserMetadata, name: &str) -> Option<String>
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}'?")))
}
}
9 changes: 1 addition & 8 deletions src/modules/function/ret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,9 @@ impl SyntaxModule<ParserMetadata> 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)
Expand Down
104 changes: 94 additions & 10 deletions src/modules/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,36 @@ 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 {
Type::Text => write!(f, "Text"),
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")
}
Expand Down Expand Up @@ -64,15 +86,19 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {
"[" => {
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)))
}
}
}
},
Expand Down Expand Up @@ -107,3 +133,61 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {

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));
}
}
10 changes: 10 additions & 0 deletions src/tests/validity/generic_array_in_argument.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Output
// 1 2 3
// Hello Hi

fun test(arr: []): Null {
echo arr
}

test([1, 2, 3])
test(["Hello", "Hi"])
10 changes: 10 additions & 0 deletions src/tests/validity/generic_array_in_argument_and_return.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Output
// 1 2 3
// Hello Hi

fun test(arr: []): [] {
return arr
}

echo test([1, 2, 3])
echo test(["Hello", "Hi"])
9 changes: 9 additions & 0 deletions src/tests/validity/generic_array_in_failable_return.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Output
// 1 2 3

fun test(arr: [Num]): []? {
if false { fail 1 }
return arr
}

echo unsafe test([1, 2, 3])
8 changes: 8 additions & 0 deletions src/tests/validity/generic_array_in_return.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Output
// 1 2 3

fun test(arr: [Num]): [] {
return arr
}

echo test([1, 2, 3])
26 changes: 26 additions & 0 deletions src/tests/validity/generic_array_type_check.ab
Original file line number Diff line number Diff line change
@@ -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"])

0 comments on commit 0e67c89

Please sign in to comment.