diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1b5108dfe9..fad03f9099 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1085,7 +1085,7 @@ fn canonicalize_pending_def<'a>( // // 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 &Closure(ClosureData { + if let Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1095,7 +1095,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, 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, // 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. if let Pattern::Identifier(symbol) = loc_can_pattern.value { - if let &Closure(ClosureData { + if let Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1235,7 +1235,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, 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, // remove its generated name from the closure map. (We'll re-insert it later.) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index dc672693ff..8a54994f68 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -138,17 +138,7 @@ pub enum Expr { field: Lowercase, }, /// field accessor as a function, e.g. (.foo) expr - Accessor { - /// 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, - }, + Accessor(AccessorData), Update { record_var: Variable, @@ -217,6 +207,70 @@ pub struct ClosureData { pub loc_body: Box>, } +/// 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)] pub struct Field { pub var: Variable, @@ -735,15 +789,16 @@ pub fn canonicalize_expr<'a>( ) } ast::Expr::AccessorFunction(field) => ( - Accessor { + Accessor(AccessorData { name: env.gen_unique_symbol(), function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), + closure_var: var_store.fresh(), closure_ext_var: var_store.fresh(), field_var: var_store.fresh(), field: (*field).into(), - }, + }), Output::default(), ), ast::Expr::GlobalTag(tag) => { diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index a0e2f5009c..d9ca97f672 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -8,7 +8,7 @@ use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; 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_collections::all::{HumanIndex, ImMap, MutMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; @@ -762,15 +762,16 @@ pub fn constrain_expr( [constraint, eq, record_con], ) } - Accessor { + Accessor(AccessorData { name: closure_name, function_var, field, record_var, - closure_ext_var: closure_var, + closure_var, + closure_ext_var, ext_var, field_var, - } => { + }) => { let ext_var = *ext_var; let ext_type = Variable(ext_var); let field_var = *field_var; @@ -793,16 +794,24 @@ pub fn constrain_expr( let lambda_set = Type::ClosureTag { name: *closure_name, - ext: *closure_var, + ext: *closure_ext_var, }; + let closure_type = Type::Variable(*closure_var); + let function_type = Type::Function( vec![record_type], - Box::new(lambda_set), + Box::new(closure_type.clone()), Box::new(field_type), ); 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, @@ -814,7 +823,14 @@ pub fn constrain_expr( ]; 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, ) } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 559b9dae1f..67fd1b3432 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3882,44 +3882,24 @@ pub fn with_hole<'a>( stmt } - Accessor { - name, - function_var, - 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, - }; + Accessor(accessor_data) => { + let field_var = accessor_data.field_var; + let fresh_record_symbol = env.unique_symbol(); - let loc_body = Loc::at_zero(body); - - let arguments = vec![( - record_var, - Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), - )]; + let ClosureData { + name, + function_type, + arguments, + loc_body, + .. + } = accessor_data.to_closure_data(fresh_record_symbol); match procs.insert_anonymous( env, name, - function_var, + function_type, arguments, - loc_body, + *loc_body, CapturedSymbols::None, field_var, layout_cache, @@ -3927,7 +3907,7 @@ pub fn with_hole<'a>( Ok(_) => { let raw_layout = return_on_layout_error!( 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 { @@ -5445,6 +5425,18 @@ pub fn from_can<'a>( 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) => { // a variable is aliased, e.g. // diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 881a4556e9..70b321afcd 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5529,4 +5529,18 @@ mod solve_expr { r#"a -> Effect a"#, ) } + + #[test] + fn generalized_accessor_function_applied() { + infer_eq_without_problem( + indoc!( + r#" + returnFoo = .foo + + returnFoo { foo: "foo" } + "# + ), + "Str", + ) + } } diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index 520db04b03..0a78ac2750 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -14,7 +14,7 @@ use crate::helpers::wasm::assert_evals_to; use indoc::indoc; #[cfg(test)] -use roc_std::RocList; +use roc_std::{RocList, RocStr}; #[test] #[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 + ); +}