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/compiler.rs b/compiler/src/compiler.rs index ce06ef37..97bfc1f8 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 8ea4121e..bb1596c6 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, @@ -978,6 +996,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 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..305bb81b 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, @@ -854,9 +849,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 +1309,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 +1331,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, @@ -2444,9 +2454,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 +2535,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 +2560,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 +2702,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, })); @@ -3932,6 +3940,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 +4027,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 +4078,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 +4101,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 +4140,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 +4200,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 +4224,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 +4299,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 +4373,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 +4448,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) }, @@ -5630,9 +5689,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 +6138,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 +6739,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/methods.rs b/compiler/src/llvm/methods.rs index ba961b3d..38373dd2 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/passes.rs b/compiler/src/llvm/passes.rs index e24be73f..7f81838e 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 @@ -2555,7 +2555,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!(), } } diff --git a/compiler/src/mir/inline.rs b/compiler/src/mir/inline.rs index dd8a6ea7..be808c26 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 330f5932..e4338447 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 155fae97..67fa8784 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 43c38540..061dab98 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 53994572..641d198c 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 8930bcbf..bc207307 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,55 @@ 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 shapes = id.shapes(db); + + if !shapes.is_empty() { + buf.push('#'); + 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 +68,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 +133,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 +169,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 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..8be9a4fb 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 { + pid.set_stack_allocated(self.db_mut()); + } + param.type_parameter_id = Some(pid); } } @@ -1048,10 +1059,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 +1076,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 +1097,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 +1139,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 +1157,7 @@ impl<'a> DefineConstructors<'a> { class_id.new_constructor( self.db_mut(), name.to_string(), - members, + args, node.location, ); } @@ -1146,7 +1166,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 +1189,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 +1225,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 +1486,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 f2523fcd..5eef1c2f 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,33 @@ 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. + // + // TODO: only do this when inside a `mut` expression for consistency. + 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 00000000..d55767d0 --- /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 4f1d0a28..708ae652 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 2c46eb43..79209202 100644 --- a/compiler/src/type_check/methods.rs +++ b/compiler/src/type_check/methods.rs @@ -668,10 +668,27 @@ 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, + ); + } + + if matches!(node.kind, hir::MethodKind::Mutable) + && !class_id.allow_mutating(self.db()) + { + let name = class_id.name(self.db()); + + self.state.diagnostics.error( + DiagnosticId::InvalidMethod, + format!( + "'{}' doesn't support mutating methods because it's an \ + immutable type", + name + ), self.file(), node.location, ); @@ -711,7 +728,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 +1013,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 +1042,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..686436be 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,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/default_method_with_bounds.inko b/std/fixtures/diagnostics/default_method_with_bounds.inko index 9a3c44f6..52c3124a 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_with_mutating_methods.inko b/std/fixtures/diagnostics/immutable_types_with_mutating_methods.inko new file mode 100644 index 00000000..59abf4cd --- /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_type_definitions.inko b/std/fixtures/diagnostics/inline_type_definitions.inko new file mode 100644 index 00000000..bdbf4c6a --- /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 00000000..d80ea384 --- /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_types_as_mutable_arguments.inko b/std/fixtures/diagnostics/inline_types_as_mutable_arguments.inko new file mode 100644 index 00000000..b9593b97 --- /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 00000000..9139b8f4 --- /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 00000000..ad6b3157 --- /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 00000000..39fe1001 --- /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 00000000..85c59ce6 --- /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/value_types_passed_to_mutable_arguments.inko b/std/fixtures/diagnostics/value_types_passed_to_mutable_arguments.inko new file mode 100644 index 00000000..0535597f --- /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 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..975f2355 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 00000000..c987dabf --- /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 00000000..7fc0b373 --- /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 86d203db..de55fa55 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 11c81806..fee424d7 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 94954de1..4cc692c8 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 31b62b88..752eb4d2 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 c6374118..72b5709f 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 110a245e..385eca8d 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 @@ -699,6 +706,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 +752,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 +812,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 +836,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 +869,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 +977,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 +1054,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 +1092,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 +1337,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 +1369,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 +1419,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 +1768,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 +1778,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 +2045,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 +2132,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 +2340,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 +2383,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 +2493,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 +2573,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..756d1047 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 3c47b8d1..103d1dae 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,11 +1801,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,15 +1832,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 +1881,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 +1964,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 +1974,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 +1982,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) } @@ -2913,7 +3126,6 @@ pub enum IdentifierKind { Unknown, Variable(VariableId), Method(CallInfo), - Field(FieldInfo), } #[derive(Clone, Debug, PartialEq, Eq)] @@ -3542,7 +3754,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 +3852,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 +3884,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 +4204,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 +4303,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 +4336,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 +4375,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 +4425,7 @@ impl TypeRef { if class.is_generic(db) && !id .type_arguments(db) + .unwrap() .iter() .all(|(_, v)| v.is_sendable_output(db)) { @@ -4222,7 +4444,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 +4479,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 +4523,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 +4585,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 +4639,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 +4651,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 +4666,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 +4680,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 +4719,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 +4823,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 +4868,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 +4892,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 +4937,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 +4946,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 +4997,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 +5050,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 +5090,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 +5110,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 +5344,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 +5457,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 +5489,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 +5693,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 +6338,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"); - assert_eq!(owned(instance(int)).as_ref(&db), immutable(instance(int))); + p2.set_stack_allocated(&mut db); + + 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 +6367,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 +6478,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 +6522,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 +6603,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 +6706,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(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, &shapes), + 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 +6924,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 9973d7db..c7e8c01e 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 4c9ee1f7..ad98963f 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)));