only generate the functions that the user wants

This commit is contained in:
Folkert 2022-02-06 12:26:34 +01:00
parent 1e4d2b3372
commit 5e16515d22
5 changed files with 155 additions and 60 deletions

View file

@ -12,54 +12,40 @@ use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::Type;
/// Functions that are always implemented for Effect
type Builder = for<'r, 's, 't0, 't1> fn(
&'r mut Env<'s>,
&'t0 mut Scope,
Symbol,
TagName,
&'t1 mut VarStore,
) -> (Symbol, Def);
#[derive(Default, Clone, Copy)]
pub(crate) struct HostedGeneratedFunctions {
pub(crate) after: bool,
pub(crate) map: bool,
pub(crate) always: bool,
pub(crate) loop_: bool,
pub(crate) forever: bool,
}
pub const BUILTIN_EFFECT_FUNCTIONS: &[(&str, Builder)] = &[
// Effect.after : Effect a, (a -> Effect b) -> Effect b
("after", build_effect_after),
// Effect.map : Effect a, (a -> b) -> Effect b
("map", build_effect_map),
// Effect.always : a -> Effect a
("always", build_effect_always),
// Effect.forever : Effect a -> Effect b
("forever", build_effect_forever),
// Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b
("loop", build_effect_loop),
];
const RECURSIVE_BUILTIN_EFFECT_FUNCTIONS: &[&str] = &["forever", "loop"];
// the Effects alias & associated functions
//
// A platform can define an Effect type in its header. It can have an arbitrary name
// (e.g. Task, IO), but we'll call it an Effect in general.
//
// From that name, we generate an effect module, an effect alias, and some functions.
//
// The effect alias is implemented as
//
// Effect a : [ @Effect ({} -> a) ]
//
// For this alias we implement the functions defined in BUILTIN_EFFECT_FUNCTIONS with the
// standard implementation.
pub fn build_effect_builtins(
/// the Effects alias & associated functions
///
/// A platform can define an Effect type in its header. It can have an arbitrary name
/// (e.g. Task, IO), but we'll call it an Effect in general.
///
/// From that name, we generate an effect module, an effect alias, and some functions.
///
/// The effect alias is implemented as
///
/// Effect a : [ @Effect ({} -> a) ]
///
/// For this alias we implement the functions specified in HostedGeneratedFunctions with the
/// standard implementation.
pub(crate) fn build_effect_builtins(
env: &mut Env,
scope: &mut Scope,
effect_symbol: Symbol,
var_store: &mut VarStore,
exposed_symbols: &mut MutSet<Symbol>,
declarations: &mut Vec<Declaration>,
generated_functions: HostedGeneratedFunctions,
) {
for (name, f) in BUILTIN_EFFECT_FUNCTIONS.iter() {
let (symbol, def) = f(
macro_rules! helper {
($f:expr) => {{
let (symbol, def) = $f(
env,
scope,
effect_symbol,
@ -67,14 +53,40 @@ pub fn build_effect_builtins(
var_store,
);
// make the outside world know this symbol exists
exposed_symbols.insert(symbol);
let is_recursive = RECURSIVE_BUILTIN_EFFECT_FUNCTIONS.iter().any(|n| n == name);
if is_recursive {
declarations.push(Declaration::DeclareRec(vec![def]));
} else {
def
}};
}
if generated_functions.after {
let def = helper!(build_effect_after);
declarations.push(Declaration::Declare(def));
}
// Effect.map : Effect a, (a -> b) -> Effect b
if generated_functions.map {
let def = helper!(build_effect_map);
declarations.push(Declaration::Declare(def));
}
// Effect.always : a -> Effect a
if generated_functions.always {
let def = helper!(build_effect_always);
declarations.push(Declaration::Declare(def));
}
// Effect.forever : Effect a -> Effect b
if generated_functions.forever {
let def = helper!(build_effect_forever);
declarations.push(Declaration::DeclareRec(vec![def]));
}
// Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b
if generated_functions.loop_ {
let def = helper!(build_effect_loop);
declarations.push(Declaration::DeclareRec(vec![def]));
}
// Useful when working on functions in this module. By default symbols that we named do now

View file

@ -1,4 +1,5 @@
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
use crate::expr::{ClosureData, Expr, Output};
use crate::operator::desugar_def;
@ -40,6 +41,30 @@ pub struct ModuleOutput {
pub scope: Scope,
}
fn validate_generate_with<'a>(
generate_with: &'a [Loc<roc_parse::header::ExposedName<'a>>],
) -> (HostedGeneratedFunctions, Vec<Loc<Ident>>) {
let mut functions = HostedGeneratedFunctions::default();
let mut unknown = Vec::new();
for generated in generate_with {
match generated.value.as_str() {
"after" => functions.after = true,
"map" => functions.map = true,
"always" => functions.always = true,
"loop" => functions.loop_ = true,
"forever" => functions.forever = true,
other => {
// we don't know how to generate this function
let ident = Ident::from(other);
unknown.push(Loc::at(generated.region, ident));
}
}
}
(functions, unknown)
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a, F>(
@ -68,9 +93,23 @@ where
scope.add_alias(name, alias.region, alias.type_variables, alias.typ);
}
let effect_symbol = if let HeaderFor::Hosted { generates, .. } = header_for {
// TODO extract effect name from the header
struct Hosted {
effect_symbol: Symbol,
generated_functions: HostedGeneratedFunctions,
}
let hosted_info = if let HeaderFor::Hosted {
generates,
generates_with,
} = header_for
{
let name: &str = generates.into();
let (generated_functions, unknown_generated) = validate_generate_with(generates_with);
for unknown in unknown_generated {
env.problem(Problem::UnknownGeneratesWith(unknown));
}
let effect_symbol = scope
.introduce(
name.into(),
@ -99,7 +138,10 @@ where
);
}
Some(effect_symbol)
Some(Hosted {
effect_symbol,
generated_functions,
})
} else {
None
};
@ -246,7 +288,11 @@ where
(Ok(mut declarations), output) => {
use crate::def::Declaration::*;
if let Some(effect_symbol) = effect_symbol {
if let Some(Hosted {
effect_symbol,
generated_functions,
}) = hosted_info
{
let mut exposed_symbols = MutSet::default();
// NOTE this currently builds all functions, not just the ones that the user requested
@ -257,6 +303,7 @@ where
var_store,
&mut exposed_symbols,
&mut declarations,
generated_functions,
);
}
@ -277,7 +324,7 @@ where
// Temporary hack: we don't know exactly what symbols are hosted symbols,
// and which are meant to be normal definitions without a body. So for now
// we just assume they are hosted functions (meant to be provided by the platform)
if let Some(effect_symbol) = effect_symbol {
if let Some(Hosted { effect_symbol, .. }) = hosted_info {
macro_rules! make_hosted_def {
() => {
let symbol = def.pattern_vars.iter().next().unwrap().0;
@ -358,7 +405,7 @@ where
let mut aliases = MutMap::default();
if let Some(effect_symbol) = effect_symbol {
if let Some(Hosted { effect_symbol, .. }) = hosted_info {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any

View file

@ -25,6 +25,7 @@ pub enum Problem {
UnusedDef(Symbol, Region),
UnusedImport(ModuleId, Region),
ExposedButNotDefined(Symbol),
UnknownGeneratesWith(Loc<Ident>),
/// First symbol is the name of the closure with that argument
/// Second symbol is the name of the argument that is unused
UnusedArgument(Symbol, Symbol, Region),

View file

@ -16,7 +16,7 @@ const UNUSED_DEF: &str = "UNUSED DEFINITION";
const UNUSED_IMPORT: &str = "UNUSED IMPORT";
const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER";
const UNUSED_ARG: &str = "UNUSED ARGUMENT";
const MISSING_DEFINITION: &str = "MISSING_DEFINITION";
const MISSING_DEFINITION: &str = "MISSING DEFINITION";
const DUPLICATE_FIELD_NAME: &str = "DUPLICATE FIELD NAME";
const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME";
const INVALID_UNICODE: &str = "INVALID UNICODE";
@ -92,6 +92,21 @@ pub fn can_problem<'b>(
title = MISSING_DEFINITION.to_string();
severity = Severity::RuntimeError;
}
Problem::UnknownGeneratesWith(loc_ident) => {
doc = alloc.stack(vec![
alloc
.reflow("I don't know how to generate the ")
.append(alloc.ident(loc_ident.value))
.append(alloc.reflow("function.")),
alloc.region(lines.convert_region(loc_ident.region)),
alloc
.reflow("Only specific functions like `after` and `map` can be generated.")
.append(alloc.reflow("Learn more about hosted modules at TODO.")),
]);
title = MISSING_DEFINITION.to_string();
severity = Severity::RuntimeError;
}
Problem::UnusedArgument(closure_symbol, argument_symbol, region) => {
let line = "\". Adding an underscore at the start of a variable name is a way of saying that the variable is not used.";

View file

@ -517,6 +517,26 @@ fn to_expr_report<'a>(
}
}
EExpr::Record(_erecord, pos) => {
let surroundings = Region::new(start, *pos);
let region = LineColumnRegion::from_pos(lines.convert_pos(*pos));
let doc = alloc.stack(vec![
alloc.reflow(r"I am partway through parsing an record, but I got stuck here:"),
alloc.region_with_subregion(lines.convert_region(surroundings), region),
alloc.concat(vec![
alloc.reflow("TODO provide more context.")
]),
]);
Report {
filename,
doc,
title: "RECORD PARSE PROBLEM".to_string(),
severity: Severity::RuntimeError,
}
}
EExpr::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos),
&EExpr::Number(ENumber::End, pos) => {