From c58149feea08b1360991e5cf1645272c5ede0247 Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Mon, 6 May 2024 17:28:35 +0200 Subject: [PATCH 01/22] records example --- scopegraphs-regular-expressions/src/emit.rs | 2 +- scopegraphs/Cargo.toml | 1 + scopegraphs/examples/records.rs | 355 ++++++++++++++++++ scopegraphs/src/completeness/critical_edge.rs | 8 +- 4 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 scopegraphs/examples/records.rs diff --git a/scopegraphs-regular-expressions/src/emit.rs b/scopegraphs-regular-expressions/src/emit.rs index 2babfde..39ad978 100644 --- a/scopegraphs-regular-expressions/src/emit.rs +++ b/scopegraphs-regular-expressions/src/emit.rs @@ -29,7 +29,7 @@ impl Automaton { quote!( match token { #( - #matchers => {self.state = #new_states;} + #alphabet::#matchers => {self.state = #new_states;} ),* _ => {self.state = #default_transition;} } diff --git a/scopegraphs/Cargo.toml b/scopegraphs/Cargo.toml index cf66646..447d0e6 100644 --- a/scopegraphs/Cargo.toml +++ b/scopegraphs/Cargo.toml @@ -28,6 +28,7 @@ scopegraphs-regular-expressions = { path = "../scopegraphs-regular-expressions", env_logger = "0.10.1" ctor = "0.2.5" futures = { version = "0.3.30", default-features = false, features = ["alloc", "executor"] } +smol = "2.0.0" [features] default = ["dot", "dynamic-regex"] diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs new file mode 100644 index 0000000..b14ae6a --- /dev/null +++ b/scopegraphs/examples/records.rs @@ -0,0 +1,355 @@ +use futures::future::join; +use scopegraphs::completeness::FutureCompleteness; +use scopegraphs::resolve::Resolve; +use scopegraphs::{query_regex, Scope, ScopeGraph, Storage}; +use scopegraphs_macros::{label_order, Label}; +use smol::LocalExecutor; +use std::cell::RefCell; +use std::collections::HashMap; +use std::env::var; +use std::fmt::{Debug, Formatter}; + +#[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] +enum RecordLabel { + TypeDefinition, + Definition, + Lexical, +} + +#[derive(Debug, Default, Hash, Eq, PartialEq)] +enum RecordData { + VarDecl { + name: String, + ty: PartialType, + }, + TypeDecl { + name: String, + ty: Scope, + }, + + #[default] + Nothing, +} + +enum Constraint { + Equal(PartialType, PartialType), +} + +#[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Copy)] +struct TypeVar(usize); + +#[derive(Clone, Debug, Hash, Eq, PartialEq)] +enum PartialType { + Variable(TypeVar), + Struct { name: String, scope: Scope }, + Number, +} + +impl PartialType { + pub fn unify(&self, other: &Self) -> (PartialType, Vec) { + todo!() + } +} + +#[derive(Clone)] +pub struct UnionFind { + parent: Vec, + vars: usize, +} + +impl Debug for UnionFind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{{")?; + for (idx, p) in self.parent.iter().enumerate() { + write!(f, "{idx} -> {p:?}")?; + if (idx + 1) < self.parent.len() { + write!(f, ", ")?; + } + } + write!(f, "}}") + } +} + +impl UnionFind { + pub fn new() -> Self { + Self { + parent: vec![], + vars: 0, + } + } + + pub fn fresh(&mut self) -> TypeVar { + let old = self.vars; + self.vars += 1; + + TypeVar(old) + } + + pub fn union(&mut self, a: TypeVar, b: PartialType) -> Vec { + let (a_tv, a_ty) = self.find(a); + let (b_tv, b_ty) = self.find_ty(b); + + let (new_parent, new_constraints) = a_ty.unify(&b_ty); + + if let Some(b_tv) = b_tv { + *self.get(a_tv) = new_parent.clone(); + *self.get(b_tv) = new_parent; + } else { + *self.get(a_tv) = new_parent; + } + + new_constraints + } + + pub fn find(&mut self, ty: TypeVar) -> (TypeVar, PartialType) { + let res = self.get(ty); + if let PartialType::Variable(v) = *res { + if v == ty { + return (v, PartialType::Variable(ty)); + } + + let root = self.find(v); + *self.get(v) = root.1.clone(); + root + } else { + (ty, self.parent[ty.0].clone()) + } + } + + pub fn find_ty(&mut self, ty: PartialType) -> (Option, PartialType) { + if let PartialType::Variable(v) = ty { + let (a, b) = self.find(v); + (Some(a), b) + } else { + (None, ty) + } + } + + fn get(&mut self, tv: TypeVar) -> &mut PartialType { + let mut parent = &mut self.parent; + for i in parent.len()..=tv.0 { + parent.push(PartialType::Variable(TypeVar(i))); + } + + &mut parent[tv.0] + } + + pub fn type_of(&mut self, var: TypeVar) -> Option { + todo!() + // Some(match self.find(var).1 { + // + // }) + } +} + +#[derive(Debug)] +enum Type { + StructRef(String), + Int, +} + +#[derive(Debug)] +struct StructDef { + name: String, + fields: HashMap, +} + +#[derive(Debug)] +enum Expr { + StructInit { + name: String, + fields: HashMap, + }, + Add(Box, Box), + Number(u64), + Ident(String), + FieldAccess(Box, String), + Let { + name: String, + value: Box, + in_expr: Box, + }, +} + +#[derive(Debug)] +struct Ast { + items: Vec, + main: Expr, +} + +async fn typecheck_expr<'sg>( + ast: &Expr, + scope: Scope, + sg: &RecordScopegraph<'sg>, + uf: &RefCell, +) -> PartialType { + match ast { + Expr::StructInit { .. } => { + todo!() + } + Expr::Add(l, r) => { + let (l, r) = Box::pin(join( + typecheck_expr(l, scope, sg, uf), + typecheck_expr(r, scope, sg, uf), + )) + .await; + + let lvar = uf.borrow_mut().fresh(); + let rvar = uf.borrow_mut().fresh(); + uf.borrow_mut().union(lvar, l); + uf.borrow_mut().union(lvar, PartialType::Number); + + uf.borrow_mut().union(rvar, r); + uf.borrow_mut().union(rvar, PartialType::Number); + + PartialType::Number + } + Expr::Number(_) => PartialType::Number, + Expr::Ident(varname) => { + let res = sg + .query() + .with_path_wellformedness(query_regex!(RecordLabel: Lexical* Definition)) + .with_label_order(label_order!(RecordLabel: Definition < Lexical)) + .with_data_wellformedness(|record_data: &RecordData| -> bool { + match record_data { + RecordData::VarDecl { name, .. } if name == varname => true, + _ => false, + } + }) + .resolve(scope) + .await; + + let mut r_iter = res.iter(); + let first = r_iter.next().expect("no query results"); + assert!(r_iter.next().is_none(), "multiple results"); + + match first.data() { + RecordData::VarDecl { ty, .. } => ty.clone(), + RecordData::TypeDecl { .. } => panic!("varialbe name refers to type"), + RecordData::Nothing => panic!("?"), + } + } + Expr::FieldAccess(inner, field) => { + let res = Box::pin(typecheck_expr(inner, scope, sg, uf)).await; + match res { + PartialType::Variable(_) => {} + PartialType::Struct { .. } => {} + PartialType::Number => panic!("number has no field {field}"), + } + } + Expr::Let { + name, + value, + in_expr, + } => { + let new_scope = + sg.add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); + sg.add_edge(new_scope, RecordLabel::Lexical, scope) + .expect("already closed"); + sg.close(new_scope, &RecordLabel::Lexical); + + let tv = uf.borrow_mut().fresh(); + sg.add_decl( + new_scope, + RecordLabel::Definition, + RecordData::VarDecl { + name: name.clone(), + ty: PartialType::Variable(tv), + }, + ) + .expect("already closed"); + + sg.close(new_scope, &RecordLabel::Definition); + + Box::pin(typecheck_expr(in_expr, new_scope, sg, uf)).await + } + } +} + +async fn typecheck_structdef<'sg>( + ast: &StructDef, + scope: Scope, + sg: &RecordScopegraph<'sg>, + uf: &RefCell, +) { + let field_scope = sg.add_scope_default(); + let decl_scope = sg.add_scope(RecordData::TypeDecl { + name: ast.name.clone(), + ty: field_scope, + }); + sg.add_edge(decl_scope, RecordLabel::TypeDefinition, scope) + .expect("already closed"); + // NO AWAIT ABOVE THIS +} + +type RecordScopegraph<'sg> = + ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; + +fn typecheck(ast: &Ast) { + let storage = Storage::new(); + let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); + let uf = RefCell::new(UnionFind::new()); + + let global_scope = sg.add_scope_default(); + + { + let fut = async { + let local = LocalExecutor::new(); + + for item in &ast.items { + local + .spawn(typecheck_structdef(item, global_scope, &sg, &uf)) + .detach(); + } + + // We can close for type definitions since the scopes for this are synchronously made + // even before the future is returned and spawned. + sg.close(global_scope, &RecordLabel::TypeDefinition); + + local + .spawn(typecheck_expr(&ast.main, global_scope, &sg, &uf)) + .detach(); + + while !local.is_empty() { + local.tick().await; + } + }; + + // sg.close(global_scope) + + smol::block_on(fut); + } +} + +fn main() { + let example = Ast { + items: vec![StructDef { + name: "A".to_string(), + fields: { + let mut m = HashMap::new(); + m.insert("y".to_string(), Type::Int); + m + }, + }], + main: Expr::Let { + name: "x".to_string(), + value: Box::new(Expr::StructInit { + name: "A".to_string(), + fields: { + let mut m = HashMap::new(); + m.insert( + "y".to_string(), + Expr::Add(Box::new(Expr::Number(4)), Box::new(Expr::Number(5))), + ); + m + }, + }), + in_expr: Box::new(Expr::FieldAccess( + Box::new(Expr::Ident("x".to_string())), + "y".to_string(), + )), + }, + }; + + println!("{:?}", example); +} diff --git a/scopegraphs/src/completeness/critical_edge.rs b/scopegraphs/src/completeness/critical_edge.rs index d313c0e..39e42e0 100644 --- a/scopegraphs/src/completeness/critical_edge.rs +++ b/scopegraphs/src/completeness/critical_edge.rs @@ -73,7 +73,7 @@ where CMPL: CriticalEdgeBasedCompleteness, { /// Adds a new scope with some open edges. - pub fn add_scope_with(&mut self, data: DATA, open_edges: I) -> Scope + pub fn add_scope_with(&self, data: DATA, open_edges: I) -> Scope where I: IntoIterator, { @@ -84,7 +84,7 @@ where } /// Adds a new scope with no open edges. - pub fn add_scope_closed(&mut self, data: DATA) -> Scope { + pub fn add_scope_closed(&self, data: DATA) -> Scope { let scope = self.inner_scope_graph.add_scope(data); self.completeness.init_scope_with(HashSet::new()); scope @@ -97,7 +97,7 @@ where CMPL: CriticalEdgeBasedCompleteness, { /// Adds a new scope with some open edges and default data. - pub fn add_scope_default_with(&mut self, open_edges: I) -> Scope + pub fn add_scope_default_with(&self, open_edges: I) -> Scope where I: IntoIterator, { @@ -105,7 +105,7 @@ where } /// Adds a new scope with no open edges and default data. - pub fn add_scope_default_closed(&mut self) -> Scope { + pub fn add_scope_default_closed(&self) -> Scope { self.add_scope_with(DATA::default(), HashSet::new()) } } From 568ff751ec54dc46cde7d6c7e53e0f1c32ad0966 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Wed, 8 May 2024 14:00:09 +0200 Subject: [PATCH 02/22] Complete except for unification variable delays --- scopegraphs/Cargo.toml | 1 + scopegraphs/examples/records.rs | 332 +++++++++++++++++++++++--------- 2 files changed, 241 insertions(+), 92 deletions(-) diff --git a/scopegraphs/Cargo.toml b/scopegraphs/Cargo.toml index 447d0e6..5f2b517 100644 --- a/scopegraphs/Cargo.toml +++ b/scopegraphs/Cargo.toml @@ -29,6 +29,7 @@ env_logger = "0.10.1" ctor = "0.2.5" futures = { version = "0.3.30", default-features = false, features = ["alloc", "executor"] } smol = "2.0.0" +async-recursion = "1.1.1" [features] default = ["dot", "dynamic-regex"] diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index b14ae6a..a76dc9a 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -1,4 +1,4 @@ -use futures::future::join; +use futures::future::{join, join_all}; use scopegraphs::completeness::FutureCompleteness; use scopegraphs::resolve::Resolve; use scopegraphs::{query_regex, Scope, ScopeGraph, Storage}; @@ -6,7 +6,6 @@ use scopegraphs_macros::{label_order, Label}; use smol::LocalExecutor; use std::cell::RefCell; use std::collections::HashMap; -use std::env::var; use std::fmt::{Debug, Formatter}; #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] @@ -31,12 +30,24 @@ enum RecordData { Nothing, } -enum Constraint { - Equal(PartialType, PartialType), +impl RecordData { + pub fn expect_var_decl(&self) -> &PartialType { + match self { + RecordData::VarDecl { ty, .. } => ty, + _ => panic!("expected var decl, got {:?}", &self), + } + } + + pub fn expect_type_decl(&self) -> &Scope { + match self { + RecordData::TypeDecl { ty, .. } => ty, + _ => panic!("expected type decl, got {:?}", &self), + } + } } #[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Copy)] -struct TypeVar(usize); +pub struct TypeVar(usize); #[derive(Clone, Debug, Hash, Eq, PartialEq)] enum PartialType { @@ -45,12 +56,6 @@ enum PartialType { Number, } -impl PartialType { - pub fn unify(&self, other: &Self) -> (PartialType, Vec) { - todo!() - } -} - #[derive(Clone)] pub struct UnionFind { parent: Vec, @@ -78,55 +83,60 @@ impl UnionFind { } } - pub fn fresh(&mut self) -> TypeVar { + fn fresh(&mut self) -> TypeVar { let old = self.vars; self.vars += 1; TypeVar(old) } - pub fn union(&mut self, a: TypeVar, b: PartialType) -> Vec { - let (a_tv, a_ty) = self.find(a); - let (b_tv, b_ty) = self.find_ty(b); - - let (new_parent, new_constraints) = a_ty.unify(&b_ty); - - if let Some(b_tv) = b_tv { - *self.get(a_tv) = new_parent.clone(); - *self.get(b_tv) = new_parent; - } else { - *self.get(a_tv) = new_parent; + fn unify(&mut self, a: PartialType, b: PartialType) { + let mut worklist = vec![(self.find_ty(a), self.find_ty(b))]; + + // FIXME: worklist is unnecessary, as there are no composite types. + // infrastructure is there for future extension + while let Some((left, right)) = worklist.pop() { + // if left variable + if let PartialType::Variable(v_left) = left { + // arbitrarily choose right as new representative + // FIXME: use rank heuristic in case right is a variable? + *self.get(v_left) = right; + } else if let PartialType::Variable(_) = right { + // left is a variable/number, but right is a variable + worklist.push((right, left)) // will match first case in next iteration + } else { + if left != right { + panic!("Cannot unify {:?} and {:?}", left, right); + } + } } - - new_constraints } - pub fn find(&mut self, ty: TypeVar) -> (TypeVar, PartialType) { + fn find(&mut self, ty: TypeVar) -> PartialType { let res = self.get(ty); if let PartialType::Variable(v) = *res { if v == ty { - return (v, PartialType::Variable(ty)); + return PartialType::Variable(ty); } let root = self.find(v); - *self.get(v) = root.1.clone(); + *self.get(v) = root.clone(); root } else { - (ty, self.parent[ty.0].clone()) + res.clone() } } - pub fn find_ty(&mut self, ty: PartialType) -> (Option, PartialType) { + fn find_ty(&mut self, ty: PartialType) -> PartialType { if let PartialType::Variable(v) = ty { - let (a, b) = self.find(v); - (Some(a), b) + self.find(v) } else { - (None, ty) + ty } } fn get(&mut self, tv: TypeVar) -> &mut PartialType { - let mut parent = &mut self.parent; + let parent = &mut self.parent; for i in parent.len()..=tv.0 { parent.push(PartialType::Variable(TypeVar(i))); } @@ -134,11 +144,12 @@ impl UnionFind { &mut parent[tv.0] } - pub fn type_of(&mut self, var: TypeVar) -> Option { - todo!() - // Some(match self.find(var).1 { - // - // }) + fn type_of(&mut self, var: TypeVar) -> Option { + match self.find(var) { + PartialType::Variable(_) => None, + PartialType::Struct { name, .. } => Some(Type::StructRef(name)), + PartialType::Number => Some(Type::Int), + } } } @@ -177,6 +188,7 @@ struct Ast { main: Expr, } +#[async_recursion::async_recursion(?Send)] async fn typecheck_expr<'sg>( ast: &Expr, scope: Scope, @@ -184,8 +196,25 @@ async fn typecheck_expr<'sg>( uf: &RefCell, ) -> PartialType { match ast { - Expr::StructInit { .. } => { - todo!() + Expr::StructInit { name, fields } => { + let struct_scope = resolve_struct_ref(sg, scope, name).await; + let fld_futures = fields.iter().map(|(fld_name, fld_init)| async { + let (decl_type, init_type) = join( + resolve_member_ref(sg, struct_scope, fld_name), + typecheck_expr(fld_init, scope, sg, uf), + ) + .await; + + uf.borrow_mut().unify(decl_type, init_type) + }); + + // FIXME: field init exhaustiveness check omitted + join_all(fld_futures).await; + // FIXME: can we make it 'return' the type before all field initializations are checked? + PartialType::Struct { + name: name.clone(), + scope: struct_scope, + } } Expr::Add(l, r) => { let (l, r) = Box::pin(join( @@ -194,46 +223,37 @@ async fn typecheck_expr<'sg>( )) .await; - let lvar = uf.borrow_mut().fresh(); - let rvar = uf.borrow_mut().fresh(); - uf.borrow_mut().union(lvar, l); - uf.borrow_mut().union(lvar, PartialType::Number); - - uf.borrow_mut().union(rvar, r); - uf.borrow_mut().union(rvar, PartialType::Number); + // assert equalities + let mut _uf = uf.borrow_mut(); + _uf.unify(l, PartialType::Number); + _uf.unify(r, PartialType::Number); PartialType::Number } Expr::Number(_) => PartialType::Number, - Expr::Ident(varname) => { - let res = sg - .query() - .with_path_wellformedness(query_regex!(RecordLabel: Lexical* Definition)) - .with_label_order(label_order!(RecordLabel: Definition < Lexical)) - .with_data_wellformedness(|record_data: &RecordData| -> bool { - match record_data { - RecordData::VarDecl { name, .. } if name == varname => true, - _ => false, - } - }) - .resolve(scope) - .await; - - let mut r_iter = res.iter(); - let first = r_iter.next().expect("no query results"); - assert!(r_iter.next().is_none(), "multiple results"); - - match first.data() { - RecordData::VarDecl { ty, .. } => ty.clone(), - RecordData::TypeDecl { .. } => panic!("varialbe name refers to type"), - RecordData::Nothing => panic!("?"), - } - } + Expr::Ident(var_name) => resolve_lexical_ref(sg, scope, var_name).await, Expr::FieldAccess(inner, field) => { let res = Box::pin(typecheck_expr(inner, scope, sg, uf)).await; - match res { - PartialType::Variable(_) => {} - PartialType::Struct { .. } => {} + let inner_expr_type = uf.borrow_mut().find_ty(res); + match inner_expr_type { + PartialType::Variable(_) => todo!("no delay mechanism yet"), + PartialType::Struct { name, scope } => { + let env = sg + .query() + .with_path_wellformedness(query_regex!(RecordLabel: Lexical* Definition)) + .with_data_wellformedness(|decl: &RecordData| match decl { + RecordData::VarDecl { name: var_name, .. } => &name == var_name, + _ => false, + }) + .with_label_order(label_order!(RecordLabel: Definition < Lexical)) + .resolve(scope) + .await; + env.get_only_item() + .expect("variable did not resolve uniquely") + .data() + .expect_var_decl() + .clone() + } PartialType::Number => panic!("number has no field {field}"), } } @@ -248,38 +268,94 @@ async fn typecheck_expr<'sg>( .expect("already closed"); sg.close(new_scope, &RecordLabel::Lexical); - let tv = uf.borrow_mut().fresh(); + let ty_var = PartialType::Variable(uf.borrow_mut().fresh()); sg.add_decl( new_scope, RecordLabel::Definition, RecordData::VarDecl { name: name.clone(), - ty: PartialType::Variable(tv), + ty: ty_var.clone(), }, ) .expect("already closed"); sg.close(new_scope, &RecordLabel::Definition); - Box::pin(typecheck_expr(in_expr, new_scope, sg, uf)).await + // compute type of the variable + let ty_var_future = async { + let ty = typecheck_expr(value, scope, sg, uf).await; + uf.borrow_mut().unify(ty_var, ty); + }; + + // compute type of the result expression + let ty_res_future = typecheck_expr(in_expr, new_scope, sg, uf); + + // run both computations concurrently + // + // this construct is set up in this way to ensure + // the `unify(tv, ty_var)` can be executed before + // the result type is computed. + // this prevents deadlocks when the result type + // is dependent on the value type, for example + // ``` + // record A { x: int } + // let r = A { x = 42 } in r.x + // ``` + let (_, ty_res) = Box::pin(join(ty_var_future, ty_res_future)).await; + + // return + ty_res } } } -async fn typecheck_structdef<'sg>( - ast: &StructDef, - scope: Scope, - sg: &RecordScopegraph<'sg>, - uf: &RefCell, -) { - let field_scope = sg.add_scope_default(); +fn init_structdef<'sg>(struct_def: &StructDef, scope: Scope, sg: &RecordScopegraph<'sg>) -> Scope { + let field_scope = sg.add_scope_default_with([RecordLabel::Definition]); + // FIXME: use Decl let decl_scope = sg.add_scope(RecordData::TypeDecl { - name: ast.name.clone(), + name: struct_def.name.clone(), ty: field_scope, }); - sg.add_edge(decl_scope, RecordLabel::TypeDefinition, scope) + sg.add_edge(scope, RecordLabel::TypeDefinition, decl_scope) .expect("already closed"); + + field_scope +} + +async fn typecheck_structdef<'sg>( + struct_def: &StructDef, + scope: Scope, + field_scope: Scope, + sg: &RecordScopegraph<'sg>, +) { // NO AWAIT ABOVE THIS + let fld_decl_futures = struct_def + .fields + .iter() + .map(|(fld_name, fld_ty)| async move { + let ty = match fld_ty { + Type::StructRef(n) => { + let struct_scope = resolve_struct_ref(sg, scope, n).await; + PartialType::Struct { + name: n.clone(), + scope: struct_scope, + } + } + Type::Int => PartialType::Number, + }; + + sg.add_decl( + field_scope, + RecordLabel::Definition, + RecordData::VarDecl { + name: fld_name.clone(), + ty, + }, + ) + .expect("unexpected close"); + }); + + join_all(fld_decl_futures).await; } type RecordScopegraph<'sg> = @@ -290,15 +366,17 @@ fn typecheck(ast: &Ast) { let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); let uf = RefCell::new(UnionFind::new()); - let global_scope = sg.add_scope_default(); + let global_scope = sg.add_scope_default_with([RecordLabel::TypeDefinition]); { let fut = async { let local = LocalExecutor::new(); for item in &ast.items { + // synchronously init record decl + let field_scope = init_structdef(item, global_scope, &sg); local - .spawn(typecheck_structdef(item, global_scope, &sg, &uf)) + .spawn(typecheck_structdef(item, global_scope, field_scope, &sg)) .detach(); } @@ -315,12 +393,82 @@ fn typecheck(ast: &Ast) { } }; - // sg.close(global_scope) + let type_checker_result = smol::block_on(fut); + type_checker_result - smol::block_on(fut); + // FIXME: return type of `main` } } +async fn resolve_struct_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &String) -> Scope { + let env = sg + .query() + .with_path_wellformedness(query_regex!(RecordLabel: Lexical* TypeDefinition)) + .with_data_wellformedness(|record_data: &RecordData| match record_data { + RecordData::TypeDecl { + name: decl_name, .. + } => decl_name == ref_name, + _ => false, + }) + .with_label_order(label_order!(RecordLabel: Definition < Lexical)) + .resolve(scope) + .await; + + *env.get_only_item() + .expect("record name did not resolve properly") + .data() + .expect_type_decl() +} + +async fn resolve_lexical_ref( + sg: &RecordScopegraph<'_>, + scope: Scope, + var_name: &String, +) -> PartialType { + let env = sg + .query() + .with_path_wellformedness(query_regex!(RecordLabel: Lexical* Definition)) + .with_label_order(label_order!(RecordLabel: Definition < Lexical)) + .with_data_wellformedness(|record_data: &RecordData| -> bool { + match record_data { + RecordData::VarDecl { name, .. } if name == var_name => true, + _ => false, + } + }) + .resolve(scope) + .await; + + env.get_only_item() + .expect("variable did not resolve uniquely") + .data() + .expect_var_decl() + .clone() +} + +async fn resolve_member_ref( + sg: &RecordScopegraph<'_>, + struct_scope: Scope, + ref_name: &String, +) -> PartialType { + let env = sg + .query() + .with_path_wellformedness(query_regex!(RecordLabel: Definition)) + .with_data_wellformedness(|record_data: &RecordData| match record_data { + RecordData::VarDecl { + name: decl_name, .. + } => decl_name == ref_name, + _ => false, + }) + .resolve(struct_scope) + .await; + + env.get_only_item() + .expect("field name did not resolve properly") + .data() + .expect_var_decl() + .clone() +} + fn main() { let example = Ast { items: vec![StructDef { @@ -351,5 +499,5 @@ fn main() { }, }; - println!("{:?}", example); + println!("{:?}", typecheck(&example)); } From b6539cf8cd76ebefa61ffb2c3364878f06f15db6 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Wed, 8 May 2024 15:14:01 +0200 Subject: [PATCH 03/22] Setup for refinement observation (blocking) --- scopegraphs/examples/records.rs | 29 ++++++++++++++++++++++++++--- scopegraphs/src/lib.rs | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index a76dc9a..06ca763 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -3,10 +3,13 @@ use scopegraphs::completeness::FutureCompleteness; use scopegraphs::resolve::Resolve; use scopegraphs::{query_regex, Scope, ScopeGraph, Storage}; use scopegraphs_macros::{label_order, Label}; +use scopegraphs::completable_future::{CompletableFuture, CompletableFutureSignal}; use smol::LocalExecutor; use std::cell::RefCell; use std::collections::HashMap; +use std::convert::Infallible; use std::fmt::{Debug, Formatter}; +use std::vec; #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] enum RecordLabel { @@ -56,10 +59,10 @@ enum PartialType { Number, } -#[derive(Clone)] pub struct UnionFind { parent: Vec, vars: usize, + callbacks: Vec>>, } impl Debug for UnionFind { @@ -80,6 +83,7 @@ impl UnionFind { Self { parent: vec![], vars: 0, + callbacks: vec![], } } @@ -100,7 +104,10 @@ impl UnionFind { if let PartialType::Variable(v_left) = left { // arbitrarily choose right as new representative // FIXME: use rank heuristic in case right is a variable? - *self.get(v_left) = right; + *self.get(v_left) = right.clone(); + for mut fut in std::mem::replace(&mut self.callbacks[v_left.0], vec![]) { + // fut.complete(right.clone()); + } } else if let PartialType::Variable(_) = right { // left is a variable/number, but right is a variable worklist.push((right, left)) // will match first case in next iteration @@ -151,6 +158,17 @@ impl UnionFind { PartialType::Number => Some(Type::Int), } } + + fn callback(&mut self, tv: TypeVar) -> impl std::future::Future { + let future = CompletableFuture::::new(); + let callbacks = &mut self.callbacks; + for i in callbacks.len()..=tv.0 { + callbacks.push(vec![]); + } + + callbacks[tv.0].push(future.signal()); + future + } } #[derive(Debug)] @@ -236,7 +254,12 @@ async fn typecheck_expr<'sg>( let res = Box::pin(typecheck_expr(inner, scope, sg, uf)).await; let inner_expr_type = uf.borrow_mut().find_ty(res); match inner_expr_type { - PartialType::Variable(_) => todo!("no delay mechanism yet"), + PartialType::Variable(tv) => { + println!("awaiting refinement of {:?}", tv); + let refined_type = uf.borrow_mut().callback(tv).await; + println!("refined type: {:?}", refined_type); + todo!() + }, PartialType::Struct { name, scope } => { let env = sg .query() diff --git a/scopegraphs/src/lib.rs b/scopegraphs/src/lib.rs index 6855240..02e3daa 100644 --- a/scopegraphs/src/lib.rs +++ b/scopegraphs/src/lib.rs @@ -30,6 +30,7 @@ pub use scopegraphs_regular_expressions::*; pub mod completeness; pub mod containers; mod future_wrapper; +pub mod completable_future; pub mod resolve; From 59c8d4460f1b7de9034a94f28c11dfc680366435 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Wed, 8 May 2024 15:24:59 +0200 Subject: [PATCH 04/22] Type-check returns type of main --- scopegraphs/examples/records.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 06ca763..d4b1971 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -9,6 +9,7 @@ use std::cell::RefCell; use std::collections::HashMap; use std::convert::Infallible; use std::fmt::{Debug, Formatter}; +use std::future::IntoFuture; use std::vec; #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] @@ -384,7 +385,7 @@ async fn typecheck_structdef<'sg>( type RecordScopegraph<'sg> = ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; -fn typecheck(ast: &Ast) { +fn typecheck(ast: &Ast) -> PartialType { let storage = Storage::new(); let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); let uf = RefCell::new(UnionFind::new()); @@ -407,13 +408,15 @@ fn typecheck(ast: &Ast) { // even before the future is returned and spawned. sg.close(global_scope, &RecordLabel::TypeDefinition); - local - .spawn(typecheck_expr(&ast.main, global_scope, &sg, &uf)) - .detach(); + let main_task = local + .spawn(typecheck_expr(&ast.main, global_scope, &sg, &uf)); while !local.is_empty() { local.tick().await; } + + // extract result from task + main_task.into_future().await }; let type_checker_result = smol::block_on(fut); From 2dc2ddb2c024a03f814e1593c7fd5d413d694248 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Wed, 8 May 2024 15:25:20 +0200 Subject: [PATCH 05/22] missing lib --- scopegraphs/src/completable_future.rs | 206 ++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 scopegraphs/src/completable_future.rs diff --git a/scopegraphs/src/completable_future.rs b/scopegraphs/src/completable_future.rs new file mode 100644 index 0000000..fcd1eec --- /dev/null +++ b/scopegraphs/src/completable_future.rs @@ -0,0 +1,206 @@ +//! Copied and adapted from https://crates.io/crates/completable_future (due to dependency mismatch) +//! +//! # Completable Future +//! +//! Similar to Java's CompletableFuture, this crate provides a simple +//! future that can be completed and properly notified from elsewhere other +//! than the executor of the future. It is sutable for some blocking +//! tasks that could block the executor if we use a future directly in +//! an executor. +//! +//! A CompletableFuture is still a future and has all the combinators that +//! you can use to chain logic working on the result or the error. Also, +//! unlike Java and inherited from Rust's poll model future, some executor +//! needs to execute the CompletableFuture in order to get the result; the +//! thread or code that completes (or errors) the future will not execute +//! the logic chained after the future. +//! +//! The CompletableFuture uses Arc and Mutex to synchronize poll and completion, +//! so there's overhead for using it. +//! +//! # Example +//! ``` +//! extern crate futures; +//! extern crate completable_future; +//! +//! use futures::prelude::*; +//! use futures::executor::block_on; +//! use std::thread::spawn; +//! use std::thread::sleep; +//! use std::time::Duration; +//! use completable_future::CompletableFuture; +//! +//! fn main() { +//! let fut1 = CompletableFuture::::new(); +//! // we will give the signal to some worker for it to complete +//! let mut signal = fut1.signal(); +//! let fut2 = fut1.and_then(|s| { +//! // this will come from whoever completes the future +//! println!("in fut2: {}", s); +//! Ok("this comes from fut2".to_string()) +//! }); +//! +//! let j = spawn(move || { +//! println!("waiter thread: I'm going to block on fut2"); +//! let ret = block_on(fut2).unwrap(); +//! println!("waiter thread: fut2 completed with message -- {}", ret); +//! }); +//! +//! spawn(move || { +//! println!("worker thread: going to block for 1000 ms"); +//! sleep(Duration::from_millis(1000)); +//! signal.complete("this comes from fut1".to_string()); +//! println!("worker thread: completed fut1"); +//! }); +//! +//! j.join().unwrap(); +//! } +//! ``` + +use futures::future::Future; +use futures::task::{Context, Waker, AtomicWaker}; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::mem; +use std::task::Poll; + +enum WakerWrapper { + Registered(AtomicWaker), + NotRegistered, +} + +impl WakerWrapper { + fn register(&mut self, waker: &Waker) { + match self { + &mut WakerWrapper::Registered(ref _dont_care) => (), + &mut WakerWrapper::NotRegistered => { + let w = AtomicWaker::new(); + w.register(waker); + *self = WakerWrapper::Registered(w) + }, + } + } + + fn wake(&self) { + match self { + &WakerWrapper::Registered(ref w) => w.wake(), + &WakerWrapper::NotRegistered => (), + }; + } +} + +enum FutureState { + Pending, + Completed(V), + Taken, +} + +impl FutureState { + fn swap(&mut self, new_val: FutureState) -> FutureState { + mem::replace(self, new_val) + } + + fn unwrap_val(&mut self) -> V { + match self.swap(FutureState::Taken) { + FutureState::Completed(val) => val, + _ => panic!("cannot unwrap because my state is not completed"), + } + } +} + +/// the state of the future; reference counted +struct SignalInternal { + waker: WakerWrapper, + state: FutureState, +} + +/// A handle to the future state. When you create a completable future, +/// you should also create a signal that somebody can use to complete +/// the future. +#[derive(Clone)] +pub struct CompletableFutureSignal { + internal: Arc>>, +} + +impl CompletableFutureSignal { + fn mutate_self(&mut self, new_state: FutureState) -> bool { + let mut internal = self.internal.lock().unwrap(); + match internal.state { + FutureState::Pending => { + internal.state.swap(new_state); + internal.waker.wake(); + true + }, + _ => false, + } + } + + /// Complete the associated CompletableFuture. This method + /// can be called safely across multiple threads multiple times, + /// but only the winning call would mutate the future; other calls + /// will be rendered noop. + /// + /// Returns whether the call successfully mutates the future. + pub fn complete(&mut self, value: V) -> bool { + self.mutate_self(FutureState::Completed(value)) + } +} + +/// A CompletableFuture is a future that you can expect a result (or error) +/// from and chain logic on. You will need some executor to actively poll +/// the result. Executors provided by the futures crate are usually good +/// enough for common situations. +/// +/// If you use a custom executor, be careful that don't poll the CompletableFuture +/// after it has already completed (or errored) in previous polls. Doing so +/// will panic your executor. +pub struct CompletableFuture { + internal: Arc>>, +} + +impl CompletableFuture { + /// Construct a CompletableFuture. + pub fn new() -> CompletableFuture { + CompletableFuture { + internal: Arc::new(Mutex::new(SignalInternal{ + waker: WakerWrapper::NotRegistered, + state: FutureState::Pending, + })) + } + } + + /// Construct a CompletableFuture that's already completed + /// with the value provided. + pub fn completed(val: V) -> CompletableFuture { + CompletableFuture { + internal: Arc::new(Mutex::new(SignalInternal{ + waker: WakerWrapper::NotRegistered, + state: FutureState::Completed(val), + })) + } + } + + /// Get a CompletableFutureSignal that can be used to complete + /// or error this CompletableFuture. + pub fn signal(&self) -> CompletableFutureSignal { + CompletableFutureSignal { + internal: self.internal.clone(), + } + } +} + +impl Future for CompletableFuture { + type Output = V; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { + let mut signal = self.internal.lock().unwrap(); + signal.waker.register(ctx.waker()); + + let state = &mut signal.state; + match state { + FutureState::Pending => Poll::Pending, + FutureState::Taken => panic!("bug: the value has been taken, yet I'm still polled again"), + FutureState::Completed(_) => Poll::Ready(state.unwrap_val()), + } + } +} \ No newline at end of file From b3bf51c97330989603e3ce6780241d26996f356a Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Wed, 8 May 2024 16:14:31 +0200 Subject: [PATCH 06/22] outline principle, lifetime errors --- scopegraphs/examples/records.rs | 416 ++++++++++++++++---------------- 1 file changed, 210 insertions(+), 206 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index d4b1971..931b084 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -7,7 +7,6 @@ use scopegraphs::completable_future::{CompletableFuture, CompletableFutureSignal use smol::LocalExecutor; use std::cell::RefCell; use std::collections::HashMap; -use std::convert::Infallible; use std::fmt::{Debug, Formatter}; use std::future::IntoFuture; use std::vec; @@ -105,9 +104,11 @@ impl UnionFind { if let PartialType::Variable(v_left) = left { // arbitrarily choose right as new representative // FIXME: use rank heuristic in case right is a variable? + println!("unify: {:?} == {:?}", left, right); *self.get(v_left) = right.clone(); for mut fut in std::mem::replace(&mut self.callbacks[v_left.0], vec![]) { - // fut.complete(right.clone()); + println!("complete: {:?} == {:?}", left, right); + fut.complete(right.clone()); } } else if let PartialType::Variable(_) = right { // left is a variable/number, but right is a variable @@ -163,7 +164,7 @@ impl UnionFind { fn callback(&mut self, tv: TypeVar) -> impl std::future::Future { let future = CompletableFuture::::new(); let callbacks = &mut self.callbacks; - for i in callbacks.len()..=tv.0 { + for _i in callbacks.len()..=tv.0 { callbacks.push(vec![]); } @@ -207,225 +208,185 @@ struct Ast { main: Expr, } -#[async_recursion::async_recursion(?Send)] -async fn typecheck_expr<'sg>( - ast: &Expr, - scope: Scope, - sg: &RecordScopegraph<'sg>, - uf: &RefCell, -) -> PartialType { - match ast { - Expr::StructInit { name, fields } => { - let struct_scope = resolve_struct_ref(sg, scope, name).await; - let fld_futures = fields.iter().map(|(fld_name, fld_init)| async { - let (decl_type, init_type) = join( - resolve_member_ref(sg, struct_scope, fld_name), - typecheck_expr(fld_init, scope, sg, uf), - ) - .await; - uf.borrow_mut().unify(decl_type, init_type) - }); +type RecordScopegraph<'sg> = +ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; + +struct TypeChecker<'sg> { + sg: RecordScopegraph<'sg>, + uf: RefCell, + ex: LocalExecutor<'sg>, +} + +impl<'a, 'sg> TypeChecker<'a> { + + fn run_detached(&self, fut: impl std::future::Future + 'a) { + self.ex.spawn(fut).detach() + } - // FIXME: field init exhaustiveness check omitted - join_all(fld_futures).await; - // FIXME: can we make it 'return' the type before all field initializations are checked? - PartialType::Struct { - name: name.clone(), - scope: struct_scope, + #[async_recursion::async_recursion(?Send)] + async fn typecheck_expr( + &self, + ast: &Expr, + scope: Scope, + ) -> PartialType { + match ast { + Expr::StructInit { name, fields } => { + let struct_scope = resolve_struct_ref(&self.sg, scope, name).await; + let fld_futures = fields.iter().map(|(fld_name, fld_init)| async move { + let (decl_type, init_type) = join( + resolve_member_ref(&self.sg, struct_scope, fld_name), + self.typecheck_expr(fld_init, scope), + ) + .await; + + self.uf.borrow_mut().unify(decl_type, init_type) + }); + + // FIXME: field init exhaustiveness check omitted + + // asynchronously check field initializations + for fut in fld_futures { + self.ex.spawn(fut).detach() + } + // .. but eagerly return the struct type + PartialType::Struct { + name: name.clone(), + scope: struct_scope, + } } - } - Expr::Add(l, r) => { - let (l, r) = Box::pin(join( - typecheck_expr(l, scope, sg, uf), - typecheck_expr(r, scope, sg, uf), - )) - .await; - - // assert equalities - let mut _uf = uf.borrow_mut(); - _uf.unify(l, PartialType::Number); - _uf.unify(r, PartialType::Number); - - PartialType::Number - } - Expr::Number(_) => PartialType::Number, - Expr::Ident(var_name) => resolve_lexical_ref(sg, scope, var_name).await, - Expr::FieldAccess(inner, field) => { - let res = Box::pin(typecheck_expr(inner, scope, sg, uf)).await; - let inner_expr_type = uf.borrow_mut().find_ty(res); - match inner_expr_type { - PartialType::Variable(tv) => { - println!("awaiting refinement of {:?}", tv); - let refined_type = uf.borrow_mut().callback(tv).await; - println!("refined type: {:?}", refined_type); - todo!() - }, - PartialType::Struct { name, scope } => { - let env = sg - .query() - .with_path_wellformedness(query_regex!(RecordLabel: Lexical* Definition)) - .with_data_wellformedness(|decl: &RecordData| match decl { - RecordData::VarDecl { name: var_name, .. } => &name == var_name, - _ => false, - }) - .with_label_order(label_order!(RecordLabel: Definition < Lexical)) - .resolve(scope) - .await; - env.get_only_item() - .expect("variable did not resolve uniquely") - .data() - .expect_var_decl() - .clone() + Expr::Add(l, r) => { + // type check left-hand-side asynchronously + self.run_detached(async { + let l_ty = self.typecheck_expr(l, scope).await; + self.uf.borrow_mut().unify(l_ty, PartialType::Number) + }); + // and type-check the right-hand-side asynchronously + self.run_detached(async { + let r_ty = self.typecheck_expr(r, scope).await; + self.uf.borrow_mut().unify(r_ty, PartialType::Number) + }); + + // ... but immediately return the current type + PartialType::Number + } + Expr::Number(_) => PartialType::Number, + Expr::Ident(var_name) => resolve_lexical_ref(&self.sg, scope, var_name).await, + Expr::FieldAccess(inner, field) => { + let res = Box::pin(self.typecheck_expr(inner, scope)).await; + let inner_expr_type = self.uf.borrow_mut().find_ty(res); + match inner_expr_type { + PartialType::Variable(tv) => { + println!("awaiting refinement of {:?}", tv); + let refined_type = self.uf.borrow_mut().callback(tv).await; + println!("refined type: {:?}", refined_type); + todo!("retry type checking with refined type") + }, + PartialType::Struct { scope, .. } => resolve_member_ref(&self.sg, scope, field).await, + PartialType::Number => panic!("number has no field {field}"), } - PartialType::Number => panic!("number has no field {field}"), } - } - Expr::Let { - name, - value, - in_expr, - } => { - let new_scope = - sg.add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); - sg.add_edge(new_scope, RecordLabel::Lexical, scope) + Expr::Let { + name, + value, + in_expr, + } => { + let new_scope = + self.sg.add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); + self.sg.add_edge(new_scope, RecordLabel::Lexical, scope) + .expect("already closed"); + self.sg.close(new_scope, &RecordLabel::Lexical); + + let ty_var = PartialType::Variable(self.uf.borrow_mut().fresh()); + self.sg.add_decl( + new_scope, + RecordLabel::Definition, + RecordData::VarDecl { + name: name.clone(), + ty: ty_var.clone(), + }, + ) .expect("already closed"); - sg.close(new_scope, &RecordLabel::Lexical); - let ty_var = PartialType::Variable(uf.borrow_mut().fresh()); - sg.add_decl( - new_scope, - RecordLabel::Definition, - RecordData::VarDecl { - name: name.clone(), - ty: ty_var.clone(), - }, - ) - .expect("already closed"); - - sg.close(new_scope, &RecordLabel::Definition); - - // compute type of the variable - let ty_var_future = async { - let ty = typecheck_expr(value, scope, sg, uf).await; - uf.borrow_mut().unify(ty_var, ty); - }; - - // compute type of the result expression - let ty_res_future = typecheck_expr(in_expr, new_scope, sg, uf); - - // run both computations concurrently - // - // this construct is set up in this way to ensure - // the `unify(tv, ty_var)` can be executed before - // the result type is computed. - // this prevents deadlocks when the result type - // is dependent on the value type, for example - // ``` - // record A { x: int } - // let r = A { x = 42 } in r.x - // ``` - let (_, ty_res) = Box::pin(join(ty_var_future, ty_res_future)).await; - - // return - ty_res + self.sg.close(new_scope, &RecordLabel::Definition); + + // compute type of the variable + let ty_var_future = async move { + let ty = self.typecheck_expr(value, scope).await; + self.uf.borrow_mut().unify(ty_var, ty); + }; + + // compute type of the result expression + let ty_res_future = self.typecheck_expr(in_expr, new_scope); + + // run both computations concurrently + // + // this construct is set up in this way to ensure + // the `unify(tv, ty_var)` can be executed before + // the result type is computed. + // this prevents deadlocks when the result type + // is dependent on the value type, for example + // ``` + // record A { x: int } + // let r = A { x = 42 } in r.x + // ``` + self.run_detached(ty_var_future); + + // return + ty_res_future.await + } } } -} - -fn init_structdef<'sg>(struct_def: &StructDef, scope: Scope, sg: &RecordScopegraph<'sg>) -> Scope { - let field_scope = sg.add_scope_default_with([RecordLabel::Definition]); - // FIXME: use Decl - let decl_scope = sg.add_scope(RecordData::TypeDecl { - name: struct_def.name.clone(), - ty: field_scope, - }); - sg.add_edge(scope, RecordLabel::TypeDefinition, decl_scope) - .expect("already closed"); - - field_scope -} -async fn typecheck_structdef<'sg>( - struct_def: &StructDef, - scope: Scope, - field_scope: Scope, - sg: &RecordScopegraph<'sg>, -) { - // NO AWAIT ABOVE THIS - let fld_decl_futures = struct_def - .fields - .iter() - .map(|(fld_name, fld_ty)| async move { - let ty = match fld_ty { - Type::StructRef(n) => { - let struct_scope = resolve_struct_ref(sg, scope, n).await; - PartialType::Struct { - name: n.clone(), - scope: struct_scope, - } - } - Type::Int => PartialType::Number, - }; - - sg.add_decl( - field_scope, - RecordLabel::Definition, - RecordData::VarDecl { - name: fld_name.clone(), - ty, - }, - ) - .expect("unexpected close"); + fn init_structdef(&self, struct_def: &StructDef, scope: Scope) -> Scope { + let field_scope = self.sg.add_scope_default_with([RecordLabel::Definition]); + // FIXME: use Decl + let decl_scope = self.sg.add_scope(RecordData::TypeDecl { + name: struct_def.name.clone(), + ty: field_scope, }); + self.sg.add_edge(scope, RecordLabel::TypeDefinition, decl_scope) + .expect("already closed"); - join_all(fld_decl_futures).await; -} - -type RecordScopegraph<'sg> = - ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; - -fn typecheck(ast: &Ast) -> PartialType { - let storage = Storage::new(); - let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); - let uf = RefCell::new(UnionFind::new()); - - let global_scope = sg.add_scope_default_with([RecordLabel::TypeDefinition]); - - { - let fut = async { - let local = LocalExecutor::new(); - - for item in &ast.items { - // synchronously init record decl - let field_scope = init_structdef(item, global_scope, &sg); - local - .spawn(typecheck_structdef(item, global_scope, field_scope, &sg)) - .detach(); - } - - // We can close for type definitions since the scopes for this are synchronously made - // even before the future is returned and spawned. - sg.close(global_scope, &RecordLabel::TypeDefinition); - - let main_task = local - .spawn(typecheck_expr(&ast.main, global_scope, &sg, &uf)); - - while !local.is_empty() { - local.tick().await; - } - - // extract result from task - main_task.into_future().await - }; + field_scope + } - let type_checker_result = smol::block_on(fut); - type_checker_result + async fn typecheck_structdef( + &self, + struct_def: &StructDef, + scope: Scope, + field_scope: Scope, + ) { + let fld_decl_futures = struct_def + .fields + .iter() + .map(|(fld_name, fld_ty)| async move { + let ty = match fld_ty { + Type::StructRef(n) => { + let struct_scope = resolve_struct_ref(&self.sg, scope, n).await; + PartialType::Struct { + name: n.clone(), + scope: struct_scope, + } + } + Type::Int => PartialType::Number, + }; + + self.sg.add_decl( + field_scope, + RecordLabel::Definition, + RecordData::VarDecl { + name: fld_name.clone(), + ty, + }, + ) + .expect("unexpected close"); + }); - // FIXME: return type of `main` + join_all(fld_decl_futures).await; } -} +} async fn resolve_struct_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &String) -> Scope { let env = sg .query() @@ -495,6 +456,49 @@ async fn resolve_member_ref( .clone() } + +fn typecheck(ast: &Ast) -> PartialType { + let storage = Storage::new(); + let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); + let uf = RefCell::new(UnionFind::new()); + let local = LocalExecutor::new(); + + let tc = TypeChecker { + sg: sg, + uf: uf, + ex: local, + }; + + let fut = async { + let global_scope = tc.sg.add_scope_default_with([RecordLabel::TypeDefinition]); + + for item in &ast.items { + // synchronously init record decl + let field_scope = tc.init_structdef(item, global_scope); + tc.ex + .spawn(tc.typecheck_structdef(item, global_scope, field_scope)) + .detach(); + } + + // We can close for type definitions since the scopes for this are synchronously made + // even before the future is returned and spawned. + tc.sg.close(global_scope, &RecordLabel::TypeDefinition); + + let main_task = tc.ex + .spawn(tc.typecheck_expr(&ast.main, global_scope)); + + while !tc.ex.is_empty() { + tc.ex.tick().await; + } + + // extract result from task + main_task.into_future().await + }; + + let type_checker_result = smol::block_on(fut); + type_checker_result +} + fn main() { let example = Ast { items: vec![StructDef { From f3de2715e77256c83c3874feb56987e9ede931fc Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Wed, 8 May 2024 16:15:51 +0200 Subject: [PATCH 07/22] comment --- scopegraphs/examples/records.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 931b084..6898cd7 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -215,7 +215,7 @@ ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; struct TypeChecker<'sg> { sg: RecordScopegraph<'sg>, uf: RefCell, - ex: LocalExecutor<'sg>, + ex: LocalExecutor<'sg>, // make executor part of type checker to allow run-and-forget semantics of type checking subtasks (see run_detached() usage sites) } impl<'a, 'sg> TypeChecker<'a> { From b7a06163d5bf0f9b937128e33b8265b727880d63 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Tue, 14 May 2024 16:34:11 +0200 Subject: [PATCH 08/22] Progress records example: - Fix lifetime issues (with RC hack) - Fix borrow_mut issues (with explicit drop) - Explicitly print main type --- scopegraphs/examples/records.rs | 217 +++++++++++++++++--------- scopegraphs/src/completable_future.rs | 40 ++--- scopegraphs/src/lib.rs | 2 +- 3 files changed, 161 insertions(+), 98 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 6898cd7..eff5f89 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -1,14 +1,16 @@ use futures::future::{join, join_all}; +use scopegraphs::completable_future::{CompletableFuture, CompletableFutureSignal}; use scopegraphs::completeness::FutureCompleteness; use scopegraphs::resolve::Resolve; use scopegraphs::{query_regex, Scope, ScopeGraph, Storage}; use scopegraphs_macros::{label_order, Label}; -use scopegraphs::completable_future::{CompletableFuture, CompletableFutureSignal}; use smol::LocalExecutor; +use std::borrow::Borrow; use std::cell::RefCell; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; use std::future::IntoFuture; +use std::rc::Rc; use std::vec; #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] @@ -208,9 +210,8 @@ struct Ast { main: Expr, } - type RecordScopegraph<'sg> = -ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; + ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; struct TypeChecker<'sg> { sg: RecordScopegraph<'sg>, @@ -218,36 +219,41 @@ struct TypeChecker<'sg> { ex: LocalExecutor<'sg>, // make executor part of type checker to allow run-and-forget semantics of type checking subtasks (see run_detached() usage sites) } -impl<'a, 'sg> TypeChecker<'a> { - +impl<'a, 'sg> TypeChecker<'a> { fn run_detached(&self, fut: impl std::future::Future + 'a) { self.ex.spawn(fut).detach() } #[async_recursion::async_recursion(?Send)] - async fn typecheck_expr( - &self, - ast: &Expr, - scope: Scope, - ) -> PartialType { + async fn typecheck_expr<'ast: 'a>(tc: Rc, ast: &'ast Expr, scope: Scope) -> PartialType + where + 'a: 'async_recursion, + { match ast { Expr::StructInit { name, fields } => { - let struct_scope = resolve_struct_ref(&self.sg, scope, name).await; - let fld_futures = fields.iter().map(|(fld_name, fld_init)| async move { - let (decl_type, init_type) = join( - resolve_member_ref(&self.sg, struct_scope, fld_name), - self.typecheck_expr(fld_init, scope), - ) - .await; - - self.uf.borrow_mut().unify(decl_type, init_type) + let struct_scope = resolve_struct_ref(&tc.sg, scope, name).await; + let fld_futures = fields.iter().map(|(fld_name, fld_init)| { + let tc = tc.clone(); + async move { + let (decl_type, init_type) = join( + resolve_member_ref(&tc.clone().sg, struct_scope, fld_name), + Self::typecheck_expr(tc.clone(), fld_init, scope), + ) + .await; + + println!("borrow_mut (struct_init) (1)"); + let mut _uf = tc.uf.borrow_mut(); + _uf.unify(decl_type, init_type); + drop(_uf); + println!("dropped (struct_init) (1)"); + } }); // FIXME: field init exhaustiveness check omitted // asynchronously check field initializations for fut in fld_futures { - self.ex.spawn(fut).detach() + tc.ex.spawn(fut).detach() } // .. but eagerly return the struct type PartialType::Struct { @@ -257,32 +263,60 @@ impl<'a, 'sg> TypeChecker<'a> { } Expr::Add(l, r) => { // type check left-hand-side asynchronously - self.run_detached(async { - let l_ty = self.typecheck_expr(l, scope).await; - self.uf.borrow_mut().unify(l_ty, PartialType::Number) + let _scope = scope; + let _tc = tc.clone(); + tc.clone().run_detached(async move { + let l_ty = Self::typecheck_expr(_tc.clone(), l, _scope).await; + println!("borrow_mut (add; left) (2)"); + let mut _uf = _tc.uf.borrow_mut(); + _uf.unify(l_ty, PartialType::Number); + drop(_uf); + println!("dropped (add; left) (2)"); }); // and type-check the right-hand-side asynchronously - self.run_detached(async { - let r_ty = self.typecheck_expr(r, scope).await; - self.uf.borrow_mut().unify(r_ty, PartialType::Number) + let _tc = tc.clone(); + tc.clone().run_detached(async move { + let r_ty = Self::typecheck_expr(_tc.clone(), r, scope).await; + println!("borrow_mut (add; right) (3)"); + let mut _uf = _tc.uf.borrow_mut(); + _uf.unify(r_ty, PartialType::Number); + drop(_uf); + println!("dropped (add; right) (3)"); }); // ... but immediately return the current type PartialType::Number } Expr::Number(_) => PartialType::Number, - Expr::Ident(var_name) => resolve_lexical_ref(&self.sg, scope, var_name).await, + Expr::Ident(var_name) => resolve_lexical_ref(&tc.sg, scope, var_name).await, Expr::FieldAccess(inner, field) => { - let res = Box::pin(self.typecheck_expr(inner, scope)).await; - let inner_expr_type = self.uf.borrow_mut().find_ty(res); + let res = Box::pin(Self::typecheck_expr(tc.clone(), inner, scope)).await; + let inner_expr_type = { + println!("borrow_mut (field_access) (4)"); + let mut _uf = tc.uf.borrow_mut(); + let ty = _uf.find_ty(res); + drop(_uf); + println!("dropped (field_access) (4)"); + ty + }; match inner_expr_type { PartialType::Variable(tv) => { println!("awaiting refinement of {:?}", tv); - let refined_type = self.uf.borrow_mut().callback(tv).await; + + let refined_type = { + println!("borrow_mut (field_access; var) (5)"); + let mut _uf = tc.uf.borrow_mut(); + let ty_fut = _uf.callback(tv); + drop(_uf); + println!("dropped (field_access; var) (5)"); + ty_fut.await + }; println!("refined type: {:?}", refined_type); - todo!("retry type checking with refined type") - }, - PartialType::Struct { scope, .. } => resolve_member_ref(&self.sg, scope, field).await, + refined_type + } + PartialType::Struct { scope, .. } => { + resolve_member_ref(&tc.sg, scope, field).await + } PartialType::Number => panic!("number has no field {field}"), } } @@ -291,33 +325,49 @@ impl<'a, 'sg> TypeChecker<'a> { value, in_expr, } => { - let new_scope = - self.sg.add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); - self.sg.add_edge(new_scope, RecordLabel::Lexical, scope) + let new_scope = tc + .sg + .add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); + tc.sg + .add_edge(new_scope, RecordLabel::Lexical, scope) + .expect("already closed"); + tc.sg.close(new_scope, &RecordLabel::Lexical); + + let ty_var = { + println!("borrow_mut (let) (6)"); + let mut _uf = tc.uf.borrow_mut(); + let ty_var = PartialType::Variable(_uf.fresh()); + drop(_uf); + println!("dropped (let) (6)"); + ty_var + }; + tc.sg + .add_decl( + new_scope, + RecordLabel::Definition, + RecordData::VarDecl { + name: name.clone(), + ty: ty_var.clone(), + }, + ) .expect("already closed"); - self.sg.close(new_scope, &RecordLabel::Lexical); - - let ty_var = PartialType::Variable(self.uf.borrow_mut().fresh()); - self.sg.add_decl( - new_scope, - RecordLabel::Definition, - RecordData::VarDecl { - name: name.clone(), - ty: ty_var.clone(), - }, - ) - .expect("already closed"); - self.sg.close(new_scope, &RecordLabel::Definition); + tc.sg.close(new_scope, &RecordLabel::Definition); // compute type of the variable + let _tc = tc.clone(); let ty_var_future = async move { - let ty = self.typecheck_expr(value, scope).await; - self.uf.borrow_mut().unify(ty_var, ty); + let ty = Self::typecheck_expr(_tc.clone(), value, scope).await; + + println!("borrow_mut (let; result) (7)"); + let mut _uf = _tc.uf.borrow_mut(); + _uf.unify(ty_var, ty); + drop(_uf); + println!("dropped (let; result) (7)"); }; // compute type of the result expression - let ty_res_future = self.typecheck_expr(in_expr, new_scope); + let ty_res_future = Self::typecheck_expr(tc.clone(), in_expr, new_scope); // run both computations concurrently // @@ -330,7 +380,7 @@ impl<'a, 'sg> TypeChecker<'a> { // record A { x: int } // let r = A { x = 42 } in r.x // ``` - self.run_detached(ty_var_future); + tc.run_detached(ty_var_future); // return ty_res_future.await @@ -345,25 +395,25 @@ impl<'a, 'sg> TypeChecker<'a> { name: struct_def.name.clone(), ty: field_scope, }); - self.sg.add_edge(scope, RecordLabel::TypeDefinition, decl_scope) + self.sg + .add_edge(scope, RecordLabel::TypeDefinition, decl_scope) .expect("already closed"); field_scope } async fn typecheck_structdef( - &self, + tc: Rc>, struct_def: &StructDef, scope: Scope, field_scope: Scope, ) { - let fld_decl_futures = struct_def - .fields - .iter() - .map(|(fld_name, fld_ty)| async move { + let fld_decl_futures = struct_def.fields.iter().map(|(fld_name, fld_ty)| { + let tc = tc.clone(); + async move { let ty = match fld_ty { Type::StructRef(n) => { - let struct_scope = resolve_struct_ref(&self.sg, scope, n).await; + let struct_scope = resolve_struct_ref(&tc.sg, scope, n).await; PartialType::Struct { name: n.clone(), scope: struct_scope, @@ -372,20 +422,21 @@ impl<'a, 'sg> TypeChecker<'a> { Type::Int => PartialType::Number, }; - self.sg.add_decl( - field_scope, - RecordLabel::Definition, - RecordData::VarDecl { - name: fld_name.clone(), - ty, - }, - ) - .expect("unexpected close"); - }); + tc.sg + .add_decl( + field_scope, + RecordLabel::Definition, + RecordData::VarDecl { + name: fld_name.clone(), + ty, + }, + ) + .expect("unexpected close"); + } + }); join_all(fld_decl_futures).await; } - } async fn resolve_struct_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &String) -> Scope { let env = sg @@ -456,27 +507,33 @@ async fn resolve_member_ref( .clone() } - fn typecheck(ast: &Ast) -> PartialType { let storage = Storage::new(); let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); let uf = RefCell::new(UnionFind::new()); let local = LocalExecutor::new(); - let tc = TypeChecker { + let tc = Rc::new(TypeChecker { sg: sg, uf: uf, ex: local, - }; + }); - let fut = async { + let _tc = tc.clone(); + + let fut = async move { let global_scope = tc.sg.add_scope_default_with([RecordLabel::TypeDefinition]); for item in &ast.items { // synchronously init record decl let field_scope = tc.init_structdef(item, global_scope); tc.ex - .spawn(tc.typecheck_structdef(item, global_scope, field_scope)) + .spawn(TypeChecker::typecheck_structdef( + tc.clone(), + item, + global_scope, + field_scope, + )) .detach(); } @@ -484,8 +541,12 @@ fn typecheck(ast: &Ast) -> PartialType { // even before the future is returned and spawned. tc.sg.close(global_scope, &RecordLabel::TypeDefinition); - let main_task = tc.ex - .spawn(tc.typecheck_expr(&ast.main, global_scope)); + let _tc = tc.clone(); + let main_task = tc.ex.spawn(async move { + let ty_main = TypeChecker::typecheck_expr(_tc, &ast.main, global_scope).await; + println!("!!! type of main: {:?}", ty_main); + ty_main + }); while !tc.ex.is_empty() { tc.ex.tick().await; diff --git a/scopegraphs/src/completable_future.rs b/scopegraphs/src/completable_future.rs index fcd1eec..0b5edba 100644 --- a/scopegraphs/src/completable_future.rs +++ b/scopegraphs/src/completable_future.rs @@ -1,5 +1,5 @@ //! Copied and adapted from https://crates.io/crates/completable_future (due to dependency mismatch) -//! +//! //! # Completable Future //! //! Similar to Java's CompletableFuture, this crate provides a simple @@ -22,18 +22,18 @@ //! ``` //! extern crate futures; //! extern crate completable_future; -//! +//! //! use futures::prelude::*; //! use futures::executor::block_on; //! use std::thread::spawn; //! use std::thread::sleep; //! use std::time::Duration; //! use completable_future::CompletableFuture; -//! +//! //! fn main() { //! let fut1 = CompletableFuture::::new(); //! // we will give the signal to some worker for it to complete -//! let mut signal = fut1.signal(); +//! let mut signal = fut1.signal(); //! let fut2 = fut1.and_then(|s| { //! // this will come from whoever completes the future //! println!("in fut2: {}", s); @@ -56,12 +56,12 @@ //! j.join().unwrap(); //! } //! ``` - + use futures::future::Future; -use futures::task::{Context, Waker, AtomicWaker}; +use futures::task::{AtomicWaker, Context, Waker}; +use std::mem; use std::pin::Pin; use std::sync::{Arc, Mutex}; -use std::mem; use std::task::Poll; enum WakerWrapper { @@ -77,7 +77,7 @@ impl WakerWrapper { let w = AtomicWaker::new(); w.register(waker); *self = WakerWrapper::Registered(w) - }, + } } } @@ -121,8 +121,8 @@ struct SignalInternal { pub struct CompletableFutureSignal { internal: Arc>>, } - -impl CompletableFutureSignal { + +impl CompletableFutureSignal { fn mutate_self(&mut self, new_state: FutureState) -> bool { let mut internal = self.internal.lock().unwrap(); match internal.state { @@ -130,7 +130,7 @@ impl CompletableFutureSignal { internal.state.swap(new_state); internal.waker.wake(); true - }, + } _ => false, } } @@ -157,15 +157,15 @@ impl CompletableFutureSignal { pub struct CompletableFuture { internal: Arc>>, } - + impl CompletableFuture { /// Construct a CompletableFuture. pub fn new() -> CompletableFuture { CompletableFuture { - internal: Arc::new(Mutex::new(SignalInternal{ + internal: Arc::new(Mutex::new(SignalInternal { waker: WakerWrapper::NotRegistered, state: FutureState::Pending, - })) + })), } } @@ -173,10 +173,10 @@ impl CompletableFuture { /// with the value provided. pub fn completed(val: V) -> CompletableFuture { CompletableFuture { - internal: Arc::new(Mutex::new(SignalInternal{ + internal: Arc::new(Mutex::new(SignalInternal { waker: WakerWrapper::NotRegistered, state: FutureState::Completed(val), - })) + })), } } @@ -191,7 +191,7 @@ impl CompletableFuture { impl Future for CompletableFuture { type Output = V; - + fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { let mut signal = self.internal.lock().unwrap(); signal.waker.register(ctx.waker()); @@ -199,8 +199,10 @@ impl Future for CompletableFuture { let state = &mut signal.state; match state { FutureState::Pending => Poll::Pending, - FutureState::Taken => panic!("bug: the value has been taken, yet I'm still polled again"), + FutureState::Taken => { + panic!("bug: the value has been taken, yet I'm still polled again") + } FutureState::Completed(_) => Poll::Ready(state.unwrap_val()), } } -} \ No newline at end of file +} diff --git a/scopegraphs/src/lib.rs b/scopegraphs/src/lib.rs index 02e3daa..2488672 100644 --- a/scopegraphs/src/lib.rs +++ b/scopegraphs/src/lib.rs @@ -27,10 +27,10 @@ mod render; pub use scopegraphs_regular_expressions::*; +pub mod completable_future; pub mod completeness; pub mod containers; mod future_wrapper; -pub mod completable_future; pub mod resolve; From 5fd626e5936406abdf5aeecb6069f21bd799eeea Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Tue, 14 May 2024 19:24:06 +0200 Subject: [PATCH 09/22] Fix completeness bugs --- scopegraphs/examples/records.rs | 123 +++++++++++----------------- scopegraphs/src/completeness/mod.rs | 1 + 2 files changed, 50 insertions(+), 74 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index eff5f89..b65bfae 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -5,7 +5,6 @@ use scopegraphs::resolve::Resolve; use scopegraphs::{query_regex, Scope, ScopeGraph, Storage}; use scopegraphs_macros::{label_order, Label}; use smol::LocalExecutor; -use std::borrow::Borrow; use std::cell::RefCell; use std::collections::HashMap; use std::fmt::{Debug, Formatter}; @@ -106,10 +105,8 @@ impl UnionFind { if let PartialType::Variable(v_left) = left { // arbitrarily choose right as new representative // FIXME: use rank heuristic in case right is a variable? - println!("unify: {:?} == {:?}", left, right); *self.get(v_left) = right.clone(); for mut fut in std::mem::replace(&mut self.callbacks[v_left.0], vec![]) { - println!("complete: {:?} == {:?}", left, right); fut.complete(right.clone()); } } else if let PartialType::Variable(_) = right { @@ -155,6 +152,7 @@ impl UnionFind { &mut parent[tv.0] } + #[allow(unused)] fn type_of(&mut self, var: TypeVar) -> Option { match self.find(var) { PartialType::Variable(_) => None, @@ -177,6 +175,7 @@ impl UnionFind { #[derive(Debug)] enum Type { + #[allow(unused)] StructRef(String), Int, } @@ -229,7 +228,7 @@ impl<'a, 'sg> TypeChecker<'a> { where 'a: 'async_recursion, { - match ast { + let ty_res = match ast { Expr::StructInit { name, fields } => { let struct_scope = resolve_struct_ref(&tc.sg, scope, name).await; let fld_futures = fields.iter().map(|(fld_name, fld_init)| { @@ -241,11 +240,7 @@ impl<'a, 'sg> TypeChecker<'a> { ) .await; - println!("borrow_mut (struct_init) (1)"); - let mut _uf = tc.uf.borrow_mut(); - _uf.unify(decl_type, init_type); - drop(_uf); - println!("dropped (struct_init) (1)"); + tc.with_unifier(|uf| uf.unify(decl_type, init_type)); } }); @@ -267,21 +262,13 @@ impl<'a, 'sg> TypeChecker<'a> { let _tc = tc.clone(); tc.clone().run_detached(async move { let l_ty = Self::typecheck_expr(_tc.clone(), l, _scope).await; - println!("borrow_mut (add; left) (2)"); - let mut _uf = _tc.uf.borrow_mut(); - _uf.unify(l_ty, PartialType::Number); - drop(_uf); - println!("dropped (add; left) (2)"); + _tc.with_unifier(|uf| uf.unify(l_ty, PartialType::Number)); }); // and type-check the right-hand-side asynchronously let _tc = tc.clone(); tc.clone().run_detached(async move { let r_ty = Self::typecheck_expr(_tc.clone(), r, scope).await; - println!("borrow_mut (add; right) (3)"); - let mut _uf = _tc.uf.borrow_mut(); - _uf.unify(r_ty, PartialType::Number); - drop(_uf); - println!("dropped (add; right) (3)"); + _tc.with_unifier(|uf| uf.unify(r_ty, PartialType::Number)); }); // ... but immediately return the current type @@ -290,35 +277,9 @@ impl<'a, 'sg> TypeChecker<'a> { Expr::Number(_) => PartialType::Number, Expr::Ident(var_name) => resolve_lexical_ref(&tc.sg, scope, var_name).await, Expr::FieldAccess(inner, field) => { - let res = Box::pin(Self::typecheck_expr(tc.clone(), inner, scope)).await; - let inner_expr_type = { - println!("borrow_mut (field_access) (4)"); - let mut _uf = tc.uf.borrow_mut(); - let ty = _uf.find_ty(res); - drop(_uf); - println!("dropped (field_access) (4)"); - ty - }; - match inner_expr_type { - PartialType::Variable(tv) => { - println!("awaiting refinement of {:?}", tv); - - let refined_type = { - println!("borrow_mut (field_access; var) (5)"); - let mut _uf = tc.uf.borrow_mut(); - let ty_fut = _uf.callback(tv); - drop(_uf); - println!("dropped (field_access; var) (5)"); - ty_fut.await - }; - println!("refined type: {:?}", refined_type); - refined_type - } - PartialType::Struct { scope, .. } => { - resolve_member_ref(&tc.sg, scope, field).await - } - PartialType::Number => panic!("number has no field {field}"), - } + let res = Self::typecheck_expr(tc.clone(), inner, scope).await; + let inner_expr_type = tc.with_unifier(|uf| uf.find_ty(res)); + Self::type_check_field_access(tc, inner_expr_type, field).await } Expr::Let { name, @@ -333,14 +294,7 @@ impl<'a, 'sg> TypeChecker<'a> { .expect("already closed"); tc.sg.close(new_scope, &RecordLabel::Lexical); - let ty_var = { - println!("borrow_mut (let) (6)"); - let mut _uf = tc.uf.borrow_mut(); - let ty_var = PartialType::Variable(_uf.fresh()); - drop(_uf); - println!("dropped (let) (6)"); - ty_var - }; + let ty_var = PartialType::Variable(tc.with_unifier(|uf| uf.fresh())); tc.sg .add_decl( new_scope, @@ -358,12 +312,7 @@ impl<'a, 'sg> TypeChecker<'a> { let _tc = tc.clone(); let ty_var_future = async move { let ty = Self::typecheck_expr(_tc.clone(), value, scope).await; - - println!("borrow_mut (let; result) (7)"); - let mut _uf = _tc.uf.borrow_mut(); - _uf.unify(ty_var, ty); - drop(_uf); - println!("dropped (let; result) (7)"); + _tc.with_unifier(|uf| uf.unify(ty_var, ty)) }; // compute type of the result expression @@ -385,19 +334,39 @@ impl<'a, 'sg> TypeChecker<'a> { // return ty_res_future.await } + }; + ty_res + } + + #[async_recursion::async_recursion(?Send)] + async fn type_check_field_access( + tc: Rc>, + inner_expr_type: PartialType, + field: &String + ) -> PartialType + where 'a: 'async_recursion + { + match inner_expr_type { + PartialType::Variable(tv) => { + let refined_type = tc.with_unifier(|uf| uf.callback(tv)).await; + Self::type_check_field_access(tc, refined_type, field).await + } + PartialType::Struct { scope, .. } => { + resolve_member_ref(&tc.sg, scope, field).await + } + PartialType::Number => panic!("number has no field {field}"), } } fn init_structdef(&self, struct_def: &StructDef, scope: Scope) -> Scope { let field_scope = self.sg.add_scope_default_with([RecordLabel::Definition]); - // FIXME: use Decl - let decl_scope = self.sg.add_scope(RecordData::TypeDecl { - name: struct_def.name.clone(), - ty: field_scope, - }); self.sg - .add_edge(scope, RecordLabel::TypeDefinition, decl_scope) + .add_decl(scope, RecordLabel::TypeDefinition, RecordData::TypeDecl { + name: struct_def.name.clone(), + ty: field_scope, + }) .expect("already closed"); + self.sg.close(scope, &RecordLabel::Definition); field_scope } @@ -436,7 +405,16 @@ impl<'a, 'sg> TypeChecker<'a> { }); join_all(fld_decl_futures).await; + tc.sg.close(field_scope, &RecordLabel::Definition); } + + fn with_unifier T>(&self, f: F) -> T { + let mut uf = self.uf.borrow_mut(); + let res = f(&mut uf); + drop(uf); + res + } + } async fn resolve_struct_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &String) -> Scope { let env = sg @@ -542,11 +520,8 @@ fn typecheck(ast: &Ast) -> PartialType { tc.sg.close(global_scope, &RecordLabel::TypeDefinition); let _tc = tc.clone(); - let main_task = tc.ex.spawn(async move { - let ty_main = TypeChecker::typecheck_expr(_tc, &ast.main, global_scope).await; - println!("!!! type of main: {:?}", ty_main); - ty_main - }); + let main_task = tc.ex + .spawn(TypeChecker::typecheck_expr(_tc.clone(), &ast.main, global_scope)); while !tc.ex.is_empty() { tc.ex.tick().await; @@ -590,5 +565,5 @@ fn main() { }, }; - println!("{:?}", typecheck(&example)); + println!("Type of example is: {:?}", typecheck(&example)); } diff --git a/scopegraphs/src/completeness/mod.rs b/scopegraphs/src/completeness/mod.rs index 9d946bf..bf7506f 100644 --- a/scopegraphs/src/completeness/mod.rs +++ b/scopegraphs/src/completeness/mod.rs @@ -69,6 +69,7 @@ pub trait Completeness: Sealed { inner_scope_graph: &InnerScopeGraph, scope: Scope, ) { + // FIXME: has all scopes open! self.cmpl_new_scope(inner_scope_graph, scope) } From 6bff77b48ba868e949bc1877fe378334a96f0e43 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Tue, 14 May 2024 19:37:05 +0200 Subject: [PATCH 10/22] formatting --- scopegraphs/examples/records.rs | 249 ++++++++++++++++++++++++-- scopegraphs/src/completable_future.rs | 208 --------------------- scopegraphs/src/lib.rs | 1 - 3 files changed, 233 insertions(+), 225 deletions(-) delete mode 100644 scopegraphs/src/completable_future.rs diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index b65bfae..8d4e200 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -1,5 +1,5 @@ +use self::completable_future::{CompletableFuture, CompletableFutureSignal}; use futures::future::{join, join_all}; -use scopegraphs::completable_future::{CompletableFuture, CompletableFutureSignal}; use scopegraphs::completeness::FutureCompleteness; use scopegraphs::resolve::Resolve; use scopegraphs::{query_regex, Scope, ScopeGraph, Storage}; @@ -340,20 +340,19 @@ impl<'a, 'sg> TypeChecker<'a> { #[async_recursion::async_recursion(?Send)] async fn type_check_field_access( - tc: Rc>, - inner_expr_type: PartialType, - field: &String - ) -> PartialType - where 'a: 'async_recursion + tc: Rc>, + inner_expr_type: PartialType, + field: &String, + ) -> PartialType + where + 'a: 'async_recursion, { match inner_expr_type { PartialType::Variable(tv) => { let refined_type = tc.with_unifier(|uf| uf.callback(tv)).await; Self::type_check_field_access(tc, refined_type, field).await } - PartialType::Struct { scope, .. } => { - resolve_member_ref(&tc.sg, scope, field).await - } + PartialType::Struct { scope, .. } => resolve_member_ref(&tc.sg, scope, field).await, PartialType::Number => panic!("number has no field {field}"), } } @@ -361,10 +360,14 @@ impl<'a, 'sg> TypeChecker<'a> { fn init_structdef(&self, struct_def: &StructDef, scope: Scope) -> Scope { let field_scope = self.sg.add_scope_default_with([RecordLabel::Definition]); self.sg - .add_decl(scope, RecordLabel::TypeDefinition, RecordData::TypeDecl { - name: struct_def.name.clone(), - ty: field_scope, - }) + .add_decl( + scope, + RecordLabel::TypeDefinition, + RecordData::TypeDecl { + name: struct_def.name.clone(), + ty: field_scope, + }, + ) .expect("already closed"); self.sg.close(scope, &RecordLabel::Definition); @@ -414,7 +417,6 @@ impl<'a, 'sg> TypeChecker<'a> { drop(uf); res } - } async fn resolve_struct_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &String) -> Scope { let env = sg @@ -520,8 +522,11 @@ fn typecheck(ast: &Ast) -> PartialType { tc.sg.close(global_scope, &RecordLabel::TypeDefinition); let _tc = tc.clone(); - let main_task = tc.ex - .spawn(TypeChecker::typecheck_expr(_tc.clone(), &ast.main, global_scope)); + let main_task = tc.ex.spawn(TypeChecker::typecheck_expr( + _tc.clone(), + &ast.main, + global_scope, + )); while !tc.ex.is_empty() { tc.ex.tick().await; @@ -567,3 +572,215 @@ fn main() { println!("Type of example is: {:?}", typecheck(&example)); } + +#[allow(unused)] +mod completable_future { + //! Copied and adapted from https://crates.io/crates/completable_future (due to dependency mismatch) + //! + //! # Completable Future + //! + //! Similar to Java's CompletableFuture, this crate provides a simple + //! future that can be completed and properly notified from elsewhere other + //! than the executor of the future. It is sutable for some blocking + //! tasks that could block the executor if we use a future directly in + //! an executor. + //! + //! A CompletableFuture is still a future and has all the combinators that + //! you can use to chain logic working on the result or the error. Also, + //! unlike Java and inherited from Rust's poll model future, some executor + //! needs to execute the CompletableFuture in order to get the result; the + //! thread or code that completes (or errors) the future will not execute + //! the logic chained after the future. + //! + //! The CompletableFuture uses Arc and Mutex to synchronize poll and completion, + //! so there's overhead for using it. + //! + //! # Example + //! ``` + //! extern crate futures; + //! extern crate completable_future; + //! + //! use futures::prelude::*; + //! use futures::executor::block_on; + //! use std::thread::spawn; + //! use std::thread::sleep; + //! use std::time::Duration; + //! use completable_future::CompletableFuture; + //! + //! fn main() { + //! let fut1 = CompletableFuture::::new(); + //! // we will give the signal to some worker for it to complete + //! let mut signal = fut1.signal(); + //! let fut2 = fut1.and_then(|s| { + //! // this will come from whoever completes the future + //! println!("in fut2: {}", s); + //! Ok("this comes from fut2".to_string()) + //! }); + //! + //! let j = spawn(move || { + //! println!("waiter thread: I'm going to block on fut2"); + //! let ret = block_on(fut2).unwrap(); + //! println!("waiter thread: fut2 completed with message -- {}", ret); + //! }); + //! + //! spawn(move || { + //! println!("worker thread: going to block for 1000 ms"); + //! sleep(Duration::from_millis(1000)); + //! signal.complete("this comes from fut1".to_string()); + //! println!("worker thread: completed fut1"); + //! }); + //! + //! j.join().unwrap(); + //! } + //! ``` + + use futures::future::Future; + use futures::task::{AtomicWaker, Context, Waker}; + use std::mem; + use std::pin::Pin; + use std::sync::{Arc, Mutex}; + use std::task::Poll; + + enum WakerWrapper { + Registered(AtomicWaker), + NotRegistered, + } + + impl WakerWrapper { + fn register(&mut self, waker: &Waker) { + match self { + &mut WakerWrapper::Registered(ref _dont_care) => (), + &mut WakerWrapper::NotRegistered => { + let w = AtomicWaker::new(); + w.register(waker); + *self = WakerWrapper::Registered(w) + } + } + } + + fn wake(&self) { + match self { + &WakerWrapper::Registered(ref w) => w.wake(), + &WakerWrapper::NotRegistered => (), + }; + } + } + + enum FutureState { + Pending, + Completed(V), + Taken, + } + + impl FutureState { + fn swap(&mut self, new_val: FutureState) -> FutureState { + mem::replace(self, new_val) + } + + fn unwrap_val(&mut self) -> V { + match self.swap(FutureState::Taken) { + FutureState::Completed(val) => val, + _ => panic!("cannot unwrap because my state is not completed"), + } + } + } + + /// the state of the future; reference counted + struct SignalInternal { + waker: WakerWrapper, + state: FutureState, + } + + /// A handle to the future state. When you create a completable future, + /// you should also create a signal that somebody can use to complete + /// the future. + #[derive(Clone)] + pub struct CompletableFutureSignal { + internal: Arc>>, + } + + impl CompletableFutureSignal { + fn mutate_self(&mut self, new_state: FutureState) -> bool { + let mut internal = self.internal.lock().unwrap(); + match internal.state { + FutureState::Pending => { + internal.state.swap(new_state); + internal.waker.wake(); + true + } + _ => false, + } + } + + /// Complete the associated CompletableFuture. This method + /// can be called safely across multiple threads multiple times, + /// but only the winning call would mutate the future; other calls + /// will be rendered noop. + /// + /// Returns whether the call successfully mutates the future. + pub fn complete(&mut self, value: V) -> bool { + self.mutate_self(FutureState::Completed(value)) + } + } + + /// A CompletableFuture is a future that you can expect a result (or error) + /// from and chain logic on. You will need some executor to actively poll + /// the result. Executors provided by the futures crate are usually good + /// enough for common situations. + /// + /// If you use a custom executor, be careful that don't poll the CompletableFuture + /// after it has already completed (or errored) in previous polls. Doing so + /// will panic your executor. + pub struct CompletableFuture { + internal: Arc>>, + } + + impl CompletableFuture { + /// Construct a CompletableFuture. + pub fn new() -> CompletableFuture { + CompletableFuture { + internal: Arc::new(Mutex::new(SignalInternal { + waker: WakerWrapper::NotRegistered, + state: FutureState::Pending, + })), + } + } + + /// Construct a CompletableFuture that's already completed + /// with the value provided. + pub fn completed(val: V) -> CompletableFuture { + CompletableFuture { + internal: Arc::new(Mutex::new(SignalInternal { + waker: WakerWrapper::NotRegistered, + state: FutureState::Completed(val), + })), + } + } + + /// Get a CompletableFutureSignal that can be used to complete + /// or error this CompletableFuture. + pub fn signal(&self) -> CompletableFutureSignal { + CompletableFutureSignal { + internal: self.internal.clone(), + } + } + } + + impl Future for CompletableFuture { + type Output = V; + + fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { + let mut signal = self.internal.lock().unwrap(); + signal.waker.register(ctx.waker()); + + let state = &mut signal.state; + match state { + FutureState::Pending => Poll::Pending, + FutureState::Taken => { + panic!("bug: the value has been taken, yet I'm still polled again") + } + FutureState::Completed(_) => Poll::Ready(state.unwrap_val()), + } + } + } +} diff --git a/scopegraphs/src/completable_future.rs b/scopegraphs/src/completable_future.rs deleted file mode 100644 index 0b5edba..0000000 --- a/scopegraphs/src/completable_future.rs +++ /dev/null @@ -1,208 +0,0 @@ -//! Copied and adapted from https://crates.io/crates/completable_future (due to dependency mismatch) -//! -//! # Completable Future -//! -//! Similar to Java's CompletableFuture, this crate provides a simple -//! future that can be completed and properly notified from elsewhere other -//! than the executor of the future. It is sutable for some blocking -//! tasks that could block the executor if we use a future directly in -//! an executor. -//! -//! A CompletableFuture is still a future and has all the combinators that -//! you can use to chain logic working on the result or the error. Also, -//! unlike Java and inherited from Rust's poll model future, some executor -//! needs to execute the CompletableFuture in order to get the result; the -//! thread or code that completes (or errors) the future will not execute -//! the logic chained after the future. -//! -//! The CompletableFuture uses Arc and Mutex to synchronize poll and completion, -//! so there's overhead for using it. -//! -//! # Example -//! ``` -//! extern crate futures; -//! extern crate completable_future; -//! -//! use futures::prelude::*; -//! use futures::executor::block_on; -//! use std::thread::spawn; -//! use std::thread::sleep; -//! use std::time::Duration; -//! use completable_future::CompletableFuture; -//! -//! fn main() { -//! let fut1 = CompletableFuture::::new(); -//! // we will give the signal to some worker for it to complete -//! let mut signal = fut1.signal(); -//! let fut2 = fut1.and_then(|s| { -//! // this will come from whoever completes the future -//! println!("in fut2: {}", s); -//! Ok("this comes from fut2".to_string()) -//! }); -//! -//! let j = spawn(move || { -//! println!("waiter thread: I'm going to block on fut2"); -//! let ret = block_on(fut2).unwrap(); -//! println!("waiter thread: fut2 completed with message -- {}", ret); -//! }); -//! -//! spawn(move || { -//! println!("worker thread: going to block for 1000 ms"); -//! sleep(Duration::from_millis(1000)); -//! signal.complete("this comes from fut1".to_string()); -//! println!("worker thread: completed fut1"); -//! }); -//! -//! j.join().unwrap(); -//! } -//! ``` - -use futures::future::Future; -use futures::task::{AtomicWaker, Context, Waker}; -use std::mem; -use std::pin::Pin; -use std::sync::{Arc, Mutex}; -use std::task::Poll; - -enum WakerWrapper { - Registered(AtomicWaker), - NotRegistered, -} - -impl WakerWrapper { - fn register(&mut self, waker: &Waker) { - match self { - &mut WakerWrapper::Registered(ref _dont_care) => (), - &mut WakerWrapper::NotRegistered => { - let w = AtomicWaker::new(); - w.register(waker); - *self = WakerWrapper::Registered(w) - } - } - } - - fn wake(&self) { - match self { - &WakerWrapper::Registered(ref w) => w.wake(), - &WakerWrapper::NotRegistered => (), - }; - } -} - -enum FutureState { - Pending, - Completed(V), - Taken, -} - -impl FutureState { - fn swap(&mut self, new_val: FutureState) -> FutureState { - mem::replace(self, new_val) - } - - fn unwrap_val(&mut self) -> V { - match self.swap(FutureState::Taken) { - FutureState::Completed(val) => val, - _ => panic!("cannot unwrap because my state is not completed"), - } - } -} - -/// the state of the future; reference counted -struct SignalInternal { - waker: WakerWrapper, - state: FutureState, -} - -/// A handle to the future state. When you create a completable future, -/// you should also create a signal that somebody can use to complete -/// the future. -#[derive(Clone)] -pub struct CompletableFutureSignal { - internal: Arc>>, -} - -impl CompletableFutureSignal { - fn mutate_self(&mut self, new_state: FutureState) -> bool { - let mut internal = self.internal.lock().unwrap(); - match internal.state { - FutureState::Pending => { - internal.state.swap(new_state); - internal.waker.wake(); - true - } - _ => false, - } - } - - /// Complete the associated CompletableFuture. This method - /// can be called safely across multiple threads multiple times, - /// but only the winning call would mutate the future; other calls - /// will be rendered noop. - /// - /// Returns whether the call successfully mutates the future. - pub fn complete(&mut self, value: V) -> bool { - self.mutate_self(FutureState::Completed(value)) - } -} - -/// A CompletableFuture is a future that you can expect a result (or error) -/// from and chain logic on. You will need some executor to actively poll -/// the result. Executors provided by the futures crate are usually good -/// enough for common situations. -/// -/// If you use a custom executor, be careful that don't poll the CompletableFuture -/// after it has already completed (or errored) in previous polls. Doing so -/// will panic your executor. -pub struct CompletableFuture { - internal: Arc>>, -} - -impl CompletableFuture { - /// Construct a CompletableFuture. - pub fn new() -> CompletableFuture { - CompletableFuture { - internal: Arc::new(Mutex::new(SignalInternal { - waker: WakerWrapper::NotRegistered, - state: FutureState::Pending, - })), - } - } - - /// Construct a CompletableFuture that's already completed - /// with the value provided. - pub fn completed(val: V) -> CompletableFuture { - CompletableFuture { - internal: Arc::new(Mutex::new(SignalInternal { - waker: WakerWrapper::NotRegistered, - state: FutureState::Completed(val), - })), - } - } - - /// Get a CompletableFutureSignal that can be used to complete - /// or error this CompletableFuture. - pub fn signal(&self) -> CompletableFutureSignal { - CompletableFutureSignal { - internal: self.internal.clone(), - } - } -} - -impl Future for CompletableFuture { - type Output = V; - - fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - let mut signal = self.internal.lock().unwrap(); - signal.waker.register(ctx.waker()); - - let state = &mut signal.state; - match state { - FutureState::Pending => Poll::Pending, - FutureState::Taken => { - panic!("bug: the value has been taken, yet I'm still polled again") - } - FutureState::Completed(_) => Poll::Ready(state.unwrap_val()), - } - } -} diff --git a/scopegraphs/src/lib.rs b/scopegraphs/src/lib.rs index 2488672..6855240 100644 --- a/scopegraphs/src/lib.rs +++ b/scopegraphs/src/lib.rs @@ -27,7 +27,6 @@ mod render; pub use scopegraphs_regular_expressions::*; -pub mod completable_future; pub mod completeness; pub mod containers; mod future_wrapper; From 1b219296943a0dc25bc53380cd3e4ceb0722620a Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Tue, 14 May 2024 19:48:45 +0200 Subject: [PATCH 11/22] mak clippy happy --- scopegraphs/examples/records.rs | 48 +++++++++---------------------- scopegraphs/src/resolve/lookup.rs | 6 ++-- 2 files changed, 17 insertions(+), 37 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 8d4e200..5d8b530 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -60,6 +60,7 @@ enum PartialType { Number, } +#[derive(Default)] pub struct UnionFind { parent: Vec, vars: usize, @@ -80,14 +81,6 @@ impl Debug for UnionFind { } impl UnionFind { - pub fn new() -> Self { - Self { - parent: vec![], - vars: 0, - callbacks: vec![], - } - } - fn fresh(&mut self) -> TypeVar { let old = self.vars; self.vars += 1; @@ -106,16 +99,14 @@ impl UnionFind { // arbitrarily choose right as new representative // FIXME: use rank heuristic in case right is a variable? *self.get(v_left) = right.clone(); - for mut fut in std::mem::replace(&mut self.callbacks[v_left.0], vec![]) { + for mut fut in std::mem::take(&mut self.callbacks[v_left.0]) { fut.complete(right.clone()); } } else if let PartialType::Variable(_) = right { // left is a variable/number, but right is a variable worklist.push((right, left)) // will match first case in next iteration - } else { - if left != right { - panic!("Cannot unify {:?} and {:?}", left, right); - } + } else if left != right { + panic!("Cannot unify {:?} and {:?}", left, right); } } } @@ -448,10 +439,7 @@ async fn resolve_lexical_ref( .with_path_wellformedness(query_regex!(RecordLabel: Lexical* Definition)) .with_label_order(label_order!(RecordLabel: Definition < Lexical)) .with_data_wellformedness(|record_data: &RecordData| -> bool { - match record_data { - RecordData::VarDecl { name, .. } if name == var_name => true, - _ => false, - } + matches!(record_data, RecordData::VarDecl { name, .. } if name == var_name) }) .resolve(scope) .await; @@ -490,16 +478,10 @@ async fn resolve_member_ref( fn typecheck(ast: &Ast) -> PartialType { let storage = Storage::new(); let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); - let uf = RefCell::new(UnionFind::new()); + let uf = RefCell::new(UnionFind::default()); let local = LocalExecutor::new(); - let tc = Rc::new(TypeChecker { - sg: sg, - uf: uf, - ex: local, - }); - - let _tc = tc.clone(); + let tc = Rc::new(TypeChecker { sg, uf, ex: local }); let fut = async move { let global_scope = tc.sg.add_scope_default_with([RecordLabel::TypeDefinition]); @@ -521,9 +503,8 @@ fn typecheck(ast: &Ast) -> PartialType { // even before the future is returned and spawned. tc.sg.close(global_scope, &RecordLabel::TypeDefinition); - let _tc = tc.clone(); let main_task = tc.ex.spawn(TypeChecker::typecheck_expr( - _tc.clone(), + tc.clone(), &ast.main, global_scope, )); @@ -536,8 +517,7 @@ fn typecheck(ast: &Ast) -> PartialType { main_task.into_future().await }; - let type_checker_result = smol::block_on(fut); - type_checker_result + smol::block_on(fut) } fn main() { @@ -649,8 +629,8 @@ mod completable_future { impl WakerWrapper { fn register(&mut self, waker: &Waker) { match self { - &mut WakerWrapper::Registered(ref _dont_care) => (), - &mut WakerWrapper::NotRegistered => { + WakerWrapper::Registered(_dont_care) => (), + WakerWrapper::NotRegistered => { let w = AtomicWaker::new(); w.register(waker); *self = WakerWrapper::Registered(w) @@ -659,9 +639,9 @@ mod completable_future { } fn wake(&self) { - match self { - &WakerWrapper::Registered(ref w) => w.wake(), - &WakerWrapper::NotRegistered => (), + match *self { + WakerWrapper::Registered(ref w) => w.wake(), + WakerWrapper::NotRegistered => (), }; } } diff --git a/scopegraphs/src/resolve/lookup.rs b/scopegraphs/src/resolve/lookup.rs index 3d84903..4973f34 100644 --- a/scopegraphs/src/resolve/lookup.rs +++ b/scopegraphs/src/resolve/lookup.rs @@ -407,7 +407,7 @@ mod tests { fn test_label_order_async() { futures::executor::block_on(async { let storage = Storage::new(); - let mut scope_graph: ScopeGraph> = + let scope_graph: ScopeGraph> = ScopeGraph::new(&storage, FutureCompleteness::default()); let s0 = scope_graph.add_scope_default_closed(); @@ -650,7 +650,7 @@ mod tests { #[test] fn test_label_order_complex_explicit_close() { let storage = Storage::new(); - let mut scope_graph: ScopeGraph> = + let scope_graph: ScopeGraph> = ScopeGraph::new(&storage, ExplicitClose::default()); let s0 = scope_graph.add_scope_default_closed(); @@ -701,7 +701,7 @@ mod tests { #[test] fn test_caching() { let storage = Storage::new(); - let mut scope_graph: ScopeGraph> = + let scope_graph: ScopeGraph> = ScopeGraph::new(&storage, ImplicitClose::default()); let s0 = scope_graph.add_scope_default_with([Def]); From 3feb3871a4d03042890517c9f2424a2c215b45e4 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Tue, 14 May 2024 20:25:05 +0200 Subject: [PATCH 12/22] Add mutually recursive records --- scopegraphs/examples/records.rs | 121 ++++++++++++++++++++++++++------ 1 file changed, 100 insertions(+), 21 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 5d8b530..0bf958c 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -99,8 +99,10 @@ impl UnionFind { // arbitrarily choose right as new representative // FIXME: use rank heuristic in case right is a variable? *self.get(v_left) = right.clone(); - for mut fut in std::mem::take(&mut self.callbacks[v_left.0]) { - fut.complete(right.clone()); + if self.callbacks.len() > v_left.0 { + for mut fut in std::mem::take(&mut self.callbacks[v_left.0]) { + fut.complete(right.clone()); + } } } else if let PartialType::Variable(_) = right { // left is a variable/number, but right is a variable @@ -166,7 +168,6 @@ impl UnionFind { #[derive(Debug)] enum Type { - #[allow(unused)] StructRef(String), Int, } @@ -178,6 +179,7 @@ struct StructDef { } #[derive(Debug)] +#[allow(unused)] enum Expr { StructInit { name: String, @@ -192,6 +194,10 @@ enum Expr { value: Box, in_expr: Box, }, + LetRec { + values: HashMap, + in_expr: Box, + }, } #[derive(Debug)] @@ -325,6 +331,39 @@ impl<'a, 'sg> TypeChecker<'a> { // return ty_res_future.await } + Expr::LetRec { values, in_expr } => { + let new_scope = tc + .sg + .add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); + tc.sg + .add_edge(new_scope, RecordLabel::Lexical, scope) + .expect("already closed"); + tc.sg.close(new_scope, &RecordLabel::Lexical); + + let _tc = tc.clone(); + values.iter().for_each(|(name, init)| { + let ty = PartialType::Variable(_tc.with_unifier(|uf| uf.fresh())); + _tc.sg + .add_decl( + new_scope, + RecordLabel::Definition, + RecordData::VarDecl { + name: name.clone(), + ty: ty.clone(), + }, + ) + .expect("already closed"); + let __tc = _tc.clone(); + _tc.run_detached(async move { + let ty_init = Self::typecheck_expr(__tc.clone(), init, new_scope).await; + __tc.with_unifier(|uf| uf.unify(ty, ty_init)) + }); + }); + tc.sg.close(new_scope, &RecordLabel::Definition); + + // compute type of the result expression + Self::typecheck_expr(tc.clone(), in_expr, new_scope).await + } }; ty_res } @@ -522,30 +561,70 @@ fn typecheck(ast: &Ast) -> PartialType { fn main() { let example = Ast { - items: vec![StructDef { - name: "A".to_string(), - fields: { - let mut m = HashMap::new(); - m.insert("y".to_string(), Type::Int); - m - }, - }], - main: Expr::Let { - name: "x".to_string(), - value: Box::new(Expr::StructInit { + items: vec![ + StructDef { name: "A".to_string(), fields: { let mut m = HashMap::new(); - m.insert( - "y".to_string(), - Expr::Add(Box::new(Expr::Number(4)), Box::new(Expr::Number(5))), - ); + m.insert("b".to_string(), Type::StructRef("B".to_string())); + m.insert("x".to_string(), Type::Int); m }, - }), + }, + StructDef { + name: "B".to_string(), + fields: { + let mut m = HashMap::new(); + m.insert("a".to_string(), Type::StructRef("A".to_string())); + m.insert("x".to_string(), Type::Int); + m + }, + }, + ], + main: Expr::LetRec { + values: { + let mut m = HashMap::new(); + m.insert( + "a".to_string(), + Expr::StructInit { + name: "A".to_string(), + fields: { + let mut m = HashMap::new(); + m.insert("b".to_string(), Expr::Ident("b".to_string())); + m.insert( + "x".to_string(), + Expr::Add(Box::new(Expr::Number(4)), Box::new(Expr::Number(5))), + ); + m + }, + }, + ); + m.insert( + "b".to_string(), + Expr::StructInit { + name: "B".to_string(), + fields: { + let mut m = HashMap::new(); + m.insert("a".to_string(), Expr::Ident("a".to_string())); + m.insert( + "x".to_string(), + Expr::Add(Box::new(Expr::Number(4)), Box::new(Expr::Number(5))), + ); + m + }, + }, + ); + m + }, in_expr: Box::new(Expr::FieldAccess( - Box::new(Expr::Ident("x".to_string())), - "y".to_string(), + Box::new(Expr::FieldAccess( + Box::new(Expr::FieldAccess( + Box::new(Expr::Ident("b".to_string())), + "a".to_string(), + )), + "b".to_string(), + )), + "x".to_string(), )), }, }; From 3d641657697dc0197b6415f34f9483818ec34acc Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Tue, 14 May 2024 20:38:42 +0200 Subject: [PATCH 13/22] Remove explicit drop --- scopegraphs/examples/records.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 0bf958c..1a9216f 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -442,10 +442,7 @@ impl<'a, 'sg> TypeChecker<'a> { } fn with_unifier T>(&self, f: F) -> T { - let mut uf = self.uf.borrow_mut(); - let res = f(&mut uf); - drop(uf); - res + f(&mut self.uf.borrow_mut()) } } async fn resolve_struct_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &String) -> Scope { From b140bc7e65ab0648aa655f929f084413c12157a9 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Tue, 14 May 2024 20:42:06 +0200 Subject: [PATCH 14/22] use run_detached --- scopegraphs/examples/records.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 1a9216f..91c63b4 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -244,9 +244,8 @@ impl<'a, 'sg> TypeChecker<'a> { // FIXME: field init exhaustiveness check omitted // asynchronously check field initializations - for fut in fld_futures { - tc.ex.spawn(fut).detach() - } + fld_futures.for_each(|fut| tc.run_detached(fut)); + // .. but eagerly return the struct type PartialType::Struct { name: name.clone(), @@ -525,14 +524,12 @@ fn typecheck(ast: &Ast) -> PartialType { for item in &ast.items { // synchronously init record decl let field_scope = tc.init_structdef(item, global_scope); - tc.ex - .spawn(TypeChecker::typecheck_structdef( - tc.clone(), - item, - global_scope, - field_scope, - )) - .detach(); + tc.run_detached(TypeChecker::typecheck_structdef( + tc.clone(), + item, + global_scope, + field_scope, + )); } // We can close for type definitions since the scopes for this are synchronously made From 57e3bb3d07e9bb3eb9060fa299b48ee04d193133 Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Fri, 24 May 2024 09:21:23 +0200 Subject: [PATCH 15/22] update record example --- scopegraphs-regular-expressions/Cargo.toml | 2 +- scopegraphs/Cargo.toml | 1 + scopegraphs/examples/records.rs | 893 ++++++++++++--------- scopegraphs/src/lib.rs | 2 + scopegraphs/src/render.rs | 1 + 5 files changed, 539 insertions(+), 360 deletions(-) diff --git a/scopegraphs-regular-expressions/Cargo.toml b/scopegraphs-regular-expressions/Cargo.toml index 8de7164..2e3071d 100644 --- a/scopegraphs-regular-expressions/Cargo.toml +++ b/scopegraphs-regular-expressions/Cargo.toml @@ -14,7 +14,7 @@ rust-version = "1.75" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -syn = { version = "2.0.29", default-features = false, features = ["parsing"] } +syn = { version = "2.0.29", default-features = false, features = ["parsing", "derive", "printing"] } quote = { version = "1.0.33", optional = true } proc-macro2 = "1.0.69" thiserror = "1.0.50" diff --git a/scopegraphs/Cargo.toml b/scopegraphs/Cargo.toml index 5f2b517..7892b3f 100644 --- a/scopegraphs/Cargo.toml +++ b/scopegraphs/Cargo.toml @@ -30,6 +30,7 @@ ctor = "0.2.5" futures = { version = "0.3.30", default-features = false, features = ["alloc", "executor"] } smol = "2.0.0" async-recursion = "1.1.1" +winnow = "0.6.8" [features] default = ["dot", "dynamic-regex"] diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 91c63b4..4e40516 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -1,69 +1,96 @@ use self::completable_future::{CompletableFuture, CompletableFutureSignal}; +use async_recursion::async_recursion; use futures::future::{join, join_all}; use scopegraphs::completeness::FutureCompleteness; -use scopegraphs::resolve::Resolve; -use scopegraphs::{query_regex, Scope, ScopeGraph, Storage}; -use scopegraphs_macros::{label_order, Label}; +use scopegraphs::{Scope, ScopeGraph, Storage}; +use scopegraphs_macros::{Label}; use smol::LocalExecutor; use std::cell::RefCell; -use std::collections::HashMap; +use std::error::Error; use std::fmt::{Debug, Formatter}; -use std::future::IntoFuture; +use std::fs::File; +use std::future::Future; use std::rc::Rc; -use std::vec; +use crate::ast::{Expr, Program, StructDef, Type}; +use crate::resolve::{resolve_lexical_ref, resolve_member_ref, resolve_record_ref}; +use scopegraphs::RenderScopeData; #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] -enum RecordLabel { +enum SgLabel { TypeDefinition, Definition, Lexical, } -#[derive(Debug, Default, Hash, Eq, PartialEq)] -enum RecordData { +#[derive(Debug, Default, Hash, Eq, PartialEq, Clone)] +enum SgData { VarDecl { name: String, ty: PartialType, }, TypeDecl { name: String, - ty: Scope, + scope: Scope, }, #[default] Nothing, } -impl RecordData { +impl RenderScopeData for SgData { + fn render(&self) -> Option { + match self { + SgData::VarDecl { name, ty} => Some(format!("var {name}: {ty:?}")), + SgData::TypeDecl { name, scope} => Some(format!("record {name} -> {scope:?}")), + SgData::Nothing => None + } + } +} + +impl SgData { pub fn expect_var_decl(&self) -> &PartialType { match self { - RecordData::VarDecl { ty, .. } => ty, + SgData::VarDecl { ty, .. } => ty, _ => panic!("expected var decl, got {:?}", &self), } } pub fn expect_type_decl(&self) -> &Scope { match self { - RecordData::TypeDecl { ty, .. } => ty, + SgData::TypeDecl { scope: ty, .. } => ty, _ => panic!("expected type decl, got {:?}", &self), } } } +/// A type variable, a placeholder for a type #[derive(Clone, Debug, Default, Hash, Eq, PartialEq, Copy)] pub struct TypeVar(usize); +/// A partial type can either still be a type variable, or a concrete type #[derive(Clone, Debug, Hash, Eq, PartialEq)] enum PartialType { + /// A variable Variable(TypeVar), - Struct { name: String, scope: Scope }, - Number, + /// A struct named `name` with a scope. + /// The scope contains the field of the struct. + /// See "scopes as types" + Record { name: String, scope: Scope }, + /// A number type + Int, } #[derive(Default)] pub struct UnionFind { + /// Records the parent of each type variable. + /// Kind of assumes type variables are assigned linearly. + /// The "parent" of type variable 0 is stored at index 0 parent: Vec, + /// Keep track of type variables we've given out vars: usize, + /// A vec of signals for each type variable. + /// Whenever type variable 0 is unified with anything, we go through + /// the list at index 0 and notify each. callbacks: Vec>>, } @@ -81,6 +108,8 @@ impl Debug for UnionFind { } impl UnionFind { + /// Create a new type variable + /// (which happens to be one bigger than the previous fresh type variable) fn fresh(&mut self) -> TypeVar { let old = self.vars; self.vars += 1; @@ -88,31 +117,45 @@ impl UnionFind { TypeVar(old) } - fn unify(&mut self, a: PartialType, b: PartialType) { - let mut worklist = vec![(self.find_ty(a), self.find_ty(b))]; - - // FIXME: worklist is unnecessary, as there are no composite types. - // infrastructure is there for future extension - while let Some((left, right)) = worklist.pop() { - // if left variable - if let PartialType::Variable(v_left) = left { - // arbitrarily choose right as new representative + /// Unify two partial types, asserting they are equal to each other. + /// + /// If one of left or right is a concrete type, and the other is a type variable, + /// we've essentially resolved what type the type variable is now, and we update the + /// data structure to represent that. The next [`find`](Self::find) of this type variable + /// will return the concrete type after this unification. + /// + /// Sometimes, two type variables are unified. In that case, one of the two is chosen by + /// a fair (trust me) dice roll and is made the representative of both input type variables. + /// Whenever one of the two is now unified with a concrete type, both input type variables + /// become equal to that concrete type. + fn unify(&mut self, left: PartialType, right: PartialType) { + let left = self.find_partial_type(left); + let right = self.find_partial_type(right); + + match (left, right) { + (PartialType::Variable(left), right) | (right, PartialType::Variable(left)) => { // FIXME: use rank heuristic in case right is a variable? - *self.get(v_left) = right.clone(); - if self.callbacks.len() > v_left.0 { - for mut fut in std::mem::take(&mut self.callbacks[v_left.0]) { + *self.get(left) = right.clone(); + if self.callbacks.len() > left.0 { + for fut in std::mem::take(&mut self.callbacks[left.0]) { fut.complete(right.clone()); } } - } else if let PartialType::Variable(_) = right { - // left is a variable/number, but right is a variable - worklist.push((right, left)) // will match first case in next iteration - } else if left != right { - panic!("Cannot unify {:?} and {:?}", left, right); } + (left, right) if left != right => { + panic!("type error: cannot unify {left:?} and {right:?}"); + } + _ => {} } } + /// Find the representative for a given type variable. + /// In the best case, this is a concrete type this type variable is equal to. + /// That's nice, because now we know what that type variable was supposed to be. + /// + /// However, it's possible we find another type variable instead (wrapped in a [`PartialType`]). + /// Now we know that this new type variable has the same type of the given type variable, + /// we just don't know yet which type that is. More unifications are needed. fn find(&mut self, ty: TypeVar) -> PartialType { let res = self.get(ty); if let PartialType::Variable(v) = *res { @@ -120,6 +163,7 @@ impl UnionFind { return PartialType::Variable(ty); } + // do path compression let root = self.find(v); *self.get(v) = root.clone(); root @@ -128,7 +172,8 @@ impl UnionFind { } } - fn find_ty(&mut self, ty: PartialType) -> PartialType { + /// [find](Self::find), but for a parial type + fn find_partial_type(&mut self, ty: PartialType) -> PartialType { if let PartialType::Variable(v) = ty { self.find(v) } else { @@ -136,6 +181,8 @@ impl UnionFind { } } + /// Get a mutable reference to parent of a given type variable. + /// Used in the implementation of [`find`](Self::find) and [`union`](Self::union) fn get(&mut self, tv: TypeVar) -> &mut PartialType { let parent = &mut self.parent; for i in parent.len()..=tv.0 { @@ -145,289 +192,283 @@ impl UnionFind { &mut parent[tv.0] } - #[allow(unused)] fn type_of(&mut self, var: TypeVar) -> Option { match self.find(var) { PartialType::Variable(_) => None, - PartialType::Struct { name, .. } => Some(Type::StructRef(name)), - PartialType::Number => Some(Type::Int), + PartialType::Record { name, .. } => Some(Type::StructRef(name)), + PartialType::Int => Some(Type::Int), + } + } + + fn type_of_partial_type(&mut self, var: PartialType) -> Option { + match self.find_partial_type(var) { + PartialType::Variable(_) => None, + PartialType::Record { name, .. } => Some(Type::StructRef(name)), + PartialType::Int => Some(Type::Int), } } - fn callback(&mut self, tv: TypeVar) -> impl std::future::Future { + /// Wait for when tv is unified with something. + fn wait_for_unification(&mut self, tv: TypeVar) -> impl Future { let future = CompletableFuture::::new(); let callbacks = &mut self.callbacks; - for _i in callbacks.len()..=tv.0 { + for _ in callbacks.len()..=tv.0 { callbacks.push(vec![]); } callbacks[tv.0].push(future.signal()); + future } } -#[derive(Debug)] -enum Type { - StructRef(String), - Int, -} +mod ast { + use std::collections::HashMap; -#[derive(Debug)] -struct StructDef { - name: String, - fields: HashMap, -} + #[derive(Debug, Clone)] + pub enum Type { + StructRef(String), + Int, + } -#[derive(Debug)] -#[allow(unused)] -enum Expr { - StructInit { - name: String, - fields: HashMap, - }, - Add(Box, Box), - Number(u64), - Ident(String), - FieldAccess(Box, String), - Let { - name: String, - value: Box, - in_expr: Box, - }, - LetRec { - values: HashMap, - in_expr: Box, - }, -} + #[derive(Debug)] + pub struct StructDef { + pub name: String, + pub fields: HashMap, + } + + #[derive(Debug, Clone)] + pub enum Expr { + StructInit { + name: String, + fields: HashMap, + }, + Add(Box, Box), + Number(u64), + Ident(String), + FieldAccess(Box, String), + Let { + name: String, + value: Box, + in_expr: Box, + }, + LetRec { + values: HashMap, + in_expr: Box, + }, + } -#[derive(Debug)] -struct Ast { - items: Vec, - main: Expr, + #[derive(Debug)] + pub struct Program { + /// Items can occur in any order. Like in Rust! + pub items: Vec, + pub main: Expr, + } } -type RecordScopegraph<'sg> = - ScopeGraph<'sg, RecordLabel, RecordData, FutureCompleteness>; +type RecordScopegraph<'sg> = ScopeGraph<'sg, SgLabel, SgData, FutureCompleteness>; -struct TypeChecker<'sg> { +struct TypeChecker<'sg, 'ex> { sg: RecordScopegraph<'sg>, uf: RefCell, - ex: LocalExecutor<'sg>, // make executor part of type checker to allow run-and-forget semantics of type checking subtasks (see run_detached() usage sites) + ex: LocalExecutor<'ex>, } -impl<'a, 'sg> TypeChecker<'a> { - fn run_detached(&self, fut: impl std::future::Future + 'a) { - self.ex.spawn(fut).detach() - } - - #[async_recursion::async_recursion(?Send)] - async fn typecheck_expr<'ast: 'a>(tc: Rc, ast: &'ast Expr, scope: Scope) -> PartialType +impl<'sg, 'ex> TypeChecker<'sg, 'ex> +where + 'sg: 'ex, +{ + fn spawn(self: &Rc, f: impl FnOnce(Rc) -> F) where - 'a: 'async_recursion, + F: Future + 'ex, + T: 'ex, { - let ty_res = match ast { + self.ex.spawn(f(self.clone())).detach() + } + + #[async_recursion(?Send)] + async fn typecheck_expr<'a>(self: Rc, ast: &'ex Expr, scope: Scope) -> PartialType { + match ast { Expr::StructInit { name, fields } => { - let struct_scope = resolve_struct_ref(&tc.sg, scope, name).await; - let fld_futures = fields.iter().map(|(fld_name, fld_init)| { - let tc = tc.clone(); - async move { + let struct_scope = resolve_record_ref(&self.sg, scope, name).await; + + // defer typechecking of all the fields.. + for (field_name, field_initializer) in fields { + self.spawn(|this| async move { let (decl_type, init_type) = join( - resolve_member_ref(&tc.clone().sg, struct_scope, fld_name), - Self::typecheck_expr(tc.clone(), fld_init, scope), + resolve_member_ref(&this.sg, struct_scope, field_name), + this.clone().typecheck_expr(field_initializer, scope), ) .await; - tc.with_unifier(|uf| uf.unify(decl_type, init_type)); - } - }); + this.uf.borrow_mut().unify(decl_type, init_type); + }); + } // FIXME: field init exhaustiveness check omitted - // asynchronously check field initializations - fld_futures.for_each(|fut| tc.run_detached(fut)); - // .. but eagerly return the struct type - PartialType::Struct { + PartialType::Record { name: name.clone(), scope: struct_scope, } } Expr::Add(l, r) => { // type check left-hand-side asynchronously - let _scope = scope; - let _tc = tc.clone(); - tc.clone().run_detached(async move { - let l_ty = Self::typecheck_expr(_tc.clone(), l, _scope).await; - _tc.with_unifier(|uf| uf.unify(l_ty, PartialType::Number)); + self.spawn(|this| async move { + let l_ty = this.clone().typecheck_expr(l, scope).await; + this.uf.borrow_mut().unify(l_ty, PartialType::Int); }); // and type-check the right-hand-side asynchronously - let _tc = tc.clone(); - tc.clone().run_detached(async move { - let r_ty = Self::typecheck_expr(_tc.clone(), r, scope).await; - _tc.with_unifier(|uf| uf.unify(r_ty, PartialType::Number)); + self.spawn(|this| async move { + let r_ty = this.clone().typecheck_expr(r, scope).await; + this.uf.borrow_mut().unify(r_ty, PartialType::Int); }); // ... but immediately return the current type - PartialType::Number + PartialType::Int } - Expr::Number(_) => PartialType::Number, - Expr::Ident(var_name) => resolve_lexical_ref(&tc.sg, scope, var_name).await, + Expr::Number(_) => PartialType::Int, + Expr::Ident(var_name) => resolve_lexical_ref(&self.sg, scope, var_name).await, Expr::FieldAccess(inner, field) => { - let res = Self::typecheck_expr(tc.clone(), inner, scope).await; - let inner_expr_type = tc.with_unifier(|uf| uf.find_ty(res)); - Self::type_check_field_access(tc, inner_expr_type, field).await + let res = self.clone().typecheck_expr(inner, scope).await; + let inner_expr_type = self.uf.borrow_mut().find_partial_type(res); + self.type_check_field_access(inner_expr_type, field).await } Expr::Let { name, value, in_expr, } => { - let new_scope = tc + let new_scope = self .sg - .add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); - tc.sg - .add_edge(new_scope, RecordLabel::Lexical, scope) + .add_scope_default_with([SgLabel::Lexical, SgLabel::Definition]); + self.sg + .add_edge(new_scope, SgLabel::Lexical, scope) .expect("already closed"); - tc.sg.close(new_scope, &RecordLabel::Lexical); + self.sg.close(new_scope, &SgLabel::Lexical); - let ty_var = PartialType::Variable(tc.with_unifier(|uf| uf.fresh())); - tc.sg + let ty_var = PartialType::Variable(self.uf.borrow_mut().fresh()); + self.sg .add_decl( new_scope, - RecordLabel::Definition, - RecordData::VarDecl { + SgLabel::Definition, + SgData::VarDecl { name: name.clone(), ty: ty_var.clone(), }, ) .expect("already closed"); - tc.sg.close(new_scope, &RecordLabel::Definition); + self.sg.close(new_scope, &SgLabel::Definition); - // compute type of the variable - let _tc = tc.clone(); - let ty_var_future = async move { - let ty = Self::typecheck_expr(_tc.clone(), value, scope).await; - _tc.with_unifier(|uf| uf.unify(ty_var, ty)) - }; + self.spawn(|this| async move { + let ty = this.clone().typecheck_expr(value, scope).await; + this.uf.borrow_mut().unify(ty_var, ty); + }); // compute type of the result expression - let ty_res_future = Self::typecheck_expr(tc.clone(), in_expr, new_scope); - - // run both computations concurrently - // - // this construct is set up in this way to ensure - // the `unify(tv, ty_var)` can be executed before - // the result type is computed. - // this prevents deadlocks when the result type - // is dependent on the value type, for example - // ``` - // record A { x: int } - // let r = A { x = 42 } in r.x - // ``` - tc.run_detached(ty_var_future); - - // return - ty_res_future.await + self.clone().typecheck_expr(in_expr, new_scope).await } Expr::LetRec { values, in_expr } => { - let new_scope = tc + let new_scope = self .sg - .add_scope_default_with([RecordLabel::Lexical, RecordLabel::Definition]); - tc.sg - .add_edge(new_scope, RecordLabel::Lexical, scope) + .add_scope_default_with([SgLabel::Lexical, SgLabel::Definition]); + self.sg + .add_edge(new_scope, SgLabel::Lexical, scope) .expect("already closed"); - tc.sg.close(new_scope, &RecordLabel::Lexical); + self.sg.close(new_scope, &SgLabel::Lexical); - let _tc = tc.clone(); - values.iter().for_each(|(name, init)| { - let ty = PartialType::Variable(_tc.with_unifier(|uf| uf.fresh())); - _tc.sg + for (name, initializer_expr) in values { + let ty = PartialType::Variable(self.uf.borrow_mut().fresh()); + self.sg .add_decl( new_scope, - RecordLabel::Definition, - RecordData::VarDecl { + SgLabel::Definition, + SgData::VarDecl { name: name.clone(), ty: ty.clone(), }, ) .expect("already closed"); - let __tc = _tc.clone(); - _tc.run_detached(async move { - let ty_init = Self::typecheck_expr(__tc.clone(), init, new_scope).await; - __tc.with_unifier(|uf| uf.unify(ty, ty_init)) + + self.spawn(|this| async move { + let init_ty = this + .clone() + .typecheck_expr(initializer_expr, new_scope) + .await; + this.uf.borrow_mut().unify(ty, init_ty) }); - }); - tc.sg.close(new_scope, &RecordLabel::Definition); + } + self.sg.close(new_scope, &SgLabel::Definition); // compute type of the result expression - Self::typecheck_expr(tc.clone(), in_expr, new_scope).await + self.typecheck_expr(in_expr, new_scope).await } - }; - ty_res + } } - #[async_recursion::async_recursion(?Send)] async fn type_check_field_access( - tc: Rc>, - inner_expr_type: PartialType, - field: &String, - ) -> PartialType - where - 'a: 'async_recursion, - { - match inner_expr_type { - PartialType::Variable(tv) => { - let refined_type = tc.with_unifier(|uf| uf.callback(tv)).await; - Self::type_check_field_access(tc, refined_type, field).await + self: Rc, + mut inner_expr_type: PartialType, + field: &str, + ) -> PartialType { + loop { + match inner_expr_type { + PartialType::Variable(tv) => { + let fut = self.uf.borrow_mut().wait_for_unification(tv); + inner_expr_type = fut.await; + } + PartialType::Record { scope, .. } => { + break resolve_member_ref(&self.sg, scope, field).await + } + PartialType::Int => panic!("number has no field {field}"), } - PartialType::Struct { scope, .. } => resolve_member_ref(&tc.sg, scope, field).await, - PartialType::Number => panic!("number has no field {field}"), } } - fn init_structdef(&self, struct_def: &StructDef, scope: Scope) -> Scope { - let field_scope = self.sg.add_scope_default_with([RecordLabel::Definition]); + fn init_struct_def(&self, struct_def: &StructDef, scope: Scope) -> Scope { + let field_scope = self.sg.add_scope_default_with([SgLabel::Definition]); self.sg .add_decl( scope, - RecordLabel::TypeDefinition, - RecordData::TypeDecl { + SgLabel::TypeDefinition, + SgData::TypeDecl { name: struct_def.name.clone(), - ty: field_scope, + scope: field_scope, }, ) .expect("already closed"); - self.sg.close(scope, &RecordLabel::Definition); + self.sg.close(scope, &SgLabel::Definition); field_scope } - async fn typecheck_structdef( - tc: Rc>, + async fn typecheck_struct_def( + self: Rc, struct_def: &StructDef, scope: Scope, field_scope: Scope, ) { let fld_decl_futures = struct_def.fields.iter().map(|(fld_name, fld_ty)| { - let tc = tc.clone(); + let this = self.clone(); async move { let ty = match fld_ty { Type::StructRef(n) => { - let struct_scope = resolve_struct_ref(&tc.sg, scope, n).await; - PartialType::Struct { + let struct_scope = resolve_record_ref(&this.sg, scope, n).await; + PartialType::Record { name: n.clone(), scope: struct_scope, } } - Type::Int => PartialType::Number, + Type::Int => PartialType::Int, }; - tc.sg + this.sg .add_decl( field_scope, - RecordLabel::Definition, - RecordData::VarDecl { + SgLabel::Definition, + SgData::VarDecl { name: fld_name.clone(), ty, }, @@ -437,80 +478,84 @@ impl<'a, 'sg> TypeChecker<'a> { }); join_all(fld_decl_futures).await; - tc.sg.close(field_scope, &RecordLabel::Definition); + self.sg.close(field_scope, &SgLabel::Definition); } +} - fn with_unifier T>(&self, f: F) -> T { - f(&mut self.uf.borrow_mut()) +mod resolve { + use scopegraphs::{query_regex, Scope}; + use scopegraphs::resolve::Resolve; + use scopegraphs_macros::label_order; + use crate::{PartialType, RecordScopegraph, SgData, SgLabel}; + + pub async fn resolve_record_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &str) -> Scope { + let env = sg + .query() + .with_path_wellformedness(query_regex!(SgLabel: Lexical* TypeDefinition)) + .with_data_wellformedness(|record_data: &SgData| match record_data { + SgData::TypeDecl { + name: decl_name, .. + } => decl_name == ref_name, + _ => false, + }) + .with_label_order(label_order!(SgLabel: Definition < Lexical)) + .resolve(scope) + .await; + + *env.get_only_item() + .expect("record name did not resolve properly") + .data() + .expect_type_decl() } -} -async fn resolve_struct_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &String) -> Scope { - let env = sg - .query() - .with_path_wellformedness(query_regex!(RecordLabel: Lexical* TypeDefinition)) - .with_data_wellformedness(|record_data: &RecordData| match record_data { - RecordData::TypeDecl { - name: decl_name, .. - } => decl_name == ref_name, - _ => false, - }) - .with_label_order(label_order!(RecordLabel: Definition < Lexical)) - .resolve(scope) - .await; - - *env.get_only_item() - .expect("record name did not resolve properly") - .data() - .expect_type_decl() -} -async fn resolve_lexical_ref( - sg: &RecordScopegraph<'_>, - scope: Scope, - var_name: &String, -) -> PartialType { - let env = sg - .query() - .with_path_wellformedness(query_regex!(RecordLabel: Lexical* Definition)) - .with_label_order(label_order!(RecordLabel: Definition < Lexical)) - .with_data_wellformedness(|record_data: &RecordData| -> bool { - matches!(record_data, RecordData::VarDecl { name, .. } if name == var_name) - }) - .resolve(scope) - .await; - - env.get_only_item() - .expect("variable did not resolve uniquely") - .data() - .expect_var_decl() - .clone() -} + pub async fn resolve_lexical_ref( + sg: &RecordScopegraph<'_>, + scope: Scope, + var_name: &str, + ) -> PartialType { + let env = sg + .query() + .with_path_wellformedness(query_regex!(SgLabel: Lexical* Definition)) + .with_label_order(label_order!(SgLabel: Lexical < Definition)) + .with_data_wellformedness(|record_data: &SgData| -> bool { + matches!(record_data, SgData::VarDecl { name, .. } if name == var_name) + }) + .resolve(scope) + .await; + + env.get_only_item() + .expect("variable did not resolve uniquely") + .data() + .expect_var_decl() + .clone() + } -async fn resolve_member_ref( - sg: &RecordScopegraph<'_>, - struct_scope: Scope, - ref_name: &String, -) -> PartialType { - let env = sg - .query() - .with_path_wellformedness(query_regex!(RecordLabel: Definition)) - .with_data_wellformedness(|record_data: &RecordData| match record_data { - RecordData::VarDecl { - name: decl_name, .. - } => decl_name == ref_name, - _ => false, - }) - .resolve(struct_scope) - .await; - - env.get_only_item() - .expect("field name did not resolve properly") - .data() - .expect_var_decl() - .clone() + pub async fn resolve_member_ref( + sg: &RecordScopegraph<'_>, + struct_scope: Scope, + ref_name: &str, + ) -> PartialType { + let env = sg + .query() + .with_path_wellformedness(query_regex!(SgLabel: Definition)) + .with_data_wellformedness(|record_data: &SgData| match record_data { + SgData::VarDecl { + name: decl_name, .. + } => decl_name == ref_name, + _ => false, + }) + .resolve(struct_scope) + .await; + + env.get_only_item() + .expect("field name did not resolve properly") + .data() + .expect_var_decl() + .clone() + } } -fn typecheck(ast: &Ast) -> PartialType { +fn typecheck(ast: &Program) -> Option { let storage = Storage::new(); let sg = RecordScopegraph::new(&storage, FutureCompleteness::default()); let uf = RefCell::new(UnionFind::default()); @@ -518,112 +563,242 @@ fn typecheck(ast: &Ast) -> PartialType { let tc = Rc::new(TypeChecker { sg, uf, ex: local }); - let fut = async move { - let global_scope = tc.sg.add_scope_default_with([RecordLabel::TypeDefinition]); - - for item in &ast.items { - // synchronously init record decl - let field_scope = tc.init_structdef(item, global_scope); - tc.run_detached(TypeChecker::typecheck_structdef( - tc.clone(), - item, - global_scope, - field_scope, - )); - } + let global_scope = tc.sg.add_scope_default_with([SgLabel::TypeDefinition]); - // We can close for type definitions since the scopes for this are synchronously made - // even before the future is returned and spawned. - tc.sg.close(global_scope, &RecordLabel::TypeDefinition); + // typecheck all the type definitions somewhere in the future + for item in &ast.items { + // synchronously init record decl + let field_scope = tc.init_struct_def(item, global_scope); + tc.spawn(|this| this.typecheck_struct_def(item, global_scope, field_scope)); + } - let main_task = tc.ex.spawn(TypeChecker::typecheck_expr( - tc.clone(), - &ast.main, - global_scope, - )); + // We can close for type definitions since the scopes for this are synchronously + // made even before the future is returned and spawned. so, at this point, + // no new type definitions are made. + tc.sg.close(global_scope, &SgLabel::TypeDefinition); + // typecheck the main expression + let res = tc + .ex + .spawn(tc.clone().typecheck_expr(&ast.main, global_scope)); + + // extract result from task + let main_ty = smol::block_on(async { while !tc.ex.is_empty() { tc.ex.tick().await; } - // extract result from task - main_task.into_future().await - }; + res.await + }); + + tc.sg.render(&mut File::create("sg.dot").unwrap(), "sg").unwrap(); + println!("{:?}", tc.uf.borrow()); - smol::block_on(fut) + let resolved_main_ty = tc.uf.borrow_mut().type_of_partial_type(main_ty); + resolved_main_ty } -fn main() { - let example = Ast { - items: vec![ - StructDef { - name: "A".to_string(), - fields: { - let mut m = HashMap::new(); - m.insert("b".to_string(), Type::StructRef("B".to_string())); - m.insert("x".to_string(), Type::Int); - m - }, - }, - StructDef { - name: "B".to_string(), - fields: { - let mut m = HashMap::new(); - m.insert("a".to_string(), Type::StructRef("A".to_string())); - m.insert("x".to_string(), Type::Int); - m - }, - }, - ], - main: Expr::LetRec { - values: { - let mut m = HashMap::new(); - m.insert( - "a".to_string(), - Expr::StructInit { - name: "A".to_string(), - fields: { - let mut m = HashMap::new(); - m.insert("b".to_string(), Expr::Ident("b".to_string())); - m.insert( - "x".to_string(), - Expr::Add(Box::new(Expr::Number(4)), Box::new(Expr::Number(5))), - ); - m - }, - }, - ); - m.insert( - "b".to_string(), - Expr::StructInit { - name: "B".to_string(), - fields: { - let mut m = HashMap::new(); - m.insert("a".to_string(), Expr::Ident("a".to_string())); - m.insert( - "x".to_string(), - Expr::Add(Box::new(Expr::Number(4)), Box::new(Expr::Number(5))), - ); - m - }, - }, - ); - m - }, - in_expr: Box::new(Expr::FieldAccess( - Box::new(Expr::FieldAccess( - Box::new(Expr::FieldAccess( - Box::new(Expr::Ident("b".to_string())), - "a".to_string(), - )), - "b".to_string(), - )), - "x".to_string(), - )), - }, - }; +mod parse { + use std::collections::HashMap; + use winnow::ascii::multispace0; + use winnow::combinator::{alt, delimited, opt, preceded, repeat, separated, terminated}; + use winnow::error::{ParserError, StrContext}; + + use winnow::prelude::*; + use winnow::seq; + use winnow::stream::AsChar; + use winnow::token::{one_of, take_while}; + use crate::ast::{Program, Expr, StructDef, Type}; + + fn ws<'a, F, O, E: ParserError<&'a str>>(inner: F) -> impl Parser<&'a str, O, E> + where + F: Parser<&'a str, O, E>, + { + delimited( + multispace0, + inner, + multispace0 + ) + } + + fn parse_ident(input: &mut &'_ str) -> PResult { + ws(( + one_of(|c: char| c.is_alpha() || c == '_'), + take_while(0.., |c: char| c.is_alphanum() || c == '_'), + ) + .recognize() + .verify(|i: &str| { + i != "in" && i != "new" && i != "letrec" && i != "record" + }) + ) + .parse_next(input) + .map(|i| i.to_string()) + } + + fn parse_int(input: &mut &'_ str) -> PResult { + repeat( + 1.., + terminated(one_of('0'..='9'), repeat(0.., '_').map(|()| ())), + ) + .map(|()| ()) + .recognize() + .parse_next(input) + .map(|i| i.parse().expect("not an integer")) + } + + fn parse_type(input: &mut &'_ str) -> PResult { + ws(alt(( + "int".value(Type::Int), + parse_ident.map(Type::StructRef), + ))).parse_next(input) + } + + fn parse_field_def(input: &mut &'_ str) -> PResult<(String, Type)> { + seq!( + _: multispace0, + parse_ident, + _: ws(":"), + parse_type, + _: multispace0, + ).parse_next(input) + } + + fn parse_field_defs(input: &mut &'_ str) -> PResult> { + terminated(separated(0.., ws(parse_field_def), ws(",")), opt(ws(","))).parse_next(input) + } + + fn parse_field(input: &mut &'_ str) -> PResult<(String, Expr)> { + seq!( + _: multispace0, + parse_ident, + _: ws(":"), + parse_expr, + _: multispace0, + ).parse_next(input) + } + + fn parse_fields(input: &mut &'_ str) -> PResult> { + terminated(separated(0.., ws(parse_field), ws(",")), opt(ws(","))).parse_next(input) + } + + fn parse_item(input: &mut &'_ str) -> PResult { + seq! {StructDef { + name: parse_ident, + // `_` fields are ignored when building the struct + _: ws("{"), + fields: parse_field_defs, + _: ws("}"), + }}.parse_next(input) + } + + fn parse_value(input: &mut &'_ str) -> PResult<(String, Expr)> { + seq!( + parse_ident, + _: ws("="), + parse_expr, + _: ws(";") + ).parse_next(input) + } + + fn parse_values(input: &mut &'_ str) -> PResult> { + repeat(0.., parse_value).parse_next(input) + } + + fn parse_basic_expr(input: &mut &'_ str) -> PResult { + alt(( + parse_int.map(Expr::Number), + parse_ident.map(Expr::Ident), + seq! { + _: ws("new"), + parse_ident, + // `_` fields are ignored when building the struct + _: ws("{"), + parse_fields, + _: ws("}"), + }.map(|(name, fields)| Expr::StructInit { name, fields }), + seq! { + _: ws("letrec"), + parse_values, + _: ws("in"), + parse_expr, + }.map(|(values, in_expr)| Expr::LetRec { values, in_expr: Box::new(in_expr) }), + seq! { + _: ws("("), + parse_expr, + _: ws(")"), + }.map(|(i, )| i), + )).context(StrContext::Label("parse expr")) + .parse_next(input) + } + + fn parse_expr(input: &mut &'_ str) -> PResult { + let first = ws(parse_basic_expr).parse_next(input)?; + let mut res = repeat(0.., (ws("."), parse_ident).map(|(_, i)| i)).fold(|| first.clone(), |acc, val| { + Expr::FieldAccess(Box::new(acc), val) + }); + + res.parse_next(input) + } + + enum ItemOrExpr { + Item(StructDef), + Expr(Expr), + } + + pub(crate) fn parse(mut input: &str) -> PResult { + let mut items = Vec::new(); + let mut main = None; + + while !input.is_empty() { + match ws( + alt(( + ws(preceded(ws("record"), parse_item.map(ItemOrExpr::Item))), + seq!( + _: ws("main"), + _: ws("="), + ws(parse_expr.map(ItemOrExpr::Expr)), + _: ws(";"), + ).map(|(i, )| i), + )).context(StrContext::Label("parse item")), + ) + .parse_next(&mut input)? + { + ItemOrExpr::Expr(e) => main = Some(e), + ItemOrExpr::Item(i) => items.push(i), + } + } + + Ok(Program { + items, + main: main.expect("no main"), + }) + } +} + +fn main() -> Result<(), Box> { + let example = parse::parse( + " +record A { + b: B, + x: int, +} +record B { + a: A, + x: int, +} + +main = letrec + a = new A {x: 4, b: b}; + b = new B {x: 3, a: a}; +in a.b.a.x; + + ", + ) + .map_err(|i| i.to_string())?; println!("Type of example is: {:?}", typecheck(&example)); + + Ok(()) } #[allow(unused)] @@ -753,7 +928,7 @@ mod completable_future { } impl CompletableFutureSignal { - fn mutate_self(&mut self, new_state: FutureState) -> bool { + fn mutate_self(&self, new_state: FutureState) -> bool { let mut internal = self.internal.lock().unwrap(); match internal.state { FutureState::Pending => { @@ -771,7 +946,7 @@ mod completable_future { /// will be rendered noop. /// /// Returns whether the call successfully mutates the future. - pub fn complete(&mut self, value: V) -> bool { + pub fn complete(&self, value: V) -> bool { self.mutate_self(FutureState::Completed(value)) } } diff --git a/scopegraphs/src/lib.rs b/scopegraphs/src/lib.rs index 6855240..214e5cb 100644 --- a/scopegraphs/src/lib.rs +++ b/scopegraphs/src/lib.rs @@ -24,6 +24,8 @@ pub use label::Label; #[cfg(feature = "dot")] mod render; +#[cfg(feature = "dot")] +pub use render::RenderScopeData; pub use scopegraphs_regular_expressions::*; diff --git a/scopegraphs/src/render.rs b/scopegraphs/src/render.rs index 69cf3db..5191420 100644 --- a/scopegraphs/src/render.rs +++ b/scopegraphs/src/render.rs @@ -9,6 +9,7 @@ use std::io; use std::io::Write; use std::path::Path; +/// Modifies how a scope is rendered based on user-defined scope data. pub trait RenderScopeData { /// Renders a scope (or probably rather, the data in a scope). /// Can return None if there's no data to render. From 5fb52a846aa6e209ace6979bce8556b77917247e Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Fri, 24 May 2024 09:48:40 +0200 Subject: [PATCH 16/22] clippy & fmt --- scopegraphs/examples/records.rs | 183 +++++++++++++++++--------------- 1 file changed, 99 insertions(+), 84 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 4e40516..a047e7d 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -1,9 +1,12 @@ use self::completable_future::{CompletableFuture, CompletableFutureSignal}; +use crate::ast::{Expr, Program, StructDef, Type}; +use crate::resolve::{resolve_lexical_ref, resolve_member_ref, resolve_record_ref}; use async_recursion::async_recursion; use futures::future::{join, join_all}; use scopegraphs::completeness::FutureCompleteness; +use scopegraphs::RenderScopeData; use scopegraphs::{Scope, ScopeGraph, Storage}; -use scopegraphs_macros::{Label}; +use scopegraphs_macros::Label; use smol::LocalExecutor; use std::cell::RefCell; use std::error::Error; @@ -11,9 +14,6 @@ use std::fmt::{Debug, Formatter}; use std::fs::File; use std::future::Future; use std::rc::Rc; -use crate::ast::{Expr, Program, StructDef, Type}; -use crate::resolve::{resolve_lexical_ref, resolve_member_ref, resolve_record_ref}; -use scopegraphs::RenderScopeData; #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] enum SgLabel { @@ -40,9 +40,9 @@ enum SgData { impl RenderScopeData for SgData { fn render(&self) -> Option { match self { - SgData::VarDecl { name, ty} => Some(format!("var {name}: {ty:?}")), - SgData::TypeDecl { name, scope} => Some(format!("record {name} -> {scope:?}")), - SgData::Nothing => None + SgData::VarDecl { name, ty } => Some(format!("var {name}: {ty:?}")), + SgData::TypeDecl { name, scope } => Some(format!("record {name} -> {scope:?}")), + SgData::Nothing => None, } } } @@ -192,6 +192,7 @@ impl UnionFind { &mut parent[tv.0] } + #[allow(unused)] fn type_of(&mut self, var: TypeVar) -> Option { match self.find(var) { PartialType::Variable(_) => None, @@ -209,7 +210,7 @@ impl UnionFind { } /// Wait for when tv is unified with something. - fn wait_for_unification(&mut self, tv: TypeVar) -> impl Future { + fn wait_for_unification(&mut self, tv: TypeVar) -> impl Future { let future = CompletableFuture::::new(); let callbacks = &mut self.callbacks; for _ in callbacks.len()..=tv.0 { @@ -217,7 +218,7 @@ impl UnionFind { } callbacks[tv.0].push(future.signal()); - + future } } @@ -243,10 +244,12 @@ mod ast { name: String, fields: HashMap, }, + #[allow(unused)] Add(Box, Box), Number(u64), Ident(String), FieldAccess(Box, String), + #[allow(unused)] Let { name: String, value: Box, @@ -483,12 +486,16 @@ where } mod resolve { - use scopegraphs::{query_regex, Scope}; + use crate::{PartialType, RecordScopegraph, SgData, SgLabel}; use scopegraphs::resolve::Resolve; + use scopegraphs::{query_regex, Scope}; use scopegraphs_macros::label_order; - use crate::{PartialType, RecordScopegraph, SgData, SgLabel}; - pub async fn resolve_record_ref(sg: &RecordScopegraph<'_>, scope: Scope, ref_name: &str) -> Scope { + pub async fn resolve_record_ref( + sg: &RecordScopegraph<'_>, + scope: Scope, + ref_name: &str, + ) -> Scope { let env = sg .query() .with_path_wellformedness(query_regex!(SgLabel: Lexical* TypeDefinition)) @@ -591,7 +598,9 @@ fn typecheck(ast: &Program) -> Option { res.await }); - tc.sg.render(&mut File::create("sg.dot").unwrap(), "sg").unwrap(); + tc.sg + .render(&mut File::create("sg.dot").unwrap(), "sg") + .unwrap(); println!("{:?}", tc.uf.borrow()); let resolved_main_ty = tc.uf.borrow_mut().type_of_partial_type(main_ty); @@ -604,21 +613,17 @@ mod parse { use winnow::combinator::{alt, delimited, opt, preceded, repeat, separated, terminated}; use winnow::error::{ParserError, StrContext}; + use crate::ast::{Expr, Program, StructDef, Type}; use winnow::prelude::*; use winnow::seq; use winnow::stream::AsChar; use winnow::token::{one_of, take_while}; - use crate::ast::{Program, Expr, StructDef, Type}; fn ws<'a, F, O, E: ParserError<&'a str>>(inner: F) -> impl Parser<&'a str, O, E> - where - F: Parser<&'a str, O, E>, + where + F: Parser<&'a str, O, E>, { - delimited( - multispace0, - inner, - multispace0 - ) + delimited(multispace0, inner, multispace0) } fn parse_ident(input: &mut &'_ str) -> PResult { @@ -627,12 +632,9 @@ mod parse { take_while(0.., |c: char| c.is_alphanum() || c == '_'), ) .recognize() - .verify(|i: &str| { - i != "in" && i != "new" && i != "letrec" && i != "record" - }) - ) - .parse_next(input) - .map(|i| i.to_string()) + .verify(|i: &str| i != "in" && i != "new" && i != "letrec" && i != "record")) + .parse_next(input) + .map(|i| i.to_string()) } fn parse_int(input: &mut &'_ str) -> PResult { @@ -640,27 +642,29 @@ mod parse { 1.., terminated(one_of('0'..='9'), repeat(0.., '_').map(|()| ())), ) - .map(|()| ()) - .recognize() - .parse_next(input) - .map(|i| i.parse().expect("not an integer")) + .map(|()| ()) + .recognize() + .parse_next(input) + .map(|i| i.parse().expect("not an integer")) } fn parse_type(input: &mut &'_ str) -> PResult { ws(alt(( "int".value(Type::Int), parse_ident.map(Type::StructRef), - ))).parse_next(input) + ))) + .parse_next(input) } fn parse_field_def(input: &mut &'_ str) -> PResult<(String, Type)> { seq!( - _: multispace0, - parse_ident, - _: ws(":"), - parse_type, - _: multispace0, - ).parse_next(input) + _: multispace0, + parse_ident, + _: ws(":"), + parse_type, + _: multispace0, + ) + .parse_next(input) } fn parse_field_defs(input: &mut &'_ str) -> PResult> { @@ -669,12 +673,13 @@ mod parse { fn parse_field(input: &mut &'_ str) -> PResult<(String, Expr)> { seq!( - _: multispace0, - parse_ident, - _: ws(":"), - parse_expr, - _: multispace0, - ).parse_next(input) + _: multispace0, + parse_ident, + _: ws(":"), + parse_expr, + _: multispace0, + ) + .parse_next(input) } fn parse_fields(input: &mut &'_ str) -> PResult> { @@ -683,21 +688,23 @@ mod parse { fn parse_item(input: &mut &'_ str) -> PResult { seq! {StructDef { - name: parse_ident, - // `_` fields are ignored when building the struct - _: ws("{"), - fields: parse_field_defs, - _: ws("}"), - }}.parse_next(input) + name: parse_ident, + // `_` fields are ignored when building the struct + _: ws("{"), + fields: parse_field_defs, + _: ws("}"), + }} + .parse_next(input) } fn parse_value(input: &mut &'_ str) -> PResult<(String, Expr)> { seq!( - parse_ident, - _: ws("="), - parse_expr, - _: ws(";") - ).parse_next(input) + parse_ident, + _: ws("="), + parse_expr, + _: ws(";") + ) + .parse_next(input) } fn parse_values(input: &mut &'_ str) -> PResult> { @@ -709,33 +716,41 @@ mod parse { parse_int.map(Expr::Number), parse_ident.map(Expr::Ident), seq! { - _: ws("new"), - parse_ident, - // `_` fields are ignored when building the struct - _: ws("{"), - parse_fields, - _: ws("}"), - }.map(|(name, fields)| Expr::StructInit { name, fields }), + _: ws("new"), + parse_ident, + // `_` fields are ignored when building the struct + _: ws("{"), + parse_fields, + _: ws("}"), + } + .map(|(name, fields)| Expr::StructInit { name, fields }), seq! { - _: ws("letrec"), - parse_values, - _: ws("in"), - parse_expr, - }.map(|(values, in_expr)| Expr::LetRec { values, in_expr: Box::new(in_expr) }), + _: ws("letrec"), + parse_values, + _: ws("in"), + parse_expr, + } + .map(|(values, in_expr)| Expr::LetRec { + values, + in_expr: Box::new(in_expr), + }), seq! { - _: ws("("), - parse_expr, - _: ws(")"), - }.map(|(i, )| i), - )).context(StrContext::Label("parse expr")) - .parse_next(input) + _: ws("("), + parse_expr, + _: ws(")"), + } + .map(|(i,)| i), + )) + .context(StrContext::Label("parse expr")) + .parse_next(input) } fn parse_expr(input: &mut &'_ str) -> PResult { let first = ws(parse_basic_expr).parse_next(input)?; - let mut res = repeat(0.., (ws("."), parse_ident).map(|(_, i)| i)).fold(|| first.clone(), |acc, val| { - Expr::FieldAccess(Box::new(acc), val) - }); + let mut res = repeat(0.., (ws("."), parse_ident).map(|(_, i)| i)).fold( + || first.clone(), + |acc, val| Expr::FieldAccess(Box::new(acc), val), + ); res.parse_next(input) } @@ -750,18 +765,18 @@ mod parse { let mut main = None; while !input.is_empty() { - match ws( - alt(( - ws(preceded(ws("record"), parse_item.map(ItemOrExpr::Item))), - seq!( + match ws(alt(( + ws(preceded(ws("record"), parse_item.map(ItemOrExpr::Item))), + seq!( _: ws("main"), _: ws("="), ws(parse_expr.map(ItemOrExpr::Expr)), _: ws(";"), - ).map(|(i, )| i), - )).context(StrContext::Label("parse item")), - ) - .parse_next(&mut input)? + ) + .map(|(i,)| i), + )) + .context(StrContext::Label("parse item"))) + .parse_next(&mut input)? { ItemOrExpr::Expr(e) => main = Some(e), ItemOrExpr::Item(i) => items.push(i), From e154027d67696228d7693231039b2a7e57a431f6 Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Fri, 24 May 2024 12:24:07 +0200 Subject: [PATCH 17/22] fix clippy --- scopegraphs-regular-expressions/src/compile.rs | 1 + scopegraphs/src/completeness/mod.rs | 2 +- scopegraphs/src/resolve/mod.rs | 5 ++++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scopegraphs-regular-expressions/src/compile.rs b/scopegraphs-regular-expressions/src/compile.rs index 070bd14..9038902 100644 --- a/scopegraphs-regular-expressions/src/compile.rs +++ b/scopegraphs-regular-expressions/src/compile.rs @@ -30,6 +30,7 @@ impl MatchState { /// This struct can either be turned into a /// * [`DynamicMatcher`](crate::dynamic::DynamicMatcher) to match on a regex that was compiled at runtime. /// * An implementation of [`RegexMatcher`](crate::RegexMatcher) generated using [emit](Automaton::emit). +/// /// This function can be called at compile time (through the `compile_regex!` macro) and it /// emits the Rust code that can match the regular expression. /// diff --git a/scopegraphs/src/completeness/mod.rs b/scopegraphs/src/completeness/mod.rs index bf7506f..8e43f75 100644 --- a/scopegraphs/src/completeness/mod.rs +++ b/scopegraphs/src/completeness/mod.rs @@ -14,7 +14,7 @@ //! queries. This is the most suitable choice for type checkers that need to do dynamic scheduling. //! Running queries can return an error, because scopes relevant to the query weren't closed yet. //! * [`FutureCompleteness`] is like [`ExplicitClose`], except queries can no longer error. Instead, -//! queries return a [`Future`](std::future::Future) that resolves when all scopes related to the query are closed. +//! queries return a [`Future`](std::future::Future) that resolves when all scopes related to the query are closed. mod future; pub use future::*; diff --git a/scopegraphs/src/resolve/mod.rs b/scopegraphs/src/resolve/mod.rs index a2d80b0..6d8f3d3 100644 --- a/scopegraphs/src/resolve/mod.rs +++ b/scopegraphs/src/resolve/mod.rs @@ -394,10 +394,13 @@ pub trait Resolve<'sg, 'rslv> { /// This depends on the [completeness strategy](crate::completeness::Completeness) used. /// /// * Using [`ImplicitClose`](crate::completeness::ImplicitClose) this is a simply a `Vec`. - /// Querying using this completeness strategy cannot fail. + /// Querying using this completeness strategy cannot fail. /// * Using [`ExplicitClose`](crate::completeness::ExplicitClose) this is a [`EdgesOrDelay`](crate::completeness::EdgesOrDelay). + /// /// Querying can fail, because a scope this query traverses wasn't closed yet. + /// /// Using [`FutureCompleteness`](crate::completeness::FutureCompleteness), this is a [`Future`](std::future::Future). + /// /// Querying can pend, because a scope this query traverses wasn't closed yet. type EnvContainer where From 8ca3d1cd1f498cfa1732d99827c0022d8988bd43 Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Fri, 24 May 2024 13:37:36 +0200 Subject: [PATCH 18/22] remove mentions of struct --- scopegraphs/examples/records.rs | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index a047e7d..3340360 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -72,8 +72,8 @@ pub struct TypeVar(usize); enum PartialType { /// A variable Variable(TypeVar), - /// A struct named `name` with a scope. - /// The scope contains the field of the struct. + /// A record named `name` with a scope. + /// The scope contains the field of the record. /// See "scopes as types" Record { name: String, scope: Scope }, /// A number type @@ -293,13 +293,13 @@ where async fn typecheck_expr<'a>(self: Rc, ast: &'ex Expr, scope: Scope) -> PartialType { match ast { Expr::StructInit { name, fields } => { - let struct_scope = resolve_record_ref(&self.sg, scope, name).await; + let record_scope = resolve_record_ref(&self.sg, scope, name).await; // defer typechecking of all the fields.. for (field_name, field_initializer) in fields { self.spawn(|this| async move { let (decl_type, init_type) = join( - resolve_member_ref(&this.sg, struct_scope, field_name), + resolve_member_ref(&this.sg, record_scope, field_name), this.clone().typecheck_expr(field_initializer, scope), ) .await; @@ -310,10 +310,10 @@ where // FIXME: field init exhaustiveness check omitted - // .. but eagerly return the struct type + // .. but eagerly return the record type PartialType::Record { name: name.clone(), - scope: struct_scope, + scope: record_scope, } } Expr::Add(l, r) => { @@ -430,14 +430,14 @@ where } } - fn init_struct_def(&self, struct_def: &StructDef, scope: Scope) -> Scope { + fn init_record_def(&self, record_def: &StructDef, scope: Scope) -> Scope { let field_scope = self.sg.add_scope_default_with([SgLabel::Definition]); self.sg .add_decl( scope, SgLabel::TypeDefinition, SgData::TypeDecl { - name: struct_def.name.clone(), + name: record_def.name.clone(), scope: field_scope, }, ) @@ -447,21 +447,21 @@ where field_scope } - async fn typecheck_struct_def( + async fn typecheck_record_def( self: Rc, - struct_def: &StructDef, + record_def: &StructDef, scope: Scope, field_scope: Scope, ) { - let fld_decl_futures = struct_def.fields.iter().map(|(fld_name, fld_ty)| { + let fld_decl_futures = record_def.fields.iter().map(|(fld_name, fld_ty)| { let this = self.clone(); async move { let ty = match fld_ty { Type::StructRef(n) => { - let struct_scope = resolve_record_ref(&this.sg, scope, n).await; + let record_scope = resolve_record_ref(&this.sg, scope, n).await; PartialType::Record { name: n.clone(), - scope: struct_scope, + scope: record_scope, } } Type::Int => PartialType::Int, @@ -539,7 +539,7 @@ mod resolve { pub async fn resolve_member_ref( sg: &RecordScopegraph<'_>, - struct_scope: Scope, + record_scope: Scope, ref_name: &str, ) -> PartialType { let env = sg @@ -551,7 +551,7 @@ mod resolve { } => decl_name == ref_name, _ => false, }) - .resolve(struct_scope) + .resolve(record_scope) .await; env.get_only_item() @@ -575,8 +575,8 @@ fn typecheck(ast: &Program) -> Option { // typecheck all the type definitions somewhere in the future for item in &ast.items { // synchronously init record decl - let field_scope = tc.init_struct_def(item, global_scope); - tc.spawn(|this| this.typecheck_struct_def(item, global_scope, field_scope)); + let field_scope = tc.init_record_def(item, global_scope); + tc.spawn(|this| this.typecheck_record_def(item, global_scope, field_scope)); } // We can close for type definitions since the scopes for this are synchronously @@ -689,7 +689,7 @@ mod parse { fn parse_item(input: &mut &'_ str) -> PResult { seq! {StructDef { name: parse_ident, - // `_` fields are ignored when building the struct + // `_` fields are ignored when building the record _: ws("{"), fields: parse_field_defs, _: ws("}"), @@ -718,7 +718,7 @@ mod parse { seq! { _: ws("new"), parse_ident, - // `_` fields are ignored when building the struct + // `_` fields are ignored when building the record _: ws("{"), parse_fields, _: ws("}"), From c5e3ae946153bbc5c0216e0e9d1257aea0ffd18b Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Fri, 24 May 2024 13:49:23 +0200 Subject: [PATCH 19/22] remove completable future --- scopegraphs/examples/records.rs | 231 ++------------------------------ 1 file changed, 11 insertions(+), 220 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 3340360..39bb2f0 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -1,4 +1,3 @@ -use self::completable_future::{CompletableFuture, CompletableFutureSignal}; use crate::ast::{Expr, Program, StructDef, Type}; use crate::resolve::{resolve_lexical_ref, resolve_member_ref, resolve_record_ref}; use async_recursion::async_recursion; @@ -14,6 +13,8 @@ use std::fmt::{Debug, Formatter}; use std::fs::File; use std::future::Future; use std::rc::Rc; +use smol::channel::{bounded, Sender}; + #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] enum SgLabel { @@ -91,7 +92,7 @@ pub struct UnionFind { /// A vec of signals for each type variable. /// Whenever type variable 0 is unified with anything, we go through /// the list at index 0 and notify each. - callbacks: Vec>>, + callbacks: Vec>>, } impl Debug for UnionFind { @@ -137,8 +138,8 @@ impl UnionFind { // FIXME: use rank heuristic in case right is a variable? *self.get(left) = right.clone(); if self.callbacks.len() > left.0 { - for fut in std::mem::take(&mut self.callbacks[left.0]) { - fut.complete(right.clone()); + for fut in self.callbacks[left.0].drain(..) { + let _ = fut.send_blocking(right.clone()); } } } @@ -211,15 +212,17 @@ impl UnionFind { /// Wait for when tv is unified with something. fn wait_for_unification(&mut self, tv: TypeVar) -> impl Future { - let future = CompletableFuture::::new(); let callbacks = &mut self.callbacks; for _ in callbacks.len()..=tv.0 { callbacks.push(vec![]); } - callbacks[tv.0].push(future.signal()); + let (tx, rx) = bounded(1); + callbacks[tv.0].push(tx); - future + async move { + rx.recv().await.expect("sender dropped") + } } } @@ -814,216 +817,4 @@ in a.b.a.x; println!("Type of example is: {:?}", typecheck(&example)); Ok(()) -} - -#[allow(unused)] -mod completable_future { - //! Copied and adapted from https://crates.io/crates/completable_future (due to dependency mismatch) - //! - //! # Completable Future - //! - //! Similar to Java's CompletableFuture, this crate provides a simple - //! future that can be completed and properly notified from elsewhere other - //! than the executor of the future. It is sutable for some blocking - //! tasks that could block the executor if we use a future directly in - //! an executor. - //! - //! A CompletableFuture is still a future and has all the combinators that - //! you can use to chain logic working on the result or the error. Also, - //! unlike Java and inherited from Rust's poll model future, some executor - //! needs to execute the CompletableFuture in order to get the result; the - //! thread or code that completes (or errors) the future will not execute - //! the logic chained after the future. - //! - //! The CompletableFuture uses Arc and Mutex to synchronize poll and completion, - //! so there's overhead for using it. - //! - //! # Example - //! ``` - //! extern crate futures; - //! extern crate completable_future; - //! - //! use futures::prelude::*; - //! use futures::executor::block_on; - //! use std::thread::spawn; - //! use std::thread::sleep; - //! use std::time::Duration; - //! use completable_future::CompletableFuture; - //! - //! fn main() { - //! let fut1 = CompletableFuture::::new(); - //! // we will give the signal to some worker for it to complete - //! let mut signal = fut1.signal(); - //! let fut2 = fut1.and_then(|s| { - //! // this will come from whoever completes the future - //! println!("in fut2: {}", s); - //! Ok("this comes from fut2".to_string()) - //! }); - //! - //! let j = spawn(move || { - //! println!("waiter thread: I'm going to block on fut2"); - //! let ret = block_on(fut2).unwrap(); - //! println!("waiter thread: fut2 completed with message -- {}", ret); - //! }); - //! - //! spawn(move || { - //! println!("worker thread: going to block for 1000 ms"); - //! sleep(Duration::from_millis(1000)); - //! signal.complete("this comes from fut1".to_string()); - //! println!("worker thread: completed fut1"); - //! }); - //! - //! j.join().unwrap(); - //! } - //! ``` - - use futures::future::Future; - use futures::task::{AtomicWaker, Context, Waker}; - use std::mem; - use std::pin::Pin; - use std::sync::{Arc, Mutex}; - use std::task::Poll; - - enum WakerWrapper { - Registered(AtomicWaker), - NotRegistered, - } - - impl WakerWrapper { - fn register(&mut self, waker: &Waker) { - match self { - WakerWrapper::Registered(_dont_care) => (), - WakerWrapper::NotRegistered => { - let w = AtomicWaker::new(); - w.register(waker); - *self = WakerWrapper::Registered(w) - } - } - } - - fn wake(&self) { - match *self { - WakerWrapper::Registered(ref w) => w.wake(), - WakerWrapper::NotRegistered => (), - }; - } - } - - enum FutureState { - Pending, - Completed(V), - Taken, - } - - impl FutureState { - fn swap(&mut self, new_val: FutureState) -> FutureState { - mem::replace(self, new_val) - } - - fn unwrap_val(&mut self) -> V { - match self.swap(FutureState::Taken) { - FutureState::Completed(val) => val, - _ => panic!("cannot unwrap because my state is not completed"), - } - } - } - - /// the state of the future; reference counted - struct SignalInternal { - waker: WakerWrapper, - state: FutureState, - } - - /// A handle to the future state. When you create a completable future, - /// you should also create a signal that somebody can use to complete - /// the future. - #[derive(Clone)] - pub struct CompletableFutureSignal { - internal: Arc>>, - } - - impl CompletableFutureSignal { - fn mutate_self(&self, new_state: FutureState) -> bool { - let mut internal = self.internal.lock().unwrap(); - match internal.state { - FutureState::Pending => { - internal.state.swap(new_state); - internal.waker.wake(); - true - } - _ => false, - } - } - - /// Complete the associated CompletableFuture. This method - /// can be called safely across multiple threads multiple times, - /// but only the winning call would mutate the future; other calls - /// will be rendered noop. - /// - /// Returns whether the call successfully mutates the future. - pub fn complete(&self, value: V) -> bool { - self.mutate_self(FutureState::Completed(value)) - } - } - - /// A CompletableFuture is a future that you can expect a result (or error) - /// from and chain logic on. You will need some executor to actively poll - /// the result. Executors provided by the futures crate are usually good - /// enough for common situations. - /// - /// If you use a custom executor, be careful that don't poll the CompletableFuture - /// after it has already completed (or errored) in previous polls. Doing so - /// will panic your executor. - pub struct CompletableFuture { - internal: Arc>>, - } - - impl CompletableFuture { - /// Construct a CompletableFuture. - pub fn new() -> CompletableFuture { - CompletableFuture { - internal: Arc::new(Mutex::new(SignalInternal { - waker: WakerWrapper::NotRegistered, - state: FutureState::Pending, - })), - } - } - - /// Construct a CompletableFuture that's already completed - /// with the value provided. - pub fn completed(val: V) -> CompletableFuture { - CompletableFuture { - internal: Arc::new(Mutex::new(SignalInternal { - waker: WakerWrapper::NotRegistered, - state: FutureState::Completed(val), - })), - } - } - - /// Get a CompletableFutureSignal that can be used to complete - /// or error this CompletableFuture. - pub fn signal(&self) -> CompletableFutureSignal { - CompletableFutureSignal { - internal: self.internal.clone(), - } - } - } - - impl Future for CompletableFuture { - type Output = V; - - fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll { - let mut signal = self.internal.lock().unwrap(); - signal.waker.register(ctx.waker()); - - let state = &mut signal.state; - match state { - FutureState::Pending => Poll::Pending, - FutureState::Taken => { - panic!("bug: the value has been taken, yet I'm still polled again") - } - FutureState::Completed(_) => Poll::Ready(state.unwrap_val()), - } - } - } -} +} \ No newline at end of file From 02b5649aa06e1f2ab91441d5206c31b6b3af46dc Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Fri, 24 May 2024 13:49:57 +0200 Subject: [PATCH 20/22] fmt&clippy --- scopegraphs/examples/records.rs | 9 +++------ scopegraphs/src/completeness/mod.rs | 6 +++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index 39bb2f0..d420f9a 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -6,6 +6,7 @@ use scopegraphs::completeness::FutureCompleteness; use scopegraphs::RenderScopeData; use scopegraphs::{Scope, ScopeGraph, Storage}; use scopegraphs_macros::Label; +use smol::channel::{bounded, Sender}; use smol::LocalExecutor; use std::cell::RefCell; use std::error::Error; @@ -13,8 +14,6 @@ use std::fmt::{Debug, Formatter}; use std::fs::File; use std::future::Future; use std::rc::Rc; -use smol::channel::{bounded, Sender}; - #[derive(Debug, Label, Copy, Clone, Hash, PartialEq, Eq)] enum SgLabel { @@ -220,9 +219,7 @@ impl UnionFind { let (tx, rx) = bounded(1); callbacks[tv.0].push(tx); - async move { - rx.recv().await.expect("sender dropped") - } + async move { rx.recv().await.expect("sender dropped") } } } @@ -817,4 +814,4 @@ in a.b.a.x; println!("Type of example is: {:?}", typecheck(&example)); Ok(()) -} \ No newline at end of file +} diff --git a/scopegraphs/src/completeness/mod.rs b/scopegraphs/src/completeness/mod.rs index 8e43f75..0dbb3d6 100644 --- a/scopegraphs/src/completeness/mod.rs +++ b/scopegraphs/src/completeness/mod.rs @@ -9,10 +9,10 @@ //! //! Currently, the module contains three safe implementations. //! * [`ImplicitClose`] is the easiest to use, and most likely the preferred choice for simple -//! sequential type checkers. +//! sequential type checkers. //! * [`ExplicitClose`] requires some manual bookkeeping, but allows more flexible handling of -//! queries. This is the most suitable choice for type checkers that need to do dynamic scheduling. -//! Running queries can return an error, because scopes relevant to the query weren't closed yet. +//! queries. This is the most suitable choice for type checkers that need to do dynamic scheduling. +//! Running queries can return an error, because scopes relevant to the query weren't closed yet. //! * [`FutureCompleteness`] is like [`ExplicitClose`], except queries can no longer error. Instead, //! queries return a [`Future`](std::future::Future) that resolves when all scopes related to the query are closed. From f464c85a81324986df6fab04125108545b6d7862 Mon Sep 17 00:00:00 2001 From: jdonszelmann Date: Fri, 24 May 2024 14:06:21 +0200 Subject: [PATCH 21/22] fix mr issues --- scopegraphs/examples/records.rs | 30 +++++++++++++++-------------- scopegraphs/src/completeness/mod.rs | 1 - 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/scopegraphs/examples/records.rs b/scopegraphs/examples/records.rs index d420f9a..226bf38 100644 --- a/scopegraphs/examples/records.rs +++ b/scopegraphs/examples/records.rs @@ -1,4 +1,4 @@ -use crate::ast::{Expr, Program, StructDef, Type}; +use crate::ast::{Expr, Program, RecordDef, Type}; use crate::resolve::{resolve_lexical_ref, resolve_member_ref, resolve_record_ref}; use async_recursion::async_recursion; use futures::future::{join, join_all}; @@ -84,13 +84,15 @@ enum PartialType { pub struct UnionFind { /// Records the parent of each type variable. /// Kind of assumes type variables are assigned linearly. - /// The "parent" of type variable 0 is stored at index 0 + /// + /// For example the "parent" of type variable 0 is stored at index 0 parent: Vec, /// Keep track of type variables we've given out vars: usize, /// A vec of signals for each type variable. - /// Whenever type variable 0 is unified with anything, we go through - /// the list at index 0 and notify each. + /// + /// For example, whenever type variable 0 is unified with anything, + /// we go through the list at index 0 and notify each. callbacks: Vec>>, } @@ -233,7 +235,7 @@ mod ast { } #[derive(Debug)] - pub struct StructDef { + pub struct RecordDef { pub name: String, pub fields: HashMap, } @@ -264,7 +266,7 @@ mod ast { #[derive(Debug)] pub struct Program { /// Items can occur in any order. Like in Rust! - pub items: Vec, + pub record_types: Vec, pub main: Expr, } } @@ -430,7 +432,7 @@ where } } - fn init_record_def(&self, record_def: &StructDef, scope: Scope) -> Scope { + fn init_record_def(&self, record_def: &RecordDef, scope: Scope) -> Scope { let field_scope = self.sg.add_scope_default_with([SgLabel::Definition]); self.sg .add_decl( @@ -449,7 +451,7 @@ where async fn typecheck_record_def( self: Rc, - record_def: &StructDef, + record_def: &RecordDef, scope: Scope, field_scope: Scope, ) { @@ -573,7 +575,7 @@ fn typecheck(ast: &Program) -> Option { let global_scope = tc.sg.add_scope_default_with([SgLabel::TypeDefinition]); // typecheck all the type definitions somewhere in the future - for item in &ast.items { + for item in &ast.record_types { // synchronously init record decl let field_scope = tc.init_record_def(item, global_scope); tc.spawn(|this| this.typecheck_record_def(item, global_scope, field_scope)); @@ -613,7 +615,7 @@ mod parse { use winnow::combinator::{alt, delimited, opt, preceded, repeat, separated, terminated}; use winnow::error::{ParserError, StrContext}; - use crate::ast::{Expr, Program, StructDef, Type}; + use crate::ast::{Expr, Program, RecordDef, Type}; use winnow::prelude::*; use winnow::seq; use winnow::stream::AsChar; @@ -686,8 +688,8 @@ mod parse { terminated(separated(0.., ws(parse_field), ws(",")), opt(ws(","))).parse_next(input) } - fn parse_item(input: &mut &'_ str) -> PResult { - seq! {StructDef { + fn parse_item(input: &mut &'_ str) -> PResult { + seq! {RecordDef { name: parse_ident, // `_` fields are ignored when building the record _: ws("{"), @@ -756,7 +758,7 @@ mod parse { } enum ItemOrExpr { - Item(StructDef), + Item(RecordDef), Expr(Expr), } @@ -784,7 +786,7 @@ mod parse { } Ok(Program { - items, + record_types: items, main: main.expect("no main"), }) } diff --git a/scopegraphs/src/completeness/mod.rs b/scopegraphs/src/completeness/mod.rs index 0dbb3d6..c96eea7 100644 --- a/scopegraphs/src/completeness/mod.rs +++ b/scopegraphs/src/completeness/mod.rs @@ -69,7 +69,6 @@ pub trait Completeness: Sealed { inner_scope_graph: &InnerScopeGraph, scope: Scope, ) { - // FIXME: has all scopes open! self.cmpl_new_scope(inner_scope_graph, scope) } From f1b638bfd48e3f1a8215bbdf6067aaf248b6acd0 Mon Sep 17 00:00:00 2001 From: Aron Zwaan Date: Fri, 24 May 2024 14:49:16 +0200 Subject: [PATCH 22/22] Fix init complete scope --- scopegraphs/src/completeness/future.rs | 7 +++++++ scopegraphs/src/completeness/mod.rs | 5 ++--- scopegraphs/src/completeness/unchecked.rs | 8 ++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/scopegraphs/src/completeness/future.rs b/scopegraphs/src/completeness/future.rs index 20ed841..4ca9274 100644 --- a/scopegraphs/src/completeness/future.rs +++ b/scopegraphs/src/completeness/future.rs @@ -43,6 +43,13 @@ impl Completeness self.explicit_close.cmpl_new_scope(inner_scope_graph, scope) } + fn cmpl_new_complete_scope(&self, _: &InnerScopeGraph, _: Scope) { + as CriticalEdgeBasedCompleteness>::init_scope_with( + self, + HashSet::new(), // init with empty label set to prevent extension + ) + } + type NewEdgeResult = as Completeness>::NewEdgeResult; fn cmpl_new_edge( diff --git a/scopegraphs/src/completeness/mod.rs b/scopegraphs/src/completeness/mod.rs index c96eea7..2de3dfd 100644 --- a/scopegraphs/src/completeness/mod.rs +++ b/scopegraphs/src/completeness/mod.rs @@ -17,6 +17,7 @@ //! queries return a [`Future`](std::future::Future) that resolves when all scopes related to the query are closed. mod future; + pub use future::*; mod critical_edge; @@ -68,9 +69,7 @@ pub trait Completeness: Sealed { &self, inner_scope_graph: &InnerScopeGraph, scope: Scope, - ) { - self.cmpl_new_scope(inner_scope_graph, scope) - } + ); type NewEdgeResult; fn cmpl_new_edge( diff --git a/scopegraphs/src/completeness/unchecked.rs b/scopegraphs/src/completeness/unchecked.rs index 06f4837..1284b7c 100644 --- a/scopegraphs/src/completeness/unchecked.rs +++ b/scopegraphs/src/completeness/unchecked.rs @@ -26,6 +26,14 @@ impl UncheckedCompleteness { impl Completeness for UncheckedCompleteness { fn cmpl_new_scope(&self, _: &InnerScopeGraph, _: Scope) {} + fn cmpl_new_complete_scope( + &self, + inner_scope_graph: &InnerScopeGraph, + scope: Scope, + ) { + self.cmpl_new_scope(inner_scope_graph, scope) + } + type NewEdgeResult = (); fn cmpl_new_edge(