Skip to content

Commit

Permalink
feat: Add the '..' operator to distribute the fields of a record
Browse files Browse the repository at this point in the history
  • Loading branch information
Marwes committed Oct 12, 2017
1 parent 285fe98 commit d6b03cc
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 41 deletions.
14 changes: 14 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ let id x = x
in { id }
```

The `..` operator can be used at the end of a record expression to take all fields of one record and fill the constructed record.

```f#, rust
let large_record = { x = 1, y = 2, name = "gluon" }
in
// Results in a record with type
// { field : Bool, x : Int, y : Int, name : String }
{
field = True,
..
large_record
}
```

### Array expressions

Arrays can be constructed with array literals.
Expand Down
13 changes: 12 additions & 1 deletion base/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ pub enum Expr<Id> {
typ: ArcType<Id>,
types: Vec<ExprField<Id, ArcType<Id>>>,
exprs: Vec<ExprField<Id, SpannedExpr<Id>>>,
base: Option<Box<SpannedExpr<Id>>>,
},
/// Tuple construction
Tuple {
Expand Down Expand Up @@ -361,6 +362,7 @@ pub fn walk_mut_expr<V: ?Sized + MutVisitor>(v: &mut V, e: &mut SpannedExpr<V::I
Expr::Record {
ref mut typ,
ref mut exprs,
ref mut base,
..
} => {
v.visit_typ(typ);
Expand All @@ -369,6 +371,9 @@ pub fn walk_mut_expr<V: ?Sized + MutVisitor>(v: &mut V, e: &mut SpannedExpr<V::I
v.visit_expr(expr);
}
}
if let Some(ref mut base) = *base {
v.visit_expr(base);
}
}
Expr::Tuple {
ref mut typ,
Expand Down Expand Up @@ -487,14 +492,20 @@ pub fn walk_expr<'a, V: ?Sized + Visitor<'a>>(v: &mut V, e: &'a SpannedExpr<V::I
}
}
Expr::Record {
ref typ, ref exprs, ..
ref typ,
ref exprs,
ref base,
..
} => {
v.visit_typ(typ);
for field in exprs {
if let Some(ref expr) = field.value {
v.visit_expr(expr);
}
}
if let Some(ref base) = *base {
v.visit_expr(base);
}
}
Expr::Tuple {
elems: ref exprs, ..
Expand Down
37 changes: 36 additions & 1 deletion check/src/typecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use base::fnv::{FnvMap, FnvSet};
use base::resolve;
use base::kind::{ArcKind, Kind, KindCache, KindEnv};
use base::merge;
use base::pos::{BytePos, Span, Spanned};
use base::pos::{self, BytePos, Span, Spanned};
use base::symbol::{Symbol, SymbolModule, SymbolRef, Symbols};
use base::types::{self, Alias, AliasData, AppVec, ArcType, Field, Generic};
use base::types::{PrimitiveEnv, RecordSelector, Type, TypeCache, TypeEnv, TypeVariable};
Expand Down Expand Up @@ -832,6 +832,7 @@ impl<'a> Typecheck<'a> {
ref mut typ,
ref mut types,
exprs: ref mut fields,
ref mut base,
} => {
let mut new_types: Vec<Field<_, _>> = Vec::with_capacity(types.len());

Expand All @@ -856,6 +857,38 @@ impl<'a> Typecheck<'a> {
new_fields.push(Field::new(field.name.value.clone(), typ));
}
}

if let Some(ref mut base) = *base {
let base_type = self.typecheck(base);
let base_type = self.remove_aliases(base_type);

let record_type = Type::poly_record(vec![], vec![], self.subs.new_var());
let base_type = self.unify_span(base.span, &record_type, base_type);

new_types.extend(
base_type
.type_field_iter()
.filter(|field| {
self.error_on_duplicated_field(
&mut duplicated_fields,
pos::spanned(base.span, field.name.clone()),
)
})
.cloned(),
);
new_fields.extend(
base_type
.row_iter()
.filter(|field| {
self.error_on_duplicated_field(
&mut duplicated_fields,
pos::spanned(base.span, field.name.clone()),
)
})
.cloned(),
);
}

let record_fields = new_fields
.iter()
.map(|f| f.name.clone())
Expand All @@ -870,6 +903,7 @@ impl<'a> Typecheck<'a> {
return Ok(TailCall::Type(typ.clone()));
}
};

let id_type = self.instantiate(&id_type);
let record_type = instantiate_generic_variables(
&mut self.named_variables,
Expand All @@ -878,6 +912,7 @@ impl<'a> Typecheck<'a> {
&record_type,
);
self.unify(&self.type_cache.record(new_types, new_fields), record_type)?;

*typ = id_type.clone();
Ok(TailCall::Type(id_type.clone()))
}
Expand Down
11 changes: 10 additions & 1 deletion check/tests/fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,6 @@ r.y
assert_err!(result, InvalidProjection(..));
}


#[test]
fn type_constructor_in_function_name() {
let _ = ::env_logger::init();
Expand All @@ -552,4 +551,14 @@ let Test x = x
"#;
let result = support::typecheck(text);
assert_err!(result, Message(..));
}

#[test]
fn record_base_not_record() {
let _ = ::env_logger::init();
let text = r#"
{ .. 1 }
"#;
let result = support::typecheck(text);
assert_unify_err!(result, TypeMismatch(..));
}
23 changes: 23 additions & 0 deletions check/tests/pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1107,3 +1107,26 @@ x

assert_eq!(result, Ok(Type::int()));
}


#[test]
fn record_expr_base() {
let _ = ::env_logger::init();
let text = r#"
let vec2 = { x = 1, y = 2 }
{ z = 3, .. vec2 }
"#;
let result = support::typecheck(text);

assert_eq!(
result,
Ok(Type::record(
vec![],
vec![
Field::new(intern("z"), typ("Int")),
Field::new(intern("x"), typ("Int")),
Field::new(intern("y"), typ("Int")),
]
))
);
}
9 changes: 8 additions & 1 deletion parser/src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ extern {
":" => Token::Colon,
"," => Token::Comma,
"." => Token::Dot,
".." => Token::DotDot,
"=" => Token::Equals,
"\\" => Token::Lambda,
"|" => Token::Pipe,
Expand Down Expand Up @@ -413,6 +414,11 @@ ValueBinding: ValueBinding<Id> = {
},
};

RecordExprBase: Option<SpannedExpr<Id>> = {
".." <SpExpr> => Some(<>),
=> None
};

AtomicExpr: Expr<Id> = {
<id: Ident> =>
Expr::Ident(new_ident(type_cache, id)),
Expand Down Expand Up @@ -440,7 +446,7 @@ AtomicExpr: Expr<Id> = {
exprs: elems,
}),

"{" <fields: Comma<FieldExpr>> "}" => {
"{" <fields: Comma<FieldExpr>> <base: RecordExprBase> "}" => {
let mut types = Vec::new();
let mut values = Vec::new();

Expand All @@ -463,6 +469,7 @@ AtomicExpr: Expr<Id> = {
typ: Type::hole(),
types: types,
exprs: values,
base: base.map(Box::new),
}
},
};
Expand Down
7 changes: 5 additions & 2 deletions parser/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pub enum Token<'input> {
Colon,
Comma,
Dot,
DotDot,
Equals,
Lambda,
Pipe,
Expand Down Expand Up @@ -86,6 +87,7 @@ impl<'input> fmt::Display for Token<'input> {
Colon => "Colon",
Comma => "Comma",
Dot => "Dot",
DotDot => "DotDot",
Equals => "Equal",
Lambda => "Lambda",
Pipe => "Pipe",
Expand Down Expand Up @@ -317,6 +319,7 @@ impl<'input> Tokenizer<'input> {

let token = match op {
"." => Token::Dot,
".." => Token::DotDot,
":" => Token::Colon,
"=" => Token::Equals,
"|" => Token::Pipe,
Expand Down Expand Up @@ -482,8 +485,8 @@ impl<'input> Iterator for Tokenizer<'input> {
Ok(None) => continue,
Err(err) => Some(Err(err)),
},
'#' if start.absolute == BytePos::from(0) &&
self.test_lookahead(|ch| ch == '!') =>
'#' if start.absolute == BytePos::from(0)
&& self.test_lookahead(|ch| ch == '!') =>
{
match self.shebang_line(start) {
Some(token) => Some(Ok(token)),
Expand Down
1 change: 1 addition & 0 deletions parser/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ fn doc_comment_on_record_field() {
value: Some(int(1)),
},
],
base: None,
})
)
}
Expand Down
1 change: 1 addition & 0 deletions parser/tests/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ pub fn record_a(
}
})
.collect(),
base: None,
})
}

Expand Down
Loading

0 comments on commit d6b03cc

Please sign in to comment.