From 656fbf8f73c3cb702eaad6d5d93e26a0c34b9e84 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Wed, 5 Jan 2022 11:52:19 +0100 Subject: [PATCH 01/40] feat: Start on polymorphic labels --- libflux/flux-core/Cargo.toml | 3 +- libflux/flux-core/src/errors.rs | 9 + libflux/flux-core/src/semantic/bootstrap.rs | 9 +- libflux/flux-core/src/semantic/convert.rs | 25 +- .../src/semantic/flatbuffers/types.rs | 11 +- .../flux-core/src/semantic/formatter/mod.rs | 2 +- libflux/flux-core/src/semantic/fresh.rs | 10 +- libflux/flux-core/src/semantic/mod.rs | 4 +- libflux/flux-core/src/semantic/nodes.rs | 114 ++++---- libflux/flux-core/src/semantic/sub.rs | 43 ++- libflux/flux-core/src/semantic/tests.rs | 14 +- .../flux-core/src/semantic/tests/labels.rs | 39 +++ libflux/flux-core/src/semantic/types.rs | 248 ++++++++++++++---- libflux/flux-core/src/semantic/vectorize.rs | 2 +- 14 files changed, 399 insertions(+), 134 deletions(-) create mode 100644 libflux/flux-core/src/semantic/tests/labels.rs diff --git a/libflux/flux-core/Cargo.toml b/libflux/flux-core/Cargo.toml index c7144190ef..6cb1963cfc 100644 --- a/libflux/flux-core/Cargo.toml +++ b/libflux/flux-core/Cargo.toml @@ -32,7 +32,8 @@ codespan-reporting = "0.11" csv = { version = "1.1", optional = true } derivative = "2.1.1" derive_more = { version = "0.99.17", default-features = false, features = [ - "display" + "display", + "from" ] } ena = "0.14" env_logger = "0.9" diff --git a/libflux/flux-core/src/errors.rs b/libflux/flux-core/src/errors.rs index c25c73888d..3c2307097f 100644 --- a/libflux/flux-core/src/errors.rs +++ b/libflux/flux-core/src/errors.rs @@ -178,6 +178,15 @@ pub struct Located { pub error: E, } +impl From for Located { + fn from(error: E) -> Self { + Self { + location: Default::default(), + error, + } + } +} + impl Located { pub(crate) fn map(self, f: impl FnOnce(E) -> F) -> Located { Located { diff --git a/libflux/flux-core/src/semantic/bootstrap.rs b/libflux/flux-core/src/semantic/bootstrap.rs index 52271cb551..d360743179 100644 --- a/libflux/flux-core/src/semantic/bootstrap.rs +++ b/libflux/flux-core/src/semantic/bootstrap.rs @@ -20,7 +20,9 @@ use crate::{ import::{Importer, Packages}, nodes::{self, Package, Symbol}, sub::{Substitutable, Substituter}, - types::{MonoType, PolyType, PolyTypeHashMap, Record, SemanticMap, Tvar, TvarKinds}, + types::{ + MonoType, PolyType, PolyTypeHashMap, Record, RecordLabel, SemanticMap, Tvar, TvarKinds, + }, Analyzer, PackageExports, }, }; @@ -311,7 +313,10 @@ fn add_record_to_map( } } env.insert( - field.k.clone().into(), + match &field.k { + RecordLabel::Concrete(s) => s.clone().into(), + RecordLabel::Variable(_) => bail!("Record contains variable labels"), + }, PolyType { vars: new_vars, cons: new_cons, diff --git a/libflux/flux-core/src/semantic/convert.rs b/libflux/flux-core/src/semantic/convert.rs index 57fe37d31c..b20940d9e3 100644 --- a/libflux/flux-core/src/semantic/convert.rs +++ b/libflux/flux-core/src/semantic/convert.rs @@ -578,12 +578,23 @@ impl<'a> Converter<'a> { } }; for prop in &rec.properties { - let name = match &prop.name { - ast::PropertyKey::Identifier(id) => &id.name, - ast::PropertyKey::StringLit(lit) => &lit.value, - }; let property = types::Property { - k: types::Label::from(self.symbols.lookup(name)), + k: match &prop.name { + ast::PropertyKey::Identifier(id) => { + if id.name.len() == 1 && id.name.starts_with(char::is_uppercase) { + let tvar = tvars + .entry(id.name.clone()) + .or_insert_with(|| self.sub.fresh()) + .clone(); + tvar.into() + } else { + types::Label::from(self.symbols.lookup(&id.name)).into() + } + } + ast::PropertyKey::StringLit(lit) => { + types::Label::from(self.symbols.lookup(&lit.value)).into() + } + }, v: self.convert_monotype(&prop.monotype, tvars), }; r = MonoType::from(types::Record::Extension { @@ -2672,13 +2683,13 @@ mod tests { #[test] fn test_convert_monotype_record() { - let monotype = Parser::new("{ A with B: int }").parse_monotype(); + let monotype = Parser::new("{ A with b: int }").parse_monotype(); let mut m = BTreeMap::::new(); let got = convert_monotype(&monotype, &mut m, &mut sub::Substitution::default()).unwrap(); let want = MonoType::from(types::Record::Extension { head: types::Property { - k: types::Label::from("B"), + k: types::RecordLabel::from("b"), v: MonoType::INT, }, tail: MonoType::BoundVar(Tvar(0)), diff --git a/libflux/flux-core/src/semantic/flatbuffers/types.rs b/libflux/flux-core/src/semantic/flatbuffers/types.rs index fd93212b8e..077b872c0d 100644 --- a/libflux/flux-core/src/semantic/flatbuffers/types.rs +++ b/libflux/flux-core/src/semantic/flatbuffers/types.rs @@ -12,7 +12,7 @@ use crate::semantic::{ nodes::Symbol, import::Packages, types::{CollectionType, - Label, + RecordLabel, Collection, Dictionary, Function, @@ -186,12 +186,12 @@ fn from_table(table: flatbuffers::Table, t: fb::MonoType) -> Option { } fb::MonoType::Fun => { let opt: Option = fb::Fun::init_from_table(table).into(); - Some(MonoType::fun(opt?)) + Some(MonoType::from(opt?)) } fb::MonoType::Record => fb::Record::init_from_table(table).into(), fb::MonoType::Dict => { let opt: Option = fb::Dict::init_from_table(table).into(); - Some(MonoType::dict(opt?)) + Some(MonoType::from(opt?)) } fb::MonoType::NONE => None, _ => unreachable!("Unknown type from table"), @@ -265,7 +265,7 @@ impl From> for Option { impl From> for Option { fn from(t: fb::Prop) -> Option { Some(Property { - k: Label::from(t.k()?), + k: RecordLabel::from(t.k()?), v: from_table(t.v()?, t.v_type())?, }) } @@ -479,6 +479,7 @@ pub fn build_type( match t { MonoType::Error => unreachable!(), MonoType::Builtin(typ) => build_basic_type(builder, typ), + MonoType::Label(_) => build_basic_type(builder, &BuiltinType::String), MonoType::BoundVar(tvr) | MonoType::Var(tvr) => { let offset = build_var(builder, *tvr); (offset.as_union_value(), fb::MonoType::Var) @@ -602,7 +603,7 @@ fn build_prop<'a>( prop: &Property, ) -> flatbuffers::WIPOffset> { let (off, typ) = build_type(builder, &prop.v); - let k = builder.create_string(prop.k.as_symbol().full_name()); + let k = builder.create_string(&prop.k.to_string()); fb::Prop::create( builder, &fb::PropArgs { diff --git a/libflux/flux-core/src/semantic/formatter/mod.rs b/libflux/flux-core/src/semantic/formatter/mod.rs index 7c4da154ec..8850806e6e 100644 --- a/libflux/flux-core/src/semantic/formatter/mod.rs +++ b/libflux/flux-core/src/semantic/formatter/mod.rs @@ -234,7 +234,7 @@ impl Formatter { } fn format_property_type(&mut self, n: &semantic::types::Property) { - self.write_string(&n.k); + self.write_string(&n.k.to_string()); self.write_string(": "); self.format_monotype(&n.v); } diff --git a/libflux/flux-core/src/semantic/fresh.rs b/libflux/flux-core/src/semantic/fresh.rs index 361c1f3928..ea011fad42 100644 --- a/libflux/flux-core/src/semantic/fresh.rs +++ b/libflux/flux-core/src/semantic/fresh.rs @@ -7,7 +7,7 @@ use crate::semantic::{ sub::{merge, merge3, merge4, merge_collect}, types::{ Collection, Dictionary, Function, Kind, Label, MonoType, MonoTypeVecMap, PolyType, - Property, Record, SemanticMap, Tvar, TvarMap, + Property, Record, RecordLabel, SemanticMap, Tvar, TvarMap, }, }; @@ -46,6 +46,12 @@ pub trait Fresh { Self: Sized; } +impl Fresh for RecordLabel { + fn fresh_ref(&self, _: &mut Fresher, _: &mut TvarMap) -> Option { + None + } +} + impl Fresh for Label { fn fresh_ref(&self, _: &mut Fresher, _: &mut TvarMap) -> Option { None @@ -143,7 +149,7 @@ impl Fresh for PolyType { impl Fresh for MonoType { fn fresh_ref(&self, f: &mut Fresher, sub: &mut TvarMap) -> Option { match self { - MonoType::Error | MonoType::Builtin(_) => None, + MonoType::Error | MonoType::Builtin(_) | MonoType::Label(_) => None, MonoType::BoundVar(tvr) => tvr.fresh_ref(f, sub).map(MonoType::BoundVar), MonoType::Var(tvr) => tvr.fresh_ref(f, sub).map(MonoType::Var), MonoType::Collection(app) => app.fresh_ref(f, sub).map(MonoType::app), diff --git a/libflux/flux-core/src/semantic/mod.rs b/libflux/flux-core/src/semantic/mod.rs index 8d7210a092..0b016267c9 100644 --- a/libflux/flux-core/src/semantic/mod.rs +++ b/libflux/flux-core/src/semantic/mod.rs @@ -45,7 +45,7 @@ use crate::{ infer::Constraints, nodes::Symbol, sub::Substitution, - types::{Label, MonoType, PolyType, PolyTypeHashMap, Property, Record}, + types::{MonoType, PolyType, PolyTypeHashMap, Property, Record, RecordLabel}, }, }; @@ -260,7 +260,7 @@ fn build_record( ); r = Record::Extension { head: Property { - k: Label::from(name.clone()), + k: RecordLabel::from(name.clone()), v: ty, }, tail: MonoType::record(r), diff --git a/libflux/flux-core/src/semantic/nodes.rs b/libflux/flux-core/src/semantic/nodes.rs index c4242fffdf..795385eedd 100644 --- a/libflux/flux-core/src/semantic/nodes.rs +++ b/libflux/flux-core/src/semantic/nodes.rs @@ -25,8 +25,8 @@ use crate::{ infer::{self, Constraint}, sub::{BindVars, Substitutable, Substituter, Substitution}, types::{ - self, Dictionary, Function, Kind, Label, MonoType, MonoTypeMap, PolyType, Tvar, - TvarKinds, + self, Dictionary, Function, Kind, Label, MonoType, MonoTypeMap, PolyType, RecordLabel, + Tvar, TvarKinds, }, }, }; @@ -184,7 +184,7 @@ pub enum Statement { } impl Statement { - fn apply(self, sub: &Substitution) -> Self { + fn apply(self, sub: &dyn Substituter) -> Self { match self { Statement::Expr(stmt) => Statement::Expr(stmt.apply(sub)), Statement::Variable(stmt) => Statement::Variable(Box::new(stmt.apply(sub))), @@ -206,7 +206,7 @@ pub enum Assignment { } impl Assignment { - fn apply(self, sub: &Substitution) -> Self { + fn apply(self, sub: &dyn Substituter) -> Self { match self { Assignment::Variable(assign) => Assignment::Variable(assign.apply(sub)), Assignment::Member(assign) => Assignment::Member(assign.apply(sub)), @@ -262,7 +262,7 @@ impl Expression { Expression::StringExpr(_) => MonoType::STRING, Expression::Integer(_) => MonoType::INT, Expression::Float(_) => MonoType::FLOAT, - Expression::StringLit(_) => MonoType::STRING, + Expression::StringLit(lit) => MonoType::Label(Label::from(lit.value.as_str())), Expression::Duration(_) => MonoType::DURATION, Expression::Uint(_) => MonoType::UINT, Expression::Boolean(_) => MonoType::BOOL, @@ -324,7 +324,7 @@ impl Expression { Expression::Error(_) => Ok(()), } } - fn apply(self, sub: &Substitution) -> Self { + fn apply(self, sub: &dyn Substituter) -> Self { match self { Expression::Identifier(e) => Expression::Identifier(e.apply(sub)), Expression::Array(e) => Expression::Array(Box::new(e.apply(sub))), @@ -372,12 +372,12 @@ where }; pkg.infer(&mut infer).map_err(|err| err.apply(infer.sub))?; - infer.env.apply_mut(infer.sub); + infer.env.apply_mut(&FinalizeTypes { sub: infer.sub }); if infer.errors.has_errors() { let sub = BindVars::new(infer.sub); for err in &mut infer.errors { - err.apply_mut(&sub); + err.apply_mut(&FinalizeTypes { sub: &sub }); } Err(infer.errors) } else { @@ -388,7 +388,27 @@ where /// Applies the substitution to the entire package. #[allow(missing_docs)] pub fn inject_pkg_types(pkg: Package, sub: &Substitution) -> Package { - pkg.apply(sub) + pkg.apply(&FinalizeTypes { sub }) +} + +struct FinalizeTypes<'a> { + sub: &'a dyn Substituter, +} + +impl Substituter for FinalizeTypes<'_> { + fn try_apply(&self, tvr: Tvar) -> Option { + self.sub.try_apply(tvr) + } + fn visit_type(&self, typ: &MonoType) -> Option { + match typ { + MonoType::Var(tvr) => { + let typ = self.sub.try_apply(*tvr)?; + Some(self.visit_type(&typ).unwrap_or(typ)) + } + MonoType::Label(_) => Some(MonoType::STRING), + _ => None, + } + } } #[derive(Debug, PartialEq, Clone)] @@ -407,7 +427,7 @@ impl Package { } Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.files = self.files.into_iter().map(|file| file.apply(sub)).collect(); self } @@ -458,7 +478,7 @@ impl File { infer.imports.clear(); Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.body = self.body.into_iter().map(|stmt| stmt.apply(sub)).collect(); self } @@ -510,7 +530,7 @@ impl OptionStmt { } } } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.assignment = self.assignment.apply(sub); self } @@ -529,7 +549,7 @@ impl BuiltinStmt { infer.env.add(self.id.name.clone(), self.typ_expr.clone()); Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -546,7 +566,7 @@ impl TestStmt { fn infer(&mut self, infer: &mut InferState<'_, '_>) -> Result<()> { self.assignment.infer(infer) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.assignment = self.assignment.apply(sub); self } @@ -577,7 +597,7 @@ impl TestCaseStmt { } Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.body = self.body.into_iter().map(|stmt| stmt.apply(sub)).collect(); self } @@ -596,7 +616,7 @@ impl ExprStmt { self.expression.infer(infer)?; Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.expression = self.expression.apply(sub); self } @@ -615,7 +635,7 @@ impl ReturnStmt { fn infer(&mut self, infer: &mut InferState<'_, '_>) -> Result { self.argument.infer(infer) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.argument = self.argument.apply(sub); self } @@ -686,7 +706,7 @@ impl VariableAssgn { infer.env.add(self.id.name.clone(), p); Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.init = self.init.apply(sub); self } @@ -702,7 +722,7 @@ pub struct MemberAssgn { } impl MemberAssgn { - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.member = self.member.apply(sub); self.init = self.init.apply(sub); self @@ -731,7 +751,7 @@ impl StringExpr { } Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.parts = self.parts.into_iter().map(|part| part.apply(sub)).collect(); self } @@ -745,7 +765,7 @@ pub enum StringExprPart { } impl StringExprPart { - fn apply(self, sub: &Substitution) -> Self { + fn apply(self, sub: &dyn Substituter) -> Self { match self { StringExprPart::Interpolated(part) => StringExprPart::Interpolated(part.apply(sub)), StringExprPart::Text(_) => self, @@ -770,7 +790,7 @@ pub struct InterpolatedPart { } impl InterpolatedPart { - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.expression = self.expression.apply(sub); self } @@ -806,7 +826,7 @@ impl ArrayExpr { self.typ = MonoType::arr(elt); Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.elements = self .elements @@ -852,7 +872,7 @@ impl DictExpr { Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.elements = self .elements @@ -1025,7 +1045,7 @@ impl FunctionExpr { ds } #[allow(missing_docs)] - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.params = self .params @@ -1091,7 +1111,7 @@ impl Block { } } } - fn apply(self, sub: &Substitution) -> Self { + fn apply(self, sub: &dyn Substituter) -> Self { match self { Block::Variable(assign, next) => { Block::Variable(Box::new(assign.apply(sub)), Box::new(next.apply(sub))) @@ -1114,7 +1134,7 @@ pub struct FunctionParameter { } impl FunctionParameter { - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { match self.default { Some(e) => { self.default = Some(e.apply(sub)); @@ -1243,7 +1263,7 @@ impl BinaryExpr { Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.left = self.left.apply(sub); self.right = self.right.apply(sub); @@ -1333,7 +1353,7 @@ impl CallExpr { Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.callee = self.callee.apply(sub); self.arguments = self @@ -1378,7 +1398,7 @@ impl ConditionalExpr { Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.test = self.test.apply(sub); self.consequent = self.consequent.apply(sub); self.alternate = self.alternate.apply(sub); @@ -1414,7 +1434,7 @@ impl LogicalExpr { ]); Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.left = self.left.apply(sub); self.right = self.right.apply(sub); self @@ -1455,7 +1475,7 @@ impl MemberExpr { let r = { self.typ = MonoType::Var(infer.sub.fresh()); let head = types::Property { - k: Label::from(self.property.to_owned()), + k: RecordLabel::from(self.property.to_owned()), v: self.typ.to_owned(), }; let tail = MonoType::Var(infer.sub.fresh()); @@ -1469,7 +1489,7 @@ impl MemberExpr { }]); Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.object = self.object.apply(sub); self @@ -1509,7 +1529,7 @@ impl IndexExpr { ]); Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.array = self.array.apply(sub); self.index = self.index.apply(sub); @@ -1544,7 +1564,7 @@ impl ObjectExpr { prop.value.infer(infer)?; r = MonoType::from(types::Record::Extension { head: types::Property { - k: Label::from(prop.key.name.clone()), + k: RecordLabel::from(prop.key.name.clone()), v: prop.value.type_of(), }, tail: r, @@ -1553,7 +1573,7 @@ impl ObjectExpr { self.typ = r; Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); if let Some(e) = self.with { self.with = Some(e.apply(sub)); @@ -1611,7 +1631,7 @@ impl UnaryExpr { } Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self.argument = self.argument.apply(sub); self @@ -1628,7 +1648,7 @@ pub struct Property { } impl Property { - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.value = self.value.apply(sub); self } @@ -1654,7 +1674,7 @@ impl IdentifierExpr { self.typ = t; Ok(()) } - fn apply(mut self, sub: &Substitution) -> Self { + fn apply(mut self, sub: &dyn Substituter) -> Self { self.typ = self.typ.apply(sub); self } @@ -1680,7 +1700,7 @@ impl BooleanLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -1697,7 +1717,7 @@ impl IntegerLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -1714,7 +1734,7 @@ impl FloatLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -1731,7 +1751,7 @@ impl RegexpLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -1748,7 +1768,7 @@ impl StringLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -1765,7 +1785,7 @@ impl UintLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -1782,7 +1802,7 @@ impl DateTimeLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } @@ -1816,7 +1836,7 @@ impl DurationLit { fn infer(&mut self) -> Result { Ok(()) } - fn apply(self, _: &Substitution) -> Self { + fn apply(self, _: &dyn Substituter) -> Self { self } } diff --git a/libflux/flux-core/src/semantic/sub.rs b/libflux/flux-core/src/semantic/sub.rs index 7988509663..fac382ad30 100644 --- a/libflux/flux-core/src/semantic/sub.rs +++ b/libflux/flux-core/src/semantic/sub.rs @@ -1,15 +1,17 @@ //! Substitutions during type inference. -use std::{borrow::Cow, cell::RefCell, iter::FusedIterator}; +use std::{borrow::Cow, cell::RefCell, collections::BTreeMap, fmt, iter::FusedIterator}; use crate::semantic::types::{union, Error, MonoType, PolyType, SubstitutionMap, Tvar, TvarKinds}; +use ena::unify::UnifyKey; + /// A substitution defines a function that takes a monotype as input /// and returns a monotype as output. The output type is interpreted /// as being equivalent to the input type. /// /// Substitutions are idempotent. Given a substitution *s* and an input /// type *x*, we have *s*(*s*(*x*)) = *s*(*x*). -#[derive(Clone, Debug, Default)] +#[derive(Clone, Default)] pub struct Substitution { table: RefCell, // TODO Add `snapshot`/`rollback_to` for `TvarKinds` (like `ena::UnificationTable`) so that @@ -18,6 +20,37 @@ pub struct Substitution { cons: RefCell, } +impl fmt::Debug for Substitution { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut roots = BTreeMap::new(); + + let mut table = self.table.borrow_mut(); + + #[derive(Debug)] + struct Root { + variables: Vec, + #[allow(dead_code)] + value: T, + } + for i in 0..table.len() as u32 { + let i = Tvar::from_index(i); + let root = table.find(i); + let root_node = roots.entry(root).or_insert_with(|| Root { + variables: Vec::new(), + value: table.probe_value(root), + }); + if i != root { + root_node.variables.push(i); + } + } + + f.debug_struct("Substitution") + .field("table", &roots) + .field("cons", &*self.cons.borrow()) + .finish() + } +} + /// An implementation of a /// (Disjoint-set](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) which is used to /// track which type variables are them same (unified) and which type they have unified to (if any) @@ -231,6 +264,12 @@ pub trait Substitutable { } } +impl Substitutable for String { + fn walk(&self, _sub: &dyn Substituter) -> Option { + None + } +} + impl Substitutable for Box where T: Substitutable, diff --git a/libflux/flux-core/src/semantic/tests.rs b/libflux/flux-core/src/semantic/tests.rs index f5bd96a2a6..cdbb83d1eb 100644 --- a/libflux/flux-core/src/semantic/tests.rs +++ b/libflux/flux-core/src/semantic/tests.rs @@ -53,18 +53,18 @@ fn parse_map(package: Option<&str>, m: HashMap<&str, &str>) -> PolyTypeHashMap Symbol::from(name), Some(package) => Symbol::from(name).with_package(package), }, - poly.unwrap(), - ); + poly, + ) }) .collect() } @@ -400,6 +400,8 @@ macro_rules! package { }} } +mod labels; + #[test] fn dictionary_literals() { test_infer! { diff --git a/libflux/flux-core/src/semantic/tests/labels.rs b/libflux/flux-core/src/semantic/tests/labels.rs new file mode 100644 index 0000000000..9b91cb2991 --- /dev/null +++ b/libflux/flux-core/src/semantic/tests/labels.rs @@ -0,0 +1,39 @@ +use super::*; + +#[test] +fn labels_simple() { + test_infer! { + env: map![ + "fill" => "(<-tables: [{ A with B: C }], ?column: B, ?value: D) => [{ A with B: D }] + where B: Label + " + ], + src: r#" + x = [{ a: 1 }] |> fill(column: "a", value: "x") + y = [{ a: 1, b: ""}] |> fill(column: "b", value: 1.0) + "#, + exp: map![ + "x" => "[{ a: string }]", + "y" => "[{ a: int, b: float }]", + ], + } +} + +#[test] +fn labels_dynamic_string() { + test_infer! { + env: map![ + "fill" => "(<-tables: [{ A with B: C }], ?column: B, ?value: D) => [{ A with B: D }] + where B: Label + " + ], + src: r#" + column = "" + "a" + x = [{ a: 1 }] |> fill(column: column, value: "x") + "#, + exp: map![ + "x" => "string", + "x" => "[{ a: string }]", + ], + } +} diff --git a/libflux/flux-core/src/semantic/types.rs b/libflux/flux-core/src/semantic/types.rs index 64378be33f..0531b8320f 100644 --- a/libflux/flux-core/src/semantic/types.rs +++ b/libflux/flux-core/src/semantic/types.rs @@ -30,6 +30,7 @@ pub type SemanticMapIter<'a, K, V> = std::collections::btree_map::Iter<'a, K, V> struct Unifier<'a, E = Error> { sub: &'a mut Substitution, + delayed_records: Vec<(Record, Record)>, errors: Errors, } @@ -37,11 +38,27 @@ impl<'a, E> Unifier<'a, E> { fn new(sub: &'a mut Substitution) -> Self { Unifier { sub, + delayed_records: Vec::new(), errors: Errors::new(), } } - fn finish(self, value: T) -> Result> { + fn finish(mut self, value: T) -> Result> + where + E: From, + { + if !self.delayed_records.is_empty() { + let mut sub_unifier = Unifier::new(self.sub); + while let Some((expected, actual)) = self.delayed_records.pop() { + let expected = expected.apply(sub_unifier.sub); + let actual = actual.apply(sub_unifier.sub); + expected.unify_now(&actual, &mut sub_unifier); + } + + self.errors + .extend(sub_unifier.errors.into_iter().map(E::from)); + } + if self.errors.has_errors() { Err(self.errors) } else { @@ -324,6 +341,7 @@ pub enum Kind { Comparable, Divisible, Equatable, + Label, Negatable, Nullable, Numeric, @@ -344,6 +362,7 @@ impl FromStr for Kind { "Numeric" => Kind::Numeric, "Comparable" => Kind::Comparable, "Equatable" => Kind::Equatable, + "Label" => Kind::Label, "Nullable" => Kind::Nullable, "Negatable" => Kind::Negatable, "Timeable" => Kind::Timeable, @@ -401,6 +420,8 @@ pub enum MonoType { #[display(fmt = "{}", _0)] Builtin(BuiltinType), + #[display(fmt = "\"{}\"", _0)] + Label(Label), #[display(fmt = "#{}", _0)] Var(Tvar), @@ -440,6 +461,7 @@ impl Serialize for MonoType { Regexp, Bytes, Var(Tvar), + Label(&'a Label), Arr(&'a MonoType), Dict(&'a Ptr), Record(&'a Ptr), @@ -469,6 +491,7 @@ impl Serialize for MonoType { CollectionType::Vector => MonoTypeSer::Vector(&p.arg), CollectionType::Stream => MonoTypeSer::Stream(&p.arg), }, + Self::Label(p) => MonoTypeSer::Label(p), Self::Dict(p) => MonoTypeSer::Dict(p), Self::Record(p) => MonoTypeSer::Record(p), Self::Fun(p) => MonoTypeSer::Fun(p), @@ -586,6 +609,7 @@ impl BuiltinType { Kind::Addable | Kind::Comparable | Kind::Equatable + | Kind::Label | Kind::Nullable | Kind::Basic | Kind::Stringable => Ok(()), @@ -647,9 +671,11 @@ impl Substitutable for MonoType { fn walk(&self, sub: &dyn Substituter) -> Option { match self { - MonoType::Error | MonoType::Builtin(_) | MonoType::BoundVar(_) | MonoType::Var(_) => { - None - } + MonoType::Error + | MonoType::Builtin(_) + | MonoType::Label(_) + | MonoType::BoundVar(_) + | MonoType::Var(_) => None, MonoType::Collection(app) => app.visit(sub).map(MonoType::app), MonoType::Dict(dict) => dict.visit(sub).map(MonoType::dict), MonoType::Record(obj) => obj.visit(sub).map(MonoType::record), @@ -778,7 +804,16 @@ impl MonoType { // An error has already occurred so assume everything is ok here so that we do not // create additional, spurious errors (MonoType::Error, _) | (_, MonoType::Error) => (), + (MonoType::Builtin(exp), MonoType::Builtin(act)) => exp.unify(*act, unifier), + + (MonoType::Label(l), MonoType::Label(r)) if l == r => {} + (MonoType::Label(_), MonoType::Label(_)) + | (MonoType::Builtin(BuiltinType::String), MonoType::Label(_)) + | (MonoType::Label(_), MonoType::Builtin(BuiltinType::String)) => { + return MonoType::STRING + } + (MonoType::Var(tv), MonoType::Var(tv2)) => { match (unifier.sub.try_apply(*tv), unifier.sub.try_apply(*tv2)) { (Some(self_), Some(actual)) => { @@ -805,10 +840,15 @@ impl MonoType { } None => tv.unify(t, unifier), }, + (MonoType::Collection(t), MonoType::Collection(s)) => t.unify(s, unifier), + (MonoType::Dict(t), MonoType::Dict(s)) => t.unify(s, unifier), + (MonoType::Record(t), MonoType::Record(s)) => t.unify(s, unifier), + (MonoType::Fun(t), MonoType::Fun(s)) => t.unify(s, unifier), + (exp, act) => { unifier.errors.push(Error::CannotUnify { exp: exp.clone(), @@ -827,6 +867,7 @@ impl MonoType { // TODO Should constrain bound vars as well, but we can't just store it in `cons` as // they would override constraints of free variables MonoType::BoundVar(_) => Ok(()), + MonoType::Label(_) => BuiltinType::String.constrain(with), MonoType::Var(tvr) => { tvr.constrain(with, cons); Ok(()) @@ -840,7 +881,9 @@ impl MonoType { fn contains(&self, tv: Tvar) -> bool { match self { - MonoType::Error | MonoType::Builtin(_) | MonoType::BoundVar(_) => false, + MonoType::Error | MonoType::Builtin(_) | MonoType::Label(_) | MonoType::BoundVar(_) => { + false + } MonoType::Var(tvr) => tv == *tvr, MonoType::Collection(app) => app.contains(tv), MonoType::Dict(dict) => dict.contains(tv), @@ -915,8 +958,8 @@ impl ena::unify::UnifyKey for Tvar { } impl ena::unify::UnifyValue for MonoType { type Error = ena::unify::NoError; - fn unify_values(_value1: &Self, _value2: &Self) -> Result { - unreachable!("We should never unify two values with each other within the substitution. If we reach this we did not resolve the variable before unifying") + fn unify_values(value1: &Self, value2: &Self) -> Result { + unreachable!("We should never unify two values with each other within the substitution. If we reach this we did not resolve the variable before unifying {} <=> {}", value1, value2) } } @@ -1121,7 +1164,7 @@ impl fmt::Display for Record { } } -fn collect_record(record: &Record) -> (RefMonoTypeVecMap<'_, Label>, Option<&MonoType>) { +fn collect_record(record: &Record) -> (RefMonoTypeVecMap<'_, RecordLabel>, Option<&MonoType>) { let mut a = RefMonoTypeVecMap::new(); let mut fields = record.fields(); @@ -1200,6 +1243,20 @@ impl Record { // self represents the expected type. // fn unify(&self, actual: &Self, unifier: &mut Unifier<'_>) { + let has_variable_label = |r: &Record| { + r.fields().any(|prop| match prop.k { + RecordLabel::Variable(v) => unifier.sub.try_apply(v).is_none(), + RecordLabel::Concrete(_) => false, + }) + }; + if has_variable_label(self) || has_variable_label(actual) { + unifier.delayed_records.push((self.clone(), actual.clone())); + return; + } + self.unify_now(actual, unifier) + } + + fn unify_now(&self, actual: &Self, unifier: &mut Unifier<'_>) { match (self, actual) { (Record::Empty, Record::Empty) => (), ( @@ -1255,6 +1312,19 @@ impl Record { tail: r, }, ) if a != b => { + match (a, b) { + (RecordLabel::Variable(a), RecordLabel::Concrete(b)) => { + if unifier.sub.try_apply(*a).is_none() { + a.unify(&MonoType::Label(b.clone()), unifier) + } + } + (RecordLabel::Concrete(a), RecordLabel::Variable(b)) => { + if unifier.sub.try_apply(*b).is_none() { + b.unify(&MonoType::Label(a.clone()), unifier) + } + } + _ => (), + } let var = unifier.sub.fresh(); let exp = MonoType::from(Record::Extension { head: Property { @@ -1390,10 +1460,7 @@ fn unify_in_context( ) where T: TypeLike, { - let mut sub_unifier = Unifier { - sub: unifier.sub, - errors: Errors::new(), - }; + let mut sub_unifier = Unifier::new(unifier.sub); exp.unify(act.typ(), &mut sub_unifier); unifier.errors.extend( @@ -1402,6 +1469,72 @@ fn unify_in_context( .into_iter() .map(|e| act.error(context(e))), ); + + unifier.delayed_records.extend(sub_unifier.delayed_records); +} + +/// Labels in records that are allowed be variables +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, derive_more::From)] +pub enum RecordLabel { + /// A variable label + Variable(Tvar), + /// A concrete label + Concrete(Label), +} + +impl Substitutable for RecordLabel { + fn walk(&self, sub: &dyn Substituter) -> Option { + match self { + Self::Variable(tvr) => sub.try_apply(*tvr).and_then(|new| match new { + MonoType::Label(l) => Some(Self::Concrete(l)), + MonoType::Var(l) => Some(Self::Variable(l)), + _ => None, + }), + Self::Concrete(_) => None, + } + } +} + +impl fmt::Display for RecordLabel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Variable(v) => v.fmt(f), + Self::Concrete(v) => v.fmt(f), + } + } +} + +impl PartialEq for RecordLabel { + fn eq(&self, other: &str) -> bool { + match self { + Self::Variable(_) => false, + Self::Concrete(l) => l == other, + } + } +} + +impl PartialEq<&str> for RecordLabel { + fn eq(&self, other: &&str) -> bool { + *self == **other + } +} + +impl From for RecordLabel { + fn from(name: String) -> Self { + Self::Concrete(Label::from(name)) + } +} + +impl From<&str> for RecordLabel { + fn from(name: &str) -> Self { + Self::Concrete(Label::from(name)) + } +} + +impl From for RecordLabel { + fn from(name: Symbol) -> Self { + Self::Concrete(Label::from(name)) + } } /// Wrapper around [`Symbol`] that ignores the package in comparisons. Allowing field lookups of @@ -1510,15 +1643,15 @@ impl Label { #[derive(Debug, Display, Clone, PartialEq, Serialize)] #[display(fmt = "{}:{}", k, v)] #[allow(missing_docs)] -pub struct Property { +pub struct Property { pub k: K, pub v: V, } impl Substitutable for Property where - K: Clone, - V: Substitutable, + K: Substitutable + Clone, + V: Substitutable + Clone, { fn walk(&self, sub: &dyn Substituter) -> Option { self.v.visit(sub).map(|v| Property { @@ -1654,11 +1787,9 @@ impl Function { ) -> Result<(), Errors> where T: TypeLike + Clone, + T::Error: From, { - let mut unifier = Unifier { - sub, - errors: Errors::new(), - }; + let mut unifier = Unifier::new(sub); self.unify(actual, &mut unifier); @@ -1801,7 +1932,7 @@ impl Function { fn constrain(&self, with: Kind, _: &mut TvarKinds) -> Result<(), Error> { Err(Error::CannotConstrain { - act: MonoType::fun(self.clone()), + act: MonoType::from(self.clone()), exp: with, }) } @@ -2015,11 +2146,11 @@ mod tests { Record::new( [ Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, } ], @@ -2032,11 +2163,11 @@ mod tests { Record::new( [ Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, } ], @@ -2210,11 +2341,11 @@ mod tests { retn: MonoType::from(Record::new( [ Property { - k: Label::from("x"), + k: RecordLabel::from("x"), v: MonoType::BoundVar(Tvar(0)), }, Property { - k: Label::from("y"), + k: RecordLabel::from("y"), v: MonoType::BoundVar(Tvar(1)), } ], @@ -2259,11 +2390,11 @@ mod tests { retn: MonoType::from(Record::new( [ Property { - k: Label::from("x"), + k: RecordLabel::from("x"), v: MonoType::BoundVar(Tvar(0)), }, Property { - k: Label::from("y"), + k: RecordLabel::from("y"), v: MonoType::BoundVar(Tvar(1)), } ], @@ -2291,11 +2422,11 @@ mod tests { retn: MonoType::from(Record::new( [ Property { - k: Label::from("x"), + k: RecordLabel::from("x"), v: MonoType::BoundVar(Tvar(0)), }, Property { - k: Label::from("y"), + k: RecordLabel::from("y"), v: MonoType::BoundVar(Tvar(1)), } ], @@ -2314,11 +2445,11 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, } ], @@ -2328,11 +2459,11 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, }, Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, } ], @@ -2344,19 +2475,19 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::INT, }, Property { - k: Label::from("c"), + k: RecordLabel::from("c"), v: MonoType::FLOAT, } ], @@ -2366,19 +2497,19 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("c"), + k: RecordLabel::from("c"), v: MonoType::FLOAT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::INT, }, Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, } ], @@ -2390,19 +2521,19 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::INT, }, Property { - k: Label::from("c"), + k: RecordLabel::from("c"), v: MonoType::FLOAT, } ], @@ -2412,19 +2543,19 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, }, Property { - k: Label::from("c"), + k: RecordLabel::from("c"), v: MonoType::FLOAT, } ], @@ -2436,11 +2567,11 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::STRING, } ], @@ -2450,11 +2581,11 @@ mod tests { MonoType::from(Record::new( [ Property { - k: Label::from("b"), + k: RecordLabel::from("b"), v: MonoType::INT, }, Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, } ], @@ -2465,7 +2596,7 @@ mod tests { // {a:int} MonoType::from(Record::Extension { head: Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, tail: MonoType::from(Record::Empty), @@ -2473,7 +2604,7 @@ mod tests { // {A with a:int} MonoType::from(Record::Extension { head: Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, tail: MonoType::BoundVar(Tvar(0)), @@ -2483,7 +2614,7 @@ mod tests { // {A with a:int} MonoType::from(Record::Extension { head: Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, tail: MonoType::BoundVar(Tvar(0)), @@ -2491,7 +2622,7 @@ mod tests { // {B with a:int} MonoType::from(Record::Extension { head: Property { - k: Label::from("a"), + k: RecordLabel::from("a"), v: MonoType::INT, }, tail: MonoType::BoundVar(Tvar(1)), @@ -2832,6 +2963,7 @@ mod tests { Kind::Numeric, Kind::Comparable, Kind::Equatable, + Kind::Label, Kind::Nullable, Kind::Record, Kind::Negatable, diff --git a/libflux/flux-core/src/semantic/vectorize.rs b/libflux/flux-core/src/semantic/vectorize.rs index ebd46d7bef..7c1ef5827b 100644 --- a/libflux/flux-core/src/semantic/vectorize.rs +++ b/libflux/flux-core/src/semantic/vectorize.rs @@ -217,7 +217,7 @@ impl FunctionExpr { loc: e.loc.clone(), typ: MonoType::from(types::Record::new( properties.iter().map(|p| types::Property { - k: Label::from(p.key.name.clone()), + k: Label::from(p.key.name.clone()).into(), v: p.value.type_of(), }), with.as_ref().map(|with| with.typ.clone()), From 54a2d5cc68b7fe6adf9145e34ca3932577e51171 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Mon, 21 Feb 2022 14:01:25 +0100 Subject: [PATCH 02/40] test: Use expect_test for better string comparisons --- libflux/flux/src/cffi.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/libflux/flux/src/cffi.rs b/libflux/flux/src/cffi.rs index bc59c800da..c86a0b88ec 100644 --- a/libflux/flux/src/cffi.rs +++ b/libflux/flux/src/cffi.rs @@ -723,23 +723,23 @@ mod tests { let mut ty = MonoType::from(Record::new( [ Property { - k: Label::from("a"), + k: Label::from("a").into(), v: MonoType::BoundVar(Tvar(4949)), }, Property { - k: Label::from("b"), + k: Label::from("b").into(), v: MonoType::BoundVar(Tvar(4949)), }, Property { - k: Label::from("e"), + k: Label::from("e").into(), v: MonoType::BoundVar(Tvar(4957)), }, Property { - k: Label::from("f"), + k: Label::from("f").into(), v: MonoType::BoundVar(Tvar(4957)), }, Property { - k: Label::from("g"), + k: Label::from("g").into(), v: MonoType::BoundVar(Tvar(4957)), }, ], @@ -770,9 +770,7 @@ vstr = v.str + "hello" v.normalize(&mut t); assert_eq!(format!("{}", t), "{B with int:int, sweet:A, str:string}"); - assert_eq!( - serde_json::to_string_pretty(&t).unwrap(), - r#"{ + expect_test::expect![[r#"{ "Record": { "type": "Extension", "head": { @@ -803,8 +801,8 @@ vstr = v.str + "hello" } } } -}"# - ); +}"#]] + .assert_eq(&serde_json::to_string_pretty(&t).unwrap()); } #[test] @@ -834,9 +832,7 @@ p = o.ethan v.normalize(&mut t); assert_eq!(format!("{}", t), "{B with int:int, ethan:A}"); - assert_eq!( - serde_json::to_string_pretty(&t).unwrap(), - r#"{ + expect_test::expect![[r#"{ "Record": { "type": "Extension", "head": { @@ -858,8 +854,8 @@ p = o.ethan } } } -}"# - ); +}"#]] + .assert_eq(&serde_json::to_string_pretty(&t).unwrap()); } #[test] From 6f304f0fc951feb4ceb09126001626a9c6ab0b85 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Mon, 21 Feb 2022 15:03:39 +0100 Subject: [PATCH 03/40] refactor: Simplify Function unification --- libflux/flux-core/src/semantic/infer.rs | 2 +- libflux/flux-core/src/semantic/nodes.rs | 47 +++++++------------ .../flux-core/src/semantic/tests/labels.rs | 17 +++++++ libflux/flux-core/src/semantic/types.rs | 20 ++++++++ 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/libflux/flux-core/src/semantic/infer.rs b/libflux/flux-core/src/semantic/infer.rs index 116ce196ef..d3bd738696 100644 --- a/libflux/flux-core/src/semantic/infer.rs +++ b/libflux/flux-core/src/semantic/infer.rs @@ -173,7 +173,7 @@ pub fn equal( ) -> Result>> { log::debug!("Constraint::Equal {:?}: {} <===> {}", loc.source, exp, act); exp.try_unify(act, sub).map_err(|error| { - log::debug!("Unify error: {} <=> {}", exp, act); + log::debug!("Unify error: {} <=> {} : {}", exp, act, error); Located { location: loc.clone(), diff --git a/libflux/flux-core/src/semantic/nodes.rs b/libflux/flux-core/src/semantic/nodes.rs index 795385eedd..d02abbd8f2 100644 --- a/libflux/flux-core/src/semantic/nodes.rs +++ b/libflux/flux-core/src/semantic/nodes.rs @@ -1310,44 +1310,29 @@ impl CallExpr { v: (p.type_of(), p.loc()), }); } + + let act = Function { + opt: MonoTypeMap::new(), + req, + pipe, + retn: (self.typ.clone(), &self.loc), + }; + match &*self.callee.type_of().apply_cow(infer.sub) { MonoType::Fun(func) => { - if let Err(err) = func.try_unify( - &Function { - opt: MonoTypeMap::new(), - req, - pipe, - retn: (self.typ.clone(), &self.loc), - }, - infer.sub, - ) { + if let Err(err) = func.try_unify(&act, infer.sub) { + log::debug!( + "Unify error: {} <=> {} : {}", + func, + act.map(|(typ, _)| typ), + err + ); infer.errors.extend(err.into_iter().map(Error::from)); } } callee => { // Constrain the callee to be a Function. - infer.equal( - callee, - &MonoType::from(Function { - opt: MonoTypeMap::new(), - req: req.into_iter().map(|(k, (v, _))| (k, v)).collect(), - pipe: pipe.map(|prop| types::Property { - k: prop.k, - v: prop.v.0, - }), - // The return type of a function call is the type of the call itself. - // Remind that, when two functions are unified, their return types are unified too. - // As an example take: - // f = (a) => a + 1 - // f(a: 0) - // The return type of `f` is `int`. - // The return type of `f(a: 0)` is `t0` (a fresh type variable). - // Upon unification a substitution "t0 => int" is created, so that the compiler - // can infer that, for instance, `f(a: 0) + 1` is legal. - retn: self.typ.clone(), - }), - &self.loc, - ); + infer.equal(callee, &MonoType::from(act.map(|(typ, _)| typ)), &self.loc); } } diff --git a/libflux/flux-core/src/semantic/tests/labels.rs b/libflux/flux-core/src/semantic/tests/labels.rs index 9b91cb2991..5d2eac4a71 100644 --- a/libflux/flux-core/src/semantic/tests/labels.rs +++ b/libflux/flux-core/src/semantic/tests/labels.rs @@ -19,6 +19,23 @@ fn labels_simple() { } } +#[test] +fn labels_unbound() { + test_infer! { + env: map![ + "f" => "(<-tables: [{ A with B: C }], ?value: D) => [{ A with B: D }] + where B: Label + " + ], + src: r#" + x = [{ a: 1, b: 2.0 }] |> f(value: "x") + "#, + exp: map![ + "x" => "[{ a: string, b: float }]", + ], + } +} + #[test] fn labels_dynamic_string() { test_infer! { diff --git a/libflux/flux-core/src/semantic/types.rs b/libflux/flux-core/src/semantic/types.rs index 0531b8320f..c479711f1d 100644 --- a/libflux/flux-core/src/semantic/types.rs +++ b/libflux/flux-core/src/semantic/types.rs @@ -1779,6 +1779,26 @@ impl Substitutable for Function { } } +impl Function { + pub(crate) fn map(self, mut f: impl FnMut(T) -> U) -> Function { + let Self { + opt, + req, + pipe, + retn, + } = self; + Function { + opt: opt.into_iter().map(|(k, v)| (k, f(v))).collect(), + req: req.into_iter().map(|(k, v)| (k, f(v))).collect(), + pipe: pipe.map(|prop| Property { + k: prop.k, + v: f(prop.v), + }), + retn: f(retn), + } + } +} + impl Function { pub(crate) fn try_unify( &self, From f4b7cf2f1ccd73b54141e6769b31873957587218 Mon Sep 17 00:00:00 2001 From: Markus Westerlind Date: Mon, 21 Feb 2022 16:02:53 +0100 Subject: [PATCH 04/40] fix: record labels in the face of bound variables --- libflux/flux-core/src/semantic/bootstrap.rs | 4 ++- libflux/flux-core/src/semantic/convert.rs | 2 +- libflux/flux-core/src/semantic/nodes.rs | 2 +- libflux/flux-core/src/semantic/types.rs | 32 +++++++++++++++------ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/libflux/flux-core/src/semantic/bootstrap.rs b/libflux/flux-core/src/semantic/bootstrap.rs index d360743179..cc6ca03a17 100644 --- a/libflux/flux-core/src/semantic/bootstrap.rs +++ b/libflux/flux-core/src/semantic/bootstrap.rs @@ -315,7 +315,9 @@ fn add_record_to_map( env.insert( match &field.k { RecordLabel::Concrete(s) => s.clone().into(), - RecordLabel::Variable(_) => bail!("Record contains variable labels"), + RecordLabel::BoundVariable(_) | RecordLabel::Variable(_) => { + bail!("Record contains variable labels") + } }, PolyType { vars: new_vars, diff --git a/libflux/flux-core/src/semantic/convert.rs b/libflux/flux-core/src/semantic/convert.rs index b20940d9e3..0dcc0bc160 100644 --- a/libflux/flux-core/src/semantic/convert.rs +++ b/libflux/flux-core/src/semantic/convert.rs @@ -586,7 +586,7 @@ impl<'a> Converter<'a> { .entry(id.name.clone()) .or_insert_with(|| self.sub.fresh()) .clone(); - tvar.into() + types::RecordLabel::BoundVariable(tvar) } else { types::Label::from(self.symbols.lookup(&id.name)).into() } diff --git a/libflux/flux-core/src/semantic/nodes.rs b/libflux/flux-core/src/semantic/nodes.rs index d02abbd8f2..c8a0efdb22 100644 --- a/libflux/flux-core/src/semantic/nodes.rs +++ b/libflux/flux-core/src/semantic/nodes.rs @@ -1654,7 +1654,7 @@ impl IdentifierExpr { fn infer(&mut self, infer: &mut InferState<'_, '_>) -> Result { let poly = infer.lookup(&self.loc, &self.name); - let (t, cons) = infer::instantiate(poly, infer.sub, self.loc.clone()); + let (t, cons) = infer::instantiate(poly.clone(), infer.sub, self.loc.clone()); infer.solve(&cons); self.typ = t; Ok(()) diff --git a/libflux/flux-core/src/semantic/types.rs b/libflux/flux-core/src/semantic/types.rs index c479711f1d..88fdc062c8 100644 --- a/libflux/flux-core/src/semantic/types.rs +++ b/libflux/flux-core/src/semantic/types.rs @@ -1246,7 +1246,7 @@ impl Record { let has_variable_label = |r: &Record| { r.fields().any(|prop| match prop.k { RecordLabel::Variable(v) => unifier.sub.try_apply(v).is_none(), - RecordLabel::Concrete(_) => false, + RecordLabel::BoundVariable(_) | RecordLabel::Concrete(_) => false, }) }; if has_variable_label(self) || has_variable_label(actual) { @@ -1474,22 +1474,39 @@ fn unify_in_context( } /// Labels in records that are allowed be variables -#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize, derive_more::From)] +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Serialize)] pub enum RecordLabel { /// A variable label Variable(Tvar), + /// A variable label + BoundVariable(Tvar), /// A concrete label Concrete(Label), } +impl From