From 3bff99b0a23006d29e4113628cc61b292ae4cb1a Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 10:53:12 -0500 Subject: [PATCH] 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 --- compiler/can/src/expr.rs | 2 + compiler/constrain/src/expr.rs | 24 +++++- compiler/mono/src/ir.rs | 122 ++++++++++++++++++++++----- compiler/solve/tests/solve_expr.rs | 14 +++ compiler/test_gen/src/gen_records.rs | 18 +++- 5 files changed, 154 insertions(+), 26 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index dc672693ff..fee1904ac4 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -144,6 +144,7 @@ pub enum Expr { name: Symbol, function_var: Variable, record_var: Variable, + closure_var: Variable, closure_ext_var: Variable, ext_var: Variable, field_var: Variable, @@ -740,6 +741,7 @@ pub fn canonicalize_expr<'a>( 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(), diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 0a97de4327..d064aa955a 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -769,7 +769,8 @@ pub fn constrain_expr( function_var, field, record_var, - closure_ext_var: closure_var, + closure_var, + closure_ext_var, ext_var, field_var, } => { @@ -795,16 +796,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, @@ -816,7 +825,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..f32541d591 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -816,6 +816,11 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { + dbg!(env + .subs + .get_content_without_compacting(annotation) + .clone() + .dbg(env.subs)); let raw_layout = layout_cache .raw_from_var(env.arena, annotation, env.subs) .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>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -3886,40 +3938,36 @@ pub fn with_hole<'a>( name, function_var, record_var, - closure_ext_var: _, + closure_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 { + let ClosureData { + name, + function_type, + arguments, + loc_body, + .. + } = accessor_to_closure( + env, + name, + function_var, record_var, + closure_var, + closure_ext_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)), - )]; + ); match procs.insert_anonymous( env, name, - function_var, + function_type, arguments, - loc_body, + *loc_body, CapturedSymbols::None, field_var, layout_cache, @@ -5445,6 +5493,38 @@ pub fn from_can<'a>( 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) => { // 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 + ); +}