Register accessor closures when they are bound

Previously we only registered record accessor closures in anonymous
contexts, where we assume they must already be specialized based on the
surrounding contexts. This is not true in general since one might bind
an accessor to a name.

Closes #2567
This commit is contained in:
ayazhafiz 2022-03-06 10:53:12 -05:00
parent d7c8c8de42
commit 3bff99b0a2
5 changed files with 154 additions and 26 deletions

View file

@ -144,6 +144,7 @@ pub enum Expr {
name: Symbol, name: Symbol,
function_var: Variable, function_var: Variable,
record_var: Variable, record_var: Variable,
closure_var: Variable,
closure_ext_var: Variable, closure_ext_var: Variable,
ext_var: Variable, ext_var: Variable,
field_var: Variable, field_var: Variable,
@ -740,6 +741,7 @@ pub fn canonicalize_expr<'a>(
function_var: var_store.fresh(), function_var: var_store.fresh(),
record_var: var_store.fresh(), record_var: var_store.fresh(),
ext_var: var_store.fresh(), ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
closure_ext_var: var_store.fresh(), closure_ext_var: var_store.fresh(),
field_var: var_store.fresh(), field_var: var_store.fresh(),
field: (*field).into(), field: (*field).into(),

View file

@ -769,7 +769,8 @@ pub fn constrain_expr(
function_var, function_var,
field, field,
record_var, record_var,
closure_ext_var: closure_var, closure_var,
closure_ext_var,
ext_var, ext_var,
field_var, field_var,
} => { } => {
@ -795,16 +796,24 @@ pub fn constrain_expr(
let lambda_set = Type::ClosureTag { let lambda_set = Type::ClosureTag {
name: *closure_name, name: *closure_name,
ext: *closure_var, ext: *closure_ext_var,
}; };
let closure_type = Type::Variable(*closure_var);
let function_type = Type::Function( let function_type = Type::Function(
vec![record_type], vec![record_type],
Box::new(lambda_set), Box::new(closure_type.clone()),
Box::new(field_type), Box::new(field_type),
); );
let cons = [ let cons = [
constraints.equal_types(
closure_type,
NoExpectation(lambda_set),
category.clone(),
region,
),
constraints.equal_types(function_type.clone(), expected, category.clone(), region), constraints.equal_types(function_type.clone(), expected, category.clone(), region),
constraints.equal_types( constraints.equal_types(
function_type, function_type,
@ -816,7 +825,14 @@ pub fn constrain_expr(
]; ];
constraints.exists_many( constraints.exists_many(
[*record_var, *function_var, *closure_var, field_var, ext_var], [
*record_var,
*function_var,
*closure_var,
*closure_ext_var,
field_var,
ext_var,
],
cons, cons,
) )
} }

View file

@ -816,6 +816,11 @@ impl<'a> Procs<'a> {
ret_var: Variable, ret_var: Variable,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
) -> Result<ProcLayout<'a>, RuntimeError> { ) -> Result<ProcLayout<'a>, RuntimeError> {
dbg!(env
.subs
.get_content_without_compacting(annotation)
.clone()
.dbg(env.subs));
let raw_layout = layout_cache let raw_layout = layout_cache
.raw_from_var(env.arena, annotation, env.subs) .raw_from_var(env.arena, annotation, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
@ -3088,6 +3093,53 @@ fn try_make_literal<'a>(
} }
} }
fn accessor_to_closure<'a>(
env: &mut Env<'a, '_>,
name: Symbol,
function_var: Variable,
record_var: Variable,
closure_var: Variable,
closure_ext_var: Variable,
ext_var: Variable,
field_var: Variable,
field: Lowercase,
) -> ClosureData {
// IDEA: convert accessor fromt
//
// .foo
//
// into
//
// (\r -> r.foo)
let record_symbol = env.unique_symbol();
let body = roc_can::expr::Expr::Access {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))),
field,
};
let loc_body = Loc::at_zero(body);
let arguments = vec![(
record_var,
Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)),
)];
ClosureData {
function_type: function_var,
closure_type: closure_var,
closure_ext_var,
return_type: field_var,
name,
captured_symbols: vec![],
recursive: roc_can::expr::Recursive::NotRecursive,
arguments,
loc_body: Box::new(loc_body),
}
}
pub fn with_hole<'a>( pub fn with_hole<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
can_expr: roc_can::expr::Expr, can_expr: roc_can::expr::Expr,
@ -3886,40 +3938,36 @@ pub fn with_hole<'a>(
name, name,
function_var, function_var,
record_var, record_var,
closure_ext_var: _, closure_var,
closure_ext_var,
ext_var, ext_var,
field_var, field_var,
field, field,
} => { } => {
// IDEA: convert accessor fromt let ClosureData {
// name,
// .foo function_type,
// arguments,
// into loc_body,
// ..
// (\r -> r.foo) } = accessor_to_closure(
let record_symbol = env.unique_symbol(); env,
let body = roc_can::expr::Expr::Access { name,
function_var,
record_var, record_var,
closure_var,
closure_ext_var,
ext_var, ext_var,
field_var, field_var,
loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))),
field, field,
}; );
let loc_body = Loc::at_zero(body);
let arguments = vec![(
record_var,
Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)),
)];
match procs.insert_anonymous( match procs.insert_anonymous(
env, env,
name, name,
function_var, function_type,
arguments, arguments,
loc_body, *loc_body,
CapturedSymbols::None, CapturedSymbols::None,
field_var, field_var,
layout_cache, layout_cache,
@ -5445,6 +5493,38 @@ pub fn from_can<'a>(
return from_can(env, variable, cont.value, procs, layout_cache); return from_can(env, variable, cont.value, procs, layout_cache);
} }
roc_can::expr::Expr::Accessor {
name,
function_var,
record_var,
closure_var,
closure_ext_var,
ext_var,
field_var,
field,
} => {
let closure_data = accessor_to_closure(
env,
name,
function_var,
record_var,
closure_var,
closure_ext_var,
ext_var,
field_var,
field,
);
register_noncapturing_closure(
env,
procs,
layout_cache,
*symbol,
closure_data,
);
return from_can(env, variable, cont.value, procs, layout_cache);
}
roc_can::expr::Expr::Var(original) => { roc_can::expr::Expr::Var(original) => {
// a variable is aliased, e.g. // a variable is aliased, e.g.
// //

View file

@ -5529,4 +5529,18 @@ mod solve_expr {
r#"a -> Effect a"#, r#"a -> Effect a"#,
) )
} }
#[test]
fn generalized_accessor_function_applied() {
infer_eq_without_problem(
indoc!(
r#"
returnFoo = .foo
returnFoo { foo: "foo" }
"#
),
"Str",
)
}
} }

View file

@ -14,7 +14,7 @@ use crate::helpers::wasm::assert_evals_to;
use indoc::indoc; use indoc::indoc;
#[cfg(test)] #[cfg(test)]
use roc_std::RocList; use roc_std::{RocList, RocStr};
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
@ -1058,3 +1058,19 @@ fn call_with_bad_record_runtime_error() {
"# "#
)) ))
} }
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn generalized_accessor() {
assert_evals_to!(
indoc!(
r#"
returnFoo = .foo
returnFoo { foo: "foo" }
"#
),
RocStr::from("foo"),
RocStr
);
}