From 6c389d4a6be7935343ce3ebf2542ea2ec8a3fafc Mon Sep 17 00:00:00 2001 From: quatquatt <78693624+quatquatt@users.noreply.github.com> Date: Wed, 7 Aug 2024 12:43:44 -0400 Subject: [PATCH 1/9] This commit updates the the link from the former, unofficial nixos wiki page to the new https://wiki.nixos.org (#199) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 192052c5..5b3ffcbb 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ The project itself provides build instructions for the [Nix package manager](htt Those can be used for the most recent version of the compiler, or for working on it. To enter the development environment, run either `nix-shell` or `nix develop` depending on whether you are using nix -with [flakes](https://nixos.wiki/wiki/Flakes) and [nix command](https://nixos.wiki/wiki/Nix_command) enabled or not. +with [flakes](https:/wiki.nixos.org/wiki/Flakes) and [nix command](https://wiki.nixos.org/wiki/Nix_command) enabled or not. Then you can build and run the project with `cargo` as described at the top of this section. Beyond that, the project will also build with `nix-build` / `nix build`, meaning you can install it on your system using From 73b7bbcef7547479f883e7ca974df8d139fd9382 Mon Sep 17 00:00:00 2001 From: jfecher Date: Wed, 7 Aug 2024 11:44:28 -0500 Subject: [PATCH 2/9] Fix nix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b3ffcbb..1d931f60 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ The project itself provides build instructions for the [Nix package manager](htt Those can be used for the most recent version of the compiler, or for working on it. To enter the development environment, run either `nix-shell` or `nix develop` depending on whether you are using nix -with [flakes](https:/wiki.nixos.org/wiki/Flakes) and [nix command](https://wiki.nixos.org/wiki/Nix_command) enabled or not. +with [flakes](https://wiki.nixos.org/wiki/Flakes) and [nix command](https://wiki.nixos.org/wiki/Nix_command) enabled or not. Then you can build and run the project with `cargo` as described at the top of this section. Beyond that, the project will also build with `nix-build` / `nix build`, meaning you can install it on your system using From d53eddf0327fdb6f373b1419c05f7265d0b5fe32 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Sun, 11 Aug 2024 12:38:06 -0500 Subject: [PATCH 3/9] Fix hover hints in LSP debugging types instead of displaying them --- ante-ls/src/main.rs | 2 +- src/error/mod.rs | 2 +- src/main.rs | 2 +- src/types/typeprinter.rs | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ante-ls/src/main.rs b/ante-ls/src/main.rs index bcfbd14b..b3c2f54a 100644 --- a/ante-ls/src/main.rs +++ b/ante-ls/src/main.rs @@ -136,7 +136,7 @@ impl LanguageServer for Backend { let name = v.kind.name(); let value = - typeprinter::show_type_and_traits(&name, typ, &info.required_traits, &info.trait_info, &cache); + typeprinter::show_type_and_traits(&name, typ, &info.required_traits, &info.trait_info, &cache, false); let location = v.locate(); let range = diff --git a/src/error/mod.rs b/src/error/mod.rs index a1b3efbe..c029a090 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -297,7 +297,7 @@ impl Display for DiagnosticKind { write!(f, "{actual} has no field '{field_name}' of type {expected}") }, DiagnosticKind::TypeError(TypeErrorKind::AssignToNonMutRef, expected, actual) => { - write!(f, "Expression of type {actual} must be a `{expected}` type to be assigned to") + write!(f, "Expression of type {actual} must be a `{expected}` to be assigned to") }, DiagnosticKind::TypeError(TypeErrorKind::AssignToWrongType, expected, actual) => { write!(f, "Cannot assign expression of type {actual} to a Ref of type {expected}") diff --git a/src/main.rs b/src/main.rs index c738c7fb..f307fdd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,7 +64,7 @@ fn print_definition_types(cache: &ModuleCache) { if let Some(typ) = &info.typ { let type_string = - types::typeprinter::show_type_and_traits(name, typ, &info.required_traits, &info.trait_info, cache); + types::typeprinter::show_type_and_traits(name, typ, &info.required_traits, &info.trait_info, cache, true); println!("{}", type_string); } else { println!("{} : (none)", name); diff --git a/src/types/typeprinter.rs b/src/types/typeprinter.rs index 0b97a484..976aeafe 100644 --- a/src/types/typeprinter.rs +++ b/src/types/typeprinter.rs @@ -63,7 +63,7 @@ fn fill_typevar_map(map: &mut HashMap, typevars: Vec)>, - cache: &ModuleCache<'_>, + cache: &ModuleCache<'_>, debug: bool, ) -> String { let mut map = HashMap::new(); let mut current = 'a'; @@ -71,7 +71,6 @@ pub fn show_type_and_traits( let typevars = typ.find_all_typevars(false, cache); fill_typevar_map(&mut map, typevars, &mut current); - let debug = true; let typ = typ.clone(); let printer = TypePrinter { typ, cache, debug, typevar_names: map.clone() }; let type_string = format!("{} : {}", name, printer); From d902bd79df21abf5b3c6f157eb18f2faa61a0e6e Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Sun, 11 Aug 2024 14:50:02 -0500 Subject: [PATCH 4/9] Add an actual subtyping relation --- src/hir/definitions.rs | 13 +-- src/hir/monomorphisation.rs | 28 ++++-- src/lifetimes/mod.rs | 133 ---------------------------- src/nameresolution/mod.rs | 24 +++-- src/parser/ast.rs | 1 + src/types/mod.rs | 57 ++++++++++-- src/types/typechecker.rs | 171 +++++++++++++++++++++--------------- src/types/typeprinter.rs | 45 ++++++---- 8 files changed, 223 insertions(+), 249 deletions(-) diff --git a/src/hir/definitions.rs b/src/hir/definitions.rs index 199ea6bf..1625a025 100644 --- a/src/hir/definitions.rs +++ b/src/hir/definitions.rs @@ -63,9 +63,9 @@ impl std::hash::Hash for DefinitionType { types::Type::TypeVariable(_) => (), // Do nothing types::Type::Function(_) => (), types::Type::TypeApplication(_, _) => (), - types::Type::Ref(shared, mutable, _) => { - shared.hash(state); - mutable.hash(state); + types::Type::Ref { sharedness, mutability, lifetime: _ } => { + sharedness.hash(state); + mutability.hash(state); }, types::Type::Struct(field_names, _) => { for name in field_names { @@ -77,6 +77,7 @@ impl std::hash::Hash for DefinitionType { id.hash(state); } }, + types::Type::Tag(tag) => tag.hash(state), } }) } @@ -98,8 +99,9 @@ fn definition_type_eq(a: &types::Type, b: &types::Type) -> bool { // This will monomorphize separate definitions for polymorphically-owned references // which is undesired. Defaulting them to shared/owned though can change behavior // if traits are involved. - (Type::Ref(shared1, mutable1, _), Type::Ref(shared2, mutable2, _)) => { - shared1 == shared2 && mutable1 == mutable2 + (Type::Ref { sharedness: shared1, mutability: mutable1, .. }, + Type::Ref { sharedness: shared2, mutability: mutable2, .. }) => { + definition_type_eq(shared1, shared2) && definition_type_eq(mutable1, mutable2) }, (Type::Function(f1), Type::Function(f2)) => { if f1.parameters.len() != f2.parameters.len() { @@ -137,6 +139,7 @@ fn definition_type_eq(a: &types::Type, b: &types::Type) -> bool { id1 == id2 && args1.iter().zip(args2).all(|(t1, t2)| definition_type_eq(t1, t2)) }) }, + (Type::Tag(tag1), Type::Tag(tag2)) => tag1 == tag2, (othera, otherb) => { assert_ne!(std::mem::discriminant(othera), std::mem::discriminant(otherb), "ICE: Missing match case"); false diff --git a/src/hir/monomorphisation.rs b/src/hir/monomorphisation.rs index fd2f9874..92fdfc8b 100644 --- a/src/hir/monomorphisation.rs +++ b/src/hir/monomorphisation.rs @@ -142,12 +142,12 @@ impl<'c> Context<'c> { let fuel = fuel - 1; match &self.cache.type_bindings[id.0] { - Bound(TypeVariable(id2) | Ref(_, _, id2)) => self.find_binding(*id2, fuel), + Bound(TypeVariable(id2)) => self.find_binding(*id2, fuel), Bound(binding) => Ok(binding), Unbound(..) => { for bindings in self.monomorphisation_bindings.iter().rev() { match bindings.get(&id) { - Some(TypeVariable(id2) | Ref(_, _, id2)) => return self.find_binding(*id2, fuel), + Some(TypeVariable(id2)) => return self.find_binding(*id2, fuel), Some(binding) => return Ok(binding), None => (), } @@ -204,7 +204,12 @@ impl<'c> Context<'c> { let args = fmap(args, |arg| self.follow_all_bindings_inner(arg, fuel)); TypeApplication(Box::new(con), args) }, - Ref(..) => typ.clone(), + Ref { mutability, sharedness, lifetime } => { + let mutability = Box::new(self.follow_all_bindings_inner(mutability, fuel)); + let sharedness = Box::new(self.follow_all_bindings_inner(sharedness, fuel)); + let lifetime = Box::new(self.follow_all_bindings_inner(lifetime, fuel)); + Ref { mutability, sharedness, lifetime } + }, Struct(fields, id) => match self.find_binding(*id, fuel) { Ok(binding) => self.follow_all_bindings_inner(binding, fuel), Err(_) => { @@ -217,6 +222,7 @@ impl<'c> Context<'c> { }, }, Effects(effects) => self.follow_all_effect_bindings_inner(effects, fuel), + Tag(tag) => Tag(*tag), } } @@ -315,6 +321,9 @@ impl<'c> Context<'c> { Primitive(FloatType) => { unreachable!("'Float' type constructor without arguments found during size_of_type") }, + Tag(tag) => { + unreachable!("'{}' found during size_of_type", tag) + } Function(..) => Self::ptr_size(), @@ -346,7 +355,7 @@ impl<'c> Context<'c> { _ => unreachable!("Kind error inside size_of_type"), }, - Ref(..) => Self::ptr_size(), + Ref { .. } => Self::ptr_size(), Struct(fields, rest) => { if let Ok(binding) = self.find_binding(*rest, RECURSION_LIMIT) { let binding = binding.clone(); @@ -519,7 +528,7 @@ impl<'c> Context<'c> { let typ = self.follow_bindings_shallow(typ); match typ { - Ok(Primitive(PrimitiveType::Ptr) | Ref(..)) => Type::Primitive(hir::PrimitiveType::Pointer), + Ok(Primitive(PrimitiveType::Ptr) | Ref { .. }) => Type::Primitive(hir::PrimitiveType::Pointer), Ok(Primitive(PrimitiveType::IntegerType)) => { if self.is_type_variable(&args[0]) { // Default to i32 @@ -553,11 +562,14 @@ impl<'c> Context<'c> { } }, - Ref(..) => { + Ref { .. } => { unreachable!( "Kind error during monomorphisation. Attempted to translate a `ref` without a type argument" ) }, + Tag(tag) => { + unreachable!("Kind error during monomorphisation. Attempted to translate a `{}` as a type", tag) + } Struct(fields, rest) => { if let Ok(binding) = self.find_binding(*rest, fuel) { let binding = binding.clone(); @@ -1517,7 +1529,7 @@ impl<'c> Context<'c> { TypeApplication(typ, args) => { match typ.as_ref() { // Pass through ref types transparently - types::Type::Ref(..) => self.get_field_index(field_name, &args[0]), + types::Type::Ref { .. } => self.get_field_index(field_name, &args[0]), // These last 2 cases are the same. They're duplicated to avoid another follow_bindings_shallow call. typ => self.get_field_index(field_name, typ), } @@ -1551,7 +1563,7 @@ impl<'c> Context<'c> { let ref_type = match lhs_type { types::Type::TypeApplication(constructor, args) => match self.follow_bindings_shallow(constructor.as_ref()) { - Ok(types::Type::Ref(..)) => Some(self.convert_type(&args[0])), + Ok(types::Type::Ref { .. }) => Some(self.convert_type(&args[0])), _ => None, }, _ => None, diff --git a/src/lifetimes/mod.rs b/src/lifetimes/mod.rs index 69d98307..a5b54c2c 100644 --- a/src/lifetimes/mod.rs +++ b/src/lifetimes/mod.rs @@ -1,138 +1,5 @@ use crate::cache::ModuleCache; use crate::parser::ast::Ast; -use crate::types::TypeVariableId; - -/// A lifetime variable is represented simply as a type variable for ease of unification -/// during the type inference pass. -pub type LifetimeVariableId = TypeVariableId; - -// struct LifetimeAnalyzer { -// pub level: StackFrameIndex, -// -// /// Map from RegionVariableId -> StackFrame -// /// Contains the stack frame index each region should be allocated in -// pub lifetimes: Vec, -// } -// -// struct StackFrameIndex(usize); #[allow(unused)] pub fn infer<'c>(_ast: &mut Ast<'c>, _cache: &mut ModuleCache<'c>) {} - -// trait InferableLifetime { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>); -// } -// -// impl<'ast> InferableLifetime for Ast<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// dispatch_on_expr!(self, InferableLifetime::infer_lifetime, analyzer, cache) -// } -// } -// -// impl<'ast> InferableLifetime for ast::Literal<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// // Do nothing: literals cannot contain a ref type -// } -// } -// -// impl<'ast> InferableLifetime for ast::Variable<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Lambda<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::FunctionCall<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Definition<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::If<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Match<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::TypeDefinition<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::TypeAnnotation<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Import<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::TraitDefinition<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::TraitImpl<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Return<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Sequence<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Extern<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::MemberAccess<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Tuple<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } -// -// impl<'ast> InferableLifetime for ast::Assignment<'ast> { -// fn infer_lifetime<'c>(&mut self, analyzer: &mut LifetimeAnalyzer, cache: &mut ModuleCache<'c>) { -// -// } -// } diff --git a/src/nameresolution/mod.rs b/src/nameresolution/mod.rs index ac67ec9d..ea9466d1 100644 --- a/src/nameresolution/mod.rs +++ b/src/nameresolution/mod.rs @@ -50,7 +50,7 @@ use crate::types::traits::ConstraintSignature; use crate::types::typed::Typed; use crate::types::{ Field, FunctionType, GeneralizedType, LetBindingLevel, PrimitiveType, Type, TypeConstructor, TypeInfoBody, - TypeInfoId, TypeVariableId, INITIAL_LEVEL, STRING_TYPE, + TypeInfoId, TypeVariableId, INITIAL_LEVEL, STRING_TYPE, TypeTag, }; use crate::util::{fmap, timing, trustme}; @@ -589,9 +589,10 @@ impl<'c> NameResolver { Type::TypeVariable(_) => 0, Type::UserDefined(id) => cache[*id].args.len(), Type::TypeApplication(_, _) => 0, - Type::Ref(..) => 1, + Type::Ref { .. } => 1, Type::Struct(_, _) => 0, Type::Effects(_) => 0, + Type::Tag(_) => 0, } } @@ -739,14 +740,27 @@ impl<'c> NameResolver { Type::TypeApplication(Box::new(pair), args) }, - ast::Type::Reference(sharednes, mutability, _) => { + ast::Type::Reference(sharedness, mutability, _) => { // When translating ref types, all have a hidden lifetime variable that is unified // under the hood by the compiler to determine the reference's stack lifetime. // This is never able to be manually specified by the programmer, so we use // next_type_variable_id on the cache rather than the NameResolver's version which // would add a name into scope. - let lifetime_variable = cache.next_type_variable_id(self.let_binding_level); - Type::Ref(*sharednes, *mutability, lifetime_variable) + let lifetime = Box::new(cache.next_type_variable(self.let_binding_level)); + + let sharedness = Box::new(match sharedness { + ast::Sharedness::Polymorphic => cache.next_type_variable(self.let_binding_level), + ast::Sharedness::Shared => Type::Tag(TypeTag::Shared), + ast::Sharedness::Owned => Type::Tag(TypeTag::Owned), + }); + + let mutability = Box::new(match mutability { + ast::Mutability::Polymorphic => cache.next_type_variable(self.let_binding_level), + ast::Mutability::Immutable => Type::Tag(TypeTag::Immutable), + ast::Mutability::Mutable => Type::Tag(TypeTag::Mutable), + }); + + Type::Ref { sharedness, mutability, lifetime } }, } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 2aa8e402..72dae648 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -223,6 +223,7 @@ pub enum Sharedness { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Mutability { + #[allow(unused)] Polymorphic, Immutable, Mutable, diff --git a/src/types/mod.rs b/src/types/mod.rs index 05f7422c..c0e0fe35 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -9,9 +9,8 @@ use std::collections::BTreeMap; use crate::cache::{DefinitionInfoId, ModuleCache}; use crate::error::location::{Locatable, Location}; use crate::lexer::token::{FloatKind, IntegerKind}; -use crate::parser::ast::{Mutability, Sharedness}; use crate::util::fmap; -use crate::{lifetimes, util}; +use crate::util; use self::typeprinter::TypePrinter; use crate::types::effects::EffectSet; @@ -103,7 +102,7 @@ pub enum Type { /// A region-allocated reference to some data. /// Contains a region variable that is unified with other refs during type /// inference. All these refs will be allocated in the same region. - Ref(Sharedness, Mutability, lifetimes::LifetimeVariableId), + Ref { mutability: Box, sharedness: Box, lifetime: Box }, /// A (row-polymorphic) struct type. Unlike normal rho variables, /// the type variable used here replaces the entire type if bound. @@ -115,6 +114,22 @@ pub enum Type { /// are included in it since they are still valid in a type position /// most notably when substituting type variables for effects. Effects(EffectSet), + + /// Tags are any type which isn't a valid type by itself but may be inside + /// a larger type. For example, `shared` is not a type, but a polymorphic + /// reference's type variable may resolve to a shared reference. + Tag(TypeTag), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum TypeTag { + // References can be polymorphic in their ownership or mutability. + // When they are, they hold type variables which later resolve to one + // of these variants. + Owned, + Shared, + Mutable, + Immutable, } #[derive(Debug, Clone)] @@ -191,13 +206,14 @@ impl Type { use Type::*; match self { Primitive(_) => None, - Ref(..) => None, + Ref { .. } => None, Function(function) => function.return_type.union_constructor_variants(cache), TypeApplication(typ, _) => typ.union_constructor_variants(cache), UserDefined(id) => cache.type_infos[id.0].union_variants(), TypeVariable(_) => unreachable!("Constructors should always have concrete types"), Struct(_, _) => None, Effects(_) => None, + Tag(_) => None, } } @@ -224,6 +240,7 @@ impl Type { match self { Type::Primitive(_) => (), Type::UserDefined(_) => (), + Type::Tag(_) => (), Type::Function(function) => { for parameter in &function.parameters { @@ -232,10 +249,15 @@ impl Type { function.environment.traverse_rec(cache, f); function.return_type.traverse_rec(cache, f); }, - Type::TypeVariable(id) | Type::Ref(_, _, id) => match &cache.type_bindings[id.0] { + Type::TypeVariable(id) => match &cache.type_bindings[id.0] { TypeBinding::Bound(binding) => binding.traverse_rec(cache, f), TypeBinding::Unbound(_, _) => (), }, + Type::Ref { sharedness, mutability, lifetime } => { + sharedness.traverse_rec(cache, f); + mutability.traverse_rec(cache, f); + lifetime.traverse_rec(cache, f); + } Type::TypeApplication(constructor, args) => { constructor.traverse_rec(cache, f); for arg in args { @@ -274,7 +296,7 @@ impl Type { Type::Primitive(_) => (), Type::UserDefined(_) => (), Type::TypeVariable(_) => (), - Type::Ref(..) => (), + Type::Tag(_) => (), Type::Function(function) => { for parameter in &function.parameters { @@ -301,6 +323,10 @@ impl Type { typ.traverse_no_follow_rec(f); } }, + Type::Ref { sharedness, mutability, lifetime: _ } => { + sharedness.traverse_no_follow_rec(f); + mutability.traverse_no_follow_rec(f); + }, } } @@ -325,7 +351,12 @@ impl Type { let args = fmap(args, |arg| arg.approx_to_string()); format!("({} {})", constructor, args.join(" ")) }, - Type::Ref(shared, mutable, id) => format!("&'{} {}{}", id.0, shared, mutable), + Type::Ref { sharedness, mutability, lifetime } => { + let shared = sharedness.approx_to_string(); + let mutable = mutability.approx_to_string(); + let lifetime = lifetime.approx_to_string(); + format!("{}{} '{}", mutable, shared, lifetime) + } Type::Struct(fields, id) => { let fields = fmap(fields, |(name, typ)| format!("{}: {}", name, typ.approx_to_string())); format!("{{ {}, ..tv{} }}", fields.join(", "), id.0) @@ -341,6 +372,18 @@ impl Type { format!("can {}, ..tv{}", effects.join(", "), set.replacement.0) } }, + Type::Tag(tag) => tag.to_string(), + } + } +} + +impl std::fmt::Display for TypeTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TypeTag::Owned => write!(f, "owned"), + TypeTag::Shared => write!(f, "shared"), + TypeTag::Mutable => write!(f, "!"), + TypeTag::Immutable => write!(f, "&"), } } } diff --git a/src/types/typechecker.rs b/src/types/typechecker.rs index 6d6853e1..67f51e5a 100644 --- a/src/types/typechecker.rs +++ b/src/types/typechecker.rs @@ -17,7 +17,7 @@ //! Most of this file is translated from: https://github.com/jfecher/algorithm-j //! That repository may be a good starting place for those new to type inference. //! For those already familiar with type inference or more interested in ante's -//! internals, the reccomended starting place while reading this file is the +//! internals, the recommended starting place while reading this file is the //! `Inferable` trait and its impls for each node. From there, you can see what //! type inference does for each node type and inspect any helpers that are used. //! @@ -30,7 +30,7 @@ use crate::cache::{DefinitionInfoId, DefinitionKind, EffectInfoId, ModuleCache, use crate::cache::{ImplScopeId, VariableId}; use crate::error::location::{Locatable, Location}; use crate::error::{Diagnostic, DiagnosticKind as D, TypeErrorKind, TypeErrorKind as TE}; -use crate::parser::ast::{self, ClosureEnvironment, Mutability, Sharedness}; +use crate::parser::ast::{self, ClosureEnvironment}; use crate::types::traits::{RequiredTrait, TraitConstraint, TraitConstraints}; use crate::types::typed::Typed; use crate::types::EffectSet; @@ -46,7 +46,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use super::mutual_recursion::{definition_is_mutually_recursive, try_generalize_definition}; use super::traits::{Callsite, ConstraintSignature, TraitConstraintId}; -use super::{GeneralizedType, TypeInfoBody}; +use super::{GeneralizedType, TypeInfoBody, TypeTag}; /// The current LetBindingLevel we are at. /// This increases by 1 whenever we enter the rhs of a `ast::Definition` and decreases @@ -153,8 +153,10 @@ pub fn type_application_bindings(info: &TypeInfo<'_>, typeargs: &[Type], cache: /// Given `a` returns `ref a` fn ref_of(typ: Type, cache: &mut ModuleCache) -> Type { - let new_var = next_type_variable_id(cache); - let constructor = Box::new(Type::Ref(Sharedness::Polymorphic, Mutability::Polymorphic, new_var)); + let sharedness = Box::new(next_type_variable(cache)); + let mutability = Box::new(next_type_variable(cache)); + let lifetime = Box::new(next_type_variable(cache)); + let constructor = Box::new(Type::Ref { sharedness, mutability, lifetime }); TypeApplication(constructor, vec![typ]) } @@ -188,8 +190,9 @@ pub fn replace_all_typevars_with_bindings( ) -> Type { match typ { Primitive(p) => Primitive(*p), + Tag(tag) => Tag(*tag), - TypeVariable(id) => replace_typevar_with_binding(*id, new_bindings, TypeVariable, cache), + TypeVariable(id) => replace_typevar_with_binding(*id, new_bindings, cache), Function(function) => { let parameters = fmap(&function.parameters, |parameter| { @@ -203,14 +206,11 @@ pub fn replace_all_typevars_with_bindings( }, UserDefined(id) => UserDefined(*id), - // We must recurse on the lifetime variable since they are unified as normal type variables - Ref(sharedness, mutability, lifetime) => { - let make_ref = |new_lifetime| Ref(*sharedness, *mutability, new_lifetime); - match replace_typevar_with_binding(*lifetime, new_bindings, make_ref, cache) { - TypeVariable(new_lifetime) => make_ref(new_lifetime), - new_ref @ Ref(..) => new_ref, - _ => unreachable!("Bound Ref lifetime to non-lifetime type"), - } + Ref { mutability, sharedness, lifetime } => { + let mutability = Box::new(replace_all_typevars_with_bindings(mutability, new_bindings, cache)); + let sharedness = Box::new(replace_all_typevars_with_bindings(sharedness, new_bindings, cache)); + let lifetime = Box::new(replace_all_typevars_with_bindings(lifetime, new_bindings, cache)); + Ref { sharedness, mutability, lifetime } }, TypeApplication(typ, args) => { @@ -239,11 +239,8 @@ pub fn replace_all_typevars_with_bindings( /// If the given TypeVariableId is unbound then return the matching binding in new_bindings. /// If there is no binding found, instantiate a new type variable and use that. -/// -/// `default` should be either TypeVariable or Ref and controls which kind of type gets -/// created that wraps the newly-instantiated TypeVariableId if one is made. fn replace_typevar_with_binding( - id: TypeVariableId, new_bindings: &mut TypeBindings, default: impl FnOnce(TypeVariableId) -> Type, + id: TypeVariableId, new_bindings: &mut TypeBindings, cache: &mut ModuleCache<'_>, ) -> Type { if let Bound(typ) = &cache.type_bindings[id.0] { @@ -252,7 +249,7 @@ fn replace_typevar_with_binding( var.clone() } else { let new_typevar = next_type_variable_id(cache); - let typ = default(new_typevar); + let typ = Type::TypeVariable(new_typevar); new_bindings.insert(id, typ.clone()); typ } @@ -266,8 +263,9 @@ fn replace_typevar_with_binding( pub fn bind_typevars(typ: &Type, type_bindings: &TypeBindings, cache: &ModuleCache<'_>) -> Type { match typ { Primitive(p) => Primitive(*p), + Tag(tag) => Tag(*tag), - TypeVariable(id) => bind_typevar(*id, type_bindings, TypeVariable, cache), + TypeVariable(id) => bind_typevar(*id, type_bindings, cache), Function(function) => { let parameters = fmap(&function.parameters, |parameter| bind_typevars(parameter, type_bindings, cache)); @@ -279,13 +277,11 @@ pub fn bind_typevars(typ: &Type, type_bindings: &TypeBindings, cache: &ModuleCac }, UserDefined(id) => UserDefined(*id), - Ref(sharedness, mutability, lifetime) => { - let make_ref = |lifetime| Ref(*sharedness, *mutability, lifetime); - match bind_typevar(*lifetime, type_bindings, make_ref, cache) { - TypeVariable(new_lifetime) => make_ref(new_lifetime), - new_ref @ Ref(..) => new_ref, - _ => unreachable!("Bound Ref lifetime to non-lifetime type"), - } + Ref { mutability, sharedness, lifetime } => { + let mutability = Box::new(bind_typevars(mutability, type_bindings, cache)); + let sharedness = Box::new(bind_typevars(sharedness, type_bindings, cache)); + let lifetime = Box::new(bind_typevars(lifetime, type_bindings, cache)); + Ref { sharedness, mutability, lifetime } }, TypeApplication(typ, args) => { @@ -327,9 +323,9 @@ pub fn bind_typevars(typ: &Type, type_bindings: &TypeBindings, cache: &ModuleCac /// Helper for bind_typevars which binds a single TypeVariableId if it is Unbound /// and it is found in the type_bindings. If a type_binding wasn't found, a -/// default TypeVariable or Ref is constructed by passing the relevant constructor to `default`. +/// default TypeVariable is constructed. fn bind_typevar( - id: TypeVariableId, type_bindings: &TypeBindings, default: impl FnOnce(TypeVariableId) -> Type, + id: TypeVariableId, type_bindings: &TypeBindings, cache: &ModuleCache<'_>, ) -> Type { // TODO: This ordering of checking type_bindings first is important. @@ -342,7 +338,7 @@ fn bind_typevar( if let Bound(typ) = &cache.type_bindings[id.0] { bind_typevars(&typ.clone(), type_bindings, cache) } else { - default(id) + Type::TypeVariable(id) } }, } @@ -354,6 +350,7 @@ pub fn contains_any_typevars_from_list(typ: &Type, list: &[TypeVariableId], cach match typ { Primitive(_) => false, UserDefined(_) => false, + Tag(_) => false, TypeVariable(id) => type_variable_contains_any_typevars_from_list(*id, list, cache), @@ -364,7 +361,11 @@ pub fn contains_any_typevars_from_list(typ: &Type, list: &[TypeVariableId], cach || contains_any_typevars_from_list(&function.effects, list, cache) }, - Ref(_, _, lifetime) => type_variable_contains_any_typevars_from_list(*lifetime, list, cache), + Ref { mutability, sharedness, lifetime } => { + contains_any_typevars_from_list(mutability, list, cache) + || contains_any_typevars_from_list(sharedness, list, cache) + || contains_any_typevars_from_list(lifetime, list, cache) + } TypeApplication(typ, args) => { contains_any_typevars_from_list(typ, list, cache) @@ -555,6 +556,7 @@ pub(super) fn occurs( match typ { Primitive(_) => OccursResult::does_not_occur(), UserDefined(_) => OccursResult::does_not_occur(), + Tag(_) => OccursResult::does_not_occur(), TypeVariable(var_id) => typevars_match(id, level, *var_id, bindings, fuel, cache), Function(function) => occurs(id, level, &function.return_type, bindings, fuel, cache) @@ -563,7 +565,10 @@ pub(super) fn occurs( .then_all(&function.parameters, |param| occurs(id, level, param, bindings, fuel, cache)), TypeApplication(typ, args) => occurs(id, level, typ, bindings, fuel, cache) .then_all(args, |arg| occurs(id, level, arg, bindings, fuel, cache)), - Ref(_, _, lifetime) => typevars_match(id, level, *lifetime, bindings, fuel, cache), + Ref { mutability, sharedness, lifetime } => + occurs(id, level, mutability, bindings, fuel, cache) + .then(|| occurs(id, level, sharedness, bindings, fuel, cache)) + .then(|| occurs(id, level, lifetime, bindings, fuel, cache)), Struct(fields, var_id) => typevars_match(id, level, *var_id, bindings, fuel, cache) .then_all(fields.iter().map(|(_, typ)| typ), |field| occurs(id, level, field, bindings, fuel, cache)), Effects(effects) => effects.occurs(id, level, bindings, fuel, cache), @@ -590,7 +595,7 @@ pub(super) fn typevars_match( /// Returns what a given type is bound to, following all typevar links until it reaches an Unbound one. pub fn follow_bindings_in_cache_and_map(typ: &Type, bindings: &UnificationBindings, cache: &ModuleCache<'_>) -> Type { match typ { - TypeVariable(id) | Ref(_, _, id) => match find_binding(*id, bindings, cache) { + TypeVariable(id) => match find_binding(*id, bindings, cache) { Bound(typ) => follow_bindings_in_cache_and_map(&typ, bindings, cache), Unbound(..) => typ.clone(), }, @@ -600,7 +605,7 @@ pub fn follow_bindings_in_cache_and_map(typ: &Type, bindings: &UnificationBindin pub fn follow_bindings_in_cache(typ: &Type, cache: &ModuleCache<'_>) -> Type { match typ { - TypeVariable(id) | Ref(_, _, id) => match &cache.type_bindings[id.0] { + TypeVariable(id) => match &cache.type_bindings[id.0] { Bound(typ) => follow_bindings_in_cache(typ, cache), Unbound(..) => typ.clone(), }, @@ -619,9 +624,9 @@ pub fn follow_bindings_in_cache(typ: &Type, cache: &ModuleCache<'_>) -> Type { /// This function performs the bulk of the work for the various unification functions. #[allow(clippy::nonminimal_bool)] pub fn try_unify_with_bindings_inner<'b>( - t1: &Type, t2: &Type, bindings: &mut UnificationBindings, location: Location<'b>, cache: &mut ModuleCache<'b>, + actual: &Type, expected: &Type, bindings: &mut UnificationBindings, location: Location<'b>, cache: &mut ModuleCache<'b>, ) -> Result<(), ()> { - match (t1, t2) { + match (actual, expected) { (Primitive(p1), Primitive(p2)) if p1 == p2 => Ok(()), (UserDefined(id1), UserDefined(id2)) if id1 == id2 => Ok(()), @@ -632,9 +637,9 @@ pub fn try_unify_with_bindings_inner<'b>( // it to the minimum scope of type variables in b. This happens within the occurs check. // The unification of the LetBindingLevel here is a form of lifetime inference for the // typevar and is used during generalization to determine which variables to generalize. - (TypeVariable(id), _) => try_unify_type_variable_with_bindings(*id, t1, t2, bindings, location, cache), + (TypeVariable(id), _) => try_unify_type_variable_with_bindings(*id, actual, expected, true, bindings, location, cache), - (_, TypeVariable(id)) => try_unify_type_variable_with_bindings(*id, t2, t1, bindings, location, cache), + (_, TypeVariable(id)) => try_unify_type_variable_with_bindings(*id, expected, actual, false, bindings, location, cache), (Function(function1), Function(function2)) => { if function1.parameters.len() != function2.parameters.len() { @@ -651,7 +656,9 @@ pub fn try_unify_with_bindings_inner<'b>( try_unify_with_bindings_inner(a_arg, b_arg, bindings, location, cache)? } - try_unify_with_bindings_inner(&function1.return_type, &function2.return_type, bindings, location, cache)?; + // Reverse the arguments when checking return types to preserve + // some subtyping relations with mutable & immutable references. + try_unify_with_bindings_inner(&function2.return_type, &function1.return_type, bindings, location, cache)?; try_unify_with_bindings_inner(&function1.environment, &function2.environment, bindings, location, cache)?; try_unify_with_bindings_inner(&function1.effects, &function2.effects, bindings, location, cache) }, @@ -672,16 +679,11 @@ pub fn try_unify_with_bindings_inner<'b>( }, // Refs have a hidden lifetime variable we need to unify here - (Ref(shared1, mut1, a_lifetime), Ref(shared2, mut2, _)) => { - if shared1 != shared2 || mut1 != mut2 { - if *shared1 != Sharedness::Polymorphic && *shared2 != Sharedness::Polymorphic { - if *mut1 != Mutability::Polymorphic && *mut2 != Mutability::Polymorphic { - return Err(()); - } - } - } - - try_unify_type_variable_with_bindings(*a_lifetime, t1, t2, bindings, location, cache) + (Ref { sharedness: a_shared, mutability: a_mut, lifetime: a_lifetime }, + Ref { sharedness: b_shared, mutability: b_mut, lifetime: b_lifetime }) => { + try_unify_with_bindings_inner(a_shared, b_shared, bindings, location, cache)?; + try_unify_with_bindings_inner(a_mut, b_mut, bindings, location, cache)?; + try_unify_with_bindings_inner(a_lifetime, b_lifetime, bindings, location, cache) }, // Follow any bindings here for convenience so we don't have to check if a or b @@ -709,6 +711,11 @@ pub fn try_unify_with_bindings_inner<'b>( Ok(()) }, + (Tag(tag1), Tag(tag2)) if tag1 == tag2 => Ok(()), + + // ! <: & + (Tag(TypeTag::Mutable), Tag(TypeTag::Immutable)) => Ok(()), + _ => Err(()), } } @@ -731,6 +738,7 @@ fn bind_struct_fields<'c>( rest1, &TypeVariable(rest1), &TypeVariable(rest2), + true, bindings, location, cache, @@ -742,11 +750,11 @@ fn bind_struct_fields<'c>( } else if new_fields.len() != fields1.len() { // Set 1 := 2 let struct2 = Struct(new_fields, rest2); - try_unify_type_variable_with_bindings(rest1, &TypeVariable(rest1), &struct2, bindings, location, cache)?; + try_unify_type_variable_with_bindings(rest1, &TypeVariable(rest1), &struct2, true, bindings, location, cache)?; } else if new_fields.len() != fields2.len() { // Set 2 := 1 let struct1 = Struct(new_fields, rest1); - try_unify_type_variable_with_bindings(rest2, &TypeVariable(rest2), &struct1, bindings, location, cache)?; + try_unify_type_variable_with_bindings(rest2, &TypeVariable(rest2), &struct1, false, bindings, location, cache)?; } Ok(()) @@ -832,7 +840,7 @@ fn get_fields( } }, TypeApplication(constructor, args) => match follow_bindings_in_cache_and_map(constructor, bindings, cache) { - Ref(..) => get_fields(&args[0], &[], bindings, cache), + Ref { .. } => get_fields(&args[0], &[], bindings, cache), other => get_fields(&other, args, bindings, cache), }, Struct(fields, rest) => match &cache.type_bindings[rest.0] { @@ -850,11 +858,19 @@ fn get_fields( /// Unify a single type variable (id arising from the type a) with an expected type b. /// Follows the given TypeBindings in bindings and the cache if a is Bound. fn try_unify_type_variable_with_bindings<'c>( - id: TypeVariableId, a: &Type, b: &Type, bindings: &mut UnificationBindings, location: Location<'c>, + id: TypeVariableId, a: &Type, b: &Type, + typevar_on_lhs: bool, + bindings: &mut UnificationBindings, location: Location<'c>, cache: &mut ModuleCache<'c>, ) -> Result<(), ()> { match find_binding(id, bindings, cache) { - Bound(a) => try_unify_with_bindings_inner(&a, b, bindings, location, cache), + Bound(a) => { + if typevar_on_lhs { + try_unify_with_bindings_inner(&a, b, bindings, location, cache) + } else { + try_unify_with_bindings_inner(b, &a, bindings, location, cache) + } + } Unbound(a_level, _a_kind) => { // Create binding for boundTy that is currently empty. // Ensure not to create recursive bindings to the same variable @@ -893,37 +909,37 @@ pub fn try_unify_with_bindings<'b>( /// set of type bindings, and returning all the newly-created bindings on success, /// or the unification error message on error. pub fn try_unify<'c>( - t1: &Type, t2: &Type, location: Location<'c>, cache: &mut ModuleCache<'c>, error_kind: TypeErrorKind, + actual: &Type, expected: &Type, location: Location<'c>, cache: &mut ModuleCache<'c>, error_kind: TypeErrorKind, ) -> UnificationResult<'c> { let mut bindings = UnificationBindings::empty(); - try_unify_with_bindings(t1, t2, &mut bindings, location, cache, error_kind).map(|()| bindings) + try_unify_with_bindings(actual, expected, &mut bindings, location, cache, error_kind).map(|()| bindings) } /// Try to unify all the given type, with the given bindings in scope. /// Will add new bindings to the given TypeBindings and return them all on success. pub fn try_unify_all_with_bindings<'c>( - vec1: &[Type], vec2: &[Type], mut bindings: UnificationBindings, location: Location<'c>, + actual: &[Type], expected: &[Type], mut bindings: UnificationBindings, location: Location<'c>, cache: &mut ModuleCache<'c>, error_kind: TypeErrorKind, ) -> UnificationResult<'c> { - if vec1.len() != vec2.len() { + if actual.len() != expected.len() { // This bad error message is the reason this function isn't used within // try_unify_with_bindings! We'd need access to the full type to give better // errors like the other function does. - let vec1 = fmap(vec1, |typ| typ.display(cache).to_string()); - let vec2 = fmap(vec2, |typ| typ.display(cache).to_string()); + let vec1 = fmap(actual, |typ| typ.display(cache).to_string()); + let vec2 = fmap(expected, |typ| typ.display(cache).to_string()); return Err(Diagnostic::new(location, D::TypeLengthMismatch(vec1, vec2))); } - for (t1, t2) in vec1.iter().zip(vec2.iter()) { - try_unify_with_bindings(t1, t2, &mut bindings, location, cache, error_kind.clone())?; + for (actual, expected) in actual.iter().zip(expected.iter()) { + try_unify_with_bindings(actual, expected, &mut bindings, location, cache, error_kind.clone())?; } Ok(bindings) } /// Unifies the two given types, remembering the unification results in the cache. /// If this operation fails, a user-facing error message is emitted. -pub fn unify<'c>(t1: &Type, t2: &Type, location: Location<'c>, cache: &mut ModuleCache<'c>, error_kind: TypeErrorKind) { - perform_bindings_or_push_error(try_unify(t1, t2, location, cache, error_kind), cache); +pub fn unify<'c>(actual: &Type, expected: &Type, location: Location<'c>, cache: &mut ModuleCache<'c>, error_kind: TypeErrorKind) { + perform_bindings_or_push_error(try_unify(actual, expected, location, cache, error_kind), cache); } /// Helper for committing to the results of try_unify. @@ -958,6 +974,7 @@ pub fn find_all_typevars(typ: &Type, polymorphic_only: bool, cache: &ModuleCache match typ { Primitive(_) => vec![], UserDefined(_) => vec![], + Tag(_) => vec![], TypeVariable(id) => find_typevars_in_typevar_binding(*id, polymorphic_only, cache), Function(function) => { let mut type_variables = vec![]; @@ -976,7 +993,12 @@ pub fn find_all_typevars(typ: &Type, polymorphic_only: bool, cache: &ModuleCache } type_variables }, - Ref(_, _, lifetime) => find_typevars_in_typevar_binding(*lifetime, polymorphic_only, cache), + Ref { sharedness, mutability, lifetime } => { + let mut type_variables = find_all_typevars(mutability, polymorphic_only, cache); + type_variables.append(&mut find_all_typevars(sharedness, polymorphic_only, cache)); + type_variables.append(&mut find_all_typevars(lifetime, polymorphic_only, cache)); + type_variables + } Struct(fields, id) => match &cache.type_bindings[id.0] { Bound(t) => find_all_typevars(t, polymorphic_only, cache), Unbound(..) => { @@ -1690,10 +1712,8 @@ impl<'a> Inferable<'a> for ast::Definition<'a> { // t, traits let mut result = infer(self.expr.as_mut(), cache); if self.mutable { - let lifetime = next_type_variable_id(cache); - let shared = Sharedness::Polymorphic; - let mutability = Mutability::Mutable; - result.typ = Type::TypeApplication(Box::new(Type::Ref(shared, mutability, lifetime)), vec![result.typ]); + let ref_type = mut_polymorphically_shared_ref(cache); + result.typ = Type::TypeApplication(Box::new(ref_type), vec![result.typ]); } // The rhs of a Definition must be inferred at a greater LetBindingLevel than @@ -1959,8 +1979,7 @@ impl<'a> Inferable<'a> for ast::Assignment<'a> { let mut rhs = infer(self.rhs.as_mut(), cache); result.combine(&mut rhs, cache); - let lifetime = next_type_variable_id(cache); - let mut_ref = Type::Ref(Sharedness::Polymorphic, Mutability::Mutable, lifetime); + let mut_ref = mut_polymorphically_shared_ref(cache); let mutref = Type::TypeApplication(Box::new(mut_ref), vec![rhs.typ.clone()]); match try_unify(&result.typ, &mutref, self.location, cache, TE::NeverShown) { @@ -1972,13 +1991,19 @@ impl<'a> Inferable<'a> for ast::Assignment<'a> { } } +fn mut_polymorphically_shared_ref(cache: &mut ModuleCache) -> Type { + let mutability = Box::new(Type::Tag(TypeTag::Mutable)); + let sharedness = Box::new(next_type_variable(cache)); + let lifetime = Box::new(next_type_variable(cache)); + Type::Ref { mutability, sharedness, lifetime } +} + fn issue_assignment_error<'c>( lhs: &Type, lhs_loc: Location<'c>, rhs: &Type, location: Location<'c>, cache: &mut ModuleCache<'c>, ) { // Try to offer a more specific error message - let lifetime = next_type_variable_id(cache); let var = next_type_variable(cache); - let mutref = Type::Ref(Sharedness::Polymorphic, Mutability::Mutable, lifetime); + let mutref = mut_polymorphically_shared_ref(cache); let mutref = Type::TypeApplication(Box::new(mutref), vec![var]); if let Err(msg) = try_unify(&mutref, lhs, lhs_loc, cache, TE::AssignToNonMutRef) { diff --git a/src/types/typeprinter.rs b/src/types/typeprinter.rs index 976aeafe..4f785790 100644 --- a/src/types/typeprinter.rs +++ b/src/types/typeprinter.rs @@ -4,7 +4,6 @@ //! types/traits are displayed via `type.display(cache)` rather than directly having //! a Display impl. use crate::cache::{ModuleCache, TraitInfoId}; -use crate::parser::ast::{Mutability, Sharedness}; use crate::types::traits::{ConstraintSignature, ConstraintSignaturePrinter, RequiredTrait, TraitConstraintId}; use crate::types::typechecker::find_all_typevars; use crate::types::{FunctionType, PrimitiveType, Type, TypeBinding, TypeInfoId, TypeVariableId}; @@ -17,6 +16,7 @@ use colored::*; use super::effects::EffectSet; use super::GeneralizedType; +use super::typechecker::follow_bindings_in_cache; /// Wrapper containing the information needed to print out a type pub struct TypePrinter<'a, 'b> { @@ -165,9 +165,10 @@ impl<'a, 'b> TypePrinter<'a, 'b> { Type::TypeVariable(id) => self.fmt_type_variable(*id, f), Type::UserDefined(id) => self.fmt_user_defined_type(*id, f), Type::TypeApplication(constructor, args) => self.fmt_type_application(constructor, args, f), - Type::Ref(shared, mutable, lifetime) => self.fmt_ref(*shared, *mutable, *lifetime, f), + Type::Ref { sharedness, mutability, lifetime } => self.fmt_ref(sharedness, mutability, lifetime, f), Type::Struct(fields, rest) => self.fmt_struct(fields, *rest, f), Type::Effects(effects) => self.fmt_effects(effects, f), + Type::Tag(tag) => write!(f, "{tag}"), } } @@ -278,26 +279,34 @@ impl<'a, 'b> TypePrinter<'a, 'b> { } fn fmt_ref( - &self, shared: Sharedness, mutable: Mutability, lifetime: TypeVariableId, f: &mut Formatter, + &self, shared: &Type, mutable: &Type, lifetime: &Type, f: &mut Formatter, ) -> std::fmt::Result { - match &self.cache.type_bindings[lifetime.0] { - TypeBinding::Bound(typ) => self.fmt_type(typ, f), - TypeBinding::Unbound(..) => { - let shared = shared.to_string(); - let mutable = mutable.to_string(); - let space = if shared.is_empty() { "" } else { " " }; + let mutable = follow_bindings_in_cache(mutable, self.cache); + let shared = follow_bindings_in_cache(shared, self.cache); + let parenthesize = matches!(shared, Type::Tag(_)) || self.debug; - write!(f, "{}{}{}{}", "&".blue(), shared.blue(), space, mutable.blue())?; + if parenthesize { + write!(f, "(")?; + } - if self.debug { - match self.typevar_names.get(&lifetime) { - Some(name) => write!(f, "{{{}}}", name)?, - None => write!(f, "{{?{}}}", lifetime.0)?, - } - } - Ok(()) - }, + match mutable { + Type::Tag(tag) => write!(f, "{tag}")?, + _ => write!(f, "?")?, + } + + if let Type::Tag(tag) = shared { + write!(f, "{tag}")?; + } + + if self.debug { + write!(f, " ")?; + self.fmt_type(lifetime, f)?; } + + if parenthesize { + write!(f, ")")?; + } + Ok(()) } fn fmt_forall(&self, typevars: &[TypeVariableId], typ: &Type, f: &mut Formatter) -> std::fmt::Result { From 1f7660e2e7cfe3b89cc30ee516aa13ed32f6d5b6 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Sun, 11 Aug 2024 15:14:13 -0500 Subject: [PATCH 5/9] Fix subtyping ordering & some bugs --- examples/typechecking/instantiation.an | 2 +- src/hir/monomorphisation.rs | 2 +- src/types/traitchecker.rs | 2 +- src/types/typechecker.rs | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/typechecking/instantiation.an b/examples/typechecking/instantiation.an index 0e6ba989..1f19dff2 100644 --- a/examples/typechecking/instantiation.an +++ b/examples/typechecking/instantiation.an @@ -17,5 +17,5 @@ id x = x // add : (forall a b c d e f g h i. ((a - c => e can g) - (a - b => c can g) -> (a => (b => e can g) can h) can i)) // id : (forall a b. (a -> a can b)) // one : (forall a b c d. ((a => b can d) - a -> b can d)) -// two1 : (forall a b c. ((a => a can c) - a -> a can c)) +// two1 : (forall a b c. ((b => b can c) - b -> b can c)) // two2 : ((a => a can c) => (a => a can c) can d) diff --git a/src/hir/monomorphisation.rs b/src/hir/monomorphisation.rs index 92fdfc8b..932326f9 100644 --- a/src/hir/monomorphisation.rs +++ b/src/hir/monomorphisation.rs @@ -700,8 +700,8 @@ impl<'c> Context<'c> { if definition.trait_impl.is_some() { let definition_type = definition.typ.as_ref().unwrap().remove_forall(); let bindings = typechecker::try_unify( - definition_type, typ, + definition_type, definition.location, &mut self.cache, TE::MonomorphizationError, diff --git a/src/types/traitchecker.rs b/src/types/traitchecker.rs index f2d2575f..369e9b77 100644 --- a/src/types/traitchecker.rs +++ b/src/types/traitchecker.rs @@ -288,8 +288,8 @@ fn find_matching_normal_impls( let location = constraint.locate(cache); let type_bindings = typechecker::try_unify_all_with_bindings( - &impl_typeargs, constraint.args(), + &impl_typeargs, bindings.clone(), location, cache, diff --git a/src/types/typechecker.rs b/src/types/typechecker.rs index 67f51e5a..d9e06dba 100644 --- a/src/types/typechecker.rs +++ b/src/types/typechecker.rs @@ -66,7 +66,7 @@ type LevelBindings = Vec<(TypeVariableId, LetBindingLevel)>; /// Arbitrary limit of maximum recursive calls to functions like find_binding. /// Expected not to happen but leads to better errors than a stack overflow when it does. -const RECURSION_LIMIT: u32 = 15; +const RECURSION_LIMIT: u32 = 100; #[derive(Debug, Clone)] pub struct UnificationBindings { @@ -716,6 +716,9 @@ pub fn try_unify_with_bindings_inner<'b>( // ! <: & (Tag(TypeTag::Mutable), Tag(TypeTag::Immutable)) => Ok(()), + // owned <: shared + (Tag(TypeTag::Owned), Tag(TypeTag::Shared)) => Ok(()), + _ => Err(()), } } From 0d704f2bd3d5fdffe8ccecf64f407beefba5e1ea Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Sun, 11 Aug 2024 15:29:08 -0500 Subject: [PATCH 6/9] Change '&mut' to '!' --- examples/codegen/mutability.an | 2 +- examples/codegen/string_builder.an | 2 +- src/lexer/mod.rs | 2 ++ src/lexer/token.rs | 4 ++++ src/parser/ast.rs | 1 - src/parser/mod.rs | 24 ++++++++++++++++-------- stdlib/HashMap.an | 12 ++++++------ stdlib/StringBuilder.an | 4 ++-- stdlib/Vec.an | 18 +++++++++--------- 9 files changed, 41 insertions(+), 28 deletions(-) diff --git a/examples/codegen/mutability.an b/examples/codegen/mutability.an index a2119fd8..9e3a2848 100644 --- a/examples/codegen/mutability.an +++ b/examples/codegen/mutability.an @@ -6,7 +6,7 @@ print num mutate num print num -mutate (n: &mut I32) = +mutate (n: !I32) = x = double @n n := x diff --git a/examples/codegen/string_builder.an b/examples/codegen/string_builder.an index 28e56781..65e8107e 100644 --- a/examples/codegen/string_builder.an +++ b/examples/codegen/string_builder.an @@ -1,6 +1,6 @@ import StringBuilder -sb: &mut StringBuilder = mut empty () +sb: !StringBuilder = mut empty () reserve sb 10 append sb "你好," append sb " World!" diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 0ecbb8ce..8ede8c2b 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -610,6 +610,8 @@ impl<'cache, 'contents> Iterator for Lexer<'cache, 'contents> { ('\\', _) => self.advance_with(Token::Backslash), ('&', _) => self.advance_with(Token::Ampersand), ('@', _) => self.advance_with(Token::At), + ('!', _) => self.advance_with(Token::ExclamationMark), + ('?', _) => self.advance_with(Token::QuestionMark), (c, _) => self.advance_with(Token::Invalid(LexerError::UnknownChar(c))), } } diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 84cfd51a..32b7a39c 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -153,6 +153,8 @@ pub enum Token { Backslash, // \ Ampersand, // & At, // @ + ExclamationMark, // ! + QuestionMark, // ? } impl Token { @@ -334,6 +336,8 @@ impl Display for Token { Token::Backslash => write!(f, "'\\'"), Token::Ampersand => write!(f, "'&'"), Token::At => write!(f, "'@'"), + Token::ExclamationMark => write!(f, "'!'"), + Token::QuestionMark => write!(f, "'?'"), } } } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 72dae648..2aa8e402 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -223,7 +223,6 @@ pub enum Sharedness { #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Mutability { - #[allow(unused)] Polymorphic, Immutable, Mutable, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8b98530d..45a8b76b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -570,7 +570,7 @@ parser!(not_expr loc = ); parser!(ref_expr loc = - token <- expect(Token::Ampersand); + token <- or(&[expect(Token::Ampersand), expect(Token::ExclamationMark)], "expression"); expr !<- term; Ast::function_call(Ast::operator(token, loc), vec![expr], loc) ); @@ -645,6 +645,7 @@ fn function_argument<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> { match input[0].0 { Token::Not => not_expr(input), Token::Ampersand => ref_expr(input), + Token::ExclamationMark => ref_expr(input), Token::At => at_expr(input), _ => member_access(input), } @@ -653,6 +654,7 @@ fn function_argument<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> { fn pattern_function_argument<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> { match input[0].0 { Token::Ampersand => ref_expr(input), + Token::ExclamationMark => ref_expr(input), Token::At => at_expr(input), _ => pattern_argument(input), } @@ -861,21 +863,27 @@ parser!(unit_type loc -> 'b Type<'b> = ); parser!(reference_type loc -> 'b Type<'b> = - _ <- expect(Token::Ampersand); + mutability <- reference_operator; sharedness <- sharedness; - mutability <- maybe(expect(Token::Mut)); element <- maybe(reference_element_type); - { - let mutability = if mutability.is_some() { Mutability::Mutable } else { Mutability::Immutable }; - make_reference_type(Type::Reference(sharedness, mutability, loc), element, loc) + make_reference_type(Type::Reference(sharedness, mutability, loc), element, loc) +); + +parser!(reference_operator loc -> 'b Mutability = + token <- or(&[expect(Token::Ampersand), expect(Token::ExclamationMark)], "type"); + match token { + Token::Ampersand => Mutability::Immutable, + Token::ExclamationMark => Mutability::Mutable, + Token::QuestionMark => Mutability::Polymorphic, + _ => unreachable!(), } ); // The basic reference type `&t` can be used without parenthesis in a type application parser!(basic_reference_type loc -> 'b Type<'b> = - _ <- expect(Token::Ampersand); + mutability <- reference_operator; element <- maybe(basic_type); - make_reference_type(Type::Reference(Sharedness::Polymorphic, Mutability::Immutable, loc), element, loc) + make_reference_type(Type::Reference(Sharedness::Polymorphic, mutability, loc), element, loc) ); parser!(reference_element_type loc -> 'b Type<'b> = diff --git a/stdlib/HashMap.an b/stdlib/HashMap.an index 7f208db8..348d8639 100644 --- a/stdlib/HashMap.an +++ b/stdlib/HashMap.an @@ -15,14 +15,14 @@ trait Hash t with empty () = HashMap 0 0 (null ()) -clear (map: &mut HashMap k v) : Unit = +clear (map: !HashMap k v) : Unit = if map.capacity != 0 then repeat map.capacity fn i -> entry = mut deref_ptr <| offset map.entries i entry.&occupied := false entry.&tombstone := false -resize (map: &mut HashMap k v) (new_capacity: Usz) : Unit = +resize (map: !HashMap k v) (new_capacity: Usz) : Unit = if new_capacity > map.capacity then new_memory = calloc new_capacity (size_of (MkType : Type (Entry k v))) @@ -44,16 +44,16 @@ should_resize (map: HashMap k v) : Bool = scale_factor = 2 (map.len + 1) * scale_factor > map.capacity -insert (map: &mut HashMap k v) (key: k) (value: v) : Unit = +insert (map: !HashMap k v) (key: k) (value: v) : Unit = if should_resize @map then resize map ((map.capacity + 1) * 2) - iter_until (map: &mut HashMap k v) (key: k) (value: v) (start: Usz) (end: Usz) : Bool = + iter_until (map: !HashMap k v) (key: k) (value: v) (start: Usz) (end: Usz) : Bool = if start >= end then false else entry_ptr = offset (map.entries) start - entry = transmute entry_ptr : &mut Entry k v + entry = transmute entry_ptr : !Entry k v if entry.occupied then iter_until map key value (start + 1) end else @@ -105,7 +105,7 @@ get (map: HashMap k v) (key: k) : Maybe v = | None -> None -remove (map: &mut HashMap k v) (key: k) : Maybe v = +remove (map: !HashMap k v) (key: k) : Maybe v = match get_entry (@map) key | None -> None | Some e2 -> diff --git a/stdlib/StringBuilder.an b/stdlib/StringBuilder.an index d21fbfa1..9747bbe6 100644 --- a/stdlib/StringBuilder.an +++ b/stdlib/StringBuilder.an @@ -6,7 +6,7 @@ type StringBuilder = empty () = StringBuilder (null ()) 0 0 // reserve space for at least n additional characters -reserve (s: &mut StringBuilder) (n: Usz) : Unit = +reserve (s: !StringBuilder) (n: Usz) : Unit = if s.length + n > s.cap then new_size = s.cap + n ptr = realloc s.data new_size @@ -19,7 +19,7 @@ reserve (s: &mut StringBuilder) (n: Usz) : Unit = s.&cap := new_size // append a string -append (s: &mut StringBuilder) (new_str: String) : Unit = +append (s: !StringBuilder) (new_str: String) : Unit = reserve s new_str.length memcpy (cast (cast s.data + s.length)) new_str.c_string (cast (new_str.length+1)) s.&length := s.length + new_str.length diff --git a/stdlib/Vec.an b/stdlib/Vec.an index 583b77d9..5e77e913 100644 --- a/stdlib/Vec.an +++ b/stdlib/Vec.an @@ -18,12 +18,12 @@ len v = v.len capacity v = v.cap //Fill Vec with items from the iterable -fill (v: &mut Vec t) iterable : &mut Vec t = +fill (v: !Vec t) iterable : !Vec t = iter iterable (push v _) v //reserve numElements in Vec v, elements will be uninitialized -reserve (v: &mut Vec t) (numElems: Usz) : Unit = +reserve (v: !Vec t) (numElems: Usz) : Unit = if v.len + numElems > v.cap then size = (v.cap + numElems) * size_of (MkType: Type t) ptr = realloc (v.data) size @@ -37,7 +37,7 @@ reserve (v: &mut Vec t) (numElems: Usz) : Unit = //push an element onto the end of the vector. //resizes if necessary -push (v: &mut Vec t) (elem: t) : Unit = +push (v: !Vec t) (elem: t) : Unit = if v.len >= v.cap then reserve v (if v.cap == 0usz then 1 else v.cap) @@ -46,7 +46,7 @@ push (v: &mut Vec t) (elem: t) : Unit = //pop the last element off if it exists //this will never resize the vector. -pop (v: &mut Vec t) : Maybe t = +pop (v: !Vec t) : Maybe t = if is_empty v then None else v.&len := v.len - 1 @@ -54,7 +54,7 @@ pop (v: &mut Vec t) : Maybe t = //remove the element at the given index and return it. //will error if the index is out of bounds. -remove_index (v: &mut Vec t) (idx:Usz) : t = +remove_index (v: !Vec t) (idx:Usz) : t = if idx == v.len - 1 then v.&len := v.len - 1 else if idx >= 0 and idx < v.len - 1 then @@ -71,7 +71,7 @@ remove_index (v: &mut Vec t) (idx:Usz) : t = //the vector or none if the element was not found. //Uses == to determine element equality. //returns the index where the element was found. -remove_first (v: &mut Vec t) (elem: t) : Maybe Usz = +remove_first (v: !Vec t) (elem: t) : Maybe Usz = loop (i = 0) -> if i >= v.len then None @@ -83,7 +83,7 @@ remove_first (v: &mut Vec t) (elem: t) : Maybe Usz = //Remove the given indices from the vector //Expects the indices to be in sorted order. //Will error if any index is out of bounds. -remove_indices (v: &mut Vec t) (idxs: Vec Usz) : Unit = +remove_indices (v: !Vec t) (idxs: Vec Usz) : Unit = moved = mut 0 iter (indices idxs) fn i -> cur = idxs.data#i @@ -105,7 +105,7 @@ remove_indices (v: &mut Vec t) (idxs: Vec Usz) : Unit = //remove all matching elements from the vector and //return the number of elements removed. //Uses = to determine element equality. -remove_all (v: &mut Vec t) (elem: t) : Usz = +remove_all (v: !Vec t) (elem: t) : Usz = idxs = mut empty () iter (indices @v) fn i -> if elem == v.data#i then @@ -118,7 +118,7 @@ remove_all (v: &mut Vec t) (elem: t) : Usz = //Remove an element by swapping it with the last element in O(1) time. //Returns true if a swap was performed or false otherwise. //Will not swap if the given index is the index of the last element. -swap_last (v: &mut Vec t) (idx:Usz) : Bool = +swap_last (v: !Vec t) (idx:Usz) : Bool = if idx >= v.len or idx + 1 == v.len then false else v.&len := v.len - 1 From 91f0d34853abfe55fe82ba6ac156d767ebb5595e Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Sun, 11 Aug 2024 16:13:53 -0500 Subject: [PATCH 7/9] Add .! for mutable field offsets --- examples/codegen/pass_by_ref.an | 2 +- src/error/mod.rs | 50 ++++++++++++++++----------------- src/hir/monomorphisation.rs | 8 +++--- src/lexer/mod.rs | 3 +- src/lexer/token.rs | 6 ++-- src/nameresolution/mod.rs | 4 +-- src/parser/ast.rs | 10 ++++--- src/parser/mod.rs | 8 ++++-- src/types/typechecker.rs | 42 ++++++++++++++------------- src/types/typeprinter.rs | 10 +++---- stdlib/HashMap.an | 16 +++++------ stdlib/StringBuilder.an | 6 ++-- stdlib/Vec.an | 16 +++++------ stdlib/prelude.an | 4 +-- 14 files changed, 99 insertions(+), 86 deletions(-) diff --git a/examples/codegen/pass_by_ref.an b/examples/codegen/pass_by_ref.an index ca0dec16..8d8dac2f 100644 --- a/examples/codegen/pass_by_ref.an +++ b/examples/codegen/pass_by_ref.an @@ -12,7 +12,7 @@ foo (x: S) (y: S) = printne "x before: " print x z = mut y - z.&message := "modifying y" + z.!message := "modifying y" printne "x after: " print x diff --git a/src/error/mod.rs b/src/error/mod.rs index c029a090..c700cb29 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -152,7 +152,7 @@ impl Display for DiagnosticKind { DiagnosticKind::PreviouslyDefinedHere(item) => { write!(f, "{} was previously defined here", item) }, - DiagnosticKind::IncorrectConstructorArgCount(item, expected, actual) => { + DiagnosticKind::IncorrectConstructorArgCount(item, actual, expected) => { let plural_s = if *expected == 1 { "" } else { "s" }; let is_are = if *actual == 1 { "is" } else { "are" }; write!( @@ -161,7 +161,7 @@ impl Display for DiagnosticKind { item, expected, plural_s, actual, is_are ) }, - DiagnosticKind::IncorrectImplTraitArgCount(trait_name, expected, actual) => { + DiagnosticKind::IncorrectImplTraitArgCount(trait_name, actual, expected) => { let plural_s = if *expected == 1 { "" } else { "s" }; write!(f, "impl has {} type argument{} but {} requires {}", expected, plural_s, trait_name, actual) }, @@ -240,78 +240,78 @@ impl Display for DiagnosticKind { "Invalid syntax in irrefutable pattern, expected a name, type annotation, or type constructor" ) }, - DiagnosticKind::FunctionParameterCountMismatch(typ, expected, actual) => { + DiagnosticKind::FunctionParameterCountMismatch(typ, actual, expected) => { let plural_s = if *expected == 1 { "" } else { "s" }; let was_were = if *actual == 1 { "was" } else { "were" }; write!(f, "Function of type {typ} declared to take {expected} parameter{plural_s}, but {actual} {was_were} supplied") }, - DiagnosticKind::TypeError(TypeErrorKind::ExpectedUnitTypeFromPattern, _expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::ExpectedUnitTypeFromPattern, actual, _expected) => { write!(f, "Expected a unit type from this pattern, but the corresponding value has the type {}", actual) }, - DiagnosticKind::TypeError(TypeErrorKind::ExpectedPairTypeFromPattern, _, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::ExpectedPairTypeFromPattern, actual, _expected) => { write!(f, "Expected a pair type from this pattern, but found {actual}") }, - DiagnosticKind::TypeError(TypeErrorKind::VariableDoesNotMatchDeclaredType, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::VariableDoesNotMatchDeclaredType, actual, expected) => { write!(f, "Variable type {actual} does not match its declared type of {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::PatternTypeDoesNotMatchAnnotatedType, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::PatternTypeDoesNotMatchAnnotatedType, actual, expected) => { write!(f, "Pattern type {actual} does not match the annotated type {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::PatternTypeDoesNotMatchDefinitionType, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::PatternTypeDoesNotMatchDefinitionType, actual, expected) => { write!(f, "Pattern type {actual} does not match the definition's type {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::FunctionBodyDoesNotMatchReturnType, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::FunctionBodyDoesNotMatchReturnType, actual, expected) => { write!(f, "Function body type {actual} does not match declared return type of {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::CalledValueIsNotAFunction, _, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::CalledValueIsNotAFunction, actual, _expected) => { write!(f, "Value being called is not a function, it is a {actual}") }, - DiagnosticKind::TypeError(TypeErrorKind::ArgumentTypeMismatch, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::ArgumentTypeMismatch, actual, expected) => { write!(f, "Expected argument of type {expected}, but found {actual}") }, - DiagnosticKind::TypeError(TypeErrorKind::NonBoolInCondition, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::NonBoolInCondition, actual, expected) => { write!(f, "{actual} should be a {expected} to be used in an if condition") }, - DiagnosticKind::TypeError(TypeErrorKind::IfBranchMismatch, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::IfBranchMismatch, actual, expected) => { write!( f, "Expected 'then' and 'else' branch types to match, but found {expected} and {actual} respectively" ) }, - DiagnosticKind::TypeError(TypeErrorKind::MatchPatternTypeDiffers, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::MatchPatternTypeDiffers, actual, expected) => { write!(f, "This pattern of type {actual} does not match the type {expected} that is being matched on") }, - DiagnosticKind::TypeError(TypeErrorKind::MatchReturnTypeDiffers, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::MatchReturnTypeDiffers, actual, expected) => { write!( f, "This branch's return type {actual} does not match the previous branches which return {expected}" ) }, - DiagnosticKind::TypeError(TypeErrorKind::DoesNotMatchAnnotatedType, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::DoesNotMatchAnnotatedType, actual, expected) => { write!(f, "Expression of type {actual} does not match its annotated type {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::ExpectedStructReference, _, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::ExpectedStructReference, actual, _expected) => { write!(f, "Expected a struct reference but found {actual} instead") }, - DiagnosticKind::TypeError(TypeErrorKind::NoFieldOfType(field_name), expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::NoFieldOfType(field_name), actual, expected) => { write!(f, "{actual} has no field '{field_name}' of type {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::AssignToNonMutRef, expected, actual) => { - write!(f, "Expression of type {actual} must be a `{expected}` to be assigned to") + DiagnosticKind::TypeError(TypeErrorKind::AssignToNonMutRef, actual, expected) => { + write!(f, "Expression of type {actual} must be a mutable reference type ({expected}) to be assigned to") }, - DiagnosticKind::TypeError(TypeErrorKind::AssignToWrongType, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::AssignToWrongType, actual, expected) => { write!(f, "Cannot assign expression of type {actual} to a Ref of type {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::HandleBranchMismatch, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::HandleBranchMismatch, actual, expected) => { write!(f, "The type of this branch ({actual}) should match the type of the expression being handled: {expected}") }, - DiagnosticKind::TypeError(TypeErrorKind::PatternReturnTypeMismatch, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::PatternReturnTypeMismatch, actual, expected) => { write!(f, "Expected type {expected} does not match the pattern's return type {actual}") }, - DiagnosticKind::TypeError(TypeErrorKind::NeverShown, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::NeverShown, actual, expected) => { unreachable!("This type error should never be shown. Expected {}, Actual {}", expected, actual) }, - DiagnosticKind::TypeError(TypeErrorKind::MonomorphizationError, expected, actual) => { + DiagnosticKind::TypeError(TypeErrorKind::MonomorphizationError, actual, expected) => { unreachable!( "Unification error during monomorphisation: Could not unify definition {} with instantiation {}", expected, actual diff --git a/src/hir/monomorphisation.rs b/src/hir/monomorphisation.rs index 932326f9..d4fb708f 100644 --- a/src/hir/monomorphisation.rs +++ b/src/hir/monomorphisation.rs @@ -1572,17 +1572,17 @@ impl<'c> Context<'c> { let result_type = self.convert_type(member_access.typ.as_ref().unwrap()); // If our collection type is a ref we do a ptr offset instead of a direct access - match (ref_type, member_access.is_offset) { - (Some(elem_type), true) => { + match (ref_type, member_access.offset) { + (Some(elem_type), Some(_)) => { let offset = Self::get_field_offset(&elem_type, index); offset_ptr(lhs, offset as u64) }, - (Some(elem_type), false) => { + (Some(elem_type), None) => { let lhs = hir::Ast::Builtin(hir::Builtin::Deref(Box::new(lhs), elem_type)); Self::extract(lhs, index, result_type) }, _ => { - assert!(!member_access.is_offset); + assert!(member_access.offset.is_none()); Self::extract(lhs, index, result_type) }, } diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 8ede8c2b..7f48ebbe 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -567,7 +567,8 @@ impl<'cache, 'contents> Iterator for Lexer<'cache, 'contents> { self.previous_token_expects_indent = true; self.advance2_with(Token::RightArrow) }, - ('.', '&') => self.advance2_with(Token::MemberReference), + ('.', '&') => self.advance2_with(Token::MemberRef), + ('.', '!') => self.advance2_with(Token::MemberMutRef), ('.', _) => self.advance_with(Token::MemberAccess), ('-', _) => self.lex_negative(), ('!', '=') => self.advance2_with(Token::NotEqual), diff --git a/src/lexer/token.rs b/src/lexer/token.rs index 32b7a39c..730f24d3 100644 --- a/src/lexer/token.rs +++ b/src/lexer/token.rs @@ -144,7 +144,8 @@ pub enum Token { Semicolon, // ; Comma, // , MemberAccess, // . - MemberReference, // .& + MemberRef, // .& + MemberMutRef, // .! LessThan, // < GreaterThan, // > LessThanOrEqual, // <= @@ -327,7 +328,8 @@ impl Display for Token { Token::Semicolon => write!(f, "';'"), Token::Comma => write!(f, "','"), Token::MemberAccess => write!(f, "'.'"), - Token::MemberReference => write!(f, "'.&'"), + Token::MemberRef => write!(f, "'.&'"), + Token::MemberMutRef => write!(f, "'.!'"), Token::LessThan => write!(f, "'<'"), Token::GreaterThan => write!(f, "'>'"), Token::LessThanOrEqual => write!(f, "'<='"), diff --git a/src/nameresolution/mod.rs b/src/nameresolution/mod.rs index ea9466d1..aa8fc924 100644 --- a/src/nameresolution/mod.rs +++ b/src/nameresolution/mod.rs @@ -555,7 +555,7 @@ impl<'c> NameResolver { let expected = self.get_expected_type_argument_count(constructor, cache); if args.len() != expected && !matches!(constructor, Type::TypeVariable(_)) { let typename = constructor.display(cache).to_string(); - cache.push_diagnostic(location, D::IncorrectConstructorArgCount(typename, expected, args.len())); + cache.push_diagnostic(location, D::IncorrectConstructorArgCount(typename, args.len(), expected)); } // Check argument is an integer/float type (issue #146) @@ -1413,7 +1413,7 @@ impl<'c> Resolvable<'c> for ast::TraitImpl<'c> { let required_arg_count = trait_info.typeargs.len() + trait_info.fundeps.len(); if self.trait_args.len() != required_arg_count { let trait_name = self.trait_name.clone(); - let error = D::IncorrectImplTraitArgCount(trait_name, self.trait_args.len(), required_arg_count); + let error = D::IncorrectImplTraitArgCount(trait_name, required_arg_count, self.trait_args.len()); cache.push_diagnostic(self.location, error); } diff --git a/src/parser/ast.rs b/src/parser/ast.rs index 2aa8e402..10c229d0 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -376,8 +376,10 @@ pub struct MemberAccess<'a> { pub lhs: Box>, pub field: String, pub location: Location<'a>, - /// True if this is an offset .& operation - pub is_offset: bool, + + /// If this member access is an offset rather + /// than a move/copy, this will contain the mutability of the offset. + pub offset: Option, pub typ: Option, } @@ -716,8 +718,8 @@ impl<'a> Ast<'a> { Ast::Extern(Extern { declarations, location, level: None, typ: None }) } - pub fn member_access(lhs: Ast<'a>, field: String, is_offset: bool, location: Location<'a>) -> Ast<'a> { - Ast::MemberAccess(MemberAccess { lhs: Box::new(lhs), field, is_offset, location, typ: None }) + pub fn member_access(lhs: Ast<'a>, field: String, offset: Option, location: Location<'a>) -> Ast<'a> { + Ast::MemberAccess(MemberAccess { lhs: Box::new(lhs), field, offset, location, typ: None }) } pub fn assignment(lhs: Ast<'a>, rhs: Ast<'a>, location: Location<'a>) -> Ast<'a> { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 45a8b76b..0705dd0c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -664,8 +664,12 @@ fn pattern_function_argument<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> fn member_access<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> { let (mut input, mut arg, mut location) = argument(input)?; - while input[0].0 == Token::MemberAccess || input[0].0 == Token::MemberReference { - let is_reference = input[0].0 == Token::MemberReference; + while input[0].0 == Token::MemberAccess || input[0].0 == Token::MemberRef || input[0].0 == Token::MemberMutRef { + let is_reference = match input[0].0 { + Token::MemberMutRef => Some(Mutability::Mutable), + Token::MemberRef => Some(Mutability::Immutable), + _ => None, + }; input = &input[1..]; let (new_input, field, field_location) = no_backtracking(identifier)(input)?; diff --git a/src/types/typechecker.rs b/src/types/typechecker.rs index d9e06dba..a2539ef4 100644 --- a/src/types/typechecker.rs +++ b/src/types/typechecker.rs @@ -30,7 +30,7 @@ use crate::cache::{DefinitionInfoId, DefinitionKind, EffectInfoId, ModuleCache, use crate::cache::{ImplScopeId, VariableId}; use crate::error::location::{Locatable, Location}; use crate::error::{Diagnostic, DiagnosticKind as D, TypeErrorKind, TypeErrorKind as TE}; -use crate::parser::ast::{self, ClosureEnvironment}; +use crate::parser::ast::{self, ClosureEnvironment, Mutability}; use crate::types::traits::{RequiredTrait, TraitConstraint, TraitConstraints}; use crate::types::typed::Typed; use crate::types::EffectSet; @@ -152,9 +152,13 @@ pub fn type_application_bindings(info: &TypeInfo<'_>, typeargs: &[Type], cache: } /// Given `a` returns `ref a` -fn ref_of(typ: Type, cache: &mut ModuleCache) -> Type { +fn ref_of(mutability: Mutability, typ: Type, cache: &mut ModuleCache) -> Type { let sharedness = Box::new(next_type_variable(cache)); - let mutability = Box::new(next_type_variable(cache)); + let mutability = match mutability { + Mutability::Polymorphic => Box::new(next_type_variable(cache)), + Mutability::Immutable => Box::new(Type::Tag(TypeTag::Immutable)), + Mutability::Mutable => Box::new(Type::Tag(TypeTag::Mutable)), + }; let lifetime = Box::new(next_type_variable(cache)); let constructor = Box::new(Type::Ref { sharedness, mutability, lifetime }); TypeApplication(constructor, vec![typ]) @@ -895,14 +899,14 @@ fn try_unify_type_variable_with_bindings<'c>( } pub fn try_unify_with_bindings<'b>( - t1: &Type, t2: &Type, bindings: &mut UnificationBindings, location: Location<'b>, cache: &mut ModuleCache<'b>, + actual: &Type, expected: &Type, bindings: &mut UnificationBindings, location: Location<'b>, cache: &mut ModuleCache<'b>, error: TypeErrorKind, ) -> Result<(), Diagnostic<'b>> { - match try_unify_with_bindings_inner(t1, t2, bindings, location, cache) { + match try_unify_with_bindings_inner(actual, expected, bindings, location, cache) { Ok(()) => Ok(()), Err(()) => { - let t1 = t1.display(cache).to_string(); - let t2 = t2.display(cache).to_string(); + let t1 = actual.display(cache).to_string(); + let t2 = expected.display(cache).to_string(); Err(Diagnostic::new(location, D::TypeError(error, t1, t2))) }, } @@ -1667,12 +1671,12 @@ fn issue_argument_types_error<'c>( let typ = Function(expected.clone()).display(cache).to_string(); cache.push_diagnostic( call.location, - D::FunctionParameterCountMismatch(typ, expected.parameters.len(), actual.parameters.len()), + D::FunctionParameterCountMismatch(typ, actual.parameters.len(), expected.parameters.len()), ); } for ((arg, param), arg_ast) in actual.parameters.into_iter().zip(expected.parameters).zip(&call.args) { - unify(¶m, &arg, arg_ast.locate(), cache, TE::ArgumentTypeMismatch); + unify(&arg, ¶m, arg_ast.locate(), cache, TE::ArgumentTypeMismatch); } }, None => cache.push_full_diagnostic(original_error), @@ -1782,7 +1786,7 @@ impl<'a> Inferable<'a> for ast::Match<'a> { let mut pattern = infer(&mut self.branches[0].0, cache); result.combine(&mut pattern, cache); - unify(&result.typ, &pattern.typ, self.branches[0].0.locate(), cache, TE::MatchPatternTypeDiffers); + unify(&pattern.typ, &result.typ, self.branches[0].0.locate(), cache, TE::MatchPatternTypeDiffers); let mut branch = infer(&mut self.branches[0].1, cache); result.combine(&mut branch, cache); @@ -1792,8 +1796,8 @@ impl<'a> Inferable<'a> for ast::Match<'a> { let mut pattern_result = infer(pattern, cache); let mut branch_result = infer(branch, cache); - unify(&result.typ, &pattern_result.typ, pattern.locate(), cache, TE::MatchPatternTypeDiffers); - unify(&return_type, &branch_result.typ, branch.locate(), cache, TE::MatchReturnTypeDiffers); + unify(&pattern_result.typ, &result.typ, pattern.locate(), cache, TE::MatchPatternTypeDiffers); + unify(&branch_result.typ, &return_type, branch.locate(), cache, TE::MatchReturnTypeDiffers); result.combine(&mut pattern_result, cache); result.combine(&mut branch_result, cache); @@ -1952,10 +1956,10 @@ impl<'a> Inferable<'a> for ast::MemberAccess<'a> { let level = LetBindingLevel(CURRENT_LEVEL.load(Ordering::SeqCst)); let mut field_type = cache.next_type_variable(level); - if self.is_offset { + if let Some(mutability) = self.offset { let collection_variable = next_type_variable(cache); - let expected = ref_of(collection_variable.clone(), cache); - unify(&expected, &result.typ, self.lhs.locate(), cache, TE::ExpectedStructReference); + let expected = ref_of(mutability, collection_variable.clone(), cache); + unify(&result.typ, &expected, self.lhs.locate(), cache, TE::ExpectedStructReference); result.typ = collection_variable; } @@ -1966,10 +1970,10 @@ impl<'a> Inferable<'a> for ast::MemberAccess<'a> { let rho = cache.next_type_variable_id(level); let struct_type = Type::Struct(fields, rho); - unify(&struct_type, &result.typ, self.location, cache, TE::NoFieldOfType(self.field.clone())); + unify(&result.typ, &struct_type, self.location, cache, TE::NoFieldOfType(self.field.clone())); - if self.is_offset { - field_type = ref_of(field_type, cache); + if let Some(mutability) = self.offset { + field_type = ref_of(mutability, field_type, cache); } result.with_type(field_type) @@ -2009,7 +2013,7 @@ fn issue_assignment_error<'c>( let mutref = mut_polymorphically_shared_ref(cache); let mutref = Type::TypeApplication(Box::new(mutref), vec![var]); - if let Err(msg) = try_unify(&mutref, lhs, lhs_loc, cache, TE::AssignToNonMutRef) { + if let Err(msg) = try_unify(lhs, &mutref, lhs_loc, cache, TE::AssignToNonMutRef) { cache.push_full_diagnostic(msg); } else { let inner_type = match follow_bindings_in_cache(lhs, cache) { diff --git a/src/types/typeprinter.rs b/src/types/typeprinter.rs index 4f785790..d3fe87df 100644 --- a/src/types/typeprinter.rs +++ b/src/types/typeprinter.rs @@ -286,16 +286,16 @@ impl<'a, 'b> TypePrinter<'a, 'b> { let parenthesize = matches!(shared, Type::Tag(_)) || self.debug; if parenthesize { - write!(f, "(")?; + write!(f, "{}", "(".blue())?; } match mutable { - Type::Tag(tag) => write!(f, "{tag}")?, - _ => write!(f, "?")?, + Type::Tag(tag) => write!(f, "{}", tag.to_string().blue())?, + _ => write!(f, "{}", "&".blue())?, } if let Type::Tag(tag) = shared { - write!(f, "{tag}")?; + write!(f, "{}", tag.to_string().blue())?; } if self.debug { @@ -304,7 +304,7 @@ impl<'a, 'b> TypePrinter<'a, 'b> { } if parenthesize { - write!(f, ")")?; + write!(f, "{}", ")".blue())?; } Ok(()) } diff --git a/stdlib/HashMap.an b/stdlib/HashMap.an index 348d8639..a83953f7 100644 --- a/stdlib/HashMap.an +++ b/stdlib/HashMap.an @@ -19,8 +19,8 @@ clear (map: !HashMap k v) : Unit = if map.capacity != 0 then repeat map.capacity fn i -> entry = mut deref_ptr <| offset map.entries i - entry.&occupied := false - entry.&tombstone := false + entry.!occupied := false + entry.!tombstone := false resize (map: !HashMap k v) (new_capacity: Usz) : Unit = if new_capacity > map.capacity then @@ -36,8 +36,8 @@ resize (map: !HashMap k v) (new_capacity: Usz) : Unit = if entry.occupied then insert new_map entry.key entry.value - map.&capacity := new_capacity - map.&entries := new_memory + map.!capacity := new_capacity + map.!entries := new_memory // Should we resize this map when pushing another element? should_resize (map: HashMap k v) : Bool = @@ -66,7 +66,7 @@ insert (map: !HashMap k v) (key: k) (value: v) : Unit = if not iter_until map key value 0 h then panic "Failed to insert entry into map" - map.&len := map.len + 1usz + map.!len := map.len + 1usz // TODO: Name resolution issue preventing this from being visible to iter_until // if it is defined within get_entry @@ -110,9 +110,9 @@ remove (map: !HashMap k v) (key: k) : Maybe v = | None -> None | Some e2 -> entry = ptr_to_ref e2 - entry.&occupied := false - entry.&tombstone := true - map.&len := map.len - 1 + entry.!occupied := false + entry.!tombstone := true + map.!len := map.len - 1 Some entry.value impl Print (HashMap k v) given Print k, Print v with diff --git a/stdlib/StringBuilder.an b/stdlib/StringBuilder.an index 9747bbe6..5a133ed3 100644 --- a/stdlib/StringBuilder.an +++ b/stdlib/StringBuilder.an @@ -15,14 +15,14 @@ reserve (s: !StringBuilder) (n: Usz) : Unit = print "Error in reserving more elements for Vec" return () - s.&data := ptr - s.&cap := new_size + s.!data := ptr + s.!cap := new_size // append a string append (s: !StringBuilder) (new_str: String) : Unit = reserve s new_str.length memcpy (cast (cast s.data + s.length)) new_str.c_string (cast (new_str.length+1)) - s.&length := s.length + new_str.length + s.!length := s.length + new_str.length // convert to string to_string (s: StringBuilder) : String = diff --git a/stdlib/Vec.an b/stdlib/Vec.an index 5e77e913..0d335db1 100644 --- a/stdlib/Vec.an +++ b/stdlib/Vec.an @@ -32,8 +32,8 @@ reserve (v: !Vec t) (numElems: Usz) : Unit = print "Error in reserving more elements for Vec" return () - v.&data := ptr - v.&cap := v.cap + numElems + v.!data := ptr + v.!cap := v.cap + numElems //push an element onto the end of the vector. //resizes if necessary @@ -42,26 +42,26 @@ push (v: !Vec t) (elem: t) : Unit = reserve v (if v.cap == 0usz then 1 else v.cap) array_insert v.data v.len elem - v.&len := v.len + 1usz + v.!len := v.len + 1usz //pop the last element off if it exists //this will never resize the vector. pop (v: !Vec t) : Maybe t = if is_empty v then None else - v.&len := v.len - 1 + v.!len := v.len - 1 Some (v.data#v.len) //remove the element at the given index and return it. //will error if the index is out of bounds. remove_index (v: !Vec t) (idx:Usz) : t = if idx == v.len - 1 then - v.&len := v.len - 1 + v.!len := v.len - 1 else if idx >= 0 and idx < v.len - 1 then iter_range idx (v.len - 1) fn i -> array_insert v.data i (v.data#(i+1)) - v.&len := v.len - 1 + v.!len := v.len - 1 else panic "Vec.remove_index: index ${idx} out of bounds for Vec of length ${v.len}" @@ -100,7 +100,7 @@ remove_indices (v: !Vec t) (idxs: Vec Usz) : Unit = iter_range (cur + 1) v.len fn j -> array_insert v.data (j - @moved) (v.data#j) - v.&len := v.len - @moved + v.!len := v.len - @moved //remove all matching elements from the vector and //return the number of elements removed. @@ -121,7 +121,7 @@ remove_all (v: !Vec t) (elem: t) : Usz = swap_last (v: !Vec t) (idx:Usz) : Bool = if idx >= v.len or idx + 1 == v.len then false else - v.&len := v.len - 1 + v.!len := v.len - 1 array_insert v.data idx (v.data#v.len) true diff --git a/stdlib/prelude.an b/stdlib/prelude.an index a65d4ead..c42680d0 100644 --- a/stdlib/prelude.an +++ b/stdlib/prelude.an @@ -328,13 +328,13 @@ deref_ptr (p: Ptr t) : t = deref <| transmute p ptr_store (p: Ptr a) (value: a) : Unit = - addr: &a = transmute p + addr: !a = transmute p addr := value array_insert (p: Ptr a) (index: Usz) (value: a) : Unit = offset p index |> ptr_store value -ptr_to_ref: Ptr a -> &a = transmute +ptr_to_ref: Ptr a -> !a = transmute (@) = deref From b2aae73c69b4cd536876089fdd67db32f33be3f9 Mon Sep 17 00:00:00 2001 From: eldesh Date: Tue, 12 Nov 2024 01:01:47 +0900 Subject: [PATCH 8/9] Remove unneeded parenthesis (#203) * Remove parenthesis from type application e.g. `(Int a)` to `Int a` * Remove unneeded parentheses from fun, app and forall type expr * Add priority of pair type * Passing cache to resolve TypeVariableId * Fix: avoid parenthesis for primitive int and float * Fix unit tests * Fix: polymorphic and concrete numeral types are now distinguished before: > foo : (Foo String Int a) > add_one : forall a. (Maybe I32 -> Maybe I32 can a) after: > foo : (Foo String (Int a)) > add_one : forall a. (Maybe I32 -> Maybe I32 can a) -- No change for concrete types refs: https://github.com/jfecher/ante/pull/203#discussion_r1835741542 * Fix the test named_constructor.an * Update src/types/mod.rs --------- Co-authored-by: jfecher --- .../129_int_defaulting_generalization.an | 6 +- examples/typechecking/bind.an | 8 +- .../typechecking/completeness_checking.an | 2 +- examples/typechecking/effects.an | 12 +-- examples/typechecking/extern.an | 8 +- examples/typechecking/functor_and_monad.an | 6 +- examples/typechecking/generalization.an | 2 +- examples/typechecking/impl.an | 2 +- examples/typechecking/instantiation.an | 10 +- examples/typechecking/member_access.an | 10 +- examples/typechecking/mutual_recursion.an | 8 +- examples/typechecking/named_constructor.an | 6 +- examples/typechecking/repeated_traits.an | 2 +- examples/typechecking/trait_fundep_result.an | 2 +- examples/typechecking/trait_generalization.an | 2 +- examples/typechecking/trait_propagation.an | 6 +- examples/typechecking/type_annotations.an | 10 +- src/types/mod.rs | 67 ++++++++++++- src/types/typeprinter.rs | 96 ++++++++++++------- 19 files changed, 176 insertions(+), 89 deletions(-) diff --git a/examples/regressions/129_int_defaulting_generalization.an b/examples/regressions/129_int_defaulting_generalization.an index ced90501..a973b740 100644 --- a/examples/regressions/129_int_defaulting_generalization.an +++ b/examples/regressions/129_int_defaulting_generalization.an @@ -12,9 +12,9 @@ second b + 2i64 // args: --check --show-types // expected stdout: -// a : (U64, I64) -// b : (U64, I64) -// c : (U64, I64) +// a : U64, I64 +// b : U64, I64 +// c : U64, I64 // Expected results with the function block uncommented: // f : (forall a. (Unit -> (U64, I64) can a)) diff --git a/examples/typechecking/bind.an b/examples/typechecking/bind.an index 0a480b86..0c654a33 100644 --- a/examples/typechecking/bind.an +++ b/examples/typechecking/bind.an @@ -12,7 +12,7 @@ add_one x = // args: --check --show-types // expected stdout: -// add_one : (forall a. ((Maybe I32) -> (Maybe I32) can a)) -// bind : (forall a b c d. ((Maybe c) - (c => (Maybe b) can d) -> (Maybe b) can d)) -// ret : (forall a b. (a -> (Maybe a) can b)) -// x : (Maybe I32) +// add_one : forall a. (Maybe I32 -> Maybe I32 can a) +// bind : forall a b c d. (Maybe c - (c => Maybe b can d) -> Maybe b can d) +// ret : forall a b. (a -> Maybe a can b) +// x : Maybe I32 diff --git a/examples/typechecking/completeness_checking.an b/examples/typechecking/completeness_checking.an index 9665c08e..283560da 100644 --- a/examples/typechecking/completeness_checking.an +++ b/examples/typechecking/completeness_checking.an @@ -46,5 +46,5 @@ match (1, 2, 3, 4) // completeness_checking.an:20:1 error: Missing case (false, false) // match (true, true) // -// completeness_checking.an:25:4 error: This pattern of type ((Int a), (Int b)) does not match the type ((Int a), ((Int b), ((Int c), (Int d)))) that is being matched on +// completeness_checking.an:25:4 error: This pattern of type Int a, Int b does not match the type Int a, Int b, Int c, Int d that is being matched on // | (1, 2) -> 1 diff --git a/examples/typechecking/effects.an b/examples/typechecking/effects.an index 1678ebd0..1c18210d 100644 --- a/examples/typechecking/effects.an +++ b/examples/typechecking/effects.an @@ -23,10 +23,10 @@ does_use x = // args: --check --show-types // expected stdout: -// does_use : (forall a b. (a -> Unit can (Use a, b))) +// does_use : forall a b. (a -> Unit can (Use a, b)) // given Add a -// get : (forall a b. (Unit -> a can (Use a, b))) -// handle_basic : (forall a. (Unit -> Unit can (Use String, a))) -// log : (forall a. (String -> Unit can (Log, a))) -// set : (forall a b. (a -> Unit can (Use a, b))) -// use_resume : (forall a. (Unit -> Unit can a)) +// get : forall a b. (Unit -> a can (Use a, b)) +// handle_basic : forall a. (Unit -> Unit can (Use String, a)) +// log : forall a. (String -> Unit can (Log, a)) +// set : forall a b. (a -> Unit can (Use a, b)) +// use_resume : forall a. (Unit -> Unit can a) diff --git a/examples/typechecking/extern.an b/examples/typechecking/extern.an index bed9dae8..a500e4ec 100644 --- a/examples/typechecking/extern.an +++ b/examples/typechecking/extern.an @@ -12,7 +12,7 @@ exit2 0 // args: --check --show-types // expected stdout: -// add : (forall a. (I32 - I32 -> I32 can a)) -// exit2 : (forall a b. (I32 -> a can b)) -// foo : (forall a b c. (a -> b can c)) -// puts2 : (forall a. (String -> Unit can a)) +// add : forall a. (I32 - I32 -> I32 can a) +// exit2 : forall a b. (I32 -> a can b) +// foo : forall a b c. (a -> b can c) +// puts2 : forall a. (String -> Unit can a) diff --git a/examples/typechecking/functor_and_monad.an b/examples/typechecking/functor_and_monad.an index b2be22d4..777ef908 100644 --- a/examples/typechecking/functor_and_monad.an +++ b/examples/typechecking/functor_and_monad.an @@ -23,9 +23,9 @@ impl Monad Maybe with // args: --check --show-types // expected stdout: -// bind : (forall a b c d e. ((a b) - (b -> (a c) can d) -> (a c) can e)) +// bind : forall a b c d e. (a b - (b -> a c can d) -> a c can e) // given Monad a -// map : (forall a b c d e. ((a b) - (b -> c can d) -> (a c) can e)) +// map : forall a b c d e. (a b - (b -> c can d) -> a c can e) // given Functor a -// wrap : (forall a b c. (b -> (a b) can c)) +// wrap : forall a b c. (b -> a b can c) // given Monad a diff --git a/examples/typechecking/generalization.an b/examples/typechecking/generalization.an index 64c8864d..2eeba95b 100644 --- a/examples/typechecking/generalization.an +++ b/examples/typechecking/generalization.an @@ -6,4 +6,4 @@ foo x = // args: --check --show-types // expected stdout: -// foo : (forall a b c d. (a -> (b => a can c) can d)) +// foo : forall a b c d. (a -> b => a can c can d) diff --git a/examples/typechecking/impl.an b/examples/typechecking/impl.an index 184b7e3f..b4f72c0b 100644 --- a/examples/typechecking/impl.an +++ b/examples/typechecking/impl.an @@ -23,5 +23,5 @@ c = foo "one" "two" // a : I32 // b : F64 // c : String -// foo : (forall a b. (a - a -> a can b)) +// foo : forall a b. (a - a -> a can b) // given Foo a diff --git a/examples/typechecking/instantiation.an b/examples/typechecking/instantiation.an index 1f19dff2..369e1b45 100644 --- a/examples/typechecking/instantiation.an +++ b/examples/typechecking/instantiation.an @@ -14,8 +14,8 @@ id x = x // args: --check --show-types // expected stdout: -// add : (forall a b c d e f g h i. ((a - c => e can g) - (a - b => c can g) -> (a => (b => e can g) can h) can i)) -// id : (forall a b. (a -> a can b)) -// one : (forall a b c d. ((a => b can d) - a -> b can d)) -// two1 : (forall a b c. ((b => b can c) - b -> b can c)) -// two2 : ((a => a can c) => (a => a can c) can d) +// add : forall a b c d e f g h i. ((a - c => e can g) - (a - b => c can g) -> a => b => e can g can h can i) +// id : forall a b. (a -> a can b) +// one : forall a b c d. ((a => b can d) - a -> b can d) +// two1 : forall a b c. ((b => b can c) - b -> b can c) +// two2 : (a => a can c) => a => a can c can d diff --git a/examples/typechecking/member_access.an b/examples/typechecking/member_access.an index 6aae4494..4741e459 100644 --- a/examples/typechecking/member_access.an +++ b/examples/typechecking/member_access.an @@ -22,11 +22,11 @@ foo_and_bar foo bar // // expected stdout: -// Bar : (forall a. (Char -> Bar can a)) -// Foo : (forall a. (F64 -> Foo can a)) -// FooBar : (forall a. (I32 - String -> FooBar can a)) +// Bar : forall a. (Char -> Bar can a) +// Foo : forall a. (F64 -> Foo can a) +// FooBar : forall a. (I32 - String -> FooBar can a) // bar : Bar // foo : Foo -// foo_and_bar : (forall a b c d. ({ foo: a, ..b } - { bar: String, ..c } -> String can d)) +// foo_and_bar : forall a b c d. ({ foo: a, ..b } - { bar: String, ..c } -> String can d) // foobar : FooBar -// stringify : (forall a. (String -> String can a)) +// stringify : forall a. (String -> String can a) diff --git a/examples/typechecking/mutual_recursion.an b/examples/typechecking/mutual_recursion.an index 4eb5c325..e460faee 100644 --- a/examples/typechecking/mutual_recursion.an +++ b/examples/typechecking/mutual_recursion.an @@ -16,7 +16,7 @@ is_even 4 // TODO: is_odd here uses `forall a c.` instead of `forall a b.` // expected stdout: -// is_even : (forall a b. ((Int a) -> Bool can b)) -// given Eq (Int a), Print (Int a), Sub (Int a) -// is_odd : (forall a c. ((Int a) -> Bool can c)) -// given Eq (Int a), Print (Int a), Sub (Int a) +// is_even : forall a b. (Int a -> Bool can b) +// given Eq Int a, Print Int a, Sub Int a +// is_odd : forall a c. (Int a -> Bool can c) +// given Eq Int a, Print Int a, Sub Int a diff --git a/examples/typechecking/named_constructor.an b/examples/typechecking/named_constructor.an index f4146bb4..e3d292ef 100644 --- a/examples/typechecking/named_constructor.an +++ b/examples/typechecking/named_constructor.an @@ -8,6 +8,6 @@ foo = hello_foo 42 // args: --check --show-types // expected stdout: -// Foo : (forall a b c. (a - b -> (Foo a b) can c)) -// foo : (Foo String (Int a)) -// hello_foo : (forall a b. (a -> (Foo String a) can b)) +// Foo : forall a b c. (a - b -> Foo a b can c) +// foo : Foo String (Int a) +// hello_foo : forall a b. (a -> Foo String a can b) diff --git a/examples/typechecking/repeated_traits.an b/examples/typechecking/repeated_traits.an index e4a31999..f4e5fc73 100644 --- a/examples/typechecking/repeated_traits.an +++ b/examples/typechecking/repeated_traits.an @@ -5,5 +5,5 @@ foo a = // Make sure output is not "... given Print a, Print a" // args: --check --show-types // expected stdout: -// foo : (forall a b. (a -> Unit can b)) +// foo : forall a b. (a -> Unit can b) // given Print a diff --git a/examples/typechecking/trait_fundep_result.an b/examples/typechecking/trait_fundep_result.an index 1f3a051b..f60abc28 100644 --- a/examples/typechecking/trait_fundep_result.an +++ b/examples/typechecking/trait_fundep_result.an @@ -9,6 +9,6 @@ str = foo 0i32 // args: --check --show-types // expected stdout: -// foo : (forall a b c. (a -> b can c)) +// foo : forall a b c. (a -> b can c) // given Foo a b // str : String diff --git a/examples/typechecking/trait_generalization.an b/examples/typechecking/trait_generalization.an index f86ca965..9c45a2ff 100644 --- a/examples/typechecking/trait_generalization.an +++ b/examples/typechecking/trait_generalization.an @@ -1,4 +1,4 @@ a = "test".c_string // args: --check --show-types -// expected stdout: a : (Ptr Char) +// expected stdout: a : Ptr Char diff --git a/examples/typechecking/trait_propagation.an b/examples/typechecking/trait_propagation.an index 97ffca46..2e72241a 100644 --- a/examples/typechecking/trait_propagation.an +++ b/examples/typechecking/trait_propagation.an @@ -11,10 +11,10 @@ foo () = baz bar // // args: --check --show-types // expected stdout: -// bar : (forall a. a) -// baz : (forall a b. (a -> Unit can b)) +// bar : forall a. a +// baz : forall a b. (a -> Unit can b) // given Baz a -// foo : (forall a. (Unit -> Unit can a)) +// foo : forall a. (Unit -> Unit can a) // expected stderr: // trait_propagation.an:6:10 error: No impl found for Baz a diff --git a/examples/typechecking/type_annotations.an b/examples/typechecking/type_annotations.an index e9d63b73..0f519a42 100644 --- a/examples/typechecking/type_annotations.an +++ b/examples/typechecking/type_annotations.an @@ -22,8 +22,8 @@ puts2 // // expected stdout: -// bar : (forall a. (I32 - I32 -> I32 can a)) -// baz : (forall a b. (Usz -> (Ptr a) can b)) -// exit2 : (forall a b. (I32 -> a can b)) -// foo : (forall a. (I32 - String -> Char can a)) -// puts2 : (forall a. ((Ptr Char) -> I32 can a)) +// bar : forall a. (I32 - I32 -> I32 can a) +// baz : forall a b. (Usz -> Ptr a can b) +// exit2 : forall a b. (I32 -> a can b) +// foo : forall a. (I32 - String -> Char can a) +// puts2 : forall a. (Ptr Char -> I32 can a) diff --git a/src/types/mod.rs b/src/types/mod.rs index c0e0fe35..a51efb1c 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -9,8 +9,8 @@ use std::collections::BTreeMap; use crate::cache::{DefinitionInfoId, ModuleCache}; use crate::error::location::{Locatable, Location}; use crate::lexer::token::{FloatKind, IntegerKind}; -use crate::util::fmap; use crate::util; +use crate::util::fmap; use self::typeprinter::TypePrinter; use crate::types::effects::EffectSet; @@ -27,6 +27,30 @@ pub mod typeprinter; #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct TypeVariableId(pub usize); +/// Priority of operator on Types +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct TypePriority(u8); + +impl From for TypePriority { + fn from(priority: u8) -> Self { + Self(priority) + } +} + +impl std::fmt::Display for TypePriority { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "priority {}", self.0) + } +} + +impl TypePriority { + pub const MAX: TypePriority = TypePriority(u8::MAX); + pub const APP: TypePriority = TypePriority(4); + pub const FORALL: TypePriority = TypePriority(3); + pub const PAIR: TypePriority = TypePriority(2); + pub const FUN: TypePriority = TypePriority(1); +} + /// Primitive types are the easy cases when unifying types. /// They're equal simply if the other type is also the same PrimitiveType variant, /// there is no recursion needed like with other Types. If the `Type` @@ -217,6 +241,36 @@ impl Type { } } + pub fn priority(&self, cache: &ModuleCache<'_>) -> TypePriority { + use Type::*; + match self { + Primitive(_) | UserDefined(_) | Struct(_, _) | Tag(_) => TypePriority::MAX, + TypeVariable(id) => match &cache.type_bindings[id.0] { + TypeBinding::Bound(typ) => typ.priority(cache), + TypeBinding::Unbound(..) => TypePriority::MAX, + }, + Function(_) => TypePriority::FUN, + TypeApplication(ctor, args) if ctor.is_polymorphic_int_type() || ctor.is_polymorphic_float_type() => { + if matches!(cache.follow_typebindings_shallow(&args[0]), Type::TypeVariable(_)) { + // type variable is unbound variable + TypePriority::APP + } else { + // type variable is bound (polymorphic int) + TypePriority::MAX + } + }, + TypeApplication(ctor, _) => { + if ctor.is_pair_type() { + TypePriority::PAIR + } else { + TypePriority::APP + } + }, + Ref { .. } => TypePriority::APP, + Effects(_) => unimplemented!("Type::priority for Effects"), + } + } + /// Pretty-print each type with each typevar substituted for a, b, c, etc. pub fn display<'a, 'b>(&self, cache: &'a ModuleCache<'b>) -> typeprinter::TypePrinter<'a, 'b> { let typ = GeneralizedType::MonoType(self.clone()); @@ -257,7 +311,7 @@ impl Type { sharedness.traverse_rec(cache, f); mutability.traverse_rec(cache, f); lifetime.traverse_rec(cache, f); - } + }, Type::TypeApplication(constructor, args) => { constructor.traverse_rec(cache, f); for arg in args { @@ -356,7 +410,7 @@ impl Type { let mutable = mutability.approx_to_string(); let lifetime = lifetime.approx_to_string(); format!("{}{} '{}", mutable, shared, lifetime) - } + }, Type::Struct(fields, id) => { let fields = fmap(fields, |(name, typ)| format!("{}: {}", name, typ.approx_to_string())); format!("{{ {}, ..tv{} }}", fields.join(", "), id.0) @@ -440,6 +494,13 @@ impl GeneralizedType { GeneralizedType::PolyType(_, _) => unreachable!(), } } + + pub fn priority(&self, cache: &ModuleCache<'_>) -> TypePriority { + match self { + GeneralizedType::MonoType(typ) => typ.priority(cache), + GeneralizedType::PolyType(..) => TypePriority::FORALL, + } + } } #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)] diff --git a/src/types/typeprinter.rs b/src/types/typeprinter.rs index d3fe87df..01c2030f 100644 --- a/src/types/typeprinter.rs +++ b/src/types/typeprinter.rs @@ -15,8 +15,9 @@ use std::fmt::{Debug, Display, Formatter}; use colored::*; use super::effects::EffectSet; -use super::GeneralizedType; use super::typechecker::follow_bindings_in_cache; +use super::GeneralizedType; +use super::TypePriority; /// Wrapper containing the information needed to print out a type pub struct TypePrinter<'a, 'b> { @@ -186,9 +187,14 @@ impl<'a, 'b> TypePrinter<'a, 'b> { } fn fmt_function(&self, function: &FunctionType, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}", "(".blue())?; for (i, param) in function.parameters.iter().enumerate() { + if TypePriority::FUN >= param.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } self.fmt_type(param, f)?; + if TypePriority::FUN >= param.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } write!(f, " ")?; if i != function.parameters.len() - 1 { @@ -206,12 +212,19 @@ impl<'a, 'b> TypePrinter<'a, 'b> { write!(f, "{}", "=> ".blue())?; } + // No parentheses are necessary if the precedence is the same, because `->` is right associative. + // i.e. `a -> b -> c` means `a -> (b -> c)` + if TypePriority::FUN > function.return_type.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } self.fmt_type(function.return_type.as_ref(), f)?; + if TypePriority::FUN > function.return_type.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } write!(f, "{}", " can ".blue())?; self.fmt_type(&function.effects, f)?; - - write!(f, "{}", ")".blue()) + Ok(()) } fn fmt_type_variable(&self, id: TypeVariableId, f: &mut Formatter) -> std::fmt::Result { @@ -232,55 +245,62 @@ impl<'a, 'b> TypePrinter<'a, 'b> { fn fmt_type_application(&self, constructor: &Type, args: &[Type], f: &mut Formatter) -> std::fmt::Result { if constructor.is_polymorphic_int_type() { - self.fmt_polymorphic_numeral(args, f, "Int") + self.fmt_polymorphic_numeral(&args[0], f, "Int") } else if constructor.is_polymorphic_float_type() { - self.fmt_polymorphic_numeral(args, f, "Float") + self.fmt_polymorphic_numeral(&args[0], f, "Float") } else { - write!(f, "{}", "(".blue())?; - if constructor.is_pair_type() { - self.fmt_pair(args, f)?; + self.fmt_pair(&args[0], &args[1], f)?; } else { self.fmt_type(constructor, f)?; - for arg in args.iter() { + for arg in args { write!(f, " ")?; + // `(app f (app a b))` should be represented as `f (a b)` + if TypePriority::APP >= arg.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } self.fmt_type(arg, f)?; + if TypePriority::APP >= arg.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } } } - - write!(f, "{}", ")".blue()) + Ok(()) } } - fn fmt_pair(&self, args: &[Type], f: &mut Formatter) -> std::fmt::Result { - assert_eq!(args.len(), 2); - - self.fmt_type(&args[0], f)?; - + fn fmt_pair(&self, arg1: &Type, arg2: &Type, f: &mut Formatter) -> std::fmt::Result { + if TypePriority::PAIR >= arg1.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } + self.fmt_type(arg1, f)?; + if TypePriority::PAIR >= arg1.priority(&self.cache) { + write!(f, "{}", ")".blue())?; + } write!(f, "{}", ", ".blue())?; - - match &args[1] { - Type::TypeApplication(constructor, args) if constructor.is_pair_type() => self.fmt_pair(args, f), - other => self.fmt_type(other, f), + // Because `(,)` is right-associative, omit parentheses if it has equal priority. + // e.g. `a, b, .., n, m` means `(a, (b, (.. (n, m)..)))`. + if TypePriority::PAIR > arg2.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + } + self.fmt_type(arg2, f)?; + if TypePriority::PAIR > arg2.priority(&self.cache) { + write!(f, "{}", ")".blue())?; } + Ok(()) } - fn fmt_polymorphic_numeral(&self, args: &[Type], f: &mut Formatter, kind: &str) -> std::fmt::Result { - assert_eq!(args.len(), 1); - - match self.cache.follow_typebindings_shallow(&args[0]) { + fn fmt_polymorphic_numeral(&self, arg: &Type, f: &mut Formatter, kind: &str) -> std::fmt::Result { + match self.cache.follow_typebindings_shallow(arg) { Type::TypeVariable(_) => { - write!(f, "{}{} ", "(".blue(), kind.blue())?; - self.fmt_type(&args[0], f)?; - write!(f, "{}", ")".blue()) + write!(f, "{} ", kind.blue())?; + self.fmt_type(arg, f) }, other => self.fmt_type(other, f), } } - fn fmt_ref( - &self, shared: &Type, mutable: &Type, lifetime: &Type, f: &mut Formatter, - ) -> std::fmt::Result { + fn fmt_ref(&self, shared: &Type, mutable: &Type, lifetime: &Type, f: &mut Formatter) -> std::fmt::Result { let mutable = follow_bindings_in_cache(mutable, self.cache); let shared = follow_bindings_in_cache(shared, self.cache); let parenthesize = matches!(shared, Type::Tag(_)) || self.debug; @@ -310,14 +330,20 @@ impl<'a, 'b> TypePrinter<'a, 'b> { } fn fmt_forall(&self, typevars: &[TypeVariableId], typ: &Type, f: &mut Formatter) -> std::fmt::Result { - write!(f, "{}", "(forall".blue())?; - for typevar in typevars.iter() { + write!(f, "{}", "forall".blue())?; + for typevar in typevars { write!(f, " ")?; self.fmt_type_variable(*typevar, f)?; } write!(f, "{}", ". ".blue())?; - self.fmt_type(typ, f)?; - write!(f, "{}", ")".blue()) + if TypePriority::FORALL > typ.priority(&self.cache) { + write!(f, "{}", "(".blue())?; + self.fmt_type(typ, f)?; + write!(f, "{}", ")".blue())?; + } else { + self.fmt_type(typ, f)?; + } + Ok(()) } fn fmt_struct( From 75412055195cf8f0a5f229c726c57c195de8b1f0 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Sun, 5 Jan 2025 19:05:31 -0600 Subject: [PATCH 9/9] Add 'unhandled effects at top-level' error --- src/error/mod.rs | 7 ++++- src/types/typechecker.rs | 56 ++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/error/mod.rs b/src/error/mod.rs index c700cb29..f5382b2b 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -83,6 +83,7 @@ pub enum DiagnosticKind { NoMatchingImpls(/*constraint*/ String), UnreachablePattern, MissingCase(/*case*/ String), + UnhandledEffectsInMain(/*effects*/ String), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -313,7 +314,7 @@ impl Display for DiagnosticKind { }, DiagnosticKind::TypeError(TypeErrorKind::MonomorphizationError, actual, expected) => { unreachable!( - "Unification error during monomorphisation: Could not unify definition {} with instantiation {}", + "Unification error during monomorphization: Could not unify definition {} with instantiation {}", expected, actual ) }, @@ -335,6 +336,9 @@ impl Display for DiagnosticKind { DiagnosticKind::MissingCase(case) => { write!(f, "Missing case {case}") }, + DiagnosticKind::UnhandledEffectsInMain(effects) => { + write!(f, "Unhandled effects at top-level: {effects}") + }, } } } @@ -381,6 +385,7 @@ impl DiagnosticKind { | InternalError(_) | NotAStruct(_) | MissingFields(_) + | UnhandledEffectsInMain(_) | NotAStructField(_) => Error, } } diff --git a/src/types/typechecker.rs b/src/types/typechecker.rs index a2539ef4..ba188ae5 100644 --- a/src/types/typechecker.rs +++ b/src/types/typechecker.rs @@ -244,8 +244,7 @@ pub fn replace_all_typevars_with_bindings( /// If the given TypeVariableId is unbound then return the matching binding in new_bindings. /// If there is no binding found, instantiate a new type variable and use that. fn replace_typevar_with_binding( - id: TypeVariableId, new_bindings: &mut TypeBindings, - cache: &mut ModuleCache<'_>, + id: TypeVariableId, new_bindings: &mut TypeBindings, cache: &mut ModuleCache<'_>, ) -> Type { if let Bound(typ) = &cache.type_bindings[id.0] { replace_all_typevars_with_bindings(&typ.clone(), new_bindings, cache) @@ -328,10 +327,7 @@ pub fn bind_typevars(typ: &Type, type_bindings: &TypeBindings, cache: &ModuleCac /// Helper for bind_typevars which binds a single TypeVariableId if it is Unbound /// and it is found in the type_bindings. If a type_binding wasn't found, a /// default TypeVariable is constructed. -fn bind_typevar( - id: TypeVariableId, type_bindings: &TypeBindings, - cache: &ModuleCache<'_>, -) -> Type { +fn bind_typevar(id: TypeVariableId, type_bindings: &TypeBindings, cache: &ModuleCache<'_>) -> Type { // TODO: This ordering of checking type_bindings first is important. // There seems to be an issue currently where forall-bound variables // can be bound in the cache, so checking the cache for bindings first @@ -369,7 +365,7 @@ pub fn contains_any_typevars_from_list(typ: &Type, list: &[TypeVariableId], cach contains_any_typevars_from_list(mutability, list, cache) || contains_any_typevars_from_list(sharedness, list, cache) || contains_any_typevars_from_list(lifetime, list, cache) - } + }, TypeApplication(typ, args) => { contains_any_typevars_from_list(typ, list, cache) @@ -569,8 +565,7 @@ pub(super) fn occurs( .then_all(&function.parameters, |param| occurs(id, level, param, bindings, fuel, cache)), TypeApplication(typ, args) => occurs(id, level, typ, bindings, fuel, cache) .then_all(args, |arg| occurs(id, level, arg, bindings, fuel, cache)), - Ref { mutability, sharedness, lifetime } => - occurs(id, level, mutability, bindings, fuel, cache) + Ref { mutability, sharedness, lifetime } => occurs(id, level, mutability, bindings, fuel, cache) .then(|| occurs(id, level, sharedness, bindings, fuel, cache)) .then(|| occurs(id, level, lifetime, bindings, fuel, cache)), Struct(fields, var_id) => typevars_match(id, level, *var_id, bindings, fuel, cache) @@ -628,7 +623,8 @@ pub fn follow_bindings_in_cache(typ: &Type, cache: &ModuleCache<'_>) -> Type { /// This function performs the bulk of the work for the various unification functions. #[allow(clippy::nonminimal_bool)] pub fn try_unify_with_bindings_inner<'b>( - actual: &Type, expected: &Type, bindings: &mut UnificationBindings, location: Location<'b>, cache: &mut ModuleCache<'b>, + actual: &Type, expected: &Type, bindings: &mut UnificationBindings, location: Location<'b>, + cache: &mut ModuleCache<'b>, ) -> Result<(), ()> { match (actual, expected) { (Primitive(p1), Primitive(p2)) if p1 == p2 => Ok(()), @@ -641,9 +637,13 @@ pub fn try_unify_with_bindings_inner<'b>( // it to the minimum scope of type variables in b. This happens within the occurs check. // The unification of the LetBindingLevel here is a form of lifetime inference for the // typevar and is used during generalization to determine which variables to generalize. - (TypeVariable(id), _) => try_unify_type_variable_with_bindings(*id, actual, expected, true, bindings, location, cache), + (TypeVariable(id), _) => { + try_unify_type_variable_with_bindings(*id, actual, expected, true, bindings, location, cache) + }, - (_, TypeVariable(id)) => try_unify_type_variable_with_bindings(*id, expected, actual, false, bindings, location, cache), + (_, TypeVariable(id)) => { + try_unify_type_variable_with_bindings(*id, expected, actual, false, bindings, location, cache) + }, (Function(function1), Function(function2)) => { if function1.parameters.len() != function2.parameters.len() { @@ -683,8 +683,10 @@ pub fn try_unify_with_bindings_inner<'b>( }, // Refs have a hidden lifetime variable we need to unify here - (Ref { sharedness: a_shared, mutability: a_mut, lifetime: a_lifetime }, - Ref { sharedness: b_shared, mutability: b_mut, lifetime: b_lifetime }) => { + ( + Ref { sharedness: a_shared, mutability: a_mut, lifetime: a_lifetime }, + Ref { sharedness: b_shared, mutability: b_mut, lifetime: b_lifetime }, + ) => { try_unify_with_bindings_inner(a_shared, b_shared, bindings, location, cache)?; try_unify_with_bindings_inner(a_mut, b_mut, bindings, location, cache)?; try_unify_with_bindings_inner(a_lifetime, b_lifetime, bindings, location, cache) @@ -865,10 +867,8 @@ fn get_fields( /// Unify a single type variable (id arising from the type a) with an expected type b. /// Follows the given TypeBindings in bindings and the cache if a is Bound. fn try_unify_type_variable_with_bindings<'c>( - id: TypeVariableId, a: &Type, b: &Type, - typevar_on_lhs: bool, - bindings: &mut UnificationBindings, location: Location<'c>, - cache: &mut ModuleCache<'c>, + id: TypeVariableId, a: &Type, b: &Type, typevar_on_lhs: bool, bindings: &mut UnificationBindings, + location: Location<'c>, cache: &mut ModuleCache<'c>, ) -> Result<(), ()> { match find_binding(id, bindings, cache) { Bound(a) => { @@ -877,7 +877,7 @@ fn try_unify_type_variable_with_bindings<'c>( } else { try_unify_with_bindings_inner(b, &a, bindings, location, cache) } - } + }, Unbound(a_level, _a_kind) => { // Create binding for boundTy that is currently empty. // Ensure not to create recursive bindings to the same variable @@ -899,8 +899,8 @@ fn try_unify_type_variable_with_bindings<'c>( } pub fn try_unify_with_bindings<'b>( - actual: &Type, expected: &Type, bindings: &mut UnificationBindings, location: Location<'b>, cache: &mut ModuleCache<'b>, - error: TypeErrorKind, + actual: &Type, expected: &Type, bindings: &mut UnificationBindings, location: Location<'b>, + cache: &mut ModuleCache<'b>, error: TypeErrorKind, ) -> Result<(), Diagnostic<'b>> { match try_unify_with_bindings_inner(actual, expected, bindings, location, cache) { Ok(()) => Ok(()), @@ -945,7 +945,9 @@ pub fn try_unify_all_with_bindings<'c>( /// Unifies the two given types, remembering the unification results in the cache. /// If this operation fails, a user-facing error message is emitted. -pub fn unify<'c>(actual: &Type, expected: &Type, location: Location<'c>, cache: &mut ModuleCache<'c>, error_kind: TypeErrorKind) { +pub fn unify<'c>( + actual: &Type, expected: &Type, location: Location<'c>, cache: &mut ModuleCache<'c>, error_kind: TypeErrorKind, +) { perform_bindings_or_push_error(try_unify(actual, expected, location, cache, error_kind), cache); } @@ -1005,7 +1007,7 @@ pub fn find_all_typevars(typ: &Type, polymorphic_only: bool, cache: &ModuleCache type_variables.append(&mut find_all_typevars(sharedness, polymorphic_only, cache)); type_variables.append(&mut find_all_typevars(lifetime, polymorphic_only, cache)); type_variables - } + }, Struct(fields, id) => match &cache.type_bindings[id.0] { Bound(t) => find_all_typevars(t, polymorphic_only, cache), Unbound(..) => { @@ -1489,8 +1491,12 @@ pub fn infer_ast<'a>(ast: &mut ast::Ast<'a>, cache: &mut ModuleCache<'a>) { // No traits should be propogated above the top-level main function assert!(exposed_traits.is_empty()); - // TODO: Better error message, check for IO effect - assert!(result.effects.effects.is_empty()); + // TODO: Check for IO effect + if !result.effects.effects.is_empty() { + let effects = Type::Effects(result.effects.clone()); + let effects = effects.display(cache).to_string(); + cache.push_diagnostic(ast.locate(), D::UnhandledEffectsInMain(effects)); + } } pub fn infer<'a, T>(ast: &mut T, cache: &mut ModuleCache<'a>) -> TypeResult