Merge branch 'desugar-last-assignment'
This commit is contained in:
commit
842001dc0d
|
@ -44,6 +44,10 @@
|
||||||
|
|
||||||
- **aiken-lang**: remove warning on discarded expect, allowing to keep 'side-effects' when necessary. See [#967](https://github.com/aiken-lang/aiken/pull/967). @KtorZ
|
- **aiken-lang**: remove warning on discarded expect, allowing to keep 'side-effects' when necessary. See [#967](https://github.com/aiken-lang/aiken/pull/967). @KtorZ
|
||||||
|
|
||||||
|
- **aiken-lang**: allow expect as last (or only) expression in function body, when clauses and if branches. Such expressions unify with `Void`. See [#1000](https://github.com/aiken-lang/aiken/pull/1000). @KtorZ
|
||||||
|
|
||||||
|
- **aiken-lang**: allow tests to return `Void`. Tests that return `Void` are treated the same as tests that return `True`. See [#1000](https://github.com/aiken-lang/aiken/pull/1000). @KtorZ
|
||||||
|
|
||||||
- **aiken-lang**: rework traces to be (1) variadic, (2) generic in its arguments and (3) structured. @KtorZ
|
- **aiken-lang**: rework traces to be (1) variadic, (2) generic in its arguments and (3) structured. @KtorZ
|
||||||
|
|
||||||
In more details:
|
In more details:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::tipo::ValueConstructorVariant;
|
||||||
|
pub(crate) use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
self, Annotation, ArgBy, ArgName, AssignmentPattern, BinOp, Bls12_381Point,
|
self, Annotation, ArgBy, ArgName, AssignmentPattern, BinOp, Bls12_381Point,
|
||||||
ByteArrayFormatPreference, CallArg, Curve, DataType, DataTypeKey, DefinitionLocation,
|
ByteArrayFormatPreference, CallArg, Curve, DataType, DataTypeKey, DefinitionLocation,
|
||||||
|
@ -472,6 +473,25 @@ impl TypedExpr {
|
||||||
.or(Some(Located::Expression(self))),
|
.or(Some(Located::Expression(self))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn void(location: Span) -> Self {
|
||||||
|
TypedExpr::Var {
|
||||||
|
name: "Void".to_string(),
|
||||||
|
constructor: ValueConstructor {
|
||||||
|
public: true,
|
||||||
|
variant: ValueConstructorVariant::Record {
|
||||||
|
name: "Void".to_string(),
|
||||||
|
arity: 0,
|
||||||
|
field_map: None,
|
||||||
|
location: Span::empty(),
|
||||||
|
module: String::new(),
|
||||||
|
constructors_count: 1,
|
||||||
|
},
|
||||||
|
tipo: void(),
|
||||||
|
},
|
||||||
|
location,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represent how a function was written so that we can format it back.
|
// Represent how a function was written so that we can format it back.
|
||||||
|
|
|
@ -52,14 +52,7 @@ Test(
|
||||||
location: 0..26,
|
location: 0..26,
|
||||||
name: "foo",
|
name: "foo",
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: Some(
|
return_annotation: None,
|
||||||
Constructor {
|
|
||||||
location: 0..39,
|
|
||||||
module: None,
|
|
||||||
name: "Bool",
|
|
||||||
arguments: [],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 38,
|
end_position: 38,
|
||||||
on_test_failure: FailImmediately,
|
on_test_failure: FailImmediately,
|
||||||
|
|
|
@ -37,14 +37,7 @@ Test(
|
||||||
location: 0..28,
|
location: 0..28,
|
||||||
name: "foo",
|
name: "foo",
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: Some(
|
return_annotation: None,
|
||||||
Constructor {
|
|
||||||
location: 0..41,
|
|
||||||
module: None,
|
|
||||||
name: "Bool",
|
|
||||||
arguments: [],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 40,
|
end_position: 40,
|
||||||
on_test_failure: FailImmediately,
|
on_test_failure: FailImmediately,
|
||||||
|
|
|
@ -44,14 +44,7 @@ Test(
|
||||||
location: 0..26,
|
location: 0..26,
|
||||||
name: "foo",
|
name: "foo",
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: Some(
|
return_annotation: None,
|
||||||
Constructor {
|
|
||||||
location: 0..39,
|
|
||||||
module: None,
|
|
||||||
name: "Bool",
|
|
||||||
arguments: [],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 38,
|
end_position: 38,
|
||||||
on_test_failure: FailImmediately,
|
on_test_failure: FailImmediately,
|
||||||
|
|
|
@ -13,14 +13,7 @@ Test(
|
||||||
location: 0..10,
|
location: 0..10,
|
||||||
name: "foo",
|
name: "foo",
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: Some(
|
return_annotation: None,
|
||||||
Constructor {
|
|
||||||
location: 0..23,
|
|
||||||
module: None,
|
|
||||||
name: "Bool",
|
|
||||||
arguments: [],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 22,
|
end_position: 22,
|
||||||
on_test_failure: FailImmediately,
|
on_test_failure: FailImmediately,
|
||||||
|
|
|
@ -44,14 +44,7 @@ Test(
|
||||||
location: 0..26,
|
location: 0..26,
|
||||||
name: "invalid_inputs",
|
name: "invalid_inputs",
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: Some(
|
return_annotation: None,
|
||||||
Constructor {
|
|
||||||
location: 0..61,
|
|
||||||
module: None,
|
|
||||||
name: "Bool",
|
|
||||||
arguments: [],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
return_type: (),
|
return_type: (),
|
||||||
end_position: 60,
|
end_position: 60,
|
||||||
on_test_failure: SucceedEventually,
|
on_test_failure: SucceedEventually,
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||||
end_position: span.end - 1,
|
end_position: span.end - 1,
|
||||||
name,
|
name,
|
||||||
public: false,
|
public: false,
|
||||||
return_annotation: Some(ast::Annotation::boolean(span)),
|
return_annotation: None,
|
||||||
return_type: (),
|
return_type: (),
|
||||||
on_test_failure: fail.unwrap_or(OnTestFailure::FailImmediately),
|
on_test_failure: fail.unwrap_or(OnTestFailure::FailImmediately),
|
||||||
})
|
})
|
||||||
|
|
|
@ -1088,6 +1088,28 @@ fn assignement_last_expr_if_final_else() {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assignment_last_expr_logical_chain() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn foo() -> Bool {
|
||||||
|
and {
|
||||||
|
expect 1 + 1 == 2,
|
||||||
|
True,
|
||||||
|
2 > 0,
|
||||||
|
or {
|
||||||
|
expect True,
|
||||||
|
False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((_, Error::LastExpressionIsAssignment { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn if_scoping() {
|
fn if_scoping() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
|
@ -1747,8 +1769,7 @@ fn pipe_wrong_arity_fully_saturated_return_fn() {
|
||||||
fn fuzzer_ok_basic() {
|
fn fuzzer_ok_basic() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
fn int() -> Fuzzer<Int> { todo }
|
fn int() -> Fuzzer<Int> { todo }
|
||||||
|
test prop(n via int()) { True }
|
||||||
test prop(n via int()) { todo }
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
assert!(check(parse(source_code)).is_ok());
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
@ -1758,8 +1779,7 @@ fn fuzzer_ok_basic() {
|
||||||
fn fuzzer_ok_explicit() {
|
fn fuzzer_ok_explicit() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo }
|
fn int(prng: PRNG) -> Option<(PRNG, Int)> { todo }
|
||||||
|
test prop(n via int) { Void }
|
||||||
test prop(n via int) { todo }
|
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
assert!(check(parse(source_code)).is_ok());
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
@ -1771,7 +1791,7 @@ fn fuzzer_ok_list() {
|
||||||
fn int() -> Fuzzer<Int> { todo }
|
fn int() -> Fuzzer<Int> { todo }
|
||||||
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
fn list(a: Fuzzer<a>) -> Fuzzer<List<a>> { todo }
|
||||||
|
|
||||||
test prop(xs via list(int())) { todo }
|
test prop(xs via list(int())) { True }
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
assert!(check(parse(source_code)).is_ok());
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
@ -2956,3 +2976,92 @@ fn pattern_bytearray_not_unify_subject() {
|
||||||
Err((_, Error::CouldNotUnify { .. }))
|
Err((_, Error::CouldNotUnify { .. }))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recover_no_assignment_sequence() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn main() {
|
||||||
|
let result = 42
|
||||||
|
expect result + 1 == 43
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recover_no_assignment_fn_body() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn is_bool(foo: Data) -> Void {
|
||||||
|
expect _: Bool = foo
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recover_no_assignment_when_clause() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn main(foo) {
|
||||||
|
when foo is {
|
||||||
|
[] -> Void
|
||||||
|
[x, ..] -> expect _: Int = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recover_no_assignment_fn_if_then_else() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn foo(weird_maths) -> Void {
|
||||||
|
if weird_maths {
|
||||||
|
expect 1 == 2
|
||||||
|
} else {
|
||||||
|
expect 1 + 1 == 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_return_explicit_void() {
|
||||||
|
let source_code = r#"
|
||||||
|
test foo() {
|
||||||
|
Void
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_return_implicit_void() {
|
||||||
|
let source_code = r#"
|
||||||
|
test foo() {
|
||||||
|
let data: Data = 42
|
||||||
|
expect _: Int = data
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_return_illegal() {
|
||||||
|
let source_code = r#"
|
||||||
|
test foo() {
|
||||||
|
42
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((_, Error::IllegalTestType { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::Type;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedFunction, UntypedPattern},
|
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedFunction, UntypedPattern},
|
||||||
error::ExtraData,
|
error::ExtraData,
|
||||||
expr::{self, UntypedExpr},
|
expr::{self, AssignmentPattern, UntypedAssignmentKind, UntypedExpr},
|
||||||
format::Formatter,
|
format::Formatter,
|
||||||
levenshtein,
|
levenshtein,
|
||||||
pretty::Documentable,
|
pretty::Documentable,
|
||||||
|
@ -15,6 +15,7 @@ use owo_colors::{
|
||||||
Stream::{Stderr, Stdout},
|
Stream::{Stderr, Stdout},
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, fmt::Display, rc::Rc};
|
use std::{collections::HashMap, fmt::Display, rc::Rc};
|
||||||
|
use vec1::Vec1;
|
||||||
|
|
||||||
#[derive(Debug, Clone, thiserror::Error)]
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
#[error(
|
#[error(
|
||||||
|
@ -470,6 +471,8 @@ If you really meant to return that last expression, try to replace it with the f
|
||||||
#[label("let-binding as last expression")]
|
#[label("let-binding as last expression")]
|
||||||
location: Span,
|
location: Span,
|
||||||
expr: expr::UntypedExpr,
|
expr: expr::UntypedExpr,
|
||||||
|
patterns: Vec1<AssignmentPattern>,
|
||||||
|
kind: UntypedAssignmentKind,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
|
@ -1023,7 +1026,7 @@ The best thing to do from here is to remove it."#))]
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("I caught a test with too many arguments.\n")]
|
#[error("I caught a test with too many arguments.\n")]
|
||||||
#[diagnostic(code("illegal::test_arity"))]
|
#[diagnostic(code("illegal::test::arity"))]
|
||||||
#[diagnostic(help(
|
#[diagnostic(help(
|
||||||
"Tests are allowed to have 0 or 1 argument, but no more. Here I've found a test definition with {count} arguments. If you need to provide multiple values to a test, use a Record or a Tuple.",
|
"Tests are allowed to have 0 or 1 argument, but no more. Here I've found a test definition with {count} arguments. If you need to provide multiple values to a test, use a Record or a Tuple.",
|
||||||
))]
|
))]
|
||||||
|
@ -1033,6 +1036,18 @@ The best thing to do from here is to remove it."#))]
|
||||||
location: Span,
|
location: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("I caught a test with an illegal return type.\n")]
|
||||||
|
#[diagnostic(code("illegal::test::return"))]
|
||||||
|
#[diagnostic(help(
|
||||||
|
"Tests must return either {Bool} or {Void}. Note that `expect` assignment are implicitly typed {Void} (and thus, may be the last expression of a test).",
|
||||||
|
Bool = "Bool".if_supports_color(Stderr, |s| s.cyan()),
|
||||||
|
Void = "Void".if_supports_color(Stderr, |s| s.cyan()),
|
||||||
|
))]
|
||||||
|
IllegalTestType {
|
||||||
|
#[label("expected Bool or Void")]
|
||||||
|
location: Span,
|
||||||
|
},
|
||||||
|
|
||||||
#[error("I choked on a generic type left in an outward-facing interface.\n")]
|
#[error("I choked on a generic type left in an outward-facing interface.\n")]
|
||||||
#[diagnostic(code("illegal::generic_in_abi"))]
|
#[diagnostic(code("illegal::generic_in_abi"))]
|
||||||
#[diagnostic(help(
|
#[diagnostic(help(
|
||||||
|
@ -1102,6 +1117,7 @@ impl ExtraData for Error {
|
||||||
| Error::UpdateMultiConstructorType { .. }
|
| Error::UpdateMultiConstructorType { .. }
|
||||||
| Error::ValidatorImported { .. }
|
| Error::ValidatorImported { .. }
|
||||||
| Error::IncorrectTestArity { .. }
|
| Error::IncorrectTestArity { .. }
|
||||||
|
| Error::IllegalTestType { .. }
|
||||||
| Error::GenericLeftAtBoundary { .. }
|
| Error::GenericLeftAtBoundary { .. }
|
||||||
| Error::UnexpectedMultiPatternAssignment { .. }
|
| Error::UnexpectedMultiPatternAssignment { .. }
|
||||||
| Error::ExpectOnOpaqueType { .. }
|
| Error::ExpectOnOpaqueType { .. }
|
||||||
|
|
|
@ -1397,9 +1397,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
let (then, typed_patterns) = self.in_new_scope(|scope| {
|
let (then, typed_patterns) = self.in_new_scope(|scope| {
|
||||||
let typed_patterns = scope.infer_clause_pattern(patterns, subject, &location)?;
|
let typed_patterns = scope.infer_clause_pattern(patterns, subject, &location)?;
|
||||||
|
|
||||||
assert_no_assignment(&then)?;
|
let then = if let Some(filler) =
|
||||||
|
recover_from_no_assignment(assert_no_assignment(&then), then.location())?
|
||||||
let then = scope.infer(then)?;
|
{
|
||||||
|
TypedExpr::Sequence {
|
||||||
|
location,
|
||||||
|
expressions: vec![scope.infer(then)?, filler],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.infer(then)?
|
||||||
|
};
|
||||||
|
|
||||||
Ok::<_, Error>((then, typed_patterns))
|
Ok::<_, Error>((then, typed_patterns))
|
||||||
})?;
|
})?;
|
||||||
|
@ -1521,8 +1528,16 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
typed_branches.push(typed_branch);
|
typed_branches.push(typed_branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_no_assignment(&final_else)?;
|
let typed_final_else = if let Some(filler) =
|
||||||
let typed_final_else = self.infer(final_else)?;
|
recover_from_no_assignment(assert_no_assignment(&final_else), final_else.location())?
|
||||||
|
{
|
||||||
|
TypedExpr::Sequence {
|
||||||
|
location: final_else.location(),
|
||||||
|
expressions: vec![self.infer(final_else)?, filler],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.infer(final_else)?
|
||||||
|
};
|
||||||
|
|
||||||
self.unify(
|
self.unify(
|
||||||
first_body_type.clone(),
|
first_body_type.clone(),
|
||||||
|
@ -1569,8 +1584,18 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
location: branch.condition.location().union(location),
|
location: branch.condition.location().union(location),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
assert_no_assignment(&branch.body)?;
|
|
||||||
let body = typer.infer(branch.body.clone())?;
|
let body = if let Some(filler) = recover_from_no_assignment(
|
||||||
|
assert_no_assignment(&branch.body),
|
||||||
|
branch.body.location(),
|
||||||
|
)? {
|
||||||
|
TypedExpr::Sequence {
|
||||||
|
location: branch.body.location(),
|
||||||
|
expressions: vec![typer.infer(branch.body.clone())?, filler],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
typer.infer(branch.body.clone())?
|
||||||
|
};
|
||||||
|
|
||||||
Ok((*value, body, Some((pattern, tipo))))
|
Ok((*value, body, Some((pattern, tipo))))
|
||||||
})?,
|
})?,
|
||||||
|
@ -1584,8 +1609,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
false,
|
false,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
assert_no_assignment(&branch.body)?;
|
let body = if let Some(filler) = recover_from_no_assignment(
|
||||||
let body = self.infer(branch.body.clone())?;
|
assert_no_assignment(&branch.body),
|
||||||
|
branch.body.location(),
|
||||||
|
)? {
|
||||||
|
TypedExpr::Sequence {
|
||||||
|
location: branch.body.location(),
|
||||||
|
expressions: vec![self.infer(branch.body.clone())?, filler],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.infer(branch.body.clone())?
|
||||||
|
};
|
||||||
|
|
||||||
(condition, body, None)
|
(condition, body, None)
|
||||||
}
|
}
|
||||||
|
@ -1631,7 +1665,9 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
body: UntypedExpr,
|
body: UntypedExpr,
|
||||||
return_type: Option<Rc<Type>>,
|
return_type: Option<Rc<Type>>,
|
||||||
) -> Result<(Vec<TypedArg>, TypedExpr, Rc<Type>), Error> {
|
) -> Result<(Vec<TypedArg>, TypedExpr, Rc<Type>), Error> {
|
||||||
assert_no_assignment(&body)?;
|
let location = body.location();
|
||||||
|
|
||||||
|
let no_assignment = assert_no_assignment(&body);
|
||||||
|
|
||||||
let (body_rigid_names, body_infer) = self.in_new_scope(|body_typer| {
|
let (body_rigid_names, body_infer) = self.in_new_scope(|body_typer| {
|
||||||
let mut argument_names = HashMap::with_capacity(args.len());
|
let mut argument_names = HashMap::with_capacity(args.len());
|
||||||
|
@ -1668,7 +1704,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
Ok((body_typer.hydrator.rigid_names(), body_typer.infer(body)))
|
Ok((body_typer.hydrator.rigid_names(), body_typer.infer(body)))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let body = body_infer.map_err(|e| e.with_unify_error_rigid_names(&body_rigid_names))?;
|
let inferred_body =
|
||||||
|
body_infer.map_err(|e| e.with_unify_error_rigid_names(&body_rigid_names));
|
||||||
|
|
||||||
|
let body = if let Some(filler) = recover_from_no_assignment(no_assignment, location)? {
|
||||||
|
TypedExpr::Sequence {
|
||||||
|
location,
|
||||||
|
expressions: vec![inferred_body?, filler],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inferred_body?
|
||||||
|
};
|
||||||
|
|
||||||
// Check that any return type is accurate.
|
// Check that any return type is accurate.
|
||||||
let return_type = match return_type {
|
let return_type = match return_type {
|
||||||
|
@ -1758,7 +1804,6 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
|
|
||||||
for expression in expressions {
|
for expression in expressions {
|
||||||
assert_no_assignment(&expression)?;
|
assert_no_assignment(&expression)?;
|
||||||
|
|
||||||
let typed_expression = self.infer(expression)?;
|
let typed_expression = self.infer(expression)?;
|
||||||
|
|
||||||
self.unify(
|
self.unify(
|
||||||
|
@ -2046,21 +2091,29 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
|
|
||||||
let typed_expression = scope.infer(expression)?;
|
let typed_expression = scope.infer(expression)?;
|
||||||
|
|
||||||
expressions.push(match i.cmp(&(count - 1)) {
|
match i.cmp(&(count - 1)) {
|
||||||
// When the expression is the last in a sequence, we enforce it is NOT
|
// When the expression is the last in a sequence, we enforce it is NOT
|
||||||
// an assignment (kind of treat assignments like statements).
|
// an assignment (kind of treat assignments like statements).
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
no_assignment?;
|
if let Some(filler) =
|
||||||
typed_expression
|
recover_from_no_assignment(no_assignment, typed_expression.location())?
|
||||||
|
{
|
||||||
|
expressions.push(typed_expression);
|
||||||
|
expressions.push(filler);
|
||||||
|
} else {
|
||||||
|
expressions.push(typed_expression);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This isn't the final expression in the sequence, so it *must*
|
// This isn't the final expression in the sequence, so it *must*
|
||||||
// be a let-binding; we do not allow anything else.
|
// be a let-binding; we do not allow anything else.
|
||||||
Ordering::Less => assert_assignment(typed_expression)?,
|
Ordering::Less => {
|
||||||
|
expressions.push(assert_assignment(typed_expression)?);
|
||||||
|
}
|
||||||
|
|
||||||
// Can't actually happen
|
// Can't actually happen
|
||||||
Ordering::Greater => typed_expression,
|
Ordering::Greater => unreachable!(),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(expressions)
|
Ok(expressions)
|
||||||
|
@ -2479,11 +2532,36 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recover_from_no_assignment(
|
||||||
|
result: Result<(), Error>,
|
||||||
|
span: Span,
|
||||||
|
) -> Result<Option<TypedExpr>, Error> {
|
||||||
|
if let Err(Error::LastExpressionIsAssignment {
|
||||||
|
ref patterns,
|
||||||
|
ref kind,
|
||||||
|
..
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
if matches!(kind, AssignmentKind::Expect { ..} if patterns.len() == 1) {
|
||||||
|
return Ok(Some(TypedExpr::void(span)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.map(|()| None)
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_no_assignment(expr: &UntypedExpr) -> Result<(), Error> {
|
fn assert_no_assignment(expr: &UntypedExpr) -> Result<(), Error> {
|
||||||
match expr {
|
match expr {
|
||||||
UntypedExpr::Assignment { value, .. } => Err(Error::LastExpressionIsAssignment {
|
UntypedExpr::Assignment {
|
||||||
|
value,
|
||||||
|
patterns,
|
||||||
|
kind,
|
||||||
|
..
|
||||||
|
} => Err(Error::LastExpressionIsAssignment {
|
||||||
location: expr.location(),
|
location: expr.location(),
|
||||||
expr: *value.clone(),
|
expr: *value.clone(),
|
||||||
|
patterns: patterns.clone(),
|
||||||
|
kind: *kind,
|
||||||
}),
|
}),
|
||||||
UntypedExpr::Trace { then, .. } => assert_no_assignment(then),
|
UntypedExpr::Trace { then, .. } => assert_no_assignment(then),
|
||||||
UntypedExpr::Fn { .. }
|
UntypedExpr::Fn { .. }
|
||||||
|
|
|
@ -392,12 +392,25 @@ fn infer_definition(
|
||||||
|
|
||||||
let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?;
|
let typed_f = infer_function(&f.into(), module_name, hydrators, environment, tracing)?;
|
||||||
|
|
||||||
environment.unify(
|
let is_bool = environment.unify(
|
||||||
typed_f.return_type.clone(),
|
typed_f.return_type.clone(),
|
||||||
builtins::bool(),
|
builtins::bool(),
|
||||||
typed_f.location,
|
typed_f.location,
|
||||||
false,
|
false,
|
||||||
)?;
|
);
|
||||||
|
|
||||||
|
let is_void = environment.unify(
|
||||||
|
typed_f.return_type.clone(),
|
||||||
|
builtins::void(),
|
||||||
|
typed_f.location,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if is_bool.or(is_void).is_err() {
|
||||||
|
return Err(Error::IllegalTestType {
|
||||||
|
location: typed_f.location,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Definition::Test(Function {
|
Ok(Definition::Test(Function {
|
||||||
doc: typed_f.doc,
|
doc: typed_f.doc,
|
||||||
|
|
|
@ -39,7 +39,11 @@ impl EvalResult {
|
||||||
} else {
|
} else {
|
||||||
self.result.is_err()
|
self.result.is_err()
|
||||||
|| matches!(self.result, Ok(Term::Error))
|
|| matches!(self.result, Ok(Term::Error))
|
||||||
|| !matches!(self.result, Ok(Term::Constant(ref con)) if matches!(con.as_ref(), Constant::Bool(true)))
|
|| !matches!(
|
||||||
|
self.result,
|
||||||
|
Ok(Term::Constant(ref con))
|
||||||
|
if matches!(con.as_ref(), Constant::Bool(true)) || matches!(con.as_ref(), Constant::Unit)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue