Add test module for derivers

This commit is contained in:
Ayaz Hafiz 2022-06-14 15:12:40 -04:00
parent f1fa29fcfc
commit ccd78a560f
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
7 changed files with 329 additions and 70 deletions

22
Cargo.lock generated
View file

@ -4797,6 +4797,28 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "test_derivers"
version = "0.1.0"
dependencies = [
"bumpalo",
"indoc",
"lazy_static",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_constrain",
"roc_load_internal",
"roc_module",
"roc_mono",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_target",
"roc_types",
"ven_pretty",
]
[[package]]
name = "test_gen"
version = "0.1.0"

View file

@ -18,6 +18,7 @@ members = [
"compiler/mono",
"compiler/alias_analysis",
"compiler/test_mono",
"compiler/test_derivers",
"compiler/load",
"compiler/load_internal",
"compiler/gen_llvm",

View file

@ -220,6 +220,73 @@ impl<Phase: ResolvePhase> IAbilitiesStore<Phase> {
self.next_specialization_id += 1;
id
}
/// Creates a store from [`self`] that closes over the abilities/members given by the
/// imported `symbols`, and their specializations (if any).
pub fn closure_from_imported(&self, symbols: &VecSet<Symbol>) -> PendingAbilitiesStore {
let Self {
members_of_ability,
ability_members,
declared_specializations,
// Covered by `declared_specializations`
specialization_to_root: _,
// Taking closure for a new module, so specialization IDs can be fresh
next_specialization_id: _,
resolved_specializations: _,
} = self;
let mut new = PendingAbilitiesStore::default();
// 1. Figure out the abilities we need to introduce.
let mut abilities_to_introduce = VecSet::with_capacity(2);
symbols.iter().for_each(|symbol| {
if let Some(member_data) = ability_members.get(symbol) {
// If the symbol is member of an ability, we need to capture the entire ability.
abilities_to_introduce.insert(member_data.parent_ability);
}
if members_of_ability.contains_key(symbol) {
abilities_to_introduce.insert(*symbol);
}
});
// 2. Add each ability, and any specializations of its members we know about.
for ability in abilities_to_introduce.into_iter() {
let members = members_of_ability.get(&ability).unwrap();
let mut imported_member_data = Vec::with_capacity(members.len());
for member in members {
let AbilityMemberData {
parent_ability,
region,
typ: _,
} = ability_members.get(member).unwrap().clone();
// All external members need to be marked as imported. We'll figure out their real
// type variables when it comes time to solve the module we're currently importing
// into.
let imported_data = AbilityMemberData {
parent_ability,
region,
typ: PendingMemberType::Imported,
};
imported_member_data.push((*member, imported_data));
}
new.register_ability(ability, imported_member_data);
// Add any specializations of the ability's members we know about.
declared_specializations
.iter()
.filter(|((member, _), _)| members.contains(member))
.for_each(|(&(member, typ), specialization)| {
new.register_specializing_symbol(specialization.symbol, member);
new.import_specialization(member, typ, specialization);
});
}
new
}
}
impl IAbilitiesStore<Resolved> {
@ -326,73 +393,6 @@ impl IAbilitiesStore<Pending> {
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
}
/// Creates a store from [`self`] that closes over the abilities/members given by the
/// imported `symbols`, and their specializations (if any).
pub fn closure_from_imported(&self, symbols: &VecSet<Symbol>) -> Self {
let Self {
members_of_ability,
ability_members,
declared_specializations,
// Covered by `declared_specializations`
specialization_to_root: _,
// Taking closure for a new module, so specialization IDs can be fresh
next_specialization_id: _,
resolved_specializations: _,
} = self;
let mut new = PendingAbilitiesStore::default();
// 1. Figure out the abilities we need to introduce.
let mut abilities_to_introduce = VecSet::with_capacity(2);
symbols.iter().for_each(|symbol| {
if let Some(member_data) = ability_members.get(symbol) {
// If the symbol is member of an ability, we need to capture the entire ability.
abilities_to_introduce.insert(member_data.parent_ability);
}
if members_of_ability.contains_key(symbol) {
abilities_to_introduce.insert(*symbol);
}
});
// 2. Add each ability, and any specializations of its members we know about.
for ability in abilities_to_introduce.into_iter() {
let members = members_of_ability.get(&ability).unwrap();
let mut imported_member_data = Vec::with_capacity(members.len());
for member in members {
let AbilityMemberData {
parent_ability,
region,
typ: _,
} = ability_members.get(member).unwrap().clone();
// All external members need to be marked as imported. We'll figure out their real
// type variables when it comes time to solve the module we're currently importing
// into.
let imported_data = AbilityMemberData {
parent_ability,
region,
typ: PendingMemberType::Imported,
};
imported_member_data.push((*member, imported_data));
}
new.register_ability(ability, imported_member_data);
// Add any specializations of the ability's members we know about.
declared_specializations
.iter()
.filter(|((member, _), _)| members.contains(member))
.for_each(|(&(member, typ), specialization)| {
new.register_specializing_symbol(specialization.symbol, member);
new.import_specialization(member, typ, specialization);
});
}
new
}
pub fn union(&mut self, other: Self) {
let Self {
members_of_ability: other_members_of_ability,

View file

@ -46,7 +46,7 @@ use roc_solve::module::SolvedModule;
use roc_solve::solve;
use roc_target::TargetInfo;
use roc_types::solved_types::Solved;
use roc_types::subs::{Subs, VarStore, Variable};
use roc_types::subs::{ExposedTypesStorageSubs, Subs, VarStore, Variable};
use roc_types::types::{Alias, AliasKind};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::HashMap;
@ -510,6 +510,7 @@ pub struct LoadedModule {
pub dep_idents: IdentIdsByModule,
pub exposed_aliases: MutMap<Symbol, Alias>,
pub exposed_values: Vec<Symbol>,
pub exposed_types_storage: ExposedTypesStorageSubs,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
pub documentation: MutMap<ModuleId, ModuleDocumentation>,
@ -687,6 +688,7 @@ enum Msg<'a> {
solved_subs: Solved<Subs>,
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
exposed_aliases_by_symbol: MutMap<Symbol, (bool, Alias)>,
exposed_types_storage: ExposedTypesStorageSubs,
dep_idents: IdentIdsByModule,
documentation: MutMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
@ -1404,6 +1406,7 @@ fn state_thread_step<'a>(
solved_subs,
exposed_vars_by_symbol,
exposed_aliases_by_symbol,
exposed_types_storage,
dep_idents,
documentation,
abilities_store,
@ -1421,6 +1424,7 @@ fn state_thread_step<'a>(
solved_subs,
exposed_aliases_by_symbol,
exposed_vars_by_symbol,
exposed_types_storage,
dep_idents,
documentation,
abilities_store,
@ -2209,6 +2213,7 @@ fn update<'a>(
solved_subs,
exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol,
exposed_aliases_by_symbol: solved_module.aliases,
exposed_types_storage: solved_module.exposed_types,
dep_idents,
documentation,
abilities_store,
@ -2663,6 +2668,7 @@ fn finish(
solved: Solved<Subs>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
exposed_types_storage: ExposedTypesStorageSubs,
dep_idents: IdentIdsByModule,
documentation: MutMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
@ -2697,6 +2703,7 @@ fn finish(
exposed_aliases: exposed_aliases_by_symbol,
exposed_values,
exposed_to_host: exposed_vars_by_symbol.into_iter().collect(),
exposed_types_storage,
sources,
timings: state.timings,
documentation,
@ -3738,7 +3745,7 @@ impl<'a> BuildTask<'a> {
}
}
fn add_imports(
pub fn add_imports(
my_module: ModuleId,
subs: &mut Subs,
mut pending_abilities: PendingAbilitiesStore,
@ -5013,7 +5020,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
/// Generic number types (Num, Int, Float, etc.) are treated as `DelayedAlias`es resolved during
/// type solving.
/// All that remains are Signed8, Signed16, etc.
fn default_aliases() -> roc_solve::solve::Aliases {
pub fn default_aliases() -> roc_solve::solve::Aliases {
use roc_types::types::Type;
let mut solve_aliases = roc_solve::solve::Aliases::default();

View file

@ -0,0 +1,28 @@
[package]
name = "test_derivers"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2021"
[[test]]
name = "test_derives"
path = "src/tests.rs"
[dev-dependencies]
roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_builtins = { path = "../builtins" }
roc_load_internal = { path = "../load_internal" }
roc_can = { path = "../can" }
roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_types = { path = "../types" }
roc_reporting = { path = "../../reporting" }
roc_constrain = { path = "../constrain" }
roc_region = { path = "../region" }
roc_solve = { path = "../solve" }
bumpalo = { version = "3.8.0", features = ["collections"] }
lazy_static = "1.4.0"
indoc = "1.0.3"
ven_pretty = { path = "../../vendor/pretty" }

View file

@ -0,0 +1,198 @@
#![cfg(test)]
use std::path::PathBuf;
use bumpalo::Bump;
use roc_can::{
abilities::{AbilitiesStore, ResolvedSpecializations},
constraint::Constraints,
expected::Expected,
expr::Expr,
module::RigidVariables,
};
use roc_collections::VecSet;
use roc_constrain::{
expr::constrain_expr,
module::{ExposedByModule, ExposedForModule, ExposedModuleTypes},
};
use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading};
use roc_module::{
ident::ModuleName,
symbol::{IdentIds, Interns, ModuleId},
};
use roc_mono::derivers::encoding::Env;
use roc_region::all::{LineInfo, Region};
use roc_reporting::report::{type_problem, RocDocAllocator};
use roc_types::{
subs::{Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, Variable},
types::{RecordField, Type},
};
use ven_pretty::DocAllocator;
use roc_mono::derivers::{encoding, synth_var};
fn encode_path() -> PathBuf {
let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?");
PathBuf::from(repo_root)
.join("compiler")
.join("builtins")
.join("roc")
.join("Encode.roc")
}
fn check_derived_typechecks(
derived: Expr,
test_module: ModuleId,
mut test_subs: Subs,
interns: &Interns,
exposed_encode_types: ExposedTypesStorageSubs,
encode_abilities_store: AbilitiesStore,
) {
let mut constraints = Constraints::new();
let mut env = roc_constrain::expr::Env {
rigids: Default::default(),
resolutions_to_make: Default::default(),
home: test_module,
};
let constr = constrain_expr(
&mut constraints,
&mut env,
Region::zero(),
&derived,
Expected::NoExpectation(Type::Variable(test_subs.fresh_unnamed_flex_var())),
);
let encode_values_to_import = exposed_encode_types
.stored_vars_by_symbol
.keys()
.copied()
.collect::<VecSet<_>>();
let pending_abilities = encode_abilities_store.closure_from_imported(&encode_values_to_import);
let mut exposed_by_module = ExposedByModule::default();
exposed_by_module.insert(
ModuleId::ENCODE,
ExposedModuleTypes {
exposed_types_storage_subs: exposed_encode_types,
resolved_specializations: ResolvedSpecializations::default(),
},
);
let exposed_for_module =
ExposedForModule::new(encode_values_to_import.iter(), exposed_by_module);
let mut def_types = Default::default();
let mut rigid_vars = Default::default();
let (import_variables, abilities_store) = add_imports(
test_module,
&mut test_subs,
pending_abilities,
exposed_for_module,
&mut def_types,
&mut rigid_vars,
);
let constr =
constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables);
let (_solved_subs, _, problems, _) = roc_solve::module::run_solve(
&constraints,
constr,
RigidVariables::default(),
test_subs,
default_aliases(),
abilities_store,
Default::default(),
);
if !problems.is_empty() {
let filename = PathBuf::from("Test.roc");
let lines = LineInfo::new(" ");
let src_lines = vec![" "];
let mut reports = Vec::new();
let alloc = RocDocAllocator::new(&src_lines, test_module, &interns);
for problem in problems.into_iter() {
if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) {
reports.push(report);
}
}
let has_reports = !reports.is_empty();
let doc = alloc
.stack(reports.into_iter().map(|v| v.pretty(&alloc)))
.append(if has_reports {
alloc.line()
} else {
alloc.nil()
});
let mut buf = String::new();
doc.1
.render_raw(80, &mut roc_reporting::report::CiWrite::new(&mut buf))
.unwrap();
panic!("Derived does not typecheck:\n{}", buf);
}
}
fn derive_test<S>(synth_input: S)
where
S: FnOnce(&mut Subs) -> Variable,
{
let arena = Bump::new();
let source = roc_builtins::roc::module_source(ModuleId::ENCODE);
let target_info = roc_target::TargetInfo::default_x86_64();
let LoadedModule {
mut interns,
exposed_types_storage: mut exposed_encode_types,
abilities_store,
..
} = roc_load_internal::file::load_and_typecheck_str(
&arena,
encode_path().file_name().unwrap().into(),
source,
&encode_path().parent().unwrap(),
Default::default(),
target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
Threading::AllAvailable,
)
.unwrap();
let test_module = interns.module_id(&ModuleName::from("Test"));
let mut test_subs = Subs::new();
let mut test_ident_ids = IdentIds::default();
let mut env = Env {
home: test_module,
arena: &arena,
subs: &mut test_subs,
ident_ids: &mut test_ident_ids,
exposed_encode_types: &mut exposed_encode_types,
};
let signature_var = synth_input(env.subs);
let derived = encoding::derive_to_encoder(&mut env, signature_var);
check_derived_typechecks(
derived,
test_module,
test_subs,
&interns,
exposed_encode_types,
abilities_store,
);
}
macro_rules! synth {
(rcd{ $($field:literal: $typ:expr),* }) => {
|subs| {
let fields = RecordFields::insert_into_subs(subs, vec![ $( ($field.into(), RecordField::Required($typ)) ,)* ]);
synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)))
}
}
}
#[test]
fn one_field_record() {
derive_test(synth!(rcd{ "a": Variable::U8 }))
}

View file

@ -0,0 +1,3 @@
#![cfg(test)]
mod encoding;