From 7b068b3981c8603e2b7835cf057b6daf3a792081 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 7 Nov 2024 01:21:45 +0100 Subject: [PATCH] WIP: support inline types --- ast/src/nodes.rs | 1 + ast/src/parser.rs | 26 +++++++++ compiler/src/docs.rs | 1 + compiler/src/format.rs | 1 + compiler/src/hir.rs | 43 ++++++++++++++ compiler/src/llvm/context.rs | 18 +++--- compiler/src/llvm/layouts.rs | 2 +- compiler/src/llvm/passes.rs | 20 +++++-- compiler/src/mir/inline.rs | 6 +- compiler/src/mir/passes.rs | 16 +++--- compiler/src/mir/pattern_matching.rs | 2 +- compiler/src/mir/specialize.rs | 14 +++-- compiler/src/type_check/define_types.rs | 76 ++++++++++--------------- compiler/src/type_check/mod.rs | 7 ++- std/fixtures/fmt/classes/input.inko | 51 +++++++++++++++++ std/fixtures/fmt/classes/output.inko | 51 +++++++++++++++++ types/src/check.rs | 8 ++- types/src/lib.rs | 68 +++++++++++++++++++--- 18 files changed, 325 insertions(+), 86 deletions(-) create mode 100644 std/fixtures/fmt/classes/input.inko create mode 100644 std/fixtures/fmt/classes/output.inko diff --git a/ast/src/nodes.rs b/ast/src/nodes.rs index fb137535..fca9e95b 100644 --- a/ast/src/nodes.rs +++ b/ast/src/nodes.rs @@ -487,6 +487,7 @@ pub enum ClassKind { Enum, Regular, Extern, + Inline, } #[derive(Debug, PartialEq, Eq)] diff --git a/ast/src/parser.rs b/ast/src/parser.rs index 075a0b0f..693f6b81 100644 --- a/ast/src/parser.rs +++ b/ast/src/parser.rs @@ -1044,6 +1044,10 @@ impl Parser { self.next(); ClassKind::Extern } + TokenKind::Inline => { + self.next(); + ClassKind::Inline + } _ => ClassKind::Regular, }; @@ -4661,6 +4665,28 @@ mod tests { ); } + #[test] + fn test_inline_class() { + assert_eq!( + top(parse("class inline A {}")), + TopLevelExpression::DefineClass(Box::new(DefineClass { + public: false, + name: Constant { + source: None, + name: "A".to_string(), + location: cols(14, 14) + }, + kind: ClassKind::Inline, + type_parameters: None, + body: ClassExpressions { + values: Vec::new(), + location: cols(16, 17) + }, + location: cols(1, 17) + })) + ); + } + #[test] fn test_async_class() { assert_eq!( diff --git a/compiler/src/docs.rs b/compiler/src/docs.rs index 71d82550..8bb290fe 100644 --- a/compiler/src/docs.rs +++ b/compiler/src/docs.rs @@ -399,6 +399,7 @@ impl<'a> GenerateDocumentation<'a> { ClassKind::Enum => " enum", ClassKind::Async => " async", ClassKind::Extern => " extern", + ClassKind::Inline => "inline", _ if id.is_builtin() => " builtin", _ => "", }, diff --git a/compiler/src/format.rs b/compiler/src/format.rs index 7affd9f8..ed473f54 100644 --- a/compiler/src/format.rs +++ b/compiler/src/format.rs @@ -722,6 +722,7 @@ impl Document { nodes::ClassKind::Builtin => header.push(Node::text("builtin ")), nodes::ClassKind::Enum => header.push(Node::text("enum ")), nodes::ClassKind::Extern => header.push(Node::text("extern ")), + nodes::ClassKind::Inline => header.push(Node::text("inline ")), nodes::ClassKind::Regular => {} } diff --git a/compiler/src/hir.rs b/compiler/src/hir.rs index 044c1b1d..b2cd89a6 100644 --- a/compiler/src/hir.rs +++ b/compiler/src/hir.rs @@ -390,6 +390,7 @@ pub(crate) enum ClassKind { Builtin, Enum, Regular, + Inline, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -1327,6 +1328,7 @@ impl<'a> LowerToHir<'a> { ast::ClassKind::Async => ClassKind::Async, ast::ClassKind::Enum => ClassKind::Enum, ast::ClassKind::Builtin => ClassKind::Builtin, + ast::ClassKind::Inline => ClassKind::Inline, _ => ClassKind::Regular, }, name: self.constant(node.name), @@ -4018,6 +4020,47 @@ 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, + class_id: None, + kind: ClassKind::Inline, + 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; diff --git a/compiler/src/llvm/context.rs b/compiler/src/llvm/context.rs index 2ad439d4..2fc11012 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.kind(db).is_stack_allocated() => { + 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..d3393ec5 100644 --- a/compiler/src/llvm/layouts.rs +++ b/compiler/src/llvm/layouts.rs @@ -196,7 +196,7 @@ impl<'ctx> Layouts<'ctx> { // processed here will have. let mut types = Vec::with_capacity(fields.len() + 1); - if kind.is_extern() { + if kind.is_stack_allocated() { for field in fields { let typ = context.llvm_type(db, &layouts, field.value_type(db)); diff --git a/compiler/src/llvm/passes.rs b/compiler/src/llvm/passes.rs index e24be73f..d9e309cc 100644 --- a/compiler/src/llvm/passes.rs +++ b/compiler/src/llvm/passes.rs @@ -2117,7 +2117,10 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { ); } Instruction::GetField(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => + if ins + .class + .kind(&self.shared.state.db) + .is_stack_allocated() => { let reg_var = self.variables[&ins.register]; let rec_var = self.variables[&ins.receiver]; @@ -2141,7 +2144,10 @@ 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 + .kind(&self.shared.state.db) + .is_stack_allocated() => { let rec_var = self.variables[&ins.receiver]; let rec_typ = self.variable_types[&ins.receiver]; @@ -2218,7 +2224,10 @@ impl<'shared, 'module, 'ctx> LowerMethod<'shared, 'module, 'ctx> { self.builder.store(reg_var, field); } Instruction::FieldPointer(ins) - if ins.class.kind(&self.shared.state.db).is_extern() => + if ins + .class + .kind(&self.shared.state.db) + .is_stack_allocated() => { let reg_var = self.variables[&ins.register]; let rec_var = self.variables[&ins.receiver]; @@ -2360,7 +2369,10 @@ 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 + .kind(&self.shared.state.db) + .is_stack_allocated() => { // Defining the alloca already reserves (uninitialised) memory, // so there's nothing we actually need to do here. Setting the diff --git a/compiler/src/mir/inline.rs b/compiler/src/mir/inline.rs index dd8a6ea7..47da2817 100644 --- a/compiler/src/mir/inline.rs +++ b/compiler/src/mir/inline.rs @@ -32,7 +32,11 @@ 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.kind(db).is_stack_allocated() => + { + 0 + } Instruction::Allocate(_) => 1, Instruction::Spawn(_) => 1, diff --git a/compiler/src/mir/passes.rs b/compiler/src/mir/passes.rs index 155fae97..cea65bb6 100644 --- a/compiler/src/mir/passes.rs +++ b/compiler/src/mir/passes.rs @@ -601,13 +601,15 @@ 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 free_self { - lower.current_block_mut().free(self_reg, class, loc); + if !class.is_stack_allocated(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 { + lower.current_block_mut().free(self_reg, class, loc); + } } if terminate { diff --git a/compiler/src/mir/pattern_matching.rs b/compiler/src/mir/pattern_matching.rs index 43c38540..8307518f 100644 --- a/compiler/src/mir/pattern_matching.rs +++ b/compiler/src/mir/pattern_matching.rs @@ -830,7 +830,7 @@ impl<'a> Compiler<'a> { Type::Finite(cons) } - ClassKind::Regular | ClassKind::Extern => { + ClassKind::Regular | ClassKind::Extern | ClassKind::Inline => { let fields = class_id.fields(self.db()); let args = fields .iter() diff --git a/compiler/src/mir/specialize.rs b/compiler/src/mir/specialize.rs index 53994572..6c98b0ba 100644 --- a/compiler/src/mir/specialize.rs +++ b/compiler/src/mir/specialize.rs @@ -1295,8 +1295,10 @@ 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.is_stack_allocated(self.db) { + self.block_mut(before_id).check_refs(value, location); + self.block_mut(before_id).free(value, class, location); + } } self.block_mut(before_id).goto(after_id, location); @@ -1397,13 +1399,13 @@ impl<'a, 'b, 'c> ExpandReference<'a, 'b, 'c> { let reg = ins.register; let val = ins.value; let typ = self.method.registers.value_type(val); - let is_extern = typ + let is_stack = typ .class_id(self.db) - .map_or(false, |i| i.kind(self.db).is_extern()); + .map_or(false, |i| i.kind(self.db).is_stack_allocated()); match typ.shape(self.db, self.shapes) { - Shape::Owned if is_extern || typ.is_permanent(self.db) => { - // Extern and permanent values are to be left as-is. + Shape::Owned if is_stack || typ.is_permanent(self.db) => { + // stack and permanent values are to be left as-is. } Shape::Int(_, _) | Shape::Float(_) diff --git a/compiler/src/type_check/define_types.rs b/compiler/src/type_check/define_types.rs index f3838a61..c13e9582 100644 --- a/compiler/src/type_check/define_types.rs +++ b/compiler/src/type_check/define_types.rs @@ -68,55 +68,39 @@ 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( - self.db_mut(), - name.clone(), - ClassKind::Enum, - vis, - module, - loc, - ), + } else { + let kind = match node.kind { + hir::ClassKind::Regular => ClassKind::Regular, + hir::ClassKind::Async => ClassKind::Async, + hir::ClassKind::Enum => ClassKind::Enum, + hir::ClassKind::Inline => ClassKind::Inline, + _ => unreachable!(), + }; + + Class::alloc(self.db_mut(), name.clone(), kind, vis, module, loc) }; if self.module.symbol_exists(self.db(), &name) { diff --git a/compiler/src/type_check/mod.rs b/compiler/src/type_check/mod.rs index de38159b..289e84c4 100644 --- a/compiler/src/type_check/mod.rs +++ b/compiler/src/type_check/mod.rs @@ -17,7 +17,7 @@ pub(crate) mod expressions; pub(crate) mod imports; pub(crate) mod methods; -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Debug)] enum RefKind { Default, Owned, @@ -270,6 +270,11 @@ impl<'a> DefineTypeSignature<'a> { id, ))) } + Symbol::Class(id) + if id.kind(&self.state.db).is_stack_allocated() => + { + TypeRef::Owned(self.define_class_instance(id, node)) + } Symbol::Class(id) => { kind.into_type_ref(self.define_class_instance(id, node)) } 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/types/src/check.rs b/types/src/check.rs index 110a245e..e8e49732 100644 --- a/types/src/check.rs +++ b/types/src/check.rs @@ -362,7 +362,11 @@ impl<'a> TypeChecker<'a> { self.check_type_id(left_id, right_id, env, rules) } - TypeRef::Uni(right_id) if is_val => { + TypeRef::Uni(right_id) + | TypeRef::UniRef(right_id) + | TypeRef::UniMut(right_id) + if is_val => + { self.check_type_id(left_id, right_id, env, rules) } TypeRef::Placeholder(id) => { @@ -1344,6 +1348,8 @@ mod tests { check_ok(&db, owned(instance(int)), immutable(instance(int))); check_ok(&db, owned(instance(int)), mutable(instance(int))); check_ok(&db, owned(instance(int)), uni(instance(int))); + check_ok(&db, owned(instance(int)), immutable_uni(instance(int))); + check_ok(&db, owned(instance(int)), mutable_uni(instance(int))); check_ok(&db, owned(instance(foo)), TypeRef::Error); check_err(&db, owned(instance(foo)), immutable(instance(foo))); check_err(&db, owned(instance(foo)), mutable(instance(foo))); diff --git a/types/src/lib.rs b/types/src/lib.rs index 22ca6456..be4520ad 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -1174,8 +1174,15 @@ impl ClassKind { matches!(self, ClassKind::Extern) } + pub fn is_stack_allocated(self) -> bool { + matches!(self, ClassKind::Extern | ClassKind::Inline) + } + pub fn allow_pattern_matching(self) -> bool { - matches!(self, ClassKind::Regular | ClassKind::Extern) + matches!( + self, + ClassKind::Regular | ClassKind::Extern | ClassKind::Inline + ) } fn is_atomic(self) -> bool { @@ -1656,6 +1663,10 @@ impl ClassId { self.kind(db).is_value_type() } + pub fn is_stack_allocated(self, db: &Database) -> bool { + self.kind(db).is_stack_allocated() + } + pub fn is_closure(self, db: &Database) -> bool { self.kind(db).is_closure() } @@ -1667,8 +1678,12 @@ impl ClassId { 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, + _ => { + matches!( + self.kind(db), + ClassKind::Enum | ClassKind::Regular | ClassKind::Tuple + ) + } } } @@ -4274,6 +4289,11 @@ impl TypeRef { { TypeRef::Pointer(TypeId::ClassInstance(ins)) } + TypeRef::Owned(TypeId::ClassInstance(ins)) + if ins.instance_of().kind(db).is_stack_allocated() => + { + TypeRef::Owned(TypeId::ClassInstance(ins)) + } TypeRef::Owned(id) | TypeRef::Any(id) | TypeRef::Mut(id) => { TypeRef::Ref(id) } @@ -4335,6 +4355,11 @@ impl TypeRef { { TypeRef::Pointer(TypeId::ClassInstance(ins)) } + TypeRef::Owned(TypeId::ClassInstance(ins)) + if ins.instance_of().kind(db).is_stack_allocated() => + { + TypeRef::Owned(TypeId::ClassInstance(ins)) + } TypeRef::Owned(id) => TypeRef::Mut(id), TypeRef::Uni(id) => TypeRef::UniMut(id), TypeRef::Placeholder(id) => { @@ -4355,6 +4380,11 @@ impl TypeRef { { TypeRef::Pointer(TypeId::ClassInstance(ins)) } + TypeRef::Owned(TypeId::ClassInstance(ins)) + if ins.instance_of().kind(db).is_stack_allocated() => + { + TypeRef::Owned(TypeId::ClassInstance(ins)) + } TypeRef::Owned(id) | TypeRef::Any(id) => TypeRef::Mut(id), TypeRef::Uni(id) => TypeRef::UniMut(id), TypeRef::Placeholder(id) => { @@ -5159,8 +5189,8 @@ mod tests { use crate::test::{ any, closure, generic_trait_instance, immutable, immutable_uni, instance, mutable, mutable_uni, new_async_class, new_class, - new_extern_class, new_module, new_parameter, new_trait, owned, - parameter, placeholder, pointer, rigid, trait_instance, uni, + new_enum_class, new_extern_class, new_module, new_parameter, new_trait, + owned, parameter, placeholder, pointer, rigid, trait_instance, uni, }; use std::mem::size_of; @@ -6035,7 +6065,7 @@ mod tests { let ext = new_extern_class(&mut db, "Extern"); let param = new_parameter(&mut db, "A"); - assert_eq!(owned(instance(int)).as_ref(&db), immutable(instance(int))); + assert_eq!(owned(instance(int)).as_ref(&db), owned(instance(int))); assert_eq!( uni(instance(int)).as_ref(&db), TypeRef::UniRef(instance(int)) @@ -6054,7 +6084,7 @@ mod tests { param2.set_mutable(&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)) @@ -6440,4 +6470,28 @@ mod tests { assert!(!mutable(instance(ext)).is_extern_instance(&db)); assert!(!pointer(instance(ext)).is_extern_instance(&db)); } + + #[test] + fn test_class_id_allow_cast() { + let mut db = Database::new(); + let enum_class = new_enum_class(&mut db, "Option"); + let regular_class = new_class(&mut db, "Regular"); + let tuple_class = Class::alloc( + &mut db, + "Tuple1".to_string(), + ClassKind::Tuple, + Visibility::Public, + ModuleId(0), + Location::default(), + ); + + assert!(!ClassId::int().allow_cast(&db)); + assert!(!ClassId::float().allow_cast(&db)); + assert!(!ClassId::boolean().allow_cast(&db)); + assert!(!ClassId::nil().allow_cast(&db)); + assert!(!ClassId::string().allow_cast(&db)); + assert!(enum_class.allow_cast(&db)); + assert!(tuple_class.allow_cast(&db)); + assert!(regular_class.allow_cast(&db)); + } }