mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Add test module for derivers
This commit is contained in:
parent
f1fa29fcfc
commit
ccd78a560f
7 changed files with 329 additions and 70 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -18,6 +18,7 @@ members = [
|
|||
"compiler/mono",
|
||||
"compiler/alias_analysis",
|
||||
"compiler/test_mono",
|
||||
"compiler/test_derivers",
|
||||
"compiler/load",
|
||||
"compiler/load_internal",
|
||||
"compiler/gen_llvm",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
28
compiler/test_derivers/Cargo.toml
Normal file
28
compiler/test_derivers/Cargo.toml
Normal 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" }
|
198
compiler/test_derivers/src/encoding.rs
Normal file
198
compiler/test_derivers/src/encoding.rs
Normal 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 }))
|
||||
}
|
3
compiler/test_derivers/src/tests.rs
Normal file
3
compiler/test_derivers/src/tests.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
#![cfg(test)]
|
||||
|
||||
mod encoding;
|
Loading…
Add table
Add a link
Reference in a new issue