feat: warn when using expect in a completely safe way

This commit is contained in:
rvcas 2023-02-09 02:10:22 -05:00
parent e9caa710c4
commit 460da20e4a
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
3 changed files with 55 additions and 10 deletions

View File

@ -774,7 +774,7 @@ impl AssignmentKind {
matches!(self, AssignmentKind::Let) matches!(self, AssignmentKind::Let)
} }
pub fn is_assert(&self) -> bool { pub fn is_expect(&self) -> bool {
matches!(self, AssignmentKind::Expect) matches!(self, AssignmentKind::Expect)
} }
} }

View File

@ -1076,6 +1076,21 @@ pub enum Warning {
sample: UntypedExpr, sample: UntypedExpr,
}, },
#[error("I found an {} trying to match a type with one constructor", "expect".purple())]
#[diagnostic(
code("single_constructor_expect"),
help("If your type has one constructor, unless you are casting {} {}, you can\nprefer using a {} binding like so...\n\n{}", "FROM".bold(), "Data".purple(), "let".purple(), format_suggestion(sample))
)]
SingleConstructorExpect {
#[label("use let")]
location: Span,
#[label("only one constructor")]
pattern_location: Span,
#[label("is not {}", "Data".purple())]
value_location: Span,
sample: UntypedExpr,
},
#[error("I found a todo left in the code.\n")] #[error("I found a todo left in the code.\n")]
#[diagnostic(help("You probably want to replace that one with real code... eventually."))] #[diagnostic(help("You probably want to replace that one with real code... eventually."))]
#[diagnostic(code("todo"))] #[diagnostic(code("todo"))]

View File

@ -817,15 +817,18 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
fn infer_assignment( fn infer_assignment(
&mut self, &mut self,
pattern: UntypedPattern, untyped_pattern: UntypedPattern,
value: UntypedExpr, untyped_value: UntypedExpr,
kind: AssignmentKind, kind: AssignmentKind,
annotation: &Option<Annotation>, annotation: &Option<Annotation>,
location: Span, location: Span,
) -> Result<TypedExpr, Error> { ) -> Result<TypedExpr, Error> {
let typed_value = self.in_new_scope(|value_typer| value_typer.infer(value.clone()))?; let typed_value =
self.in_new_scope(|value_typer| value_typer.infer(untyped_value.clone()))?;
let mut value_typ = typed_value.tipo(); let mut value_typ = typed_value.tipo();
let value_is_data = value_typ.is_data();
// Check that any type annotation is accurate. // Check that any type annotation is accurate.
let pattern = if let Some(ann) = annotation { let pattern = if let Some(ann) = annotation {
let ann_typ = self let ann_typ = self
@ -836,25 +839,25 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
ann_typ.clone(), ann_typ.clone(),
value_typ.clone(), value_typ.clone(),
typed_value.type_defining_location(), typed_value.type_defining_location(),
(kind.is_let() && ann_typ.is_data()) || (kind.is_assert() && value_typ.is_data()), (kind.is_let() && ann_typ.is_data()) || (kind.is_expect() && value_is_data),
)?; )?;
value_typ = ann_typ.clone(); value_typ = ann_typ.clone();
// Ensure the pattern matches the type of the value // Ensure the pattern matches the type of the value
PatternTyper::new(self.environment, &self.hydrator).unify( PatternTyper::new(self.environment, &self.hydrator).unify(
pattern, untyped_pattern.clone(),
value_typ.clone(), value_typ.clone(),
Some(ann_typ), Some(ann_typ),
)? )?
} else { } else {
if value_typ.is_data() && !pattern.is_var() && !pattern.is_discard() { if value_is_data && !untyped_pattern.is_var() && !untyped_pattern.is_discard() {
return Err(Error::CastDataNoAnn { return Err(Error::CastDataNoAnn {
location, location,
value: UntypedExpr::Assignment { value: UntypedExpr::Assignment {
location, location,
value: value.into(), value: untyped_value.into(),
pattern, pattern: untyped_pattern,
kind, kind,
annotation: Some(Annotation::Constructor { annotation: Some(Annotation::Constructor {
location: Span::empty(), location: Span::empty(),
@ -868,7 +871,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
// Ensure the pattern matches the type of the value // Ensure the pattern matches the type of the value
PatternTyper::new(self.environment, &self.hydrator).unify( PatternTyper::new(self.environment, &self.hydrator).unify(
pattern, untyped_pattern.clone(),
value_typ.clone(), value_typ.clone(),
None, None,
)? )?
@ -888,6 +891,33 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
unmatched, unmatched,
}); });
} }
} else if !value_is_data
&& self
.environment
.check_exhaustiveness(
vec![pattern.clone()],
collapse_links(value_typ.clone()),
location,
)
.is_ok()
{
self.environment
.warnings
.push(Warning::SingleConstructorExpect {
location: Span {
start: location.start,
end: location.start + 6,
},
pattern_location: dbg!(untyped_pattern.location()),
value_location: dbg!(untyped_value.location()),
sample: UntypedExpr::Assignment {
location: Span::empty(),
value: Box::new(untyped_value),
pattern: untyped_pattern,
kind: AssignmentKind::Let,
annotation: None,
},
})
} }
Ok(TypedExpr::Assignment { Ok(TypedExpr::Assignment {