diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index cd358ee5..6bb5ab7d 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -29,14 +29,6 @@ pub struct ScopeResetData { local_values: HashMap, } -const EXCLUDE_DATA_UNIFY: [&str; 5] = [ - builtins::INT, - builtins::BYTE_ARRAY, - builtins::STRING, - builtins::BOOL, - builtins::LIST, -]; - #[derive(Debug)] pub struct Environment<'a> { /// Accessors defined in the current module @@ -1218,16 +1210,8 @@ impl<'a> Environment<'a> { return Ok(()); } - if let (Type::App { name: name1, .. }, Type::App { name: name2, .. }) = - (t1.deref(), t2.deref()) - { - if name1 == "Data" && !EXCLUDE_DATA_UNIFY.contains(&name2.as_str()) { - return Ok(()); - } - - if name2 == "Data" && !EXCLUDE_DATA_UNIFY.contains(&name1.as_str()) { - return Ok(()); - } + if t2.is_data() { + return Ok(()); } // Collapse right hand side type links. Left hand side will be collapsed in the next block. diff --git a/crates/aiken-lang/src/tipo/error.rs b/crates/aiken-lang/src/tipo/error.rs index 756cf425..a36c6e9e 100644 --- a/crates/aiken-lang/src/tipo/error.rs +++ b/crates/aiken-lang/src/tipo/error.rs @@ -52,6 +52,39 @@ impl Diagnostic for UnknownLabels { #[derive(Debug, thiserror::Error, Diagnostic)] 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, + given: Arc, + situation: Option, + rigid_type_names: HashMap, + }, + + #[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, + }, + #[error("I found two function arguments both called '{}'.\n", label.purple())] #[diagnostic(code("duplicate::argument"))] #[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, }, - #[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())] #[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. @@ -116,6 +138,17 @@ For example: 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())] #[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). @@ -145,6 +178,28 @@ You can use '{discard}' and numbers to distinguish between similar names. 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")] #[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."))] @@ -164,22 +219,6 @@ You can use '{discard}' and numbers to distinguish between similar names. 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())] #[diagnostic(url("https://aiken-lang.org/language-tour/custom-types"))] #[diagnostic(code("arity::constructor"))] @@ -223,6 +262,18 @@ From there, you can define 'increment', a function that takes a single argument 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())] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#matching"))] #[diagnostic(code("arity::pattern"))] @@ -240,18 +291,6 @@ From there, you can define 'increment', a function that takes a single argument 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())] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))] #[diagnostic(code("arity::tuple"))] @@ -285,6 +324,71 @@ Perhaps, try the following: 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, + }, + #[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(code("non_exhaustive_pattern_match"))] @@ -319,28 +423,6 @@ In this particular instance, the following cases are missing: tipo: Arc, }, - #[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")] #[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))] #[diagnostic(code("unexpected::positional_argument"))] @@ -407,6 +489,15 @@ You can help me by providing a type-annotation for 'x', as such: 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())] #[diagnostic(code("illegal::module_name"))] #[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 }, + #[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())] #[diagnostic(url("https://aiken-lang.org/language-tour/functions#labeled-arguments"))] #[diagnostic(code("unexpected::module_name"))] @@ -498,24 +603,6 @@ Perhaps, try the following: type_constructors: Vec, }, - #[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, - }, - #[error("I looked for '{}' in '{}' but couldn't find it.\n", name.purple(), module_name.purple())] #[diagnostic(code("unknown::module_type"))] #[diagnostic(help( @@ -534,6 +621,24 @@ Perhaps, try the following: type_constructors: Vec, }, + #[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, + }, + #[error( "I looked for the field '{}' in a record of type '{}' couldn't find it.\n", label.purple(), @@ -566,6 +671,19 @@ Perhaps, try the following: types: Vec, }, + #[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, + }, + #[error("I found a reference to an unknown variable.\n")] #[diagnostic(code("unknown::variable"))] #[diagnostic(help( @@ -587,19 +705,6 @@ Perhaps, try the following: variables: Vec, }, - #[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, - }, - #[error("I discovered a redundant spread operator.\n")] #[diagnostic(url("https://aiken-lang.org/language-tour/control-flow#destructuring"))] #[diagnostic(code("unexpected::spread_operator"))] @@ -620,102 +725,6 @@ The best thing to do from here is to remove it."#))] 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, - given: Arc, - situation: Option, - rigid_type_names: HashMap, - }, - - #[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, - }, - - #[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, - }, - - #[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())] #[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()))] @@ -724,15 +733,6 @@ The best thing to do from here is to remove it."#))] location: Span, 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 { @@ -951,6 +951,19 @@ fn suggest_unify( expected.green(), 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! { r#"I am inferring the following type: @@ -1187,6 +1200,9 @@ pub enum UnifyErrorSituation { /// The operands of a binary operator were incorrect. Operator(BinOp), + + /// Called a function with something of type Data but something else was expected + UnsafeCast, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 3e48878f..851a80c3 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -17,7 +17,7 @@ use crate::{ use super::{ environment::{assert_no_labeled_arguments, collapse_links, EntityKind, Environment}, - error::{Error, Warning}, + error::{Error, UnifyErrorSituation, Warning}, hydrator::Hydrator, pattern::PatternTyper, pipe::PipeTyper, @@ -941,7 +941,17 @@ impl<'a, 'b> ExprTyper<'a, 'b> { (_, 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) }