Skip to content

Commit

Permalink
fix: Insert implicit arguments when none one has not been specified
Browse files Browse the repository at this point in the history
  • Loading branch information
Marwes committed Mar 10, 2018
1 parent f865777 commit c9a4226
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 36 deletions.
6 changes: 3 additions & 3 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,20 +492,20 @@ This different looking argument is an implicit argument which means that you do

Since searching all possible bindings currently in scope would introduce to many ambiguity errors the compiler does not search all bindings when trying to determine an implicit argument. Instead, whether a binding is considered for implicit resolution is controlled by the `@implicit` attribute. When marking a `let` binding as `@implicit` and this binding is in scope it will be considered as a candidate for all implicit arguments. The `@implicit` attribute can also be set on a `type` binding in which case it applied to all `let` bindings which has the type declared by the `type` binding.

```f#
```f#,rust
/// @implicit
type Test = | Test ()
let f y: [a] -> a -> a = y
let i = Test ()
// `i` gets selected as the implicit argument since `@implicit` is marked on the type and `i : Test`
f (Test ())
()
```

#### Passing implicit arguments explicitly

If you only use implicit functions as explained above then it might just seem like a different name for traits (Rust) or type classes (Haskell). While it is true that the main reason for implicit arguments is to emulate traits/type classes implicit arguments is more powerful than those approaches as it is also possible to override the implicit resolution and instead give the argument explicitly by prefixing the argument with `?`.

```f#
```f#,rust
let list @ { List } = import! std.list
// Make a custom equality function which returns true regardless of the elements of the list
let { (==) = (===) } = list.eq ?{ (==) = \x y -> True }
Expand Down
11 changes: 9 additions & 2 deletions base/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ pub struct Array<Id> {
#[derive(Clone, PartialEq, Debug)]
pub struct Lambda<Id> {
pub id: TypedIdent<Id>,
pub args: Vec<SpannedIdent<Id>>,
pub args: Vec<Argument<Id>>,
pub body: Box<SpannedExpr<Id>>,
}

Expand Down Expand Up @@ -356,6 +356,13 @@ impl<Id> Argument<Id> {
name,
}
}

pub fn implicit(name: SpannedIdent<Id>) -> Self {
Argument {
arg_type: ArgType::Implicit,
name,
}
}
}

#[derive(Clone, PartialEq, Debug)]
Expand Down Expand Up @@ -519,7 +526,7 @@ pub fn walk_mut_expr<V: ?Sized + MutVisitor>(v: &mut V, e: &mut SpannedExpr<V::I
Expr::Lambda(ref mut lambda) => {
v.visit_ident(&mut lambda.id);
for arg in &mut lambda.args {
v.visit_spanned_typed_ident(arg);
v.visit_spanned_typed_ident(&mut arg.name);
}
v.visit_expr(&mut lambda.body);
}
Expand Down
8 changes: 5 additions & 3 deletions check/src/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,14 +325,16 @@ where
typ: typ.clone(),
},
);
lambda.args.insert(implicit_arg_count, arg);
lambda
.args
.insert(implicit_arg_count, ast::Argument::implicit(arg));
implicit_arg_count += 1;
}
for (typ, arg) in
types::arg_iter(iter.typ).zip(&mut lambda.args[implicit_arg_count..])
{
arg.value.name =
self.stack_var(arg.value.name.clone(), expr.span, typ.clone());
arg.name.value.name =
self.stack_var(arg.name.value.name.clone(), expr.span, typ.clone());
}

self.visit_expr(&mut lambda.body);
Expand Down
79 changes: 60 additions & 19 deletions check/src/typecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use std::mem;
use itertools::Itertools;

use base::scoped_map::ScopedMap;
use base::ast::{DisplayEnv, Do, Expr, Literal, MutVisitor, Pattern, PatternField, SpannedExpr};
use base::ast::{AstType, SpannedIdent, SpannedPattern, TypeBinding, Typed, TypedIdent,
ValueBinding};
use base::ast::{Argument, AstType, DisplayEnv, Do, Expr, Literal, MutVisitor, Pattern,
PatternField, SpannedExpr, SpannedIdent, SpannedPattern, TypeBinding, Typed,
TypedIdent, ValueBinding};
use base::error::Errors;
use base::fnv::{FnvMap, FnvSet};
use base::metadata::{Metadata, MetadataEnv};
Expand Down Expand Up @@ -925,8 +925,12 @@ impl<'a> Typecheck<'a> {
.cloned()
.unwrap_or_else(|| self.subs.new_var());

let mut args = lambda.args.iter_mut().map(|arg| (ArgType::Explicit, arg));
let mut typ = self.typecheck_lambda(function_type, &mut args, &mut lambda.body);
let mut typ = self.typecheck_lambda(
function_type,
expr.span.start,
&mut lambda.args,
&mut lambda.body,
);

self.generalize_type(level, &mut typ);
lambda.id.typ = typ.clone();
Expand Down Expand Up @@ -1252,7 +1256,8 @@ impl<'a> Typecheck<'a> {
fn typecheck_lambda<'i>(
&mut self,
mut function_type: ArcType,
args: &mut Iterator<Item = (ArgType, &'i mut SpannedIdent<Symbol>)>,
before_args_pos: BytePos,
args: &mut Vec<Argument<Symbol>>,
body: &mut SpannedExpr<Symbol>,
) -> ArcType {
self.enter_scope();
Expand All @@ -1263,7 +1268,6 @@ impl<'a> Typecheck<'a> {
let mut return_type = function_type.clone();
let mut iter1 = function_arg_iter(self, function_type);

let mut args = args.peekable();
let mut next_type_arg = iter1.next();

let make_new_arg = |tc: &mut Self, span: Span<BytePos>, typ: &mut ArcType| {
Expand All @@ -1280,7 +1284,9 @@ impl<'a> Typecheck<'a> {
arg
};

while args.peek().is_some() || next_type_arg.is_some() {
let mut i = 0;

while i < args.len() || next_type_arg.is_some() {
let (type_implicit, arg_type) = match next_type_arg.take() {
Some((type_implicit, arg_type)) => (type_implicit, Some(arg_type)),
None => (ArgType::Explicit, None),
Expand All @@ -1289,9 +1295,34 @@ impl<'a> Typecheck<'a> {
match type_implicit {
ArgType::Implicit => {
let arg_type = arg_type.unwrap();
let id = match args.peek().map(|t| t.0) {
Some(ArgType::Implicit) => args.next().unwrap().1.value.name.clone(),
_ => Symbol::from(format!("implicit_arg")),
let id = match args.get(i).map(|t| t.arg_type) {
Some(ArgType::Implicit) => {
i += 1;
args[i - 1].name.value.name.clone()
}
_ => {
let id = Symbol::from(format!("implicit_arg"));
let pos = if i == 0 {
before_args_pos
} else {
args.get(i - 1)
.map(|arg| arg.name.span.end)
.unwrap_or(before_args_pos)
};
args.insert(
i,
Argument::implicit(pos::spanned2(
pos,
pos,
TypedIdent {
typ: arg_type.clone(),
name: id.clone(),
},
)),
);
i += 1;
id
}
};
arg_types.push(arg_type.clone());
iter1.tc.implicit_resolver.add_implicits_of_record(
Expand All @@ -1301,16 +1332,24 @@ impl<'a> Typecheck<'a> {
);
iter1.tc.stack_var(id, arg_type.clone());
}
ArgType::Explicit => match args.next() {
Some((ArgType::Implicit, arg)) => {
ArgType::Explicit => match args.get_mut(i) {
Some(&mut Argument {
arg_type: ArgType::Implicit,
name: ref arg,
}) => {
i += 1;
iter1.tc.error(
arg.span,
TypeError::Message(format!(
"Expected implicit argument but an explicit argument was specified"
)),
);
}
Some((ArgType::Explicit, arg)) => {
Some(&mut Argument {
arg_type: ArgType::Explicit,
name: ref mut arg,
}) => {
i += 1;
let arg_type = arg_type.unwrap_or_else(|| {
make_new_arg(iter1.tc, arg.span, &mut iter1.typ)
});
Expand Down Expand Up @@ -1650,15 +1689,17 @@ impl<'a> Typecheck<'a> {
}

let typ = self.new_skolem_scope_signature(&bind.resolved_type);
self.typecheck_lambda(typ, &mut None.into_iter(), &mut bind.expr)
self.typecheck_lambda(typ, bind.name.span.end, &mut bind.args, &mut bind.expr)
} else {
let typ = self.new_skolem_scope_signature(&bind.resolved_type);
let function_type = self.skolemize(&typ);

let mut args = bind.args
.iter_mut()
.map(|arg| (arg.arg_type, &mut arg.name));
self.typecheck_lambda(function_type, &mut args, &mut bind.expr)
self.typecheck_lambda(
function_type,
bind.name.span.end,
&mut bind.args,
&mut bind.expr,
)
};

debug!("let {:?} : {}", bind.name, typ);
Expand Down
10 changes: 5 additions & 5 deletions completion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,16 +587,16 @@ where
}
Expr::Lambda(ref lambda) => {
for arg in &lambda.args {
self.on_found.on_ident(&arg.value);
self.on_found.on_ident(&arg.name.value);
}

let selection = self.select_spanned(&lambda.args, |arg| arg.span);
let selection = self.select_spanned(&lambda.args, |arg| arg.name.span);
match selection {
(false, Some(arg)) => {
self.found = MatchState::Found(Match::Ident(
arg.span,
&arg.value.name,
&arg.value.typ,
arg.name.span,
&arg.name.value.name,
&arg.name.value.typ,
));
}
_ => self.visit_expr(&lambda.body),
Expand Down
2 changes: 1 addition & 1 deletion format/src/pretty_print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ where
let decl = chain![arena;
"\\",
arena.concat(lambda.args.iter().map(|arg| {
arena.text(arg.value.name.as_ref()).append(" ")
arena.text(arg.name.value.name.as_ref()).append(" ")
})),
"->"
];
Expand Down
9 changes: 8 additions & 1 deletion parser/src/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -538,10 +538,17 @@ AppExpr = {
Expr::App { func: Box::new(expr), implicit_args, args },
};


LambdaArgument: Argument<Id> = {
<name: SpannedIdent> => {
Argument::explicit(name)
}
};

InfixExpr = {
AppExpr,

"\\" <args: SpannedIdent+> "->" <body: SpExpr> =>
"\\" <args: LambdaArgument+> "->" <body: SpExpr> =>
Expr::Lambda(Lambda {
id: new_ident(type_cache, env.from_str("")),
args,
Expand Down
2 changes: 1 addition & 1 deletion parser/tests/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ pub fn lambda(name: &str, args: Vec<String>, body: SpExpr) -> SpExpr {
no_loc(Expr::Lambda(Lambda {
id: TypedIdent::new(intern(name)),
args: args.into_iter()
.map(|id| no_loc(TypedIdent::new(id)))
.map(|id| Argument::explicit(no_loc(TypedIdent::new(id))))
.collect(),
body: Box::new(body),
}))
Expand Down
11 changes: 11 additions & 0 deletions tests/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,17 @@ r#"
true
}

test_expr!{ implicit_argument_selection,
r#"
/// @implicit
type Test = | Test ()
let f y: [a] -> a -> () = ()
let i = Test ()
f (Test ())
"#,
()
}

#[test]
fn rename_types_after_binding() {
let _ = ::env_logger::try_init();
Expand Down
6 changes: 5 additions & 1 deletion vm/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,11 @@ impl<'a, 'e> Translator<'a, 'e> {
ast::Expr::Lambda(ref lambda) => self.new_lambda(
expr.span.start,
lambda.id.clone(),
lambda.args.iter().map(|arg| arg.value.clone()).collect(),
lambda
.args
.iter()
.map(|arg| arg.name.value.clone())
.collect(),
self.translate_alloc(&lambda.body),
expr.span,
),
Expand Down

0 comments on commit c9a4226

Please sign in to comment.