mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 22:34:45 +00:00
only generate the functions that the user wants
This commit is contained in:
parent
1e4d2b3372
commit
5e16515d22
5 changed files with 155 additions and 60 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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.";
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue