From 8ca46bfe5854642728cde2711c3a7db5cad92628 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 21 Nov 2024 15:49:09 +0100 Subject: [PATCH] Add support for inline types An inline type is an immutable value type that's allocated inline/on the stack. Both regular and enum types can be defined as "inline". Inline types don't have object headers, and combined with them being stored inline means they don't incur extra indirection/overhead. For example, consider this type: class inline A { let @a: Int let @b: Int } If this were a regular type, its size would be 32 bytes: 16 bytes for the header, and 16 bytes for the two fields. Because it's an inline type though, it only needs 16 bytes. Inline types are restricted to types that can be trivially copied, such as Int, Float, and other inline types. String isn't allowed in inline types at this point as this could result in an unexpected copy cost due to String using atomic reference counting. Inline types are immutable because supporting mutations introduces significant compiler complexity, especially when dealing with closures that capture generic type parameters, as support for mutations would require rewriting part of the generated code as part of type specialization. This fixes https://github.com/inko-lang/inko/issues/750. Changelog: added --- ast/src/nodes.rs | 3 + ast/src/parser.rs | 81 ++ compiler/src/compiler.rs | 19 +- compiler/src/diagnostics.rs | 60 +- compiler/src/docs.rs | 25 +- compiler/src/format.rs | 34 +- compiler/src/hir.rs | 196 ++- compiler/src/llvm/builder.rs | 32 +- compiler/src/llvm/context.rs | 153 ++- compiler/src/llvm/layouts.rs | 511 ++++---- compiler/src/llvm/methods.rs | 7 +- compiler/src/llvm/module.rs | 41 +- compiler/src/llvm/passes.rs | 609 ++++++---- compiler/src/mir/inline.rs | 4 +- compiler/src/mir/mod.rs | 50 +- compiler/src/mir/passes.rs | 217 ++-- compiler/src/mir/pattern_matching.rs | 7 +- compiler/src/mir/specialize.rs | 318 ++--- compiler/src/symbol_names.rs | 137 ++- compiler/src/target.rs | 11 - compiler/src/type_check/define_types.rs | 248 ++-- compiler/src/type_check/expressions.rs | 237 ++-- compiler/src/type_check/graph.rs | 103 ++ compiler/src/type_check/imports.rs | 52 + compiler/src/type_check/methods.rs | 81 +- compiler/src/type_check/mod.rs | 42 +- docs/inko.pkg | 1 + docs/source/design/compiler.md | 8 + docs/source/getting-started/classes.md | 97 +- docs/source/getting-started/ffi.md | 4 +- docs/source/getting-started/generics.md | 88 ++ docs/source/getting-started/traits.md | 21 + docs/source/references/syntax.md | 8 + .../diagnostics/casting_value_types.inko | 33 + .../default_method_with_bounds.inko | 4 +- ...e_types_implementing_mutating_methods.inko | 21 + ...immutable_types_with_mutating_methods.inko | 10 + .../diagnostics/inline_enum_definitions.inko | 14 + .../diagnostics/inline_type_definitions.inko | 14 + .../diagnostics/inline_type_instances.inko | 15 + .../diagnostics/inline_type_parameters.inko | 15 + .../inline_types_as_mutable_arguments.inko | 7 + .../diagnostics/inline_types_with_fields.inko | 23 + .../inline_types_with_methods.inko | 12 + .../diagnostics/mutating_inline_types.inko | 11 + .../diagnostics/recursive_classes.inko | 66 + .../diagnostics/type_parameter_bounds.inko | 12 + .../type_parameter_requirements.inko | 10 + ...lue_types_passed_to_mutable_arguments.inko | 42 + std/fixtures/fmt/classes/input.inko | 51 + std/fixtures/fmt/classes/output.inko | 51 + std/fixtures/fmt/type_parameters/input.inko | 11 + std/fixtures/fmt/type_parameters/output.inko | 11 + std/src/std/string.inko | 4 +- std/test/compiler/test_extern_types.inko | 17 + std/test/compiler/test_inline_types.inko | 83 ++ std/test/std/test_array.inko | 31 +- std/test/std/test_deque.inko | 20 +- std/test/std/test_iter.inko | 14 +- std/test/std/test_map.inko | 6 +- std/test/std/test_option.inko | 7 +- types/src/check.rs | 279 +++-- types/src/format.rs | 179 ++- types/src/lib.rs | 1067 +++++++++++++---- types/src/resolve.rs | 10 +- types/src/specialize.rs | 268 +++-- 66 files changed, 4243 insertions(+), 1680 deletions(-) create mode 100644 compiler/src/type_check/graph.rs create mode 100644 std/fixtures/diagnostics/casting_value_types.inko create mode 100644 std/fixtures/diagnostics/immutable_types_implementing_mutating_methods.inko create mode 100644 std/fixtures/diagnostics/immutable_types_with_mutating_methods.inko create mode 100644 std/fixtures/diagnostics/inline_enum_definitions.inko create mode 100644 std/fixtures/diagnostics/inline_type_definitions.inko create mode 100644 std/fixtures/diagnostics/inline_type_instances.inko create mode 100644 std/fixtures/diagnostics/inline_type_parameters.inko create mode 100644 std/fixtures/diagnostics/inline_types_as_mutable_arguments.inko create mode 100644 std/fixtures/diagnostics/inline_types_with_fields.inko create mode 100644 std/fixtures/diagnostics/inline_types_with_methods.inko create mode 100644 std/fixtures/diagnostics/mutating_inline_types.inko create mode 100644 std/fixtures/diagnostics/recursive_classes.inko create mode 100644 std/fixtures/diagnostics/type_parameter_bounds.inko create mode 100644 std/fixtures/diagnostics/type_parameter_requirements.inko create mode 100644 std/fixtures/diagnostics/value_types_passed_to_mutable_arguments.inko create mode 100644 std/fixtures/fmt/classes/input.inko create mode 100644 std/fixtures/fmt/classes/output.inko create mode 100644 std/fixtures/fmt/type_parameters/input.inko create mode 100644 std/fixtures/fmt/type_parameters/output.inko create mode 100644 std/test/compiler/test_extern_types.inko create mode 100644 std/test/compiler/test_inline_types.inko diff --git a/ast/src/nodes.rs b/ast/src/nodes.rs index fb1375353..2e0eefe95 100644 --- a/ast/src/nodes.rs +++ b/ast/src/nodes.rs @@ -492,6 +492,7 @@ pub enum ClassKind { #[derive(Debug, PartialEq, Eq)] pub struct DefineClass { pub public: bool, + pub inline: bool, pub kind: ClassKind, pub name: Constant, pub type_parameters: Option, @@ -637,6 +638,7 @@ impl Node for ReopenClass { pub enum Requirement { Trait(TypeName), Mutable(Location), + Inline(Location), } impl Node for Requirement { @@ -644,6 +646,7 @@ impl Node for Requirement { match self { Requirement::Trait(n) => &n.location, Requirement::Mutable(loc) => loc, + Requirement::Inline(loc) => loc, } } } diff --git a/ast/src/parser.rs b/ast/src/parser.rs index 075a0b0fa..a65c52187 100644 --- a/ast/src/parser.rs +++ b/ast/src/parser.rs @@ -1027,6 +1027,12 @@ impl Parser { start: Token, ) -> Result { let public = self.next_is_public(); + let inline = if self.peek().kind == TokenKind::Inline { + self.next(); + true + } else { + false + }; let kind = match self.peek().kind { TokenKind::Async => { self.next(); @@ -1064,6 +1070,7 @@ impl Parser { Ok(TopLevelExpression::DefineClass(Box::new(DefineClass { public, + inline, kind, name, type_parameters, @@ -1284,6 +1291,7 @@ impl Parser { let token = self.require()?; let req = match token.kind { TokenKind::Mut => Requirement::Mutable(token.location), + TokenKind::Inline => Requirement::Inline(token.location), _ => Requirement::Trait( self.type_name_with_optional_namespace(token)?, ), @@ -4604,6 +4612,7 @@ mod tests { top(parse("class A {}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4623,6 +4632,7 @@ mod tests { top(parse("class pub A {}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: true, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4645,6 +4655,7 @@ mod tests { top(parse("class extern A {}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4661,12 +4672,36 @@ mod tests { ); } + #[test] + fn test_inline_class() { + assert_eq!( + top(parse("class inline A {}")), + TopLevelExpression::DefineClass(Box::new(DefineClass { + public: false, + inline: true, + name: Constant { + source: None, + name: "A".to_string(), + location: cols(14, 14) + }, + kind: ClassKind::Regular, + type_parameters: None, + body: ClassExpressions { + values: Vec::new(), + location: cols(16, 17) + }, + location: cols(1, 17) + })) + ); + } + #[test] fn test_async_class() { assert_eq!( top(parse("class async A {}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4689,6 +4724,7 @@ mod tests { top(parse("class A { fn async foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4727,6 +4763,7 @@ mod tests { top(parse("class A { fn async mut foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4768,6 +4805,7 @@ mod tests { top(parse("class A[B: X, C] {}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4820,6 +4858,7 @@ mod tests { top(parse("class A[B: a.X] {}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4867,6 +4906,7 @@ mod tests { top(parse("class A { fn foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4905,6 +4945,7 @@ mod tests { top(parse("class A { fn pub foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4946,6 +4987,7 @@ mod tests { top(parse("class A { fn move foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -4987,6 +5029,7 @@ mod tests { top(parse("class A { fn inline foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -5028,6 +5071,7 @@ mod tests { top(parse("class A { fn mut foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -5069,6 +5113,7 @@ mod tests { top(parse("class A { fn static foo {} }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -5110,6 +5155,7 @@ mod tests { top(parse("class A { let @foo: A }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -5147,6 +5193,7 @@ mod tests { top(parse("class A { let pub @foo: A }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, name: Constant { source: None, name: "A".to_string(), @@ -5507,6 +5554,37 @@ mod tests { })) ); + assert_eq!( + top(parse("impl A if T: inline {}")), + TopLevelExpression::ReopenClass(Box::new(ReopenClass { + class_name: Constant { + source: None, + name: "A".to_string(), + location: cols(6, 6) + }, + body: ImplementationExpressions { + values: Vec::new(), + location: cols(21, 22) + }, + bounds: Some(TypeBounds { + values: vec![TypeBound { + name: Constant { + source: None, + name: "T".to_string(), + location: cols(11, 11) + }, + requirements: Requirements { + values: vec![Requirement::Inline(cols(14, 19))], + location: cols(14, 19) + }, + location: cols(11, 19) + }], + location: cols(11, 19) + }), + location: cols(1, 19) + })) + ); + assert_eq!( top(parse("impl A if T: mut, {}")), TopLevelExpression::ReopenClass(Box::new(ReopenClass { @@ -6166,6 +6244,7 @@ mod tests { top(parse("class builtin A {}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, kind: ClassKind::Builtin, name: Constant { source: None, @@ -9600,6 +9679,7 @@ mod tests { top(parse("class enum Option[T] { case Some(T) case None }")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, kind: ClassKind::Enum, name: Constant { source: None, @@ -9699,6 +9779,7 @@ mod tests { top(parse_with_comments("class A {\n# foo\n}")), TopLevelExpression::DefineClass(Box::new(DefineClass { public: false, + inline: false, kind: ClassKind::Regular, name: Constant { source: None, diff --git a/compiler/src/compiler.rs b/compiler/src/compiler.rs index ce06ef37d..97bfc1f82 100644 --- a/compiler/src/compiler.rs +++ b/compiler/src/compiler.rs @@ -18,15 +18,15 @@ use crate::pkg::version::Version; use crate::state::State; use crate::symbol_names::SymbolNames; use crate::type_check::define_types::{ - CheckTraitImplementations, CheckTraitRequirements, CheckTypeParameters, - DefineConstructors, DefineFields, DefineTraitRequirements, - DefineTypeParameterRequirements, DefineTypeParameters, DefineTypes, - ImplementTraits, InsertPrelude, + check_recursive_types, CheckTraitImplementations, CheckTraitRequirements, + CheckTypeParameters, DefineConstructors, DefineFields, + DefineTraitRequirements, DefineTypeParameterRequirements, + DefineTypeParameters, DefineTypes, ImplementTraits, InsertPrelude, }; -use crate::type_check::expressions::{ - check_unused_imports, define_constants, Expressions, +use crate::type_check::expressions::{define_constants, Expressions}; +use crate::type_check::imports::{ + check_unused_imports, CollectExternImports, DefineImportedTypes, }; -use crate::type_check::imports::{CollectExternImports, DefineImportedTypes}; use crate::type_check::methods::{ CheckMainMethod, DefineMethods, DefineModuleMethodNames, ImplementTraitMethods, @@ -262,6 +262,10 @@ impl Compiler { // MIR to LLVM, otherwise we may generate incorrect code. self.specialize_mir(&mut mir); + // At this point we can get rid of various data structures stored in the + // type database. This must be done _after_ specialization. + self.state.db.compact(); + // Splitting is done _after_ specialization, since specialization // introduces new types and methods. mir.split_modules(&mut self.state); @@ -522,6 +526,7 @@ LLVM module timings: && CheckTypeParameters::run_all(state, modules) && DefineConstructors::run_all(state, modules) && DefineFields::run_all(state, modules) + && check_recursive_types(state, modules) && DefineMethods::run_all(state, modules) && CheckMainMethod::run(state) && ImplementTraitMethods::run_all(state, modules) diff --git a/compiler/src/diagnostics.rs b/compiler/src/diagnostics.rs index 8ea4121ee..1b798f7c7 100644 --- a/compiler/src/diagnostics.rs +++ b/compiler/src/diagnostics.rs @@ -256,6 +256,24 @@ impl Diagnostics { ); } + pub(crate) fn not_a_stack_type( + &mut self, + name: &str, + file: PathBuf, + location: Location, + ) { + self.error( + DiagnosticId::InvalidType, + format!( + "an 'inline' or 'extern' type is expected, \ + but '{}' is a heap type", + name + ), + file, + location, + ); + } + pub(crate) fn fields_not_allowed( &mut self, name: &str, @@ -841,15 +859,34 @@ impl Diagnostics { ); } - pub(crate) fn type_parameter_already_mutable( + pub(crate) fn duplicate_type_parameter_requirement( &mut self, - name: &str, + param: &str, + req: &str, file: PathBuf, location: Location, ) { self.error( DiagnosticId::InvalidType, - format!("the type parameter '{}' is already mutable", name), + format!( + "type parameter '{}' already defines the '{}' requirement", + param, req + ), + file, + location, + ); + } + + pub(crate) fn mutable_inline_type_parameter( + &mut self, + file: PathBuf, + location: Location, + ) { + self.error( + DiagnosticId::InvalidType, + "type parameters can't be both 'mut' and 'inline', \ + as 'inline' types are immutable" + .to_string(), file, location, ); @@ -978,6 +1015,23 @@ impl Diagnostics { ); } + pub(crate) fn invalid_mut_type( + &mut self, + name: &str, + file: PathBuf, + location: Location, + ) { + self.error( + DiagnosticId::InvalidType, + format!( + "mutable borrows of type '{}' are invalid as '{}' is immutable", + name, name + ), + file, + location, + ); + } + pub(crate) fn iter(&self) -> impl Iterator { self.values.iter() } diff --git a/compiler/src/docs.rs b/compiler/src/docs.rs index b427ae567..e73940b87 100644 --- a/compiler/src/docs.rs +++ b/compiler/src/docs.rs @@ -6,7 +6,7 @@ use location::Location; use std::fs::{read_to_string, write}; use std::mem::take; use std::path::Path; -use types::format::format_type; +use types::format::{format_type, type_parameter_capabilities}; use types::{ ClassId, ClassKind, Database, MethodId, ModuleId, TraitId, TypeBounds, }; @@ -25,13 +25,13 @@ fn location_to_json(location: Location) -> Json { Json::Object(obj) } -fn class_kind(kind: ClassKind) -> i64 { +fn class_kind(kind: ClassKind, stack: bool) -> i64 { match kind { ClassKind::Enum => 1, ClassKind::Async => 2, ClassKind::Extern => 3, - ClassKind::ValueType => 4, ClassKind::Atomic => 5, + _ if stack => 4, _ => 0, } } @@ -44,19 +44,19 @@ fn format_bounds(db: &Database, bounds: &TypeBounds) -> String { pairs.sort_by(|(a, _), (b, _)| a.cmp(b)); buf.push_str("\nif\n"); - for (idx, (param, req)) in pairs.into_iter().enumerate() { - let is_mut = req.is_mutable(db); + for (idx, (param, &req)) in pairs.into_iter().enumerate() { let reqs = req.requirements(db); + let capa = type_parameter_capabilities(db, req); buf.push_str(&format!( "{} {}: {}", if idx > 0 { ",\n" } else { "" }, param, - if is_mut { "mut" } else { "" } + capa.unwrap_or("") )); if !reqs.is_empty() { - if is_mut { + if capa.is_some() { buf.push_str(" + "); } @@ -391,22 +391,25 @@ impl<'a> GenerateDocumentation<'a> { let name = id.name(self.db()).clone(); let docs = id.documentation(self.db()).clone(); + let is_stack = id.is_stack_allocated(self.db()); let mut obj = Object::new(); let typ = format!( "class{}{} {}", if public { " pub" } else { "" }, match kind { + ClassKind::Enum if is_stack => " inline enum", ClassKind::Enum => " enum", ClassKind::Async => " async", ClassKind::Extern => " extern", _ if id.is_builtin() => " builtin", + _ if is_stack => " inline", _ => "", }, format_type(self.db(), id) ); obj.add("name", Json::String(name)); - obj.add("kind", Json::Int(class_kind(kind))); + obj.add("kind", Json::Int(class_kind(kind, is_stack))); obj.add("location", location_to_json(id.location(self.db()))); obj.add("public", Json::Bool(public)); obj.add("type", Json::String(typ)); @@ -574,9 +577,9 @@ impl<'a> GenerateDocumentation<'a> { let mut obj = Object::new(); let name = con.name(self.db()).clone(); let args: Vec = con - .members(self.db()) - .into_iter() - .map(|t| format_type(self.db(), t)) + .arguments(self.db()) + .iter() + .map(|&t| format_type(self.db(), t)) .collect(); let typ = format!("{}({})", name, args.join(", ")); diff --git a/compiler/src/format.rs b/compiler/src/format.rs index 7affd9f8c..b055da847 100644 --- a/compiler/src/format.rs +++ b/compiler/src/format.rs @@ -28,6 +28,21 @@ const INDENT: char = ' '; /// 3. At least for prose it's generally considered the ideal line limit const LIMIT: usize = 80; +enum Order<'a> { + Position(usize), + Name(&'a str), +} + +impl<'a> Order<'a> { + fn for_requirement(node: &'a nodes::Requirement) -> Order<'a> { + match node { + Requirement::Trait(n) => Order::Name(&n.name.name), + Requirement::Mutable(_) => Order::Position(1), + Requirement::Inline(_) => Order::Position(0), + } + } +} + #[derive(Clone, Debug)] enum Node { /// A node for which to (recursively) disable wrapping. @@ -717,6 +732,10 @@ impl Document { header.push(Node::text("pub ")); } + if node.inline { + header.push(Node::text("inline ")); + } + match node.kind { nodes::ClassKind::Async => header.push(Node::text("async ")), nodes::ClassKind::Builtin => header.push(Node::text("builtin ")), @@ -2351,14 +2370,12 @@ impl Document { let mut pairs = Vec::new(); let mut reqs = nodes.values.iter().collect::>(); - reqs.sort_by(|a, b| match (a, b) { - (Requirement::Mutable(_), Requirement::Mutable(_)) => { - Ordering::Equal - } - (Requirement::Mutable(_), _) => Ordering::Less, - (_, Requirement::Mutable(_)) => Ordering::Greater, - (Requirement::Trait(lhs), Requirement::Trait(rhs)) => { - lhs.name.name.cmp(&rhs.name.name) + reqs.sort_by(|a, b| { + match (Order::for_requirement(a), Order::for_requirement(b)) { + (Order::Position(a), Order::Position(b)) => a.cmp(&b), + (Order::Name(a), Order::Name(b)) => a.cmp(b), + (Order::Name(_), _) => Ordering::Greater, + (Order::Position(_), _) => Ordering::Less, } }); @@ -2373,6 +2390,7 @@ impl Document { let val = match node { nodes::Requirement::Trait(n) => self.type_name(n, None), nodes::Requirement::Mutable(_) => Node::text("mut"), + nodes::Requirement::Inline(_) => Node::text("inline"), }; pair.push(val); diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 044c1b1dd..29714848e 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -276,12 +276,6 @@ pub(crate) enum MethodKind { Mutable, } -impl MethodKind { - pub(crate) fn is_moving(self) -> bool { - self == Self::Moving - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct DefineInstanceMethod { pub(crate) documentation: String, @@ -396,6 +390,7 @@ pub(crate) enum ClassKind { pub(crate) struct DefineClass { pub(crate) documentation: String, pub(crate) public: bool, + pub(crate) inline: bool, pub(crate) class_id: Option, pub(crate) kind: ClassKind, pub(crate) name: Constant, @@ -477,6 +472,7 @@ pub(crate) struct TypeBound { pub(crate) name: Constant, pub(crate) requirements: Vec, pub(crate) mutable: bool, + pub(crate) inline: bool, pub(crate) location: Location, } @@ -649,6 +645,7 @@ pub(crate) struct TypeParameter { pub(crate) name: Constant, pub(crate) requirements: Vec, pub(crate) mutable: bool, + pub(crate) inline: bool, pub(crate) location: Location, } @@ -854,9 +851,8 @@ pub(crate) struct Field { #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct FieldRef { - pub(crate) field_id: Option, + pub(crate) info: Option, pub(crate) name: String, - pub(crate) resolved_type: types::TypeRef, pub(crate) location: Location, pub(crate) in_mut: bool, } @@ -1315,6 +1311,21 @@ impl<'a> LowerToHir<'a> { node: ast::DefineClass, documentation: String, ) -> TopLevelExpression { + if node.inline { + match node.kind { + ast::ClassKind::Enum | ast::ClassKind::Regular => {} + _ => { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + "only regular and 'enum' types support the 'inline' \ + attribute", + self.file(), + node.name.location, + ); + } + } + } + if let ast::ClassKind::Extern = node.kind { return self.define_extern_class(node, documentation); } @@ -1322,6 +1333,7 @@ impl<'a> LowerToHir<'a> { TopLevelExpression::Class(Box::new(DefineClass { documentation, public: node.public, + inline: node.inline, class_id: None, kind: match node.kind { ast::ClassKind::Async => ClassKind::Async, @@ -1562,28 +1574,68 @@ impl<'a> LowerToHir<'a> { fn type_bound(&mut self, node: ast::TypeBound) -> TypeBound { let name = self.constant(node.name); + let (reqs, mutable, inline) = self.define_type_parameter_requirements( + &name.name, + node.requirements.values, + ); + + TypeBound { + name, + requirements: reqs, + mutable, + inline, + location: node.location, + } + } + + fn define_type_parameter_requirements( + &mut self, + name: &str, + nodes: Vec, + ) -> (Vec, bool, bool) { let mut mutable = false; + let mut inline = false; let mut requirements = Vec::new(); - for req in node.requirements.values { + for req in nodes { match req { ast::Requirement::Trait(n) => { requirements.push(self.type_name(n)) } ast::Requirement::Mutable(loc) if mutable => { - self.state.diagnostics.type_parameter_already_mutable( - &name.name, - self.file(), - loc, - ); + let file = self.file(); + + self.state + .diagnostics + .duplicate_type_parameter_requirement( + name, "mut", file, loc, + ); } - ast::Requirement::Mutable(_) => { - mutable = true; + ast::Requirement::Mutable(loc) if inline => { + self.state + .diagnostics + .mutable_inline_type_parameter(self.file(), loc); } + ast::Requirement::Inline(loc) if inline => { + let file = self.file(); + + self.state + .diagnostics + .duplicate_type_parameter_requirement( + name, "mut", file, loc, + ); + } + ast::Requirement::Inline(loc) if mutable => { + self.state + .diagnostics + .mutable_inline_type_parameter(self.file(), loc); + } + ast::Requirement::Mutable(_) => mutable = true, + ast::Requirement::Inline(_) => inline = true, } } - TypeBound { name, requirements, mutable, location: node.location } + (requirements, mutable, inline) } fn define_trait( @@ -1893,35 +1945,19 @@ impl<'a> LowerToHir<'a> { fn type_parameter(&mut self, node: ast::TypeParameter) -> TypeParameter { let name = self.constant(node.name); let location = node.location; - let mut mutable = false; - let mut requirements = Vec::new(); - - if let Some(reqs) = node.requirements { - for req in reqs.values { - match req { - ast::Requirement::Trait(n) => { - requirements.push(self.type_name(n)) - } - ast::Requirement::Mutable(loc) if mutable => { - self.state.diagnostics.type_parameter_already_mutable( - &name.name, - self.file(), - loc, - ); - } - ast::Requirement::Mutable(_) => { - mutable = true; - } - } - } - } + let (reqs, mutable, inline) = if let Some(reqs) = node.requirements { + self.define_type_parameter_requirements(&name.name, reqs.values) + } else { + (Vec::new(), false, false) + }; TypeParameter { type_parameter_id: None, name, - requirements, + requirements: reqs, location, mutable, + inline, } } @@ -2444,9 +2480,8 @@ impl<'a> LowerToHir<'a> { fn field_ref(&self, node: ast::Field) -> Box { Box::new(FieldRef { - field_id: None, + info: None, name: node.name, - resolved_type: types::TypeRef::Unknown, in_mut: false, location: node.location, }) @@ -2526,7 +2561,7 @@ impl<'a> LowerToHir<'a> { DiagnosticId::InvalidCall, "this builtin function call is invalid", self.file(), - node.location, + node.name.location, ); Expression::Nil(Box::new(Nil { @@ -2551,7 +2586,7 @@ impl<'a> LowerToHir<'a> { DiagnosticId::InvalidCall, "builtin calls don't support named arguments", self.file(), - node.location, + node.name.location, ); self.expression(node.value) @@ -2693,9 +2728,8 @@ impl<'a> LowerToHir<'a> { let op = Operator::from_ast(node.operator.kind); let field = self.field(node.field); let receiver = Expression::FieldRef(Box::new(FieldRef { - field_id: None, + info: None, name: field.name.clone(), - resolved_type: types::TypeRef::Unknown, in_mut: false, location: field.location, })); @@ -3752,6 +3786,7 @@ mod tests { location: cols(11, 11) }], mutable: false, + inline: false, location: cols(8, 11) }], arguments: vec![MethodArgument { @@ -3932,6 +3967,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, kind: ClassKind::Regular, class_id: None, name: Constant { name: "A".to_string(), location: cols(7, 7) }, @@ -3952,6 +3988,7 @@ mod tests { location: cols(12, 12) }], mutable: false, + inline: false, location: cols(9, 12) }], body: vec![ClassExpression::Field(Box::new(DefineField { @@ -4018,6 +4055,48 @@ mod tests { ); } + #[test] + fn test_lower_inline_class() { + let hir = lower_top_expr("class inline A { let @a: B }").0; + + assert_eq!( + hir, + TopLevelExpression::Class(Box::new(DefineClass { + documentation: String::new(), + public: false, + inline: true, + class_id: None, + kind: ClassKind::Regular, + name: Constant { + name: "A".to_string(), + location: cols(14, 14) + }, + body: vec![ClassExpression::Field(Box::new(DefineField { + documentation: String::new(), + public: false, + field_id: None, + name: Identifier { + name: "a".to_string(), + location: cols(22, 23) + }, + value_type: Type::Named(Box::new(TypeName { + source: None, + resolved_type: types::TypeRef::Unknown, + name: Constant { + name: "B".to_string(), + location: cols(26, 26) + }, + arguments: Vec::new(), + location: cols(26, 26) + })), + location: cols(18, 26), + }))], + type_parameters: Vec::new(), + location: cols(1, 28) + })), + ); + } + #[test] fn test_lower_public_class() { let hir = lower_top_expr("class pub A {}").0; @@ -4027,6 +4106,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: true, + inline: false, kind: ClassKind::Regular, class_id: None, name: Constant { @@ -4049,6 +4129,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, kind: ClassKind::Regular, class_id: None, name: Constant { name: "A".to_string(), location: cols(7, 7) }, @@ -4087,6 +4168,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, class_id: None, kind: ClassKind::Builtin, name: Constant { @@ -4110,6 +4192,7 @@ mod tests { location: cols(20, 20) }], mutable: false, + inline: false, location: cols(17, 20) }], body: vec![ClassExpression::Field(Box::new(DefineField { @@ -4146,6 +4229,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, class_id: None, kind: ClassKind::Async, name: Constant { @@ -4169,6 +4253,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, class_id: None, kind: ClassKind::Regular, name: Constant { name: "A".to_string(), location: cols(7, 7) }, @@ -4190,6 +4275,7 @@ mod tests { }, requirements: Vec::new(), mutable: false, + inline: false, location: cols(23, 23) }], arguments: vec![MethodArgument { @@ -4243,6 +4329,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, class_id: None, kind: ClassKind::Regular, name: Constant { name: "A".to_string(), location: cols(7, 7) }, @@ -4264,6 +4351,7 @@ mod tests { }, requirements: Vec::new(), mutable: false, + inline: false, location: cols(22, 22) }], arguments: vec![MethodArgument { @@ -4316,6 +4404,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, class_id: None, kind: ClassKind::Regular, name: Constant { name: "A".to_string(), location: cols(7, 7) }, @@ -4338,6 +4427,7 @@ mod tests { }, requirements: Vec::new(), mutable: false, + inline: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -4390,6 +4480,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, class_id: None, kind: ClassKind::Regular, name: Constant { name: "A".to_string(), location: cols(7, 7) }, @@ -4450,6 +4541,7 @@ mod tests { }, requirements: Vec::new(), mutable: false, + inline: false, location: cols(9, 9) }], requirements: vec![TypeName { @@ -4520,6 +4612,7 @@ mod tests { }, requirements: Vec::new(), mutable: false, + inline: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -4659,6 +4752,7 @@ mod tests { }, requirements: Vec::new(), mutable: false, + inline: false, location: cols(16, 16) }], arguments: vec![MethodArgument { @@ -4769,6 +4863,7 @@ mod tests { }, requirements: Vec::new(), mutable: true, + inline: false, location: cols(11, 16), }], body: Vec::new(), @@ -5024,6 +5119,7 @@ mod tests { location: cols(20, 20) },], mutable: true, + inline: false, location: cols(17, 26) }], body: Vec::new(), @@ -5630,9 +5726,8 @@ mod tests { assert_eq!( hir, Expression::FieldRef(Box::new(FieldRef { - field_id: None, + info: None, name: "a".to_string(), - resolved_type: types::TypeRef::Unknown, in_mut: false, location: cols(8, 9) })) @@ -6080,9 +6175,8 @@ mod tests { value: Expression::Call(Box::new(Call { kind: types::CallKind::Unknown, receiver: Some(Expression::FieldRef(Box::new(FieldRef { - field_id: None, + info: None, name: "a".to_string(), - resolved_type: types::TypeRef::Unknown, in_mut: false, location: cols(8, 9) }))), @@ -6682,6 +6776,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, kind: ClassKind::Enum, class_id: None, name: Constant { @@ -6696,6 +6791,7 @@ mod tests { }, requirements: Vec::new(), mutable: false, + inline: false, location: cols(19, 19) }], body: vec![ diff --git a/compiler/src/llvm/builder.rs b/compiler/src/llvm/builder.rs index ae25f4f5e..c8c36f753 100644 --- a/compiler/src/llvm/builder.rs +++ b/compiler/src/llvm/builder.rs @@ -75,7 +75,7 @@ impl<'ctx> Builder<'ctx> { ) -> BasicValueEnum<'ctx> { let field_ptr = self.field_address(receiver_type, receiver, index); - self.inner.build_load(typ, field_ptr, "").unwrap() + self.load(typ, field_ptr) } pub(crate) fn field_address( @@ -172,30 +172,21 @@ impl<'ctx> Builder<'ctx> { &self, variable: PointerValue<'ctx>, ) -> IntValue<'ctx> { - self.inner - .build_load(self.context.i64_type(), variable, "") - .unwrap() - .into_int_value() + self.load(self.context.i64_type(), variable).into_int_value() } pub(crate) fn load_float( &self, variable: PointerValue<'ctx>, ) -> FloatValue<'ctx> { - self.inner - .build_load(self.context.f64_type(), variable, "") - .unwrap() - .into_float_value() + self.load(self.context.f64_type(), variable).into_float_value() } pub(crate) fn load_bool( &self, variable: PointerValue<'ctx>, ) -> IntValue<'ctx> { - self.inner - .build_load(self.context.bool_type(), variable, "") - .unwrap() - .into_int_value() + self.load(self.context.bool_type(), variable).into_int_value() } pub(crate) fn load_pointer( @@ -205,7 +196,7 @@ impl<'ctx> Builder<'ctx> { self.load(self.context.pointer_type(), variable).into_pointer_value() } - pub(crate) fn call( + pub(crate) fn call_with_return( &self, function: FunctionValue<'ctx>, arguments: &[BasicMetadataValueEnum<'ctx>], @@ -227,12 +218,12 @@ impl<'ctx> Builder<'ctx> { self.inner.build_indirect_call(typ, func, args, "").unwrap() } - pub(crate) fn call_void( + pub(crate) fn direct_call( &self, function: FunctionValue<'ctx>, arguments: &[BasicMetadataValueEnum<'ctx>], - ) { - self.inner.build_call(function, arguments, "").unwrap(); + ) -> CallSiteValue<'ctx> { + self.inner.build_call(function, arguments, "").unwrap() } pub(crate) fn pointer_to_int( @@ -335,10 +326,7 @@ impl<'ctx> Builder<'ctx> { &self, variable: PointerValue<'ctx>, ) -> IntValue<'ctx> { - let res = self - .inner - .build_load(self.context.i32_type(), variable, "") - .unwrap(); + let res = self.load(self.context.i32_type(), variable); let ins = res.as_instruction_value().unwrap(); // If the alignment doesn't match the value size, LLVM compiles this to @@ -790,7 +778,7 @@ impl<'ctx> Builder<'ctx> { // The block to jump to when the allocation failed. self.switch_to_block(err_block); - self.call_void(err_func, &[size.into()]); + self.direct_call(err_func, &[size.into()]); self.unreachable(); // The block to jump to when the allocation succeeds. diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index 2ad439d4a..e34967cee 100644 --- a/compiler/src/llvm/context.rs +++ b/compiler/src/llvm/context.rs @@ -1,4 +1,6 @@ -use crate::llvm::layouts::Layouts; +use crate::llvm::layouts::{ArgumentType, Layouts, ReturnType}; +use crate::state::State; +use crate::target::Architecture; use inkwell::attributes::Attribute; use inkwell::basic_block::BasicBlock; use inkwell::builder::Builder; @@ -49,6 +51,10 @@ impl Context { self.inner.bool_type() } + pub(crate) fn custom_int(&self, bits: u32) -> IntType { + self.inner.custom_width_int_type(bits) + } + pub(crate) fn i8_type(&self) -> IntType { self.inner.i8_type() } @@ -185,36 +191,143 @@ impl Context { TypeId::ClassInstance(ins) => { let cls = ins.instance_of(); - if cls.kind(db).is_extern() { - layouts.instances[ins.instance_of().0 as usize] - .as_basic_type_enum() - } else { - match cls.0 { - BOOL_ID | NIL_ID => { - self.bool_type().as_basic_type_enum() - } - INT_ID => self.i64_type().as_basic_type_enum(), - FLOAT_ID => self.f64_type().as_basic_type_enum(), - _ => self.pointer_type().as_basic_type_enum(), + match cls.0 { + BOOL_ID | NIL_ID => self.bool_type().as_basic_type_enum(), + INT_ID => self.i64_type().as_basic_type_enum(), + FLOAT_ID => self.f64_type().as_basic_type_enum(), + _ if cls.is_stack_allocated(db) => { + layouts.instances[cls.0 as usize].as_basic_type_enum() } + _ => self.pointer_type().as_basic_type_enum(), } } _ => self.pointer_type().as_basic_type_enum(), } } - pub(crate) fn return_type<'a>( - &'a self, - db: &Database, - layouts: &Layouts<'a>, + pub(crate) fn argument_type<'ctx>( + &'ctx self, + state: &State, + layouts: &Layouts<'ctx>, + typ: BasicTypeEnum<'ctx>, + ) -> ArgumentType<'ctx> { + let BasicTypeEnum::StructType(typ) = typ else { + return ArgumentType::Regular(typ); + }; + let bytes = layouts.target_data.get_abi_size(&typ) as u32; + + match state.config.target.arch { + Architecture::Amd64 => { + if bytes <= 8 { + let bits = self.custom_int(bytes * 8); + + ArgumentType::Regular(bits.as_basic_type_enum()) + } else if bytes <= 16 { + ArgumentType::Regular( + self.abi_struct_type(layouts, typ).as_basic_type_enum(), + ) + } else { + ArgumentType::StructValue(typ) + } + } + Architecture::Arm64 => { + if bytes <= 8 { + ArgumentType::Regular(self.i64_type().as_basic_type_enum()) + } else if bytes <= 16 { + let word = self.i64_type().into(); + let sret = self.struct_type(&[word, word]); + + ArgumentType::Regular(sret.as_basic_type_enum()) + } else { + // clang and Rust don't use "byval" for ARM64 when the + // struct is too large, so neither do we. + ArgumentType::Pointer + } + } + } + } + + pub(crate) fn method_return_type<'ctx>( + &'ctx self, + state: &State, + layouts: &Layouts<'ctx>, method: MethodId, - ) -> Option> { - if method.returns_value(db) { - Some(self.llvm_type(db, layouts, method.return_type(db))) + ) -> ReturnType<'ctx> { + if method.returns_value(&state.db) { + let typ = self.llvm_type( + &state.db, + layouts, + method.return_type(&state.db), + ); + + self.return_type(state, layouts, typ) } else { - None + ReturnType::None } } + + pub(crate) fn return_type<'ctx>( + &'ctx self, + state: &State, + layouts: &Layouts<'ctx>, + typ: BasicTypeEnum<'ctx>, + ) -> ReturnType<'ctx> { + let BasicTypeEnum::StructType(typ) = typ else { + return ReturnType::Regular(typ); + }; + + let bytes = layouts.target_data.get_abi_size(&typ) as u32; + + match state.config.target.arch { + Architecture::Amd64 => { + if bytes <= 8 { + let bits = self.custom_int(bytes * 8); + + ReturnType::Regular(bits.as_basic_type_enum()) + } else if bytes <= 16 { + ReturnType::Regular( + self.abi_struct_type(layouts, typ).as_basic_type_enum(), + ) + } else { + ReturnType::Struct(typ) + } + } + Architecture::Arm64 => { + if bytes <= 8 { + let bits = self.custom_int(bytes * 8); + + ReturnType::Regular(bits.as_basic_type_enum()) + } else if bytes <= 16 { + let word = self.i64_type().into(); + let sret = self.struct_type(&[word, word]); + + ReturnType::Regular(sret.as_basic_type_enum()) + } else { + ReturnType::Struct(typ) + } + } + } + } + + fn abi_struct_type<'ctx>( + &'ctx self, + layouts: &Layouts<'ctx>, + typ: StructType<'ctx>, + ) -> StructType<'ctx> { + // We need to ensure each field is treated as a single/whole value, + // otherwise (e.g. if a field is an array of bytes) we may end up + // generating code that's not ABI compliant. + let fields: Vec<_> = typ + .get_field_types_iter() + .map(|t| { + let bits = layouts.target_data.get_abi_size(&t) as u32 * 8; + + self.custom_int(bits).as_basic_type_enum() + }) + .collect(); + + self.struct_type(&fields) + } } #[cfg(test)] diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs index 744161a1c..26afc86d4 100644 --- a/compiler/src/llvm/layouts.rs +++ b/compiler/src/llvm/layouts.rs @@ -4,33 +4,168 @@ use crate::state::State; use crate::target::OperatingSystem; use inkwell::targets::TargetData; use inkwell::types::{ - AnyType, BasicMetadataTypeEnum, BasicType, FunctionType, StructType, + BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, StructType, }; +use std::collections::VecDeque; use types::{ - CallConvention, BOOL_ID, BYTE_ARRAY_ID, FLOAT_ID, INT_ID, NIL_ID, STRING_ID, + CallConvention, ClassId, Database, MethodId, TypeId, TypeRef, BOOL_ID, + BYTE_ARRAY_ID, FLOAT_ID, INT_ID, NIL_ID, STRING_ID, }; /// The size of an object header. const HEADER_SIZE: u32 = 16; -fn size_of_type(target_data: &TargetData, typ: &dyn AnyType) -> u64 { - target_data.get_bit_size(typ) - / (target_data.get_pointer_byte_size(None) as u64) +struct Sized { + map: Vec, +} + +impl Sized { + fn new(amount: usize) -> Sized { + Sized { map: vec![false; amount] } + } + + fn has_size(&self, db: &Database, typ: TypeRef) -> bool { + match typ { + TypeRef::Owned(TypeId::ClassInstance(ins)) + | TypeRef::Uni(TypeId::ClassInstance(ins)) => { + let cls = ins.instance_of(); + + cls.is_heap_allocated(db) || self.map[cls.0 as usize] + } + // Everything else (e.g. borrows) uses pointers and thus have a + // known size. + _ => true, + } + } + + fn set_has_size(&mut self, id: ClassId) { + self.map[id.0 as usize] = true; + } } #[derive(Copy, Clone)] -pub(crate) struct Method<'ctx> { - pub(crate) signature: FunctionType<'ctx>, +pub(crate) enum ArgumentType<'ctx> { + /// The argument should be passed as a normal value. + Regular(BasicTypeEnum<'ctx>), + + /// The argument should be passed as a pointer. + Pointer, + + /// The argument should be a pointer to a struct that's passed using the + /// "byval" attribute. + StructValue(StructType<'ctx>), + + /// The argument is the struct return argument. + StructReturn(StructType<'ctx>), +} + +#[derive(Copy, Clone)] +pub(crate) enum ReturnType<'ctx> { + /// The function doesn't return anything. + None, + + /// The function returns a regular value. + Regular(BasicTypeEnum<'ctx>), + /// The function returns a structure using the ABIs struct return + /// convention. + Struct(StructType<'ctx>), +} + +impl<'ctx> ReturnType<'ctx> { + pub(crate) fn is_struct(self) -> bool { + matches!(self, ReturnType::Struct(_)) + } + + pub(crate) fn is_regular(self) -> bool { + matches!(self, ReturnType::Regular(_)) + } +} + +#[derive(Clone)] +pub(crate) struct Method<'ctx> { /// The calling convention to use for this method. pub(crate) call_convention: CallConvention, - /// If the function returns a structure on the stack, its type is stored - /// here. - /// - /// This is needed separately because the signature's return type will be - /// `void` in this case. - pub(crate) struct_return: Option>, + /// If the method is a variadic method or not. + pub(crate) variadic: bool, + + /// The return type, if any. + pub(crate) returns: ReturnType<'ctx>, + + /// The types of the arguments. + pub(crate) arguments: Vec>, +} + +impl<'ctx> Method<'ctx> { + pub(crate) fn new() -> Method<'ctx> { + Method { + call_convention: CallConvention::Inko, + variadic: false, + returns: ReturnType::None, + arguments: Vec::new(), + } + } + + pub(crate) fn regular( + state: &State, + context: &'ctx Context, + layouts: &Layouts<'ctx>, + method: MethodId, + ) -> Method<'ctx> { + let db = &state.db; + let ret = context.method_return_type(state, layouts, method); + let mut args = if let ReturnType::Struct(t) = ret { + vec![ArgumentType::StructReturn(t)] + } else { + Vec::new() + }; + + for &typ in method + .is_instance(db) + .then(|| method.receiver(db)) + .iter() + .chain(method.argument_types(db)) + { + let raw = context.llvm_type(db, layouts, typ); + let typ = context.argument_type(state, layouts, raw); + + args.push(typ); + } + + Method { + call_convention: CallConvention::new(method.is_extern(db)), + variadic: method.is_variadic(db), + arguments: args, + returns: ret, + } + } + + pub(crate) fn signature( + &self, + context: &'ctx Context, + ) -> FunctionType<'ctx> { + let var = self.variadic; + let mut args: Vec = Vec::new(); + + for &arg in &self.arguments { + match arg { + ArgumentType::Regular(t) => args.push(t.into()), + ArgumentType::StructValue(_) + | ArgumentType::StructReturn(_) + | ArgumentType::Pointer => { + args.push(context.pointer_type().into()) + } + } + } + + match self.returns { + ReturnType::None | ReturnType::Struct(_) => { + context.void_type().fn_type(&args, var) + } + ReturnType::Regular(t) => t.fn_type(&args, var), + } + } } /// Types and layout information to expose to all modules. @@ -148,12 +283,6 @@ impl<'ctx> Layouts<'ctx> { // optimizations, but that's OK as in the worst case we just waste a few // KiB. let num_methods = db.number_of_methods(); - let dummy_method = Method { - call_convention: CallConvention::Inko, - signature: context.void_type().fn_type(&[], false), - struct_return: None, - }; - let mut layouts = Self { target_data, empty_class: context.class_type(method), @@ -162,10 +291,26 @@ impl<'ctx> Layouts<'ctx> { state: state_layout, header, method_counts: method_counts_layout, - methods: vec![dummy_method; num_methods], + methods: vec![Method::new(); num_methods], process_stack_data: stack_data_layout, }; + // The order here is important: types must come first, then the dynamic + // calls, then the methods (as those depend on the types). + layouts.define_types(state, mir, context); + layouts.define_dynamic_calls(state, mir, context); + layouts.define_methods(state, mir, context); + layouts + } + + fn define_types( + &mut self, + state: &State, + mir: &Mir, + context: &'ctx Context, + ) { + let db = &state.db; + let num_classes = db.number_of_classes(); let process_size = match state.config.target.os { OperatingSystem::Linux | OperatingSystem::Freebsd => { // Mutexes are smaller on Linux, resulting in a smaller process @@ -181,31 +326,103 @@ impl<'ctx> Layouts<'ctx> { // stable). let process_private_size = process_size - HEADER_SIZE; - for id in mir.classes.keys() { + // Type A might depend on type B, which in turn may depend on type C. + // This means that to calculate the size of A, we'd first have to + // calculate it of B and C. We do this using a work list approach: if + // the size of a type can be calculated immediately we do just that, + // otherwise we reschedule it for later (re)processing. + let mut queue = mir.classes.keys().cloned().collect::>(); + let mut sized = Sized::new(num_classes); + + // These types have a fixed size and don't define any fields. To ensure + // the work loop terminates, we manually flag them as known. + for id in [BYTE_ARRAY_ID, INT_ID, FLOAT_ID, BOOL_ID, NIL_ID] { + sized.set_has_size(ClassId(id as _)); + } + + while let Some(id) = queue.pop_front() { + let kind = id.kind(db); + // String is a built-in class, but it's defined like a regular one, // so we _don't_ want to skip it here. if id.is_builtin() && id.0 != STRING_ID { continue; } - let layout = layouts.instances[id.0 as usize]; - let kind = id.kind(db); + // If the type we're checking is _not_ a class instance then we + // default to _true_ instead of false, since this means we can + // trivially calculate the size of that field (e.g. it's a pointer). + let size_known = if kind.is_enum() { + id.constructors(db).into_iter().all(|c| { + c.arguments(db).iter().all(|&t| sized.has_size(db, t)) + }) + } else { + id.fields(db) + .into_iter() + .all(|f| sized.has_size(db, f.value_type(db))) + }; + + if !size_known { + queue.push_back(id); + continue; + } + + if kind.is_enum() { + let layout = self.instances[id.0 as usize]; + let fields = id.fields(db); + let mut types = Vec::with_capacity(fields.len() + 1); + + if id.has_object_header(db) { + types.push(self.header.into()); + } + + // Add the type for the tag. + let tag_typ = fields[0].value_type(db); + + types.push(context.llvm_type(db, self, tag_typ)); + + // For each constructor argument we generate a field with an + // opaque type. The size of this type must equal that of the + // largest type. + let mut opaque = + vec![ + context.i8_type().array_type(0).as_basic_type_enum(); + fields.len() - 1 + ]; + + for con in id.constructors(db) { + for (idx, &typ) in con.arguments(db).iter().enumerate() { + let llvm_typ = context.llvm_type(db, self, typ); + let size = self.target_data.get_abi_size(&llvm_typ); + let ex = self.target_data.get_abi_size(&opaque[idx]); + + if size > ex { + opaque[idx] = context + .i8_type() + .array_type(size as _) + .as_basic_type_enum(); + } + } + } + + types.append(&mut opaque); + sized.set_has_size(id); + layout.set_body(&types, false); + continue; + } + + let layout = self.instances[id.0 as usize]; let fields = id.fields(db); // We add 1 to account for the header that almost all classes // processed here will have. let mut types = Vec::with_capacity(fields.len() + 1); - if kind.is_extern() { - for field in fields { - let typ = - context.llvm_type(db, &layouts, field.value_type(db)); - - types.push(typ); - } - } else { - types.push(header.into()); + if id.has_object_header(db) { + types.push(self.header.into()); + } + if kind.is_async() { // For processes, the memory layout is as follows: // // +--------------------------+ @@ -215,209 +432,69 @@ impl<'ctx> Layouts<'ctx> { // +--------------------------+ // | user-defined fields | // +--------------------------+ - if kind.is_async() { - types.push( - context - .i8_type() - .array_type(process_private_size) - .into(), - ); - } - - if kind.is_enum() { - // For enums we generate a base/opaque layout for when the - // constructor isn't known, and one layout for each - // constructor. - types.push(context.llvm_type( - db, - &layouts, - fields[0].value_type(db), - )); - - let cons = id.constructors(db); - let mut opaque_types = vec![ - context - .i8_type() - .array_type(1) - .as_basic_type_enum(); - fields.len() - 1 - ]; - - for con in &cons { - for (idx, typ) in - con.members(db).into_iter().enumerate() - { - let typ = context.llvm_type(db, &layouts, typ); - let size = size_of_type(target_data, &typ); - let ex = - size_of_type(target_data, &opaque_types[idx]); - - if size > ex { - opaque_types[idx] = context - .i8_type() - .array_type(size as _) - .as_basic_type_enum(); - } - } - } + types.push( + context.i8_type().array_type(process_private_size).into(), + ); + } - types.append(&mut opaque_types); - } else { - for field in fields { - let typ = context.llvm_type( - db, - &layouts, - field.value_type(db), - ); - - types.push(typ); - } - } + for field in fields { + types.push(context.llvm_type(db, self, field.value_type(db))); } layout.set_body(&types, false); + sized.set_has_size(id); } + } + fn define_dynamic_calls( + &mut self, + state: &State, + mir: &Mir, + context: &'ctx Context, + ) { for calls in mir.dynamic_calls.values() { - for (method, _) in calls { - let mut args: Vec = vec![ - context.pointer_type().into(), // Receiver - ]; - - for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); - } - - let signature = context - .return_type(db, &layouts, *method) - .map(|t| t.fn_type(&args, false)) - .unwrap_or_else(|| { - context.void_type().fn_type(&args, false) - }); - - layouts.methods[method.0 as usize] = Method { - call_convention: CallConvention::new(method.is_extern(db)), - signature, - struct_return: None, - }; + for (id, _) in calls { + self.methods[id.0 as usize] = + Method::regular(state, context, self, *id); } } + } - // Now that all the LLVM structs are defined, we can process all - // methods. - for mir_class in mir.classes.values() { - // Define the method signatures once (so we can cheaply retrieve - // them whenever needed), and assign the methods to their method - // table slots. - for &method in &mir_class.methods { - let typ = if method.is_async(db) { - context - .void_type() - .fn_type(&[context.pointer_type().into()], false) - } else { - let mut args: Vec = Vec::new(); - - // For instance methods, the receiver is passed as an - // explicit argument before any user-defined arguments. - if method.is_instance(db) { - args.push( - context - .llvm_type(db, &layouts, method.receiver(db)) - .into(), - ); - } + fn define_methods( + &mut self, + state: &State, + mir: &Mir, + context: &'ctx Context, + ) { + let db = &state.db; - for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); + for mir_class in mir.classes.values() { + for &id in &mir_class.methods { + self.methods[id.0 as usize] = if id.is_async(db) { + let args = vec![ArgumentType::Regular( + context.pointer_type().as_basic_type_enum(), + )]; + + Method { + call_convention: CallConvention::Inko, + variadic: false, + arguments: args, + returns: ReturnType::None, } - - context - .return_type(db, &layouts, method) - .map(|t| t.fn_type(&args, false)) - .unwrap_or_else(|| { - context.void_type().fn_type(&args, false) - }) - }; - - layouts.methods[method.0 as usize] = Method { - call_convention: CallConvention::new(method.is_extern(db)), - signature: typ, - struct_return: None, + } else { + Method::regular(state, context, self, id) }; } } - for &method in mir.methods.keys().filter(|m| m.is_static(db)) { - let mut args: Vec = Vec::new(); - - for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); - } - - let typ = context - .return_type(db, &layouts, method) - .map(|t| t.fn_type(&args, false)) - .unwrap_or_else(|| context.void_type().fn_type(&args, false)); - - layouts.methods[method.0 as usize] = Method { - call_convention: CallConvention::new(method.is_extern(db)), - signature: typ, - struct_return: None, - }; + for &id in mir.methods.keys().filter(|m| m.is_static(db)) { + self.methods[id.0 as usize] = + Method::regular(state, context, self, id); } - for &method in &mir.extern_methods { - let mut args: Vec = - Vec::with_capacity(method.number_of_arguments(db) + 1); - - // The regular return type, and the type of the structure to pass - // with the `sret` attribute. If `ret` is `None`, it means the - // function returns `void`. If `sret` is `None`, it means the - // function doesn't return a struct. - let mut ret = None; - let mut sret = None; - - if let Some(typ) = context.return_type(db, &layouts, method) { - // The C ABI mandates that structures are either passed through - // registers (if small enough), or using a pointer. LLVM doesn't - // detect when this is needed for us, so sadly we (and everybody - // else using LLVM) have to do this ourselves. - // - // In the future we may want/need to also handle this for Inko - // methods, but for now they always return pointers. - if typ.is_struct_type() { - let typ = typ.into_struct_type(); - - if target_data.get_bit_size(&typ) - > state.config.target.pass_struct_size() - { - args.push(context.pointer_type().into()); - sret = Some(typ); - } else { - ret = Some(typ.as_basic_type_enum()); - } - } else { - ret = Some(typ); - } - } - - for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); - } - - let variadic = method.is_variadic(db); - let sig = - ret.map(|t| t.fn_type(&args, variadic)).unwrap_or_else(|| { - context.void_type().fn_type(&args, variadic) - }); - - layouts.methods[method.0 as usize] = Method { - call_convention: CallConvention::C, - signature: sig, - struct_return: sret, - }; + for &id in &mir.extern_methods { + self.methods[id.0 as usize] = + Method::regular(state, context, self, id); } - - layouts } } diff --git a/compiler/src/llvm/methods.rs b/compiler/src/llvm/methods.rs index ba961b3d2..38373dd24 100644 --- a/compiler/src/llvm/methods.rs +++ b/compiler/src/llvm/methods.rs @@ -1,8 +1,8 @@ use crate::llvm::constants::{CLOSURE_CALL_INDEX, DROPPER_INDEX}; use crate::llvm::method_hasher::MethodHasher; use crate::mir::Mir; +use crate::symbol_names::format_shapes; use std::cmp::max; -use std::fmt::Write as _; use types::{Database, MethodId, Shape, CALL_METHOD, DROPPER_METHOD}; /// Method table sizes are multiplied by this value in an attempt to reduce the @@ -45,10 +45,7 @@ fn round_methods(mut value: usize) -> usize { fn hash_key(db: &Database, method: MethodId, shapes: &[Shape]) -> String { let mut key = method.name(db).clone(); - for shape in shapes { - let _ = write!(key, "{}", shape); - } - + format_shapes(db, shapes, &mut key); key } diff --git a/compiler/src/llvm/module.rs b/compiler/src/llvm/module.rs index afc0cc11e..6a8ec2388 100644 --- a/compiler/src/llvm/module.rs +++ b/compiler/src/llvm/module.rs @@ -1,6 +1,6 @@ use crate::llvm::builder::DebugBuilder; use crate::llvm::context::Context; -use crate::llvm::layouts::Layouts; +use crate::llvm::layouts::{ArgumentType, Layouts}; use crate::llvm::runtime_function::RuntimeFunction; use crate::symbol_names::SYMBOL_PREFIX; use inkwell::attributes::AttributeLoc; @@ -100,7 +100,8 @@ impl<'a, 'ctx> Module<'a, 'ctx> { ) -> FunctionValue<'ctx> { self.inner.get_function(name).unwrap_or_else(|| { let info = &self.layouts.methods[method.0 as usize]; - let func = self.inner.add_function(name, info.signature, None); + let fn_typ = info.signature(self.context); + let fn_val = self.inner.add_function(name, fn_typ, None); let conv = match info.call_convention { // LLVM uses 0 for the C calling convention. CallConvention::C => 0, @@ -110,19 +111,33 @@ impl<'a, 'ctx> Module<'a, 'ctx> { CallConvention::Inko => 0, }; - func.set_call_conventions(conv); - - if let Some(typ) = info.struct_return { - let sret = self.context.type_attribute("sret", typ.into()); - let noalias = self.context.enum_attribute("noalias", 0); - let nocapt = self.context.enum_attribute("nocapture", 0); - - func.add_attribute(AttributeLoc::Param(0), sret); - func.add_attribute(AttributeLoc::Param(0), noalias); - func.add_attribute(AttributeLoc::Param(0), nocapt); + fn_val.set_call_conventions(conv); + + for (idx, &arg) in info.arguments.iter().enumerate() { + match arg { + ArgumentType::StructValue(t) => { + fn_val.add_attribute( + AttributeLoc::Param(idx as _), + self.context.type_attribute("byval", t.into()), + ); + } + ArgumentType::StructReturn(t) => { + let loc = AttributeLoc::Param(0); + let sret = + self.context.type_attribute("sret", t.into()); + let noalias = self.context.enum_attribute("noalias", 0); + let nocapt = + self.context.enum_attribute("nocapture", 0); + + fn_val.add_attribute(loc, sret); + fn_val.add_attribute(loc, noalias); + fn_val.add_attribute(loc, nocapt); + } + _ => {} + } } - func + fn_val }) } diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index e24be73fb..c5af272be 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -8,7 +8,9 @@ use crate::llvm::constants::{ STACK_DATA_EPOCH_INDEX, STACK_DATA_PROCESS_INDEX, STATE_EPOCH_INDEX, }; use crate::llvm::context::Context; -use crate::llvm::layouts::Layouts; +use crate::llvm::layouts::{ + ArgumentType, Layouts, Method as MethodLayout, ReturnType, +}; use crate::llvm::methods::Methods; use crate::llvm::module::Module; use crate::llvm::runtime_function::RuntimeFunction; @@ -20,6 +22,7 @@ use crate::state::State; use crate::symbol_names::{SymbolNames, STACK_MASK_GLOBAL, STATE_GLOBAL}; use crate::target::Architecture; use blake3::{hash, Hasher}; +use inkwell::attributes::AttributeLoc; use inkwell::basic_block::BasicBlock; use inkwell::debug_info::AsDIScope as _; use inkwell::module::Linkage; @@ -28,9 +31,7 @@ use inkwell::targets::{ CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple, }; -use inkwell::types::{ - BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FunctionType, -}; +use inkwell::types::{BasicType, BasicTypeEnum, FunctionType}; use inkwell::values::{ BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, FunctionValue, GlobalValue, IntValue, PointerValue, @@ -374,6 +375,11 @@ pub(crate) struct CompileResult { pub(crate) timings: Vec<(ModuleName, Duration)>, } +enum CallKind<'ctx> { + Direct(FunctionValue<'ctx>), + Indirect(FunctionType<'ctx>, PointerValue<'ctx>), +} + /// The state shared between worker threads. struct SharedState<'a> { state: &'a State, @@ -538,16 +544,14 @@ impl<'a> Worker<'a> { let layout = layouts.target_data.get_data_layout(); let opts = PassBuilderOptions::create(); let passes = if let Opt::Aggressive = self.shared.state.config.opt { - &["default"] + ["default"].join(",") } else { - &["mem2reg"] + ["mem2reg"].join(",") }; module.set_data_layout(&layout); module.set_triple(&self.machine.get_triple()); - module - .run_passes(passes.join(",").as_str(), &self.machine, opts) - .unwrap(); + module.run_passes(passes.as_str(), &self.machine, opts).unwrap(); } fn write_object_file( @@ -679,7 +683,10 @@ impl<'shared, 'module, 'ctx> LowerModule<'shared, 'module, 'ctx> { ); builder - .call(class_new, &[name_ptr, size.into(), methods_len]) + .call_with_return( + class_new, + &[name_ptr, size.into(), methods_len], + ) .into_pointer_value() } }; @@ -892,7 +899,7 @@ impl<'shared, 'module, 'ctx> LowerModule<'shared, 'module, 'ctx> { let len = builder.u64_literal(value.len() as u64).into(); let func = self.module.runtime_function(RuntimeFunction::StringNew); - builder.call(func, &[state.into(), bytes_var.into(), len]) + builder.call_with_return(func, &[state.into(), bytes_var.into(), len]) } fn load_state(&mut self, builder: &Builder<'ctx>) -> PointerValue<'ctx> { @@ -921,6 +928,10 @@ pub struct LowerMethod<'shared, 'module, 'ctx> { /// The LLVM types for each MIR register. variable_types: HashMap>, + + /// The pointer to write structs to when performing an ABI compliant + /// structure return. + struct_return_value: Option>, } impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { @@ -933,6 +944,14 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let function = module.add_method(&shared.names.methods[&method.id], method.id); let builder = Builder::new(module.context, function); + let entry_block = builder.add_block(); + + builder.switch_to_block(entry_block); + + let sret = layouts.methods[method.id.0 as usize] + .returns + .is_struct() + .then(|| builder.argument(0).into_pointer_value()); LowerMethod { shared, @@ -942,6 +961,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { builder, variables: HashMap::new(), variable_types: HashMap::new(), + struct_return_value: sret, } } @@ -954,15 +974,29 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { } fn regular_method(&mut self) { - let entry_block = self.builder.add_block(); - - self.builder.switch_to_block(entry_block); self.define_register_variables(); + // When returning structs, the first argument is a pointer to write the + // data to, instead of the receiver. + let off = self.struct_return_value.is_some() as usize; + for (arg, reg) in - self.builder.arguments().zip(self.method.arguments.iter()) + self.builder.arguments().skip(off).zip(self.method.arguments.iter()) { - self.builder.store(self.variables[reg], arg); + let var = self.variables[reg]; + let typ = self.variable_types[reg]; + + // Depending on the ABI requirements we may pass a struct in as a + // pointer, but expect it as a value. In this case we need to load + // the argument pointer's value into the stack slot, instead of + // loading the argument as-is. + if typ.is_struct_type() && arg.is_pointer_value() { + let val = self.builder.load(typ, arg.into_pointer_value()); + + self.builder.store(var, val); + } else { + self.builder.store(var, arg); + } } let debug_func = self.module.debug_builder.new_function( @@ -976,9 +1010,6 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { } fn async_method(&mut self) { - let entry_block = self.builder.add_block(); - - self.builder.switch_to_block(entry_block); self.define_register_variables(); let arg_types = self @@ -1003,10 +1034,13 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { // Populate the argument stack variables according to the values stored // in the context structure. + let args = self.builder.load_pointer(args_var); + for (index, reg) in self.method.arguments.iter().skip(1).enumerate() { let var = self.variables[reg]; - let args = self.builder.load_pointer(args_var); - let val = self.builder.load_field(args_type, args, index as _); + let typ = self.variable_types[reg]; + let val = + self.builder.load_field_as(args_type, args, index as _, typ); self.builder.store(var, val); } @@ -1230,7 +1264,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let res = self .builder - .call(func, &[val.into()]) + .call_with_return(func, &[val.into()]) .into_float_value(); self.builder.store(reg_var, res); @@ -1246,7 +1280,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let res = self .builder - .call(func, &[val.into()]) + .call_with_return(func, &[val.into()]) .into_float_value(); self.builder.store(reg_var, res); @@ -1334,7 +1368,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let pos_val = self .builder - .call(fabs, &[val.into()]) + .call_with_return(fabs, &[val.into()]) .into_float_value(); let pos_inf = self.builder.f64_literal(f64::INFINITY); @@ -1361,7 +1395,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let res = self .builder - .call(func, &[val.into()]) + .call_with_return(func, &[val.into()]) .into_float_value(); self.builder.store(reg_var, res); @@ -1383,7 +1417,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let res = self .builder - .call(func, &[lhs.into(), rhs.into()]) + .call_with_return(func, &[lhs.into(), rhs.into()]) .into_float_value(); self.builder.store(reg_var, res); @@ -1400,7 +1434,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { ); let res = self .builder - .call(func, &[lhs, lhs, rhs]) + .call_with_return(func, &[lhs, lhs, rhs]) .into_int_value(); self.builder.store(reg_var, res); @@ -1417,7 +1451,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { ); let res = self .builder - .call(func, &[lhs, lhs, rhs]) + .call_with_return(func, &[lhs, lhs, rhs]) .into_int_value(); self.builder.store(reg_var, res); @@ -1495,7 +1529,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let res = self .builder - .call(add, &[lhs.into(), rhs.into()]) + .call_with_return(add, &[lhs.into(), rhs.into()]) .into_struct_value(); self.builder.store(reg_var, res); @@ -1513,7 +1547,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let res = self .builder - .call(add, &[lhs.into(), rhs.into()]) + .call_with_return(add, &[lhs.into(), rhs.into()]) .into_struct_value(); self.builder.store(reg_var, res); @@ -1531,7 +1565,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let res = self .builder - .call(add, &[lhs.into(), rhs.into()]) + .call_with_return(add, &[lhs.into(), rhs.into()]) .into_struct_value(); self.builder.store(reg_var, res); @@ -1557,7 +1591,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.module.intrinsic("llvm.bswap", &[val_typ]); let swapped = self .builder - .call(fun, &[val.into()]) + .call_with_return(fun, &[val.into()]) .into_int_value(); let res = self.builder.int_to_int(swapped, 64, signed); @@ -1575,7 +1609,10 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let no_poison = self.builder.bool_literal(false); let res = self .builder - .call(fun, &[val.into(), no_poison.into()]) + .call_with_return( + fun, + &[val.into(), no_poison.into()], + ) .into_int_value(); self.builder.store(reg_var, res); @@ -1601,7 +1638,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let func = self.module.runtime_function(func_name); let proc = self.load_process().into(); - self.builder.call_void(func, &[proc, val.into()]); + self.builder.direct_call(func, &[proc, val.into()]); self.builder.unreachable(); } Intrinsic::StringConcat => { @@ -1628,7 +1665,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let state = self.load_state(); let func_name = RuntimeFunction::StringConcat; let func = self.module.runtime_function(func_name); - let res = self.builder.call( + let res = self.builder.call_with_return( func, &[state.into(), temp_var.into(), len.into()], ); @@ -1656,7 +1693,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let func = self .module .intrinsic("llvm.x86.sse2.pause", &[]); - self.builder.call_void(func, &[]); + self.builder.direct_call(func, &[]); } Architecture::Arm64 => { // For ARM64 we use the same approach as Rust by @@ -1666,7 +1703,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { .module .intrinsic("llvm.aarch64.isb", &[]); - self.builder.call_void(func, &[sy.into()]); + self.builder.direct_call(func, &[sy.into()]); } }; @@ -1690,10 +1727,31 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { } Instruction::Return(ins) => { let var = self.variables[&ins.register]; - let typ = self.variable_types[&ins.register]; - let val = self.builder.load(typ, var); - self.builder.return_value(Some(&val)); + if let Some(ptr) = self.struct_return_value { + let typ = self.variable_types[&ins.register]; + let val = self.builder.load(typ, var); + + self.builder.store(ptr, val); + self.builder.return_value(None); + } else { + // When returning a struct on the stack, the return type + // will be structurally compatible but might be nominally + // different. + // + // For example, if the struct is `{ i64 }` we may + // in fact return a value of type `i64`. While both have the + // same layout, they're not compatible at the LLVM level. + let typ = self + .builder + .function + .get_type() + .get_return_type() + .unwrap(); + let val = self.builder.load(typ, var); + + self.builder.return_value(Some(&val)); + } } Instruction::Branch(ins) => { let var = self.variables[&ins.condition]; @@ -1761,85 +1819,38 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { Instruction::CallExtern(ins) => { self.set_debug_location(ins.location); - let func_name = ins.method.name(&self.shared.state.db); - let func = self.module.add_method(func_name, ins.method); - let mut args: Vec = - Vec::with_capacity(ins.arguments.len() + 1); - - let sret = if let Some(typ) = - self.layouts.methods[ins.method.0 as usize].struct_return - { - let var = self.builder.new_stack_slot(typ); - - args.push(var.into()); - Some((typ, var)) - } else { - None - }; - - for ® in &ins.arguments { - let typ = self.variable_types[®]; - let var = self.variables[®]; - - args.push(self.builder.load(typ, var).into()); - } - - if func.get_type().get_return_type().is_some() { - let var = self.variables[&ins.register]; - - self.builder.store(var, self.builder.call(func, &args)); - } else { - self.builder.call_void(func, &args); - - if let Some((typ, temp)) = sret { - let ret = self.variables[&ins.register]; - - self.builder.store(ret, self.builder.load(typ, temp)); - } + let name = ins.method.name(&self.shared.state.db); + let fn_val = self.module.add_method(name, ins.method); + let kind = CallKind::Direct(fn_val); + let layout = &self.layouts.methods[ins.method.0 as usize]; - if self - .register_type(ins.register) - .is_never(&self.shared.state.db) - { - self.builder.unreachable(); - } - } + self.call(kind, layout, ins.register, None, &ins.arguments) } Instruction::CallStatic(ins) => { self.set_debug_location(ins.location); let func_name = &self.shared.names.methods[&ins.method]; let func = self.module.add_method(func_name, ins.method); - let mut args: Vec = - Vec::with_capacity(ins.arguments.len()); - - for reg in &ins.arguments { - let var = self.variables[reg]; - let typ = self.variable_types[reg]; + let kind = CallKind::Direct(func); + let layout = &self.layouts.methods[ins.method.0 as usize]; - args.push(self.builder.load(typ, var).into()); - } - - self.call(ins.register, func, &args); + self.call(kind, layout, ins.register, None, &ins.arguments); } Instruction::CallInstance(ins) => { self.set_debug_location(ins.location); - let rec_var = self.variables[&ins.receiver]; - let rec_typ = self.variable_types[&ins.receiver]; - let func_name = &self.shared.names.methods[&ins.method]; - let func = self.module.add_method(func_name, ins.method); - let mut args: Vec = - vec![self.builder.load(rec_typ, rec_var).into()]; - - for reg in &ins.arguments { - let typ = self.variable_types[reg]; - let var = self.variables[reg]; - - args.push(self.builder.load(typ, var).into()); - } + let name = &self.shared.names.methods[&ins.method]; + let func = self.module.add_method(name, ins.method); + let kind = CallKind::Direct(func); + let layout = &self.layouts.methods[ins.method.0 as usize]; - self.call(ins.register, func, &args); + self.call( + kind, + layout, + ins.register, + Some(ins.receiver), + &ins.arguments, + ); } Instruction::CallDynamic(ins) => { self.set_debug_location(ins.location); @@ -1857,8 +1868,8 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let rec_typ = self.variable_types[&ins.receiver]; let rec = self.builder.load(rec_typ, rec_var); let info = &self.shared.methods.info[ins.method.0 as usize]; - let fn_typ = - self.layouts.methods[ins.method.0 as usize].signature; + let layout = &self.layouts.methods[ins.method.0 as usize]; + let fn_typ = layout.signature(self.builder.context); let rec_class = self .builder .load_field( @@ -1947,36 +1958,49 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { fn_var, self.builder.extract_field(method, METHOD_FUNCTION_INDEX), ); + let fn_val = self.builder.load_pointer(fn_var); + let kind = CallKind::Indirect(fn_typ, fn_val); - let mut args: Vec = vec![rec.into()]; - - for reg in &ins.arguments { - let typ = self.variable_types[reg]; - let var = self.variables[reg]; - - args.push(self.builder.load(typ, var).into()); - } - - let func_val = self.builder.load_pointer(fn_var); - - self.indirect_call(ins.register, fn_typ, func_val, &args); + self.call( + kind, + layout, + ins.register, + Some(ins.receiver), + &ins.arguments, + ); } Instruction::CallClosure(ins) => { self.set_debug_location(ins.location); let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; + let reg_typ = self.variable_types[&ins.register]; // For closures we generate the signature on the fly, as the // method for `call` isn't always clearly defined: for an // argument typed as a closure, we don't know what the actual // method is, thus we can't retrieve an existing signature. - let mut sig_args: Vec = vec![ - self.builder.context.pointer_type().into(), // Closure - ]; + let mut layout = MethodLayout::new(); + + layout.returns = self.builder.context.return_type( + self.shared.state, + self.layouts, + reg_typ, + ); - for reg in &ins.arguments { - sig_args.push(self.variable_types[reg].into()); + if let ReturnType::Struct(t) = layout.returns { + layout.arguments.push(ArgumentType::StructReturn(t)) + } + + for reg in [ins.receiver].iter().chain(ins.arguments.iter()) { + let raw = self.variable_types[reg]; + let typ = self.builder.context.argument_type( + self.shared.state, + self.layouts, + raw, + ); + + layout.arguments.push(typ); } // Load the method from the method table. @@ -1990,15 +2014,6 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { ) .into_pointer_value(); - let mut args: Vec = vec![rec.into()]; - - for reg in &ins.arguments { - let typ = self.variable_types[reg]; - let var = self.variables[reg]; - - args.push(self.builder.load(typ, var).into()); - } - let slot = self.builder.u32_literal(CLOSURE_CALL_INDEX); let method_addr = self.builder.array_field_index_address( self.layouts.empty_class, @@ -2011,25 +2026,35 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { .builder .load(self.layouts.method, method_addr) .into_struct_value(); - - let func_val = self + let fn_val = self .builder .extract_field(method, METHOD_FUNCTION_INDEX) .into_pointer_value(); - - let func_type = self.variable_types[&ins.register] - .fn_type(&sig_args, false); - - self.indirect_call(ins.register, func_type, func_val, &args); + let fn_type = layout.signature(self.builder.context); + let kind = CallKind::Indirect(fn_type, fn_val); + + self.call( + kind, + &layout, + ins.register, + Some(ins.receiver), + &ins.arguments, + ); } Instruction::CallDropper(ins) => { self.set_debug_location(ins.location); + let reg_typ = self.variable_types[&ins.register]; let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; - let sig_args: Vec = vec![ - self.builder.context.pointer_type().into(), // Receiver - ]; + let mut layout = MethodLayout::new(); + + layout.returns = ReturnType::Regular(reg_typ); + layout.arguments.push(self.builder.context.argument_type( + self.shared.state, + self.layouts, + rec_typ, + )); let rec = self.builder.load(rec_typ, rec_var); let class = self @@ -2040,31 +2065,25 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { HEADER_CLASS_INDEX, ) .into_pointer_value(); - - let args: Vec = vec![rec.into()]; - let slot = self.builder.u32_literal(DROPPER_INDEX); - let method_addr = self.builder.array_field_index_address( + let addr = self.builder.array_field_index_address( self.layouts.empty_class, class, CLASS_METHODS_INDEX, slot, ); - let method = self .builder - .load(self.layouts.method, method_addr) + .load(self.layouts.method, addr) .into_struct_value(); - - let func_val = self + let fn_val = self .builder .extract_field(method, METHOD_FUNCTION_INDEX) .into_pointer_value(); + let fn_typ = layout.signature(self.builder.context); + let kind = CallKind::Indirect(fn_typ, fn_val); - let func_type = self.variable_types[&ins.register] - .fn_type(&sig_args, false); - - self.indirect_call(ins.register, func_type, func_val, &args); + self.call(kind, &layout, ins.register, Some(ins.receiver), &[]); } Instruction::Send(ins) => { self.set_debug_location(ins.location); @@ -2111,58 +2130,72 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let sender = self.load_process().into(); let rec = self.builder.load(rec_typ, rec_var).into(); - self.builder.call_void( + self.builder.direct_call( send_message, &[state.into(), sender, rec, method, args.into()], ); } Instruction::GetField(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => + if ins.class.is_heap_allocated(&self.shared.state.db) => { let reg_var = self.variables[&ins.register]; + let reg_typ = self.variable_types[&ins.register]; let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; - let layout = self.layouts.instances[ins.class.0 as usize]; - let index = ins.field.index(&self.shared.state.db) as u32; - let field = if rec_typ.is_pointer_type() { - let rec = self - .builder - .load(rec_typ, rec_var) - .into_pointer_value(); - - self.builder.load_field(layout, rec, index) + let class_kind = ins.class.kind(&self.shared.state.db); + let base = if class_kind.is_async() { + PROCESS_FIELD_OFFSET } else { - let rec = - self.builder.load(rec_typ, rec_var).into_struct_value(); - - self.builder.extract_field(rec, index) + FIELD_OFFSET }; + let index = + (base + ins.field.index(&self.shared.state.db)) as u32; + let layout = self.layouts.instances[ins.class.0 as usize]; + let rec = self.builder.load(rec_typ, rec_var); + + // When loading fields from enums we may load from an opaque + // field type, depending on what constructor we're dealing with. + // To ensure we always use the correct type, we use the type of + // the return value instead of using the layout's field type + // as-is. + let field = self.builder.load_field_as( + layout, + rec.into_pointer_value(), + index, + reg_typ, + ); + self.builder.store(reg_var, field); } - Instruction::SetField(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => - { + Instruction::GetField(ins) => { + let reg_var = self.variables[&ins.register]; + let reg_typ = self.variable_types[&ins.register]; let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; - let val_var = self.variables[&ins.value]; let layout = self.layouts.instances[ins.class.0 as usize]; let index = ins.field.index(&self.shared.state.db) as u32; - let val_typ = self.variable_types[&ins.value]; - let val = self.builder.load(val_typ, val_var); - - if rec_typ.is_pointer_type() { + let field = if rec_typ.is_pointer_type() { let rec = self .builder .load(rec_typ, rec_var) .into_pointer_value(); - self.builder.store_field(layout, rec, index, val); + self.builder.load_field_as(layout, rec, index, reg_typ) } else { - self.builder.store_field(layout, rec_var, index, val); - } + // We don't use extractvalue because the type of the field + // may not match that of the target register (e.g. when + // loading an enum constructor field). Using getelementptr + // plus a load allows us to perform a load using a specific + // type. + self.builder.load_field_as(layout, rec_var, index, reg_typ) + }; + + self.builder.store(reg_var, field); } - Instruction::SetField(ins) => { + Instruction::SetField(ins) + if ins.class.is_heap_allocated(&self.shared.state.db) => + { let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; let val_var = self.variables[&ins.value]; @@ -2186,13 +2219,33 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { val, ); } - Instruction::GetField(ins) => { + Instruction::SetField(ins) => { + let rec_var = self.variables[&ins.receiver]; + let rec_typ = self.variable_types[&ins.receiver]; + let val_var = self.variables[&ins.value]; + let layout = self.layouts.instances[ins.class.0 as usize]; + let index = ins.field.index(&self.shared.state.db) as u32; + let val_typ = self.variable_types[&ins.value]; + let val = self.builder.load(val_typ, val_var); + + if rec_typ.is_pointer_type() { + let rec = self + .builder + .load(rec_typ, rec_var) + .into_pointer_value(); + + self.builder.store_field(layout, rec, index, val); + } else { + self.builder.store_field(layout, rec_var, index, val); + } + } + Instruction::FieldPointer(ins) + if ins.class.is_heap_allocated(&self.shared.state.db) => + { let reg_var = self.variables[&ins.register]; - let reg_typ = self.variable_types[&ins.register]; let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; - let class_kind = ins.class.kind(&self.shared.state.db); - let base = if class_kind.is_async() { + let base = if ins.class.kind(&self.shared.state.db).is_async() { PROCESS_FIELD_OFFSET } else { FIELD_OFFSET @@ -2202,24 +2255,15 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { (base + ins.field.index(&self.shared.state.db)) as u32; let layout = self.layouts.instances[ins.class.0 as usize]; let rec = self.builder.load(rec_typ, rec_var); - - // When loading fields from enums we may load from an opaque - // field type, depending on what constructor we're dealing with. - // To ensure we always use the correct type, we use the type of - // the return value instead of using the layout's field type - // as-is. - let field = self.builder.load_field_as( + let addr = self.builder.field_address( layout, rec.into_pointer_value(), index, - reg_typ, ); - self.builder.store(reg_var, field); + self.builder.store(reg_var, addr); } - Instruction::FieldPointer(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => - { + Instruction::FieldPointer(ins) => { let reg_var = self.variables[&ins.register]; let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; @@ -2235,28 +2279,6 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, addr); } - Instruction::FieldPointer(ins) => { - let reg_var = self.variables[&ins.register]; - let rec_var = self.variables[&ins.receiver]; - let rec_typ = self.variable_types[&ins.receiver]; - let base = if ins.class.kind(&self.shared.state.db).is_async() { - PROCESS_FIELD_OFFSET - } else { - FIELD_OFFSET - }; - - let index = - (base + ins.field.index(&self.shared.state.db)) as u32; - let layout = self.layouts.instances[ins.class.0 as usize]; - let rec = self.builder.load(rec_typ, rec_var); - let addr = self.builder.field_address( - layout, - rec.into_pointer_value(), - index, - ); - - self.builder.store(reg_var, addr); - } Instruction::MethodPointer(ins) => { let reg_var = self.variables[&ins.register]; let func_name = &self.shared.names.methods[&ins.method]; @@ -2290,7 +2312,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { .module .runtime_function(RuntimeFunction::ReferenceCountError); - self.builder.call_void(func, &[proc.into(), val.into()]); + self.builder.direct_call(func, &[proc.into(), val.into()]); self.builder.unreachable(); // The block to jump to when the count is zero. @@ -2303,7 +2325,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let ptr = self.builder.load_pointer(var); let func = self.module.runtime_function(RuntimeFunction::Free); - self.builder.call_void(func, &[ptr.into()]); + self.builder.direct_call(func, &[ptr.into()]); } Instruction::Increment(ins) => { let reg_var = self.variables[&ins.register]; @@ -2360,7 +2382,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.branch(is_zero, drop_block, after_block); } Instruction::Allocate(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => + if ins.class.is_stack_allocated(&self.shared.state.db) => { // Defining the alloca already reserves (uninitialised) memory, // so there's nothing we actually need to do here. Setting the @@ -2384,7 +2406,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let proc = self.load_process().into(); let func = self.module.runtime_function(RuntimeFunction::ProcessNew); - let ptr = self.builder.call(func, &[proc, class]); + let ptr = self.builder.call_with_return(func, &[proc, class]); self.builder.store(reg_var, ptr); } @@ -2431,7 +2453,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { let func = self.module.runtime_function(RuntimeFunction::ProcessYield); - self.builder.call_void(func, &[proc.into()]); + self.builder.direct_call(func, &[proc.into()]); self.builder.jump(cont_block); // The block to jump to if we can continue running. @@ -2446,7 +2468,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { .module .runtime_function(RuntimeFunction::ProcessFinishMessage); - self.builder.call_void(func, &[proc, terminate]); + self.builder.direct_call(func, &[proc, terminate]); self.builder.unreachable(); } Instruction::Cast(ins) => { @@ -2555,7 +2577,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, typ.size_of().unwrap()); } - Instruction::Reference(_) => unreachable!(), + Instruction::Borrow(_) => unreachable!(), Instruction::Drop(_) => unreachable!(), } } @@ -2581,41 +2603,91 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { fn call( &self, + kind: CallKind<'ctx>, + layout: &MethodLayout<'ctx>, register: RegisterId, - function: FunctionValue<'ctx>, - arguments: &[BasicMetadataValueEnum], + receiver: Option, + arguments: &[RegisterId], ) { - let var = self.variables[®ister]; + let mut args: Vec = Vec::with_capacity( + arguments.len() + + receiver.is_some() as usize + + layout.returns.is_struct() as usize, + ); - if self.register_type(register).is_never(&self.shared.state.db) { - self.builder.call_void(function, arguments); - self.builder.unreachable(); + // When using struct returns, the returned data is written to a pointer + // which we then read into the desired return register _after_ the call. + let mut attrs = Vec::new(); + let sret = if let ReturnType::Struct(typ) = layout.returns { + let var = self.builder.new_stack_slot(typ); + + attrs.push(( + AttributeLoc::Param(0), + self.builder.context.type_attribute("sret", typ.into()), + )); + args.push(var.into()); + Some((typ, var)) } else { - self.builder.store(var, self.builder.call(function, arguments)); + None + }; + + for (idx, reg) in receiver.iter().chain(arguments.iter()).enumerate() { + let idx = if sret.is_some() { idx + 1 } else { idx }; + let var = self.variables[reg]; + + match layout.arguments.get(idx).cloned() { + Some(ArgumentType::Regular(t)) => { + args.push(self.builder.load(t, var).into()); + } + Some(ArgumentType::StructValue(t)) => { + attrs.push(( + AttributeLoc::Param(idx as u32), + self.builder.context.type_attribute("byval", t.into()), + )); + + args.push(var.into()); + } + Some(ArgumentType::StructReturn(_)) => { + // We only iterate over explicitly provided arguments and + // those don't include the sret pointer. In addition, we + // handle sret arguments before the iteration, so there's + // nothing we need to do here. + } + Some(ArgumentType::Pointer) => { + args.push(var.into()); + } + None => { + // We may run into this case when calling a variadic + // function and passing more arguments than are defined. + let typ = self.variable_types[reg]; + + args.push(self.builder.load(typ, var).into()); + } + } } - } - fn indirect_call( - &self, - register: RegisterId, - function_type: FunctionType<'ctx>, - function: PointerValue<'ctx>, - arguments: &[BasicMetadataValueEnum], - ) { - let var = self.variables[®ister]; + let reg_var = self.variables[®ister]; + let call_site = match kind { + CallKind::Direct(f) => self.builder.direct_call(f, &args), + CallKind::Indirect(t, f) => self.builder.indirect_call(t, f, &args), + }; + + for (loc, attr) in attrs { + call_site.add_attribute(loc, attr); + } + + if layout.returns.is_regular() { + let val = call_site.try_as_basic_value().left().unwrap(); + + self.builder.store(reg_var, val); + } else if let Some((typ, tmp)) = sret { + let val = self.builder.load(typ, tmp); + + self.builder.store(reg_var, val); + } if self.register_type(register).is_never(&self.shared.state.db) { - self.builder.indirect_call(function_type, function, arguments); self.builder.unreachable(); - } else { - self.builder.store( - var, - self.builder - .indirect_call(function_type, function, arguments) - .try_as_basic_value() - .left() - .unwrap(), - ); } } @@ -2695,7 +2767,7 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { &[target.into(), source.get_type().into()], ); - self.builder.call(func, &[source.into()]).into_int_value() + self.builder.call_with_return(func, &[source.into()]).into_int_value() } fn load_process(&mut self) -> PointerValue<'ctx> { @@ -2717,8 +2789,10 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.shared.state.config.target.stack_pointer_register_name(); let mname = self.builder.context.inner.metadata_string(rsp_name); let mnode = self.builder.context.inner.metadata_node(&[mname.into()]); - let rsp_addr = - self.builder.call(func, &[mnode.into()]).into_int_value(); + let rsp_addr = self + .builder + .call_with_return(func, &[mnode.into()]) + .into_int_value(); let mask = self.load_stack_mask(); let addr = self.builder.bit_and(rsp_addr, mask); @@ -2814,7 +2888,10 @@ impl<'a, 'ctx> GenerateMain<'a, 'ctx> { self.module.runtime_function(RuntimeFunction::RuntimeStackMask); let runtime = self .builder - .call(rt_new, &[counts.into(), argc.into(), argv.into()]) + .call_with_return( + rt_new, + &[counts.into(), argc.into(), argv.into()], + ) .into_pointer_value(); // The state is needed by various runtime functions. Because this data @@ -2822,8 +2899,10 @@ impl<'a, 'ctx> GenerateMain<'a, 'ctx> { // global and thus remove the need to pass it as a hidden argument to // every Inko method. let state_global = self.module.add_global_pointer(STATE_GLOBAL); - let state = - self.builder.call(rt_state, &[runtime.into()]).into_pointer_value(); + let state = self + .builder + .call_with_return(rt_state, &[runtime.into()]) + .into_pointer_value(); state_global.set_initializer( &self @@ -2850,7 +2929,7 @@ impl<'a, 'ctx> GenerateMain<'a, 'ctx> { let stack_size = self .builder - .call(rt_stack_mask, &[runtime.into()]) + .call_with_return(rt_stack_mask, &[runtime.into()]) .into_int_value(); self.builder.store(stack_size_global.as_pointer_value(), stack_size); @@ -2864,7 +2943,7 @@ impl<'a, 'ctx> GenerateMain<'a, 'ctx> { let name = &self.names.setup_classes[&module.id]; let func = self.module.add_setup_function(name); - self.builder.call_void(func, &[]); + self.builder.direct_call(func, &[]); } // Constants need to be defined in a separate pass, as they may depends @@ -2874,7 +2953,7 @@ impl<'a, 'ctx> GenerateMain<'a, 'ctx> { let name = &self.names.setup_constants[&module.id]; let func = self.module.add_setup_function(name); - self.builder.call_void(func, &[]); + self.builder.direct_call(func, &[]); } let main_class_id = self.db.main_class().unwrap(); @@ -2899,7 +2978,7 @@ impl<'a, 'ctx> GenerateMain<'a, 'ctx> { let main_class = self.builder.load_pointer(main_class_ptr); - self.builder.call_void( + self.builder.direct_call( rt_start, &[runtime.into(), main_class.into(), main_method.into()], ); @@ -2910,7 +2989,7 @@ impl<'a, 'ctx> GenerateMain<'a, 'ctx> { // we're exiting here. We _do_ drop the runtime in case we want to hook // any additional logic into that step at some point, though technically // this isn't necessary. - self.builder.call_void(rt_drop, &[runtime.into()]); + self.builder.direct_call(rt_drop, &[runtime.into()]); self.builder.return_value(Some(&self.builder.u32_literal(0))); } diff --git a/compiler/src/mir/inline.rs b/compiler/src/mir/inline.rs index dd8a6ea74..be808c26a 100644 --- a/compiler/src/mir/inline.rs +++ b/compiler/src/mir/inline.rs @@ -32,7 +32,7 @@ fn instruction_weight(db: &Database, instruction: &Instruction) -> u16 { // give them a weight of zero. Regular allocations and spawning // processes translate into a function call, so we give them the same // weight as calls. - Instruction::Allocate(ins) if ins.class.kind(db).is_extern() => 0, + Instruction::Allocate(ins) if ins.class.is_stack_allocated(db) => 0, Instruction::Allocate(_) => 1, Instruction::Spawn(_) => 1, @@ -314,7 +314,7 @@ impl CallSite { ins.location.set_inlined_call_id(inline_offset); ins.register += reg_start; } - Instruction::Reference(ins) => { + Instruction::Borrow(ins) => { ins.location.set_inlined_call_id(inline_offset); ins.register += reg_start; ins.value += reg_start; diff --git a/compiler/src/mir/mod.rs b/compiler/src/mir/mod.rs index 330f59324..e43384474 100644 --- a/compiler/src/mir/mod.rs +++ b/compiler/src/mir/mod.rs @@ -9,7 +9,7 @@ pub(crate) mod printer; pub(crate) mod specialize; use crate::state::State; -use crate::symbol_names::{shapes, SymbolNames}; +use crate::symbol_names::{qualified_class_name, SymbolNames}; use indexmap::IndexMap; use location::Location; use std::collections::{HashMap, HashSet}; @@ -377,13 +377,13 @@ impl Block { ))); } - pub(crate) fn reference( + pub(crate) fn borrow( &mut self, register: RegisterId, value: RegisterId, location: InstructionLocation, ) { - self.instructions.push(Instruction::Reference(Box::new(Reference { + self.instructions.push(Instruction::Borrow(Box::new(Borrow { register, value, location, @@ -772,6 +772,24 @@ impl Block { location, }))); } + + fn split_when bool, T: Fn(Instruction) -> R>( + &mut self, + when: W, + then: T, + ) -> Option<(R, Vec)> { + if let Some(idx) = self.instructions.iter().position(when) { + let ins = then(self.instructions.remove(idx)); + let rest = self.instructions.split_off(idx); + + // This ensures we don't keep redundant memory around if + // the number of instructions was very large. + self.instructions.shrink_to_fit(); + Some((ins, rest)) + } else { + None + } + } } #[derive(Clone, Debug)] @@ -963,7 +981,7 @@ pub(crate) struct Free { } #[derive(Clone)] -pub(crate) struct Reference { +pub(crate) struct Borrow { pub(crate) register: RegisterId, pub(crate) value: RegisterId, pub(crate) location: InstructionLocation, @@ -1273,7 +1291,7 @@ pub(crate) enum Instruction { CheckRefs(Box), Drop(Box), Free(Box), - Reference(Box), + Borrow(Box), Increment(Box), Decrement(Box), IncrementAtomic(Box), @@ -1318,7 +1336,7 @@ impl Instruction { Instruction::CheckRefs(ref v) => v.location, Instruction::Drop(ref v) => v.location, Instruction::Free(ref v) => v.location, - Instruction::Reference(ref v) => v.location, + Instruction::Borrow(ref v) => v.location, Instruction::Increment(ref v) => v.location, Instruction::Decrement(ref v) => v.location, Instruction::IncrementAtomic(ref v) => v.location, @@ -1489,8 +1507,8 @@ impl Instruction { v.value.0 ) } - Instruction::Reference(ref v) => { - format!("r{} = ref r{}", v.register.0, v.value.0) + Instruction::Borrow(ref v) => { + format!("r{} = borrow r{}", v.register.0, v.value.0) } Instruction::Increment(ref v) => { format!("increment r{}", v.register.0) @@ -1725,7 +1743,7 @@ impl Method { Instruction::Free(i) => { uses[i.register.0] += 1; } - Instruction::Reference(i) => { + Instruction::Borrow(i) => { uses[i.value.0] += 1; } Instruction::Increment(i) => { @@ -2080,8 +2098,8 @@ impl Mir { } } - /// Splits modules into smaller ones, such that each specialized - /// type has its own module. + /// Splits modules into smaller ones, such that each specialized type has + /// its own module. /// /// This is done to make caching and parallel compilation more effective, /// such that adding a newly specialized type won't flush many caches @@ -2106,13 +2124,11 @@ impl Mir { let file = old_module.id.file(&state.db); let orig_name = old_module.id.name(&state.db).clone(); - let name = ModuleName::new(format!( - "{}({}#{})", - orig_name, - class_id.name(&state.db), - shapes(class_id.shapes(&state.db)) + let name = ModuleName::new(qualified_class_name( + &state.db, + old_module.id, + class_id, )); - let new_mod_id = ModuleType::alloc(&mut state.db, name.clone(), file); diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 155fae973..67fa8784f 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -399,12 +399,18 @@ pub(crate) struct GenerateDropper<'a> { } impl<'a> GenerateDropper<'a> { - pub(crate) fn run(mut self) -> MethodId { + pub(crate) fn run(mut self) { + // Stack values don't support destructors and won't have object headers, + // so we don't generate droppers. + if self.class.is_stack_allocated(&self.state.db) { + return; + } + match self.class.kind(&self.state.db) { types::ClassKind::Async => self.async_class(), types::ClassKind::Enum => self.enum_class(), _ => self.regular_class(), - } + }; } /// Generates the dropper method for a regular class. @@ -513,10 +519,14 @@ impl<'a> GenerateDropper<'a> { lower.add_edge(before_block, block); - let members = con.members(lower.db()); + let members = con.arguments(lower.db()).to_vec(); let fields = &enum_fields[0..members.len()]; for (&field, typ) in fields.iter().zip(members.into_iter()).rev() { + if typ.is_stack_allocated(lower.db()) { + continue; + } + let reg = lower.new_register(typ); lower @@ -540,10 +550,13 @@ impl<'a> GenerateDropper<'a> { // Destructors may introduce new references, so we have to check again. // We do this _after_ processing fields so we can correctly drop cyclic // types. - lower.current_block_mut().check_refs(self_reg, loc); + if class.has_object_header(lower.db()) { + lower.current_block_mut().check_refs(self_reg, loc); + } - lower.drop_register(tag_reg, loc); - lower.current_block_mut().free(self_reg, class, loc); + if class.is_heap_allocated(lower.db()) { + lower.current_block_mut().free(self_reg, class, loc); + } let nil_reg = lower.get_nil(loc); @@ -588,7 +601,7 @@ impl<'a> GenerateDropper<'a> { for field in class.fields(lower.db()).into_iter().rev() { let typ = field.value_type(lower.db()); - if typ.is_permanent(lower.db()) { + if typ.is_stack_allocated(lower.db()) { continue; } @@ -601,12 +614,14 @@ impl<'a> GenerateDropper<'a> { lower.drop_register(reg, loc); } - // Destructors may introduce new references, so we have to check again. - // We do this _after_ processing fields so we can correctly drop cyclic - // types. - lower.current_block_mut().check_refs(self_reg, loc); + if class.has_object_header(lower.db()) { + // Destructors may introduce new references, so we have to check + // again. We do this _after_ processing fields so we can correctly + // drop cyclic types. + lower.current_block_mut().check_refs(self_reg, loc); + } - if free_self { + if class.is_heap_allocated(lower.db()) && free_self { lower.current_block_mut().free(self_reg, class, loc); } @@ -1164,7 +1179,6 @@ impl<'a> LowerToMir<'a> { } lower.mark_register_as_moved(ins_reg); - lower.mark_register_as_moved(tag_reg); lower.current_block_mut().return_value(ins_reg, loc); method } @@ -1393,7 +1407,7 @@ impl<'a> LowerMethod<'a> { for (id, typ) in self.method.id.fields(self.db()) { self.field_register( id, - typ.cast_according_to(rec, self.db()), + typ.cast_according_to(self.db(), rec), location, ); } @@ -1819,7 +1833,7 @@ impl<'a> LowerMethod<'a> { // When returning a field using the syntax `x.y`, we _must_ copy // or create a reference, otherwise it's possible to drop `x` // while the result of `y` is still in use. - if typ.is_permanent(self.db()) || info.as_pointer { + if info.as_pointer || typ.is_stack_allocated(self.db()) { reg } else if typ.is_value_type(self.db()) { let copy = self.clone_value_type(reg, typ, true, loc); @@ -1830,7 +1844,7 @@ impl<'a> LowerMethod<'a> { } else { let ref_reg = self.new_register(typ); - self.current_block_mut().reference(ref_reg, reg, loc); + self.current_block_mut().borrow(ref_reg, reg, loc); self.mark_register_as_moved(reg); ref_reg } @@ -1972,8 +1986,8 @@ impl<'a> LowerMethod<'a> { // Argument registers must be defined _before_ the receiver register, // ensuring we drop them _after_ dropping the receiver (i.e in // reverse-lexical order). - let arg_regs = self.call_arguments(info.id, arguments); - let result = self.new_register(info.returns); + let args = self.call_arguments(info.id, arguments); + let res = self.new_register(info.returns); let targs = self.mir.add_type_arguments(info.type_arguments); if info.id.is_async(self.db()) { @@ -1981,20 +1995,18 @@ impl<'a> LowerMethod<'a> { // otherwise we may end up scheduling the async dropper prematurely // (e.g. if new references are created before it runs). self.current_block_mut().increment_atomic(rec, ins_loc); - self.current_block_mut() - .send(rec, info.id, arg_regs, targs, ins_loc); - - self.current_block_mut().nil_literal(result, ins_loc); + self.current_block_mut().send(rec, info.id, args, targs, ins_loc); + self.current_block_mut().nil_literal(res, ins_loc); } else if info.dynamic { self.current_block_mut() - .call_dynamic(result, rec, info.id, arg_regs, targs, ins_loc); + .call_dynamic(res, rec, info.id, args, targs, ins_loc); } else { self.current_block_mut() - .call_instance(result, rec, info.id, arg_regs, targs, ins_loc); + .call_instance(res, rec, info.id, args, targs, ins_loc); } self.after_call(info.returns); - result + res } fn call_arguments( @@ -2102,7 +2114,7 @@ impl<'a> LowerMethod<'a> { let arg = self.input_expression(node.value, Some(exp)); let old = self.new_register(info.variable_type); - if !info.variable_type.is_permanent(self.db()) { + if !info.variable_type.is_stack_allocated(self.db()) { self.current_block_mut() .get_field(old, rec, info.class, info.id, loc); self.drop_register(old, loc); @@ -2158,7 +2170,7 @@ impl<'a> LowerMethod<'a> { let rec = self.surrounding_type_register; let class = self.register_type(rec).class_id(self.db()).unwrap(); - if !exp.is_permanent(self.db()) { + if !exp.is_stack_allocated(self.db()) { // The captured variable may be exposed as a reference in `reg`, // but if the value is owned we need to drop it, not decrement // it. @@ -2213,7 +2225,7 @@ impl<'a> LowerMethod<'a> { let class = self.register_type(rec).class_id(self.db()).unwrap(); let is_moved = self.register_is_moved(reg); - if !is_moved && !exp.is_permanent(self.db()) { + if !is_moved && !exp.is_stack_allocated(self.db()) { // `reg` may be a reference for a non-moving method, so we need // a temporary register using the raw field type and drop that // instead. @@ -2239,7 +2251,7 @@ impl<'a> LowerMethod<'a> { let rec = self.self_register; let class = self.register_type(rec).class_id(self.db()).unwrap(); - if !exp.is_permanent(self.db()) { + if !exp.is_stack_allocated(self.db()) { let old = self.new_register(exp); // Closures capture `self` as a whole, so we can't end up with a @@ -2456,9 +2468,7 @@ impl<'a> LowerMethod<'a> { self.mark_register_as_moved(reg); self.mark_register_as_moved(result_reg); self.drop_all_registers(loc); - self.current_block_mut().return_value(result_reg, loc); - self.add_current_block(); self.new_register(TypeRef::Never) } @@ -2568,7 +2578,7 @@ impl<'a> LowerMethod<'a> { } else { let reg = self.new_register(return_type); - self.current_block_mut().reference(reg, val, location); + self.current_block_mut().borrow(reg, val, location); reg } } @@ -2633,7 +2643,6 @@ impl<'a> LowerMethod<'a> { // The result is untracked as otherwise an explicit return may drop it // before we write to it. let output_reg = self.new_untracked_register(node.resolved_type); - let mut rows = Vec::new(); let mut vars = pmatch::Variables::new(); let input_var = vars.new_variable(input_type); @@ -2865,7 +2874,7 @@ impl<'a> LowerMethod<'a> { let source = state.registers[pvar.0]; let target = *self.variable_mapping.get(&id).unwrap(); - self.mark_register_as_moved(source); + self.mark_local_register_as_moved(source); self.add_drop_flag(target, loc); match state.actions.get(&source) { @@ -2884,12 +2893,12 @@ impl<'a> LowerMethod<'a> { let copy = self .clone_value_type(source, typ, false, loc); - self.mark_register_as_moved(copy); + self.mark_local_register_as_moved(copy); self.current_block_mut() .move_register(target, copy, loc); } else { self.current_block_mut() - .reference(target, source, loc); + .borrow(target, source, loc); } } None => { @@ -2901,23 +2910,16 @@ impl<'a> LowerMethod<'a> { pmatch::Binding::Ignored(pvar) => { let reg = state.registers[pvar.0]; + if self.register_is_stack_allocated(reg) { + continue; + } + match state.actions.get(®) { Some(&RegisterAction::Move(parent)) => { self.mark_register_as_partially_moved(parent); - - if self.register_type(reg).is_permanent(self.db()) { - self.mark_register_as_moved(reg); - } else { - self.drop_with_children(state, reg, loc); - } - } - None => { - if self.register_type(reg).is_permanent(self.db()) { - self.mark_register_as_moved(reg); - } else { - self.drop_with_children(state, reg, loc); - } + self.drop_with_children(state, reg, loc); } + None => self.drop_with_children(state, reg, loc), _ => self.mark_register_as_moved(reg), } } @@ -2954,10 +2956,12 @@ impl<'a> LowerMethod<'a> { let loc = InstructionLocation::new(body_loc); let reg = self.body(exprs, loc); - if state.write_result { - self.mark_register_as_moved(reg); - } else if self.in_connected_block() { - self.drop_register(reg, loc); + if !self.register_is_stack_allocated(reg) { + if state.write_result { + self.mark_register_as_moved(reg); + } else if self.in_connected_block() { + self.drop_register(reg, loc); + } } // We don't enter a scope in this method, because we must enter a new @@ -2987,7 +2991,7 @@ impl<'a> LowerMethod<'a> { while let Some(reg) = registers.pop() { // We may encounter values partially moved, such as for the pattern // `(a, b)` where the surrounding tuple is partially moved. - if self.register_is_moved(reg) { + if self.register_is_moved_or_permanent(reg) { continue; } @@ -3009,11 +3013,6 @@ impl<'a> LowerMethod<'a> { } self.mark_register_as_moved(reg); - - if self.register_type(reg).is_permanent(self.db()) { - continue; - } - self.current_block_mut().drop_without_dropper(reg, loc); } } @@ -3041,10 +3040,14 @@ impl<'a> LowerMethod<'a> { }; while let Some(regs) = work.pop() { - for reg in regs { - self.mark_register_as_moved(*reg); + for ® in regs { + if self.register_is_stack_allocated(reg) { + continue; + } + + self.mark_register_as_moved(reg); - if let Some(regs) = state.child_registers.get(reg) { + if let Some(regs) = state.child_registers.get(®) { work.push(regs); } } @@ -3209,15 +3212,14 @@ impl<'a> LowerMethod<'a> { }; let test_type = self.register_type(test_reg); + let owned = test_type.is_owned_or_uni(self.db()); + let class = self.register_type(test_reg).class_id(self.db()).unwrap(); registers.push(test_reg); for (arg, field) in case.arguments.into_iter().zip(fields.into_iter()) { let reg = state.registers[arg.0]; - let class = - self.register_type(test_reg).class_id(self.db()).unwrap(); - - let action = if test_type.is_owned_or_uni(self.db()) { + let action = if owned { RegisterAction::Move(test_reg) } else { RegisterAction::Increment(test_reg) @@ -3302,37 +3304,25 @@ impl<'a> LowerMethod<'a> { self.exit_call_scope(entered, reg, ins_loc); reg } - types::IdentifierKind::Field(info) => { - if !self.register_is_available(self.self_register) { - self.state.diagnostics.implicit_receiver_moved( - &node.name, - self.file(), - loc, - ); - } - - let rec = self.self_register; - let reg = self.new_field(info.id, info.variable_type); - - self.current_block_mut() - .get_field(reg, rec, info.class, info.id, ins_loc); - reg - } types::IdentifierKind::Unknown => unreachable!(), } } fn field(&mut self, node: hir::FieldRef) -> RegisterId { let loc = InstructionLocation::new(node.location); - let id = node.field_id.unwrap(); + let info = node.info.unwrap(); + let id = info.id; + let typ = info.variable_type; let reg = if self.in_closure() { - self.new_field(id, node.resolved_type) + self.new_field(id, typ) + } else if info.as_pointer { + self.new_register(typ) } else { self.field_mapping.get(&id).cloned().unwrap() }; let rec = self.self_register; - let class = self.register_type(rec).class_id(self.db()).unwrap(); + let class = info.class; let name = &node.name; let check_loc = node.location; @@ -3349,19 +3339,13 @@ impl<'a> LowerMethod<'a> { } } - let typ = id.value_type(self.db()); - - if (node.in_mut && typ.is_foreign_type(self.db())) - || typ.is_extern_instance(self.db()) - { - let reg = self.new_register(typ.as_pointer(self.db())); - + if info.as_pointer { self.current_block_mut().field_pointer(reg, rec, class, id, loc); - reg } else { self.current_block_mut().get_field(reg, rec, class, id, loc); - reg } + + reg } fn constant(&mut self, node: hir::ConstantRef) -> RegisterId { @@ -3714,8 +3698,7 @@ impl<'a> LowerMethod<'a> { if self.register_kind(reg).new_reference_on_return() { let res = self.new_register(typ); - self.current_block_mut().reference(res, reg, ins_loc); - + self.current_block_mut().borrow(res, reg, ins_loc); return res; } @@ -3825,7 +3808,7 @@ impl<'a> LowerMethod<'a> { ) -> RegisterId { let ins_loc = InstructionLocation::new(location); - if register_type.is_permanent(self.db()) { + if register_type.is_stack_allocated(self.db()) { return register; } @@ -3851,11 +3834,11 @@ impl<'a> LowerMethod<'a> { // Regular owned values passed to references are implicitly // passed as references. if !exp.is_owned_or_uni(self.db()) { - let typ = register_type.cast_according_to(exp, self.db()); + let typ = register_type.cast_according_to(self.db(), exp); let reg = self.new_register(typ); self.mark_register_as_moved(reg); - self.current_block_mut().reference(reg, register, ins_loc); + self.current_block_mut().borrow(reg, register, ins_loc); return reg; } @@ -3891,7 +3874,7 @@ impl<'a> LowerMethod<'a> { { let reg = self.new_register(register_type); - self.current_block_mut().reference(reg, register, ins_loc); + self.current_block_mut().borrow(reg, register, ins_loc); self.mark_register_as_moved(reg); return reg; @@ -3917,9 +3900,11 @@ impl<'a> LowerMethod<'a> { force_clone: bool, location: InstructionLocation, ) -> RegisterId { - if typ.is_permanent(self.db()) - || (self.register_kind(source).is_regular() && !force_clone) - { + if typ.is_stack_allocated(self.db()) { + return source; + } + + if self.register_kind(source).is_regular() && !force_clone { self.mark_register_as_moved(source); // Value types not bound to any variables/fields don't need to be @@ -4042,7 +4027,7 @@ impl<'a> LowerMethod<'a> { let self_type = self.register_type(self_reg); if !self.method.id.is_moving(self.db()) - || self_type.is_permanent(self.db()) + || self_type.is_stack_allocated(self.db()) { return; } @@ -4059,10 +4044,9 @@ impl<'a> LowerMethod<'a> { for (id, _) in &fields { let reg = self.field_mapping.get(id).cloned().unwrap(); - if self.register_is_moved(reg) { + if self.register_is_moved_or_permanent(reg) { continue; } - self.drop_field(self_reg, *id, reg, location); } } @@ -4212,9 +4196,7 @@ impl<'a> LowerMethod<'a> { ) { let typ = self.register_type(register); - if typ.use_reference_counting(self.db()) - || typ.is_value_type(self.db()) - || typ.is_permanent(self.db()) + if typ.use_reference_counting(self.db()) || typ.is_value_type(self.db()) { return; } @@ -4330,6 +4312,10 @@ impl<'a> LowerMethod<'a> { self.register_state(register) == RegisterState::Available } + fn register_is_stack_allocated(&self, register: RegisterId) -> bool { + self.register_type(register).is_stack_allocated(self.db()) + } + fn register_is_moved(&mut self, register: RegisterId) -> bool { self.register_state(register) == RegisterState::Moved } @@ -4338,9 +4324,14 @@ impl<'a> LowerMethod<'a> { self.register_state(register) == RegisterState::MaybeMoved } + fn register_is_moved_or_permanent(&mut self, register: RegisterId) -> bool { + self.register_is_moved(register) + || self.register_is_stack_allocated(register) + } + fn should_drop_register(&mut self, register: RegisterId) -> bool { if self.register_is_moved(register) - || self.register_type(register).is_permanent(self.db()) + || self.register_is_stack_allocated(register) { return false; } @@ -4442,6 +4433,12 @@ impl<'a> LowerMethod<'a> { self.update_register_state(register, RegisterState::PartiallyMoved); } + fn mark_local_register_as_moved(&mut self, register: RegisterId) { + if !self.register_is_stack_allocated(register) { + self.update_register_state(register, RegisterState::Moved); + } + } + fn mark_register_as_moved(&mut self, register: RegisterId) { self.update_register_state(register, RegisterState::Moved); } diff --git a/compiler/src/mir/pattern_matching.rs b/compiler/src/mir/pattern_matching.rs index 43c38540a..061dab98f 100644 --- a/compiler/src/mir/pattern_matching.rs +++ b/compiler/src/mir/pattern_matching.rs @@ -774,7 +774,7 @@ impl<'a> Compiler<'a> { .into_iter() .map(|t| { self.new_variable( - t.cast_according_to(source_variable_type, self.db()), + t.cast_according_to(self.db(), source_variable_type), ) }) .collect(); @@ -788,7 +788,7 @@ impl<'a> Compiler<'a> { let inferred = TypeResolver::new(&mut self.state.db, &args, &self.bounds) .resolve(raw_type) - .cast_according_to(source_variable_type, self.db()); + .cast_according_to(self.db(), source_variable_type); self.new_variable(inferred) }) @@ -818,7 +818,8 @@ impl<'a> Compiler<'a> { .constructors(self.db()) .into_iter() .map(|constructor| { - let members = constructor.members(self.db()); + let members = + constructor.arguments(self.db()).to_vec(); ( Constructor::Constructor(constructor), diff --git a/compiler/src/mir/specialize.rs b/compiler/src/mir/specialize.rs index 539945725..641d198c2 100644 --- a/compiler/src/mir/specialize.rs +++ b/compiler/src/mir/specialize.rs @@ -1,7 +1,7 @@ use crate::mir::{ - Block, BlockId, CallDynamic, CallInstance, CastType, Class as MirClass, - Drop, Instruction, InstructionLocation, Method, Mir, Reference, RegisterId, - SELF_ID, + Block, BlockId, Borrow, CallDynamic, CallInstance, CastType, + Class as MirClass, Drop, Instruction, InstructionLocation, Method, Mir, + RegisterId, SELF_ID, }; use crate::state::State; use indexmap::{IndexMap, IndexSet}; @@ -9,27 +9,33 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::mem::swap; use types::specialize::{ordered_shapes_from_map, TypeSpecializer}; use types::{ - Block as _, ClassId, ClassInstance, Database, MethodId, Shape, - TypeArguments, TypeParameterId, TypeRef, CALL_METHOD, DROPPER_METHOD, + Block as _, ClassId, ClassInstance, Database, InternedTypeArguments, + MethodId, Shape, TypeArguments, TypeParameterId, TypeRef, CALL_METHOD, + DROPPER_METHOD, }; fn argument_shape( db: &Database, + interned: &mut InternedTypeArguments, shapes: &HashMap, arguments: &TypeArguments, parameter: TypeParameterId, ) -> Shape { - arguments.get_recursive(db, parameter).unwrap().shape(db, shapes) + arguments.get_recursive(db, parameter).unwrap().shape(db, interned, shapes) } -fn specialize_constants(db: &mut Database, mir: &mut Mir) { +fn specialize_constants( + db: &mut Database, + mir: &mut Mir, + interned: &mut InternedTypeArguments, +) { let mut classes = Vec::new(); let shapes = HashMap::new(); for &id in mir.constants.keys() { let old_typ = id.value_type(db); - let new_typ = - TypeSpecializer::new(db, &shapes, &mut classes).specialize(old_typ); + let new_typ = TypeSpecializer::new(db, interned, &shapes, &mut classes) + .specialize(old_typ); id.set_value_type(db, new_typ); } @@ -43,23 +49,56 @@ fn specialize_constants(db: &mut Database, mir: &mut Mir) { } } -/// Returns `true` if the given shapes are _not_ compatible with the method -/// bounds, if there are any. +/// Returns `true` if the given shapes are compatible with the method bounds, if +/// there are any. /// /// It's possible to trigger method specialization for types such as /// `Result[Int32, String]`. Since foreign types don't implement traits, don't /// have headers and thus don't support dynamic dispatch, we have to skip /// generating methods for such cases, otherwise we may generate incorrect code. -fn shapes_not_compatible_with_bounds( +fn shapes_compatible_with_bounds( db: &Database, method: MethodId, shapes: &HashMap, ) -> bool { let bounds = method.bounds(db); - shapes.iter().any(|(¶m, shape)| { - bounds.get(param).is_some() && shape.is_foreign() - }) + for (¶m, &shape) in shapes { + if let Some(bound) = bounds.get(param) { + // Foreign types don't support traits, so these are never + // compatible. + if shape.is_foreign() { + return false; + } + + // When encountering a shape for a specific type, we'll end up + // trying to devirtualize calls in the method to specialize. This is + // only possible if the type is compatible with the bounds, i.e. all + // the required traits are implemented. + // + // We don't need to perform a full type-check here: if a trait _is_ + // implemented then correctness is already enforced at the call/cast + // site, so all we need to do here is check if the trait is + // implemented in the first place. + if let Shape::Stack(ins) = shape { + // For specialized types the trait implementations are stored in + // the base class. + let cls = ins + .instance_of() + .specialization_source(db) + .unwrap_or(ins.instance_of()); + let valid = bound.requirements(db).into_iter().all(|r| { + cls.trait_implementation(db, r.instance_of()).is_some() + }); + + if !valid { + return false; + } + } + } + } + + true } struct Job { @@ -194,6 +233,7 @@ pub(crate) struct Specialize<'a, 'b> { method: MethodId, state: &'a mut State, work: &'b mut Work, + intern: &'b mut InternedTypeArguments, shapes: HashMap, /// Regular methods that have been processed. @@ -232,6 +272,7 @@ impl<'a, 'b> Specialize<'a, 'b> { let mut work = Work::new(); let mut dcalls = DynamicCalls::new(); + let mut intern = InternedTypeArguments::new(); let main_class = state.db.main_class().unwrap(); let main_method = state.db.main_method().unwrap(); let main_mod = main_class.module(&state.db); @@ -246,6 +287,7 @@ impl<'a, 'b> Specialize<'a, 'b> { while let Some(job) = work.pop() { Specialize { state, + intern: &mut intern, method: job.method, shapes: job.shapes, work: &mut work, @@ -268,7 +310,7 @@ impl<'a, 'b> Specialize<'a, 'b> { // 2. The type isn't used anywhere else (highly unlikely). In this case // we don't need to generate a dropper, because constants are never // dropped. - specialize_constants(&mut state.db, mir); + specialize_constants(&mut state.db, mir, &mut intern); // Specialization may create many new methods, and in the process makes // the original generic methods redundant and unused. In fact, compiling @@ -320,7 +362,7 @@ impl<'a, 'b> Specialize<'a, 'b> { for block in &method.body.blocks { for instruction in &block.instructions { match instruction { - Instruction::Reference(ins) if ins.value.0 == SELF_ID => { + Instruction::Borrow(ins) if ins.value.0 == SELF_ID => { self_regs[ins.register.0] = true; } Instruction::MoveRegister(ins) @@ -359,6 +401,7 @@ impl<'a, 'b> Specialize<'a, 'b> { for reg in method.registers.iter_mut() { reg.value_type = TypeSpecializer::new( &mut self.state.db, + self.intern, &self.shapes, &mut self.classes, ) @@ -367,22 +410,17 @@ impl<'a, 'b> Specialize<'a, 'b> { for block in &mut method.body.blocks { for instruction in &mut block.instructions { - // When specializing a method, we _don't_ store them in any - // class types. Different specializations of the same method use - // the same name, so if they are stored on the same class they'd - // overwrite each other. Since we don't need to look up any - // methods by their names at and beyond this point, we just not - // store them in the class types to begin with. match instruction { - Instruction::Reference(ins) => { + Instruction::Borrow(ins) => { let src = method.registers.value_type(ins.value); - let target = method.registers.value_type(ins.register); + let reg = method.registers.value_type(ins.register); + let db = &self.state.db; method.registers.get_mut(ins.register).value_type = - if target.is_ref(&self.state.db) { - src.as_ref(&self.state.db) - } else if target.is_mut(&self.state.db) { - src.force_as_mut(&self.state.db) + if reg.is_ref(db) { + src.as_ref(db) + } else if reg.is_mut(db) { + src.force_as_mut(db) } else { src }; @@ -526,6 +564,7 @@ impl<'a, 'b> Specialize<'a, 'b> { Instruction::SizeOf(ins) => { ins.argument = TypeSpecializer::new( &mut self.state.db, + self.intern, &self.shapes, &mut self.classes, ) @@ -540,10 +579,21 @@ impl<'a, 'b> Specialize<'a, 'b> { fn expand_instructions(&mut self, mir: &mut Mir) { let method = mir.methods.get_mut(&self.method).unwrap(); - ExpandDrop { db: &self.state.db, method, shapes: &self.shapes }.run(); + ExpandDrop { + db: &self.state.db, + intern: self.intern, + method, + shapes: &self.shapes, + } + .run(); - ExpandReference { db: &self.state.db, method, shapes: &self.shapes } - .run(); + ExpandBorrow { + db: &self.state.db, + intern: self.intern, + method, + shapes: &self.shapes, + } + .run(); } fn process_specialized_types( @@ -608,7 +658,7 @@ impl<'a, 'b> Specialize<'a, 'b> { shapes.insert(par, shape); } - if shapes_not_compatible_with_bounds( + if !shapes_compatible_with_bounds( &self.state.db, call.method, &shapes, @@ -763,7 +813,7 @@ impl<'a, 'b> Specialize<'a, 'b> { shapes.insert(param, shape); } - if shapes_not_compatible_with_bounds( + if !shapes_compatible_with_bounds( &self.state.db, method_impl, &shapes, @@ -910,7 +960,11 @@ impl<'a, 'b> Specialize<'a, 'b> { fn generate_dropper(&mut self, original: ClassId, class: ClassId) { let name = DROPPER_METHOD; - let method = original.method(&self.state.db, name).unwrap(); + + // Stack types won't have droppers, so there's nothing to do here. + let Some(method) = original.method(&self.state.db, name) else { + return; + }; if original == class { if self.work.push(method, HashMap::new()) { @@ -970,7 +1024,7 @@ impl<'a, 'b> Specialize<'a, 'b> { method.type_parameters(&self.state.db) }; - let method_shapes = shape_params + let method_shapes: Vec<_> = shape_params .into_iter() .map(|p| *shapes.get(&p).unwrap()) .collect(); @@ -981,6 +1035,7 @@ impl<'a, 'b> Specialize<'a, 'b> { for arg in method.arguments(&self.state.db) { let arg_type = TypeSpecializer::new( &mut self.state.db, + self.intern, shapes, &mut self.classes, ) @@ -990,6 +1045,7 @@ impl<'a, 'b> Specialize<'a, 'b> { let var_loc = arg.variable.location(&self.state.db); let var_type = TypeSpecializer::new( &mut self.state.db, + self.intern, shapes, &mut self.classes, ) @@ -1004,9 +1060,13 @@ impl<'a, 'b> Specialize<'a, 'b> { ); } - let new_ret = - TypeSpecializer::new(&mut self.state.db, shapes, &mut self.classes) - .specialize(old_ret); + let new_ret = TypeSpecializer::new( + &mut self.state.db, + self.intern, + shapes, + &mut self.classes, + ) + .specialize(old_ret); let bounds = method.bounds(&self.state.db).clone(); @@ -1034,6 +1094,7 @@ impl<'a, 'b> Specialize<'a, 'b> { { let arg_type = TypeSpecializer::new( &mut self.state.db, + self.intern, shapes, &mut self.classes, ) @@ -1042,6 +1103,7 @@ impl<'a, 'b> Specialize<'a, 'b> { let raw_var_type = arg.variable.value_type(&self.state.db); let var_type = TypeSpecializer::new( &mut self.state.db, + self.intern, shapes, &mut self.classes, ) @@ -1056,9 +1118,13 @@ impl<'a, 'b> Specialize<'a, 'b> { } let old_ret = method.return_type(&self.state.db); - let new_ret = - TypeSpecializer::new(&mut self.state.db, shapes, &mut self.classes) - .specialize(old_ret); + let new_ret = TypeSpecializer::new( + &mut self.state.db, + self.intern, + shapes, + &mut self.classes, + ) + .specialize(old_ret); method.set_return_type(&mut self.state.db, new_ret); } @@ -1071,8 +1137,13 @@ impl<'a, 'b> Specialize<'a, 'b> { let mut shapes = HashMap::new(); for (&par, &bound) in method.bounds(&self.state.db).iter() { - let shape = - argument_shape(&self.state.db, &self.shapes, arguments, par); + let shape = argument_shape( + &self.state.db, + self.intern, + &self.shapes, + arguments, + par, + ); shapes.insert(bound, shape); shapes.insert(par, shape); @@ -1090,7 +1161,10 @@ impl<'a, 'b> Specialize<'a, 'b> { } } - shapes.insert(par, arg.shape(&self.state.db, &self.shapes)); + shapes.insert( + par, + arg.shape(&self.state.db, self.intern, &self.shapes), + ); } shapes @@ -1107,7 +1181,7 @@ impl<'a, 'b> Specialize<'a, 'b> { } fn add_implementation_shapes( - &self, + &mut self, method: MethodId, shapes: &mut HashMap, ) { @@ -1118,12 +1192,18 @@ impl<'a, 'b> Specialize<'a, 'b> { // (e.g. `Equal.!=`). We need to make sure we map those parameters // to their shapes. if tins.instance_of().is_generic(&self.state.db) { - let args = tins.type_arguments(&self.state.db); + let args = tins.type_arguments(&self.state.db).unwrap(); for &par in args.keys() { shapes.insert( par, - argument_shape(&self.state.db, shapes, args, par), + argument_shape( + &self.state.db, + self.intern, + shapes, + args, + par, + ), ); } } @@ -1138,7 +1218,13 @@ impl<'a, 'b> Specialize<'a, 'b> { for &par in args.keys() { shapes.insert( par, - argument_shape(&self.state.db, shapes, args, par), + argument_shape( + &self.state.db, + self.intern, + shapes, + args, + par, + ), ); } } @@ -1151,54 +1237,34 @@ struct ExpandDrop<'a, 'b, 'c> { db: &'a Database, method: &'b mut Method, shapes: &'c HashMap, + intern: &'c mut InternedTypeArguments, } impl<'a, 'b, 'c> ExpandDrop<'a, 'b, 'c> { fn run(mut self) { let mut block_idx = 0; - // We use a `while` loop here as both the list of blocks and - // instructions are modified during iteration, meaning we can't use a - // fixed range to iterate over. while block_idx < self.method.body.blocks.len() { - let block_id = BlockId(block_idx); - - if let Some(ins_idx) = self - .block_mut(block_id) - .instructions - .iter() - .position(|ins| matches!(ins, Instruction::Drop(_))) - { - let (ins, remaining_ins) = { - let block = self.block_mut(block_id); + let bid = BlockId(block_idx); - if let Instruction::Drop(ins) = - block.instructions.remove(ins_idx) - { - let ret = (ins, block.instructions.split_off(ins_idx)); - - // This ensures we don't keep redundant memory around if - // the number of instructions was very large. - block.instructions.shrink_to_fit(); - ret - } else { - unreachable!() - } - }; - - let after_id = self.add_block(); - let succ = self.block_mut(block_id).take_successors(); + if let Some((ins, remaining_ins)) = self.block_mut(bid).split_when( + |ins| matches!(ins, Instruction::Drop(_)), + |ins| match ins { + Instruction::Drop(i) => i, + _ => unreachable!(), + }, + ) { + let after = self.add_block(); + let succ = self.block_mut(bid).take_successors(); - self.insert(*ins, block_id, after_id); + self.insert(*ins, bid, after); - // The new end block must be properly connected to the block(s) - // the original block was connected to. for succ_id in succ { - self.method.body.remove_predecessor(succ_id, block_id); - self.method.body.add_edge(after_id, succ_id); + self.method.body.remove_predecessor(succ_id, bid); + self.method.body.add_edge(after, succ_id); } - self.block_mut(after_id).instructions = remaining_ins; + self.block_mut(after).instructions = remaining_ins; } block_idx += 1; @@ -1210,12 +1276,13 @@ impl<'a, 'b, 'c> ExpandDrop<'a, 'b, 'c> { let val = ins.register; let typ = self.method.registers.value_type(val); - match typ.shape(self.db, self.shapes) { + match typ.shape(self.db, self.intern, self.shapes) { Shape::Int(_, _) | Shape::Float(_) | Shape::Nil | Shape::Boolean - | Shape::Pointer => { + | Shape::Pointer + | Shape::Stack(_) => { self.ignore_value(block_id, after_id); } Shape::Mut | Shape::Ref => { @@ -1224,9 +1291,6 @@ impl<'a, 'b, 'c> ExpandDrop<'a, 'b, 'c> { Shape::Atomic | Shape::String => { self.drop_atomic(block_id, after_id, val, loc); } - Shape::Owned if typ.is_permanent(self.db) => { - self.ignore_value(block_id, after_id); - } Shape::Owned => { self.drop_owned(block_id, after_id, val, ins.dropper, loc); } @@ -1295,8 +1359,13 @@ impl<'a, 'b, 'c> ExpandDrop<'a, 'b, 'c> { .class_id(self.db) .unwrap(); - self.block_mut(before_id).check_refs(value, location); - self.block_mut(before_id).free(value, class, location); + if class.has_object_header(self.db) { + self.block_mut(before_id).check_refs(value, location); + } + + if class.is_heap_allocated(self.db) { + self.block_mut(before_id).free(value, class, location); + } } self.block_mut(before_id).goto(after_id, location); @@ -1325,7 +1394,7 @@ impl<'a, 'b, 'c> ExpandDrop<'a, 'b, 'c> { None, location, ); - } else if !typ.is_permanent(self.db) { + } else if !typ.is_stack_allocated(self.db) { self.block_mut(block).call_dropper(reg, value, location); } } @@ -1339,79 +1408,58 @@ impl<'a, 'b, 'c> ExpandDrop<'a, 'b, 'c> { } } -struct ExpandReference<'a, 'b, 'c> { +struct ExpandBorrow<'a, 'b, 'c> { db: &'a types::Database, method: &'b mut Method, shapes: &'c HashMap, + intern: &'c mut InternedTypeArguments, } -impl<'a, 'b, 'c> ExpandReference<'a, 'b, 'c> { +impl<'a, 'b, 'c> ExpandBorrow<'a, 'b, 'c> { fn run(mut self) { let mut block_idx = 0; while block_idx < self.method.body.blocks.len() { - let block_id = BlockId(block_idx); + let bid = BlockId(block_idx); - if let Some(ins_idx) = self - .block_mut(block_id) - .instructions - .iter() - .position(|ins| matches!(ins, Instruction::Reference(_))) - { - let (ins, remaining_ins) = { - let block = self.block_mut(block_id); + if let Some((ins, remaining_ins)) = self.block_mut(bid).split_when( + |ins| matches!(ins, Instruction::Borrow(_)), + |ins| match ins { + Instruction::Borrow(i) => i, + _ => unreachable!(), + }, + ) { + let after = self.method.body.add_block(); + let succ = self.block_mut(bid).take_successors(); - if let Instruction::Reference(ins) = - block.instructions.remove(ins_idx) - { - let ret = (ins, block.instructions.split_off(ins_idx)); - - // This ensures we don't keep redundant memory around if - // the number of instructions was very large. - block.instructions.shrink_to_fit(); - ret - } else { - unreachable!() - } - }; - - let after_id = self.method.body.add_block(); - let succ = self.block_mut(block_id).take_successors(); - - self.insert(*ins, block_id, after_id); + self.insert(*ins, bid, after); for succ_id in succ { - self.method.body.remove_predecessor(succ_id, block_id); - self.method.body.add_edge(after_id, succ_id); + self.method.body.remove_predecessor(succ_id, bid); + self.method.body.add_edge(after, succ_id); } - self.block_mut(after_id).instructions = remaining_ins; + self.block_mut(after).instructions = remaining_ins; } block_idx += 1; } } - fn insert(&mut self, ins: Reference, block_id: BlockId, after_id: BlockId) { + fn insert(&mut self, ins: Borrow, block_id: BlockId, after_id: BlockId) { let loc = ins.location; let reg = ins.register; let val = ins.value; let typ = self.method.registers.value_type(val); - let is_extern = typ - .class_id(self.db) - .map_or(false, |i| i.kind(self.db).is_extern()); - match typ.shape(self.db, self.shapes) { - Shape::Owned if is_extern || typ.is_permanent(self.db) => { - // Extern and permanent values are to be left as-is. - } + match typ.shape(self.db, self.intern, self.shapes) { Shape::Int(_, _) | Shape::Float(_) | Shape::Nil | Shape::Boolean - | Shape::Pointer => { - // These are unboxed value types, or permanent types, both which - // we should leave as-is. + | Shape::Pointer + | Shape::Stack(_) => { + // These values should be left as-is. } Shape::Mut | Shape::Ref | Shape::Owned => { self.block_mut(block_id).increment(val, loc); diff --git a/compiler/src/symbol_names.rs b/compiler/src/symbol_names.rs index 8930bcbfb..fe4361a59 100644 --- a/compiler/src/symbol_names.rs +++ b/compiler/src/symbol_names.rs @@ -2,7 +2,7 @@ use crate::mir::Mir; use std::collections::HashMap; use std::fmt::Write as _; -use types::{ClassId, ConstantId, Database, MethodId, ModuleId, Shape}; +use types::{ClassId, ConstantId, Database, MethodId, ModuleId, Shape, Sign}; pub(crate) const SYMBOL_PREFIX: &str = "_I"; @@ -12,18 +12,65 @@ pub(crate) const STATE_GLOBAL: &str = "_IG_INKO_STATE"; /// The name of the global variable that stores the stack mask. pub(crate) const STACK_MASK_GLOBAL: &str = "_IG_INKO_STACK_MASK"; -pub(crate) fn shapes(shapes: &[Shape]) -> String { - let mut name = String::new(); +pub(crate) fn format_shape(db: &Database, shape: Shape, buf: &mut String) { + let _ = match shape { + Shape::Owned => write!(buf, "o"), + Shape::Mut => write!(buf, "m"), + Shape::Ref => write!(buf, "r"), + Shape::Int(s, Sign::Signed) => write!(buf, "i{}", s), + Shape::Int(s, Sign::Unsigned) => write!(buf, "u{}", s), + Shape::Float(s) => write!(buf, "f{}", s), + Shape::Boolean => write!(buf, "b"), + Shape::String => write!(buf, "s"), + Shape::Atomic => write!(buf, "a"), + Shape::Nil => write!(buf, "n"), + Shape::Pointer => write!(buf, "p"), + Shape::Stack(ins) => { + let cls = ins.instance_of(); + let _ = write!(buf, "S{}.", cls.module(db).name(db)); + + format_class_name(db, cls, buf); + Ok(()) + } + }; +} - for shape in shapes { - let _ = write!(name, "{}", shape); +pub(crate) fn format_shapes(db: &Database, shapes: &[Shape], buf: &mut String) { + for &shape in shapes { + format_shape(db, shape, buf); } +} - name +pub(crate) fn format_class_name(db: &Database, id: ClassId, buf: &mut String) { + buf.push_str(id.name(db)); + + let is_stack = id.is_stack_allocated(db); + let shapes = id.shapes(db); + + if !shapes.is_empty() || is_stack { + buf.push('#'); + } + + // In case we infer a type to be stack allocated (or we did so in the past + // but it's no longer the case), this ensures we flush the object cache. + if is_stack { + buf.push('S'); + } + + if !shapes.is_empty() { + format_shapes(db, shapes, buf); + } } -pub(crate) fn class_name(db: &Database, id: ClassId) -> String { - format!("{}#{}", id.name(db), shapes(id.shapes(db))) +pub(crate) fn qualified_class_name( + db: &Database, + module: ModuleId, + class: ClassId, +) -> String { + let mut name = format!("{}.", module.name(db)); + + format_class_name(db, class, &mut name); + name } pub(crate) fn method_name( @@ -31,12 +78,17 @@ pub(crate) fn method_name( class: ClassId, id: MethodId, ) -> String { - format!( - "{}#{}{}", - id.name(db), - shapes(class.shapes(db)), - shapes(id.shapes(db)), - ) + let mut name = id.name(db).to_string(); + let cshapes = class.shapes(db); + let mshapes = id.shapes(db); + + if !cshapes.is_empty() || !mshapes.is_empty() { + name.push('#'); + format_shapes(db, cshapes, &mut name); + format_shapes(db, mshapes, &mut name); + } + + name } fn mangled_method_name(db: &Database, method: MethodId) -> String { @@ -91,10 +143,9 @@ impl SymbolNames { for module in mir.modules.values() { for &class in &module.classes { let class_name = format!( - "{}T_{}.{}", + "{}T_{}", SYMBOL_PREFIX, - module.id.name(db).as_str(), - class_name(db, class) + qualified_class_name(db, module.id, class) ); classes.insert(class, class_name); @@ -128,3 +179,55 @@ impl SymbolNames { Self { classes, methods, constants, setup_classes, setup_constants } } } + +#[cfg(test)] +mod tests { + use super::*; + use location::Location; + use types::module_name::ModuleName; + use types::{Class, ClassInstance, ClassKind, Module, Visibility}; + + fn name(db: &Database, shape: Shape) -> String { + let mut buf = String::new(); + + format_shape(db, shape, &mut buf); + buf + } + + #[test] + fn test_format_shape() { + let mut db = Database::new(); + let mid = + Module::alloc(&mut db, ModuleName::new("a.b.c"), "c.inko".into()); + let kind = ClassKind::Regular; + let vis = Visibility::Public; + let loc = Location::default(); + let cls1 = Class::alloc(&mut db, "A".to_string(), kind, vis, mid, loc); + let cls2 = Class::alloc(&mut db, "B".to_string(), kind, vis, mid, loc); + + cls1.set_shapes( + &mut db, + vec![ + Shape::Int(64, Sign::Signed), + Shape::Stack(ClassInstance::new(cls2)), + ], + ); + cls2.set_shapes(&mut db, vec![Shape::String]); + + assert_eq!(name(&db, Shape::Owned), "o"); + assert_eq!(name(&db, Shape::Mut), "m"); + assert_eq!(name(&db, Shape::Ref), "r"); + assert_eq!(name(&db, Shape::Int(32, Sign::Signed)), "i32"); + assert_eq!(name(&db, Shape::Int(32, Sign::Unsigned)), "u32"); + assert_eq!(name(&db, Shape::Float(32)), "f32"); + assert_eq!(name(&db, Shape::Boolean), "b"); + assert_eq!(name(&db, Shape::String), "s"); + assert_eq!(name(&db, Shape::Atomic), "a"); + assert_eq!(name(&db, Shape::Nil), "n"); + assert_eq!(name(&db, Shape::Pointer), "p"); + assert_eq!( + name(&db, Shape::Stack(ClassInstance::new(cls1))), + "Sa.b.c.A#i64Sa.b.c.B#s" + ); + } +} diff --git a/compiler/src/target.rs b/compiler/src/target.rs index 7b8ce03df..be3873da3 100644 --- a/compiler/src/target.rs +++ b/compiler/src/target.rs @@ -237,17 +237,6 @@ impl Target { self == &Target::native() } - /// Returns the maximum size (in bits) of a struct that can be passed - /// through registers. - /// - /// If a struct is larger than this size, it must be passed using a pointer. - pub(crate) fn pass_struct_size(&self) -> u64 { - // The exact size may differ per platform, but both amd64 and arm64 have - // the same requirement, and those are the only platforms we support at - // this time. - 128 - } - pub(crate) fn stack_pointer_register_name(&self) -> &str { match self.arch { Architecture::Amd64 => "rsp", diff --git a/compiler/src/type_check/define_types.rs b/compiler/src/type_check/define_types.rs index f3838a61d..347ef441a 100644 --- a/compiler/src/type_check/define_types.rs +++ b/compiler/src/type_check/define_types.rs @@ -2,6 +2,7 @@ use crate::diagnostics::DiagnosticId; use crate::hir; use crate::state::State; +use crate::type_check::graph::RecursiveClassChecker; use crate::type_check::{ define_type_bounds, CheckTypeSignature, DefineAndCheckTypeSignature, DefineTypeSignature, Rules, TypeScope, @@ -13,9 +14,9 @@ use types::format::format_type; use types::{ Class, ClassId, ClassInstance, ClassKind, Constant, Database, ModuleId, Symbol, Trait, TraitId, TraitImplementation, TypeId, TypeRef, Visibility, - ARRAY_INTERNAL_NAME, ENUM_TAG_FIELD, ENUM_TAG_INDEX, FIELDS_LIMIT, - MAIN_CLASS, OPTION_CLASS, OPTION_MODULE, RESULT_CLASS, RESULT_MODULE, - VARIANTS_LIMIT, + ARRAY_INTERNAL_NAME, CONSTRUCTORS_LIMIT, ENUM_TAG_FIELD, ENUM_TAG_INDEX, + FIELDS_LIMIT, MAIN_CLASS, OPTION_CLASS, OPTION_MODULE, RESULT_CLASS, + RESULT_MODULE, }; /// The maximum number of arguments a single constructor can accept. We subtract @@ -68,55 +69,51 @@ impl<'a> DefineTypes<'a> { let module = self.module; let vis = Visibility::public(node.public); let loc = node.location; - let id = match node.kind { - hir::ClassKind::Builtin => { - if !self.module.is_std(self.db()) { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - "builtin classes can only be defined in 'std' modules", - self.file(), - node.location, - ); - } + let id = if let hir::ClassKind::Builtin = node.kind { + if !self.module.is_std(self.db()) { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + "builtin classes can only be defined in 'std' modules", + self.file(), + node.location, + ); + } - if let Some(id) = self.db().builtin_class(&name) { - id.set_module(self.db_mut(), module); - id - } else { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!("'{}' isn't a valid builtin class", name), - self.file(), - node.location, - ); + if let Some(id) = self.db().builtin_class(&name) { + id.set_module(self.db_mut(), module); + id + } else { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + format!("'{}' isn't a valid builtin class", name), + self.file(), + node.location, + ); - return; - } + return; } - hir::ClassKind::Regular => Class::alloc( - self.db_mut(), - name.clone(), - ClassKind::Regular, - vis, - module, - loc, - ), - hir::ClassKind::Async => Class::alloc( - self.db_mut(), - name.clone(), - ClassKind::Async, - vis, - module, - loc, - ), - hir::ClassKind::Enum => Class::alloc( + } else { + let kind = match node.kind { + hir::ClassKind::Regular => ClassKind::Regular, + hir::ClassKind::Async => ClassKind::Async, + hir::ClassKind::Enum => ClassKind::Enum, + _ => unreachable!(), + }; + + let cls = Class::alloc( self.db_mut(), name.clone(), - ClassKind::Enum, + kind, vis, module, loc, - ), + ); + + if node.inline { + cls.set_stack_allocated(self.db_mut()); + } + + cls }; if self.module.symbol_exists(self.db(), &name) { @@ -275,7 +272,7 @@ impl<'a> ImplementTraits<'a> { if !class_id.allow_trait_implementations(self.db()) { self.state.diagnostics.error( DiagnosticId::InvalidImplementation, - "traits can't be implemented for this class", + "traits can't be implemented for this type", self.file(), node.location, ); @@ -329,16 +326,23 @@ impl<'a> ImplementTraits<'a> { if !node.bounds.is_empty() { self.state.diagnostics.error( DiagnosticId::InvalidImplementation, - "the trait 'std::drop::Drop' doesn't support type \ - parameter bounds", + "type parameter bounds can't be applied to \ + implementations of this trait", + self.file(), + node.location, + ); + } + + if class_id.is_stack_allocated(self.db()) { + self.state.diagnostics.error( + DiagnosticId::InvalidImplementation, + "this trait can't be implemented for 'inline' types", self.file(), node.location, ); } - class_ins - .instance_of() - .mark_as_having_destructor(self.db_mut()); + class_id.mark_as_having_destructor(self.db_mut()); } node.trait_instance = Some(instance); @@ -551,8 +555,9 @@ impl<'a> DefineFields<'a> { fn define_class(&mut self, node: &mut hir::DefineClass) { let class_id = node.class_id.unwrap(); let mut id: usize = 0; - let is_enum = class_id.kind(self.db()).is_enum(); let scope = TypeScope::new(self.module, TypeId::Class(class_id), None); + let is_enum = class_id.kind(self.db()).is_enum(); + let is_stack = class_id.is_stack_allocated(self.db()); let is_main = self.main_module && node.name.name == MAIN_CLASS; for expr in &mut node.body { @@ -612,6 +617,14 @@ impl<'a> DefineFields<'a> { ) .define_type(&mut fnode.value_type); + if is_stack && !typ.is_stack_allocated(self.db()) { + self.state.diagnostics.not_a_stack_type( + &format_type(self.db(), typ), + self.file(), + fnode.location, + ); + } + if !class_id.is_public(self.db()) && vis == Visibility::Public { self.state .diagnostics @@ -668,18 +681,11 @@ impl<'a> DefineFields<'a> { ) .define_type(&mut node.value_type); - // We can't allow regular Inko types in external classes, as that - // would allow violating their ownership constraints. - if !typ.is_error(self.db()) - && !typ.is_foreign_type(self.db()) - && !typ.is_value_type(self.db()) - { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "'{}' isn't a C type or value type", - format_type(self.db(), typ) - ), + // We can't allow heap values in external classes, as that would + // allow violating their single ownership constraints. + if !typ.is_stack_allocated(self.db()) { + self.state.diagnostics.not_a_stack_type( + &format_type(self.db(), typ), self.file(), node.value_type.location(), ); @@ -757,6 +763,7 @@ impl<'a> DefineTypeParameters<'a> { fn define_class(&mut self, node: &mut hir::DefineClass) { let id = node.class_id.unwrap(); + let is_stack = id.is_stack_allocated(self.db()); for param in &mut node.type_parameters { let name = ¶m.name.name; @@ -774,6 +781,10 @@ impl<'a> DefineTypeParameters<'a> { pid.set_mutable(self.db_mut()); } + if is_stack || param.inline { + pid.set_stack_allocated(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -798,6 +809,10 @@ impl<'a> DefineTypeParameters<'a> { pid.set_mutable(self.db_mut()); } + if param.inline { + pid.set_stack_allocated(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -1048,10 +1063,11 @@ impl<'a> DefineConstructors<'a> { fn define_class(&mut self, node: &mut hir::DefineClass) { let class_id = node.class_id.unwrap(); let is_enum = class_id.kind(self.db()).is_enum(); + let is_stack = class_id.is_stack_allocated(self.db()); let rules = Rules::default(); let scope = TypeScope::new(self.module, TypeId::Class(class_id), None); let mut constructors_count = 0; - let mut members_count = 0; + let mut args_count = 0; for expr in &mut node.body { let node = @@ -1064,7 +1080,7 @@ impl<'a> DefineConstructors<'a> { if !is_enum { self.state.diagnostics.error( DiagnosticId::InvalidSymbol, - "constructors can only be defined for enum classes", + "constructors can only be defined for 'enum' types", self.file(), node.location, ); @@ -1085,24 +1101,32 @@ impl<'a> DefineConstructors<'a> { continue; } - let members: Vec<_> = node - .members - .iter_mut() - .map(|n| { - DefineAndCheckTypeSignature::new( - self.state, - self.module, - &scope, - rules, - ) - .define_type(n) - }) - .collect(); - - let len = members.len(); - - if len > members_count { - members_count = len; + let mut args = Vec::new(); + + for n in node.members.iter_mut() { + let typ = DefineAndCheckTypeSignature::new( + self.state, + self.module, + &scope, + rules, + ) + .define_type(n); + + if is_stack && !typ.is_stack_allocated(self.db()) { + self.state.diagnostics.not_a_stack_type( + &format_type(self.db(), typ), + self.file(), + n.location(), + ); + } + + args.push(typ); + } + + let len = args.len(); + + if len > args_count { + args_count = len; } if len > MAX_MEMBERS { @@ -1119,12 +1143,12 @@ impl<'a> DefineConstructors<'a> { continue; } - if constructors_count == VARIANTS_LIMIT { + if constructors_count == CONSTRUCTORS_LIMIT { self.state.diagnostics.error( DiagnosticId::InvalidSymbol, format!( - "enums can't specify more than {} constructors", - VARIANTS_LIMIT + "enums can't define more than {} constructors", + CONSTRUCTORS_LIMIT ), self.file(), node.location, @@ -1137,7 +1161,7 @@ impl<'a> DefineConstructors<'a> { class_id.new_constructor( self.db_mut(), name.to_string(), - members, + args, node.location, ); } @@ -1146,7 +1170,7 @@ impl<'a> DefineConstructors<'a> { if constructors_count == 0 { self.state.diagnostics.error( DiagnosticId::InvalidType, - "enum classes must define at least a single constructor", + "'enum' types must define at least a single constructor", self.file(), node.location, ); @@ -1169,7 +1193,7 @@ impl<'a> DefineConstructors<'a> { loc, ); - for index in 0..members_count { + for index in 0..args_count { let id = index + 1; // The type of the field is the largest constructor argument for @@ -1205,6 +1229,49 @@ impl<'a> DefineConstructors<'a> { } } +/// A compiler pass that adds errors for recursive stack allocated classes. +pub(crate) fn check_recursive_types( + state: &mut State, + modules: &[hir::Module], +) -> bool { + for module in modules { + for expr in &module.expressions { + let (class, loc) = match expr { + hir::TopLevelExpression::Class(ref n) => { + let id = n.class_id.unwrap(); + + // Heap types _are_ allowed to be recursive as they can't + // recursive into themselves without indirection. + if !id.is_stack_allocated(&state.db) { + continue; + } + + (id, n.location) + } + hir::TopLevelExpression::ExternClass(ref n) => { + (n.class_id.unwrap(), n.location) + } + _ => continue, + }; + + // The recursion check is extracted into a separate type so we can + // separate visiting the IR and performing the actual check. + if !RecursiveClassChecker::new(&state.db).is_recursive(class) { + continue; + } + + state.diagnostics.error( + DiagnosticId::InvalidType, + "'inline' and 'extern' types can't be recursive", + module.module_id.file(&state.db), + loc, + ); + } + } + + !state.diagnostics.has_errors() +} + #[cfg(test)] mod tests { use super::*; @@ -1423,7 +1490,8 @@ mod tests { assert!(ImplementTraits::run_all(&mut state, &mut modules)); let imp = string.trait_implementation(&state.db, to_string).unwrap(); - let arg = imp.instance.type_arguments(&state.db).get(param).unwrap(); + let arg = + imp.instance.type_arguments(&state.db).unwrap().get(param).unwrap(); assert_eq!(imp.instance.instance_of(), to_string); diff --git a/compiler/src/type_check/expressions.rs b/compiler/src/type_check/expressions.rs index f2523fcdc..d0eb6b4c1 100644 --- a/compiler/src/type_check/expressions.rs +++ b/compiler/src/type_check/expressions.rs @@ -17,7 +17,7 @@ use types::{ FieldId, FieldInfo, IdentifierKind, IntrinsicCall, MethodId, MethodLookup, ModuleId, Receiver, Sign, Symbol, ThrowKind, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeId, TypeRef, Variable, VariableId, - CALL_METHOD, DEREF_POINTER_FIELD, IMPORT_MODULE_ITSELF_NAME, + CALL_METHOD, DEREF_POINTER_FIELD, }; const IGNORE_VARIABLE: &str = "_"; @@ -444,7 +444,7 @@ impl MethodCall { let name = self.method.name(&state.db); let rec = self.receiver; - if self.method.is_moving(&state.db) && !rec.allow_moving() { + if self.method.is_moving(&state.db) && !rec.allow_moving(&state.db) { state.diagnostics.error( DiagnosticId::InvalidCall, format!( @@ -493,7 +493,7 @@ impl MethodCall { expected: TypeRef, location: Location, ) -> TypeRef { - let given = argument.cast_according_to(expected, &state.db); + let given = argument.cast_according_to(&state.db, expected); if self.require_sendable || given.is_uni_ref(&state.db) { self.check_sendable.push((given, location)); @@ -1065,6 +1065,7 @@ impl<'a> CheckConstant<'a> { return TypeRef::Error; }; + let loc = node.location; let mut call = MethodCall::new( self.state, self.module, @@ -1074,8 +1075,8 @@ impl<'a> CheckConstant<'a> { method, ); - call.check_mutability(self.state, node.location); - call.check_type_bounds(self.state, node.location); + call.check_mutability(self.state, loc); + call.check_type_bounds(self.state, loc); call.arguments = 1; if let Some(expected) = @@ -1089,9 +1090,9 @@ impl<'a> CheckConstant<'a> { ); } - call.check_arguments(self.state, node.location); + call.check_arguments(self.state, loc); call.resolve_return_type(self.state); - call.check_sendable(self.state, node.location); + call.check_sendable(self.state, loc); node.resolved_type = call.return_type; node.resolved_type @@ -1581,7 +1582,7 @@ impl<'a> CheckMethodBody<'a> { self.check_if_self_is_allowed(scope, node.location); if scope.in_recover() { - typ = typ.as_uni_reference(self.db()); + typ = typ.as_uni_borrow(self.db()); } scope.mark_closures_as_capturing_self(self.db_mut()); @@ -1625,7 +1626,7 @@ impl<'a> CheckMethodBody<'a> { let var_type = if let Some(tnode) = node.value_type.as_mut() { let exp_type = self.type_signature(tnode, self.self_type); let value_casted = - value_type.cast_according_to(exp_type, self.db()); + value_type.cast_according_to(self.db(), exp_type); if !TypeChecker::check(self.db(), value_casted, exp_type) { self.state.diagnostics.type_error( @@ -1811,7 +1812,7 @@ impl<'a> CheckMethodBody<'a> { return; }; - let members = constructor.members(self.db()); + let members = constructor.arguments(self.db()); if !members.is_empty() { self.state.diagnostics.incorrect_pattern_arguments( @@ -1949,7 +1950,7 @@ impl<'a> CheckMethodBody<'a> { let fields = ins.instance_of().fields(self.db()); for (patt, vtype) in node.values.iter_mut().zip(raw_types.into_iter()) { - let typ = vtype.cast_according_to(value_type, self.db()); + let typ = vtype.cast_according_to(self.db(), value_type); self.pattern(patt, typ, pattern); values.push(typ); @@ -1969,33 +1970,21 @@ impl<'a> CheckMethodBody<'a> { return; } - let ins = match value_type { - TypeRef::Owned(TypeId::ClassInstance(ins)) - | TypeRef::Uni(TypeId::ClassInstance(ins)) - | TypeRef::Mut(TypeId::ClassInstance(ins)) - | TypeRef::Ref(TypeId::ClassInstance(ins)) - if ins - .instance_of() - .kind(self.db()) - .allow_pattern_matching() => - { - ins - } - _ => { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "a regular or extern class instance is expected, \ - but the input type is an instance of type '{}'", - format_type(self.db(), value_type), - ), - self.file(), - node.location, - ); + let Some(ins) = + value_type.as_class_instance_for_pattern_matching(self.db()) + else { + self.state.diagnostics.error( + DiagnosticId::InvalidType, + format!( + "this pattern can't be used with values of type '{}'", + format_type(self.db(), value_type), + ), + self.file(), + node.location, + ); - self.field_error_patterns(&mut node.values, pattern); - return; - } + self.field_error_patterns(&mut node.values, pattern); + return; }; let class = ins.instance_of(); @@ -2050,7 +2039,7 @@ impl<'a> CheckMethodBody<'a> { TypeResolver::new(&mut self.state.db, &args, self.bounds) .with_immutable(immutable) .resolve(raw_type) - .cast_according_to(value_type, self.db()); + .cast_according_to(self.db(), value_type); node.field_id = Some(field); @@ -2163,7 +2152,7 @@ impl<'a> CheckMethodBody<'a> { return; }; - let members = constructor.members(self.db()); + let members = constructor.arguments(self.db()).to_vec(); if members.len() != node.values.len() { self.state.diagnostics.incorrect_pattern_arguments( @@ -2185,7 +2174,7 @@ impl<'a> CheckMethodBody<'a> { let typ = TypeResolver::new(self.db_mut(), &args, bounds) .with_immutable(immutable) .resolve(member) - .cast_according_to(value_type, self.db()); + .cast_according_to(self.db(), value_type); self.pattern(patt, typ, pattern); } @@ -2535,6 +2524,7 @@ impl<'a> CheckMethodBody<'a> { } }; + let loc = node.location; let mut call = MethodCall::new( self.state, module, @@ -2544,11 +2534,11 @@ impl<'a> CheckMethodBody<'a> { method, ); - call.check_mutability(self.state, node.location); - call.check_type_bounds(self.state, node.location); - call.check_arguments(self.state, node.location); + call.check_mutability(self.state, loc); + call.check_type_bounds(self.state, loc); + call.check_arguments(self.state, loc); call.resolve_return_type(self.state); - call.check_sendable(self.state, node.location); + call.check_sendable(self.state, loc); let returns = call.return_type; @@ -2657,6 +2647,7 @@ impl<'a> CheckMethodBody<'a> { } }; + let loc = node.location; let mut call = MethodCall::new( self.state, module, @@ -2666,11 +2657,11 @@ impl<'a> CheckMethodBody<'a> { method, ); - call.check_mutability(self.state, node.location); - call.check_type_bounds(self.state, node.location); - call.check_arguments(self.state, node.location); + call.check_mutability(self.state, loc); + call.check_type_bounds(self.state, loc); + call.check_arguments(self.state, loc); call.resolve_return_type(self.state); - call.check_sendable(self.state, node.location); + call.check_sendable(self.state, loc); let returns = call.return_type; node.kind = IdentifierKind::Method(CallInfo { @@ -2702,18 +2693,26 @@ impl<'a> CheckMethodBody<'a> { return TypeRef::Error; }; - node.field_id = Some(field); - - let mut ret = - raw_type.cast_according_to(scope.surrounding_type, self.db()); + let (mut ret, as_pointer) = self.borrow_field( + scope.surrounding_type, + raw_type, + node.in_mut, + false, + ); if scope.in_recover() { - ret = ret.as_uni_reference(self.db()); + ret = ret.as_uni_borrow(self.db()); } + node.info = Some(FieldInfo { + class: scope.surrounding_type.class_id(self.db()).unwrap(), + id: field, + variable_type: ret, + as_pointer, + }); + scope.mark_closures_as_capturing_self(self.db_mut()); - node.resolved_type = ret; - node.resolved_type + ret } fn assign_field( @@ -3103,7 +3102,7 @@ impl<'a> CheckMethodBody<'a> { let expr = self.expression(&mut node.value, scope); - if !expr.allow_as_mut(self.db()) { + if !expr.allow_mutating(self.db()) { self.state.diagnostics.error( DiagnosticId::InvalidType, format!( @@ -3245,7 +3244,7 @@ impl<'a> CheckMethodBody<'a> { } }; - let loc = node.location; + let loc = node.name.location; let mut call = MethodCall::new( self.state, self.module, @@ -3262,7 +3261,7 @@ impl<'a> CheckMethodBody<'a> { call.check_arguments(self.state, loc); call.resolve_return_type(self.state); - call.check_sendable(self.state, node.location); + call.check_sendable(self.state, loc); let returns = call.return_type; let rec_info = Receiver::with_receiver(self.db(), receiver, method); @@ -3315,7 +3314,7 @@ impl<'a> CheckMethodBody<'a> { let bounds = self.bounds; let var_type = TypeResolver::new(self.db_mut(), &targs, bounds).resolve(raw_type); - let value = value.cast_according_to(var_type, self.db()); + let value = value.cast_according_to(self.db(), var_type); if !TypeChecker::check(self.db(), value, var_type) { self.state.diagnostics.type_error( @@ -3405,7 +3404,7 @@ impl<'a> CheckMethodBody<'a> { let var_type = TypeResolver::new(self.db_mut(), &targs, bounds).resolve(raw_type); - let value = value.cast_according_to(var_type, self.db()); + let value = value.cast_according_to(self.db(), var_type); if !TypeChecker::check(self.db(), value, var_type) { self.state.diagnostics.type_error( @@ -3526,7 +3525,7 @@ impl<'a> CheckMethodBody<'a> { let given = self .argument_expression(exp, &mut pos_node.value, scope, &targs) - .cast_according_to(exp, self.db()); + .cast_according_to(self.db(), exp); if !TypeChecker::check(self.db(), given, exp) { self.state.diagnostics.type_error( @@ -3671,6 +3670,7 @@ impl<'a> CheckMethodBody<'a> { } }; + let loc = node.name.location; let mut call = MethodCall::new( self.state, self.module, @@ -3680,12 +3680,12 @@ impl<'a> CheckMethodBody<'a> { method, ); - call.check_mutability(self.state, node.location); - call.check_type_bounds(self.state, node.location); + call.check_mutability(self.state, loc); + call.check_type_bounds(self.state, loc); self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_arguments(self.state, node.location); + call.check_arguments(self.state, loc); call.resolve_return_type(self.state); - call.check_sendable(self.state, node.location); + call.check_sendable(self.state, loc); let returns = call.return_type; let rec_info = Receiver::with_receiver(self.db(), receiver, method); @@ -3765,7 +3765,7 @@ impl<'a> CheckMethodBody<'a> { self.state.diagnostics.undefined_symbol( name, self.file(), - node.location, + node.name.location, ); return TypeRef::Error; @@ -3774,6 +3774,7 @@ impl<'a> CheckMethodBody<'a> { } }; + let loc = node.name.location; let mut call = MethodCall::new( self.state, self.module, @@ -3783,12 +3784,12 @@ impl<'a> CheckMethodBody<'a> { method, ); - call.check_mutability(self.state, node.location); - call.check_type_bounds(self.state, node.location); + call.check_mutability(self.state, loc); + call.check_type_bounds(self.state, loc); self.call_arguments(&mut node.arguments, &mut call, scope); - call.check_arguments(self.state, node.location); + call.check_arguments(self.state, loc); call.resolve_return_type(self.state); - call.check_sendable(self.state, node.location); + call.check_sendable(self.state, loc); let returns = call.return_type; @@ -3882,7 +3883,7 @@ impl<'a> CheckMethodBody<'a> { ); } - let targs = ins.type_arguments(self.db()).clone(); + let targs = ins.type_arguments(self.db()).unwrap().clone(); // The field type is the _raw_ type, but we want one that takes into // account what we have inferred thus far. Consider the following @@ -3912,7 +3913,7 @@ impl<'a> CheckMethodBody<'a> { }; let value = self.expression(val_expr, scope); - let value_casted = value.cast_according_to(expected, self.db()); + let value_casted = value.cast_according_to(self.db(), expected); let checker = TypeChecker::new(self.db()); let mut env = Environment::new(value_casted.type_arguments(self.db()), targs); @@ -3999,26 +4000,17 @@ impl<'a> CheckMethodBody<'a> { self.lookup_field_with_receiver(receiver_id, &node.name)?; let raw_type = field.value_type(self.db_mut()); let immutable = receiver.is_ref(self.db_mut()); - let args = ins.type_arguments(self.db_mut()).clone(); + let args = ins.type_arguments(self.db_mut()).unwrap().clone(); let bounds = self.bounds; - let mut returns = TypeResolver::new(self.db_mut(), &args, bounds) + let returns = TypeResolver::new(self.db_mut(), &args, bounds) .with_immutable(immutable) .resolve(raw_type); - let as_pointer = returns.is_extern_instance(self.db()) - || (node.in_mut && returns.is_foreign_type(self.db())); - if returns.is_value_type(self.db()) { - returns = if as_pointer { - returns.as_pointer(self.db()) - } else { - returns.as_owned(self.db()) - }; - } else if !immutable && raw_type.is_owned_or_uni(self.db()) { - returns = returns.as_mut(self.db()); - } + let (mut returns, as_pointer) = + self.borrow_field(receiver, returns, node.in_mut, true); if receiver.require_sendable_arguments(self.db()) { - returns = returns.as_uni_reference(self.db()); + returns = returns.as_uni_borrow(self.db()); } node.kind = CallKind::GetField(FieldInfo { @@ -4046,7 +4038,7 @@ impl<'a> CheckMethodBody<'a> { self.state.diagnostics.undefined_symbol( &node.name.name, self.file(), - node.location, + node.name.location, ); return TypeRef::Error; @@ -4544,7 +4536,7 @@ impl<'a> CheckMethodBody<'a> { while let Some(scope) = scopes.pop() { match scope.kind { ScopeKind::Recover => { - expose_as = expose_as.as_uni_reference(self.db()); + expose_as = expose_as.as_uni_borrow(self.db()); } ScopeKind::Closure(closure) => { let moving = closure.is_moving(self.db()); @@ -4645,56 +4637,31 @@ impl<'a> CheckMethodBody<'a> { Some((ins, field)) } -} - -/// A pass that checks for any unused imported symbols. -pub(crate) fn check_unused_imports( - state: &mut State, - modules: &[hir::Module], -) -> bool { - for module in modules { - let mod_id = module.module_id; - - for expr in &module.expressions { - let import = if let hir::TopLevelExpression::Import(v) = expr { - v - } else { - continue; - }; - let tail = &import.source.last().unwrap().name; - - if import.symbols.is_empty() { - if mod_id.symbol_is_used(&state.db, tail) { - continue; - } - - let file = mod_id.file(&state.db); - let loc = import.location; - - state.diagnostics.unused_symbol(tail, file, loc); - } else { - for sym in &import.symbols { - let mut name = &sym.import_as.name; - - if name == IMPORT_MODULE_ITSELF_NAME { - name = tail; - } + fn borrow_field( + &self, + receiver: TypeRef, + typ: TypeRef, + in_mut: bool, + borrow: bool, + ) -> (TypeRef, bool) { + let db = self.db(); - if mod_id.symbol_is_used(&state.db, name) - || name.starts_with('_') - { - continue; - } + // Foreign types are as raw pointers when necessary for FFI purposes. + if (in_mut && typ.is_foreign_type(db)) || typ.is_extern_instance(db) { + return (typ.as_pointer(db), true); + } - let file = mod_id.file(&state.db); - let loc = sym.location; + let res = if typ.is_value_type(db) { + typ + } else if receiver.is_ref(db) { + typ.as_ref(db) + } else if receiver.is_mut(db) || borrow { + typ.as_mut(db) + } else { + typ + }; - state.diagnostics.unused_symbol(name, file, loc); - } - } - } + (res, false) } - - !state.diagnostics.has_errors() } diff --git a/compiler/src/type_check/graph.rs b/compiler/src/type_check/graph.rs new file mode 100644 index 000000000..d55767d0d --- /dev/null +++ b/compiler/src/type_check/graph.rs @@ -0,0 +1,103 @@ +//! Helpers for performing graph-like operations on types, such as checking if a +//! class is recursive. +use types::{ClassId, ClassInstance, Database, TypeRef}; + +#[derive(Copy, Clone)] +enum Visit { + /// The node has yet to be visited. + Unvisited, + + /// The node is in the queue but has yet to be visited. + /// + /// This state exists to ensure we don't schedule the same node multiple + /// times. + Scheduled, + + /// A node's edges are being visited. + Visiting, + + /// The node and its edges have been visited. + Visited, +} + +/// A type used for checking if a stack class is a recursive class. +pub(crate) struct RecursiveClassChecker<'a> { + db: &'a Database, + states: Vec, + work: Vec, +} + +impl<'a> RecursiveClassChecker<'a> { + pub(crate) fn new(db: &'a Database) -> RecursiveClassChecker<'a> { + RecursiveClassChecker { + db, + states: vec![Visit::Unvisited; db.number_of_classes()], + work: Vec::new(), + } + } + + pub(crate) fn is_recursive(&mut self, class: ClassId) -> bool { + self.add(class); + + while let Some(&class) = self.work.last() { + if let Visit::Visiting = self.state(class) { + self.set_state(class, Visit::Visited); + self.work.pop(); + continue; + } + + self.set_state(class, Visit::Visiting); + + for field in class.fields(self.db) { + let typ = field.value_type(self.db); + let Some(ins) = self.edge(typ) else { continue }; + + match self.state(ins.instance_of()) { + Visit::Unvisited => self.add(ins.instance_of()), + Visit::Visiting => return true, + _ => continue, + } + + if !ins.instance_of().is_generic(self.db) { + continue; + } + + for (_, &typ) in ins.type_arguments(self.db).unwrap().iter() { + let Some(ins) = self.edge(typ) else { continue }; + + match self.state(ins.instance_of()) { + Visit::Unvisited => self.add(ins.instance_of()), + Visit::Visiting => return true, + _ => continue, + } + } + } + } + + false + } + + fn edge(&self, typ: TypeRef) -> Option { + // Pointers _are_ stack allocated, but they introduce indirection that + // breaks recursion so we don't need to process them. + if typ.is_pointer(self.db) { + return None; + } + + typ.as_class_instance(self.db) + .filter(|v| v.instance_of().is_stack_allocated(self.db)) + } + + fn set_state(&mut self, id: ClassId, state: Visit) { + self.states[id.0 as usize] = state; + } + + fn state(&self, id: ClassId) -> Visit { + self.states[id.0 as usize] + } + + fn add(&mut self, id: ClassId) { + self.set_state(id, Visit::Scheduled); + self.work.push(id); + } +} diff --git a/compiler/src/type_check/imports.rs b/compiler/src/type_check/imports.rs index 4f1d0a28b..708ae6527 100644 --- a/compiler/src/type_check/imports.rs +++ b/compiler/src/type_check/imports.rs @@ -178,6 +178,58 @@ impl<'a> CollectExternImports<'a> { } } +/// A pass that checks for any unused imported symbols. +pub(crate) fn check_unused_imports( + state: &mut State, + modules: &[hir::Module], +) -> bool { + for module in modules { + let mod_id = module.module_id; + + for expr in &module.expressions { + let import = if let hir::TopLevelExpression::Import(v) = expr { + v + } else { + continue; + }; + + let tail = &import.source.last().unwrap().name; + + if import.symbols.is_empty() { + if mod_id.symbol_is_used(&state.db, tail) { + continue; + } + + let file = mod_id.file(&state.db); + let loc = import.location; + + state.diagnostics.unused_symbol(tail, file, loc); + } else { + for sym in &import.symbols { + let mut name = &sym.import_as.name; + + if name == IMPORT_MODULE_ITSELF_NAME { + name = tail; + } + + if mod_id.symbol_is_used(&state.db, name) + || name.starts_with('_') + { + continue; + } + + let file = mod_id.file(&state.db); + let loc = sym.location; + + state.diagnostics.unused_symbol(name, file, loc); + } + } + } + } + + !state.diagnostics.has_errors() +} + #[cfg(test)] mod tests { use super::*; diff --git a/compiler/src/type_check/methods.rs b/compiler/src/type_check/methods.rs index 2c46eb43b..483616a91 100644 --- a/compiler/src/type_check/methods.rs +++ b/compiler/src/type_check/methods.rs @@ -97,6 +97,10 @@ trait MethodDefiner { pid.set_mutable(self.db_mut()); } + if param_node.inline { + pid.set_stack_allocated(self.db_mut()); + } + param_node.type_parameter_id = Some(pid); } } @@ -244,6 +248,33 @@ trait MethodDefiner { class_id.add_method(self.db_mut(), name.to_string(), method); } } + + fn check_if_mutating_method_is_allowed( + &mut self, + kind: hir::MethodKind, + class: ClassId, + location: Location, + ) { + if !matches!(kind, hir::MethodKind::Mutable) + || class.allow_mutating(self.db()) + { + return; + } + + let name = class.name(self.db()).clone(); + let file = self.file(); + + self.state_mut().diagnostics.error( + DiagnosticId::InvalidMethod, + format!( + "'{}' doesn't support mutating methods because it's an \ + immutable type", + name + ), + file, + location, + ); + } } /// A compiler pass that defines the basic details for module methods. @@ -668,15 +699,21 @@ impl<'a> DefineMethods<'a> { ) { let async_class = class_id.kind(self.db()).is_async(); - if node.kind.is_moving() && async_class { + if matches!(node.kind, hir::MethodKind::Moving) && async_class { self.state.diagnostics.error( DiagnosticId::InvalidMethod, - "moving methods can't be defined for async classes", + "moving methods can't be defined for 'async' types", self.file(), node.location, ); } + self.check_if_mutating_method_is_allowed( + node.kind, + class_id, + node.location, + ); + let self_id = TypeId::Class(class_id); let module = self.module; let vis = if async_class { @@ -711,7 +748,7 @@ impl<'a> DefineMethods<'a> { if async_class && method.is_public(self.db()) { self.state.diagnostics.error( DiagnosticId::InvalidMethod, - "regular instance methods for async classes must be private", + "instance methods defined on 'async' types must be private", self.file(), node.location, ); @@ -996,6 +1033,7 @@ impl<'a> DefineMethods<'a> { node.method_id = Some(method); } + #[allow(clippy::unnecessary_to_owned)] fn define_constructor_method( &mut self, class_id: ClassId, @@ -1024,7 +1062,7 @@ impl<'a> DefineMethods<'a> { class_id.constructor(self.db(), &node.name.name).unwrap(); for (index, typ) in - constructor.members(self.db()).into_iter().enumerate() + constructor.arguments(self.db()).to_vec().into_iter().enumerate() { let var_type = typ.as_rigid_type(self.db_mut(), &bounds); @@ -1194,8 +1232,23 @@ impl<'a> ImplementTraitMethods<'a> { let trait_id = trait_ins.instance_of(); let class_ins = node.class_instance.unwrap(); let class_id = class_ins.instance_of(); + let mut mut_error = false; + let allow_mut = class_id.allow_mutating(self.db()); for method in trait_id.default_methods(self.db()) { + if method.is_mutable(self.db()) && !allow_mut && !mut_error { + self.state.diagnostics.error( + DiagnosticId::InvalidImplementation, + "the trait '{}' can't be implemented because it defines \ + one or more mutating methods, and '{}' is an immutable \ + type", + self.file(), + node.location, + ); + + mut_error = true; + } + if !class_id.method_exists(self.db(), method.name(self.db())) { continue; } @@ -1205,7 +1258,7 @@ impl<'a> ImplementTraitMethods<'a> { let method_name = format_type(self.db(), method); let file = self.file(); - self.state_mut().diagnostics.error( + self.state.diagnostics.error( DiagnosticId::InvalidImplementation, format!( "the trait '{}' can't be implemented for '{}', as its \ @@ -1302,6 +1355,20 @@ impl<'a> ImplementTraitMethods<'a> { return; }; + let is_drop = trait_instance.instance_of() == self.drop_trait + && name == DROP_METHOD; + + // `Drop.drop` is the only exception because it may be used to e.g. + // deallocate memory, which is an immutable type (as is the case for + // `String.drop`). + if !is_drop { + self.check_if_mutating_method_is_allowed( + node.kind, + class_instance.instance_of(), + node.location, + ); + } + let self_type = TypeId::ClassInstance(class_instance); let module = self.module; let method = Method::alloc( @@ -1381,9 +1448,7 @@ impl<'a> ImplementTraitMethods<'a> { ); } - if trait_instance.instance_of() == self.drop_trait - && name == DROP_METHOD - { + if is_drop { // We do this after the type-check so incorrect implementations are // detected properly. method.mark_as_destructor(self.db_mut()); diff --git a/compiler/src/type_check/mod.rs b/compiler/src/type_check/mod.rs index de38159b2..8a6390a8c 100644 --- a/compiler/src/type_check/mod.rs +++ b/compiler/src/type_check/mod.rs @@ -9,15 +9,16 @@ use types::format::format_type; use types::{ Block, ClassId, ClassInstance, Closure, Database, MethodId, ModuleId, Symbol, TraitId, TraitInstance, TypeArguments, TypeBounds, TypeId, - TypeParameter, TypeParameterId, TypeRef, + TypeParameterId, TypeRef, }; pub(crate) mod define_types; pub(crate) mod expressions; +pub(crate) mod graph; pub(crate) mod imports; pub(crate) mod methods; -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Debug)] enum RefKind { Default, Owned, @@ -270,6 +271,19 @@ impl<'a> DefineTypeSignature<'a> { id, ))) } + Symbol::Class(id) if id.is_stack_allocated(&self.state.db) => { + if matches!(kind, RefKind::Mut) { + let name = &id.name(self.db()).clone(); + + self.state.diagnostics.invalid_mut_type( + name, + self.file(), + node.location, + ); + } + + TypeRef::Owned(self.define_class_instance(id, node)) + } Symbol::Class(id) => { kind.into_type_ref(self.define_class_instance(id, node)) } @@ -413,13 +427,10 @@ impl<'a> DefineTypeSignature<'a> { if let RefKind::Mut = kind { if !param_id.is_mutable(self.db()) { - self.state.diagnostics.error( - DiagnosticId::InvalidType, - format!( - "the type 'mut {name}' is invalid, as '{name}' \ - might be immutable at runtime", - name = id.name(self.db()), - ), + let name = id.name(self.db()).clone(); + + self.state.diagnostics.invalid_mut_type( + &name, self.file(), node.location, ); @@ -696,7 +707,7 @@ impl<'a> CheckTypeSignature<'a> { self.check_argument_types( node, instance.instance_of().type_parameters(self.db()), - instance.type_arguments(self.db()).clone(), + instance.type_arguments(self.db()).unwrap().clone(), ); } } @@ -717,7 +728,7 @@ impl<'a> CheckTypeSignature<'a> { self.check_argument_types( node, instance.instance_of().type_parameters(self.db()), - instance.type_arguments(self.db()).clone(), + instance.type_arguments(self.db()).unwrap().clone(), ); } } @@ -902,8 +913,8 @@ pub(crate) fn define_type_bounds( continue; } - let mut reqs = param.requirements(&state.db); - let new_param = TypeParameter::alloc(&mut state.db, name.clone()); + let mut reqs = Vec::new(); + let new_param = param.clone_for_bound(&mut state.db); for req in &mut bound.requirements { let rules = Rules::default(); @@ -920,7 +931,10 @@ pub(crate) fn define_type_bounds( new_param.set_mutable(&mut state.db); } - new_param.set_original(&mut state.db, param); + if bound.inline { + new_param.set_stack_allocated(&mut state.db); + } + new_param.add_requirements(&mut state.db, reqs); bounds.set(param, new_param); } diff --git a/docs/inko.pkg b/docs/inko.pkg index ff27427af..a62abf175 100644 --- a/docs/inko.pkg +++ b/docs/inko.pkg @@ -1,3 +1,4 @@ require https://github.com/yorickpeterse/inko-wobsite 0.19.0 fa5e47733423aa6a902028e69de5a374a1757377 require https://github.com/yorickpeterse/inko-builder 0.13.0 7a38803e1fcd80e19ad2ea8fd90b9babf70e93a6 require https://github.com/yorickpeterse/inko-markdown 0.21.0 3726c10b499242cb3febc931a82e35217e2f987a +require https://github.com/yorickpeterse/inko-syntax 0.13.0 18c5c4c31512c2717fcbf13f6163cbf33e80e2b8 diff --git a/docs/source/design/compiler.md b/docs/source/design/compiler.md index a5d6b47f8..df45c360f 100644 --- a/docs/source/design/compiler.md +++ b/docs/source/design/compiler.md @@ -325,3 +325,11 @@ slot = hash & (size - 1) To handle conflicts we use linear probing, resulting in an efficient implementation of dynamic dispatch. For more information, refer to the article ["Shenanigans With Hash Tables"](https://thume.ca/2019/07/29/shenanigans-with-hash-tables/). + +### ABI + +Officially generated code has no stable ABI, and this is unlikely to change any +time soon. Internally, we implement the C/system ABI as we need to do so for +interacting with foreign code anyway. This means that for example structures on +the stack are passed and returned the same way as is done by C compilers such as +clang. diff --git a/docs/source/getting-started/classes.md b/docs/source/getting-started/classes.md index aaebeb6b4..058f5f482 100644 --- a/docs/source/getting-started/classes.md +++ b/docs/source/getting-started/classes.md @@ -4,8 +4,9 @@ } --- -Classes are used for storing state used by methods. One such class we've seen -many times so far is the `Main` class, which defines the main process to run. +Classes are used for storing state used by methods, and instances of classes are +allocated on the heap. One such class we've seen many times so far is the `Main` +class, which defines the main process to run. Classes are defined using the `class` keyword like so: @@ -84,6 +85,26 @@ When moving a field, the remaining fields are dropped individually and the owner of the moved field is partially dropped. If a type defines a custom destructor, a `move` method can't move the fields out of its receiver. +## Reopening classes + +A class can be reopened using the `impl` keyword like so: + +```inko +class Person { + let @name: String + let @age: Int +} + +impl Person { + fn greet -> String { + 'Hello ${@name}' + } +} +``` + +When reopening a class, only new methods can be added to the class. It's a +compile-time error to try to add a field or overwrite an existing method. + ## Swapping field values Similar to local variables, `:=` can be used to assign a field a new value and @@ -191,6 +212,78 @@ OptionalString.Some('hello') Unlike other types of classes, you can't use the syntax `OptionalString(...)` to create an instance of an enum class. +## Value types + +While allocating instances of classes on the heap increases flexibility (e.g. +they can be moved around while they're also borrowed), this can reduce +performance when many such instances are allocated. + +We can avoid this by using the `inline` modifier when defining a class: + +```inko +class inline Number { + let @value: Int +} +``` + +The `inline` modifier is also available for enums: + +```inko +class inline enum Example { + case A(Int) + case B(Float) +} +``` + +When using this modifier, instances of the class are allocated on the stack and +become _immutable_ value types that are copied upon a move. Unlike their heap +counterparts, such types don't use an object header. For the above `Number` +example that means the memory representation is the same as that of the `Int` +type. + +Because these types are immutable, it's not possible to assign fields new values +or define `fn mut` methods on such types. Instead, the approach to "mutation" is +to return a new copy of the instance containing the appropriate changes. For +example: + +```inko +class inline Number { + let @value: Int + + fn increment(amount: Int) -> Number { + Number(@value + amount) + } +} +``` + +Classes defined using the `inline` modifier can only store the following types: + +- `Int`, `Float`, `Bool`, `Nil` +- Other `inline` types + +Most notably, `String` values can't be stored in an `inline` type since `String` +uses atomic reference counting. This means the following definition is invalid: + +```inko +class inline InvalidType { + let @value: Array[Int] # Array[Int] isn't an `inline` type +} +``` + +The same restriction applies to generic type parameters: + +```inko +class inline Box[T] { + let @value: T +} + +Box([10]) # T requires an `inline` type, but `Array[Int]` isn't such a type +``` + +It's recommended to use the `inline` modifier whenever possible (i.e. a class +just stores a bunch of `Int` values), provided the above restrictions don't get +in your way of course. + ## Processes Processes are defined using `class async`, and creating instances of such diff --git a/docs/source/getting-started/ffi.md b/docs/source/getting-started/ffi.md index 6cbefd65e..91885800a 100644 --- a/docs/source/getting-started/ffi.md +++ b/docs/source/getting-started/ffi.md @@ -655,9 +655,7 @@ of the C libraries out there. Most notably, the following isn't supported: support this it wouldn't make your life easier. - Compiling C source code as part of the Inko build process. - Compile-time expressions such as `sizeof()` to automatically get type sizes. -- Setting `errno` to a custom value. `errno` is implemented differently across - libc implementations, and Rust (which we use for getting the value) doesn't - support writing to `errno`. +- Setting `errno` to a custom value. [^1]: On 32-bit platforms this type would have a size of 32 bits, but Inko doesn't support 32-bit platforms, so in practise this value is always 64 diff --git a/docs/source/getting-started/generics.md b/docs/source/getting-started/generics.md index 3d89e4498..1b77caa60 100644 --- a/docs/source/getting-started/generics.md +++ b/docs/source/getting-started/generics.md @@ -149,3 +149,91 @@ class Example[B: Equal[B]] { In this example `B: Equal[B]` means that for a type `Foo` to be compatible with `B`, it must implement the trait `Equal[Foo]`. + +Apart from using traits as requirements, you can also use the following +capabilities in the requirements list: + +- `mut`: restricts types owned types and mutable borrows, and allows the use of + `fn mut` methods +- `inline`: restricts types to those that are `inline` + +::: warn +It's a compile-time error to specify _both_ the `mut` and `inline` requirements, +as `inline` types are immutable. +::: + +Take this type for example: + +```inko +trait Update { + fn mut update +} + +class Example[T: Update] { + let @value: T + + fn mut update { + @value.update + } +} +``` + +If you try to compile this code it will fail with a compile-time error: + +``` +test.inko:9:12 error(invalid-call): the method 'update' requires a mutable receiver, but 'ref T' isn't mutable +``` + +The reason for this error is that `T` allows the assignment of immutable types, +such as a `ref Something`, and we can't call mutating methods on such types. +We'd get a similar compile-time error when trying to create a mutable borrow of +`@value`, as we can't borrow something mutably without ensuring it's in fact +mutable. + +To fix such compile-time errors, specify the `mut` requirement like so: + +```inko +trait Update { + fn mut update +} + +class Example[T: mut + Update] { + let @value: T + + fn mut update { + @value.update + } +} +``` + +Type parameter requirements can also be specified when reopening a generic +class: + +```inko +trait Update { + fn mut update +} + +class Example[T] { + let @value: T +} + +impl Example if T: mut + Update { + fn mut update { + @value.update + } +} +``` + +This allows adding of methods with additional requirements to an existing type, +without requiring the extra requirements for all instances of the type. For +example, the standard library uses this for methods such as `Array.get_mut`: + +```inko +impl Array if T: mut { + fn pub mut get_mut(index: Int) -> mut T { + bounds_check(index, @size) + get_unchecked_mut(index) + } +} +``` diff --git a/docs/source/getting-started/traits.md b/docs/source/getting-started/traits.md index 3b6fceef3..ada2edb81 100644 --- a/docs/source/getting-started/traits.md +++ b/docs/source/getting-started/traits.md @@ -146,3 +146,24 @@ It's possible for different traits to define methods with the same name. If a type tries to implement such traits, a compile-time error is produced. Inko doesn't support renaming of trait methods as part of the implementation, so you'll need to find a way to resolve such conflicts yourself. + +## Conditional trait implementations + +Sometimes we want to implement a trait, but only if additional requirements are +met. For example, we want to implement `std.cmp.Equal` for `Array` but only if +its sub values also implement `std.cmp.Equal`. This is done as follows: + +```inko +import std.cmp (Equal) + +impl Equal[ref Array[T]] for Array if T: Equal[ref T] { + fn pub ==(other: ref Array[T]) -> Bool { + ... + } +} +``` + +What happens here is that we implement `Equal` over `ref Array[T]`, for any +`Array[T]` _provided_ that whatever is assigned to `T` also implements +`Equal[ref T]`. For example, given an `Array[User]`, the `Array.==` method is +only available if `User` implements `Equal[ref User]`. diff --git a/docs/source/references/syntax.md b/docs/source/references/syntax.md index 3bcfc2385..c12e73d5c 100644 --- a/docs/source/references/syntax.md +++ b/docs/source/references/syntax.md @@ -265,6 +265,14 @@ class enum Result { Enum classes can't define regular fields. +### Stack allocated classes + +Stack allocated classes are defined using the `inline` keyword: + +```inko +class pub inline Example {} +``` + ### Generic classes Generic classes are defined like so: diff --git a/std/fixtures/diagnostics/casting_value_types.inko b/std/fixtures/diagnostics/casting_value_types.inko new file mode 100644 index 000000000..d03d56fa8 --- /dev/null +++ b/std/fixtures/diagnostics/casting_value_types.inko @@ -0,0 +1,33 @@ +import std.string (ToString) + +class inline A {} + +impl ToString for A { + fn pub to_string -> String { + 'A' + } +} + +class B {} + +impl ToString for B { + fn pub to_string -> String { + 'B' + } +} + +fn example { + '10' as ToString + 10 as ToString + 1.0 as ToString + A() as ToString + B() as ToString + A() as UInt64 + 0x4 as Pointer[UInt64] as UInt64 +} + +# casting_value_types.inko:20:3 error(invalid-cast): the type 'String' can't be cast to 'ToString' +# casting_value_types.inko:21:3 error(invalid-cast): the type 'Int' can't be cast to 'ToString' +# casting_value_types.inko:22:3 error(invalid-cast): the type 'Float' can't be cast to 'ToString' +# casting_value_types.inko:23:3 error(invalid-cast): the type 'A' can't be cast to 'ToString' +# casting_value_types.inko:25:3 error(invalid-cast): the type 'A' can't be cast to 'UInt64' diff --git a/std/fixtures/diagnostics/default_method_with_bounds.inko b/std/fixtures/diagnostics/default_method_with_bounds.inko index 9a3c44f60..52c3124af 100644 --- a/std/fixtures/diagnostics/default_method_with_bounds.inko +++ b/std/fixtures/diagnostics/default_method_with_bounds.inko @@ -28,5 +28,5 @@ fn invalid { Box(10).bar } -# default_method_with_bounds.inko:27:3 error(invalid-symbol): the method 'foo' exists but isn't available because one or more type parameter bounds aren't met -# default_method_with_bounds.inko:28:3 error(invalid-symbol): the method 'bar' exists but isn't available because one or more type parameter bounds aren't met +# default_method_with_bounds.inko:27:11 error(invalid-symbol): the method 'foo' exists but isn't available because one or more type parameter bounds aren't met +# default_method_with_bounds.inko:28:11 error(invalid-symbol): the method 'bar' exists but isn't available because one or more type parameter bounds aren't met diff --git a/std/fixtures/diagnostics/immutable_types_implementing_mutating_methods.inko b/std/fixtures/diagnostics/immutable_types_implementing_mutating_methods.inko new file mode 100644 index 000000000..9793d1f72 --- /dev/null +++ b/std/fixtures/diagnostics/immutable_types_implementing_mutating_methods.inko @@ -0,0 +1,21 @@ +trait Mutate { + fn mut foo + + fn mut bar {} + + fn mut baz {} + + fn mut quix {} +} + +class inline A {} + +impl Mutate for A { + fn mut foo {} + + fn mut quix {} +} + +# immutable_types_implementing_mutating_methods.inko:13:1 error(invalid-implementation): the trait '{}' can't be implemented because it defines one or more mutating methods, and '{}' is an immutable type +# immutable_types_implementing_mutating_methods.inko:14:3 error(invalid-method): 'A' doesn't support mutating methods because it's an immutable type +# immutable_types_implementing_mutating_methods.inko:16:3 error(invalid-method): 'A' doesn't support mutating methods because it's an immutable type diff --git a/std/fixtures/diagnostics/immutable_types_with_mutating_methods.inko b/std/fixtures/diagnostics/immutable_types_with_mutating_methods.inko new file mode 100644 index 000000000..59abf4cd7 --- /dev/null +++ b/std/fixtures/diagnostics/immutable_types_with_mutating_methods.inko @@ -0,0 +1,10 @@ +class inline A { + fn mut invalid {} +} + +impl String { + fn mut invalid {} +} + +# immutable_types_with_mutating_methods.inko:2:3 error(invalid-method): 'A' doesn't support mutating methods because it's an immutable type +# immutable_types_with_mutating_methods.inko:6:3 error(invalid-method): 'String' doesn't support mutating methods because it's an immutable type diff --git a/std/fixtures/diagnostics/inline_enum_definitions.inko b/std/fixtures/diagnostics/inline_enum_definitions.inko new file mode 100644 index 000000000..11b658190 --- /dev/null +++ b/std/fixtures/diagnostics/inline_enum_definitions.inko @@ -0,0 +1,14 @@ +class inline enum Valid { + case A(Int, Float) +} + +class inline enum Invalid1 { + case A(Int, String) +} + +class inline enum Invalid2 { + case A(Int, Array[Int]) +} + +# inline_enum_definitions.inko:6:15 error(invalid-type): an 'inline' or 'extern' type is expected, but 'String' is a heap type +# inline_enum_definitions.inko:10:15 error(invalid-type): an 'inline' or 'extern' type is expected, but 'Array[Int]' is a heap type diff --git a/std/fixtures/diagnostics/inline_type_definitions.inko b/std/fixtures/diagnostics/inline_type_definitions.inko new file mode 100644 index 000000000..bdbf4c6ad --- /dev/null +++ b/std/fixtures/diagnostics/inline_type_definitions.inko @@ -0,0 +1,14 @@ +class inline A {} + +class pub inline B {} + +class inline enum C {} + +class pub inline enum D {} + +class inline async E {} + +class inline extern F {} + +# inline_type_definitions.inko:9:20 error(invalid-type): only regular and 'enum' types support the 'inline' attribute +# inline_type_definitions.inko:11:21 error(invalid-type): only regular and 'enum' types support the 'inline' attribute diff --git a/std/fixtures/diagnostics/inline_type_instances.inko b/std/fixtures/diagnostics/inline_type_instances.inko new file mode 100644 index 000000000..d80ea384c --- /dev/null +++ b/std/fixtures/diagnostics/inline_type_instances.inko @@ -0,0 +1,15 @@ +class inline A[T] { + let @value: T +} + +class extern B { + let @value: Int +} + +fn example1 { + A(value: 42) + A(value: B(value: 42)) + A(value: 'not a stack type') +} + +# inline_type_instances.inko:12:12 error(invalid-type): expected a value of type 'T: inline', found 'String' diff --git a/std/fixtures/diagnostics/inline_type_parameters.inko b/std/fixtures/diagnostics/inline_type_parameters.inko new file mode 100644 index 000000000..17b5f109e --- /dev/null +++ b/std/fixtures/diagnostics/inline_type_parameters.inko @@ -0,0 +1,15 @@ +class Example[T: inline] { + let @value: T +} + +fn example[T: inline](value: T) {} + +fn examples { + Example(42) + Example([10]) + example(42) + example([10]) +} + +# inline_type_parameters.inko:9:11 error(invalid-type): expected a value of type 'T: inline', found 'Array[Int]' +# inline_type_parameters.inko:11:11 error(invalid-type): expected a value of type 'T: inline', found 'Array[Int]' diff --git a/std/fixtures/diagnostics/inline_types_as_mutable_arguments.inko b/std/fixtures/diagnostics/inline_types_as_mutable_arguments.inko new file mode 100644 index 000000000..b9593b97b --- /dev/null +++ b/std/fixtures/diagnostics/inline_types_as_mutable_arguments.inko @@ -0,0 +1,7 @@ +class inline A { + let @value: Int +} + +fn example(value: mut A) {} + +# inline_types_as_mutable_arguments.inko:5:23 error(invalid-type): mutable borrows of type 'A' are invalid as 'A' is immutable diff --git a/std/fixtures/diagnostics/inline_types_with_fields.inko b/std/fixtures/diagnostics/inline_types_with_fields.inko new file mode 100644 index 000000000..9139b8f49 --- /dev/null +++ b/std/fixtures/diagnostics/inline_types_with_fields.inko @@ -0,0 +1,23 @@ +class inline A { + let @value: Int +} + +class inline B { + let @value1: A + let @value2: C +} + +class extern C { + let @value: Int +} + +class inline D[T] { + let @value: T +} + +class inline E { + let @valid: D[Int] + let @invalid: D[String] +} + +# inline_types_with_fields.inko:20:19 error(invalid-type): 'String' can't be assigned to type parameter 'T: inline' diff --git a/std/fixtures/diagnostics/inline_types_with_methods.inko b/std/fixtures/diagnostics/inline_types_with_methods.inko new file mode 100644 index 000000000..ad6b31577 --- /dev/null +++ b/std/fixtures/diagnostics/inline_types_with_methods.inko @@ -0,0 +1,12 @@ +class inline A { + fn a { + self.test + } + + fn move b { + self.test + } +} + +# inline_types_with_methods.inko:3:5 error(invalid-symbol): the method 'test' isn't defined for type 'A' +# inline_types_with_methods.inko:7:5 error(invalid-symbol): the method 'test' isn't defined for type 'A' diff --git a/std/fixtures/diagnostics/mutating_inline_types.inko b/std/fixtures/diagnostics/mutating_inline_types.inko new file mode 100644 index 000000000..39fe1001c --- /dev/null +++ b/std/fixtures/diagnostics/mutating_inline_types.inko @@ -0,0 +1,11 @@ +class inline A { + let @value: Int +} + +fn example { + let a = A(value: 1) + + a.value = 2 +} + +# mutating_inline_types.inko:8:3 error(invalid-assign): can't assign a new value to field 'value', as its receiver is immutable diff --git a/std/fixtures/diagnostics/recursive_classes.inko b/std/fixtures/diagnostics/recursive_classes.inko new file mode 100644 index 000000000..85c59ce66 --- /dev/null +++ b/std/fixtures/diagnostics/recursive_classes.inko @@ -0,0 +1,66 @@ +class A { + let @a: A +} + +class inline B { + let @a: Int + let @b: Float + let @c: Pointer[Int64] +} + +class inline C { + let @a: D +} + +class inline D { + let @a: Int +} + +class inline E { + let @a: E +} + +class inline F { + let @a: G[F] +} + +class inline G[T] { + let @a: T +} + +class inline H { + let @a: I[Int] +} + +class inline I[T] { + let @a: T + let @b: H +} + +class extern J { + let @a: Int64 +} + +class extern K { + let @a: K +} + +class extern L { + let @a: M +} + +class extern M { + let @a: L +} + +class extern N { + let @a: Pointer[N] +} + +# recursive_classes.inko:19:1 error(invalid-type): 'inline' and 'extern' types can't be recursive +# recursive_classes.inko:23:1 error(invalid-type): 'inline' and 'extern' types can't be recursive +# recursive_classes.inko:31:1 error(invalid-type): 'inline' and 'extern' types can't be recursive +# recursive_classes.inko:35:1 error(invalid-type): 'inline' and 'extern' types can't be recursive +# recursive_classes.inko:44:1 error(invalid-type): 'inline' and 'extern' types can't be recursive +# recursive_classes.inko:48:1 error(invalid-type): 'inline' and 'extern' types can't be recursive +# recursive_classes.inko:52:1 error(invalid-type): 'inline' and 'extern' types can't be recursive diff --git a/std/fixtures/diagnostics/type_parameter_bounds.inko b/std/fixtures/diagnostics/type_parameter_bounds.inko new file mode 100644 index 000000000..2af0f053a --- /dev/null +++ b/std/fixtures/diagnostics/type_parameter_bounds.inko @@ -0,0 +1,12 @@ +class A[T] {} + +impl A if T: mut {} + +impl A if T: inline {} + +impl A if T: mut + inline {} + +impl A if T: inline + mut {} + +# type_parameter_bounds.inko:7:20 error(invalid-type): type parameters can't be both 'mut' and 'inline', as 'inline' types are immutable +# type_parameter_bounds.inko:9:23 error(invalid-type): type parameters can't be both 'mut' and 'inline', as 'inline' types are immutable diff --git a/std/fixtures/diagnostics/type_parameter_requirements.inko b/std/fixtures/diagnostics/type_parameter_requirements.inko new file mode 100644 index 000000000..1139fe083 --- /dev/null +++ b/std/fixtures/diagnostics/type_parameter_requirements.inko @@ -0,0 +1,10 @@ +class A[T: mut] {} + +class B[T: inline] {} + +class C[T: mut + inline] {} + +class D[T: inline + mut] {} + +# type_parameter_requirements.inko:5:18 error(invalid-type): type parameters can't be both 'mut' and 'inline', as 'inline' types are immutable +# type_parameter_requirements.inko:7:21 error(invalid-type): type parameters can't be both 'mut' and 'inline', as 'inline' types are immutable diff --git a/std/fixtures/diagnostics/value_types_passed_to_mutable_arguments.inko b/std/fixtures/diagnostics/value_types_passed_to_mutable_arguments.inko new file mode 100644 index 000000000..0535597f2 --- /dev/null +++ b/std/fixtures/diagnostics/value_types_passed_to_mutable_arguments.inko @@ -0,0 +1,42 @@ +class inline A { + let @value: Int +} + +fn owned[T](value: T) {} + +fn mutable_owned[T: mut](value: T) {} + +fn mutable_borrow[T: mut](value: mut T) {} + +fn immutable_borrow[T](value: ref T) {} + +fn example { + owned(1) + owned(1.0) + owned('test') + owned(A(1)) + + immutable_borrow(1) + immutable_borrow(1.0) + immutable_borrow('test') + immutable_borrow(A(1)) + + mutable_owned(1) + mutable_owned(1.0) + mutable_owned('test') + mutable_owned(A(1)) + + mutable_borrow(1) + mutable_borrow(1.0) + mutable_borrow('test') + mutable_borrow(A(1)) +} + +# value_types_passed_to_mutable_arguments.inko:24:17 error(invalid-type): expected a value of type 'T: mut', found 'Int' +# value_types_passed_to_mutable_arguments.inko:25:17 error(invalid-type): expected a value of type 'T: mut', found 'Float' +# value_types_passed_to_mutable_arguments.inko:26:17 error(invalid-type): expected a value of type 'T: mut', found 'String' +# value_types_passed_to_mutable_arguments.inko:27:17 error(invalid-type): expected a value of type 'T: mut', found 'A' +# value_types_passed_to_mutable_arguments.inko:29:18 error(invalid-type): expected a value of type 'mut T: mut', found 'Int' +# value_types_passed_to_mutable_arguments.inko:30:18 error(invalid-type): expected a value of type 'mut T: mut', found 'Float' +# value_types_passed_to_mutable_arguments.inko:31:18 error(invalid-type): expected a value of type 'mut T: mut', found 'String' +# value_types_passed_to_mutable_arguments.inko:32:18 error(invalid-type): expected a value of type 'mut T: mut', found 'A' diff --git a/std/fixtures/fmt/classes/input.inko b/std/fixtures/fmt/classes/input.inko new file mode 100644 index 000000000..b1fac1bf4 --- /dev/null +++ b/std/fixtures/fmt/classes/input.inko @@ -0,0 +1,51 @@ +class A { + let @b: Int +} + +class pub B { + let @b: Int +} + +class async C { + let @b: Int +} + +class pub async D { + let @b: Int +} + +class builtin E { + let @b: Int +} + +class enum F { + case A(Int) +} + +class pub enum G { + case A(Int) +} + +class extern H { + let @a: Int +} + +class pub extern I { + let @a: Int +} + +class inline J { + let @a: Int +} + +class pub inline K { + let @a: Int +} + +class L { + let @a: Int +} + +class pub M { + let @a: Int +} diff --git a/std/fixtures/fmt/classes/output.inko b/std/fixtures/fmt/classes/output.inko new file mode 100644 index 000000000..b1fac1bf4 --- /dev/null +++ b/std/fixtures/fmt/classes/output.inko @@ -0,0 +1,51 @@ +class A { + let @b: Int +} + +class pub B { + let @b: Int +} + +class async C { + let @b: Int +} + +class pub async D { + let @b: Int +} + +class builtin E { + let @b: Int +} + +class enum F { + case A(Int) +} + +class pub enum G { + case A(Int) +} + +class extern H { + let @a: Int +} + +class pub extern I { + let @a: Int +} + +class inline J { + let @a: Int +} + +class pub inline K { + let @a: Int +} + +class L { + let @a: Int +} + +class pub M { + let @a: Int +} diff --git a/std/fixtures/fmt/type_parameters/input.inko b/std/fixtures/fmt/type_parameters/input.inko new file mode 100644 index 000000000..9294c2626 --- /dev/null +++ b/std/fixtures/fmt/type_parameters/input.inko @@ -0,0 +1,11 @@ +fn a[P: A] {} + +fn b[P: B + A] {} + +fn c[P: B + A + mut] {} + +fn d[P: B + A + inline] {} + +fn e[P: B + A + inline + mut] {} + +fn f[P: mut + inline] {} diff --git a/std/fixtures/fmt/type_parameters/output.inko b/std/fixtures/fmt/type_parameters/output.inko new file mode 100644 index 000000000..084aa1692 --- /dev/null +++ b/std/fixtures/fmt/type_parameters/output.inko @@ -0,0 +1,11 @@ +fn a[P: A] {} + +fn b[P: A + B] {} + +fn c[P: mut + A + B] {} + +fn d[P: inline + A + B] {} + +fn e[P: inline + mut + A + B] {} + +fn f[P: inline + mut] {} diff --git a/std/src/std/string.inko b/std/src/std/string.inko index 861f4f0d2..975f23551 100644 --- a/std/src/std/string.inko +++ b/std/src/std/string.inko @@ -19,7 +19,7 @@ import std.ptr class extern StringResult { let @tag: Int - let @value: String + let @value: Pointer[UInt8] } fn extern inko_string_to_lower(state: Pointer[UInt8], string: String) -> String @@ -916,7 +916,7 @@ class pub Chars { impl Iter[String] for Chars { fn pub mut next -> Option[String] { match inko_string_chars_next(_INKO.state, @iter) { - case { @tag = 0, @value = v } -> Option.Some(v) + case { @tag = 0, @value = v } -> Option.Some(v as UInt64 as String) case _ -> Option.None } } diff --git a/std/test/compiler/test_extern_types.inko b/std/test/compiler/test_extern_types.inko new file mode 100644 index 000000000..c987dabf4 --- /dev/null +++ b/std/test/compiler/test_extern_types.inko @@ -0,0 +1,17 @@ +import std.test (Tests) + +class extern Example { + let @a: Int + let @b: Int +} + +fn pub tests(t: mut Tests) { + t.test('extern types can be used in generic contexts', fn (t) { + t.true( + match Option.Some(Example(a: 10, b: 20)) { + case Some({ @a = 10, @b = 20 }) -> true + case _ -> false + }, + ) + }) +} diff --git a/std/test/compiler/test_inline_types.inko b/std/test/compiler/test_inline_types.inko new file mode 100644 index 000000000..7fc0b3732 --- /dev/null +++ b/std/test/compiler/test_inline_types.inko @@ -0,0 +1,83 @@ +import std.string (ToString) +import std.test (Tests) + +class inline Example[A, B] { + let @a: A + let @b: B +} + +impl ToString for Example if A: ToString, B: ToString { + fn pub to_string -> String { + @a.to_string + @b.to_string + } +} + +fn to_string[T: ToString](value: T) -> String { + value.to_string +} + +class inline enum Enum[A, B] { + case A(Example[A, B]) + case B(Int) +} + +fn pub tests(t: mut Tests) { + t.test('inline types can be used in generic contexts', fn (t) { + t.true( + match Option.Some(Example(a: 10, b: 20)) { + case Some({ @a = 10, @b = 20 }) -> true + case _ -> false + }, + ) + }) + + # This is just a simple smoke test to make sure field sizes and offsets are + # correct for the different specializations. + t.test('Generic inline types are specialized correctly', fn (t) { + let a = Example(a: 10, b: 20) + let b = Example(a: 1.0, b: 2.0) + let c = Enum.A(Example(a: 100, b: 200)) + let d: Enum[Int32, Int] = Enum.B(42) + + t.equal(a.a, 10) + t.equal(a.b, 20) + t.equal(b.a, 1.0) + t.equal(b.b, 2.0) + t.true( + match c { + case A({ @a = 100, @b = 200 }) -> true + case _ -> false + }, + ) + t.true( + match d { + case B(42) -> true + case _ -> false + }, + ) + }) + + t.test('Inline types are copied when they are moved', fn (t) { + let a = Example(a: 10, b: 20) + let b = a + + t.equal(a.a, 10) + t.equal(a.b, 20) + t.equal(b.a, 10) + t.equal(b.b, 20) + }) + + t.test('Closures capture inline values by copying them', fn (t) { + let a = Example(a: 10, b: 20) + let f1 = fn { t.equal(a.b, 20) } + let f2 = fn move { t.equal(a.b, 20) } + + f1.call + f2.call + t.equal(a.b, 20) + }) + + t.test('Inline types support method calls in generic contexts', fn (t) { + t.equal(to_string(Example(a: 10, b: 20)), '1020') + }) +} diff --git a/std/test/std/test_array.inko b/std/test/std/test_array.inko index 86d203db8..de55fa555 100644 --- a/std/test/std/test_array.inko +++ b/std/test/std/test_array.inko @@ -97,15 +97,16 @@ fn pub tests(t: mut Tests) { t.test('Array.opt', fn (t) { let vals = [10, 20, 30] - t.equal(vals.opt(1), Option.Some(ref 20)) + t.equal(vals.opt(1), Option.Some(20)) t.equal(vals.opt(5), Option.None) t.equal(vals.opt(-5), Option.None) }) t.test('Array.opt_mut', fn (t) { - let vals = [10, 20, 30] + let vals = [(1, 2), (2, 3), (3, 4)] + let exp = (2, 3) - t.equal(vals.opt_mut(1), Option.Some(mut 20)) + t.equal(vals.opt_mut(1), Option.Some(mut exp)) t.equal(vals.opt_mut(5), Option.None) t.equal(vals.opt_mut(-5), Option.None) }) @@ -131,13 +132,15 @@ fn pub tests(t: mut Tests) { t.test('Array.iter', fn (t) { let vals = [10, 20, 30] - t.equal(vals.iter.to_array, [ref 10, ref 20, ref 30]) + t.equal(vals.iter.to_array, [10, 20, 30]) }) t.test('Array.iter_mut', fn (t) { - let vals = [10, 20, 30] + let vals = [(1, 2), (2, 3)] + let a = (1, 2) + let b = (2, 3) - t.equal(vals.iter_mut.to_array, [mut 10, mut 20, mut 30]) + t.equal(vals.iter_mut.to_array, [mut a, mut b]) }) t.test('Array.into_iter', fn (t) { @@ -149,7 +152,7 @@ fn pub tests(t: mut Tests) { t.test('Array.reverse_iter', fn (t) { let vals = [10, 20, 30] - t.equal(vals.reverse_iter.to_array, [ref 30, ref 20, ref 10]) + t.equal(vals.reverse_iter.to_array, [30, 20, 10]) }) t.test('Array.append', fn (t) { @@ -221,9 +224,13 @@ fn pub tests(t: mut Tests) { t.panic('Array.get with an invalid index', fn { [10].get(1) }) - t.test('Array.get_mut', fn (t) { t.equal([10].get_mut(0), 10) }) + t.test('Array.get_mut', fn (t) { + let exp = (1, 2) - t.panic('Array.get_mut with an invalid index', fn { [10].get_mut(1) }) + t.equal([(1, 2)].get_mut(0), mut exp) + }) + + t.panic('Array.get_mut with an invalid index', fn { [(1, 2)].get_mut(1) }) t.test('Array.set', fn (t) { let count = Counter.new @@ -295,8 +302,10 @@ fn pub tests(t: mut Tests) { }) t.test('Array.last_mut', fn (t) { - t.equal([].last_mut as Option[Int], Option.None) - t.equal([10, 20].last_mut, Option.Some(20)) + let exp = (2, 3) + + t.equal(([] as Array[(Int, Int)]).last_mut, Option.None) + t.equal([(1, 2), (2, 3)].last_mut, Option.Some(mut exp)) }) t.test('Array.reserve', fn (t) { diff --git a/std/test/std/test_deque.inko b/std/test/std/test_deque.inko index 11c818068..fee424d78 100644 --- a/std/test/std/test_deque.inko +++ b/std/test/std/test_deque.inko @@ -3,7 +3,7 @@ import std.test (Tests) fn pub tests(t: mut Tests) { t.test('Deque.new', fn (t) { - let q = Deque.new + let q: Deque[Int] = Deque.new t.equal(q.size, 0) t.equal(q.capacity, 0) @@ -12,7 +12,7 @@ fn pub tests(t: mut Tests) { }) t.test('Deque.with_capacity', fn (t) { - let q = Deque.with_capacity(4) + let q: Deque[Int] = Deque.with_capacity(4) t.equal(q.size, 0) t.equal(q.capacity, 4) @@ -143,12 +143,15 @@ fn pub tests(t: mut Tests) { t.test('Deque.iter_mut', fn (t) { let q = Deque.new + let a = (1, 0) + let b = (2, 0) + let c = (3, 0) - q.push_back(20) - q.push_back(30) - q.push_front(10) + q.push_back((2, 0)) + q.push_back((3, 0)) + q.push_front((1, 0)) - t.equal(q.iter_mut.to_array, [10, 20, 30]) + t.equal(q.iter_mut.to_array, [mut a, mut b, mut c]) }) t.test('Deque.into_iter', fn (t) { @@ -194,10 +197,11 @@ fn pub tests(t: mut Tests) { t.test('Deque.opt_mut', fn (t) { let q = Deque.new + let exp = (1, 0) - q.push_back(10) + q.push_back((1, 0)) - t.equal(q.opt_mut(0), Option.Some(10)) + t.equal(q.opt_mut(0), Option.Some(mut exp)) t.equal(q.opt_mut(1), Option.None) }) } diff --git a/std/test/std/test_iter.inko b/std/test/std/test_iter.inko index 94954de16..4cc692c8d 100644 --- a/std/test/std/test_iter.inko +++ b/std/test/std/test_iter.inko @@ -268,15 +268,15 @@ fn pub tests(t: mut Tests) { }) t.test('Peekable.peek_mut with an iterator with values', fn (t) { - let vals = [1, 2, 3] + let vals = [(1, 0), (2, 0), (3, 0)] let iter = vals.iter_mut.peekable - t.equal(iter.peek_mut, Option.Some(1)) - t.equal(iter.peek_mut, Option.Some(1)) - t.equal(iter.next, Option.Some(1)) - t.equal(iter.peek_mut, Option.Some(2)) - t.equal(iter.next, Option.Some(2)) - t.equal(iter.next, Option.Some(3)) + t.equal(iter.peek_mut, Option.Some(mut (1, 0))) + t.equal(iter.peek_mut, Option.Some(mut (1, 0))) + t.equal(iter.next, Option.Some(mut (1, 0))) + t.equal(iter.peek_mut, Option.Some(mut (2, 0))) + t.equal(iter.next, Option.Some(mut (2, 0))) + t.equal(iter.next, Option.Some(mut (3, 0))) t.equal(iter.next, Option.None) t.equal(iter.peek_mut, Option.None) }) diff --git a/std/test/std/test_map.inko b/std/test/std/test_map.inko index 31b62b88e..752eb4d29 100644 --- a/std/test/std/test_map.inko +++ b/std/test/std/test_map.inko @@ -142,10 +142,10 @@ fn pub tests(t: mut Tests) { t.test('Map.opt_mut', fn (t) { let map = Map.new - map.set('name', 'Alice') + map.set('foo', (1, 0)) - t.equal(map.opt_mut('name'), Option.Some(mut 'Alice')) - t.equal(map.opt_mut('city'), Option.None) + t.equal(map.opt_mut('foo'), Option.Some(mut (1, 0))) + t.equal(map.opt_mut('bar'), Option.None) }) t.test('Map.entry', fn (t) { diff --git a/std/test/std/test_option.inko b/std/test/std/test_option.inko index c6374118f..72b5709f3 100644 --- a/std/test/std/test_option.inko +++ b/std/test/std/test_option.inko @@ -11,10 +11,11 @@ fn pub tests(t: mut Tests) { }) t.test('Option.as_mut', fn (t) { - let a = Option.Some('thing') - let b: Option[String] = Option.None + let a = Option.Some((1, 0)) + let b: Option[(Int, Int)] = Option.None + let exp = (1, 0) - t.equal(a.as_mut, Option.Some(mut 'thing')) + t.equal(a.as_mut, Option.Some(mut exp)) t.equal(b.as_mut, Option.None) }) diff --git a/types/src/check.rs b/types/src/check.rs index 110a245e5..5cb491809 100644 --- a/types/src/check.rs +++ b/types/src/check.rs @@ -286,7 +286,13 @@ impl<'a> TypeChecker<'a> { let mut env = env.with_left_as_right(); let rules = Rules::new().with_subtyping(); - if bound.is_mutable(self.db) && !val.is_mutable(self.db) { + if bound.is_mutable(self.db) && !val.allow_mutating(self.db) { + return false; + } + + if bound.is_stack_allocated(self.db) + && !val.is_stack_allocated(self.db) + { return false; } @@ -325,6 +331,7 @@ impl<'a> TypeChecker<'a> { // This indicates if the value on the left of the check is a value type // (e.g. Int or String). let is_val = left.is_value_type(self.db); + let allow_mut = left.allow_mutating(self.db); // If at this point we encounter a type placeholder, it means the // placeholder is yet to be assigned a value. @@ -355,20 +362,26 @@ impl<'a> TypeChecker<'a> { TypeRef::Owned(right_id) => { self.check_type_id(left_id, right_id, env, rules) } - TypeRef::Ref(right_id) | TypeRef::Mut(right_id) - if is_val || allow_ref => - { + TypeRef::Mut(right_id) if allow_mut && allow_ref => { + let rules = rules.without_implicit_root_ref(); + + self.check_type_id(left_id, right_id, env, rules) + } + TypeRef::Ref(right_id) if is_val || allow_ref => { let rules = rules.without_implicit_root_ref(); self.check_type_id(left_id, right_id, env, rules) } - TypeRef::Uni(right_id) if is_val => { + TypeRef::Uni(right_id) | TypeRef::UniRef(right_id) + if is_val => + { self.check_type_id(left_id, right_id, env, rules) } TypeRef::Placeholder(id) => { let allow = match id.ownership { Ownership::Any | Ownership::Owned => true, - Ownership::Ref | Ownership::Mut => is_val || allow_ref, + Ownership::Ref => is_val || allow_ref, + Ownership::Mut => allow_mut && allow_ref, Ownership::Uni => is_val, _ => false, }; @@ -407,7 +420,6 @@ impl<'a> TypeChecker<'a> { rules.uni_compatible_with_owned || is_val } Ownership::Any | Ownership::Uni => true, - Ownership::Ref | Ownership::Mut => is_val, _ => false, }; @@ -449,8 +461,6 @@ impl<'a> TypeChecker<'a> { } TypeRef::Owned(right_id) | TypeRef::Uni(right_id) - | TypeRef::Mut(right_id) - | TypeRef::UniMut(right_id) | TypeRef::UniRef(right_id) if is_val => { @@ -459,16 +469,11 @@ impl<'a> TypeChecker<'a> { TypeRef::Placeholder(id) => { match id.ownership { Ownership::Any | Ownership::Ref => {} + Ownership::Mut => return false, _ if is_val => {} _ => return false, } - if let Some(req) = id.required(self.db) { - if req.is_mutable(self.db) && !is_val { - return false; - } - } - self.check_type_id_with_placeholder( left, left_id, orig_right, id, env, rules, ) @@ -650,8 +655,8 @@ impl<'a> TypeChecker<'a> { return true; } - let lhs_args = lhs.type_arguments(self.db); - let rhs_args = rhs.type_arguments(self.db); + let lhs_args = lhs.type_arguments(self.db).unwrap(); + let rhs_args = rhs.type_arguments(self.db).unwrap(); lhs.instance_of.type_parameters(self.db).into_iter().all( |param| { @@ -664,11 +669,9 @@ impl<'a> TypeChecker<'a> { }, ) } - TypeId::TraitInstance(rhs) - if !lhs.instance_of().kind(self.db).is_extern() => - { + TypeId::TraitInstance(rhs) => { if rules.kind.is_cast() - && !lhs.instance_of().allow_cast(self.db) + && !lhs.instance_of().allow_cast_to_trait(self.db) { return false; } @@ -676,9 +679,13 @@ impl<'a> TypeChecker<'a> { self.check_class_with_trait(lhs, rhs, env, trait_rules) } TypeId::TypeParameter(_) if rules.kind.is_cast() => false, - TypeId::TypeParameter(rhs) - if !lhs.instance_of().kind(self.db).is_extern() => - { + TypeId::TypeParameter(rhs) => { + if rhs.is_stack_allocated(self.db) + && !lhs.instance_of().is_stack_allocated(self.db) + { + return false; + } + rhs.requirements(self.db).into_iter().all(|req| { // One-time subtyping is enabled because we want to // allow passing classes to type parameters with @@ -691,7 +698,10 @@ impl<'a> TypeChecker<'a> { ) }) } - TypeId::Foreign(_) => rules.kind.is_cast(), + TypeId::Foreign(_) => { + rules.kind.is_cast() + && lhs.instance_of().allow_cast_to_foreign(self.db) + } _ => false, }, TypeId::TraitInstance(lhs) => match right_id { @@ -699,6 +709,11 @@ impl<'a> TypeChecker<'a> { self.check_traits(lhs, rhs, env, rules) } TypeId::TypeParameter(_) if rules.kind.is_cast() => false, + TypeId::TypeParameter(rhs) + if rhs.is_stack_allocated(self.db) => + { + false + } TypeId::TypeParameter(rhs) => rhs .requirements(self.db) .into_iter() @@ -740,7 +755,7 @@ impl<'a> TypeChecker<'a> { // Closures can't implement traits, so they're only // compatible with type parameters that don't have any // requirements. - true + !rhs.is_stack_allocated(self.db) } _ => false, }, @@ -800,6 +815,12 @@ impl<'a> TypeChecker<'a> { return true; } + if left.is_stack_allocated(self.db) + != rhs.is_stack_allocated(self.db) + { + return false; + } + rhs.requirements(self.db).into_iter().all(|req| { self.check_parameter_with_trait(left, req, env, rules) }) @@ -818,15 +839,6 @@ impl<'a> TypeChecker<'a> { env: &mut Environment, rules: Rules, ) -> bool { - // We don't support specializing stack allocated structures at this - // point, so we disallow passing such types to type - // parameters/placeholders. - if let TypeId::ClassInstance(ins) = left_id { - if ins.instance_of().kind(self.db).is_extern() { - return false; - } - } - // By assigning the placeholder first, recursive checks against the same // placeholder don't keep recursing into this method, instead checking // against the value on the left. @@ -860,6 +872,14 @@ impl<'a> TypeChecker<'a> { return true; }; + if (req.is_mutable(self.db) && !left.allow_mutating(self.db)) + || (req.is_stack_allocated(self.db) + && !left.is_stack_allocated(self.db)) + { + placeholder.assign_internal(self.db, TypeRef::Unknown); + return false; + } + let reqs = req.requirements(self.db); if reqs.is_empty() { @@ -960,7 +980,9 @@ impl<'a> TypeChecker<'a> { // implementing class, so we need to expose those using a new scope. let mut sub_scope = env.clone(); - left.type_arguments(self.db).copy_into(&mut sub_scope.left); + left.type_arguments(self.db) + .unwrap() + .copy_into(&mut sub_scope.left); self.check_bounds(&imp.bounds, &mut sub_scope) && self.check_traits(imp.instance, right, &mut sub_scope, rules) @@ -1035,6 +1057,11 @@ impl<'a> TypeChecker<'a> { return true; } + if left.is_stack_allocated(self.db) != right.is_stack_allocated(self.db) + { + return false; + } + right .requirements(self.db) .into_iter() @@ -1068,8 +1095,8 @@ impl<'a> TypeChecker<'a> { return true; } - let lhs_args = left.type_arguments(self.db); - let rhs_args = right.type_arguments(self.db); + let lhs_args = left.type_arguments(self.db).unwrap(); + let rhs_args = right.type_arguments(self.db).unwrap(); left.instance_of.type_parameters(self.db).into_iter().all(|param| { lhs_args @@ -1313,11 +1340,14 @@ mod tests { let int = ClassId::int(); let var1 = TypePlaceholder::alloc(&mut db, None); let to_string = new_trait(&mut db, "ToString"); - let param = new_parameter(&mut db, "T"); - let var2 = TypePlaceholder::alloc(&mut db, Some(param)); - let var3 = TypePlaceholder::alloc(&mut db, Some(param)); - - param.add_requirements(&mut db, vec![trait_instance(to_string)]); + let p1 = new_parameter(&mut db, "T"); + let p2 = new_parameter(&mut db, "X"); + let var2 = TypePlaceholder::alloc(&mut db, Some(p1)); + let var3 = TypePlaceholder::alloc(&mut db, Some(p1)); + let var4 = TypePlaceholder::alloc(&mut db, Some(p2)); + + p2.set_stack_allocated(&mut db); + p1.add_requirements(&mut db, vec![trait_instance(to_string)]); implement(&mut db, trait_instance(to_string), bar); check_ok(&db, owned(instance(foo)), owned(instance(foo))); @@ -1342,14 +1372,44 @@ mod tests { // Value types can be passed to a reference/unique values. check_ok(&db, owned(instance(int)), immutable(instance(int))); - check_ok(&db, owned(instance(int)), mutable(instance(int))); check_ok(&db, owned(instance(int)), uni(instance(int))); + check_ok(&db, owned(instance(int)), immutable_uni(instance(int))); check_ok(&db, owned(instance(foo)), TypeRef::Error); + check_ok(&db, owned(instance(int)), owned(parameter(p2))); + + check_err(&db, owned(instance(int)), mutable(instance(int))); + check_err(&db, owned(instance(int)), mutable_uni(instance(int))); check_err(&db, owned(instance(foo)), immutable(instance(foo))); check_err(&db, owned(instance(foo)), mutable(instance(foo))); check_err(&db, owned(instance(foo)), owned(instance(bar))); check_err(&db, owned(instance(foo)), TypeRef::Never); check_err(&db, owned(instance(foo)), pointer(instance(foo))); + + check_err(&db, owned(instance(foo)), owned(parameter(p2))); + check_err(&db, owned(instance(foo)), placeholder(var4)); + check_err(&db, owned(parameter(p1)), placeholder(var4)); + check_err(&db, uni(instance(foo)), uni(parameter(p2))); + check_err(&db, mutable(instance(foo)), mutable(parameter(p2))); + check_err(&db, immutable(instance(foo)), immutable(parameter(p2))); + + // Trait values are always on the heap. + check_err( + &db, + owned(trait_instance_id(to_string)), + owned(parameter(p2)), + ); + check_err(&db, owned(trait_instance_id(to_string)), placeholder(var4)); + check_err(&db, uni(trait_instance_id(to_string)), uni(parameter(p2))); + check_err( + &db, + mutable(trait_instance_id(to_string)), + mutable(parameter(p2)), + ); + check_err( + &db, + immutable(trait_instance_id(to_string)), + immutable(parameter(p2)), + ); } #[test] @@ -1362,8 +1422,8 @@ mod tests { check_ok(&db, owned(instance(foo)), owned(instance(foo))); check_err(&db, owned(instance(foo)), owned(instance(bar))); - check_err(&db, owned(instance(foo)), owned(parameter(param))); - check_err(&db, uni(instance(foo)), owned(parameter(param))); + check_ok(&db, owned(instance(foo)), owned(parameter(param))); + check_ok(&db, uni(instance(foo)), owned(parameter(param))); } #[test] @@ -1711,7 +1771,6 @@ mod tests { check_ok(&db, immutable(instance(thing)), any(instance(thing))); // Value types can be passed around this way. - check_ok(&db, immutable(instance(int)), mutable(instance(int))); check_ok(&db, immutable(instance(int)), owned(instance(int))); check_ok(&db, immutable(instance(int)), uni(instance(int))); @@ -1722,6 +1781,7 @@ mod tests { check_ok(&db, immutable(instance(int)), any(parameter(param))); check_ok(&db, immutable(instance(int)), placeholder(mutable_var)); + check_err(&db, immutable(instance(int)), mutable(instance(int))); check_err(&db, immutable(instance(thing)), mutable(instance(thing))); check_err(&db, immutable(instance(thing)), owned(instance(thing))); check_err(&db, immutable(instance(thing)), any(parameter(param))); @@ -1988,8 +2048,8 @@ mod tests { check_err(&db, owned(instance(thing)), placeholder(uni_var)); check_ok(&db, owned(instance(int)), placeholder(ref_var)); - check_ok(&db, owned(instance(int)), placeholder(mut_var)); check_ok(&db, owned(instance(int)), placeholder(uni_var)); + check_err(&db, owned(instance(int)), placeholder(mut_var)); } #[test] @@ -2075,7 +2135,7 @@ mod tests { let class = new_extern_class(&mut db, "A"); let var = TypePlaceholder::alloc(&mut db, None); - check_err(&db, owned(instance(class)), placeholder(var)); + check_ok(&db, owned(instance(class)), placeholder(var)); } #[test] @@ -2283,20 +2343,28 @@ mod tests { #[test] fn test_parameters() { let mut db = Database::new(); - let param1 = new_parameter(&mut db, "A"); - let param2 = new_parameter(&mut db, "B"); - let param3 = new_parameter(&mut db, "C"); - let param4 = new_parameter(&mut db, "D"); + let p1 = new_parameter(&mut db, "A"); + let p2 = new_parameter(&mut db, "B"); + let p3 = new_parameter(&mut db, "C"); + let p4 = new_parameter(&mut db, "D"); + let p5 = new_parameter(&mut db, "E"); + let p6 = new_parameter(&mut db, "F"); let equal = new_trait(&mut db, "Equal"); let test = new_trait(&mut db, "Test"); test.add_required_trait(&mut db, trait_instance(equal)); - param3.add_requirements(&mut db, vec![trait_instance(equal)]); - param4.add_requirements(&mut db, vec![trait_instance(test)]); + p3.add_requirements(&mut db, vec![trait_instance(equal)]); + p4.add_requirements(&mut db, vec![trait_instance(test)]); + p5.set_stack_allocated(&mut db); + p6.set_stack_allocated(&mut db); + + check_ok(&db, owned(parameter(p1)), owned(parameter(p2))); + check_ok(&db, owned(parameter(p4)), owned(parameter(p3))); + check_ok(&db, owned(parameter(p5)), owned(parameter(p6))); - check_ok(&db, owned(parameter(param1)), owned(parameter(param2))); - check_ok(&db, owned(parameter(param4)), owned(parameter(param3))); - check_err(&db, owned(parameter(param3)), owned(parameter(param4))); + check_err(&db, owned(parameter(p3)), owned(parameter(p4))); + check_err(&db, owned(parameter(p1)), owned(parameter(p5))); + check_err(&db, owned(parameter(p5)), owned(parameter(p1))); } #[test] @@ -2318,23 +2386,31 @@ mod tests { #[test] fn test_rigid() { let mut db = Database::new(); - let param1 = new_parameter(&mut db, "A"); - let param2 = new_parameter(&mut db, "B"); + let p1 = new_parameter(&mut db, "A"); + let p2 = new_parameter(&mut db, "B"); + let p3 = new_parameter(&mut db, "C"); + let p4 = new_parameter(&mut db, "C"); let var = TypePlaceholder::alloc(&mut db, None); - check_ok(&db, owned(rigid(param1)), TypeRef::Error); - check_ok(&db, immutable(rigid(param1)), immutable(rigid(param1))); - check_ok(&db, owned(rigid(param1)), owned(rigid(param1))); - check_ok(&db, owned(rigid(param1)), any(rigid(param1))); - check_ok(&db, owned(rigid(param1)), any(parameter(param1))); - check_ok(&db, immutable(rigid(param1)), immutable(parameter(param1))); - check_ok(&db, owned(rigid(param1)), owned(parameter(param1))); + p3.set_stack_allocated(&mut db); + + check_ok(&db, owned(rigid(p1)), TypeRef::Error); + check_ok(&db, immutable(rigid(p1)), immutable(rigid(p1))); + check_ok(&db, owned(rigid(p1)), owned(rigid(p1))); + check_ok(&db, owned(rigid(p1)), any(rigid(p1))); + check_ok(&db, owned(rigid(p1)), any(parameter(p1))); + check_ok(&db, immutable(rigid(p1)), immutable(parameter(p1))); + check_ok(&db, owned(rigid(p1)), owned(parameter(p1))); + check_ok(&db, owned(rigid(p3)), owned(rigid(p3))); - check_ok(&db, owned(rigid(param1)), placeholder(var)); - assert_eq!(var.value(&db), Some(owned(rigid(param1)))); + check_ok(&db, owned(rigid(p1)), placeholder(var)); + assert_eq!(var.value(&db), Some(owned(rigid(p1)))); - check_err(&db, owned(rigid(param1)), owned(rigid(param2))); - check_err(&db, immutable(rigid(param1)), immutable(rigid(param2))); + check_err(&db, owned(rigid(p1)), owned(rigid(p2))); + check_err(&db, owned(rigid(p1)), owned(rigid(p3))); + check_err(&db, owned(rigid(p3)), owned(rigid(p1))); + check_err(&db, owned(rigid(p3)), owned(parameter(p4))); + check_err(&db, immutable(rigid(p1)), immutable(rigid(p2))); } #[test] @@ -2420,13 +2496,16 @@ mod tests { let mut db = Database::new(); let fun = Closure::alloc(&mut db, false); let equal = new_trait(&mut db, "Equal"); - let param1 = new_parameter(&mut db, "A"); - let param2 = new_parameter(&mut db, "B"); + let p1 = new_parameter(&mut db, "A"); + let p2 = new_parameter(&mut db, "B"); + let p3 = new_parameter(&mut db, "C"); - param2.add_requirements(&mut db, vec![trait_instance(equal)]); + p2.add_requirements(&mut db, vec![trait_instance(equal)]); + p3.set_stack_allocated(&mut db); - check_ok(&db, owned(closure(fun)), owned(parameter(param1))); - check_err(&db, owned(closure(fun)), owned(parameter(param2))); + check_ok(&db, owned(closure(fun)), owned(parameter(p1))); + check_err(&db, owned(closure(fun)), owned(parameter(p2))); + check_err(&db, owned(closure(fun)), owned(parameter(p3))); } #[test] @@ -2497,6 +2576,46 @@ mod tests { check_err(&db, ref_things, owned(parameter(exp_param))); } + #[test] + fn test_inline_bounds() { + let mut db = Database::new(); + let array = ClassId::array(); + let heap = new_class(&mut db, "Heap"); + let stack = new_class(&mut db, "Stack"); + + stack.set_stack_allocated(&mut db); + + let update = new_trait(&mut db, "Update"); + let array_param = array.new_type_parameter(&mut db, "T".to_string()); + let array_bounds = new_parameter(&mut db, "T"); + let exp_param = new_parameter(&mut db, "Expected"); + + exp_param.add_requirements(&mut db, vec![trait_instance(update)]); + array_bounds.set_stack_allocated(&mut db); + array.add_trait_implementation( + &mut db, + TraitImplementation { + instance: trait_instance(update), + bounds: type_bounds(vec![(array_param, array_bounds)]), + }, + ); + + let stack_ary = owned(generic_instance_id( + &mut db, + array, + vec![owned(instance(stack))], + )); + + let heap_ary = owned(generic_instance_id( + &mut db, + array, + vec![owned(instance(heap))], + )); + + check_ok(&db, stack_ary, owned(parameter(exp_param))); + check_err(&db, heap_ary, owned(parameter(exp_param))); + } + #[test] fn test_array_of_generic_classes_with_traits() { let mut db = Database::new(); @@ -2805,6 +2924,9 @@ mod tests { let mut db = Database::new(); let to_string = new_trait(&mut db, "ToString"); let param = new_parameter(&mut db, "T"); + let stack = new_class(&mut db, "Stack"); + + stack.set_stack_allocated(&mut db); for class in [ ClassId::int(), @@ -2831,6 +2953,11 @@ mod tests { check_err_cast(&db, TypeRef::string(), to_string_ins); check_err_cast(&db, TypeRef::int(), owned(parameter(param))); check_err_cast(&db, to_string_ins, owned(parameter(param))); + check_err_cast( + &db, + owned(instance(stack)), + owned(TypeId::Foreign(ForeignType::Int(32, Sign::Signed))), + ); } #[test] diff --git a/types/src/format.rs b/types/src/format.rs index b68ad29bc..756d1047b 100644 --- a/types/src/format.rs +++ b/types/src/format.rs @@ -20,6 +20,23 @@ pub fn format_type_with_arguments( TypeFormatter::new(db, Some(arguments)).format(typ) } +pub fn type_parameter_capabilities( + db: &Database, + id: TypeParameterId, +) -> Option<&'static str> { + let param = id.get(db); + + if param.stack && param.mutable { + Some("inline + mut") + } else if param.stack { + Some("inline") + } else if param.mutable { + Some("mut") + } else { + None + } +} + fn format_type_parameter_without_argument( id: TypeParameterId, buffer: &mut TypeFormatter, @@ -34,12 +51,16 @@ fn format_type_parameter_without_argument( buffer.write(¶m.name); - if param.mutable { - buffer.write(": mut"); - } + let capa = if let Some(v) = type_parameter_capabilities(buffer.db, id) { + buffer.write(": "); + buffer.write(v); + true + } else { + false + }; if requirements && id.has_requirements(buffer.db) { - if param.mutable { + if capa { buffer.write(" + "); } else { buffer.write(": "); @@ -172,14 +193,14 @@ impl<'a> TypeFormatter<'a> { pub(crate) fn type_arguments( &mut self, parameters: &[TypeParameterId], - arguments: &TypeArguments, + arguments: Option<&TypeArguments>, ) { for (index, ¶m) in parameters.iter().enumerate() { if index > 0 { self.write(", "); } - match arguments.get(param) { + match arguments.and_then(|a| a.get(param)) { Some(TypeRef::Placeholder(id)) if id.value(self.db).is_none() => { @@ -300,10 +321,9 @@ impl FormatType for TraitInstance { if !ins_of.type_parameters.is_empty() { let params: Vec<_> = ins_of.type_parameters.values().cloned().collect(); - let args = self.type_arguments(buffer.db); buffer.write("["); - buffer.type_arguments(¶ms, args); + buffer.type_arguments(¶ms, self.type_arguments(buffer.db)); buffer.write("]"); } }); @@ -322,12 +342,12 @@ impl FormatType for ClassInstance { buffer.descend(|buffer| { let ins_of = self.instance_of.get(buffer.db); - if ins_of.kind != ClassKind::Tuple { + if !matches!(ins_of.kind, ClassKind::Tuple) { buffer.write(&ins_of.name); } if !ins_of.type_parameters.is_empty() { - let (open, close) = if ins_of.kind == ClassKind::Tuple { + let (open, close) = if let ClassKind::Tuple = ins_of.kind { ("(", ")") } else { ("[", "]") @@ -335,10 +355,9 @@ impl FormatType for ClassInstance { let params: Vec<_> = ins_of.type_parameters.values().cloned().collect(); - let args = self.type_arguments(buffer.db); buffer.write(open); - buffer.type_arguments(¶ms, args); + buffer.type_arguments(¶ms, self.type_arguments(buffer.db)); buffer.write(close); } }); @@ -423,23 +442,38 @@ impl FormatType for TypeRef { TypeRef::Owned(id) => id.format_type(buffer), TypeRef::Any(id) => id.format_type(buffer), TypeRef::Uni(id) => { - buffer.write_ownership("uni "); + if !self.is_value_type(buffer.db) { + buffer.write_ownership("uni "); + } + id.format_type(buffer); } TypeRef::UniRef(id) => { - buffer.write_ownership("uni ref "); + if !self.is_value_type(buffer.db) { + buffer.write_ownership("uni ref "); + } + id.format_type(buffer); } TypeRef::UniMut(id) => { - buffer.write_ownership("uni mut "); + if !self.is_value_type(buffer.db) { + buffer.write_ownership("uni mut "); + } + id.format_type(buffer); } TypeRef::Ref(id) => { - buffer.write_ownership("ref "); + if !self.is_value_type(buffer.db) { + buffer.write_ownership("ref "); + } + id.format_type(buffer); } TypeRef::Mut(id) => { - buffer.write_ownership("mut "); + if !self.is_value_type(buffer.db) { + buffer.write_ownership("mut "); + } + id.format_type(buffer); } TypeRef::Never => buffer.write("Never"), @@ -487,7 +521,10 @@ impl FormatType for TypeId { #[cfg(test)] mod tests { use super::*; - use crate::test::{new_parameter, placeholder}; + use crate::test::{ + any, immutable, immutable_uni, instance, mutable, mutable_uni, + new_class, new_parameter, owned, placeholder, uni, + }; use crate::{ Block, Class, ClassInstance, ClassKind, Closure, Database, Inline, Location, Method, MethodKind, Module, ModuleId, ModuleName, Trait, @@ -848,6 +885,26 @@ mod tests { assert_eq!(format_type(&db, ins), "String"); } + #[test] + fn test_type_id_format_type_with_generic_class_instance_without_arguments() + { + let mut db = Database::new(); + let id = Class::alloc( + &mut db, + "Array".to_string(), + ClassKind::Regular, + Visibility::Private, + ModuleId(0), + Location::default(), + ); + + id.new_type_parameter(&mut db, "T".to_string()); + + let ins = TypeId::ClassInstance(ClassInstance::new(id)); + + assert_eq!(format_type(&db, ins), "Array[T]"); + } + #[test] fn test_type_id_format_type_with_tuple_instance() { let mut db = Database::new(); @@ -1026,41 +1083,39 @@ mod tests { #[test] fn test_type_ref_type_name() { let mut db = Database::new(); - let string = Class::alloc( - &mut db, - "String".to_string(), - ClassKind::Regular, - Visibility::Private, - ModuleId(0), - Location::default(), - ); - let string_ins = TypeId::ClassInstance(ClassInstance::new(string)); + let cls = new_class(&mut db, "A"); + let ins = instance(cls); + let int = instance(ClassId::int()); let param = TypeId::TypeParameter(TypeParameter::alloc( &mut db, "T".to_string(), )); + let var1 = TypePlaceholder::alloc(&mut db, None); + let var2 = TypePlaceholder::alloc(&mut db, None); + var1.assign(&mut db, owned(ins)); + var2.assign(&mut db, owned(int)); + + // Regular types + assert_eq!(format_type(&db, owned(ins)), "A".to_string()); + assert_eq!(format_type(&db, uni(ins)), "uni A".to_string()); + assert_eq!(format_type(&db, mutable_uni(ins)), "uni mut A".to_string()); assert_eq!( - format_type(&db, TypeRef::Owned(string_ins)), - "String".to_string() - ); - assert_eq!( - format_type(&db, TypeRef::Uni(string_ins)), - "uni String".to_string() - ); - assert_eq!( - format_type(&db, TypeRef::UniMut(string_ins)), - "uni mut String".to_string() - ); - assert_eq!( - format_type(&db, TypeRef::UniRef(string_ins)), - "uni ref String".to_string() - ); - assert_eq!(format_type(&db, TypeRef::Any(param)), "T".to_string()); - assert_eq!( - format_type(&db, TypeRef::Ref(string_ins)), - "ref String".to_string() + format_type(&db, immutable_uni(ins)), + "uni ref A".to_string() ); + assert_eq!(format_type(&db, any(param)), "T".to_string()); + assert_eq!(format_type(&db, immutable(ins)), "ref A".to_string()); + assert_eq!(format_type(&db, mutable(ins)), "mut A".to_string()); + + // Value types + assert_eq!(format_type(&db, owned(int)), "Int".to_string()); + assert_eq!(format_type(&db, uni(int)), "Int".to_string()); + assert_eq!(format_type(&db, mutable_uni(int)), "Int".to_string()); + assert_eq!(format_type(&db, immutable_uni(int)), "Int".to_string()); + assert_eq!(format_type(&db, immutable(int)), "Int".to_string()); + assert_eq!(format_type(&db, mutable(int)), "Int".to_string()); + assert_eq!(format_type(&db, TypeRef::Never), "Never".to_string()); assert_eq!(format_type(&db, TypeRef::Error), "".to_string()); assert_eq!(format_type(&db, TypeRef::Unknown), "".to_string()); @@ -1131,6 +1186,38 @@ mod tests { } } + #[test] + fn test_format_placeholder_with_assigned_value() { + let mut db = Database::new(); + let heap = owned(instance(new_class(&mut db, "Heap"))); + let stack = owned(instance(ClassId::int())); + let mut var = TypePlaceholder::alloc(&mut db, None); + let tests = vec![ + (heap, Ownership::Any, "Heap"), + (heap, Ownership::Owned, "Heap"), + (heap, Ownership::Uni, "uni Heap"), + (heap, Ownership::Ref, "ref Heap"), + (heap, Ownership::Mut, "mut Heap"), + (heap, Ownership::UniRef, "uni ref Heap"), + (heap, Ownership::UniMut, "uni mut Heap"), + (heap, Ownership::Pointer, "Pointer[Heap]"), + (stack, Ownership::Any, "Int"), + (stack, Ownership::Owned, "Int"), + (stack, Ownership::Uni, "Int"), + (stack, Ownership::Ref, "Int"), + (stack, Ownership::Mut, "Int"), + (stack, Ownership::UniRef, "Int"), + (stack, Ownership::UniMut, "Int"), + (stack, Ownership::Pointer, "Pointer[Int]"), + ]; + + for (typ, ownership, format) in tests { + var.ownership = ownership; + var.assign(&mut db, typ); + assert_eq!(format_type(&db, placeholder(var)), format); + } + } + #[test] fn test_format_placeholder_with_ownership_without_requirement() { let mut db = Database::new(); diff --git a/types/src/lib.rs b/types/src/lib.rs index 3c47b8d1a..222a15fa6 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -19,7 +19,6 @@ use indexmap::IndexMap; use location::Location; use std::cell::Cell; use std::collections::{HashMap, HashSet}; -use std::fmt; use std::path::PathBuf; // The IDs of these built-in types must match the order of the fields in the @@ -98,7 +97,7 @@ pub const ENUM_TAG_INDEX: usize = 0; /// The maximum number of enum constructors that can be defined in a single /// class. -pub const VARIANTS_LIMIT: usize = u16::MAX as usize; +pub const CONSTRUCTORS_LIMIT: usize = u16::MAX as usize; /// The maximum number of fields a class can define. pub const FIELDS_LIMIT: usize = u8::MAX as usize; @@ -329,6 +328,9 @@ pub struct TypeParameter { /// If mutable references to this type parameter are allowed. mutable: bool, + /// If types assigned to this parameter must be allocated on the stack. + stack: bool, + /// The ID of the original type parameter in case the current one is a /// parameter introduced through additional type bounds. original: Option, @@ -347,7 +349,13 @@ impl TypeParameter { } fn new(name: String) -> Self { - Self { name, requirements: Vec::new(), mutable: false, original: None } + Self { + name, + requirements: Vec::new(), + mutable: false, + stack: false, + original: None, + } } } @@ -399,6 +407,14 @@ impl TypeParameterId { self.get(db).mutable } + pub fn set_stack_allocated(self, db: &mut Database) { + self.get_mut(db).stack = true; + } + + pub fn is_stack_allocated(self, db: &Database) -> bool { + self.get(db).stack + } + pub fn as_immutable(self, db: &mut Database) -> TypeParameterId { let mut copy = self.get(db).clone(); @@ -406,6 +422,13 @@ impl TypeParameterId { TypeParameter::add(db, copy) } + pub fn clone_for_bound(self, db: &mut Database) -> TypeParameterId { + let mut copy = self.get(db).clone(); + + copy.original = Some(self); + TypeParameter::add(db, copy) + } + pub(crate) fn has_requirements(self, db: &Database) -> bool { !self.get(db).requirements.is_empty() } @@ -434,7 +457,7 @@ pub struct TypeArguments { impl TypeArguments { pub fn for_class(db: &Database, instance: ClassInstance) -> TypeArguments { if instance.instance_of().is_generic(db) { - instance.type_arguments(db).clone() + instance.type_arguments(db).unwrap().clone() } else { TypeArguments::new() } @@ -442,7 +465,7 @@ impl TypeArguments { pub fn for_trait(db: &Database, instance: TraitInstance) -> TypeArguments { if instance.instance_of().is_generic(db) { - instance.type_arguments(db).clone() + instance.type_arguments(db).unwrap().clone() } else { TypeArguments::new() } @@ -531,6 +554,77 @@ impl TypeArguments { } } +/// A type that maps/interns type arguments, such that structurually different +/// but semantically equivalent type arguments all map to the same type arguments +/// ID. +/// +/// As part of type specialization we want to specialize over specific types, +/// such as when using `Shape::Stack`. Since type argument IDs differ for +/// different occurrences of the same generic type instance, we need a way to +/// map those to a common type arguments ID. If we don't do this, we may end up +/// specializing the same type many times. +pub struct InternedTypeArguments { + /// A cache that maps the raw class instances to their interned type + /// arguments ID. + /// + /// This cache is used to avoid the more expensive key generation process + /// when comparing the exact same type many times. + cache: HashMap, + + /// A mapping of the flattened type IDs from a class instance to the common + /// type arguments ID. + /// + /// For ClassInstance and TraitInstance types, the TypeId is stripped of its + /// TypeArguments ID such that it's consistent when hashed. + mapping: HashMap, u32>, +} + +impl InternedTypeArguments { + pub fn new() -> InternedTypeArguments { + InternedTypeArguments { cache: HashMap::new(), mapping: HashMap::new() } + } + + pub fn intern(&mut self, db: &Database, instance: ClassInstance) -> u32 { + // The cache is used such that if we use the exact same type N times, we + // only perform the more expensive type walking once. + if let Some(&id) = self.cache.get(&instance) { + return id; + } + + let mut key = Vec::new(); + let mut stack = vec![TypeId::ClassInstance(instance)]; + + // The order of the values in the key doesn't matter, as long as it's + // consistent. + while let Some(tid) = stack.pop() { + let (val, args) = match tid { + TypeId::ClassInstance(i) if i.instance_of().is_generic(db) => ( + TypeId::ClassInstance(ClassInstance::new(i.instance_of())), + i.type_arguments(db), + ), + TypeId::TraitInstance(i) if i.instance_of().is_generic(db) => ( + TypeId::TraitInstance(TraitInstance::new(i.instance_of())), + i.type_arguments(db), + ), + _ => (tid, None), + }; + + if let Some(args) = args { + for id in args.iter().flat_map(|(_, t)| t.type_id(db).ok()) { + stack.push(id); + } + } + + key.push(val); + } + + let id = *self.mapping.entry(key).or_insert(instance.type_arguments); + + self.cache.insert(instance, id); + id + } +} + /// An Inko trait. pub struct Trait { name: String, @@ -654,7 +748,7 @@ impl TraitId { requirement.instance_of.get(db).inherited_type_arguments.clone(); if requirement.instance_of.is_generic(db) { - requirement.type_arguments(db).copy_into(&mut base); + requirement.type_arguments(db).unwrap().copy_into(&mut base); } let self_typ = self.get_mut(db); @@ -789,6 +883,9 @@ pub struct TraitInstance { /// /// If the trait is a regular trait, this ID is always 0 and shouldn't be /// used. + /// + /// After type specialization takes place, this value shouldn't be used any + /// more as specialized types won't have their type arguments set. type_arguments: u32, } @@ -835,8 +932,8 @@ impl TraitInstance { self.instance_of } - pub fn type_arguments(self, db: &Database) -> &TypeArguments { - &db.type_arguments[self.type_arguments as usize] + pub fn type_arguments(self, db: &Database) -> Option<&TypeArguments> { + db.type_arguments.get(self.type_arguments as usize) } pub fn copy_new_arguments_from( @@ -863,7 +960,7 @@ impl TraitInstance { return; } - self.type_arguments(db).copy_into(target); + self.type_arguments(db).unwrap().copy_into(target); } pub fn method(self, db: &Database, name: &str) -> Option { @@ -1046,7 +1143,7 @@ pub struct Constructor { name: String, documentation: String, location: Location, - members: Vec, + arguments: Vec, } impl Constructor { @@ -1062,7 +1159,7 @@ impl Constructor { db.constructors.push(Constructor { id, name, - members, + arguments: members, location, documentation: String::new(), }); @@ -1082,16 +1179,20 @@ impl ConstructorId { &self.get(db).name } - pub fn members(self, db: &Database) -> Vec { - self.get(db).members.clone() + /// Returns the arguments of a constructor. + /// + /// The arguments are returned as a slice so one can inspect a constructor's + /// arguments without always cloning the arguments. + pub fn arguments(self, db: &Database) -> &[TypeRef] { + &self.get(db).arguments } - pub fn set_members(self, db: &mut Database, members: Vec) { - self.get_mut(db).members = members; + pub fn set_arguments(self, db: &mut Database, members: Vec) { + self.get_mut(db).arguments = members; } - pub fn number_of_members(self, db: &Database) -> usize { - self.get(db).members.len() + pub fn number_of_arguments(self, db: &Database) -> usize { + self.get(db).arguments.len() } pub fn location(self, db: &Database) -> Location { @@ -1115,17 +1216,41 @@ impl ConstructorId { } } +/// A type describing where something should be allocated. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum Storage { + /// The value must be allocated on the heap. + Heap, + + /// The value must be allocated inline/on the stack. + Stack, +} + #[derive(Clone, Copy, Eq, PartialEq, Debug)] pub enum ClassKind { + /// The type is an async type, aka a process. Async, + + /// The type is a type that uses atomic reference counting. Atomic, + + /// The type is a closure. Closure, + + /// The type is an enumeration. Enum, + + /// The type is a C structure. Extern, + + /// The type is a module. Module, + + /// The type is a regular user-defined type. Regular, + + /// The type is a N-arity tuple. Tuple, - ValueType, } impl ClassKind { @@ -1137,10 +1262,6 @@ impl ClassKind { matches!(self, ClassKind::Enum) } - pub fn is_regular(self) -> bool { - matches!(self, ClassKind::Regular) - } - pub fn is_tuple(self) -> bool { matches!(self, ClassKind::Tuple) } @@ -1164,16 +1285,6 @@ impl ClassKind { fn is_atomic(self) -> bool { matches!(self, ClassKind::Async | ClassKind::Atomic) } - - fn is_value_type(self) -> bool { - matches!( - self, - ClassKind::Async - | ClassKind::Atomic - | ClassKind::Extern - | ClassKind::ValueType - ) - } } /// An Inko class as declared using the `class` keyword. @@ -1181,11 +1292,16 @@ pub struct Class { kind: ClassKind, name: String, documentation: String, + // A flag indicating the presence of a custom destructor. // // We store a flag for this so we can check for the presence of a destructor // without having to look up traits. destructor: bool, + + /// A type describing how instances of this type should be stored. + storage: Storage, + module: ModuleId, location: Location, visibility: Visibility, @@ -1213,10 +1329,15 @@ impl Class { module: ModuleId, location: Location, ) -> ClassId { + let class = Class::new(name, kind, visibility, module, location); + + Class::add(db, class) + } + + fn add(db: &mut Database, class: Class) -> ClassId { assert!(db.classes.len() < u32::MAX as usize); let id = db.classes.len() as u32; - let class = Class::new(name, kind, visibility, module, location); db.classes.push(class); ClassId(id) @@ -1229,11 +1350,18 @@ impl Class { module: ModuleId, location: Location, ) -> Self { + let storage = if let ClassKind::Extern = kind { + Storage::Stack + } else { + Storage::Heap + }; + Self { name, documentation: String::new(), kind, visibility, + storage, destructor: false, fields: IndexMap::new(), type_parameters: IndexMap::new(), @@ -1259,13 +1387,16 @@ impl Class { } fn value_type(name: String) -> Self { - Self::new( + let mut cls = Self::new( name, - ClassKind::ValueType, + ClassKind::Regular, Visibility::Public, ModuleId(DEFAULT_BUILTIN_MODULE_ID), Location::default(), - ) + ); + + cls.storage = Storage::Stack; + cls } fn atomic(name: String) -> Self { @@ -1553,7 +1684,7 @@ impl ClassId { pub fn enum_fields(self, db: &Database) -> Vec { let obj = self.get(db); - if obj.kind == ClassKind::Enum { + if let ClassKind::Enum = obj.kind { // The first value is the tag, so we skip it. obj.fields[1..].values().cloned().collect() } else { @@ -1636,7 +1767,30 @@ impl ClassId { } pub fn is_value_type(self, db: &Database) -> bool { - self.kind(db).is_value_type() + let typ = self.get(db); + + match typ.kind { + // These types are allocated on the heap but treated as value types. + ClassKind::Async | ClassKind::Atomic => true, + _ => matches!(typ.storage, Storage::Stack), + } + } + + pub fn is_heap_allocated(self, db: &Database) -> bool { + matches!(self.get(db).storage, Storage::Heap) + } + + pub fn is_stack_allocated(self, db: &Database) -> bool { + matches!(self.get(db).storage, Storage::Stack) + } + + pub fn has_object_header(self, db: &Database) -> bool { + // Currently heap objects always have an object header and stack objects + // never have one, but this may change at some point. For example, if an + // object is somehow promoted from the heap to the stack it might retain + // its header. Using a separate method for this helps future proof + // things. + !self.is_stack_allocated(db) } pub fn is_closure(self, db: &Database) -> bool { @@ -1647,14 +1801,26 @@ impl ClassId { matches!(self.0, INT_ID | FLOAT_ID) } - pub fn allow_cast(self, db: &Database) -> bool { - match self.0 { - INT_ID | FLOAT_ID | BOOL_ID | NIL_ID | STRING_ID => false, - _ if self.kind(db).is_atomic() => false, - _ => true, + pub fn allow_cast_to_trait(self, db: &Database) -> bool { + let typ = self.get(db); + + match typ.kind { + // Only heap allocated versions of these types have a header and + // thus can be casted to a trait. + ClassKind::Enum | ClassKind::Regular | ClassKind::Tuple => { + matches!(typ.storage, Storage::Heap) + } + // Other types such as closures, processes and extern classes can't + // ever be casted to a trait. + _ => false, } } + pub fn allow_cast_to_foreign(self, db: &Database) -> bool { + matches!(self.get(db).storage, Storage::Heap) + || matches!(self.0, INT_ID | FLOAT_ID | BOOL_ID) + } + pub fn documentation(self, db: &Database) -> &String { &self.get(db).documentation } @@ -1671,15 +1837,31 @@ impl ClassId { self.get_mut(db).location = value; } - fn shape(self, db: &Database, default: Shape) -> Shape { - match self.0 { - INT_ID => Shape::int(), - FLOAT_ID => Shape::float(), - BOOL_ID => Shape::Boolean, - NIL_ID => Shape::Nil, - STRING_ID => Shape::String, - _ if self.kind(db).is_atomic() => Shape::Atomic, - _ => default, + pub fn set_stack_allocated(self, db: &mut Database) { + self.get_mut(db).storage = Storage::Stack; + } + + pub fn clone_for_specialization(self, db: &mut Database) -> ClassId { + let src = self.get(db); + let mut new = Class::new( + src.name.clone(), + src.kind, + src.visibility, + src.module, + src.location, + ); + + new.storage = src.storage; + Class::add(db, new) + } + + pub fn allow_mutating(self, db: &Database) -> bool { + let obj = self.get(db); + + match obj.kind { + ClassKind::Extern => true, + ClassKind::Atomic => false, + _ => matches!(obj.storage, Storage::Heap), } } @@ -1704,6 +1886,9 @@ pub struct ClassInstance { /// /// If the class isn't generic, this index shouldn't be used to obtain the /// type arguments, as it won't be used. + /// + /// After type specialization takes place, this value shouldn't be used any + /// more as specialized types won't have their type arguments set. type_arguments: u32, } @@ -1784,8 +1969,8 @@ impl ClassInstance { self.instance_of } - pub fn type_arguments(self, db: &Database) -> &TypeArguments { - &db.type_arguments[self.type_arguments as usize] + pub fn type_arguments(self, db: &Database) -> Option<&TypeArguments> { + db.type_arguments.get(self.type_arguments as usize) } pub fn method(self, db: &Database, name: &str) -> Option { @@ -1794,7 +1979,7 @@ impl ClassInstance { pub fn ordered_type_arguments(self, db: &Database) -> Vec { let params = self.instance_of.type_parameters(db); - let args = self.type_arguments(db); + let args = self.type_arguments(db).unwrap(); params .into_iter() @@ -1802,6 +1987,39 @@ impl ClassInstance { .collect() } + fn shape( + self, + db: &Database, + interned: &mut InternedTypeArguments, + default: Shape, + ) -> Shape { + match self.instance_of.0 { + INT_ID => Shape::int(), + FLOAT_ID => Shape::float(), + BOOL_ID => Shape::Boolean, + NIL_ID => Shape::Nil, + STRING_ID => Shape::String, + _ if self.instance_of.kind(db).is_atomic() => Shape::Atomic, + _ if self.instance_of.is_stack_allocated(db) => { + let targs = if self.instance_of.is_generic(db) { + // We need to make sure that for different references to the + // same type (e.g. `SomeType[Int]`), the type arguments ID + // is the same so we can reliable compare and hash the + // returned Shape. + interned.intern(db, self) + } else { + 0 + }; + + Shape::Stack(ClassInstance { + instance_of: self.instance_of, + type_arguments: targs, + }) + } + _ => default, + } + } + fn named_type(self, db: &Database, name: &str) -> Option { self.instance_of.named_type(db, name) } @@ -2446,39 +2664,42 @@ impl MethodId { } pub fn is_mutable(self, db: &Database) -> bool { - matches!( - self.get(db).kind, - MethodKind::Mutable | MethodKind::AsyncMutable - ) + matches!(self.kind(db), MethodKind::Mutable | MethodKind::AsyncMutable) } pub fn is_immutable(self, db: &Database) -> bool { matches!( - self.get(db).kind, + self.kind(db), MethodKind::Async | MethodKind::Static | MethodKind::Instance ) } pub fn is_async(self, db: &Database) -> bool { - matches!( - self.get(db).kind, - MethodKind::Async | MethodKind::AsyncMutable - ) + matches!(self.kind(db), MethodKind::Async | MethodKind::AsyncMutable) } pub fn is_static(self, db: &Database) -> bool { - matches!( - self.get(db).kind, - MethodKind::Static | MethodKind::Constructor - ) + matches!(self.kind(db), MethodKind::Static | MethodKind::Constructor) } pub fn is_extern(self, db: &Database) -> bool { - matches!(self.get(db).kind, MethodKind::Extern) + matches!(self.kind(db), MethodKind::Extern) } pub fn is_moving(self, db: &Database) -> bool { - matches!(self.get(db).kind, MethodKind::Moving) + matches!(self.kind(db), MethodKind::Moving) + } + + pub fn is_instance(self, db: &Database) -> bool { + matches!( + self.kind(db), + MethodKind::Async + | MethodKind::AsyncMutable + | MethodKind::Instance + | MethodKind::Moving + | MethodKind::Mutable + | MethodKind::Destructor + ) } pub fn set_variadic(self, db: &mut Database) { @@ -2563,10 +2784,6 @@ impl MethodId { self.get(db).kind } - pub fn is_instance(self, db: &Database) -> bool { - !self.is_static(db) - } - pub fn module(self, db: &Database) -> ModuleId { self.get(db).module } @@ -2913,7 +3130,6 @@ pub enum IdentifierKind { Unknown, Variable(VariableId), Method(CallInfo), - Field(FieldInfo), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -3542,7 +3758,7 @@ impl ClosureId { } match closure.captured_self_type { - Some(typ) if typ.is_permanent(db) => true, + Some(typ) if typ.is_stack_allocated(db) => true, Some(_) => false, _ => true, } @@ -3640,6 +3856,16 @@ pub enum Shape { /// - It better signals the purpose is for raw pointers and not random /// integers Pointer, + + /// A shape for a specific stack allocated type. + /// + /// While comparing `ClassInstance` values for equality is normally not + /// reliable (as different occurrences of the same generic type use + /// different type argument IDs), this is made reliable by interning + /// structurually different but semantically equivalent `ClassInstance` + /// values into a single common `ClassInstance`, such that the comparison + /// _is_ reliable. + Stack(ClassInstance), } impl Shape { @@ -3662,24 +3888,6 @@ impl Shape { } } -impl fmt::Display for Shape { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Shape::Owned => write!(f, "o"), - Shape::Mut => write!(f, "m"), - Shape::Ref => write!(f, "r"), - Shape::Int(s, Sign::Signed) => write!(f, "i{}", s), - Shape::Int(s, Sign::Unsigned) => write!(f, "u{}", s), - Shape::Float(s) => write!(f, "f{}", s), - Shape::Boolean => write!(f, "b"), - Shape::String => write!(f, "s"), - Shape::Atomic => write!(f, "a"), - Shape::Nil => write!(f, "n"), - Shape::Pointer => write!(f, "p"), - } - } -} - /// A reference to a type. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub enum TypeRef { @@ -4000,19 +4208,19 @@ impl TypeRef { Ok(TypeId::TraitInstance(ins)) if ins.instance_of.is_generic(db) => { - ins.type_arguments(db).clone() + ins.type_arguments(db).unwrap().clone() } Ok(TypeId::ClassInstance(ins)) if ins.instance_of.is_generic(db) => { - ins.type_arguments(db).clone() + ins.type_arguments(db).unwrap().clone() } Ok(TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id)) => { id.requirements(db) .into_iter() .filter(|r| r.instance_of.is_generic(db)) .fold(TypeArguments::new(), |mut targs, req| { - req.type_arguments(db).copy_into(&mut targs); + req.type_arguments(db).unwrap().copy_into(&mut targs); req.instance_of() .get(db) .inherited_type_arguments @@ -4099,23 +4307,6 @@ impl TypeRef { } } - pub fn is_mutable(self, db: &Database) -> bool { - match self { - TypeRef::Owned(_) - | TypeRef::Uni(_) - | TypeRef::Mut(_) - | TypeRef::Any(_) - | TypeRef::Pointer(_) - | TypeRef::Error - | TypeRef::Unknown - | TypeRef::Never => true, - TypeRef::Placeholder(id) => { - id.value(db).map_or(true, |v| v.is_mutable(db)) - } - _ => false, - } - } - pub fn use_reference_counting(self, db: &Database) -> bool { match self { TypeRef::Ref(_) @@ -4149,12 +4340,26 @@ impl TypeRef { self.is_instance_of(db, ClassId::nil()) } - pub fn allow_moving(self) -> bool { - matches!(self, TypeRef::Owned(_) | TypeRef::Uni(_)) + pub fn allow_moving(self, db: &Database) -> bool { + match self { + TypeRef::Owned(_) | TypeRef::Uni(_) => true, + TypeRef::UniRef(TypeId::ClassInstance(i)) + | TypeRef::UniMut(TypeId::ClassInstance(i)) => { + i.instance_of.is_stack_allocated(db) + } + TypeRef::Placeholder(id) => { + id.value(db).map_or(false, |v| v.allow_moving(db)) + } + _ => false, + } } pub fn allow_mutating(self, db: &Database) -> bool { match self { + TypeRef::Owned(TypeId::ClassInstance(ins)) + | TypeRef::Mut(TypeId::ClassInstance(ins)) => { + ins.instance_of.allow_mutating(db) + } TypeRef::Owned(_) | TypeRef::Uni(_) | TypeRef::Mut(_) @@ -4174,6 +4379,26 @@ impl TypeRef { } } + pub fn as_class_instance_for_pattern_matching( + self, + db: &Database, + ) -> Option { + match self { + TypeRef::Owned(TypeId::ClassInstance(ins)) + | TypeRef::Uni(TypeId::ClassInstance(ins)) + | TypeRef::Mut(TypeId::ClassInstance(ins)) + | TypeRef::Ref(TypeId::ClassInstance(ins)) + if ins.instance_of.kind(db).allow_pattern_matching() => + { + Some(ins) + } + TypeRef::Placeholder(id) => id + .value(db) + .and_then(|v| v.as_class_instance_for_pattern_matching(db)), + _ => None, + } + } + pub fn is_uni_ref(self, db: &Database) -> bool { match self { TypeRef::UniRef(_) | TypeRef::UniMut(_) => true, @@ -4204,6 +4429,7 @@ impl TypeRef { if class.is_generic(db) && !id .type_arguments(db) + .unwrap() .iter() .all(|(_, v)| v.is_sendable_output(db)) { @@ -4222,7 +4448,7 @@ impl TypeRef { } } - pub fn cast_according_to(self, other: Self, db: &Database) -> Self { + pub fn cast_according_to(self, db: &Database, other: TypeRef) -> Self { if self.is_value_type(db) { return if other.is_uni(db) { self.as_uni(db) @@ -4257,9 +4483,23 @@ impl TypeRef { { TypeRef::Pointer(TypeId::ClassInstance(ins)) } - TypeRef::Owned(id) | TypeRef::Any(id) | TypeRef::Mut(id) => { - TypeRef::Ref(id) + TypeRef::Owned(TypeId::ClassInstance(ins)) + if ins.instance_of().is_stack_allocated(db) => + { + TypeRef::Owned(TypeId::ClassInstance(ins)) } + TypeRef::Owned(id) + | TypeRef::Any(id) + | TypeRef::Mut(id) + | TypeRef::Ref(id) => match id { + TypeId::TypeParameter(pid) + | TypeId::RigidTypeParameter(pid) + if pid.is_stack_allocated(db) => + { + TypeRef::Owned(id) + } + _ => TypeRef::Ref(id), + }, TypeRef::Uni(id) | TypeRef::UniMut(id) => TypeRef::UniRef(id), TypeRef::Placeholder(id) => { if let Some(v) = id.value(db) { @@ -4287,37 +4527,48 @@ impl TypeRef { } } - pub fn allow_as_mut(self, db: &Database) -> bool { - match self { - TypeRef::Owned(TypeId::RigidTypeParameter(id)) - | TypeRef::Any(TypeId::RigidTypeParameter(id)) => id.is_mutable(db), - TypeRef::Owned(_) | TypeRef::Mut(_) | TypeRef::Uni(_) => true, - TypeRef::Pointer(_) => true, - TypeRef::Placeholder(id) => { - id.value(db).map_or(false, |v| v.allow_as_mut(db)) - } - TypeRef::Error => true, - _ => false, - } - } - pub fn as_mut(self, db: &Database) -> Self { match self { TypeRef::Any( id @ TypeId::RigidTypeParameter(pid) | id @ TypeId::TypeParameter(pid), ) => { - if pid.is_mutable(db) { + if pid.is_stack_allocated(db) { + TypeRef::Owned(id) + } else if pid.is_mutable(db) { TypeRef::Mut(id) } else { TypeRef::Ref(id) } } + TypeRef::Owned( + id @ TypeId::RigidTypeParameter(pid) + | id @ TypeId::TypeParameter(pid), + ) + | TypeRef::Mut( + id @ TypeId::RigidTypeParameter(pid) + | id @ TypeId::TypeParameter(pid), + ) => { + if pid.is_stack_allocated(db) { + TypeRef::Owned(id) + } else { + TypeRef::Mut(id) + } + } + TypeRef::Uni( + id @ TypeId::RigidTypeParameter(pid) + | id @ TypeId::TypeParameter(pid), + ) if pid.is_stack_allocated(db) => TypeRef::Owned(id), TypeRef::Owned(TypeId::ClassInstance(ins)) if ins.instance_of().kind(db).is_extern() => { TypeRef::Pointer(TypeId::ClassInstance(ins)) } + TypeRef::Owned(TypeId::ClassInstance(ins)) + if ins.instance_of().is_value_type(db) => + { + TypeRef::Owned(TypeId::ClassInstance(ins)) + } TypeRef::Owned(id) => TypeRef::Mut(id), TypeRef::Uni(id) => TypeRef::UniMut(id), TypeRef::Placeholder(id) => { @@ -4338,8 +4589,31 @@ impl TypeRef { { TypeRef::Pointer(TypeId::ClassInstance(ins)) } - TypeRef::Owned(id) | TypeRef::Any(id) => TypeRef::Mut(id), - TypeRef::Uni(id) => TypeRef::UniMut(id), + TypeRef::Owned(TypeId::ClassInstance(ins)) + if ins.instance_of().is_value_type(db) => + { + TypeRef::Owned(TypeId::ClassInstance(ins)) + } + TypeRef::Owned(id) | TypeRef::Any(id) | TypeRef::Mut(id) => { + match id { + TypeId::TypeParameter(tid) + | TypeId::RigidTypeParameter(tid) + if tid.is_stack_allocated(db) => + { + TypeRef::Owned(id) + } + _ => TypeRef::Mut(id), + } + } + TypeRef::Uni(id) => match id { + TypeId::TypeParameter(tid) + | TypeId::RigidTypeParameter(tid) + if tid.is_stack_allocated(db) => + { + TypeRef::Owned(id) + } + _ => TypeRef::UniMut(id), + }, TypeRef::Placeholder(id) => { if let Some(v) = id.value(db) { v.force_as_mut(db) @@ -4369,7 +4643,7 @@ impl TypeRef { } } - pub fn as_uni_reference(self, db: &Database) -> Self { + pub fn as_uni_borrow(self, db: &Database) -> Self { // Value types can always be exposed to recover blocks, as we can simply // copy them upon moving them around. if self.is_value_type(db) { @@ -4381,7 +4655,7 @@ impl TypeRef { TypeRef::Ref(id) => TypeRef::UniRef(id), TypeRef::Placeholder(id) => { if let Some(v) = id.value(db) { - v.as_uni_reference(db) + v.as_uni_borrow(db) } else { TypeRef::Placeholder(id.as_uni_mut()) } @@ -4396,7 +4670,9 @@ impl TypeRef { | TypeRef::Any(id) | TypeRef::Uni(id) | TypeRef::Mut(id) - | TypeRef::Ref(id) => TypeRef::UniRef(id), + | TypeRef::Ref(id) + | TypeRef::UniRef(id) + | TypeRef::UniMut(id) => TypeRef::UniRef(id), TypeRef::Placeholder(id) => { if let Some(v) = id.value(db) { v.as_uni_ref(db) @@ -4408,6 +4684,26 @@ impl TypeRef { } } + pub fn as_uni_mut(self, db: &Database) -> Self { + match self { + TypeRef::Owned(id) + | TypeRef::Uni(id) + | TypeRef::Mut(id) + | TypeRef::UniMut(id) => TypeRef::UniMut(id), + TypeRef::Ref(id) | TypeRef::Any(id) | TypeRef::UniRef(id) => { + TypeRef::UniRef(id) + } + TypeRef::Placeholder(id) => { + if let Some(v) = id.value(db) { + v.as_uni_mut(db) + } else { + TypeRef::Placeholder(id.as_uni_mut()) + } + } + _ => self, + } + } + pub fn force_as_uni_mut(self, db: &Database) -> Self { match self { TypeRef::Owned(id) @@ -4427,6 +4723,10 @@ impl TypeRef { } pub fn as_uni(self, db: &Database) -> Self { + if self.is_value_type(db) { + return self; + } + match self { TypeRef::Owned(id) | TypeRef::Any(id) @@ -4527,7 +4827,9 @@ impl TypeRef { TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) - | TypeRef::Ref(TypeId::ClassInstance(ins)) => { + | TypeRef::Ref(TypeId::ClassInstance(ins)) + | TypeRef::UniRef(TypeId::ClassInstance(ins)) + | TypeRef::UniMut(TypeId::ClassInstance(ins)) => { ins.instance_of().fields(db) } TypeRef::Placeholder(id) => { @@ -4570,6 +4872,11 @@ impl TypeRef { } } + /// Returns `true` if `self` is a value type. + /// + /// Value types are types that use atomic reference counting (processes and + /// strings), those allocated on the stack (Int, pointers, inline types, + /// etc), or non-values (e.g. modules). pub fn is_value_type(self, db: &Database) -> bool { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) @@ -4589,31 +4896,35 @@ impl TypeRef { TypeRef::Owned(TypeId::Foreign(_)) => true, TypeRef::Pointer(_) => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(true, |v| v.is_value_type(db)) + id.value(db).map_or(false, |v| v.is_value_type(db)) } _ => false, } } - pub fn is_permanent(self, db: &Database) -> bool { + /// Returns `true` if the type is allocated on the stack. + pub fn is_stack_allocated(self, db: &Database) -> bool { match self { - TypeRef::Owned(TypeId::ClassInstance(ins)) - | TypeRef::Ref(TypeId::ClassInstance(ins)) - | TypeRef::Mut(TypeId::ClassInstance(ins)) - | TypeRef::Uni(TypeId::ClassInstance(ins)) - | TypeRef::UniMut(TypeId::ClassInstance(ins)) - | TypeRef::UniRef(TypeId::ClassInstance(ins)) => { - ins.instance_of.kind(db).is_extern() - } - TypeRef::Owned(TypeId::Foreign(_)) => true, - TypeRef::Owned(TypeId::Module(_)) => true, - TypeRef::Owned(TypeId::Class(_)) => true, - TypeRef::Never => true, + TypeRef::Owned(id) + | TypeRef::Uni(id) + | TypeRef::Ref(id) + | TypeRef::UniRef(id) + | TypeRef::Mut(id) + | TypeRef::UniMut(id) + | TypeRef::Any(id) => match id { + TypeId::ClassInstance(ins) => { + ins.instance_of().is_stack_allocated(db) + } + TypeId::TypeParameter(tid) + | TypeId::RigidTypeParameter(tid) => tid.is_stack_allocated(db), + TypeId::Foreign(_) => true, + _ => false, + }, + TypeRef::Error | TypeRef::Pointer(_) => true, TypeRef::Placeholder(id) => { - id.value(db).map_or(true, |v| v.is_permanent(db)) + id.value(db).map_or(false, |v| v.is_stack_allocated(db)) } - TypeRef::Pointer(_) => true, - _ => false, + TypeRef::Never | TypeRef::Unknown => false, } } @@ -4630,6 +4941,7 @@ impl TypeRef { if ins.instance_of.is_generic(db) => { ins.type_arguments(db) + .unwrap() .mapping .values() .all(|v| v.is_inferred(db)) @@ -4638,6 +4950,7 @@ impl TypeRef { if ins.instance_of.is_generic(db) => { ins.type_arguments(db) + .unwrap() .mapping .values() .all(|v| v.is_inferred(db)) @@ -4688,13 +5001,13 @@ impl TypeRef { let params = ins.instance_of.type_parameters(db); if ins.instance_of == res_class { - let args = ins.type_arguments(db); + let args = ins.type_arguments(db).unwrap(); let ok = args.get(params[0]).unwrap(); let err = args.get(params[1]).unwrap(); ThrowKind::Result(ok, err) } else if ins.instance_of == opt_class { - let args = ins.type_arguments(db); + let args = ins.type_arguments(db).unwrap(); let some = args.get(params[0]).unwrap(); ThrowKind::Option(some) @@ -4741,20 +5054,21 @@ impl TypeRef { pub fn shape( self, db: &Database, + interned: &mut InternedTypeArguments, shapes: &HashMap, ) -> Shape { match self { TypeRef::Owned(TypeId::ClassInstance(ins)) | TypeRef::Uni(TypeId::ClassInstance(ins)) => { - ins.instance_of.shape(db, Shape::Owned) + ins.shape(db, interned, Shape::Owned) } TypeRef::Mut(TypeId::ClassInstance(ins)) | TypeRef::UniMut(TypeId::ClassInstance(ins)) => { - ins.instance_of.shape(db, Shape::Mut) + ins.shape(db, interned, Shape::Mut) } TypeRef::Ref(TypeId::ClassInstance(ins)) | TypeRef::UniRef(TypeId::ClassInstance(ins)) => { - ins.instance_of.shape(db, Shape::Ref) + ins.shape(db, interned, Shape::Ref) } TypeRef::Any( TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id), @@ -4780,7 +5094,6 @@ impl TypeRef { TypeRef::Owned(TypeId::AtomicTypeParameter(_)) | TypeRef::Ref(TypeId::AtomicTypeParameter(_)) | TypeRef::Mut(TypeId::AtomicTypeParameter(_)) => Shape::Atomic, - TypeRef::Mut( TypeId::TypeParameter(id) | TypeId::RigidTypeParameter(id), ) @@ -4801,9 +5114,9 @@ impl TypeRef { }, TypeRef::Mut(_) | TypeRef::UniMut(_) => Shape::Mut, TypeRef::Ref(_) | TypeRef::UniRef(_) => Shape::Ref, - TypeRef::Placeholder(id) => { - id.value(db).map_or(Shape::Owned, |v| v.shape(db, shapes)) - } + TypeRef::Placeholder(id) => id + .value(db) + .map_or(Shape::Owned, |v| v.shape(db, interned, shapes)), TypeRef::Owned(TypeId::Foreign(ForeignType::Int(size, sign))) | TypeRef::Uni(TypeId::Foreign(ForeignType::Int(size, sign))) => { Shape::Int(size, sign) @@ -5035,6 +5348,14 @@ impl Database { } } + pub fn compact(&mut self) { + // After specialization, the type arguments are no longer in use. + // Removing them here frees the memory, and ensures we don't continue to + // use them by mistake. + self.type_arguments.clear(); + self.type_arguments.shrink_to_fit(); + } + pub fn builtin_class(&self, name: &str) -> Option { match name { INT_NAME => Some(ClassId::int()), @@ -5140,10 +5461,11 @@ impl Database { mod tests { use super::*; use crate::test::{ - any, closure, generic_trait_instance, immutable, immutable_uni, - instance, mutable, mutable_uni, new_async_class, new_class, - new_extern_class, new_module, new_parameter, new_trait, owned, - parameter, placeholder, pointer, rigid, trait_instance, uni, + any, closure, generic_instance_id, generic_trait_instance, immutable, + immutable_uni, instance, mutable, mutable_uni, new_async_class, + new_class, new_enum_class, new_extern_class, new_module, new_parameter, + new_trait, owned, parameter, placeholder, pointer, rigid, + trait_instance, uni, }; use std::mem::size_of; @@ -5171,6 +5493,23 @@ mod tests { assert_eq!(&db.type_parameters[0].name, &"A".to_string()); } + #[test] + fn test_type_parameter_clone_for_bound() { + let mut db = Database::new(); + let param1 = TypeParameter::alloc(&mut db, "A".to_string()); + + param1.set_mutable(&mut db); + param1.set_stack_allocated(&mut db); + + let param2 = param1.clone_for_bound(&mut db); + + assert_eq!(param1.is_mutable(&db), param2.is_mutable(&db)); + assert_eq!( + param1.is_stack_allocated(&db), + param2.is_stack_allocated(&db) + ); + } + #[test] fn test_type_parameter_new() { let param = TypeParameter::new("A".to_string()); @@ -5358,6 +5697,18 @@ mod tests { ); } + #[test] + fn test_class_clone_for_specialization() { + let mut db = Database::new(); + let class1 = new_class(&mut db, "A"); + + class1.set_stack_allocated(&mut db); + + let class2 = class1.clone_for_specialization(&mut db); + + assert!(class2.is_stack_allocated(&db)); + } + #[test] fn test_class_new() { let class = Class::new( @@ -5991,40 +6342,28 @@ mod tests { assert!(uni(instance(int)).allow_as_ref(&db)); } - #[test] - fn test_type_ref_allow_as_mut() { - let mut db = Database::new(); - let int = ClassId::int(); - let var = TypePlaceholder::alloc(&mut db, None); - let param1 = new_parameter(&mut db, "A"); - let param2 = new_parameter(&mut db, "A"); - - param2.set_mutable(&mut db); - var.assign(&mut db, owned(instance(int))); - - assert!(owned(instance(int)).allow_as_mut(&db)); - assert!(mutable(instance(int)).allow_as_mut(&db)); - assert!(placeholder(var).allow_as_mut(&db)); - assert!(owned(rigid(param2)).allow_as_mut(&db)); - assert!(!immutable(instance(int)).allow_as_mut(&db)); - assert!(!owned(rigid(param1)).allow_as_mut(&db)); - assert!(uni(instance(int)).allow_as_mut(&db)); - } - #[test] fn test_type_ref_as_ref() { let mut db = Database::new(); let int = ClassId::int(); let ext = new_extern_class(&mut db, "Extern"); - let param = new_parameter(&mut db, "A"); + let p1 = new_parameter(&mut db, "A"); + let p2 = new_parameter(&mut db, "A"); + + p2.set_stack_allocated(&mut db); - assert_eq!(owned(instance(int)).as_ref(&db), immutable(instance(int))); + assert_eq!(owned(instance(int)).as_ref(&db), owned(instance(int))); assert_eq!( uni(instance(int)).as_ref(&db), TypeRef::UniRef(instance(int)) ); - assert_eq!(owned(rigid(param)).as_ref(&db), immutable(rigid(param))); + assert_eq!(owned(rigid(p1)).as_ref(&db), immutable(rigid(p1))); assert_eq!(owned(instance(ext)).as_ref(&db), pointer(instance(ext))); + + assert_eq!(owned(parameter(p2)).as_ref(&db), owned(parameter(p2))); + assert_eq!(immutable(parameter(p2)).as_ref(&db), owned(parameter(p2))); + assert_eq!(mutable(parameter(p2)).as_ref(&db), owned(parameter(p2))); + assert_eq!(owned(rigid(p2)).as_ref(&db), owned(rigid(p2))); } #[test] @@ -6032,30 +6371,31 @@ mod tests { let mut db = Database::new(); let int = ClassId::int(); let ext = new_extern_class(&mut db, "Extern"); - let param1 = new_parameter(&mut db, "A"); - let param2 = new_parameter(&mut db, "A"); + let p1 = new_parameter(&mut db, "A"); + let p2 = new_parameter(&mut db, "A"); + let p3 = new_parameter(&mut db, "A"); - param2.set_mutable(&mut db); + p2.set_mutable(&mut db); + p3.set_stack_allocated(&mut db); - assert_eq!(owned(instance(int)).as_mut(&db), mutable(instance(int))); + assert_eq!(owned(instance(int)).as_mut(&db), owned(instance(int))); assert_eq!( uni(instance(int)).as_mut(&db), TypeRef::UniMut(instance(int)) ); - assert_eq!(any(rigid(param1)).as_mut(&db), immutable(rigid(param1))); - assert_eq!( - owned(parameter(param1)).as_mut(&db), - mutable(parameter(param1)) - ); + assert_eq!(any(rigid(p1)).as_mut(&db), immutable(rigid(p1))); + assert_eq!(owned(parameter(p1)).as_mut(&db), mutable(parameter(p1))); - assert_eq!(owned(rigid(param1)).as_mut(&db), mutable(rigid(param1))); - assert_eq!(owned(rigid(param2)).as_mut(&db), mutable(rigid(param2))); - assert_eq!( - owned(parameter(param2)).as_mut(&db), - mutable(parameter(param2)) - ); + assert_eq!(owned(rigid(p1)).as_mut(&db), mutable(rigid(p1))); + assert_eq!(owned(rigid(p2)).as_mut(&db), mutable(rigid(p2))); + assert_eq!(owned(parameter(p2)).as_mut(&db), mutable(parameter(p2))); assert_eq!(owned(instance(ext)).as_mut(&db), pointer(instance(ext))); + + assert_eq!(owned(parameter(p3)).as_mut(&db), owned(parameter(p3))); + assert_eq!(mutable(parameter(p3)).as_mut(&db), owned(parameter(p3))); + assert_eq!(uni(parameter(p3)).as_mut(&db), owned(parameter(p3))); + assert_eq!(owned(rigid(p3)).as_mut(&db), owned(rigid(p3))); } #[test] @@ -6142,6 +6482,32 @@ mod tests { ); } + #[test] + fn test_type_ref_force_as_mut() { + let mut db = Database::new(); + let p1 = new_parameter(&mut db, "A"); + let p2 = new_parameter(&mut db, "A"); + + p2.set_stack_allocated(&mut db); + + assert_eq!( + owned(parameter(p1)).force_as_mut(&db), + mutable(parameter(p1)) + ); + assert_eq!(owned(rigid(p1)).force_as_mut(&db), mutable(rigid(p1))); + + assert_eq!( + owned(parameter(p2)).force_as_mut(&db), + owned(parameter(p2)) + ); + assert_eq!( + mutable(parameter(p2)).force_as_mut(&db), + owned(parameter(p2)) + ); + assert_eq!(uni(parameter(p2)).force_as_mut(&db), owned(parameter(p2))); + assert_eq!(owned(rigid(p2)).force_as_mut(&db), owned(rigid(p2))); + } + #[test] fn test_type_ref_force_as_uni_mut_with_placeholder() { let mut db = Database::new(); @@ -6160,24 +6526,79 @@ mod tests { let int = ClassId::int(); assert_eq!( - owned(instance(foo)).as_uni_reference(&db), + owned(instance(foo)).as_uni_borrow(&db), TypeRef::UniMut(instance(foo)) ); assert_eq!( - owned(instance(int)).as_uni_reference(&db), + owned(instance(int)).as_uni_borrow(&db), TypeRef::Owned(instance(int)) ); assert_eq!( - immutable(instance(foo)).as_uni_reference(&db), + immutable(instance(foo)).as_uni_borrow(&db), TypeRef::UniRef(instance(foo)) ); assert_eq!( - mutable(instance(foo)).as_uni_reference(&db), + mutable(instance(foo)).as_uni_borrow(&db), TypeRef::UniMut(instance(foo)) ); + assert_eq!(uni(instance(foo)).as_uni_borrow(&db), uni(instance(foo))); + } + + #[test] + fn test_type_ref_as_uni_ref() { + let mut db = Database::new(); + let foo = new_class(&mut db, "Foo"); + + assert_eq!( + owned(instance(foo)).as_uni_ref(&db), + immutable_uni(instance(foo)) + ); + assert_eq!( + mutable(instance(foo)).as_uni_ref(&db), + immutable_uni(instance(foo)) + ); + assert_eq!( + uni(instance(foo)).as_uni_ref(&db), + immutable_uni(instance(foo)) + ); + assert_eq!( + immutable_uni(instance(foo)).as_uni_ref(&db), + immutable_uni(instance(foo)) + ); + assert_eq!( + mutable_uni(instance(foo)).as_uni_ref(&db), + immutable_uni(instance(foo)) + ); + } + + #[test] + fn test_type_ref_as_uni_mut() { + let mut db = Database::new(); + let foo = new_class(&mut db, "Foo"); + + assert_eq!( + owned(instance(foo)).as_uni_mut(&db), + mutable_uni(instance(foo)) + ); + assert_eq!( + any(instance(foo)).as_uni_mut(&db), + immutable_uni(instance(foo)) + ); + assert_eq!( + mutable(instance(foo)).as_uni_mut(&db), + mutable_uni(instance(foo)) + ); + assert_eq!( + uni(instance(foo)).as_uni_mut(&db), + mutable_uni(instance(foo)) + ); + assert_eq!( + immutable_uni(instance(foo)).as_uni_mut(&db), + immutable_uni(instance(foo)) + ); assert_eq!( - uni(instance(foo)).as_uni_reference(&db), - uni(instance(foo)) + mutable_uni(instance(foo)).as_uni_mut(&db), + mutable_uni(instance(foo)) ); } @@ -6186,24 +6607,47 @@ mod tests { let mut db = Database::new(); let param1 = new_parameter(&mut db, "T"); let param2 = new_parameter(&mut db, "T"); + let proc = new_async_class(&mut db, "X"); param2.set_mutable(&mut db); - assert!(TypeRef::int().allow_mutating(&db)); assert!(uni(instance(ClassId::string())).allow_mutating(&db)); - assert!(owned(instance(ClassId::string())).allow_mutating(&db)); assert!(immutable(instance(ClassId::string())).allow_mutating(&db)); assert!(mutable(parameter(param1)).allow_mutating(&db)); assert!(mutable(rigid(param1)).allow_mutating(&db)); assert!(owned(parameter(param1)).allow_mutating(&db)); assert!(owned(rigid(param1)).allow_mutating(&db)); - assert!(!any(parameter(param1)).allow_mutating(&db)); - assert!(!any(rigid(param1)).allow_mutating(&db)); assert!(any(parameter(param2)).allow_mutating(&db)); assert!(any(rigid(param2)).allow_mutating(&db)); assert!(uni(parameter(param2)).allow_mutating(&db)); assert!(uni(rigid(param2)).allow_mutating(&db)); + assert!(owned(instance(proc)).allow_mutating(&db)); + assert!(mutable(instance(proc)).allow_mutating(&db)); + + assert!(!immutable(instance(proc)).allow_mutating(&db)); assert!(!immutable(parameter(param1)).allow_mutating(&db)); + assert!(!owned(instance(ClassId::string())).allow_mutating(&db)); + assert!(!any(parameter(param1)).allow_mutating(&db)); + assert!(!any(rigid(param1)).allow_mutating(&db)); + assert!(!TypeRef::int().allow_mutating(&db)); + } + + #[test] + fn test_type_ref_allow_moving() { + let mut db = Database::new(); + let heap = new_class(&mut db, "A"); + let stack = new_class(&mut db, "B"); + + stack.set_stack_allocated(&mut db); + + assert!(owned(instance(heap)).allow_moving(&db)); + assert!(uni(instance(heap)).allow_moving(&db)); + assert!(owned(instance(stack)).allow_moving(&db)); + assert!(uni(instance(stack)).allow_moving(&db)); + assert!(immutable_uni(instance(stack)).allow_moving(&db)); + assert!(mutable_uni(instance(stack)).allow_moving(&db)); + assert!(!mutable(instance(heap)).allow_moving(&db)); + assert!(!immutable(instance(heap)).allow_moving(&db)); } #[test] @@ -6266,110 +6710,171 @@ mod tests { #[test] fn test_type_ref_shape() { let mut db = Database::new(); + let mut inter = InternedTypeArguments::new(); let string = ClassId::string(); let int = ClassId::int(); let float = ClassId::float(); let boolean = ClassId::boolean(); - let class = new_class(&mut db, "Thing"); + let cls1 = new_class(&mut db, "Thing"); + let cls2 = new_class(&mut db, "Foo"); let var = TypePlaceholder::alloc(&mut db, None); let param1 = new_parameter(&mut db, "T"); let param2 = new_parameter(&mut db, "X"); let mut shapes = HashMap::new(); + cls2.set_stack_allocated(&mut db); shapes.insert(param1, Shape::int()); var.assign(&mut db, TypeRef::int()); - assert_eq!(TypeRef::int().shape(&db, &shapes), Shape::int()); - assert_eq!(TypeRef::float().shape(&db, &shapes), Shape::float()); - assert_eq!(TypeRef::boolean().shape(&db, &shapes), Shape::Boolean); - assert_eq!(TypeRef::nil().shape(&db, &shapes), Shape::Nil); - assert_eq!(TypeRef::string().shape(&db, &shapes), Shape::String); - assert_eq!(uni(instance(class)).shape(&db, &shapes), Shape::Owned); - assert_eq!(owned(instance(class)).shape(&db, &shapes), Shape::Owned); - assert_eq!(immutable(instance(class)).shape(&db, &shapes), Shape::Ref); - assert_eq!(mutable(instance(class)).shape(&db, &shapes), Shape::Mut); - assert_eq!(uni(instance(class)).shape(&db, &shapes), Shape::Owned); - assert_eq!(placeholder(var).shape(&db, &shapes), Shape::int()); - assert_eq!(owned(parameter(param1)).shape(&db, &shapes), Shape::int()); assert_eq!( - immutable(parameter(param1)).shape(&db, &shapes), + TypeRef::int().shape(&db, &mut inter, &shapes), Shape::int() ); assert_eq!( - mutable(parameter(param1)).shape(&db, &shapes), + TypeRef::float().shape(&db, &mut inter, &shapes), + Shape::float() + ); + assert_eq!( + TypeRef::boolean().shape(&db, &mut inter, &shapes), + Shape::Boolean + ); + assert_eq!(TypeRef::nil().shape(&db, &mut inter, &shapes), Shape::Nil); + assert_eq!( + TypeRef::string().shape(&db, &mut inter, &shapes), + Shape::String + ); + assert_eq!( + uni(instance(cls1)).shape(&db, &mut inter, &shapes), + Shape::Owned + ); + assert_eq!( + owned(instance(cls1)).shape(&db, &mut inter, &shapes), + Shape::Owned + ); + assert_eq!( + immutable(instance(cls1)).shape(&db, &mut inter, &shapes), + Shape::Ref + ); + assert_eq!( + mutable(instance(cls1)).shape(&db, &mut inter, &shapes), + Shape::Mut + ); + assert_eq!( + uni(instance(cls1)).shape(&db, &mut inter, &shapes), + Shape::Owned + ); + assert_eq!( + placeholder(var).shape(&db, &mut inter, &shapes), Shape::int() ); assert_eq!( - owned(TypeId::AtomicTypeParameter(param2)).shape(&db, &shapes), + owned(parameter(param1)).shape(&db, &mut inter, &shapes), + Shape::int() + ); + assert_eq!( + immutable(parameter(param1)).shape(&db, &mut inter, &shapes), + Shape::int() + ); + assert_eq!( + mutable(parameter(param1)).shape(&db, &mut inter, &shapes), + Shape::int() + ); + assert_eq!( + owned(TypeId::AtomicTypeParameter(param2)) + .shape(&db, &mut inter, &shapes), Shape::Atomic ); assert_eq!( - immutable(TypeId::AtomicTypeParameter(param2)).shape(&db, &shapes), + immutable(TypeId::AtomicTypeParameter(param2)) + .shape(&db, &mut inter, &shapes), Shape::Atomic ); assert_eq!( - mutable(TypeId::AtomicTypeParameter(param2)).shape(&db, &shapes), + mutable(TypeId::AtomicTypeParameter(param2)) + .shape(&db, &mut inter, &shapes), Shape::Atomic ); assert_eq!( - immutable(instance(string)).shape(&db, &shapes), + immutable(instance(string)).shape(&db, &mut inter, &shapes), Shape::String ); - assert_eq!(immutable(instance(int)).shape(&db, &shapes), Shape::int()); assert_eq!( - immutable(instance(float)).shape(&db, &shapes), + immutable(instance(int)).shape(&db, &mut inter, &shapes), + Shape::int() + ); + assert_eq!( + immutable(instance(float)).shape(&db, &mut inter, &shapes), Shape::float() ); assert_eq!( - immutable(instance(boolean)).shape(&db, &shapes), + immutable(instance(boolean)).shape(&db, &mut inter, &shapes), Shape::Boolean ); assert_eq!( - mutable(instance(string)).shape(&db, &shapes), + mutable(instance(string)).shape(&db, &mut inter, &shapes), Shape::String ); - assert_eq!(mutable(instance(int)).shape(&db, &shapes), Shape::int()); assert_eq!( - mutable(instance(float)).shape(&db, &shapes), + mutable(instance(int)).shape(&db, &mut inter, &shapes), + Shape::int() + ); + assert_eq!( + mutable(instance(float)).shape(&db, &mut inter, &shapes), Shape::float() ); assert_eq!( - mutable(instance(boolean)).shape(&db, &shapes), + mutable(instance(boolean)).shape(&db, &mut inter, &shapes), Shape::Boolean ); assert_eq!( owned(TypeId::Foreign(ForeignType::Int(32, Sign::Signed))) - .shape(&db, &shapes), + .shape(&db, &mut inter, &shapes), Shape::Int(32, Sign::Signed) ); assert_eq!( owned(TypeId::Foreign(ForeignType::Int(32, Sign::Unsigned))) - .shape(&db, &shapes), + .shape(&db, &mut inter, &shapes), Shape::Int(32, Sign::Unsigned) ); assert_eq!( uni(TypeId::Foreign(ForeignType::Int(32, Sign::Unsigned))) - .shape(&db, &shapes), + .shape(&db, &mut inter, &shapes), Shape::Int(32, Sign::Unsigned) ); assert_eq!( - owned(TypeId::Foreign(ForeignType::Float(32))).shape(&db, &shapes), + owned(TypeId::Foreign(ForeignType::Float(32))) + .shape(&db, &mut inter, &shapes), Shape::Float(32) ); assert_eq!( - owned(TypeId::Foreign(ForeignType::Float(64))).shape(&db, &shapes), + owned(TypeId::Foreign(ForeignType::Float(64))) + .shape(&db, &mut inter, &shapes), Shape::Float(64) ); assert_eq!( - uni(TypeId::Foreign(ForeignType::Float(64))).shape(&db, &shapes), + uni(TypeId::Foreign(ForeignType::Float(64))) + .shape(&db, &mut inter, &shapes), Shape::Float(64) ); assert_eq!( pointer(TypeId::Foreign(ForeignType::Int(64, Sign::Signed))) - .shape(&db, &shapes), + .shape(&db, &mut inter, &shapes), Shape::Pointer ); + + assert_eq!( + owned(instance(cls2)).shape(&db, &mut inter, &shapes), + Shape::Stack(ClassInstance::new(cls2)) + ); + assert_eq!( + mutable(instance(cls2)).shape(&db, &mut inter, &shapes), + Shape::Stack(ClassInstance::new(cls2)) + ); + assert_eq!( + immutable(instance(cls2)).shape(&db, &mut inter, &shapes), + Shape::Stack(ClassInstance::new(cls2)) + ); } #[test] @@ -6423,4 +6928,64 @@ mod tests { assert!(!mutable(instance(ext)).is_extern_instance(&db)); assert!(!pointer(instance(ext)).is_extern_instance(&db)); } + + #[test] + fn test_class_id_allow_cast() { + let mut db = Database::new(); + let enum_class = new_enum_class(&mut db, "Option"); + let regular_class = new_class(&mut db, "Regular"); + let tuple_class = Class::alloc( + &mut db, + "Tuple1".to_string(), + ClassKind::Tuple, + Visibility::Public, + ModuleId(0), + Location::default(), + ); + + assert!(!ClassId::int().allow_cast_to_trait(&db)); + assert!(!ClassId::float().allow_cast_to_trait(&db)); + assert!(!ClassId::boolean().allow_cast_to_trait(&db)); + assert!(!ClassId::nil().allow_cast_to_trait(&db)); + assert!(!ClassId::string().allow_cast_to_trait(&db)); + assert!(enum_class.allow_cast_to_trait(&db)); + assert!(tuple_class.allow_cast_to_trait(&db)); + assert!(regular_class.allow_cast_to_trait(&db)); + } + + #[test] + fn test_interned_type_arguments() { + let mut db = Database::new(); + let mut intern = InternedTypeArguments::new(); + let ary = ClassId::array(); + let int = TypeRef::int(); + let param = ary.new_type_parameter(&mut db, "T".to_string()); + let val1 = { + let sub = owned(generic_instance_id(&mut db, ary, vec![int])); + + owned(generic_instance_id(&mut db, ary, vec![sub])) + }; + let val2 = { + let sub = owned(generic_instance_id(&mut db, ary, vec![int])); + + owned(generic_instance_id(&mut db, ary, vec![sub])) + }; + let mut targs1 = TypeArguments::new(); + let mut targs2 = TypeArguments::new(); + + targs1.assign(param, val1); + targs2.assign(param, val2); + + let ins1 = ClassInstance::generic(&mut db, ary, targs1); + let ins2 = ClassInstance::generic(&mut db, ary, targs2); + let id1 = intern.intern(&db, ins1); + let id2 = intern.intern(&db, ins2); + let id3 = intern.intern(&db, ins1); + let id4 = intern.intern(&db, ins2); + + assert_eq!(id1, ins1.type_arguments); + assert_eq!(id2, id1); + assert_eq!(id3, id1); + assert_eq!(id4, id1); + } } diff --git a/types/src/resolve.rs b/types/src/resolve.rs index 9973d7dbe..c7e8c01e3 100644 --- a/types/src/resolve.rs +++ b/types/src/resolve.rs @@ -209,7 +209,7 @@ impl<'a> TypeResolver<'a> { return Either::Left(id); } - let mut args = ins.type_arguments(self.db).clone(); + let mut args = ins.type_arguments(self.db).unwrap().clone(); self.resolve_arguments(&mut args); @@ -224,7 +224,7 @@ impl<'a> TypeResolver<'a> { return Either::Left(id); } - let mut args = ins.type_arguments(self.db).clone(); + let mut args = ins.type_arguments(self.db).unwrap().clone(); self.resolve_arguments(&mut args); @@ -746,7 +746,7 @@ mod tests { let arg = match resolve(&mut db, &args, &bounds, input) { TypeRef::Owned(TypeId::ClassInstance(ins)) => { - ins.type_arguments(&db).get(array_param).unwrap() + ins.type_arguments(&db).unwrap().get(array_param).unwrap() } _ => TypeRef::Unknown, }; @@ -777,7 +777,7 @@ mod tests { let arg = match resolve(&mut db, &args, &bounds, input) { TypeRef::Owned(TypeId::ClassInstance(ins)) => { - ins.type_arguments(&db).get(array_param).unwrap() + ins.type_arguments(&db).unwrap().get(array_param).unwrap() } _ => TypeRef::Unknown, }; @@ -802,7 +802,7 @@ mod tests { let arg = match resolve(&mut db, &args, &bounds, input) { TypeRef::Owned(TypeId::TraitInstance(ins)) => { - ins.type_arguments(&db).get(trait_param).unwrap() + ins.type_arguments(&db).unwrap().get(trait_param).unwrap() } _ => TypeRef::Unknown, }; diff --git a/types/src/specialize.rs b/types/src/specialize.rs index 4c9ee1f7d..ad98963ff 100644 --- a/types/src/specialize.rs +++ b/types/src/specialize.rs @@ -1,6 +1,6 @@ use crate::{ - Class, ClassId, ClassInstance, Database, Shape, TypeId, TypeParameterId, - TypeRef, + ClassId, ClassInstance, Database, InternedTypeArguments, Shape, TypeId, + TypeParameterId, TypeRef, }; use std::collections::HashMap; @@ -25,6 +25,7 @@ pub fn ordered_shapes_from_map( /// pass. pub struct TypeSpecializer<'a, 'b, 'c> { db: &'a mut Database, + interned: &'b mut InternedTypeArguments, /// The list of classes created during type specialization. classes: &'c mut Vec, @@ -42,10 +43,11 @@ pub struct TypeSpecializer<'a, 'b, 'c> { impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { pub fn new( db: &'a mut Database, + interned: &'b mut InternedTypeArguments, shapes: &'b HashMap, classes: &'c mut Vec, ) -> TypeSpecializer<'a, 'b, 'c> { - TypeSpecializer { db, shapes, classes } + TypeSpecializer { db, interned, shapes, classes } } pub fn specialize(&mut self, value: TypeRef) -> TypeRef { @@ -75,6 +77,9 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { Some(Shape::Atomic) => { TypeRef::Owned(TypeId::AtomicTypeParameter(pid)) } + Some(Shape::Stack(ins)) => TypeRef::Owned( + TypeId::ClassInstance(self.specialize_class_instance(*ins)), + ), _ => value, }, TypeRef::Ref( @@ -93,6 +98,9 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { Some(Shape::Atomic) => { TypeRef::Ref(TypeId::AtomicTypeParameter(id)) } + Some(Shape::Stack(ins)) => TypeRef::Owned( + TypeId::ClassInstance(self.specialize_class_instance(*ins)), + ), _ => value.as_ref(self.db), }, TypeRef::Mut( @@ -112,20 +120,26 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { Some(Shape::Atomic) => { TypeRef::Mut(TypeId::AtomicTypeParameter(id)) } + Some(Shape::Stack(ins)) => TypeRef::Owned( + TypeId::ClassInstance(self.specialize_class_instance(*ins)), + ), _ => value.force_as_mut(self.db), }, - TypeRef::Owned(id) | TypeRef::Any(id) => { TypeRef::Owned(self.specialize_type_id(id)) } TypeRef::Uni(id) => TypeRef::Uni(self.specialize_type_id(id)), - // Value types should always be specialized as owned types, even - // when using e.g. `ref Int`. + // when using e.g. `ref Int`. We don't account for UniMut and UniRef + // here because: + // + // 1. Applying `uni mut` and `uni ref` to types such as Int and + // String results in the ownership always being owned + // 2. We may explicitly expose certain types as `uni mut` or `uni + // ref` and want to retain that ownership (e.g. when referring to + // stack allocated field values) TypeRef::Ref(TypeId::ClassInstance(ins)) | TypeRef::Mut(TypeId::ClassInstance(ins)) - | TypeRef::UniRef(TypeId::ClassInstance(ins)) - | TypeRef::UniMut(TypeId::ClassInstance(ins)) if ins.instance_of().is_value_type(self.db) => { TypeRef::Owned( @@ -147,30 +161,36 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { } fn specialize_type_id(&mut self, id: TypeId) -> TypeId { - match id { - TypeId::ClassInstance(ins) => { - let cls = ins.instance_of(); - - // For closures we always specialize the classes, based on the - // assumption that most (if not almost all closures) are likely - // to capture generic types, and thus any "does this closure - // capture generics?" check is likely to be true most of the - // time. Even if it's false, the worst case is that we perform - // some redundant work. - if cls.is_generic(self.db) { - TypeId::ClassInstance(self.specialize_generic_instance(ins)) - } else if cls.is_closure(self.db) { - TypeId::ClassInstance(self.specialize_closure_instance(ins)) - } else { - // Regular types may contain generic types in their fields - // or constructors, so we'll need to update those types. - TypeId::ClassInstance(self.specialize_regular_instance(ins)) - } - } - _ => id, + if let TypeId::ClassInstance(ins) = id { + TypeId::ClassInstance(self.specialize_class_instance(ins)) + } else { + id } } + fn specialize_class_instance( + &mut self, + ins: ClassInstance, + ) -> ClassInstance { + let cls = ins.instance_of(); + + // For closures we always specialize the classes, based on the + // assumption that most (if not almost all closures) are likely to + // capture generic types, and thus any "does this closure capture + // generics?" check is likely to be true most of the time. Even if it's + // false, the worst case is that we perform some redundant work. + if cls.is_generic(self.db) { + self.specialize_generic_instance(ins) + } else if cls.is_closure(self.db) { + self.specialize_closure_instance(ins) + } else { + // Regular types may contain generic types in their fields or + // constructors, so we'll need to update those types. + self.specialize_regular_instance(ins) + } + } + + #[allow(clippy::unnecessary_to_owned)] fn specialize_regular_instance( &mut self, ins: ClassInstance, @@ -188,23 +208,34 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { if class.kind(self.db).is_enum() { for var in class.constructors(self.db) { - let members = var - .members(self.db) + let args = var + .arguments(self.db) + .to_vec() .into_iter() .map(|v| { - TypeSpecializer::new(self.db, self.shapes, self.classes) - .specialize(v) + TypeSpecializer::new( + self.db, + self.interned, + self.shapes, + self.classes, + ) + .specialize(v) }) .collect(); - var.set_members(self.db, members); + var.set_arguments(self.db, args); } } for field in class.fields(self.db) { let old = field.value_type(self.db); - let new = TypeSpecializer::new(self.db, self.shapes, self.classes) - .specialize(old); + let new = TypeSpecializer::new( + self.db, + self.interned, + self.shapes, + self.classes, + ) + .specialize(old); field.set_value_type(self.db, new); } @@ -222,16 +253,18 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { return ins; } - let mut args = ins.type_arguments(self.db).clone(); - let mut key = Vec::new(); + let mut args = ins.type_arguments(self.db).unwrap().clone(); + let key = class + .type_parameters(self.db) + .into_iter() + .map(|p| { + let raw = args.get(p).unwrap(); + let typ = self.specialize(raw); - for param in class.type_parameters(self.db) { - let arg = self.specialize(args.get(param).unwrap()); - let shape = arg.shape(self.db, self.shapes); - - key.push(shape); - args.assign(param, arg); - } + args.assign(p, typ); + typ.shape(self.db, self.interned, self.shapes) + }) + .collect(); let new = class .get(self.db) @@ -240,6 +273,9 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { .cloned() .unwrap_or_else(|| self.specialize_class(class, key)); + // We keep the type arguments so we can perform type checking where + // necessary during specialization (e.g. when checking if a stack type + // implements a trait). ClassInstance::generic(self.db, new, args) } @@ -269,15 +305,9 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { ClassInstance::new(new) } + #[allow(clippy::unnecessary_to_owned)] fn specialize_class(&mut self, class: ClassId, key: Vec) -> ClassId { - let (name, kind, vis, module, loc) = { - let cls = class.get(self.db); - let loc = class.location(self.db); - - (cls.name.clone(), cls.kind, cls.visibility, cls.module, loc) - }; - - let new = Class::alloc(self.db, name, kind, vis, module, loc); + let new = class.clone_for_specialization(self.db); self.classes.push(new); new.set_specialization_source(self.db, class); @@ -316,16 +346,22 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { for old_var in class.constructors(self.db) { let name = old_var.name(self.db).clone(); let loc = old_var.location(self.db); - let members = old_var - .members(self.db) + let args = old_var + .arguments(self.db) + .to_vec() .into_iter() .map(|v| { - TypeSpecializer::new(self.db, mapping, self.classes) - .specialize(v) + TypeSpecializer::new( + self.db, + self.interned, + mapping, + self.classes, + ) + .specialize(v) }) .collect(); - new.new_constructor(self.db, name, members, loc); + new.new_constructor(self.db, name, args, loc); } } @@ -342,8 +378,13 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { ) }; - let typ = TypeSpecializer::new(self.db, mapping, self.classes) - .specialize(orig_typ); + let typ = TypeSpecializer::new( + self.db, + self.interned, + mapping, + self.classes, + ) + .specialize(orig_typ); new.new_field(self.db, name, idx as _, typ, vis, module, loc); } @@ -357,14 +398,15 @@ mod tests { use super::*; use crate::format::format_type; use crate::test::{ - any, generic_instance_id, immutable, instance, mutable, new_enum_class, - new_parameter, owned, parameter, rigid, uni, + any, generic_instance_id, immutable, instance, mutable, new_class, + new_enum_class, new_parameter, owned, parameter, rigid, uni, }; use crate::{ClassId, Location, ModuleId, Visibility}; #[test] fn test_specialize_type() { let mut db = Database::new(); + let mut interned = InternedTypeArguments::new(); let class = ClassId::array(); let shapes = HashMap::new(); @@ -374,10 +416,12 @@ mod tests { let raw1 = owned(generic_instance_id(&mut db, class, vec![int])); let raw2 = owned(generic_instance_id(&mut db, class, vec![int])); let mut classes = Vec::new(); - let spec1 = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(raw1); - let spec2 = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(raw2); + let spec1 = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(raw1); + let spec2 = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(raw2); assert_eq!(format_type(&db, spec1), "Array[Int]"); assert_eq!(format_type(&db, spec2), "Array[Int]"); @@ -407,6 +451,7 @@ mod tests { #[test] fn test_specialize_pointer_type() { let mut db = Database::new(); + let mut interned = InternedTypeArguments::new(); let class = ClassId::array(); let shapes = HashMap::new(); @@ -416,8 +461,9 @@ mod tests { let raw = TypeRef::Pointer(generic_instance_id(&mut db, class, vec![int])); let mut classes = Vec::new(); - let spec = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(raw); + let spec = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(raw); assert_eq!(format_type(&db, spec), "Pointer[Array[Int]]"); } @@ -425,21 +471,31 @@ mod tests { #[test] fn test_specialize_type_with_ref_value_types() { let mut db = Database::new(); - let class = ClassId::array(); + let mut interned = InternedTypeArguments::new(); + let foo = new_class(&mut db, "Foo"); + let ary = ClassId::array(); let shapes = HashMap::new(); - class.new_type_parameter(&mut db, "T".to_string()); + ary.new_type_parameter(&mut db, "T".to_string()); let raw = owned(generic_instance_id( &mut db, - class, - vec![immutable(instance(ClassId::int()))], + ary, + vec![immutable(instance(foo))], )); let mut classes = Vec::new(); - let spec = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(raw); + let spec = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(raw); - assert_eq!(format_type(&db, spec), "Array[Int]"); + assert_eq!(format_type(&db, spec), "Array[ref Foo]"); + assert_eq!( + spec, + TypeRef::Owned(TypeId::ClassInstance(ClassInstance { + instance_of: ClassId(db.number_of_classes() as u32 - 1), + type_arguments: 1 + })) + ); assert_eq!(classes.len(), 2); } @@ -503,9 +559,11 @@ mod tests { ], )); + let mut interned = InternedTypeArguments::new(); let mut classes = Vec::new(); - let spec = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(raw); + let spec = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(raw); assert_eq!(format_type(&db, spec), "(Int, ref X, mut Y: mut)"); assert_eq!(classes.len(), 2); @@ -554,13 +612,15 @@ mod tests { Location::default(), ); + let mut interned = InternedTypeArguments::new(); let mut classes = Vec::new(); let shapes = HashMap::new(); let raw = owned(generic_instance_id(&mut db, opt, vec![TypeRef::int()])); - let res = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(raw); + let res = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(raw); assert_eq!(classes.len(), 2); assert!(classes[1].kind(&db).is_enum()); @@ -574,7 +634,7 @@ mod tests { assert!(ins.instance_of().kind(&db).is_enum()); assert_eq!(ins.instance_of().shapes(&db), &[Shape::int()]); assert_eq!( - ins.instance_of().constructor(&db, "Some").unwrap().members(&db), + ins.instance_of().constructor(&db, "Some").unwrap().arguments(&db), vec![TypeRef::int()] ); } @@ -590,11 +650,14 @@ mod tests { let int = TypeRef::int(); let raw = owned(generic_instance_id(&mut db, class, vec![int])); let mut classes = Vec::new(); - let res1 = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(raw); + let mut interned = InternedTypeArguments::new(); + let res1 = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(raw); - let res2 = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(res1); + let res2 = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(res1); assert_eq!(res1, res2); assert_eq!(classes, &[ClassId::int(), res1.class_id(&db).unwrap()]); @@ -605,18 +668,22 @@ mod tests { let mut db = Database::new(); let mut shapes = HashMap::new(); let mut classes = Vec::new(); + let mut interned = InternedTypeArguments::new(); let param = new_parameter(&mut db, "A"); shapes.insert(param, Shape::Atomic); - let owned = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(owned(parameter(param))); + let owned = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(owned(parameter(param))); - let immutable = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(immutable(parameter(param))); + let immutable = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(immutable(parameter(param))); - let mutable = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(mutable(parameter(param))); + let mutable = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(mutable(parameter(param))); assert_eq!(owned, TypeRef::Owned(TypeId::AtomicTypeParameter(param))); assert_eq!(immutable, TypeRef::Ref(TypeId::AtomicTypeParameter(param))); @@ -628,21 +695,26 @@ mod tests { let mut db = Database::new(); let mut shapes = HashMap::new(); let mut classes = Vec::new(); + let mut interned = InternedTypeArguments::new(); let param = new_parameter(&mut db, "A"); shapes.insert(param, Shape::Mut); - let owned = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(owned(parameter(param))); + let owned = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(owned(parameter(param))); - let uni = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(uni(parameter(param))); + let uni = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(uni(parameter(param))); - let immutable = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(immutable(parameter(param))); + let immutable = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(immutable(parameter(param))); - let mutable = TypeSpecializer::new(&mut db, &shapes, &mut classes) - .specialize(mutable(parameter(param))); + let mutable = + TypeSpecializer::new(&mut db, &mut interned, &shapes, &mut classes) + .specialize(mutable(parameter(param))); assert_eq!(owned, TypeRef::Mut(TypeId::TypeParameter(param))); assert_eq!(uni, TypeRef::UniMut(TypeId::TypeParameter(param)));