Revise desugaring following feedback
- We now consistently desugar an expect in the last position as `Void`. Regardless of the pattern. Desugaring to a boolean value is deemed too confusing. - This commit also removes the desugaring for let-binding. It's only ever allowed for _expect_ which then behaves like a side effect. - We also now allow tests to return either `Bool` or `Void`. A test that returns `Void` is treated the same as a test returning `True`.
This commit is contained in:
parent
fbe6f02fd1
commit
9aa9070f56
|
@ -1423,26 +1423,6 @@ impl UntypedPattern {
|
||||||
is_record: false,
|
is_record: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns Some(<bool>) if the pattern is a [`Boolean`] literal,
|
|
||||||
/// holding the target value. None if it isn't a bool pattern.
|
|
||||||
pub fn get_bool(&self) -> Option<bool> {
|
|
||||||
match self {
|
|
||||||
Self::Constructor {
|
|
||||||
module: None,
|
|
||||||
name,
|
|
||||||
constructor: (),
|
|
||||||
..
|
|
||||||
} if name == "True" => Some(true),
|
|
||||||
Self::Constructor {
|
|
||||||
module: None,
|
|
||||||
name,
|
|
||||||
constructor: (),
|
|
||||||
..
|
|
||||||
} if name == "False" => Some(false),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypedPattern {
|
impl TypedPattern {
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub(crate) use crate::{
|
||||||
TypedDataType, TypedIfBranch, TypedRecordUpdateArg, UnOp, UntypedArg,
|
TypedDataType, TypedIfBranch, TypedRecordUpdateArg, UnOp, UntypedArg,
|
||||||
UntypedAssignmentKind, UntypedClause, UntypedIfBranch, UntypedRecordUpdateArg,
|
UntypedAssignmentKind, UntypedClause, UntypedIfBranch, UntypedRecordUpdateArg,
|
||||||
},
|
},
|
||||||
builtins::{bool, void},
|
builtins::void,
|
||||||
parser::token::Base,
|
parser::token::Base,
|
||||||
tipo::{
|
tipo::{
|
||||||
check_replaceable_opaque_type, convert_opaque_type, lookup_data_type_by_tipo,
|
check_replaceable_opaque_type, convert_opaque_type, lookup_data_type_by_tipo,
|
||||||
|
@ -492,25 +492,6 @@ impl TypedExpr {
|
||||||
location,
|
location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bool(value: bool, location: Span) -> Self {
|
|
||||||
TypedExpr::Var {
|
|
||||||
name: "Bool".to_string(),
|
|
||||||
constructor: ValueConstructor {
|
|
||||||
public: true,
|
|
||||||
variant: ValueConstructorVariant::Record {
|
|
||||||
name: if value { "True" } else { "False" }.to_string(),
|
|
||||||
arity: 0,
|
|
||||||
field_map: None,
|
|
||||||
location: Span::empty(),
|
|
||||||
module: String::new(),
|
|
||||||
constructors_count: 2,
|
|
||||||
},
|
|
||||||
tipo: bool(),
|
|
||||||
},
|
|
||||||
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),
|
||||||
})
|
})
|
||||||
|
|
|
@ -1009,6 +1009,107 @@ fn anonymous_function_dupicate_args() {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assignement_last_expr_when() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn foo() {
|
||||||
|
let bar = None
|
||||||
|
|
||||||
|
when bar is {
|
||||||
|
Some(_) -> {
|
||||||
|
let wow = 1
|
||||||
|
}
|
||||||
|
None -> {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((_, Error::LastExpressionIsAssignment { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assignement_last_expr_if_first_branch() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn foo() {
|
||||||
|
if True {
|
||||||
|
let thing = 1
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((_, Error::LastExpressionIsAssignment { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assignement_last_expr_if_branches() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn foo() {
|
||||||
|
if True {
|
||||||
|
2
|
||||||
|
} else if False {
|
||||||
|
let thing = 1
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((_, Error::LastExpressionIsAssignment { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn assignement_last_expr_if_final_else() {
|
||||||
|
let source_code = r#"
|
||||||
|
pub fn foo() {
|
||||||
|
if True {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
let thing = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
check(parse(source_code)),
|
||||||
|
Err((_, Error::LastExpressionIsAssignment { .. }))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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#"
|
||||||
|
@ -1668,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());
|
||||||
|
@ -1679,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());
|
||||||
|
@ -1692,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());
|
||||||
|
@ -2906,24 +3005,19 @@ fn recover_no_assignment_when_clause() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
pub fn main(foo) {
|
pub fn main(foo) {
|
||||||
when foo is {
|
when foo is {
|
||||||
[] -> let bar = foo
|
[] -> Void
|
||||||
[x, ..] -> expect _: Int = x
|
[x, ..] -> expect _: Int = x
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
let (warnings, _) = check(parse(source_code)).unwrap();
|
assert!(check(parse(source_code)).is_ok());
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
&warnings[..],
|
|
||||||
[Warning::UnusedVariable { name, .. }] if name == "bar",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recover_no_assignment_fn_if_then_else() {
|
fn recover_no_assignment_fn_if_then_else() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
pub fn foo(weird_maths) -> Bool {
|
pub fn foo(weird_maths) -> Void {
|
||||||
if weird_maths {
|
if weird_maths {
|
||||||
expect 1 == 2
|
expect 1 == 2
|
||||||
} else {
|
} else {
|
||||||
|
@ -2936,20 +3030,38 @@ fn recover_no_assignment_fn_if_then_else() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recover_no_assignment_logical_chain_op() {
|
fn test_return_explicit_void() {
|
||||||
let source_code = r#"
|
let source_code = r#"
|
||||||
pub fn foo() -> Bool {
|
test foo() {
|
||||||
and {
|
Void
|
||||||
expect 1 + 1 == 2,
|
|
||||||
True,
|
|
||||||
2 > 0,
|
|
||||||
or {
|
|
||||||
expect True,
|
|
||||||
False,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
assert!(check(parse(source_code)).is_ok());
|
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, AssignmentPattern, UntypedExpr},
|
expr::{self, AssignmentPattern, UntypedAssignmentKind, UntypedExpr},
|
||||||
format::Formatter,
|
format::Formatter,
|
||||||
levenshtein,
|
levenshtein,
|
||||||
pretty::Documentable,
|
pretty::Documentable,
|
||||||
|
@ -472,6 +472,7 @@ If you really meant to return that last expression, try to replace it with the f
|
||||||
location: Span,
|
location: Span,
|
||||||
expr: expr::UntypedExpr,
|
expr: expr::UntypedExpr,
|
||||||
patterns: Vec1<AssignmentPattern>,
|
patterns: Vec1<AssignmentPattern>,
|
||||||
|
kind: UntypedAssignmentKind,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
|
@ -1025,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.",
|
||||||
))]
|
))]
|
||||||
|
@ -1035,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(
|
||||||
|
@ -1104,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 { .. }
|
||||||
|
|
|
@ -1803,17 +1803,8 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
let mut typed_expressions = vec![];
|
let mut typed_expressions = vec![];
|
||||||
|
|
||||||
for expression in expressions {
|
for expression in expressions {
|
||||||
let typed_expression = if let Some(filler) = recover_from_no_assignment(
|
assert_no_assignment(&expression)?;
|
||||||
assert_no_assignment(&expression),
|
let typed_expression = self.infer(expression)?;
|
||||||
expression.location(),
|
|
||||||
)? {
|
|
||||||
TypedExpr::Sequence {
|
|
||||||
location: expression.location(),
|
|
||||||
expressions: vec![self.infer(expression)?, filler],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.infer(expression)?
|
|
||||||
};
|
|
||||||
|
|
||||||
self.unify(
|
self.unify(
|
||||||
bool(),
|
bool(),
|
||||||
|
@ -2545,24 +2536,32 @@ fn recover_from_no_assignment(
|
||||||
result: Result<(), Error>,
|
result: Result<(), Error>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Option<TypedExpr>, Error> {
|
) -> Result<Option<TypedExpr>, Error> {
|
||||||
if let Err(Error::LastExpressionIsAssignment { patterns, .. }) = result {
|
if let Err(Error::LastExpressionIsAssignment {
|
||||||
match patterns.first().pattern.get_bool() {
|
ref patterns,
|
||||||
Some(expected) if patterns.len() == 1 => Ok(Some(TypedExpr::bool(expected, span))),
|
ref kind,
|
||||||
_ => Ok(Some(TypedExpr::void(span))),
|
..
|
||||||
|
}) = result
|
||||||
|
{
|
||||||
|
if matches!(kind, AssignmentKind::Expect { ..} if patterns.len() == 1) {
|
||||||
|
return Ok(Some(TypedExpr::void(span)));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
result.map(|()| None)
|
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 {
|
UntypedExpr::Assignment {
|
||||||
value, patterns, ..
|
value,
|
||||||
|
patterns,
|
||||||
|
kind,
|
||||||
|
..
|
||||||
} => Err(Error::LastExpressionIsAssignment {
|
} => Err(Error::LastExpressionIsAssignment {
|
||||||
location: expr.location(),
|
location: expr.location(),
|
||||||
expr: *value.clone(),
|
expr: *value.clone(),
|
||||||
patterns: patterns.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