Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/target
.agent/

# Test files
test*.ab
Expand All @@ -16,4 +17,3 @@ flamegraph.svg
# Nixos
.direnv
/result

2 changes: 1 addition & 1 deletion grammar.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ KEYWORD_WHILE = 'while' ;
ANY_CHAR = ? any character ? ;
LETTER = 'A'..'Z' | 'a'..'z' ;
DIGIT = '0'..'9' ;
TYPE = 'Text' | 'Num' | 'Bool' | 'Null';
TYPE = ( 'Text' | 'Num' | 'Bool' | 'Null' ), { '|', TYPE };
UNARY_OP = '-' | KEYWORD_NOT ;
BINARY_OP = '+' | '-' | '*' | '/' | '%' | KEYWORD_AND | KEYWORD_OR | '==' | '!=' | '<' | '<=' | '>' | '>=' ;
VISIBILITY = KEYWORD_PUB ;
Expand Down
35 changes: 34 additions & 1 deletion src/modules/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum Type {
Num,
Int,
Array(Box<Type>),
Union(Vec<Type>),
Generic
}

Expand All @@ -23,13 +24,16 @@ impl Type {

pub fn is_subset_of(&self, other: &Type) -> bool {
match (self, other) {
(Type::Generic, Type::Generic) => false,
(_, Type::Generic) => true,
(Type::Int, Type::Num) => true,
(Type::Array(current), Type::Array(other)) => match (&**current, &**other) {
(current, Type::Generic) if *current != Type::Generic => true,
(Type::Int, Type::Num) => true,
_ => false
(a, b) => a.is_subset_of(b),
},
(Type::Union(types), other) => types.iter().all(|t| t.is_allowed_in(other)),
(other, Type::Union(types)) => types.iter().any(|t| other.is_allowed_in(t)),
_ => false
}
}
Expand All @@ -42,6 +46,8 @@ impl Type {
matches!(self, Type::Array(_))
}



pub fn pretty_join(types: &[Self], op: &str) -> String {
let mut all_types = types.iter().map(|kind| kind.to_string()).collect_vec();
let last_item = all_types.pop();
Expand Down Expand Up @@ -71,6 +77,7 @@ impl Display for Type {
} else {
write!(f, "[{t}]")
},
Type::Union(types) => write!(f, "{}", types.iter().map(|t| t.to_string()).collect::<Vec<String>>().join(" | ")),
Type::Generic => write!(f, "Generic")
}
}
Expand All @@ -89,6 +96,32 @@ pub fn parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {

// Tries to parse the type - if it fails, it fails quietly
pub fn try_parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {
let mut left = try_parse_simple_type(meta)?;
// Parse union type
while token(meta, "|").is_ok() {
let right = try_parse_simple_type(meta)?;
left = match (left, right) {
(Type::Union(mut left_types), Type::Union(mut right_types)) => {
left_types.append(&mut right_types);
Type::Union(left_types)
},
(Type::Union(mut left_types), right) => {
left_types.push(right);
Type::Union(left_types)
},
(left, Type::Union(mut right_types)) => {
let mut left_types = vec![left];
left_types.append(&mut right_types);
Type::Union(left_types)
},
(left, right) => Type::Union(vec![left, right])
}
}

Ok(left)
}

fn try_parse_simple_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {
let tok = meta.get_current_token();
let res = match tok.clone() {
Some(matched_token) => {
Expand Down
8 changes: 8 additions & 0 deletions src/tests/erroring/union_types_mismatch.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Output
// 1st argument 'x' of function 'foo' expects type 'Int | Bool', but 'Text' was given

fun foo(x: Int | Bool) {
echo x
}

foo("hello")
30 changes: 30 additions & 0 deletions src/tests/validity/union_types_valid.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Output
// 1
// 1
// hello
// 1 2
// 1 0
// 1 2
// 1 0

fun foo(x: Int | Bool | Text) {
echo x
}

foo(1)
foo(true)
foo("hello")

fun bar(x: [Int] | [Bool]) {
echo x
}

bar([1, 2])
bar([true, false])

fun baz(x: [Int | Bool]) {
echo x
}

baz([1, 2])
baz([true, false])