Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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 src/modules/command/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct CommandExpr {

impl Typed for CommandExpr {
fn get_type(&self) -> Type {
Type::Text
Type::Failable(Box::new(<Type::Text>));
}
}

Expand Down
8 changes: 8 additions & 0 deletions src/modules/expression/unop/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ impl SyntaxModule<ParserMetadata> for Cast {
let message = Message::new_warn_at_token(meta, tok)
.message(format!("Casting a value of type '{l_type}' value to a '{r_type}' is not recommended"))
.comment(format!("To suppress this warning, use '{flag_name}' compiler flag"));
let (l_type, r_type) = match (l_type, r_type) {
(Type::Failable(l), Type::Failable(r)) => (*l, *r),
(Type::Failable(_), _) | (_, Type::Failable(_)) => {
meta.add_message(message);
return Ok(());
},
types => types
};
match (l_type, r_type) {
(Type::Array(left), Type::Array(right)) => {
if *left != *right && !matches!(*left, Type::Bool | Type::Num) && !matches!(*right, Type::Bool | Type::Num) {
Expand Down
14 changes: 13 additions & 1 deletion src/modules/function/declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ impl SyntaxModule<ParserMetadata> for FunctionDeclaration {
self.arg_types.push(Type::Generic);
}
}
if let Type::Failable(_) = arg_type {
return error!(meta, name_token, "Failable types cannot be used as arguments");
}
match token(meta,"=") {
Ok(_) => {
if is_ref {
Expand All @@ -198,14 +201,23 @@ impl SyntaxModule<ParserMetadata> for FunctionDeclaration {
Err(_) => token(meta, ",")?
};
}
let mut returns_tok = None;
// Optionally parse the return type
match token(meta, ":") {
Ok(_) => self.returns = parse_type(meta)?,
Ok(_) => {
returns_tok = meta.get_current_token();
self.returns = parse_type(meta)?
},
Err(_) => self.returns = Type::Generic
}
// Parse the body
token(meta, "{")?;
let (index_begin, index_end, is_failable) = skip_function_body(meta);
if is_failable && !matches!(self.returns, Type::Failable(_) | Type::Generic) {
return error!(meta, returns_tok, "Failable functions must return a Failable type");
} else if !is_failable && matches!(self.returns, Type::Failable(_)) {
return error!(meta, returns_tok, "Non-failable functions cannot return a Failable type");
}
// Create a new context with the function body
let expr = meta.context.expr[index_begin..index_end].to_vec();
let ctx = meta.context.clone().function_invocation(expr);
Expand Down
14 changes: 9 additions & 5 deletions src/modules/function/invocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,17 @@ impl SyntaxModule<ParserMetadata> for FunctionInvocation {
}
}

let types = self.args.iter().map(|e| e.get_type()).collect::<Vec<Type>>();
let var_names = self.args.iter().map(|e| e.is_var()).collect::<Vec<bool>>();
self.refs.clone_from(&function_unit.arg_refs);
(self.kind, self.variant_id) = handle_function_parameters(meta, self.id, function_unit.clone(), &types, &var_names, tok.clone())?;

self.is_failable = function_unit.is_failable;
if self.is_failable {
match syntax(meta, &mut self.failed) {
Ok(_) => {},
Ok(_) => if let Type::Failable(t) = &self.kind {
self.kind = *t.clone();
},
Err(Failure::Quiet(_)) => return error!(meta, tok => {
message: "This function can fail. Please handle the failure",
comment: "You can use '?' in the end to propagate the failure"
Expand All @@ -113,10 +120,7 @@ impl SyntaxModule<ParserMetadata> for FunctionInvocation {
meta.add_message(message);
}
}
let types = self.args.iter().map(|e| e.get_type()).collect::<Vec<Type>>();
let var_names = self.args.iter().map(|e| e.is_var()).collect::<Vec<bool>>();
self.refs.clone_from(&function_unit.arg_refs);
(self.kind, self.variant_id) = handle_function_parameters(meta, self.id, function_unit, &types, &var_names, tok)?;

Ok(())
})
}
Expand Down
25 changes: 18 additions & 7 deletions src/modules/function/ret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,26 @@ impl SyntaxModule<ParserMetadata> for Return {
});
}
syntax(meta, &mut self.expr)?;
match meta.context.fun_ret_type.as_ref() {
Some(ret_type) => if ret_type != &self.expr.get_type() {
return error!(meta, tok => {
message: "Return type does not match function return type",
comment: format!("Given type: {}, expected type: {}", self.expr.get_type(), ret_type)
});
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 {
return error!(meta, tok => {
message: "Return type does not match function return type",
comment: format!("Given type: {}, expected type: {}", expr_type, ret_type)
});
}
},
None => {
meta.context.fun_ret_type = Some(self.expr.get_type());
meta.context.fun_ret_type = Some(expr_type.clone());
}
}
Ok(())
Expand Down
12 changes: 10 additions & 2 deletions src/modules/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub enum Type {
Num,
Null,
Array(Box<Type>),
Failable(Box<Type>),
Generic
}

Expand All @@ -21,6 +22,7 @@ impl Display for Type {
Type::Num => write!(f, "Num"),
Type::Null => write!(f, "Null"),
Type::Array(t) => write!(f, "[{}]", t),
Type::Failable(t) => write!(f, "{}?", t),
Type::Generic => write!(f, "Generic")
}
}
Expand All @@ -40,7 +42,7 @@ 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 tok = meta.get_current_token();
match tok.clone() {
let res = match tok.clone() {
Some(matched_token) => {
match matched_token.word.as_ref() {
"Text" => {
Expand Down Expand Up @@ -97,5 +99,11 @@ pub fn try_parse_type(meta: &mut ParserMetadata) -> Result<Type, Failure> {
None => {
Err(Failure::Quiet(PositionInfo::at_eof(meta)))
}
};

if token(meta, "?").is_ok() {
return res.map(|t| Type::Failable(Box::new(t)))
}
}

res
}
2 changes: 1 addition & 1 deletion src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub fn get_rules() -> Rules {
let symbols = vec![
'+', '-', '*', '/', '%', '\n', ';', ':',
'(', ')', '[', ']', '{', '}', ',', '.',
'<', '>', '=', '!'
'<', '>', '=', '!', '?'
];
let compounds = vec![
('<', '='),
Expand Down
10 changes: 5 additions & 5 deletions src/std/env.ab
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,23 @@ pub fun shell_isset(name: Text): Bool {
return true
}

pub fun shell_constant_set(name: Text, val: Text): Null {
pub fun shell_constant_set(name: Text, val: Text): Null? {
$readonly \${nameof name}="\${nameof val}" 2> /dev/null$?
}

pub fun shell_constant_get(name: Text): Text {
pub fun shell_constant_get(name: Text): Text? {
return $echo \$\{!{nameof name}}$?
}

pub fun shell_var_set(name: Text, val: Text): Null {
pub fun shell_var_set(name: Text, val: Text): Null? {
$export \${nameof name}="\${nameof val}" 2> /dev/null$?
}

pub fun shell_var_get(name: Text): Text {
pub fun shell_var_get(name: Text): Text? {
return $echo \$\{!{nameof name}}$?
}

pub fun shell_unset(name: Text): Null {
pub fun shell_unset(name: Text): Null? {
$unset {name}$?
}

Expand Down
2 changes: 1 addition & 1 deletion src/std/text.ab
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fun upper(text: Text): Text {
}

#[allow_absurd_cast]
pub fun parse(text: Text): Num {
pub fun parse(text: Text): Num? {
$[ -n "{text}" ] && [ "{text}" -eq "{text}" ] 2>/dev/null$?
return text as Num
}
Expand Down
55 changes: 55 additions & 0 deletions src/tests/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::compiler::AmberCompiler;
use crate::Cli;
use crate::test_amber;

#[test]
#[should_panic(expected = "ERROR: Return type does not match function return type")]
fn function_with_wrong_typed_return() {
let code = r#"
pub fun test(): Num {
return "Hello, World!"
}
echo test()
"#;

test_amber!(code, "Hello, World!");
}

#[test]
#[should_panic(expected = "ERROR: Failable functions must return a Failable type")]
fn function_failable_with_typed_nonfailable_return() {
let code = r#"
pub fun test(): Null {
fail 1
}
echo test() failed: echo "Failed"
"#;

test_amber!(code, "Failed");
}

#[test]
#[should_panic(expected = "ERROR: Non-failable functions cannot return a Failable type")]
fn function_nonfailable_with_typed_failable_return() {
let code = r#"
pub fun test(): Null? {
echo "Hello, World!"
}
echo test() failed: echo "Failed"
"#;

test_amber!(code, "Hello, World!");
}

#[test]
#[should_panic(expected = "ERROR: Failable types cannot be used as arguments")]
fn function_with_failable_typed_arg() {
let code = r#"
pub fun test(a: Text?) {
echo a
}
test("Hello, World!")
"#;

test_amber!(code, "Hello, World!");
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::Cli;

pub mod cli;
pub mod formatter;
pub mod errors;
pub mod stdlib;
pub mod validity;

Expand Down
11 changes: 11 additions & 0 deletions src/tests/validity/failed_block_unwraps_failable_type.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fun failable(): Num? {
if 0 > 5 {
fail 1
}

return 1
}

let a = failable() failed: echo "Failed"

if a is Num: echo "Succeded"
11 changes: 11 additions & 0 deletions src/tests/validity/failed_unwraps_failable_type.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
fun failable(): Num? {
if 0 > 5 {
fail 1
}

return 1
}

let a = failable() failed: echo "Failed"

if a is Num: echo "Succeded"
9 changes: 9 additions & 0 deletions src/tests/validity/function_failable.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fun test() {
if 0 < 5 {
fail 1
}

return 42
}

test() failed: echo "Succeded"
9 changes: 9 additions & 0 deletions src/tests/validity/function_failable_with_typed_return.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub fun test(): Num? {
if 0 < 5 {
fail 1
}

return 42
}

echo test() failed: echo "Succeded"
5 changes: 5 additions & 0 deletions src/tests/validity/function_with_typed_return.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub fun test(): Text {
return "Succeded"
}

echo test()
2 changes: 1 addition & 1 deletion src/tests/validity/unsafe_function_call.ab
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Output
// 6, 0

fun safe_division(a: Num, b: Num): Num {
fun safe_division(a: Num, b: Num): Num? {
if b == 0:
fail 1
return a / b
Expand Down
9 changes: 9 additions & 0 deletions src/tests/validity/unsafe_unwraps_failable_type.ab
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fun test(): Num? {
if 0 < 5 {
fail 1
}

return 42
}

if unsafe test() is Num: echo "Succeded"