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:
rvcas 2023-10-03 01:01:46 -04:00 committed by Lucas
parent fb6cbbec8b
commit 135dbd8335
5 changed files with 129 additions and 27 deletions

View File

@ -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#"

View File

@ -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:

View File

@ -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,

View File

@ -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,

View File

@ -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"]