diff --git a/fir/src/checks.rs b/fir/src/checks.rs index f9840a8d..6a9c0ffd 100644 --- a/fir/src/checks.rs +++ b/fir/src/checks.rs @@ -118,7 +118,7 @@ impl Fir { // FIXME: Check `to` as well } Kind::Assignment { to, from: _ } => { - check!(to => Kind::TypedValue { .. }, node); + check!(to => Kind::TypedValue { .. } | Kind::Binding { .. }, node); // FIXME: Check `from` as well } Kind::Instantiation { diff --git a/fir/src/lib.rs b/fir/src/lib.rs index eb2a7425..6cc7bb66 100644 --- a/fir/src/lib.rs +++ b/fir/src/lib.rs @@ -254,9 +254,9 @@ impl Index<&OriginIdx> for Fir { #[derive(Debug, Clone)] pub struct Node { - pub data: T, pub origin: OriginIdx, pub kind: Kind, + pub data: T, } /// An instance of [`Fir`] is similar to a graph, containing [`Node`]s and relationships binding them together. diff --git a/fire/src/instance.rs b/fire/src/instance.rs index 93d86dd7..3be67e6c 100644 --- a/fire/src/instance.rs +++ b/fire/src/instance.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + // FIXME: This is invalid // what's a type? at runtime? // just the hash of the type? -> that's good enough @@ -23,7 +25,10 @@ pub enum Instance { Bool(bool), Char(char), String(String), - Other { + /// A slow representation of a record type instance - useful for debugging + SlowRecord(HashMap), + /// A speedy record type instance + Record { ty: Type, data: Vec, }, diff --git a/fire/src/lib.rs b/fire/src/lib.rs index 764f6c8f..f9948775 100644 --- a/fire/src/lib.rs +++ b/fire/src/lib.rs @@ -59,6 +59,7 @@ impl Interpret for Fir> { } /// It's called [`GarbajKollector`] because this way you have "jk" in the middle of the word :) +#[derive(Debug)] pub struct GarbaJKollector(HashMap); // FIXME: Add documentation for all methods @@ -323,6 +324,52 @@ impl<'ast, 'fir> Fire<'ast, 'fir> { KeepGoing } + #[must_use] + fn fire_instantiation( + &mut self, + node: &Node>, + to: &RefIdx, + _generics: &[RefIdx], + fields: &[RefIdx], + ) -> ControlFlow { + let definition = self.access(to); + let fields_def = match &definition.kind { + Kind::RecordType { fields, .. } => fields, + _ => unreachable!(), + }; + + // FIXME: This is absolutely disgusting + let map = match fields_def.iter().zip(fields).try_fold( + HashMap::new(), + |mut field_map, (field_def, field)| { + let field_def = self + .access(field_def) + .data + .ast + .symbol() + .expect("interpreter error - field definition without a symbol"); + + match self.fire_node_ref(field) { + ControlFlow::Continue(_) => Ok(()), + ControlFlow::Break(early) => Err(early), + }?; + + let value = self.gc.lookup(&field.expect_resolved()).cloned().unwrap(); + + field_map.insert(field_def.access().to_string(), value); + + Ok::, EarlyExit>(field_map) + }, + ) { + Ok(map) => ControlFlow::Continue(map), + Err(early) => ControlFlow::Break(early), + }?; + + self.gc.allocate(node.origin, Instance::SlowRecord(map)); + + KeepGoing + } + #[must_use] fn fire_node(&mut self, node: &Node>) -> ControlFlow { match &node.kind { @@ -345,12 +392,18 @@ impl<'ast, 'fir> Fire<'ast, 'fir> { // return_type, // block, // } => self.traverse_function( node, generics, args, return_type, block), - // Kind::Assignment { to, from } => self.traverse_assignment( node, to, from), - // Kind::Instantiation { - // to, - // generics, - // fields, - // } => self.traverse_instantiation( node, to, generics, fields), + // FIXME: Rework this - invalid + Kind::Assignment { from, .. } => { + self.fire_node_ref(from)?; + self.gc.transfer(from, node.origin); + + KeepGoing + }, + Kind::Instantiation { + to, + generics, + fields, + } => self.fire_instantiation(node, to, generics, fields), // Kind::TypeOffset { instance, field } => { // self.traverse_type_offset( node, instance, field) // } diff --git a/name_resolve/src/lib.rs b/name_resolve/src/lib.rs index 16e7c98f..6d724e7a 100644 --- a/name_resolve/src/lib.rs +++ b/name_resolve/src/lib.rs @@ -49,7 +49,7 @@ mod resolver; mod scoper; use declarator::Declarator; -use resolver::{ResolveKind, Resolver}; +use resolver::{LatentResolver, ResolveKind, Resolver}; use scoper::Scoper; /// Error reported when an item (variable, function, type) was already declared @@ -343,7 +343,37 @@ impl<'enclosing> NameResolveCtx<'enclosing> { &mut self, fir: Fir>, ) -> Result>, Incomplete, NameResolutionError>> { - Resolver(self).map(fir) + let mut resolver = Resolver { + ctx: self, + required_parents: Vec::new(), + }; + + let fir = resolver.map(fir)?; + + let latent_map = + resolver + .required_parents + .into_iter() + .fold(HashMap::new(), |mut map, reqd| { + let required_node = &fir[&reqd]; + + let scope = match &required_node.kind { + Kind::Instantiation { to, .. } => Scope(to.expect_resolved()), + other => unreachable!( + "interpreter error - expected Instantiation, got {other:#?}" + ), + }; + + map.insert(reqd, scope); + + map + }); + + LatentResolver { + ctx: self, + latent_map, + } + .map(fir) } } @@ -676,4 +706,32 @@ mod tests { assert!(fir.is_err()) } + + #[test] + fn field_instantiation_valid() { + let ast = ast! { + type Foo; + + type Record(of: Foo); + where x = Record(of: Foo); + }; + + let fir = ast.flatten().name_resolve(); + + assert!(fir.is_ok()); + } + + #[test] + fn field_instantiation_invalid() { + let ast = ast! { + type Foo; + + type Record(of: Foo); + where x = Record(not_of: Foo); + }; + + let fir = ast.flatten().name_resolve(); + + assert!(fir.is_err()); + } } diff --git a/name_resolve/src/resolver.rs b/name_resolve/src/resolver.rs index 22bc55a6..f45ec6f0 100644 --- a/name_resolve/src/resolver.rs +++ b/name_resolve/src/resolver.rs @@ -1,17 +1,20 @@ -use std::fmt::{Display, Formatter, Result as FmtResult}; +use std::{ + collections::HashMap, + fmt::{Display, Formatter, Result as FmtResult}, +}; use fir::{Kind, Mapper, Node, OriginIdx, RefIdx}; use flatten::FlattenData; use location::SpanTuple; use symbol::Symbol; -use crate::{NameResolutionError, NameResolveCtx}; +use crate::{NameResolutionError, NameResolveCtx, Scope}; #[derive(Clone, Copy)] pub(crate) enum ResolveKind { Call, Type, - Var, + Binding, } impl Display for ResolveKind { @@ -19,12 +22,29 @@ impl Display for ResolveKind { match self { ResolveKind::Call => write!(f, "call"), ResolveKind::Type => write!(f, "type"), - ResolveKind::Var => write!(f, "binding"), + ResolveKind::Binding => write!(f, "binding"), } } } -pub(crate) struct Resolver<'ctx, 'enclosing>(pub(crate) &'ctx mut NameResolveCtx<'enclosing>); +pub(crate) struct Resolver<'ctx, 'enclosing> { + pub(crate) ctx: &'ctx mut NameResolveCtx<'enclosing>, + /// The `required_parents` vector contains information about origins whose resolution is necessary + /// for the resolution of other nodes. For example, when resolving a type instantiation like + /// `Foo(bar: 15)`, we need to have `Foo` resolved to its definition in order to resolve `bar` to + /// that type's `.bar` field. + // FIXME: Rework this part of the doc + /// This vector is created as part of the first resolving pass - which + /// we can call early-resolving, or straightforward-resolving, and will be used by a secondary, + /// smaller resolving pass whose purpose is to map these undecidable usages to their definition. + pub(crate) required_parents: Vec, +} + +/// Created thanks to [`Resolver`] +pub(crate) struct LatentResolver<'ctx, 'enclosing> { + pub(crate) ctx: &'ctx mut NameResolveCtx<'enclosing>, + pub(crate) latent_map: HashMap, +} impl<'ctx, 'enclosing> Resolver<'ctx, 'enclosing> { fn get_definition( @@ -37,14 +57,14 @@ impl<'ctx, 'enclosing> Resolver<'ctx, 'enclosing> { let symbol = sym.expect("attempting to get definition for non existent symbol - interpreter bug"); - let mappings = &self.0.mappings; + let mappings = &self.ctx.mappings; - let scope = self.0.enclosing_scope[node]; + let scope = self.ctx.enclosing_scope[node]; let origin = match kind { ResolveKind::Call => mappings.functions.lookup(symbol, scope), ResolveKind::Type => mappings.types.lookup(symbol, scope), - ResolveKind::Var => mappings.bindings.lookup(symbol, scope), + ResolveKind::Binding => mappings.bindings.lookup(symbol, scope), }; origin.map_or_else( @@ -102,7 +122,7 @@ impl<'ast, 'ctx, 'enclosing> Mapper, FlattenData<'ast>, NameRe ty: RefIdx, ) -> Result>, NameResolutionError> { let var_def = self.get_definition( - ResolveKind::Var, + ResolveKind::Binding, data.ast.symbol(), data.ast.location(), origin, @@ -173,4 +193,92 @@ impl<'ast, 'ctx, 'enclosing> Mapper, FlattenData<'ast>, NameRe }) } } + + fn map_assignment( + &mut self, + data: FlattenData<'ast>, + origin: OriginIdx, + to: RefIdx, + from: RefIdx, + ) -> Result>, NameResolutionError> { + // we should probably do that ONLY if we can't resolve the binding? + // that's very spaghetti... + + self.required_parents + .push(self.ctx.enclosing_scope[origin].0); + + Ok(Node { + data, + origin, + kind: Kind::Assignment { to, from }, + }) + } + + fn map_instantiation( + &mut self, + data: FlattenData<'ast>, + origin: OriginIdx, + _to: RefIdx, + generics: Vec, + fields: Vec, + ) -> Result>, NameResolutionError> { + // FIXME: Can we have _to be resolved already? + + let definition = self.get_definition( + ResolveKind::Type, + data.ast.symbol(), + data.ast.location(), + origin, + )?; + + // do we have to go through all the fields here? should we instead have type fields count as declarations? e.g. encode them as ? + + Ok(Node { + data, + origin, + kind: Kind::Instantiation { + to: RefIdx::Resolved(definition), + generics, + fields, + }, + }) + } +} + +impl<'ast, 'ctx, 'enclosing> Mapper, FlattenData<'ast>, NameResolutionError> + for LatentResolver<'ctx, 'enclosing> +{ + // FIXME: Disgusting + fn map_assignment( + &mut self, + data: FlattenData<'ast>, + origin: OriginIdx, + _: RefIdx, + from: RefIdx, + ) -> Result>, NameResolutionError> { + // FIXME: Do nothing if _to is resolved already + + let instan = self.ctx.enclosing_scope[origin]; + let scope = self.latent_map[&instan.0]; + + let bindings = self.ctx.mappings.bindings.scopes.get(&scope).unwrap(); + + let resolved = bindings.get(data.ast.symbol().expect("interpreter error")); + + match resolved { + Some(field) => Ok(Node { + data, + origin, + kind: Kind::Assignment { + to: RefIdx::Resolved(*field), + from, + }, + }), + None => Err(NameResolutionError::Unresolved( + ResolveKind::Binding, + data.ast.symbol().unwrap().clone(), + data.ast.location().clone(), + )), + } + } } diff --git a/name_resolve/src/scoper.rs b/name_resolve/src/scoper.rs index 07f401e3..20de5391 100644 --- a/name_resolve/src/scoper.rs +++ b/name_resolve/src/scoper.rs @@ -118,6 +118,7 @@ impl<'ast> Traversal, ScoperError> for Scoper { self.maybe_visit_child(fir, value)?; self.maybe_visit_child(fir, ty) } + // FIXME: Is it valid to put a union's variants in its scope? Kind::RecordType { fields: subs, .. } | Kind::UnionType { variants: subs, .. } => { let old = self.enter_scope(node.origin); @@ -144,10 +145,14 @@ impl<'ast> Traversal, ScoperError> for Scoper { .iter() .for_each(|generic| self.maybe_visit_child(fir, generic).unwrap()); + let old = self.enter_scope(node.origin); + fields .iter() .for_each(|field| { self.maybe_visit_child(fir, field) }.unwrap()); + self.enter_scope(old); + Ok(()) } Kind::Call { to, generics, args } => { diff --git a/typecheck/src/lib.rs b/typecheck/src/lib.rs index e1e90d56..1ec87b08 100644 --- a/typecheck/src/lib.rs +++ b/typecheck/src/lib.rs @@ -219,6 +219,7 @@ mod tests { } #[test] + #[ignore = "Reevaluate once #653 is done"] fn assignment_valid() { let ast = ast! { type Marker0; @@ -235,6 +236,7 @@ mod tests { } #[test] + #[ignore = "Reevaluate once #653 is done"] fn assignment_invalid() { let ast = ast! { type Marker0; @@ -248,6 +250,33 @@ mod tests { assert!(fir.is_err()); } + #[test] + fn field_instantiation_valid() { + let ast = ast! { + type Foo; + + type Record(m: Foo); + where x = Record(m: Foo); + }; + let fir = fir!(ast).type_check(); + + assert!(fir.is_ok()); + } + + #[test] + fn field_instantiation_invalid() { + let ast = ast! { + type Foo; + type Bar; + + type Record(m: Foo); + where x = Record(m: Bar); + }; + let fir = fir!(ast).type_check(); + + assert!(fir.is_err()); + } + #[test] fn constant_bool() { let ast = ast! { diff --git a/xrepl/src/lib.rs b/xrepl/src/lib.rs index 5ba3fb37..8145349c 100644 --- a/xrepl/src/lib.rs +++ b/xrepl/src/lib.rs @@ -37,9 +37,8 @@ fn pipeline(ast: &ast::Ast) -> ControlFlow { Instance::Bool(inner) => println!("{inner}"), Instance::Char(inner) => println!("{inner}"), Instance::String(inner) => println!("{inner}"), - Instance::Other { ty, .. } => { - println!("// #{ty}") - } + Instance::Record { ty, .. } => println!("// #{ty}"), + Instance::SlowRecord(map) => println!("// {map:?}"), Instance::Empty => {} } } @@ -51,7 +50,7 @@ fn pipeline(ast: &ast::Ast) -> ControlFlow { pub fn repl() -> Result<(), Error> { let mut rl = rustyline::DefaultEditor::new().unwrap(); // FIXME: How to handle this? - let builtin_types = "type bool; type int; type float; type char; type string;"; + let builtin_types = "type unit; type bool; type int; type float; type char; type string;"; let ast = xparser::parse(builtin_types, Source::Input(builtin_types)).unwrap(); // FIXME: No unwrap