From 59831f2e8505906c47dedcac2a6700e455d996a9 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 22 Sep 2021 21:02:43 +0200 Subject: [PATCH 01/12] more refactoring for seperate ast crate --- Cargo.lock | 19 + Cargo.toml | 1 + ast/Cargo.toml | 24 + ast/src/ast_error.rs | 30 + ast/src/canonicalize/canonicalize.rs | 289 ++++ ast/src/canonicalize/mod.rs | 2 + ast/src/canonicalize/module.rs | 319 ++++ ast/src/constrain.rs | 1746 ++++++++++++++++++++ ast/src/lang/core/ast.rs | 31 + ast/src/lang/core/def/def.rs | 1437 +++++++++++++++++ ast/src/lang/core/def/def2.rs | 15 + ast/src/lang/core/def/def_to_def2.rs | 75 + ast/src/lang/core/def/mod.rs | 3 + ast/src/lang/core/expr/expr2.rs | 235 +++ ast/src/lang/core/expr/expr2_to_string.rs | 136 ++ ast/src/lang/core/expr/expr_to_expr2.rs | 694 ++++++++ ast/src/lang/core/expr/introduced_vars.rs | 52 + ast/src/lang/core/expr/mod.rs | 5 + ast/src/lang/core/expr/output.rs | 28 + ast/src/lang/core/fun_def.rs | 56 + ast/src/lang/core/header.rs | 12 + ast/src/lang/core/mod.rs | 8 + ast/src/lang/core/pattern.rs | 626 ++++++++ ast/src/lang/core/types.rs | 867 ++++++++++ ast/src/lang/core/val_def.rs | 91 ++ ast/src/lang/env.rs | 168 ++ ast/src/lang/mod.rs | 4 + ast/src/lang/rigids.rs | 80 + ast/src/lang/scope.rs | 330 ++++ ast/src/lib.rs | 5 + ast/src/mod.rs | 14 + ast/src/parse/parse_ast.rs | 34 + ast/src/parse/parse_expr.rs | 0 ast/src/parse/parse_header.rs | 11 + ast/src/pool/mod.rs | 4 + ast/src/pool/pool.rs | 228 +++ ast/src/pool/pool_str.rs | 88 ++ ast/src/pool/pool_vec.rs | 324 ++++ ast/src/pool/shallow_clone.rs | 33 + ast/src/roc_file.rs | 133 ++ ast/src/solve_type.rs | 1752 +++++++++++++++++++++ editor/Cargo.toml | 1 - editor/src/lang/pool.rs | 24 +- 43 files changed, 10010 insertions(+), 24 deletions(-) create mode 100644 ast/Cargo.toml create mode 100644 ast/src/ast_error.rs create mode 100644 ast/src/canonicalize/canonicalize.rs create mode 100644 ast/src/canonicalize/mod.rs create mode 100644 ast/src/canonicalize/module.rs create mode 100644 ast/src/constrain.rs create mode 100644 ast/src/lang/core/ast.rs create mode 100644 ast/src/lang/core/def/def.rs create mode 100644 ast/src/lang/core/def/def2.rs create mode 100644 ast/src/lang/core/def/def_to_def2.rs create mode 100644 ast/src/lang/core/def/mod.rs create mode 100644 ast/src/lang/core/expr/expr2.rs create mode 100644 ast/src/lang/core/expr/expr2_to_string.rs create mode 100644 ast/src/lang/core/expr/expr_to_expr2.rs create mode 100644 ast/src/lang/core/expr/introduced_vars.rs create mode 100644 ast/src/lang/core/expr/mod.rs create mode 100644 ast/src/lang/core/expr/output.rs create mode 100644 ast/src/lang/core/fun_def.rs create mode 100644 ast/src/lang/core/header.rs create mode 100644 ast/src/lang/core/mod.rs create mode 100644 ast/src/lang/core/pattern.rs create mode 100644 ast/src/lang/core/types.rs create mode 100644 ast/src/lang/core/val_def.rs create mode 100644 ast/src/lang/env.rs create mode 100644 ast/src/lang/mod.rs create mode 100644 ast/src/lang/rigids.rs create mode 100644 ast/src/lang/scope.rs create mode 100644 ast/src/lib.rs create mode 100644 ast/src/mod.rs create mode 100644 ast/src/parse/parse_ast.rs create mode 100644 ast/src/parse/parse_expr.rs create mode 100644 ast/src/parse/parse_header.rs create mode 100644 ast/src/pool/mod.rs create mode 100644 ast/src/pool/pool.rs create mode 100644 ast/src/pool/pool_str.rs create mode 100644 ast/src/pool/pool_vec.rs create mode 100644 ast/src/pool/shallow_clone.rs create mode 100644 ast/src/roc_file.rs create mode 100644 ast/src/solve_type.rs diff --git a/Cargo.lock b/Cargo.lock index 1a03a50a6f..a7d51f68b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3387,6 +3387,25 @@ dependencies = [ "libc", ] +[[package]] +name = "roc_ast" +version = "0.1.0" +dependencies = [ + "arraystring", + "bumpalo", + "libc", + "page_size", + "roc_can", + "roc_collections", + "roc_module", + "roc_parse", + "roc_problem", + "roc_region", + "roc_types", + "snafu", + "ven_graph", +] + [[package]] name = "roc_build" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7363ba8600..e2864e9243 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "vendor/pathfinding", "vendor/pretty", "editor", + "ast", "cli", "cli/cli_utils", "roc_std", diff --git a/ast/Cargo.toml b/ast/Cargo.toml new file mode 100644 index 0000000000..8b03f69880 --- /dev/null +++ b/ast/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "roc_ast" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "AST as used by the editor and docs. In contrast to the compiler, these types do not keep track of a location in a file." + +[dependencies] +roc_can = { path = "../compiler/can" } +roc_collections = { path = "../compiler/collections" } +roc_region = { path = "../compiler/region" } +roc_module = { path = "../compiler/module" } +roc_parse = { path = "../compiler/parse" } +roc_problem = { path = "../compiler/problem" } +roc_types = { path = "../compiler/types" } +arraystring = "0.3.0" +bumpalo = { version = "3.6.1", features = ["collections"] } +libc = "0.2" +page_size = "0.4" +snafu = { version = "0.6", features = ["backtraces"] } +ven_graph = { path = "../vendor/pathfinding" } + +[dev-dependencies] \ No newline at end of file diff --git a/ast/src/ast_error.rs b/ast/src/ast_error.rs new file mode 100644 index 0000000000..b18970e852 --- /dev/null +++ b/ast/src/ast_error.rs @@ -0,0 +1,30 @@ + + +use snafu::{Backtrace, Snafu}; + +use crate::lang::core::ast::ASTNodeId; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum ASTError { + #[snafu(display( + "ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .", + ast_node_id + ))] + ASTNodeIdWithoutExprId { + ast_node_id: ASTNodeId, + backtrace: Backtrace, + }, + #[snafu(display( + "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", + required_pattern2, + encountered_pattern2, + ))] + UnexpectedPattern2Variant { + required_pattern2: String, + encountered_pattern2: String, + backtrace: Backtrace, + }, +} + +pub type ASTResult = std::result::Result; \ No newline at end of file diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs new file mode 100644 index 0000000000..1e74257bc5 --- /dev/null +++ b/ast/src/canonicalize/canonicalize.rs @@ -0,0 +1,289 @@ + +use roc_can::{env::Env, expr::Output, scope::Scope}; +use roc_collections::all::MutMap; +use roc_problem::can::{Problem}; +use roc_region::all::{Located, Region}; +use roc_types::{subs::Variable}; + +use crate::{lang::core::{def::def::References, expr::expr2::{Expr2, ExprId, RecordField, WhenBranch}}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; + +enum CanonicalizeRecordProblem { + InvalidOptionalValue { + field_name: PoolStr, + field_region: Region, + record_region: Region, + }, +} + +enum FieldVar { + VarAndExprId(Variable, ExprId), + OnlyVar(Variable), +} + +fn canonicalize_fields<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + fields: &'a [Located>>], +) -> Result<(PoolVec, Output), CanonicalizeRecordProblem> { + let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); + let mut output = Output::default(); + + for loc_field in fields.iter() { + match canonicalize_field(env, scope, &loc_field.value) { + Ok(can_field) => { + match can_field { + CanonicalField::LabelAndValue { + label, + value_expr, + value_output, + var, + } => { + let expr_id = env.pool.add(value_expr); + + let replaced = + can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id)); + + if let Some(_old) = replaced { + // env.problems.push(Problem::DuplicateRecordFieldValue { + // field_name: label, + // field_region: loc_field.region, + // record_region: region, + // replaced_region: old.region, + // }); + todo!() + } + + output.references.union_mut(value_output.references); + } + CanonicalField::InvalidLabelOnly { label, var } => { + let replaced = can_fields.insert(label, FieldVar::OnlyVar(var)); + + if let Some(_old) = replaced { + todo!() + } + } + } + } + + Err(CanonicalizeFieldProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + }) => { + // env.problem(Problem::InvalidOptionalValue { + // field_name: field_name.clone(), + // field_region, + // record_region: region, + // }); + // return Err(CanonicalizeRecordProblem::InvalidOptionalValue { + // field_name, + // field_region, + // record_region: region, + // }); + todo!() + } + } + } + + let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool); + + for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) { + let name = PoolStr::new(string, env.pool); + + match field_var { + FieldVar::VarAndExprId(var, expr_id) => { + env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); + } + FieldVar::OnlyVar(var) => { + env.pool[node_id] = RecordField::InvalidLabelOnly(name, var); + } // TODO RecordField::LabelOnly + } + } + + Ok((pool_vec, output)) +} + +enum CanonicalizeFieldProblem { + InvalidOptionalValue { + field_name: PoolStr, + field_region: Region, + }, +} +enum CanonicalField<'a> { + LabelAndValue { + label: &'a str, + value_expr: Expr2, + value_output: Output, + var: Variable, + }, + InvalidLabelOnly { + label: &'a str, + var: Variable, + }, // TODO make ValidLabelOnly +} +fn canonicalize_field<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>, +) -> Result, CanonicalizeFieldProblem> { + use roc_parse::ast::AssignedField::*; + + match field { + // Both a label and a value, e.g. `{ name: "blah" }` + RequiredValue(label, _, loc_expr) => { + let field_var = env.var_store.fresh(); + let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); + + Ok(CanonicalField::LabelAndValue { + label: label.value, + value_expr: loc_can_expr, + value_output: output, + var: field_var, + }) + } + + OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { + field_name: PoolStr::new(label.value, env.pool), + field_region: Region::span_across(&label.region, &loc_expr.region), + }), + + // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) + LabelOnly(label) => { + let field_var = env.var_store.fresh(); + // TODO return ValidLabel if label points to in scope variable + Ok(CanonicalField::InvalidLabelOnly { + label: label.value, + var: field_var, + }) + } + + SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => { + canonicalize_field(env, scope, sub_field) + } + + Malformed(_string) => { + panic!("TODO canonicalize malformed record field"); + } + } +} + +#[inline(always)] +fn canonicalize_when_branch<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + branch: &'a roc_parse::ast::WhenBranch<'a>, + output: &mut Output, +) -> (WhenBranch, References) { + let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool); + + let original_scope = scope; + let mut scope = original_scope.shallow_clone(); + + // TODO report symbols not bound in all patterns + for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) { + let (new_output, can_pattern) = to_pattern2( + env, + &mut scope, + roc_parse::pattern::PatternType::WhenBranch, + &loc_pattern.value, + loc_pattern.region, + ); + + output.union(new_output); + + env.set_region(node_id, loc_pattern.region); + env.pool[node_id] = can_pattern; + } + + let (value, mut branch_output) = + to_expr2(env, &mut scope, &branch.value.value, branch.value.region); + let value_id = env.pool.add(value); + env.set_region(value_id, branch.value.region); + + let guard = match &branch.guard { + None => None, + Some(loc_expr) => { + let (can_guard, guard_branch_output) = + to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region); + + let expr_id = env.pool.add(can_guard); + env.set_region(expr_id, loc_expr.region); + + branch_output.union(guard_branch_output); + Some(expr_id) + } + }; + + // Now that we've collected all the references for this branch, check to see if + // any of the new idents it defined were unused. If any were, report it. + for (symbol, region) in scope.symbols() { + let symbol = symbol; + + if !output.references.has_lookup(symbol) + && !branch_output.references.has_lookup(symbol) + && !original_scope.contains_symbol(symbol) + { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + let references = branch_output.references.clone(); + output.union(branch_output); + + ( + WhenBranch { + patterns, + body: value_id, + guard, + }, + references, + ) +} + +fn canonicalize_lookup( + env: &mut Env<'_>, + scope: &mut Scope, + module_name: &str, + ident: &str, + region: Region, +) -> (Expr2, Output) { + use Expr2::*; + + let mut output = Output::default(); + let can_expr = if module_name.is_empty() { + // Since module_name was empty, this is an unqualified var. + // Look it up in scope! + match scope.lookup(&(*ident).into(), region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(problem) => { + env.problem(Problem::RuntimeError(problem.clone())); + + RuntimeError() + } + } + } else { + // Since module_name was nonempty, this is a qualified var. + // Look it up in the env! + match env.qualified_lookup(module_name, ident, region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(problem) => { + // Either the module wasn't imported, or + // it was imported but it doesn't expose this ident. + env.problem(Problem::RuntimeError(problem.clone())); + + RuntimeError() + } + } + }; + + // If it's valid, this ident should be in scope already. + + (can_expr, output) +} \ No newline at end of file diff --git a/ast/src/canonicalize/mod.rs b/ast/src/canonicalize/mod.rs new file mode 100644 index 0000000000..45b2084adf --- /dev/null +++ b/ast/src/canonicalize/mod.rs @@ -0,0 +1,2 @@ +pub mod canonicalize; +pub mod module; \ No newline at end of file diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalize/module.rs new file mode 100644 index 0000000000..28d37e18cb --- /dev/null +++ b/ast/src/canonicalize/module.rs @@ -0,0 +1,319 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] +use bumpalo::Bump; +use roc_can::operator::desugar_def; +use roc_can::scope::Scope; +use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_module::ident::Ident; +use roc_module::ident::Lowercase; +use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_parse::ast; +use roc_parse::pattern::PatternType; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore, Variable}; + +use crate::env::Env; +use crate::expr::output::Output; +use crate::lang::expr::output::Output; +use crate::pool::pool::NodeId; +use crate::pool::pool::Pool; +use crate::pool::pool_vec::PoolVec; +use crate::types::Alias; + +pub struct ModuleOutput { + pub aliases: MutMap>, + pub rigid_variables: MutMap, + pub declarations: Vec, + pub exposed_imports: MutMap, + pub lookups: Vec<(Symbol, Variable, Region)>, + pub problems: Vec, + pub ident_ids: IdentIds, + pub references: MutSet, +} + +// TODO trim these down +#[allow(clippy::too_many_arguments)] +pub fn canonicalize_module_defs<'a>( + arena: &Bump, + loc_defs: &'a [Located>], + home: ModuleId, + module_ids: &ModuleIds, + exposed_ident_ids: IdentIds, + dep_idents: MutMap, + aliases: MutMap, + exposed_imports: MutMap, + mut exposed_symbols: MutSet, + var_store: &mut VarStore, +) -> Result { + let mut pool = Pool::with_capacity(1 << 10); + let mut can_exposed_imports = MutMap::default(); + let mut scope = Scope::new(home, &mut pool, var_store); + let num_deps = dep_idents.len(); + + for (name, alias) in aliases.into_iter() { + let vars = PoolVec::with_capacity(alias.targs.len() as u32, &mut pool); + + for (node_id, targ_id) in vars.iter_node_ids().zip(alias.targs.iter_node_ids()) { + let (poolstr, var) = &pool[targ_id]; + pool[node_id] = (poolstr.shallow_clone(), *var); + } + scope.add_alias(&mut pool, name, vars, alias.actual); + } + + // Desugar operators (convert them to Apply calls, taking into account + // operator precedence and associativity rules), before doing other canonicalization. + // + // If we did this *during* canonicalization, then each time we + // visited a BinOp node we'd recursively try to apply this to each of its nested + // operators, and then again on *their* nested operators, ultimately applying the + // rules multiple times unnecessarily. + let mut desugared = + bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena); + + for loc_def in loc_defs.iter() { + desugared.push(&*arena.alloc(Located { + value: desugar_def(arena, &loc_def.value), + region: loc_def.region, + })); + } + + let mut env = Env::new( + home, + arena, + &mut pool, + var_store, + dep_idents, + module_ids, + exposed_ident_ids, + ); + let mut lookups = Vec::with_capacity(num_deps); + let rigid_variables = MutMap::default(); + + // Exposed values are treated like defs that appear before any others, e.g. + // + // imports [ Foo.{ bar, baz } ] + // + // ...is basically the same as if we'd added these extra defs at the start of the module: + // + // bar = Foo.bar + // baz = Foo.baz + // + // Here we essentially add those "defs" to "the beginning of the module" + // by canonicalizing them right before we canonicalize the actual ast::Def nodes. + for (ident, (symbol, region)) in exposed_imports { + let first_char = ident.as_inline_str().chars().next().unwrap(); + + if first_char.is_lowercase() { + // this is a value definition + let expr_var = env.var_store.fresh(); + + match scope.import(ident, symbol, region) { + Ok(()) => { + // Add an entry to exposed_imports using the current module's name + // as the key; e.g. if this is the Foo module and we have + // exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when + // anything references `baz` in this Foo module, it will resolve to Bar.baz. + can_exposed_imports.insert(symbol, expr_var); + + // This will be used during constraint generation, + // to add the usual Lookup constraint as if this were a normal def. + lookups.push((symbol, expr_var, region)); + } + Err((_shadowed_symbol, _region)) => { + panic!("TODO gracefully handle shadowing in imports.") + } + } + } else { + // This is a type alias + + // the should already be added to the scope when this module is canonicalized + debug_assert!(scope.contains_alias(symbol)); + } + } + + let (defs, _scope, output, symbols_introduced) = canonicalize_defs( + &mut env, + Output::default(), + &scope, + &desugared, + PatternType::TopLevelDef, + ); + + // See if any of the new idents we defined went unused. + // If any were unused and also not exposed, report it. + for (symbol, region) in symbols_introduced { + if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + // TODO register rigids + // for (var, lowercase) in output.introduced_variables.name_by_var.clone() { + // rigid_variables.insert(var, lowercase); + // } + + let mut references = MutSet::default(); + + // Gather up all the symbols that were referenced across all the defs' lookups. + for symbol in output.references.lookups.iter() { + references.insert(*symbol); + } + + // Gather up all the symbols that were referenced across all the defs' calls. + for symbol in output.references.calls.iter() { + references.insert(*symbol); + } + + // Gather up all the symbols that were referenced from other modules. + for symbol in env.qualified_lookups.iter() { + references.insert(*symbol); + } + + // NOTE previously we inserted builtin defs into the list of defs here + // this is now done later, in file.rs. + + match sort_can_defs(&mut env, defs, Output::default()) { + (Ok(mut declarations), output) => { + use Declaration::*; + + for decl in declarations.iter() { + match decl { + Declare(def) => { + for symbol in def.symbols(env.pool) { + if exposed_symbols.contains(&symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + } + } + } + DeclareRec(defs) => { + for def in defs { + for symbol in def.symbols(env.pool) { + if exposed_symbols.contains(&symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + } + } + } + } + + InvalidCycle(identifiers, _) => { + panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers); + } + Builtin(def) => { + // Builtins cannot be exposed in module declarations. + // This should never happen! + debug_assert!(def + .symbols(env.pool) + .iter() + .all(|symbol| !exposed_symbols.contains(symbol))); + } + } + } + + let mut aliases = MutMap::default(); + + for (symbol, alias) in output.aliases { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + + aliases.insert(symbol, alias); + } + + // By this point, all exposed symbols should have been removed from + // exposed_symbols and added to exposed_vars_by_symbol. If any were + // not, that means they were declared as exposed but there was + // no actual declaration with that name! + for symbol in exposed_symbols { + env.problem(Problem::ExposedButNotDefined(symbol)); + + // In case this exposed value is referenced by other modules, + // create a decl for it whose implementation is a runtime error. + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(symbol, env.var_store.fresh()); + + let runtime_error = RuntimeError::ExposedButNotDefined(symbol); + + let value_def = { + let pattern_id = env.pool.add(Pattern2::Identifier(symbol)); + let expr_id = env.pool.add(Expr2::RuntimeError()); + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var: env.var_store.fresh(), + } + }; + + let def = Def::Value(value_def); + + declarations.push(Declaration::Declare(def)); + } + + // Incorporate any remaining output.lookups entries into references. + for symbol in output.references.lookups { + references.insert(symbol); + } + + // Incorporate any remaining output.calls entries into references. + for symbol in output.references.calls { + references.insert(symbol); + } + + // Gather up all the symbols that were referenced from other modules. + for symbol in env.qualified_lookups.iter() { + references.insert(*symbol); + } + + // TODO find captured variables + // for declaration in declarations.iter_mut() { + // match declaration { + // Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()), + // DeclareRec(defs) => { + // fix_values_captured_in_closure_defs(defs, &mut MutSet::default()) + // } + // InvalidCycle(_, _) | Builtin(_) => {} + // } + // } + + // TODO this loops over all symbols in the module, we can speed it up by having an + // iterator over all builtin symbols + + // TODO move over the builtins + // for symbol in references.iter() { + // if symbol.is_builtin() { + // // this can fail when the symbol is for builtin types, or has no implementation yet + // if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) { + // declarations.push(Declaration::Builtin(def)); + // } + // } + // } + + Ok(ModuleOutput { + aliases, + rigid_variables, + declarations, + references, + exposed_imports: can_exposed_imports, + problems: vec![], // TODO env.problems, + lookups, + ident_ids: env.ident_ids, + }) + } + (Err(runtime_error), _) => Err(runtime_error), + } +} diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs new file mode 100644 index 0000000000..77d4edb0cd --- /dev/null +++ b/ast/src/constrain.rs @@ -0,0 +1,1746 @@ +use bumpalo::{collections::Vec as BumpVec, Bump}; + +use crate::lang::{ + ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch}, + expr::Env, + pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, + pool::{Pool, PoolStr, PoolVec, ShallowClone}, + types::{Type2, TypeId}, +}; + +use roc_can::expected::{Expected, PExpected}; +use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; +use roc_module::{ + ident::{Lowercase, TagName}, + symbol::Symbol, +}; +use roc_region::all::Region; +use roc_types::{ + subs::Variable, + types::{self, AnnotationSource, PReason, PatternCategory}, + types::{Category, Reason}, +}; + +#[derive(Debug)] +pub enum Constraint<'a> { + Eq(Type2, Expected, Category, Region), + // Store(Type, Variable, &'static str, u32), + Lookup(Symbol, Expected, Region), + Pattern(Region, PatternCategory, Type2, PExpected), + And(BumpVec<'a, Constraint<'a>>), + Let(&'a LetConstraint<'a>), + // SaveTheEnvironment, + True, // Used for things that always unify, e.g. blanks and runtime errors +} + +#[derive(Debug)] +pub struct LetConstraint<'a> { + pub rigid_vars: BumpVec<'a, Variable>, + pub flex_vars: BumpVec<'a, Variable>, + pub def_types: BumpMap, + pub defs_constraint: Constraint<'a>, + pub ret_constraint: Constraint<'a>, +} + +pub fn constrain_expr<'a>( + arena: &'a Bump, + env: &mut Env, + expr: &Expr2, + expected: Expected, + region: Region, +) -> Constraint<'a> { + use Constraint::*; + + match expr { + Expr2::Blank | Expr2::RuntimeError() | Expr2::InvalidLookup(_) => True, + Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, region), + Expr2::SmallStr(_) => Eq(str_type(env.pool), expected, Category::Str, region), + Expr2::Var(symbol) => Lookup(*symbol, expected, region), + Expr2::EmptyRecord => constrain_empty_record(expected, region), + Expr2::SmallInt { var, .. } | Expr2::I128 { var, .. } | Expr2::U128 { var, .. } => { + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + + flex_vars.push(*var); + + let precision_var = env.var_store.fresh(); + + let range_type = Type2::Variable(precision_var); + + let range_type_id = env.pool.add(range_type); + + exists( + arena, + flex_vars, + Eq( + num_num(env.pool, range_type_id), + expected, + Category::Num, + region, + ), + ) + } + Expr2::Float { var, .. } => { + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + let num_type = Type2::Variable(*var); + + flex_vars.push(*var); + + let precision_var = env.var_store.fresh(); + + let range_type = Type2::Variable(precision_var); + + let range_type_id = env.pool.add(range_type); + + and_constraints.push(Eq( + num_type.shallow_clone(), + Expected::ForReason( + Reason::FloatLiteral, + num_float(env.pool, range_type_id), + region, + ), + Category::Int, + region, + )); + + and_constraints.push(Eq(num_type, expected, Category::Float, region)); + + let defs_constraint = And(and_constraints); + + exists(arena, flex_vars, defs_constraint) + } + Expr2::List { + elem_var, elems, .. + } => { + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + + flex_vars.push(*elem_var); + + if elems.is_empty() { + exists( + arena, + flex_vars, + Eq( + empty_list_type(env.pool, *elem_var), + expected, + Category::List, + region, + ), + ) + } else { + let mut constraints = BumpVec::with_capacity_in(1 + elems.len(), arena); + + let list_elem_type = Type2::Variable(*elem_var); + + let indexed_node_ids: Vec<(usize, ExprId)> = + elems.iter(env.pool).copied().enumerate().collect(); + + for (index, elem_node_id) in indexed_node_ids { + let elem_expr = env.pool.get(elem_node_id); + + let elem_expected = Expected::ForReason( + Reason::ElemInList { + index: Index::zero_based(index), + }, + list_elem_type.shallow_clone(), + region, + ); + + let constraint = constrain_expr(arena, env, elem_expr, elem_expected, region); + + constraints.push(constraint); + } + + constraints.push(Eq( + list_type(env.pool, list_elem_type), + expected, + Category::List, + region, + )); + + exists(arena, flex_vars, And(constraints)) + } + } + Expr2::Record { fields, record_var } => { + if fields.is_empty() { + constrain_empty_record(expected, region) + } else { + let field_types = PoolVec::with_capacity(fields.len() as u32, env.pool); + + let mut field_vars = BumpVec::with_capacity_in(fields.len(), arena); + + // Constraints need capacity for each field + // + 1 for the record itself + 1 for record var + let mut constraints = BumpVec::with_capacity_in(2 + fields.len(), arena); + + for (record_field_node_id, field_type_node_id) in + fields.iter_node_ids().zip(field_types.iter_node_ids()) + { + let record_field = env.pool.get(record_field_node_id); + + match record_field { + RecordField::LabeledValue(pool_str, var, node_id) => { + let expr = env.pool.get(*node_id); + + let (field_type, field_con) = constrain_field(arena, env, *var, expr); + + field_vars.push(*var); + + let field_type_id = env.pool.add(field_type); + + env.pool[field_type_node_id] = + (*pool_str, types::RecordField::Required(field_type_id)); + + constraints.push(field_con); + } + e => todo!("{:?}", e), + } + } + + let record_type = Type2::Record(field_types, env.pool.add(Type2::EmptyRec)); + + let record_con = Eq( + record_type, + expected.shallow_clone(), + Category::Record, + region, + ); + + constraints.push(record_con); + + // variable to store in the AST + let stored_con = Eq( + Type2::Variable(*record_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + field_vars.push(*record_var); + constraints.push(stored_con); + + exists(arena, field_vars, And(constraints)) + } + } + Expr2::GlobalTag { + variant_var, + ext_var, + name, + arguments, + } => { + let tag_name = TagName::Global(name.as_str(env.pool).into()); + + constrain_tag( + arena, + env, + expected, + region, + tag_name, + arguments, + *ext_var, + *variant_var, + ) + } + Expr2::PrivateTag { + name, + arguments, + ext_var, + variant_var, + } => { + let tag_name = TagName::Private(*name); + + constrain_tag( + arena, + env, + expected, + region, + tag_name, + arguments, + *ext_var, + *variant_var, + ) + } + Expr2::Call { + args, + expr_var, + expr: expr_node_id, + closure_var, + fn_var, + .. + } => { + // The expression that evaluates to the function being called, e.g. `foo` in + // (foo) bar baz + let call_expr = env.pool.get(*expr_node_id); + + let opt_symbol = if let Expr2::Var(symbol) = call_expr { + Some(*symbol) + } else { + None + }; + + let fn_type = Type2::Variable(*fn_var); + let fn_region = region; + let fn_expected = Expected::NoExpectation(fn_type.shallow_clone()); + + let fn_reason = Reason::FnCall { + name: opt_symbol, + arity: args.len() as u8, + }; + + let fn_con = constrain_expr(arena, env, call_expr, fn_expected, region); + + // The function's return type + // TODO: don't use expr_var? + let ret_type = Type2::Variable(*expr_var); + + // type of values captured in the closure + let closure_type = Type2::Variable(*closure_var); + + // This will be used in the occurs check + let mut vars = BumpVec::with_capacity_in(2 + args.len(), arena); + + vars.push(*fn_var); + // TODO: don't use expr_var? + vars.push(*expr_var); + vars.push(*closure_var); + + let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); + let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); + + for (index, arg_node_id) in args.iter_node_ids().enumerate() { + let (arg_var, arg) = env.pool.get(arg_node_id); + let arg_expr = env.pool.get(*arg); + + let region = region; + let arg_type = Type2::Variable(*arg_var); + + let reason = Reason::FnArg { + name: opt_symbol, + arg_index: Index::zero_based(index), + }; + + let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region); + + let arg_con = constrain_expr(arena, env, arg_expr, expected_arg, region); + + vars.push(*arg_var); + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let expected_fn_type = Expected::ForReason( + fn_reason, + Type2::Function( + PoolVec::new(arg_types.into_iter(), env.pool), + env.pool.add(closure_type), + env.pool.add(ret_type.shallow_clone()), + ), + region, + ); + + let category = Category::CallResult(opt_symbol); + + let mut and_constraints = BumpVec::with_capacity_in(4, arena); + + and_constraints.push(fn_con); + and_constraints.push(Eq(fn_type, expected_fn_type, category.clone(), fn_region)); + and_constraints.push(And(arg_cons)); + and_constraints.push(Eq(ret_type, expected, category, region)); + + exists(arena, vars, And(and_constraints)) + } + Expr2::Accessor { + function_var, + closure_var, + field, + record_var, + ext_var, + field_var, + } => { + let ext_var = *ext_var; + let ext_type = Type2::Variable(ext_var); + + let field_var = *field_var; + let field_type = Type2::Variable(field_var); + + let record_field = + types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); + + let record_type = Type2::Record( + PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), + env.pool.add(ext_type), + ); + + let category = Category::Accessor(field.as_str(env.pool).into()); + + let record_expected = Expected::NoExpectation(record_type.shallow_clone()); + let record_con = Eq( + Type2::Variable(*record_var), + record_expected, + category.clone(), + region, + ); + + let function_type = Type2::Function( + PoolVec::new(vec![record_type].into_iter(), env.pool), + env.pool.add(Type2::Variable(*closure_var)), + env.pool.add(field_type), + ); + + let mut flex_vars = BumpVec::with_capacity_in(5, arena); + + flex_vars.push(*record_var); + flex_vars.push(*function_var); + flex_vars.push(*closure_var); + flex_vars.push(field_var); + flex_vars.push(ext_var); + + let mut and_constraints = BumpVec::with_capacity_in(3, arena); + + and_constraints.push(Eq( + function_type.shallow_clone(), + expected, + category.clone(), + region, + )); + + and_constraints.push(Eq( + function_type, + Expected::NoExpectation(Type2::Variable(*function_var)), + category, + region, + )); + + and_constraints.push(record_con); + + exists(arena, flex_vars, And(and_constraints)) + } + Expr2::Access { + expr: expr_id, + field, + field_var, + record_var, + ext_var, + } => { + let ext_type = Type2::Variable(*ext_var); + + let field_type = Type2::Variable(*field_var); + + let record_field = + types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); + + let record_type = Type2::Record( + PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), + env.pool.add(ext_type), + ); + + let record_expected = Expected::NoExpectation(record_type); + + let category = Category::Access(field.as_str(env.pool).into()); + + let record_con = Eq( + Type2::Variable(*record_var), + record_expected.shallow_clone(), + category.clone(), + region, + ); + + let access_expr = env.pool.get(*expr_id); + + let constraint = constrain_expr(arena, env, access_expr, record_expected, region); + + let mut flex_vars = BumpVec::with_capacity_in(3, arena); + + flex_vars.push(*record_var); + flex_vars.push(*field_var); + flex_vars.push(*ext_var); + + let mut and_constraints = BumpVec::with_capacity_in(3, arena); + + and_constraints.push(constraint); + and_constraints.push(Eq(field_type, expected, category, region)); + and_constraints.push(record_con); + + exists(arena, flex_vars, And(and_constraints)) + } + Expr2::If { + cond_var, + expr_var, + branches, + final_else, + } => { + let expect_bool = |region| { + let bool_type = Type2::Variable(Variable::BOOL); + Expected::ForReason(Reason::IfCondition, bool_type, region) + }; + + let mut branch_cons = BumpVec::with_capacity_in(2 * branches.len() + 3, arena); + + // TODO why does this cond var exist? is it for error messages? + // let first_cond_region = branches[0].0.region; + let cond_var_is_bool_con = Eq( + Type2::Variable(*cond_var), + expect_bool(region), + Category::If, + region, + ); + + branch_cons.push(cond_var_is_bool_con); + + let final_else_expr = env.pool.get(*final_else); + + let mut flex_vars = BumpVec::with_capacity_in(2, arena); + + flex_vars.push(*cond_var); + flex_vars.push(*expr_var); + + match expected { + Expected::FromAnnotation(name, arity, _, tipe) => { + let num_branches = branches.len() + 1; + + for (index, branch_id) in branches.iter_node_ids().enumerate() { + let (cond_id, body_id) = env.pool.get(branch_id); + + let cond = env.pool.get(*cond_id); + let body = env.pool.get(*body_id); + + let cond_con = + constrain_expr(arena, env, cond, expect_bool(region), region); + + let then_con = constrain_expr( + arena, + env, + body, + Expected::FromAnnotation( + name.clone(), + arity, + AnnotationSource::TypedIfBranch { + index: Index::zero_based(index), + num_branches, + }, + tipe.shallow_clone(), + ), + region, + ); + + branch_cons.push(cond_con); + branch_cons.push(then_con); + } + + let else_con = constrain_expr( + arena, + env, + final_else_expr, + Expected::FromAnnotation( + name, + arity, + AnnotationSource::TypedIfBranch { + index: Index::zero_based(branches.len()), + num_branches, + }, + tipe.shallow_clone(), + ), + region, + ); + + let ast_con = Eq( + Type2::Variable(*expr_var), + Expected::NoExpectation(tipe), + Category::Storage(std::file!(), std::line!()), + region, + ); + + branch_cons.push(ast_con); + branch_cons.push(else_con); + + exists(arena, flex_vars, And(branch_cons)) + } + _ => { + for (index, branch_id) in branches.iter_node_ids().enumerate() { + let (cond_id, body_id) = env.pool.get(branch_id); + + let cond = env.pool.get(*cond_id); + let body = env.pool.get(*body_id); + + let cond_con = + constrain_expr(arena, env, cond, expect_bool(region), region); + + let then_con = constrain_expr( + arena, + env, + body, + Expected::ForReason( + Reason::IfBranch { + index: Index::zero_based(index), + total_branches: branches.len(), + }, + Type2::Variable(*expr_var), + // should be from body + region, + ), + region, + ); + + branch_cons.push(cond_con); + branch_cons.push(then_con); + } + + let else_con = constrain_expr( + arena, + env, + final_else_expr, + Expected::ForReason( + Reason::IfBranch { + index: Index::zero_based(branches.len()), + total_branches: branches.len() + 1, + }, + Type2::Variable(*expr_var), + // should come from final_else + region, + ), + region, + ); + + branch_cons.push(Eq( + Type2::Variable(*expr_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + )); + + branch_cons.push(else_con); + + exists(arena, flex_vars, And(branch_cons)) + } + } + } + Expr2::When { + cond_var, + expr_var, + cond: cond_id, + branches, + } => { + // Infer the condition expression's type. + let cond_type = Type2::Variable(*cond_var); + + let cond = env.pool.get(*cond_id); + + let expr_con = constrain_expr( + arena, + env, + cond, + Expected::NoExpectation(cond_type.shallow_clone()), + region, + ); + + let mut constraints = BumpVec::with_capacity_in(branches.len() + 1, arena); + + constraints.push(expr_con); + + let mut flex_vars = BumpVec::with_capacity_in(2, arena); + + flex_vars.push(*cond_var); + flex_vars.push(*expr_var); + + match &expected { + Expected::FromAnnotation(name, arity, _, _typ) => { + // NOTE deviation from elm. + // + // in elm, `_typ` is used, but because we have this `expr_var` too + // and need to constrain it, this is what works and gives better error messages + let typ = Type2::Variable(*expr_var); + + for (index, when_branch_id) in branches.iter_node_ids().enumerate() { + let when_branch = env.pool.get(when_branch_id); + + let pattern_region = region; + // let pattern_region = Region::across_all( + // when_branch.patterns.iter(env.pool).map(|v| &v.region), + // ); + + let branch_con = constrain_when_branch( + arena, + env, + // TODO: when_branch.value.region, + region, + when_branch, + PExpected::ForReason( + PReason::WhenMatch { + index: Index::zero_based(index), + }, + cond_type.shallow_clone(), + pattern_region, + ), + Expected::FromAnnotation( + name.clone(), + *arity, + AnnotationSource::TypedWhenBranch { + index: Index::zero_based(index), + }, + typ.shallow_clone(), + ), + ); + + constraints.push(branch_con); + } + + constraints.push(Eq(typ, expected, Category::When, region)); + + return exists(arena, flex_vars, And(constraints)); + } + + _ => { + let branch_type = Type2::Variable(*expr_var); + let mut branch_cons = BumpVec::with_capacity_in(branches.len(), arena); + + for (index, when_branch_id) in branches.iter_node_ids().enumerate() { + let when_branch = env.pool.get(when_branch_id); + + let pattern_region = region; + // let pattern_region = + // Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + + let branch_con = constrain_when_branch( + arena, + env, + region, + when_branch, + PExpected::ForReason( + PReason::WhenMatch { + index: Index::zero_based(index), + }, + cond_type.shallow_clone(), + pattern_region, + ), + Expected::ForReason( + Reason::WhenBranch { + index: Index::zero_based(index), + }, + branch_type.shallow_clone(), + // TODO: when_branch.value.region, + region, + ), + ); + + branch_cons.push(branch_con); + } + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(And(branch_cons)); + and_constraints.push(Eq(branch_type, expected, Category::When, region)); + + constraints.push(And(and_constraints)); + } + } + + // exhautiveness checking happens when converting to mono::Expr + exists(arena, flex_vars, And(constraints)) + } + Expr2::LetValue { + def_id, + body_id, + body_var, + } => { + let value_def = env.pool.get(*def_id); + let body = env.pool.get(*body_id); + + let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region); + + match value_def { + ValueDef::WithAnnotation { .. } => todo!("implement {:?}", value_def), + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + let pattern = env.pool.get(*pattern_id); + + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + flex_vars.push(*body_var); + + let expr_type = Type2::Variable(*expr_var); + + let pattern_expected = PExpected::NoExpectation(expr_type.shallow_clone()); + let mut state = PatternState2 { + headers: BumpMap::new_in(arena), + vars: BumpVec::with_capacity_in(1, arena), + constraints: BumpVec::with_capacity_in(1, arena), + }; + + constrain_pattern(arena, env, pattern, region, pattern_expected, &mut state); + state.vars.push(*expr_var); + + let def_expr = env.pool.get(*expr_id); + + let constrained_def = Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), // always empty + flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments + def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments! + defs_constraint: And(state.constraints), + ret_constraint: constrain_expr( + arena, + env, + def_expr, + Expected::NoExpectation(expr_type), + region, + ), + })), + ret_constraint: body_con, + })); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(constrained_def); + and_constraints.push(Eq( + Type2::Variable(*body_var), + expected, + Category::Storage(std::file!(), std::line!()), + // TODO: needs to be ret region + region, + )); + + exists(arena, flex_vars, And(and_constraints)) + } + } + } + Expr2::Update { + symbol, + updates, + ext_var, + record_var, + } => { + let field_types = PoolVec::with_capacity(updates.len() as u32, env.pool); + let mut flex_vars = BumpVec::with_capacity_in(updates.len() + 2, arena); + let mut cons = BumpVec::with_capacity_in(updates.len() + 1, arena); + let mut record_key_updates = SendMap::default(); + + for (record_field_id, field_type_node_id) in + updates.iter_node_ids().zip(field_types.iter_node_ids()) + { + let record_field = env.pool.get(record_field_id); + + match record_field { + RecordField::LabeledValue(pool_str, var, node_id) => { + let expr = env.pool.get(*node_id); + + let (field_type, field_con) = constrain_field_update( + arena, + env, + *var, + pool_str.as_str(env.pool).into(), + expr, + ); + + let field_type_id = env.pool.add(field_type); + + env.pool[field_type_node_id] = + (*pool_str, types::RecordField::Required(field_type_id)); + + record_key_updates.insert(pool_str.as_str(env.pool).into(), Region::zero()); + + flex_vars.push(*var); + cons.push(field_con); + } + e => todo!("{:?}", e), + } + } + + let fields_type = Type2::Record(field_types, env.pool.add(Type2::Variable(*ext_var))); + let record_type = Type2::Variable(*record_var); + + // NOTE from elm compiler: fields_type is separate so that Error propagates better + let fields_con = Eq( + record_type.shallow_clone(), + Expected::NoExpectation(fields_type), + Category::Record, + region, + ); + let record_con = Eq( + record_type.shallow_clone(), + expected, + Category::Record, + region, + ); + + flex_vars.push(*record_var); + flex_vars.push(*ext_var); + + let con = Lookup( + *symbol, + Expected::ForReason( + Reason::RecordUpdateKeys(*symbol, record_key_updates), + record_type, + region, + ), + region, + ); + + // ensure constraints are solved in this order, gives better errors + cons.insert(0, fields_con); + cons.insert(1, con); + cons.insert(2, record_con); + + exists(arena, flex_vars, And(cons)) + } + + Expr2::RunLowLevel { op, args, ret_var } => { + // This is a modified version of what we do for function calls. + + // The operation's return type + let ret_type = Type2::Variable(*ret_var); + + // This will be used in the occurs check + let mut vars = BumpVec::with_capacity_in(1 + args.len(), arena); + + vars.push(*ret_var); + + let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); + let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); + + for (index, node_id) in args.iter_node_ids().enumerate() { + let (arg_var, arg_id) = env.pool.get(node_id); + + vars.push(*arg_var); + + let arg_type = Type2::Variable(*arg_var); + + let reason = Reason::LowLevelOpArg { + op: *op, + arg_index: Index::zero_based(index), + }; + let expected_arg = + Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); + let arg = env.pool.get(*arg_id); + + let arg_con = constrain_expr(arena, env, arg, expected_arg, Region::zero()); + + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let category = Category::LowLevelOpResult(*op); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(And(arg_cons)); + and_constraints.push(Eq(ret_type, expected, category, region)); + + exists(arena, vars, And(and_constraints)) + } + Expr2::Closure { + args, + name, + body: body_id, + function_type: fn_var, + extra, + .. + } => { + // NOTE defs are treated somewhere else! + let body = env.pool.get(*body_id); + + let ClosureExtra { + captured_symbols, + return_type: ret_var, + closure_type: closure_var, + closure_ext_var, + } = env.pool.get(*extra); + + let closure_type = Type2::Variable(*closure_var); + let return_type = Type2::Variable(*ret_var); + + let (mut vars, pattern_state, function_type) = + constrain_untyped_args(arena, env, args, closure_type, return_type.shallow_clone()); + + vars.push(*ret_var); + vars.push(*closure_var); + vars.push(*closure_ext_var); + vars.push(*fn_var); + + let expected_body_type = Expected::NoExpectation(return_type); + // Region here should come from body expr + let ret_constraint = constrain_expr(arena, env, body, expected_body_type, region); + + let captured_symbols_as_vec = captured_symbols + .iter(env.pool) + .copied() + .collect::>(); + + // make sure the captured symbols are sorted! + debug_assert_eq!(captured_symbols_as_vec, { + let mut copy: Vec<(Symbol, Variable)> = captured_symbols_as_vec.clone(); + + copy.sort(); + + copy + }); + + let closure_constraint = constrain_closure_size( + arena, + env, + *name, + region, + captured_symbols, + *closure_var, + *closure_ext_var, + &mut vars, + ); + + let mut and_constraints = BumpVec::with_capacity_in(4, arena); + + and_constraints.push(Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: pattern_state.vars, + def_types: pattern_state.headers, + defs_constraint: And(pattern_state.constraints), + ret_constraint, + }))); + + // "the closure's type is equal to expected type" + and_constraints.push(Eq( + function_type.shallow_clone(), + expected, + Category::Lambda, + region, + )); + + // "fn_var is equal to the closure's type" - fn_var is used in code gen + and_constraints.push(Eq( + Type2::Variable(*fn_var), + Expected::NoExpectation(function_type), + Category::Storage(std::file!(), std::line!()), + region, + )); + + and_constraints.push(closure_constraint); + + exists(arena, vars, And(and_constraints)) + } + Expr2::LetRec { .. } => todo!(), + Expr2::LetFunction { .. } => todo!(), + } +} + +fn exists<'a>( + arena: &'a Bump, + flex_vars: BumpVec<'a, Variable>, + defs_constraint: Constraint<'a>, +) -> Constraint<'a> { + Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars, + def_types: BumpMap::new_in(arena), + defs_constraint, + ret_constraint: Constraint::True, + })) +} + +#[allow(clippy::too_many_arguments)] +fn constrain_tag<'a>( + arena: &'a Bump, + env: &mut Env, + expected: Expected, + region: Region, + tag_name: TagName, + arguments: &PoolVec<(Variable, ExprId)>, + ext_var: Variable, + variant_var: Variable, +) -> Constraint<'a> { + use Constraint::*; + + let mut flex_vars = BumpVec::with_capacity_in(arguments.len(), arena); + let types = PoolVec::with_capacity(arguments.len() as u32, env.pool); + let mut arg_cons = BumpVec::with_capacity_in(arguments.len(), arena); + + for (argument_node_id, type_node_id) in arguments.iter_node_ids().zip(types.iter_node_ids()) { + let (var, expr_node_id) = env.pool.get(argument_node_id); + + let argument_expr = env.pool.get(*expr_node_id); + + let arg_con = constrain_expr( + arena, + env, + argument_expr, + Expected::NoExpectation(Type2::Variable(*var)), + region, + ); + + arg_cons.push(arg_con); + flex_vars.push(*var); + + env.pool[type_node_id] = Type2::Variable(*var); + } + + let union_con = Eq( + Type2::TagUnion( + PoolVec::new(std::iter::once((tag_name.clone(), types)), env.pool), + env.pool.add(Type2::Variable(ext_var)), + ), + expected.shallow_clone(), + Category::TagApply { + tag_name, + args_count: arguments.len(), + }, + region, + ); + + let ast_con = Eq( + Type2::Variable(variant_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + flex_vars.push(variant_var); + flex_vars.push(ext_var); + + arg_cons.push(union_con); + arg_cons.push(ast_con); + + exists(arena, flex_vars, And(arg_cons)) +} + +fn constrain_field<'a>( + arena: &'a Bump, + env: &mut Env, + field_var: Variable, + expr: &Expr2, +) -> (Type2, Constraint<'a>) { + let field_type = Type2::Variable(field_var); + let field_expected = Expected::NoExpectation(field_type.shallow_clone()); + let constraint = constrain_expr(arena, env, expr, field_expected, Region::zero()); + + (field_type, constraint) +} + +#[inline(always)] +fn constrain_field_update<'a>( + arena: &'a Bump, + env: &mut Env, + field_var: Variable, + field: Lowercase, + expr: &Expr2, +) -> (Type2, Constraint<'a>) { + let field_type = Type2::Variable(field_var); + let reason = Reason::RecordUpdateValue(field); + let field_expected = Expected::ForReason(reason, field_type.shallow_clone(), Region::zero()); + let con = constrain_expr(arena, env, expr, field_expected, Region::zero()); + + (field_type, con) +} + +fn constrain_empty_record<'a>(expected: Expected, region: Region) -> Constraint<'a> { + Constraint::Eq(Type2::EmptyRec, expected, Category::Record, region) +} + +#[inline(always)] +fn constrain_when_branch<'a>( + arena: &'a Bump, + env: &mut Env, + region: Region, + when_branch: &WhenBranch, + pattern_expected: PExpected, + expr_expected: Expected, +) -> Constraint<'a> { + let when_expr = env.pool.get(when_branch.body); + + let ret_constraint = constrain_expr(arena, env, when_expr, expr_expected, region); + + let mut state = PatternState2 { + headers: BumpMap::new_in(arena), + vars: BumpVec::with_capacity_in(1, arena), + constraints: BumpVec::with_capacity_in(1, arena), + }; + + // TODO investigate for error messages, is it better to unify all branches with a variable, + // then unify that variable with the expectation? + for pattern_id in when_branch.patterns.iter_node_ids() { + let pattern = env.pool.get(pattern_id); + + constrain_pattern( + arena, + env, + pattern, + // loc_pattern.region, + region, + pattern_expected.shallow_clone(), + &mut state, + ); + } + + if let Some(guard_id) = &when_branch.guard { + let guard = env.pool.get(*guard_id); + + let guard_constraint = constrain_expr( + arena, + env, + guard, + Expected::ForReason( + Reason::WhenGuard, + Type2::Variable(Variable::BOOL), + // TODO: loc_guard.region, + region, + ), + region, + ); + + // must introduce the headers from the pattern before constraining the guard + Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Constraint::And(state.constraints), + ret_constraint: Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: BumpVec::new_in(arena), + def_types: BumpMap::new_in(arena), + defs_constraint: guard_constraint, + ret_constraint, + })), + })) + } else { + Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Constraint::And(state.constraints), + ret_constraint, + })) + } +} + +/// This accepts PatternState (rather than returning it) so that the caller can +/// initialize the Vecs in PatternState using with_capacity +/// based on its knowledge of their lengths. +pub fn constrain_pattern<'a>( + arena: &'a Bump, + env: &mut Env, + pattern: &Pattern2, + region: Region, + expected: PExpected, + state: &mut PatternState2<'a>, +) { + use Pattern2::*; + + match pattern { + Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => { + // Neither the _ pattern nor erroneous ones add any constraints. + } + + Identifier(symbol) => { + state.headers.insert(*symbol, expected.get_type()); + } + + NumLiteral(var, _) => { + state.vars.push(*var); + + let type_id = env.pool.add(Type2::Variable(*var)); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Num, + num_num(env.pool, type_id), + expected, + )); + } + + IntLiteral(_int_val) => { + let precision_var = env.var_store.fresh(); + + let range = env.add(Type2::Variable(precision_var), region); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Int, + num_int(env.pool, range), + expected, + )); + } + + FloatLiteral(_float_val) => { + let precision_var = env.var_store.fresh(); + + let range = env.add(Type2::Variable(precision_var), region); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Float, + num_float(env.pool, range), + expected, + )); + } + + StrLiteral(_) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Str, + str_type(env.pool), + expected, + )); + } + + RecordDestructure { + whole_var, + ext_var, + destructs, + } => { + state.vars.push(*whole_var); + state.vars.push(*ext_var); + let ext_type = Type2::Variable(*ext_var); + + let mut field_types = Vec::new(); + + for destruct_id in destructs.iter_node_ids() { + let RecordDestruct { + var, + label, + symbol, + typ, + } = env.pool.get(destruct_id); + + let pat_type = Type2::Variable(*var); + let expected = PExpected::NoExpectation(pat_type.shallow_clone()); + + if !state.headers.contains_key(symbol) { + state.headers.insert(*symbol, pat_type.shallow_clone()); + } + + let destruct_type = env.pool.get(*typ); + + let field_type = match destruct_type { + DestructType::Guard(guard_var, guard_id) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::PatternGuard, + Type2::Variable(*guard_var), + PExpected::ForReason( + PReason::PatternGuard, + pat_type.shallow_clone(), + // TODO: region should be from guard_id + region, + ), + )); + + state.vars.push(*guard_var); + + let guard = env.pool.get(*guard_id); + + // TODO: region should be from guard_id + constrain_pattern(arena, env, guard, region, expected, state); + + types::RecordField::Demanded(env.pool.add(pat_type)) + } + DestructType::Optional(expr_var, expr_id) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::PatternDefault, + Type2::Variable(*expr_var), + PExpected::ForReason( + PReason::OptionalField, + pat_type.shallow_clone(), + // TODO: region should be from expr_id + region, + ), + )); + + state.vars.push(*expr_var); + + let expr_expected = Expected::ForReason( + Reason::RecordDefaultField(label.as_str(env.pool).into()), + pat_type.shallow_clone(), + // TODO: region should be from expr_id + region, + ); + + let expr = env.pool.get(*expr_id); + + // TODO: region should be from expr_id + let expr_con = constrain_expr(arena, env, expr, expr_expected, region); + + state.constraints.push(expr_con); + + types::RecordField::Optional(env.pool.add(pat_type)) + } + DestructType::Required => { + // No extra constraints necessary. + types::RecordField::Demanded(env.pool.add(pat_type)) + } + }; + + field_types.push((*label, field_type)); + + state.vars.push(*var); + } + + let record_type = Type2::Record( + PoolVec::new(field_types.into_iter(), env.pool), + env.pool.add(ext_type), + ); + + let whole_con = Constraint::Eq( + Type2::Variable(*whole_var), + Expected::NoExpectation(record_type), + Category::Storage(std::file!(), std::line!()), + region, + ); + + let record_con = Constraint::Pattern( + region, + PatternCategory::Record, + Type2::Variable(*whole_var), + expected, + ); + + state.constraints.push(whole_con); + state.constraints.push(record_con); + } + GlobalTag { + whole_var, + ext_var, + tag_name: name, + arguments, + } => { + let tag_name = TagName::Global(name.as_str(env.pool).into()); + + constrain_tag_pattern( + arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, + ); + } + PrivateTag { + whole_var, + ext_var, + tag_name: name, + arguments, + } => { + let tag_name = TagName::Private(*name); + + constrain_tag_pattern( + arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, + ); + } + } +} + +#[allow(clippy::too_many_arguments)] +fn constrain_tag_pattern<'a>( + arena: &'a Bump, + env: &mut Env, + region: Region, + expected: PExpected, + state: &mut PatternState2<'a>, + whole_var: Variable, + ext_var: Variable, + arguments: &PoolVec<(Variable, PatternId)>, + tag_name: TagName, +) { + let mut argument_types = Vec::with_capacity(arguments.len()); + + for (index, arg_id) in arguments.iter_node_ids().enumerate() { + let (pattern_var, pattern_id) = env.pool.get(arg_id); + let pattern = env.pool.get(*pattern_id); + + state.vars.push(*pattern_var); + + let pattern_type = Type2::Variable(*pattern_var); + argument_types.push(pattern_type.shallow_clone()); + + let expected = PExpected::ForReason( + PReason::TagArg { + tag_name: tag_name.clone(), + index: Index::zero_based(index), + }, + pattern_type, + region, + ); + + // TODO region should come from pattern + constrain_pattern(arena, env, pattern, region, expected, state); + } + + let whole_con = Constraint::Eq( + Type2::Variable(whole_var), + Expected::NoExpectation(Type2::TagUnion( + PoolVec::new( + vec![( + tag_name.clone(), + PoolVec::new(argument_types.into_iter(), env.pool), + )] + .into_iter(), + env.pool, + ), + env.pool.add(Type2::Variable(ext_var)), + )), + Category::Storage(std::file!(), std::line!()), + region, + ); + + let tag_con = Constraint::Pattern( + region, + PatternCategory::Ctor(tag_name), + Type2::Variable(whole_var), + expected, + ); + + state.vars.push(whole_var); + state.vars.push(ext_var); + state.constraints.push(whole_con); + state.constraints.push(tag_con); +} + +fn constrain_untyped_args<'a>( + arena: &'a Bump, + env: &mut Env, + arguments: &PoolVec<(Variable, PatternId)>, + closure_type: Type2, + return_type: Type2, +) -> (BumpVec<'a, Variable>, PatternState2<'a>, Type2) { + let mut vars = BumpVec::with_capacity_in(arguments.len(), arena); + + let pattern_types = PoolVec::with_capacity(arguments.len() as u32, env.pool); + + let mut pattern_state = PatternState2 { + headers: BumpMap::new_in(arena), + vars: BumpVec::with_capacity_in(1, arena), + constraints: BumpVec::with_capacity_in(1, arena), + }; + + for (arg_node_id, pattern_type_id) in + arguments.iter_node_ids().zip(pattern_types.iter_node_ids()) + { + let (pattern_var, pattern_id) = env.pool.get(arg_node_id); + let pattern = env.pool.get(*pattern_id); + + let pattern_type = Type2::Variable(*pattern_var); + let pattern_expected = PExpected::NoExpectation(pattern_type.shallow_clone()); + + env.pool[pattern_type_id] = pattern_type; + + constrain_pattern( + arena, + env, + pattern, + // TODO needs to come from pattern + Region::zero(), + pattern_expected, + &mut pattern_state, + ); + + vars.push(*pattern_var); + } + + let function_type = Type2::Function( + pattern_types, + env.pool.add(closure_type), + env.pool.add(return_type), + ); + + (vars, pattern_state, function_type) +} + +#[allow(clippy::too_many_arguments)] +fn constrain_closure_size<'a>( + arena: &'a Bump, + env: &mut Env, + name: Symbol, + region: Region, + captured_symbols: &PoolVec<(Symbol, Variable)>, + closure_var: Variable, + closure_ext_var: Variable, + variables: &mut BumpVec<'a, Variable>, +) -> Constraint<'a> { + use Constraint::*; + + debug_assert!(variables.iter().any(|s| *s == closure_var)); + debug_assert!(variables.iter().any(|s| *s == closure_ext_var)); + + let tag_arguments = PoolVec::with_capacity(captured_symbols.len() as u32, env.pool); + let mut captured_symbols_constraints = BumpVec::with_capacity_in(captured_symbols.len(), arena); + + for (captured_symbol_id, tag_arg_id) in captured_symbols + .iter_node_ids() + .zip(tag_arguments.iter_node_ids()) + { + let (symbol, var) = env.pool.get(captured_symbol_id); + + // make sure the variable is registered + variables.push(*var); + + let tag_arg_type = Type2::Variable(*var); + + // this symbol is captured, so it must be part of the closure type + env.pool[tag_arg_id] = tag_arg_type.shallow_clone(); + + // make the variable equal to the looked-up type of symbol + captured_symbols_constraints.push(Lookup( + *symbol, + Expected::NoExpectation(tag_arg_type), + Region::zero(), + )); + } + + let tag_name = TagName::Closure(name); + let closure_type = Type2::TagUnion( + PoolVec::new(vec![(tag_name, tag_arguments)].into_iter(), env.pool), + env.pool.add(Type2::Variable(closure_ext_var)), + ); + + let finalizer = Eq( + Type2::Variable(closure_var), + Expected::NoExpectation(closure_type), + Category::ClosureSize, + region, + ); + + captured_symbols_constraints.push(finalizer); + + And(captured_symbols_constraints) +} + +#[inline(always)] +fn builtin_type(symbol: Symbol, args: PoolVec) -> Type2 { + Type2::Apply(symbol, args) +} + +#[inline(always)] +fn str_type(pool: &mut Pool) -> Type2 { + builtin_type(Symbol::STR_STR, PoolVec::empty(pool)) +} + +#[inline(always)] +fn empty_list_type(pool: &mut Pool, var: Variable) -> Type2 { + list_type(pool, Type2::Variable(var)) +} + +#[inline(always)] +fn list_type(pool: &mut Pool, typ: Type2) -> Type2 { + builtin_type(Symbol::LIST_LIST, PoolVec::new(vec![typ].into_iter(), pool)) +} + +#[inline(always)] +fn num_float(pool: &mut Pool, range: TypeId) -> Type2 { + let num_floatingpoint_type = num_floatingpoint(pool, range); + let num_floatingpoint_id = pool.add(num_floatingpoint_type); + + let num_num_type = num_num(pool, num_floatingpoint_id); + let num_num_id = pool.add(num_num_type); + + Type2::Alias( + Symbol::NUM_FLOAT, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + num_num_id, + ) +} + +#[inline(always)] +fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 { + let range_type = pool.get(range); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_FLOATINGPOINT, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + pool.add(alias_content), + ) +} + +#[inline(always)] +fn num_int(pool: &mut Pool, range: TypeId) -> Type2 { + let num_integer_type = _num_integer(pool, range); + let num_integer_id = pool.add(num_integer_type); + + let num_num_type = num_num(pool, num_integer_id); + let num_num_id = pool.add(num_num_type); + + Type2::Alias( + Symbol::NUM_INT, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + num_num_id, + ) +} + +#[inline(always)] +fn _num_signed64(pool: &mut Pool) -> Type2 { + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_SIGNED64), + PoolVec::empty(pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_SIGNED64, + PoolVec::empty(pool), + pool.add(alias_content), + ) +} + +#[inline(always)] +fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { + let range_type = pool.get(range); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_INTEGER), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_INTEGER, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + pool.add(alias_content), + ) +} + +#[inline(always)] +fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { + let range_type = pool.get(type_id); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_NUM), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_NUM, + PoolVec::new( + vec![(PoolStr::new("range", pool), type_id)].into_iter(), + pool, + ), + pool.add(alias_content), + ) +} diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs new file mode 100644 index 0000000000..2cf25730b9 --- /dev/null +++ b/ast/src/lang/core/ast.rs @@ -0,0 +1,31 @@ +use crate::ast_error::{ASTResult, ASTNodeIdWithoutExprId}; + +use super::{def::def2::DefId, expr::expr2::ExprId, header::AppHeader}; + +#[derive(Debug)] +pub struct AST { + pub header: AppHeader, + pub def_ids: Vec, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ASTNodeId { + ADefId(DefId), + AExprId(ExprId), +} + +impl ASTNodeId { + pub fn to_expr_id(&self) -> ASTResult { + match self { + ASTNodeId::AExprId(expr_id) => Ok(*expr_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } + + pub fn to_def_id(&self) -> ASTResult { + match self { + ASTNodeId::ADefId(def_id) => Ok(*def_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs new file mode 100644 index 0000000000..02c9e94ff9 --- /dev/null +++ b/ast/src/lang/core/def/def.rs @@ -0,0 +1,1437 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] +// use crate::annotation::canonicalize_annotation; +// use crate::annotation::IntroducedVariables; +// use crate::env::Env; +// use crate::expr::Expr::{self, *}; +// use crate::expr::{ +// canonicalize_expr, local_successors, references_from_call, references_from_local, Output, +// Recursive, +// }; +// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; +// use crate::procedure::References; +use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap}; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_parse::ast; +use roc_parse::pattern::PatternType; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore, Variable}; +use std::collections::HashMap; +use std::fmt::Debug; +use ven_graph::{strongly_connected_components, topological_sort_into_groups}; + +use crate::{lang::{core::{expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output}, fun_def::FunctionDef, pattern::{self, Pattern2, PatternId, symbols_from_pattern, to_pattern_id}, types::{Alias, Annotation2, Signature, Type2, TypeId, to_annotation2}, val_def::ValueDef}, env::Env, rigids::Rigids, scope::Scope}, pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}}; + +#[derive(Debug)] +pub enum Def { + AnnotationOnly { rigids: Rigids, annotation: TypeId }, + Value(ValueDef), + Function(FunctionDef), +} + +impl Def { + pub fn symbols(&self, pool: &Pool) -> MutSet { + let mut output = MutSet::default(); + + match self { + Def::AnnotationOnly { .. } => todo!("lost pattern information here ... "), + Def::Value(value_def) => match value_def { + ValueDef::WithAnnotation { pattern_id, .. } + | ValueDef::NoAnnotation { pattern_id, .. } => { + let pattern2 = &pool[*pattern_id]; + output.extend(symbols_from_pattern(pool, pattern2)); + } + }, + Def::Function(function_def) => match function_def { + FunctionDef::NoAnnotation { name, .. } + | FunctionDef::WithAnnotation { name, .. } => { + output.insert(*name); + } + }, + } + + output + } +} + +impl ShallowClone for Def { + fn shallow_clone(&self) -> Self { + match self { + Self::AnnotationOnly { rigids, annotation } => Self::AnnotationOnly { + rigids: rigids.shallow_clone(), + annotation: *annotation, + }, + Self::Value(def) => Self::Value(def.shallow_clone()), + Self::Function(def) => Self::Function(def.shallow_clone()), + } + } +} + +/// A Def that has had patterns and type annnotations canonicalized, +/// but no Expr canonicalization has happened yet. Also, it has had spaces +/// and nesting resolved, and knows whether annotations are standalone or not. +#[derive(Debug)] +pub enum PendingDef<'a> { + /// A standalone annotation with no body + AnnotationOnly( + &'a Located>, + PatternId, + &'a Located>, + ), + /// A body with no type annotation + Body( + &'a Located>, + PatternId, + &'a Located>, + ), + /// A body with a type annotation + TypedBody( + &'a Located>, + PatternId, + &'a Located>, + &'a Located>, + ), + + /// A type alias, e.g. `Ints : List Int` + Alias { + name: Located, + vars: Vec>, + ann: &'a Located>, + }, + + /// An invalid alias, that is ignored in the rest of the pipeline + /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` + /// with an incorrect pattern + InvalidAlias, +} + +fn to_pending_def<'a>( + env: &mut Env<'a>, + def: &'a ast::Def<'a>, + scope: &mut Scope, + pattern_type: PatternType, +) -> Option<(Output, PendingDef<'a>)> { + use roc_parse::ast::Def::*; + + match def { + Annotation(loc_pattern, loc_ann) => { + // This takes care of checking for shadowing and adding idents to scope. + let (output, loc_can_pattern) = pattern::to_pattern_id( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + Some(( + output, + PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), + )) + } + Body(loc_pattern, loc_expr) => { + // This takes care of checking for shadowing and adding idents to scope. + let (output, loc_can_pattern) = pattern::to_pattern_id( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + Some(( + output, + PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr), + )) + } + + AnnotatedBody { + ann_pattern, + ann_type, + comment: _, + body_pattern, + body_expr, + } => { + if ann_pattern.value.equivalent(&body_pattern.value) { + // NOTE: Pick the body pattern, picking the annotation one is + // incorrect in the presence of optional record fields! + // + // { x, y } : { x : Int, y ? Bool }* + // { x, y ? False } = rec + Some(pending_typed_body( + env, + body_pattern, + ann_type, + body_expr, + scope, + pattern_type, + )) + } else { + // the pattern of the annotation does not match the pattern of the body direc + env.problem(Problem::SignatureDefMismatch { + annotation_pattern: ann_pattern.region, + def_pattern: body_pattern.region, + }); + + // TODO: Should we instead build some PendingDef::InvalidAnnotatedBody ? This would + // remove the `Option` on this function (and be probably more reliable for further + // problem/error reporting) + None + } + } + + roc_parse::ast::Def::Alias { name, vars, ann } => { + let region = Region::span_across(&name.region, &ann.region); + + match scope.introduce( + name.value.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); + + for loc_var in vars.iter() { + match loc_var.value { + ast::Pattern::Identifier(name) + if name.chars().next().unwrap().is_lowercase() => + { + let lowercase = Lowercase::from(name); + can_rigids.push(Located { + value: lowercase, + region: loc_var.region, + }); + } + _ => { + // any other pattern in this position is a syntax error. + env.problem(Problem::InvalidAliasRigid { + alias_name: symbol, + region: loc_var.region, + }); + + return Some((Output::default(), PendingDef::InvalidAlias)); + } + } + } + + Some(( + Output::default(), + PendingDef::Alias { + name: Located { + region: name.region, + value: symbol, + }, + vars: can_rigids, + ann, + }, + )) + } + + Err((original_region, loc_shadowed_symbol)) => { + env.problem(Problem::ShadowingInAnnotation { + original_region, + shadow: loc_shadowed_symbol, + }); + + Some((Output::default(), PendingDef::InvalidAlias)) + } + } + } + + Expect(_) => todo!(), + + SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { + to_pending_def(env, sub_def, scope, pattern_type) + } + + NotYetImplemented(s) => todo!("{}", s), + } +} + +fn pending_typed_body<'a>( + env: &mut Env<'a>, + loc_pattern: &'a Located>, + loc_ann: &'a Located>, + loc_expr: &'a Located>, + scope: &mut Scope, + pattern_type: PatternType, +) -> (Output, PendingDef<'a>) { + // This takes care of checking for shadowing and adding idents to scope. + let (output, loc_can_pattern) = to_pattern_id( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + ( + output, + PendingDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr), + ) +} + +fn from_pending_alias<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + name: Located, + vars: Vec>, + ann: &'a Located>, + mut output: Output, +) -> Output { + let symbol = name.value; + + match to_annotation2(env, scope, &ann.value, ann.region) { + Annotation2::Erroneous(_) => todo!(), + Annotation2::Annotation { + named_rigids, + unnamed_rigids, + symbols, + signature, + } => { + // Record all the annotation's references in output.references.lookups + + for symbol in symbols { + output.references.lookups.insert(symbol); + output.references.referenced_aliases.insert(symbol); + } + + for loc_lowercase in vars { + if !named_rigids.contains_key(loc_lowercase.value.as_str()) { + env.problem(Problem::PhantomTypeArgument { + alias: symbol, + variable_region: loc_lowercase.region, + variable_name: loc_lowercase.value.clone(), + }); + } + } + + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); + + let annotation = match signature { + Signature::Value { annotation } => annotation, + Signature::Function { + arguments, + closure_type_id, + return_type_id, + } => Type2::Function(arguments, closure_type_id, return_type_id), + Signature::FunctionWithAliases { annotation, .. } => annotation, + }; + + if annotation.contains_symbol(env.pool, symbol) { + // the alias is recursive. If it's a tag union, we attempt to fix this + if let Type2::TagUnion(tags, ext) = annotation { + // re-canonicalize the alias with the alias already in scope + let rec_var = env.var_store.fresh(); + let rec_type_union = Type2::RecursiveTagUnion(rec_var, tags, ext); + + // NOTE this only looks at the symbol, and just assumes that the + // recursion is not polymorphic + rec_type_union.substitute_alias(env.pool, symbol, Type2::Variable(rec_var)); + + let annotation_id = env.add(rec_type_union, ann.region); + let named = rigids.named(env.pool); + + scope.add_alias(env.pool, symbol, named, annotation_id); + } else { + env.problem(Problem::CyclicAlias(symbol, name.region, vec![])); + return output; + } + } else { + let annotation_id = env.add(annotation, ann.region); + let named = rigids.named(env.pool); + + scope.add_alias(env.pool, symbol, named, annotation_id); + } + + output + } + } +} + +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +#[allow(clippy::cognitive_complexity)] +fn canonicalize_pending_def<'a>( + env: &mut Env<'a>, + pending_def: PendingDef<'a>, + mut output: Output, + scope: &mut Scope, + can_defs_by_symbol: &mut MutMap, + refs_by_symbol: &mut MutMap, + aliases: &mut MutMap, +) -> Output { + use PendingDef::*; + + // Make types for the body expr, even if we won't end up having a body. + let expr_var = env.var_store.fresh(); + + match pending_def { + AnnotationOnly(_, loc_can_pattern, loc_ann) => { + // annotation sans body cannot introduce new rigids that are visible in other annotations + // but the rigids can show up in type error messages, so still register them + + match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { + Annotation2::Erroneous(_) => todo!(), + Annotation2::Annotation { + named_rigids, + unnamed_rigids, + symbols, + signature, + } => { + // Record all the annotation's references in output.references.lookups + + for symbol in symbols { + output.references.lookups.insert(symbol); + output.references.referenced_aliases.insert(symbol); + } + + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); + + let annotation = match signature { + Signature::Value { annotation } => annotation, + Signature::Function { + arguments, + closure_type_id, + return_type_id, + } => Type2::Function(arguments, closure_type_id, return_type_id), + Signature::FunctionWithAliases { annotation, .. } => annotation, + }; + let annotation = env.add(annotation, loc_ann.region); + + let def = Def::AnnotationOnly { rigids, annotation }; + + for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { + can_defs_by_symbol.insert(symbol, def.shallow_clone()); + } + + output + } + } + } + + PendingDef::Alias { name, ann, vars } => { + from_pending_alias(env, scope, name, vars, ann, output) + } + + InvalidAlias => { + // invalid aliases (shadowed, incorrect patterns ) + todo!() + } + + TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { + match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { + Annotation2::Erroneous(_) => todo!(), + Annotation2::Annotation { + named_rigids, + unnamed_rigids, + symbols, + signature, + } => { + // Record all the annotation's references in output.references.lookups + + for symbol in symbols { + output.references.lookups.insert(symbol); + output.references.referenced_aliases.insert(symbol); + } + + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); + + // bookkeeping for tail-call detection. If we're assigning to an + // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. + let outer_identifier = env.tailcallable_symbol; + + if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { + env.tailcallable_symbol = Some(*defined_symbol); + }; + + // regiser the name of this closure, to make sure the closure won't capture it's own name + if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = + (&env.pool[loc_can_pattern], &loc_expr.value) + { + env.closure_name_symbol = Some(*defined_symbol); + }; + + let (loc_can_expr, can_output) = + to_expr2(env, scope, &loc_expr.value, loc_expr.region); + + output.references.union_mut(can_output.references.clone()); + + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_identifier; + + // First, make sure we are actually assigning an identifier instead of (for example) a tag. + // + // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, + // which also implies it's not a self tail call! + // + // Only defs of the form (foo = ...) can be closure declarations or self tail calls. + match loc_can_expr { + Expr2::Closure { + args: closure_args, + body, + extra, + name: closure_symbol, + .. + } => { + let symbol = match env.pool[loc_can_pattern] { + Pattern2::Identifier(ref s) => *s, + _ => todo!( + r"this is an error; functions must be bound with an identifier pattern!" + ), + }; + + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. + let references = + env.closures.remove(&closure_symbol).unwrap_or_else(|| { + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + }); + + // TODO should we re-insert this function into env.closures? + env.closures.insert(symbol, references); + + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.lookups.remove(&symbol); + }); + + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + let refs = References::new(); + + let arguments: PoolVec<(PatternId, Type2)> = + PoolVec::with_capacity(closure_args.len() as u32, env.pool); + + let return_type: TypeId; + + let annotation = match signature { + Signature::Value { .. } => { + todo!("type annotation says 0 arguments, but it's a function") + } + Signature::Function { + arguments: type_arguments, + closure_type_id, + return_type_id, + } + | Signature::FunctionWithAliases { + arguments: type_arguments, + closure_type_id, + return_type_id, + .. + } => { + if arguments.len() != type_arguments.len() { + panic!("argument number mismatch"); + } + + let it: Vec<_> = closure_args + .iter(env.pool) + .map(|(x, y)| (*x, *y)) + .zip( + type_arguments + .iter(env.pool) + .map(|t| t.shallow_clone()), + ) + .collect(); + + for (node_id, ((_, pattern_id), typ)) in + arguments.iter_node_ids().zip(it.into_iter()) + { + env.pool[node_id] = (pattern_id, typ); + } + + return_type = return_type_id; + } + }; + + let function_def = FunctionDef::WithAnnotation { + name: symbol, + arguments, + rigids: env.pool.add(rigids), + return_type, + body, + }; + + let def = Def::Function(function_def); + + can_defs_by_symbol.insert(symbol, def); + + output + } + + _ => { + let annotation = match signature { + Signature::Value { annotation } => annotation, + Signature::Function { + arguments, + closure_type_id, + return_type_id, + } => Type2::Function(arguments, closure_type_id, return_type_id), + Signature::FunctionWithAliases { annotation, .. } => annotation, + }; + let annotation = env.add(annotation, loc_ann.region); + + let value_def = ValueDef::WithAnnotation { + pattern_id: loc_can_pattern, + expr_id: env.pool.add(loc_can_expr), + type_id: annotation, + rigids: rigids, + expr_var: env.var_store.fresh(), + }; + + let def = Def::Value(value_def); + + for symbol in + symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) + { + can_defs_by_symbol.insert(symbol, def.shallow_clone()); + } + + for (_, (symbol, region)) in scope.idents() { + // if !vars_by_symbol.contains_key(&symbol) { + // continue; + // } + let refs = can_output.references.clone(); + refs_by_symbol.insert(*symbol, (*region, refs)); + } + + output + } + } + } + } + } + + Body(loc_pattern, loc_can_pattern, loc_expr) => { + // bookkeeping for tail-call detection. If we're assigning to an + // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. + let outer_identifier = env.tailcallable_symbol; + + if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { + env.tailcallable_symbol = Some(*defined_symbol); + }; + + // regiser the name of this closure, to make sure the closure won't capture it's own name + if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = + (&env.pool[loc_can_pattern], &loc_expr.value) + { + env.closure_name_symbol = Some(*defined_symbol); + }; + + let (loc_can_expr, can_output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); + + output.references.union_mut(can_output.references.clone()); + + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_identifier; + + // First, make sure we are actually assigning an identifier instead of (for example) a tag. + // + // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, + // which also implies it's not a self tail call! + // + // Only defs of the form (foo = ...) can be closure declarations or self tail calls. + match loc_can_expr { + Expr2::Closure { + args: closure_args, + body, + extra, + name: closure_symbol, + .. + } => { + let symbol = match env.pool[loc_can_pattern] { + Pattern2::Identifier(ref s) => *s, + _ => todo!( + r"this is an error; functions must be bound with an identifier pattern!" + ), + }; + + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. + let references = + env.closures.remove(&closure_symbol).unwrap_or_else(|| { + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + }); + + // TODO should we re-insert this function into env.closures? + env.closures.insert(symbol, references); + + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.lookups.remove(&symbol); + }); + + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + let refs = References::new(); + + let arguments: PoolVec<(PatternId, Variable)> = + PoolVec::with_capacity(closure_args.len() as u32, env.pool); + + let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect(); + + for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter()) + { + env.pool[node_id] = (pattern_id, env.var_store.fresh()); + } + + let function_def = FunctionDef::NoAnnotation { + name: symbol, + arguments, + return_var: env.var_store.fresh(), + body, + }; + + let def = Def::Function(function_def); + + can_defs_by_symbol.insert(symbol, def); + + output + } + + _ => { + let value_def = ValueDef::NoAnnotation { + pattern_id: loc_can_pattern, + expr_id: env.pool.add(loc_can_expr), + expr_var: env.var_store.fresh(), + }; + + let def = Def::Value(value_def); + + for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { + can_defs_by_symbol.insert(symbol, def.shallow_clone()); + } + + for (_, (symbol, region)) in scope.idents() { + // if !vars_by_symbol.contains_key(&symbol) { + // continue; + // } + let refs = can_output.references.clone(); + refs_by_symbol.insert(*symbol, (*region, refs)); + } + + output + } + } + } + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct References { + pub bound_symbols: MutSet, + pub lookups: MutSet, + pub referenced_aliases: MutSet, + pub calls: MutSet, +} + +impl References { + pub fn new() -> References { + Self::default() + } + + pub fn union_mut(&mut self, other: References) { + self.lookups.extend(other.lookups); + self.calls.extend(other.calls); + self.bound_symbols.extend(other.bound_symbols); + self.referenced_aliases.extend(other.referenced_aliases); + } + + pub fn has_lookup(&self, symbol: Symbol) -> bool { + self.lookups.contains(&symbol) + } +} + +#[derive(Debug)] +pub struct CanDefs { + pub refs_by_symbol: MutMap, + pub can_defs_by_symbol: MutMap, + pub aliases: MutMap, +} + +#[inline(always)] +pub fn canonicalize_defs<'a>( + env: &mut Env<'a>, + mut output: Output, + original_scope: &Scope, + loc_defs: &'a [&'a Located>], + pattern_type: PatternType, +) -> (CanDefs, Scope, Output, MutMap) { + // Canonicalizing defs while detecting shadowing involves a multi-step process: + // + // 1. Go through each of the patterns. + // 2. For each identifier pattern, get the scope.symbol() for the ident. (That symbol will use the home module for its module.) + // 3. If that symbol is already in scope, then we're about to shadow it. Error! + // 4. Otherwise, add it to the scope immediately, so we can detect shadowing within the same + // pattern (e.g. (Foo a a) = ...) + // 5. Add this canonicalized pattern and its corresponding ast::Expr to pending_exprs. + // 5. Once every pattern has been processed and added to scope, go back and canonicalize the exprs from + // pending_exprs, this time building up a canonical def for each one. + // + // This way, whenever any expr is doing lookups, it knows everything that's in scope - + // even defs that appear after it in the source. + // + // This naturally handles recursion too, because a given expr which refers + // to itself won't be processed until after its def has been added to scope. + + // Record both the original and final idents from the scope, + // so we can diff them while detecting unused defs. + let mut scope = original_scope.shallow_clone(); + let num_defs = loc_defs.len(); + let mut refs_by_symbol = MutMap::default(); + let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); + let mut pending = Vec::with_capacity(num_defs); // TODO bump allocate this! + + // Canonicalize all the patterns, record shadowing problems, and store + // the ast::Expr values in pending_exprs for further canonicalization + // once we've finished assembling the entire scope. + for loc_def in loc_defs { + match to_pending_def(env, &loc_def.value, &mut scope, pattern_type) { + None => (), + Some((new_output, pending_def)) => { + // store the top-level defs, used to ensure that closures won't capture them + if let PatternType::TopLevelDef = pattern_type { + match &pending_def { + PendingDef::AnnotationOnly(_, loc_can_pattern, _) + | PendingDef::Body(_, loc_can_pattern, _) + | PendingDef::TypedBody(_, loc_can_pattern, _, _) => { + env.top_level_symbols.extend(symbols_from_pattern( + env.pool, + env.pool.get(*loc_can_pattern), + )) + } + PendingDef::Alias { .. } | PendingDef::InvalidAlias => {} + } + } + // Record the ast::Expr for later. We'll do another pass through these + // once we have the entire scope assembled. If we were to canonicalize + // the exprs right now, they wouldn't have symbols in scope from defs + // that get would have gotten added later in the defs list! + pending.push(pending_def); + output.union(new_output); + } + } + } + + if cfg!(debug_assertions) { + env.home.register_debug_idents(&env.ident_ids); + } + + // TODO what to do here? aliases are already in the scope! + let mut aliases = MutMap::default(); + let mut value_defs = Vec::new(); + + for pending_def in pending.into_iter() { + match pending_def { + PendingDef::Alias { name, vars, ann } => { + output = from_pending_alias(env, &mut scope, name, vars, ann, output); + } + other => value_defs.push(other), + } + } + + // TODO + // correct_mutual_recursive_type_alias(env, &mut aliases, var_store); + + // Now that we have the scope completely assembled, and shadowing resolved, + // we're ready to canonicalize any body exprs. + for pending_def in value_defs.into_iter() { + output = canonicalize_pending_def( + env, + pending_def, + output, + &mut scope, + &mut can_defs_by_symbol, + &mut refs_by_symbol, + &mut aliases, + ); + + // TODO we should do something with these references; they include + // things like type annotations. + } + + // Determine which idents we introduced in the course of this process. + let mut symbols_introduced = MutMap::default(); + + for (symbol, region) in scope.symbols() { + if !original_scope.contains_symbol(symbol) { + symbols_introduced.insert(symbol, region); + } + } + + // This returns both the defs info as well as the new scope. + // + // We have to return the new scope because we added defs to it + // (and those lookups shouldn't fail later, e.g. when canonicalizing + // the return expr), but we didn't want to mutate the original scope + // directly because we wanted to keep a clone of it around to diff + // when looking for unused idents. + // + // We have to return the scope separately from the defs, because the + // defs need to get moved later. + ( + CanDefs { + refs_by_symbol, + can_defs_by_symbol, + aliases, + }, + scope, + output, + symbols_introduced, + ) +} + +// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum Declaration { + Declare(Def), + DeclareRec(Vec), + Builtin(Def), + InvalidCycle(Vec, Vec<(Region /* pattern */, Region /* expr */)>), +} + +impl Declaration { + pub fn def_count(&self) -> usize { + use Declaration::*; + match self { + Declare(_) => 1, + DeclareRec(defs) => defs.len(), + InvalidCycle(_, _) => 0, + Builtin(_) => 0, + } + } +} + +#[inline(always)] +pub fn sort_can_defs( + env: &mut Env<'_>, + defs: CanDefs, + mut output: Output, +) -> (Result, RuntimeError>, Output) { + let CanDefs { + refs_by_symbol, + can_defs_by_symbol, + aliases, + } = defs; + + // for (symbol, alias) in aliases.into_iter() { + // output.aliases.insert(symbol, alias); + // } + + // Determine the full set of references by traversing the graph. + let mut visited_symbols = MutSet::default(); + let returned_lookups = MutSet::clone(&output.references.lookups); + + // Start with the return expression's referenced locals. They're the only ones that count! + // + // If I have two defs which reference each other, but neither of them is referenced + // in the return expression, I don't want either of them (or their references) to end up + // in the final output.references. They were unused, and so were their references! + // + // The reason we need a graph here is so we don't overlook transitive dependencies. + // For example, if I have `a = b + 1` and the def returns `a + 1`, then the + // def as a whole references both `a` *and* `b`, even though it doesn't + // directly mention `b` - because `a` depends on `b`. If we didn't traverse a graph here, + // we'd erroneously give a warning that `b` was unused since it wasn't directly referenced. + for symbol in returned_lookups.into_iter() { + // We only care about local symbols in this analysis. + if symbol.module_id() == env.home { + // Traverse the graph and look up *all* the references for this local symbol. + let refs = + references_from_local(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); + + output.references.union_mut(refs); + } + } + + for symbol in output.references.calls.clone() { + // Traverse the graph and look up *all* the references for this call. + // Reuse the same visited_symbols as before; if we already visited it, + // we won't learn anything new from visiting it again! + let refs = + references_from_call(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); + + output.references.union_mut(refs); + } + + let mut defined_symbols: Vec = Vec::new(); + let mut defined_symbols_set: MutSet = MutSet::default(); + + for symbol in can_defs_by_symbol.keys().into_iter() { + defined_symbols.push(*symbol); + defined_symbols_set.insert(*symbol); + } + + // Use topological sort to reorder the defs based on their dependencies to one another. + // This way, during code gen, no def will refer to a value that hasn't been initialized yet. + // As a bonus, the topological sort also reveals any cycles between the defs, allowing + // us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually + // recursive definitions. + + // All successors that occur in the body of a symbol. + let all_successors_without_self = |symbol: &Symbol| -> MutSet { + // This may not be in refs_by_symbol. For example, the `f` in `f x` here: + // + // f = \z -> z + // + // (\x -> + // a = f x + // x + // ) + // + // It's not part of the current defs (the one with `a = f x`); rather, + // it's in the enclosing scope. It's still referenced though, so successors + // will receive it as an argument! + match refs_by_symbol.get(symbol) { + Some((_, references)) => { + // We can only sort the symbols at the current level. That is safe because + // symbols defined at higher levels cannot refer to symbols at lower levels. + // Therefore they can never form a cycle! + // + // In the above example, `f` cannot reference `a`, and in the closure + // a call to `f` cannot cycle back to `a`. + let mut loc_succ = local_successors(&references, &env.closures); + + // if the current symbol is a closure, peek into its body + if let Some(References { lookups, .. }) = env.closures.get(symbol) { + let home = env.home; + + for lookup in lookups { + if lookup != symbol && lookup.module_id() == home { + // DO NOT register a self-call behind a lambda! + // + // We allow `boom = \_ -> boom {}`, but not `x = x` + loc_succ.insert(*lookup); + } + } + } + + // remove anything that is not defined in the current block + loc_succ.retain(|key| defined_symbols_set.contains(key)); + + loc_succ + } + None => MutSet::default(), + } + }; + + // All successors that occur in the body of a symbol, including the symbol itself + // This is required to determine whether a symbol is recursive. Recursive symbols + // (that are not faulty) always need a DeclareRec, even if there is just one symbol in the + // group + let mut all_successors_with_self = |symbol: &Symbol| -> MutSet { + // This may not be in refs_by_symbol. For example, the `f` in `f x` here: + // + // f = \z -> z + // + // (\x -> + // a = f x + // x + // ) + // + // It's not part of the current defs (the one with `a = f x`); rather, + // it's in the enclosing scope. It's still referenced though, so successors + // will receive it as an argument! + match refs_by_symbol.get(symbol) { + Some((_, references)) => { + // We can only sort the symbols at the current level. That is safe because + // symbols defined at higher levels cannot refer to symbols at lower levels. + // Therefore they can never form a cycle! + // + // In the above example, `f` cannot reference `a`, and in the closure + // a call to `f` cannot cycle back to `a`. + let mut loc_succ = local_successors(&references, &env.closures); + + // if the current symbol is a closure, peek into its body + if let Some(References { lookups, .. }) = env.closures.get(symbol) { + for lookup in lookups { + loc_succ.insert(*lookup); + } + } + + // remove anything that is not defined in the current block + loc_succ.retain(|key| defined_symbols_set.contains(key)); + + loc_succ + } + None => MutSet::default(), + } + }; + + // If a symbol is a direct successor of itself, there is an invalid cycle. + // The difference with the function above is that this one does not look behind lambdas, + // but does consider direct self-recursion. + let direct_successors = |symbol: &Symbol| -> MutSet { + match refs_by_symbol.get(symbol) { + Some((_, references)) => { + let mut loc_succ = local_successors(&references, &env.closures); + + // NOTE: if the symbol is a closure we DONT look into its body + + // remove anything that is not defined in the current block + loc_succ.retain(|key| defined_symbols_set.contains(key)); + + // NOTE: direct recursion does matter here: `x = x` is invalid recursion! + + loc_succ + } + None => MutSet::default(), + } + }; + + // TODO also do the same `addDirects` check elm/compiler does, so we can + // report an error if a recursive definition can't possibly terminate! + match ven_graph::topological_sort_into_groups( + defined_symbols.as_slice(), + all_successors_without_self, + ) { + Ok(groups) => { + let mut declarations = Vec::new(); + + // groups are in reversed order + let mut can_defs_by_symbol = can_defs_by_symbol; + let cdbs = &mut can_defs_by_symbol; + for group in groups.into_iter().rev() { + group_to_declaration( + &group, + &env.closures, + &mut all_successors_with_self, + cdbs, + &mut declarations, + ); + } + + (Ok(declarations), output) + } + Err((mut groups, nodes_in_cycle)) => { + let mut declarations = Vec::new(); + let problems = Vec::new(); + + // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, + // and in general it's impossible to decide whether it is. So we use a crude heuristic: + // + // Definitions where the cycle occurs behind a lambda are OK + // + // boom = \_ -> boom {} + // + // But otherwise we report an error, e.g. + // + // foo = if b then foo else bar + + for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) + { + // check whether the cycle is faulty, which is when it has + // a direct successor in the current cycle. This catches things like: + // + // x = x + // + // or + // + // p = q + // q = p + let is_invalid_cycle = match cycle.get(0) { + Some(symbol) => { + let mut succs = direct_successors(symbol); + + succs.retain(|key| cycle.contains(key)); + + !succs.is_empty() + } + None => false, + }; + + if is_invalid_cycle { + // We want to show the entire cycle in the error message, so expand it out. + let mut loc_symbols = Vec::new(); + + for symbol in cycle { + match refs_by_symbol.get(&symbol) { + None => unreachable!( + r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, + symbol, refs_by_symbol + ), + Some((region, _)) => { + loc_symbols.push(Located::at(*region, symbol)); + } + } + } + + // TODO we don't store those regions any more! + // let regions = Vec::with_capacity(can_defs_by_symbol.len()); + // for def in can_defs_by_symbol.values() { + // regions.push((def.loc_pattern.region, def.loc_expr.region)); + // } + // + // // Sort them by line number to make the report more helpful. + // loc_symbols.sort(); + // regions.sort(); + + // let symbols_in_cycle: Vec = + // loc_symbols.into_iter().map(|s| s.value).collect(); + // + // problems.push(Problem::RuntimeError(RuntimeError::CircularDef( + // symbols_in_cycle.clone(), + // regions.clone(), + // ))); + // + // declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions)); + panic!("Invalid Cycle"); + } else { + // slightly inefficient, because we know this becomes exactly one DeclareRec already + groups.push(cycle); + } + } + + // now we have a collection of groups whose dependencies are not cyclic. + // They are however not yet topologically sorted. Here we have to get a bit + // creative to get all the definitions in the correct sorted order. + + let mut group_ids = Vec::with_capacity(groups.len()); + let mut symbol_to_group_index = MutMap::default(); + for (i, group) in groups.iter().enumerate() { + for symbol in group { + symbol_to_group_index.insert(*symbol, i); + } + + group_ids.push(i); + } + + let successors_of_group = |group_id: &usize| { + let mut result = MutSet::default(); + + // for each symbol in this group + for symbol in &groups[*group_id] { + // find its successors + for succ in all_successors_without_self(symbol) { + // and add its group to the result + result.insert(symbol_to_group_index[&succ]); + } + } + + // don't introduce any cycles to self + result.remove(group_id); + + result + }; + + match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { + Ok(sorted_group_ids) => { + let mut can_defs_by_symbol = can_defs_by_symbol; + let cdbs = &mut can_defs_by_symbol; + for sorted_group in sorted_group_ids.iter().rev() { + for group_id in sorted_group.iter().rev() { + let group = &groups[*group_id]; + + group_to_declaration( + group, + &env.closures, + &mut all_successors_with_self, + cdbs, + &mut declarations, + ); + } + } + } + Err(_) => unreachable!("there should be no cycles now!"), + } + + for problem in problems { + env.problem(problem); + } + + (Ok(declarations), output) + } + } +} + +pub fn references_from_local<'a, T>( + defined_symbol: Symbol, + visited: &'a mut MutSet, + refs_by_def: &'a MutMap, + closures: &'a MutMap, +) -> References +where + T: Debug, +{ + let mut answer: References = References::new(); + + match refs_by_def.get(&defined_symbol) { + Some((_, refs)) => { + visited.insert(defined_symbol); + + for local in refs.lookups.iter() { + if !visited.contains(&local) { + let other_refs: References = + references_from_local(*local, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.lookups.insert(*local); + } + + for call in refs.calls.iter() { + if !visited.contains(&call) { + let other_refs = references_from_call(*call, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.calls.insert(*call); + } + + answer + } + None => answer, + } +} + +pub fn references_from_call<'a, T>( + call_symbol: Symbol, + visited: &'a mut MutSet, + refs_by_def: &'a MutMap, + closures: &'a MutMap, +) -> References +where + T: Debug, +{ + match closures.get(&call_symbol) { + Some(references) => { + let mut answer = references.clone(); + + visited.insert(call_symbol); + + for closed_over_local in references.lookups.iter() { + if !visited.contains(&closed_over_local) { + let other_refs = + references_from_local(*closed_over_local, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.lookups.insert(*closed_over_local); + } + + for call in references.calls.iter() { + if !visited.contains(&call) { + let other_refs = references_from_call(*call, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.calls.insert(*call); + } + + answer + } + None => { + // If the call symbol was not in the closure map, that means we're calling a non-function and + // will get a type mismatch later. For now, assume no references as a result of the "call." + References::new() + } + } +} + +fn local_successors( + references: &References, + closures: &MutMap, +) -> MutSet { + let mut answer = references.lookups.clone(); + + for call_symbol in references.calls.iter() { + answer.extend(call_successors(*call_symbol, closures)); + } + + answer +} + +fn call_successors<'a>( + call_symbol: Symbol, + closures: &'a MutMap, +) -> MutSet { + let mut answer = MutSet::default(); + let mut seen = MutSet::default(); + let mut queue = vec![call_symbol]; + + while let Some(symbol) = queue.pop() { + if seen.contains(&symbol) { + continue; + } + + if let Some(references) = closures.get(&symbol) { + answer.extend(references.lookups.iter().copied()); + queue.extend(references.calls.iter().copied()); + + seen.insert(symbol); + } + } + + answer +} + +fn group_to_declaration( + group: &[Symbol], + closures: &MutMap, + successors: &mut dyn FnMut(&Symbol) -> MutSet, + can_defs_by_symbol: &mut MutMap, + declarations: &mut Vec, +) { + use Declaration::*; + + // We want only successors in the current group, otherwise definitions get duplicated + let filtered_successors = |symbol: &Symbol| -> MutSet { + let mut result = successors(symbol); + + result.retain(|key| group.contains(key)); + result + }; + + // TODO fix this + // Patterns like + // + // { x, y } = someDef + // + // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), + // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key + // for a definition, so every definition is only inserted (thus typechecked and emitted) once + // let mut seen_pattern_regions: MutSet = MutSet::default(); + + for cycle in strongly_connected_components(&group, filtered_successors) { + if cycle.len() == 1 { + let symbol = &cycle[0]; + + if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { + // Determine recursivity of closures that are not tail-recursive + + let is_recursive = successors(&symbol).contains(&symbol); + + if is_recursive { + declarations.push(DeclareRec(vec![can_def])); + } else { + declarations.push(Declare(can_def)); + } + } + } else { + let mut can_defs = Vec::new(); + + // Topological sort gives us the reverse of the sorting we want! + for symbol in cycle.into_iter().rev() { + if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { + can_defs.push(can_def); + } + } + + declarations.push(DeclareRec(can_defs)); + } + } +} diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs new file mode 100644 index 0000000000..578a831d62 --- /dev/null +++ b/ast/src/lang/core/def/def2.rs @@ -0,0 +1,15 @@ +use crate::{lang::core::{expr::expr2::Expr2, pattern::Pattern2}, pool::pool::NodeId}; + + +// A top level definition, not inside a function. For example: `main = "Hello, world!"` +#[derive(Debug)] +pub enum Def2 { + // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" + ValueDef { + identifier_id: NodeId, + expr_id: NodeId, + }, + Blank, +} + +pub type DefId = NodeId; \ No newline at end of file diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs new file mode 100644 index 0000000000..377eaa37ab --- /dev/null +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -0,0 +1,75 @@ +use bumpalo::Bump; +use bumpalo::collections::Vec as BumpVec; +use roc_parse::pattern::PatternType; +use roc_region::all::Region; + +use crate::lang::{core::pattern::to_pattern2, env::Env, scope::Scope}; + +use super::def2::Def2; + +pub fn defs_to_defs2<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_defs: &'a BumpVec>>, + region: Region, +) -> Vec { + + parsed_defs + .iter() + .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) + .collect() +} + +pub fn to_def2_from_def<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_def: &'a roc_parse::ast::Def<'a>, + region: Region, +) -> Def2 { + use roc_parse::ast::Def::*; + + match parsed_def { + SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + Body(&loc_pattern, &loc_expr) => { + // TODO loc_pattern use identifier + let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; + let expr_id = env.pool.add(expr2); + + use roc_parse::ast::Pattern::*; + + match loc_pattern.value { + Identifier(_) => { + let (_, pattern2) = to_pattern2( + env, + scope, + PatternType::TopLevelDef, + &loc_pattern.value, + region, + ); + let pattern_id = env.pool.add(pattern2); + + // TODO support with annotation + Def2::ValueDef { + identifier_id: pattern_id, + expr_id, + } + } + other => { + unimplemented!( + "I don't yet know how to convert the pattern {:?} into an expr2", + other + ) + } + } + } + other => { + unimplemented!( + "I don't know how to make an expr2 from this def yet: {:?}", + other + ) + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/def/mod.rs b/ast/src/lang/core/def/mod.rs new file mode 100644 index 0000000000..0f62252832 --- /dev/null +++ b/ast/src/lang/core/def/mod.rs @@ -0,0 +1,3 @@ +mod def_to_def2; +pub mod def; +pub mod def2; \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs new file mode 100644 index 0000000000..6b5b5b8328 --- /dev/null +++ b/ast/src/lang/core/expr/expr2.rs @@ -0,0 +1,235 @@ +use arraystring::{typenum::U30, ArrayString}; +use roc_types::subs::Variable; + +use crate::{lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}}; +use roc_can::expr::Recursive; +use roc_module::symbol::Symbol; +use roc_module::low_level::LowLevel; +use roc_module::operator::CalledVia; + +pub type ArrString = ArrayString; + +// TODO make the inner types private? +pub type ExprId = NodeId; + +/// An Expr that fits in 32B. +/// It has a 1B discriminant and variants which hold payloads of at most 31B. +#[derive(Debug)] +pub enum Expr2 { + /// A negative number literal without a dot + SmallInt { + number: IntVal, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + // TODO(rvcas): rename this eventually + /// A large (over 64-bit) negative number literal without a dot. + /// This variant can't use IntVal because if IntVal stored 128-bit + /// integers, it would be 32B on its own because of alignment. + I128 { + number: i128, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + // TODO(rvcas): rename this eventually + /// A large (over 64-bit) nonnegative number literal without a dot + /// This variant can't use IntVal because if IntVal stored 128-bit + /// integers, it would be 32B on its own because of alignment. + U128 { + number: u128, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + /// A floating-point literal (with a dot) + Float { + number: FloatVal, // 16B + var: Variable, // 4B + text: PoolStr, // 8B + }, + /// string literals of length up to 30B + SmallStr(ArrString), // 31B + /// string literals of length 31B or more + Str(PoolStr), // 8B + // Lookups + Var(Symbol), // 8B + InvalidLookup(PoolStr), // 8B + + List { + elem_var: Variable, // 4B + elems: PoolVec, // 8B + }, + If { + cond_var: Variable, // 4B + expr_var: Variable, // 4B + branches: PoolVec<(ExprId, ExprId)>, // 8B + final_else: ExprId, // 4B + }, + When { + cond_var: Variable, // 4B + expr_var: Variable, // 4B + branches: PoolVec, // 8B + cond: ExprId, // 4B + }, + LetRec { + defs: PoolVec, // 8B + body_var: Variable, // 8B + body_id: ExprId, // 4B + }, + LetFunction { + def_id: NodeId, // 4B + body_var: Variable, // 8B + body_id: ExprId, // 4B + }, + LetValue { + def_id: NodeId, // 4B + body_id: ExprId, // 4B + body_var: Variable, // 4B + }, + Call { + args: PoolVec<(Variable, ExprId)>, // 8B + expr: ExprId, // 4B + expr_var: Variable, // 4B + fn_var: Variable, // 4B + closure_var: Variable, // 4B + called_via: CalledVia, // 2B + }, + RunLowLevel { + op: LowLevel, // 1B + args: PoolVec<(Variable, ExprId)>, // 8B + ret_var: Variable, // 4B + }, + Closure { + args: PoolVec<(Variable, NodeId)>, // 8B + name: Symbol, // 8B + body: ExprId, // 4B + function_type: Variable, // 4B + recursive: Recursive, // 1B + extra: NodeId, // 4B + }, + // Product Types + Record { + record_var: Variable, // 4B + fields: PoolVec, // 8B + }, + /// Empty record constant + EmptyRecord, + /// Look up exactly one field on a record, e.g. (expr).foo. + Access { + field: PoolStr, // 4B + expr: ExprId, // 4B + record_var: Variable, // 4B + ext_var: Variable, // 4B + field_var: Variable, // 4B + }, + + /// field accessor as a function, e.g. (.foo) expr + Accessor { + function_var: Variable, // 4B + closure_var: Variable, // 4B + field: PoolStr, // 4B + record_var: Variable, // 4B + ext_var: Variable, // 4B + field_var: Variable, // 4B + }, + Update { + symbol: Symbol, // 8B + updates: PoolVec, // 8B + record_var: Variable, // 4B + ext_var: Variable, // 4B + }, + + // Sum Types + GlobalTag { + name: PoolStr, // 4B + variant_var: Variable, // 4B + ext_var: Variable, // 4B + arguments: PoolVec<(Variable, ExprId)>, // 8B + }, + PrivateTag { + name: Symbol, // 8B + variant_var: Variable, // 4B + ext_var: Variable, // 4B + arguments: PoolVec<(Variable, ExprId)>, // 8B + }, + Blank, // Rendered as empty box in editor + + // Compiles, but will crash if reached + RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Problem { + RanOutOfNodeIds, +} + +pub type Res = Result; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntStyle { + Decimal, + Octal, + Hex, + Binary, +} + +impl IntStyle { + pub fn from_base(base: roc_parse::ast::Base) -> Self { + use roc_parse::ast::Base; + match base { + Base::Decimal => Self::Decimal, + Base::Octal => Self::Octal, + Base::Hex => Self::Hex, + Base::Binary => Self::Binary, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntVal { + I64(i64), + U64(u64), + I32(i32), + U32(u32), + I16(i16), + U16(u16), + I8(i8), + U8(u8), +} + +#[test] +fn size_of_intval() { + assert_eq!(std::mem::size_of::(), 16); +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum FloatVal { + F64(f64), + F32(f32), +} + +#[derive(Debug)] +pub enum RecordField { + InvalidLabelOnly(PoolStr, Variable), + LabelOnly(PoolStr, Variable, Symbol), + LabeledValue(PoolStr, Variable, ExprId), +} + +#[derive(Debug)] +pub struct WhenBranch { + pub patterns: PoolVec, // 4B + pub body: ExprId, // 3B + pub guard: Option, // 4B +} + +/// This is overflow data from a Closure variant, which needs to store +/// more than 32B of total data +#[derive(Debug)] +pub struct ClosureExtra { + pub return_type: Variable, // 4B + pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B + pub closure_type: Variable, // 4B + pub closure_ext_var: Variable, // 4B +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs new file mode 100644 index 0000000000..7ab0ec4650 --- /dev/null +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -0,0 +1,136 @@ +use crate::{lang::core::{expr::expr2::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; + +use roc_types::subs::Variable; +use super::expr2::{Expr2, ExprId}; + +pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { + let mut full_string = String::new(); + let expr2 = pool.get(node_id); + + expr2_to_string_helper(expr2, 0, pool, &mut full_string); + + full_string +} + +fn get_spacing(indent_level: usize) -> String { + std::iter::repeat(" ") + .take(indent_level) + .collect::>() + .join("") +} + +fn expr2_to_string_helper( + expr2: &Expr2, + indent_level: usize, + pool: &Pool, + out_string: &mut String, +) { + out_string.push_str(&get_spacing(indent_level)); + + match expr2 { + Expr2::SmallStr(arr_string) => out_string.push_str(&format!( + "{}{}{}", + "SmallStr(\"", + arr_string.as_str(), + "\")", + )), + Expr2::Str(pool_str) => { + out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",)) + } + Expr2::Blank => out_string.push_str("Blank"), + Expr2::EmptyRecord => out_string.push_str("EmptyRecord"), + Expr2::Record { record_var, fields } => { + out_string.push_str("Record:\n"); + out_string.push_str(&var_to_string(record_var, indent_level + 1)); + + out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1))); + + let mut first_child = true; + + for field in fields.iter(pool) { + if !first_child { + out_string.push_str(", ") + } else { + first_child = false; + } + + match field { + RecordField::InvalidLabelOnly(pool_str, var) => { + out_string.push_str(&format!( + "{}({}, Var({:?})", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + )); + } + RecordField::LabelOnly(pool_str, var, symbol) => { + out_string.push_str(&format!( + "{}({}, Var({:?}), Symbol({:?})", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + symbol + )); + } + RecordField::LabeledValue(pool_str, var, val_node_id) => { + out_string.push_str(&format!( + "{}({}, Var({:?}), Expr2(\n", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + )); + + let val_expr2 = pool.get(*val_node_id); + expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string); + out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2))); + } + } + } + + out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); + } + Expr2::List { elem_var, elems } => { + out_string.push_str("List:\n"); + out_string.push_str(&var_to_string(elem_var, indent_level + 1)); + out_string.push_str(&format!("{}elems: [\n", get_spacing(indent_level + 1))); + + let mut first_elt = true; + + for elem_expr2_id in elems.iter(pool) { + if !first_elt { + out_string.push_str(", ") + } else { + first_elt = false; + } + + let elem_expr2 = pool.get(*elem_expr2_id); + + expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string) + } + + out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); + } + Expr2::InvalidLookup(pool_str) => { + out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool))); + } + Expr2::SmallInt { text, .. } => { + out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); + } + Expr2::LetValue { + def_id, body_id, .. + } => { + out_string.push_str(&format!( + "LetValue(def_id: >>{:?}), body_id: >>{:?})", + value_def_to_string(pool.get(*def_id), pool), + pool.get(*body_id) + )); + } + other => todo!("Implement for {:?}", other), + } + + out_string.push('\n'); +} + +fn var_to_string(some_var: &Variable, indent_level: usize) -> String { + format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs new file mode 100644 index 0000000000..0dc093ee91 --- /dev/null +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -0,0 +1,694 @@ +use bumpalo::Bump; +use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::operator::desugar_expr; +use roc_parse::{ast::Expr, pattern::PatternType}; +use roc_problem::can::{Problem, RuntimeError}; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; + +use crate::lang::core::pattern::flatten_str_literal; +use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; + +use super::{expr2::Expr2, output::Output}; + +pub fn loc_expr_to_expr2<'a>( + arena: &'a Bump, + loc_expr: Located>, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> (Expr2, Output) { + let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); + + to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) +} + +const ZERO: Region = Region::zero(); + +pub fn to_expr2<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + parse_expr: &'a roc_parse::ast::Expr<'a>, + region: Region, +) -> (Expr2, self::Output) { + use roc_parse::ast::Expr::*; + + match parse_expr { + Float(string) => { + match finish_parsing_float(string) { + Ok(float) => { + let expr = Expr2::Float { + number: FloatVal::F64(float), + var: env.var_store.fresh(), + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + Num(string) => { + match finish_parsing_int(string) { + Ok(int) => { + let expr = Expr2::SmallInt { + number: IntVal::I64(int), + var: env.var_store.fresh(), + // TODO non-hardcode + style: IntStyle::Decimal, + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidInt( + error, + roc_parse::ast::Base::Decimal, + ZERO, + raw.into(), + ); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + NonBase10Int { + string, + base, + is_negative, + } => { + match finish_parsing_base(string, *base, *is_negative) { + Ok(int) => { + let expr = Expr2::SmallInt { + number: IntVal::I64(int), + var: env.var_store.fresh(), + // TODO non-hardcode + style: IntStyle::from_base(*base), + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + + Str(literal) => flatten_str_literal(env, scope, &literal), + + List { items, .. } => { + let mut output = Output::default(); + let output_ref = &mut output; + + let elems: PoolVec = PoolVec::with_capacity(items.len() as u32, env.pool); + + for (node_id, item) in elems.iter_node_ids().zip(items.iter()) { + let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region); + + output_ref.union(sub_output); + + let expr_id = env.pool.add(expr); + env.pool[node_id] = expr_id; + } + + let expr = Expr2::List { + elem_var: env.var_store.fresh(), + elems, + }; + + (expr, output) + } + + GlobalTag(tag) => { + // a global tag without any arguments + ( + Expr2::GlobalTag { + name: PoolStr::new(tag, env.pool), + variant_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + arguments: PoolVec::empty(env.pool), + }, + Output::default(), + ) + } + PrivateTag(name) => { + // a private tag without any arguments + let ident_id = env.ident_ids.get_or_insert(&(*name).into()); + let name = Symbol::new(env.home, ident_id); + ( + Expr2::PrivateTag { + name, + variant_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + arguments: PoolVec::empty(env.pool), + }, + Output::default(), + ) + } + + RecordUpdate { + fields, + update: loc_update, + final_comments: _, + } => { + let (can_update, update_out) = + to_expr2(env, scope, &loc_update.value, loc_update.region); + + if let Expr2::Var(symbol) = &can_update { + match canonicalize_fields(env, scope, fields) { + Ok((can_fields, mut output)) => { + output.references.union_mut(update_out.references); + + let answer = Expr2::Update { + record_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + symbol: *symbol, + updates: can_fields, + }; + + (answer, output) + } + Err(CanonicalizeRecordProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + record_region: _, + }) => { + // let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue { + // field_name, + // field_region, + // record_region, + // }; + // + // env.problem(Problem::RuntimeError(runtime_error)); + + todo!() + } + } + } else { + // only (optionally qualified) variables can be updated, not arbitrary expressions + + // let error = roc_problem::can::RuntimeError::InvalidRecordUpdate { + // region: can_update.region, + // }; + // + // let answer = Expr::RuntimeError(error.clone()); + // + // env.problems.push(Problem::RuntimeError(error)); + // + // (answer, Output::default()) + todo!() + } + } + + Record { + fields, + final_comments: _, + } => { + if fields.is_empty() { + (Expr2::EmptyRecord, Output::default()) + } else { + match canonicalize_fields(env, scope, fields) { + Ok((can_fields, output)) => ( + Expr2::Record { + record_var: env.var_store.fresh(), + fields: can_fields, + }, + output, + ), + Err(CanonicalizeRecordProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + record_region: _, + }) => { + // let runtime_error = RuntimeError::InvalidOptionalValue { + // field_name, + // field_region, + // record_region, + // }; + // + // env.problem(runtime_error); + // ( + // Expr::RuntimeError( + // ), + // Output::default(), + // + // ) + todo!() + } + } + } + } + + Access(record_expr, field) => { + // TODO + let region = ZERO; + let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region); + + ( + Expr2::Access { + record_var: env.var_store.fresh(), + field_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + expr: record_expr_id, + field: PoolStr::new(field, env.pool), + }, + output, + ) + } + + AccessorFunction(field) => ( + Expr2::Accessor { + function_var: env.var_store.fresh(), + record_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + field_var: env.var_store.fresh(), + field: PoolStr::new(field, env.pool), + }, + Output::default(), + ), + + If(branches, final_else) => { + let mut new_branches = Vec::with_capacity(branches.len()); + let mut output = Output::default(); + + for (condition, then_branch) in branches.iter() { + let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region); + + let (then_expr, then_output) = + to_expr2(env, scope, &then_branch.value, then_branch.region); + + output.references.union_mut(cond_output.references); + output.references.union_mut(then_output.references); + + new_branches.push((env.pool.add(cond), env.pool.add(then_expr))); + } + + let (else_expr, else_output) = + to_expr2(env, scope, &final_else.value, final_else.region); + + output.references.union_mut(else_output.references); + + let expr = Expr2::If { + cond_var: env.var_store.fresh(), + expr_var: env.var_store.fresh(), + branches: PoolVec::new(new_branches.into_iter(), env.pool), + final_else: env.pool.add(else_expr), + }; + + (expr, output) + } + + When(loc_cond, branches) => { + // Infer the condition expression's type. + let cond_var = env.var_store.fresh(); + let (can_cond, mut output) = to_expr2(env, scope, &loc_cond.value, loc_cond.region); + + // the condition can never be a tail-call + output.tail_call = None; + + let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool); + + for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) { + let (can_when_branch, branch_references) = + canonicalize_when_branch(env, scope, *branch, &mut output); + + output.references.union_mut(branch_references); + + env.pool[node_id] = can_when_branch; + } + + // A "when" with no branches is a runtime error, but it will mess things up + // if code gen mistakenly thinks this is a tail call just because its condition + // happened to be one. (The condition gave us our initial output value.) + if branches.is_empty() { + output.tail_call = None; + } + + // Incorporate all three expressions into a combined Output value. + let expr = Expr2::When { + expr_var: env.var_store.fresh(), + cond_var, + cond: env.pool.add(can_cond), + branches: can_branches, + }; + + (expr, output) + } + + Closure(loc_arg_patterns, loc_body_expr) => { + // The globally unique symbol that will refer to this closure once it gets converted + // into a top-level procedure for code gen. + // + // In the Foo module, this will look something like Foo.$1 or Foo.$2. + let symbol = env + .closure_name_symbol + .unwrap_or_else(|| env.gen_unique_symbol()); + env.closure_name_symbol = None; + + // The body expression gets a new scope for canonicalization. + // Shadow `scope` to make sure we don't accidentally use the original one for the + // rest of this block, but keep the original around for later diffing. + let original_scope = scope; + let mut scope = original_scope.shallow_clone(); + let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool); + let mut output = Output::default(); + + let mut bound_by_argument_patterns = MutSet::default(); + + for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) { + let (new_output, can_arg) = to_pattern2( + env, + &mut scope, + roc_parse::pattern::PatternType::FunctionArg, + &loc_pattern.value, + loc_pattern.region, + ); + + bound_by_argument_patterns + .extend(new_output.references.bound_symbols.iter().copied()); + + output.union(new_output); + + let pattern_id = env.add(can_arg, loc_pattern.region); + env.pool[node_id] = (env.var_store.fresh(), pattern_id); + } + + let (body_expr, new_output) = + to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region); + + let mut captured_symbols: MutSet = + new_output.references.lookups.iter().copied().collect(); + + // filter out the closure's name itself + captured_symbols.remove(&symbol); + + // symbols bound either in this pattern or deeper down are not captured! + captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); + captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); + + // filter out top-level symbols + // those will be globally available, and don't need to be captured + captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); + + // filter out imported symbols + // those will be globally available, and don't need to be captured + captured_symbols.retain(|s| s.module_id() == env.home); + + // TODO any Closure that has an empty `captured_symbols` list could be excluded! + + output.union(new_output); + + // filter out aliases + captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s)); + + // filter out functions that don't close over anything + captured_symbols.retain(|s| !output.non_closures.contains(s)); + + // Now that we've collected all the references, check to see if any of the args we defined + // went unreferenced. If any did, report them as unused arguments. + for (sub_symbol, region) in scope.symbols() { + if !original_scope.contains_symbol(sub_symbol) { + if !output.references.has_lookup(sub_symbol) { + // The body never referenced this argument we declared. It's an unused argument! + env.problem(Problem::UnusedArgument(symbol, sub_symbol, region)); + } + + // We shouldn't ultimately count arguments as referenced locals. Otherwise, + // we end up with weird conclusions like the expression (\x -> x + 1) + // references the (nonexistant) local variable x! + output.references.lookups.remove(&sub_symbol); + } + } + + env.register_closure(symbol, output.references.clone()); + + let mut captured_symbols: Vec<_> = captured_symbols + .into_iter() + .map(|s| (s, env.var_store.fresh())) + .collect(); + + // sort symbols, so we know the order in which they're stored in the closure record + captured_symbols.sort(); + + // store that this function doesn't capture anything. It will be promoted to a + // top-level function, and does not need to be captured by other surrounding functions. + if captured_symbols.is_empty() { + output.non_closures.insert(symbol); + } + + let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool); + + let extra = ClosureExtra { + return_type: env.var_store.fresh(), // 4B + captured_symbols, // 8B + closure_type: env.var_store.fresh(), // 4B + closure_ext_var: env.var_store.fresh(), // 4B + }; + + ( + Expr2::Closure { + function_type: env.var_store.fresh(), + name: symbol, + recursive: Recursive::NotRecursive, + args: can_args, + body: env.add(body_expr, loc_body_expr.region), + extra: env.pool.add(extra), + }, + output, + ) + } + + Apply(loc_fn, loc_args, application_style) => { + // The expression that evaluates to the function being called, e.g. `foo` in + // (foo) bar baz + let fn_region = loc_fn.region; + + // Canonicalize the function expression and its arguments + let (fn_expr, mut output) = to_expr2(env, scope, &loc_fn.value, fn_region); + + // The function's return type + let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool); + + for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) { + let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region); + + env.pool[node_id] = (env.var_store.fresh(), arg_expr_id); + + output.references.union_mut(arg_out.references); + } + + // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. + output.tail_call = None; + + let expr = match fn_expr { + Expr2::Var(ref symbol) => { + output.references.calls.insert(*symbol); + + // we're tail-calling a symbol by name, check if it's the tail-callable symbol + output.tail_call = match &env.tailcallable_symbol { + Some(tc_sym) if *tc_sym == *symbol => Some(*symbol), + Some(_) | None => None, + }; + + // IDEA: Expr2::CallByName? + let fn_expr_id = env.add(fn_expr, fn_region); + Expr2::Call { + args, + expr: fn_expr_id, + expr_var: env.var_store.fresh(), + fn_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + called_via: *application_style, + } + } + Expr2::RuntimeError() => { + // We can't call a runtime error; bail out by propagating it! + return (fn_expr, output); + } + Expr2::GlobalTag { + variant_var, + ext_var, + name, + .. + } => Expr2::GlobalTag { + variant_var, + ext_var, + name, + arguments: args, + }, + Expr2::PrivateTag { + variant_var, + ext_var, + name, + .. + } => Expr2::PrivateTag { + variant_var, + ext_var, + name, + arguments: args, + }, + _ => { + // This could be something like ((if True then fn1 else fn2) arg1 arg2). + let fn_expr_id = env.add(fn_expr, fn_region); + Expr2::Call { + args, + expr: fn_expr_id, + expr_var: env.var_store.fresh(), + fn_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + called_via: *application_style, + } + } + }; + + (expr, output) + } + + Defs(loc_defs, loc_ret) => { + let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( + env, + Output::default(), + &scope, + loc_defs, + PatternType::DefExpr, + ); + + // The def as a whole is a tail call iff its return expression is a tail call. + // Use its output as a starting point because its tail_call already has the right answer! + let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region); + + output + .introduced_variables + .union(&defs_output.introduced_variables); + + output.references.union_mut(defs_output.references); + + // Now that we've collected all the references, check to see if any of the new idents + // we defined went unused by the return expression. If any were unused, report it. + for (symbol, region) in symbols_introduced { + if !output.references.has_lookup(symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + let (can_defs, output) = sort_can_defs(env, unsorted, output); + + match can_defs { + Ok(decls) => { + let mut expr = ret_expr; + + for declaration in decls.into_iter().rev() { + expr = decl_to_let(env.pool, env.var_store, declaration, expr); + } + + (expr, output) + } + Err(_err) => { + // TODO: fix this to be something from Expr2 + // (RuntimeError(err), output) + todo!() + } + } + } + + PrecedenceConflict { .. } => { + // use roc_problem::can::RuntimeError::*; + // + // let problem = PrecedenceProblem::BothNonAssociative( + // *whole_region, + // binop1.clone(), + // binop2.clone(), + // ); + // + // env.problem(Problem::PrecedenceProblem(problem.clone())); + // + // ( + // RuntimeError(InvalidPrecedence(problem, region)), + // Output::default(), + // ) + todo!() + } + MalformedClosure => { + // use roc_problem::can::RuntimeError::*; + // (RuntimeError(MalformedClosure(region)), Output::default()) + todo!() + } + MalformedIdent(_name, _problem) => { + // use roc_problem::can::RuntimeError::*; + // + // let problem = MalformedIdentifier((*name).into(), region); + // env.problem(Problem::RuntimeError(problem.clone())); + // + // (RuntimeError(problem), Output::default()) + todo!() + } + Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), + + // Below this point, we shouln't see any of these nodes anymore because + // operator desugaring should have removed them! + bad_expr @ ParensAround(_) => { + panic!( + "A ParensAround did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ SpaceBefore(_, _) => { + panic!( + "A SpaceBefore did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ SpaceAfter(_, _) => { + panic!( + "A SpaceAfter did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ BinOps { .. } => { + panic!( + "A binary operator chain did not get desugared somehow: {:#?}", + bad_expr + ); + } + bad_expr @ UnaryOp(_, _) => { + panic!( + "A unary operator did not get desugared somehow: {:#?}", + bad_expr + ); + } + + rest => todo!("not yet implemented {:?}", rest), + } +} + +pub fn to_expr_id<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + parse_expr: &'a roc_parse::ast::Expr<'a>, + region: Region, +) -> (ExprId, Output) { + let (expr, output) = to_expr2(env, scope, parse_expr, region); + + (env.add(expr, region), output) +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/introduced_vars.rs b/ast/src/lang/core/expr/introduced_vars.rs new file mode 100644 index 0000000000..7a8db4c3fa --- /dev/null +++ b/ast/src/lang/core/expr/introduced_vars.rs @@ -0,0 +1,52 @@ + +use roc_types::subs::{Variable}; +use roc_collections::all::{MutMap}; +use roc_module::ident::{Lowercase}; +use roc_module::symbol::{Symbol}; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct IntroducedVariables { + // Rigids must be unique within a type annoation. + // E.g. in `identity : a -> a`, there should only be one + // variable (a rigid one, with name "a"). + // Hence `rigids : Map` + // + // But then between annotations, the same name can occur multiple times, + // but a variable can only have one name. Therefore + // `ftv : Map`. + pub wildcards: Vec, + pub var_by_name: MutMap, + pub name_by_var: MutMap, + pub host_exposed_aliases: MutMap, +} + +impl IntroducedVariables { + pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + self.var_by_name.insert(name.clone(), var); + self.name_by_var.insert(var, name); + } + + pub fn insert_wildcard(&mut self, var: Variable) { + self.wildcards.push(var); + } + + pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { + self.host_exposed_aliases.insert(symbol, var); + } + + pub fn union(&mut self, other: &Self) { + self.wildcards.extend(other.wildcards.iter().cloned()); + self.var_by_name.extend(other.var_by_name.clone()); + self.name_by_var.extend(other.name_by_var.clone()); + self.host_exposed_aliases + .extend(other.host_exposed_aliases.clone()); + } + + pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { + self.var_by_name.get(name) + } + + pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { + self.name_by_var.get(&var) + } +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/mod.rs b/ast/src/lang/core/expr/mod.rs new file mode 100644 index 0000000000..186f65c9c0 --- /dev/null +++ b/ast/src/lang/core/expr/mod.rs @@ -0,0 +1,5 @@ +pub mod expr2; +pub mod expr2_to_string; +pub (crate) mod output; +mod introduced_vars; +pub (crate) mod expr_to_expr2; diff --git a/ast/src/lang/core/expr/output.rs b/ast/src/lang/core/expr/output.rs new file mode 100644 index 0000000000..3c84e9a618 --- /dev/null +++ b/ast/src/lang/core/expr/output.rs @@ -0,0 +1,28 @@ +use crate::{lang::core::{def::def::References, types::{Alias}}, pool::pool::NodeId}; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::symbol::Symbol; + +use super::introduced_vars::IntroducedVariables; + +#[derive(Clone, Default, Debug, PartialEq)] +pub struct Output { + pub references: References, + pub tail_call: Option, + pub introduced_variables: IntroducedVariables, + pub aliases: MutMap>, + pub non_closures: MutSet, +} + +impl Output { + pub fn union(&mut self, other: Self) { + self.references.union_mut(other.references); + + if let (None, Some(later)) = (self.tail_call, other.tail_call) { + self.tail_call = Some(later); + } + + self.aliases.extend(other.aliases); + self.non_closures.extend(other.non_closures); + } +} + \ No newline at end of file diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs new file mode 100644 index 0000000000..ea67f75082 --- /dev/null +++ b/ast/src/lang/core/fun_def.rs @@ -0,0 +1,56 @@ + + +use crate::{lang::rigids::Rigids, pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use roc_module::symbol::Symbol; +use roc_types::subs::Variable; + +use super::{expr::expr2::ExprId, pattern::PatternId, types::{Type2, TypeId}}; + +#[derive(Debug)] +pub enum FunctionDef { + WithAnnotation { + name: Symbol, // 8B + arguments: PoolVec<(PatternId, Type2)>, // 8B + rigids: NodeId, // 4B + return_type: TypeId, // 4B + body: ExprId, // 4B + }, + NoAnnotation { + name: Symbol, // 8B + arguments: PoolVec<(PatternId, Variable)>, // 8B + return_var: Variable, // 4B + body: ExprId, // 4B + }, +} + +impl ShallowClone for FunctionDef { + fn shallow_clone(&self) -> Self { + match self { + Self::WithAnnotation { + name, + arguments, + rigids, + return_type, + body, + } => Self::WithAnnotation { + name: *name, + arguments: arguments.shallow_clone(), + rigids: *rigids, + return_type: *return_type, + body: *body, + }, + + Self::NoAnnotation { + name, + arguments, + return_var, + body, + } => Self::NoAnnotation { + name: *name, + arguments: arguments.shallow_clone(), + return_var: *return_var, + body: *body, + }, + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/header.rs b/ast/src/lang/core/header.rs new file mode 100644 index 0000000000..60c359af0e --- /dev/null +++ b/ast/src/lang/core/header.rs @@ -0,0 +1,12 @@ + +use super::expr::expr2::ExprId; + + +#[derive(Debug)] +pub struct AppHeader { + pub app_name: String, + pub packages_base: String, + pub imports: Vec, + pub provides: Vec, + pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId +} \ No newline at end of file diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs new file mode 100644 index 0000000000..7347e5a924 --- /dev/null +++ b/ast/src/lang/core/mod.rs @@ -0,0 +1,8 @@ +pub mod def; +pub mod expr; +pub mod header; +pub mod ast; +mod val_def; +mod fun_def; +mod pattern; +pub mod types; \ No newline at end of file diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs new file mode 100644 index 0000000000..ddab1bef5a --- /dev/null +++ b/ast/src/lang/core/pattern.rs @@ -0,0 +1,626 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use bumpalo::collections::Vec as BumpVec; +use roc_can::expr::unescape_char; +use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_collections::all::BumpMap; +use roc_module::symbol::{Interns, Symbol}; +use roc_parse::ast::{StrLiteral, StrSegment}; +use roc_parse::pattern::PatternType; +use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; +use roc_region::all::Region; +use roc_types::subs::Variable; + +use crate::constrain::Constraint; +use crate::lang::core::expr::expr_to_expr2::to_expr_id; +use crate::lang::env::Env; +use crate::lang::scope::Scope; +use crate::pool::pool::{NodeId, Pool}; +use crate::pool::pool_str::PoolStr; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; +use crate::ast_error::{ASTResult, UnexpectedPattern2Variant}; + +use super::expr::expr2::{ExprId, FloatVal, IntVal}; +use super::expr::output::Output; +use super::types::Type2; + +pub type PatternId = NodeId; + +#[derive(Debug)] +pub enum Pattern2 { + Identifier(Symbol), // 8B + NumLiteral(Variable, i64), // 4B + 8B + IntLiteral(IntVal), // 16B + FloatLiteral(FloatVal), // 16B + StrLiteral(PoolStr), // 8B + Underscore, // 0B + GlobalTag { + whole_var: Variable, // 4B + ext_var: Variable, // 4B + tag_name: PoolStr, // 8B + arguments: PoolVec<(Variable, PatternId)>, // 8B + }, + PrivateTag { + whole_var: Variable, // 4B + ext_var: Variable, // 4B + tag_name: Symbol, // 8B + arguments: PoolVec<(Variable, PatternId)>, // 8B + }, + RecordDestructure { + whole_var: Variable, // 4B + ext_var: Variable, // 4B + destructs: PoolVec, // 8B + }, + + // Runtime Exceptions + // TODO: figure out how to better handle regions + // to keep this member under 32. With 2 Regions + // it ends up at size 40 + Shadowed { + shadowed_ident: PoolStr, + // definition: Region, + // shadowed_at: Region, + }, + + /// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! + UnsupportedPattern(Region), + // parse error patterns + MalformedPattern(MalformedPatternProblem, Region), +} + +impl ShallowClone for Pattern2 { + fn shallow_clone(&self) -> Self { + todo!() + } +} + +#[derive(Debug)] +pub struct PatternState2<'a> { + pub headers: BumpMap, + pub vars: BumpVec<'a, Variable>, + pub constraints: BumpVec<'a, Constraint<'a>>, +} + +#[derive(Debug)] +pub struct RecordDestruct { + pub var: Variable, // 4B + pub label: PoolStr, // 8B + pub symbol: Symbol, // 8B + pub typ: NodeId, // 4B +} + +#[derive(Clone, Debug)] +pub enum DestructType { + Required, + Optional(Variable, ExprId), // 4B + 4B + Guard(Variable, PatternId), // 4B + 4B +} + +pub fn as_pattern_id<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + pattern_id: PatternId, + pattern_type: PatternType, + pattern: &roc_parse::ast::Pattern<'a>, + region: Region, +) -> Output { + let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); + + env.pool[pattern_id] = can_pattern; + env.set_region(pattern_id, region); + + output +} + +pub fn to_pattern_id<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + pattern_type: PatternType, + pattern: &roc_parse::ast::Pattern<'a>, + region: Region, +) -> (Output, PatternId) { + let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); + + let pattern_id = env.pool.add(can_pattern); + env.set_region(pattern_id, region); + + (output, pattern_id) +} + +pub fn to_pattern2<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + pattern_type: PatternType, + pattern: &roc_parse::ast::Pattern<'a>, + region: Region, +) -> (Output, Pattern2) { + use roc_parse::ast::Pattern::*; + use PatternType::*; + + let mut output = Output::default(); + let can_pattern = match pattern { + Identifier(name) => match scope.introduce( + (*name).into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + output.references.bound_symbols.insert(symbol); + + Pattern2::Identifier(symbol) + } + Err((original_region, shadow)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + })); + + let name: &str = shadow.value.as_ref(); + + Pattern2::Shadowed { + shadowed_ident: PoolStr::new(name, env.pool), + } + } + }, + + QualifiedIdentifier { .. } => { + let problem = MalformedPatternProblem::QualifiedIdentifier; + malformed_pattern(env, problem, region) + } + + Underscore(_) => match pattern_type { + WhenBranch | FunctionArg => Pattern2::Underscore, + TopLevelDef | DefExpr => underscore_in_def(env, region), + }, + + FloatLiteral(ref string) => match pattern_type { + WhenBranch => match finish_parsing_float(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedFloat; + malformed_pattern(env, problem, region) + } + Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)), + }, + ptype => unsupported_pattern(env, ptype, region), + }, + + NumLiteral(string) => match pattern_type { + WhenBranch => match finish_parsing_int(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } + Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), + }, + ptype => unsupported_pattern(env, ptype, region), + }, + + NonBase10Literal { + string, + base, + is_negative, + } => match pattern_type { + WhenBranch => match finish_parsing_base(string, *base, *is_negative) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedBase(*base); + malformed_pattern(env, problem, region) + } + Ok(int) => { + if *is_negative { + Pattern2::IntLiteral(IntVal::I64(-int)) + } else { + Pattern2::IntLiteral(IntVal::I64(int)) + } + } + }, + ptype => unsupported_pattern(env, ptype, region), + }, + + StrLiteral(literal) => match pattern_type { + WhenBranch => flatten_str_literal(env.pool, literal), + ptype => unsupported_pattern(env, ptype, region), + }, + + GlobalTag(name) => { + // Canonicalize the tag's name. + Pattern2::GlobalTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: PoolStr::new(name, env.pool), + arguments: PoolVec::empty(env.pool), + } + } + PrivateTag(name) => { + let ident_id = env.ident_ids.get_or_insert(&(*name).into()); + + // Canonicalize the tag's name. + Pattern2::PrivateTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: Symbol::new(env.home, ident_id), + arguments: PoolVec::empty(env.pool), + } + } + + Apply(tag, patterns) => { + let can_patterns = PoolVec::with_capacity(patterns.len() as u32, env.pool); + for (loc_pattern, node_id) in (*patterns).iter().zip(can_patterns.iter_node_ids()) { + let (new_output, can_pattern) = to_pattern2( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + output.union(new_output); + + let can_pattern_id = env.pool.add(can_pattern); + + env.pool[node_id] = (env.var_store.fresh(), can_pattern_id); + } + + match tag.value { + GlobalTag(name) => Pattern2::GlobalTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: PoolStr::new(name, env.pool), + arguments: can_patterns, + }, + PrivateTag(name) => { + let ident_id = env.ident_ids.get_or_insert(&name.into()); + + Pattern2::PrivateTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: Symbol::new(env.home, ident_id), + arguments: can_patterns, + } + } + _ => unreachable!("Other patterns cannot be applied"), + } + } + + RecordDestructure(patterns) => { + let ext_var = env.var_store.fresh(); + let whole_var = env.var_store.fresh(); + let destructs = PoolVec::with_capacity(patterns.len() as u32, env.pool); + let opt_erroneous = None; + + for (node_id, loc_pattern) in destructs.iter_node_ids().zip((*patterns).iter()) { + match loc_pattern.value { + Identifier(label) => { + match scope.introduce( + label.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + output.references.bound_symbols.insert(symbol); + + let destruct = RecordDestruct { + var: env.var_store.fresh(), + label: PoolStr::new(label, env.pool), + symbol, + typ: env.pool.add(DestructType::Required), + }; + + env.pool[node_id] = destruct; + env.set_region(node_id, loc_pattern.region); + } + Err((original_region, shadow)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + })); + + // let shadowed = Pattern2::Shadowed { + // definition: original_region, + // shadowed_at: loc_pattern.region, + // shadowed_ident: shadow.value, + // }; + + // No matter what the other patterns + // are, we're definitely shadowed and will + // get a runtime exception as soon as we + // encounter the first bad pattern. + // opt_erroneous = Some(); + // env.pool[node_id] = sha; + // env.set_region(node_id, loc_pattern.region); + todo!("we must both report/store the problem, but also not lose any information") + } + }; + } + + RequiredField(label, loc_guard) => { + // a guard does not introduce the label into scope! + let symbol = scope.ignore(label.into(), &mut env.ident_ids); + let (new_output, can_guard) = to_pattern_id( + env, + scope, + pattern_type, + &loc_guard.value, + loc_guard.region, + ); + + let destruct = RecordDestruct { + var: env.var_store.fresh(), + label: PoolStr::new(label, env.pool), + symbol, + typ: env + .pool + .add(DestructType::Guard(env.var_store.fresh(), can_guard)), + }; + + output.union(new_output); + + env.pool[node_id] = destruct; + env.set_region(node_id, loc_pattern.region); + } + OptionalField(label, loc_default) => { + // an optional DOES introduce the label into scope! + match scope.introduce( + label.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + let (can_default, expr_output) = + to_expr_id(env, scope, &loc_default.value, loc_default.region); + + // an optional field binds the symbol! + output.references.bound_symbols.insert(symbol); + + output.union(expr_output); + + let destruct = RecordDestruct { + var: env.var_store.fresh(), + label: PoolStr::new(label, env.pool), + symbol, + typ: env.pool.add(DestructType::Optional( + env.var_store.fresh(), + can_default, + )), + }; + + env.pool[node_id] = destruct; + env.set_region(node_id, loc_pattern.region); + } + Err((original_region, shadow)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + })); + + // No matter what the other patterns + // are, we're definitely shadowed and will + // get a runtime exception as soon as we + // encounter the first bad pattern. + // opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); + todo!("must report problem but also not loose any information") + } + }; + } + _ => unreachable!("Any other pattern should have given a parse error"), + } + } + + // If we encountered an erroneous pattern (e.g. one with shadowing), + // use the resulting RuntimeError. Otherwise, return a successful record destructure. + opt_erroneous.unwrap_or(Pattern2::RecordDestructure { + whole_var, + ext_var, + destructs, + }) + } + + RequiredField(_name, _loc_pattern) => { + unreachable!("should have been handled in RecordDestructure"); + } + OptionalField(_name, _loc_pattern) => { + unreachable!("should have been handled in RecordDestructure"); + } + + Malformed(_str) => { + let problem = MalformedPatternProblem::Unknown; + malformed_pattern(env, problem, region) + } + + MalformedIdent(_str, bad_ident) => { + let problem = MalformedPatternProblem::BadIdent(*bad_ident); + malformed_pattern(env, problem, region) + } + + SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { + return to_pattern2(env, scope, pattern_type, sub_pattern, region) + } + }; + + (output, can_pattern) +} + +pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { + use Pattern2::*; + let mut symbols = Vec::new(); + let mut stack = vec![initial]; + + while let Some(pattern) = stack.pop() { + match pattern { + Identifier(symbol) => { + symbols.push(*symbol); + } + + GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + for (_, pat_id) in arguments.iter(pool) { + let pat = pool.get(*pat_id); + stack.push(pat); + } + } + + RecordDestructure { destructs, .. } => { + for destruct in destructs.iter(pool) { + let destruct_type = pool.get(destruct.typ); + + if let DestructType::Guard(_, subpattern_id) = &destruct_type { + let subpattern = pool.get(*subpattern_id); + stack.push(subpattern); + } else { + symbols.push(destruct.symbol); + } + } + } + + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | MalformedPattern(_, _) + | Shadowed { .. } + | UnsupportedPattern(_) => {} + } + } + + symbols +} + +pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult { + match pattern { + Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), + other => UnexpectedPattern2Variant { + required_pattern2: "Identifier".to_string(), + encountered_pattern2: format!("{:?}", other), + } + .fail()?, + } +} + +pub fn symbols_and_variables_from_pattern( + pool: &Pool, + initial: &Pattern2, + initial_var: Variable, +) -> Vec<(Symbol, Variable)> { + use Pattern2::*; + let mut symbols = Vec::new(); + let mut stack = vec![(initial_var, initial)]; + + while let Some((variable, pattern)) = stack.pop() { + match pattern { + Identifier(symbol) => { + symbols.push((*symbol, variable)); + } + + GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + for (var, pat_id) in arguments.iter(pool) { + let pat = pool.get(*pat_id); + stack.push((*var, pat)); + } + } + + RecordDestructure { destructs, .. } => { + for destruct in destructs.iter(pool) { + let destruct_type = pool.get(destruct.typ); + + if let DestructType::Guard(_, subpattern_id) = &destruct_type { + let subpattern = pool.get(*subpattern_id); + stack.push((destruct.var, subpattern)); + } else { + symbols.push((destruct.symbol, destruct.var)); + } + } + } + + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | MalformedPattern(_, _) + | Shadowed { .. } + | UnsupportedPattern(_) => {} + } + } + + symbols +} + +/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't +/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern. +fn unsupported_pattern<'a>( + env: &mut Env<'a>, + pattern_type: PatternType, + region: Region, +) -> Pattern2 { + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::Unsupported(pattern_type), + region, + )); + + Pattern2::UnsupportedPattern(region) +} + +fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::UnderscoreInDef, + region, + )); + + Pattern2::UnsupportedPattern(region) +} + +pub (crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { + use roc_parse::ast::StrLiteral::*; + + match literal { + PlainLine(str_slice) => Pattern2::StrLiteral(PoolStr::new(str_slice, pool)), + Line(segments) => flatten_str_lines(pool, &[segments]), + Block(lines) => flatten_str_lines(pool, lines), + } +} + +pub (crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { + use StrSegment::*; + + let mut buf = String::new(); + + for line in lines { + for segment in line.iter() { + match segment { + Plaintext(string) => { + buf.push_str(string); + } + Unicode(loc_digits) => { + todo!("parse unicode digits {:?}", loc_digits); + } + Interpolated(loc_expr) => { + return Pattern2::UnsupportedPattern(loc_expr.region); + } + EscapedChar(escaped) => buf.push(unescape_char(escaped)), + } + } + } + + Pattern2::StrLiteral(PoolStr::new(&buf, pool)) +} + +/// When we detect a malformed pattern like `3.X` or `0b5`, +/// report it to Env and return an UnsupportedPattern runtime error pattern. +fn malformed_pattern<'a>( + env: &mut Env<'a>, + problem: MalformedPatternProblem, + region: Region, +) -> Pattern2 { + env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( + problem, region, + ))); + + Pattern2::MalformedPattern(problem, region) +} diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs new file mode 100644 index 0000000000..8279cd04e5 --- /dev/null +++ b/ast/src/lang/core/types.rs @@ -0,0 +1,867 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +// use roc_can::expr::Output; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, TagName}; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; +use roc_types::types::{Problem, RecordField}; +use roc_types::{subs::Variable, types::ErrorType}; + +use crate::lang::env::Env; +use crate::lang::scope::Scope; +use crate::pool::pool::{NodeId, Pool}; +use crate::pool::pool_str::PoolStr; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; + +pub type TypeId = NodeId; + +#[derive(Debug)] +pub enum Type2 { + Variable(Variable), + + Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B + AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B + + // 32B + HostExposedAlias { + name: Symbol, // 8B + arguments: PoolVec<(PoolStr, TypeId)>, // 12B + actual_var: Variable, // 4B + actual: TypeId, // 4B + }, + EmptyTagUnion, + TagUnion(PoolVec<(TagName, PoolVec)>, TypeId), // 16B = 12B + 4B + RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec)>, TypeId), // 20B = 4B + 12B + 4B + + EmptyRec, + Record(PoolVec<(PoolStr, RecordField)>, TypeId), // 16B = 12B + 4B + + Function(PoolVec, TypeId, TypeId), // 20B = 12B + 4B + 4B + Apply(Symbol, PoolVec), // 20B = 8B + 12B + + Erroneous(Problem2), +} + +#[derive(Debug)] +pub enum Problem2 { + CanonicalizationProblem, + CircularType(Symbol, NodeId), // 12B = 8B + 4B + CyclicAlias(Symbol, PoolVec), // 20B = 8B + 12B + UnrecognizedIdent(PoolStr), // 8B + Shadowed(Located), + BadTypeArguments { + symbol: Symbol, // 8B + type_got: u8, // 1B + alias_needs: u8, // 1B + }, + InvalidModule, + SolvedTypeError, +} + +impl ShallowClone for Type2 { + fn shallow_clone(&self) -> Self { + match self { + Self::Variable(var) => Self::Variable(*var), + Self::Alias(symbol, args, alias_type_id) => { + Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone()) + } + Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()), + Self::Function(args, closure_type_id, ret_type_id) => Self::Function( + args.shallow_clone(), + closure_type_id.clone(), + ret_type_id.clone(), + ), + rest => todo!("{:?}", rest), + } + } +} + +impl Type2 { + fn substitute(_pool: &mut Pool, _subs: &MutMap, _type_id: TypeId) { + todo!() + } + + pub fn variables(&self, pool: &mut Pool) -> MutSet { + use Type2::*; + + let mut stack = vec![self]; + let mut result = MutSet::default(); + + while let Some(this) = stack.pop() { + match this { + Variable(v) => { + result.insert(*v); + } + Alias(_, _, actual) | AsAlias(_, _, actual) => { + stack.push(pool.get(*actual)); + } + HostExposedAlias { + actual_var, actual, .. + } => { + result.insert(*actual_var); + stack.push(pool.get(*actual)); + } + EmptyTagUnion | EmptyRec | Erroneous(_) => {} + TagUnion(tags, ext) => { + for (_, args) in tags.iter(pool) { + stack.extend(args.iter(pool)); + } + stack.push(pool.get(*ext)); + } + RecursiveTagUnion(rec, tags, ext) => { + for (_, args) in tags.iter(pool) { + stack.extend(args.iter(pool)); + } + stack.push(pool.get(*ext)); + result.insert(*rec); + } + Record(fields, ext) => { + for (_, field) in fields.iter(pool) { + stack.push(pool.get(*field.as_inner())); + } + stack.push(pool.get(*ext)); + } + Function(args, closure, result) => { + stack.extend(args.iter(pool)); + stack.push(pool.get(*closure)); + stack.push(pool.get(*result)); + } + Apply(_, args) => { + stack.extend(args.iter(pool)); + } + } + } + + result + } + + pub fn contains_symbol(&self, _pool: &mut Pool, _needle: Symbol) -> bool { + todo!() + } + + pub fn substitute_alias(&self, _pool: &mut Pool, _needle: Symbol, _actual: Self) { + todo!() + } +} + +impl NodeId { + pub fn variables(&self, _pool: &mut Pool) -> MutSet { + todo!() + } +} + +/// A temporary data structure to return a bunch of values to Def construction +pub enum Signature { + FunctionWithAliases { + annotation: Type2, + arguments: PoolVec, + closure_type_id: TypeId, + return_type_id: TypeId, + }, + Function { + arguments: PoolVec, + closure_type_id: TypeId, + return_type_id: TypeId, + }, + Value { + annotation: Type2, + }, +} + +pub enum Annotation2<'a> { + Annotation { + named_rigids: MutMap<&'a str, Variable>, + unnamed_rigids: MutSet, + symbols: MutSet, + signature: Signature, + }, + Erroneous(roc_types::types::Problem), +} + +pub fn to_annotation2<'a>( + env: &mut Env, + scope: &mut Scope, + annotation: &'a roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) -> Annotation2<'a> { + let mut references = References::default(); + + let annotation = to_type2(env, scope, &mut references, annotation, region); + + // we dealias until we hit a non-alias, then we either hit a function type (and produce a + // function annotation) or anything else (and produce a value annotation) + match annotation { + Type2::Function(arguments, closure_type_id, return_type_id) => { + let References { + named, + unnamed, + symbols, + .. + } = references; + + let signature = Signature::Function { + arguments, + closure_type_id, + return_type_id, + }; + + Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + } + } + Type2::Alias(_, _, _) => { + // most of the time, the annotation is not an alias, so this case is comparatively + // less efficient + shallow_dealias(env, references, annotation) + } + _ => { + let References { + named, + unnamed, + symbols, + .. + } = references; + + let signature = Signature::Value { annotation }; + + Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + } + } + } +} + +fn shallow_dealias<'a>( + env: &mut Env, + references: References<'a>, + annotation: Type2, +) -> Annotation2<'a> { + let References { + named, + unnamed, + symbols, + .. + } = references; + let mut inner = &annotation; + + loop { + match inner { + Type2::Alias(_, _, actual) => { + inner = env.pool.get(*actual); + } + Type2::Function(arguments, closure_type_id, return_type_id) => { + let signature = Signature::FunctionWithAliases { + arguments: arguments.shallow_clone(), + closure_type_id: *closure_type_id, + return_type_id: *return_type_id, + annotation, + }; + + return Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + }; + } + _ => { + let signature = Signature::Value { annotation }; + + return Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + }; + } + } + } +} + +#[derive(Default)] +pub struct References<'a> { + named: MutMap<&'a str, Variable>, + unnamed: MutSet, + hidden: MutSet, + symbols: MutSet, +} + +pub fn to_type_id<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + annotation: &roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) -> TypeId { + let type2 = to_type2(env, scope, rigids, annotation, region); + + env.add(type2, region) +} + +pub fn as_type_id<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + type_id: TypeId, + annotation: &roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) { + let type2 = to_type2(env, scope, rigids, annotation, region); + + env.pool[type_id] = type2; + env.set_region(type_id, region); +} + +pub fn to_type2<'a>( + env: &mut Env, + scope: &mut Scope, + references: &mut References<'a>, + annotation: &roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) -> Type2 { + use roc_parse::ast::TypeAnnotation::*; + + match annotation { + Apply(module_name, ident, targs) => { + match to_type_apply(env, scope, references, module_name, ident, targs, region) { + TypeApply::Apply(symbol, args) => { + references.symbols.insert(symbol); + Type2::Apply(symbol, args) + } + TypeApply::Alias(symbol, args, actual) => { + references.symbols.insert(symbol); + Type2::Alias(symbol, args, actual) + } + TypeApply::Erroneous(_problem) => { + // Type2::Erroneous(problem) + todo!() + } + } + } + Function(argument_types, return_type) => { + let arguments = PoolVec::with_capacity(argument_types.len() as u32, env.pool); + + for (type_id, loc_arg) in arguments.iter_node_ids().zip(argument_types.iter()) { + as_type_id( + env, + scope, + references, + type_id, + &loc_arg.value, + loc_arg.region, + ); + } + + let return_type_id = to_type_id( + env, + scope, + references, + &return_type.value, + return_type.region, + ); + + let closure_type = Type2::Variable(env.var_store.fresh()); + let closure_type_id = env.pool.add(closure_type); + + Type2::Function(arguments, closure_type_id, return_type_id) + } + BoundVariable(v) => { + // a rigid type variable + match references.named.get(v) { + Some(var) => Type2::Variable(*var), + None => { + let var = env.var_store.fresh(); + + references.named.insert(v, var); + + Type2::Variable(var) + } + } + } + Wildcard | Malformed(_) => { + let var = env.var_store.fresh(); + + references.unnamed.insert(var); + + Type2::Variable(var) + } + Record { fields, ext, .. } => { + let field_types_map = can_assigned_fields(env, scope, references, fields, region); + + let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool); + + for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) { + let poolstr = PoolStr::new(label, env.pool); + + let rec_field = match field { + RecordField::Optional(_) => { + let field_id = env.pool.add(field.into_inner()); + RecordField::Optional(field_id) + } + RecordField::Demanded(_) => { + let field_id = env.pool.add(field.into_inner()); + RecordField::Demanded(field_id) + } + RecordField::Required(_) => { + let field_id = env.pool.add(field.into_inner()); + RecordField::Required(field_id) + } + }; + env.pool[node_id] = (poolstr, rec_field); + } + + let ext_type = match ext { + Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), + None => env.add(Type2::EmptyRec, region), + }; + + Type2::Record(field_types, ext_type) + } + TagUnion { tags, ext, .. } => { + let tag_types_vec = can_tags(env, scope, references, tags, region); + + let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool); + + for (node_id, (tag_name, field)) in tag_types.iter_node_ids().zip(tag_types_vec) { + env.pool[node_id] = (tag_name, field); + } + + let ext_type = match ext { + Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), + None => env.add(Type2::EmptyTagUnion, region), + }; + + Type2::TagUnion(tag_types, ext_type) + } + As(loc_inner, _spaces, loc_as) => { + // e.g. `{ x : Int, y : Int } as Point }` + match loc_as.value { + Apply(module_name, ident, loc_vars) if module_name.is_empty() => { + let symbol = match scope.introduce( + ident.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => symbol, + + Err((_original_region, _shadow)) => { + // let problem = Problem2::Shadowed(original_region, shadow.clone()); + + // env.problem(roc_problem::can::Problem::ShadowingInAnnotation { + // original_region, + // shadow, + // }); + + // return Type2::Erroneous(problem); + todo!(); + } + }; + + let inner_type = to_type2(env, scope, references, &loc_inner.value, region); + let vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); + + let lowercase_vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); + + for ((loc_var, named_id), var_id) in loc_vars + .iter() + .zip(lowercase_vars.iter_node_ids()) + .zip(vars.iter_node_ids()) + { + match loc_var.value { + BoundVariable(ident) => { + let var_name = ident; + + if let Some(var) = references.named.get(&var_name) { + let poolstr = PoolStr::new(var_name, env.pool); + + let type_id = env.pool.add(Type2::Variable(*var)); + env.pool[var_id] = (poolstr.shallow_clone(), type_id); + + env.pool[named_id] = (poolstr, *var); + env.set_region(named_id, loc_var.region); + } else { + let var = env.var_store.fresh(); + + references.named.insert(var_name.clone(), var); + let poolstr = PoolStr::new(var_name, env.pool); + + let type_id = env.pool.add(Type2::Variable(var)); + env.pool[var_id] = (poolstr.shallow_clone(), type_id); + + env.pool[named_id] = (poolstr, var); + env.set_region(named_id, loc_var.region); + } + } + _ => { + // If anything other than a lowercase identifier + // appears here, the whole annotation is invalid. + return Type2::Erroneous(Problem2::CanonicalizationProblem); + } + } + } + + let alias_actual = inner_type; + // TODO instantiate recursive tag union + // let alias_actual = if let Type2::TagUnion(tags, ext) = inner_type { + // let rec_var = env.var_store.fresh(); + // + // let mut new_tags = Vec::with_capacity(tags.len()); + // for (tag_name, args) in tags { + // let mut new_args = Vec::with_capacity(args.len()); + // for arg in args { + // let mut new_arg = arg.clone(); + // new_arg.substitute_alias(symbol, &Type2::Variable(rec_var)); + // new_args.push(new_arg); + // } + // new_tags.push((tag_name.clone(), new_args)); + // } + // Type2::RecursiveTagUnion(rec_var, new_tags, ext) + // } else { + // inner_type + // }; + + let mut hidden_variables = MutSet::default(); + hidden_variables.extend(alias_actual.variables(env.pool)); + + for (_, var) in lowercase_vars.iter(env.pool) { + hidden_variables.remove(var); + } + + let alias_actual_id = env.pool.add(alias_actual); + scope.add_alias(env.pool, symbol, lowercase_vars, alias_actual_id); + + let alias = scope.lookup_alias(symbol).unwrap(); + // local_aliases.insert(symbol, alias.clone()); + + // TODO host-exposed + // if vars.is_empty() && env.home == symbol.module_id() { + // let actual_var = env.var_store.fresh(); + // rigids.host_exposed.insert(symbol, actual_var); + // Type::HostExposedAlias { + // name: symbol, + // arguments: vars, + // actual: Box::new(alias.typ.clone()), + // actual_var, + // } + // } else { + // Type::Alias(symbol, vars, Box::new(alias.typ.clone())) + // } + Type2::AsAlias(symbol, vars, alias.actual) + } + _ => { + // This is a syntactically invalid type alias. + Type2::Erroneous(Problem2::CanonicalizationProblem) + } + } + } + SpaceBefore(nested, _) | SpaceAfter(nested, _) => { + to_type2(env, scope, references, nested, region) + } + } +} + +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +fn can_assigned_fields<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + fields: &&[Located>>], + region: Region, +) -> MutMap<&'a str, RecordField> { + use roc_parse::ast::AssignedField::*; + use roc_types::types::RecordField::*; + + // SendMap doesn't have a `with_capacity` + let mut field_types = MutMap::default(); + + // field names we've seen so far in this record + let mut seen = std::collections::HashMap::with_capacity(fields.len()); + + 'outer: for loc_field in fields.iter() { + let mut field = &loc_field.value; + + // use this inner loop to unwrap the SpaceAfter/SpaceBefore + // when we find the name of this field, break out of the loop + // with that value, so we can check whether the field name is + // a duplicate + let new_name = 'inner: loop { + match field { + RequiredValue(field_name, _, annotation) => { + let field_type = + to_type2(env, scope, rigids, &annotation.value, annotation.region); + + let label = field_name.value; + field_types.insert(label, Required(field_type)); + + break 'inner label; + } + OptionalValue(field_name, _, annotation) => { + let field_type = + to_type2(env, scope, rigids, &annotation.value, annotation.region); + + let label = field_name.value; + field_types.insert(label.clone(), Optional(field_type)); + + break 'inner label; + } + LabelOnly(loc_field_name) => { + // Interpret { a, b } as { a : a, b : b } + let field_name = loc_field_name.value; + let field_type = { + if let Some(var) = rigids.named.get(&field_name) { + Type2::Variable(*var) + } else { + let field_var = env.var_store.fresh(); + rigids.named.insert(field_name, field_var); + Type2::Variable(field_var) + } + }; + + field_types.insert(field_name.clone(), Required(field_type)); + + break 'inner field_name; + } + SpaceBefore(nested, _) | SpaceAfter(nested, _) => { + // check the nested field instead + field = nested; + continue 'inner; + } + Malformed(_) => { + // TODO report this? + // completely skip this element, advance to the next tag + continue 'outer; + } + } + }; + + // ensure that the new name is not already in this record: + // note that the right-most tag wins when there are two with the same name + if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) { + env.problem(roc_problem::can::Problem::DuplicateRecordFieldType { + field_name: new_name.into(), + record_region: region, + field_region: loc_field.region, + replaced_region, + }); + } + } + + field_types +} + +fn can_tags<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + tags: &'a [Located>], + region: Region, +) -> Vec<(TagName, PoolVec)> { + use roc_parse::ast::Tag; + let mut tag_types = Vec::with_capacity(tags.len()); + + // tag names we've seen so far in this tag union + let mut seen = std::collections::HashMap::with_capacity(tags.len()); + + 'outer: for loc_tag in tags.iter() { + let mut tag = &loc_tag.value; + + // use this inner loop to unwrap the SpaceAfter/SpaceBefore + // when we find the name of this tag, break out of the loop + // with that value, so we can check whether the tag name is + // a duplicate + let new_name = 'inner: loop { + match tag { + Tag::Global { name, args } => { + let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); + + for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { + as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); + } + + let tag_name = TagName::Global(name.value.into()); + tag_types.push((tag_name.clone(), arg_types)); + + break 'inner tag_name; + } + Tag::Private { name, args } => { + let ident_id = env.ident_ids.get_or_insert(&name.value.into()); + let symbol = Symbol::new(env.home, ident_id); + + let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); + + for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { + as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); + } + + let tag_name = TagName::Private(symbol); + tag_types.push((tag_name.clone(), arg_types)); + + break 'inner tag_name; + } + Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => { + // check the nested tag instead + tag = nested; + continue 'inner; + } + Tag::Malformed(_) => { + // TODO report this? + // completely skip this element, advance to the next tag + continue 'outer; + } + } + }; + + // ensure that the new name is not already in this tag union: + // note that the right-most tag wins when there are two with the same name + if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) { + env.problem(roc_problem::can::Problem::DuplicateTag { + tag_region: loc_tag.region, + tag_union_region: region, + replaced_region, + tag_name: new_name, + }); + } + } + + tag_types +} + +enum TypeApply { + Apply(Symbol, PoolVec), + Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), + Erroneous(roc_types::types::Problem), +} + +#[inline(always)] +fn to_type_apply<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + module_name: &str, + ident: &str, + type_arguments: &[Located>], + region: Region, +) -> TypeApply { + let symbol = if module_name.is_empty() { + // Since module_name was empty, this is an unqualified type. + // Look it up in scope! + let ident: Ident = (*ident).into(); + + match scope.lookup(&ident, region) { + Ok(symbol) => symbol, + Err(problem) => { + env.problem(roc_problem::can::Problem::RuntimeError(problem)); + + return TypeApply::Erroneous(Problem::UnrecognizedIdent(ident.into())); + } + } + } else { + match env.qualified_lookup(module_name, ident, region) { + Ok(symbol) => symbol, + Err(problem) => { + // Either the module wasn't imported, or + // it was imported but it doesn't expose this ident. + env.problem(roc_problem::can::Problem::RuntimeError(problem)); + + return TypeApply::Erroneous(Problem::UnrecognizedIdent((*ident).into())); + } + } + }; + + let argument_type_ids = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); + + for (type_id, loc_arg) in argument_type_ids.iter_node_ids().zip(type_arguments.iter()) { + as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); + } + + let args = type_arguments; + let opt_alias = scope.lookup_alias(symbol); + match opt_alias { + Some(ref alias) => { + // use a known alias + let actual = alias.actual; + let mut substitutions: MutMap = MutMap::default(); + + if alias.targs.len() != args.len() { + let error = TypeApply::Erroneous(Problem::BadTypeArguments { + symbol, + region, + alias_needs: alias.targs.len() as u8, + type_got: args.len() as u8, + }); + return error; + } + + let arguments = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); + + let it = arguments.iter_node_ids().zip( + argument_type_ids + .iter_node_ids() + .zip(alias.targs.iter_node_ids()), + ); + + for (node_id, (type_id, loc_var_id)) in it { + let loc_var = &env.pool[loc_var_id]; + let name = loc_var.0.shallow_clone(); + let var = loc_var.1; + + env.pool[node_id] = (name, type_id); + + substitutions.insert(var, type_id); + } + + // make sure the recursion variable is freshly instantiated + // have to allocate these outside of the if for lifetime reasons... + let new = env.var_store.fresh(); + let fresh = env.pool.add(Type2::Variable(new)); + if let Type2::RecursiveTagUnion(rvar, ref tags, ext) = &mut env.pool[actual] { + substitutions.insert(*rvar, fresh); + + env.pool[actual] = Type2::RecursiveTagUnion(new, tags.shallow_clone(), *ext); + } + + // make sure hidden variables are freshly instantiated + for var_id in alias.hidden_variables.iter_node_ids() { + let var = env.pool[var_id]; + let fresh = env.pool.add(Type2::Variable(env.var_store.fresh())); + substitutions.insert(var, fresh); + } + + // instantiate variables + Type2::substitute(env.pool, &substitutions, actual); + + TypeApply::Alias(symbol, arguments, actual) + } + None => TypeApply::Apply(symbol, argument_type_ids), + } +} + +#[derive(Debug)] +pub struct Alias { + pub targs: PoolVec<(PoolStr, Variable)>, + pub actual: TypeId, + + /// hidden type variables, like the closure variable in `a -> b` + pub hidden_variables: PoolVec, +} + +impl ShallowClone for Alias { + fn shallow_clone(&self) -> Self { + Self { + targs: self.targs.shallow_clone(), + hidden_variables: self.hidden_variables.shallow_clone(), + actual: self.actual, + } + } +} diff --git a/ast/src/lang/core/val_def.rs b/ast/src/lang/core/val_def.rs new file mode 100644 index 0000000000..c97df3f810 --- /dev/null +++ b/ast/src/lang/core/val_def.rs @@ -0,0 +1,91 @@ +use crate::{lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, pool::{pool::{NodeId, Pool}, shallow_clone::ShallowClone}}; +use roc_types::subs::{Variable}; + +use super::{expr::expr2::ExprId, pattern::{Pattern2, PatternId}, types::TypeId}; + +#[derive(Debug)] +pub enum ValueDef { + WithAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + type_id: TypeId, + rigids: Rigids, + expr_var: Variable, // 4B + }, + NoAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + expr_var: Variable, // 4B + }, +} + +impl ShallowClone for ValueDef { + fn shallow_clone(&self) -> Self { + match self { + Self::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => Self::WithAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + type_id: *type_id, + rigids: rigids.shallow_clone(), + expr_var: *expr_var, + }, + Self::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => Self::NoAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + expr_var: *expr_var, + }, + } + } +} + +impl ValueDef { + pub fn get_expr_id(&self) -> ExprId { + match self { + ValueDef::WithAnnotation { expr_id, .. } => *expr_id, + ValueDef::NoAnnotation { expr_id, .. } => *expr_id, + } + } + + pub fn get_pattern_id(&self) -> NodeId { + match self { + ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, + ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, + } + } +} + +pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { + match val_def { + ValueDef::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => { + format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) + } + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + format!( + "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", + pool.get(*pattern_id), + expr2_to_string(*expr_id, pool), + expr_var + ) + } + } +} \ No newline at end of file diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs new file mode 100644 index 0000000000..5a6d212fcf --- /dev/null +++ b/ast/src/lang/env.rs @@ -0,0 +1,168 @@ +use bumpalo::{collections::Vec as BumpVec, Bump}; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, ModuleName}; +use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore}; + +use crate::pool::pool::{NodeId, Pool}; + +use super::core::def::def::References; + +#[derive(Debug)] +pub struct Env<'a> { + pub home: ModuleId, + pub var_store: &'a mut VarStore, + pub pool: &'a mut Pool, + pub arena: &'a Bump, + + pub problems: BumpVec<'a, Problem>, + + pub dep_idents: MutMap, + pub module_ids: &'a ModuleIds, + pub ident_ids: IdentIds, + pub exposed_ident_ids: IdentIds, + + pub closures: MutMap, + /// Symbols which were referenced by qualified lookups. + pub qualified_lookups: MutSet, + + pub top_level_symbols: MutSet, + + pub closure_name_symbol: Option, + pub tailcallable_symbol: Option, +} + +impl<'a> Env<'a> { + pub fn new( + home: ModuleId, + arena: &'a Bump, + pool: &'a mut Pool, + var_store: &'a mut VarStore, + dep_idents: MutMap, + module_ids: &'a ModuleIds, + exposed_ident_ids: IdentIds, + ) -> Env<'a> { + Env { + home, + arena, + pool, + problems: BumpVec::new_in(arena), + var_store, + dep_idents, + module_ids, + ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later + exposed_ident_ids, + closures: MutMap::default(), + qualified_lookups: MutSet::default(), + tailcallable_symbol: None, + closure_name_symbol: None, + top_level_symbols: MutSet::default(), + } + } + + pub fn add(&mut self, item: T, region: Region) -> NodeId { + let id = self.pool.add(item); + self.set_region(id, region); + + id + } + + pub fn problem(&mut self, problem: Problem) { + self.problems.push(problem); + } + + pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { + dbg!("Don't Forget to set the region eventually"); + } + + pub fn register_closure(&mut self, symbol: Symbol, references: References) { + self.closures.insert(symbol, references); + } + + /// Generates a unique, new symbol like "$1" or "$5", + /// using the home module as the module_id. + /// + /// This is used, for example, during canonicalization of an Expr::Closure + /// to generate a unique symbol to refer to that closure. + pub fn gen_unique_symbol(&mut self) -> Symbol { + let ident_id = self.ident_ids.gen_unique(); + + Symbol::new(self.home, ident_id) + } + + /// Returns Err if the symbol resolved, but it was not exposed by the given module + pub fn qualified_lookup( + &mut self, + module_name: &str, + ident: &str, + region: Region, + ) -> Result { + debug_assert!( + !module_name.is_empty(), + "Called env.qualified_lookup with an unqualified ident: {:?}", + ident + ); + + let module_name: ModuleName = module_name.into(); + + match self.module_ids.get_id(&module_name) { + Some(&module_id) => { + let ident: Ident = ident.into(); + + // You can do qualified lookups on your own module, e.g. + // if I'm in the Foo module, I can do a `Foo.bar` lookup. + if module_id == self.home { + match self.ident_ids.get_id(&ident) { + Some(ident_id) => { + let symbol = Symbol::new(module_id, *ident_id); + + self.qualified_lookups.insert(symbol); + + Ok(symbol) + } + None => Err(RuntimeError::LookupNotInScope( + Located { + value: ident, + region, + }, + self.ident_ids + .idents() + .map(|(_, string)| string.as_ref().into()) + .collect(), + )), + } + } else { + match self + .dep_idents + .get(&module_id) + .and_then(|exposed_ids| exposed_ids.get_id(&ident)) + { + Some(ident_id) => { + let symbol = Symbol::new(module_id, *ident_id); + + self.qualified_lookups.insert(symbol); + + Ok(symbol) + } + None => Err(RuntimeError::ValueNotExposed { + module_name: ModuleName::from(module_name), + ident, + region, + }), + } + } + } + None => Err(RuntimeError::ModuleNotImported { + module_name, + imported_modules: self + .module_ids + .available_modules() + .map(|string| string.as_ref().into()) + .collect(), + region, + }), + } + } +} \ No newline at end of file diff --git a/ast/src/lang/mod.rs b/ast/src/lang/mod.rs new file mode 100644 index 0000000000..9437f39470 --- /dev/null +++ b/ast/src/lang/mod.rs @@ -0,0 +1,4 @@ +pub mod core; +mod scope; +mod rigids; +mod env; diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs new file mode 100644 index 0000000000..94bca10038 --- /dev/null +++ b/ast/src/lang/rigids.rs @@ -0,0 +1,80 @@ +use std::{collections::{HashMap, HashSet}, hash::BuildHasherDefault}; + +use crate::pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}; +use roc_collections::all::WyHash; +use roc_types::subs::{Variable}; + +#[derive(Debug)] +pub struct Rigids { + pub names: PoolVec<(Option, Variable)>, // 8B + padding: [u8; 1], +} + +#[allow(clippy::needless_collect)] +impl Rigids { + pub fn new( + named: HashMap<&str, Variable, BuildHasherDefault>, + unnamed: HashSet>, + pool: &mut Pool, + ) -> Self { + let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool); + + let mut temp_names = Vec::new(); + + temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); + + temp_names.extend(unnamed.iter().map(|var| (None, *var))); + + for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) { + let poolstr = opt_name.map(|name| PoolStr::new(name, pool)); + + pool[node_id] = (poolstr, variable); + } + + Self { + names, + padding: Default::default(), + } + } + + pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> { + let named = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + if let Some(pool_str) = opt_pool_str { + Some((*pool_str, *var)) + } else { + None + } + }) + .collect::>(); + + PoolVec::new(named.into_iter(), pool) + } + + pub fn unnamed(&self, pool: &mut Pool) -> PoolVec { + let unnamed = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + if opt_pool_str.is_none() { + Some(*var) + } else { + None + } + }) + .collect::>(); + + PoolVec::new(unnamed.into_iter(), pool) + } +} + +impl ShallowClone for Rigids { + fn shallow_clone(&self) -> Self { + Self { + names: self.names.shallow_clone(), + padding: self.padding, + } + } +} diff --git a/ast/src/lang/scope.rs b/ast/src/lang/scope.rs new file mode 100644 index 0000000000..fa8cc7e9d2 --- /dev/null +++ b/ast/src/lang/scope.rs @@ -0,0 +1,330 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use crate::pool::pool::Pool; +use crate::pool::pool_str::PoolStr; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, Lowercase}; +use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_problem::can::RuntimeError; +use roc_region::all::{Located, Region}; +use roc_types::{ + builtin_aliases, + solved_types::{BuiltinAlias, FreeVars, SolvedType}, + subs::{VarId, VarStore, Variable}, +}; + +use super::core::types::{Alias, Type2, TypeId}; + +fn solved_type_to_type_id( + pool: &mut Pool, + solved_type: &SolvedType, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> TypeId { + let typ2 = to_type2(pool, solved_type, free_vars, var_store); + + pool.add(typ2) +} + +fn to_type2( + pool: &mut Pool, + solved_type: &SolvedType, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> Type2 { + match solved_type { + SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual) => { + let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); + + for (type_variable_node_id, (lowercase, solved_arg)) in type_variables + .iter_node_ids() + .zip(solved_type_variables.iter()) + { + let typ2 = to_type2(pool, solved_arg, free_vars, var_store); + + let node = pool.add(typ2); + + pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node); + } + + let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store); + + let actual = pool.add(actual_typ2); + + let typ2 = Type2::Alias(*symbol, type_variables, actual); + + typ2 + } + SolvedType::TagUnion(tags, ext) => { + let new_tags = PoolVec::with_capacity(tags.len() as u32, pool); + + for (tag_node_id, (tag_name, args)) in new_tags.iter_node_ids().zip(tags.iter()) { + let new_args: PoolVec = PoolVec::with_capacity(args.len() as u32, pool); + + for (arg_node_id, arg) in new_args.iter_node_ids().zip(args.iter()) { + let node = to_type2(pool, arg, free_vars, var_store); + + pool[arg_node_id] = node; + } + + pool[tag_node_id] = (tag_name.clone(), new_args); + } + + let actual_typ2 = to_type2(pool, ext, free_vars, var_store); + + let actual = pool.add(actual_typ2); + + let typ2 = Type2::TagUnion(new_tags, actual); + + typ2 + } + SolvedType::Flex(var_id) => { + Type2::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)) + } + SolvedType::EmptyTagUnion => Type2::EmptyTagUnion, + rest => todo!("{:?}", rest), + } +} + +fn var_id_to_flex_var( + var_id: VarId, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> Variable { + if let Some(var) = free_vars.unnamed_vars.get(&var_id) { + *var + } else { + let var = var_store.fresh(); + free_vars.unnamed_vars.insert(var_id, var); + + var + } +} + +#[derive(Debug)] +pub struct Scope { + /// All the identifiers in scope, mapped to were they were defined and + /// the Symbol they resolve to. + idents: MutMap, + + /// A cache of all the symbols in scope. This makes lookups much + /// faster when checking for unused defs and unused arguments. + symbols: MutMap, + + /// The type aliases currently in scope + aliases: MutMap, + + /// The current module being processed. This will be used to turn + /// unqualified idents into Symbols. + home: ModuleId, +} + +impl Scope { + pub fn new(home: ModuleId, pool: &mut Pool, var_store: &mut VarStore) -> Scope { + let solved_aliases = builtin_aliases::aliases(); + let mut aliases = MutMap::default(); + + for (symbol, builtin_alias) in solved_aliases { + // let BuiltinAlias { region, vars, typ } = builtin_alias; + let BuiltinAlias { vars, typ, .. } = builtin_alias; + + let mut free_vars = FreeVars::default(); + + // roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + let actual = solved_type_to_type_id(pool, &typ, &mut free_vars, var_store); + + // make sure to sort these variables to make them line up with the type arguments + let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); + type_variables.sort(); + + debug_assert_eq!(vars.len(), type_variables.len()); + let variables = PoolVec::with_capacity(vars.len() as u32, pool); + + let it = variables + .iter_node_ids() + .zip(vars.iter()) + .zip(type_variables); + for ((node_id, loc_name), (_, var)) in it { + // TODO region is ignored, but "fake" anyway. How to resolve? + let name = PoolStr::new(loc_name.value.as_str(), pool); + pool[node_id] = (name, var); + } + + let alias = Alias { + actual, + /// We know that builtin aliases have no hiddden variables (e.g. in closures) + hidden_variables: PoolVec::empty(pool), + targs: variables, + }; + + aliases.insert(symbol, alias); + } + + let idents = Symbol::default_in_scope(); + let idents: MutMap<_, _> = idents.into_iter().collect(); + + Scope { + home, + idents, + symbols: MutMap::default(), + aliases, + } + } + + pub fn idents(&self) -> impl Iterator { + self.idents.iter() + } + + pub fn symbols(&self) -> impl Iterator + '_ { + self.symbols.iter().map(|(x, y)| (*x, *y)) + } + + pub fn contains_ident(&self, ident: &Ident) -> bool { + self.idents.contains_key(ident) + } + + pub fn contains_symbol(&self, symbol: Symbol) -> bool { + self.symbols.contains_key(&symbol) + } + + pub fn num_idents(&self) -> usize { + self.idents.len() + } + + pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result { + match self.idents.get(ident) { + Some((symbol, _)) => Ok(*symbol), + None => Err(RuntimeError::LookupNotInScope( + Located { + region, + value: ident.clone().into(), + }, + self.idents.keys().map(|v| v.as_ref().into()).collect(), + )), + } + } + + pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> { + self.aliases.get(&symbol) + } + + /// Introduce a new ident to scope. + /// + /// Returns Err if this would shadow an existing ident, including the + /// Symbol and Region of the ident we already had in scope under that name. + pub fn introduce( + &mut self, + ident: Ident, + exposed_ident_ids: &IdentIds, + all_ident_ids: &mut IdentIds, + region: Region, + ) -> Result)> { + match self.idents.get(&ident) { + Some((_, original_region)) => { + let shadow = Located { + value: ident, + region, + }; + + Err((*original_region, shadow)) + } + None => { + // If this IdentId was already added previously + // when the value was exposed in the module header, + // use that existing IdentId. Otherwise, create a fresh one. + let ident_id = match exposed_ident_ids.get_id(&ident) { + Some(ident_id) => *ident_id, + None => all_ident_ids.add(ident.clone().into()), + }; + + let symbol = Symbol::new(self.home, ident_id); + + self.symbols.insert(symbol, region); + self.idents.insert(ident, (symbol, region)); + + Ok(symbol) + } + } + } + + /// Ignore an identifier. + /// + /// Used for record guards like { x: Just _ } + pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { + let ident_id = all_ident_ids.add(ident.into()); + Symbol::new(self.home, ident_id) + } + + /// Import a Symbol from another module into this module's top-level scope. + /// + /// Returns Err if this would shadow an existing ident, including the + /// Symbol and Region of the ident we already had in scope under that name. + pub fn import( + &mut self, + ident: Ident, + symbol: Symbol, + region: Region, + ) -> Result<(), (Symbol, Region)> { + match self.idents.get(&ident) { + Some(shadowed) => Err(*shadowed), + None => { + self.symbols.insert(symbol, region); + self.idents.insert(ident, (symbol, region)); + + Ok(()) + } + } + } + + pub fn add_alias( + &mut self, + pool: &mut Pool, + name: Symbol, + vars: PoolVec<(PoolStr, Variable)>, + typ: TypeId, + ) { + let mut hidden_variables = MutSet::default(); + hidden_variables.extend(typ.variables(pool)); + + for loc_var in vars.iter(pool) { + hidden_variables.remove(&loc_var.1); + } + + let hidden_variables_vec = PoolVec::with_capacity(hidden_variables.len() as u32, pool); + + for (node_id, var) in hidden_variables_vec.iter_node_ids().zip(hidden_variables) { + pool[node_id] = var; + } + + let alias = Alias { + targs: vars, + hidden_variables: hidden_variables_vec, + actual: typ, + }; + + self.aliases.insert(name, alias); + } + + pub fn contains_alias(&mut self, name: Symbol) -> bool { + self.aliases.contains_key(&name) + } +} + +impl ShallowClone for Scope { + fn shallow_clone(&self) -> Self { + Self { + idents: self.idents.clone(), + symbols: self.symbols.clone(), + aliases: self + .aliases + .iter() + .map(|(s, a)| (*s, a.shallow_clone())) + .collect(), + home: self.home, + } + } +} diff --git a/ast/src/lib.rs b/ast/src/lib.rs new file mode 100644 index 0000000000..84c6ff305b --- /dev/null +++ b/ast/src/lib.rs @@ -0,0 +1,5 @@ +pub mod lang; +pub mod pool; +mod constrain; +mod canonicalize; +mod ast_error; \ No newline at end of file diff --git a/ast/src/mod.rs b/ast/src/mod.rs new file mode 100644 index 0000000000..addb2cebaf --- /dev/null +++ b/ast/src/mod.rs @@ -0,0 +1,14 @@ +pub mod ast; +mod constrain; +pub mod lang; +mod module; +pub mod parse; +mod pattern; +pub mod pool; +pub mod roc_file; +mod scope; +mod solve; +mod types; +mod rigids; +mod canonicalize; +mod ast_error; \ No newline at end of file diff --git a/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs new file mode 100644 index 0000000000..2f99b8a071 --- /dev/null +++ b/ast/src/parse/parse_ast.rs @@ -0,0 +1,34 @@ + + +pub fn parse_from_string<'a>( + code_str: &'a str, + env: &mut Env<'a>, + ast_arena: &'a Bump, +) -> Result> { + let blank_line_indx = code_str + .find("\n\n") + .expect("I was expecting a double newline to split header and rest of code."); + + let header_str = &code_str[0..blank_line_indx]; + let tail_str = &code_str[blank_line_indx..]; + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + let region = Region::new(0, 0, 0, 0); + + let mut def_ids = Vec::::new(); + + let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; + + for def2 in def2_vec { + let def_id = env.pool.add(def2); + + def_ids.push(def_id); + } + + let ast_node_id = env.pool.add(Expr2::Blank); + + Ok(AST { + header: AppHeader::parse_from_string(header_str, ast_node_id), + def_ids, + }) +} \ No newline at end of file diff --git a/ast/src/parse/parse_expr.rs b/ast/src/parse/parse_expr.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ast/src/parse/parse_header.rs b/ast/src/parse/parse_header.rs new file mode 100644 index 0000000000..0b9f2016f9 --- /dev/null +++ b/ast/src/parse/parse_header.rs @@ -0,0 +1,11 @@ + +// TODO don't use mock struct and actually parse string +pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { + AppHeader { + app_name: "\"untitled-app\"".to_owned(), + packages_base: "\"platform\"".to_owned(), + imports: vec![], + provides: vec!["main".to_owned()], + ast_node_id, + } +} \ No newline at end of file diff --git a/ast/src/pool/mod.rs b/ast/src/pool/mod.rs new file mode 100644 index 0000000000..1b0cd90832 --- /dev/null +++ b/ast/src/pool/mod.rs @@ -0,0 +1,4 @@ +pub mod pool; +pub mod pool_str; +pub mod pool_vec; +pub mod shallow_clone; \ No newline at end of file diff --git a/ast/src/pool/pool.rs b/ast/src/pool/pool.rs new file mode 100644 index 0000000000..8663ca4bcb --- /dev/null +++ b/ast/src/pool/pool.rs @@ -0,0 +1,228 @@ +/// A memory pool of 32-byte nodes. The node value 0 is reserved for the pool's +/// use, and valid nodes may never have that value. +/// +/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes +/// into one page at a time, and when it runs out, it uses mmap to reserve an +/// anonymous memory page in which to store nodes. +/// +/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a +/// particular node by its NodeId, which is an opaque wrapper around a pointer. +/// +/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. +/// This is important for performance. +use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; +use std::any::type_name; +use std::marker::PhantomData; +use std::mem::size_of; +use std::ptr::null; + +pub const NODE_BYTES: usize = 32; + +// Each page has 128 slots. Each slot holds one 32B node +// This means each page is 4096B, which is the size of a memory page +// on typical systems where the compiler will be run. +// +// Nice things about this system include: +// * Allocating a new page is as simple as asking the OS for a memory page. +// * Since each node is 32B, each node's memory address will be a multiple of 16. +// * Thanks to the free lists and our consistent chunk sizes, we should +// end up with very little fragmentation. +// * Finding a slot for a given node should be very fast: see if the relevant +// free list has any openings; if not, try the next size up. +// +// Less nice things include: +// * This system makes it very hard to ever give a page back to the OS. +// We could try doing the Mesh Allocator strategy: whenever we allocate +// something, assign it to a random slot in the page, and then periodically +// try to merge two pages into one (by locking and remapping them in the OS) +// and then returning the redundant physical page back to the OS. This should +// work in theory, but is pretty complicated, and we'd need to schedule it. +// Keep in mind that we can't use the Mesh Allocator itself because it returns +// usize pointers, which would be too big for us to have 16B nodes. +// On the plus side, we could be okay with higher memory usage early on, +// and then later use the Mesh strategy to reduce long-running memory usage. +// +// With this system, we can allocate up to 4B nodes. If we wanted to keep +// a generational index in there, like https://crates.io/crates/sharded-slab +// does, we could use some of the 32 bits for that. For example, if we wanted +// to have a 5-bit generational index (supporting up to 32 generations), then +// we would have 27 bits remaining, meaning we could only support at most +// 134M nodes. Since the editor has a separate Pool for each module, is that +// enough for any single module we'll encounter in practice? Probably, and +// especially if we allocate super large collection literals on the heap instead +// of in the pool. +// +// Another possible design is to try to catch reuse bugs using an "ASan" like +// approach: in development builds, whenever we "free" a particular slot, we +// can add it to a dev-build-only "freed nodes" list and don't hand it back +// out (so, we leak the memory.) Then we can (again, in development builds only) +// check to see if we're about to store something in zeroed-out memory; if so, check +// to see if it was + +#[derive(Debug, Eq)] +pub (crate) struct NodeId { + pub (super) index: u32, + _phantom: PhantomData, +} + +impl Clone for NodeId { + fn clone(&self) -> Self { + NodeId { + index: self.index, + _phantom: PhantomData::default(), + } + } +} + +impl PartialEq for NodeId { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} + +impl Copy for NodeId {} + +#[derive(Debug)] +pub struct Pool { + pub (super) nodes: *mut [u8; NODE_BYTES], + num_nodes: u32, + capacity: u32, + // free_1node_slots: Vec>, +} + +impl Pool { + pub fn with_capacity(nodes: u32) -> Self { + // round up number of nodes requested to nearest page size in bytes + let bytes_per_page = page_size::get(); + let node_bytes = NODE_BYTES * nodes as usize; + let leftover = node_bytes % bytes_per_page; + let bytes_to_mmap = if leftover == 0 { + node_bytes + } else { + node_bytes + bytes_per_page - leftover + }; + + let nodes = unsafe { + // mmap anonymous memory pages - that is, contiguous virtual memory + // addresses from the OS which will be lazily translated into + // physical memory one 4096-byte page at a time, once we actually + // try to read or write in that page's address range. + libc::mmap( + null::() as *mut c_void, + bytes_to_mmap, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + 0, + 0, + ) + } as *mut [u8; NODE_BYTES]; + + // This is our actual capacity, in nodes. + // It might be higher than the requested capacity due to rounding up + // to nearest page size. + let capacity = (bytes_to_mmap / NODE_BYTES) as u32; + + Pool { + nodes, + num_nodes: 0, + capacity, + } + } + + pub fn add(&mut self, node: T) -> NodeId { + // It's only safe to store this if T fits in S. + debug_assert!( + size_of::() <= NODE_BYTES, + "{} has a size of {}, but it needs to be at most {}", + type_name::(), + size_of::(), + NODE_BYTES + ); + + let node_id = self.reserve(1); + let node_ptr = unsafe { self.nodes.offset(node_id.index as isize) } as *mut T; + + unsafe { *node_ptr = node }; + + node_id + } + + /// Reserves the given number of contiguous node slots, and returns + /// the NodeId of the first one. We only allow reserving 2^32 in a row. + pub (super) fn reserve(&mut self, nodes: u32) -> NodeId { + // TODO once we have a free list, look in there for an open slot first! + let index = self.num_nodes; + + if index < self.capacity { + self.num_nodes = index + nodes; + + NodeId { + index, + _phantom: PhantomData::default(), + } + } else { + todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows."); + } + } + + pub fn get<'a, 'b, T>(&'a self, node_id: NodeId) -> &'b T { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *const T; + + &*node_ptr + } + } + + pub fn get_mut(&mut self, node_id: NodeId) -> &mut T { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; + + &mut *node_ptr + } + } + + pub fn set(&mut self, node_id: NodeId, element: T) { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; + + *node_ptr = element; + } + } + + // A node is available iff its bytes are all zeroes + #[allow(dead_code)] + fn is_available(&self, node_id: NodeId) -> bool { + debug_assert_eq!(size_of::(), NODE_BYTES); + + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES]; + + *node_ptr == [0; NODE_BYTES] + } + } +} + +impl std::ops::Index> for Pool { + type Output = T; + + fn index(&self, node_id: NodeId) -> &Self::Output { + self.get(node_id) + } +} + +impl std::ops::IndexMut> for Pool { + fn index_mut(&mut self, node_id: NodeId) -> &mut Self::Output { + self.get_mut(node_id) + } +} + +impl Drop for Pool { + fn drop(&mut self) { + unsafe { + libc::munmap( + self.nodes as *mut c_void, + NODE_BYTES * self.capacity as usize, + ); + } + } +} \ No newline at end of file diff --git a/ast/src/pool/pool_str.rs b/ast/src/pool/pool_str.rs new file mode 100644 index 0000000000..02a9d47603 --- /dev/null +++ b/ast/src/pool/pool_str.rs @@ -0,0 +1,88 @@ + +use super::pool::{NodeId, Pool, NODE_BYTES}; +use super::shallow_clone::ShallowClone; +use libc::{c_void}; +use std::marker::PhantomData; +use std::mem::size_of; + + +/// A string containing at most 2^32 pool-allocated bytes. +#[derive(Debug, Copy, Clone)] +pub struct PoolStr { + first_node_id: NodeId<()>, + len: u32, +} + +#[test] +fn pool_str_size() { + assert_eq!(size_of::(), 8); +} + +impl PoolStr { + pub fn new(string: &str, pool: &mut Pool) -> Self { + debug_assert!(string.len() <= u32::MAX as usize); + + let chars_per_node = NODE_BYTES / size_of::(); + + let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32; + + if number_of_nodes > 0 { + let first_node_id = pool.reserve(number_of_nodes); + let index = first_node_id.index as isize; + let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void; + + unsafe { + libc::memcpy( + next_node_ptr, + string.as_ptr() as *const c_void, + string.len(), + ); + } + + PoolStr { + first_node_id, + len: string.len() as u32, + } + } else { + PoolStr { + first_node_id: NodeId { + index: 0, + _phantom: PhantomData::default(), + }, + len: 0, + } + } + } + + pub fn as_str(&self, pool: &Pool) -> &str { + unsafe { + let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8; + + let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize); + + std::str::from_utf8_unchecked(&node_slice[0..self.len as usize]) + } + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, pool: &Pool) -> usize { + let contents = self.as_str(pool); + + contents.len() + } + + pub fn is_empty(&self, pool: &Pool) -> bool { + self.len(pool) == 0 + } +} + +impl ShallowClone for PoolStr { + fn shallow_clone(&self) -> Self { + // Question: should this fully clone, or is a shallow copy + // (and the aliasing it entails) OK? + Self { + first_node_id: self.first_node_id, + len: self.len, + } + } +} \ No newline at end of file diff --git a/ast/src/pool/pool_vec.rs b/ast/src/pool/pool_vec.rs new file mode 100644 index 0000000000..3da278e485 --- /dev/null +++ b/ast/src/pool/pool_vec.rs @@ -0,0 +1,324 @@ + +use super::pool::{NodeId, Pool, NODE_BYTES}; +use super::shallow_clone::ShallowClone; +use libc::{c_void}; +use std::marker::PhantomData; +use std::mem::size_of; +use std::any::type_name; +use std::cmp::Ordering; + +/// An array of at most 2^32 pool-allocated nodes. +#[derive(Debug)] +pub struct PoolVec { + first_node_id: NodeId, + len: u32, +} + +#[test] +fn pool_vec_size() { + assert_eq!(size_of::>(), 8); +} + +impl<'a, T: 'a + Sized> PoolVec { + pub fn empty(pool: &mut Pool) -> Self { + Self::new(std::iter::empty(), pool) + } + + pub fn with_capacity(len: u32, pool: &mut Pool) -> Self { + debug_assert!( + size_of::() <= NODE_BYTES, + "{} has a size of {}", + type_name::(), + size_of::() + ); + + if len == 0 { + Self::empty(pool) + } else { + let first_node_id = pool.reserve(len); + + PoolVec { first_node_id, len } + } + } + + pub fn len(&self) -> usize { + self.len as usize + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn new>(nodes: I, pool: &mut Pool) -> Self { + debug_assert!(nodes.len() <= u32::MAX as usize); + debug_assert!(size_of::() <= NODE_BYTES); + + let len = nodes.len() as u32; + + if len > 0 { + let first_node_id = pool.reserve(len); + let index = first_node_id.index as isize; + let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T; + + for (indx_inc, node) in nodes.enumerate() { + unsafe { + *next_node_ptr = node; + + next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T; + } + } + + PoolVec { first_node_id, len } + } else { + PoolVec { + first_node_id: NodeId { + index: 0, + _phantom: PhantomData::default(), + }, + len: 0, + } + } + } + + pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator { + self.pool_list_iter(pool) + } + + pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator { + self.pool_list_iter_mut(pool) + } + + pub fn iter_node_ids(&self) -> impl ExactSizeIterator> { + self.pool_list_iter_node_ids() + } + + /// Private version of into_iter which exposes the implementation detail + /// of PoolVecIter. We don't want that struct to be public, but we + /// actually do want to have this separate function for code reuse + /// in the iterator's next() method. + #[inline(always)] + fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> { + PoolVecIter { + pool, + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + #[inline(always)] + fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> { + PoolVecIterMut { + pool, + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + #[inline(always)] + fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds { + PoolVecIterNodeIds { + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + pub fn free(self, pool: &'a mut Pool) { + // zero out the memory + unsafe { + let index = self.first_node_id.index as isize; + let node_ptr = pool.nodes.offset(index) as *mut c_void; + let bytes = self.len as usize * NODE_BYTES; + + libc::memset(node_ptr, 0, bytes); + } + + // TODO insert it into the pool's free list + } +} + +impl ShallowClone for PoolVec { + fn shallow_clone(&self) -> Self { + // Question: should this fully clone, or is a shallow copy + // (and the aliasing it entails) OK? + Self { + first_node_id: self.first_node_id, + len: self.len, + } + } +} + +struct PoolVecIter<'a, T> { + pool: &'a Pool, + current_node_id: NodeId, + len_remaining: u32, +} + +impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T> +where + T: 'a, +{ + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl<'a, T> Iterator for PoolVecIter<'a, T> +where + T: 'a, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(unsafe { &*node_ptr }) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; + + Some(unsafe { &*node_ptr }) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +struct PoolVecIterMut<'a, T> { + pool: &'a Pool, + current_node_id: NodeId, + len_remaining: u32, +} + +impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T> +where + T: 'a, +{ + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl<'a, T> Iterator for PoolVecIterMut<'a, T> +where + T: 'a, +{ + type Item = &'a mut T; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(unsafe { &mut *node_ptr }) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; + + Some(unsafe { &mut *node_ptr }) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +struct PoolVecIterNodeIds { + current_node_id: NodeId, + len_remaining: u32, +} + +impl ExactSizeIterator for PoolVecIterNodeIds { + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl Iterator for PoolVecIterNodeIds { + type Item = NodeId; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let current = self.current_node_id; + let index = current.index; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(current) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + Some(self.current_node_id) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +#[test] +fn pool_vec_iter_test() { + let expected_vec: Vec = vec![2, 4, 8, 16]; + + let mut test_pool = Pool::with_capacity(1024); + let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool); + + let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); + + assert_eq!(current_vec, expected_vec); +} \ No newline at end of file diff --git a/ast/src/pool/shallow_clone.rs b/ast/src/pool/shallow_clone.rs new file mode 100644 index 0000000000..cf7c24f8f0 --- /dev/null +++ b/ast/src/pool/shallow_clone.rs @@ -0,0 +1,33 @@ + +use roc_can::expected::Expected; +use roc_can::expected::PExpected; + +/// Clones the outer node, but does not clone any nodeids +pub trait ShallowClone { + fn shallow_clone(&self) -> Self; +} + +impl ShallowClone for Expected { + fn shallow_clone(&self) -> Self { + use Expected::*; + + match self { + NoExpectation(t) => NoExpectation(t.shallow_clone()), + ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), + FromAnnotation(loc_pat, n, source, t) => { + FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone()) + } + } + } +} + +impl ShallowClone for PExpected { + fn shallow_clone(&self) -> Self { + use PExpected::*; + + match self { + NoExpectation(t) => NoExpectation(t.shallow_clone()), + ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), + } + } +} diff --git a/ast/src/roc_file.rs b/ast/src/roc_file.rs new file mode 100644 index 0000000000..5379e9097a --- /dev/null +++ b/ast/src/roc_file.rs @@ -0,0 +1,133 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; +use roc_fmt::def::fmt_def; +use roc_fmt::module::fmt_module; +use roc_parse::ast::{Def, Module}; +use roc_parse::module::module_defs; +use roc_parse::parser; +use roc_parse::parser::{Parser, SyntaxError}; +use roc_region::all::Located; +use std::ffi::OsStr; +use std::path::Path; +use std::{fs, io}; + +#[derive(Debug)] +pub struct File<'a> { + path: &'a Path, + module_header: Module<'a>, + content: Vec<'a, Located>>, +} + +#[derive(Debug)] +pub enum ReadError<'a> { + Read(std::io::Error), + ParseDefs(SyntaxError<'a>), + ParseHeader(SyntaxError<'a>), + DoesntHaveRocExtension, +} + +impl<'a> File<'a> { + pub fn read(path: &'a Path, arena: &'a Bump) -> Result, ReadError<'a>> { + if path.extension() != Some(OsStr::new("roc")) { + return Err(ReadError::DoesntHaveRocExtension); + } + + let bytes = fs::read(path).map_err(ReadError::Read)?; + + let allocation = arena.alloc(bytes); + + let module_parse_state = parser::State::new(allocation); + let parsed_module = roc_parse::module::parse_header(arena, module_parse_state); + + match parsed_module { + Ok((module, state)) => { + let parsed_defs = module_defs().parse(arena, state); + + match parsed_defs { + Ok((_, defs, _)) => Ok(File { + path, + module_header: module, + content: defs, + }), + Err((_, error, _)) => Err(ReadError::ParseDefs(error)), + } + } + Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))), + } + } + + pub fn fmt(&self) -> String { + let arena = Bump::new(); + let mut formatted_file = String::new(); + + let mut module_header_buf = bumpalo::collections::String::new_in(&arena); + fmt_module(&mut module_header_buf, &self.module_header); + + formatted_file.push_str(module_header_buf.as_str()); + + for def in &self.content { + let mut def_buf = bumpalo::collections::String::new_in(&arena); + + fmt_def(&mut def_buf, &def.value, 0); + + formatted_file.push_str(def_buf.as_str()); + } + + formatted_file + } + + pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> { + let formatted_file = self.fmt(); + + fs::write(write_path, formatted_file) + } + + pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> { + self.fmt_then_write_to( + self.path + .with_file_name(new_name) + .with_extension("roc") + .as_path(), + ) + } + + pub fn fmt_then_write(&self) -> io::Result<()> { + self.fmt_then_write_to(self.path) + } +} + +#[cfg(test)] +mod test_file { + use crate::lang::roc_file; + use bumpalo::Bump; + use std::path::Path; + + #[test] + fn read_and_fmt_simple_roc_module() { + let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc"); + + let arena = Bump::new(); + + let file = roc_file::File::read(simple_module_path, &arena) + .expect("Could not read SimpleUnformatted.roc in test_file test"); + + assert_eq!( + file.fmt(), + indoc!( + r#" + interface Simple + exposes [ + v, x + ] + imports [] + + v : Str + + v = "Value!" + + x : Int + x = 4"# + ) + ); + } +} diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs new file mode 100644 index 0000000000..68b29c7635 --- /dev/null +++ b/ast/src/solve_type.rs @@ -0,0 +1,1752 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +use crate::lang::constrain::Constraint::{self, *}; +use crate::lang::pool::{Pool, PoolVec, ShallowClone}; +use crate::lang::types::Type2; +use bumpalo::Bump; +use roc_can::expected::{Expected, PExpected}; +use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; +use roc_module::ident::TagName; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; +use roc_types::solved_types::Solved; +use roc_types::subs::{ + AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, + SubsSlice, UnionTags, Variable, VariableSubsSlice, +}; +use roc_types::types::{ + gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField, +}; +use roc_unify::unify::unify; +use roc_unify::unify::Unified::*; + +// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed +// https://github.com/elm/compiler +// Thank you, Evan! + +// A lot of energy was put into making type inference fast. That means it's pretty intimidating. +// +// Fundamentally, type inference assigns very general types based on syntax, and then tries to +// make all the pieces fit together. For instance when writing +// +// > f x +// +// We know that `f` is a function, and thus must have some type `a -> b`. +// `x` is just a variable, that gets the type `c` +// +// Next comes constraint generation. For `f x` to be well-typed, +// it must be the case that `c = a`, So a constraint `Eq(c, a)` is generated. +// But `Eq` is a bit special: `c` does not need to equal `a` exactly, but they need to be equivalent. +// This allows for instance the use of aliases. `c` could be an alias, and so looks different from +// `a`, but they still represent the same type. +// +// Then we get to solving, which happens in this file. +// +// When we hit an `Eq` constraint, then we check whether the two involved types are in fact +// equivalent using unification, and when they are, we can substitute one for the other. +// +// When all constraints are processed, and no unification errors have occurred, then the program +// is type-correct. Otherwise the errors are reported. +// +// Now, coming back to efficiency, this type checker uses *ranks* to optimize +// The rank tracks the number of let-bindings a variable is "under". Top-level definitions +// have rank 1. A let in a top-level definition gets rank 2, and so on. +// +// This has to do with generalization of type variables. This is described here +// +// http://okmij.org/ftp/ML/generalization.html#levels +// +// The problem is that when doing inference naively, this program would fail to typecheck +// +// f = +// id = \x -> x +// +// { a: id 1, b: id "foo" } +// +// Because `id` is applied to an integer, the type `Int -> Int` is inferred, which then gives a +// type error for `id "foo"`. +// +// Thus instead the inferred type for `id` is generalized (see the `generalize` function) to `a -> a`. +// Ranks are used to limit the number of type variables considered for generalization. Only those inside +// of the let (so those used in inferring the type of `\x -> x`) are considered. + +#[derive(PartialEq, Debug, Clone)] +pub enum TypeError { + BadExpr(Region, Category, ErrorType, Expected), + BadPattern(Region, PatternCategory, ErrorType, PExpected), + CircularType(Region, Symbol, ErrorType), + BadType(roc_types::types::Problem), + UnexposedLookup(Symbol), +} + +#[derive(Clone, Debug, Default)] +pub struct Env { + pub vars_by_symbol: MutMap, + pub aliases: MutMap, +} + +const DEFAULT_POOLS: usize = 8; + +#[derive(Clone, Debug)] +struct Pools(Vec>); + +impl Default for Pools { + fn default() -> Self { + Pools::new(DEFAULT_POOLS) + } +} + +impl Pools { + pub fn new(num_pools: usize) -> Self { + Pools(vec![Vec::new(); num_pools]) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { + self.0 + .get_mut(rank.into_usize()) + .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) + } + + pub fn get(&self, rank: Rank) -> &Vec { + self.0 + .get(rank.into_usize()) + .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) + } + + pub fn iter(&self) -> std::slice::Iter<'_, Vec> { + self.0.iter() + } + + pub fn split_last(&self) -> (&Vec, &[Vec]) { + self.0 + .split_last() + .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")) + } + + pub fn extend_to(&mut self, n: usize) { + for _ in self.len()..n { + self.0.push(Vec::new()); + } + } +} + +#[derive(Clone)] +struct State { + env: Env, + mark: Mark, +} + +pub fn run<'a>( + arena: &'a Bump, + mempool: &mut Pool, + env: &Env, + problems: &mut Vec, + mut subs: Subs, + constraint: &Constraint, +) -> (Solved, Env) { + let env = run_in_place(arena, mempool, env, problems, &mut subs, constraint); + + (Solved(subs), env) +} + +/// Modify an existing subs in-place instead +pub fn run_in_place<'a>( + arena: &'a Bump, + mempool: &mut Pool, + env: &Env, + problems: &mut Vec, + subs: &mut Subs, + constraint: &Constraint, +) -> Env { + let mut pools = Pools::default(); + let state = State { + env: env.clone(), + mark: Mark::NONE.next(), + }; + let rank = Rank::toplevel(); + let state = solve( + arena, + mempool, + env, + state, + rank, + &mut pools, + problems, + &mut MutMap::default(), + subs, + constraint, + ); + + state.env +} + +#[allow(clippy::too_many_arguments)] +fn solve<'a>( + arena: &'a Bump, + mempool: &mut Pool, + env: &Env, + state: State, + rank: Rank, + pools: &mut Pools, + problems: &mut Vec, + cached_aliases: &mut MutMap, + subs: &mut Subs, + constraint: &Constraint, +) -> State { + match constraint { + True => state, + // SaveTheEnvironment => { + // // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment + // let mut copy = state; + // + // copy.env = env.clone(); + // + // copy + // } + Eq(typ, expectation, category, region) => { + let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); + let expected = type_to_var( + arena, + mempool, + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + category.clone(), + actual_type, + expectation.replace_ref(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + // Store(source, target, _filename, _linenr) => { + // // a special version of Eq that is used to store types in the AST. + // // IT DOES NOT REPORT ERRORS! + // let actual = type_to_var(subs, rank, pools, cached_aliases, source); + // let target = *target; + // + // match unify(subs, actual, target) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // Failure(vars, _actual_type, _expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // BadType(vars, _problem) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // } + // } + Lookup(symbol, expectation, region) => { + match env.vars_by_symbol.get(&symbol) { + Some(var) => { + // Deep copy the vars associated with this symbol before unifying them. + // Otherwise, suppose we have this: + // + // identity = \a -> a + // + // x = identity 5 + // + // When we call (identity 5), it's important that we not unify + // on identity's original vars. If we do, the type of `identity` will be + // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; + // the type of `identity` is more general than that! + // + // Instead, we want to unify on a *copy* of its vars. If the copy unifies + // successfully (in this case, to `Int -> Int`), we can use that to + // infer the type of this lookup (in this case, `Int`) without ever + // having mutated the original. + // + // If this Lookup is targeting a value in another module, + // then we copy from that module's Subs into our own. If the value + // is being looked up in this module, then we use our Subs as both + // the source and destination. + let actual = deep_copy_var(subs, rank, pools, *var); + + let expected = type_to_var( + arena, + mempool, + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + Category::Lookup(*symbol), + actual_type, + expectation.shallow_clone().replace(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + None => { + problems.push(TypeError::UnexposedLookup(*symbol)); + + state + } + } + } + And(sub_constraints) => { + let mut state = state; + + for sub_constraint in sub_constraints.iter() { + state = solve( + arena, + mempool, + env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + sub_constraint, + ); + } + + state + } + Pattern(region, category, typ, expectation) => { + let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); + let expected = type_to_var( + arena, + mempool, + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadPattern( + *region, + category.clone(), + actual_type, + expectation.shallow_clone().replace(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + Let(let_con) => { + match &let_con.ret_constraint { + True if let_con.rigid_vars.is_empty() => { + introduce(subs, rank, pools, &let_con.flex_vars); + + // If the return expression is guaranteed to solve, + // solve the assignments themselves and move on. + solve( + arena, + mempool, + &env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ) + } + ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { + let state = solve( + arena, + mempool, + env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ); + + // Add a variable for each def to new_vars_by_env. + let mut local_def_vars = BumpMap::new_in(arena); + + for (symbol, typ) in let_con.def_types.iter() { + let var = + type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); + + // TODO: region should come from typ + local_def_vars.insert( + *symbol, + Located { + value: var, + region: Region::zero(), + }, + ); + } + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + if !new_env.vars_by_symbol.contains_key(&symbol) { + new_env.vars_by_symbol.insert(*symbol, loc_var.value); + } + } + + let new_state = solve( + arena, + mempool, + &new_env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + ret_con, + ); + + for (symbol, loc_var) in local_def_vars { + check_for_infinite_type(subs, problems, symbol, loc_var); + } + + new_state + } + ret_con => { + let rigid_vars = &let_con.rigid_vars; + let flex_vars = &let_con.flex_vars; + + // work in the next pool to localize header + let next_rank = rank.next(); + + // introduce variables + for &var in rigid_vars.iter().chain(flex_vars.iter()) { + subs.set_rank(var, next_rank); + } + + // determine the next pool + let next_pools; + if next_rank.into_usize() < pools.len() { + next_pools = pools + } else { + // we should be off by one at this point + debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); + pools.extend_to(next_rank.into_usize()); + next_pools = pools; + } + + let pool: &mut Vec = next_pools.get_mut(next_rank); + + // Replace the contents of this pool with rigid_vars and flex_vars + pool.clear(); + pool.reserve(rigid_vars.len() + flex_vars.len()); + pool.extend(rigid_vars.iter()); + pool.extend(flex_vars.iter()); + + // run solver in next pool + + // Add a variable for each def to local_def_vars. + let mut local_def_vars = BumpMap::new_in(arena); + + for (symbol, typ) in let_con.def_types.iter() { + let var = type_to_var( + arena, + mempool, + subs, + next_rank, + next_pools, + cached_aliases, + typ, + ); + + // TODO: region should come from type + local_def_vars.insert( + *symbol, + Located { + value: var, + region: Region::zero(), + }, + ); + } + + // Solve the assignments' constraints first. + let State { + env: saved_env, + mark, + } = solve( + arena, + mempool, + &env, + state, + next_rank, + next_pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ); + + let young_mark = mark; + let visit_mark = young_mark.next(); + let final_mark = visit_mark.next(); + + debug_assert_eq!( + { + let offenders = next_pools + .get(next_rank) + .iter() + .filter(|var| { + let current_rank = + subs.get_rank(roc_types::subs::Variable::clone(var)); + + current_rank.into_usize() > next_rank.into_usize() + }) + .collect::>(); + + let result = offenders.len(); + + if result > 0 { + dbg!(&subs, &offenders, &let_con.def_types); + } + + result + }, + 0 + ); + + // pop pool + generalize(subs, young_mark, visit_mark, next_rank, next_pools); + + next_pools.get_mut(next_rank).clear(); + + // check that things went well + debug_assert!({ + // NOTE the `subs.redundant` check is added for the uniqueness + // inference, and does not come from elm. It's unclear whether this is + // a bug with uniqueness inference (something is redundant that + // shouldn't be) or that it just never came up in elm. + let failing: Vec<_> = rigid_vars + .iter() + .filter(|&var| { + !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE + }) + .collect(); + + if !failing.is_empty() { + println!("Rigids {:?}", &rigid_vars); + println!("Failing {:?}", failing); + } + + failing.is_empty() + }); + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + // when there are duplicates, keep the one from `env` + if !new_env.vars_by_symbol.contains_key(&symbol) { + new_env.vars_by_symbol.insert(*symbol, loc_var.value); + } + } + + // Note that this vars_by_symbol is the one returned by the + // previous call to solve() + let temp_state = State { + env: saved_env, + mark: final_mark, + }; + + // Now solve the body, using the new vars_by_symbol which includes + // the assignments' name-to-variable mappings. + let new_state = solve( + arena, + mempool, + &new_env, + temp_state, + rank, + next_pools, + problems, + cached_aliases, + subs, + &ret_con, + ); + + for (symbol, loc_var) in local_def_vars { + check_for_infinite_type(subs, problems, symbol, loc_var); + } + + new_state + } + } + } // _ => todo!("implement {:?}", constraint), + } +} + +fn type_to_var<'a>( + arena: &'a Bump, + mempool: &mut Pool, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + typ: &Type2, +) -> Variable { + type_to_variable(arena, mempool, subs, rank, pools, cached, typ) +} + +/// Abusing existing functions for our purposes +/// this is to put a solved type back into subs +pub fn insert_type_into_subs<'a>( + arena: &'a Bump, + mempool: &mut Pool, + subs: &mut Subs, + typ: &Type2, +) -> Variable { + let rank = Rank::NONE; + let mut pools = Pools::default(); + let mut cached = MutMap::default(); + + type_to_variable(arena, mempool, subs, rank, &mut pools, &mut cached, typ) +} + +fn type_to_variable<'a>( + arena: &'a Bump, + mempool: &Pool, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + typ: &Type2, +) -> Variable { + use Type2::*; + + match typ { + Variable(var) => *var, + Apply(symbol, args) => { + let mut arg_vars = Vec::with_capacity(args.len()); + for var_id in args.iter_node_ids() { + let arg = mempool.get(var_id); + arg_vars.push(type_to_variable( + arena, mempool, subs, rank, pools, cached, arg, + )) + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, arg_vars); + let flat_type = FlatType::Apply(*symbol, arg_vars); + let content = Content::Structure(flat_type); + + register(subs, rank, pools, content) + } + + EmptyRec => roc_types::subs::Variable::EMPTY_RECORD, + EmptyTagUnion => roc_types::subs::Variable::EMPTY_TAG_UNION, + + Record(fields, ext_id) => { + let mut field_vars = Vec::new(); + + for node_id in fields.iter_node_ids() { + use RecordField::*; + + let (field, field_type) = mempool.get(node_id); + + let field_var = match field_type { + Required(type_id) => Required(type_to_variable( + arena, + mempool, + subs, + rank, + pools, + cached, + mempool.get(*type_id), + )), + Optional(type_id) => Optional(type_to_variable( + arena, + mempool, + subs, + rank, + pools, + cached, + mempool.get(*type_id), + )), + Demanded(type_id) => Demanded(type_to_variable( + arena, + mempool, + subs, + rank, + pools, + cached, + mempool.get(*type_id), + )), + }; + + field_vars.push((field.as_str(mempool).into(), field_var)); + } + + let ext = mempool.get(*ext_id); + let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); + + let (it, new_ext_var) = + gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); + + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + field_vars.extend(it); + field_vars.sort_unstable_by(RecordFields::compare); + + let record_fields = RecordFields::insert_into_subs(subs, field_vars); + + let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); + + register(subs, rank, pools, content) + } + + Alias(symbol, args, alias_type_id) => { + // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! + // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) + // different variables (once for each occurrence). The recursion restriction is required + // for uniqueness types only: recursive aliases "introduce" an unbound uniqueness + // attribute in the body, when + // + // Peano : [ S Peano, Z ] + // + // becomes + // + // Peano : [ S (Attr u Peano), Z ] + // + // This `u` variable can be different between lists, so giving just one variable to + // this type is incorrect. + // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable + + let alias_type = mempool.get(*alias_type_id); + let is_recursive = false; // alias_type.is_recursive(); + let no_args = args.is_empty(); + /* + if no_args && !is_recursive { + if let Some(var) = cached.get(symbol) { + return *var; + } + } + */ + + let mut arg_vars = Vec::with_capacity(args.len()); + let mut new_aliases = BumpMap::new_in(arena); + + for (arg, arg_type_id) in args.iter(mempool) { + let arg_type = mempool.get(*arg_type_id); + + let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); + + let arg_str = arg.as_str(mempool); + + arg_vars.push((roc_module::ident::Lowercase::from(arg_str), arg_var)); + new_aliases.insert(arg_str, arg_var); + } + + let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []); + + let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type); + let content = Content::Alias(*symbol, arg_vars, alias_var); + + let result = register(subs, rank, pools, content); + + if no_args && !is_recursive { + // cached.insert(*symbol, result); + } + + result + } + TagUnion(tags, ext_id) => { + let ext = mempool.get(*ext_id); + + let (union_tags, ext) = + type_to_union_tags(arena, mempool, subs, rank, pools, cached, tags, ext); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + register(subs, rank, pools, content) + } + // This case is important for the rank of boolean variables + Function(arg_vars, closure_type_id, ret_type_id) => { + let closure_type = mempool.get(*closure_type_id); + let ret_type = mempool.get(*ret_type_id); + + let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); + + for var_id in arg_vars.iter_node_ids() { + let arg = mempool.get(var_id); + let var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg); + new_arg_vars.push(var) + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + let ret_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ret_type); + let closure_var = + type_to_variable(arena, mempool, subs, rank, pools, cached, closure_type); + + let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); + + register(subs, rank, pools, content) + } + other => todo!("not implemented {:?}", &other), + // RecursiveTagUnion(rec_var, tags, ext) => { + // let mut tag_vars = MutMap::default(); + // + // for (tag, tag_argument_types) in tags { + // let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); + // + // for arg_type in tag_argument_types { + // tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); + // } + // + // tag_vars.insert(tag.clone(), tag_argument_vars); + // } + // + // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + // let mut ext_tag_vec = Vec::new(); + // let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( + // subs, + // temp_ext_var, + // &mut ext_tag_vec, + // ) { + // Ok(()) => Variable::EMPTY_TAG_UNION, + // Err((new, _)) => new, + // }; + // tag_vars.extend(ext_tag_vec.into_iter()); + // + // let content = + // Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var)); + // + // let tag_union_var = register(subs, rank, pools, content); + // + // subs.set_content( + // *rec_var, + // Content::RecursionVar { + // opt_name: None, + // structure: tag_union_var, + // }, + // ); + // + // tag_union_var + // } + // HostExposedAlias { + // name: symbol, + // arguments: args, + // actual: alias_type, + // actual_var, + // .. + // } => { + // let mut arg_vars = Vec::with_capacity(args.len()); + // let mut new_aliases = ImMap::default(); + // + // for (arg, arg_type) in args { + // let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + // + // arg_vars.push((arg.clone(), arg_var)); + // new_aliases.insert(arg.clone(), arg_var); + // } + // + // let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + // + // // unify the actual_var with the result var + // // this can be used to access the type of the actual_var + // // to determine its layout later + // // subs.set_content(*actual_var, descriptor.content); + // + // //subs.set(*actual_var, descriptor.clone()); + // let content = Content::Alias(*symbol, arg_vars, alias_var); + // + // let result = register(subs, rank, pools, content); + // + // // We only want to unify the actual_var with the alias once + // // if it's already redirected (and therefore, redundant) + // // don't do it again + // if !subs.redundant(*actual_var) { + // let descriptor = subs.get(result); + // subs.union(result, *actual_var, descriptor); + // } + // + // result + // } + // Erroneous(problem) => { + // let content = Content::Structure(FlatType::Erroneous(problem.clone())); + // + // register(subs, rank, pools, content) + // } + } +} + +fn type_to_union_tags<'a>( + arena: &'a Bump, + mempool: &Pool, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + tags: &PoolVec<(TagName, PoolVec)>, + ext: &Type2, +) -> (UnionTags, Variable) { + let mut tag_vars = Vec::with_capacity(tags.len()); + + let mut tag_argument_vars = Vec::new(); + for id in tags.iter_node_ids() { + let (tag, tag_argument_types) = mempool.get(id); + for arg_id in tag_argument_types.iter_node_ids() { + let arg_type = mempool.get(arg_id); + let new_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); + tag_argument_vars.push(new_var); + } + + let new_slice = VariableSubsSlice::insert_into_subs(subs, tag_argument_vars.drain(..)); + + tag_vars.push((tag.clone(), new_slice)); + } + + let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); + + let ext = { + let (it, ext) = + roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); + + tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); + tag_vars.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + + // deduplicate, keeping the right-most occurrence of a tag name + let mut i = 0; + + while i < tag_vars.len() { + match (tag_vars.get(i), tag_vars.get(i + 1)) { + (Some((t1, _)), Some((t2, _))) => { + if t1 == t2 { + tag_vars.remove(i); + } else { + i += 1; + } + } + _ => break, + } + } + + ext + }; + + (UnionTags::insert_slices_into_subs(subs, tag_vars), ext) +} + +fn check_for_infinite_type( + subs: &mut Subs, + problems: &mut Vec, + symbol: Symbol, + loc_var: Located, +) { + let var = loc_var.value; + + while let Err((recursive, _chain)) = subs.occurs(var) { + let description = subs.get(recursive); + let content = description.content; + + // try to make a tag union recursive, see if that helps + match content { + Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + let rec_var = subs.fresh_unnamed_flex_var(); + subs.set_rank(rec_var, description.rank); + subs.set_content( + rec_var, + Content::RecursionVar { + opt_name: None, + structure: recursive, + }, + ); + + let mut new_tags = Vec::with_capacity(tags.len()); + + for (name_index, slice_index) in tags.iter_all() { + let slice = subs[slice_index]; + + let mut new_vars = Vec::new(); + for var_index in slice { + let var = subs[var_index]; + new_vars.push(subs.explicit_substitute(recursive, rec_var, var)); + } + + new_tags.push((subs[name_index].clone(), new_vars)); + } + + let new_ext_var = subs.explicit_substitute(recursive, rec_var, ext_var); + + let new_tags = UnionTags::insert_into_subs(subs, new_tags); + + let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext_var); + + subs.set_content(recursive, Content::Structure(flat_type)); + } + + _other => circular_error(subs, problems, symbol, &loc_var), + } + } +} + +fn circular_error( + subs: &mut Subs, + problems: &mut Vec, + symbol: Symbol, + loc_var: &Located, +) { + let var = loc_var.value; + let (error_type, _) = subs.var_to_error_type(var); + let problem = TypeError::CircularType(loc_var.region, symbol, error_type); + + subs.set_content(var, Content::Error); + + problems.push(problem); +} + +fn generalize( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + young_rank: Rank, + pools: &mut Pools, +) { + let young_vars = pools.get(young_rank); + let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars); + + // Get the ranks right for each entry. + // Start at low ranks so we only have to pass over the information once. + for (index, table) in rank_table.iter().enumerate() { + for &var in table.iter() { + adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var); + } + } + + let (last_pool, all_but_last_pool) = rank_table.split_last(); + + // For variables that have rank lowerer than young_rank, register them in + // the appropriate old pool if they are not redundant. + for vars in all_but_last_pool { + for &var in vars { + if !subs.redundant(var) { + let rank = subs.get_rank(var); + + pools.get_mut(rank).push(var); + } + } + } + + // For variables with rank young_rank, if rank < young_rank: register in old pool, + // otherwise generalize + for &var in last_pool { + if !subs.redundant(var) { + let desc_rank = subs.get_rank(var); + + if desc_rank < young_rank { + pools.get_mut(desc_rank).push(var); + } else { + subs.set_rank(var, Rank::NONE); + } + } + } +} + +fn pool_to_rank_table( + subs: &mut Subs, + young_mark: Mark, + young_rank: Rank, + young_vars: &[Variable], +) -> Pools { + let mut pools = Pools::new(young_rank.into_usize() + 1); + + // Sort the variables into buckets by rank. + for &var in young_vars.iter() { + let rank = subs.get_rank(var); + subs.set_mark(var, young_mark); + + debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); + pools.get_mut(rank).push(var); + } + + pools +} + +/// Adjust variable ranks such that ranks never increase as you move deeper. +/// This way the outermost rank is representative of the entire structure. +fn adjust_rank( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + group_rank: Rank, + var: Variable, +) -> Rank { + let desc = subs.get(var); + + if desc.mark == young_mark { + let Descriptor { + content, + rank: _, + mark: _, + copy, + } = desc; + + // Mark the variable as visited before adjusting content, as it may be cyclic. + subs.set_mark(var, visit_mark); + + let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content); + + subs.set( + var, + Descriptor { + content, + rank: max_rank, + mark: visit_mark, + copy, + }, + ); + + max_rank + } else if desc.mark == visit_mark { + // nothing changes + desc.rank + } else { + let mut desc = desc; + + let min_rank = group_rank.min(desc.rank); + + // TODO from elm-compiler: how can min_rank ever be group_rank? + desc.rank = min_rank; + desc.mark = visit_mark; + + subs.set(var, desc); + + min_rank + } +} + +fn adjust_rank_content( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + group_rank: Rank, + content: &Content, +) -> Rank { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + match content { + FlexVar(_) | RigidVar(_) | Error => group_rank, + + RecursionVar { .. } => group_rank, + + Structure(flat_type) => { + match flat_type { + Apply(_, args) => { + let mut rank = Rank::toplevel(); + + for var_index in args.into_iter() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + rank + } + + Func(arg_vars, closure_var, ret_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ret_var); + + // TODO investigate further. + // + // My theory is that because the closure_var contains variables already + // contained in the signature only, it does not need to be part of the rank + // calculuation + if true { + rank = rank.max(adjust_rank( + subs, + young_mark, + visit_mark, + group_rank, + *closure_var, + )); + } + + for index in arg_vars.into_iter() { + let var = subs[index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + rank + } + + EmptyRecord => { + // from elm-compiler: THEORY: an empty record never needs to get generalized + Rank::toplevel() + } + + EmptyTagUnion => Rank::toplevel(), + + Record(fields, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for index in fields.iter_variables() { + let var = subs[index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + rank + } + + TagUnion(tags, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + } + + rank + } + + FunctionOrTagUnion(_, _, ext_var) => { + adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var) + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + } + + // THEORY: the recursion var has the same rank as the tag union itself + // all types it uses are also in the tags already, so it cannot influence the + // rank + debug_assert!( + rank >= adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var) + ); + + rank + } + + Erroneous(_) => group_rank, + } + } + + Alias(_, args, real_var) => { + let mut rank = Rank::toplevel(); + + for var_index in args.variables() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() + // this theory is not true in Roc! aliases of function types capture the closure var + rank = rank.max(adjust_rank( + subs, young_mark, visit_mark, group_rank, *real_var, + )); + + rank + } + } +} + +/// Introduce some variables to Pools at the given rank. +/// Also, set each of their ranks in Subs to be the given rank. +fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { + let pool: &mut Vec = pools.get_mut(rank); + + for &var in vars.iter() { + subs.set_rank(var, rank); + } + + pool.extend(vars); +} + +/// Function that converts rigids variables to flex variables +/// this is used during the monomorphization process +pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { + let rank = Rank::NONE; + let mut pools = Pools::default(); + + instantiate_rigids_help(subs, rank, &mut pools, var); +} + +fn instantiate_rigids_help( + subs: &mut Subs, + max_rank: Rank, + pools: &mut Pools, + var: Variable, +) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + let desc = subs.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + return copy; + } + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let content = desc.content; + let copy = var; + + pools.get_mut(max_rank).push(copy); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + subs.set( + var, + Descriptor { + content: content.clone(), + rank: desc.rank, + mark: Mark::NONE, + copy: copy.into(), + }, + ); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match content { + Structure(flat_type) => { + match flat_type { + Apply(_, args) => { + for var_index in args.into_iter() { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + Func(arg_vars, closure_var, ret_var) => { + instantiate_rigids_help(subs, max_rank, pools, ret_var); + instantiate_rigids_help(subs, max_rank, pools, closure_var); + + for index in arg_vars.into_iter() { + let var = subs[index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + EmptyRecord | EmptyTagUnion | Erroneous(_) => {} + + Record(fields, ext_var) => { + for index in fields.iter_variables() { + let var = subs[index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + + TagUnion(tags, ext_var) => { + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + + FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + instantiate_rigids_help(subs, max_rank, pools, rec_var); + + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + }; + } + + FlexVar(_) | Error => {} + + RecursionVar { structure, .. } => { + instantiate_rigids_help(subs, max_rank, pools, structure); + } + + RigidVar(name) => { + // what it's all about: convert the rigid var into a flex var + subs.set(copy, make_descriptor(FlexVar(Some(name)))); + } + + Alias(_, args, real_type_var) => { + for var_index in args.variables() { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + + instantiate_rigids_help(subs, max_rank, pools, real_type_var); + } + } + + var +} + +fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { + let copy = deep_copy_var_help(subs, rank, pools, var); + + subs.restore(var); + + copy +} + +fn deep_copy_var_help( + subs: &mut Subs, + max_rank: Rank, + pools: &mut Pools, + var: Variable, +) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + let desc = subs.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + return copy; + } else if desc.rank != Rank::NONE { + return var; + } + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let content = desc.content; + let copy = subs.fresh(make_descriptor(content.clone())); + + pools.get_mut(max_rank).push(copy); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + subs.set( + var, + Descriptor { + content: content.clone(), + rank: desc.rank, + mark: Mark::NONE, + copy: copy.into(), + }, + ); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match content { + Structure(flat_type) => { + let new_flat_type = match flat_type { + Apply(symbol, args) => { + let mut new_arg_vars = Vec::with_capacity(args.len()); + + for index in args.into_iter() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + new_arg_vars.push(copy_var); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + Apply(symbol, arg_vars) + } + + Func(arg_vars, closure_var, ret_var) => { + let new_ret_var = deep_copy_var_help(subs, max_rank, pools, ret_var); + let new_closure_var = deep_copy_var_help(subs, max_rank, pools, closure_var); + + let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); + + for index in arg_vars.into_iter() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + new_arg_vars.push(copy_var); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + Func(arg_vars, new_closure_var, new_ret_var) + } + + same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, + + Record(fields, ext_var) => { + let record_fields = { + let mut new_vars = Vec::with_capacity(fields.len()); + + for index in fields.iter_variables() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + + new_vars.push(copy_var); + } + + let field_names_start = subs.field_names.len() as u32; + let variables_start = subs.variables.len() as u32; + let field_types_start = subs.record_fields.len() as u32; + + let mut length = 0; + + for ((i1, _, i3), var) in fields.iter_all().zip(new_vars) { + let record_field = subs[i3].map(|_| var); + + subs.field_names.push(subs[i1].clone()); + subs.record_fields.push(record_field.map(|_| ())); + subs.variables.push(*record_field.as_inner()); + + length += 1; + } + + RecordFields { + length, + field_names_start, + variables_start, + field_types_start, + } + }; + + Record( + record_fields, + deep_copy_var_help(subs, max_rank, pools, ext_var), + ) + } + + TagUnion(tags, ext_var) => { + let mut new_variable_slices = Vec::with_capacity(tags.len()); + + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); + } + + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + TagUnion(union_tags, new_ext) + } + + FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( + tag_name, + symbol, + deep_copy_var_help(subs, max_rank, pools, ext_var), + ), + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut new_variable_slices = Vec::with_capacity(tags.len()); + + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); + } + + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); + FlatType::RecursiveTagUnion(new_rec_var, union_tags, new_ext) + } + }; + + subs.set(copy, make_descriptor(Structure(new_flat_type))); + + copy + } + + FlexVar(_) | Error => copy, + + RecursionVar { + opt_name, + structure, + } => { + let new_structure = deep_copy_var_help(subs, max_rank, pools, structure); + + subs.set( + copy, + make_descriptor(RecursionVar { + opt_name, + structure: new_structure, + }), + ); + + copy + } + + RigidVar(name) => { + subs.set(copy, make_descriptor(FlexVar(Some(name)))); + + copy + } + + Alias(symbol, mut args, real_type_var) => { + let mut new_args = Vec::with_capacity(args.variables().len()); + + for var_index in args.variables() { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_args.push(new_var); + } + + args.replace_variables(subs, new_args); + + let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); + let new_content = Alias(symbol, args, new_real_type_var); + + subs.set(copy, make_descriptor(new_content)); + + copy + } + } +} + +fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { + let var = subs.fresh(Descriptor { + content, + rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }); + + pools.get_mut(rank).push(var); + + var +} diff --git a/editor/Cargo.toml b/editor/Cargo.toml index f9207b2e38..40ebc820bd 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -5,7 +5,6 @@ authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" description = "An editor for Roc" -exclude = ["src/shaders/*.spv"] [dependencies] roc_collections = { path = "../compiler/collections" } diff --git a/editor/src/lang/pool.rs b/editor/src/lang/pool.rs index 2504cfcffc..ac0fd67206 100644 --- a/editor/src/lang/pool.rs +++ b/editor/src/lang/pool.rs @@ -26,29 +26,7 @@ pub const NODE_BYTES: usize = 32; // on typical systems where the compiler will be run. // // Nice things about this system include: -// * Allocating a new page is as simple as asking the OS for a memory page. -// * Since each node is 32B, each node's memory address will be a multiple of 16. -// * Thanks to the free lists and our consistent chunk sizes, we should -// end up with very little fragmentation. -// * Finding a slot for a given node should be very fast: see if the relevant -// free list has any openings; if not, try the next size up. -// -// Less nice things include: -// * This system makes it very hard to ever give a page back to the OS. -// We could try doing the Mesh Allocator strategy: whenever we allocate -// something, assign it to a random slot in the page, and then periodically -// try to merge two pages into one (by locking and remapping them in the OS) -// and then returning the redundant physical page back to the OS. This should -// work in theory, but is pretty complicated, and we'd need to schedule it. -// Keep in mind that we can't use the Mesh Allocator itself because it returns -// usize pointers, which would be too big for us to have 16B nodes. -// On the plus side, we could be okay with higher memory usage early on, -// and then later use the Mesh strategy to reduce long-running memory usage. -// -// With this system, we can allocate up to 4B nodes. If we wanted to keep -// a generational index in there, like https://crates.io/crates/sharded-slab -// does, we could use some of the 32 bits for that. For example, if we wanted -// to have a 5-bit generational index (supporting up to 32 generations), then +// * Allocating a new pagShallowCloneal index (supporting up to 32 generations), then // we would have 27 bits remaining, meaning we could only support at most // 134M nodes. Since the editor has a separate Pool for each module, is that // enough for any single module we'll encounter in practice? Probably, and From 43fc0f87ad2a63eecff77813716a5a4dd46ab839 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Sep 2021 15:14:34 +0200 Subject: [PATCH 02/12] fixed many imports after refactoring --- ast/src/canonicalize/canonicalize.rs | 12 +- ast/src/canonicalize/module.rs | 16 ++- ast/src/constrain.rs | 10 +- ast/src/lang/core/declaration.rs | 62 +++++++++ ast/src/lang/core/def/def_to_def2.rs | 2 +- ast/src/lang/core/expr/expr_to_expr2.rs | 11 +- ast/src/lang/core/mod.rs | 8 +- ast/src/lang/core/str.rs | 164 ++++++++++++++++++++++++ ast/src/lang/mod.rs | 4 +- 9 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 ast/src/lang/core/declaration.rs create mode 100644 ast/src/lang/core/str.rs diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index 1e74257bc5..c8bddb58fd 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -1,13 +1,13 @@ -use roc_can::{env::Env, expr::Output, scope::Scope}; + use roc_collections::all::MutMap; use roc_problem::can::{Problem}; use roc_region::all::{Located, Region}; use roc_types::{subs::Variable}; -use crate::{lang::core::{def::def::References, expr::expr2::{Expr2, ExprId, RecordField, WhenBranch}}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, RecordField, WhenBranch}, expr_to_expr2::to_expr2, output::Output}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; -enum CanonicalizeRecordProblem { +pub (crate) enum CanonicalizeRecordProblem { InvalidOptionalValue { field_name: PoolStr, field_region: Region, @@ -20,7 +20,7 @@ enum FieldVar { OnlyVar(Variable), } -fn canonicalize_fields<'a>( +pub fn canonicalize_fields<'a>( env: &mut Env<'a>, scope: &mut Scope, fields: &'a [Located>>], @@ -167,7 +167,7 @@ fn canonicalize_field<'a>( } #[inline(always)] -fn canonicalize_when_branch<'a>( +pub (crate) fn canonicalize_when_branch<'a>( env: &mut Env<'a>, scope: &mut Scope, branch: &'a roc_parse::ast::WhenBranch<'a>, @@ -239,7 +239,7 @@ fn canonicalize_when_branch<'a>( ) } -fn canonicalize_lookup( +pub (crate) fn canonicalize_lookup( env: &mut Env<'_>, scope: &mut Scope, module_name: &str, diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalize/module.rs index 28d37e18cb..0c1ad7b089 100644 --- a/ast/src/canonicalize/module.rs +++ b/ast/src/canonicalize/module.rs @@ -4,7 +4,6 @@ #![allow(unused_variables)] use bumpalo::Bump; use roc_can::operator::desugar_def; -use roc_can::scope::Scope; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::Ident; use roc_module::ident::Lowercase; @@ -15,13 +14,20 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; -use crate::env::Env; -use crate::expr::output::Output; -use crate::lang::expr::output::Output; +use crate::lang::core::def::def::Def; +use crate::lang::core::def::def::{sort_can_defs, Declaration}; +use crate::lang::core::expr::expr2::Expr2; +use crate::lang::core::expr::output::Output; +use crate::lang::core::pattern::Pattern2; +use crate::lang::core::types::Alias; +use crate::lang::core::val_def::ValueDef; +use crate::lang::env::Env; +use crate::lang::scope::Scope; use crate::pool::pool::NodeId; use crate::pool::pool::Pool; use crate::pool::pool_vec::PoolVec; -use crate::types::Alias; +use crate::pool::shallow_clone::ShallowClone; +use crate::lang::core::def::def::canonicalize_defs; pub struct ModuleOutput { pub aliases: MutMap>, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 77d4edb0cd..b0e3f8fa5d 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1,13 +1,5 @@ use bumpalo::{collections::Vec as BumpVec, Bump}; -use crate::lang::{ - ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch}, - expr::Env, - pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, - pool::{Pool, PoolStr, PoolVec, ShallowClone}, - types::{Type2, TypeId}, -}; - use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; use roc_module::{ @@ -21,6 +13,8 @@ use roc_types::{ types::{Category, Reason}, }; +use crate::{lang::{core::{expr::expr2::{ClosureExtra, Expr2, ExprId, RecordField, WhenBranch}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; + #[derive(Debug)] pub enum Constraint<'a> { Eq(Type2, Expected, Category, Region), diff --git a/ast/src/lang/core/declaration.rs b/ast/src/lang/core/declaration.rs new file mode 100644 index 0000000000..207886bece --- /dev/null +++ b/ast/src/lang/core/declaration.rs @@ -0,0 +1,62 @@ +use roc_types::subs::VarStore; + +use crate::{lang::core::{def::def::Def, expr::expr2::Expr2}, pool::{pool::Pool, pool_vec::PoolVec}}; + +use super::def::def::Declaration; + +pub(crate) fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { + match decl { + Declaration::Declare(def) => match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Value(value_def) => { + let def_id = pool.add(value_def); + + let body_id = pool.add(ret); + + Expr2::LetValue { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + Def::Function(function_def) => { + let def_id = pool.add(function_def); + let body_id = pool.add(ret); + + Expr2::LetFunction { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + }, + Declaration::DeclareRec(defs) => { + let mut function_defs = vec![]; + + for def in defs { + match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Function(function_def) => function_defs.push(function_def), + Def::Value(_) => unreachable!(), + } + } + + let body_id = pool.add(ret); + + Expr2::LetRec { + defs: PoolVec::new(function_defs.into_iter(), pool), + body_var: var_store.fresh(), + body_id, + } + } + Declaration::InvalidCycle(_entries, _) => { + // TODO: replace with something from Expr2 + // Expr::RuntimeError(RuntimeError::CircularDef(entries)) + todo!() + } + Declaration::Builtin(_) => { + // Builtins should only be added to top-level decls, not to let-exprs! + unreachable!() + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs index 377eaa37ab..a30a65e3c6 100644 --- a/ast/src/lang/core/def/def_to_def2.rs +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -3,7 +3,7 @@ use bumpalo::collections::Vec as BumpVec; use roc_parse::pattern::PatternType; use roc_region::all::Region; -use crate::lang::{core::pattern::to_pattern2, env::Env, scope::Scope}; +use crate::lang::{core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, env::Env, scope::Scope}; use super::def2::Def2; diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 0dc093ee91..9a5a67f9a6 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -1,14 +1,21 @@ use bumpalo::Bump; +use roc_can::expr::Recursive; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::operator::desugar_expr; +use roc_collections::all::MutSet; use roc_parse::{ast::Expr, pattern::PatternType}; use roc_problem::can::{Problem, RuntimeError}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; -use crate::lang::core::pattern::flatten_str_literal; +use crate::lang::core::declaration::decl_to_let; +use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs}; +use crate::lang::core::expr::expr2::ClosureExtra; +use crate::lang::core::pattern::to_pattern2; +use crate::lang::core::str::flatten_str_literal; +use crate::pool::shallow_clone::ShallowClone; use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; - +use crate::canonicalize::canonicalize::{CanonicalizeRecordProblem, canonicalize_fields, canonicalize_lookup, canonicalize_when_branch}; use super::{expr2::Expr2, output::Output}; pub fn loc_expr_to_expr2<'a>( diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs index 7347e5a924..2ee105850f 100644 --- a/ast/src/lang/core/mod.rs +++ b/ast/src/lang/core/mod.rs @@ -2,7 +2,9 @@ pub mod def; pub mod expr; pub mod header; pub mod ast; -mod val_def; +pub mod val_def; mod fun_def; -mod pattern; -pub mod types; \ No newline at end of file +pub mod pattern; +pub mod types; +mod str; +mod declaration; \ No newline at end of file diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs new file mode 100644 index 0000000000..bc203c0753 --- /dev/null +++ b/ast/src/lang/core/str.rs @@ -0,0 +1,164 @@ +use roc_module::{operator::CalledVia, symbol::Symbol}; +use roc_parse::ast::StrLiteral; + +use crate::{lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; + +use super::expr::{expr2::Expr2, output::Output}; + +pub (crate) fn flatten_str_literal<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + literal: &StrLiteral<'a>, +) -> (Expr2, Output) { + use roc_parse::ast::StrLiteral::*; + + match literal { + PlainLine(str_slice) => { + // TODO use smallstr + let expr = Expr2::Str(PoolStr::new(str_slice, &mut env.pool)); + + (expr, Output::default()) + } + Line(segments) => flatten_str_lines(env, scope, &[segments]), + Block(lines) => flatten_str_lines(env, scope, lines), + } +} + +enum StrSegment { + Interpolation(Expr2), + Plaintext(PoolStr), +} + +fn flatten_str_lines<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + lines: &[&[roc_parse::ast::StrSegment<'a>]], +) -> (Expr2, Output) { + use roc_parse::ast::StrSegment::*; + + let mut buf = String::new(); + let mut segments = Vec::new(); + let mut output = Output::default(); + + for line in lines { + for segment in line.iter() { + match segment { + Plaintext(string) => { + buf.push_str(string); + } + Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) { + Ok(code_pt) => match std::char::from_u32(code_pt) { + Some(ch) => { + buf.push(ch); + } + None => { + // env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt( + // loc_hex_digits.region, + // )), + // output, + // ); + todo!() + } + }, + Err(_) => { + // env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidHexadecimal( + // loc_hex_digits.region, + // )), + // output, + // ); + todo!() + } + }, + Interpolated(loc_expr) => { + if roc_can::expr::is_valid_interpolation(loc_expr.value) { + // Interpolations desugar to Str.concat calls + output.references.calls.insert(Symbol::STR_CONCAT); + + if !buf.is_empty() { + segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); + + buf = String::new(); + } + + let (loc_expr, new_output) = + to_expr2(env, scope, loc_expr.value, loc_expr.region); + + output.union(new_output); + + segments.push(StrSegment::Interpolation(loc_expr)); + } else { + // env.problem(Problem::InvalidInterpolation(loc_expr.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)), + // output, + // ); + todo!() + } + } + EscapedChar(escaped) => buf.push(roc_can::expr::unescape_char(escaped)), + } + } + } + + if !buf.is_empty() { + segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); + } + + (desugar_str_segments(env, segments), output) +} + +/// Resolve string interpolations by desugaring a sequence of StrSegments +/// into nested calls to Str.concat +fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Expr2 { + use StrSegment::*; + + let pool = &mut env.pool; + let var_store = &mut env.var_store; + + let mut iter = segments.into_iter().rev(); + let mut expr = match iter.next() { + Some(Plaintext(pool_str)) => Expr2::Str(pool_str), + Some(Interpolation(expr_id)) => expr_id, + None => { + // No segments? Empty string! + + let pool_str = PoolStr::new("", pool); + Expr2::Str(pool_str) + } + }; + + for seg in iter { + let new_expr = match seg { + Plaintext(string) => Expr2::Str(string), + Interpolation(expr_id) => expr_id, + }; + + let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT)); + + let args = vec![ + (var_store.fresh(), pool.add(new_expr)), + (var_store.fresh(), pool.add(expr)), + ]; + let args = PoolVec::new(args.into_iter(), pool); + + let new_call = Expr2::Call { + args, + expr: concat_expr_id, + expr_var: var_store.fresh(), + fn_var: var_store.fresh(), + closure_var: var_store.fresh(), + called_via: CalledVia::Space, + }; + + expr = new_call + } + + expr +} \ No newline at end of file diff --git a/ast/src/lang/mod.rs b/ast/src/lang/mod.rs index 9437f39470..b6a8753591 100644 --- a/ast/src/lang/mod.rs +++ b/ast/src/lang/mod.rs @@ -1,4 +1,4 @@ pub mod core; -mod scope; +pub mod scope; mod rigids; -mod env; +pub mod env; From ea62f15ac60a573f94299143e4390861d65ee38a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Sep 2021 15:41:45 +0200 Subject: [PATCH 03/12] no more errors --- ast/src/canonicalize/canonicalize.rs | 2 +- ast/src/lang/core/def/def_to_def2.rs | 21 ++++++++++++++++++++- ast/src/lang/core/expr/expr_to_expr2.rs | 14 ++++++++++++++ ast/src/mod.rs | 2 +- ast/src/pool/pool.rs | 4 ++-- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index c8bddb58fd..72d9e83846 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -20,7 +20,7 @@ enum FieldVar { OnlyVar(Variable), } -pub fn canonicalize_fields<'a>( +pub (crate) fn canonicalize_fields<'a>( env: &mut Env<'a>, scope: &mut Scope, fields: &'a [Located>>], diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs index a30a65e3c6..b35b7e3890 100644 --- a/ast/src/lang/core/def/def_to_def2.rs +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -1,6 +1,6 @@ use bumpalo::Bump; use bumpalo::collections::Vec as BumpVec; -use roc_parse::pattern::PatternType; +use roc_parse::{parser::SyntaxError, pattern::PatternType}; use roc_region::all::Region; use crate::lang::{core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, env::Env, scope::Scope}; @@ -72,4 +72,23 @@ pub fn to_def2_from_def<'a>( ) } } +} + +pub fn str_to_def2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> Result, SyntaxError<'a>> { + match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { + Ok(vec_loc_def) => Ok(defs_to_defs2( + arena, + env, + scope, + arena.alloc(vec_loc_def), + region, + )), + Err(fail) => Err(fail), + } } \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 9a5a67f9a6..4e277d590c 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -3,6 +3,7 @@ use roc_can::expr::Recursive; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::operator::desugar_expr; use roc_collections::all::MutSet; +use roc_parse::parser::SyntaxError; use roc_parse::{ast::Expr, pattern::PatternType}; use roc_problem::can::{Problem, RuntimeError}; use roc_module::symbol::Symbol; @@ -18,6 +19,19 @@ use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env: use crate::canonicalize::canonicalize::{CanonicalizeRecordProblem, canonicalize_fields, canonicalize_lookup, canonicalize_when_branch}; use super::{expr2::Expr2, output::Output}; +pub fn str_to_expr2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> Result<(Expr2, self::Output), SyntaxError<'a>> { + match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { + Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), + Err(fail) => Err(fail), + } +} + pub fn loc_expr_to_expr2<'a>( arena: &'a Bump, loc_expr: Located>, diff --git a/ast/src/mod.rs b/ast/src/mod.rs index addb2cebaf..7aed5d1c66 100644 --- a/ast/src/mod.rs +++ b/ast/src/mod.rs @@ -1,5 +1,5 @@ pub mod ast; -mod constrain; +pub mod constrain; pub mod lang; mod module; pub mod parse; diff --git a/ast/src/pool/pool.rs b/ast/src/pool/pool.rs index 8663ca4bcb..97ec3ca7af 100644 --- a/ast/src/pool/pool.rs +++ b/ast/src/pool/pool.rs @@ -60,9 +60,9 @@ pub const NODE_BYTES: usize = 32; // to see if it was #[derive(Debug, Eq)] -pub (crate) struct NodeId { +pub struct NodeId { pub (super) index: u32, - _phantom: PhantomData, + pub (super) _phantom: PhantomData, } impl Clone for NodeId { From f9e2e3469ba25ca833d49fb0d6c3237ae4e6ea1f Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Sep 2021 20:08:49 +0200 Subject: [PATCH 04/12] separated markup and util crates, no more errors --- Cargo.lock | 20 + Cargo.toml | 2 + ast/Cargo.toml | 2 +- ast/src/canonicalize/canonicalize.rs | 2 +- ast/src/constrain.rs | 2 +- ast/src/lang/core/expr/expr2.rs | 9 +- ast/src/lang/core/expr/expr2_to_string.rs | 2 +- ast/src/lang/core/expr/mod.rs | 1 + ast/src/lang/core/expr/record_field.rs | 52 ++ ast/src/lib.rs | 2 +- ast/src/mod.rs | 2 +- code_markup/Cargo.toml | 18 + code_markup/src/colors.rs | 22 + code_markup/src/lib.rs | 5 + code_markup/src/markup/attribute.rs | 123 +++ code_markup/src/markup/common_nodes.rs | 107 +++ code_markup/src/markup/mod.rs | 4 + code_markup/src/markup/nodes.rs | 880 ++++++++++++++++++++++ code_markup/src/markup/top_level_def.rs | 39 + code_markup/src/markup_error.rs | 56 ++ code_markup/src/slow_pool.rs | 76 ++ code_markup/src/syntax_highlight.rs | 48 ++ editor/src/editor/slow_pool.rs | 5 +- utils/Cargo.toml | 12 + utils/src/lib.rs | 96 +++ utils/src/util_error.rs | 36 + 26 files changed, 1609 insertions(+), 14 deletions(-) create mode 100644 ast/src/lang/core/expr/record_field.rs create mode 100644 code_markup/Cargo.toml create mode 100644 code_markup/src/colors.rs create mode 100644 code_markup/src/lib.rs create mode 100644 code_markup/src/markup/attribute.rs create mode 100644 code_markup/src/markup/common_nodes.rs create mode 100644 code_markup/src/markup/mod.rs create mode 100644 code_markup/src/markup/nodes.rs create mode 100644 code_markup/src/markup/top_level_def.rs create mode 100644 code_markup/src/markup_error.rs create mode 100644 code_markup/src/slow_pool.rs create mode 100644 code_markup/src/syntax_highlight.rs create mode 100644 utils/Cargo.toml create mode 100644 utils/src/lib.rs create mode 100644 utils/src/util_error.rs diff --git a/Cargo.lock b/Cargo.lock index a7d51f68b9..3fdcc1094b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3529,6 +3529,19 @@ dependencies = [ "wasmer-wasi", ] +[[package]] +name = "roc_code_markup" +version = "0.1.0" +dependencies = [ + "bumpalo", + "palette", + "roc_ast", + "roc_module", + "roc_utils", + "serde", + "snafu", +] + [[package]] name = "roc_collections" version = "0.1.0" @@ -3956,6 +3969,13 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_utils" +version = "0.1.0" +dependencies = [ + "snafu", +] + [[package]] name = "ropey" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index e2864e9243..a2fe43c612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,9 @@ members = [ "ast", "cli", "cli/cli_utils", + "code_markup", "roc_std", + "utils", "docs", "linker", ] diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 8b03f69880..76d1658885 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" -description = "AST as used by the editor and docs. In contrast to the compiler, these types do not keep track of a location in a file." +description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file." [dependencies] roc_can = { path = "../compiler/can" } diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index 72d9e83846..459fb6c7d1 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -5,7 +5,7 @@ use roc_problem::can::{Problem}; use roc_region::all::{Located, Region}; use roc_types::{subs::Variable}; -use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, RecordField, WhenBranch}, expr_to_expr2::to_expr2, output::Output}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, WhenBranch}, expr_to_expr2::to_expr2, output::Output, record_field::RecordField}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; pub (crate) enum CanonicalizeRecordProblem { InvalidOptionalValue { diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index b0e3f8fa5d..07c7ba8480 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -13,7 +13,7 @@ use roc_types::{ types::{Category, Reason}, }; -use crate::{lang::{core::{expr::expr2::{ClosureExtra, Expr2, ExprId, RecordField, WhenBranch}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{lang::{core::{expr::{expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, record_field::RecordField}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; #[derive(Debug)] pub enum Constraint<'a> { diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 6b5b5b8328..83e263b3d2 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -7,6 +7,8 @@ use roc_module::symbol::Symbol; use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; +use super::record_field::RecordField; + pub type ArrString = ArrayString; // TODO make the inner types private? @@ -210,13 +212,6 @@ pub enum FloatVal { F32(f32), } -#[derive(Debug)] -pub enum RecordField { - InvalidLabelOnly(PoolStr, Variable), - LabelOnly(PoolStr, Variable, Symbol), - LabeledValue(PoolStr, Variable, ExprId), -} - #[derive(Debug)] pub struct WhenBranch { pub patterns: PoolVec, // 4B diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs index 7ab0ec4650..b9ec8a234b 100644 --- a/ast/src/lang/core/expr/expr2_to_string.rs +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -1,4 +1,4 @@ -use crate::{lang::core::{expr::expr2::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; +use crate::{lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; use roc_types::subs::Variable; use super::expr2::{Expr2, ExprId}; diff --git a/ast/src/lang/core/expr/mod.rs b/ast/src/lang/core/expr/mod.rs index 186f65c9c0..1a58685fc2 100644 --- a/ast/src/lang/core/expr/mod.rs +++ b/ast/src/lang/core/expr/mod.rs @@ -3,3 +3,4 @@ pub mod expr2_to_string; pub (crate) mod output; mod introduced_vars; pub (crate) mod expr_to_expr2; +pub mod record_field; diff --git a/ast/src/lang/core/expr/record_field.rs b/ast/src/lang/core/expr/record_field.rs new file mode 100644 index 0000000000..2db2fc6fe5 --- /dev/null +++ b/ast/src/lang/core/expr/record_field.rs @@ -0,0 +1,52 @@ + +use roc_types::subs::Variable; + +use crate::{pool::{pool_str::PoolStr}}; +use roc_module::symbol::Symbol; + +use super::expr2::ExprId; + +#[derive(Debug)] +pub enum RecordField { + InvalidLabelOnly(PoolStr, Variable), + LabelOnly(PoolStr, Variable, Symbol), + LabeledValue(PoolStr, Variable, ExprId), +} + +use RecordField::*; + +impl RecordField { + + + pub fn get_record_field_var(&self) -> &Variable { + match self { + InvalidLabelOnly(_, var) => var, + LabelOnly(_, var, _) => var, + LabeledValue(_, var, _) => var, + } + } + + pub fn get_record_field_pool_str(&self) -> &PoolStr { + match self { + InvalidLabelOnly(pool_str, _) => pool_str, + LabelOnly(pool_str, _, _) => pool_str, + LabeledValue(pool_str, _, _) => pool_str, + } + } + + pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr { + match self { + InvalidLabelOnly(pool_str, _) => pool_str, + LabelOnly(pool_str, _, _) => pool_str, + LabeledValue(pool_str, _, _) => pool_str, + } + } + + pub fn get_record_field_val_node_id(&self) -> Option { + match self { + InvalidLabelOnly(_, _) => None, + LabelOnly(_, _, _) => None, + LabeledValue(_, _, field_val_id) => Some(*field_val_id), + } + } +} \ No newline at end of file diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 84c6ff305b..02cf199e70 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -2,4 +2,4 @@ pub mod lang; pub mod pool; mod constrain; mod canonicalize; -mod ast_error; \ No newline at end of file +pub mod ast_error; \ No newline at end of file diff --git a/ast/src/mod.rs b/ast/src/mod.rs index 7aed5d1c66..273c344ee5 100644 --- a/ast/src/mod.rs +++ b/ast/src/mod.rs @@ -11,4 +11,4 @@ mod solve; mod types; mod rigids; mod canonicalize; -mod ast_error; \ No newline at end of file +pub mod ast_error; \ No newline at end of file diff --git a/code_markup/Cargo.toml b/code_markup/Cargo.toml new file mode 100644 index 0000000000..2583ee0006 --- /dev/null +++ b/code_markup/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "roc_code_markup" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Our own markup language for Roc code. Used by the editor and (soon) the docs." + +[dependencies] +roc_ast = { path = "../ast" } +roc_module = { path = "../compiler/module" } +roc_utils = { path = "../utils" } +serde = { version = "1.0.123", features = ["derive"] } +palette = "0.5" +snafu = { version = "0.6", features = ["backtraces"] } +bumpalo = { version = "3.2", features = ["collections"] } + +[dev-dependencies] \ No newline at end of file diff --git a/code_markup/src/colors.rs b/code_markup/src/colors.rs new file mode 100644 index 0000000000..93c6c452b6 --- /dev/null +++ b/code_markup/src/colors.rs @@ -0,0 +1,22 @@ +use palette::{Hsv, LinSrgb}; + +pub type RgbaTup = (f32, f32, f32, f32); +pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0); + +pub fn to_slice((r, g, b, a): RgbaTup) -> [f32; 4] { + [r, g, b, a] +} + +pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup { + from_hsba(hue, saturation, brightness, 1.0) +} + +pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup { + let rgb = LinSrgb::from(Hsv::new( + hue as f32, + (saturation as f32) / 100.0, + (brightness as f32) / 100.0, + )); + + (rgb.red, rgb.green, rgb.blue, alpha) +} diff --git a/code_markup/src/lib.rs b/code_markup/src/lib.rs new file mode 100644 index 0000000000..df6e4f24ed --- /dev/null +++ b/code_markup/src/lib.rs @@ -0,0 +1,5 @@ +pub mod markup; +pub mod markup_error; +pub mod slow_pool; +pub mod syntax_highlight; +pub mod colors; diff --git a/code_markup/src/markup/attribute.rs b/code_markup/src/markup/attribute.rs new file mode 100644 index 0000000000..31a575d35c --- /dev/null +++ b/code_markup/src/markup/attribute.rs @@ -0,0 +1,123 @@ +#![allow(dead_code)] +use snafu::ensure; + +use crate::markup_error::{MarkResult, CaretNotFound}; + +#[derive(Debug, Copy, Clone)] +pub struct Caret { + pub offset_col: usize, +} + +impl Caret { + pub fn new_attr(offset_col: usize) -> Attribute { + Attribute::Caret { + caret: Caret { offset_col }, + } + } +} +#[derive(Debug)] +pub struct SelectionStart { + offset_col: usize, +} +#[derive(Debug)] +pub struct SelectionEnd { + offset_col: usize, +} + +// Highlight is used for example when searching for a specific string to highlight all search results in the module +#[derive(Debug)] +pub struct HighlightStart { + offset_col: usize, +} +#[derive(Debug)] +pub struct HighlightEnd { + offset_col: usize, +} + +// Underline is used for warnings and errors +#[derive(Debug)] +pub struct UnderlineStart { + offset_col: usize, +} +#[derive(Debug)] +pub struct UnderlineEnd { + offset_col: usize, +} + +#[derive(Debug)] +pub enum Attribute { + // Rust does not yet support types for enum variants so we have to do it like this + Caret { caret: Caret }, + + SelectionStart { selection_start: SelectionStart }, + SelectionEnd { selection_end: SelectionEnd }, + + HighlightStart { highlight_start: HighlightStart }, + HighlightEnd { highlight_end: HighlightEnd }, + + UnderlineStart { underline_start: UnderlineStart }, + UnderlineEnd { underline_end: UnderlineEnd }, +} + +#[derive(Debug)] +pub struct Attributes { + pub all: Vec, +} + +impl Attributes { + pub fn new() -> Attributes { + Attributes { all: Vec::new() } + } + + pub fn add(&mut self, attr: Attribute) { + self.all.push(attr); + } + + pub fn add_caret(&mut self, offset_col: usize) { + self.all.push(Attribute::Caret { + caret: Caret { offset_col }, + }); + } + + pub fn get_mut_carets(&mut self) -> Vec<&mut Caret> { + let mut carets = Vec::new(); + + for attr in self.all.iter_mut() { + if let Attribute::Caret { caret } = attr { + carets.push(caret) + } + } + + carets + } + + pub fn get_carets(&self) -> Vec { + let mut carets = Vec::new(); + + for attr in self.all.iter() { + if let Attribute::Caret { caret } = attr { + carets.push(*caret) + } + } + + carets + } + + pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> MarkResult<()> { + let old_len = self.all.len(); + + self.all.retain(|attr| { + if let Attribute::Caret { caret } = attr { + caret.offset_col != offset_col + } else { + true + } + }); + + let new_len = self.all.len(); + + ensure!(old_len != new_len, CaretNotFound { node_id }); + + Ok(()) + } +} diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs new file mode 100644 index 0000000000..c44000534e --- /dev/null +++ b/code_markup/src/markup/common_nodes.rs @@ -0,0 +1,107 @@ + +use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId}; + +use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}; + +use super::{attribute::Attributes, nodes, nodes::MarkupNode}; + +pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::EQUALS.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COMMA.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn_w_nls( + ast_node_id: ASTNodeId, + parent_id_opt: Option, + nr_of_newlines: usize, +) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: nr_of_newlines, + } +} + +pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COLON.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} diff --git a/code_markup/src/markup/mod.rs b/code_markup/src/markup/mod.rs new file mode 100644 index 0000000000..e3ce137f80 --- /dev/null +++ b/code_markup/src/markup/mod.rs @@ -0,0 +1,4 @@ +pub mod attribute; +pub mod common_nodes; +pub mod nodes; +pub mod top_level_def; diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs new file mode 100644 index 0000000000..6a08432891 --- /dev/null +++ b/code_markup/src/markup/nodes.rs @@ -0,0 +1,880 @@ +use crate::{markup::common_nodes::{new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn, new_right_square_mn}, markup_error::MarkResult, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; + +use super::{attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node}; +/*use crate::editor::ed_error::EdResult; +use crate::editor::ed_error::ExpectedTextNode; +use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; +use crate::editor::markup::common_nodes::new_blank_mn; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::markup::common_nodes::new_colon_mn; +use crate::editor::markup::common_nodes::new_comma_mn; +use crate::editor::markup::common_nodes::new_equals_mn; +use crate::editor::markup::common_nodes::new_left_accolade_mn; +use crate::editor::markup::common_nodes::new_left_square_mn; +use crate::editor::markup::common_nodes::new_right_accolade_mn; +use crate::editor::markup::common_nodes::new_right_square_mn; +use crate::editor::mvc::tld_value_update::tld_mark_node; +use crate::editor::slow_pool::MarkNodeId; +use crate::editor::slow_pool::SlowPool; +use crate::editor::syntax_highlight::HighlightStyle; +use crate::editor::util::index_of; +use crate::lang::ast::Def2; +use crate::lang::ast::DefId; +use crate::lang::ast::ExprId; +use crate::lang::ast::RecordField; +use crate::lang::ast::ValueDef; +use crate::lang::parse::ASTNodeId; +use crate::lang::parse::{AppHeader, AST}; +use crate::lang::pattern::get_identifier_string; +use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; +use crate::ui::util::slice_get;*/ +use bumpalo::Bump; +use roc_ast::{ast_error::ASTResult, lang::{core::{ast::{AST, ASTNodeId}, def::def2::{Def2, DefId}, expr::{expr2::{Expr2, ExprId}, record_field::RecordField}, header::AppHeader, pattern::get_identifier_string, val_def::ValueDef}, env::Env}, pool::pool_str::PoolStr}; +use roc_utils::{index_of, slice_get}; +use roc_module::symbol::Interns; +use std::fmt; +use crate::markup_error::{NestedNodeMissingChild, NestedNodeRequired, ExpectedTextNode}; + +#[derive(Debug)] +pub enum MarkupNode { + Nested { + ast_node_id: ASTNodeId, + children_ids: Vec, + parent_id_opt: Option, + newlines_at_end: usize, + }, + Text { + content: String, + ast_node_id: ASTNodeId, + syn_high_style: HighlightStyle, + attributes: Attributes, + parent_id_opt: Option, + newlines_at_end: usize, + }, + Blank { + ast_node_id: ASTNodeId, + attributes: Attributes, + syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank + parent_id_opt: Option, + newlines_at_end: usize, + }, +} + +impl MarkupNode { + pub fn get_ast_node_id(&self) -> ASTNodeId { + match self { + MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, + MarkupNode::Text { ast_node_id, .. } => *ast_node_id, + MarkupNode::Blank { ast_node_id, .. } => *ast_node_id, + } + } + + pub fn get_parent_id_opt(&self) -> Option { + match self { + MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt, + MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt, + MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt, + } + } + + pub fn get_children_ids(&self) -> Vec { + match self { + MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(), + MarkupNode::Text { .. } => vec![], + MarkupNode::Blank { .. } => vec![], + } + } + + pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec { + if let Some(parent_id) = self.get_parent_id_opt() { + let parent_node = mark_node_pool.get(parent_id); + + parent_node.get_children_ids() + } else { + vec![] + } + } + + // return (index of child in list of children, closest ast index of child corresponding to ast node) + pub fn get_child_indices( + &self, + child_id: MarkNodeId, + mark_node_pool: &SlowPool, + ) -> MarkResult<(usize, usize)> { + match self { + MarkupNode::Nested { children_ids, .. } => { + let mut mark_child_index_opt: Option = None; + let mut child_ids_with_ast: Vec = Vec::new(); + let self_ast_id = self.get_ast_node_id(); + + for (indx, &mark_child_id) in children_ids.iter().enumerate() { + if mark_child_id == child_id { + mark_child_index_opt = Some(indx); + } + + let child_mark_node = mark_node_pool.get(mark_child_id); + // a node that points to the same ast_node as the parent is a ',', '[', ']' + // those are not "real" ast children + if child_mark_node.get_ast_node_id() != self_ast_id { + child_ids_with_ast.push(mark_child_id) + } + } + + if let Some(child_index) = mark_child_index_opt { + if child_index == (children_ids.len() - 1) { + let ast_child_index = child_ids_with_ast.len(); + + Ok((child_index, ast_child_index)) + } else { + // we want to find the index of the closest ast mark node to child_index + let mut indices_in_mark = vec![]; + + for &c_id in child_ids_with_ast.iter() { + indices_in_mark.push(index_of(c_id, children_ids)?); + } + + let mut last_diff = usize::MAX; + let mut best_index = 0; + + for index in indices_in_mark.iter() { + let curr_diff = + isize::abs((*index as isize) - (child_index as isize)) as usize; + + if curr_diff >= last_diff { + break; + } else { + last_diff = curr_diff; + best_index = *index; + } + } + + let closest_ast_child = slice_get(best_index, children_ids)?; + + let closest_ast_child_index = + index_of(*closest_ast_child, &child_ids_with_ast)?; + + // +1 because we want to insert after ast_child + Ok((child_index, closest_ast_child_index + 1)) + } + } else { + NestedNodeMissingChild { + node_id: child_id, + children_ids: children_ids.clone(), + } + .fail() + } + } + _ => NestedNodeRequired { + node_type: self.node_type_as_string(), + } + .fail(), + } + } + + pub fn get_content(&self) -> String { + match self { + MarkupNode::Nested { .. } => "".to_owned(), + MarkupNode::Text { content, .. } => content.clone(), + MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), + } + } + + // gets content and adds newline from newline_at_end + pub fn get_full_content(&self) -> String { + let mut full_content = self.get_content(); + + for _ in 0..self.get_newlines_at_end() { + full_content.push('\n') + } + + full_content + } + + pub fn get_content_mut(&mut self) -> MarkResult<&mut String> { + match self { + MarkupNode::Nested { .. } => ExpectedTextNode { + function_name: "set_content".to_owned(), + node_type: self.node_type_as_string(), + } + .fail(), + MarkupNode::Text { content, .. } => Ok(content), + MarkupNode::Blank { .. } => ExpectedTextNode { + function_name: "set_content".to_owned(), + node_type: self.node_type_as_string(), + } + .fail(), + } + } + + pub fn is_all_alphanumeric(&self) -> bool { + self.get_content() + .chars() + .all(|chr| chr.is_ascii_alphanumeric()) + } + + pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> MarkResult<()> { + if let MarkupNode::Nested { children_ids, .. } = self { + children_ids.splice(index..index, vec![child_id]); + } else { + NestedNodeRequired { + node_type: self.node_type_as_string(), + } + .fail()?; + } + + Ok(()) + } + + pub fn node_type_as_string(&self) -> String { + let type_str = match self { + MarkupNode::Nested { .. } => "Nested", + MarkupNode::Text { .. } => "Text", + MarkupNode::Blank { .. } => "Blank", + }; + + type_str.to_owned() + } + + pub fn is_blank(&self) -> bool { + matches!(self, MarkupNode::Blank { .. }) + } + + pub fn is_nested(&self) -> bool { + matches!(self, MarkupNode::Nested { .. }) + } + + pub fn get_newlines_at_end(&self) -> usize { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end, + } + } + + pub fn add_newline_at_end(&mut self) { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end += 1, + } + } +} + +fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { + pool_str.as_str(env.pool).to_owned() +} + +pub const BLANK_PLACEHOLDER: &str = " "; +pub const LEFT_ACCOLADE: &str = "{ "; +pub const RIGHT_ACCOLADE: &str = " }"; +pub const LEFT_SQUARE_BR: &str = "[ "; +pub const RIGHT_SQUARE_BR: &str = " ]"; +pub const COLON: &str = ": "; +pub const COMMA: &str = ", "; +pub const STRING_QUOTES: &str = "\"\""; +pub const EQUALS: &str = " = "; + +fn new_markup_node( + text: String, + node_id: ASTNodeId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let node = MarkupNode::Text { + content: text, + ast_node_id: node_id, + syn_high_style: highlight_style, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(node) +} + +pub fn def2_to_markup<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + def2: &Def2, + def2_node_id: DefId, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult { + let ast_node_id = ASTNodeId::ADefId(def2_node_id); + + let mark_node_id = match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + let expr_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let tld_mn = tld_mark_node( + *identifier_id, + expr_mn_id, + ast_node_id, + mark_node_pool, + env, + interns, + )?; + + mark_node_pool.add(tld_mn) + } + Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), + }; + + Ok(mark_node_id) +} + +// make Markup Nodes: generate String representation, assign Highlighting Style +pub fn expr2_to_markup<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + expr2: &Expr2, + expr2_node_id: ExprId, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult { + let ast_node_id = ASTNodeId::AExprId(expr2_node_id); + + let mark_node_id = match expr2 { + Expr2::SmallInt { text, .. } + | Expr2::I128 { text, .. } + | Expr2::U128 { text, .. } + | Expr2::Float { text, .. } => { + let num_str = get_string(env, text); + + new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) + } + Expr2::Str(text) => new_markup_node( + "\"".to_owned() + text.as_str(env.pool) + "\"", + ast_node_id, + HighlightStyle::String, + mark_node_pool, + ), + Expr2::GlobalTag { name, .. } => new_markup_node( + get_string(env, name), + ast_node_id, + HighlightStyle::Type, + mark_node_pool, + ), + Expr2::Call { expr: expr_id, .. } => { + let expr = env.pool.get(*expr_id); + expr2_to_markup(arena, env, expr, *expr_id, mark_node_pool, interns)? + } + Expr2::Var(symbol) => { + //TODO make bump_format with arena + let text = format!("{:?}", symbol); + new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) + } + Expr2::List { elems, .. } => { + let mut children_ids = + vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; + + let indexed_node_ids: Vec<(usize, ExprId)> = + elems.iter(env.pool).copied().enumerate().collect(); + + for (idx, node_id) in indexed_node_ids.iter() { + let sub_expr2 = env.pool.get(*node_id); + + children_ids.push(expr2_to_markup( + arena, + env, + sub_expr2, + *node_id, + mark_node_pool, + interns, + )?); + + if idx + 1 < elems.len() { + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); + } + } + children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); + + let list_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(list_node) + } + Expr2::EmptyRecord => { + let children_ids = vec![ + mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), + mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), + ]; + + let record_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(record_node) + } + Expr2::Record { fields, .. } => { + let mut children_ids = + vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; + + for (idx, field_node_id) in fields.iter_node_ids().enumerate() { + let record_field = env.pool.get(field_node_id); + + let field_name = record_field.get_record_field_pool_str(); + + children_ids.push(new_markup_node( + field_name.as_str(env.pool).to_owned(), + ast_node_id, + HighlightStyle::RecordField, + mark_node_pool, + )); + + match record_field { + RecordField::InvalidLabelOnly(_, _) => (), + RecordField::LabelOnly(_, _, _) => (), + RecordField::LabeledValue(_, _, sub_expr2_node_id) => { + children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); + + let sub_expr2 = env.pool.get(*sub_expr2_node_id); + children_ids.push(expr2_to_markup( + arena, + env, + sub_expr2, + *sub_expr2_node_id, + mark_node_pool, + interns, + )?); + } + } + + if idx + 1 < fields.len() { + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); + } + } + + children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); + + let record_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(record_node) + } + Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), + Expr2::LetValue { + def_id, + body_id: _, + body_var: _, + } => { + let pattern_id = env.pool.get(*def_id).get_pattern_id(); + + let pattern2 = env.pool.get(pattern_id); + + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let value_def = env.pool.get(*def_id); + + match value_def { + ValueDef::NoAnnotation { + pattern_id: _, + expr_id, + expr_var: _, + } => { + let body_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let body_mn = mark_node_pool.get_mut(body_mn_id); + body_mn.add_newline_at_end(); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + mark_node_pool.add(full_let_node) + } + other => { + unimplemented!( + "I don't know how to convert {:?} into a MarkupNode yet.", + other + ) + } + } + } + Expr2::RuntimeError() => new_markup_node( + "RunTimeError".to_string(), + ast_node_id, + HighlightStyle::Blank, + mark_node_pool, + ), + rest => todo!("implement expr2_to_markup for {:?}", rest), + }; + + Ok(mark_node_id) +} + +pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { + let node = mark_node_pool.get(markup_node_id); + + if let MarkupNode::Nested { + ast_node_id: _, + children_ids, + parent_id_opt: _, + newlines_at_end: _, + } = node + { + // need to clone because of borrowing issues + let children_ids_clone = children_ids.clone(); + + for child_id in children_ids_clone { + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); + } + } +} + +pub fn set_parent_for_all_helper( + markup_node_id: MarkNodeId, + parent_node_id: MarkNodeId, + mark_node_pool: &mut SlowPool, +) { + let node = mark_node_pool.get_mut(markup_node_id); + + match node { + MarkupNode::Nested { + children_ids, + parent_id_opt, + .. + } => { + *parent_id_opt = Some(parent_node_id); + + // need to clone because of borrowing issues + let children_ids_clone = children_ids.clone(); + + for child_id in children_ids_clone { + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); + } + } + MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), + MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), + } +} + +fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::PackageRelated, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +fn header_val_mn( + content: String, + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: highlight_style, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let expr_id = app_header.ast_node_id; + let ast_node_id = ASTNodeId::AExprId(expr_id); + + let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); + + let app_name_node_id = header_val_mn( + app_header.app_name.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let full_app_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![app_node_id, app_name_node_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); + + let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); + + let pack_base_node_id = header_val_mn( + "base: ".to_owned(), + expr_id, + HighlightStyle::RecordField, + mark_node_pool, + ); + + let pack_val_node_id = header_val_mn( + app_header.packages_base.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); + + let full_packages_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + packages_node_id, + pack_left_acc_node_id, + pack_base_node_id, + pack_val_node_id, + pack_right_acc_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); + + let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut import_child_ids: Vec = add_header_mn_list( + &app_header.imports, + expr_id, + HighlightStyle::Import, + mark_node_pool, + ); + + let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; + + full_import_children.append(&mut import_child_ids); + full_import_children.push(imports_right_square_node_id); + + let full_import_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_import_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); + + let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut provides_val_node_ids: Vec = add_header_mn_list( + &app_header.provides, + expr_id, + HighlightStyle::Provides, + mark_node_pool, + ); + + let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); + + let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; + + full_provides_children.append(&mut provides_val_node_ids); + full_provides_children.push(provides_right_square_node_id); + full_provides_children.push(provides_end_node_id); + + let full_provides_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_provides_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let full_app_node_id = mark_node_pool.add(full_app_node); + let full_packages_node = mark_node_pool.add(full_packages_node); + let full_import_node_id = mark_node_pool.add(full_import_node); + let full_provides_node_id = mark_node_pool.add(full_provides_node); + + let header_mark_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + full_app_node_id, + full_packages_node, + full_import_node_id, + full_provides_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let header_mn_id = mark_node_pool.add(header_mark_node); + + set_parent_for_all(header_mn_id, mark_node_pool); + + header_mn_id +} + +// Used for provides and imports +fn add_header_mn_list( + str_vec: &[String], + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> Vec { + let nr_of_elts = str_vec.len(); + + str_vec + .iter() + .enumerate() + .map(|(indx, provide_str)| { + let provide_str = header_val_mn( + provide_str.to_owned(), + expr_id, + highlight_style, + mark_node_pool, + ); + + if indx != nr_of_elts - 1 { + vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] + } else { + vec![provide_str] + } + }) + .flatten() + .collect() +} + +pub fn ast_to_mark_nodes<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + ast: &AST, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult> { + let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; + + for &def_id in ast.def_ids.iter() { + let def2 = env.pool.get(def_id); + + let expr2_markup_id = def2_to_markup(arena, env, def2, def_id, mark_node_pool, interns)?; + + set_parent_for_all(expr2_markup_id, mark_node_pool); + + all_mark_node_ids.push(expr2_markup_id); + } + + Ok(all_mark_node_ids) +} + +impl fmt::Display for MarkupNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} ({}, {})", + self.node_type_as_string(), + self.get_content(), + self.get_newlines_at_end() + ) + } +} + +pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { + let mut full_string = "\n(mark_node_tree)\n".to_owned(); + + let node = mark_node_pool.get(root_node_id); + + full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id)); + + tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); + + full_string +} + +fn tree_as_string_helper( + node: &MarkupNode, + level: usize, + tree_string: &mut String, + mark_node_pool: &SlowPool, +) { + for child_id in node.get_children_ids() { + let mut full_str = std::iter::repeat("|--- ") + .take(level) + .collect::>() + .join("") + .to_owned(); + + let child = mark_node_pool.get(child_id); + let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n"); + + full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id)); + + tree_string.push_str(&full_str); + + tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); + } +} + +// return to the the root parent_id of the current node +pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId { + let mut curr_mark_node_id = mark_node_id; + let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + + while let Some(curr_parent_id) = curr_parent_id_opt { + curr_mark_node_id = curr_parent_id; + curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + } + + curr_mark_node_id +} diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs new file mode 100644 index 0000000000..78d086f3fe --- /dev/null +++ b/code_markup/src/markup/top_level_def.rs @@ -0,0 +1,39 @@ +use roc_ast::{ast_error::ASTResult, lang::{core::{ast::ASTNodeId, pattern::{PatternId, get_identifier_string}}, env::Env}}; +use roc_module::symbol::Interns; + +use crate::{markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; + + +pub fn tld_mark_node<'a>( + identifier_id: PatternId, + expr_mark_node_id: MarkNodeId, + ast_node_id: ASTNodeId, + mark_node_pool: &mut SlowPool, + env: &Env<'a>, + interns: &Interns, +) -> ASTResult { + let pattern2 = env.pool.get(identifier_id); + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id], + parent_id_opt: None, + newlines_at_end: 2, + }; + + Ok(full_let_node) +} \ No newline at end of file diff --git a/code_markup/src/markup_error.rs b/code_markup/src/markup_error.rs new file mode 100644 index 0000000000..a3d3d2a680 --- /dev/null +++ b/code_markup/src/markup_error.rs @@ -0,0 +1,56 @@ + +use roc_utils::util_error::UtilError; +use snafu::{Backtrace, NoneError, Snafu, ResultExt}; + +use crate::slow_pool::MarkNodeId; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum MarkError { + #[snafu(display( + "CaretNotFound: No carets were found in the expected node with id {}", + node_id + ))] + CaretNotFound { + node_id: MarkNodeId, + backtrace: Backtrace, + }, + #[snafu(display( + "ExpectedTextNode: the function {} expected a Text node, got {} instead.", + function_name, + node_type + ))] + ExpectedTextNode { + function_name: String, + node_type: String, + backtrace: Backtrace, + }, + #[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))] + NestedNodeMissingChild { + node_id: MarkNodeId, + children_ids: Vec, + backtrace: Backtrace, + }, + #[snafu(display( + "NestedNodeRequired: required a Nested node at this position, node was a {}.", + node_type + ))] + NestedNodeRequired { + node_type: String, + backtrace: Backtrace, + }, + #[snafu(display("UIError: {}", msg))] + UtilErrorBacktrace { msg: String, backtrace: Backtrace }, +} + +pub type MarkResult = std::result::Result; + +impl From for MarkError { + fn from(util_err: UtilError) -> Self { + let msg = format!("{}", util_err); + + // hack to handle MarkError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(UtilErrorBacktrace { msg }).unwrap_err() + } +} \ No newline at end of file diff --git a/code_markup/src/slow_pool.rs b/code_markup/src/slow_pool.rs new file mode 100644 index 0000000000..df8335b570 --- /dev/null +++ b/code_markup/src/slow_pool.rs @@ -0,0 +1,76 @@ + +use std::fmt; + +use crate::markup::nodes::MarkupNode; + +pub type MarkNodeId = usize; + +#[derive(Debug)] +pub struct SlowPool { + nodes: Vec, +} + +impl SlowPool { + pub fn new() -> SlowPool { + SlowPool { nodes: Vec::new() } + } + + pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { + let id = self.nodes.len(); + + self.nodes.push(node); + + id + } + + pub fn get(&self, node_id: MarkNodeId) -> &MarkupNode { + // unwrap because Pool doesn't return Result either + self.nodes.get(node_id).unwrap() + } + + pub fn get_mut(&mut self, node_id: MarkNodeId) -> &mut MarkupNode { + // unwrap because Pool doesn't return Result either + self.nodes.get_mut(node_id).unwrap() + } + + pub fn replace_node(&mut self, node_id: MarkNodeId, new_node: MarkupNode) { + self.nodes[node_id] = new_node; + + // TODO delete children of old node, this requires SlowPool to be changed to + // make sure the indexes still make sense after removal/compaction + } +} + +impl fmt::Display for SlowPool { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\n\n(mark_node_pool)\n")?; + + for (index, node) in self.nodes.iter().enumerate() { + let ast_node_id_str = format!("{:?}", node.get_ast_node_id()); + let ast_node_id: String = ast_node_id_str + .chars() + .filter(|c| c.is_ascii_digit()) + .collect(); + + let mut child_str = String::new(); + + let node_children = node.get_children_ids(); + + if !node_children.is_empty() { + child_str = format!("children: {:?}", node_children); + } + + writeln!( + f, + "{}: {} ({}) ast_id {:?} {}", + index, + node.node_type_as_string(), + node.get_content(), + ast_node_id.parse::().unwrap(), + child_str + )?; + } + + Ok(()) + } +} diff --git a/code_markup/src/syntax_highlight.rs b/code_markup/src/syntax_highlight.rs new file mode 100644 index 0000000000..2f5fcf6865 --- /dev/null +++ b/code_markup/src/syntax_highlight.rs @@ -0,0 +1,48 @@ + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::colors::{self, RgbaTup, from_hsb}; + +#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] +pub enum HighlightStyle { + Operator, // =+-<>... + String, + FunctionName, + Type, + Bracket, + Number, + PackageRelated, // app, packages, imports, exposes, provides... + Variable, + RecordField, + Import, + Provides, + Blank, +} + +pub fn default_highlight_map() -> HashMap { + use HighlightStyle::*; + + let mut highlight_map = HashMap::new(); + [ + (Operator, colors::WHITE), + (String, from_hsb(346, 65, 97)), + (FunctionName, colors::WHITE), + (Type, colors::WHITE), + (Bracket, from_hsb(347, 80, 100)), + (Number, from_hsb(185, 50, 75)), + (PackageRelated, colors::WHITE), + (Variable, colors::WHITE), + (RecordField, from_hsb(258, 50, 90)), + (Import, from_hsb(185, 50, 75)), + (Provides, from_hsb(185, 50, 75)), + (Blank, from_hsb(258, 50, 90)), + // comment from_hsb(285, 6, 47) or 186, 35, 40 + ] + .iter() + .for_each(|tup| { + highlight_map.insert(tup.0, tup.1); + }); + + highlight_map +} diff --git a/editor/src/editor/slow_pool.rs b/editor/src/editor/slow_pool.rs index 72dc43336c..01058e9668 100644 --- a/editor/src/editor/slow_pool.rs +++ b/editor/src/editor/slow_pool.rs @@ -1,6 +1,9 @@ -use crate::editor::markup::nodes::MarkupNode; + use std::fmt; +use super::markup::nodes::MarkupNode; + + pub type MarkNodeId = usize; #[derive(Debug)] diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000000..75c3f928a4 --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "roc_utils" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Utility functions used all over the code base." + +[dependencies] +snafu = { version = "0.6", features = ["backtraces"] } + +[dev-dependencies] \ No newline at end of file diff --git a/utils/src/lib.rs b/utils/src/lib.rs new file mode 100644 index 0000000000..d9402be3e5 --- /dev/null +++ b/utils/src/lib.rs @@ -0,0 +1,96 @@ + +use snafu::OptionExt; +use util_error::{UtilResult, KeyNotFound, IndexOfFailed, OutOfBounds}; +use std::{collections::HashMap, slice::SliceIndex}; + +pub mod util_error; + +// replace HashMap method that returns Option with one that returns Result and proper Error +pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( + hash_map: &'a HashMap, + key: &K, +) -> UtilResult<&'a V> { + let value = hash_map.get(key).context(KeyNotFound { + key_str: format!("{:?}", key), + })?; + + Ok(value) +} + +pub fn index_of(elt: T, slice: &[T]) -> UtilResult { + let index = slice + .iter() + .position(|slice_elt| *slice_elt == elt) + .with_context(|| { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailed { + elt_str, + collection_str, + } + })?; + + Ok(index) +} + +// replace slice method that return Option with one that return Result and proper Error +pub fn slice_get(index: usize, slice: &[T]) -> UtilResult<&>::Output> { + let elt_ref = slice.get(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: slice.len(), + })?; + + Ok(elt_ref) +} + +pub fn slice_get_mut( + index: usize, + slice: &mut [T], +) -> UtilResult<&mut >::Output> { + let slice_len = slice.len(); + + let elt_ref = slice.get_mut(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: slice_len, + })?; + + Ok(elt_ref) +} + +// returns the index of the first occurrence of element and index of the last occurrence +pub fn first_last_index_of( + elt: T, + slice: &[T], +) -> UtilResult<(usize, usize)> { + let mut first_index_opt = None; + let mut last_index_opt = None; + + for (index, list_elt) in slice.iter().enumerate() { + if *list_elt == elt { + if first_index_opt.is_none() { + first_index_opt = Some(index); + last_index_opt = Some(index); + } else { + last_index_opt = Some(index) + } + } else if last_index_opt.is_some() { + break; + } + } + + if let (Some(first_index), Some(last_index)) = (first_index_opt, last_index_opt) { + Ok((first_index, last_index)) + } else { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailed { + elt_str, + collection_str, + } + .fail() + } +} diff --git a/utils/src/util_error.rs b/utils/src/util_error.rs new file mode 100644 index 0000000000..fda0c1a565 --- /dev/null +++ b/utils/src/util_error.rs @@ -0,0 +1,36 @@ + +use snafu::{Backtrace, Snafu}; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum UtilError { + #[snafu(display( + "IndexOfFailed: Element {} was not found in collection {}.", + elt_str, + collection_str + ))] + IndexOfFailed { + elt_str: String, + collection_str: String, + backtrace: Backtrace, + }, + #[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))] + KeyNotFound { + key_str: String, + backtrace: Backtrace, + }, + #[snafu(display( + "OutOfBounds: index {} was out of bounds for {} with length {}.", + index, + collection_name, + len + ))] + OutOfBounds { + index: usize, + collection_name: String, + len: usize, + backtrace: Backtrace, + }, +} + +pub type UtilResult = std::result::Result; \ No newline at end of file From 69391b26d7d76e908b1d95814be74cc12fe83ba8 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 17:14:32 +0200 Subject: [PATCH 05/12] removed lang and markup folder from editors, changed imports to use new crates --- Cargo.lock | 5 + ast/Cargo.toml | 5 +- ast/src/ast_error.rs | 10 + ast/src/constrain.rs | 362 +++++ ast/src/lang/core/ast.rs | 11 +- ast/src/lang/core/def/def2.rs | 27 +- ast/src/lang/core/def/mod.rs | 2 +- ast/src/lang/core/mod.rs | 2 +- ast/src/lang/core/str.rs | 61 +- ast/src/lib.rs | 6 +- ast/src/mod.rs | 2 +- ast/src/parse/mod.rs | 3 + ast/src/parse/parse_ast.rs | 10 +- ast/src/parse/parse_header.rs | 2 + ast/src/solve_type.rs | 12 +- editor/Cargo.toml | 2 + editor/src/editor/ed_error.rs | 42 +- editor/src/editor/grid_node_map.rs | 11 +- editor/src/editor/main.rs | 4 +- editor/src/editor/markup/attribute.rs | 123 -- editor/src/editor/markup/common_nodes.rs | 107 -- editor/src/editor/markup/mod.rs | 3 - editor/src/editor/markup/nodes.rs | 876 ----------- editor/src/editor/mod.rs | 3 - editor/src/editor/mvc/break_line.rs | 7 +- editor/src/editor/mvc/ed_model.rs | 33 +- editor/src/editor/mvc/ed_update.rs | 36 +- editor/src/editor/mvc/ed_view.rs | 2 +- editor/src/editor/mvc/int_update.rs | 20 +- editor/src/editor/mvc/let_update.rs | 17 +- editor/src/editor/mvc/list_update.rs | 17 +- editor/src/editor/mvc/lookup_update.rs | 7 +- editor/src/editor/mvc/record_update.rs | 25 +- editor/src/editor/mvc/string_update.rs | 17 +- editor/src/editor/mvc/tld_value_update.rs | 16 +- editor/src/editor/render_ast.rs | 6 +- editor/src/editor/render_debug.rs | 4 +- editor/src/editor/slow_pool.rs | 77 - editor/src/editor/syntax_highlight.rs | 47 - editor/src/editor/theme.rs | 2 +- editor/src/lang/ast.rs | 742 --------- editor/src/lang/constrain.rs | 1746 -------------------- editor/src/lang/def.rs | 1444 ----------------- editor/src/lang/expr.rs | 1555 ------------------ editor/src/lang/mod.rs | 12 - editor/src/lang/module.rs | 318 ---- editor/src/lang/parse.rs | 98 -- editor/src/lang/pattern.rs | 619 -------- editor/src/lang/pool.rs | 635 -------- editor/src/lang/roc_file.rs | 133 -- editor/src/lang/scope.rs | 325 ---- editor/src/lang/solve.rs | 1752 --------------------- editor/src/lang/types.rs | 863 ---------- editor/src/lib.rs | 1 - editor/tests/solve_expr2.rs | 372 ----- 55 files changed, 653 insertions(+), 11986 deletions(-) create mode 100644 ast/src/parse/mod.rs delete mode 100644 editor/src/editor/markup/attribute.rs delete mode 100644 editor/src/editor/markup/common_nodes.rs delete mode 100644 editor/src/editor/markup/mod.rs delete mode 100644 editor/src/editor/markup/nodes.rs delete mode 100644 editor/src/editor/slow_pool.rs delete mode 100644 editor/src/editor/syntax_highlight.rs delete mode 100644 editor/src/lang/ast.rs delete mode 100644 editor/src/lang/constrain.rs delete mode 100644 editor/src/lang/def.rs delete mode 100644 editor/src/lang/expr.rs delete mode 100644 editor/src/lang/mod.rs delete mode 100644 editor/src/lang/module.rs delete mode 100644 editor/src/lang/parse.rs delete mode 100644 editor/src/lang/pattern.rs delete mode 100644 editor/src/lang/pool.rs delete mode 100644 editor/src/lang/roc_file.rs delete mode 100644 editor/src/lang/scope.rs delete mode 100644 editor/src/lang/solve.rs delete mode 100644 editor/src/lang/types.rs delete mode 100644 editor/tests/solve_expr2.rs diff --git a/Cargo.lock b/Cargo.lock index 3fdcc1094b..d54543d19f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3393,8 +3393,10 @@ version = "0.1.0" dependencies = [ "arraystring", "bumpalo", + "indoc 1.0.3", "libc", "page_size", + "pretty_assertions 0.6.1", "roc_can", "roc_collections", "roc_module", @@ -3402,6 +3404,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_types", + "roc_unify", "snafu", "ven_graph", ] @@ -3619,8 +3622,10 @@ dependencies = [ "quickcheck 1.0.3", "quickcheck_macros 1.0.0", "rand 0.8.4", + "roc_ast", "roc_builtins", "roc_can", + "roc_code_markup", "roc_collections", "roc_fmt", "roc_load", diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 76d1658885..39bf52a471 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -14,11 +14,14 @@ roc_module = { path = "../compiler/module" } roc_parse = { path = "../compiler/parse" } roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } +roc_unify = { path = "../compiler/unify"} arraystring = "0.3.0" bumpalo = { version = "3.6.1", features = ["collections"] } libc = "0.2" page_size = "0.4" snafu = { version = "0.6", features = ["backtraces"] } ven_graph = { path = "../vendor/pathfinding" } +indoc = "1.0" -[dev-dependencies] \ No newline at end of file +[dev-dependencies] +pretty_assertions = "0.6" diff --git a/ast/src/ast_error.rs b/ast/src/ast_error.rs index b18970e852..cf42c7762d 100644 --- a/ast/src/ast_error.rs +++ b/ast/src/ast_error.rs @@ -15,6 +15,16 @@ pub enum ASTError { ast_node_id: ASTNodeId, backtrace: Backtrace, }, + #[snafu(display( + "UnexpectedASTNode: required a {} at this position, node was a {}.", + required_node_type, + encountered_node_type + ))] + UnexpectedASTNode { + required_node_type: String, + encountered_node_type: String, + backtrace: Backtrace, + }, #[snafu(display( "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", required_pattern2, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 07c7ba8480..48a1d0cb8c 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1738,3 +1738,365 @@ fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { pool.add(alias_content), ) } + +pub mod test_constrain { + use bumpalo::Bump; + use roc_can::expected::Expected; + use roc_collections::all::MutMap; + use roc_module::{ident::Lowercase, symbol::{IdentIds, Interns, ModuleIds, Symbol}}; + use roc_region::all::Region; + use roc_types::{pretty_print::content_to_string, solved_types::Solved, subs::{Subs, VarStore, Variable}}; + + use crate::{constrain::constrain_expr, lang::{core::{expr::expr_to_expr2::str_to_expr2, types::Type2}, env::Env, scope::Scope}, pool::pool::Pool, solve_type}; + use super::Constraint; + use indoc::indoc; + + + fn run_solve<'a>( + arena: &'a Bump, + mempool: &mut Pool, + aliases: MutMap, + rigid_variables: MutMap, + constraint: Constraint, + var_store: VarStore, + ) -> (Solved, solve_type::Env, Vec) { + let env = solve_type::Env { + vars_by_symbol: MutMap::default(), + aliases, + }; + + let mut subs = Subs::new(var_store); + + for (var, name) in rigid_variables { + subs.rigid_var(var, name); + } + + // Now that the module is parsed, canonicalized, and constrained, + // we need to type check it. + let mut problems = Vec::new(); + + // Run the solver to populate Subs. + let (solved_subs, solved_env) = + solve_type::run(arena, mempool, &env, &mut problems, subs, &constraint); + + (solved_subs, solved_env, problems) + } + + fn infer_eq(actual: &str, expected_str: &str) { + let mut env_pool = Pool::with_capacity(1024); + let env_arena = Bump::new(); + let code_arena = Bump::new(); + + let mut var_store = VarStore::default(); + let var = var_store.fresh(); + let dep_idents = IdentIds::exposed_builtins(8); + let exposed_ident_ids = IdentIds::default(); + let mut module_ids = ModuleIds::default(); + let mod_id = module_ids.get_or_insert(&"ModId123".into()); + + let mut env = Env::new( + mod_id, + &env_arena, + &mut env_pool, + &mut var_store, + dep_idents, + &module_ids, + exposed_ident_ids, + ); + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + + let region = Region::zero(); + + let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); + + match expr2_result { + Ok((expr, _)) => { + let constraint = constrain_expr( + &code_arena, + &mut env, + &expr, + Expected::NoExpectation(Type2::Variable(var)), + Region::zero(), + ); + + let Env { + pool, + var_store: ref_var_store, + mut dep_idents, + .. + } = env; + + // extract the var_store out of the env again + let mut var_store = VarStore::default(); + std::mem::swap(ref_var_store, &mut var_store); + + let (mut solved, _, _) = run_solve( + &code_arena, + pool, + Default::default(), + Default::default(), + constraint, + var_store, + ); + + let subs = solved.inner_mut(); + + let content = subs.get_content_without_compacting(var); + + // Connect the ModuleId to it's IdentIds + dep_idents.insert(mod_id, env.ident_ids); + + let interns = Interns { + module_ids: env.module_ids.clone(), + all_ident_ids: dep_idents, + }; + + let actual_str = content_to_string(content, subs, mod_id, &interns); + + assert_eq!(actual_str, expected_str); + } + Err(e) => panic!("syntax error {:?}", e), + } + } + + #[test] + fn constrain_str() { + infer_eq( + indoc!( + r#" + "type inference!" + "# + ), + "Str", + ) + } + + // This will be more useful once we actually map + // strings less than 15 chars to SmallStr + #[test] + fn constrain_small_str() { + infer_eq( + indoc!( + r#" + "a" + "# + ), + "Str", + ) + } + + #[test] + fn constrain_empty_record() { + infer_eq( + indoc!( + r#" + {} + "# + ), + "{}", + ) + } + + #[test] + fn constrain_small_int() { + infer_eq( + indoc!( + r#" + 12 + "# + ), + "Num *", + ) + } + + #[test] + fn constrain_float() { + infer_eq( + indoc!( + r#" + 3.14 + "# + ), + "Float *", + ) + } + + #[test] + fn constrain_record() { + infer_eq( + indoc!( + r#" + { x : 1, y : "hi" } + "# + ), + "{ x : Num *, y : Str }", + ) + } + + #[test] + fn constrain_empty_list() { + infer_eq( + indoc!( + r#" + [] + "# + ), + "List *", + ) + } + + #[test] + fn constrain_list() { + infer_eq( + indoc!( + r#" + [ 1, 2 ] + "# + ), + "List (Num *)", + ) + } + + #[test] + fn constrain_list_of_records() { + infer_eq( + indoc!( + r#" + [ { x: 1 }, { x: 3 } ] + "# + ), + "List { x : Num * }", + ) + } + + #[test] + fn constrain_global_tag() { + infer_eq( + indoc!( + r#" + Foo + "# + ), + "[ Foo ]*", + ) + } + + #[test] + fn constrain_private_tag() { + infer_eq( + indoc!( + r#" + @Foo + "# + ), + "[ @Foo ]*", + ) + } + + #[test] + fn constrain_call_and_accessor() { + infer_eq( + indoc!( + r#" + .foo { foo: "bar" } + "# + ), + "Str", + ) + } + + #[test] + fn constrain_access() { + infer_eq( + indoc!( + r#" + { foo: "bar" }.foo + "# + ), + "Str", + ) + } + + #[test] + fn constrain_if() { + infer_eq( + indoc!( + r#" + if True then Green else Red + "# + ), + "[ Green, Red ]*", + ) + } + + #[test] + fn constrain_when() { + infer_eq( + indoc!( + r#" + when if True then Green else Red is + Green -> Blue + Red -> Purple + "# + ), + "[ Blue, Purple ]*", + ) + } + + #[test] + fn constrain_let_value() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + person + "# + ), + "{ name : Str }", + ) + } + + #[test] + fn constrain_update() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + { person & name: "bird" } + "# + ), + "{ name : Str }", + ) + } + + #[ignore = "TODO: implement builtins in the editor"] + #[test] + fn constrain_run_low_level() { + infer_eq( + indoc!( + r#" + List.map [ { name: "roc" }, { name: "bird" } ] .name + "# + ), + "List Str", + ) + } + + #[test] + fn constrain_closure() { + infer_eq( + indoc!( + r#" + x = 1 + + \{} -> x + "# + ), + "{}* -> Num *", + ) + } + +} diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs index 2cf25730b9..12241c567b 100644 --- a/ast/src/lang/core/ast.rs +++ b/ast/src/lang/core/ast.rs @@ -1,6 +1,6 @@ -use crate::ast_error::{ASTResult, ASTNodeIdWithoutExprId}; +use crate::{ast_error::{ASTResult, ASTNodeIdWithoutExprId}, pool::pool::Pool}; -use super::{def::def2::DefId, expr::expr2::ExprId, header::AppHeader}; +use super::{def::def2::{DefId, def2_to_string}, expr::{expr2::ExprId, expr2_to_string::expr2_to_string}, header::AppHeader}; #[derive(Debug)] pub struct AST { @@ -28,4 +28,11 @@ impl ASTNodeId { _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, } } +} + +pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { + match node_id { + ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), + ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), + } } \ No newline at end of file diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs index 578a831d62..935bfe6609 100644 --- a/ast/src/lang/core/def/def2.rs +++ b/ast/src/lang/core/def/def2.rs @@ -1,4 +1,4 @@ -use crate::{lang::core::{expr::expr2::Expr2, pattern::Pattern2}, pool::pool::NodeId}; +use crate::{lang::core::{expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, pattern::Pattern2}, pool::pool::{NodeId, Pool}}; // A top level definition, not inside a function. For example: `main = "Hello, world!"` @@ -12,4 +12,27 @@ pub enum Def2 { Blank, } -pub type DefId = NodeId; \ No newline at end of file +pub type DefId = NodeId; + +pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { + let mut full_string = String::new(); + let def2 = pool.get(node_id); + + match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + full_string.push_str(&format!( + "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", + pool.get(*identifier_id), + expr2_to_string(*expr_id, pool) + )); + } + Def2::Blank => { + full_string.push_str("Def2::Blank"); + } + } + + full_string +} \ No newline at end of file diff --git a/ast/src/lang/core/def/mod.rs b/ast/src/lang/core/def/mod.rs index 0f62252832..d790ec3ea5 100644 --- a/ast/src/lang/core/def/mod.rs +++ b/ast/src/lang/core/def/mod.rs @@ -1,3 +1,3 @@ -mod def_to_def2; +pub mod def_to_def2; pub mod def; pub mod def2; \ No newline at end of file diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs index 2ee105850f..41e852d20f 100644 --- a/ast/src/lang/core/mod.rs +++ b/ast/src/lang/core/mod.rs @@ -6,5 +6,5 @@ pub mod val_def; mod fun_def; pub mod pattern; pub mod types; -mod str; +pub mod str; mod declaration; \ No newline at end of file diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index bc203c0753..5e94ad76e3 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -1,9 +1,9 @@ use roc_module::{operator::CalledVia, symbol::Symbol}; use roc_parse::ast::StrLiteral; -use crate::{lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{ast_error::{ASTResult, UnexpectedASTNode}, lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}}; -use super::expr::{expr2::Expr2, output::Output}; +use super::expr::{expr2::{Expr2, ExprId}, output::Output}; pub (crate) fn flatten_str_literal<'a>( env: &mut Env<'a>, @@ -161,4 +161,61 @@ fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Exp } expr +} + +pub fn update_str_expr( + node_id: ExprId, + new_char: char, + insert_index: usize, + pool: &mut Pool, +) -> ASTResult<()> { + let str_expr = pool.get_mut(node_id); + + enum Either { + MyString(String), + MyPoolStr(PoolStr), + Done, + } + + let insert_either = match str_expr { + Expr2::SmallStr(arr_string) => { + let insert_res = arr_string.try_insert(insert_index as u8, new_char); + + match insert_res { + Ok(_) => Either::Done, + _ => { + let mut new_string = arr_string.as_str().to_string(); + new_string.insert(insert_index, new_char); + + Either::MyString(new_string) + } + } + } + Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), + other => UnexpectedASTNode { + required_node_type: "SmallStr or Str", + encountered_node_type: format!("{:?}", other), + } + .fail()?, + }; + + match insert_either { + Either::MyString(new_string) => { + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::MyPoolStr(old_pool_str) => { + let mut new_string = old_pool_str.as_str(pool).to_owned(); + + new_string.insert(insert_index, new_char); + + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::Done => (), + } + + Ok(()) } \ No newline at end of file diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 02cf199e70..55191171a8 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -1,5 +1,7 @@ pub mod lang; pub mod pool; -mod constrain; +pub mod parse; +pub mod constrain; mod canonicalize; -pub mod ast_error; \ No newline at end of file +pub mod ast_error; +pub mod solve_type; \ No newline at end of file diff --git a/ast/src/mod.rs b/ast/src/mod.rs index 273c344ee5..f9d51e4b1f 100644 --- a/ast/src/mod.rs +++ b/ast/src/mod.rs @@ -7,7 +7,7 @@ mod pattern; pub mod pool; pub mod roc_file; mod scope; -mod solve; +pub mod solve; mod types; mod rigids; mod canonicalize; diff --git a/ast/src/parse/mod.rs b/ast/src/parse/mod.rs new file mode 100644 index 0000000000..095e190b43 --- /dev/null +++ b/ast/src/parse/mod.rs @@ -0,0 +1,3 @@ +pub mod parse_ast; +pub mod parse_expr; +pub mod parse_header; \ No newline at end of file diff --git a/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs index 2f99b8a071..55ea683966 100644 --- a/ast/src/parse/parse_ast.rs +++ b/ast/src/parse/parse_ast.rs @@ -1,3 +1,11 @@ +use bumpalo::Bump; +use roc_parse::parser::SyntaxError; +use roc_region::all::Region; + +use crate::lang::{core::{ast::AST, def::{def2::DefId, def_to_def2::str_to_def2}, expr::expr2::Expr2}, env::Env, scope::Scope}; + +use super::parse_header; + pub fn parse_from_string<'a>( @@ -28,7 +36,7 @@ pub fn parse_from_string<'a>( let ast_node_id = env.pool.add(Expr2::Blank); Ok(AST { - header: AppHeader::parse_from_string(header_str, ast_node_id), + header: parse_header::parse_from_string(header_str, ast_node_id), def_ids, }) } \ No newline at end of file diff --git a/ast/src/parse/parse_header.rs b/ast/src/parse/parse_header.rs index 0b9f2016f9..6287806656 100644 --- a/ast/src/parse/parse_header.rs +++ b/ast/src/parse/parse_header.rs @@ -1,3 +1,5 @@ +use crate::lang::core::{expr::expr2::ExprId, header::AppHeader}; + // TODO don't use mock struct and actually parse string pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 68b29c7635..7c89b3440a 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -1,8 +1,5 @@ #![allow(clippy::all)] #![allow(dead_code)] -use crate::lang::constrain::Constraint::{self, *}; -use crate::lang::pool::{Pool, PoolVec, ShallowClone}; -use crate::lang::types::Type2; use bumpalo::Bump; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; @@ -20,6 +17,12 @@ use roc_types::types::{ use roc_unify::unify::unify; use roc_unify::unify::Unified::*; +use crate::constrain::Constraint; +use crate::lang::core::types::Type2; +use crate::pool::pool::Pool; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; + // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler // Thank you, Evan! @@ -197,6 +200,9 @@ fn solve<'a>( subs: &mut Subs, constraint: &Constraint, ) -> State { + + use crate::solve_type::Constraint::*; + match constraint { True => state, // SaveTheEnvironment => { diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 40ebc820bd..f825b7dbc5 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -7,10 +7,12 @@ edition = "2018" description = "An editor for Roc" [dependencies] +roc_ast = { path = "../ast" } roc_collections = { path = "../compiler/collections" } roc_load = { path = "../compiler/load" } roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } +roc_code_markup = { path = "../code_markup"} roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index b838fdef42..e79e90e23e 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,7 +1,10 @@ -use crate::lang::parse::ASTNodeId; -use crate::ui::ui_error::UIResult; -use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; + +use crate::{ui::text::text_pos::TextPos}; use colored::*; +use roc_ast::ast_error::ASTError; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_code_markup::markup_error::MarkError; +use roc_code_markup::slow_pool::MarkNodeId; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; //import errors as follows: @@ -212,8 +215,12 @@ pub enum EdError { #[snafu(display("StringParseError: {}", msg))] StringParseError { msg: String, backtrace: Backtrace }, + #[snafu(display("ASTError: {}", msg))] + ASTErrorBacktrace { msg: String, backtrace: Backtrace }, #[snafu(display("UIError: {}", msg))] UIErrorBacktrace { msg: String, backtrace: Backtrace }, + #[snafu(display("MarkError: {}", msg))] + MarkErrorBacktrace { msg: String, backtrace: Backtrace }, } pub type EdResult = std::result::Result; @@ -226,14 +233,6 @@ pub fn print_err(err: &EdError) { } } -/*pub fn print_ui_err(err: &UIError) { - eprintln!("{}", format!("{}", err).truecolor(255, 0, 0)); - - if let Some(backtrace) = ErrorCompat::backtrace(err) { - eprintln!("{}", color_backtrace(backtrace)); - } -}*/ - fn color_backtrace(backtrace: &snafu::Backtrace) -> String { let backtrace_str = format!("{}", backtrace); let backtrace_split = backtrace_str.split('\n'); @@ -293,9 +292,22 @@ impl From for EdError { } } -pub fn from_ui_res(ui_res: UIResult) -> EdResult { - match ui_res { - Ok(t) => Ok(t), - Err(ui_err) => Err(EdError::from(ui_err)), +impl From for EdError { + fn from(mark_err: MarkError) -> Self { + let msg = format!("{}", mark_err); + + // hack to handle EdError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(MarkErrorBacktrace { msg }).unwrap_err() } } + +impl From for EdError { + fn from(ast_err: ASTError) -> Self { + let msg = format!("{}", ast_err); + + // hack to handle EdError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(ASTErrorBacktrace { msg }).unwrap_err() + } +} \ No newline at end of file diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index 2cece54f43..38355aacc2 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -2,20 +2,19 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::NestedNodeWithoutChildren; use crate::editor::ed_error::{NoDefMarkNodeBeforeLineNr, NodeIdNotInGridNodeMap}; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; use crate::editor::util::first_last_index_of; use crate::editor::util::index_of; -use crate::lang::parse::ASTNodeId; use crate::ui::text::selection::Selection; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::{slice_get, slice_get_mut}; -use snafu::OptionExt; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_code_markup::markup::nodes::get_root_mark_node_id; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::slow_pool::SlowPool; use std::cmp::Ordering; use std::fmt; - -use super::markup::nodes::get_root_mark_node_id; +use snafu::OptionExt; #[derive(Debug)] pub struct GridNodeMap { diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 9ab70d32ba..a3cf2d3fe7 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -17,14 +17,14 @@ use crate::graphics::{ primitives::rect::Rect, primitives::text::{build_glyph_brush, example_code_glyph_rect, queue_text_draw, Text}, }; -use crate::lang::expr::Env; -use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::CaretPos; use crate::ui::util::path_to_string; use bumpalo::Bump; use cgmath::Vector2; use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use pipelines::RectResources; +use roc_ast::lang::env::Env; +use roc_ast::pool::pool::Pool; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load; diff --git a/editor/src/editor/markup/attribute.rs b/editor/src/editor/markup/attribute.rs deleted file mode 100644 index 304c4aadce..0000000000 --- a/editor/src/editor/markup/attribute.rs +++ /dev/null @@ -1,123 +0,0 @@ -#![allow(dead_code)] - -use crate::editor::ed_error::{CaretNotFound, EdResult}; -use snafu::ensure; - -#[derive(Debug, Copy, Clone)] -pub struct Caret { - pub offset_col: usize, -} - -impl Caret { - pub fn new_attr(offset_col: usize) -> Attribute { - Attribute::Caret { - caret: Caret { offset_col }, - } - } -} -#[derive(Debug)] -pub struct SelectionStart { - offset_col: usize, -} -#[derive(Debug)] -pub struct SelectionEnd { - offset_col: usize, -} - -// Highlight is used for example when searching for a specific string to highlight all search results in the module -#[derive(Debug)] -pub struct HighlightStart { - offset_col: usize, -} -#[derive(Debug)] -pub struct HighlightEnd { - offset_col: usize, -} - -// Underline is used for warnings and errors -#[derive(Debug)] -pub struct UnderlineStart { - offset_col: usize, -} -#[derive(Debug)] -pub struct UnderlineEnd { - offset_col: usize, -} - -#[derive(Debug)] -pub enum Attribute { - // Rust does not yet support types for enum variants so we have to do it like this - Caret { caret: Caret }, - - SelectionStart { selection_start: SelectionStart }, - SelectionEnd { selection_end: SelectionEnd }, - - HighlightStart { highlight_start: HighlightStart }, - HighlightEnd { highlight_end: HighlightEnd }, - - UnderlineStart { underline_start: UnderlineStart }, - UnderlineEnd { underline_end: UnderlineEnd }, -} - -#[derive(Debug)] -pub struct Attributes { - pub all: Vec, -} - -impl Attributes { - pub fn new() -> Attributes { - Attributes { all: Vec::new() } - } - - pub fn add(&mut self, attr: Attribute) { - self.all.push(attr); - } - - pub fn add_caret(&mut self, offset_col: usize) { - self.all.push(Attribute::Caret { - caret: Caret { offset_col }, - }); - } - - pub fn get_mut_carets(&mut self) -> Vec<&mut Caret> { - let mut carets = Vec::new(); - - for attr in self.all.iter_mut() { - if let Attribute::Caret { caret } = attr { - carets.push(caret) - } - } - - carets - } - - pub fn get_carets(&self) -> Vec { - let mut carets = Vec::new(); - - for attr in self.all.iter() { - if let Attribute::Caret { caret } = attr { - carets.push(*caret) - } - } - - carets - } - - pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> EdResult<()> { - let old_len = self.all.len(); - - self.all.retain(|attr| { - if let Attribute::Caret { caret } = attr { - caret.offset_col != offset_col - } else { - true - } - }); - - let new_len = self.all.len(); - - ensure!(old_len != new_len, CaretNotFound { node_id }); - - Ok(()) - } -} diff --git a/editor/src/editor/markup/common_nodes.rs b/editor/src/editor/markup/common_nodes.rs deleted file mode 100644 index f74648f3fa..0000000000 --- a/editor/src/editor/markup/common_nodes.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::{ - editor::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}, - lang::{ast::ExprId, parse::ASTNodeId}, -}; - -use super::{attribute::Attributes, nodes, nodes::MarkupNode}; - -pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::EQUALS.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::COMMA.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Blank { - ast_node_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_blank_mn_w_nls( - ast_node_id: ASTNodeId, - parent_id_opt: Option, - nr_of_newlines: usize, -) -> MarkupNode { - MarkupNode::Blank { - ast_node_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: nr_of_newlines, - } -} - -pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::COLON.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::LEFT_ACCOLADE.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::RIGHT_ACCOLADE.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::LEFT_SQUARE_BR.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::RIGHT_SQUARE_BR.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} diff --git a/editor/src/editor/markup/mod.rs b/editor/src/editor/markup/mod.rs deleted file mode 100644 index de015ebd80..0000000000 --- a/editor/src/editor/markup/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod attribute; -pub mod common_nodes; -pub mod nodes; diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs deleted file mode 100644 index 00b5358e88..0000000000 --- a/editor/src/editor/markup/nodes.rs +++ /dev/null @@ -1,876 +0,0 @@ -use super::attribute::Attributes; -use crate::editor::ed_error::EdResult; -use crate::editor::ed_error::ExpectedTextNode; -use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_colon_mn; -use crate::editor::markup::common_nodes::new_comma_mn; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_left_square_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::common_nodes::new_right_square_mn; -use crate::editor::mvc::tld_value_update::tld_mark_node; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::ast::DefId; -use crate::lang::ast::ExprId; -use crate::lang::ast::RecordField; -use crate::lang::ast::ValueDef; -use crate::lang::parse::ASTNodeId; -use crate::lang::parse::{AppHeader, AST}; -use crate::lang::pattern::get_identifier_string; -use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; -use crate::ui::util::slice_get; -use bumpalo::Bump; -use roc_module::symbol::Interns; -use std::fmt; - -#[derive(Debug)] -pub enum MarkupNode { - Nested { - ast_node_id: ASTNodeId, - children_ids: Vec, - parent_id_opt: Option, - newlines_at_end: usize, - }, - Text { - content: String, - ast_node_id: ASTNodeId, - syn_high_style: HighlightStyle, - attributes: Attributes, - parent_id_opt: Option, - newlines_at_end: usize, - }, - Blank { - ast_node_id: ASTNodeId, - attributes: Attributes, - syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank - parent_id_opt: Option, - newlines_at_end: usize, - }, -} - -impl MarkupNode { - pub fn get_ast_node_id(&self) -> ASTNodeId { - match self { - MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, - MarkupNode::Text { ast_node_id, .. } => *ast_node_id, - MarkupNode::Blank { ast_node_id, .. } => *ast_node_id, - } - } - - pub fn get_parent_id_opt(&self) -> Option { - match self { - MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt, - MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt, - MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt, - } - } - - pub fn get_children_ids(&self) -> Vec { - match self { - MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(), - MarkupNode::Text { .. } => vec![], - MarkupNode::Blank { .. } => vec![], - } - } - - pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec { - if let Some(parent_id) = self.get_parent_id_opt() { - let parent_node = mark_node_pool.get(parent_id); - - parent_node.get_children_ids() - } else { - vec![] - } - } - - // return (index of child in list of children, closest ast index of child corresponding to ast node) - pub fn get_child_indices( - &self, - child_id: MarkNodeId, - mark_node_pool: &SlowPool, - ) -> EdResult<(usize, usize)> { - match self { - MarkupNode::Nested { children_ids, .. } => { - let mut mark_child_index_opt: Option = None; - let mut child_ids_with_ast: Vec = Vec::new(); - let self_ast_id = self.get_ast_node_id(); - - for (indx, &mark_child_id) in children_ids.iter().enumerate() { - if mark_child_id == child_id { - mark_child_index_opt = Some(indx); - } - - let child_mark_node = mark_node_pool.get(mark_child_id); - // a node that points to the same ast_node as the parent is a ',', '[', ']' - // those are not "real" ast children - if child_mark_node.get_ast_node_id() != self_ast_id { - child_ids_with_ast.push(mark_child_id) - } - } - - if let Some(child_index) = mark_child_index_opt { - if child_index == (children_ids.len() - 1) { - let ast_child_index = child_ids_with_ast.len(); - - Ok((child_index, ast_child_index)) - } else { - // we want to find the index of the closest ast mark node to child_index - let indices_in_mark_res: EdResult> = child_ids_with_ast - .iter() - .map(|c_id| index_of(*c_id, children_ids)) - .collect(); - - let indices_in_mark = indices_in_mark_res?; - - let mut last_diff = usize::MAX; - let mut best_index = 0; - - for index in indices_in_mark.iter() { - let curr_diff = - isize::abs((*index as isize) - (child_index as isize)) as usize; - - if curr_diff >= last_diff { - break; - } else { - last_diff = curr_diff; - best_index = *index; - } - } - - let closest_ast_child = slice_get(best_index, children_ids)?; - - let closest_ast_child_index = - index_of(*closest_ast_child, &child_ids_with_ast)?; - - // +1 because we want to insert after ast_child - Ok((child_index, closest_ast_child_index + 1)) - } - } else { - NestedNodeMissingChild { - node_id: child_id, - children_ids: children_ids.clone(), - } - .fail() - } - } - _ => NestedNodeRequired { - node_type: self.node_type_as_string(), - } - .fail(), - } - } - - pub fn get_content(&self) -> String { - match self { - MarkupNode::Nested { .. } => "".to_owned(), - MarkupNode::Text { content, .. } => content.clone(), - MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), - } - } - - // gets content and adds newline from newline_at_end - pub fn get_full_content(&self) -> String { - let mut full_content = self.get_content(); - - for _ in 0..self.get_newlines_at_end() { - full_content.push('\n') - } - - full_content - } - - pub fn get_content_mut(&mut self) -> EdResult<&mut String> { - match self { - MarkupNode::Nested { .. } => ExpectedTextNode { - function_name: "set_content".to_owned(), - node_type: self.node_type_as_string(), - } - .fail(), - MarkupNode::Text { content, .. } => Ok(content), - MarkupNode::Blank { .. } => ExpectedTextNode { - function_name: "set_content".to_owned(), - node_type: self.node_type_as_string(), - } - .fail(), - } - } - - pub fn is_all_alphanumeric(&self) -> bool { - self.get_content() - .chars() - .all(|chr| chr.is_ascii_alphanumeric()) - } - - pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> { - if let MarkupNode::Nested { children_ids, .. } = self { - children_ids.splice(index..index, vec![child_id]); - } else { - NestedNodeRequired { - node_type: self.node_type_as_string(), - } - .fail()?; - } - - Ok(()) - } - - pub fn node_type_as_string(&self) -> String { - let type_str = match self { - MarkupNode::Nested { .. } => "Nested", - MarkupNode::Text { .. } => "Text", - MarkupNode::Blank { .. } => "Blank", - }; - - type_str.to_owned() - } - - pub fn is_blank(&self) -> bool { - matches!(self, MarkupNode::Blank { .. }) - } - - pub fn is_nested(&self) -> bool { - matches!(self, MarkupNode::Nested { .. }) - } - - pub fn get_newlines_at_end(&self) -> usize { - match self { - MarkupNode::Nested { - newlines_at_end, .. - } => *newlines_at_end, - MarkupNode::Text { - newlines_at_end, .. - } => *newlines_at_end, - MarkupNode::Blank { - newlines_at_end, .. - } => *newlines_at_end, - } - } - - pub fn add_newline_at_end(&mut self) { - match self { - MarkupNode::Nested { - newlines_at_end, .. - } => *newlines_at_end += 1, - MarkupNode::Text { - newlines_at_end, .. - } => *newlines_at_end += 1, - MarkupNode::Blank { - newlines_at_end, .. - } => *newlines_at_end += 1, - } - } -} - -fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { - pool_str.as_str(env.pool).to_owned() -} - -pub const BLANK_PLACEHOLDER: &str = " "; -pub const LEFT_ACCOLADE: &str = "{ "; -pub const RIGHT_ACCOLADE: &str = " }"; -pub const LEFT_SQUARE_BR: &str = "[ "; -pub const RIGHT_SQUARE_BR: &str = " ]"; -pub const COLON: &str = ": "; -pub const COMMA: &str = ", "; -pub const STRING_QUOTES: &str = "\"\""; -pub const EQUALS: &str = " = "; - -fn new_markup_node( - text: String, - node_id: ASTNodeId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> MarkNodeId { - let node = MarkupNode::Text { - content: text, - ast_node_id: node_id, - syn_high_style: highlight_style, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(node) -} - -pub fn def2_to_markup<'a, 'b>( - arena: &'a Bump, - env: &mut Env<'b>, - def2: &Def2, - def2_node_id: DefId, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> EdResult { - let ast_node_id = ASTNodeId::ADefId(def2_node_id); - - let mark_node_id = match def2 { - Def2::ValueDef { - identifier_id, - expr_id, - } => { - let expr_mn_id = expr2_to_markup( - arena, - env, - env.pool.get(*expr_id), - *expr_id, - mark_node_pool, - interns, - )?; - - let tld_mn = tld_mark_node( - *identifier_id, - expr_mn_id, - ast_node_id, - mark_node_pool, - env, - interns, - )?; - - mark_node_pool.add(tld_mn) - } - Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), - }; - - Ok(mark_node_id) -} - -// make Markup Nodes: generate String representation, assign Highlighting Style -pub fn expr2_to_markup<'a, 'b>( - arena: &'a Bump, - env: &mut Env<'b>, - expr2: &Expr2, - expr2_node_id: ExprId, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> EdResult { - let ast_node_id = ASTNodeId::AExprId(expr2_node_id); - - let mark_node_id = match expr2 { - Expr2::SmallInt { text, .. } - | Expr2::I128 { text, .. } - | Expr2::U128 { text, .. } - | Expr2::Float { text, .. } => { - let num_str = get_string(env, text); - - new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) - } - Expr2::Str(text) => new_markup_node( - "\"".to_owned() + text.as_str(env.pool) + "\"", - ast_node_id, - HighlightStyle::String, - mark_node_pool, - ), - Expr2::GlobalTag { name, .. } => new_markup_node( - get_string(env, name), - ast_node_id, - HighlightStyle::Type, - mark_node_pool, - ), - Expr2::Call { expr: expr_id, .. } => { - let expr = env.pool.get(*expr_id); - expr2_to_markup(arena, env, expr, *expr_id, mark_node_pool, interns)? - } - Expr2::Var(symbol) => { - //TODO make bump_format with arena - let text = format!("{:?}", symbol); - new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) - } - Expr2::List { elems, .. } => { - let mut children_ids = - vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; - - let indexed_node_ids: Vec<(usize, ExprId)> = - elems.iter(env.pool).copied().enumerate().collect(); - - for (idx, node_id) in indexed_node_ids.iter() { - let sub_expr2 = env.pool.get(*node_id); - - children_ids.push(expr2_to_markup( - arena, - env, - sub_expr2, - *node_id, - mark_node_pool, - interns, - )?); - - if idx + 1 < elems.len() { - children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); - } - } - children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); - - let list_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(list_node) - } - Expr2::EmptyRecord => { - let children_ids = vec![ - mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), - mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), - ]; - - let record_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(record_node) - } - Expr2::Record { fields, .. } => { - let mut children_ids = - vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; - - for (idx, field_node_id) in fields.iter_node_ids().enumerate() { - let record_field = env.pool.get(field_node_id); - - let field_name = record_field.get_record_field_pool_str(); - - children_ids.push(new_markup_node( - field_name.as_str(env.pool).to_owned(), - ast_node_id, - HighlightStyle::RecordField, - mark_node_pool, - )); - - match record_field { - RecordField::InvalidLabelOnly(_, _) => (), - RecordField::LabelOnly(_, _, _) => (), - RecordField::LabeledValue(_, _, sub_expr2_node_id) => { - children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); - - let sub_expr2 = env.pool.get(*sub_expr2_node_id); - children_ids.push(expr2_to_markup( - arena, - env, - sub_expr2, - *sub_expr2_node_id, - mark_node_pool, - interns, - )?); - } - } - - if idx + 1 < fields.len() { - children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); - } - } - - children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); - - let record_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(record_node) - } - Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), - Expr2::LetValue { - def_id, - body_id: _, - body_var: _, - } => { - let pattern_id = env.pool.get(*def_id).get_pattern_id(); - - let pattern2 = env.pool.get(pattern_id); - - let val_name = get_identifier_string(pattern2, interns)?; - - let val_name_mn = MarkupNode::Text { - content: val_name, - ast_node_id, - syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - let val_name_mn_id = mark_node_pool.add(val_name_mn); - - let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); - - let value_def = env.pool.get(*def_id); - - match value_def { - ValueDef::NoAnnotation { - pattern_id: _, - expr_id, - expr_var: _, - } => { - let body_mn_id = expr2_to_markup( - arena, - env, - env.pool.get(*expr_id), - *expr_id, - mark_node_pool, - interns, - )?; - - let body_mn = mark_node_pool.get_mut(body_mn_id); - body_mn.add_newline_at_end(); - - let full_let_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], - parent_id_opt: None, - newlines_at_end: 1, - }; - - mark_node_pool.add(full_let_node) - } - other => { - unimplemented!( - "I don't know how to convert {:?} into a MarkupNode yet.", - other - ) - } - } - } - Expr2::RuntimeError() => new_markup_node( - "RunTimeError".to_string(), - ast_node_id, - HighlightStyle::Blank, - mark_node_pool, - ), - rest => todo!("implement expr2_to_markup for {:?}", rest), - }; - - Ok(mark_node_id) -} - -pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { - let node = mark_node_pool.get(markup_node_id); - - if let MarkupNode::Nested { - ast_node_id: _, - children_ids, - parent_id_opt: _, - newlines_at_end: _, - } = node - { - // need to clone because of borrowing issues - let children_ids_clone = children_ids.clone(); - - for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); - } - } -} - -pub fn set_parent_for_all_helper( - markup_node_id: MarkNodeId, - parent_node_id: MarkNodeId, - mark_node_pool: &mut SlowPool, -) { - let node = mark_node_pool.get_mut(markup_node_id); - - match node { - MarkupNode::Nested { - children_ids, - parent_id_opt, - .. - } => { - *parent_id_opt = Some(parent_node_id); - - // need to clone because of borrowing issues - let children_ids_clone = children_ids.clone(); - - for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); - } - } - MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), - MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), - } -} - -fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { - let mark_node = MarkupNode::Text { - content, - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::PackageRelated, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(mark_node) -} - -fn header_val_mn( - content: String, - expr_id: ExprId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> MarkNodeId { - let mark_node = MarkupNode::Text { - content, - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: highlight_style, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(mark_node) -} - -pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { - let expr_id = app_header.ast_node_id; - let ast_node_id = ASTNodeId::AExprId(expr_id); - - let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); - - let app_name_node_id = header_val_mn( - app_header.app_name.clone(), - expr_id, - HighlightStyle::String, - mark_node_pool, - ); - - let full_app_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![app_node_id, app_name_node_id], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); - - let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); - - let pack_base_node_id = header_val_mn( - "base: ".to_owned(), - expr_id, - HighlightStyle::RecordField, - mark_node_pool, - ); - - let pack_val_node_id = header_val_mn( - app_header.packages_base.clone(), - expr_id, - HighlightStyle::String, - mark_node_pool, - ); - - let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); - - let full_packages_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![ - packages_node_id, - pack_left_acc_node_id, - pack_base_node_id, - pack_val_node_id, - pack_right_acc_node_id, - ], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); - - let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); - - let mut import_child_ids: Vec = add_header_mn_list( - &app_header.imports, - expr_id, - HighlightStyle::Import, - mark_node_pool, - ); - - let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); - - let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; - - full_import_children.append(&mut import_child_ids); - full_import_children.push(imports_right_square_node_id); - - let full_import_node = MarkupNode::Nested { - ast_node_id, - children_ids: full_import_children, - parent_id_opt: None, - newlines_at_end: 1, - }; - - let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); - - let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); - - let mut provides_val_node_ids: Vec = add_header_mn_list( - &app_header.provides, - expr_id, - HighlightStyle::Provides, - mark_node_pool, - ); - - let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); - - let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); - - let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; - - full_provides_children.append(&mut provides_val_node_ids); - full_provides_children.push(provides_right_square_node_id); - full_provides_children.push(provides_end_node_id); - - let full_provides_node = MarkupNode::Nested { - ast_node_id, - children_ids: full_provides_children, - parent_id_opt: None, - newlines_at_end: 1, - }; - - let full_app_node_id = mark_node_pool.add(full_app_node); - let full_packages_node = mark_node_pool.add(full_packages_node); - let full_import_node_id = mark_node_pool.add(full_import_node); - let full_provides_node_id = mark_node_pool.add(full_provides_node); - - let header_mark_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![ - full_app_node_id, - full_packages_node, - full_import_node_id, - full_provides_node_id, - ], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let header_mn_id = mark_node_pool.add(header_mark_node); - - set_parent_for_all(header_mn_id, mark_node_pool); - - header_mn_id -} - -// Used for provides and imports -fn add_header_mn_list( - str_vec: &[String], - expr_id: ExprId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> Vec { - let nr_of_elts = str_vec.len(); - - str_vec - .iter() - .enumerate() - .map(|(indx, provide_str)| { - let provide_str = header_val_mn( - provide_str.to_owned(), - expr_id, - highlight_style, - mark_node_pool, - ); - - if indx != nr_of_elts - 1 { - vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] - } else { - vec![provide_str] - } - }) - .flatten() - .collect() -} - -pub fn ast_to_mark_nodes<'a, 'b>( - arena: &'a Bump, - env: &mut Env<'b>, - ast: &AST, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> EdResult> { - let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; - - for &def_id in ast.def_ids.iter() { - let def2 = env.pool.get(def_id); - - let expr2_markup_id = def2_to_markup(arena, env, def2, def_id, mark_node_pool, interns)?; - - set_parent_for_all(expr2_markup_id, mark_node_pool); - - all_mark_node_ids.push(expr2_markup_id); - } - - Ok(all_mark_node_ids) -} - -impl fmt::Display for MarkupNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} ({}, {})", - self.node_type_as_string(), - self.get_content(), - self.get_newlines_at_end() - ) - } -} - -pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { - let mut full_string = "\n(mark_node_tree)\n".to_owned(); - - let node = mark_node_pool.get(root_node_id); - - full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id)); - - tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); - - full_string -} - -fn tree_as_string_helper( - node: &MarkupNode, - level: usize, - tree_string: &mut String, - mark_node_pool: &SlowPool, -) { - for child_id in node.get_children_ids() { - let mut full_str = std::iter::repeat("|--- ") - .take(level) - .collect::>() - .join("") - .to_owned(); - - let child = mark_node_pool.get(child_id); - let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n"); - - full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id)); - - tree_string.push_str(&full_str); - - tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); - } -} - -// return to the the root parent_id of the current node -pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId { - let mut curr_mark_node_id = mark_node_id; - let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); - - while let Some(curr_parent_id) = curr_parent_id_opt { - curr_mark_node_id = curr_parent_id; - curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); - } - - curr_mark_node_id -} diff --git a/editor/src/editor/mod.rs b/editor/src/editor/mod.rs index 89efb3a318..f776acc44c 100644 --- a/editor/src/editor/mod.rs +++ b/editor/src/editor/mod.rs @@ -4,13 +4,10 @@ pub mod ed_error; mod grid_node_map; mod keyboard_input; pub mod main; -mod markup; mod mvc; mod render_ast; mod render_debug; mod resources; -mod slow_pool; mod style; -mod syntax_highlight; mod theme; mod util; diff --git a/editor/src/editor/mvc/break_line.rs b/editor/src/editor/mvc/break_line.rs index 6fc8c7ca13..b8de1c1077 100644 --- a/editor/src/editor/mvc/break_line.rs +++ b/editor/src/editor/mvc/break_line.rs @@ -1,10 +1,11 @@ +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::def::def2::Def2; +use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls; + use crate::editor::ed_error::EdResult; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::parse::ASTNodeId; use crate::ui::text::text_pos::TextPos; // put everything after caret on new line, create a Def2::Blank if there was nothing after the caret. diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index a522bc6553..4d42e4e12c 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -1,21 +1,22 @@ use crate::editor::code_lines::CodeLines; use crate::editor::grid_node_map::GridNodeMap; -use crate::editor::markup::nodes::ast_to_mark_nodes; -use crate::editor::slow_pool::{MarkNodeId, SlowPool}; use crate::editor::{ ed_error::SrcParseError, ed_error::{EdResult, EmptyCodeString, MissingParent, NoNodeAtCaretPosition}, }; use crate::graphics::primitives::rect::Rect; -use crate::lang::expr::Env; -use crate::lang::parse::{ASTNodeId, AST}; -use crate::lang::pool::PoolStr; use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect}; use crate::ui::text::lines::SelectableLines; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; use bumpalo::Bump; use nonempty::NonEmpty; +use roc_ast::lang::core::ast::{AST, ASTNodeId}; +use roc_ast::lang::env::Env; +use roc_ast::parse::parse_ast; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::markup::nodes::ast_to_mark_nodes; +use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use roc_load::file::LoadedModule; use std::path::Path; @@ -61,12 +62,14 @@ pub fn init_model<'a>( let markup_ids = if code_str.is_empty() { EmptyCodeString {}.fail() } else { - ast_to_mark_nodes( - code_arena, - &mut module.env, - &module.ast, - &mut mark_node_pool, - &loaded_module.interns, + Ok( + ast_to_mark_nodes( + code_arena, + &mut module.env, + &module.ast, + &mut mark_node_pool, + &loaded_module.interns, + )? ) }?; @@ -152,7 +155,7 @@ impl<'a> EdModel<'a> { if let Some(parent_id) = curr_mark_node.get_parent_id_opt() { let parent = self.mark_node_pool.get(parent_id); - parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool) + Ok(parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool)?) } else { MissingParent { node_id: curr_mark_node_id, @@ -180,7 +183,7 @@ pub struct EdModule<'a> { impl<'a> EdModule<'a> { pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult> { if !code_str.is_empty() { - let parse_res = AST::parse_from_string(code_str, &mut env, ast_arena); + let parse_res = parse_ast::parse_from_string(code_str, &mut env, ast_arena); match parse_res { Ok(ast) => Ok(EdModule { env, ast }), @@ -201,8 +204,6 @@ pub mod test_ed_model { use crate::editor::main::load_module; use crate::editor::mvc::ed_model; use crate::editor::resources::strings::HELLO_WORLD; - use crate::lang::expr::Env; - use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::caret_w_select::CaretPos; @@ -211,6 +212,8 @@ pub mod test_ed_model { use crate::ui::ui_error::UIResult; use bumpalo::Bump; use ed_model::EdModel; + use roc_ast::lang::env::Env; + use roc_ast::pool::pool::Pool; use roc_load::file::LoadedModule; use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 96a9f61d33..2e8c527d2c 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -4,14 +4,14 @@ use std::process::Command; use std::process::Stdio; use crate::editor::code_lines::CodeLines; -use crate::editor::ed_error::from_ui_res; +//use crate::editor::ed_error::from_ui_res; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingSelection; use crate::editor::grid_node_map::GridNodeMap; -use crate::editor::markup::attribute::Attributes; +/*use crate::editor::markup::attribute::Attributes; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; -use crate::editor::markup::nodes::EQUALS; +use crate::editor::markup::nodes::EQUALS;*/ use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_model::SelectedBlock; @@ -27,7 +27,7 @@ use crate::editor::mvc::string_update::start_new_string; use crate::editor::mvc::string_update::update_small_string; use crate::editor::mvc::string_update::update_string; use crate::editor::mvc::tld_value_update::{start_new_tld_value, update_tld_val_name}; -use crate::editor::slow_pool::MarkNodeId; +/*use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; use crate::lang::ast::Def2; @@ -38,7 +38,7 @@ use crate::lang::parse::ASTNodeId; use crate::lang::pool::Pool; use crate::lang::pool::PoolStr; use crate::lang::types::Type2; -use crate::lang::{constrain::Constraint, solve}; +use crate::lang::{constrain::Constraint, solve};*/ use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::lines::MoveCaretFun; use crate::ui::text::selection::validate_raw_sel; @@ -51,7 +51,25 @@ use crate::ui::util::path_to_string; use crate::ui::util::write_to_file; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; +use roc_ast::constrain::Constraint; +use roc_ast::constrain::constrain_expr; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::def::def2::Def2; +use roc_ast::lang::core::def::def2::DefId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::expr::expr2::ExprId; +use roc_ast::lang::core::types::Type2; +use roc_ast::pool::pool::Pool; +use roc_ast::pool::pool_str::PoolStr; +use roc_ast::solve_type; use roc_can::expected::Expected; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::EQUALS; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::slow_pool::SlowPool; +use roc_code_markup::syntax_highlight::HighlightStyle; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -492,8 +510,8 @@ impl<'a> EdModel<'a> { rigid_variables: MutMap, constraint: Constraint, var_store: VarStore, - ) -> (Solved, solve::Env, Vec) { - let env = solve::Env { + ) -> (Solved, solve_type::Env, Vec) { + let env = solve_type::Env { vars_by_symbol: MutMap::default(), aliases, }; @@ -511,7 +529,7 @@ impl<'a> EdModel<'a> { // Run the solver to populate Subs. let (solved_subs, solved_env) = - solve::run(&arena, mempool, &env, &mut problems, subs, &constraint); + solve_type::run(&arena, mempool, &env, &mut problems, subs, &constraint); (solved_subs, solved_env, problems) } @@ -804,7 +822,7 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> fn if_modifiers(modifiers: &Modifiers, shortcut_result: UIResult<()>) -> EdResult<()> { if modifiers.cmd_or_ctrl() { - from_ui_res(shortcut_result) + Ok(shortcut_result?) } else { Ok(()) } diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index 6fee1301f5..ab35ecbdbd 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -7,7 +7,6 @@ use crate::editor::render_debug::build_debug_graphics; use crate::editor::resources::strings::START_TIP; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text::{owned_section_from_text, Text}; -use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::make_caret_rect; use crate::ui::text::caret_w_select::make_selection_rect; use crate::ui::text::caret_w_select::CaretWSelect; @@ -15,6 +14,7 @@ use crate::ui::text::selection::Selection; use crate::ui::tooltip::ToolTip; use crate::ui::ui_error::MissingGlyphDims; use cgmath::Vector2; +use roc_ast::pool::pool::Pool; use snafu::OptionExt; use winit::dpi::PhysicalSize; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index 237da57d86..bee8f0623d 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -1,17 +1,18 @@ +use roc_ast::lang::core::expr::expr2::IntStyle; +use roc_ast::lang::core::expr::expr2::Expr2::{SmallInt}; +use roc_ast::lang::core::expr::expr2::IntVal; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::syntax_highlight::HighlightStyle; + use crate::editor::ed_error::EdResult; use crate::editor::ed_error::StringParseError; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::Expr2::SmallInt; -use crate::lang::ast::IntVal; -use crate::lang::ast::{IntStyle, IntVal::*}; -use crate::lang::pool::PoolStr; use crate::ui::text::lines::SelectableLines; // digit_char should be verified to be a digit before calling this function @@ -145,6 +146,9 @@ pub fn update_int( } fn update_small_int_num(number: &mut IntVal, updated_str: &str) -> EdResult<()> { + + use IntVal::*; + *number = match number { I64(_) => I64(check_parse_res(updated_str.parse::())?), U64(_) => U64(check_parse_res(updated_str.parse::())?), diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs index 697531bb5a..bab48fb6b9 100644 --- a/editor/src/editor/mvc/let_update.rs +++ b/editor/src/editor/mvc/let_update.rs @@ -1,18 +1,19 @@ +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::pattern::Pattern2; +use roc_ast::lang::core::val_def::ValueDef; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls; +use roc_code_markup::markup::common_nodes::new_equals_mn; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::syntax_highlight::HighlightStyle; use roc_module::symbol::Symbol; use crate::editor::ed_error::EdResult; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::{Expr2, ValueDef}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pattern::Pattern2; pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult { let NodeContext { diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 65e6fce10b..764b81892b 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,19 +1,16 @@ +use roc_ast::lang::core::ast::{ASTNodeId, ast_node_to_string}; +use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; +use roc_ast::pool::pool_vec::PoolVec; +use roc_code_markup::markup::common_nodes::{new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn}; +use roc_code_markup::markup::nodes::{self, MarkupNode}; +use roc_code_markup::slow_pool::MarkNodeId; + use crate::editor::ed_error::EdResult; use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; -use crate::editor::markup::common_nodes::{ - new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, -}; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::lang::ast::ExprId; -use crate::lang::ast::{ast_node_to_string, Expr2}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pool::PoolVec; use crate::ui::text::text_pos::TextPos; pub fn start_new_list(ed_model: &mut EdModel) -> EdResult { diff --git a/editor/src/editor/mvc/lookup_update.rs b/editor/src/editor/mvc/lookup_update.rs index 03a4905942..53db22427a 100644 --- a/editor/src/editor/mvc/lookup_update.rs +++ b/editor/src/editor/mvc/lookup_update.rs @@ -1,9 +1,10 @@ +use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::slow_pool::MarkNodeId; + use crate::editor::ed_error::EdResult; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::slow_pool::MarkNodeId; -use crate::lang::ast::{Expr2, ExprId}; -use crate::lang::pool::PoolStr; use crate::ui::text::lines::SelectableLines; pub fn update_invalid_lookup( diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 0b8c911ab6..94f824375c 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -1,23 +1,26 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::RecordWithoutFields; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; -use crate::lang::ast::{Expr2, ExprId, RecordField}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pool::{PoolStr, PoolVec}; use crate::ui::text::text_pos::TextPos; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::expr::expr2::ExprId; +use roc_ast::lang::core::expr::record_field::RecordField; +use roc_ast::pool::pool_str::PoolStr; +use roc_ast::pool::pool_vec::PoolVec; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::common_nodes::new_blank_mn; +use roc_code_markup::markup::common_nodes::new_left_accolade_mn; +use roc_code_markup::markup::common_nodes::new_right_accolade_mn; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::syntax_highlight::HighlightStyle; use snafu::OptionExt; pub fn start_new_record(ed_model: &mut EdModel) -> EdResult { diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index 2c35ca89fd..fca9100495 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -1,16 +1,17 @@ +use roc_ast::lang::core::expr::expr2::ArrString; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::str::update_str_expr; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::syntax_highlight::HighlightStyle; + use crate::editor::ed_error::EdResult; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::update_str_expr; -use crate::lang::ast::ArrString; -use crate::lang::ast::Expr2; -use crate::lang::pool::PoolStr; pub fn update_small_string( new_char: &char, diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index 8cdb85feba..b07e1c7010 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -1,22 +1,10 @@ +use roc_ast::{lang::{core::{ast::ASTNodeId, def::def2::Def2, expr::expr2::Expr2, pattern::{Pattern2, get_identifier_string}}, env::Env}, pool::pool::NodeId}; +use roc_code_markup::{markup::{attribute::Attributes, common_nodes::{new_blank_mn_w_nls, new_equals_mn}, nodes::{MarkupNode, set_parent_for_all}}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; use roc_module::symbol::{Interns, Symbol}; use crate::{ editor::{ ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, - markup::{ - attribute::Attributes, - common_nodes::{new_blank_mn_w_nls, new_equals_mn}, - nodes::{set_parent_for_all, MarkupNode}, - }, - slow_pool::{MarkNodeId, SlowPool}, - syntax_highlight::HighlightStyle, - }, - lang::{ - ast::{Def2, Expr2}, - expr::Env, - parse::ASTNodeId, - pattern::{get_identifier_string, Pattern2}, - pool::NodeId, }, ui::text::text_pos::TextPos, }; diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index 3fa42bc1de..651b55b47d 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,11 +1,11 @@ -use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; -use super::slow_pool::MarkNodeId; + use crate::editor::mvc::ed_view::RenderedWgpu; -use crate::editor::slow_pool::SlowPool; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text as gr_text; use cgmath::Vector2; +use roc_code_markup::markup::nodes::{BLANK_PLACEHOLDER, MarkupNode}; +use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use winit::dpi::PhysicalSize; use crate::{editor::config::Config, graphics::colors}; diff --git a/editor/src/editor/render_debug.rs b/editor/src/editor/render_debug.rs index 36e4377d80..2fc1f23863 100644 --- a/editor/src/editor/render_debug.rs +++ b/editor/src/editor/render_debug.rs @@ -1,11 +1,11 @@ use crate::editor::ed_error::EdResult; -use crate::editor::markup::nodes::tree_as_string; use crate::editor::mvc::ed_model::EdModel; use crate::graphics::colors; use crate::graphics::colors::from_hsb; use crate::graphics::primitives::text as gr_text; -use crate::lang::ast::def2_to_string; use cgmath::Vector2; +use roc_ast::lang::core::def::def2::def2_to_string; +use roc_code_markup::markup::nodes::tree_as_string; use winit::dpi::PhysicalSize; use crate::editor::config::Config; diff --git a/editor/src/editor/slow_pool.rs b/editor/src/editor/slow_pool.rs deleted file mode 100644 index 01058e9668..0000000000 --- a/editor/src/editor/slow_pool.rs +++ /dev/null @@ -1,77 +0,0 @@ - -use std::fmt; - -use super::markup::nodes::MarkupNode; - - -pub type MarkNodeId = usize; - -#[derive(Debug)] -pub struct SlowPool { - nodes: Vec, -} - -impl SlowPool { - pub fn new() -> SlowPool { - SlowPool { nodes: Vec::new() } - } - - pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { - let id = self.nodes.len(); - - self.nodes.push(node); - - id - } - - pub fn get(&self, node_id: MarkNodeId) -> &MarkupNode { - // unwrap because Pool doesn't return Result either - self.nodes.get(node_id).unwrap() - } - - pub fn get_mut(&mut self, node_id: MarkNodeId) -> &mut MarkupNode { - // unwrap because Pool doesn't return Result either - self.nodes.get_mut(node_id).unwrap() - } - - pub fn replace_node(&mut self, node_id: MarkNodeId, new_node: MarkupNode) { - self.nodes[node_id] = new_node; - - // TODO delete children of old node, this requires SlowPool to be changed to - // make sure the indexes still make sense after removal/compaction - } -} - -impl fmt::Display for SlowPool { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "\n\n(mark_node_pool)\n")?; - - for (index, node) in self.nodes.iter().enumerate() { - let ast_node_id_str = format!("{:?}", node.get_ast_node_id()); - let ast_node_id: String = ast_node_id_str - .chars() - .filter(|c| c.is_ascii_digit()) - .collect(); - - let mut child_str = String::new(); - - let node_children = node.get_children_ids(); - - if !node_children.is_empty() { - child_str = format!("children: {:?}", node_children); - } - - writeln!( - f, - "{}: {} ({}) ast_id {:?} {}", - index, - node.node_type_as_string(), - node.get_content(), - ast_node_id.parse::().unwrap(), - child_str - )?; - } - - Ok(()) - } -} diff --git a/editor/src/editor/syntax_highlight.rs b/editor/src/editor/syntax_highlight.rs deleted file mode 100644 index 602a335b48..0000000000 --- a/editor/src/editor/syntax_highlight.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::graphics::colors as gr_colors; -use gr_colors::{from_hsb, RgbaTup}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] -pub enum HighlightStyle { - Operator, // =+-<>... - String, - FunctionName, - Type, - Bracket, - Number, - PackageRelated, // app, packages, imports, exposes, provides... - Variable, - RecordField, - Import, - Provides, - Blank, -} - -pub fn default_highlight_map() -> HashMap { - use HighlightStyle::*; - - let mut highlight_map = HashMap::new(); - [ - (Operator, gr_colors::WHITE), - (String, from_hsb(346, 65, 97)), - (FunctionName, gr_colors::WHITE), - (Type, gr_colors::WHITE), - (Bracket, from_hsb(347, 80, 100)), - (Number, from_hsb(185, 50, 75)), - (PackageRelated, gr_colors::WHITE), - (Variable, gr_colors::WHITE), - (RecordField, from_hsb(258, 50, 90)), - (Import, from_hsb(185, 50, 75)), - (Provides, from_hsb(185, 50, 75)), - (Blank, from_hsb(258, 50, 90)), - // comment from_hsb(285, 6, 47) or 186, 35, 40 - ] - .iter() - .for_each(|tup| { - highlight_map.insert(tup.0, tup.1); - }); - - highlight_map -} diff --git a/editor/src/editor/theme.rs b/editor/src/editor/theme.rs index f0809cd890..dcc3b437b1 100644 --- a/editor/src/editor/theme.rs +++ b/editor/src/editor/theme.rs @@ -1,8 +1,8 @@ use gr_colors::{from_hsb, RgbaTup}; +use roc_code_markup::syntax_highlight::{HighlightStyle, default_highlight_map}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::editor::syntax_highlight::{default_highlight_map, HighlightStyle}; use crate::graphics::colors as gr_colors; use crate::ui::theme::UITheme; diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs deleted file mode 100644 index 3fffb3cdad..0000000000 --- a/editor/src/lang/ast.rs +++ /dev/null @@ -1,742 +0,0 @@ -#![allow(clippy::manual_map)] - -use std::collections::{HashMap, HashSet}; -use std::hash::BuildHasherDefault; - -use crate::editor::ed_error::{EdResult, UnexpectedASTNode}; -use crate::lang::pattern::{Pattern2, PatternId}; -use crate::lang::pool::Pool; -use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; -use crate::lang::types::{Type2, TypeId}; -use arraystring::{typenum::U30, ArrayString}; -use roc_can::expr::Recursive; -use roc_collections::all::WyHash; -use roc_module::low_level::LowLevel; -use roc_module::operator::CalledVia; -use roc_module::symbol::Symbol; -use roc_types::subs::Variable; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Problem { - RanOutOfNodeIds, -} - -pub type Res = Result; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum IntStyle { - Decimal, - Octal, - Hex, - Binary, -} - -impl IntStyle { - pub fn from_base(base: roc_parse::ast::Base) -> Self { - use roc_parse::ast::Base; - match base { - Base::Decimal => Self::Decimal, - Base::Octal => Self::Octal, - Base::Hex => Self::Hex, - Base::Binary => Self::Binary, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum IntVal { - I64(i64), - U64(u64), - I32(i32), - U32(u32), - I16(i16), - U16(u16), - I8(i8), - U8(u8), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum FloatVal { - F64(f64), - F32(f32), -} - -#[derive(Debug)] -pub enum RecordField { - InvalidLabelOnly(PoolStr, Variable), - LabelOnly(PoolStr, Variable, Symbol), - LabeledValue(PoolStr, Variable, ExprId), -} - -#[test] -fn size_of_intval() { - assert_eq!(std::mem::size_of::(), 16); -} - -pub type ArrString = ArrayString; - -/// An Expr that fits in 32B. -/// It has a 1B discriminant and variants which hold payloads of at most 31B. -#[derive(Debug)] -pub enum Expr2 { - /// A negative number literal without a dot - SmallInt { - number: IntVal, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - // TODO(rvcas): rename this eventually - /// A large (over 64-bit) negative number literal without a dot. - /// This variant can't use IntVal because if IntVal stored 128-bit - /// integers, it would be 32B on its own because of alignment. - I128 { - number: i128, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - // TODO(rvcas): rename this eventually - /// A large (over 64-bit) nonnegative number literal without a dot - /// This variant can't use IntVal because if IntVal stored 128-bit - /// integers, it would be 32B on its own because of alignment. - U128 { - number: u128, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - /// A floating-point literal (with a dot) - Float { - number: FloatVal, // 16B - var: Variable, // 4B - text: PoolStr, // 8B - }, - /// string literals of length up to 30B - SmallStr(ArrString), // 31B - /// string literals of length 31B or more - Str(PoolStr), // 8B - // Lookups - Var(Symbol), // 8B - InvalidLookup(PoolStr), // 8B - - List { - elem_var: Variable, // 4B - elems: PoolVec, // 8B - }, - If { - cond_var: Variable, // 4B - expr_var: Variable, // 4B - branches: PoolVec<(ExprId, ExprId)>, // 8B - final_else: ExprId, // 4B - }, - When { - cond_var: Variable, // 4B - expr_var: Variable, // 4B - branches: PoolVec, // 8B - cond: ExprId, // 4B - }, - LetRec { - defs: PoolVec, // 8B - body_var: Variable, // 8B - body_id: ExprId, // 4B - }, - LetFunction { - def_id: NodeId, // 4B - body_var: Variable, // 8B - body_id: ExprId, // 4B - }, - LetValue { - def_id: NodeId, // 4B - body_id: ExprId, // 4B - body_var: Variable, // 4B - }, - Call { - args: PoolVec<(Variable, ExprId)>, // 8B - expr: ExprId, // 4B - expr_var: Variable, // 4B - fn_var: Variable, // 4B - closure_var: Variable, // 4B - called_via: CalledVia, // 2B - }, - RunLowLevel { - op: LowLevel, // 1B - args: PoolVec<(Variable, ExprId)>, // 8B - ret_var: Variable, // 4B - }, - Closure { - args: PoolVec<(Variable, NodeId)>, // 8B - name: Symbol, // 8B - body: ExprId, // 4B - function_type: Variable, // 4B - recursive: Recursive, // 1B - extra: NodeId, // 4B - }, - // Product Types - Record { - record_var: Variable, // 4B - fields: PoolVec, // 8B - }, - /// Empty record constant - EmptyRecord, - /// Look up exactly one field on a record, e.g. (expr).foo. - Access { - field: PoolStr, // 4B - expr: ExprId, // 4B - record_var: Variable, // 4B - ext_var: Variable, // 4B - field_var: Variable, // 4B - }, - - /// field accessor as a function, e.g. (.foo) expr - Accessor { - function_var: Variable, // 4B - closure_var: Variable, // 4B - field: PoolStr, // 4B - record_var: Variable, // 4B - ext_var: Variable, // 4B - field_var: Variable, // 4B - }, - Update { - symbol: Symbol, // 8B - updates: PoolVec, // 8B - record_var: Variable, // 4B - ext_var: Variable, // 4B - }, - - // Sum Types - GlobalTag { - name: PoolStr, // 4B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, - PrivateTag { - name: Symbol, // 8B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, - Blank, // Rendered as empty box in editor - - // Compiles, but will crash if reached - RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), -} - -// A top level definition, not inside a function. For example: `main = "Hello, world!"` -#[derive(Debug)] -pub enum Def2 { - // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" - ValueDef { - identifier_id: NodeId, - expr_id: NodeId, - }, - Blank, -} - -#[derive(Debug)] -pub enum ValueDef { - WithAnnotation { - pattern_id: PatternId, // 4B - expr_id: ExprId, // 4B - type_id: TypeId, - rigids: Rigids, - expr_var: Variable, // 4B - }, - NoAnnotation { - pattern_id: PatternId, // 4B - expr_id: ExprId, // 4B - expr_var: Variable, // 4B - }, -} - -impl ShallowClone for ValueDef { - fn shallow_clone(&self) -> Self { - match self { - Self::WithAnnotation { - pattern_id, - expr_id, - type_id, - rigids, - expr_var, - } => Self::WithAnnotation { - pattern_id: *pattern_id, - expr_id: *expr_id, - type_id: *type_id, - rigids: rigids.shallow_clone(), - expr_var: *expr_var, - }, - Self::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => Self::NoAnnotation { - pattern_id: *pattern_id, - expr_id: *expr_id, - expr_var: *expr_var, - }, - } - } -} - -impl ValueDef { - pub fn get_expr_id(&self) -> ExprId { - match self { - ValueDef::WithAnnotation { expr_id, .. } => *expr_id, - ValueDef::NoAnnotation { expr_id, .. } => *expr_id, - } - } - - pub fn get_pattern_id(&self) -> NodeId { - match self { - ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, - ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, - } - } -} - -pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { - match val_def { - ValueDef::WithAnnotation { - pattern_id, - expr_id, - type_id, - rigids, - expr_var, - } => { - format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) - } - ValueDef::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => { - format!( - "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", - pool.get(*pattern_id), - expr2_to_string(*expr_id, pool), - expr_var - ) - } - } -} - -#[derive(Debug)] -pub enum FunctionDef { - WithAnnotation { - name: Symbol, // 8B - arguments: PoolVec<(PatternId, Type2)>, // 8B - rigids: NodeId, // 4B - return_type: TypeId, // 4B - body: ExprId, // 4B - }, - NoAnnotation { - name: Symbol, // 8B - arguments: PoolVec<(PatternId, Variable)>, // 8B - return_var: Variable, // 4B - body: ExprId, // 4B - }, -} - -impl ShallowClone for FunctionDef { - fn shallow_clone(&self) -> Self { - match self { - Self::WithAnnotation { - name, - arguments, - rigids, - return_type, - body, - } => Self::WithAnnotation { - name: *name, - arguments: arguments.shallow_clone(), - rigids: *rigids, - return_type: *return_type, - body: *body, - }, - - Self::NoAnnotation { - name, - arguments, - return_var, - body, - } => Self::NoAnnotation { - name: *name, - arguments: arguments.shallow_clone(), - return_var: *return_var, - body: *body, - }, - } - } -} - -#[derive(Debug)] -pub struct Rigids { - pub names: PoolVec<(Option, Variable)>, // 8B - padding: [u8; 1], -} - -#[allow(clippy::needless_collect)] -impl Rigids { - pub fn new( - named: HashMap<&str, Variable, BuildHasherDefault>, - unnamed: HashSet>, - pool: &mut Pool, - ) -> Self { - let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool); - - let mut temp_names = Vec::new(); - - temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); - - temp_names.extend(unnamed.iter().map(|var| (None, *var))); - - for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) { - let poolstr = opt_name.map(|name| PoolStr::new(name, pool)); - - pool[node_id] = (poolstr, variable); - } - - Self { - names, - padding: Default::default(), - } - } - - pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> { - let named = self - .names - .iter(pool) - .filter_map(|(opt_pool_str, var)| { - if let Some(pool_str) = opt_pool_str { - Some((*pool_str, *var)) - } else { - None - } - }) - .collect::>(); - - PoolVec::new(named.into_iter(), pool) - } - - pub fn unnamed(&self, pool: &mut Pool) -> PoolVec { - let unnamed = self - .names - .iter(pool) - .filter_map(|(opt_pool_str, var)| { - if opt_pool_str.is_none() { - Some(*var) - } else { - None - } - }) - .collect::>(); - - PoolVec::new(unnamed.into_iter(), pool) - } -} - -/// This is overflow data from a Closure variant, which needs to store -/// more than 32B of total data -#[derive(Debug)] -pub struct ClosureExtra { - pub return_type: Variable, // 4B - pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B - pub closure_type: Variable, // 4B - pub closure_ext_var: Variable, // 4B -} - -#[derive(Debug)] -pub struct WhenBranch { - pub patterns: PoolVec, // 4B - pub body: ExprId, // 3B - pub guard: Option, // 4B -} - -// TODO make the inner types private? -pub type ExprId = NodeId; - -pub type DefId = NodeId; - -use RecordField::*; - -use super::parse::ASTNodeId; -impl RecordField { - pub fn get_record_field_var(&self) -> &Variable { - match self { - InvalidLabelOnly(_, var) => var, - LabelOnly(_, var, _) => var, - LabeledValue(_, var, _) => var, - } - } - - pub fn get_record_field_pool_str(&self) -> &PoolStr { - match self { - InvalidLabelOnly(pool_str, _) => pool_str, - LabelOnly(pool_str, _, _) => pool_str, - LabeledValue(pool_str, _, _) => pool_str, - } - } - - pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr { - match self { - InvalidLabelOnly(pool_str, _) => pool_str, - LabelOnly(pool_str, _, _) => pool_str, - LabeledValue(pool_str, _, _) => pool_str, - } - } - - pub fn get_record_field_val_node_id(&self) -> Option { - match self { - InvalidLabelOnly(_, _) => None, - LabelOnly(_, _, _) => None, - LabeledValue(_, _, field_val_id) => Some(*field_val_id), - } - } -} - -pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { - match node_id { - ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), - ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), - } -} - -pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { - let mut full_string = String::new(); - let expr2 = pool.get(node_id); - - expr2_to_string_helper(expr2, 0, pool, &mut full_string); - - full_string -} - -fn get_spacing(indent_level: usize) -> String { - std::iter::repeat(" ") - .take(indent_level) - .collect::>() - .join("") -} - -fn expr2_to_string_helper( - expr2: &Expr2, - indent_level: usize, - pool: &Pool, - out_string: &mut String, -) { - out_string.push_str(&get_spacing(indent_level)); - - match expr2 { - Expr2::SmallStr(arr_string) => out_string.push_str(&format!( - "{}{}{}", - "SmallStr(\"", - arr_string.as_str(), - "\")", - )), - Expr2::Str(pool_str) => { - out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",)) - } - Expr2::Blank => out_string.push_str("Blank"), - Expr2::EmptyRecord => out_string.push_str("EmptyRecord"), - Expr2::Record { record_var, fields } => { - out_string.push_str("Record:\n"); - out_string.push_str(&var_to_string(record_var, indent_level + 1)); - - out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1))); - - let mut first_child = true; - - for field in fields.iter(pool) { - if !first_child { - out_string.push_str(", ") - } else { - first_child = false; - } - - match field { - RecordField::InvalidLabelOnly(pool_str, var) => { - out_string.push_str(&format!( - "{}({}, Var({:?})", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - )); - } - RecordField::LabelOnly(pool_str, var, symbol) => { - out_string.push_str(&format!( - "{}({}, Var({:?}), Symbol({:?})", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - symbol - )); - } - RecordField::LabeledValue(pool_str, var, val_node_id) => { - out_string.push_str(&format!( - "{}({}, Var({:?}), Expr2(\n", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - )); - - let val_expr2 = pool.get(*val_node_id); - expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string); - out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2))); - } - } - } - - out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); - } - Expr2::List { elem_var, elems } => { - out_string.push_str("List:\n"); - out_string.push_str(&var_to_string(elem_var, indent_level + 1)); - out_string.push_str(&format!("{}elems: [\n", get_spacing(indent_level + 1))); - - let mut first_elt = true; - - for elem_expr2_id in elems.iter(pool) { - if !first_elt { - out_string.push_str(", ") - } else { - first_elt = false; - } - - let elem_expr2 = pool.get(*elem_expr2_id); - - expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string) - } - - out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); - } - Expr2::InvalidLookup(pool_str) => { - out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool))); - } - Expr2::SmallInt { text, .. } => { - out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); - } - Expr2::LetValue { - def_id, body_id, .. - } => { - out_string.push_str(&format!( - "LetValue(def_id: >>{:?}), body_id: >>{:?})", - value_def_to_string(pool.get(*def_id), pool), - pool.get(*body_id) - )); - } - other => todo!("Implement for {:?}", other), - } - - out_string.push('\n'); -} - -pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { - let mut full_string = String::new(); - let def2 = pool.get(node_id); - - match def2 { - Def2::ValueDef { - identifier_id, - expr_id, - } => { - full_string.push_str(&format!( - "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", - pool.get(*identifier_id), - expr2_to_string(*expr_id, pool) - )); - } - Def2::Blank => { - full_string.push_str("Def2::Blank"); - } - } - - full_string -} - -fn var_to_string(some_var: &Variable, indent_level: usize) -> String { - format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) -} - -// get string from SmallStr or Str -pub fn get_string_from_expr2(node_id: ExprId, pool: &Pool) -> EdResult { - match pool.get(node_id) { - Expr2::SmallStr(arr_string) => Ok(arr_string.as_str().to_string()), - Expr2::Str(pool_str) => Ok(pool_str.as_str(pool).to_owned()), - other => UnexpectedASTNode { - required_node_type: "SmallStr or Str", - encountered_node_type: format!("{:?}", other), - } - .fail()?, - } -} - -pub fn update_str_expr( - node_id: ExprId, - new_char: char, - insert_index: usize, - pool: &mut Pool, -) -> EdResult<()> { - let str_expr = pool.get_mut(node_id); - - enum Either { - MyString(String), - MyPoolStr(PoolStr), - Done, - } - - let insert_either = match str_expr { - Expr2::SmallStr(arr_string) => { - let insert_res = arr_string.try_insert(insert_index as u8, new_char); - - match insert_res { - Ok(_) => Either::Done, - _ => { - let mut new_string = arr_string.as_str().to_string(); - new_string.insert(insert_index, new_char); - - Either::MyString(new_string) - } - } - } - Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), - other => UnexpectedASTNode { - required_node_type: "SmallStr or Str", - encountered_node_type: format!("{:?}", other), - } - .fail()?, - }; - - match insert_either { - Either::MyString(new_string) => { - let new_pool_str = PoolStr::new(&new_string, pool); - - pool.set(node_id, Expr2::Str(new_pool_str)) - } - Either::MyPoolStr(old_pool_str) => { - let mut new_string = old_pool_str.as_str(pool).to_owned(); - - new_string.insert(insert_index, new_char); - - let new_pool_str = PoolStr::new(&new_string, pool); - - pool.set(node_id, Expr2::Str(new_pool_str)) - } - Either::Done => (), - } - - Ok(()) -} - -#[test] -fn size_of_expr() { - assert_eq!(std::mem::size_of::(), crate::lang::pool::NODE_BYTES); -} - -impl ShallowClone for Rigids { - fn shallow_clone(&self) -> Self { - Self { - names: self.names.shallow_clone(), - padding: self.padding, - } - } -} diff --git a/editor/src/lang/constrain.rs b/editor/src/lang/constrain.rs deleted file mode 100644 index 77d4edb0cd..0000000000 --- a/editor/src/lang/constrain.rs +++ /dev/null @@ -1,1746 +0,0 @@ -use bumpalo::{collections::Vec as BumpVec, Bump}; - -use crate::lang::{ - ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch}, - expr::Env, - pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, - pool::{Pool, PoolStr, PoolVec, ShallowClone}, - types::{Type2, TypeId}, -}; - -use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; -use roc_module::{ - ident::{Lowercase, TagName}, - symbol::Symbol, -}; -use roc_region::all::Region; -use roc_types::{ - subs::Variable, - types::{self, AnnotationSource, PReason, PatternCategory}, - types::{Category, Reason}, -}; - -#[derive(Debug)] -pub enum Constraint<'a> { - Eq(Type2, Expected, Category, Region), - // Store(Type, Variable, &'static str, u32), - Lookup(Symbol, Expected, Region), - Pattern(Region, PatternCategory, Type2, PExpected), - And(BumpVec<'a, Constraint<'a>>), - Let(&'a LetConstraint<'a>), - // SaveTheEnvironment, - True, // Used for things that always unify, e.g. blanks and runtime errors -} - -#[derive(Debug)] -pub struct LetConstraint<'a> { - pub rigid_vars: BumpVec<'a, Variable>, - pub flex_vars: BumpVec<'a, Variable>, - pub def_types: BumpMap, - pub defs_constraint: Constraint<'a>, - pub ret_constraint: Constraint<'a>, -} - -pub fn constrain_expr<'a>( - arena: &'a Bump, - env: &mut Env, - expr: &Expr2, - expected: Expected, - region: Region, -) -> Constraint<'a> { - use Constraint::*; - - match expr { - Expr2::Blank | Expr2::RuntimeError() | Expr2::InvalidLookup(_) => True, - Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, region), - Expr2::SmallStr(_) => Eq(str_type(env.pool), expected, Category::Str, region), - Expr2::Var(symbol) => Lookup(*symbol, expected, region), - Expr2::EmptyRecord => constrain_empty_record(expected, region), - Expr2::SmallInt { var, .. } | Expr2::I128 { var, .. } | Expr2::U128 { var, .. } => { - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - - flex_vars.push(*var); - - let precision_var = env.var_store.fresh(); - - let range_type = Type2::Variable(precision_var); - - let range_type_id = env.pool.add(range_type); - - exists( - arena, - flex_vars, - Eq( - num_num(env.pool, range_type_id), - expected, - Category::Num, - region, - ), - ) - } - Expr2::Float { var, .. } => { - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - let num_type = Type2::Variable(*var); - - flex_vars.push(*var); - - let precision_var = env.var_store.fresh(); - - let range_type = Type2::Variable(precision_var); - - let range_type_id = env.pool.add(range_type); - - and_constraints.push(Eq( - num_type.shallow_clone(), - Expected::ForReason( - Reason::FloatLiteral, - num_float(env.pool, range_type_id), - region, - ), - Category::Int, - region, - )); - - and_constraints.push(Eq(num_type, expected, Category::Float, region)); - - let defs_constraint = And(and_constraints); - - exists(arena, flex_vars, defs_constraint) - } - Expr2::List { - elem_var, elems, .. - } => { - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - - flex_vars.push(*elem_var); - - if elems.is_empty() { - exists( - arena, - flex_vars, - Eq( - empty_list_type(env.pool, *elem_var), - expected, - Category::List, - region, - ), - ) - } else { - let mut constraints = BumpVec::with_capacity_in(1 + elems.len(), arena); - - let list_elem_type = Type2::Variable(*elem_var); - - let indexed_node_ids: Vec<(usize, ExprId)> = - elems.iter(env.pool).copied().enumerate().collect(); - - for (index, elem_node_id) in indexed_node_ids { - let elem_expr = env.pool.get(elem_node_id); - - let elem_expected = Expected::ForReason( - Reason::ElemInList { - index: Index::zero_based(index), - }, - list_elem_type.shallow_clone(), - region, - ); - - let constraint = constrain_expr(arena, env, elem_expr, elem_expected, region); - - constraints.push(constraint); - } - - constraints.push(Eq( - list_type(env.pool, list_elem_type), - expected, - Category::List, - region, - )); - - exists(arena, flex_vars, And(constraints)) - } - } - Expr2::Record { fields, record_var } => { - if fields.is_empty() { - constrain_empty_record(expected, region) - } else { - let field_types = PoolVec::with_capacity(fields.len() as u32, env.pool); - - let mut field_vars = BumpVec::with_capacity_in(fields.len(), arena); - - // Constraints need capacity for each field - // + 1 for the record itself + 1 for record var - let mut constraints = BumpVec::with_capacity_in(2 + fields.len(), arena); - - for (record_field_node_id, field_type_node_id) in - fields.iter_node_ids().zip(field_types.iter_node_ids()) - { - let record_field = env.pool.get(record_field_node_id); - - match record_field { - RecordField::LabeledValue(pool_str, var, node_id) => { - let expr = env.pool.get(*node_id); - - let (field_type, field_con) = constrain_field(arena, env, *var, expr); - - field_vars.push(*var); - - let field_type_id = env.pool.add(field_type); - - env.pool[field_type_node_id] = - (*pool_str, types::RecordField::Required(field_type_id)); - - constraints.push(field_con); - } - e => todo!("{:?}", e), - } - } - - let record_type = Type2::Record(field_types, env.pool.add(Type2::EmptyRec)); - - let record_con = Eq( - record_type, - expected.shallow_clone(), - Category::Record, - region, - ); - - constraints.push(record_con); - - // variable to store in the AST - let stored_con = Eq( - Type2::Variable(*record_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - field_vars.push(*record_var); - constraints.push(stored_con); - - exists(arena, field_vars, And(constraints)) - } - } - Expr2::GlobalTag { - variant_var, - ext_var, - name, - arguments, - } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag( - arena, - env, - expected, - region, - tag_name, - arguments, - *ext_var, - *variant_var, - ) - } - Expr2::PrivateTag { - name, - arguments, - ext_var, - variant_var, - } => { - let tag_name = TagName::Private(*name); - - constrain_tag( - arena, - env, - expected, - region, - tag_name, - arguments, - *ext_var, - *variant_var, - ) - } - Expr2::Call { - args, - expr_var, - expr: expr_node_id, - closure_var, - fn_var, - .. - } => { - // The expression that evaluates to the function being called, e.g. `foo` in - // (foo) bar baz - let call_expr = env.pool.get(*expr_node_id); - - let opt_symbol = if let Expr2::Var(symbol) = call_expr { - Some(*symbol) - } else { - None - }; - - let fn_type = Type2::Variable(*fn_var); - let fn_region = region; - let fn_expected = Expected::NoExpectation(fn_type.shallow_clone()); - - let fn_reason = Reason::FnCall { - name: opt_symbol, - arity: args.len() as u8, - }; - - let fn_con = constrain_expr(arena, env, call_expr, fn_expected, region); - - // The function's return type - // TODO: don't use expr_var? - let ret_type = Type2::Variable(*expr_var); - - // type of values captured in the closure - let closure_type = Type2::Variable(*closure_var); - - // This will be used in the occurs check - let mut vars = BumpVec::with_capacity_in(2 + args.len(), arena); - - vars.push(*fn_var); - // TODO: don't use expr_var? - vars.push(*expr_var); - vars.push(*closure_var); - - let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); - let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); - - for (index, arg_node_id) in args.iter_node_ids().enumerate() { - let (arg_var, arg) = env.pool.get(arg_node_id); - let arg_expr = env.pool.get(*arg); - - let region = region; - let arg_type = Type2::Variable(*arg_var); - - let reason = Reason::FnArg { - name: opt_symbol, - arg_index: Index::zero_based(index), - }; - - let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region); - - let arg_con = constrain_expr(arena, env, arg_expr, expected_arg, region); - - vars.push(*arg_var); - arg_types.push(arg_type); - arg_cons.push(arg_con); - } - - let expected_fn_type = Expected::ForReason( - fn_reason, - Type2::Function( - PoolVec::new(arg_types.into_iter(), env.pool), - env.pool.add(closure_type), - env.pool.add(ret_type.shallow_clone()), - ), - region, - ); - - let category = Category::CallResult(opt_symbol); - - let mut and_constraints = BumpVec::with_capacity_in(4, arena); - - and_constraints.push(fn_con); - and_constraints.push(Eq(fn_type, expected_fn_type, category.clone(), fn_region)); - and_constraints.push(And(arg_cons)); - and_constraints.push(Eq(ret_type, expected, category, region)); - - exists(arena, vars, And(and_constraints)) - } - Expr2::Accessor { - function_var, - closure_var, - field, - record_var, - ext_var, - field_var, - } => { - let ext_var = *ext_var; - let ext_type = Type2::Variable(ext_var); - - let field_var = *field_var; - let field_type = Type2::Variable(field_var); - - let record_field = - types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); - - let record_type = Type2::Record( - PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), - env.pool.add(ext_type), - ); - - let category = Category::Accessor(field.as_str(env.pool).into()); - - let record_expected = Expected::NoExpectation(record_type.shallow_clone()); - let record_con = Eq( - Type2::Variable(*record_var), - record_expected, - category.clone(), - region, - ); - - let function_type = Type2::Function( - PoolVec::new(vec![record_type].into_iter(), env.pool), - env.pool.add(Type2::Variable(*closure_var)), - env.pool.add(field_type), - ); - - let mut flex_vars = BumpVec::with_capacity_in(5, arena); - - flex_vars.push(*record_var); - flex_vars.push(*function_var); - flex_vars.push(*closure_var); - flex_vars.push(field_var); - flex_vars.push(ext_var); - - let mut and_constraints = BumpVec::with_capacity_in(3, arena); - - and_constraints.push(Eq( - function_type.shallow_clone(), - expected, - category.clone(), - region, - )); - - and_constraints.push(Eq( - function_type, - Expected::NoExpectation(Type2::Variable(*function_var)), - category, - region, - )); - - and_constraints.push(record_con); - - exists(arena, flex_vars, And(and_constraints)) - } - Expr2::Access { - expr: expr_id, - field, - field_var, - record_var, - ext_var, - } => { - let ext_type = Type2::Variable(*ext_var); - - let field_type = Type2::Variable(*field_var); - - let record_field = - types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); - - let record_type = Type2::Record( - PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), - env.pool.add(ext_type), - ); - - let record_expected = Expected::NoExpectation(record_type); - - let category = Category::Access(field.as_str(env.pool).into()); - - let record_con = Eq( - Type2::Variable(*record_var), - record_expected.shallow_clone(), - category.clone(), - region, - ); - - let access_expr = env.pool.get(*expr_id); - - let constraint = constrain_expr(arena, env, access_expr, record_expected, region); - - let mut flex_vars = BumpVec::with_capacity_in(3, arena); - - flex_vars.push(*record_var); - flex_vars.push(*field_var); - flex_vars.push(*ext_var); - - let mut and_constraints = BumpVec::with_capacity_in(3, arena); - - and_constraints.push(constraint); - and_constraints.push(Eq(field_type, expected, category, region)); - and_constraints.push(record_con); - - exists(arena, flex_vars, And(and_constraints)) - } - Expr2::If { - cond_var, - expr_var, - branches, - final_else, - } => { - let expect_bool = |region| { - let bool_type = Type2::Variable(Variable::BOOL); - Expected::ForReason(Reason::IfCondition, bool_type, region) - }; - - let mut branch_cons = BumpVec::with_capacity_in(2 * branches.len() + 3, arena); - - // TODO why does this cond var exist? is it for error messages? - // let first_cond_region = branches[0].0.region; - let cond_var_is_bool_con = Eq( - Type2::Variable(*cond_var), - expect_bool(region), - Category::If, - region, - ); - - branch_cons.push(cond_var_is_bool_con); - - let final_else_expr = env.pool.get(*final_else); - - let mut flex_vars = BumpVec::with_capacity_in(2, arena); - - flex_vars.push(*cond_var); - flex_vars.push(*expr_var); - - match expected { - Expected::FromAnnotation(name, arity, _, tipe) => { - let num_branches = branches.len() + 1; - - for (index, branch_id) in branches.iter_node_ids().enumerate() { - let (cond_id, body_id) = env.pool.get(branch_id); - - let cond = env.pool.get(*cond_id); - let body = env.pool.get(*body_id); - - let cond_con = - constrain_expr(arena, env, cond, expect_bool(region), region); - - let then_con = constrain_expr( - arena, - env, - body, - Expected::FromAnnotation( - name.clone(), - arity, - AnnotationSource::TypedIfBranch { - index: Index::zero_based(index), - num_branches, - }, - tipe.shallow_clone(), - ), - region, - ); - - branch_cons.push(cond_con); - branch_cons.push(then_con); - } - - let else_con = constrain_expr( - arena, - env, - final_else_expr, - Expected::FromAnnotation( - name, - arity, - AnnotationSource::TypedIfBranch { - index: Index::zero_based(branches.len()), - num_branches, - }, - tipe.shallow_clone(), - ), - region, - ); - - let ast_con = Eq( - Type2::Variable(*expr_var), - Expected::NoExpectation(tipe), - Category::Storage(std::file!(), std::line!()), - region, - ); - - branch_cons.push(ast_con); - branch_cons.push(else_con); - - exists(arena, flex_vars, And(branch_cons)) - } - _ => { - for (index, branch_id) in branches.iter_node_ids().enumerate() { - let (cond_id, body_id) = env.pool.get(branch_id); - - let cond = env.pool.get(*cond_id); - let body = env.pool.get(*body_id); - - let cond_con = - constrain_expr(arena, env, cond, expect_bool(region), region); - - let then_con = constrain_expr( - arena, - env, - body, - Expected::ForReason( - Reason::IfBranch { - index: Index::zero_based(index), - total_branches: branches.len(), - }, - Type2::Variable(*expr_var), - // should be from body - region, - ), - region, - ); - - branch_cons.push(cond_con); - branch_cons.push(then_con); - } - - let else_con = constrain_expr( - arena, - env, - final_else_expr, - Expected::ForReason( - Reason::IfBranch { - index: Index::zero_based(branches.len()), - total_branches: branches.len() + 1, - }, - Type2::Variable(*expr_var), - // should come from final_else - region, - ), - region, - ); - - branch_cons.push(Eq( - Type2::Variable(*expr_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - )); - - branch_cons.push(else_con); - - exists(arena, flex_vars, And(branch_cons)) - } - } - } - Expr2::When { - cond_var, - expr_var, - cond: cond_id, - branches, - } => { - // Infer the condition expression's type. - let cond_type = Type2::Variable(*cond_var); - - let cond = env.pool.get(*cond_id); - - let expr_con = constrain_expr( - arena, - env, - cond, - Expected::NoExpectation(cond_type.shallow_clone()), - region, - ); - - let mut constraints = BumpVec::with_capacity_in(branches.len() + 1, arena); - - constraints.push(expr_con); - - let mut flex_vars = BumpVec::with_capacity_in(2, arena); - - flex_vars.push(*cond_var); - flex_vars.push(*expr_var); - - match &expected { - Expected::FromAnnotation(name, arity, _, _typ) => { - // NOTE deviation from elm. - // - // in elm, `_typ` is used, but because we have this `expr_var` too - // and need to constrain it, this is what works and gives better error messages - let typ = Type2::Variable(*expr_var); - - for (index, when_branch_id) in branches.iter_node_ids().enumerate() { - let when_branch = env.pool.get(when_branch_id); - - let pattern_region = region; - // let pattern_region = Region::across_all( - // when_branch.patterns.iter(env.pool).map(|v| &v.region), - // ); - - let branch_con = constrain_when_branch( - arena, - env, - // TODO: when_branch.value.region, - region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: Index::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), - Expected::FromAnnotation( - name.clone(), - *arity, - AnnotationSource::TypedWhenBranch { - index: Index::zero_based(index), - }, - typ.shallow_clone(), - ), - ); - - constraints.push(branch_con); - } - - constraints.push(Eq(typ, expected, Category::When, region)); - - return exists(arena, flex_vars, And(constraints)); - } - - _ => { - let branch_type = Type2::Variable(*expr_var); - let mut branch_cons = BumpVec::with_capacity_in(branches.len(), arena); - - for (index, when_branch_id) in branches.iter_node_ids().enumerate() { - let when_branch = env.pool.get(when_branch_id); - - let pattern_region = region; - // let pattern_region = - // Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); - - let branch_con = constrain_when_branch( - arena, - env, - region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: Index::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), - Expected::ForReason( - Reason::WhenBranch { - index: Index::zero_based(index), - }, - branch_type.shallow_clone(), - // TODO: when_branch.value.region, - region, - ), - ); - - branch_cons.push(branch_con); - } - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - and_constraints.push(And(branch_cons)); - and_constraints.push(Eq(branch_type, expected, Category::When, region)); - - constraints.push(And(and_constraints)); - } - } - - // exhautiveness checking happens when converting to mono::Expr - exists(arena, flex_vars, And(constraints)) - } - Expr2::LetValue { - def_id, - body_id, - body_var, - } => { - let value_def = env.pool.get(*def_id); - let body = env.pool.get(*body_id); - - let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region); - - match value_def { - ValueDef::WithAnnotation { .. } => todo!("implement {:?}", value_def), - ValueDef::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => { - let pattern = env.pool.get(*pattern_id); - - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - flex_vars.push(*body_var); - - let expr_type = Type2::Variable(*expr_var); - - let pattern_expected = PExpected::NoExpectation(expr_type.shallow_clone()); - let mut state = PatternState2 { - headers: BumpMap::new_in(arena), - vars: BumpVec::with_capacity_in(1, arena), - constraints: BumpVec::with_capacity_in(1, arena), - }; - - constrain_pattern(arena, env, pattern, region, pattern_expected, &mut state); - state.vars.push(*expr_var); - - let def_expr = env.pool.get(*expr_id); - - let constrained_def = Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), // always empty - flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments - def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments! - defs_constraint: And(state.constraints), - ret_constraint: constrain_expr( - arena, - env, - def_expr, - Expected::NoExpectation(expr_type), - region, - ), - })), - ret_constraint: body_con, - })); - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - and_constraints.push(constrained_def); - and_constraints.push(Eq( - Type2::Variable(*body_var), - expected, - Category::Storage(std::file!(), std::line!()), - // TODO: needs to be ret region - region, - )); - - exists(arena, flex_vars, And(and_constraints)) - } - } - } - Expr2::Update { - symbol, - updates, - ext_var, - record_var, - } => { - let field_types = PoolVec::with_capacity(updates.len() as u32, env.pool); - let mut flex_vars = BumpVec::with_capacity_in(updates.len() + 2, arena); - let mut cons = BumpVec::with_capacity_in(updates.len() + 1, arena); - let mut record_key_updates = SendMap::default(); - - for (record_field_id, field_type_node_id) in - updates.iter_node_ids().zip(field_types.iter_node_ids()) - { - let record_field = env.pool.get(record_field_id); - - match record_field { - RecordField::LabeledValue(pool_str, var, node_id) => { - let expr = env.pool.get(*node_id); - - let (field_type, field_con) = constrain_field_update( - arena, - env, - *var, - pool_str.as_str(env.pool).into(), - expr, - ); - - let field_type_id = env.pool.add(field_type); - - env.pool[field_type_node_id] = - (*pool_str, types::RecordField::Required(field_type_id)); - - record_key_updates.insert(pool_str.as_str(env.pool).into(), Region::zero()); - - flex_vars.push(*var); - cons.push(field_con); - } - e => todo!("{:?}", e), - } - } - - let fields_type = Type2::Record(field_types, env.pool.add(Type2::Variable(*ext_var))); - let record_type = Type2::Variable(*record_var); - - // NOTE from elm compiler: fields_type is separate so that Error propagates better - let fields_con = Eq( - record_type.shallow_clone(), - Expected::NoExpectation(fields_type), - Category::Record, - region, - ); - let record_con = Eq( - record_type.shallow_clone(), - expected, - Category::Record, - region, - ); - - flex_vars.push(*record_var); - flex_vars.push(*ext_var); - - let con = Lookup( - *symbol, - Expected::ForReason( - Reason::RecordUpdateKeys(*symbol, record_key_updates), - record_type, - region, - ), - region, - ); - - // ensure constraints are solved in this order, gives better errors - cons.insert(0, fields_con); - cons.insert(1, con); - cons.insert(2, record_con); - - exists(arena, flex_vars, And(cons)) - } - - Expr2::RunLowLevel { op, args, ret_var } => { - // This is a modified version of what we do for function calls. - - // The operation's return type - let ret_type = Type2::Variable(*ret_var); - - // This will be used in the occurs check - let mut vars = BumpVec::with_capacity_in(1 + args.len(), arena); - - vars.push(*ret_var); - - let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); - let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); - - for (index, node_id) in args.iter_node_ids().enumerate() { - let (arg_var, arg_id) = env.pool.get(node_id); - - vars.push(*arg_var); - - let arg_type = Type2::Variable(*arg_var); - - let reason = Reason::LowLevelOpArg { - op: *op, - arg_index: Index::zero_based(index), - }; - let expected_arg = - Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); - let arg = env.pool.get(*arg_id); - - let arg_con = constrain_expr(arena, env, arg, expected_arg, Region::zero()); - - arg_types.push(arg_type); - arg_cons.push(arg_con); - } - - let category = Category::LowLevelOpResult(*op); - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - and_constraints.push(And(arg_cons)); - and_constraints.push(Eq(ret_type, expected, category, region)); - - exists(arena, vars, And(and_constraints)) - } - Expr2::Closure { - args, - name, - body: body_id, - function_type: fn_var, - extra, - .. - } => { - // NOTE defs are treated somewhere else! - let body = env.pool.get(*body_id); - - let ClosureExtra { - captured_symbols, - return_type: ret_var, - closure_type: closure_var, - closure_ext_var, - } = env.pool.get(*extra); - - let closure_type = Type2::Variable(*closure_var); - let return_type = Type2::Variable(*ret_var); - - let (mut vars, pattern_state, function_type) = - constrain_untyped_args(arena, env, args, closure_type, return_type.shallow_clone()); - - vars.push(*ret_var); - vars.push(*closure_var); - vars.push(*closure_ext_var); - vars.push(*fn_var); - - let expected_body_type = Expected::NoExpectation(return_type); - // Region here should come from body expr - let ret_constraint = constrain_expr(arena, env, body, expected_body_type, region); - - let captured_symbols_as_vec = captured_symbols - .iter(env.pool) - .copied() - .collect::>(); - - // make sure the captured symbols are sorted! - debug_assert_eq!(captured_symbols_as_vec, { - let mut copy: Vec<(Symbol, Variable)> = captured_symbols_as_vec.clone(); - - copy.sort(); - - copy - }); - - let closure_constraint = constrain_closure_size( - arena, - env, - *name, - region, - captured_symbols, - *closure_var, - *closure_ext_var, - &mut vars, - ); - - let mut and_constraints = BumpVec::with_capacity_in(4, arena); - - and_constraints.push(Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: pattern_state.vars, - def_types: pattern_state.headers, - defs_constraint: And(pattern_state.constraints), - ret_constraint, - }))); - - // "the closure's type is equal to expected type" - and_constraints.push(Eq( - function_type.shallow_clone(), - expected, - Category::Lambda, - region, - )); - - // "fn_var is equal to the closure's type" - fn_var is used in code gen - and_constraints.push(Eq( - Type2::Variable(*fn_var), - Expected::NoExpectation(function_type), - Category::Storage(std::file!(), std::line!()), - region, - )); - - and_constraints.push(closure_constraint); - - exists(arena, vars, And(and_constraints)) - } - Expr2::LetRec { .. } => todo!(), - Expr2::LetFunction { .. } => todo!(), - } -} - -fn exists<'a>( - arena: &'a Bump, - flex_vars: BumpVec<'a, Variable>, - defs_constraint: Constraint<'a>, -) -> Constraint<'a> { - Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars, - def_types: BumpMap::new_in(arena), - defs_constraint, - ret_constraint: Constraint::True, - })) -} - -#[allow(clippy::too_many_arguments)] -fn constrain_tag<'a>( - arena: &'a Bump, - env: &mut Env, - expected: Expected, - region: Region, - tag_name: TagName, - arguments: &PoolVec<(Variable, ExprId)>, - ext_var: Variable, - variant_var: Variable, -) -> Constraint<'a> { - use Constraint::*; - - let mut flex_vars = BumpVec::with_capacity_in(arguments.len(), arena); - let types = PoolVec::with_capacity(arguments.len() as u32, env.pool); - let mut arg_cons = BumpVec::with_capacity_in(arguments.len(), arena); - - for (argument_node_id, type_node_id) in arguments.iter_node_ids().zip(types.iter_node_ids()) { - let (var, expr_node_id) = env.pool.get(argument_node_id); - - let argument_expr = env.pool.get(*expr_node_id); - - let arg_con = constrain_expr( - arena, - env, - argument_expr, - Expected::NoExpectation(Type2::Variable(*var)), - region, - ); - - arg_cons.push(arg_con); - flex_vars.push(*var); - - env.pool[type_node_id] = Type2::Variable(*var); - } - - let union_con = Eq( - Type2::TagUnion( - PoolVec::new(std::iter::once((tag_name.clone(), types)), env.pool), - env.pool.add(Type2::Variable(ext_var)), - ), - expected.shallow_clone(), - Category::TagApply { - tag_name, - args_count: arguments.len(), - }, - region, - ); - - let ast_con = Eq( - Type2::Variable(variant_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - flex_vars.push(variant_var); - flex_vars.push(ext_var); - - arg_cons.push(union_con); - arg_cons.push(ast_con); - - exists(arena, flex_vars, And(arg_cons)) -} - -fn constrain_field<'a>( - arena: &'a Bump, - env: &mut Env, - field_var: Variable, - expr: &Expr2, -) -> (Type2, Constraint<'a>) { - let field_type = Type2::Variable(field_var); - let field_expected = Expected::NoExpectation(field_type.shallow_clone()); - let constraint = constrain_expr(arena, env, expr, field_expected, Region::zero()); - - (field_type, constraint) -} - -#[inline(always)] -fn constrain_field_update<'a>( - arena: &'a Bump, - env: &mut Env, - field_var: Variable, - field: Lowercase, - expr: &Expr2, -) -> (Type2, Constraint<'a>) { - let field_type = Type2::Variable(field_var); - let reason = Reason::RecordUpdateValue(field); - let field_expected = Expected::ForReason(reason, field_type.shallow_clone(), Region::zero()); - let con = constrain_expr(arena, env, expr, field_expected, Region::zero()); - - (field_type, con) -} - -fn constrain_empty_record<'a>(expected: Expected, region: Region) -> Constraint<'a> { - Constraint::Eq(Type2::EmptyRec, expected, Category::Record, region) -} - -#[inline(always)] -fn constrain_when_branch<'a>( - arena: &'a Bump, - env: &mut Env, - region: Region, - when_branch: &WhenBranch, - pattern_expected: PExpected, - expr_expected: Expected, -) -> Constraint<'a> { - let when_expr = env.pool.get(when_branch.body); - - let ret_constraint = constrain_expr(arena, env, when_expr, expr_expected, region); - - let mut state = PatternState2 { - headers: BumpMap::new_in(arena), - vars: BumpVec::with_capacity_in(1, arena), - constraints: BumpVec::with_capacity_in(1, arena), - }; - - // TODO investigate for error messages, is it better to unify all branches with a variable, - // then unify that variable with the expectation? - for pattern_id in when_branch.patterns.iter_node_ids() { - let pattern = env.pool.get(pattern_id); - - constrain_pattern( - arena, - env, - pattern, - // loc_pattern.region, - region, - pattern_expected.shallow_clone(), - &mut state, - ); - } - - if let Some(guard_id) = &when_branch.guard { - let guard = env.pool.get(*guard_id); - - let guard_constraint = constrain_expr( - arena, - env, - guard, - Expected::ForReason( - Reason::WhenGuard, - Type2::Variable(Variable::BOOL), - // TODO: loc_guard.region, - region, - ), - region, - ); - - // must introduce the headers from the pattern before constraining the guard - Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), - ret_constraint: Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: BumpVec::new_in(arena), - def_types: BumpMap::new_in(arena), - defs_constraint: guard_constraint, - ret_constraint, - })), - })) - } else { - Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), - ret_constraint, - })) - } -} - -/// This accepts PatternState (rather than returning it) so that the caller can -/// initialize the Vecs in PatternState using with_capacity -/// based on its knowledge of their lengths. -pub fn constrain_pattern<'a>( - arena: &'a Bump, - env: &mut Env, - pattern: &Pattern2, - region: Region, - expected: PExpected, - state: &mut PatternState2<'a>, -) { - use Pattern2::*; - - match pattern { - Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => { - // Neither the _ pattern nor erroneous ones add any constraints. - } - - Identifier(symbol) => { - state.headers.insert(*symbol, expected.get_type()); - } - - NumLiteral(var, _) => { - state.vars.push(*var); - - let type_id = env.pool.add(Type2::Variable(*var)); - - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Num, - num_num(env.pool, type_id), - expected, - )); - } - - IntLiteral(_int_val) => { - let precision_var = env.var_store.fresh(); - - let range = env.add(Type2::Variable(precision_var), region); - - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Int, - num_int(env.pool, range), - expected, - )); - } - - FloatLiteral(_float_val) => { - let precision_var = env.var_store.fresh(); - - let range = env.add(Type2::Variable(precision_var), region); - - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Float, - num_float(env.pool, range), - expected, - )); - } - - StrLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Str, - str_type(env.pool), - expected, - )); - } - - RecordDestructure { - whole_var, - ext_var, - destructs, - } => { - state.vars.push(*whole_var); - state.vars.push(*ext_var); - let ext_type = Type2::Variable(*ext_var); - - let mut field_types = Vec::new(); - - for destruct_id in destructs.iter_node_ids() { - let RecordDestruct { - var, - label, - symbol, - typ, - } = env.pool.get(destruct_id); - - let pat_type = Type2::Variable(*var); - let expected = PExpected::NoExpectation(pat_type.shallow_clone()); - - if !state.headers.contains_key(symbol) { - state.headers.insert(*symbol, pat_type.shallow_clone()); - } - - let destruct_type = env.pool.get(*typ); - - let field_type = match destruct_type { - DestructType::Guard(guard_var, guard_id) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::PatternGuard, - Type2::Variable(*guard_var), - PExpected::ForReason( - PReason::PatternGuard, - pat_type.shallow_clone(), - // TODO: region should be from guard_id - region, - ), - )); - - state.vars.push(*guard_var); - - let guard = env.pool.get(*guard_id); - - // TODO: region should be from guard_id - constrain_pattern(arena, env, guard, region, expected, state); - - types::RecordField::Demanded(env.pool.add(pat_type)) - } - DestructType::Optional(expr_var, expr_id) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::PatternDefault, - Type2::Variable(*expr_var), - PExpected::ForReason( - PReason::OptionalField, - pat_type.shallow_clone(), - // TODO: region should be from expr_id - region, - ), - )); - - state.vars.push(*expr_var); - - let expr_expected = Expected::ForReason( - Reason::RecordDefaultField(label.as_str(env.pool).into()), - pat_type.shallow_clone(), - // TODO: region should be from expr_id - region, - ); - - let expr = env.pool.get(*expr_id); - - // TODO: region should be from expr_id - let expr_con = constrain_expr(arena, env, expr, expr_expected, region); - - state.constraints.push(expr_con); - - types::RecordField::Optional(env.pool.add(pat_type)) - } - DestructType::Required => { - // No extra constraints necessary. - types::RecordField::Demanded(env.pool.add(pat_type)) - } - }; - - field_types.push((*label, field_type)); - - state.vars.push(*var); - } - - let record_type = Type2::Record( - PoolVec::new(field_types.into_iter(), env.pool), - env.pool.add(ext_type), - ); - - let whole_con = Constraint::Eq( - Type2::Variable(*whole_var), - Expected::NoExpectation(record_type), - Category::Storage(std::file!(), std::line!()), - region, - ); - - let record_con = Constraint::Pattern( - region, - PatternCategory::Record, - Type2::Variable(*whole_var), - expected, - ); - - state.constraints.push(whole_con); - state.constraints.push(record_con); - } - GlobalTag { - whole_var, - ext_var, - tag_name: name, - arguments, - } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag_pattern( - arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, - ); - } - PrivateTag { - whole_var, - ext_var, - tag_name: name, - arguments, - } => { - let tag_name = TagName::Private(*name); - - constrain_tag_pattern( - arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, - ); - } - } -} - -#[allow(clippy::too_many_arguments)] -fn constrain_tag_pattern<'a>( - arena: &'a Bump, - env: &mut Env, - region: Region, - expected: PExpected, - state: &mut PatternState2<'a>, - whole_var: Variable, - ext_var: Variable, - arguments: &PoolVec<(Variable, PatternId)>, - tag_name: TagName, -) { - let mut argument_types = Vec::with_capacity(arguments.len()); - - for (index, arg_id) in arguments.iter_node_ids().enumerate() { - let (pattern_var, pattern_id) = env.pool.get(arg_id); - let pattern = env.pool.get(*pattern_id); - - state.vars.push(*pattern_var); - - let pattern_type = Type2::Variable(*pattern_var); - argument_types.push(pattern_type.shallow_clone()); - - let expected = PExpected::ForReason( - PReason::TagArg { - tag_name: tag_name.clone(), - index: Index::zero_based(index), - }, - pattern_type, - region, - ); - - // TODO region should come from pattern - constrain_pattern(arena, env, pattern, region, expected, state); - } - - let whole_con = Constraint::Eq( - Type2::Variable(whole_var), - Expected::NoExpectation(Type2::TagUnion( - PoolVec::new( - vec![( - tag_name.clone(), - PoolVec::new(argument_types.into_iter(), env.pool), - )] - .into_iter(), - env.pool, - ), - env.pool.add(Type2::Variable(ext_var)), - )), - Category::Storage(std::file!(), std::line!()), - region, - ); - - let tag_con = Constraint::Pattern( - region, - PatternCategory::Ctor(tag_name), - Type2::Variable(whole_var), - expected, - ); - - state.vars.push(whole_var); - state.vars.push(ext_var); - state.constraints.push(whole_con); - state.constraints.push(tag_con); -} - -fn constrain_untyped_args<'a>( - arena: &'a Bump, - env: &mut Env, - arguments: &PoolVec<(Variable, PatternId)>, - closure_type: Type2, - return_type: Type2, -) -> (BumpVec<'a, Variable>, PatternState2<'a>, Type2) { - let mut vars = BumpVec::with_capacity_in(arguments.len(), arena); - - let pattern_types = PoolVec::with_capacity(arguments.len() as u32, env.pool); - - let mut pattern_state = PatternState2 { - headers: BumpMap::new_in(arena), - vars: BumpVec::with_capacity_in(1, arena), - constraints: BumpVec::with_capacity_in(1, arena), - }; - - for (arg_node_id, pattern_type_id) in - arguments.iter_node_ids().zip(pattern_types.iter_node_ids()) - { - let (pattern_var, pattern_id) = env.pool.get(arg_node_id); - let pattern = env.pool.get(*pattern_id); - - let pattern_type = Type2::Variable(*pattern_var); - let pattern_expected = PExpected::NoExpectation(pattern_type.shallow_clone()); - - env.pool[pattern_type_id] = pattern_type; - - constrain_pattern( - arena, - env, - pattern, - // TODO needs to come from pattern - Region::zero(), - pattern_expected, - &mut pattern_state, - ); - - vars.push(*pattern_var); - } - - let function_type = Type2::Function( - pattern_types, - env.pool.add(closure_type), - env.pool.add(return_type), - ); - - (vars, pattern_state, function_type) -} - -#[allow(clippy::too_many_arguments)] -fn constrain_closure_size<'a>( - arena: &'a Bump, - env: &mut Env, - name: Symbol, - region: Region, - captured_symbols: &PoolVec<(Symbol, Variable)>, - closure_var: Variable, - closure_ext_var: Variable, - variables: &mut BumpVec<'a, Variable>, -) -> Constraint<'a> { - use Constraint::*; - - debug_assert!(variables.iter().any(|s| *s == closure_var)); - debug_assert!(variables.iter().any(|s| *s == closure_ext_var)); - - let tag_arguments = PoolVec::with_capacity(captured_symbols.len() as u32, env.pool); - let mut captured_symbols_constraints = BumpVec::with_capacity_in(captured_symbols.len(), arena); - - for (captured_symbol_id, tag_arg_id) in captured_symbols - .iter_node_ids() - .zip(tag_arguments.iter_node_ids()) - { - let (symbol, var) = env.pool.get(captured_symbol_id); - - // make sure the variable is registered - variables.push(*var); - - let tag_arg_type = Type2::Variable(*var); - - // this symbol is captured, so it must be part of the closure type - env.pool[tag_arg_id] = tag_arg_type.shallow_clone(); - - // make the variable equal to the looked-up type of symbol - captured_symbols_constraints.push(Lookup( - *symbol, - Expected::NoExpectation(tag_arg_type), - Region::zero(), - )); - } - - let tag_name = TagName::Closure(name); - let closure_type = Type2::TagUnion( - PoolVec::new(vec![(tag_name, tag_arguments)].into_iter(), env.pool), - env.pool.add(Type2::Variable(closure_ext_var)), - ); - - let finalizer = Eq( - Type2::Variable(closure_var), - Expected::NoExpectation(closure_type), - Category::ClosureSize, - region, - ); - - captured_symbols_constraints.push(finalizer); - - And(captured_symbols_constraints) -} - -#[inline(always)] -fn builtin_type(symbol: Symbol, args: PoolVec) -> Type2 { - Type2::Apply(symbol, args) -} - -#[inline(always)] -fn str_type(pool: &mut Pool) -> Type2 { - builtin_type(Symbol::STR_STR, PoolVec::empty(pool)) -} - -#[inline(always)] -fn empty_list_type(pool: &mut Pool, var: Variable) -> Type2 { - list_type(pool, Type2::Variable(var)) -} - -#[inline(always)] -fn list_type(pool: &mut Pool, typ: Type2) -> Type2 { - builtin_type(Symbol::LIST_LIST, PoolVec::new(vec![typ].into_iter(), pool)) -} - -#[inline(always)] -fn num_float(pool: &mut Pool, range: TypeId) -> Type2 { - let num_floatingpoint_type = num_floatingpoint(pool, range); - let num_floatingpoint_id = pool.add(num_floatingpoint_type); - - let num_num_type = num_num(pool, num_floatingpoint_id); - let num_num_id = pool.add(num_num_type); - - Type2::Alias( - Symbol::NUM_FLOAT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - num_num_id, - ) -} - -#[inline(always)] -fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 { - let range_type = pool.get(range); - - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_FLOATINGPOINT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - pool.add(alias_content), - ) -} - -#[inline(always)] -fn num_int(pool: &mut Pool, range: TypeId) -> Type2 { - let num_integer_type = _num_integer(pool, range); - let num_integer_id = pool.add(num_integer_type); - - let num_num_type = num_num(pool, num_integer_id); - let num_num_id = pool.add(num_num_type); - - Type2::Alias( - Symbol::NUM_INT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - num_num_id, - ) -} - -#[inline(always)] -fn _num_signed64(pool: &mut Pool) -> Type2 { - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_SIGNED64), - PoolVec::empty(pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_SIGNED64, - PoolVec::empty(pool), - pool.add(alias_content), - ) -} - -#[inline(always)] -fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { - let range_type = pool.get(range); - - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_INTEGER), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_INTEGER, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - pool.add(alias_content), - ) -} - -#[inline(always)] -fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { - let range_type = pool.get(type_id); - - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_NUM), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_NUM, - PoolVec::new( - vec![(PoolStr::new("range", pool), type_id)].into_iter(), - pool, - ), - pool.add(alias_content), - ) -} diff --git a/editor/src/lang/def.rs b/editor/src/lang/def.rs deleted file mode 100644 index c179145a7c..0000000000 --- a/editor/src/lang/def.rs +++ /dev/null @@ -1,1444 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_variables)] -// use crate::annotation::canonicalize_annotation; -// use crate::annotation::IntroducedVariables; -// use crate::env::Env; -// use crate::expr::Expr::{self, *}; -// use crate::expr::{ -// canonicalize_expr, local_successors, references_from_call, references_from_local, Output, -// Recursive, -// }; -// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; -// use crate::procedure::References; -use crate::lang::ast::{Expr2, FunctionDef, Rigids, ValueDef}; -use crate::lang::expr::Output; -use crate::lang::expr::{to_expr2, to_expr_id, Env}; -use crate::lang::pattern::{ - symbols_and_variables_from_pattern, symbols_from_pattern, to_pattern_id, Pattern2, PatternId, -}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId}; -use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap}; -use roc_module::ident::Lowercase; -use roc_module::symbol::Symbol; -use roc_parse::ast; -use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; -use std::collections::HashMap; -use std::fmt::Debug; -use ven_graph::{strongly_connected_components, topological_sort_into_groups}; - -#[derive(Debug)] -pub enum Def { - AnnotationOnly { rigids: Rigids, annotation: TypeId }, - Value(ValueDef), - Function(FunctionDef), -} - -impl Def { - pub fn symbols(&self, pool: &Pool) -> MutSet { - let mut output = MutSet::default(); - - match self { - Def::AnnotationOnly { .. } => todo!("lost pattern information here ... "), - Def::Value(value_def) => match value_def { - ValueDef::WithAnnotation { pattern_id, .. } - | ValueDef::NoAnnotation { pattern_id, .. } => { - let pattern2 = &pool[*pattern_id]; - output.extend(symbols_from_pattern(pool, pattern2)); - } - }, - Def::Function(function_def) => match function_def { - FunctionDef::NoAnnotation { name, .. } - | FunctionDef::WithAnnotation { name, .. } => { - output.insert(*name); - } - }, - } - - output - } -} - -impl ShallowClone for Def { - fn shallow_clone(&self) -> Self { - match self { - Self::AnnotationOnly { rigids, annotation } => Self::AnnotationOnly { - rigids: rigids.shallow_clone(), - annotation: *annotation, - }, - Self::Value(def) => Self::Value(def.shallow_clone()), - Self::Function(def) => Self::Function(def.shallow_clone()), - } - } -} - -/// A Def that has had patterns and type annnotations canonicalized, -/// but no Expr canonicalization has happened yet. Also, it has had spaces -/// and nesting resolved, and knows whether annotations are standalone or not. -#[derive(Debug)] -pub enum PendingDef<'a> { - /// A standalone annotation with no body - AnnotationOnly( - &'a Located>, - PatternId, - &'a Located>, - ), - /// A body with no type annotation - Body( - &'a Located>, - PatternId, - &'a Located>, - ), - /// A body with a type annotation - TypedBody( - &'a Located>, - PatternId, - &'a Located>, - &'a Located>, - ), - - /// A type alias, e.g. `Ints : List Int` - Alias { - name: Located, - vars: Vec>, - ann: &'a Located>, - }, - - /// An invalid alias, that is ignored in the rest of the pipeline - /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` - /// with an incorrect pattern - InvalidAlias, -} - -fn to_pending_def<'a>( - env: &mut Env<'a>, - def: &'a ast::Def<'a>, - scope: &mut Scope, - pattern_type: PatternType, -) -> Option<(Output, PendingDef<'a>)> { - use roc_parse::ast::Def::*; - - match def { - Annotation(loc_pattern, loc_ann) => { - // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - Some(( - output, - PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), - )) - } - Body(loc_pattern, loc_expr) => { - // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - Some(( - output, - PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr), - )) - } - - AnnotatedBody { - ann_pattern, - ann_type, - comment: _, - body_pattern, - body_expr, - } => { - if ann_pattern.value.equivalent(&body_pattern.value) { - // NOTE: Pick the body pattern, picking the annotation one is - // incorrect in the presence of optional record fields! - // - // { x, y } : { x : Int, y ? Bool }* - // { x, y ? False } = rec - Some(pending_typed_body( - env, - body_pattern, - ann_type, - body_expr, - scope, - pattern_type, - )) - } else { - // the pattern of the annotation does not match the pattern of the body direc - env.problem(Problem::SignatureDefMismatch { - annotation_pattern: ann_pattern.region, - def_pattern: body_pattern.region, - }); - - // TODO: Should we instead build some PendingDef::InvalidAnnotatedBody ? This would - // remove the `Option` on this function (and be probably more reliable for further - // problem/error reporting) - None - } - } - - roc_parse::ast::Def::Alias { name, vars, ann } => { - let region = Region::span_across(&name.region, &ann.region); - - match scope.introduce( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); - - for loc_var in vars.iter() { - match loc_var.value { - ast::Pattern::Identifier(name) - if name.chars().next().unwrap().is_lowercase() => - { - let lowercase = Lowercase::from(name); - can_rigids.push(Located { - value: lowercase, - region: loc_var.region, - }); - } - _ => { - // any other pattern in this position is a syntax error. - env.problem(Problem::InvalidAliasRigid { - alias_name: symbol, - region: loc_var.region, - }); - - return Some((Output::default(), PendingDef::InvalidAlias)); - } - } - } - - Some(( - Output::default(), - PendingDef::Alias { - name: Located { - region: name.region, - value: symbol, - }, - vars: can_rigids, - ann, - }, - )) - } - - Err((original_region, loc_shadowed_symbol)) => { - env.problem(Problem::ShadowingInAnnotation { - original_region, - shadow: loc_shadowed_symbol, - }); - - Some((Output::default(), PendingDef::InvalidAlias)) - } - } - } - - Expect(_) => todo!(), - - SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { - to_pending_def(env, sub_def, scope, pattern_type) - } - - NotYetImplemented(s) => todo!("{}", s), - } -} - -fn pending_typed_body<'a>( - env: &mut Env<'a>, - loc_pattern: &'a Located>, - loc_ann: &'a Located>, - loc_expr: &'a Located>, - scope: &mut Scope, - pattern_type: PatternType, -) -> (Output, PendingDef<'a>) { - // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = to_pattern_id( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - ( - output, - PendingDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr), - ) -} - -fn from_pending_alias<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - name: Located, - vars: Vec>, - ann: &'a Located>, - mut output: Output, -) -> Output { - let symbol = name.value; - - match to_annotation2(env, scope, &ann.value, ann.region) { - Annotation2::Erroneous(_) => todo!(), - Annotation2::Annotation { - named_rigids, - unnamed_rigids, - symbols, - signature, - } => { - // Record all the annotation's references in output.references.lookups - - for symbol in symbols { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - for loc_lowercase in vars { - if !named_rigids.contains_key(loc_lowercase.value.as_str()) { - env.problem(Problem::PhantomTypeArgument { - alias: symbol, - variable_region: loc_lowercase.region, - variable_name: loc_lowercase.value.clone(), - }); - } - } - - let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); - - let annotation = match signature { - Signature::Value { annotation } => annotation, - Signature::Function { - arguments, - closure_type_id, - return_type_id, - } => Type2::Function(arguments, closure_type_id, return_type_id), - Signature::FunctionWithAliases { annotation, .. } => annotation, - }; - - if annotation.contains_symbol(env.pool, symbol) { - // the alias is recursive. If it's a tag union, we attempt to fix this - if let Type2::TagUnion(tags, ext) = annotation { - // re-canonicalize the alias with the alias already in scope - let rec_var = env.var_store.fresh(); - let rec_type_union = Type2::RecursiveTagUnion(rec_var, tags, ext); - - // NOTE this only looks at the symbol, and just assumes that the - // recursion is not polymorphic - rec_type_union.substitute_alias(env.pool, symbol, Type2::Variable(rec_var)); - - let annotation_id = env.add(rec_type_union, ann.region); - let named = rigids.named(env.pool); - - scope.add_alias(env.pool, symbol, named, annotation_id); - } else { - env.problem(Problem::CyclicAlias(symbol, name.region, vec![])); - return output; - } - } else { - let annotation_id = env.add(annotation, ann.region); - let named = rigids.named(env.pool); - - scope.add_alias(env.pool, symbol, named, annotation_id); - } - - output - } - } -} - -// TODO trim down these arguments! -#[allow(clippy::too_many_arguments)] -#[allow(clippy::cognitive_complexity)] -fn canonicalize_pending_def<'a>( - env: &mut Env<'a>, - pending_def: PendingDef<'a>, - mut output: Output, - scope: &mut Scope, - can_defs_by_symbol: &mut MutMap, - refs_by_symbol: &mut MutMap, - aliases: &mut MutMap, -) -> Output { - use PendingDef::*; - - // Make types for the body expr, even if we won't end up having a body. - let expr_var = env.var_store.fresh(); - - match pending_def { - AnnotationOnly(_, loc_can_pattern, loc_ann) => { - // annotation sans body cannot introduce new rigids that are visible in other annotations - // but the rigids can show up in type error messages, so still register them - - match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { - Annotation2::Erroneous(_) => todo!(), - Annotation2::Annotation { - named_rigids, - unnamed_rigids, - symbols, - signature, - } => { - // Record all the annotation's references in output.references.lookups - - for symbol in symbols { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); - - let annotation = match signature { - Signature::Value { annotation } => annotation, - Signature::Function { - arguments, - closure_type_id, - return_type_id, - } => Type2::Function(arguments, closure_type_id, return_type_id), - Signature::FunctionWithAliases { annotation, .. } => annotation, - }; - let annotation = env.add(annotation, loc_ann.region); - - let def = Def::AnnotationOnly { rigids, annotation }; - - for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { - can_defs_by_symbol.insert(symbol, def.shallow_clone()); - } - - output - } - } - } - - PendingDef::Alias { name, ann, vars } => { - from_pending_alias(env, scope, name, vars, ann, output) - } - - InvalidAlias => { - // invalid aliases (shadowed, incorrect patterns ) - todo!() - } - - TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { - match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { - Annotation2::Erroneous(_) => todo!(), - Annotation2::Annotation { - named_rigids, - unnamed_rigids, - symbols, - signature, - } => { - // Record all the annotation's references in output.references.lookups - - for symbol in symbols { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); - - // bookkeeping for tail-call detection. If we're assigning to an - // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. - let outer_identifier = env.tailcallable_symbol; - - if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { - env.tailcallable_symbol = Some(*defined_symbol); - }; - - // regiser the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&env.pool[loc_can_pattern], &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - let (loc_can_expr, can_output) = - to_expr2(env, scope, &loc_expr.value, loc_expr.region); - - output.references.union_mut(can_output.references.clone()); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // First, make sure we are actually assigning an identifier instead of (for example) a tag. - // - // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, - // which also implies it's not a self tail call! - // - // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - match loc_can_expr { - Expr2::Closure { - args: closure_args, - body, - extra, - name: closure_symbol, - .. - } => { - let symbol = match env.pool[loc_can_pattern] { - Pattern2::Identifier(ref s) => *s, - _ => todo!( - r"this is an error; functions must be bound with an identifier pattern!" - ), - }; - - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. - let references = - env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) - }); - - // TODO should we re-insert this function into env.closures? - env.closures.insert(symbol, references); - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.lookups.remove(&symbol); - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - - let arguments: PoolVec<(PatternId, Type2)> = - PoolVec::with_capacity(closure_args.len() as u32, env.pool); - - let return_type: TypeId; - - let annotation = match signature { - Signature::Value { .. } => { - todo!("type annotation says 0 arguments, but it's a function") - } - Signature::Function { - arguments: type_arguments, - closure_type_id, - return_type_id, - } - | Signature::FunctionWithAliases { - arguments: type_arguments, - closure_type_id, - return_type_id, - .. - } => { - if arguments.len() != type_arguments.len() { - panic!("argument number mismatch"); - } - - let it: Vec<_> = closure_args - .iter(env.pool) - .map(|(x, y)| (*x, *y)) - .zip( - type_arguments - .iter(env.pool) - .map(|t| t.shallow_clone()), - ) - .collect(); - - for (node_id, ((_, pattern_id), typ)) in - arguments.iter_node_ids().zip(it.into_iter()) - { - env.pool[node_id] = (pattern_id, typ); - } - - return_type = return_type_id; - } - }; - - let function_def = FunctionDef::WithAnnotation { - name: symbol, - arguments, - rigids: env.pool.add(rigids), - return_type, - body, - }; - - let def = Def::Function(function_def); - - can_defs_by_symbol.insert(symbol, def); - - output - } - - _ => { - let annotation = match signature { - Signature::Value { annotation } => annotation, - Signature::Function { - arguments, - closure_type_id, - return_type_id, - } => Type2::Function(arguments, closure_type_id, return_type_id), - Signature::FunctionWithAliases { annotation, .. } => annotation, - }; - let annotation = env.add(annotation, loc_ann.region); - - let value_def = ValueDef::WithAnnotation { - pattern_id: loc_can_pattern, - expr_id: env.pool.add(loc_can_expr), - type_id: annotation, - rigids: rigids, - expr_var: env.var_store.fresh(), - }; - - let def = Def::Value(value_def); - - for symbol in - symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) - { - can_defs_by_symbol.insert(symbol, def.shallow_clone()); - } - - for (_, (symbol, region)) in scope.idents() { - // if !vars_by_symbol.contains_key(&symbol) { - // continue; - // } - let refs = can_output.references.clone(); - refs_by_symbol.insert(*symbol, (*region, refs)); - } - - output - } - } - } - } - } - - Body(loc_pattern, loc_can_pattern, loc_expr) => { - // bookkeeping for tail-call detection. If we're assigning to an - // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. - let outer_identifier = env.tailcallable_symbol; - - if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { - env.tailcallable_symbol = Some(*defined_symbol); - }; - - // regiser the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&env.pool[loc_can_pattern], &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - let (loc_can_expr, can_output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); - - output.references.union_mut(can_output.references.clone()); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // First, make sure we are actually assigning an identifier instead of (for example) a tag. - // - // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, - // which also implies it's not a self tail call! - // - // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - match loc_can_expr { - Expr2::Closure { - args: closure_args, - body, - extra, - name: closure_symbol, - .. - } => { - let symbol = match env.pool[loc_can_pattern] { - Pattern2::Identifier(ref s) => *s, - _ => todo!( - r"this is an error; functions must be bound with an identifier pattern!" - ), - }; - - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. - let references = - env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) - }); - - // TODO should we re-insert this function into env.closures? - env.closures.insert(symbol, references); - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.lookups.remove(&symbol); - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - - let arguments: PoolVec<(PatternId, Variable)> = - PoolVec::with_capacity(closure_args.len() as u32, env.pool); - - let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect(); - - for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter()) - { - env.pool[node_id] = (pattern_id, env.var_store.fresh()); - } - - let function_def = FunctionDef::NoAnnotation { - name: symbol, - arguments, - return_var: env.var_store.fresh(), - body, - }; - - let def = Def::Function(function_def); - - can_defs_by_symbol.insert(symbol, def); - - output - } - - _ => { - let value_def = ValueDef::NoAnnotation { - pattern_id: loc_can_pattern, - expr_id: env.pool.add(loc_can_expr), - expr_var: env.var_store.fresh(), - }; - - let def = Def::Value(value_def); - - for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { - can_defs_by_symbol.insert(symbol, def.shallow_clone()); - } - - for (_, (symbol, region)) in scope.idents() { - // if !vars_by_symbol.contains_key(&symbol) { - // continue; - // } - let refs = can_output.references.clone(); - refs_by_symbol.insert(*symbol, (*region, refs)); - } - - output - } - } - } - } -} - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct References { - pub bound_symbols: MutSet, - pub lookups: MutSet, - pub referenced_aliases: MutSet, - pub calls: MutSet, -} - -impl References { - pub fn new() -> References { - Self::default() - } - - pub fn union_mut(&mut self, other: References) { - self.lookups.extend(other.lookups); - self.calls.extend(other.calls); - self.bound_symbols.extend(other.bound_symbols); - self.referenced_aliases.extend(other.referenced_aliases); - } - - pub fn has_lookup(&self, symbol: Symbol) -> bool { - self.lookups.contains(&symbol) - } -} - -#[derive(Debug)] -pub struct CanDefs { - pub refs_by_symbol: MutMap, - pub can_defs_by_symbol: MutMap, - pub aliases: MutMap, -} - -#[inline(always)] -pub fn canonicalize_defs<'a>( - env: &mut Env<'a>, - mut output: Output, - original_scope: &Scope, - loc_defs: &'a [&'a Located>], - pattern_type: PatternType, -) -> (CanDefs, Scope, Output, MutMap) { - // Canonicalizing defs while detecting shadowing involves a multi-step process: - // - // 1. Go through each of the patterns. - // 2. For each identifier pattern, get the scope.symbol() for the ident. (That symbol will use the home module for its module.) - // 3. If that symbol is already in scope, then we're about to shadow it. Error! - // 4. Otherwise, add it to the scope immediately, so we can detect shadowing within the same - // pattern (e.g. (Foo a a) = ...) - // 5. Add this canonicalized pattern and its corresponding ast::Expr to pending_exprs. - // 5. Once every pattern has been processed and added to scope, go back and canonicalize the exprs from - // pending_exprs, this time building up a canonical def for each one. - // - // This way, whenever any expr is doing lookups, it knows everything that's in scope - - // even defs that appear after it in the source. - // - // This naturally handles recursion too, because a given expr which refers - // to itself won't be processed until after its def has been added to scope. - - // Record both the original and final idents from the scope, - // so we can diff them while detecting unused defs. - let mut scope = original_scope.shallow_clone(); - let num_defs = loc_defs.len(); - let mut refs_by_symbol = MutMap::default(); - let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); - let mut pending = Vec::with_capacity(num_defs); // TODO bump allocate this! - - // Canonicalize all the patterns, record shadowing problems, and store - // the ast::Expr values in pending_exprs for further canonicalization - // once we've finished assembling the entire scope. - for loc_def in loc_defs { - match to_pending_def(env, &loc_def.value, &mut scope, pattern_type) { - None => (), - Some((new_output, pending_def)) => { - // store the top-level defs, used to ensure that closures won't capture them - if let PatternType::TopLevelDef = pattern_type { - match &pending_def { - PendingDef::AnnotationOnly(_, loc_can_pattern, _) - | PendingDef::Body(_, loc_can_pattern, _) - | PendingDef::TypedBody(_, loc_can_pattern, _, _) => { - env.top_level_symbols.extend(symbols_from_pattern( - env.pool, - env.pool.get(*loc_can_pattern), - )) - } - PendingDef::Alias { .. } | PendingDef::InvalidAlias => {} - } - } - // Record the ast::Expr for later. We'll do another pass through these - // once we have the entire scope assembled. If we were to canonicalize - // the exprs right now, they wouldn't have symbols in scope from defs - // that get would have gotten added later in the defs list! - pending.push(pending_def); - output.union(new_output); - } - } - } - - if cfg!(debug_assertions) { - env.home.register_debug_idents(&env.ident_ids); - } - - // TODO what to do here? aliases are already in the scope! - let mut aliases = MutMap::default(); - let mut value_defs = Vec::new(); - - for pending_def in pending.into_iter() { - match pending_def { - PendingDef::Alias { name, vars, ann } => { - output = from_pending_alias(env, &mut scope, name, vars, ann, output); - } - other => value_defs.push(other), - } - } - - // TODO - // correct_mutual_recursive_type_alias(env, &mut aliases, var_store); - - // Now that we have the scope completely assembled, and shadowing resolved, - // we're ready to canonicalize any body exprs. - for pending_def in value_defs.into_iter() { - output = canonicalize_pending_def( - env, - pending_def, - output, - &mut scope, - &mut can_defs_by_symbol, - &mut refs_by_symbol, - &mut aliases, - ); - - // TODO we should do something with these references; they include - // things like type annotations. - } - - // Determine which idents we introduced in the course of this process. - let mut symbols_introduced = MutMap::default(); - - for (symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(symbol) { - symbols_introduced.insert(symbol, region); - } - } - - // This returns both the defs info as well as the new scope. - // - // We have to return the new scope because we added defs to it - // (and those lookups shouldn't fail later, e.g. when canonicalizing - // the return expr), but we didn't want to mutate the original scope - // directly because we wanted to keep a clone of it around to diff - // when looking for unused idents. - // - // We have to return the scope separately from the defs, because the - // defs need to get moved later. - ( - CanDefs { - refs_by_symbol, - can_defs_by_symbol, - aliases, - }, - scope, - output, - symbols_introduced, - ) -} - -// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -pub enum Declaration { - Declare(Def), - DeclareRec(Vec), - Builtin(Def), - InvalidCycle(Vec, Vec<(Region /* pattern */, Region /* expr */)>), -} - -impl Declaration { - pub fn def_count(&self) -> usize { - use Declaration::*; - match self { - Declare(_) => 1, - DeclareRec(defs) => defs.len(), - InvalidCycle(_, _) => 0, - Builtin(_) => 0, - } - } -} - -#[inline(always)] -pub fn sort_can_defs( - env: &mut Env<'_>, - defs: CanDefs, - mut output: Output, -) -> (Result, RuntimeError>, Output) { - let CanDefs { - refs_by_symbol, - can_defs_by_symbol, - aliases, - } = defs; - - // for (symbol, alias) in aliases.into_iter() { - // output.aliases.insert(symbol, alias); - // } - - // Determine the full set of references by traversing the graph. - let mut visited_symbols = MutSet::default(); - let returned_lookups = MutSet::clone(&output.references.lookups); - - // Start with the return expression's referenced locals. They're the only ones that count! - // - // If I have two defs which reference each other, but neither of them is referenced - // in the return expression, I don't want either of them (or their references) to end up - // in the final output.references. They were unused, and so were their references! - // - // The reason we need a graph here is so we don't overlook transitive dependencies. - // For example, if I have `a = b + 1` and the def returns `a + 1`, then the - // def as a whole references both `a` *and* `b`, even though it doesn't - // directly mention `b` - because `a` depends on `b`. If we didn't traverse a graph here, - // we'd erroneously give a warning that `b` was unused since it wasn't directly referenced. - for symbol in returned_lookups.into_iter() { - // We only care about local symbols in this analysis. - if symbol.module_id() == env.home { - // Traverse the graph and look up *all* the references for this local symbol. - let refs = - references_from_local(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); - - output.references.union_mut(refs); - } - } - - for symbol in output.references.calls.clone() { - // Traverse the graph and look up *all* the references for this call. - // Reuse the same visited_symbols as before; if we already visited it, - // we won't learn anything new from visiting it again! - let refs = - references_from_call(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); - - output.references.union_mut(refs); - } - - let mut defined_symbols: Vec = Vec::new(); - let mut defined_symbols_set: MutSet = MutSet::default(); - - for symbol in can_defs_by_symbol.keys().into_iter() { - defined_symbols.push(*symbol); - defined_symbols_set.insert(*symbol); - } - - // Use topological sort to reorder the defs based on their dependencies to one another. - // This way, during code gen, no def will refer to a value that hasn't been initialized yet. - // As a bonus, the topological sort also reveals any cycles between the defs, allowing - // us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually - // recursive definitions. - - // All successors that occur in the body of a symbol. - let all_successors_without_self = |symbol: &Symbol| -> MutSet { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors(&references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(References { lookups, .. }) = env.closures.get(symbol) { - let home = env.home; - - for lookup in lookups { - if lookup != symbol && lookup.module_id() == home { - // DO NOT register a self-call behind a lambda! - // - // We allow `boom = \_ -> boom {}`, but not `x = x` - loc_succ.insert(*lookup); - } - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols_set.contains(key)); - - loc_succ - } - None => MutSet::default(), - } - }; - - // All successors that occur in the body of a symbol, including the symbol itself - // This is required to determine whether a symbol is recursive. Recursive symbols - // (that are not faulty) always need a DeclareRec, even if there is just one symbol in the - // group - let mut all_successors_with_self = |symbol: &Symbol| -> MutSet { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors(&references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(References { lookups, .. }) = env.closures.get(symbol) { - for lookup in lookups { - loc_succ.insert(*lookup); - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols_set.contains(key)); - - loc_succ - } - None => MutSet::default(), - } - }; - - // If a symbol is a direct successor of itself, there is an invalid cycle. - // The difference with the function above is that this one does not look behind lambdas, - // but does consider direct self-recursion. - let direct_successors = |symbol: &Symbol| -> MutSet { - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - let mut loc_succ = local_successors(&references, &env.closures); - - // NOTE: if the symbol is a closure we DONT look into its body - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols_set.contains(key)); - - // NOTE: direct recursion does matter here: `x = x` is invalid recursion! - - loc_succ - } - None => MutSet::default(), - } - }; - - // TODO also do the same `addDirects` check elm/compiler does, so we can - // report an error if a recursive definition can't possibly terminate! - match ven_graph::topological_sort_into_groups( - defined_symbols.as_slice(), - all_successors_without_self, - ) { - Ok(groups) => { - let mut declarations = Vec::new(); - - // groups are in reversed order - let mut can_defs_by_symbol = can_defs_by_symbol; - let cdbs = &mut can_defs_by_symbol; - for group in groups.into_iter().rev() { - group_to_declaration( - &group, - &env.closures, - &mut all_successors_with_self, - cdbs, - &mut declarations, - ); - } - - (Ok(declarations), output) - } - Err((mut groups, nodes_in_cycle)) => { - let mut declarations = Vec::new(); - let problems = Vec::new(); - - // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, - // and in general it's impossible to decide whether it is. So we use a crude heuristic: - // - // Definitions where the cycle occurs behind a lambda are OK - // - // boom = \_ -> boom {} - // - // But otherwise we report an error, e.g. - // - // foo = if b then foo else bar - - for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) - { - // check whether the cycle is faulty, which is when it has - // a direct successor in the current cycle. This catches things like: - // - // x = x - // - // or - // - // p = q - // q = p - let is_invalid_cycle = match cycle.get(0) { - Some(symbol) => { - let mut succs = direct_successors(symbol); - - succs.retain(|key| cycle.contains(key)); - - !succs.is_empty() - } - None => false, - }; - - if is_invalid_cycle { - // We want to show the entire cycle in the error message, so expand it out. - let mut loc_symbols = Vec::new(); - - for symbol in cycle { - match refs_by_symbol.get(&symbol) { - None => unreachable!( - r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, - symbol, refs_by_symbol - ), - Some((region, _)) => { - loc_symbols.push(Located::at(*region, symbol)); - } - } - } - - // TODO we don't store those regions any more! - // let regions = Vec::with_capacity(can_defs_by_symbol.len()); - // for def in can_defs_by_symbol.values() { - // regions.push((def.loc_pattern.region, def.loc_expr.region)); - // } - // - // // Sort them by line number to make the report more helpful. - // loc_symbols.sort(); - // regions.sort(); - - // let symbols_in_cycle: Vec = - // loc_symbols.into_iter().map(|s| s.value).collect(); - // - // problems.push(Problem::RuntimeError(RuntimeError::CircularDef( - // symbols_in_cycle.clone(), - // regions.clone(), - // ))); - // - // declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions)); - panic!("Invalid Cycle"); - } else { - // slightly inefficient, because we know this becomes exactly one DeclareRec already - groups.push(cycle); - } - } - - // now we have a collection of groups whose dependencies are not cyclic. - // They are however not yet topologically sorted. Here we have to get a bit - // creative to get all the definitions in the correct sorted order. - - let mut group_ids = Vec::with_capacity(groups.len()); - let mut symbol_to_group_index = MutMap::default(); - for (i, group) in groups.iter().enumerate() { - for symbol in group { - symbol_to_group_index.insert(*symbol, i); - } - - group_ids.push(i); - } - - let successors_of_group = |group_id: &usize| { - let mut result = MutSet::default(); - - // for each symbol in this group - for symbol in &groups[*group_id] { - // find its successors - for succ in all_successors_without_self(symbol) { - // and add its group to the result - result.insert(symbol_to_group_index[&succ]); - } - } - - // don't introduce any cycles to self - result.remove(group_id); - - result - }; - - match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { - Ok(sorted_group_ids) => { - let mut can_defs_by_symbol = can_defs_by_symbol; - let cdbs = &mut can_defs_by_symbol; - for sorted_group in sorted_group_ids.iter().rev() { - for group_id in sorted_group.iter().rev() { - let group = &groups[*group_id]; - - group_to_declaration( - group, - &env.closures, - &mut all_successors_with_self, - cdbs, - &mut declarations, - ); - } - } - } - Err(_) => unreachable!("there should be no cycles now!"), - } - - for problem in problems { - env.problem(problem); - } - - (Ok(declarations), output) - } - } -} - -pub fn references_from_local<'a, T>( - defined_symbol: Symbol, - visited: &'a mut MutSet, - refs_by_def: &'a MutMap, - closures: &'a MutMap, -) -> References -where - T: Debug, -{ - let mut answer: References = References::new(); - - match refs_by_def.get(&defined_symbol) { - Some((_, refs)) => { - visited.insert(defined_symbol); - - for local in refs.lookups.iter() { - if !visited.contains(&local) { - let other_refs: References = - references_from_local(*local, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.lookups.insert(*local); - } - - for call in refs.calls.iter() { - if !visited.contains(&call) { - let other_refs = references_from_call(*call, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.calls.insert(*call); - } - - answer - } - None => answer, - } -} - -pub fn references_from_call<'a, T>( - call_symbol: Symbol, - visited: &'a mut MutSet, - refs_by_def: &'a MutMap, - closures: &'a MutMap, -) -> References -where - T: Debug, -{ - match closures.get(&call_symbol) { - Some(references) => { - let mut answer = references.clone(); - - visited.insert(call_symbol); - - for closed_over_local in references.lookups.iter() { - if !visited.contains(&closed_over_local) { - let other_refs = - references_from_local(*closed_over_local, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.lookups.insert(*closed_over_local); - } - - for call in references.calls.iter() { - if !visited.contains(&call) { - let other_refs = references_from_call(*call, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.calls.insert(*call); - } - - answer - } - None => { - // If the call symbol was not in the closure map, that means we're calling a non-function and - // will get a type mismatch later. For now, assume no references as a result of the "call." - References::new() - } - } -} - -fn local_successors( - references: &References, - closures: &MutMap, -) -> MutSet { - let mut answer = references.lookups.clone(); - - for call_symbol in references.calls.iter() { - answer.extend(call_successors(*call_symbol, closures)); - } - - answer -} - -fn call_successors<'a>( - call_symbol: Symbol, - closures: &'a MutMap, -) -> MutSet { - let mut answer = MutSet::default(); - let mut seen = MutSet::default(); - let mut queue = vec![call_symbol]; - - while let Some(symbol) = queue.pop() { - if seen.contains(&symbol) { - continue; - } - - if let Some(references) = closures.get(&symbol) { - answer.extend(references.lookups.iter().copied()); - queue.extend(references.calls.iter().copied()); - - seen.insert(symbol); - } - } - - answer -} - -fn group_to_declaration( - group: &[Symbol], - closures: &MutMap, - successors: &mut dyn FnMut(&Symbol) -> MutSet, - can_defs_by_symbol: &mut MutMap, - declarations: &mut Vec, -) { - use Declaration::*; - - // We want only successors in the current group, otherwise definitions get duplicated - let filtered_successors = |symbol: &Symbol| -> MutSet { - let mut result = successors(symbol); - - result.retain(|key| group.contains(key)); - result - }; - - // TODO fix this - // Patterns like - // - // { x, y } = someDef - // - // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), - // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key - // for a definition, so every definition is only inserted (thus typechecked and emitted) once - // let mut seen_pattern_regions: MutSet = MutSet::default(); - - for cycle in strongly_connected_components(&group, filtered_successors) { - if cycle.len() == 1 { - let symbol = &cycle[0]; - - if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { - // Determine recursivity of closures that are not tail-recursive - - let is_recursive = successors(&symbol).contains(&symbol); - - if is_recursive { - declarations.push(DeclareRec(vec![can_def])); - } else { - declarations.push(Declare(can_def)); - } - } - } else { - let mut can_defs = Vec::new(); - - // Topological sort gives us the reverse of the sorting we want! - for symbol in cycle.into_iter().rev() { - if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { - can_defs.push(can_def); - } - } - - declarations.push(DeclareRec(can_defs)); - } - } -} diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs deleted file mode 100644 index a50c315f94..0000000000 --- a/editor/src/lang/expr.rs +++ /dev/null @@ -1,1555 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use bumpalo::{collections::Vec as BumpVec, Bump}; -use std::collections::HashMap; -use std::iter::FromIterator; - -use crate::lang::ast::{ - expr2_to_string, value_def_to_string, ClosureExtra, Def2, Expr2, ExprId, FloatVal, IntStyle, - IntVal, RecordField, ValueDef, WhenBranch, -}; -use crate::lang::def::{ - canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, -}; -use crate::lang::pattern::{to_pattern2, Pattern2, PatternId}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::{Alias, Annotation2, Type2, TypeId}; - -use roc_can::expr::Recursive; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; -use roc_can::operator::desugar_expr; -use roc_collections::all::default_hasher; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, Lowercase, ModuleName}; -use roc_module::low_level::LowLevel; -use roc_module::operator::CalledVia; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; -use roc_parse::ast; -use roc_parse::ast::Expr; -use roc_parse::ast::StrLiteral; -use roc_parse::parser::{loc, Parser, State, SyntaxError}; -use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; - -#[derive(Clone, Debug, PartialEq, Default)] -pub struct IntroducedVariables { - // Rigids must be unique within a type annoation. - // E.g. in `identity : a -> a`, there should only be one - // variable (a rigid one, with name "a"). - // Hence `rigids : Map` - // - // But then between annotations, the same name can occur multiple times, - // but a variable can only have one name. Therefore - // `ftv : Map`. - pub wildcards: Vec, - pub var_by_name: MutMap, - pub name_by_var: MutMap, - pub host_exposed_aliases: MutMap, -} - -impl IntroducedVariables { - pub fn insert_named(&mut self, name: Lowercase, var: Variable) { - self.var_by_name.insert(name.clone(), var); - self.name_by_var.insert(var, name); - } - - pub fn insert_wildcard(&mut self, var: Variable) { - self.wildcards.push(var); - } - - pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { - self.host_exposed_aliases.insert(symbol, var); - } - - pub fn union(&mut self, other: &Self) { - self.wildcards.extend(other.wildcards.iter().cloned()); - self.var_by_name.extend(other.var_by_name.clone()); - self.name_by_var.extend(other.name_by_var.clone()); - self.host_exposed_aliases - .extend(other.host_exposed_aliases.clone()); - } - - pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { - self.var_by_name.get(name) - } - - pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { - self.name_by_var.get(&var) - } -} - -#[derive(Clone, Default, Debug, PartialEq)] -pub struct Output { - pub references: References, - pub tail_call: Option, - pub introduced_variables: IntroducedVariables, - pub aliases: MutMap>, - pub non_closures: MutSet, -} - -impl Output { - pub fn union(&mut self, other: Self) { - self.references.union_mut(other.references); - - if let (None, Some(later)) = (self.tail_call, other.tail_call) { - self.tail_call = Some(later); - } - - self.aliases.extend(other.aliases); - self.non_closures.extend(other.non_closures); - } -} - -#[derive(Debug)] -pub struct Env<'a> { - pub home: ModuleId, - pub var_store: &'a mut VarStore, - pub pool: &'a mut Pool, - pub arena: &'a Bump, - - pub problems: BumpVec<'a, Problem>, - - pub dep_idents: MutMap, - pub module_ids: &'a ModuleIds, - pub ident_ids: IdentIds, - pub exposed_ident_ids: IdentIds, - - pub closures: MutMap, - /// Symbols which were referenced by qualified lookups. - pub qualified_lookups: MutSet, - - pub top_level_symbols: MutSet, - - pub closure_name_symbol: Option, - pub tailcallable_symbol: Option, -} - -impl<'a> Env<'a> { - pub fn new( - home: ModuleId, - arena: &'a Bump, - pool: &'a mut Pool, - var_store: &'a mut VarStore, - dep_idents: MutMap, - module_ids: &'a ModuleIds, - exposed_ident_ids: IdentIds, - ) -> Env<'a> { - Env { - home, - arena, - pool, - problems: BumpVec::new_in(arena), - var_store, - dep_idents, - module_ids, - ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later - exposed_ident_ids, - closures: MutMap::default(), - qualified_lookups: MutSet::default(), - tailcallable_symbol: None, - closure_name_symbol: None, - top_level_symbols: MutSet::default(), - } - } - - pub fn add(&mut self, item: T, region: Region) -> NodeId { - let id = self.pool.add(item); - self.set_region(id, region); - - id - } - - pub fn problem(&mut self, problem: Problem) { - self.problems.push(problem); - } - - pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { - dbg!("Don't Forget to set the region eventually"); - } - - pub fn register_closure(&mut self, symbol: Symbol, references: References) { - self.closures.insert(symbol, references); - } - - /// Generates a unique, new symbol like "$1" or "$5", - /// using the home module as the module_id. - /// - /// This is used, for example, during canonicalization of an Expr::Closure - /// to generate a unique symbol to refer to that closure. - pub fn gen_unique_symbol(&mut self) -> Symbol { - let ident_id = self.ident_ids.gen_unique(); - - Symbol::new(self.home, ident_id) - } - - /// Returns Err if the symbol resolved, but it was not exposed by the given module - pub fn qualified_lookup( - &mut self, - module_name: &str, - ident: &str, - region: Region, - ) -> Result { - debug_assert!( - !module_name.is_empty(), - "Called env.qualified_lookup with an unqualified ident: {:?}", - ident - ); - - let module_name: ModuleName = module_name.into(); - - match self.module_ids.get_id(&module_name) { - Some(&module_id) => { - let ident: Ident = ident.into(); - - // You can do qualified lookups on your own module, e.g. - // if I'm in the Foo module, I can do a `Foo.bar` lookup. - if module_id == self.home { - match self.ident_ids.get_id(&ident) { - Some(ident_id) => { - let symbol = Symbol::new(module_id, *ident_id); - - self.qualified_lookups.insert(symbol); - - Ok(symbol) - } - None => Err(RuntimeError::LookupNotInScope( - Located { - value: ident, - region, - }, - self.ident_ids - .idents() - .map(|(_, string)| string.as_ref().into()) - .collect(), - )), - } - } else { - match self - .dep_idents - .get(&module_id) - .and_then(|exposed_ids| exposed_ids.get_id(&ident)) - { - Some(ident_id) => { - let symbol = Symbol::new(module_id, *ident_id); - - self.qualified_lookups.insert(symbol); - - Ok(symbol) - } - None => Err(RuntimeError::ValueNotExposed { - module_name: ModuleName::from(module_name), - ident, - region, - }), - } - } - } - None => Err(RuntimeError::ModuleNotImported { - module_name, - imported_modules: self - .module_ids - .available_modules() - .map(|string| string.as_ref().into()) - .collect(), - region, - }), - } - } -} - -const ZERO: Region = Region::zero(); - -pub fn as_expr_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - expr_id: ExprId, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> Output { - let (expr, output) = to_expr2(env, scope, parse_expr, region); - - env.pool[expr_id] = expr; - env.set_region(expr_id, region); - - output -} - -pub fn to_expr_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> (ExprId, Output) { - let (expr, output) = to_expr2(env, scope, parse_expr, region); - - (env.add(expr, region), output) -} - -pub fn str_to_def2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result, SyntaxError<'a>> { - match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { - Ok(vec_loc_def) => Ok(defs_to_defs2( - arena, - env, - scope, - arena.alloc(vec_loc_def), - region, - )), - Err(fail) => Err(fail), - } -} - -pub fn str_to_expr2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result<(Expr2, self::Output), SyntaxError<'a>> { - match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), - Err(fail) => Err(fail), - } -} - -fn loc_expr_to_expr2<'a>( - arena: &'a Bump, - loc_expr: Located>, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> (Expr2, self::Output) { - let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); - - to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) -} - -pub fn to_expr2<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> (Expr2, self::Output) { - use roc_parse::ast::Expr::*; - - match parse_expr { - Float(string) => { - match finish_parsing_float(string) { - Ok(float) => { - let expr = Expr2::Float { - number: FloatVal::F64(float), - var: env.var_store.fresh(), - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - Num(string) => { - match finish_parsing_int(string) { - Ok(int) => { - let expr = Expr2::SmallInt { - number: IntVal::I64(int), - var: env.var_store.fresh(), - // TODO non-hardcode - style: IntStyle::Decimal, - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidInt( - error, - roc_parse::ast::Base::Decimal, - ZERO, - raw.into(), - ); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - NonBase10Int { - string, - base, - is_negative, - } => { - match finish_parsing_base(string, *base, *is_negative) { - Ok(int) => { - let expr = Expr2::SmallInt { - number: IntVal::I64(int), - var: env.var_store.fresh(), - // TODO non-hardcode - style: IntStyle::from_base(*base), - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - - Str(literal) => flatten_str_literal(env, scope, &literal), - - List { items, .. } => { - let mut output = Output::default(); - let output_ref = &mut output; - - let elems: PoolVec = PoolVec::with_capacity(items.len() as u32, env.pool); - - for (node_id, item) in elems.iter_node_ids().zip(items.iter()) { - let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region); - - output_ref.union(sub_output); - - let expr_id = env.pool.add(expr); - env.pool[node_id] = expr_id; - } - - let expr = Expr2::List { - elem_var: env.var_store.fresh(), - elems, - }; - - (expr, output) - } - - GlobalTag(tag) => { - // a global tag without any arguments - ( - Expr2::GlobalTag { - name: PoolStr::new(tag, env.pool), - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } - PrivateTag(name) => { - // a private tag without any arguments - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - let name = Symbol::new(env.home, ident_id); - ( - Expr2::PrivateTag { - name, - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } - - RecordUpdate { - fields, - update: loc_update, - final_comments: _, - } => { - let (can_update, update_out) = - to_expr2(env, scope, &loc_update.value, loc_update.region); - - if let Expr2::Var(symbol) = &can_update { - match canonicalize_fields(env, scope, fields) { - Ok((can_fields, mut output)) => { - output.references.union_mut(update_out.references); - - let answer = Expr2::Update { - record_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - symbol: *symbol, - updates: can_fields, - }; - - (answer, output) - } - Err(CanonicalizeRecordProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - record_region: _, - }) => { - // let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue { - // field_name, - // field_region, - // record_region, - // }; - // - // env.problem(Problem::RuntimeError(runtime_error)); - - todo!() - } - } - } else { - // only (optionally qualified) variables can be updated, not arbitrary expressions - - // let error = roc_problem::can::RuntimeError::InvalidRecordUpdate { - // region: can_update.region, - // }; - // - // let answer = Expr::RuntimeError(error.clone()); - // - // env.problems.push(Problem::RuntimeError(error)); - // - // (answer, Output::default()) - todo!() - } - } - - Record { - fields, - final_comments: _, - } => { - if fields.is_empty() { - (Expr2::EmptyRecord, Output::default()) - } else { - match canonicalize_fields(env, scope, fields) { - Ok((can_fields, output)) => ( - Expr2::Record { - record_var: env.var_store.fresh(), - fields: can_fields, - }, - output, - ), - Err(CanonicalizeRecordProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - record_region: _, - }) => { - // let runtime_error = RuntimeError::InvalidOptionalValue { - // field_name, - // field_region, - // record_region, - // }; - // - // env.problem(runtime_error); - // ( - // Expr::RuntimeError( - // ), - // Output::default(), - // - // ) - todo!() - } - } - } - } - - Access(record_expr, field) => { - // TODO - let region = ZERO; - let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region); - - ( - Expr2::Access { - record_var: env.var_store.fresh(), - field_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - expr: record_expr_id, - field: PoolStr::new(field, env.pool), - }, - output, - ) - } - - AccessorFunction(field) => ( - Expr2::Accessor { - function_var: env.var_store.fresh(), - record_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - field_var: env.var_store.fresh(), - field: PoolStr::new(field, env.pool), - }, - Output::default(), - ), - - If(branches, final_else) => { - let mut new_branches = Vec::with_capacity(branches.len()); - let mut output = Output::default(); - - for (condition, then_branch) in branches.iter() { - let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region); - - let (then_expr, then_output) = - to_expr2(env, scope, &then_branch.value, then_branch.region); - - output.references.union_mut(cond_output.references); - output.references.union_mut(then_output.references); - - new_branches.push((env.pool.add(cond), env.pool.add(then_expr))); - } - - let (else_expr, else_output) = - to_expr2(env, scope, &final_else.value, final_else.region); - - output.references.union_mut(else_output.references); - - let expr = Expr2::If { - cond_var: env.var_store.fresh(), - expr_var: env.var_store.fresh(), - branches: PoolVec::new(new_branches.into_iter(), env.pool), - final_else: env.pool.add(else_expr), - }; - - (expr, output) - } - - When(loc_cond, branches) => { - // Infer the condition expression's type. - let cond_var = env.var_store.fresh(); - let (can_cond, mut output) = to_expr2(env, scope, &loc_cond.value, loc_cond.region); - - // the condition can never be a tail-call - output.tail_call = None; - - let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool); - - for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) { - let (can_when_branch, branch_references) = - canonicalize_when_branch(env, scope, *branch, &mut output); - - output.references.union_mut(branch_references); - - env.pool[node_id] = can_when_branch; - } - - // A "when" with no branches is a runtime error, but it will mess things up - // if code gen mistakenly thinks this is a tail call just because its condition - // happened to be one. (The condition gave us our initial output value.) - if branches.is_empty() { - output.tail_call = None; - } - - // Incorporate all three expressions into a combined Output value. - let expr = Expr2::When { - expr_var: env.var_store.fresh(), - cond_var, - cond: env.pool.add(can_cond), - branches: can_branches, - }; - - (expr, output) - } - - Closure(loc_arg_patterns, loc_body_expr) => { - // The globally unique symbol that will refer to this closure once it gets converted - // into a top-level procedure for code gen. - // - // In the Foo module, this will look something like Foo.$1 or Foo.$2. - let symbol = env - .closure_name_symbol - .unwrap_or_else(|| env.gen_unique_symbol()); - env.closure_name_symbol = None; - - // The body expression gets a new scope for canonicalization. - // Shadow `scope` to make sure we don't accidentally use the original one for the - // rest of this block, but keep the original around for later diffing. - let original_scope = scope; - let mut scope = original_scope.shallow_clone(); - let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool); - let mut output = Output::default(); - - let mut bound_by_argument_patterns = MutSet::default(); - - for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) { - let (new_output, can_arg) = to_pattern2( - env, - &mut scope, - roc_parse::pattern::PatternType::FunctionArg, - &loc_pattern.value, - loc_pattern.region, - ); - - bound_by_argument_patterns - .extend(new_output.references.bound_symbols.iter().copied()); - - output.union(new_output); - - let pattern_id = env.add(can_arg, loc_pattern.region); - env.pool[node_id] = (env.var_store.fresh(), pattern_id); - } - - let (body_expr, new_output) = - to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region); - - let mut captured_symbols: MutSet = - new_output.references.lookups.iter().copied().collect(); - - // filter out the closure's name itself - captured_symbols.remove(&symbol); - - // symbols bound either in this pattern or deeper down are not captured! - captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); - captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); - - // filter out top-level symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); - - // filter out imported symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| s.module_id() == env.home); - - // TODO any Closure that has an empty `captured_symbols` list could be excluded! - - output.union(new_output); - - // filter out aliases - captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s)); - - // filter out functions that don't close over anything - captured_symbols.retain(|s| !output.non_closures.contains(s)); - - // Now that we've collected all the references, check to see if any of the args we defined - // went unreferenced. If any did, report them as unused arguments. - for (sub_symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(sub_symbol) { - if !output.references.has_lookup(sub_symbol) { - // The body never referenced this argument we declared. It's an unused argument! - env.problem(Problem::UnusedArgument(symbol, sub_symbol, region)); - } - - // We shouldn't ultimately count arguments as referenced locals. Otherwise, - // we end up with weird conclusions like the expression (\x -> x + 1) - // references the (nonexistant) local variable x! - output.references.lookups.remove(&sub_symbol); - } - } - - env.register_closure(symbol, output.references.clone()); - - let mut captured_symbols: Vec<_> = captured_symbols - .into_iter() - .map(|s| (s, env.var_store.fresh())) - .collect(); - - // sort symbols, so we know the order in which they're stored in the closure record - captured_symbols.sort(); - - // store that this function doesn't capture anything. It will be promoted to a - // top-level function, and does not need to be captured by other surrounding functions. - if captured_symbols.is_empty() { - output.non_closures.insert(symbol); - } - - let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool); - - let extra = ClosureExtra { - return_type: env.var_store.fresh(), // 4B - captured_symbols, // 8B - closure_type: env.var_store.fresh(), // 4B - closure_ext_var: env.var_store.fresh(), // 4B - }; - - ( - Expr2::Closure { - function_type: env.var_store.fresh(), - name: symbol, - recursive: Recursive::NotRecursive, - args: can_args, - body: env.add(body_expr, loc_body_expr.region), - extra: env.pool.add(extra), - }, - output, - ) - } - - Apply(loc_fn, loc_args, application_style) => { - // The expression that evaluates to the function being called, e.g. `foo` in - // (foo) bar baz - let fn_region = loc_fn.region; - - // Canonicalize the function expression and its arguments - let (fn_expr, mut output) = to_expr2(env, scope, &loc_fn.value, fn_region); - - // The function's return type - let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool); - - for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) { - let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region); - - env.pool[node_id] = (env.var_store.fresh(), arg_expr_id); - - output.references.union_mut(arg_out.references); - } - - // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. - output.tail_call = None; - - let expr = match fn_expr { - Expr2::Var(ref symbol) => { - output.references.calls.insert(*symbol); - - // we're tail-calling a symbol by name, check if it's the tail-callable symbol - output.tail_call = match &env.tailcallable_symbol { - Some(tc_sym) if *tc_sym == *symbol => Some(*symbol), - Some(_) | None => None, - }; - - // IDEA: Expr2::CallByName? - let fn_expr_id = env.add(fn_expr, fn_region); - Expr2::Call { - args, - expr: fn_expr_id, - expr_var: env.var_store.fresh(), - fn_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - called_via: *application_style, - } - } - Expr2::RuntimeError() => { - // We can't call a runtime error; bail out by propagating it! - return (fn_expr, output); - } - Expr2::GlobalTag { - variant_var, - ext_var, - name, - .. - } => Expr2::GlobalTag { - variant_var, - ext_var, - name, - arguments: args, - }, - Expr2::PrivateTag { - variant_var, - ext_var, - name, - .. - } => Expr2::PrivateTag { - variant_var, - ext_var, - name, - arguments: args, - }, - _ => { - // This could be something like ((if True then fn1 else fn2) arg1 arg2). - let fn_expr_id = env.add(fn_expr, fn_region); - Expr2::Call { - args, - expr: fn_expr_id, - expr_var: env.var_store.fresh(), - fn_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - called_via: *application_style, - } - } - }; - - (expr, output) - } - - Defs(loc_defs, loc_ret) => { - let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( - env, - Output::default(), - &scope, - loc_defs, - PatternType::DefExpr, - ); - - // The def as a whole is a tail call iff its return expression is a tail call. - // Use its output as a starting point because its tail_call already has the right answer! - let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region); - - output - .introduced_variables - .union(&defs_output.introduced_variables); - - output.references.union_mut(defs_output.references); - - // Now that we've collected all the references, check to see if any of the new idents - // we defined went unused by the return expression. If any were unused, report it. - for (symbol, region) in symbols_introduced { - if !output.references.has_lookup(symbol) { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - let (can_defs, output) = sort_can_defs(env, unsorted, output); - - match can_defs { - Ok(decls) => { - let mut expr = ret_expr; - - for declaration in decls.into_iter().rev() { - expr = decl_to_let(env.pool, env.var_store, declaration, expr); - } - - (expr, output) - } - Err(_err) => { - // TODO: fix this to be something from Expr2 - // (RuntimeError(err), output) - todo!() - } - } - } - - PrecedenceConflict { .. } => { - // use roc_problem::can::RuntimeError::*; - // - // let problem = PrecedenceProblem::BothNonAssociative( - // *whole_region, - // binop1.clone(), - // binop2.clone(), - // ); - // - // env.problem(Problem::PrecedenceProblem(problem.clone())); - // - // ( - // RuntimeError(InvalidPrecedence(problem, region)), - // Output::default(), - // ) - todo!() - } - MalformedClosure => { - // use roc_problem::can::RuntimeError::*; - // (RuntimeError(MalformedClosure(region)), Output::default()) - todo!() - } - MalformedIdent(_name, _problem) => { - // use roc_problem::can::RuntimeError::*; - // - // let problem = MalformedIdentifier((*name).into(), region); - // env.problem(Problem::RuntimeError(problem.clone())); - // - // (RuntimeError(problem), Output::default()) - todo!() - } - Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), - - // Below this point, we shouln't see any of these nodes anymore because - // operator desugaring should have removed them! - bad_expr @ ParensAround(_) => { - panic!( - "A ParensAround did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ SpaceBefore(_, _) => { - panic!( - "A SpaceBefore did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ SpaceAfter(_, _) => { - panic!( - "A SpaceAfter did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ BinOps { .. } => { - panic!( - "A binary operator chain did not get desugared somehow: {:#?}", - bad_expr - ); - } - bad_expr @ UnaryOp(_, _) => { - panic!( - "A unary operator did not get desugared somehow: {:#?}", - bad_expr - ); - } - - rest => todo!("not yet implemented {:?}", rest), - } -} - -pub fn defs_to_defs2<'a>( - arena: &'a Bump, - env: &mut Env<'a>, - scope: &mut Scope, - parsed_defs: &'a BumpVec>>, - region: Region, -) -> Vec { - use roc_parse::ast::Expr::*; - - parsed_defs - .iter() - .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) - .collect() -} - -pub fn to_def2_from_def<'a>( - arena: &'a Bump, - env: &mut Env<'a>, - scope: &mut Scope, - parsed_def: &'a roc_parse::ast::Def<'a>, - region: Region, -) -> Def2 { - use roc_parse::ast::Def::*; - - match parsed_def { - SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), - SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), - Body(&loc_pattern, &loc_expr) => { - // TODO loc_pattern use identifier - let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; - let expr_id = env.pool.add(expr2); - - use roc_parse::ast::Pattern::*; - - match loc_pattern.value { - Identifier(_) => { - let (_, pattern2) = to_pattern2( - env, - scope, - PatternType::TopLevelDef, - &loc_pattern.value, - region, - ); - let pattern_id = env.pool.add(pattern2); - - // TODO support with annotation - Def2::ValueDef { - identifier_id: pattern_id, - expr_id, - } - } - other => { - unimplemented!( - "I don't yet know how to convert the pattern {:?} into an expr2", - other - ) - } - } - } - other => { - unimplemented!( - "I don't know how to make an expr2 from this def yet: {:?}", - other - ) - } - } -} - -fn flatten_str_literal<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - literal: &StrLiteral<'a>, -) -> (Expr2, Output) { - use roc_parse::ast::StrLiteral::*; - - match literal { - PlainLine(str_slice) => { - // TODO use smallstr - let expr = Expr2::Str(PoolStr::new(str_slice, &mut env.pool)); - - (expr, Output::default()) - } - Line(segments) => flatten_str_lines(env, scope, &[segments]), - Block(lines) => flatten_str_lines(env, scope, lines), - } -} - -enum StrSegment { - Interpolation(Expr2), - Plaintext(PoolStr), -} - -fn flatten_str_lines<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - lines: &[&[roc_parse::ast::StrSegment<'a>]], -) -> (Expr2, Output) { - use roc_parse::ast::StrSegment::*; - - let mut buf = String::new(); - let mut segments = Vec::new(); - let mut output = Output::default(); - - for line in lines { - for segment in line.iter() { - match segment { - Plaintext(string) => { - buf.push_str(string); - } - Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) { - Ok(code_pt) => match std::char::from_u32(code_pt) { - Some(ch) => { - buf.push(ch); - } - None => { - // env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt( - // loc_hex_digits.region, - // )), - // output, - // ); - todo!() - } - }, - Err(_) => { - // env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidHexadecimal( - // loc_hex_digits.region, - // )), - // output, - // ); - todo!() - } - }, - Interpolated(loc_expr) => { - if roc_can::expr::is_valid_interpolation(loc_expr.value) { - // Interpolations desugar to Str.concat calls - output.references.calls.insert(Symbol::STR_CONCAT); - - if !buf.is_empty() { - segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); - - buf = String::new(); - } - - let (loc_expr, new_output) = - to_expr2(env, scope, loc_expr.value, loc_expr.region); - - output.union(new_output); - - segments.push(StrSegment::Interpolation(loc_expr)); - } else { - // env.problem(Problem::InvalidInterpolation(loc_expr.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)), - // output, - // ); - todo!() - } - } - EscapedChar(escaped) => buf.push(roc_can::expr::unescape_char(escaped)), - } - } - } - - if !buf.is_empty() { - segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); - } - - (desugar_str_segments(env, segments), output) -} - -/// Resolve string interpolations by desugaring a sequence of StrSegments -/// into nested calls to Str.concat -fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Expr2 { - use StrSegment::*; - - let pool = &mut env.pool; - let var_store = &mut env.var_store; - - let mut iter = segments.into_iter().rev(); - let mut expr = match iter.next() { - Some(Plaintext(pool_str)) => Expr2::Str(pool_str), - Some(Interpolation(expr_id)) => expr_id, - None => { - // No segments? Empty string! - - let pool_str = PoolStr::new("", pool); - Expr2::Str(pool_str) - } - }; - - for seg in iter { - let new_expr = match seg { - Plaintext(string) => Expr2::Str(string), - Interpolation(expr_id) => expr_id, - }; - - let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT)); - - let args = vec![ - (var_store.fresh(), pool.add(new_expr)), - (var_store.fresh(), pool.add(expr)), - ]; - let args = PoolVec::new(args.into_iter(), pool); - - let new_call = Expr2::Call { - args, - expr: concat_expr_id, - expr_var: var_store.fresh(), - fn_var: var_store.fresh(), - closure_var: var_store.fresh(), - called_via: CalledVia::Space, - }; - - expr = new_call - } - - expr -} - -enum CanonicalizeRecordProblem { - InvalidOptionalValue { - field_name: PoolStr, - field_region: Region, - record_region: Region, - }, -} - -enum FieldVar { - VarAndExprId(Variable, ExprId), - OnlyVar(Variable), -} - -fn canonicalize_fields<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - fields: &'a [Located>>], -) -> Result<(PoolVec, Output), CanonicalizeRecordProblem> { - let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); - let mut output = Output::default(); - - for loc_field in fields.iter() { - match canonicalize_field(env, scope, &loc_field.value) { - Ok(can_field) => { - match can_field { - CanonicalField::LabelAndValue { - label, - value_expr, - value_output, - var, - } => { - let expr_id = env.pool.add(value_expr); - - let replaced = - can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id)); - - if let Some(_old) = replaced { - // env.problems.push(Problem::DuplicateRecordFieldValue { - // field_name: label, - // field_region: loc_field.region, - // record_region: region, - // replaced_region: old.region, - // }); - todo!() - } - - output.references.union_mut(value_output.references); - } - CanonicalField::InvalidLabelOnly { label, var } => { - let replaced = can_fields.insert(label, FieldVar::OnlyVar(var)); - - if let Some(_old) = replaced { - todo!() - } - } - } - } - - Err(CanonicalizeFieldProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - }) => { - // env.problem(Problem::InvalidOptionalValue { - // field_name: field_name.clone(), - // field_region, - // record_region: region, - // }); - // return Err(CanonicalizeRecordProblem::InvalidOptionalValue { - // field_name, - // field_region, - // record_region: region, - // }); - todo!() - } - } - } - - let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool); - - for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) { - let name = PoolStr::new(string, env.pool); - - match field_var { - FieldVar::VarAndExprId(var, expr_id) => { - env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); - } - FieldVar::OnlyVar(var) => { - env.pool[node_id] = RecordField::InvalidLabelOnly(name, var); - } // TODO RecordField::LabelOnly - } - } - - Ok((pool_vec, output)) -} - -enum CanonicalizeFieldProblem { - InvalidOptionalValue { - field_name: PoolStr, - field_region: Region, - }, -} -enum CanonicalField<'a> { - LabelAndValue { - label: &'a str, - value_expr: Expr2, - value_output: Output, - var: Variable, - }, - InvalidLabelOnly { - label: &'a str, - var: Variable, - }, // TODO make ValidLabelOnly -} -fn canonicalize_field<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>, -) -> Result, CanonicalizeFieldProblem> { - use roc_parse::ast::AssignedField::*; - - match field { - // Both a label and a value, e.g. `{ name: "blah" }` - RequiredValue(label, _, loc_expr) => { - let field_var = env.var_store.fresh(); - let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); - - Ok(CanonicalField::LabelAndValue { - label: label.value, - value_expr: loc_can_expr, - value_output: output, - var: field_var, - }) - } - - OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { - field_name: PoolStr::new(label.value, env.pool), - field_region: Region::span_across(&label.region, &loc_expr.region), - }), - - // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) - LabelOnly(label) => { - let field_var = env.var_store.fresh(); - // TODO return ValidLabel if label points to in scope variable - Ok(CanonicalField::InvalidLabelOnly { - label: label.value, - var: field_var, - }) - } - - SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => { - canonicalize_field(env, scope, sub_field) - } - - Malformed(_string) => { - panic!("TODO canonicalize malformed record field"); - } - } -} - -#[inline(always)] -fn canonicalize_when_branch<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - branch: &'a roc_parse::ast::WhenBranch<'a>, - output: &mut Output, -) -> (WhenBranch, References) { - let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool); - - let original_scope = scope; - let mut scope = original_scope.shallow_clone(); - - // TODO report symbols not bound in all patterns - for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) { - let (new_output, can_pattern) = to_pattern2( - env, - &mut scope, - roc_parse::pattern::PatternType::WhenBranch, - &loc_pattern.value, - loc_pattern.region, - ); - - output.union(new_output); - - env.set_region(node_id, loc_pattern.region); - env.pool[node_id] = can_pattern; - } - - let (value, mut branch_output) = - to_expr2(env, &mut scope, &branch.value.value, branch.value.region); - let value_id = env.pool.add(value); - env.set_region(value_id, branch.value.region); - - let guard = match &branch.guard { - None => None, - Some(loc_expr) => { - let (can_guard, guard_branch_output) = - to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region); - - let expr_id = env.pool.add(can_guard); - env.set_region(expr_id, loc_expr.region); - - branch_output.union(guard_branch_output); - Some(expr_id) - } - }; - - // Now that we've collected all the references for this branch, check to see if - // any of the new idents it defined were unused. If any were, report it. - for (symbol, region) in scope.symbols() { - let symbol = symbol; - - if !output.references.has_lookup(symbol) - && !branch_output.references.has_lookup(symbol) - && !original_scope.contains_symbol(symbol) - { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - let references = branch_output.references.clone(); - output.union(branch_output); - - ( - WhenBranch { - patterns, - body: value_id, - guard, - }, - references, - ) -} - -fn canonicalize_lookup( - env: &mut Env<'_>, - scope: &mut Scope, - module_name: &str, - ident: &str, - region: Region, -) -> (Expr2, Output) { - use Expr2::*; - - let mut output = Output::default(); - let can_expr = if module_name.is_empty() { - // Since module_name was empty, this is an unqualified var. - // Look it up in scope! - match scope.lookup(&(*ident).into(), region) { - Ok(symbol) => { - output.references.lookups.insert(symbol); - - Var(symbol) - } - Err(problem) => { - env.problem(Problem::RuntimeError(problem.clone())); - - RuntimeError() - } - } - } else { - // Since module_name was nonempty, this is a qualified var. - // Look it up in the env! - match env.qualified_lookup(module_name, ident, region) { - Ok(symbol) => { - output.references.lookups.insert(symbol); - - Var(symbol) - } - Err(problem) => { - // Either the module wasn't imported, or - // it was imported but it doesn't expose this ident. - env.problem(Problem::RuntimeError(problem.clone())); - - RuntimeError() - } - } - }; - - // If it's valid, this ident should be in scope already. - - (can_expr, output) -} - -fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { - match decl { - Declaration::Declare(def) => match def { - Def::AnnotationOnly { .. } => todo!(), - Def::Value(value_def) => { - let def_id = pool.add(value_def); - - let body_id = pool.add(ret); - - Expr2::LetValue { - def_id, - body_id, - body_var: var_store.fresh(), - } - } - Def::Function(function_def) => { - let def_id = pool.add(function_def); - let body_id = pool.add(ret); - - Expr2::LetFunction { - def_id, - body_id, - body_var: var_store.fresh(), - } - } - }, - Declaration::DeclareRec(defs) => { - let mut function_defs = vec![]; - - for def in defs { - match def { - Def::AnnotationOnly { .. } => todo!(), - Def::Function(function_def) => function_defs.push(function_def), - Def::Value(_) => unreachable!(), - } - } - - let body_id = pool.add(ret); - - Expr2::LetRec { - defs: PoolVec::new(function_defs.into_iter(), pool), - body_var: var_store.fresh(), - body_id, - } - } - Declaration::InvalidCycle(_entries, _) => { - // TODO: replace with something from Expr2 - // Expr::RuntimeError(RuntimeError::CircularDef(entries)) - todo!() - } - Declaration::Builtin(_) => { - // Builtins should only be added to top-level decls, not to let-exprs! - unreachable!() - } - } -} diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs deleted file mode 100644 index fa747386e1..0000000000 --- a/editor/src/lang/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod ast; -pub mod constrain; -mod def; -pub mod expr; -mod module; -pub mod parse; -pub mod pattern; -pub mod pool; -pub mod roc_file; -pub mod scope; -pub mod solve; -pub mod types; diff --git a/editor/src/lang/module.rs b/editor/src/lang/module.rs deleted file mode 100644 index c0494d3193..0000000000 --- a/editor/src/lang/module.rs +++ /dev/null @@ -1,318 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_variables)] -use crate::lang::ast::{Expr2, FunctionDef, ValueDef}; -use crate::lang::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; -use crate::lang::expr::Env; -use crate::lang::expr::Output; -use crate::lang::pattern::Pattern2; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::Alias; -use bumpalo::Bump; -use roc_can::operator::desugar_def; -use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; -use roc_module::ident::Ident; -use roc_module::ident::Lowercase; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; -use roc_parse::ast; -use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; - -pub struct ModuleOutput { - pub aliases: MutMap>, - pub rigid_variables: MutMap, - pub declarations: Vec, - pub exposed_imports: MutMap, - pub lookups: Vec<(Symbol, Variable, Region)>, - pub problems: Vec, - pub ident_ids: IdentIds, - pub references: MutSet, -} - -// TODO trim these down -#[allow(clippy::too_many_arguments)] -pub fn canonicalize_module_defs<'a>( - arena: &Bump, - loc_defs: &'a [Located>], - home: ModuleId, - module_ids: &ModuleIds, - exposed_ident_ids: IdentIds, - dep_idents: MutMap, - aliases: MutMap, - exposed_imports: MutMap, - mut exposed_symbols: MutSet, - var_store: &mut VarStore, -) -> Result { - let mut pool = Pool::with_capacity(1 << 10); - let mut can_exposed_imports = MutMap::default(); - let mut scope = Scope::new(home, &mut pool, var_store); - let num_deps = dep_idents.len(); - - for (name, alias) in aliases.into_iter() { - let vars = PoolVec::with_capacity(alias.targs.len() as u32, &mut pool); - - for (node_id, targ_id) in vars.iter_node_ids().zip(alias.targs.iter_node_ids()) { - let (poolstr, var) = &pool[targ_id]; - pool[node_id] = (poolstr.shallow_clone(), *var); - } - scope.add_alias(&mut pool, name, vars, alias.actual); - } - - // Desugar operators (convert them to Apply calls, taking into account - // operator precedence and associativity rules), before doing other canonicalization. - // - // If we did this *during* canonicalization, then each time we - // visited a BinOp node we'd recursively try to apply this to each of its nested - // operators, and then again on *their* nested operators, ultimately applying the - // rules multiple times unnecessarily. - let mut desugared = - bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena); - - for loc_def in loc_defs.iter() { - desugared.push(&*arena.alloc(Located { - value: desugar_def(arena, &loc_def.value), - region: loc_def.region, - })); - } - - let mut env = Env::new( - home, - arena, - &mut pool, - var_store, - dep_idents, - module_ids, - exposed_ident_ids, - ); - let mut lookups = Vec::with_capacity(num_deps); - let rigid_variables = MutMap::default(); - - // Exposed values are treated like defs that appear before any others, e.g. - // - // imports [ Foo.{ bar, baz } ] - // - // ...is basically the same as if we'd added these extra defs at the start of the module: - // - // bar = Foo.bar - // baz = Foo.baz - // - // Here we essentially add those "defs" to "the beginning of the module" - // by canonicalizing them right before we canonicalize the actual ast::Def nodes. - for (ident, (symbol, region)) in exposed_imports { - let first_char = ident.as_inline_str().chars().next().unwrap(); - - if first_char.is_lowercase() { - // this is a value definition - let expr_var = env.var_store.fresh(); - - match scope.import(ident, symbol, region) { - Ok(()) => { - // Add an entry to exposed_imports using the current module's name - // as the key; e.g. if this is the Foo module and we have - // exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when - // anything references `baz` in this Foo module, it will resolve to Bar.baz. - can_exposed_imports.insert(symbol, expr_var); - - // This will be used during constraint generation, - // to add the usual Lookup constraint as if this were a normal def. - lookups.push((symbol, expr_var, region)); - } - Err((_shadowed_symbol, _region)) => { - panic!("TODO gracefully handle shadowing in imports.") - } - } - } else { - // This is a type alias - - // the should already be added to the scope when this module is canonicalized - debug_assert!(scope.contains_alias(symbol)); - } - } - - let (defs, _scope, output, symbols_introduced) = canonicalize_defs( - &mut env, - Output::default(), - &scope, - &desugared, - PatternType::TopLevelDef, - ); - - // See if any of the new idents we defined went unused. - // If any were unused and also not exposed, report it. - for (symbol, region) in symbols_introduced { - if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - // TODO register rigids - // for (var, lowercase) in output.introduced_variables.name_by_var.clone() { - // rigid_variables.insert(var, lowercase); - // } - - let mut references = MutSet::default(); - - // Gather up all the symbols that were referenced across all the defs' lookups. - for symbol in output.references.lookups.iter() { - references.insert(*symbol); - } - - // Gather up all the symbols that were referenced across all the defs' calls. - for symbol in output.references.calls.iter() { - references.insert(*symbol); - } - - // Gather up all the symbols that were referenced from other modules. - for symbol in env.qualified_lookups.iter() { - references.insert(*symbol); - } - - // NOTE previously we inserted builtin defs into the list of defs here - // this is now done later, in file.rs. - - match sort_can_defs(&mut env, defs, Output::default()) { - (Ok(mut declarations), output) => { - use Declaration::*; - - for decl in declarations.iter() { - match decl { - Declare(def) => { - for symbol in def.symbols(env.pool) { - if exposed_symbols.contains(&symbol) { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_symbols.remove(&symbol); - } - } - } - DeclareRec(defs) => { - for def in defs { - for symbol in def.symbols(env.pool) { - if exposed_symbols.contains(&symbol) { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_symbols.remove(&symbol); - } - } - } - } - - InvalidCycle(identifiers, _) => { - panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers); - } - Builtin(def) => { - // Builtins cannot be exposed in module declarations. - // This should never happen! - debug_assert!(def - .symbols(env.pool) - .iter() - .all(|symbol| !exposed_symbols.contains(symbol))); - } - } - } - - let mut aliases = MutMap::default(); - - for (symbol, alias) in output.aliases { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_symbols.remove(&symbol); - - aliases.insert(symbol, alias); - } - - // By this point, all exposed symbols should have been removed from - // exposed_symbols and added to exposed_vars_by_symbol. If any were - // not, that means they were declared as exposed but there was - // no actual declaration with that name! - for symbol in exposed_symbols { - env.problem(Problem::ExposedButNotDefined(symbol)); - - // In case this exposed value is referenced by other modules, - // create a decl for it whose implementation is a runtime error. - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(symbol, env.var_store.fresh()); - - let runtime_error = RuntimeError::ExposedButNotDefined(symbol); - - let value_def = { - let pattern_id = env.pool.add(Pattern2::Identifier(symbol)); - let expr_id = env.pool.add(Expr2::RuntimeError()); - ValueDef::NoAnnotation { - pattern_id, - expr_id, - expr_var: env.var_store.fresh(), - } - }; - - let def = Def::Value(value_def); - - declarations.push(Declaration::Declare(def)); - } - - // Incorporate any remaining output.lookups entries into references. - for symbol in output.references.lookups { - references.insert(symbol); - } - - // Incorporate any remaining output.calls entries into references. - for symbol in output.references.calls { - references.insert(symbol); - } - - // Gather up all the symbols that were referenced from other modules. - for symbol in env.qualified_lookups.iter() { - references.insert(*symbol); - } - - // TODO find captured variables - // for declaration in declarations.iter_mut() { - // match declaration { - // Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()), - // DeclareRec(defs) => { - // fix_values_captured_in_closure_defs(defs, &mut MutSet::default()) - // } - // InvalidCycle(_, _) | Builtin(_) => {} - // } - // } - - // TODO this loops over all symbols in the module, we can speed it up by having an - // iterator over all builtin symbols - - // TODO move over the builtins - // for symbol in references.iter() { - // if symbol.is_builtin() { - // // this can fail when the symbol is for builtin types, or has no implementation yet - // if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) { - // declarations.push(Declaration::Builtin(def)); - // } - // } - // } - - Ok(ModuleOutput { - aliases, - rigid_variables, - declarations, - references, - exposed_imports: can_exposed_imports, - problems: vec![], // TODO env.problems, - lookups, - ident_ids: env.ident_ids, - }) - } - (Err(runtime_error), _) => Err(runtime_error), - } -} diff --git a/editor/src/lang/parse.rs b/editor/src/lang/parse.rs deleted file mode 100644 index 9c7253bb75..0000000000 --- a/editor/src/lang/parse.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::Debug; - -use crate::{ - editor::ed_error::ASTNodeIdWithoutExprId, editor::ed_error::EdResult, lang::scope::Scope, -}; -use bumpalo::Bump; -use roc_parse::parser::SyntaxError; -use roc_region::all::Region; - -use super::{ - ast::{DefId, Expr2, ExprId}, - expr::{str_to_def2, Env}, -}; - -#[derive(Debug)] -pub struct AST { - pub header: AppHeader, - pub def_ids: Vec, -} - -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum ASTNodeId { - ADefId(DefId), - AExprId(ExprId), -} - -impl ASTNodeId { - pub fn to_expr_id(&self) -> EdResult { - match self { - ASTNodeId::AExprId(expr_id) => Ok(*expr_id), - _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, - } - } - - pub fn to_def_id(&self) -> EdResult { - match self { - ASTNodeId::ADefId(def_id) => Ok(*def_id), - _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, - } - } -} - -#[derive(Debug)] -pub struct AppHeader { - pub app_name: String, - pub packages_base: String, - pub imports: Vec, - pub provides: Vec, - pub ast_node_id: ExprId, // TODO probably want to use HeaderId -} - -impl AST { - pub fn parse_from_string<'a>( - code_str: &'a str, - env: &mut Env<'a>, - ast_arena: &'a Bump, - ) -> Result> { - let blank_line_indx = code_str - .find("\n\n") - .expect("I was expecting a double newline to split header and rest of code."); - - let header_str = &code_str[0..blank_line_indx]; - let tail_str = &code_str[blank_line_indx..]; - - let mut scope = Scope::new(env.home, env.pool, env.var_store); - let region = Region::new(0, 0, 0, 0); - - let mut def_ids = Vec::::new(); - - let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; - - for def2 in def2_vec { - let def_id = env.pool.add(def2); - - def_ids.push(def_id); - } - - let ast_node_id = env.pool.add(Expr2::Blank); - - Ok(AST { - header: AppHeader::parse_from_string(header_str, ast_node_id), - def_ids, - }) - } -} - -impl AppHeader { - // TODO don't use mock struct and actually parse string - pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> Self { - AppHeader { - app_name: "\"untitled-app\"".to_owned(), - packages_base: "\"platform\"".to_owned(), - imports: vec![], - provides: vec!["main".to_owned()], - ast_node_id, - } - } -} diff --git a/editor/src/lang/pattern.rs b/editor/src/lang/pattern.rs deleted file mode 100644 index aae7797e8a..0000000000 --- a/editor/src/lang/pattern.rs +++ /dev/null @@ -1,619 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant}; -use crate::lang::ast::{ExprId, FloatVal, IntVal}; -use crate::lang::expr::{to_expr_id, Env, Output}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use bumpalo::collections::Vec as BumpVec; -use roc_can::expr::unescape_char; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; -use roc_collections::all::BumpMap; -use roc_module::symbol::{Interns, Symbol}; -use roc_parse::ast::{StrLiteral, StrSegment}; -use roc_parse::pattern::PatternType; -use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; -use roc_region::all::Region; -use roc_types::subs::Variable; - -use super::constrain::Constraint; -use super::types::Type2; - -pub type PatternId = NodeId; - -#[derive(Debug)] -pub enum Pattern2 { - Identifier(Symbol), // 8B - NumLiteral(Variable, i64), // 4B + 8B - IntLiteral(IntVal), // 16B - FloatLiteral(FloatVal), // 16B - StrLiteral(PoolStr), // 8B - Underscore, // 0B - GlobalTag { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - tag_name: PoolStr, // 8B - arguments: PoolVec<(Variable, PatternId)>, // 8B - }, - PrivateTag { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - tag_name: Symbol, // 8B - arguments: PoolVec<(Variable, PatternId)>, // 8B - }, - RecordDestructure { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - destructs: PoolVec, // 8B - }, - - // Runtime Exceptions - // TODO: figure out how to better handle regions - // to keep this member under 32. With 2 Regions - // it ends up at size 40 - Shadowed { - shadowed_ident: PoolStr, - // definition: Region, - // shadowed_at: Region, - }, - - /// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! - UnsupportedPattern(Region), - // parse error patterns - MalformedPattern(MalformedPatternProblem, Region), -} - -impl ShallowClone for Pattern2 { - fn shallow_clone(&self) -> Self { - todo!() - } -} - -#[derive(Debug)] -pub struct PatternState2<'a> { - pub headers: BumpMap, - pub vars: BumpVec<'a, Variable>, - pub constraints: BumpVec<'a, Constraint<'a>>, -} - -#[derive(Debug)] -pub struct RecordDestruct { - pub var: Variable, // 4B - pub label: PoolStr, // 8B - pub symbol: Symbol, // 8B - pub typ: NodeId, // 4B -} - -#[derive(Clone, Debug)] -pub enum DestructType { - Required, - Optional(Variable, ExprId), // 4B + 4B - Guard(Variable, PatternId), // 4B + 4B -} - -pub fn as_pattern_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - pattern_id: PatternId, - pattern_type: PatternType, - pattern: &roc_parse::ast::Pattern<'a>, - region: Region, -) -> Output { - let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); - - env.pool[pattern_id] = can_pattern; - env.set_region(pattern_id, region); - - output -} - -pub fn to_pattern_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - pattern_type: PatternType, - pattern: &roc_parse::ast::Pattern<'a>, - region: Region, -) -> (Output, PatternId) { - let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); - - let pattern_id = env.pool.add(can_pattern); - env.set_region(pattern_id, region); - - (output, pattern_id) -} - -pub fn to_pattern2<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - pattern_type: PatternType, - pattern: &roc_parse::ast::Pattern<'a>, - region: Region, -) -> (Output, Pattern2) { - use roc_parse::ast::Pattern::*; - use PatternType::*; - - let mut output = Output::default(); - let can_pattern = match pattern { - Identifier(name) => match scope.introduce( - (*name).into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - output.references.bound_symbols.insert(symbol); - - Pattern2::Identifier(symbol) - } - Err((original_region, shadow)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - })); - - let name: &str = shadow.value.as_ref(); - - Pattern2::Shadowed { - shadowed_ident: PoolStr::new(name, env.pool), - } - } - }, - - QualifiedIdentifier { .. } => { - let problem = MalformedPatternProblem::QualifiedIdentifier; - malformed_pattern(env, problem, region) - } - - Underscore(_) => match pattern_type { - WhenBranch | FunctionArg => Pattern2::Underscore, - TopLevelDef | DefExpr => underscore_in_def(env, region), - }, - - FloatLiteral(ref string) => match pattern_type { - WhenBranch => match finish_parsing_float(string) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedFloat; - malformed_pattern(env, problem, region) - } - Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)), - }, - ptype => unsupported_pattern(env, ptype, region), - }, - - NumLiteral(string) => match pattern_type { - WhenBranch => match finish_parsing_int(string) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedInt; - malformed_pattern(env, problem, region) - } - Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), - }, - ptype => unsupported_pattern(env, ptype, region), - }, - - NonBase10Literal { - string, - base, - is_negative, - } => match pattern_type { - WhenBranch => match finish_parsing_base(string, *base, *is_negative) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedBase(*base); - malformed_pattern(env, problem, region) - } - Ok(int) => { - if *is_negative { - Pattern2::IntLiteral(IntVal::I64(-int)) - } else { - Pattern2::IntLiteral(IntVal::I64(int)) - } - } - }, - ptype => unsupported_pattern(env, ptype, region), - }, - - StrLiteral(literal) => match pattern_type { - WhenBranch => flatten_str_literal(env.pool, literal), - ptype => unsupported_pattern(env, ptype, region), - }, - - GlobalTag(name) => { - // Canonicalize the tag's name. - Pattern2::GlobalTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: PoolStr::new(name, env.pool), - arguments: PoolVec::empty(env.pool), - } - } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - - // Canonicalize the tag's name. - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: PoolVec::empty(env.pool), - } - } - - Apply(tag, patterns) => { - let can_patterns = PoolVec::with_capacity(patterns.len() as u32, env.pool); - for (loc_pattern, node_id) in (*patterns).iter().zip(can_patterns.iter_node_ids()) { - let (new_output, can_pattern) = to_pattern2( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - output.union(new_output); - - let can_pattern_id = env.pool.add(can_pattern); - - env.pool[node_id] = (env.var_store.fresh(), can_pattern_id); - } - - match tag.value { - GlobalTag(name) => Pattern2::GlobalTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: PoolStr::new(name, env.pool), - arguments: can_patterns, - }, - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&name.into()); - - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: can_patterns, - } - } - _ => unreachable!("Other patterns cannot be applied"), - } - } - - RecordDestructure(patterns) => { - let ext_var = env.var_store.fresh(); - let whole_var = env.var_store.fresh(); - let destructs = PoolVec::with_capacity(patterns.len() as u32, env.pool); - let opt_erroneous = None; - - for (node_id, loc_pattern) in destructs.iter_node_ids().zip((*patterns).iter()) { - match loc_pattern.value { - Identifier(label) => { - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - output.references.bound_symbols.insert(symbol); - - let destruct = RecordDestruct { - var: env.var_store.fresh(), - label: PoolStr::new(label, env.pool), - symbol, - typ: env.pool.add(DestructType::Required), - }; - - env.pool[node_id] = destruct; - env.set_region(node_id, loc_pattern.region); - } - Err((original_region, shadow)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - })); - - // let shadowed = Pattern2::Shadowed { - // definition: original_region, - // shadowed_at: loc_pattern.region, - // shadowed_ident: shadow.value, - // }; - - // No matter what the other patterns - // are, we're definitely shadowed and will - // get a runtime exception as soon as we - // encounter the first bad pattern. - // opt_erroneous = Some(); - // env.pool[node_id] = sha; - // env.set_region(node_id, loc_pattern.region); - todo!("we must both report/store the problem, but also not lose any information") - } - }; - } - - RequiredField(label, loc_guard) => { - // a guard does not introduce the label into scope! - let symbol = scope.ignore(label.into(), &mut env.ident_ids); - let (new_output, can_guard) = to_pattern_id( - env, - scope, - pattern_type, - &loc_guard.value, - loc_guard.region, - ); - - let destruct = RecordDestruct { - var: env.var_store.fresh(), - label: PoolStr::new(label, env.pool), - symbol, - typ: env - .pool - .add(DestructType::Guard(env.var_store.fresh(), can_guard)), - }; - - output.union(new_output); - - env.pool[node_id] = destruct; - env.set_region(node_id, loc_pattern.region); - } - OptionalField(label, loc_default) => { - // an optional DOES introduce the label into scope! - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - let (can_default, expr_output) = - to_expr_id(env, scope, &loc_default.value, loc_default.region); - - // an optional field binds the symbol! - output.references.bound_symbols.insert(symbol); - - output.union(expr_output); - - let destruct = RecordDestruct { - var: env.var_store.fresh(), - label: PoolStr::new(label, env.pool), - symbol, - typ: env.pool.add(DestructType::Optional( - env.var_store.fresh(), - can_default, - )), - }; - - env.pool[node_id] = destruct; - env.set_region(node_id, loc_pattern.region); - } - Err((original_region, shadow)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - })); - - // No matter what the other patterns - // are, we're definitely shadowed and will - // get a runtime exception as soon as we - // encounter the first bad pattern. - // opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); - todo!("must report problem but also not loose any information") - } - }; - } - _ => unreachable!("Any other pattern should have given a parse error"), - } - } - - // If we encountered an erroneous pattern (e.g. one with shadowing), - // use the resulting RuntimeError. Otherwise, return a successful record destructure. - opt_erroneous.unwrap_or(Pattern2::RecordDestructure { - whole_var, - ext_var, - destructs, - }) - } - - RequiredField(_name, _loc_pattern) => { - unreachable!("should have been handled in RecordDestructure"); - } - OptionalField(_name, _loc_pattern) => { - unreachable!("should have been handled in RecordDestructure"); - } - - Malformed(_str) => { - let problem = MalformedPatternProblem::Unknown; - malformed_pattern(env, problem, region) - } - - MalformedIdent(_str, bad_ident) => { - let problem = MalformedPatternProblem::BadIdent(*bad_ident); - malformed_pattern(env, problem, region) - } - - SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { - return to_pattern2(env, scope, pattern_type, sub_pattern, region) - } - }; - - (output, can_pattern) -} - -pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { - use Pattern2::*; - let mut symbols = Vec::new(); - let mut stack = vec![initial]; - - while let Some(pattern) = stack.pop() { - match pattern { - Identifier(symbol) => { - symbols.push(*symbol); - } - - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { - for (_, pat_id) in arguments.iter(pool) { - let pat = pool.get(*pat_id); - stack.push(pat); - } - } - - RecordDestructure { destructs, .. } => { - for destruct in destructs.iter(pool) { - let destruct_type = pool.get(destruct.typ); - - if let DestructType::Guard(_, subpattern_id) = &destruct_type { - let subpattern = pool.get(*subpattern_id); - stack.push(subpattern); - } else { - symbols.push(destruct.symbol); - } - } - } - - NumLiteral(_, _) - | IntLiteral(_) - | FloatLiteral(_) - | StrLiteral(_) - | Underscore - | MalformedPattern(_, _) - | Shadowed { .. } - | UnsupportedPattern(_) => {} - } - } - - symbols -} - -pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult { - match pattern { - Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), - other => UnexpectedPattern2Variant { - required_pattern2: "Identifier".to_string(), - encountered_pattern2: format!("{:?}", other), - } - .fail()?, - } -} - -pub fn symbols_and_variables_from_pattern( - pool: &Pool, - initial: &Pattern2, - initial_var: Variable, -) -> Vec<(Symbol, Variable)> { - use Pattern2::*; - let mut symbols = Vec::new(); - let mut stack = vec![(initial_var, initial)]; - - while let Some((variable, pattern)) = stack.pop() { - match pattern { - Identifier(symbol) => { - symbols.push((*symbol, variable)); - } - - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { - for (var, pat_id) in arguments.iter(pool) { - let pat = pool.get(*pat_id); - stack.push((*var, pat)); - } - } - - RecordDestructure { destructs, .. } => { - for destruct in destructs.iter(pool) { - let destruct_type = pool.get(destruct.typ); - - if let DestructType::Guard(_, subpattern_id) = &destruct_type { - let subpattern = pool.get(*subpattern_id); - stack.push((destruct.var, subpattern)); - } else { - symbols.push((destruct.symbol, destruct.var)); - } - } - } - - NumLiteral(_, _) - | IntLiteral(_) - | FloatLiteral(_) - | StrLiteral(_) - | Underscore - | MalformedPattern(_, _) - | Shadowed { .. } - | UnsupportedPattern(_) => {} - } - } - - symbols -} - -/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't -/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern. -fn unsupported_pattern<'a>( - env: &mut Env<'a>, - pattern_type: PatternType, - region: Region, -) -> Pattern2 { - use roc_problem::can::BadPattern; - env.problem(Problem::UnsupportedPattern( - BadPattern::Unsupported(pattern_type), - region, - )); - - Pattern2::UnsupportedPattern(region) -} - -fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { - use roc_problem::can::BadPattern; - env.problem(Problem::UnsupportedPattern( - BadPattern::UnderscoreInDef, - region, - )); - - Pattern2::UnsupportedPattern(region) -} - -fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { - use roc_parse::ast::StrLiteral::*; - - match literal { - PlainLine(str_slice) => Pattern2::StrLiteral(PoolStr::new(str_slice, pool)), - Line(segments) => flatten_str_lines(pool, &[segments]), - Block(lines) => flatten_str_lines(pool, lines), - } -} - -fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { - use StrSegment::*; - - let mut buf = String::new(); - - for line in lines { - for segment in line.iter() { - match segment { - Plaintext(string) => { - buf.push_str(string); - } - Unicode(loc_digits) => { - todo!("parse unicode digits {:?}", loc_digits); - } - Interpolated(loc_expr) => { - return Pattern2::UnsupportedPattern(loc_expr.region); - } - EscapedChar(escaped) => buf.push(unescape_char(escaped)), - } - } - } - - Pattern2::StrLiteral(PoolStr::new(&buf, pool)) -} - -/// When we detect a malformed pattern like `3.X` or `0b5`, -/// report it to Env and return an UnsupportedPattern runtime error pattern. -fn malformed_pattern<'a>( - env: &mut Env<'a>, - problem: MalformedPatternProblem, - region: Region, -) -> Pattern2 { - env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( - problem, region, - ))); - - Pattern2::MalformedPattern(problem, region) -} diff --git a/editor/src/lang/pool.rs b/editor/src/lang/pool.rs deleted file mode 100644 index ac0fd67206..0000000000 --- a/editor/src/lang/pool.rs +++ /dev/null @@ -1,635 +0,0 @@ -/// A pool of 32-byte nodes. The node value 0 is reserved for the pool's -/// use, and valid nodes may never have that value. -/// -/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes -/// into one page at a time, and when it runs out, it uses mmap to reserve an -/// anonymous memory page in which to store nodes. -/// -/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a -/// particular node by its NodeId, which is an opaque wrapper around a pointer. -/// -/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. -/// This is important for performance. -use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; -use roc_can::expected::Expected; -use roc_can::expected::PExpected; -use std::any::type_name; -use std::cmp::Ordering; -use std::marker::PhantomData; -use std::mem::size_of; -use std::ptr::null; - -pub const NODE_BYTES: usize = 32; - -// Each page has 128 slots. Each slot holds one 32B node -// This means each page is 4096B, which is the size of a memory page -// on typical systems where the compiler will be run. -// -// Nice things about this system include: -// * Allocating a new pagShallowCloneal index (supporting up to 32 generations), then -// we would have 27 bits remaining, meaning we could only support at most -// 134M nodes. Since the editor has a separate Pool for each module, is that -// enough for any single module we'll encounter in practice? Probably, and -// especially if we allocate super large collection literals on the heap instead -// of in the pool. -// -// Another possible design is to try to catch reuse bugs using an "ASan" like -// approach: in development builds, whenever we "free" a particular slot, we -// can add it to a dev-build-only "freed nodes" list and don't hand it back -// out (so, we leak the memory.) Then we can (again, in development builds only) -// check to see if we're about to store something in zeroed-out memory; if so, check -// to see if it was - -#[derive(Debug, Eq)] -pub struct NodeId { - index: u32, - _phantom: PhantomData, -} - -impl Clone for NodeId { - fn clone(&self) -> Self { - NodeId { - index: self.index, - _phantom: PhantomData::default(), - } - } -} - -impl PartialEq for NodeId { - fn eq(&self, other: &Self) -> bool { - self.index == other.index - } -} - -impl Copy for NodeId {} - -#[derive(Debug)] -pub struct Pool { - nodes: *mut [u8; NODE_BYTES], - num_nodes: u32, - capacity: u32, - // free_1node_slots: Vec>, -} - -impl Pool { - pub fn with_capacity(nodes: u32) -> Self { - // round up number of nodes requested to nearest page size in bytes - let bytes_per_page = page_size::get(); - let node_bytes = NODE_BYTES * nodes as usize; - let leftover = node_bytes % bytes_per_page; - let bytes_to_mmap = if leftover == 0 { - node_bytes - } else { - node_bytes + bytes_per_page - leftover - }; - - let nodes = unsafe { - // mmap anonymous memory pages - that is, contiguous virtual memory - // addresses from the OS which will be lazily translated into - // physical memory one 4096-byte page at a time, once we actually - // try to read or write in that page's address range. - libc::mmap( - null::() as *mut c_void, - bytes_to_mmap, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, - 0, - 0, - ) - } as *mut [u8; NODE_BYTES]; - - // This is our actual capacity, in nodes. - // It might be higher than the requested capacity due to rounding up - // to nearest page size. - let capacity = (bytes_to_mmap / NODE_BYTES) as u32; - - Pool { - nodes, - num_nodes: 0, - capacity, - } - } - - pub fn add(&mut self, node: T) -> NodeId { - // It's only safe to store this if T fits in S. - debug_assert!( - size_of::() <= NODE_BYTES, - "{} has a size of {}, but it needs to be at most {}", - type_name::(), - size_of::(), - NODE_BYTES - ); - - let node_id = self.reserve(1); - let node_ptr = unsafe { self.nodes.offset(node_id.index as isize) } as *mut T; - - unsafe { *node_ptr = node }; - - node_id - } - - /// Reserves the given number of contiguous node slots, and returns - /// the NodeId of the first one. We only allow reserving 2^32 in a row. - fn reserve(&mut self, nodes: u32) -> NodeId { - // TODO once we have a free list, look in there for an open slot first! - let index = self.num_nodes; - - if index < self.capacity { - self.num_nodes = index + nodes; - - NodeId { - index, - _phantom: PhantomData::default(), - } - } else { - todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows."); - } - } - - pub fn get<'a, 'b, T>(&'a self, node_id: NodeId) -> &'b T { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *const T; - - &*node_ptr - } - } - - pub fn get_mut(&mut self, node_id: NodeId) -> &mut T { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; - - &mut *node_ptr - } - } - - pub fn set(&mut self, node_id: NodeId, element: T) { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; - - *node_ptr = element; - } - } - - // A node is available iff its bytes are all zeroes - #[allow(dead_code)] - fn is_available(&self, node_id: NodeId) -> bool { - debug_assert_eq!(size_of::(), NODE_BYTES); - - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES]; - - *node_ptr == [0; NODE_BYTES] - } - } -} - -impl std::ops::Index> for Pool { - type Output = T; - - fn index(&self, node_id: NodeId) -> &Self::Output { - self.get(node_id) - } -} - -impl std::ops::IndexMut> for Pool { - fn index_mut(&mut self, node_id: NodeId) -> &mut Self::Output { - self.get_mut(node_id) - } -} - -impl Drop for Pool { - fn drop(&mut self) { - unsafe { - libc::munmap( - self.nodes as *mut c_void, - NODE_BYTES * self.capacity as usize, - ); - } - } -} - -/// A string containing at most 2^32 pool-allocated bytes. -#[derive(Debug, Copy, Clone)] -pub struct PoolStr { - first_node_id: NodeId<()>, - len: u32, -} - -#[test] -fn pool_str_size() { - assert_eq!(size_of::(), 8); -} - -impl PoolStr { - pub fn new(string: &str, pool: &mut Pool) -> Self { - debug_assert!(string.len() <= u32::MAX as usize); - - let chars_per_node = NODE_BYTES / size_of::(); - - let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32; - - if number_of_nodes > 0 { - let first_node_id = pool.reserve(number_of_nodes); - let index = first_node_id.index as isize; - let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void; - - unsafe { - libc::memcpy( - next_node_ptr, - string.as_ptr() as *const c_void, - string.len(), - ); - } - - PoolStr { - first_node_id, - len: string.len() as u32, - } - } else { - PoolStr { - first_node_id: NodeId { - index: 0, - _phantom: PhantomData::default(), - }, - len: 0, - } - } - } - - pub fn as_str(&self, pool: &Pool) -> &str { - unsafe { - let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8; - - let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize); - - std::str::from_utf8_unchecked(&node_slice[0..self.len as usize]) - } - } - - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, pool: &Pool) -> usize { - let contents = self.as_str(pool); - - contents.len() - } - - pub fn is_empty(&self, pool: &Pool) -> bool { - self.len(pool) == 0 - } -} - -impl ShallowClone for PoolStr { - fn shallow_clone(&self) -> Self { - // Question: should this fully clone, or is a shallow copy - // (and the aliasing it entails) OK? - Self { - first_node_id: self.first_node_id, - len: self.len, - } - } -} - -/// An array of at most 2^32 pool-allocated nodes. -#[derive(Debug)] -pub struct PoolVec { - first_node_id: NodeId, - len: u32, -} - -#[test] -fn pool_vec_size() { - assert_eq!(size_of::>(), 8); -} - -impl<'a, T: 'a + Sized> PoolVec { - pub fn empty(pool: &mut Pool) -> Self { - Self::new(std::iter::empty(), pool) - } - - pub fn with_capacity(len: u32, pool: &mut Pool) -> Self { - debug_assert!( - size_of::() <= NODE_BYTES, - "{} has a size of {}", - type_name::(), - size_of::() - ); - - if len == 0 { - Self::empty(pool) - } else { - let first_node_id = pool.reserve(len); - - PoolVec { first_node_id, len } - } - } - - pub fn len(&self) -> usize { - self.len as usize - } - - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - pub fn new>(nodes: I, pool: &mut Pool) -> Self { - debug_assert!(nodes.len() <= u32::MAX as usize); - debug_assert!(size_of::() <= NODE_BYTES); - - let len = nodes.len() as u32; - - if len > 0 { - let first_node_id = pool.reserve(len); - let index = first_node_id.index as isize; - let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T; - - for (indx_inc, node) in nodes.enumerate() { - unsafe { - *next_node_ptr = node; - - next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T; - } - } - - PoolVec { first_node_id, len } - } else { - PoolVec { - first_node_id: NodeId { - index: 0, - _phantom: PhantomData::default(), - }, - len: 0, - } - } - } - - pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator { - self.pool_list_iter(pool) - } - - pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator { - self.pool_list_iter_mut(pool) - } - - pub fn iter_node_ids(&self) -> impl ExactSizeIterator> { - self.pool_list_iter_node_ids() - } - - /// Private version of into_iter which exposes the implementation detail - /// of PoolVecIter. We don't want that struct to be public, but we - /// actually do want to have this separate function for code reuse - /// in the iterator's next() method. - #[inline(always)] - fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> { - PoolVecIter { - pool, - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - #[inline(always)] - fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> { - PoolVecIterMut { - pool, - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - #[inline(always)] - fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds { - PoolVecIterNodeIds { - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - pub fn free(self, pool: &'a mut Pool) { - // zero out the memory - unsafe { - let index = self.first_node_id.index as isize; - let node_ptr = pool.nodes.offset(index) as *mut c_void; - let bytes = self.len as usize * NODE_BYTES; - - libc::memset(node_ptr, 0, bytes); - } - - // TODO insert it into the pool's free list - } -} - -impl ShallowClone for PoolVec { - fn shallow_clone(&self) -> Self { - // Question: should this fully clone, or is a shallow copy - // (and the aliasing it entails) OK? - Self { - first_node_id: self.first_node_id, - len: self.len, - } - } -} - -struct PoolVecIter<'a, T> { - pool: &'a Pool, - current_node_id: NodeId, - len_remaining: u32, -} - -impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T> -where - T: 'a, -{ - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl<'a, T> Iterator for PoolVecIter<'a, T> -where - T: 'a, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(unsafe { &*node_ptr }) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; - - Some(unsafe { &*node_ptr }) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -struct PoolVecIterMut<'a, T> { - pool: &'a Pool, - current_node_id: NodeId, - len_remaining: u32, -} - -impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T> -where - T: 'a, -{ - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl<'a, T> Iterator for PoolVecIterMut<'a, T> -where - T: 'a, -{ - type Item = &'a mut T; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(unsafe { &mut *node_ptr }) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; - - Some(unsafe { &mut *node_ptr }) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -struct PoolVecIterNodeIds { - current_node_id: NodeId, - len_remaining: u32, -} - -impl ExactSizeIterator for PoolVecIterNodeIds { - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl Iterator for PoolVecIterNodeIds { - type Item = NodeId; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let current = self.current_node_id; - let index = current.index; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(current) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - Some(self.current_node_id) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -#[test] -fn pool_vec_iter_test() { - let expected_vec: Vec = vec![2, 4, 8, 16]; - - let mut test_pool = Pool::with_capacity(1024); - let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool); - - let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); - - assert_eq!(current_vec, expected_vec); -} -/// Clones the outer node, but does not clone any nodeids -pub trait ShallowClone { - fn shallow_clone(&self) -> Self; -} - -impl ShallowClone for Expected { - fn shallow_clone(&self) -> Self { - use Expected::*; - - match self { - NoExpectation(t) => NoExpectation(t.shallow_clone()), - ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), - FromAnnotation(loc_pat, n, source, t) => { - FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone()) - } - } - } -} - -impl ShallowClone for PExpected { - fn shallow_clone(&self) -> Self { - use PExpected::*; - - match self { - NoExpectation(t) => NoExpectation(t.shallow_clone()), - ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), - } - } -} diff --git a/editor/src/lang/roc_file.rs b/editor/src/lang/roc_file.rs deleted file mode 100644 index 5379e9097a..0000000000 --- a/editor/src/lang/roc_file.rs +++ /dev/null @@ -1,133 +0,0 @@ -use bumpalo::collections::Vec; -use bumpalo::Bump; -use roc_fmt::def::fmt_def; -use roc_fmt::module::fmt_module; -use roc_parse::ast::{Def, Module}; -use roc_parse::module::module_defs; -use roc_parse::parser; -use roc_parse::parser::{Parser, SyntaxError}; -use roc_region::all::Located; -use std::ffi::OsStr; -use std::path::Path; -use std::{fs, io}; - -#[derive(Debug)] -pub struct File<'a> { - path: &'a Path, - module_header: Module<'a>, - content: Vec<'a, Located>>, -} - -#[derive(Debug)] -pub enum ReadError<'a> { - Read(std::io::Error), - ParseDefs(SyntaxError<'a>), - ParseHeader(SyntaxError<'a>), - DoesntHaveRocExtension, -} - -impl<'a> File<'a> { - pub fn read(path: &'a Path, arena: &'a Bump) -> Result, ReadError<'a>> { - if path.extension() != Some(OsStr::new("roc")) { - return Err(ReadError::DoesntHaveRocExtension); - } - - let bytes = fs::read(path).map_err(ReadError::Read)?; - - let allocation = arena.alloc(bytes); - - let module_parse_state = parser::State::new(allocation); - let parsed_module = roc_parse::module::parse_header(arena, module_parse_state); - - match parsed_module { - Ok((module, state)) => { - let parsed_defs = module_defs().parse(arena, state); - - match parsed_defs { - Ok((_, defs, _)) => Ok(File { - path, - module_header: module, - content: defs, - }), - Err((_, error, _)) => Err(ReadError::ParseDefs(error)), - } - } - Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))), - } - } - - pub fn fmt(&self) -> String { - let arena = Bump::new(); - let mut formatted_file = String::new(); - - let mut module_header_buf = bumpalo::collections::String::new_in(&arena); - fmt_module(&mut module_header_buf, &self.module_header); - - formatted_file.push_str(module_header_buf.as_str()); - - for def in &self.content { - let mut def_buf = bumpalo::collections::String::new_in(&arena); - - fmt_def(&mut def_buf, &def.value, 0); - - formatted_file.push_str(def_buf.as_str()); - } - - formatted_file - } - - pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> { - let formatted_file = self.fmt(); - - fs::write(write_path, formatted_file) - } - - pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> { - self.fmt_then_write_to( - self.path - .with_file_name(new_name) - .with_extension("roc") - .as_path(), - ) - } - - pub fn fmt_then_write(&self) -> io::Result<()> { - self.fmt_then_write_to(self.path) - } -} - -#[cfg(test)] -mod test_file { - use crate::lang::roc_file; - use bumpalo::Bump; - use std::path::Path; - - #[test] - fn read_and_fmt_simple_roc_module() { - let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc"); - - let arena = Bump::new(); - - let file = roc_file::File::read(simple_module_path, &arena) - .expect("Could not read SimpleUnformatted.roc in test_file test"); - - assert_eq!( - file.fmt(), - indoc!( - r#" - interface Simple - exposes [ - v, x - ] - imports [] - - v : Str - - v = "Value!" - - x : Int - x = 4"# - ) - ); - } -} diff --git a/editor/src/lang/scope.rs b/editor/src/lang/scope.rs deleted file mode 100644 index e17f3e09a9..0000000000 --- a/editor/src/lang/scope.rs +++ /dev/null @@ -1,325 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use crate::lang::pool::{Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::types::{Alias, Type2, TypeId}; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, Lowercase}; -use roc_module::symbol::{IdentIds, ModuleId, Symbol}; -use roc_problem::can::RuntimeError; -use roc_region::all::{Located, Region}; -use roc_types::{ - builtin_aliases, - solved_types::{BuiltinAlias, FreeVars, SolvedType}, - subs::{VarId, VarStore, Variable}, -}; - -fn solved_type_to_type_id( - pool: &mut Pool, - solved_type: &SolvedType, - free_vars: &mut FreeVars, - var_store: &mut VarStore, -) -> TypeId { - let typ2 = to_type2(pool, solved_type, free_vars, var_store); - - pool.add(typ2) -} - -fn to_type2( - pool: &mut Pool, - solved_type: &SolvedType, - free_vars: &mut FreeVars, - var_store: &mut VarStore, -) -> Type2 { - match solved_type { - SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual) => { - let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); - - for (type_variable_node_id, (lowercase, solved_arg)) in type_variables - .iter_node_ids() - .zip(solved_type_variables.iter()) - { - let typ2 = to_type2(pool, solved_arg, free_vars, var_store); - - let node = pool.add(typ2); - - pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node); - } - - let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store); - - let actual = pool.add(actual_typ2); - - let typ2 = Type2::Alias(*symbol, type_variables, actual); - - typ2 - } - SolvedType::TagUnion(tags, ext) => { - let new_tags = PoolVec::with_capacity(tags.len() as u32, pool); - - for (tag_node_id, (tag_name, args)) in new_tags.iter_node_ids().zip(tags.iter()) { - let new_args: PoolVec = PoolVec::with_capacity(args.len() as u32, pool); - - for (arg_node_id, arg) in new_args.iter_node_ids().zip(args.iter()) { - let node = to_type2(pool, arg, free_vars, var_store); - - pool[arg_node_id] = node; - } - - pool[tag_node_id] = (tag_name.clone(), new_args); - } - - let actual_typ2 = to_type2(pool, ext, free_vars, var_store); - - let actual = pool.add(actual_typ2); - - let typ2 = Type2::TagUnion(new_tags, actual); - - typ2 - } - SolvedType::Flex(var_id) => { - Type2::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)) - } - SolvedType::EmptyTagUnion => Type2::EmptyTagUnion, - rest => todo!("{:?}", rest), - } -} - -fn var_id_to_flex_var( - var_id: VarId, - free_vars: &mut FreeVars, - var_store: &mut VarStore, -) -> Variable { - if let Some(var) = free_vars.unnamed_vars.get(&var_id) { - *var - } else { - let var = var_store.fresh(); - free_vars.unnamed_vars.insert(var_id, var); - - var - } -} - -#[derive(Debug)] -pub struct Scope { - /// All the identifiers in scope, mapped to were they were defined and - /// the Symbol they resolve to. - idents: MutMap, - - /// A cache of all the symbols in scope. This makes lookups much - /// faster when checking for unused defs and unused arguments. - symbols: MutMap, - - /// The type aliases currently in scope - aliases: MutMap, - - /// The current module being processed. This will be used to turn - /// unqualified idents into Symbols. - home: ModuleId, -} - -impl Scope { - pub fn new(home: ModuleId, pool: &mut Pool, var_store: &mut VarStore) -> Scope { - let solved_aliases = builtin_aliases::aliases(); - let mut aliases = MutMap::default(); - - for (symbol, builtin_alias) in solved_aliases { - // let BuiltinAlias { region, vars, typ } = builtin_alias; - let BuiltinAlias { vars, typ, .. } = builtin_alias; - - let mut free_vars = FreeVars::default(); - - // roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); - let actual = solved_type_to_type_id(pool, &typ, &mut free_vars, var_store); - - // make sure to sort these variables to make them line up with the type arguments - let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); - type_variables.sort(); - - debug_assert_eq!(vars.len(), type_variables.len()); - let variables = PoolVec::with_capacity(vars.len() as u32, pool); - - let it = variables - .iter_node_ids() - .zip(vars.iter()) - .zip(type_variables); - for ((node_id, loc_name), (_, var)) in it { - // TODO region is ignored, but "fake" anyway. How to resolve? - let name = PoolStr::new(loc_name.value.as_str(), pool); - pool[node_id] = (name, var); - } - - let alias = Alias { - actual, - /// We know that builtin aliases have no hiddden variables (e.g. in closures) - hidden_variables: PoolVec::empty(pool), - targs: variables, - }; - - aliases.insert(symbol, alias); - } - - let idents = Symbol::default_in_scope(); - let idents: MutMap<_, _> = idents.into_iter().collect(); - - Scope { - home, - idents, - symbols: MutMap::default(), - aliases, - } - } - - pub fn idents(&self) -> impl Iterator { - self.idents.iter() - } - - pub fn symbols(&self) -> impl Iterator + '_ { - self.symbols.iter().map(|(x, y)| (*x, *y)) - } - - pub fn contains_ident(&self, ident: &Ident) -> bool { - self.idents.contains_key(ident) - } - - pub fn contains_symbol(&self, symbol: Symbol) -> bool { - self.symbols.contains_key(&symbol) - } - - pub fn num_idents(&self) -> usize { - self.idents.len() - } - - pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result { - match self.idents.get(ident) { - Some((symbol, _)) => Ok(*symbol), - None => Err(RuntimeError::LookupNotInScope( - Located { - region, - value: ident.clone().into(), - }, - self.idents.keys().map(|v| v.as_ref().into()).collect(), - )), - } - } - - pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> { - self.aliases.get(&symbol) - } - - /// Introduce a new ident to scope. - /// - /// Returns Err if this would shadow an existing ident, including the - /// Symbol and Region of the ident we already had in scope under that name. - pub fn introduce( - &mut self, - ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, - region: Region, - ) -> Result)> { - match self.idents.get(&ident) { - Some((_, original_region)) => { - let shadow = Located { - value: ident, - region, - }; - - Err((*original_region, shadow)) - } - None => { - // If this IdentId was already added previously - // when the value was exposed in the module header, - // use that existing IdentId. Otherwise, create a fresh one. - let ident_id = match exposed_ident_ids.get_id(&ident) { - Some(ident_id) => *ident_id, - None => all_ident_ids.add(ident.clone().into()), - }; - - let symbol = Symbol::new(self.home, ident_id); - - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); - - Ok(symbol) - } - } - } - - /// Ignore an identifier. - /// - /// Used for record guards like { x: Just _ } - pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { - let ident_id = all_ident_ids.add(ident.into()); - Symbol::new(self.home, ident_id) - } - - /// Import a Symbol from another module into this module's top-level scope. - /// - /// Returns Err if this would shadow an existing ident, including the - /// Symbol and Region of the ident we already had in scope under that name. - pub fn import( - &mut self, - ident: Ident, - symbol: Symbol, - region: Region, - ) -> Result<(), (Symbol, Region)> { - match self.idents.get(&ident) { - Some(shadowed) => Err(*shadowed), - None => { - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); - - Ok(()) - } - } - } - - pub fn add_alias( - &mut self, - pool: &mut Pool, - name: Symbol, - vars: PoolVec<(PoolStr, Variable)>, - typ: TypeId, - ) { - let mut hidden_variables = MutSet::default(); - hidden_variables.extend(typ.variables(pool)); - - for loc_var in vars.iter(pool) { - hidden_variables.remove(&loc_var.1); - } - - let hidden_variables_vec = PoolVec::with_capacity(hidden_variables.len() as u32, pool); - - for (node_id, var) in hidden_variables_vec.iter_node_ids().zip(hidden_variables) { - pool[node_id] = var; - } - - let alias = Alias { - targs: vars, - hidden_variables: hidden_variables_vec, - actual: typ, - }; - - self.aliases.insert(name, alias); - } - - pub fn contains_alias(&mut self, name: Symbol) -> bool { - self.aliases.contains_key(&name) - } -} - -impl ShallowClone for Scope { - fn shallow_clone(&self) -> Self { - Self { - idents: self.idents.clone(), - symbols: self.symbols.clone(), - aliases: self - .aliases - .iter() - .map(|(s, a)| (*s, a.shallow_clone())) - .collect(), - home: self.home, - } - } -} diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs deleted file mode 100644 index 68b29c7635..0000000000 --- a/editor/src/lang/solve.rs +++ /dev/null @@ -1,1752 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -use crate::lang::constrain::Constraint::{self, *}; -use crate::lang::pool::{Pool, PoolVec, ShallowClone}; -use crate::lang::types::Type2; -use bumpalo::Bump; -use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; -use roc_module::ident::TagName; -use roc_module::symbol::Symbol; -use roc_region::all::{Located, Region}; -use roc_types::solved_types::Solved; -use roc_types::subs::{ - AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, - SubsSlice, UnionTags, Variable, VariableSubsSlice, -}; -use roc_types::types::{ - gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField, -}; -use roc_unify::unify::unify; -use roc_unify::unify::Unified::*; - -// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed -// https://github.com/elm/compiler -// Thank you, Evan! - -// A lot of energy was put into making type inference fast. That means it's pretty intimidating. -// -// Fundamentally, type inference assigns very general types based on syntax, and then tries to -// make all the pieces fit together. For instance when writing -// -// > f x -// -// We know that `f` is a function, and thus must have some type `a -> b`. -// `x` is just a variable, that gets the type `c` -// -// Next comes constraint generation. For `f x` to be well-typed, -// it must be the case that `c = a`, So a constraint `Eq(c, a)` is generated. -// But `Eq` is a bit special: `c` does not need to equal `a` exactly, but they need to be equivalent. -// This allows for instance the use of aliases. `c` could be an alias, and so looks different from -// `a`, but they still represent the same type. -// -// Then we get to solving, which happens in this file. -// -// When we hit an `Eq` constraint, then we check whether the two involved types are in fact -// equivalent using unification, and when they are, we can substitute one for the other. -// -// When all constraints are processed, and no unification errors have occurred, then the program -// is type-correct. Otherwise the errors are reported. -// -// Now, coming back to efficiency, this type checker uses *ranks* to optimize -// The rank tracks the number of let-bindings a variable is "under". Top-level definitions -// have rank 1. A let in a top-level definition gets rank 2, and so on. -// -// This has to do with generalization of type variables. This is described here -// -// http://okmij.org/ftp/ML/generalization.html#levels -// -// The problem is that when doing inference naively, this program would fail to typecheck -// -// f = -// id = \x -> x -// -// { a: id 1, b: id "foo" } -// -// Because `id` is applied to an integer, the type `Int -> Int` is inferred, which then gives a -// type error for `id "foo"`. -// -// Thus instead the inferred type for `id` is generalized (see the `generalize` function) to `a -> a`. -// Ranks are used to limit the number of type variables considered for generalization. Only those inside -// of the let (so those used in inferring the type of `\x -> x`) are considered. - -#[derive(PartialEq, Debug, Clone)] -pub enum TypeError { - BadExpr(Region, Category, ErrorType, Expected), - BadPattern(Region, PatternCategory, ErrorType, PExpected), - CircularType(Region, Symbol, ErrorType), - BadType(roc_types::types::Problem), - UnexposedLookup(Symbol), -} - -#[derive(Clone, Debug, Default)] -pub struct Env { - pub vars_by_symbol: MutMap, - pub aliases: MutMap, -} - -const DEFAULT_POOLS: usize = 8; - -#[derive(Clone, Debug)] -struct Pools(Vec>); - -impl Default for Pools { - fn default() -> Self { - Pools::new(DEFAULT_POOLS) - } -} - -impl Pools { - pub fn new(num_pools: usize) -> Self { - Pools(vec![Vec::new(); num_pools]) - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { - self.0 - .get_mut(rank.into_usize()) - .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) - } - - pub fn get(&self, rank: Rank) -> &Vec { - self.0 - .get(rank.into_usize()) - .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) - } - - pub fn iter(&self) -> std::slice::Iter<'_, Vec> { - self.0.iter() - } - - pub fn split_last(&self) -> (&Vec, &[Vec]) { - self.0 - .split_last() - .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")) - } - - pub fn extend_to(&mut self, n: usize) { - for _ in self.len()..n { - self.0.push(Vec::new()); - } - } -} - -#[derive(Clone)] -struct State { - env: Env, - mark: Mark, -} - -pub fn run<'a>( - arena: &'a Bump, - mempool: &mut Pool, - env: &Env, - problems: &mut Vec, - mut subs: Subs, - constraint: &Constraint, -) -> (Solved, Env) { - let env = run_in_place(arena, mempool, env, problems, &mut subs, constraint); - - (Solved(subs), env) -} - -/// Modify an existing subs in-place instead -pub fn run_in_place<'a>( - arena: &'a Bump, - mempool: &mut Pool, - env: &Env, - problems: &mut Vec, - subs: &mut Subs, - constraint: &Constraint, -) -> Env { - let mut pools = Pools::default(); - let state = State { - env: env.clone(), - mark: Mark::NONE.next(), - }; - let rank = Rank::toplevel(); - let state = solve( - arena, - mempool, - env, - state, - rank, - &mut pools, - problems, - &mut MutMap::default(), - subs, - constraint, - ); - - state.env -} - -#[allow(clippy::too_many_arguments)] -fn solve<'a>( - arena: &'a Bump, - mempool: &mut Pool, - env: &Env, - state: State, - rank: Rank, - pools: &mut Pools, - problems: &mut Vec, - cached_aliases: &mut MutMap, - subs: &mut Subs, - constraint: &Constraint, -) -> State { - match constraint { - True => state, - // SaveTheEnvironment => { - // // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment - // let mut copy = state; - // - // copy.env = env.clone(); - // - // copy - // } - Eq(typ, expectation, category, region) => { - let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - arena, - mempool, - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - match unify(subs, actual, expected) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadExpr( - *region, - category.clone(), - actual_type, - expectation.replace_ref(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - // Store(source, target, _filename, _linenr) => { - // // a special version of Eq that is used to store types in the AST. - // // IT DOES NOT REPORT ERRORS! - // let actual = type_to_var(subs, rank, pools, cached_aliases, source); - // let target = *target; - // - // match unify(subs, actual, target) { - // Success(vars) => { - // introduce(subs, rank, pools, &vars); - // - // state - // } - // Failure(vars, _actual_type, _expected_type) => { - // introduce(subs, rank, pools, &vars); - // - // // ERROR NOT REPORTED - // - // state - // } - // BadType(vars, _problem) => { - // introduce(subs, rank, pools, &vars); - // - // // ERROR NOT REPORTED - // - // state - // } - // } - // } - Lookup(symbol, expectation, region) => { - match env.vars_by_symbol.get(&symbol) { - Some(var) => { - // Deep copy the vars associated with this symbol before unifying them. - // Otherwise, suppose we have this: - // - // identity = \a -> a - // - // x = identity 5 - // - // When we call (identity 5), it's important that we not unify - // on identity's original vars. If we do, the type of `identity` will be - // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; - // the type of `identity` is more general than that! - // - // Instead, we want to unify on a *copy* of its vars. If the copy unifies - // successfully (in this case, to `Int -> Int`), we can use that to - // infer the type of this lookup (in this case, `Int`) without ever - // having mutated the original. - // - // If this Lookup is targeting a value in another module, - // then we copy from that module's Subs into our own. If the value - // is being looked up in this module, then we use our Subs as both - // the source and destination. - let actual = deep_copy_var(subs, rank, pools, *var); - - let expected = type_to_var( - arena, - mempool, - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - match unify(subs, actual, expected) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadExpr( - *region, - Category::Lookup(*symbol), - actual_type, - expectation.shallow_clone().replace(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - None => { - problems.push(TypeError::UnexposedLookup(*symbol)); - - state - } - } - } - And(sub_constraints) => { - let mut state = state; - - for sub_constraint in sub_constraints.iter() { - state = solve( - arena, - mempool, - env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - sub_constraint, - ); - } - - state - } - Pattern(region, category, typ, expectation) => { - let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - arena, - mempool, - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - match unify(subs, actual, expected) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadPattern( - *region, - category.clone(), - actual_type, - expectation.shallow_clone().replace(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - Let(let_con) => { - match &let_con.ret_constraint { - True if let_con.rigid_vars.is_empty() => { - introduce(subs, rank, pools, &let_con.flex_vars); - - // If the return expression is guaranteed to solve, - // solve the assignments themselves and move on. - solve( - arena, - mempool, - &env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ) - } - ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { - let state = solve( - arena, - mempool, - env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - // Add a variable for each def to new_vars_by_env. - let mut local_def_vars = BumpMap::new_in(arena); - - for (symbol, typ) in let_con.def_types.iter() { - let var = - type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); - - // TODO: region should come from typ - local_def_vars.insert( - *symbol, - Located { - value: var, - region: Region::zero(), - }, - ); - } - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - if !new_env.vars_by_symbol.contains_key(&symbol) { - new_env.vars_by_symbol.insert(*symbol, loc_var.value); - } - } - - let new_state = solve( - arena, - mempool, - &new_env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - ret_con, - ); - - for (symbol, loc_var) in local_def_vars { - check_for_infinite_type(subs, problems, symbol, loc_var); - } - - new_state - } - ret_con => { - let rigid_vars = &let_con.rigid_vars; - let flex_vars = &let_con.flex_vars; - - // work in the next pool to localize header - let next_rank = rank.next(); - - // introduce variables - for &var in rigid_vars.iter().chain(flex_vars.iter()) { - subs.set_rank(var, next_rank); - } - - // determine the next pool - let next_pools; - if next_rank.into_usize() < pools.len() { - next_pools = pools - } else { - // we should be off by one at this point - debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); - pools.extend_to(next_rank.into_usize()); - next_pools = pools; - } - - let pool: &mut Vec = next_pools.get_mut(next_rank); - - // Replace the contents of this pool with rigid_vars and flex_vars - pool.clear(); - pool.reserve(rigid_vars.len() + flex_vars.len()); - pool.extend(rigid_vars.iter()); - pool.extend(flex_vars.iter()); - - // run solver in next pool - - // Add a variable for each def to local_def_vars. - let mut local_def_vars = BumpMap::new_in(arena); - - for (symbol, typ) in let_con.def_types.iter() { - let var = type_to_var( - arena, - mempool, - subs, - next_rank, - next_pools, - cached_aliases, - typ, - ); - - // TODO: region should come from type - local_def_vars.insert( - *symbol, - Located { - value: var, - region: Region::zero(), - }, - ); - } - - // Solve the assignments' constraints first. - let State { - env: saved_env, - mark, - } = solve( - arena, - mempool, - &env, - state, - next_rank, - next_pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - let young_mark = mark; - let visit_mark = young_mark.next(); - let final_mark = visit_mark.next(); - - debug_assert_eq!( - { - let offenders = next_pools - .get(next_rank) - .iter() - .filter(|var| { - let current_rank = - subs.get_rank(roc_types::subs::Variable::clone(var)); - - current_rank.into_usize() > next_rank.into_usize() - }) - .collect::>(); - - let result = offenders.len(); - - if result > 0 { - dbg!(&subs, &offenders, &let_con.def_types); - } - - result - }, - 0 - ); - - // pop pool - generalize(subs, young_mark, visit_mark, next_rank, next_pools); - - next_pools.get_mut(next_rank).clear(); - - // check that things went well - debug_assert!({ - // NOTE the `subs.redundant` check is added for the uniqueness - // inference, and does not come from elm. It's unclear whether this is - // a bug with uniqueness inference (something is redundant that - // shouldn't be) or that it just never came up in elm. - let failing: Vec<_> = rigid_vars - .iter() - .filter(|&var| { - !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE - }) - .collect(); - - if !failing.is_empty() { - println!("Rigids {:?}", &rigid_vars); - println!("Failing {:?}", failing); - } - - failing.is_empty() - }); - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - // when there are duplicates, keep the one from `env` - if !new_env.vars_by_symbol.contains_key(&symbol) { - new_env.vars_by_symbol.insert(*symbol, loc_var.value); - } - } - - // Note that this vars_by_symbol is the one returned by the - // previous call to solve() - let temp_state = State { - env: saved_env, - mark: final_mark, - }; - - // Now solve the body, using the new vars_by_symbol which includes - // the assignments' name-to-variable mappings. - let new_state = solve( - arena, - mempool, - &new_env, - temp_state, - rank, - next_pools, - problems, - cached_aliases, - subs, - &ret_con, - ); - - for (symbol, loc_var) in local_def_vars { - check_for_infinite_type(subs, problems, symbol, loc_var); - } - - new_state - } - } - } // _ => todo!("implement {:?}", constraint), - } -} - -fn type_to_var<'a>( - arena: &'a Bump, - mempool: &mut Pool, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - cached: &mut MutMap, - typ: &Type2, -) -> Variable { - type_to_variable(arena, mempool, subs, rank, pools, cached, typ) -} - -/// Abusing existing functions for our purposes -/// this is to put a solved type back into subs -pub fn insert_type_into_subs<'a>( - arena: &'a Bump, - mempool: &mut Pool, - subs: &mut Subs, - typ: &Type2, -) -> Variable { - let rank = Rank::NONE; - let mut pools = Pools::default(); - let mut cached = MutMap::default(); - - type_to_variable(arena, mempool, subs, rank, &mut pools, &mut cached, typ) -} - -fn type_to_variable<'a>( - arena: &'a Bump, - mempool: &Pool, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - cached: &mut MutMap, - typ: &Type2, -) -> Variable { - use Type2::*; - - match typ { - Variable(var) => *var, - Apply(symbol, args) => { - let mut arg_vars = Vec::with_capacity(args.len()); - for var_id in args.iter_node_ids() { - let arg = mempool.get(var_id); - arg_vars.push(type_to_variable( - arena, mempool, subs, rank, pools, cached, arg, - )) - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, arg_vars); - let flat_type = FlatType::Apply(*symbol, arg_vars); - let content = Content::Structure(flat_type); - - register(subs, rank, pools, content) - } - - EmptyRec => roc_types::subs::Variable::EMPTY_RECORD, - EmptyTagUnion => roc_types::subs::Variable::EMPTY_TAG_UNION, - - Record(fields, ext_id) => { - let mut field_vars = Vec::new(); - - for node_id in fields.iter_node_ids() { - use RecordField::*; - - let (field, field_type) = mempool.get(node_id); - - let field_var = match field_type { - Required(type_id) => Required(type_to_variable( - arena, - mempool, - subs, - rank, - pools, - cached, - mempool.get(*type_id), - )), - Optional(type_id) => Optional(type_to_variable( - arena, - mempool, - subs, - rank, - pools, - cached, - mempool.get(*type_id), - )), - Demanded(type_id) => Demanded(type_to_variable( - arena, - mempool, - subs, - rank, - pools, - cached, - mempool.get(*type_id), - )), - }; - - field_vars.push((field.as_str(mempool).into(), field_var)); - } - - let ext = mempool.get(*ext_id); - let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); - - let (it, new_ext_var) = - gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); - - let it = it - .into_iter() - .map(|(field, field_type)| (field.clone(), field_type)); - - field_vars.extend(it); - field_vars.sort_unstable_by(RecordFields::compare); - - let record_fields = RecordFields::insert_into_subs(subs, field_vars); - - let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); - - register(subs, rank, pools, content) - } - - Alias(symbol, args, alias_type_id) => { - // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! - // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) - // different variables (once for each occurrence). The recursion restriction is required - // for uniqueness types only: recursive aliases "introduce" an unbound uniqueness - // attribute in the body, when - // - // Peano : [ S Peano, Z ] - // - // becomes - // - // Peano : [ S (Attr u Peano), Z ] - // - // This `u` variable can be different between lists, so giving just one variable to - // this type is incorrect. - // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable - - let alias_type = mempool.get(*alias_type_id); - let is_recursive = false; // alias_type.is_recursive(); - let no_args = args.is_empty(); - /* - if no_args && !is_recursive { - if let Some(var) = cached.get(symbol) { - return *var; - } - } - */ - - let mut arg_vars = Vec::with_capacity(args.len()); - let mut new_aliases = BumpMap::new_in(arena); - - for (arg, arg_type_id) in args.iter(mempool) { - let arg_type = mempool.get(*arg_type_id); - - let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); - - let arg_str = arg.as_str(mempool); - - arg_vars.push((roc_module::ident::Lowercase::from(arg_str), arg_var)); - new_aliases.insert(arg_str, arg_var); - } - - let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []); - - let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type); - let content = Content::Alias(*symbol, arg_vars, alias_var); - - let result = register(subs, rank, pools, content); - - if no_args && !is_recursive { - // cached.insert(*symbol, result); - } - - result - } - TagUnion(tags, ext_id) => { - let ext = mempool.get(*ext_id); - - let (union_tags, ext) = - type_to_union_tags(arena, mempool, subs, rank, pools, cached, tags, ext); - let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); - - register(subs, rank, pools, content) - } - // This case is important for the rank of boolean variables - Function(arg_vars, closure_type_id, ret_type_id) => { - let closure_type = mempool.get(*closure_type_id); - let ret_type = mempool.get(*ret_type_id); - - let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); - - for var_id in arg_vars.iter_node_ids() { - let arg = mempool.get(var_id); - let var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg); - new_arg_vars.push(var) - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - - let ret_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ret_type); - let closure_var = - type_to_variable(arena, mempool, subs, rank, pools, cached, closure_type); - - let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); - - register(subs, rank, pools, content) - } - other => todo!("not implemented {:?}", &other), - // RecursiveTagUnion(rec_var, tags, ext) => { - // let mut tag_vars = MutMap::default(); - // - // for (tag, tag_argument_types) in tags { - // let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); - // - // for arg_type in tag_argument_types { - // tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); - // } - // - // tag_vars.insert(tag.clone(), tag_argument_vars); - // } - // - // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - // let mut ext_tag_vec = Vec::new(); - // let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( - // subs, - // temp_ext_var, - // &mut ext_tag_vec, - // ) { - // Ok(()) => Variable::EMPTY_TAG_UNION, - // Err((new, _)) => new, - // }; - // tag_vars.extend(ext_tag_vec.into_iter()); - // - // let content = - // Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var)); - // - // let tag_union_var = register(subs, rank, pools, content); - // - // subs.set_content( - // *rec_var, - // Content::RecursionVar { - // opt_name: None, - // structure: tag_union_var, - // }, - // ); - // - // tag_union_var - // } - // HostExposedAlias { - // name: symbol, - // arguments: args, - // actual: alias_type, - // actual_var, - // .. - // } => { - // let mut arg_vars = Vec::with_capacity(args.len()); - // let mut new_aliases = ImMap::default(); - // - // for (arg, arg_type) in args { - // let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); - // - // arg_vars.push((arg.clone(), arg_var)); - // new_aliases.insert(arg.clone(), arg_var); - // } - // - // let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); - // - // // unify the actual_var with the result var - // // this can be used to access the type of the actual_var - // // to determine its layout later - // // subs.set_content(*actual_var, descriptor.content); - // - // //subs.set(*actual_var, descriptor.clone()); - // let content = Content::Alias(*symbol, arg_vars, alias_var); - // - // let result = register(subs, rank, pools, content); - // - // // We only want to unify the actual_var with the alias once - // // if it's already redirected (and therefore, redundant) - // // don't do it again - // if !subs.redundant(*actual_var) { - // let descriptor = subs.get(result); - // subs.union(result, *actual_var, descriptor); - // } - // - // result - // } - // Erroneous(problem) => { - // let content = Content::Structure(FlatType::Erroneous(problem.clone())); - // - // register(subs, rank, pools, content) - // } - } -} - -fn type_to_union_tags<'a>( - arena: &'a Bump, - mempool: &Pool, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - cached: &mut MutMap, - tags: &PoolVec<(TagName, PoolVec)>, - ext: &Type2, -) -> (UnionTags, Variable) { - let mut tag_vars = Vec::with_capacity(tags.len()); - - let mut tag_argument_vars = Vec::new(); - for id in tags.iter_node_ids() { - let (tag, tag_argument_types) = mempool.get(id); - for arg_id in tag_argument_types.iter_node_ids() { - let arg_type = mempool.get(arg_id); - let new_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); - tag_argument_vars.push(new_var); - } - - let new_slice = VariableSubsSlice::insert_into_subs(subs, tag_argument_vars.drain(..)); - - tag_vars.push((tag.clone(), new_slice)); - } - - let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); - - let ext = { - let (it, ext) = - roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); - - tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); - tag_vars.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); - - // deduplicate, keeping the right-most occurrence of a tag name - let mut i = 0; - - while i < tag_vars.len() { - match (tag_vars.get(i), tag_vars.get(i + 1)) { - (Some((t1, _)), Some((t2, _))) => { - if t1 == t2 { - tag_vars.remove(i); - } else { - i += 1; - } - } - _ => break, - } - } - - ext - }; - - (UnionTags::insert_slices_into_subs(subs, tag_vars), ext) -} - -fn check_for_infinite_type( - subs: &mut Subs, - problems: &mut Vec, - symbol: Symbol, - loc_var: Located, -) { - let var = loc_var.value; - - while let Err((recursive, _chain)) = subs.occurs(var) { - let description = subs.get(recursive); - let content = description.content; - - // try to make a tag union recursive, see if that helps - match content { - Content::Structure(FlatType::TagUnion(tags, ext_var)) => { - let rec_var = subs.fresh_unnamed_flex_var(); - subs.set_rank(rec_var, description.rank); - subs.set_content( - rec_var, - Content::RecursionVar { - opt_name: None, - structure: recursive, - }, - ); - - let mut new_tags = Vec::with_capacity(tags.len()); - - for (name_index, slice_index) in tags.iter_all() { - let slice = subs[slice_index]; - - let mut new_vars = Vec::new(); - for var_index in slice { - let var = subs[var_index]; - new_vars.push(subs.explicit_substitute(recursive, rec_var, var)); - } - - new_tags.push((subs[name_index].clone(), new_vars)); - } - - let new_ext_var = subs.explicit_substitute(recursive, rec_var, ext_var); - - let new_tags = UnionTags::insert_into_subs(subs, new_tags); - - let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext_var); - - subs.set_content(recursive, Content::Structure(flat_type)); - } - - _other => circular_error(subs, problems, symbol, &loc_var), - } - } -} - -fn circular_error( - subs: &mut Subs, - problems: &mut Vec, - symbol: Symbol, - loc_var: &Located, -) { - let var = loc_var.value; - let (error_type, _) = subs.var_to_error_type(var); - let problem = TypeError::CircularType(loc_var.region, symbol, error_type); - - subs.set_content(var, Content::Error); - - problems.push(problem); -} - -fn generalize( - subs: &mut Subs, - young_mark: Mark, - visit_mark: Mark, - young_rank: Rank, - pools: &mut Pools, -) { - let young_vars = pools.get(young_rank); - let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars); - - // Get the ranks right for each entry. - // Start at low ranks so we only have to pass over the information once. - for (index, table) in rank_table.iter().enumerate() { - for &var in table.iter() { - adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var); - } - } - - let (last_pool, all_but_last_pool) = rank_table.split_last(); - - // For variables that have rank lowerer than young_rank, register them in - // the appropriate old pool if they are not redundant. - for vars in all_but_last_pool { - for &var in vars { - if !subs.redundant(var) { - let rank = subs.get_rank(var); - - pools.get_mut(rank).push(var); - } - } - } - - // For variables with rank young_rank, if rank < young_rank: register in old pool, - // otherwise generalize - for &var in last_pool { - if !subs.redundant(var) { - let desc_rank = subs.get_rank(var); - - if desc_rank < young_rank { - pools.get_mut(desc_rank).push(var); - } else { - subs.set_rank(var, Rank::NONE); - } - } - } -} - -fn pool_to_rank_table( - subs: &mut Subs, - young_mark: Mark, - young_rank: Rank, - young_vars: &[Variable], -) -> Pools { - let mut pools = Pools::new(young_rank.into_usize() + 1); - - // Sort the variables into buckets by rank. - for &var in young_vars.iter() { - let rank = subs.get_rank(var); - subs.set_mark(var, young_mark); - - debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); - pools.get_mut(rank).push(var); - } - - pools -} - -/// Adjust variable ranks such that ranks never increase as you move deeper. -/// This way the outermost rank is representative of the entire structure. -fn adjust_rank( - subs: &mut Subs, - young_mark: Mark, - visit_mark: Mark, - group_rank: Rank, - var: Variable, -) -> Rank { - let desc = subs.get(var); - - if desc.mark == young_mark { - let Descriptor { - content, - rank: _, - mark: _, - copy, - } = desc; - - // Mark the variable as visited before adjusting content, as it may be cyclic. - subs.set_mark(var, visit_mark); - - let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content); - - subs.set( - var, - Descriptor { - content, - rank: max_rank, - mark: visit_mark, - copy, - }, - ); - - max_rank - } else if desc.mark == visit_mark { - // nothing changes - desc.rank - } else { - let mut desc = desc; - - let min_rank = group_rank.min(desc.rank); - - // TODO from elm-compiler: how can min_rank ever be group_rank? - desc.rank = min_rank; - desc.mark = visit_mark; - - subs.set(var, desc); - - min_rank - } -} - -fn adjust_rank_content( - subs: &mut Subs, - young_mark: Mark, - visit_mark: Mark, - group_rank: Rank, - content: &Content, -) -> Rank { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - match content { - FlexVar(_) | RigidVar(_) | Error => group_rank, - - RecursionVar { .. } => group_rank, - - Structure(flat_type) => { - match flat_type { - Apply(_, args) => { - let mut rank = Rank::toplevel(); - - for var_index in args.into_iter() { - let var = subs[var_index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - rank - } - - Func(arg_vars, closure_var, ret_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ret_var); - - // TODO investigate further. - // - // My theory is that because the closure_var contains variables already - // contained in the signature only, it does not need to be part of the rank - // calculuation - if true { - rank = rank.max(adjust_rank( - subs, - young_mark, - visit_mark, - group_rank, - *closure_var, - )); - } - - for index in arg_vars.into_iter() { - let var = subs[index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - rank - } - - EmptyRecord => { - // from elm-compiler: THEORY: an empty record never needs to get generalized - Rank::toplevel() - } - - EmptyTagUnion => Rank::toplevel(), - - Record(fields, ext_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - - for index in fields.iter_variables() { - let var = subs[index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - rank - } - - TagUnion(tags, ext_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - rank = rank - .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - } - - rank - } - - FunctionOrTagUnion(_, _, ext_var) => { - adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var) - } - - RecursiveTagUnion(rec_var, tags, ext_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - rank = rank - .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - } - - // THEORY: the recursion var has the same rank as the tag union itself - // all types it uses are also in the tags already, so it cannot influence the - // rank - debug_assert!( - rank >= adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var) - ); - - rank - } - - Erroneous(_) => group_rank, - } - } - - Alias(_, args, real_var) => { - let mut rank = Rank::toplevel(); - - for var_index in args.variables() { - let var = subs[var_index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() - // this theory is not true in Roc! aliases of function types capture the closure var - rank = rank.max(adjust_rank( - subs, young_mark, visit_mark, group_rank, *real_var, - )); - - rank - } - } -} - -/// Introduce some variables to Pools at the given rank. -/// Also, set each of their ranks in Subs to be the given rank. -fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { - let pool: &mut Vec = pools.get_mut(rank); - - for &var in vars.iter() { - subs.set_rank(var, rank); - } - - pool.extend(vars); -} - -/// Function that converts rigids variables to flex variables -/// this is used during the monomorphization process -pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { - let rank = Rank::NONE; - let mut pools = Pools::default(); - - instantiate_rigids_help(subs, rank, &mut pools, var); -} - -fn instantiate_rigids_help( - subs: &mut Subs, - max_rank: Rank, - pools: &mut Pools, - var: Variable, -) -> Variable { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - let desc = subs.get_without_compacting(var); - - if let Some(copy) = desc.copy.into_variable() { - return copy; - } - - let make_descriptor = |content| Descriptor { - content, - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - let content = desc.content; - let copy = var; - - pools.get_mut(max_rank).push(copy); - - // Link the original variable to the new variable. This lets us - // avoid making multiple copies of the variable we are instantiating. - // - // Need to do this before recursively copying to avoid looping. - subs.set( - var, - Descriptor { - content: content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: copy.into(), - }, - ); - - // Now we recursively copy the content of the variable. - // We have already marked the variable as copied, so we - // will not repeat this work or crawl this variable again. - match content { - Structure(flat_type) => { - match flat_type { - Apply(_, args) => { - for var_index in args.into_iter() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - Func(arg_vars, closure_var, ret_var) => { - instantiate_rigids_help(subs, max_rank, pools, ret_var); - instantiate_rigids_help(subs, max_rank, pools, closure_var); - - for index in arg_vars.into_iter() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - EmptyRecord | EmptyTagUnion | Erroneous(_) => {} - - Record(fields, ext_var) => { - for index in fields.iter_variables() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - - TagUnion(tags, ext_var) => { - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - - FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - - RecursiveTagUnion(rec_var, tags, ext_var) => { - instantiate_rigids_help(subs, max_rank, pools, rec_var); - - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - }; - } - - FlexVar(_) | Error => {} - - RecursionVar { structure, .. } => { - instantiate_rigids_help(subs, max_rank, pools, structure); - } - - RigidVar(name) => { - // what it's all about: convert the rigid var into a flex var - subs.set(copy, make_descriptor(FlexVar(Some(name)))); - } - - Alias(_, args, real_type_var) => { - for var_index in args.variables() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - - instantiate_rigids_help(subs, max_rank, pools, real_type_var); - } - } - - var -} - -fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { - let copy = deep_copy_var_help(subs, rank, pools, var); - - subs.restore(var); - - copy -} - -fn deep_copy_var_help( - subs: &mut Subs, - max_rank: Rank, - pools: &mut Pools, - var: Variable, -) -> Variable { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - let desc = subs.get_without_compacting(var); - - if let Some(copy) = desc.copy.into_variable() { - return copy; - } else if desc.rank != Rank::NONE { - return var; - } - - let make_descriptor = |content| Descriptor { - content, - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - let content = desc.content; - let copy = subs.fresh(make_descriptor(content.clone())); - - pools.get_mut(max_rank).push(copy); - - // Link the original variable to the new variable. This lets us - // avoid making multiple copies of the variable we are instantiating. - // - // Need to do this before recursively copying to avoid looping. - subs.set( - var, - Descriptor { - content: content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: copy.into(), - }, - ); - - // Now we recursively copy the content of the variable. - // We have already marked the variable as copied, so we - // will not repeat this work or crawl this variable again. - match content { - Structure(flat_type) => { - let new_flat_type = match flat_type { - Apply(symbol, args) => { - let mut new_arg_vars = Vec::with_capacity(args.len()); - - for index in args.into_iter() { - let var = subs[index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, var); - new_arg_vars.push(copy_var); - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - - Apply(symbol, arg_vars) - } - - Func(arg_vars, closure_var, ret_var) => { - let new_ret_var = deep_copy_var_help(subs, max_rank, pools, ret_var); - let new_closure_var = deep_copy_var_help(subs, max_rank, pools, closure_var); - - let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); - - for index in arg_vars.into_iter() { - let var = subs[index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, var); - new_arg_vars.push(copy_var); - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - - Func(arg_vars, new_closure_var, new_ret_var) - } - - same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, - - Record(fields, ext_var) => { - let record_fields = { - let mut new_vars = Vec::with_capacity(fields.len()); - - for index in fields.iter_variables() { - let var = subs[index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, var); - - new_vars.push(copy_var); - } - - let field_names_start = subs.field_names.len() as u32; - let variables_start = subs.variables.len() as u32; - let field_types_start = subs.record_fields.len() as u32; - - let mut length = 0; - - for ((i1, _, i3), var) in fields.iter_all().zip(new_vars) { - let record_field = subs[i3].map(|_| var); - - subs.field_names.push(subs[i1].clone()); - subs.record_fields.push(record_field.map(|_| ())); - subs.variables.push(*record_field.as_inner()); - - length += 1; - } - - RecordFields { - length, - field_names_start, - variables_start, - field_types_start, - } - }; - - Record( - record_fields, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) - } - - TagUnion(tags, ext_var) => { - let mut new_variable_slices = Vec::with_capacity(tags.len()); - - let mut new_variables = Vec::new(); - for index in tags.variables() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_variables.push(new_var); - } - - let new_slice = - VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); - - new_variable_slices.push(new_slice); - } - - let new_variables = { - let start = subs.variable_slices.len() as u32; - let length = new_variable_slices.len() as u16; - subs.variable_slices.extend(new_variable_slices); - - SubsSlice::new(start, length) - }; - - let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); - - let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); - TagUnion(union_tags, new_ext) - } - - FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( - tag_name, - symbol, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ), - - RecursiveTagUnion(rec_var, tags, ext_var) => { - let mut new_variable_slices = Vec::with_capacity(tags.len()); - - let mut new_variables = Vec::new(); - for index in tags.variables() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_variables.push(new_var); - } - - let new_slice = - VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); - - new_variable_slices.push(new_slice); - } - - let new_variables = { - let start = subs.variable_slices.len() as u32; - let length = new_variable_slices.len() as u16; - subs.variable_slices.extend(new_variable_slices); - - SubsSlice::new(start, length) - }; - - let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); - - let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); - let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); - FlatType::RecursiveTagUnion(new_rec_var, union_tags, new_ext) - } - }; - - subs.set(copy, make_descriptor(Structure(new_flat_type))); - - copy - } - - FlexVar(_) | Error => copy, - - RecursionVar { - opt_name, - structure, - } => { - let new_structure = deep_copy_var_help(subs, max_rank, pools, structure); - - subs.set( - copy, - make_descriptor(RecursionVar { - opt_name, - structure: new_structure, - }), - ); - - copy - } - - RigidVar(name) => { - subs.set(copy, make_descriptor(FlexVar(Some(name)))); - - copy - } - - Alias(symbol, mut args, real_type_var) => { - let mut new_args = Vec::with_capacity(args.variables().len()); - - for var_index in args.variables() { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_args.push(new_var); - } - - args.replace_variables(subs, new_args); - - let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); - let new_content = Alias(symbol, args, new_real_type_var); - - subs.set(copy, make_descriptor(new_content)); - - copy - } - } -} - -fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { - let var = subs.fresh(Descriptor { - content, - rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }); - - pools.get_mut(rank).push(var); - - var -} diff --git a/editor/src/lang/types.rs b/editor/src/lang/types.rs deleted file mode 100644 index c935901acd..0000000000 --- a/editor/src/lang/types.rs +++ /dev/null @@ -1,863 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use crate::lang::expr::Env; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -// use roc_can::expr::Output; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, TagName}; -use roc_module::symbol::Symbol; -use roc_region::all::{Located, Region}; -use roc_types::types::{Problem, RecordField}; -use roc_types::{subs::Variable, types::ErrorType}; - -pub type TypeId = NodeId; - -#[derive(Debug)] -pub enum Type2 { - Variable(Variable), - - Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B - AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B - - // 32B - HostExposedAlias { - name: Symbol, // 8B - arguments: PoolVec<(PoolStr, TypeId)>, // 12B - actual_var: Variable, // 4B - actual: TypeId, // 4B - }, - EmptyTagUnion, - TagUnion(PoolVec<(TagName, PoolVec)>, TypeId), // 16B = 12B + 4B - RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec)>, TypeId), // 20B = 4B + 12B + 4B - - EmptyRec, - Record(PoolVec<(PoolStr, RecordField)>, TypeId), // 16B = 12B + 4B - - Function(PoolVec, TypeId, TypeId), // 20B = 12B + 4B + 4B - Apply(Symbol, PoolVec), // 20B = 8B + 12B - - Erroneous(Problem2), -} - -#[derive(Debug)] -pub enum Problem2 { - CanonicalizationProblem, - CircularType(Symbol, NodeId), // 12B = 8B + 4B - CyclicAlias(Symbol, PoolVec), // 20B = 8B + 12B - UnrecognizedIdent(PoolStr), // 8B - Shadowed(Located), - BadTypeArguments { - symbol: Symbol, // 8B - type_got: u8, // 1B - alias_needs: u8, // 1B - }, - InvalidModule, - SolvedTypeError, -} - -impl ShallowClone for Type2 { - fn shallow_clone(&self) -> Self { - match self { - Self::Variable(var) => Self::Variable(*var), - Self::Alias(symbol, args, alias_type_id) => { - Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone()) - } - Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()), - Self::Function(args, closure_type_id, ret_type_id) => Self::Function( - args.shallow_clone(), - closure_type_id.clone(), - ret_type_id.clone(), - ), - rest => todo!("{:?}", rest), - } - } -} - -impl Type2 { - fn substitute(_pool: &mut Pool, _subs: &MutMap, _type_id: TypeId) { - todo!() - } - - pub fn variables(&self, pool: &mut Pool) -> MutSet { - use Type2::*; - - let mut stack = vec![self]; - let mut result = MutSet::default(); - - while let Some(this) = stack.pop() { - match this { - Variable(v) => { - result.insert(*v); - } - Alias(_, _, actual) | AsAlias(_, _, actual) => { - stack.push(pool.get(*actual)); - } - HostExposedAlias { - actual_var, actual, .. - } => { - result.insert(*actual_var); - stack.push(pool.get(*actual)); - } - EmptyTagUnion | EmptyRec | Erroneous(_) => {} - TagUnion(tags, ext) => { - for (_, args) in tags.iter(pool) { - stack.extend(args.iter(pool)); - } - stack.push(pool.get(*ext)); - } - RecursiveTagUnion(rec, tags, ext) => { - for (_, args) in tags.iter(pool) { - stack.extend(args.iter(pool)); - } - stack.push(pool.get(*ext)); - result.insert(*rec); - } - Record(fields, ext) => { - for (_, field) in fields.iter(pool) { - stack.push(pool.get(*field.as_inner())); - } - stack.push(pool.get(*ext)); - } - Function(args, closure, result) => { - stack.extend(args.iter(pool)); - stack.push(pool.get(*closure)); - stack.push(pool.get(*result)); - } - Apply(_, args) => { - stack.extend(args.iter(pool)); - } - } - } - - result - } - - pub fn contains_symbol(&self, _pool: &mut Pool, _needle: Symbol) -> bool { - todo!() - } - - pub fn substitute_alias(&self, _pool: &mut Pool, _needle: Symbol, _actual: Self) { - todo!() - } -} - -impl NodeId { - pub fn variables(&self, _pool: &mut Pool) -> MutSet { - todo!() - } -} - -/// A temporary data structure to return a bunch of values to Def construction -pub enum Signature { - FunctionWithAliases { - annotation: Type2, - arguments: PoolVec, - closure_type_id: TypeId, - return_type_id: TypeId, - }, - Function { - arguments: PoolVec, - closure_type_id: TypeId, - return_type_id: TypeId, - }, - Value { - annotation: Type2, - }, -} - -pub enum Annotation2<'a> { - Annotation { - named_rigids: MutMap<&'a str, Variable>, - unnamed_rigids: MutSet, - symbols: MutSet, - signature: Signature, - }, - Erroneous(roc_types::types::Problem), -} - -pub fn to_annotation2<'a>( - env: &mut Env, - scope: &mut Scope, - annotation: &'a roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) -> Annotation2<'a> { - let mut references = References::default(); - - let annotation = to_type2(env, scope, &mut references, annotation, region); - - // we dealias until we hit a non-alias, then we either hit a function type (and produce a - // function annotation) or anything else (and produce a value annotation) - match annotation { - Type2::Function(arguments, closure_type_id, return_type_id) => { - let References { - named, - unnamed, - symbols, - .. - } = references; - - let signature = Signature::Function { - arguments, - closure_type_id, - return_type_id, - }; - - Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - } - } - Type2::Alias(_, _, _) => { - // most of the time, the annotation is not an alias, so this case is comparatively - // less efficient - shallow_dealias(env, references, annotation) - } - _ => { - let References { - named, - unnamed, - symbols, - .. - } = references; - - let signature = Signature::Value { annotation }; - - Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - } - } - } -} - -fn shallow_dealias<'a>( - env: &mut Env, - references: References<'a>, - annotation: Type2, -) -> Annotation2<'a> { - let References { - named, - unnamed, - symbols, - .. - } = references; - let mut inner = &annotation; - - loop { - match inner { - Type2::Alias(_, _, actual) => { - inner = env.pool.get(*actual); - } - Type2::Function(arguments, closure_type_id, return_type_id) => { - let signature = Signature::FunctionWithAliases { - arguments: arguments.shallow_clone(), - closure_type_id: *closure_type_id, - return_type_id: *return_type_id, - annotation, - }; - - return Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - }; - } - _ => { - let signature = Signature::Value { annotation }; - - return Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - }; - } - } - } -} - -#[derive(Default)] -pub struct References<'a> { - named: MutMap<&'a str, Variable>, - unnamed: MutSet, - hidden: MutSet, - symbols: MutSet, -} - -pub fn to_type_id<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - annotation: &roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) -> TypeId { - let type2 = to_type2(env, scope, rigids, annotation, region); - - env.add(type2, region) -} - -pub fn as_type_id<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - type_id: TypeId, - annotation: &roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) { - let type2 = to_type2(env, scope, rigids, annotation, region); - - env.pool[type_id] = type2; - env.set_region(type_id, region); -} - -pub fn to_type2<'a>( - env: &mut Env, - scope: &mut Scope, - references: &mut References<'a>, - annotation: &roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) -> Type2 { - use roc_parse::ast::TypeAnnotation::*; - - match annotation { - Apply(module_name, ident, targs) => { - match to_type_apply(env, scope, references, module_name, ident, targs, region) { - TypeApply::Apply(symbol, args) => { - references.symbols.insert(symbol); - Type2::Apply(symbol, args) - } - TypeApply::Alias(symbol, args, actual) => { - references.symbols.insert(symbol); - Type2::Alias(symbol, args, actual) - } - TypeApply::Erroneous(_problem) => { - // Type2::Erroneous(problem) - todo!() - } - } - } - Function(argument_types, return_type) => { - let arguments = PoolVec::with_capacity(argument_types.len() as u32, env.pool); - - for (type_id, loc_arg) in arguments.iter_node_ids().zip(argument_types.iter()) { - as_type_id( - env, - scope, - references, - type_id, - &loc_arg.value, - loc_arg.region, - ); - } - - let return_type_id = to_type_id( - env, - scope, - references, - &return_type.value, - return_type.region, - ); - - let closure_type = Type2::Variable(env.var_store.fresh()); - let closure_type_id = env.pool.add(closure_type); - - Type2::Function(arguments, closure_type_id, return_type_id) - } - BoundVariable(v) => { - // a rigid type variable - match references.named.get(v) { - Some(var) => Type2::Variable(*var), - None => { - let var = env.var_store.fresh(); - - references.named.insert(v, var); - - Type2::Variable(var) - } - } - } - Wildcard | Malformed(_) => { - let var = env.var_store.fresh(); - - references.unnamed.insert(var); - - Type2::Variable(var) - } - Record { fields, ext, .. } => { - let field_types_map = can_assigned_fields(env, scope, references, fields, region); - - let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool); - - for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) { - let poolstr = PoolStr::new(label, env.pool); - - let rec_field = match field { - RecordField::Optional(_) => { - let field_id = env.pool.add(field.into_inner()); - RecordField::Optional(field_id) - } - RecordField::Demanded(_) => { - let field_id = env.pool.add(field.into_inner()); - RecordField::Demanded(field_id) - } - RecordField::Required(_) => { - let field_id = env.pool.add(field.into_inner()); - RecordField::Required(field_id) - } - }; - env.pool[node_id] = (poolstr, rec_field); - } - - let ext_type = match ext { - Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), - None => env.add(Type2::EmptyRec, region), - }; - - Type2::Record(field_types, ext_type) - } - TagUnion { tags, ext, .. } => { - let tag_types_vec = can_tags(env, scope, references, tags, region); - - let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool); - - for (node_id, (tag_name, field)) in tag_types.iter_node_ids().zip(tag_types_vec) { - env.pool[node_id] = (tag_name, field); - } - - let ext_type = match ext { - Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), - None => env.add(Type2::EmptyTagUnion, region), - }; - - Type2::TagUnion(tag_types, ext_type) - } - As(loc_inner, _spaces, loc_as) => { - // e.g. `{ x : Int, y : Int } as Point }` - match loc_as.value { - Apply(module_name, ident, loc_vars) if module_name.is_empty() => { - let symbol = match scope.introduce( - ident.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => symbol, - - Err((_original_region, _shadow)) => { - // let problem = Problem2::Shadowed(original_region, shadow.clone()); - - // env.problem(roc_problem::can::Problem::ShadowingInAnnotation { - // original_region, - // shadow, - // }); - - // return Type2::Erroneous(problem); - todo!(); - } - }; - - let inner_type = to_type2(env, scope, references, &loc_inner.value, region); - let vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); - - let lowercase_vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); - - for ((loc_var, named_id), var_id) in loc_vars - .iter() - .zip(lowercase_vars.iter_node_ids()) - .zip(vars.iter_node_ids()) - { - match loc_var.value { - BoundVariable(ident) => { - let var_name = ident; - - if let Some(var) = references.named.get(&var_name) { - let poolstr = PoolStr::new(var_name, env.pool); - - let type_id = env.pool.add(Type2::Variable(*var)); - env.pool[var_id] = (poolstr.shallow_clone(), type_id); - - env.pool[named_id] = (poolstr, *var); - env.set_region(named_id, loc_var.region); - } else { - let var = env.var_store.fresh(); - - references.named.insert(var_name.clone(), var); - let poolstr = PoolStr::new(var_name, env.pool); - - let type_id = env.pool.add(Type2::Variable(var)); - env.pool[var_id] = (poolstr.shallow_clone(), type_id); - - env.pool[named_id] = (poolstr, var); - env.set_region(named_id, loc_var.region); - } - } - _ => { - // If anything other than a lowercase identifier - // appears here, the whole annotation is invalid. - return Type2::Erroneous(Problem2::CanonicalizationProblem); - } - } - } - - let alias_actual = inner_type; - // TODO instantiate recursive tag union - // let alias_actual = if let Type2::TagUnion(tags, ext) = inner_type { - // let rec_var = env.var_store.fresh(); - // - // let mut new_tags = Vec::with_capacity(tags.len()); - // for (tag_name, args) in tags { - // let mut new_args = Vec::with_capacity(args.len()); - // for arg in args { - // let mut new_arg = arg.clone(); - // new_arg.substitute_alias(symbol, &Type2::Variable(rec_var)); - // new_args.push(new_arg); - // } - // new_tags.push((tag_name.clone(), new_args)); - // } - // Type2::RecursiveTagUnion(rec_var, new_tags, ext) - // } else { - // inner_type - // }; - - let mut hidden_variables = MutSet::default(); - hidden_variables.extend(alias_actual.variables(env.pool)); - - for (_, var) in lowercase_vars.iter(env.pool) { - hidden_variables.remove(var); - } - - let alias_actual_id = env.pool.add(alias_actual); - scope.add_alias(env.pool, symbol, lowercase_vars, alias_actual_id); - - let alias = scope.lookup_alias(symbol).unwrap(); - // local_aliases.insert(symbol, alias.clone()); - - // TODO host-exposed - // if vars.is_empty() && env.home == symbol.module_id() { - // let actual_var = env.var_store.fresh(); - // rigids.host_exposed.insert(symbol, actual_var); - // Type::HostExposedAlias { - // name: symbol, - // arguments: vars, - // actual: Box::new(alias.typ.clone()), - // actual_var, - // } - // } else { - // Type::Alias(symbol, vars, Box::new(alias.typ.clone())) - // } - Type2::AsAlias(symbol, vars, alias.actual) - } - _ => { - // This is a syntactically invalid type alias. - Type2::Erroneous(Problem2::CanonicalizationProblem) - } - } - } - SpaceBefore(nested, _) | SpaceAfter(nested, _) => { - to_type2(env, scope, references, nested, region) - } - } -} - -// TODO trim down these arguments! -#[allow(clippy::too_many_arguments)] -fn can_assigned_fields<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - fields: &&[Located>>], - region: Region, -) -> MutMap<&'a str, RecordField> { - use roc_parse::ast::AssignedField::*; - use roc_types::types::RecordField::*; - - // SendMap doesn't have a `with_capacity` - let mut field_types = MutMap::default(); - - // field names we've seen so far in this record - let mut seen = std::collections::HashMap::with_capacity(fields.len()); - - 'outer: for loc_field in fields.iter() { - let mut field = &loc_field.value; - - // use this inner loop to unwrap the SpaceAfter/SpaceBefore - // when we find the name of this field, break out of the loop - // with that value, so we can check whether the field name is - // a duplicate - let new_name = 'inner: loop { - match field { - RequiredValue(field_name, _, annotation) => { - let field_type = - to_type2(env, scope, rigids, &annotation.value, annotation.region); - - let label = field_name.value; - field_types.insert(label, Required(field_type)); - - break 'inner label; - } - OptionalValue(field_name, _, annotation) => { - let field_type = - to_type2(env, scope, rigids, &annotation.value, annotation.region); - - let label = field_name.value; - field_types.insert(label.clone(), Optional(field_type)); - - break 'inner label; - } - LabelOnly(loc_field_name) => { - // Interpret { a, b } as { a : a, b : b } - let field_name = loc_field_name.value; - let field_type = { - if let Some(var) = rigids.named.get(&field_name) { - Type2::Variable(*var) - } else { - let field_var = env.var_store.fresh(); - rigids.named.insert(field_name, field_var); - Type2::Variable(field_var) - } - }; - - field_types.insert(field_name.clone(), Required(field_type)); - - break 'inner field_name; - } - SpaceBefore(nested, _) | SpaceAfter(nested, _) => { - // check the nested field instead - field = nested; - continue 'inner; - } - Malformed(_) => { - // TODO report this? - // completely skip this element, advance to the next tag - continue 'outer; - } - } - }; - - // ensure that the new name is not already in this record: - // note that the right-most tag wins when there are two with the same name - if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) { - env.problem(roc_problem::can::Problem::DuplicateRecordFieldType { - field_name: new_name.into(), - record_region: region, - field_region: loc_field.region, - replaced_region, - }); - } - } - - field_types -} - -fn can_tags<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - tags: &'a [Located>], - region: Region, -) -> Vec<(TagName, PoolVec)> { - use roc_parse::ast::Tag; - let mut tag_types = Vec::with_capacity(tags.len()); - - // tag names we've seen so far in this tag union - let mut seen = std::collections::HashMap::with_capacity(tags.len()); - - 'outer: for loc_tag in tags.iter() { - let mut tag = &loc_tag.value; - - // use this inner loop to unwrap the SpaceAfter/SpaceBefore - // when we find the name of this tag, break out of the loop - // with that value, so we can check whether the tag name is - // a duplicate - let new_name = 'inner: loop { - match tag { - Tag::Global { name, args } => { - let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); - - for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let tag_name = TagName::Global(name.value.into()); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::Private { name, args } => { - let ident_id = env.ident_ids.get_or_insert(&name.value.into()); - let symbol = Symbol::new(env.home, ident_id); - - let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); - - for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let tag_name = TagName::Private(symbol); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => { - // check the nested tag instead - tag = nested; - continue 'inner; - } - Tag::Malformed(_) => { - // TODO report this? - // completely skip this element, advance to the next tag - continue 'outer; - } - } - }; - - // ensure that the new name is not already in this tag union: - // note that the right-most tag wins when there are two with the same name - if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) { - env.problem(roc_problem::can::Problem::DuplicateTag { - tag_region: loc_tag.region, - tag_union_region: region, - replaced_region, - tag_name: new_name, - }); - } - } - - tag_types -} - -enum TypeApply { - Apply(Symbol, PoolVec), - Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), - Erroneous(roc_types::types::Problem), -} - -#[inline(always)] -fn to_type_apply<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - module_name: &str, - ident: &str, - type_arguments: &[Located>], - region: Region, -) -> TypeApply { - let symbol = if module_name.is_empty() { - // Since module_name was empty, this is an unqualified type. - // Look it up in scope! - let ident: Ident = (*ident).into(); - - match scope.lookup(&ident, region) { - Ok(symbol) => symbol, - Err(problem) => { - env.problem(roc_problem::can::Problem::RuntimeError(problem)); - - return TypeApply::Erroneous(Problem::UnrecognizedIdent(ident.into())); - } - } - } else { - match env.qualified_lookup(module_name, ident, region) { - Ok(symbol) => symbol, - Err(problem) => { - // Either the module wasn't imported, or - // it was imported but it doesn't expose this ident. - env.problem(roc_problem::can::Problem::RuntimeError(problem)); - - return TypeApply::Erroneous(Problem::UnrecognizedIdent((*ident).into())); - } - } - }; - - let argument_type_ids = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); - - for (type_id, loc_arg) in argument_type_ids.iter_node_ids().zip(type_arguments.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let args = type_arguments; - let opt_alias = scope.lookup_alias(symbol); - match opt_alias { - Some(ref alias) => { - // use a known alias - let actual = alias.actual; - let mut substitutions: MutMap = MutMap::default(); - - if alias.targs.len() != args.len() { - let error = TypeApply::Erroneous(Problem::BadTypeArguments { - symbol, - region, - alias_needs: alias.targs.len() as u8, - type_got: args.len() as u8, - }); - return error; - } - - let arguments = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); - - let it = arguments.iter_node_ids().zip( - argument_type_ids - .iter_node_ids() - .zip(alias.targs.iter_node_ids()), - ); - - for (node_id, (type_id, loc_var_id)) in it { - let loc_var = &env.pool[loc_var_id]; - let name = loc_var.0.shallow_clone(); - let var = loc_var.1; - - env.pool[node_id] = (name, type_id); - - substitutions.insert(var, type_id); - } - - // make sure the recursion variable is freshly instantiated - // have to allocate these outside of the if for lifetime reasons... - let new = env.var_store.fresh(); - let fresh = env.pool.add(Type2::Variable(new)); - if let Type2::RecursiveTagUnion(rvar, ref tags, ext) = &mut env.pool[actual] { - substitutions.insert(*rvar, fresh); - - env.pool[actual] = Type2::RecursiveTagUnion(new, tags.shallow_clone(), *ext); - } - - // make sure hidden variables are freshly instantiated - for var_id in alias.hidden_variables.iter_node_ids() { - let var = env.pool[var_id]; - let fresh = env.pool.add(Type2::Variable(env.var_store.fresh())); - substitutions.insert(var, fresh); - } - - // instantiate variables - Type2::substitute(env.pool, &substitutions, actual); - - TypeApply::Alias(symbol, arguments, actual) - } - None => TypeApply::Apply(symbol, argument_type_ids), - } -} - -#[derive(Debug)] -pub struct Alias { - pub targs: PoolVec<(PoolStr, Variable)>, - pub actual: TypeId, - - /// hidden type variables, like the closure variable in `a -> b` - pub hidden_variables: PoolVec, -} - -impl ShallowClone for Alias { - fn shallow_clone(&self) -> Self { - Self { - targs: self.targs.shallow_clone(), - hidden_variables: self.hidden_variables.shallow_clone(), - actual: self.actual, - } - } -} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f6f3053840..3e4b259885 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -10,7 +10,6 @@ extern crate pest_derive; mod editor; mod graphics; -pub mod lang; //TODO remove pub for unused warnings mod ui; mod window; diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs deleted file mode 100644 index 88f5e5248b..0000000000 --- a/editor/tests/solve_expr2.rs +++ /dev/null @@ -1,372 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -use bumpalo::Bump; -use roc_can::expected::Expected; -use roc_collections::all::MutMap; -use roc_editor::lang::solve; -use roc_editor::lang::{ - constrain::constrain_expr, - constrain::Constraint, - expr::{str_to_expr2, Env}, - pool::Pool, - scope::Scope, - types::Type2, -}; -use roc_module::ident::Lowercase; -use roc_module::symbol::Interns; -use roc_module::symbol::Symbol; -use roc_module::symbol::{IdentIds, ModuleIds}; -use roc_region::all::Region; -use roc_types::solved_types::Solved; -use roc_types::subs::{Subs, Variable}; -use roc_types::{pretty_print::content_to_string, subs::VarStore}; - -fn run_solve<'a>( - arena: &'a Bump, - mempool: &mut Pool, - aliases: MutMap, - rigid_variables: MutMap, - constraint: Constraint, - var_store: VarStore, -) -> (Solved, solve::Env, Vec) { - let env = solve::Env { - vars_by_symbol: MutMap::default(), - aliases, - }; - - let mut subs = Subs::new(var_store); - - for (var, name) in rigid_variables { - subs.rigid_var(var, name); - } - - // Now that the module is parsed, canonicalized, and constrained, - // we need to type check it. - let mut problems = Vec::new(); - - // Run the solver to populate Subs. - let (solved_subs, solved_env) = - solve::run(arena, mempool, &env, &mut problems, subs, &constraint); - - (solved_subs, solved_env, problems) -} - -fn infer_eq(actual: &str, expected_str: &str) { - let mut env_pool = Pool::with_capacity(1024); - let env_arena = Bump::new(); - let code_arena = Bump::new(); - - let mut var_store = VarStore::default(); - let var = var_store.fresh(); - let dep_idents = IdentIds::exposed_builtins(8); - let exposed_ident_ids = IdentIds::default(); - let mut module_ids = ModuleIds::default(); - let mod_id = module_ids.get_or_insert(&"ModId123".into()); - - let mut env = Env::new( - mod_id, - &env_arena, - &mut env_pool, - &mut var_store, - dep_idents, - &module_ids, - exposed_ident_ids, - ); - - let mut scope = Scope::new(env.home, env.pool, env.var_store); - - let region = Region::zero(); - - let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); - - match expr2_result { - Ok((expr, _)) => { - let constraint = constrain_expr( - &code_arena, - &mut env, - &expr, - Expected::NoExpectation(Type2::Variable(var)), - Region::zero(), - ); - - let Env { - pool, - var_store: ref_var_store, - mut dep_idents, - .. - } = env; - - // extract the var_store out of the env again - let mut var_store = VarStore::default(); - std::mem::swap(ref_var_store, &mut var_store); - - let (mut solved, _, _) = run_solve( - &code_arena, - pool, - Default::default(), - Default::default(), - constraint, - var_store, - ); - - let subs = solved.inner_mut(); - - let content = subs.get_content_without_compacting(var); - - // Connect the ModuleId to it's IdentIds - dep_idents.insert(mod_id, env.ident_ids); - - let interns = Interns { - module_ids: env.module_ids.clone(), - all_ident_ids: dep_idents, - }; - - let actual_str = content_to_string(content, subs, mod_id, &interns); - - assert_eq!(actual_str, expected_str); - } - Err(e) => panic!("syntax error {:?}", e), - } -} - -#[test] -fn constrain_str() { - infer_eq( - indoc!( - r#" - "type inference!" - "# - ), - "Str", - ) -} - -// This will be more useful once we actually map -// strings less than 15 chars to SmallStr -#[test] -fn constrain_small_str() { - infer_eq( - indoc!( - r#" - "a" - "# - ), - "Str", - ) -} - -#[test] -fn constrain_empty_record() { - infer_eq( - indoc!( - r#" - {} - "# - ), - "{}", - ) -} - -#[test] -fn constrain_small_int() { - infer_eq( - indoc!( - r#" - 12 - "# - ), - "Num *", - ) -} - -#[test] -fn constrain_float() { - infer_eq( - indoc!( - r#" - 3.14 - "# - ), - "Float *", - ) -} - -#[test] -fn constrain_record() { - infer_eq( - indoc!( - r#" - { x : 1, y : "hi" } - "# - ), - "{ x : Num *, y : Str }", - ) -} - -#[test] -fn constrain_empty_list() { - infer_eq( - indoc!( - r#" - [] - "# - ), - "List *", - ) -} - -#[test] -fn constrain_list() { - infer_eq( - indoc!( - r#" - [ 1, 2 ] - "# - ), - "List (Num *)", - ) -} - -#[test] -fn constrain_list_of_records() { - infer_eq( - indoc!( - r#" - [ { x: 1 }, { x: 3 } ] - "# - ), - "List { x : Num * }", - ) -} - -#[test] -fn constrain_global_tag() { - infer_eq( - indoc!( - r#" - Foo - "# - ), - "[ Foo ]*", - ) -} - -#[test] -fn constrain_private_tag() { - infer_eq( - indoc!( - r#" - @Foo - "# - ), - "[ @Foo ]*", - ) -} - -#[test] -fn constrain_call_and_accessor() { - infer_eq( - indoc!( - r#" - .foo { foo: "bar" } - "# - ), - "Str", - ) -} - -#[test] -fn constrain_access() { - infer_eq( - indoc!( - r#" - { foo: "bar" }.foo - "# - ), - "Str", - ) -} - -#[test] -fn constrain_if() { - infer_eq( - indoc!( - r#" - if True then Green else Red - "# - ), - "[ Green, Red ]*", - ) -} - -#[test] -fn constrain_when() { - infer_eq( - indoc!( - r#" - when if True then Green else Red is - Green -> Blue - Red -> Purple - "# - ), - "[ Blue, Purple ]*", - ) -} - -#[test] -fn constrain_let_value() { - infer_eq( - indoc!( - r#" - person = { name: "roc" } - - person - "# - ), - "{ name : Str }", - ) -} - -#[test] -fn constrain_update() { - infer_eq( - indoc!( - r#" - person = { name: "roc" } - - { person & name: "bird" } - "# - ), - "{ name : Str }", - ) -} - -#[ignore = "TODO: implement builtins in the editor"] -#[test] -fn constrain_run_low_level() { - infer_eq( - indoc!( - r#" - List.map [ { name: "roc" }, { name: "bird" } ] .name - "# - ), - "List Str", - ) -} - -#[test] -fn constrain_closure() { - infer_eq( - indoc!( - r#" - x = 1 - - \{} -> x - "# - ), - "{}* -> Num *", - ) -} From 4c2de0215ab24da53862f46248232c699fdafe8f Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 18:38:04 +0200 Subject: [PATCH 06/12] tests working --- ast/src/ast_error.rs | 4 +- ast/src/canonicalize/canonicalize.rs | 36 ++++-- ast/src/canonicalize/mod.rs | 2 +- ast/src/canonicalize/module.rs | 2 +- ast/src/constrain.rs | 138 ++++++++++++++-------- ast/src/lang/core/ast.rs | 13 +- ast/src/lang/core/declaration.rs | 14 ++- ast/src/lang/core/def/def.rs | 16 ++- ast/src/lang/core/def/def2.rs | 11 +- ast/src/lang/core/def/def_to_def2.rs | 11 +- ast/src/lang/core/def/mod.rs | 4 +- ast/src/lang/core/expr/expr2.rs | 9 +- ast/src/lang/core/expr/expr2_to_string.rs | 9 +- ast/src/lang/core/expr/expr_to_expr2.rs | 33 +++--- ast/src/lang/core/expr/introduced_vars.rs | 11 +- ast/src/lang/core/expr/mod.rs | 4 +- ast/src/lang/core/expr/output.rs | 6 +- ast/src/lang/core/expr/record_field.rs | 7 +- ast/src/lang/core/fun_def.rs | 15 ++- ast/src/lang/core/header.rs | 4 +- ast/src/lang/core/mod.rs | 10 +- ast/src/lang/core/pattern.rs | 6 +- ast/src/lang/core/str.rs | 15 ++- ast/src/lang/core/val_def.rs | 18 ++- ast/src/lang/env.rs | 4 +- ast/src/lang/mod.rs | 4 +- ast/src/lang/rigids.rs | 7 +- ast/src/lib.rs | 12 +- ast/src/parse/mod.rs | 2 +- ast/src/parse/parse_ast.rs | 14 ++- ast/src/parse/parse_expr.rs | 1 + ast/src/parse/parse_header.rs | 3 +- ast/src/pool/mod.rs | 2 +- ast/src/pool/pool.rs | 10 +- ast/src/pool/pool_str.rs | 6 +- ast/src/pool/pool_vec.rs | 9 +- ast/src/pool/shallow_clone.rs | 1 - ast/src/solve_type.rs | 1 - code_markup/src/lib.rs | 2 +- code_markup/src/markup/attribute.rs | 2 +- code_markup/src/markup/common_nodes.rs | 1 - code_markup/src/markup/nodes.rs | 37 +++++- code_markup/src/markup/top_level_def.rs | 20 +++- code_markup/src/markup_error.rs | 5 +- code_markup/src/slow_pool.rs | 1 - code_markup/src/syntax_highlight.rs | 3 +- editor/src/editor/ed_error.rs | 5 +- editor/src/editor/grid_node_map.rs | 2 +- editor/src/editor/mvc/ed_model.rs | 18 ++- editor/src/editor/mvc/ed_update.rs | 4 +- editor/src/editor/mvc/int_update.rs | 3 +- editor/src/editor/mvc/list_update.rs | 6 +- editor/src/editor/mvc/tld_value_update.rs | 27 ++++- editor/src/editor/render_ast.rs | 3 +- editor/src/editor/theme.rs | 2 +- editor/src/lib.rs | 1 - utils/src/lib.rs | 3 +- utils/src/util_error.rs | 3 +- 58 files changed, 394 insertions(+), 228 deletions(-) diff --git a/ast/src/ast_error.rs b/ast/src/ast_error.rs index cf42c7762d..6fb8546212 100644 --- a/ast/src/ast_error.rs +++ b/ast/src/ast_error.rs @@ -1,5 +1,3 @@ - - use snafu::{Backtrace, Snafu}; use crate::lang::core::ast::ASTNodeId; @@ -37,4 +35,4 @@ pub enum ASTError { }, } -pub type ASTResult = std::result::Result; \ No newline at end of file +pub type ASTResult = std::result::Result; diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index 459fb6c7d1..b3adc2ab1d 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -1,13 +1,28 @@ - - use roc_collections::all::MutMap; -use roc_problem::can::{Problem}; +use roc_problem::can::Problem; use roc_region::all::{Located, Region}; -use roc_types::{subs::Variable}; +use roc_types::subs::Variable; -use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, WhenBranch}, expr_to_expr2::to_expr2, output::Output, record_field::RecordField}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::{ + core::{ + def::def::References, + expr::{ + expr2::{Expr2, ExprId, WhenBranch}, + expr_to_expr2::to_expr2, + output::Output, + record_field::RecordField, + }, + pattern::to_pattern2, + }, + env::Env, + scope::Scope, + }, + pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; -pub (crate) enum CanonicalizeRecordProblem { +pub(crate) enum CanonicalizeRecordProblem { + #[allow(dead_code)] InvalidOptionalValue { field_name: PoolStr, field_region: Region, @@ -20,7 +35,7 @@ enum FieldVar { OnlyVar(Variable), } -pub (crate) fn canonicalize_fields<'a>( +pub(crate) fn canonicalize_fields<'a>( env: &mut Env<'a>, scope: &mut Scope, fields: &'a [Located>>], @@ -102,6 +117,7 @@ pub (crate) fn canonicalize_fields<'a>( Ok((pool_vec, output)) } +#[allow(dead_code)] enum CanonicalizeFieldProblem { InvalidOptionalValue { field_name: PoolStr, @@ -167,7 +183,7 @@ fn canonicalize_field<'a>( } #[inline(always)] -pub (crate) fn canonicalize_when_branch<'a>( +pub(crate) fn canonicalize_when_branch<'a>( env: &mut Env<'a>, scope: &mut Scope, branch: &'a roc_parse::ast::WhenBranch<'a>, @@ -239,7 +255,7 @@ pub (crate) fn canonicalize_when_branch<'a>( ) } -pub (crate) fn canonicalize_lookup( +pub(crate) fn canonicalize_lookup( env: &mut Env<'_>, scope: &mut Scope, module_name: &str, @@ -286,4 +302,4 @@ pub (crate) fn canonicalize_lookup( // If it's valid, this ident should be in scope already. (can_expr, output) -} \ No newline at end of file +} diff --git a/ast/src/canonicalize/mod.rs b/ast/src/canonicalize/mod.rs index 45b2084adf..fdbc754fc0 100644 --- a/ast/src/canonicalize/mod.rs +++ b/ast/src/canonicalize/mod.rs @@ -1,2 +1,2 @@ pub mod canonicalize; -pub mod module; \ No newline at end of file +pub mod module; diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalize/module.rs index 0c1ad7b089..ace177df8b 100644 --- a/ast/src/canonicalize/module.rs +++ b/ast/src/canonicalize/module.rs @@ -14,6 +14,7 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; +use crate::lang::core::def::def::canonicalize_defs; use crate::lang::core::def::def::Def; use crate::lang::core::def::def::{sort_can_defs, Declaration}; use crate::lang::core::expr::expr2::Expr2; @@ -27,7 +28,6 @@ use crate::pool::pool::NodeId; use crate::pool::pool::Pool; use crate::pool::pool_vec::PoolVec; use crate::pool::shallow_clone::ShallowClone; -use crate::lang::core::def::def::canonicalize_defs; pub struct ModuleOutput { pub aliases: MutMap>, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 48a1d0cb8c..ca6b1a9a8d 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -13,7 +13,21 @@ use roc_types::{ types::{Category, Reason}, }; -use crate::{lang::{core::{expr::{expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, record_field::RecordField}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::{ + core::{ + expr::{ + expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, + record_field::RecordField, + }, + pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, + types::{Type2, TypeId}, + val_def::ValueDef, + }, + env::Env, + }, + pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; #[derive(Debug)] pub enum Constraint<'a> { @@ -1739,19 +1753,39 @@ fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { ) } +#[cfg(test)] pub mod test_constrain { use bumpalo::Bump; use roc_can::expected::Expected; use roc_collections::all::MutMap; - use roc_module::{ident::Lowercase, symbol::{IdentIds, Interns, ModuleIds, Symbol}}; + use roc_module::{ + ident::Lowercase, + symbol::{IdentIds, Interns, ModuleIds, Symbol}, + }; + use roc_parse::parser::SyntaxError; use roc_region::all::Region; - use roc_types::{pretty_print::content_to_string, solved_types::Solved, subs::{Subs, VarStore, Variable}}; + use roc_types::{ + pretty_print::content_to_string, + solved_types::Solved, + subs::{Subs, VarStore, Variable}, + }; - use crate::{constrain::constrain_expr, lang::{core::{expr::expr_to_expr2::str_to_expr2, types::Type2}, env::Env, scope::Scope}, pool::pool::Pool, solve_type}; use super::Constraint; + use crate::{ + constrain::constrain_expr, + lang::{ + core::{ + expr::{expr2::Expr2, expr_to_expr2::loc_expr_to_expr2, output::Output}, + types::Type2, + }, + env::Env, + scope::Scope, + }, + pool::pool::Pool, + solve_type, + }; use indoc::indoc; - fn run_solve<'a>( arena: &'a Bump, mempool: &mut Pool, @@ -1764,36 +1798,36 @@ pub mod test_constrain { vars_by_symbol: MutMap::default(), aliases, }; - + let mut subs = Subs::new(var_store); - + for (var, name) in rigid_variables { subs.rigid_var(var, name); } - + // Now that the module is parsed, canonicalized, and constrained, // we need to type check it. let mut problems = Vec::new(); - + // Run the solver to populate Subs. let (solved_subs, solved_env) = solve_type::run(arena, mempool, &env, &mut problems, subs, &constraint); - + (solved_subs, solved_env, problems) } - + fn infer_eq(actual: &str, expected_str: &str) { let mut env_pool = Pool::with_capacity(1024); let env_arena = Bump::new(); let code_arena = Bump::new(); - + let mut var_store = VarStore::default(); let var = var_store.fresh(); let dep_idents = IdentIds::exposed_builtins(8); let exposed_ident_ids = IdentIds::default(); let mut module_ids = ModuleIds::default(); let mod_id = module_ids.get_or_insert(&"ModId123".into()); - + let mut env = Env::new( mod_id, &env_arena, @@ -1803,13 +1837,13 @@ pub mod test_constrain { &module_ids, exposed_ident_ids, ); - + let mut scope = Scope::new(env.home, env.pool, env.var_store); - + let region = Region::zero(); - + let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); - + match expr2_result { Ok((expr, _)) => { let constraint = constrain_expr( @@ -1819,18 +1853,18 @@ pub mod test_constrain { Expected::NoExpectation(Type2::Variable(var)), Region::zero(), ); - + let Env { pool, var_store: ref_var_store, mut dep_idents, .. } = env; - + // extract the var_store out of the env again let mut var_store = VarStore::default(); std::mem::swap(ref_var_store, &mut var_store); - + let (mut solved, _, _) = run_solve( &code_arena, pool, @@ -1839,27 +1873,40 @@ pub mod test_constrain { constraint, var_store, ); - + let subs = solved.inner_mut(); - + let content = subs.get_content_without_compacting(var); - + // Connect the ModuleId to it's IdentIds dep_idents.insert(mod_id, env.ident_ids); - + let interns = Interns { module_ids: env.module_ids.clone(), all_ident_ids: dep_idents, }; - + let actual_str = content_to_string(content, subs, mod_id, &interns); - + assert_eq!(actual_str, expected_str); } Err(e) => panic!("syntax error {:?}", e), } } - + + pub fn str_to_expr2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, + ) -> Result<(Expr2, Output), SyntaxError<'a>> { + match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { + Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), + Err(fail) => Err(fail), + } + } + #[test] fn constrain_str() { infer_eq( @@ -1871,7 +1918,7 @@ pub mod test_constrain { "Str", ) } - + // This will be more useful once we actually map // strings less than 15 chars to SmallStr #[test] @@ -1885,7 +1932,7 @@ pub mod test_constrain { "Str", ) } - + #[test] fn constrain_empty_record() { infer_eq( @@ -1897,7 +1944,7 @@ pub mod test_constrain { "{}", ) } - + #[test] fn constrain_small_int() { infer_eq( @@ -1909,7 +1956,7 @@ pub mod test_constrain { "Num *", ) } - + #[test] fn constrain_float() { infer_eq( @@ -1921,7 +1968,7 @@ pub mod test_constrain { "Float *", ) } - + #[test] fn constrain_record() { infer_eq( @@ -1933,7 +1980,7 @@ pub mod test_constrain { "{ x : Num *, y : Str }", ) } - + #[test] fn constrain_empty_list() { infer_eq( @@ -1945,7 +1992,7 @@ pub mod test_constrain { "List *", ) } - + #[test] fn constrain_list() { infer_eq( @@ -1957,7 +2004,7 @@ pub mod test_constrain { "List (Num *)", ) } - + #[test] fn constrain_list_of_records() { infer_eq( @@ -1969,7 +2016,7 @@ pub mod test_constrain { "List { x : Num * }", ) } - + #[test] fn constrain_global_tag() { infer_eq( @@ -1981,7 +2028,7 @@ pub mod test_constrain { "[ Foo ]*", ) } - + #[test] fn constrain_private_tag() { infer_eq( @@ -1993,7 +2040,7 @@ pub mod test_constrain { "[ @Foo ]*", ) } - + #[test] fn constrain_call_and_accessor() { infer_eq( @@ -2005,7 +2052,7 @@ pub mod test_constrain { "Str", ) } - + #[test] fn constrain_access() { infer_eq( @@ -2017,7 +2064,7 @@ pub mod test_constrain { "Str", ) } - + #[test] fn constrain_if() { infer_eq( @@ -2029,7 +2076,7 @@ pub mod test_constrain { "[ Green, Red ]*", ) } - + #[test] fn constrain_when() { infer_eq( @@ -2043,7 +2090,7 @@ pub mod test_constrain { "[ Blue, Purple ]*", ) } - + #[test] fn constrain_let_value() { infer_eq( @@ -2057,7 +2104,7 @@ pub mod test_constrain { "{ name : Str }", ) } - + #[test] fn constrain_update() { infer_eq( @@ -2071,7 +2118,7 @@ pub mod test_constrain { "{ name : Str }", ) } - + #[ignore = "TODO: implement builtins in the editor"] #[test] fn constrain_run_low_level() { @@ -2084,7 +2131,7 @@ pub mod test_constrain { "List Str", ) } - + #[test] fn constrain_closure() { infer_eq( @@ -2098,5 +2145,4 @@ pub mod test_constrain { "{}* -> Num *", ) } - } diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs index 12241c567b..d337d91e6f 100644 --- a/ast/src/lang/core/ast.rs +++ b/ast/src/lang/core/ast.rs @@ -1,6 +1,13 @@ -use crate::{ast_error::{ASTResult, ASTNodeIdWithoutExprId}, pool::pool::Pool}; +use crate::{ + ast_error::{ASTNodeIdWithoutExprId, ASTResult}, + pool::pool::Pool, +}; -use super::{def::def2::{DefId, def2_to_string}, expr::{expr2::ExprId, expr2_to_string::expr2_to_string}, header::AppHeader}; +use super::{ + def::def2::{def2_to_string, DefId}, + expr::{expr2::ExprId, expr2_to_string::expr2_to_string}, + header::AppHeader, +}; #[derive(Debug)] pub struct AST { @@ -35,4 +42,4 @@ pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/declaration.rs b/ast/src/lang/core/declaration.rs index 207886bece..0c9ca03bfe 100644 --- a/ast/src/lang/core/declaration.rs +++ b/ast/src/lang/core/declaration.rs @@ -1,10 +1,18 @@ use roc_types::subs::VarStore; -use crate::{lang::core::{def::def::Def, expr::expr2::Expr2}, pool::{pool::Pool, pool_vec::PoolVec}}; +use crate::{ + lang::core::{def::def::Def, expr::expr2::Expr2}, + pool::{pool::Pool, pool_vec::PoolVec}, +}; use super::def::def::Declaration; -pub(crate) fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { +pub(crate) fn decl_to_let( + pool: &mut Pool, + var_store: &mut VarStore, + decl: Declaration, + ret: Expr2, +) -> Expr2 { match decl { Declaration::Declare(def) => match def { Def::AnnotationOnly { .. } => todo!(), @@ -59,4 +67,4 @@ pub(crate) fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Decla unreachable!() } } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 02c9e94ff9..3bcd60f670 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -24,7 +24,21 @@ use std::collections::HashMap; use std::fmt::Debug; use ven_graph::{strongly_connected_components, topological_sort_into_groups}; -use crate::{lang::{core::{expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output}, fun_def::FunctionDef, pattern::{self, Pattern2, PatternId, symbols_from_pattern, to_pattern_id}, types::{Alias, Annotation2, Signature, Type2, TypeId, to_annotation2}, val_def::ValueDef}, env::Env, rigids::Rigids, scope::Scope}, pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::{ + core::{ + expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output}, + fun_def::FunctionDef, + pattern::{self, symbols_from_pattern, to_pattern_id, Pattern2, PatternId}, + types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId}, + val_def::ValueDef, + }, + env::Env, + rigids::Rigids, + scope::Scope, + }, + pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; #[derive(Debug)] pub enum Def { diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs index 935bfe6609..85cd2b9a64 100644 --- a/ast/src/lang/core/def/def2.rs +++ b/ast/src/lang/core/def/def2.rs @@ -1,5 +1,10 @@ -use crate::{lang::core::{expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, pattern::Pattern2}, pool::pool::{NodeId, Pool}}; - +use crate::{ + lang::core::{ + expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, + pattern::Pattern2, + }, + pool::pool::{NodeId, Pool}, +}; // A top level definition, not inside a function. For example: `main = "Hello, world!"` #[derive(Debug)] @@ -35,4 +40,4 @@ pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { } full_string -} \ No newline at end of file +} diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs index b35b7e3890..05cf2747a6 100644 --- a/ast/src/lang/core/def/def_to_def2.rs +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -1,9 +1,13 @@ -use bumpalo::Bump; use bumpalo::collections::Vec as BumpVec; +use bumpalo::Bump; use roc_parse::{parser::SyntaxError, pattern::PatternType}; use roc_region::all::Region; -use crate::lang::{core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, env::Env, scope::Scope}; +use crate::lang::{ + core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, + env::Env, + scope::Scope, +}; use super::def2::Def2; @@ -14,7 +18,6 @@ pub fn defs_to_defs2<'a>( parsed_defs: &'a BumpVec>>, region: Region, ) -> Vec { - parsed_defs .iter() .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) @@ -91,4 +94,4 @@ pub fn str_to_def2<'a>( )), Err(fail) => Err(fail), } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/def/mod.rs b/ast/src/lang/core/def/mod.rs index d790ec3ea5..7ab541b811 100644 --- a/ast/src/lang/core/def/mod.rs +++ b/ast/src/lang/core/def/mod.rs @@ -1,3 +1,3 @@ -pub mod def_to_def2; pub mod def; -pub mod def2; \ No newline at end of file +pub mod def2; +pub mod def_to_def2; diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 83e263b3d2..8b2ad1b84b 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -1,11 +1,14 @@ use arraystring::{typenum::U30, ArrayString}; use roc_types::subs::Variable; -use crate::{lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{ + lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, + pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, +}; use roc_can::expr::Recursive; -use roc_module::symbol::Symbol; use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; +use roc_module::symbol::Symbol; use super::record_field::RecordField; @@ -227,4 +230,4 @@ pub struct ClosureExtra { pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B pub closure_type: Variable, // 4B pub closure_ext_var: Variable, // 4B -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs index b9ec8a234b..6f8255ccf6 100644 --- a/ast/src/lang/core/expr/expr2_to_string.rs +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -1,7 +1,10 @@ -use crate::{lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; +use crate::{ + lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, + pool::pool::Pool, +}; -use roc_types::subs::Variable; use super::expr2::{Expr2, ExprId}; +use roc_types::subs::Variable; pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { let mut full_string = String::new(); @@ -133,4 +136,4 @@ fn expr2_to_string_helper( fn var_to_string(some_var: &Variable, indent_level: usize) -> String { format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 4e277d590c..861ee2d49d 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -3,34 +3,29 @@ use roc_can::expr::Recursive; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::operator::desugar_expr; use roc_collections::all::MutSet; -use roc_parse::parser::SyntaxError; +use roc_module::symbol::Symbol; use roc_parse::{ast::Expr, pattern::PatternType}; use roc_problem::can::{Problem, RuntimeError}; -use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; +use super::{expr2::Expr2, output::Output}; +use crate::canonicalize::canonicalize::{ + canonicalize_fields, canonicalize_lookup, canonicalize_when_branch, CanonicalizeRecordProblem, +}; use crate::lang::core::declaration::decl_to_let; use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs}; use crate::lang::core::expr::expr2::ClosureExtra; use crate::lang::core::pattern::to_pattern2; use crate::lang::core::str::flatten_str_literal; use crate::pool::shallow_clone::ShallowClone; -use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; -use crate::canonicalize::canonicalize::{CanonicalizeRecordProblem, canonicalize_fields, canonicalize_lookup, canonicalize_when_branch}; -use super::{expr2::Expr2, output::Output}; - -pub fn str_to_expr2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result<(Expr2, self::Output), SyntaxError<'a>> { - match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), - Err(fail) => Err(fail), - } -} +use crate::{ + lang::{ + core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, + env::Env, + scope::Scope, + }, + pool::{pool_str::PoolStr, pool_vec::PoolVec}, +}; pub fn loc_expr_to_expr2<'a>( arena: &'a Bump, @@ -712,4 +707,4 @@ pub fn to_expr_id<'a>( let (expr, output) = to_expr2(env, scope, parse_expr, region); (env.add(expr, region), output) -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/introduced_vars.rs b/ast/src/lang/core/expr/introduced_vars.rs index 7a8db4c3fa..0abb087815 100644 --- a/ast/src/lang/core/expr/introduced_vars.rs +++ b/ast/src/lang/core/expr/introduced_vars.rs @@ -1,8 +1,7 @@ - -use roc_types::subs::{Variable}; -use roc_collections::all::{MutMap}; -use roc_module::ident::{Lowercase}; -use roc_module::symbol::{Symbol}; +use roc_collections::all::MutMap; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_types::subs::Variable; #[derive(Clone, Debug, PartialEq, Default)] pub struct IntroducedVariables { @@ -49,4 +48,4 @@ impl IntroducedVariables { pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { self.name_by_var.get(&var) } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/mod.rs b/ast/src/lang/core/expr/mod.rs index 1a58685fc2..32d768c4c9 100644 --- a/ast/src/lang/core/expr/mod.rs +++ b/ast/src/lang/core/expr/mod.rs @@ -1,6 +1,6 @@ pub mod expr2; pub mod expr2_to_string; -pub (crate) mod output; +pub(crate) mod expr_to_expr2; mod introduced_vars; -pub (crate) mod expr_to_expr2; +pub(crate) mod output; pub mod record_field; diff --git a/ast/src/lang/core/expr/output.rs b/ast/src/lang/core/expr/output.rs index 3c84e9a618..af7f455465 100644 --- a/ast/src/lang/core/expr/output.rs +++ b/ast/src/lang/core/expr/output.rs @@ -1,4 +1,7 @@ -use crate::{lang::core::{def::def::References, types::{Alias}}, pool::pool::NodeId}; +use crate::{ + lang::core::{def::def::References, types::Alias}, + pool::pool::NodeId, +}; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::Symbol; @@ -25,4 +28,3 @@ impl Output { self.non_closures.extend(other.non_closures); } } - \ No newline at end of file diff --git a/ast/src/lang/core/expr/record_field.rs b/ast/src/lang/core/expr/record_field.rs index 2db2fc6fe5..90c1fb6ed7 100644 --- a/ast/src/lang/core/expr/record_field.rs +++ b/ast/src/lang/core/expr/record_field.rs @@ -1,7 +1,6 @@ - use roc_types::subs::Variable; -use crate::{pool::{pool_str::PoolStr}}; +use crate::pool::pool_str::PoolStr; use roc_module::symbol::Symbol; use super::expr2::ExprId; @@ -16,8 +15,6 @@ pub enum RecordField { use RecordField::*; impl RecordField { - - pub fn get_record_field_var(&self) -> &Variable { match self { InvalidLabelOnly(_, var) => var, @@ -49,4 +46,4 @@ impl RecordField { LabeledValue(_, _, field_val_id) => Some(*field_val_id), } } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs index ea67f75082..773e6ecd66 100644 --- a/ast/src/lang/core/fun_def.rs +++ b/ast/src/lang/core/fun_def.rs @@ -1,10 +1,15 @@ - - -use crate::{lang::rigids::Rigids, pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::rigids::Rigids, + pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; use roc_module::symbol::Symbol; use roc_types::subs::Variable; -use super::{expr::expr2::ExprId, pattern::PatternId, types::{Type2, TypeId}}; +use super::{ + expr::expr2::ExprId, + pattern::PatternId, + types::{Type2, TypeId}, +}; #[derive(Debug)] pub enum FunctionDef { @@ -53,4 +58,4 @@ impl ShallowClone for FunctionDef { }, } } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/header.rs b/ast/src/lang/core/header.rs index 60c359af0e..4b10dad537 100644 --- a/ast/src/lang/core/header.rs +++ b/ast/src/lang/core/header.rs @@ -1,7 +1,5 @@ - use super::expr::expr2::ExprId; - #[derive(Debug)] pub struct AppHeader { pub app_name: String, @@ -9,4 +7,4 @@ pub struct AppHeader { pub imports: Vec, pub provides: Vec, pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId -} \ No newline at end of file +} diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs index 41e852d20f..74300dab4f 100644 --- a/ast/src/lang/core/mod.rs +++ b/ast/src/lang/core/mod.rs @@ -1,10 +1,10 @@ +pub mod ast; +mod declaration; pub mod def; pub mod expr; -pub mod header; -pub mod ast; -pub mod val_def; mod fun_def; +pub mod header; pub mod pattern; -pub mod types; pub mod str; -mod declaration; \ No newline at end of file +pub mod types; +pub mod val_def; diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index ddab1bef5a..230812285e 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -13,6 +13,7 @@ use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::Region; use roc_types::subs::Variable; +use crate::ast_error::{ASTResult, UnexpectedPattern2Variant}; use crate::constrain::Constraint; use crate::lang::core::expr::expr_to_expr2::to_expr_id; use crate::lang::env::Env; @@ -21,7 +22,6 @@ use crate::pool::pool::{NodeId, Pool}; use crate::pool::pool_str::PoolStr; use crate::pool::pool_vec::PoolVec; use crate::pool::shallow_clone::ShallowClone; -use crate::ast_error::{ASTResult, UnexpectedPattern2Variant}; use super::expr::expr2::{ExprId, FloatVal, IntVal}; use super::expr::output::Output; @@ -576,7 +576,7 @@ fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { Pattern2::UnsupportedPattern(region) } -pub (crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { +pub(crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { use roc_parse::ast::StrLiteral::*; match literal { @@ -586,7 +586,7 @@ pub (crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> } } -pub (crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { +pub(crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { use StrSegment::*; let mut buf = String::new(); diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 5e94ad76e3..69d2cca7d2 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -1,11 +1,18 @@ use roc_module::{operator::CalledVia, symbol::Symbol}; use roc_parse::ast::StrLiteral; -use crate::{ast_error::{ASTResult, UnexpectedASTNode}, lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{ + ast_error::{ASTResult, UnexpectedASTNode}, + lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, + pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}, +}; -use super::expr::{expr2::{Expr2, ExprId}, output::Output}; +use super::expr::{ + expr2::{Expr2, ExprId}, + output::Output, +}; -pub (crate) fn flatten_str_literal<'a>( +pub(crate) fn flatten_str_literal<'a>( env: &mut Env<'a>, scope: &mut Scope, literal: &StrLiteral<'a>, @@ -218,4 +225,4 @@ pub fn update_str_expr( } Ok(()) -} \ No newline at end of file +} diff --git a/ast/src/lang/core/val_def.rs b/ast/src/lang/core/val_def.rs index c97df3f810..2b97221c77 100644 --- a/ast/src/lang/core/val_def.rs +++ b/ast/src/lang/core/val_def.rs @@ -1,7 +1,17 @@ -use crate::{lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, pool::{pool::{NodeId, Pool}, shallow_clone::ShallowClone}}; -use roc_types::subs::{Variable}; +use crate::{ + lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, + pool::{ + pool::{NodeId, Pool}, + shallow_clone::ShallowClone, + }, +}; +use roc_types::subs::Variable; -use super::{expr::expr2::ExprId, pattern::{Pattern2, PatternId}, types::TypeId}; +use super::{ + expr::expr2::ExprId, + pattern::{Pattern2, PatternId}, + types::TypeId, +}; #[derive(Debug)] pub enum ValueDef { @@ -88,4 +98,4 @@ pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { ) } } -} \ No newline at end of file +} diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index 5a6d212fcf..c3ee64edc3 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -4,7 +4,7 @@ use roc_module::ident::{Ident, ModuleName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore}; +use roc_types::subs::VarStore; use crate::pool::pool::{NodeId, Pool}; @@ -165,4 +165,4 @@ impl<'a> Env<'a> { }), } } -} \ No newline at end of file +} diff --git a/ast/src/lang/mod.rs b/ast/src/lang/mod.rs index b6a8753591..fa21ea740d 100644 --- a/ast/src/lang/mod.rs +++ b/ast/src/lang/mod.rs @@ -1,4 +1,4 @@ pub mod core; -pub mod scope; -mod rigids; pub mod env; +mod rigids; +pub mod scope; diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index 94bca10038..be43173714 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -1,8 +1,11 @@ -use std::{collections::{HashMap, HashSet}, hash::BuildHasherDefault}; +use std::{ + collections::{HashMap, HashSet}, + hash::BuildHasherDefault, +}; use crate::pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}; use roc_collections::all::WyHash; -use roc_types::subs::{Variable}; +use roc_types::subs::Variable; #[derive(Debug)] pub struct Rigids { diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 55191171a8..e7f03db59d 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -1,7 +1,7 @@ -pub mod lang; -pub mod pool; -pub mod parse; -pub mod constrain; -mod canonicalize; pub mod ast_error; -pub mod solve_type; \ No newline at end of file +mod canonicalize; +pub mod constrain; +pub mod lang; +pub mod parse; +pub mod pool; +pub mod solve_type; diff --git a/ast/src/parse/mod.rs b/ast/src/parse/mod.rs index 095e190b43..f14f61f5a0 100644 --- a/ast/src/parse/mod.rs +++ b/ast/src/parse/mod.rs @@ -1,3 +1,3 @@ pub mod parse_ast; pub mod parse_expr; -pub mod parse_header; \ No newline at end of file +pub mod parse_header; diff --git a/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs index 55ea683966..3ae3cca0d7 100644 --- a/ast/src/parse/parse_ast.rs +++ b/ast/src/parse/parse_ast.rs @@ -2,12 +2,18 @@ use bumpalo::Bump; use roc_parse::parser::SyntaxError; use roc_region::all::Region; -use crate::lang::{core::{ast::AST, def::{def2::DefId, def_to_def2::str_to_def2}, expr::expr2::Expr2}, env::Env, scope::Scope}; +use crate::lang::{ + core::{ + ast::AST, + def::{def2::DefId, def_to_def2::str_to_def2}, + expr::expr2::Expr2, + }, + env::Env, + scope::Scope, +}; use super::parse_header; - - pub fn parse_from_string<'a>( code_str: &'a str, env: &mut Env<'a>, @@ -39,4 +45,4 @@ pub fn parse_from_string<'a>( header: parse_header::parse_from_string(header_str, ast_node_id), def_ids, }) -} \ No newline at end of file +} diff --git a/ast/src/parse/parse_expr.rs b/ast/src/parse/parse_expr.rs index e69de29bb2..8b13789179 100644 --- a/ast/src/parse/parse_expr.rs +++ b/ast/src/parse/parse_expr.rs @@ -0,0 +1 @@ + diff --git a/ast/src/parse/parse_header.rs b/ast/src/parse/parse_header.rs index 6287806656..e387fea026 100644 --- a/ast/src/parse/parse_header.rs +++ b/ast/src/parse/parse_header.rs @@ -1,6 +1,5 @@ use crate::lang::core::{expr::expr2::ExprId, header::AppHeader}; - // TODO don't use mock struct and actually parse string pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { AppHeader { @@ -10,4 +9,4 @@ pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { provides: vec!["main".to_owned()], ast_node_id, } -} \ No newline at end of file +} diff --git a/ast/src/pool/mod.rs b/ast/src/pool/mod.rs index 1b0cd90832..b6c8c83b8f 100644 --- a/ast/src/pool/mod.rs +++ b/ast/src/pool/mod.rs @@ -1,4 +1,4 @@ pub mod pool; pub mod pool_str; pub mod pool_vec; -pub mod shallow_clone; \ No newline at end of file +pub mod shallow_clone; diff --git a/ast/src/pool/pool.rs b/ast/src/pool/pool.rs index 97ec3ca7af..ab4ae5548d 100644 --- a/ast/src/pool/pool.rs +++ b/ast/src/pool/pool.rs @@ -61,8 +61,8 @@ pub const NODE_BYTES: usize = 32; #[derive(Debug, Eq)] pub struct NodeId { - pub (super) index: u32, - pub (super) _phantom: PhantomData, + pub(super) index: u32, + pub(super) _phantom: PhantomData, } impl Clone for NodeId { @@ -84,7 +84,7 @@ impl Copy for NodeId {} #[derive(Debug)] pub struct Pool { - pub (super) nodes: *mut [u8; NODE_BYTES], + pub(super) nodes: *mut [u8; NODE_BYTES], num_nodes: u32, capacity: u32, // free_1node_slots: Vec>, @@ -149,7 +149,7 @@ impl Pool { /// Reserves the given number of contiguous node slots, and returns /// the NodeId of the first one. We only allow reserving 2^32 in a row. - pub (super) fn reserve(&mut self, nodes: u32) -> NodeId { + pub(super) fn reserve(&mut self, nodes: u32) -> NodeId { // TODO once we have a free list, look in there for an open slot first! let index = self.num_nodes; @@ -225,4 +225,4 @@ impl Drop for Pool { ); } } -} \ No newline at end of file +} diff --git a/ast/src/pool/pool_str.rs b/ast/src/pool/pool_str.rs index 02a9d47603..435d4586bb 100644 --- a/ast/src/pool/pool_str.rs +++ b/ast/src/pool/pool_str.rs @@ -1,11 +1,9 @@ - use super::pool::{NodeId, Pool, NODE_BYTES}; use super::shallow_clone::ShallowClone; -use libc::{c_void}; +use libc::c_void; use std::marker::PhantomData; use std::mem::size_of; - /// A string containing at most 2^32 pool-allocated bytes. #[derive(Debug, Copy, Clone)] pub struct PoolStr { @@ -85,4 +83,4 @@ impl ShallowClone for PoolStr { len: self.len, } } -} \ No newline at end of file +} diff --git a/ast/src/pool/pool_vec.rs b/ast/src/pool/pool_vec.rs index 3da278e485..65c9e89b1b 100644 --- a/ast/src/pool/pool_vec.rs +++ b/ast/src/pool/pool_vec.rs @@ -1,11 +1,10 @@ - use super::pool::{NodeId, Pool, NODE_BYTES}; use super::shallow_clone::ShallowClone; -use libc::{c_void}; -use std::marker::PhantomData; -use std::mem::size_of; +use libc::c_void; use std::any::type_name; use std::cmp::Ordering; +use std::marker::PhantomData; +use std::mem::size_of; /// An array of at most 2^32 pool-allocated nodes. #[derive(Debug)] @@ -321,4 +320,4 @@ fn pool_vec_iter_test() { let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); assert_eq!(current_vec, expected_vec); -} \ No newline at end of file +} diff --git a/ast/src/pool/shallow_clone.rs b/ast/src/pool/shallow_clone.rs index cf7c24f8f0..f444b1f897 100644 --- a/ast/src/pool/shallow_clone.rs +++ b/ast/src/pool/shallow_clone.rs @@ -1,4 +1,3 @@ - use roc_can::expected::Expected; use roc_can::expected::PExpected; diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 7c89b3440a..986f867856 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -200,7 +200,6 @@ fn solve<'a>( subs: &mut Subs, constraint: &Constraint, ) -> State { - use crate::solve_type::Constraint::*; match constraint { diff --git a/code_markup/src/lib.rs b/code_markup/src/lib.rs index df6e4f24ed..9e5d220855 100644 --- a/code_markup/src/lib.rs +++ b/code_markup/src/lib.rs @@ -1,5 +1,5 @@ +pub mod colors; pub mod markup; pub mod markup_error; pub mod slow_pool; pub mod syntax_highlight; -pub mod colors; diff --git a/code_markup/src/markup/attribute.rs b/code_markup/src/markup/attribute.rs index 31a575d35c..0adb489659 100644 --- a/code_markup/src/markup/attribute.rs +++ b/code_markup/src/markup/attribute.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use snafu::ensure; -use crate::markup_error::{MarkResult, CaretNotFound}; +use crate::markup_error::{CaretNotFound, MarkResult}; #[derive(Debug, Copy, Clone)] pub struct Caret { diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs index c44000534e..6cdc31eda9 100644 --- a/code_markup/src/markup/common_nodes.rs +++ b/code_markup/src/markup/common_nodes.rs @@ -1,4 +1,3 @@ - use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId}; use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}; diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index 6a08432891..4fe4d5c954 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -1,6 +1,16 @@ -use crate::{markup::common_nodes::{new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn, new_right_square_mn}, markup_error::MarkResult, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; +use crate::{ + markup::common_nodes::{ + new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, + new_left_square_mn, new_right_accolade_mn, new_right_square_mn, + }, + markup_error::MarkResult, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; -use super::{attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node}; +use super::{ + attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node, +}; /*use crate::editor::ed_error::EdResult; use crate::editor::ed_error::ExpectedTextNode; use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; @@ -28,12 +38,29 @@ use crate::lang::parse::{AppHeader, AST}; use crate::lang::pattern::get_identifier_string; use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; use crate::ui::util::slice_get;*/ +use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired}; use bumpalo::Bump; -use roc_ast::{ast_error::ASTResult, lang::{core::{ast::{AST, ASTNodeId}, def::def2::{Def2, DefId}, expr::{expr2::{Expr2, ExprId}, record_field::RecordField}, header::AppHeader, pattern::get_identifier_string, val_def::ValueDef}, env::Env}, pool::pool_str::PoolStr}; -use roc_utils::{index_of, slice_get}; +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::{ASTNodeId, AST}, + def::def2::{Def2, DefId}, + expr::{ + expr2::{Expr2, ExprId}, + record_field::RecordField, + }, + header::AppHeader, + pattern::get_identifier_string, + val_def::ValueDef, + }, + env::Env, + }, + pool::pool_str::PoolStr, +}; use roc_module::symbol::Interns; +use roc_utils::{index_of, slice_get}; use std::fmt; -use crate::markup_error::{NestedNodeMissingChild, NestedNodeRequired, ExpectedTextNode}; #[derive(Debug)] pub enum MarkupNode { diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs index 78d086f3fe..5cadc131ac 100644 --- a/code_markup/src/markup/top_level_def.rs +++ b/code_markup/src/markup/top_level_def.rs @@ -1,8 +1,20 @@ -use roc_ast::{ast_error::ASTResult, lang::{core::{ast::ASTNodeId, pattern::{PatternId, get_identifier_string}}, env::Env}}; +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::ASTNodeId, + pattern::{get_identifier_string, PatternId}, + }, + env::Env, + }, +}; use roc_module::symbol::Interns; -use crate::{markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; - +use crate::{ + markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; pub fn tld_mark_node<'a>( identifier_id: PatternId, @@ -36,4 +48,4 @@ pub fn tld_mark_node<'a>( }; Ok(full_let_node) -} \ No newline at end of file +} diff --git a/code_markup/src/markup_error.rs b/code_markup/src/markup_error.rs index a3d3d2a680..5de9768fba 100644 --- a/code_markup/src/markup_error.rs +++ b/code_markup/src/markup_error.rs @@ -1,6 +1,5 @@ - use roc_utils::util_error::UtilError; -use snafu::{Backtrace, NoneError, Snafu, ResultExt}; +use snafu::{Backtrace, NoneError, ResultExt, Snafu}; use crate::slow_pool::MarkNodeId; @@ -53,4 +52,4 @@ impl From for MarkError { let dummy_res: Result<(), NoneError> = Err(NoneError {}); dummy_res.context(UtilErrorBacktrace { msg }).unwrap_err() } -} \ No newline at end of file +} diff --git a/code_markup/src/slow_pool.rs b/code_markup/src/slow_pool.rs index df8335b570..1f8a12c4af 100644 --- a/code_markup/src/slow_pool.rs +++ b/code_markup/src/slow_pool.rs @@ -1,4 +1,3 @@ - use std::fmt; use crate::markup::nodes::MarkupNode; diff --git a/code_markup/src/syntax_highlight.rs b/code_markup/src/syntax_highlight.rs index 2f5fcf6865..12c0ef33e1 100644 --- a/code_markup/src/syntax_highlight.rs +++ b/code_markup/src/syntax_highlight.rs @@ -1,8 +1,7 @@ - use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::colors::{self, RgbaTup, from_hsb}; +use crate::colors::{self, from_hsb, RgbaTup}; #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] pub enum HighlightStyle { diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index e79e90e23e..357693bb69 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,5 +1,4 @@ - -use crate::{ui::text::text_pos::TextPos}; +use crate::ui::text::text_pos::TextPos; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId; @@ -310,4 +309,4 @@ impl From for EdError { let dummy_res: Result<(), NoneError> = Err(NoneError {}); dummy_res.context(ASTErrorBacktrace { msg }).unwrap_err() } -} \ No newline at end of file +} diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index 38355aacc2..e342d624c1 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -12,9 +12,9 @@ use roc_ast::lang::core::ast::ASTNodeId; use roc_code_markup::markup::nodes::get_root_mark_node_id; use roc_code_markup::slow_pool::MarkNodeId; use roc_code_markup::slow_pool::SlowPool; +use snafu::OptionExt; use std::cmp::Ordering; use std::fmt; -use snafu::OptionExt; #[derive(Debug)] pub struct GridNodeMap { diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 4d42e4e12c..12eb4bb110 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -11,7 +11,7 @@ use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; use bumpalo::Bump; use nonempty::NonEmpty; -use roc_ast::lang::core::ast::{AST, ASTNodeId}; +use roc_ast::lang::core::ast::{ASTNodeId, AST}; use roc_ast::lang::env::Env; use roc_ast::parse::parse_ast; use roc_ast::pool::pool_str::PoolStr; @@ -62,15 +62,13 @@ pub fn init_model<'a>( let markup_ids = if code_str.is_empty() { EmptyCodeString {}.fail() } else { - Ok( - ast_to_mark_nodes( - code_arena, - &mut module.env, - &module.ast, - &mut mark_node_pool, - &loaded_module.interns, - )? - ) + Ok(ast_to_mark_nodes( + code_arena, + &mut module.env, + &module.ast, + &mut mark_node_pool, + &loaded_module.interns, + )?) }?; let mut code_lines = CodeLines::default(); diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 2e8c527d2c..ce0c894955 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -51,8 +51,8 @@ use crate::ui::util::path_to_string; use crate::ui::util::write_to_file; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; -use roc_ast::constrain::Constraint; use roc_ast::constrain::constrain_expr; +use roc_ast::constrain::Constraint; use roc_ast::lang::core::ast::ASTNodeId; use roc_ast::lang::core::def::def2::Def2; use roc_ast::lang::core::def::def2::DefId; @@ -65,8 +65,8 @@ use roc_ast::solve_type; use roc_can::expected::Expected; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::nodes; -use roc_code_markup::markup::nodes::EQUALS; use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::markup::nodes::EQUALS; use roc_code_markup::slow_pool::MarkNodeId; use roc_code_markup::slow_pool::SlowPool; use roc_code_markup::syntax_highlight::HighlightStyle; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index bee8f0623d..c2b63212f8 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -1,5 +1,5 @@ +use roc_ast::lang::core::expr::expr2::Expr2::SmallInt; use roc_ast::lang::core::expr::expr2::IntStyle; -use roc_ast::lang::core::expr::expr2::Expr2::{SmallInt}; use roc_ast::lang::core::expr::expr2::IntVal; use roc_ast::pool::pool_str::PoolStr; use roc_code_markup::markup::attribute::Attributes; @@ -146,7 +146,6 @@ pub fn update_int( } fn update_small_int_num(number: &mut IntVal, updated_str: &str) -> EdResult<()> { - use IntVal::*; *number = match number { diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 764b81892b..7036701b9d 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,7 +1,9 @@ -use roc_ast::lang::core::ast::{ASTNodeId, ast_node_to_string}; +use roc_ast::lang::core::ast::{ast_node_to_string, ASTNodeId}; use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; use roc_ast::pool::pool_vec::PoolVec; -use roc_code_markup::markup::common_nodes::{new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn}; +use roc_code_markup::markup::common_nodes::{ + new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, +}; use roc_code_markup::markup::nodes::{self, MarkupNode}; use roc_code_markup::slow_pool::MarkNodeId; diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index b07e1c7010..e7a8b3258d 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -1,11 +1,28 @@ -use roc_ast::{lang::{core::{ast::ASTNodeId, def::def2::Def2, expr::expr2::Expr2, pattern::{Pattern2, get_identifier_string}}, env::Env}, pool::pool::NodeId}; -use roc_code_markup::{markup::{attribute::Attributes, common_nodes::{new_blank_mn_w_nls, new_equals_mn}, nodes::{MarkupNode, set_parent_for_all}}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; +use roc_ast::{ + lang::{ + core::{ + ast::ASTNodeId, + def::def2::Def2, + expr::expr2::Expr2, + pattern::{get_identifier_string, Pattern2}, + }, + env::Env, + }, + pool::pool::NodeId, +}; +use roc_code_markup::{ + markup::{ + attribute::Attributes, + common_nodes::{new_blank_mn_w_nls, new_equals_mn}, + nodes::{set_parent_for_all, MarkupNode}, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; use roc_module::symbol::{Interns, Symbol}; use crate::{ - editor::{ - ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, - }, + editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, ui::text::text_pos::TextPos, }; diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index 651b55b47d..00d035a692 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,10 +1,9 @@ - use crate::editor::mvc::ed_view::RenderedWgpu; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text as gr_text; use cgmath::Vector2; -use roc_code_markup::markup::nodes::{BLANK_PLACEHOLDER, MarkupNode}; +use roc_code_markup::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use winit::dpi::PhysicalSize; diff --git a/editor/src/editor/theme.rs b/editor/src/editor/theme.rs index dcc3b437b1..30e45a47a9 100644 --- a/editor/src/editor/theme.rs +++ b/editor/src/editor/theme.rs @@ -1,5 +1,5 @@ use gr_colors::{from_hsb, RgbaTup}; -use roc_code_markup::syntax_highlight::{HighlightStyle, default_highlight_map}; +use roc_code_markup::syntax_highlight::{default_highlight_map, HighlightStyle}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 3e4b259885..5ee7ea9fd5 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -3,7 +3,6 @@ #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] #[cfg_attr(test, macro_use)] -extern crate indoc; extern crate pest; #[cfg_attr(test, macro_use)] extern crate pest_derive; diff --git a/utils/src/lib.rs b/utils/src/lib.rs index d9402be3e5..bbab75e59b 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,7 +1,6 @@ - use snafu::OptionExt; -use util_error::{UtilResult, KeyNotFound, IndexOfFailed, OutOfBounds}; use std::{collections::HashMap, slice::SliceIndex}; +use util_error::{IndexOfFailed, KeyNotFound, OutOfBounds, UtilResult}; pub mod util_error; diff --git a/utils/src/util_error.rs b/utils/src/util_error.rs index fda0c1a565..d19c230f11 100644 --- a/utils/src/util_error.rs +++ b/utils/src/util_error.rs @@ -1,4 +1,3 @@ - use snafu::{Backtrace, Snafu}; #[derive(Debug, Snafu)] @@ -33,4 +32,4 @@ pub enum UtilError { }, } -pub type UtilResult = std::result::Result; \ No newline at end of file +pub type UtilResult = std::result::Result; From 95a30210ce7675c191f07cb174ec55a298986be8 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 19:17:12 +0200 Subject: [PATCH 07/12] clippy fixes, fmt --- .../canonicalize.rs | 6 +++--- .../{canonicalize => canonicalization}/mod.rs | 0 .../module.rs | 8 ++++---- ast/src/constrain.rs | 4 ++-- ast/src/lang/core/ast.rs | 2 +- ast/src/lang/core/declaration.rs | 2 +- ast/src/lang/core/def/def.rs | 2 +- ast/src/lang/core/def/def2.rs | 2 +- ast/src/lang/core/expr/expr2.rs | 2 +- ast/src/lang/core/expr/expr2_to_string.rs | 2 +- ast/src/lang/core/expr/expr_to_expr2.rs | 16 ++++++++-------- ast/src/lang/core/expr/output.rs | 2 +- ast/src/lang/core/expr/record_field.rs | 2 +- ast/src/lang/core/fun_def.rs | 2 +- ast/src/lang/core/pattern.rs | 8 ++++---- ast/src/lang/core/str.rs | 4 ++-- ast/src/lang/core/types.rs | 8 ++++---- ast/src/lang/core/val_def.rs | 2 +- ast/src/lang/env.rs | 4 ++-- ast/src/lang/rigids.rs | 10 ++++------ ast/src/lang/scope.rs | 8 ++++---- ast/src/lib.rs | 4 ++-- ast/src/{pool => mem_pool}/mod.rs | 0 ast/src/{pool => mem_pool}/pool.rs | 0 ast/src/{pool => mem_pool}/pool_str.rs | 0 ast/src/{pool => mem_pool}/pool_vec.rs | 0 ast/src/{pool => mem_pool}/shallow_clone.rs | 0 ast/src/solve_type.rs | 6 +++--- code_markup/src/markup/attribute.rs | 10 ++++++---- code_markup/src/markup/common_nodes.rs | 18 +++++++++--------- code_markup/src/markup/nodes.rs | 10 +++++----- code_markup/src/markup/top_level_def.rs | 2 +- code_markup/src/slow_pool.rs | 10 ++++++---- editor/src/editor/main.rs | 2 +- editor/src/editor/mvc/ed_model.rs | 6 +++--- editor/src/editor/mvc/ed_update.rs | 6 +++--- editor/src/editor/mvc/ed_view.rs | 2 +- editor/src/editor/mvc/int_update.rs | 4 ++-- editor/src/editor/mvc/let_update.rs | 2 +- editor/src/editor/mvc/list_update.rs | 2 +- editor/src/editor/mvc/lookup_update.rs | 2 +- editor/src/editor/mvc/record_update.rs | 8 ++++---- editor/src/editor/mvc/string_update.rs | 4 ++-- editor/src/editor/mvc/tld_value_update.rs | 4 ++-- 44 files changed, 100 insertions(+), 98 deletions(-) rename ast/src/{canonicalize => canonicalization}/canonicalize.rs (97%) rename ast/src/{canonicalize => canonicalization}/mod.rs (100%) rename ast/src/{canonicalize => canonicalization}/module.rs (98%) rename ast/src/{pool => mem_pool}/mod.rs (100%) rename ast/src/{pool => mem_pool}/pool.rs (100%) rename ast/src/{pool => mem_pool}/pool_str.rs (100%) rename ast/src/{pool => mem_pool}/pool_vec.rs (100%) rename ast/src/{pool => mem_pool}/shallow_clone.rs (100%) diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalization/canonicalize.rs similarity index 97% rename from ast/src/canonicalize/canonicalize.rs rename to ast/src/canonicalization/canonicalize.rs index b3adc2ab1d..211e22cf97 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalization/canonicalize.rs @@ -18,7 +18,7 @@ use crate::{ env::Env, scope::Scope, }, - pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; pub(crate) enum CanonicalizeRecordProblem { @@ -275,7 +275,7 @@ pub(crate) fn canonicalize_lookup( Var(symbol) } Err(problem) => { - env.problem(Problem::RuntimeError(problem.clone())); + env.problem(Problem::RuntimeError(problem)); RuntimeError() } @@ -292,7 +292,7 @@ pub(crate) fn canonicalize_lookup( Err(problem) => { // Either the module wasn't imported, or // it was imported but it doesn't expose this ident. - env.problem(Problem::RuntimeError(problem.clone())); + env.problem(Problem::RuntimeError(problem)); RuntimeError() } diff --git a/ast/src/canonicalize/mod.rs b/ast/src/canonicalization/mod.rs similarity index 100% rename from ast/src/canonicalize/mod.rs rename to ast/src/canonicalization/mod.rs diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalization/module.rs similarity index 98% rename from ast/src/canonicalize/module.rs rename to ast/src/canonicalization/module.rs index ace177df8b..c8286f8c2d 100644 --- a/ast/src/canonicalize/module.rs +++ b/ast/src/canonicalization/module.rs @@ -24,10 +24,10 @@ use crate::lang::core::types::Alias; use crate::lang::core::val_def::ValueDef; use crate::lang::env::Env; use crate::lang::scope::Scope; -use crate::pool::pool::NodeId; -use crate::pool::pool::Pool; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::NodeId; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; pub struct ModuleOutput { pub aliases: MutMap>, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index ca6b1a9a8d..1454f1d936 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -26,7 +26,7 @@ use crate::{ }, env::Env, }, - pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; #[derive(Debug)] @@ -1781,7 +1781,7 @@ pub mod test_constrain { env::Env, scope::Scope, }, - pool::pool::Pool, + mem_pool::pool::Pool, solve_type, }; use indoc::indoc; diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs index d337d91e6f..586b29f19c 100644 --- a/ast/src/lang/core/ast.rs +++ b/ast/src/lang/core/ast.rs @@ -1,6 +1,6 @@ use crate::{ ast_error::{ASTNodeIdWithoutExprId, ASTResult}, - pool::pool::Pool, + mem_pool::pool::Pool, }; use super::{ diff --git a/ast/src/lang/core/declaration.rs b/ast/src/lang/core/declaration.rs index 0c9ca03bfe..e45668b484 100644 --- a/ast/src/lang/core/declaration.rs +++ b/ast/src/lang/core/declaration.rs @@ -2,7 +2,7 @@ use roc_types::subs::VarStore; use crate::{ lang::core::{def::def::Def, expr::expr2::Expr2}, - pool::{pool::Pool, pool_vec::PoolVec}, + mem_pool::{pool::Pool, pool_vec::PoolVec}, }; use super::def::def::Declaration; diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 3bcd60f670..c8654b3d73 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -37,7 +37,7 @@ use crate::{ rigids::Rigids, scope::Scope, }, - pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; #[derive(Debug)] diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs index 85cd2b9a64..023a39760d 100644 --- a/ast/src/lang/core/def/def2.rs +++ b/ast/src/lang/core/def/def2.rs @@ -3,7 +3,7 @@ use crate::{ expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, pattern::Pattern2, }, - pool::pool::{NodeId, Pool}, + mem_pool::pool::{NodeId, Pool}, }; // A top level definition, not inside a function. For example: `main = "Hello, world!"` diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 8b2ad1b84b..e06d169ece 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -3,7 +3,7 @@ use roc_types::subs::Variable; use crate::{ lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, - pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, + mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, }; use roc_can::expr::Recursive; use roc_module::low_level::LowLevel; diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs index 6f8255ccf6..cbd2967766 100644 --- a/ast/src/lang/core/expr/expr2_to_string.rs +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -1,6 +1,6 @@ use crate::{ lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, - pool::pool::Pool, + mem_pool::pool::Pool, }; use super::expr2::{Expr2, ExprId}; diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 861ee2d49d..febad6b13a 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -9,7 +9,7 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use super::{expr2::Expr2, output::Output}; -use crate::canonicalize::canonicalize::{ +use crate::canonicalization::canonicalize::{ canonicalize_fields, canonicalize_lookup, canonicalize_when_branch, CanonicalizeRecordProblem, }; use crate::lang::core::declaration::decl_to_let; @@ -17,14 +17,14 @@ use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs}; use crate::lang::core::expr::expr2::ClosureExtra; use crate::lang::core::pattern::to_pattern2; use crate::lang::core::str::flatten_str_literal; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::shallow_clone::ShallowClone; use crate::{ lang::{ core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope, }, - pool::{pool_str::PoolStr, pool_vec::PoolVec}, + mem_pool::{pool_str::PoolStr, pool_vec::PoolVec}, }; pub fn loc_expr_to_expr2<'a>( @@ -65,7 +65,7 @@ pub fn to_expr2<'a>( // emit runtime error let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); - env.problem(Problem::RuntimeError(runtime_error.clone())); + env.problem(Problem::RuntimeError(runtime_error)); // // Expr::RuntimeError(runtime_error) todo!() @@ -94,7 +94,7 @@ pub fn to_expr2<'a>( raw.into(), ); - env.problem(Problem::RuntimeError(runtime_error.clone())); + env.problem(Problem::RuntimeError(runtime_error)); // // Expr::RuntimeError(runtime_error) todo!() @@ -122,7 +122,7 @@ pub fn to_expr2<'a>( // emit runtime error let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); - env.problem(Problem::RuntimeError(runtime_error.clone())); + env.problem(Problem::RuntimeError(runtime_error)); // // Expr::RuntimeError(runtime_error) todo!() @@ -130,7 +130,7 @@ pub fn to_expr2<'a>( } } - Str(literal) => flatten_str_literal(env, scope, &literal), + Str(literal) => flatten_str_literal(env, scope, literal), List { items, .. } => { let mut output = Output::default(); @@ -585,7 +585,7 @@ pub fn to_expr2<'a>( let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( env, Output::default(), - &scope, + scope, loc_defs, PatternType::DefExpr, ); diff --git a/ast/src/lang/core/expr/output.rs b/ast/src/lang/core/expr/output.rs index af7f455465..4287e3f72f 100644 --- a/ast/src/lang/core/expr/output.rs +++ b/ast/src/lang/core/expr/output.rs @@ -1,6 +1,6 @@ use crate::{ lang::core::{def::def::References, types::Alias}, - pool::pool::NodeId, + mem_pool::pool::NodeId, }; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::Symbol; diff --git a/ast/src/lang/core/expr/record_field.rs b/ast/src/lang/core/expr/record_field.rs index 90c1fb6ed7..aaf464799f 100644 --- a/ast/src/lang/core/expr/record_field.rs +++ b/ast/src/lang/core/expr/record_field.rs @@ -1,6 +1,6 @@ use roc_types::subs::Variable; -use crate::pool::pool_str::PoolStr; +use crate::mem_pool::pool_str::PoolStr; use roc_module::symbol::Symbol; use super::expr2::ExprId; diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs index 773e6ecd66..588d07d996 100644 --- a/ast/src/lang/core/fun_def.rs +++ b/ast/src/lang/core/fun_def.rs @@ -1,6 +1,6 @@ use crate::{ lang::rigids::Rigids, - pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; use roc_module::symbol::Symbol; use roc_types::subs::Variable; diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 230812285e..19a956bcd8 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -18,10 +18,10 @@ use crate::constrain::Constraint; use crate::lang::core::expr::expr_to_expr2::to_expr_id; use crate::lang::env::Env; use crate::lang::scope::Scope; -use crate::pool::pool::{NodeId, Pool}; -use crate::pool::pool_str::PoolStr; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; use super::expr::expr2::{ExprId, FloatVal, IntVal}; use super::expr::output::Output; diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 69d2cca7d2..53b0a999cf 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -4,7 +4,7 @@ use roc_parse::ast::StrLiteral; use crate::{ ast_error::{ASTResult, UnexpectedASTNode}, lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, - pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}, + mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}, }; use super::expr::{ @@ -123,7 +123,7 @@ fn flatten_str_lines<'a>( /// Resolve string interpolations by desugaring a sequence of StrSegments /// into nested calls to Str.concat -fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Expr2 { +fn desugar_str_segments(env: &mut Env, segments: Vec) -> Expr2 { use StrSegment::*; let pool = &mut env.pool; diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs index 8279cd04e5..107a6e7946 100644 --- a/ast/src/lang/core/types.rs +++ b/ast/src/lang/core/types.rs @@ -11,10 +11,10 @@ use roc_types::{subs::Variable, types::ErrorType}; use crate::lang::env::Env; use crate::lang::scope::Scope; -use crate::pool::pool::{NodeId, Pool}; -use crate::pool::pool_str::PoolStr; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; pub type TypeId = NodeId; diff --git a/ast/src/lang/core/val_def.rs b/ast/src/lang/core/val_def.rs index 2b97221c77..b30bfb3ace 100644 --- a/ast/src/lang/core/val_def.rs +++ b/ast/src/lang/core/val_def.rs @@ -1,6 +1,6 @@ use crate::{ lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, - pool::{ + mem_pool::{ pool::{NodeId, Pool}, shallow_clone::ShallowClone, }, diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index c3ee64edc3..87a048151e 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -6,7 +6,7 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::VarStore; -use crate::pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool::{NodeId, Pool}; use super::core::def::def::References; @@ -147,7 +147,7 @@ impl<'a> Env<'a> { Ok(symbol) } None => Err(RuntimeError::ValueNotExposed { - module_name: ModuleName::from(module_name), + module_name, ident, region, }), diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index be43173714..a10dce0016 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -3,7 +3,9 @@ use std::{ hash::BuildHasherDefault, }; -use crate::pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}; +use crate::mem_pool::{ + pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone, +}; use roc_collections::all::WyHash; use roc_types::subs::Variable; @@ -45,11 +47,7 @@ impl Rigids { .names .iter(pool) .filter_map(|(opt_pool_str, var)| { - if let Some(pool_str) = opt_pool_str { - Some((*pool_str, *var)) - } else { - None - } + opt_pool_str.as_ref().map(|pool_str| (*pool_str, *var)) }) .collect::>(); diff --git a/ast/src/lang/scope.rs b/ast/src/lang/scope.rs index fa8cc7e9d2..9f3a11c60c 100644 --- a/ast/src/lang/scope.rs +++ b/ast/src/lang/scope.rs @@ -2,10 +2,10 @@ #![allow(dead_code)] #![allow(unused_imports)] -use crate::pool::pool::Pool; -use crate::pool::pool_str::PoolStr; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; diff --git a/ast/src/lib.rs b/ast/src/lib.rs index e7f03db59d..b3d987f99f 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -1,7 +1,7 @@ pub mod ast_error; -mod canonicalize; +mod canonicalization; pub mod constrain; pub mod lang; +pub mod mem_pool; pub mod parse; -pub mod pool; pub mod solve_type; diff --git a/ast/src/pool/mod.rs b/ast/src/mem_pool/mod.rs similarity index 100% rename from ast/src/pool/mod.rs rename to ast/src/mem_pool/mod.rs diff --git a/ast/src/pool/pool.rs b/ast/src/mem_pool/pool.rs similarity index 100% rename from ast/src/pool/pool.rs rename to ast/src/mem_pool/pool.rs diff --git a/ast/src/pool/pool_str.rs b/ast/src/mem_pool/pool_str.rs similarity index 100% rename from ast/src/pool/pool_str.rs rename to ast/src/mem_pool/pool_str.rs diff --git a/ast/src/pool/pool_vec.rs b/ast/src/mem_pool/pool_vec.rs similarity index 100% rename from ast/src/pool/pool_vec.rs rename to ast/src/mem_pool/pool_vec.rs diff --git a/ast/src/pool/shallow_clone.rs b/ast/src/mem_pool/shallow_clone.rs similarity index 100% rename from ast/src/pool/shallow_clone.rs rename to ast/src/mem_pool/shallow_clone.rs diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 986f867856..2b39c606b3 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -19,9 +19,9 @@ use roc_unify::unify::Unified::*; use crate::constrain::Constraint; use crate::lang::core::types::Type2; -use crate::pool::pool::Pool; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler diff --git a/code_markup/src/markup/attribute.rs b/code_markup/src/markup/attribute.rs index 0adb489659..7df80cbc70 100644 --- a/code_markup/src/markup/attribute.rs +++ b/code_markup/src/markup/attribute.rs @@ -65,10 +65,6 @@ pub struct Attributes { } impl Attributes { - pub fn new() -> Attributes { - Attributes { all: Vec::new() } - } - pub fn add(&mut self, attr: Attribute) { self.all.push(attr); } @@ -121,3 +117,9 @@ impl Attributes { Ok(()) } } + +impl Default for Attributes { + fn default() -> Self { + Attributes { all: Vec::new() } + } +} diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs index 6cdc31eda9..89a8a8c2d9 100644 --- a/code_markup/src/markup/common_nodes.rs +++ b/code_markup/src/markup/common_nodes.rs @@ -9,7 +9,7 @@ pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) content: nodes::EQUALS.to_owned(), ast_node_id, syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -20,7 +20,7 @@ pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> Marku content: nodes::COMMA.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -30,7 +30,7 @@ pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) - MarkupNode::Blank { ast_node_id, syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -44,7 +44,7 @@ pub fn new_blank_mn_w_nls( MarkupNode::Blank { ast_node_id, syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: nr_of_newlines, } @@ -55,7 +55,7 @@ pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> Marku content: nodes::COLON.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -66,7 +66,7 @@ pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) content: nodes::LEFT_ACCOLADE.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -77,7 +77,7 @@ pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) content: nodes::RIGHT_ACCOLADE.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -88,7 +88,7 @@ pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> content: nodes::LEFT_SQUARE_BR.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -99,7 +99,7 @@ pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) - content: nodes::RIGHT_SQUARE_BR.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index 4fe4d5c954..ff8907be30 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -56,7 +56,7 @@ use roc_ast::{ }, env::Env, }, - pool::pool_str::PoolStr, + mem_pool::pool_str::PoolStr, }; use roc_module::symbol::Interns; use roc_utils::{index_of, slice_get}; @@ -323,7 +323,7 @@ fn new_markup_node( content: text, ast_node_id: node_id, syn_high_style: highlight_style, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -528,7 +528,7 @@ pub fn expr2_to_markup<'a, 'b>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -637,7 +637,7 @@ fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::PackageRelated, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -655,7 +655,7 @@ fn header_val_mn( content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: highlight_style, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs index 5cadc131ac..d2122fc790 100644 --- a/code_markup/src/markup/top_level_def.rs +++ b/code_markup/src/markup/top_level_def.rs @@ -31,7 +31,7 @@ pub fn tld_mark_node<'a>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; diff --git a/code_markup/src/slow_pool.rs b/code_markup/src/slow_pool.rs index 1f8a12c4af..d69e1a1a9c 100644 --- a/code_markup/src/slow_pool.rs +++ b/code_markup/src/slow_pool.rs @@ -10,10 +10,6 @@ pub struct SlowPool { } impl SlowPool { - pub fn new() -> SlowPool { - SlowPool { nodes: Vec::new() } - } - pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { let id = self.nodes.len(); @@ -73,3 +69,9 @@ impl fmt::Display for SlowPool { Ok(()) } } + +impl Default for SlowPool { + fn default() -> Self { + SlowPool { nodes: Vec::new() } + } +} diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index a3cf2d3fe7..4b68ed3f8d 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -24,7 +24,7 @@ use cgmath::Vector2; use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use pipelines::RectResources; use roc_ast::lang::env::Env; -use roc_ast::pool::pool::Pool; +use roc_ast::mem_pool::pool::Pool; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load; diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 12eb4bb110..d255bdbeb5 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -13,8 +13,8 @@ use bumpalo::Bump; use nonempty::NonEmpty; use roc_ast::lang::core::ast::{ASTNodeId, AST}; use roc_ast::lang::env::Env; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_ast::parse::parse_ast; -use roc_ast::pool::pool_str::PoolStr; use roc_code_markup::markup::nodes::ast_to_mark_nodes; use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use roc_load::file::LoadedModule; @@ -57,7 +57,7 @@ pub fn init_model<'a>( ) -> EdResult> { let mut module = EdModule::new(code_str, env, code_arena)?; - let mut mark_node_pool = SlowPool::new(); + let mut mark_node_pool = SlowPool::default(); let markup_ids = if code_str.is_empty() { EmptyCodeString {}.fail() @@ -211,7 +211,7 @@ pub mod test_ed_model { use bumpalo::Bump; use ed_model::EdModel; use roc_ast::lang::env::Env; - use roc_ast::pool::pool::Pool; + use roc_ast::mem_pool::pool::Pool; use roc_load::file::LoadedModule; use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index ce0c894955..cc093c6e2b 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -59,8 +59,8 @@ use roc_ast::lang::core::def::def2::DefId; use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::expr::expr2::ExprId; use roc_ast::lang::core::types::Type2; -use roc_ast::pool::pool::Pool; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool::Pool; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_ast::solve_type; use roc_can::expected::Expected; use roc_code_markup::markup::attribute::Attributes; @@ -589,7 +589,7 @@ impl<'a> EdModel<'a> { let blank_replacement = MarkupNode::Blank { ast_node_id: sel_block.ast_node_id, - attributes: Attributes::new(), + attributes: Attributes::default(), syn_high_style: HighlightStyle::Blank, parent_id_opt: expr2_level_mark_node.get_parent_id_opt(), newlines_at_end, diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index ab35ecbdbd..b9d4693b9e 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -14,7 +14,7 @@ use crate::ui::text::selection::Selection; use crate::ui::tooltip::ToolTip; use crate::ui::ui_error::MissingGlyphDims; use cgmath::Vector2; -use roc_ast::pool::pool::Pool; +use roc_ast::mem_pool::pool::Pool; use snafu::OptionExt; use winit::dpi::PhysicalSize; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index c2b63212f8..75a8354b60 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -1,7 +1,7 @@ use roc_ast::lang::core::expr::expr2::Expr2::SmallInt; use roc_ast::lang::core::expr::expr2::IntStyle; use roc_ast::lang::core::expr::expr2::IntVal; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::nodes::MarkupNode; use roc_code_markup::slow_pool::MarkNodeId; @@ -49,7 +49,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult EdResult< content: val_name_string, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: Some(curr_mark_node_id), newlines_at_end: curr_mark_node_nls, }; diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 7036701b9d..fef31b7627 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,6 +1,6 @@ use roc_ast::lang::core::ast::{ast_node_to_string, ASTNodeId}; use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; -use roc_ast::pool::pool_vec::PoolVec; +use roc_ast::mem_pool::pool_vec::PoolVec; use roc_code_markup::markup::common_nodes::{ new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, }; diff --git a/editor/src/editor/mvc/lookup_update.rs b/editor/src/editor/mvc/lookup_update.rs index 53db22427a..5d5c9f5396 100644 --- a/editor/src/editor/mvc/lookup_update.rs +++ b/editor/src/editor/mvc/lookup_update.rs @@ -1,5 +1,5 @@ use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_code_markup::slow_pool::MarkNodeId; use crate::editor::ed_error::EdResult; diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 94f824375c..d27bb0c5d0 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -11,8 +11,8 @@ use roc_ast::lang::core::ast::ASTNodeId; use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::expr::expr2::ExprId; use roc_ast::lang::core::expr::record_field::RecordField; -use roc_ast::pool::pool_str::PoolStr; -use roc_ast::pool::pool_vec::PoolVec; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_vec::PoolVec; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::common_nodes::new_blank_mn; use roc_code_markup::markup::common_nodes::new_left_accolade_mn; @@ -126,7 +126,7 @@ pub fn update_empty_record( content: new_input.to_owned(), ast_node_id, syn_high_style: HighlightStyle::RecordField, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, }; @@ -235,7 +235,7 @@ pub fn update_record_colon( content: record_colon.to_owned(), ast_node_id: ASTNodeId::AExprId(record_ast_node_id), syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: Some(parent_id), newlines_at_end: 0, }; diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index fca9100495..8ee2ddd881 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -1,7 +1,7 @@ use roc_ast::lang::core::expr::expr2::ArrString; use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::str::update_str_expr; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::nodes; use roc_code_markup::markup::nodes::MarkupNode; @@ -150,7 +150,7 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { content: nodes::STRING_QUOTES.to_owned(), ast_node_id, syn_high_style: HighlightStyle::String, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: curr_mark_node_nls, }; diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index e7a8b3258d..6ca2f8e0fd 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -8,7 +8,7 @@ use roc_ast::{ }, env::Env, }, - pool::pool::NodeId, + mem_pool::pool::NodeId, }; use roc_code_markup::{ markup::{ @@ -49,7 +49,7 @@ pub fn tld_mark_node<'a>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; From 358664a51a65a61c80837a0f8c622cdbe7df38e7 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 19:27:48 +0200 Subject: [PATCH 08/12] minor cleanup --- ast/src/mod.rs | 14 -------------- ast/src/parse/mod.rs | 1 - ast/src/parse/parse_expr.rs | 1 - code_markup/src/markup/nodes.rs | 28 +--------------------------- 4 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 ast/src/mod.rs delete mode 100644 ast/src/parse/parse_expr.rs diff --git a/ast/src/mod.rs b/ast/src/mod.rs deleted file mode 100644 index f9d51e4b1f..0000000000 --- a/ast/src/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod ast; -pub mod constrain; -pub mod lang; -mod module; -pub mod parse; -mod pattern; -pub mod pool; -pub mod roc_file; -mod scope; -pub mod solve; -mod types; -mod rigids; -mod canonicalize; -pub mod ast_error; \ No newline at end of file diff --git a/ast/src/parse/mod.rs b/ast/src/parse/mod.rs index f14f61f5a0..31bf78fd5e 100644 --- a/ast/src/parse/mod.rs +++ b/ast/src/parse/mod.rs @@ -1,3 +1,2 @@ pub mod parse_ast; -pub mod parse_expr; pub mod parse_header; diff --git a/ast/src/parse/parse_expr.rs b/ast/src/parse/parse_expr.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/ast/src/parse/parse_expr.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index ff8907be30..cea08f288b 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -11,33 +11,7 @@ use crate::{ use super::{ attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node, }; -/*use crate::editor::ed_error::EdResult; -use crate::editor::ed_error::ExpectedTextNode; -use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_colon_mn; -use crate::editor::markup::common_nodes::new_comma_mn; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_left_square_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::common_nodes::new_right_square_mn; -use crate::editor::mvc::tld_value_update::tld_mark_node; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::ast::DefId; -use crate::lang::ast::ExprId; -use crate::lang::ast::RecordField; -use crate::lang::ast::ValueDef; -use crate::lang::parse::ASTNodeId; -use crate::lang::parse::{AppHeader, AST}; -use crate::lang::pattern::get_identifier_string; -use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; -use crate::ui::util::slice_get;*/ + use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired}; use bumpalo::Bump; use roc_ast::{ From 142841ebd8367b358d45939d9b91b580141fa965 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 26 Sep 2021 10:23:12 +0200 Subject: [PATCH 09/12] copy ast and code_markup folders inside earthly --- Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Earthfile b/Earthfile index f2a31d2fc7..85308c46b6 100644 --- a/Earthfile +++ b/Earthfile @@ -46,7 +46,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli compiler docs editor roc_std vendor examples linker Cargo.toml Cargo.lock ./ + COPY --dir cli compiler docs editor ast code_markup roc_std vendor examples linker Cargo.toml Cargo.lock ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt @@ -66,7 +66,7 @@ check-rustfmt: check-typos: RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically - COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ + COPY --dir .github ci cli compiler docs editor examples ast code_markup linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ RUN typos test-rust: From 01c731fd93198a84995e1b3b6b3b6feed044802b Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 26 Sep 2021 10:26:01 +0200 Subject: [PATCH 10/12] forgot about utils --- Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Earthfile b/Earthfile index 85308c46b6..04c9db6b8d 100644 --- a/Earthfile +++ b/Earthfile @@ -46,7 +46,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli compiler docs editor ast code_markup roc_std vendor examples linker Cargo.toml Cargo.lock ./ + COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt @@ -66,7 +66,7 @@ check-rustfmt: check-typos: RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically - COPY --dir .github ci cli compiler docs editor examples ast code_markup linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ + COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ RUN typos test-rust: From 3ff134e1ce23b1c7ea78f8099ee1a028d17809cd Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:42:02 +0200 Subject: [PATCH 11/12] removed erroneous imports --- editor/src/editor/ed_error.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index f0090e7609..1ebd3a88e6 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,5 +1,4 @@ -use crate::lang::parse::ASTNodeId; -use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; +use crate::{ui::text::text_pos::TextPos}; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId; From 9a88ba72d7c7230887c51707b87e548e1f7d132a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:56:35 +0200 Subject: [PATCH 12/12] fmt --- editor/src/editor/ed_error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index 1ebd3a88e6..357693bb69 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,4 +1,4 @@ -use crate::{ui::text::text_pos::TextPos}; +use crate::ui::text::text_pos::TextPos; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId;