diff --git a/ast/src/nodes.rs b/ast/src/nodes.rs index fb137535..c31c0630 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, diff --git a/ast/src/parser.rs b/ast/src/parser.rs index 075a0b0f..e36dc760 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, @@ -4604,6 +4611,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 +4631,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 +4654,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 +4671,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 +4723,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 +4762,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 +4804,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 +4857,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 +4905,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 +4944,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 +4986,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 +5028,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 +5070,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 +5112,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 +5154,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 +5192,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(), @@ -6166,6 +6212,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 +9647,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 +9747,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/diagnostics.rs b/compiler/src/diagnostics.rs index 8ea4121e..915dbfa8 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, diff --git a/compiler/src/docs.rs b/compiler/src/docs.rs index b427ae56..7cad7002 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, }; @@ -30,8 +30,7 @@ fn class_kind(kind: ClassKind) -> i64 { ClassKind::Enum => 1, ClassKind::Async => 2, ClassKind::Extern => 3, - ClassKind::ValueType => 4, - ClassKind::Atomic => 5, + ClassKind::Atomic => 4, _ => 0, } } @@ -44,19 +43,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,15 +390,18 @@ 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) @@ -574,9 +576,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 7affd9f8..c82d358e 100644 --- a/compiler/src/format.rs +++ b/compiler/src/format.rs @@ -717,6 +717,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 ")), diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 044c1b1d..c1bb1232 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -396,6 +396,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, @@ -1315,6 +1316,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, + "the 'inline' attribute is only available to \ + regular and enum classes", + self.file(), + node.name.location, + ); + } + } + } + if let ast::ClassKind::Extern = node.kind { return self.define_extern_class(node, documentation); } @@ -1322,6 +1338,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, @@ -3932,6 +3949,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) }, @@ -4018,6 +4036,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 +4087,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 +4110,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 +4149,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, class_id: None, kind: ClassKind::Builtin, name: Constant { @@ -4146,6 +4209,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 +4233,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) }, @@ -4243,6 +4308,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) }, @@ -4316,6 +4382,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) }, @@ -4390,6 +4457,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) }, @@ -6682,6 +6750,7 @@ mod tests { TopLevelExpression::Class(Box::new(DefineClass { documentation: String::new(), public: false, + inline: false, kind: ClassKind::Enum, class_id: None, name: Constant { diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index 2ad439d4..85dcda89 100644 --- a/compiler/src/llvm/context.rs +++ b/compiler/src/llvm/context.rs @@ -185,18 +185,14 @@ 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(), diff --git a/compiler/src/llvm/layouts.rs b/compiler/src/llvm/layouts.rs index 744161a1..45ed43f3 100644 --- a/compiler/src/llvm/layouts.rs +++ b/compiler/src/llvm/layouts.rs @@ -4,8 +4,9 @@ use crate::state::State; use crate::target::OperatingSystem; use inkwell::targets::TargetData; use inkwell::types::{ - AnyType, BasicMetadataTypeEnum, BasicType, FunctionType, StructType, + BasicMetadataTypeEnum, BasicType, FunctionType, StructType, }; +use std::collections::VecDeque; use types::{ CallConvention, BOOL_ID, BYTE_ARRAY_ID, FLOAT_ID, INT_ID, NIL_ID, STRING_ID, }; @@ -13,11 +14,6 @@ use types::{ /// 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) -} - #[derive(Copy, Clone)] pub(crate) struct Method<'ctx> { pub(crate) signature: FunctionType<'ctx>, @@ -166,6 +162,23 @@ impl<'ctx> Layouts<'ctx> { 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, target_data); + layouts.define_dynamic_calls(state, mir, context); + layouts.define_methods(state, mir, context, target_data); + layouts + } + + fn define_types( + &mut self, + state: &State, + mir: &Mir, + context: &'ctx Context, + target_data: &'ctx TargetData, + ) { + 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 +194,47 @@ impl<'ctx> Layouts<'ctx> { // stable). let process_private_size = process_size - HEADER_SIZE; + // For enums the type definition process is a little more complex: for + // each constructor argument we need to generate a field with the + // correct largest size, regardless of what order classes are defined + // in. + // + // To handle that, we build a queue of enum classes to process. When + // encountering an enum that depends on another enum of which the size + // is not yet known, the depending enum is rescheduled. + let mut enums_queue = VecDeque::new(); + + // A map that tracks the enum classes for which the sizes are known. + let mut size_known = vec![true; num_classes]; + for id in mir.classes.keys() { + let kind = id.kind(db); + + // Enums are handled in a separate iteration further down. + if kind.is_enum() { + size_known[id.0 as usize] = id.is_heap_allocated(db); + enums_queue.push_back(id); + continue; + } + // 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); + 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,68 +244,82 @@ impl<'ctx> Layouts<'ctx> { // +--------------------------+ // | user-defined fields | // +--------------------------+ - if kind.is_async() { - types.push( - context - .i8_type() - .array_type(process_private_size) - .into(), - ); - } + 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(); - } - } - } + for field in fields { + let typ = field.value_type(db); - types.append(&mut opaque_types); - } else { - for field in fields { - let typ = context.llvm_type( - db, - &layouts, - field.value_type(db), - ); + types.push(context.llvm_type(db, self, typ)); + } + + layout.set_body(&types, false); + } + + while let Some(id) = enums_queue.pop_front() { + let cons = id.constructors(db); + let sized = cons.iter().all(|c| { + c.arguments(db).iter().all(|t| { + t.class_id(db).map_or(true, |id| size_known[id.0 as usize]) + }) + }); + + if !sized { + enums_queue.push_back(id); + continue; + } + + let layout = self.instances[id.0 as usize]; + let fields = id.fields(db); + let mut types = Vec::with_capacity(fields.len() + 1); - types.push(typ); + 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_types = + vec![ + context.i8_type().array_type(1).as_basic_type_enum(); + fields.len() - 1 + ]; + + for con in cons { + for (idx, &typ) in con.arguments(db).iter().enumerate() { + let llvm_typ = context.llvm_type(db, self, typ); + let size = target_data.get_abi_size(&llvm_typ); + let ex = target_data.get_abi_size(&opaque_types[idx]); + + if size > ex { + opaque_types[idx] = context + .i8_type() + .array_type(size as _) + .as_basic_type_enum(); } } } + types.append(&mut opaque_types); + size_known[id.0 as usize] = true; layout.set_body(&types, false); } + } + + fn define_dynamic_calls( + &mut self, + state: &State, + mir: &Mir, + context: &'ctx Context, + ) { + let db = &state.db; for calls in mir.dynamic_calls.values() { for (method, _) in calls { @@ -285,26 +328,34 @@ impl<'ctx> Layouts<'ctx> { ]; for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); + args.push(context.llvm_type(db, self, typ).into()); } let signature = context - .return_type(db, &layouts, *method) + .return_type(db, self, *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 { + self.methods[method.0 as usize] = Method { call_convention: CallConvention::new(method.is_extern(db)), signature, struct_return: None, }; } } + } + + fn define_methods( + &mut self, + state: &State, + mir: &Mir, + context: &'ctx Context, + target_data: &'ctx TargetData, + ) { + let db = &state.db; - // 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 @@ -322,24 +373,24 @@ impl<'ctx> Layouts<'ctx> { if method.is_instance(db) { args.push( context - .llvm_type(db, &layouts, method.receiver(db)) + .llvm_type(db, self, method.receiver(db)) .into(), ); } for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); + args.push(context.llvm_type(db, self, typ).into()); } context - .return_type(db, &layouts, method) + .return_type(db, self, 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 { + self.methods[method.0 as usize] = Method { call_convention: CallConvention::new(method.is_extern(db)), signature: typ, struct_return: None, @@ -351,15 +402,15 @@ impl<'ctx> Layouts<'ctx> { let mut args: Vec = Vec::new(); for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); + args.push(context.llvm_type(db, self, typ).into()); } let typ = context - .return_type(db, &layouts, method) + .return_type(db, self, 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 { + self.methods[method.0 as usize] = Method { call_convention: CallConvention::new(method.is_extern(db)), signature: typ, struct_return: None, @@ -377,7 +428,7 @@ impl<'ctx> Layouts<'ctx> { let mut ret = None; let mut sret = None; - if let Some(typ) = context.return_type(db, &layouts, method) { + if let Some(typ) = context.return_type(db, self, 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 @@ -388,7 +439,7 @@ impl<'ctx> Layouts<'ctx> { if typ.is_struct_type() { let typ = typ.into_struct_type(); - if target_data.get_bit_size(&typ) + if target_data.get_abi_size(&typ) > state.config.target.pass_struct_size() { args.push(context.pointer_type().into()); @@ -402,7 +453,7 @@ impl<'ctx> Layouts<'ctx> { } for &typ in method.argument_types(db) { - args.push(context.llvm_type(db, &layouts, typ).into()); + args.push(context.llvm_type(db, self, typ).into()); } let variadic = method.is_variadic(db); @@ -411,13 +462,11 @@ impl<'ctx> Layouts<'ctx> { context.void_type().fn_type(&args, variadic) }); - layouts.methods[method.0 as usize] = Method { + self.methods[method.0 as usize] = Method { call_convention: CallConvention::C, signature: sig, struct_return: sret, }; } - - layouts } } diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index e24be73f..d9f078ac 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -2117,8 +2117,39 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { ); } Instruction::GetField(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => + if ins.class.has_object_header(&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() { + 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); + + // 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::GetField(ins) => { let reg_var = self.variables[&ins.register]; let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; @@ -2141,28 +2172,8 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, field); } Instruction::SetField(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => + if ins.class.has_object_header(&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]; - 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::SetField(ins) => { let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; let val_var = self.variables[&ins.value]; @@ -2186,13 +2197,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.has_object_header(&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 +2233,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 +2257,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]; @@ -2360,7 +2360,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 diff --git a/compiler/src/mir/inline.rs b/compiler/src/mir/inline.rs index dd8a6ea7..545d9984 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, diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 155fae97..cb5b9cb6 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -513,10 +513,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_permanent(lower.db()) { + continue; + } + let reg = lower.new_register(typ); lower @@ -540,10 +544,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); @@ -601,12 +608,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 +1173,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 } @@ -2456,9 +2464,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) } @@ -2633,7 +2639,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 +2870,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,7 +2889,7 @@ 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 { @@ -2901,23 +2906,16 @@ impl<'a> LowerMethod<'a> { pmatch::Binding::Ignored(pvar) => { let reg = state.registers[pvar.0]; + if self.register_is_permanent(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 +2952,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_permanent(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 +2987,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 +3009,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 +3036,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_permanent(reg) { + continue; + } - if let Some(regs) = state.child_registers.get(reg) { + self.mark_register_as_moved(reg); + + if let Some(regs) = state.child_registers.get(®) { work.push(regs); } } @@ -3917,9 +3916,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_permanent(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 @@ -4059,10 +4060,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 +4212,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 +4328,10 @@ impl<'a> LowerMethod<'a> { self.register_state(register) == RegisterState::Available } + fn register_is_permanent(&self, register: RegisterId) -> bool { + self.register_type(register).is_permanent(self.db()) + } + fn register_is_moved(&mut self, register: RegisterId) -> bool { self.register_state(register) == RegisterState::Moved } @@ -4338,9 +4340,13 @@ 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_permanent(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_permanent(register) { return false; } @@ -4442,6 +4448,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_permanent(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 43c38540..65d3719b 100644 --- a/compiler/src/mir/pattern_matching.rs +++ b/compiler/src/mir/pattern_matching.rs @@ -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 53994572..59e78255 100644 --- a/compiler/src/mir/specialize.rs +++ b/compiler/src/mir/specialize.rs @@ -1295,8 +1295,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); @@ -1397,13 +1402,10 @@ impl<'a, 'b, 'c> ExpandReference<'a, 'b, 'c> { 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. + Shape::Owned if typ.is_permanent(self.db) => { + // Stack and permanent values are to be left as-is. } Shape::Int(_, _) | Shape::Float(_) diff --git a/compiler/src/target.rs b/compiler/src/target.rs index 7b8ce03d..049a5a0b 100644 --- a/compiler/src/target.rs +++ b/compiler/src/target.rs @@ -237,7 +237,7 @@ impl Target { self == &Target::native() } - /// Returns the maximum size (in bits) of a struct that can be passed + /// Returns the maximum size (in bytes) of a struct that can be passed /// through registers. /// /// If a struct is larger than this size, it must be passed using a pointer. @@ -245,7 +245,7 @@ impl Target { // 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 + 16 } pub(crate) fn stack_pointer_register_name(&self) -> &str { diff --git a/compiler/src/type_check/define_types.rs b/compiler/src/type_check/define_types.rs index f3838a61..9957171c 100644 --- a/compiler/src/type_check/define_types.rs +++ b/compiler/src/type_check/define_types.rs @@ -13,9 +13,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 +68,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 +271,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, ); @@ -551,8 +547,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 +609,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 @@ -670,16 +675,11 @@ impl<'a> DefineFields<'a> { // 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) - ), + // + // TODO: only allow Int, Float, and foreign types + 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 +757,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 +775,10 @@ impl<'a> DefineTypeParameters<'a> { pid.set_mutable(self.db_mut()); } + if is_stack { + pid.set_stack_allocated(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -1048,10 +1053,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 +1070,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 +1091,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 +1133,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 +1151,7 @@ impl<'a> DefineConstructors<'a> { class_id.new_constructor( self.db_mut(), name.to_string(), - members, + args, node.location, ); } @@ -1146,7 +1160,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 +1183,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 diff --git a/compiler/src/type_check/expressions.rs b/compiler/src/type_check/expressions.rs index f2523fcd..da1a10ee 100644 --- a/compiler/src/type_check/expressions.rs +++ b/compiler/src/type_check/expressions.rs @@ -1811,7 +1811,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( @@ -2163,7 +2163,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( diff --git a/compiler/src/type_check/methods.rs b/compiler/src/type_check/methods.rs index 2c46eb43..366e7b90 100644 --- a/compiler/src/type_check/methods.rs +++ b/compiler/src/type_check/methods.rs @@ -996,6 +996,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 +1025,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); diff --git a/compiler/src/type_check/mod.rs b/compiler/src/type_check/mod.rs index de38159b..47b95bb9 100644 --- a/compiler/src/type_check/mod.rs +++ b/compiler/src/type_check/mod.rs @@ -9,7 +9,7 @@ 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; @@ -17,7 +17,7 @@ pub(crate) mod expressions; pub(crate) mod imports; pub(crate) mod methods; -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Debug)] enum RefKind { Default, Owned, @@ -270,6 +270,9 @@ impl<'a> DefineTypeSignature<'a> { id, ))) } + Symbol::Class(id) if id.is_stack_allocated(&self.state.db) => { + TypeRef::Owned(self.define_class_instance(id, node)) + } Symbol::Class(id) => { kind.into_type_ref(self.define_class_instance(id, node)) } @@ -417,7 +420,7 @@ impl<'a> DefineTypeSignature<'a> { DiagnosticId::InvalidType, format!( "the type 'mut {name}' is invalid, as '{name}' \ - might be immutable at runtime", + might be immutable at runtime", name = id.name(self.db()), ), self.file(), @@ -902,8 +905,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 +923,6 @@ pub(crate) fn define_type_bounds( new_param.set_mutable(&mut state.db); } - new_param.set_original(&mut state.db, param); new_param.add_requirements(&mut state.db, reqs); bounds.set(param, new_param); } diff --git a/std/fixtures/diagnostics/inline_class_instances.inko b/std/fixtures/diagnostics/inline_class_instances.inko new file mode 100644 index 00000000..212168a1 --- /dev/null +++ b/std/fixtures/diagnostics/inline_class_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_class_instances.inko:12:12 error(invalid-type): expected a value of type 'T: inline', found 'String' diff --git a/std/fixtures/diagnostics/inline_classes.inko b/std/fixtures/diagnostics/inline_classes.inko new file mode 100644 index 00000000..56058815 --- /dev/null +++ b/std/fixtures/diagnostics/inline_classes.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_classes.inko:9:20 error(invalid-type): the 'inline' attribute is only available to regular and enum classes +# inline_classes.inko:11:21 error(invalid-type): the 'inline' attribute is only available to regular and enum classes diff --git a/std/fixtures/diagnostics/inline_classes_with_fields.inko b/std/fixtures/diagnostics/inline_classes_with_fields.inko new file mode 100644 index 00000000..4d81e69d --- /dev/null +++ b/std/fixtures/diagnostics/inline_classes_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_classes_with_fields.inko:20:19 error(invalid-type): 'String' can't be assigned to type parameter 'T: inline' diff --git a/std/fixtures/fmt/classes/input.inko b/std/fixtures/fmt/classes/input.inko new file mode 100644 index 00000000..b1fac1bf --- /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 00000000..b1fac1bf --- /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/src/std/string.inko b/std/src/std/string.inko index 861f4f0d..5196e865 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 @@ -678,6 +678,16 @@ class builtin String { fn inline byte_unchecked(index: Int) -> Int { (@ptr as Int + index as Pointer[UInt8]).0 as Int } + + fn pub inline opt_fast(index: Int) -> OptionalByte { + if index < 0 or index >= size { return OptionalByte(-1) } + + OptionalByte(byte_unchecked(index)) + } +} + +class pub inline OptionalByte { + let pub @value: Int } impl Bytes for String { @@ -916,7 +926,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/types/src/check.rs b/types/src/check.rs index 110a245e..0f369427 100644 --- a/types/src/check.rs +++ b/types/src/check.rs @@ -290,6 +290,12 @@ impl<'a> TypeChecker<'a> { return false; } + if bound.is_stack_allocated(self.db) + && !val.is_stack_allocated(self.db) + { + return false; + } + bound.requirements(self.db).into_iter().all(|r| { self.check_type_ref_with_trait(val, r, &mut env, rules) }) @@ -362,7 +368,11 @@ impl<'a> TypeChecker<'a> { 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) + | TypeRef::UniMut(right_id) + if is_val => + { self.check_type_id(left_id, right_id, env, rules) } TypeRef::Placeholder(id) => { @@ -664,11 +674,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 +684,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 @@ -699,6 +711,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 +757,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 +817,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 +841,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 +874,12 @@ impl<'a> TypeChecker<'a> { return true; }; + if 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() { @@ -1035,6 +1055,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() @@ -1313,11 +1338,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))); @@ -1344,12 +1372,42 @@ mod tests { 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(int)), mutable_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(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 +1420,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] @@ -2075,7 +2133,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 +2341,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(param1)), owned(parameter(param2))); - check_ok(&db, owned(parameter(param4)), owned(parameter(param3))); - check_err(&db, owned(parameter(param3)), owned(parameter(param4))); + 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_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 +2384,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(param1)), placeholder(var)); - assert_eq!(var.value(&db), Some(owned(rigid(param1)))); + 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_err(&db, owned(rigid(param1)), owned(rigid(param2))); - check_err(&db, immutable(rigid(param1)), immutable(rigid(param2))); + check_ok(&db, owned(rigid(p1)), placeholder(var)); + assert_eq!(var.value(&db), Some(owned(rigid(p1)))); + + 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 +2494,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 +2574,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(); diff --git a/types/src/format.rs b/types/src/format.rs index b68ad29b..73c83abd 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(": "); @@ -322,12 +343,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 { ("[", "]") diff --git a/types/src/lib.rs b/types/src/lib.rs index 3c47b8d1..a4f19ef0 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -98,7 +98,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 +329,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 +350,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 +408,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 +423,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() } @@ -1046,7 +1070,7 @@ pub struct Constructor { name: String, documentation: String, location: Location, - members: Vec, + arguments: Vec, } impl Constructor { @@ -1062,7 +1086,7 @@ impl Constructor { db.constructors.push(Constructor { id, name, - members, + arguments: members, location, documentation: String::new(), }); @@ -1082,16 +1106,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 +1143,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 +1189,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 +1212,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 +1219,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 +1256,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 +1277,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 +1314,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 +1611,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 +1694,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,11 +1728,18 @@ 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, } } @@ -1671,6 +1759,24 @@ impl ClassId { self.get_mut(db).location = value; } + 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) + } + fn shape(self, db: &Database, default: Shape) -> Shape { match self.0 { INT_ID => Shape::int(), @@ -4257,8 +4363,21 @@ impl TypeRef { { TypeRef::Pointer(TypeId::ClassInstance(ins)) } + 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(tid) + | TypeId::RigidTypeParameter(tid) + if tid.is_stack_allocated(db) => + { + TypeRef::Owned(id) + } + _ => TypeRef::Ref(id), + } } TypeRef::Uni(id) | TypeRef::UniMut(id) => TypeRef::UniRef(id), TypeRef::Placeholder(id) => { @@ -4309,15 +4428,32 @@ impl TypeRef { ) => { if pid.is_mutable(db) { TypeRef::Mut(id) + } else if pid.is_stack_allocated(db) { + TypeRef::Owned(id) } else { TypeRef::Ref(id) } } + TypeRef::Owned( + id @ TypeId::RigidTypeParameter(pid) + | id @ TypeId::TypeParameter(pid), + ) => { + if pid.is_stack_allocated(db) { + TypeRef::Owned(id) + } else { + TypeRef::Mut(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_stack_allocated(db) => + { + TypeRef::Owned(TypeId::ClassInstance(ins)) + } TypeRef::Owned(id) => TypeRef::Mut(id), TypeRef::Uni(id) => TypeRef::UniMut(id), TypeRef::Placeholder(id) => { @@ -4338,7 +4474,20 @@ impl TypeRef { { TypeRef::Pointer(TypeId::ClassInstance(ins)) } - TypeRef::Owned(id) | TypeRef::Any(id) => TypeRef::Mut(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) => match id { + TypeId::TypeParameter(tid) + | TypeId::RigidTypeParameter(tid) + if tid.is_stack_allocated(db) => + { + TypeRef::Owned(id) + } + _ => TypeRef::Mut(id), + }, TypeRef::Uni(id) => TypeRef::UniMut(id), TypeRef::Placeholder(id) => { if let Some(v) = id.value(db) { @@ -4570,6 +4719,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)) @@ -4595,25 +4749,63 @@ impl TypeRef { } } + /// Returns `true` if `self` is a type that should never be dropped. + /// + /// The difference between this and `TypeRef::is_stack_allocated()` is that + /// all stack allocated types are permanent types, but not all permanent + /// types are stack allocated (e.g. modules). + /// + /// TODO: maybe just replace this with is_stack_allocated()? pub fn is_permanent(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(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::Module(_) | TypeId::Class(_) | TypeId::Foreign(_) => { + true + } + _ => false, + }, + TypeRef::Error | TypeRef::Pointer(_) => true, + TypeRef::Placeholder(id) => { + id.value(db).map_or(false, |v| v.is_permanent(db)) } - TypeRef::Owned(TypeId::Foreign(_)) => true, - TypeRef::Owned(TypeId::Module(_)) => true, - TypeRef::Owned(TypeId::Class(_)) => true, - TypeRef::Never => true, + TypeRef::Never | TypeRef::Unknown => false, + } + } + + /// Returns `true` if the type is allocated on the stack. + pub fn is_stack_allocated(self, db: &Database) -> bool { + match self { + 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, } } @@ -5142,8 +5334,8 @@ mod tests { 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, + 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 +5363,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 +5567,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( @@ -6016,15 +6237,21 @@ mod tests { 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!(owned(rigid(p2)).as_ref(&db), owned(rigid(p2))); } #[test] @@ -6032,30 +6259,29 @@ 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!(owned(rigid(p3)).as_mut(&db), owned(rigid(p3))); } #[test] @@ -6142,6 +6368,27 @@ 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!(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(); @@ -6423,4 +6670,28 @@ 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)); + } } diff --git a/types/src/specialize.rs b/types/src/specialize.rs index 4c9ee1f7..33733dce 100644 --- a/types/src/specialize.rs +++ b/types/src/specialize.rs @@ -1,6 +1,5 @@ use crate::{ - Class, ClassId, ClassInstance, Database, Shape, TypeId, TypeParameterId, - TypeRef, + ClassId, ClassInstance, Database, Shape, TypeId, TypeParameterId, TypeRef, }; use std::collections::HashMap; @@ -171,6 +170,7 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { } } + #[allow(clippy::unnecessary_to_owned)] fn specialize_regular_instance( &mut self, ins: ClassInstance, @@ -188,8 +188,9 @@ 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) @@ -197,7 +198,7 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { }) .collect(); - var.set_members(self.db, members); + var.set_arguments(self.db, args); } } @@ -269,15 +270,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,8 +311,9 @@ 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) @@ -325,7 +321,7 @@ impl<'a, 'b, 'c> TypeSpecializer<'a, 'b, 'c> { }) .collect(); - new.new_constructor(self.db, name, members, loc); + new.new_constructor(self.db, name, args, loc); } } @@ -574,7 +570,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()] ); }