Merge pull request #2657 from rtfeldman/i/2567

Register accessor closures when they are bound
This commit is contained in:
hafiz 2022-03-06 12:59:55 -05:00 committed by GitHub
commit de9da2d8a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 151 additions and 58 deletions

View file

@ -1085,7 +1085,7 @@ fn canonicalize_pending_def<'a>(
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let Pattern::Identifier(symbol) = loc_can_pattern.value { if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let &Closure(ClosureData { if let Closure(ClosureData {
function_type, function_type,
closure_type, closure_type,
closure_ext_var, closure_ext_var,
@ -1095,7 +1095,7 @@ fn canonicalize_pending_def<'a>(
loc_body: ref body, loc_body: ref body,
ref captured_symbols, ref captured_symbols,
.. ..
}) = &loc_can_expr.value }) = loc_can_expr.value
{ {
// Since everywhere in the code it'll be referred to by its defined name, // Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.) // remove its generated name from the closure map. (We'll re-insert it later.)
@ -1225,7 +1225,7 @@ fn canonicalize_pending_def<'a>(
// //
// Only defs of the form (foo = ...) can be closure declarations or self tail calls. // Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let Pattern::Identifier(symbol) = loc_can_pattern.value { if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let &Closure(ClosureData { if let Closure(ClosureData {
function_type, function_type,
closure_type, closure_type,
closure_ext_var, closure_ext_var,
@ -1235,7 +1235,7 @@ fn canonicalize_pending_def<'a>(
loc_body: ref body, loc_body: ref body,
ref captured_symbols, ref captured_symbols,
.. ..
}) = &loc_can_expr.value }) = loc_can_expr.value
{ {
// Since everywhere in the code it'll be referred to by its defined name, // Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.) // remove its generated name from the closure map. (We'll re-insert it later.)

View file

@ -138,17 +138,7 @@ pub enum Expr {
field: Lowercase, field: Lowercase,
}, },
/// field accessor as a function, e.g. (.foo) expr /// field accessor as a function, e.g. (.foo) expr
Accessor { Accessor(AccessorData),
/// accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set
name: Symbol,
function_var: Variable,
record_var: Variable,
closure_ext_var: Variable,
ext_var: Variable,
field_var: Variable,
field: Lowercase,
},
Update { Update {
record_var: Variable, record_var: Variable,
@ -217,6 +207,70 @@ pub struct ClosureData {
pub loc_body: Box<Loc<Expr>>, pub loc_body: Box<Loc<Expr>>,
} }
/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
/// Accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq)]
pub struct AccessorData {
pub name: Symbol,
pub function_var: Variable,
pub record_var: Variable,
pub closure_var: Variable,
pub closure_ext_var: Variable,
pub ext_var: Variable,
pub field_var: Variable,
pub field: Lowercase,
}
impl AccessorData {
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
let AccessorData {
name,
function_var,
record_var,
closure_var,
closure_ext_var,
ext_var,
field_var,
field,
} = self;
// IDEA: convert accessor from
//
// .foo
//
// into
//
// (\r -> r.foo)
let body = Expr::Access {
record_var,
ext_var,
field_var,
loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol))),
field,
};
let loc_body = Loc::at_zero(body);
let arguments = vec![(record_var, Loc::at_zero(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: Recursive::NotRecursive,
arguments,
loc_body: Box::new(loc_body),
}
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Field { pub struct Field {
pub var: Variable, pub var: Variable,
@ -735,15 +789,16 @@ pub fn canonicalize_expr<'a>(
) )
} }
ast::Expr::AccessorFunction(field) => ( ast::Expr::AccessorFunction(field) => (
Accessor { Accessor(AccessorData {
name: env.gen_unique_symbol(), name: env.gen_unique_symbol(),
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(),
}, }),
Output::default(), Output::default(),
), ),
ast::Expr::GlobalTag(tag) => { ast::Expr::GlobalTag(tag) => {

View file

@ -8,7 +8,7 @@ use roc_can::def::{Declaration, Def};
use roc_can::expected::Expected::{self, *}; use roc_can::expected::Expected::{self, *};
use roc_can::expected::PExpected; use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Field, WhenBranch}; use roc_can::expr::{AccessorData, ClosureData, Field, WhenBranch};
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_collections::all::{HumanIndex, ImMap, MutMap, SendMap}; use roc_collections::all::{HumanIndex, ImMap, MutMap, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
@ -762,15 +762,16 @@ pub fn constrain_expr(
[constraint, eq, record_con], [constraint, eq, record_con],
) )
} }
Accessor { Accessor(AccessorData {
name: closure_name, name: closure_name,
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,
} => { }) => {
let ext_var = *ext_var; let ext_var = *ext_var;
let ext_type = Variable(ext_var); let ext_type = Variable(ext_var);
let field_var = *field_var; let field_var = *field_var;
@ -793,16 +794,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,
@ -814,7 +823,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

@ -3882,44 +3882,24 @@ pub fn with_hole<'a>(
stmt stmt
} }
Accessor { Accessor(accessor_data) => {
name, let field_var = accessor_data.field_var;
function_var, let fresh_record_symbol = env.unique_symbol();
record_var,
closure_ext_var: _,
ext_var,
field_var,
field,
} => {
// 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 ClosureData {
name,
let arguments = vec![( function_type,
record_var, arguments,
Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), loc_body,
)]; ..
} = accessor_data.to_closure_data(fresh_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,
@ -3927,7 +3907,7 @@ pub fn with_hole<'a>(
Ok(_) => { Ok(_) => {
let raw_layout = return_on_layout_error!( let raw_layout = return_on_layout_error!(
env, env,
layout_cache.raw_from_var(env.arena, function_var, env.subs) layout_cache.raw_from_var(env.arena, function_type, env.subs)
); );
match raw_layout { match raw_layout {
@ -5445,6 +5425,18 @@ 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(accessor_data) => {
let fresh_record_symbol = env.unique_symbol();
register_noncapturing_closure(
env,
procs,
layout_cache,
*symbol,
accessor_data.to_closure_data(fresh_record_symbol),
);
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
);
}