Test monomorphizing string literals

This commit is contained in:
Richard Feldman 2024-11-08 21:07:18 -05:00
parent 7b80489772
commit 8b73efc2ec
No known key found for this signature in database
GPG key ID: DAC334802F365236
7 changed files with 173 additions and 39 deletions

View file

@ -12,7 +12,7 @@ use roc_collections::Push;
use roc_solve::module::Solved; use roc_solve::module::Solved;
use roc_types::subs::Subs; use roc_types::subs::Subs;
pub struct Env<'a, 'c, 'd, 's, 't, P> { pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> {
arena: &'a Bump, arena: &'a Bump,
subs: &'s mut Subs, subs: &'s mut Subs,
types_cache: &'c mut MonoCache, types_cache: &'c mut MonoCache,
@ -21,11 +21,11 @@ pub struct Env<'a, 'c, 'd, 's, 't, P> {
record_field_ids: RecordFieldIds, record_field_ids: RecordFieldIds,
tuple_elem_ids: TupleElemIds, tuple_elem_ids: TupleElemIds,
debug_info: &'d mut Option<DebugInfo>, debug_info: &'d mut Option<DebugInfo>,
string_interns: &'a mut Interns<'a>, string_interns: &'i mut Interns<'a>,
problems: P, problems: P,
} }
impl<'a, 'c, 'd, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 's, 't, P> { impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
pub fn new( pub fn new(
arena: &'a Bump, arena: &'a Bump,
subs: &'s mut Solved<Subs>, subs: &'s mut Solved<Subs>,
@ -34,7 +34,7 @@ impl<'a, 'c, 'd, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 's, 't, P> {
mono_exprs: &'t mut MonoExprs, mono_exprs: &'t mut MonoExprs,
record_field_ids: RecordFieldIds, record_field_ids: RecordFieldIds,
tuple_elem_ids: TupleElemIds, tuple_elem_ids: TupleElemIds,
string_interns: &'a mut Interns<'a>, string_interns: &'i mut Interns<'a>,
debug_info: &'d mut Option<DebugInfo>, debug_info: &'d mut Option<DebugInfo>,
problems: P, problems: P,
) -> Self { ) -> Self {
@ -88,6 +88,7 @@ impl<'a, 'c, 'd, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 's, 't, P> {
} }
}, },
Expr::Num(var, _str, int_value, _) | Expr::Int(var, _, _str, int_value, _) => { Expr::Num(var, _str, int_value, _) | Expr::Int(var, _, _str, int_value, _) => {
// Numbers can specialize
match mono_from_var(var) { match mono_from_var(var) {
Some(mono_id) => match mono_types.get(mono_id) { Some(mono_id) => match mono_types.get(mono_id) {
MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems), MonoType::Primitive(primitive) => to_num(*primitive, int_value, problems),
@ -115,9 +116,10 @@ impl<'a, 'c, 'd, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 's, 't, P> {
return compiler_bug!(Problem::CharSpecializedToWrongType(None)); return compiler_bug!(Problem::CharSpecializedToWrongType(None));
} }
}, },
Expr::Str(contents) => { Expr::Str(contents) => MonoExpr::Str(
MonoExpr::Str(self.string_interns.get(self.arena.alloc(contents))) self.string_interns
} .get(self.arena, self.arena.alloc(contents)),
),
Expr::EmptyRecord => { Expr::EmptyRecord => {
// Empty records are zero-sized and should be discarded. // Empty records are zero-sized and should be discarded.
return None; return None;

View file

@ -1,3 +1,4 @@
use bumpalo::Bump;
use roc_solve::module::Solved; use roc_solve::module::Solved;
use roc_types::subs::Subs; use roc_types::subs::Subs;
@ -26,6 +27,7 @@ impl MonoModule {
pub struct InternedStrId(u32); pub struct InternedStrId(u32);
/// TODO move this to its own crate /// TODO move this to its own crate
#[derive(Debug)]
pub struct Interns<'a> { pub struct Interns<'a> {
interned: Vec<&'a str>, interned: Vec<&'a str>,
} }
@ -37,7 +39,7 @@ impl<'a> Interns<'a> {
} }
} }
pub fn get(&mut self, string: &'a str) -> InternedStrId { pub fn get(&mut self, _arena: &'a Bump, string: &'a str) -> InternedStrId {
match self match self
.interned .interned
.iter() .iter()
@ -53,4 +55,15 @@ impl<'a> Interns<'a> {
} }
} }
} }
pub fn try_get(&self, _arena: &'a Bump, string: &'a str) -> Option<InternedStrId> {
match self
.interned
.iter()
.position(|&interned| interned == string)
{
Some(index) => Some(InternedStrId(index as u32)),
None => None,
}
}
} }

View file

@ -48,7 +48,7 @@ pub struct MonoCache {
} }
impl MonoCache { impl MonoCache {
pub fn from_subs(subs: &Solved<Subs>) -> Self { pub fn from_solved_subs(subs: &Solved<Subs>) -> Self {
Self { Self {
inner: VecMap::with_capacity(subs.inner().len()), inner: VecMap::with_capacity(subs.inner().len()),
} }

View file

@ -5,31 +5,148 @@ extern crate bumpalo;
#[cfg(test)] #[cfg(test)]
mod specialize_types { mod specialize_types {
use roc_specialize_types::{MonoExpr, Number}; use bumpalo::Bump;
use test_compile::specialize_expr; use roc_load::LoadedModule;
use roc_solve::FunctionKind;
use roc_specialize_types::{
DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, Number, RecordFieldIds,
TupleElemIds,
};
use test_compile::{trim_and_deindent, SpecializedExprOut};
use test_solve_helpers::{format_problems, run_load_and_infer};
// HELPERS
fn specialize_expr<'a>(
arena: &'a Bump,
src: &str,
string_interns: &mut Interns<'a>,
) -> SpecializedExprOut {
let (
LoadedModule {
module_id: home,
mut declarations_by_id,
mut can_problems,
mut type_problems,
interns,
mut solved,
mut exposed_to_host,
abilities_store,
..
},
src,
) = run_load_and_infer(
trim_and_deindent(&arena, src),
[],
false,
FunctionKind::LambdaSet,
)
.unwrap();
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
// Disregard UnusedDef problems, because those are unavoidable when
// returning a function from the test expression.
can_problems.retain(|prob| {
!matches!(
prob,
roc_problem::can::Problem::UnusedDef(_, _)
| roc_problem::can::Problem::UnusedBranchDef(..)
)
});
let (can_problems, type_problems) =
format_problems(&src, home, &interns, can_problems, type_problems);
assert_eq!(can_problems, String::new());
assert_eq!(type_problems, String::new());
exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s));
let mut problems = Vec::new();
let mut debug_info: Option<DebugInfo> = None;
let mut types_cache = MonoCache::from_solved_subs(&solved);
let mut mono_types = MonoTypes::new();
let mut mono_exprs = MonoExprs::new();
let mut env = Env::new(
&arena,
&mut solved,
&mut types_cache,
&mut mono_types,
&mut mono_exprs,
RecordFieldIds::default(),
TupleElemIds::default(),
string_interns,
&mut debug_info,
&mut problems,
);
let mut home_decls = declarations_by_id.remove(&home).unwrap();
let main_expr = home_decls.expressions.pop().unwrap().value;
// This should be our only expr
assert_eq!(0, home_decls.expressions.len());
let mono_expr_id = env.to_mono_expr(main_expr);
SpecializedExprOut {
mono_expr_id,
problems,
mono_types,
mono_exprs,
}
}
fn expect_no_expr(input: impl AsRef<str>) { fn expect_no_expr(input: impl AsRef<str>) {
let out = specialize_expr(input.as_ref()); let arena = Bump::new();
let mut interns = Interns::new();
let out = specialize_expr(&arena, input.as_ref(), &mut interns);
let actual = out.mono_expr_id.map(|id| out.mono_exprs.get(id)); let actual = out.mono_expr_id.map(|id| out.mono_exprs.get(id));
assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref()); assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref());
} }
fn expect_mono_expr(input: impl AsRef<str>, mono_expr: MonoExpr) { fn expect_mono_expr(input: impl AsRef<str>, mono_expr: MonoExpr) {
let out = specialize_expr(input.as_ref()); expect_mono_expr_with_interns(|_, _| {}, input, |_| mono_expr);
let mono_expr_id = out }
.mono_expr_id
.expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}");
assert_eq!(&mono_expr, out.mono_exprs.get(mono_expr_id)); fn expect_mono_expr_with_interns<T>(
from_interns: impl for<'a> FnOnce(&'a Bump, &Interns<'a>) -> T,
input: impl AsRef<str>,
to_mono_expr: impl FnOnce(T) -> MonoExpr,
) {
let arena = Bump::new();
let mut string_interns = Interns::new();
let out = specialize_expr(&arena, input.as_ref(), &mut string_interns);
let mono_expr_id = out
.mono_expr_id
.expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}");
let actual_expr = out.mono_exprs.get(mono_expr_id); // Must run first, to populate string interns!
let expected_expr = to_mono_expr(from_interns(&arena, &string_interns));
assert_eq!(&expected_expr, actual_expr);
} }
#[test] #[test]
fn empty_record() { fn empty_record() {
let todo = (); // TODO need to get earlier stage working, specifically constraint + solve, by replicating existing solve tests.
expect_no_expr("{}"); expect_no_expr("{}");
} }
#[test]
fn string_literal() {
let string = "foo";
let expected = format!("\"{string}\"");
expect_mono_expr_with_interns(
|arena, interns| interns.try_get(arena, string).unwrap(),
expected,
|id| MonoExpr::Str(id),
);
}
#[test] #[test]
fn unbound_num() { fn unbound_num() {
let expected = 42; let expected = 42;

View file

@ -51,7 +51,7 @@ mod specialize_types {
debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}"); debug_assert!(exposed_to_host.len() == 1, "{exposed_to_host:?}");
let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap();
let mut mono_cache = MonoCache::from_subs(&solved); let mut mono_cache = MonoCache::from_solved_subs(&solved);
let mut mono_types = MonoTypes::new(); let mut mono_types = MonoTypes::new();
let debug_info = DebugInfo::new(); let debug_info = DebugInfo::new();
let mut record_field_ids = RecordFieldIds::new(); let mut record_field_ids = RecordFieldIds::new();

View file

@ -19,29 +19,34 @@ pub struct SpecializedExpr {
} }
impl SpecializedExpr { impl SpecializedExpr {
pub fn specialize_expr<'a>(&'a self, input: &'a str) -> SpecializedExprOut { pub fn specialize_expr<'a>(
&'a self,
input: &'a str,
string_interns: &'a mut Interns<'a>,
) -> SpecializedExprOut {
let mut solved_out = self.solved_expr.solve_expr(input); let mut solved_out = self.solved_expr.solve_expr(input);
let mut problems = Vec::new(); let mut problems = Vec::new();
let mut debug_info: Option<DebugInfo> = None; let mut debug_info: Option<DebugInfo> = None;
let mut types_cache = MonoCache::from_subs(&solved_out.subs); let mut types_cache = MonoCache::from_solved_subs(&solved_out.subs);
let mut mono_types = MonoTypes::new(); let mut mono_types = MonoTypes::new();
let mut mono_exprs = MonoExprs::new(); let mut mono_exprs = MonoExprs::new();
let mut string_interns = Interns::new();
let mut env = Env::new( let mono_expr_id = {
self.solved_expr.arena(), let mut env = Env::new(
&mut solved_out.subs, self.solved_expr.arena(),
&mut types_cache, &mut solved_out.subs,
&mut mono_types, &mut types_cache,
&mut mono_exprs, &mut mono_types,
RecordFieldIds::default(), &mut mono_exprs,
TupleElemIds::default(), RecordFieldIds::default(),
&mut string_interns, TupleElemIds::default(),
&mut debug_info, string_interns,
&mut problems, &mut debug_info,
); &mut problems,
);
let mono_expr_id = env.to_mono_expr(solved_out.expr); env.to_mono_expr(solved_out.expr)
};
SpecializedExprOut { SpecializedExprOut {
mono_expr_id, mono_expr_id,

View file

@ -5,6 +5,7 @@ mod help_parse;
mod help_solve; mod help_solve;
mod help_specialize; mod help_specialize;
pub use deindent::trim_and_deindent;
pub use help_can::{CanExpr, CanExprOut}; pub use help_can::{CanExpr, CanExprOut};
pub use help_parse::ParseExpr; pub use help_parse::ParseExpr;
pub use help_solve::{SolvedExpr, SolvedExprOut}; pub use help_solve::{SolvedExpr, SolvedExprOut};
@ -17,7 +18,3 @@ pub fn can_expr<'a>(input: &'a str) -> CanExprOut {
pub fn solve_expr<'a>(input: &'a str) -> SolvedExprOut { pub fn solve_expr<'a>(input: &'a str) -> SolvedExprOut {
SolvedExpr::default().solve_expr(input) SolvedExpr::default().solve_expr(input)
} }
pub fn specialize_expr<'a>(input: &'a str) -> SpecializedExprOut {
SpecializedExpr::default().specialize_expr(input)
}