mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24: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::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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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.";
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue