Merge remote-tracking branch 'origin/trunk' into num

This commit is contained in:
Folkert 2020-03-11 14:30:50 +01:00
commit fb8e8570bc
17 changed files with 320 additions and 192 deletions

View file

@ -33,7 +33,12 @@ pub struct Output {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Expr { pub enum Expr {
// Literals // Literals
// Num stores the `a` variable in `Num a`. Not the same as the variable
// stored in Int and Float below, which is strictly for better error messages
Num(Variable, i64), Num(Variable, i64),
// Int and Float store a variable to generate better error messages
Int(Variable, i64), Int(Variable, i64),
Float(Variable, f64), Float(Variable, f64),
Str(Box<str>), Str(Box<str>),

View file

@ -33,6 +33,7 @@ pub fn int_expr_from_result(
result: Result<i64, &str>, result: Result<i64, &str>,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Int stores a variable to generate better error messages
match result { match result {
Ok(int) => Expr::Int(var_store.fresh(), int), Ok(int) => Expr::Int(var_store.fresh(), int),
Err(raw) => { Err(raw) => {
@ -51,6 +52,7 @@ pub fn float_expr_from_result(
result: Result<f64, &str>, result: Result<f64, &str>,
env: &mut Env, env: &mut Env,
) -> Expr { ) -> Expr {
// Float stores a variable to generate better error messages
match result { match result {
Ok(float) => Expr::Float(var_store.fresh(), float), Ok(float) => Expr::Float(var_store.fresh(), float),
Err(raw) => { Err(raw) => {

View file

@ -10,7 +10,7 @@ use roc_can::expr::Expr::{self, *};
use roc_can::expr::Field; use roc_can::expr::Field;
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, SendMap}; use roc_collections::all::{ImMap, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::Lowercase;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::Variable; use roc_types::subs::Variable;
@ -326,14 +326,7 @@ pub fn constrain_expr(
branches, branches,
final_else, final_else,
} => { } => {
// TODO use Bool alias here, so we don't allocate this type every time let bool_type = Type::Variable(Variable::BOOL);
let bool_type = Type::TagUnion(
vec![
(TagName::Global("True".into()), vec![]),
(TagName::Global("False".into()), vec![]),
],
Box::new(Type::EmptyTagUnion),
);
let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region); let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region);
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2);

View file

@ -7,7 +7,7 @@ use roc_can::expected::{Expected, PExpected};
use roc_can::expr::{Expr, Field}; use roc_can::expr::{Expr, Field};
use roc_can::pattern::{Pattern, RecordDestruct}; use roc_can::pattern::{Pattern, RecordDestruct};
use roc_collections::all::{ImMap, ImSet, SendMap}; use roc_collections::all::{ImMap, ImSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::boolean_algebra::{Atom, Bool}; use roc_types::boolean_algebra::{Atom, Bool};
@ -802,14 +802,7 @@ pub fn constrain_expr(
final_else, final_else,
} => { } => {
// TODO use Bool alias here, so we don't allocate this type every time // TODO use Bool alias here, so we don't allocate this type every time
let bool_type = Type::TagUnion( let bool_type = Type::Variable(Variable::BOOL);
vec![
(TagName::Global("True".into()), vec![]),
(TagName::Global("False".into()), vec![]),
],
Box::new(Type::EmptyTagUnion),
);
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2);
let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2); let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2);

View file

@ -27,6 +27,8 @@ pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>) -> Type
Builtin(builtin) => match builtin { Builtin(builtin) => match builtin {
Int64 => types::I64, Int64 => types::I64,
Float64 => types::F64, Float64 => types::F64,
Bool(_, _) => types::B1,
Byte(_) => types::I8,
Str | Map(_, _) | Set(_) | List(_) => cfg.pointer_type(), Str | Map(_, _) | Set(_) | List(_) => cfg.pointer_type(),
}, },
} }

View file

@ -50,6 +50,8 @@ pub fn basic_type_from_layout<'ctx>(
Builtin(builtin) => match builtin { Builtin(builtin) => match builtin {
Int64 => context.i64_type().as_basic_type_enum(), Int64 => context.i64_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Bool(_, _) => context.bool_type().as_basic_type_enum(),
Byte(_) => context.i8_type().as_basic_type_enum(),
Str => context Str => context
.i8_type() .i8_type()
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)

View file

@ -37,6 +37,9 @@ mod test_gen {
use std::mem; use std::mem;
use std::os::raw::c_char; use std::os::raw::c_char;
// Pointer size on 64-bit platforms
const POINTER_SIZE: u32 = std::mem::size_of::<u64>() as u32;
macro_rules! assert_crane_evals_to { macro_rules! assert_crane_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
let arena = Bump::new(); let arena = Bump::new();
@ -57,7 +60,7 @@ mod test_gen {
let main_fn_name = "$Test.main"; let main_fn_name = "$Test.main";
// Compute main_fn_ret_type before moving subs to Env // Compute main_fn_ret_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs) let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert content to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in test: could not convert content to layout. Err was {:?} and Subs were {:?}", err, subs));
let main_ret_type = type_from_layout(cfg, &layout); let main_ret_type = type_from_layout(cfg, &layout);
@ -72,7 +75,7 @@ mod test_gen {
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr // Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mono_expr = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); let mono_expr = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE);
// Put this module's ident_ids back in the interns // Put this module's ident_ids back in the interns
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);
@ -195,7 +198,7 @@ mod test_gen {
fpm.initialize(); fpm.initialize();
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs) let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let main_fn_type = basic_type_from_layout(&context, &layout) let main_fn_type = basic_type_from_layout(&context, &layout)
.fn_type(&[], false); .fn_type(&[], false);
@ -221,7 +224,7 @@ mod test_gen {
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
// Populate Procs and get the low-level Expr from the canonical Expr // Populate Procs and get the low-level Expr from the canonical Expr
let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE);
// Put this module's ident_ids back in the interns, so we can use them in Env. // Put this module's ident_ids back in the interns, so we can use them in Env.
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);
@ -330,7 +333,7 @@ mod test_gen {
fpm.initialize(); fpm.initialize();
// Compute main_fn_type before moving subs to Env // Compute main_fn_type before moving subs to Env
let layout = Layout::from_content(&arena, content, &subs) let layout = Layout::from_content(&arena, content, &subs, POINTER_SIZE)
.unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs));
let main_fn_type = basic_type_from_layout(&context, &layout) let main_fn_type = basic_type_from_layout(&context, &layout)
.fn_type(&[], false); .fn_type(&[], false);
@ -356,7 +359,7 @@ mod test_gen {
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
// Populate Procs and get the low-level Expr from the canonical Expr // Populate Procs and get the low-level Expr from the canonical Expr
let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids, POINTER_SIZE);
// Put this module's ident_ids back in the interns, so we can use them in Env. // Put this module's ident_ids back in the interns, so we can use them in Env.
env.interns.all_ident_ids.insert(home, ident_ids); env.interns.all_ident_ids.insert(home, ident_ids);

View file

@ -24,6 +24,7 @@ struct Env<'a, 'i> {
pub subs: &'a Subs, pub subs: &'a Subs,
pub home: ModuleId, pub home: ModuleId,
pub ident_ids: &'i mut IdentIds, pub ident_ids: &'i mut IdentIds,
pub pointer_size: u32,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -89,7 +90,6 @@ pub enum Expr<'a> {
}, },
Tag { Tag {
tag_layout: Layout<'a>, tag_layout: Layout<'a>,
ext_layout: Layout<'a>,
name: TagName, name: TagName,
arguments: &'a [Expr<'a>], arguments: &'a [Expr<'a>],
}, },
@ -119,12 +119,14 @@ impl<'a> Expr<'a> {
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
home: ModuleId, home: ModuleId,
ident_ids: &mut IdentIds, ident_ids: &mut IdentIds,
pointer_size: u32,
) -> Self { ) -> Self {
let mut env = Env { let mut env = Env {
arena, arena,
subs, subs,
home, home,
ident_ids, ident_ids,
pointer_size,
}; };
from_can(&mut env, can_expr, procs, None) from_can(&mut env, can_expr, procs, None)
@ -317,8 +319,8 @@ fn from_can<'a>(
args.push(from_can(env, loc_arg.value, procs, None)); args.push(from_can(env, loc_arg.value, procs, None));
} }
let layout = let layout = Layout::from_var(env.arena, fn_var, env.subs, env.pointer_size)
Layout::from_var(env.arena, fn_var, env.subs).unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("TODO turn fn_var into a RuntimeError {:?}", err) panic!("TODO turn fn_var into a RuntimeError {:?}", err)
}); });
Expr::CallByPointer(&*env.arena.alloc(ptr), args.into_bump_slice(), layout) Expr::CallByPointer(&*env.arena.alloc(ptr), args.into_bump_slice(), layout)
@ -344,7 +346,7 @@ fn from_can<'a>(
field_bodies.push((label, expr)); field_bodies.push((label, expr));
} }
let struct_layout = match Layout::from_var(arena, ext_var, subs) { let struct_layout = match Layout::from_var(arena, ext_var, subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid field! // Invalid field!
@ -358,6 +360,40 @@ fn from_can<'a>(
} }
} }
Tag {
variant_var,
name,
arguments: args,
..
} => {
let arena = env.arena;
match Layout::from_var(arena, variant_var, &env.subs, env.pointer_size) {
Ok(Layout::Builtin(Builtin::Bool(_smaller, larger))) => Expr::Bool(name == larger),
Ok(Layout::Builtin(Builtin::Byte(tags))) => match tags.get(&name) {
Some(v) => Expr::Byte(*v),
None => panic!("Tag name is not part of the type"),
},
Ok(layout) => {
let mut arguments = Vec::with_capacity_in(args.len(), arena);
for (_, arg) in args {
arguments.push(from_can(env, arg.value, procs, None));
}
Expr::Tag {
tag_layout: layout,
name,
arguments: arguments.into_bump_slice(),
}
}
Err(()) => {
// Invalid field!
panic!("TODO gracefully handle Access with invalid struct_layout");
}
}
}
Access { Access {
ext_var, ext_var,
field_var, field_var,
@ -367,7 +403,7 @@ fn from_can<'a>(
let subs = env.subs; let subs = env.subs;
let arena = env.arena; let arena = env.arena;
let struct_layout = match Layout::from_var(arena, ext_var, subs) { let struct_layout = match Layout::from_var(arena, ext_var, subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid field! // Invalid field!
@ -375,7 +411,7 @@ fn from_can<'a>(
} }
}; };
let field_layout = match Layout::from_var(arena, field_var, subs) { let field_layout = match Layout::from_var(arena, field_var, subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid field! // Invalid field!
@ -396,7 +432,7 @@ fn from_can<'a>(
} => { } => {
let subs = env.subs; let subs = env.subs;
let arena = env.arena; let arena = env.arena;
let elem_layout = match Layout::from_var(arena, elem_var, subs) { let elem_layout = match Layout::from_var(arena, elem_var, subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
panic!("TODO gracefully handle List with invalid element layout"); panic!("TODO gracefully handle List with invalid element layout");
@ -431,7 +467,7 @@ fn add_closure<'a>(
let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena); let mut proc_args = Vec::with_capacity_in(loc_args.len(), arena);
for (arg_var, loc_arg) in loc_args.iter() { for (arg_var, loc_arg) in loc_args.iter() {
let layout = match Layout::from_var(arena, *arg_var, subs) { let layout = match Layout::from_var(arena, *arg_var, subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid closure! // Invalid closure!
@ -451,7 +487,7 @@ fn add_closure<'a>(
proc_args.push((layout, arg_name)); proc_args.push((layout, arg_name));
} }
let ret_layout = Layout::from_var(arena, ret_var, subs) let ret_layout = Layout::from_var(arena, ret_var, subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err)); .unwrap_or_else(|err| panic!("TODO handle invalid function {:?}", err));
let proc = Proc { let proc = Proc {
@ -476,7 +512,7 @@ fn store_pattern<'a>(
) { ) {
use roc_can::pattern::Pattern::*; use roc_can::pattern::Pattern::*;
let layout = match Layout::from_var(env.arena, var, env.subs) { let layout = match Layout::from_var(env.arena, var, env.subs, env.pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
panic!("TODO gen a runtime error here"); panic!("TODO gen a runtime error here");
@ -575,8 +611,8 @@ fn from_can_when<'a>(
let cond_rhs = arena.alloc(cond_rhs_expr); let cond_rhs = arena.alloc(cond_rhs_expr);
let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
let ret_layout = let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("TODO turn this into a RuntimeError {:?}", err) panic!("TODO turn this into a RuntimeError {:?}", err)
}); });
@ -594,8 +630,8 @@ fn from_can_when<'a>(
let cond_rhs = arena.alloc(Expr::Int(*int)); let cond_rhs = arena.alloc(Expr::Int(*int));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
let ret_layout = let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("TODO turn this into a RuntimeError {:?}", err) panic!("TODO turn this into a RuntimeError {:?}", err)
}); });
@ -613,8 +649,8 @@ fn from_can_when<'a>(
let cond_rhs = arena.alloc(Expr::Float(*float)); let cond_rhs = arena.alloc(Expr::Float(*float));
let pass = arena.alloc(from_can(env, loc_then.value, procs, None)); let pass = arena.alloc(from_can(env, loc_then.value, procs, None));
let fail = arena.alloc(from_can(env, loc_else.value, procs, None)); let fail = arena.alloc(from_can(env, loc_else.value, procs, None));
let ret_layout = let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("TODO turn this into a RuntimeError {:?}", err) panic!("TODO turn this into a RuntimeError {:?}", err)
}); });
@ -637,7 +673,7 @@ fn from_can_when<'a>(
let arena = env.arena; let arena = env.arena;
let cond = from_can(env, loc_cond.value, procs, None); let cond = from_can(env, loc_cond.value, procs, None);
let subs = &env.subs; let subs = &env.subs;
let layout = Layout::from_var(arena, cond_var, subs) let layout = Layout::from_var(arena, cond_var, subs, env.pointer_size)
.unwrap_or_else(|_| panic!("TODO generate a runtime error in from_can_when here!")); .unwrap_or_else(|_| panic!("TODO generate a runtime error in from_can_when here!"));
// We can Switch on integers and tags, because they both have // We can Switch on integers and tags, because they both have
@ -734,12 +770,12 @@ fn from_can_when<'a>(
debug_assert!(opt_default_branch.is_some()); debug_assert!(opt_default_branch.is_some());
let default_branch = opt_default_branch.unwrap(); let default_branch = opt_default_branch.unwrap();
let cond_layout = let cond_layout = Layout::from_var(arena, cond_var, env.subs, env.pointer_size)
Layout::from_var(arena, cond_var, env.subs).unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("TODO turn cond_layout into a RuntimeError {:?}", err) panic!("TODO turn cond_layout into a RuntimeError {:?}", err)
}); });
let ret_layout = let ret_layout = Layout::from_var(arena, expr_var, env.subs, env.pointer_size)
Layout::from_var(arena, expr_var, env.subs).unwrap_or_else(|err| { .unwrap_or_else(|err| {
panic!("TODO turn ret_layout into a RuntimeError {:?}", err) panic!("TODO turn ret_layout into a RuntimeError {:?}", err)
}); });
@ -779,7 +815,7 @@ fn call_by_name<'a>(
let arena = env.arena; let arena = env.arena;
for (var, loc_arg) in loc_args { for (var, loc_arg) in loc_args {
let layout = Layout::from_var(arena, var, subs) let layout = Layout::from_var(arena, var, subs, env.pointer_size)
.unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err)); .unwrap_or_else(|err| panic!("TODO gracefully handle bad layout: {:?}", err));
args.push((from_can(env, loc_arg.value, procs, None), layout)); args.push((from_can(env, loc_arg.value, procs, None), layout));

View file

@ -19,6 +19,8 @@ pub enum Layout<'a> {
pub enum Builtin<'a> { pub enum Builtin<'a> {
Int64, Int64,
Float64, Float64,
Bool(TagName, TagName),
Byte(MutMap<TagName, u8>),
Str, Str,
Map(&'a Layout<'a>, &'a Layout<'a>), Map(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>), Set(&'a Layout<'a>),
@ -29,20 +31,30 @@ impl<'a> Layout<'a> {
/// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure. /// Returns Err(()) if given an error, or Ok(Layout) if given a non-erroneous Structure.
/// Panics if given a FlexVar or RigidVar, since those should have been /// Panics if given a FlexVar or RigidVar, since those should have been
/// monomorphized away already! /// monomorphized away already!
pub fn from_var(arena: &'a Bump, var: Variable, subs: &Subs) -> Result<Self, ()> { pub fn from_var(
arena: &'a Bump,
var: Variable,
subs: &Subs,
pointer_size: u32,
) -> Result<Self, ()> {
let content = subs.get_without_compacting(var).content; let content = subs.get_without_compacting(var).content;
Self::from_content(arena, content, subs) Self::from_content(arena, content, subs, pointer_size)
} }
pub fn from_content(arena: &'a Bump, content: Content, subs: &Subs) -> Result<Self, ()> { pub fn from_content(
arena: &'a Bump,
content: Content,
subs: &Subs,
pointer_size: u32,
) -> Result<Self, ()> {
use roc_types::subs::Content::*; use roc_types::subs::Content::*;
match content { match content {
var @ FlexVar(_) | var @ RigidVar(_) => { var @ FlexVar(_) | var @ RigidVar(_) => {
panic!("Layout::from_content encountered an unresolved {:?}", var); panic!("Layout::from_content encountered an unresolved {:?}", var);
} }
Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs), Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs, pointer_size),
Alias(Symbol::INT_INT, args, _) => { Alias(Symbol::INT_INT, args, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
@ -52,9 +64,12 @@ impl<'a> Layout<'a> {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Float64)) Ok(Layout::Builtin(Builtin::Float64))
} }
Alias(_, _, var) => { Alias(_, _, var) => Self::from_content(
Self::from_content(arena, subs.get_without_compacting(var).content, subs) arena,
} subs.get_without_compacting(var).content,
subs,
pointer_size,
),
Error => Err(()), Error => Err(()),
} }
} }
@ -81,6 +96,8 @@ impl<'a> Layout<'a> {
impl<'a> Builtin<'a> { impl<'a> Builtin<'a> {
const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32; const I64_SIZE: u32 = std::mem::size_of::<i64>() as u32;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
const BOOL_SIZE: u32 = std::mem::size_of::<bool>() as u32;
const BYTE_SIZE: u32 = std::mem::size_of::<u8>() as u32;
/// Number of machine words in an empty one of these /// Number of machine words in an empty one of these
const STR_WORDS: u32 = 3; const STR_WORDS: u32 = 3;
@ -94,6 +111,8 @@ impl<'a> Builtin<'a> {
match self { match self {
Int64 => Builtin::I64_SIZE, Int64 => Builtin::I64_SIZE,
Float64 => Builtin::F64_SIZE, Float64 => Builtin::F64_SIZE,
Bool(_, _) => Builtin::BOOL_SIZE,
Byte(_) => Builtin::BYTE_SIZE,
Str => Builtin::STR_WORDS * pointer_size, Str => Builtin::STR_WORDS * pointer_size,
Map(_, _) => Builtin::MAP_WORDS * pointer_size, Map(_, _) => Builtin::MAP_WORDS * pointer_size,
Set(_) => Builtin::SET_WORDS * pointer_size, Set(_) => Builtin::SET_WORDS * pointer_size,
@ -106,6 +125,7 @@ fn layout_from_flat_type<'a>(
arena: &'a Bump, arena: &'a Bump,
flat_type: FlatType, flat_type: FlatType,
subs: &Subs, subs: &Subs,
pointer_size: u32,
) -> Result<Layout<'a>, ()> { ) -> Result<Layout<'a>, ()> {
use roc_types::subs::FlatType::*; use roc_types::subs::FlatType::*;
@ -131,7 +151,7 @@ fn layout_from_flat_type<'a>(
} }
Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)), Symbol::STR_STR => Ok(Layout::Builtin(Builtin::Str)),
Symbol::LIST_LIST => { Symbol::LIST_LIST => {
let elem_layout = Layout::from_var(arena, args[0], subs)?; let elem_layout = Layout::from_var(arena, args[0], subs, pointer_size)?;
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
} }
@ -145,7 +165,7 @@ fn layout_from_flat_type<'a>(
// For now, layout is unaffected by uniqueness. // For now, layout is unaffected by uniqueness.
// (Incorporating refcounting may change this.) // (Incorporating refcounting may change this.)
// Unwrap and continue // Unwrap and continue
Layout::from_var(arena, wrapped_var, subs) Layout::from_var(arena, wrapped_var, subs, pointer_size)
} }
_ => { _ => {
panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args)); panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args));
@ -158,11 +178,16 @@ fn layout_from_flat_type<'a>(
for arg_var in args { for arg_var in args {
let arg_content = subs.get_without_compacting(arg_var).content; let arg_content = subs.get_without_compacting(arg_var).content;
fn_args.push(Layout::from_content(arena, arg_content, subs)?); fn_args.push(Layout::from_content(
arena,
arg_content,
subs,
pointer_size,
)?);
} }
let ret_content = subs.get_without_compacting(ret_var).content; let ret_content = subs.get_without_compacting(ret_var).content;
let ret = Layout::from_content(arena, ret_content, subs)?; let ret = Layout::from_content(arena, ret_content, subs, pointer_size)?;
Ok(Layout::FunctionPointer( Ok(Layout::FunctionPointer(
fn_args.into_bump_slice(), fn_args.into_bump_slice(),
@ -172,7 +197,7 @@ fn layout_from_flat_type<'a>(
Record(mut fields, ext_var) => { Record(mut fields, ext_var) => {
flatten_record(&mut fields, ext_var, subs); flatten_record(&mut fields, ext_var, subs);
let ext_content = subs.get_without_compacting(ext_var).content; let ext_content = subs.get_without_compacting(ext_var).content;
let ext_layout = match Layout::from_content(arena, ext_content, subs) { let ext_layout = match Layout::from_content(arena, ext_content, subs, pointer_size) {
Ok(layout) => layout, Ok(layout) => layout,
Err(()) => { Err(()) => {
// Invalid record! // Invalid record!
@ -200,13 +225,14 @@ fn layout_from_flat_type<'a>(
for (label, field_var) in fields { for (label, field_var) in fields {
let field_content = subs.get_without_compacting(field_var).content; let field_content = subs.get_without_compacting(field_var).content;
let field_layout = match Layout::from_content(arena, field_content, subs) { let field_layout =
Ok(layout) => layout, match Layout::from_content(arena, field_content, subs, pointer_size) {
Err(()) => { Ok(layout) => layout,
// Invalid field! Err(()) => {
panic!("TODO gracefully handle record with invalid field.var"); // Invalid field!
} panic!("TODO gracefully handle record with invalid field.var");
}; }
};
field_layouts.push((label.clone(), field_layout)); field_layouts.push((label.clone(), field_layout));
} }
@ -243,7 +269,50 @@ fn layout_from_flat_type<'a>(
} }
} }
_ => { _ => {
panic!("TODO handle a tag union with mutliple tags: {:?}", tags); // Check if we can turn this tag union into an enum
// The arguments of all tags must have size 0.
// That is trivially the case when there are no arguments
//
// [ Orange, Apple, Banana ]
//
// But when one-tag tag unions are optimized away, we can also use an enum for
//
// [ Foo [ Unit ], Bar [ Unit ] ]
let arguments_have_size_0 = || {
tags.iter().all(|(_, args)| {
args.iter().all(|var| {
Layout::from_var(arena, *var, subs, pointer_size)
.map(|v| v.stack_size(pointer_size))
== Ok(0)
})
})
};
// up to 256 enum keys can be stored in a byte
if tags.len() <= std::u8::MAX as usize + 1 && arguments_have_size_0() {
if tags.len() <= 2 {
// Up to 2 enum tags can be stored (in theory) in one bit
let mut it = tags.keys();
let a: TagName = it.next().unwrap().clone();
let b: TagName = it.next().unwrap().clone();
if a < b {
Ok(Layout::Builtin(Builtin::Bool(a, b)))
} else {
Ok(Layout::Builtin(Builtin::Bool(b, a)))
}
} else {
// up to 256 enum tags can be stored in a byte
let mut tag_to_u8 = MutMap::default();
for (counter, (name, _)) in tags.into_iter().enumerate() {
tag_to_u8.insert(name, counter as u8);
}
Ok(Layout::Builtin(Builtin::Byte(tag_to_u8)))
}
} else {
panic!("TODO handle a tag union with mutliple tags: {:?}", tags);
}
} }
} }
} }
@ -301,13 +370,15 @@ fn flatten_union(
match subs.get_without_compacting(ext_var).content { match subs.get_without_compacting(ext_var).content {
Structure(EmptyTagUnion) => (), Structure(EmptyTagUnion) => (),
Structure(TagUnion(new_tags, new_ext_var)) => { Structure(TagUnion(new_tags, new_ext_var))
| Structure(RecursiveTagUnion(_, new_tags, new_ext_var)) => {
for (tag_name, vars) in new_tags { for (tag_name, vars) in new_tags {
tags.insert(tag_name, vars); tags.insert(tag_name, vars);
} }
flatten_union(tags, new_ext_var, subs) flatten_union(tags, new_ext_var, subs)
} }
Alias(_, _, actual) => flatten_union(tags, actual, subs),
invalid => { invalid => {
panic!("Compiler error: flatten_union got an ext_var in a tag union that wasn't itself a tag union; instead, it was: {:?}", invalid); panic!("Compiler error: flatten_union got an ext_var in a tag union that wasn't itself a tag union; instead, it was: {:?}", invalid);
} }
@ -329,6 +400,7 @@ fn flatten_record(fields: &mut MutMap<Lowercase, Variable>, ext_var: Variable, s
flatten_record(fields, new_ext_var, subs) flatten_record(fields, new_ext_var, subs)
} }
Alias(_, _, actual) => flatten_record(fields, actual, subs),
invalid => { invalid => {
panic!("Compiler error: flatten_record encountered an ext_var in a record that wasn't itself a record; instead, it was: {:?}", invalid); panic!("Compiler error: flatten_record encountered an ext_var in a record that wasn't itself a record; instead, it was: {:?}", invalid);
} }

View file

@ -11,10 +11,11 @@ mod helpers;
// Test monomorphization // Test monomorphization
#[cfg(test)] #[cfg(test)]
mod test_mono { mod test_mono {
use crate::helpers::{can_expr, infer_expr, CanExprOut}; use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut};
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::ident::TagName::*;
use roc_module::symbol::{Interns, Symbol};
use roc_mono::expr::Expr::{self, *}; use roc_mono::expr::Expr::{self, *};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use roc_types::subs::Subs; use roc_types::subs::Subs;
@ -22,6 +23,13 @@ mod test_mono {
// HELPERS // HELPERS
fn compiles_to(src: &str, expected: Expr<'_>) { fn compiles_to(src: &str, expected: Expr<'_>) {
compiles_to_with_interns(src, |_| expected)
}
fn compiles_to_with_interns<'a, F>(src: &str, get_expected: F)
where
F: FnOnce(Interns) -> Expr<'a>,
{
let arena = Bump::new(); let arena = Bump::new();
let CanExprOut { let CanExprOut {
loc_expr, loc_expr,
@ -40,6 +48,9 @@ mod test_mono {
let mut procs = MutMap::default(); let mut procs = MutMap::default();
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
// assume 64-bit pointers
let pointer_size = std::mem::size_of::<u64>() as u32;
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr // Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mono_expr = Expr::new( let mono_expr = Expr::new(
&arena, &arena,
@ -48,9 +59,13 @@ mod test_mono {
&mut procs, &mut procs,
home, home,
&mut ident_ids, &mut ident_ids,
pointer_size,
); );
assert_eq!(mono_expr, expected); // Put this module's ident_ids back in the interns
interns.all_ident_ids.insert(home, ident_ids);
assert_eq!(mono_expr, get_expected(interns));
} }
#[test] #[test]
@ -63,10 +78,95 @@ mod test_mono {
compiles_to("0.5", Float(0.5)); compiles_to("0.5", Float(0.5));
} }
#[test]
fn bool_literal() {
let arena = Bump::new();
compiles_to_with_interns(
r#"
x : Bool
x = True
x
"#,
|interns| {
let home = test_home();
let var_x = interns.symbol(home, "x".into());
let stores = [(
var_x,
Layout::Builtin(Builtin::Bool(Global("False".into()), Global("True".into()))),
Bool(true),
)];
let load = Load(var_x);
Store(arena.alloc(stores), arena.alloc(load))
},
);
}
#[test]
fn two_element_enum() {
let arena = Bump::new();
compiles_to_with_interns(
r#"
x : [ Yes, No ]
x = No
x
"#,
|interns| {
let home = test_home();
let var_x = interns.symbol(home, "x".into());
let stores = [(
var_x,
Layout::Builtin(Builtin::Bool(Global("No".into()), Global("Yes".into()))),
Bool(false),
)];
let load = Load(var_x);
Store(arena.alloc(stores), arena.alloc(load))
},
);
}
#[test]
fn three_element_enum() {
let arena = Bump::new();
compiles_to_with_interns(
r#"
# this test is brought to you by fruits.com!
x : [ Apple, Orange, Banana ]
x = Orange
x
"#,
|interns| {
let home = test_home();
let var_x = interns.symbol(home, "x".into());
let mut fruits = MutMap::default();
fruits.insert(Global("Banana".into()), 0);
fruits.insert(Global("Orange".into()), 1);
fruits.insert(Global("Apple".into()), 2);
let stores = [(var_x, Layout::Builtin(Builtin::Byte(fruits)), Byte(1))];
let load = Load(var_x);
Store(arena.alloc(stores), arena.alloc(load))
},
);
}
#[test] #[test]
fn set_unique_int_list() { fn set_unique_int_list() {
compiles_to( compiles_to("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", {
"List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1",
CallByName( CallByName(
Symbol::LIST_GET_UNSAFE, Symbol::LIST_GET_UNSAFE,
&vec![ &vec![
@ -91,7 +191,7 @@ mod test_mono {
), ),
(Int(1), Layout::Builtin(Builtin::Int64)), (Int(1), Layout::Builtin(Builtin::Int64)),
], ],
), )
); });
} }
} }

View file

@ -31,6 +31,9 @@ mod test_opt {
let mut procs = MutMap::default(); let mut procs = MutMap::default();
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
// assume 64-bit pointers
let pointer_size = std::mem::size_of::<u64>() as u32;
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr // Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mono_expr = Expr::new( let mono_expr = Expr::new(
&arena, &arena,
@ -39,6 +42,7 @@ mod test_opt {
&mut procs, &mut procs,
home, home,
&mut ident_ids, &mut ident_ids,
pointer_size,
); );
assert_eq!(mono_expr, expected); assert_eq!(mono_expr, expected);

View file

@ -8,7 +8,7 @@ use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output}; use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator; use roc_can::operator;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; use roc_collections::all::{ImMap, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import}; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident; use roc_module::ident::Ident;
@ -350,108 +350,3 @@ pub fn fixtures_dir<'a>() -> PathBuf {
pub fn builtins_dir<'a>() -> PathBuf { pub fn builtins_dir<'a>() -> PathBuf {
PathBuf::new().join("builtins") PathBuf::new().join("builtins")
} }
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
#[allow(dead_code)]
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.clone().into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) });
used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

View file

@ -17,7 +17,7 @@ mod test_report {
use roc_types::types; use roc_types::types;
use std::path::PathBuf; use std::path::PathBuf;
// use roc_region::all; // use roc_region::all;
use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; use crate::helpers::{can_expr, infer_expr, CanExprOut};
use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value}; use roc_reporting::report::ReportText::{EmText, Plain, Region, Type, Url, Value};
use roc_types::subs::Content::{FlexVar, RigidVar, Structure}; use roc_types::subs::Content::{FlexVar, RigidVar, Structure};
use roc_types::subs::FlatType::EmptyRecord; use roc_types::subs::FlatType::EmptyRecord;
@ -53,8 +53,6 @@ mod test_report {
} = can_expr(expr_src); } = can_expr(expr_src);
let mut subs = Subs::new(var_store.into()); let mut subs = Subs::new(var_store.into());
assert_correct_variable_usage(&constraint);
for (var, name) in output.introduced_variables.name_by_var { for (var, name) in output.introduced_variables.name_by_var {
subs.rigid_var(var, name); subs.rigid_var(var, name);
} }

View file

@ -546,6 +546,7 @@ fn type_to_variable(
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL,
Alias(symbol, args, alias_type) => { Alias(symbol, args, alias_type) => {
// Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n)
// different variables (once for each occurence). The recursion restriction is required // different variables (once for each occurence). The recursion restriction is required
@ -560,6 +561,7 @@ fn type_to_variable(
// //
// This `u` variable can be different between lists, so giving just one variable to // This `u` variable can be different between lists, so giving just one variable to
// this type is incorrect. // this type is incorrect.
// TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable
let is_recursive = alias_type.is_recursive(); let is_recursive = alias_type.is_recursive();
let no_args = args.is_empty(); let no_args = args.is_empty();
if no_args && !is_recursive { if no_args && !is_recursive {

View file

@ -400,9 +400,10 @@ pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut used = ImSet::default(); let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used); variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); // ..= because there is an extra undeclared variable that contains the type of the full expression
used.remove(unsafe { &Variable::unsafe_test_debug_variable(2) }); for i in 0..=Variable::RESERVED {
used.remove(unsafe { &Variable::unsafe_test_debug_variable(3) }); used.remove(unsafe { &Variable::unsafe_test_debug_variable(i as u32) });
}
let mut used_vec: Vec<Variable> = used.into_iter().collect(); let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort(); used_vec.sort();

View file

@ -2113,8 +2113,8 @@ mod test_uniq_solve {
f f
"# "#
), ),
"Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))" "Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))",
//"Attr * (Attr (* | a | b) { p : (Attr a *), q : (Attr b *) }* -> Attr * (Num (Attr * *)))", //"Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * (Num (Attr * *)))"
); );
} }

View file

@ -151,9 +151,11 @@ impl Variable {
pub const EMPTY_RECORD: Variable = Variable(1); pub const EMPTY_RECORD: Variable = Variable(1);
pub const EMPTY_TAG_UNION: Variable = Variable(2); pub const EMPTY_TAG_UNION: Variable = Variable(2);
const BOOL_ENUM: Variable = Variable(3);
pub const BOOL: Variable = Variable(4);
pub const RESERVED: usize = 5;
// variables 1 and 2 are reserved for EmptyRecord and EmptyTagUnion const FIRST_USER_SPACE_VAR: Variable = Variable(Self::RESERVED as u32);
const FIRST_USER_SPACE_VAR: Variable = Variable(3);
/// # Safety /// # Safety
/// ///
@ -228,8 +230,26 @@ impl Subs {
subs.utable.new_key(flex_var_descriptor()); subs.utable.new_key(flex_var_descriptor());
} }
subs.set_content(Variable(1), Content::Structure(FlatType::EmptyRecord)); subs.set_content(
subs.set_content(Variable(2), Content::Structure(FlatType::EmptyTagUnion)); Variable::EMPTY_RECORD,
Content::Structure(FlatType::EmptyRecord),
);
subs.set_content(
Variable::EMPTY_TAG_UNION,
Content::Structure(FlatType::EmptyTagUnion),
);
subs.set_content(Variable::BOOL_ENUM, {
let mut tags = MutMap::default();
tags.insert(TagName::Global("False".into()), vec![]);
tags.insert(TagName::Global("True".into()), vec![]);
Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION))
});
subs.set_content(Variable::BOOL, {
Content::Alias(Symbol::BOOL_BOOL, vec![], Variable::BOOL_ENUM)
});
subs subs
} }