feat: better rules around Data casting

* you cannot cast to Data ever
* you can cast from Data to ANY TYPE
* you cannot cast via a function call arg hack
This commit is contained in:
rvcas 2023-02-02 00:43:45 -05:00
parent 0ee083cc9a
commit 39e0716f5f
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
3 changed files with 227 additions and 217 deletions

View File

@ -29,14 +29,6 @@ pub struct ScopeResetData {
local_values: HashMap<String, ValueConstructor>, local_values: HashMap<String, ValueConstructor>,
} }
const EXCLUDE_DATA_UNIFY: [&str; 5] = [
builtins::INT,
builtins::BYTE_ARRAY,
builtins::STRING,
builtins::BOOL,
builtins::LIST,
];
#[derive(Debug)] #[derive(Debug)]
pub struct Environment<'a> { pub struct Environment<'a> {
/// Accessors defined in the current module /// Accessors defined in the current module
@ -1218,16 +1210,8 @@ impl<'a> Environment<'a> {
return Ok(()); return Ok(());
} }
if let (Type::App { name: name1, .. }, Type::App { name: name2, .. }) = if t2.is_data() {
(t1.deref(), t2.deref()) return Ok(());
{
if name1 == "Data" && !EXCLUDE_DATA_UNIFY.contains(&name2.as_str()) {
return Ok(());
}
if name2 == "Data" && !EXCLUDE_DATA_UNIFY.contains(&name1.as_str()) {
return Ok(());
}
} }
// Collapse right hand side type links. Left hand side will be collapsed in the next block. // Collapse right hand side type links. Left hand side will be collapsed in the next block.

View File

@ -52,6 +52,39 @@ impl Diagnostic for UnknownLabels {
#[derive(Debug, thiserror::Error, Diagnostic)] #[derive(Debug, thiserror::Error, Diagnostic)]
pub enum Error { pub enum Error {
#[error("I discovered a type cast from Data without an annotation")]
#[diagnostic(code("illegal::type_cast"))]
#[diagnostic(help("Try adding an annotation...\n\n{}", format_suggestion(value)))]
CastDataNoAnn {
#[label("missing annotation")]
location: Span,
value: UntypedExpr,
},
#[error("I struggled to unify the types of two expressions.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types"))]
#[diagnostic(code("type_mismatch"))]
#[diagnostic(help("{}", suggest_unify(expected, given, situation, rigid_type_names)))]
CouldNotUnify {
#[label(
"expected type '{}'",
expected.to_pretty_with_names(rigid_type_names.clone(), 0),
)]
location: Span,
expected: Arc<Type>,
given: Arc<Type>,
situation: Option<UnifyErrorSituation>,
rigid_type_names: HashMap<u64, String>,
},
#[error("I almost got caught in an infinite cycle of type definitions.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-aliases"))]
#[diagnostic(code("cycle"))]
CyclicTypeDefinitions {
#[related]
errors: Vec<Snippet>,
},
#[error("I found two function arguments both called '{}'.\n", label.purple())] #[error("I found two function arguments both called '{}'.\n", label.purple())]
#[diagnostic(code("duplicate::argument"))] #[diagnostic(code("duplicate::argument"))]
#[diagnostic(help("Function arguments cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names." #[diagnostic(help("Function arguments cannot have the same name. You can use '{discard}' and numbers to distinguish between similar names."
@ -78,17 +111,6 @@ pub enum Error {
name: String, name: String,
}, },
#[error("I noticed you were importing '{}' twice.\n", name.purple())]
#[diagnostic(code("duplicate::import"))]
#[diagnostic(help("The best thing to do from here is to remove one of them."))]
DuplicateImport {
#[label]
location: Span,
#[label]
previous_location: Span,
name: String,
},
#[error("I stumbled upon the field '{}' twice in a data-type definition.\n", label.purple())] #[error("I stumbled upon the field '{}' twice in a data-type definition.\n", label.purple())]
#[diagnostic(code("duplicate::field"))] #[diagnostic(code("duplicate::field"))]
#[diagnostic(help(r#"Data-types must have fields with strictly different names. You can use '{discard}' and numbers to distinguish between similar names. #[diagnostic(help(r#"Data-types must have fields with strictly different names. You can use '{discard}' and numbers to distinguish between similar names.
@ -116,6 +138,17 @@ For example:
label: String, label: String,
}, },
#[error("I noticed you were importing '{}' twice.\n", name.purple())]
#[diagnostic(code("duplicate::import"))]
#[diagnostic(help("The best thing to do from here is to remove one of them."))]
DuplicateImport {
#[label]
location: Span,
#[label]
previous_location: Span,
name: String,
},
#[error("I discovered two top-level objects referred to as '{}'.\n", name.purple())] #[error("I discovered two top-level objects referred to as '{}'.\n", name.purple())]
#[diagnostic(code("duplicate::name"))] #[diagnostic(code("duplicate::name"))]
#[diagnostic(help(r#"Top-level definitions cannot have the same name, even if they refer to objects with different natures (e.g. function and test). #[diagnostic(help(r#"Top-level definitions cannot have the same name, even if they refer to objects with different natures (e.g. function and test).
@ -145,6 +178,28 @@ You can use '{discard}' and numbers to distinguish between similar names.
name: String, name: String,
}, },
#[error("I realized the variable '{}' was mentioned more than once in an alternative pattern.\n", name.purple())]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
#[diagnostic(code("duplicate::pattern"))]
DuplicateVarInPattern {
#[label]
location: Span,
name: String,
},
#[error("I tripped over an extra variable in an alternative pattern: {}.\n", name.purple())]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
#[diagnostic(code("unexpected::variable"))]
ExtraVarInAlternativePattern {
#[label]
location: Span,
name: String,
},
#[error("I found a data type that has a function type in it. This is not allowed.\n")] #[error("I found a data type that has a function type in it. This is not allowed.\n")]
#[diagnostic(code("illegal::function_in_type"))] #[diagnostic(code("illegal::function_in_type"))]
#[diagnostic(help("Data-types can't hold functions. If you want to define method-like functions, group the type definition and the methods under a common namespace in a standalone module."))] #[diagnostic(help("Data-types can't hold functions. If you want to define method-like functions, group the type definition and the methods under a common namespace in a standalone module."))]
@ -164,22 +219,6 @@ You can use '{discard}' and numbers to distinguish between similar names.
location: Span, location: Span,
}, },
#[error("I discovered a function which is ending with an assignment.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/functions#named-functions"))]
#[diagnostic(code("illegal::return"))]
#[diagnostic(help(r#"In Aiken, functions must return an explicit result in the form of an expression. While assignments are technically speaking expressions, they aren't allowed to be the last expression of a function because they convey a different meaning and this could be error-prone.
If you really meant to return that last expression, try to replace it with the following:
{sample}"#
, sample = format_suggestion(expr)
))]
LastExpressionIsAssignment {
#[label("let-binding as last expression")]
location: Span,
expr: expr::UntypedExpr,
},
#[error("I saw a {} fields in a context where there should be {}.\n", given.purple(), expected.purple())] #[error("I saw a {} fields in a context where there should be {}.\n", given.purple(), expected.purple())]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types"))] #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types"))]
#[diagnostic(code("arity::constructor"))] #[diagnostic(code("arity::constructor"))]
@ -223,6 +262,18 @@ From there, you can define 'increment', a function that takes a single argument
given: usize, given: usize,
}, },
// TODO: Since we do not actually support patterns on multiple items, we won't likely ever
// encounter that error. We could simplify a bit the type-checker and get rid of that error
// eventually.
#[error("I counted {} different clauses in a multi-pattern instead of {}.\n", given.purple(), expected.purple())]
#[diagnostic(code("arity::clause"))]
IncorrectNumClausePatterns {
#[label]
location: Span,
expected: usize,
given: usize,
},
#[error("I saw a pattern on a constructor that has {} field(s) be matched with {} argument(s).\n", expected.purple(), given.len().purple())] #[error("I saw a pattern on a constructor that has {} field(s) be matched with {} argument(s).\n", expected.purple(), given.len().purple())]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))]
#[diagnostic(code("arity::pattern"))] #[diagnostic(code("arity::pattern"))]
@ -240,18 +291,6 @@ From there, you can define 'increment', a function that takes a single argument
is_record: bool, is_record: bool,
}, },
// TODO: Since we do not actually support patterns on multiple items, we won't likely ever
// encounter that error. We could simplify a bit the type-checker and get rid of that error
// eventually.
#[error("I counted {} different clauses in a multi-pattern instead of {}.\n", given.purple(), expected.purple())]
#[diagnostic(code("arity::clause"))]
IncorrectNumClausePatterns {
#[label]
location: Span,
expected: usize,
given: usize,
},
#[error("I saw a pattern on a {}-tuple be matched into a {}-tuple.\n", expected.purple(), given.purple())] #[error("I saw a pattern on a {}-tuple be matched into a {}-tuple.\n", expected.purple(), given.purple())]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))]
#[diagnostic(code("arity::tuple"))] #[diagnostic(code("arity::tuple"))]
@ -285,6 +324,71 @@ Perhaps, try the following:
given: usize, given: usize,
}, },
#[error(
"I realized the module '{}' contains the keyword '{}', which is forbidden.\n",
name.purple(),
keyword.purple()
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/modules"))]
#[diagnostic(code("illegal::module_name"))]
#[diagnostic(help(r#"You cannot use keywords as part of a module path name. As a quick reminder, here's a list of all the keywords (and thus, of invalid module path names):
as, assert, check, const, else, fn, if, is, let, opaque, pub, test, todo, trace, type, use, when"#))]
KeywordInModuleName { name: String, keyword: String },
#[error("I discovered a function which is ending with an assignment.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/functions#named-functions"))]
#[diagnostic(code("illegal::return"))]
#[diagnostic(help(r#"In Aiken, functions must return an explicit result in the form of an expression. While assignments are technically speaking expressions, they aren't allowed to be the last expression of a function because they convey a different meaning and this could be error-prone.
If you really meant to return that last expression, try to replace it with the following:
{sample}"#
, sample = format_suggestion(expr)
))]
LastExpressionIsAssignment {
#[label("let-binding as last expression")]
location: Span,
expr: expr::UntypedExpr,
},
#[error("I found a missing variable in an alternative pattern: {}.\n", name.purple())]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
#[diagnostic(code("missing::variable"))]
MissingVarInAlternativePattern {
#[label]
location: Span,
name: String,
},
#[error("I stumbled upon an invalid (non-local) clause guard '{}'.\n", name.purple())]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))]
#[diagnostic(code("illegal::clause_guard"))]
#[diagnostic(help("There are some conditions regarding what can be used in a guard. Values must be either local to the function, or defined as module constants. You can't use functions or records in there."))]
NonLocalClauseGuardVariable {
#[label]
location: Span,
name: String,
},
#[error(
"I tripped over an attempt to access tuple elements on something else than a tuple.\n"
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
#[diagnostic(code("illegal::tuple_index"))]
#[diagnostic(help(r#"Because you used a tuple-index on an element, I assumed it had to be a tuple or some kind, but instead I found:
{type_info}"#
, type_info = tipo.to_pretty(4).red()
))]
NotATuple {
#[label]
location: Span,
tipo: Arc<Type>,
},
#[error("I realized that a given 'when/is' expression is non-exhaustive.\n")] #[error("I realized that a given 'when/is' expression is non-exhaustive.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))]
#[diagnostic(code("non_exhaustive_pattern_match"))] #[diagnostic(code("non_exhaustive_pattern_match"))]
@ -319,28 +423,6 @@ In this particular instance, the following cases are missing:
tipo: Arc<Type>, tipo: Arc<Type>,
}, },
#[error(
"I realized the module '{}' contains the keyword '{}', which is forbidden.\n",
name.purple(),
keyword.purple()
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/modules"))]
#[diagnostic(code("illegal::module_name"))]
#[diagnostic(help(r#"You cannot use keywords as part of a module path name. As a quick reminder, here's a list of all the keywords (and thus, of invalid module path names):
as, assert, check, const, else, fn, if, is, let, opaque, pub, test, todo, trace, type, use, when"#))]
KeywordInModuleName { name: String, keyword: String },
#[error("I stumbled upon an invalid (non-local) clause guard '{}'.\n", name.purple())]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#checking-equality-and-ordering-in-patterns"))]
#[diagnostic(code("illegal::clause_guard"))]
#[diagnostic(help("There are some conditions regarding what can be used in a guard. Values must be either local to the function, or defined as module constants. You can't use functions or records in there."))]
NonLocalClauseGuardVariable {
#[label]
location: Span,
name: String,
},
#[error("I discovered a positional argument after a label argument.\n")] #[error("I discovered a positional argument after a label argument.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))] #[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))]
#[diagnostic(code("unexpected::positional_argument"))] #[diagnostic(code("unexpected::positional_argument"))]
@ -407,6 +489,15 @@ You can help me by providing a type-annotation for 'x', as such:
location: Span, location: Span,
}, },
#[error("I almost got caught in an endless loop while inferring a recursive type.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-annotations"))]
#[diagnostic(code("missing::type_annotation"))]
#[diagnostic(help("I have several aptitudes, but inferring recursive types isn't one them. It is still possible to define recursive types just fine, but I will need a little help in the form of type annotation to infer their types should they show up."))]
RecursiveType {
#[label]
location: Span,
},
#[error("I realized you used '{}' as a module name, which is reserved (and not available).\n", name.purple())] #[error("I realized you used '{}' as a module name, which is reserved (and not available).\n", name.purple())]
#[diagnostic(code("illegal::module_name"))] #[diagnostic(code("illegal::module_name"))]
#[diagnostic(help(r#"Some module names are reserved for internal use. This the case of: #[diagnostic(help(r#"Some module names are reserved for internal use. This the case of:
@ -418,6 +509,20 @@ Note that 'aiken' is also imported by default; but you can refer to it explicitl
))] ))]
ReservedModuleName { name: String }, ReservedModuleName { name: String },
#[error(
"I discovered an attempt to access the {} element of a {}-tuple.\n",
Ordinal(*index + 1).to_string().purple(),
size.purple()
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
#[diagnostic(code("invalid::tuple_index"))]
TupleIndexOutOfBound {
#[label]
location: Span,
index: usize,
size: usize,
},
#[error("I tripped over the following labeled argument: {}.\n", label.purple())] #[error("I tripped over the following labeled argument: {}.\n", label.purple())]
#[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))] #[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))]
#[diagnostic(code("unexpected::module_name"))] #[diagnostic(code("unexpected::module_name"))]
@ -498,24 +603,6 @@ Perhaps, try the following:
type_constructors: Vec<String>, type_constructors: Vec<String>,
}, },
#[error("I looked for '{}' in '{}' but couldn't find it.\n", name.purple(), module_name.purple())]
#[diagnostic(code("unknown::module_value"))]
#[diagnostic(help(
"{}",
suggest_neighbor(
name,
value_constructors.iter(),
&suggest_make_public()
)
))]
UnknownModuleValue {
#[label]
location: Span,
name: String,
module_name: String,
value_constructors: Vec<String>,
},
#[error("I looked for '{}' in '{}' but couldn't find it.\n", name.purple(), module_name.purple())] #[error("I looked for '{}' in '{}' but couldn't find it.\n", name.purple(), module_name.purple())]
#[diagnostic(code("unknown::module_type"))] #[diagnostic(code("unknown::module_type"))]
#[diagnostic(help( #[diagnostic(help(
@ -534,6 +621,24 @@ Perhaps, try the following:
type_constructors: Vec<String>, type_constructors: Vec<String>,
}, },
#[error("I looked for '{}' in '{}' but couldn't find it.\n", name.purple(), module_name.purple())]
#[diagnostic(code("unknown::module_value"))]
#[diagnostic(help(
"{}",
suggest_neighbor(
name,
value_constructors.iter(),
&suggest_make_public()
)
))]
UnknownModuleValue {
#[label]
location: Span,
name: String,
module_name: String,
value_constructors: Vec<String>,
},
#[error( #[error(
"I looked for the field '{}' in a record of type '{}' couldn't find it.\n", "I looked for the field '{}' in a record of type '{}' couldn't find it.\n",
label.purple(), label.purple(),
@ -566,6 +671,19 @@ Perhaps, try the following:
types: Vec<String>, types: Vec<String>,
}, },
#[error("I found a reference to an unknown data-type constructor: '{}'.\n", name.purple())]
#[diagnostic(code("unknown::type_constructor"))]
#[diagnostic(help(
"{}",
suggest_neighbor(name, constructors.iter(), "Did you forget to import it?")
))]
UnknownTypeConstructor {
#[label]
location: Span,
name: String,
constructors: Vec<String>,
},
#[error("I found a reference to an unknown variable.\n")] #[error("I found a reference to an unknown variable.\n")]
#[diagnostic(code("unknown::variable"))] #[diagnostic(code("unknown::variable"))]
#[diagnostic(help( #[diagnostic(help(
@ -587,19 +705,6 @@ Perhaps, try the following:
variables: Vec<String>, variables: Vec<String>,
}, },
#[error("I found a reference to an unknown data-type constructor: '{}'.\n", name.purple())]
#[diagnostic(code("unknown::type_constructor"))]
#[diagnostic(help(
"{}",
suggest_neighbor(name, constructors.iter(), "Did you forget to import it?")
))]
UnknownTypeConstructor {
#[label]
location: Span,
name: String,
constructors: Vec<String>,
},
#[error("I discovered a redundant spread operator.\n")] #[error("I discovered a redundant spread operator.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))]
#[diagnostic(code("unexpected::spread_operator"))] #[diagnostic(code("unexpected::spread_operator"))]
@ -620,102 +725,6 @@ The best thing to do from here is to remove it."#))]
location: Span, location: Span,
}, },
#[error("I struggled to unify the types of two expressions.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types"))]
#[diagnostic(code("type_mismatch"))]
#[diagnostic(help("{}", suggest_unify(expected, given, situation, rigid_type_names)))]
CouldNotUnify {
#[label(
"expected type '{}'",
expected.to_pretty_with_names(rigid_type_names.clone(), 0),
)]
location: Span,
expected: Arc<Type>,
given: Arc<Type>,
situation: Option<UnifyErrorSituation>,
rigid_type_names: HashMap<u64, String>,
},
#[error("I tripped over an extra variable in an alternative pattern: {}.\n", name.purple())]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
#[diagnostic(code("unexpected::variable"))]
ExtraVarInAlternativePattern {
#[label]
location: Span,
name: String,
},
#[error("I found a missing variable in an alternative pattern: {}.\n", name.purple())]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
#[diagnostic(code("missing::variable"))]
MissingVarInAlternativePattern {
#[label]
location: Span,
name: String,
},
#[error("I realized the variable '{}' was mentioned more than once in an alternative pattern.\n", name.purple())]
#[diagnostic(url(
"https://aiken-lang.org/language-tour/control-flow#alternative-clause-patterns"
))]
#[diagnostic(code("duplicate::pattern"))]
DuplicateVarInPattern {
#[label]
location: Span,
name: String,
},
#[error("I almost got caught in an infinite cycle of type definitions.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-aliases"))]
#[diagnostic(code("cycle"))]
CyclicTypeDefinitions {
#[related]
errors: Vec<Snippet>,
},
#[error("I almost got caught in an endless loop while inferring a recursive type.\n")]
#[diagnostic(url("https://aiken-lang.org/language-tour/custom-types#type-annotations"))]
#[diagnostic(code("missing::type_annotation"))]
#[diagnostic(help("I have several aptitudes, but inferring recursive types isn't one them. It is still possible to define recursive types just fine, but I will need a little help in the form of type annotation to infer their types should they show up."))]
RecursiveType {
#[label]
location: Span,
},
#[error(
"I tripped over an attempt to access tuple elements on something else than a tuple.\n"
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
#[diagnostic(code("illegal::tuple_index"))]
#[diagnostic(help(r#"Because you used a tuple-index on an element, I assumed it had to be a tuple or some kind, but instead I found:
{type_info}"#
, type_info = tipo.to_pretty(4).red()
))]
NotATuple {
#[label]
location: Span,
tipo: Arc<Type>,
},
#[error(
"I discovered an attempt to access the {} element of a {}-tuple.\n",
Ordinal(*index + 1).to_string().purple(),
size.purple()
)]
#[diagnostic(url("https://aiken-lang.org/language-tour/primitive-types#tuples"))]
#[diagnostic(code("invalid::tuple_index"))]
TupleIndexOutOfBound {
#[label]
location: Span,
index: usize,
size: usize,
},
#[error("I discovered an attempt to import a validator module: '{}'\n", name.purple())] #[error("I discovered an attempt to import a validator module: '{}'\n", name.purple())]
#[diagnostic(code("illegal::import"))] #[diagnostic(code("illegal::import"))]
#[diagnostic(help("If you are trying to share code defined in a validator then move it to a library module under {}", "lib/".purple()))] #[diagnostic(help("If you are trying to share code defined in a validator then move it to a library module under {}", "lib/".purple()))]
@ -724,15 +733,6 @@ The best thing to do from here is to remove it."#))]
location: Span, location: Span,
name: String, name: String,
}, },
#[error("I discovered a type cast from Data without an annotation")]
#[diagnostic(code("illegal::type_cast"))]
#[diagnostic(help("Try adding an annotation...\n\n{}", format_suggestion(value)))]
CastDataNoAnn {
#[label]
location: Span,
value: UntypedExpr,
},
} }
impl Error { impl Error {
@ -951,6 +951,19 @@ fn suggest_unify(
expected.green(), expected.green(),
given.red() given.red()
}, },
Some(UnifyErrorSituation::UnsafeCast) => formatdoc! {
r#"I am inferring the following type:
{}
but I found an expression with a different type:
{}
It is unsafe to cast Data without using assert"#,
expected.green(),
given.red()
},
None => formatdoc! { None => formatdoc! {
r#"I am inferring the following type: r#"I am inferring the following type:
@ -1187,6 +1200,9 @@ pub enum UnifyErrorSituation {
/// The operands of a binary operator were incorrect. /// The operands of a binary operator were incorrect.
Operator(BinOp), Operator(BinOp),
/// Called a function with something of type Data but something else was expected
UnsafeCast,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]

View File

@ -17,7 +17,7 @@ use crate::{
use super::{ use super::{
environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment}, environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment},
error::{Error, Warning}, error::{Error, UnifyErrorSituation, Warning},
hydrator::Hydrator, hydrator::Hydrator,
pattern::PatternTyper, pattern::PatternTyper,
pipe::PipeTyper, pipe::PipeTyper,
@ -941,7 +941,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
(_, value) => self.infer(value), (_, value) => self.infer(value),
}?; }?;
self.unify(tipo, value.tipo(), value.location())?; self.unify(tipo.clone(), value.tipo(), value.location())?;
if value.tipo().is_data() && !tipo.is_data() {
return Err(Error::CouldNotUnify {
location: value.location(),
expected: tipo,
given: value.tipo(),
situation: Some(UnifyErrorSituation::UnsafeCast),
rigid_type_names: HashMap::new(),
});
}
Ok(value) Ok(value)
} }