feat: handle pipe fn infer TODOs
This improves error messages for `a |> b(x)`. We need to do a special check when looping over the args and unifying. This information is within a function that does not belong to pipe typer so I used a closure to forward along a way to add metadata to the error when the first argument in the loop has a unification error. Simply adding the metadata at the pipe typer level is not good enough because then we may annotate regular unification errors from the args.
This commit is contained in:
parent
fb6cbbec8b
commit
135dbd8335
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
builtins,
|
builtins,
|
||||||
expr::TypedExpr,
|
expr::TypedExpr,
|
||||||
parser,
|
parser,
|
||||||
tipo::error::{Error, Warning},
|
tipo::error::{Error, UnifyErrorSituation, Warning},
|
||||||
IdGenerator,
|
IdGenerator,
|
||||||
};
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -1006,6 +1006,104 @@ fn trace_if_false_ko() {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_with_wrong_type() {
|
||||||
|
let source_code = r#"
|
||||||
|
test foo() {
|
||||||
|
True |> bar
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(n: Int) {
|
||||||
|
n - 1
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((
|
||||||
|
_,
|
||||||
|
Error::CouldNotUnify {
|
||||||
|
situation: Some(UnifyErrorSituation::PipeTypeMismatch),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_with_wrong_type_and_args() {
|
||||||
|
let source_code = r#"
|
||||||
|
test foo() {
|
||||||
|
True |> bar(False)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(n: Int, l: Bool) {
|
||||||
|
n - 1
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((
|
||||||
|
_,
|
||||||
|
Error::CouldNotUnify {
|
||||||
|
situation: Some(UnifyErrorSituation::PipeTypeMismatch),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_with_right_type_and_wrong_args() {
|
||||||
|
let source_code = r#"
|
||||||
|
test foo() {
|
||||||
|
1 |> bar(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(n: Int, l: Bool) {
|
||||||
|
n - 1
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((
|
||||||
|
_,
|
||||||
|
Error::CouldNotUnify {
|
||||||
|
situation: None,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pipe_with_wrong_type_and_full_args() {
|
||||||
|
let source_code = r#"
|
||||||
|
test foo() {
|
||||||
|
True |> bar(False)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bar(l: Bool) -> fn(Int) -> Int {
|
||||||
|
fn(n: Int) {
|
||||||
|
n - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
dbg!(check(parse(source_code))),
|
||||||
|
Err((
|
||||||
|
_,
|
||||||
|
Error::CouldNotUnify {
|
||||||
|
situation: Some(UnifyErrorSituation::PipeTypeMismatch),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn utf8_hex_literal_warning() {
|
fn utf8_hex_literal_warning() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
|
|
|
@ -1158,7 +1158,7 @@ fn suggest_unify(
|
||||||
given
|
given
|
||||||
},
|
},
|
||||||
Some(UnifyErrorSituation::PipeTypeMismatch) => formatdoc! {
|
Some(UnifyErrorSituation::PipeTypeMismatch) => formatdoc! {
|
||||||
r#"As I was looking at a pipeline you have defined, I realized that one of the pipe isn't valid.
|
r#"As I was looking at a pipeline you have defined, I realized that one of the pipes isn't valid.
|
||||||
|
|
||||||
I am expecting the pipe to send into something of type:
|
I am expecting the pipe to send into something of type:
|
||||||
|
|
||||||
|
|
|
@ -76,17 +76,21 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
) -> Result<(TypedExpr, Vec<TypedCallArg>, Rc<Type>), Error> {
|
) -> Result<(TypedExpr, Vec<TypedCallArg>, Rc<Type>), Error> {
|
||||||
let fun = self.infer(fun)?;
|
let fun = self.infer(fun)?;
|
||||||
|
|
||||||
let (fun, args, typ) = self.do_infer_call_with_known_fun(fun, args, location)?;
|
let (fun, args, typ) = self.do_infer_call_with_known_fun(fun, args, location, |e| e)?;
|
||||||
|
|
||||||
Ok((fun, args, typ))
|
Ok((fun, args, typ))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_infer_call_with_known_fun(
|
pub fn do_infer_call_with_known_fun<F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
fun: TypedExpr,
|
fun: TypedExpr,
|
||||||
mut args: Vec<CallArg<UntypedExpr>>,
|
mut args: Vec<CallArg<UntypedExpr>>,
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> Result<(TypedExpr, Vec<TypedCallArg>, Rc<Type>), Error> {
|
map_err: F,
|
||||||
|
) -> Result<(TypedExpr, Vec<TypedCallArg>, Rc<Type>), Error>
|
||||||
|
where
|
||||||
|
F: Copy + FnOnce(Error) -> Error,
|
||||||
|
{
|
||||||
// Check to see if the function accepts labelled arguments
|
// Check to see if the function accepts labelled arguments
|
||||||
match self.get_field_map(&fun, location)? {
|
match self.get_field_map(&fun, location)? {
|
||||||
// The fun has a field map so labelled arguments may be present and need to be reordered.
|
// The fun has a field map so labelled arguments may be present and need to be reordered.
|
||||||
|
@ -105,14 +109,20 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
|
|
||||||
let mut arguments = Vec::new();
|
let mut arguments = Vec::new();
|
||||||
|
|
||||||
for (tipo, arg) in args_types.iter_mut().zip(args) {
|
for (index, (tipo, arg)) in args_types.iter_mut().zip(args).enumerate() {
|
||||||
let CallArg {
|
let CallArg {
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
location,
|
location,
|
||||||
} = arg;
|
} = arg;
|
||||||
|
|
||||||
let value = self.infer_call_argument(value, tipo.clone())?;
|
let value = self.infer_call_argument(value, tipo.clone());
|
||||||
|
|
||||||
|
let value = if index == 0 {
|
||||||
|
value.map_err(map_err)?
|
||||||
|
} else {
|
||||||
|
value?
|
||||||
|
};
|
||||||
|
|
||||||
arguments.push(CallArg {
|
arguments.push(CallArg {
|
||||||
label,
|
label,
|
||||||
|
|
|
@ -202,9 +202,9 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> {
|
||||||
args: Vec<CallArg<UntypedExpr>>,
|
args: Vec<CallArg<UntypedExpr>>,
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> Result<TypedExpr, Error> {
|
) -> Result<TypedExpr, Error> {
|
||||||
let (function, args, tipo) = self
|
let (function, args, tipo) =
|
||||||
.expr_typer
|
self.expr_typer
|
||||||
.do_infer_call_with_known_fun(function, args, location)?;
|
.do_infer_call_with_known_fun(function, args, location, |e| e)?;
|
||||||
|
|
||||||
let function = TypedExpr::Call {
|
let function = TypedExpr::Call {
|
||||||
location,
|
location,
|
||||||
|
@ -214,14 +214,11 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let args = vec![self.untyped_left_hand_value_variable_call_argument()];
|
let args = vec![self.untyped_left_hand_value_variable_call_argument()];
|
||||||
// TODO: use `.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)`
|
let (function, args, tipo) =
|
||||||
// This will require the typing of the arguments to be lifted up out of
|
self.expr_typer
|
||||||
// the function below. If it is not we don't know if the error comes
|
.do_infer_call_with_known_fun(function, args, location, |e| {
|
||||||
// from incorrect usage of the pipe or if it originates from the
|
e.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)
|
||||||
// argument expressions.
|
})?;
|
||||||
let (function, args, tipo) = self
|
|
||||||
.expr_typer
|
|
||||||
.do_infer_call_with_known_fun(function, args, location)?;
|
|
||||||
|
|
||||||
Ok(TypedExpr::Call {
|
Ok(TypedExpr::Call {
|
||||||
location,
|
location,
|
||||||
|
@ -239,14 +236,11 @@ impl<'a, 'b, 'c> PipeTyper<'a, 'b, 'c> {
|
||||||
location: Span,
|
location: Span,
|
||||||
) -> Result<TypedExpr, Error> {
|
) -> Result<TypedExpr, Error> {
|
||||||
arguments.insert(0, self.untyped_left_hand_value_variable_call_argument());
|
arguments.insert(0, self.untyped_left_hand_value_variable_call_argument());
|
||||||
// TODO: use `.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)`
|
let (fun, args, tipo) =
|
||||||
// This will require the typing of the arguments to be lifted up out of
|
self.expr_typer
|
||||||
// the function below. If it is not we don't know if the error comes
|
.do_infer_call_with_known_fun(function, arguments, location, |e| {
|
||||||
// from incorrect usage of the pipe or if it originates from the
|
e.with_unify_error_situation(UnifyErrorSituation::PipeTypeMismatch)
|
||||||
// argument expressions.
|
})?;
|
||||||
let (fun, args, tipo) = self
|
|
||||||
.expr_typer
|
|
||||||
.do_infer_call_with_known_fun(function, arguments, location)?;
|
|
||||||
|
|
||||||
Ok(TypedExpr::Call {
|
Ok(TypedExpr::Call {
|
||||||
location,
|
location,
|
||||||
|
|
|
@ -13,4 +13,4 @@ requirements = []
|
||||||
source = "github"
|
source = "github"
|
||||||
|
|
||||||
[etags]
|
[etags]
|
||||||
"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1695230782, nanos_since_epoch = 772075000 }, "a5918f742d4589d2f5a91daf232eb03a2a0972a367ec0b016e9e8670e28c1b47"]
|
"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1696308400, nanos_since_epoch = 613231000 }, "a721cf2738274f806efefb5a33c6ff9ae049476f0d45a42049b71793949f4d1d"]
|
||||||
|
|
Loading…
Reference in New Issue