mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00
Merge pull request #2657 from rtfeldman/i/2567
Register accessor closures when they are bound
This commit is contained in:
commit
de9da2d8a5
6 changed files with 151 additions and 58 deletions
|
@ -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.)
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
//
|
//
|
||||||
|
|
|
@ -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",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue