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::subs::{VarStore, Variable};
use roc_types::types::Type; use roc_types::types::Type;
/// Functions that are always implemented for Effect #[derive(Default, Clone, Copy)]
type Builder = for<'r, 's, 't0, 't1> fn( pub(crate) struct HostedGeneratedFunctions {
&'r mut Env<'s>, pub(crate) after: bool,
&'t0 mut Scope, pub(crate) map: bool,
Symbol, pub(crate) always: bool,
TagName, pub(crate) loop_: bool,
&'t1 mut VarStore, pub(crate) forever: bool,
) -> (Symbol, Def); }
pub const BUILTIN_EFFECT_FUNCTIONS: &[(&str, Builder)] = &[ /// the Effects alias & associated functions
// Effect.after : Effect a, (a -> Effect b) -> Effect b ///
("after", build_effect_after), /// A platform can define an Effect type in its header. It can have an arbitrary name
// Effect.map : Effect a, (a -> b) -> Effect b /// (e.g. Task, IO), but we'll call it an Effect in general.
("map", build_effect_map), ///
// Effect.always : a -> Effect a /// From that name, we generate an effect module, an effect alias, and some functions.
("always", build_effect_always), ///
// Effect.forever : Effect a -> Effect b /// The effect alias is implemented as
("forever", build_effect_forever), ///
// Effect.loop : a, (a -> Effect [ Step a, Done b ]) -> Effect b /// Effect a : [ @Effect ({} -> a) ]
("loop", build_effect_loop), ///
]; /// For this alias we implement the functions specified in HostedGeneratedFunctions with the
/// standard implementation.
const RECURSIVE_BUILTIN_EFFECT_FUNCTIONS: &[&str] = &["forever", "loop"]; pub(crate) 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 defined in BUILTIN_EFFECT_FUNCTIONS with the
// standard implementation.
pub fn build_effect_builtins(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
effect_symbol: Symbol, effect_symbol: Symbol,
var_store: &mut VarStore, var_store: &mut VarStore,
exposed_symbols: &mut MutSet<Symbol>, exposed_symbols: &mut MutSet<Symbol>,
declarations: &mut Vec<Declaration>, declarations: &mut Vec<Declaration>,
generated_functions: HostedGeneratedFunctions,
) { ) {
for (name, f) in BUILTIN_EFFECT_FUNCTIONS.iter() { macro_rules! helper {
let (symbol, def) = f( ($f:expr) => {{
let (symbol, def) = $f(
env, env,
scope, scope,
effect_symbol, effect_symbol,
@ -67,14 +53,40 @@ pub fn build_effect_builtins(
var_store, var_store,
); );
// make the outside world know this symbol exists
exposed_symbols.insert(symbol); exposed_symbols.insert(symbol);
let is_recursive = RECURSIVE_BUILTIN_EFFECT_FUNCTIONS.iter().any(|n| n == name); def
if is_recursive { }};
declarations.push(Declaration::DeclareRec(vec![def])); }
} else {
if generated_functions.after {
let def = helper!(build_effect_after);
declarations.push(Declaration::Declare(def)); 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 // 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::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env; use crate::env::Env;
use crate::expr::{ClosureData, Expr, Output}; use crate::expr::{ClosureData, Expr, Output};
use crate::operator::desugar_def; use crate::operator::desugar_def;
@ -40,6 +41,30 @@ pub struct ModuleOutput {
pub scope: Scope, 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 // TODO trim these down
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a, F>( pub fn canonicalize_module_defs<'a, F>(
@ -68,9 +93,23 @@ where
scope.add_alias(name, alias.region, alias.type_variables, alias.typ); scope.add_alias(name, alias.region, alias.type_variables, alias.typ);
} }
let effect_symbol = if let HeaderFor::Hosted { generates, .. } = header_for { struct Hosted {
// TODO extract effect name from the header effect_symbol: Symbol,
generated_functions: HostedGeneratedFunctions,
}
let hosted_info = if let HeaderFor::Hosted {
generates,
generates_with,
} = header_for
{
let name: &str = generates.into(); 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 let effect_symbol = scope
.introduce( .introduce(
name.into(), name.into(),
@ -99,7 +138,10 @@ where
); );
} }
Some(effect_symbol) Some(Hosted {
effect_symbol,
generated_functions,
})
} else { } else {
None None
}; };
@ -246,7 +288,11 @@ where
(Ok(mut declarations), output) => { (Ok(mut declarations), output) => {
use crate::def::Declaration::*; 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(); let mut exposed_symbols = MutSet::default();
// NOTE this currently builds all functions, not just the ones that the user requested // NOTE this currently builds all functions, not just the ones that the user requested
@ -257,6 +303,7 @@ where
var_store, var_store,
&mut exposed_symbols, &mut exposed_symbols,
&mut declarations, &mut declarations,
generated_functions,
); );
} }
@ -277,7 +324,7 @@ where
// Temporary hack: we don't know exactly what symbols are hosted symbols, // 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 // 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) // 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 { macro_rules! make_hosted_def {
() => { () => {
let symbol = def.pattern_vars.iter().next().unwrap().0; let symbol = def.pattern_vars.iter().next().unwrap().0;
@ -358,7 +405,7 @@ where
let mut aliases = MutMap::default(); 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, // Remove this from exposed_symbols,
// so that at the end of the process, // so that at the end of the process,
// we can see if there were any // we can see if there were any

View file

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

View file

@ -16,7 +16,7 @@ const UNUSED_DEF: &str = "UNUSED DEFINITION";
const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_IMPORT: &str = "UNUSED IMPORT";
const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER";
const UNUSED_ARG: &str = "UNUSED ARGUMENT"; 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_FIELD_NAME: &str = "DUPLICATE FIELD NAME";
const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME"; const DUPLICATE_TAG_NAME: &str = "DUPLICATE TAG NAME";
const INVALID_UNICODE: &str = "INVALID UNICODE"; const INVALID_UNICODE: &str = "INVALID UNICODE";
@ -92,6 +92,21 @@ pub fn can_problem<'b>(
title = MISSING_DEFINITION.to_string(); title = MISSING_DEFINITION.to_string();
severity = Severity::RuntimeError; 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) => { 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."; 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::Space(error, pos) => to_space_report(alloc, lines, filename, error, *pos),
&EExpr::Number(ENumber::End, pos) => { &EExpr::Number(ENumber::End, pos) => {