Merge remote-tracking branch 'origin/trunk' into gen-dev/quicksort2

This commit is contained in:
Brendan Hansknecht 2022-03-01 15:29:52 -08:00
commit 3bada97067
102 changed files with 3628 additions and 2851 deletions

14
Cargo.lock generated
View file

@ -3222,6 +3222,7 @@ name = "repl_test"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"indoc", "indoc",
"lazy_static",
"roc_cli", "roc_cli",
"roc_repl_cli", "roc_repl_cli",
"roc_test_utils", "roc_test_utils",
@ -3501,6 +3502,16 @@ dependencies = [
name = "roc_error_macros" name = "roc_error_macros"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "roc_exhaustive"
version = "0.1.0"
dependencies = [
"roc_collections",
"roc_module",
"roc_region",
"roc_std",
]
[[package]] [[package]]
name = "roc_fmt" name = "roc_fmt"
version = "0.1.0" version = "0.1.0"
@ -3650,6 +3661,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros", "roc_error_macros",
"roc_exhaustive",
"roc_module", "roc_module",
"roc_problem", "roc_problem",
"roc_region", "roc_region",
@ -3713,6 +3725,7 @@ dependencies = [
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
"roc_repl_eval", "roc_repl_eval",
"roc_std",
"roc_target", "roc_target",
"roc_types", "roc_types",
"rustyline", "rustyline",
@ -3770,6 +3783,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_exhaustive",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",

View file

@ -3,6 +3,7 @@ members = [
"compiler/ident", "compiler/ident",
"compiler/region", "compiler/region",
"compiler/collections", "compiler/collections",
"compiler/exhaustive",
"compiler/module", "compiler/module",
"compiler/parse", "compiler/parse",
"compiler/can", "compiler/can",

View file

@ -33,12 +33,15 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN rustup component add clippy RUN rustup component add clippy
# rustfmt # rustfmt
RUN rustup component add rustfmt RUN rustup component add rustfmt
# wasm repl
RUN rustup target add wasm32-unknown-unknown
RUN apt -y install libssl-dev
RUN OPENSSL_NO_VENDOR=1 cargo install wasm-pack
# criterion # criterion
RUN cargo install cargo-criterion RUN cargo install cargo-criterion
# editor # editor
RUN apt -y install libxkbcommon-dev RUN apt -y install libxkbcommon-dev
# sccache # sccache
RUN apt -y install libssl-dev
RUN cargo install sccache RUN cargo install sccache
RUN sccache -V RUN sccache -V
ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache ENV RUSTC_WRAPPER=/usr/local/cargo/bin/sccache
@ -86,6 +89,9 @@ test-rust:
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job # gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \
cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
# repl_test: build the compiler for wasm target, then run the tests on native target
RUN --mount=type=cache,target=$SCCACHE_DIR \
repl_test/test_wasm.sh && sccache --show-stats
# run i386 (32-bit linux) cli tests # run i386 (32-bit linux) cli tests
RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc RUN echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
RUN --mount=type=cache,target=$SCCACHE_DIR \ RUN --mount=type=cache,target=$SCCACHE_DIR \

View file

@ -1623,8 +1623,7 @@ If you like, you can always annotate your functions as accepting open records. H
always be the nicest choice. For example, let's say you have a `User` type alias, like so: always be the nicest choice. For example, let's say you have a `User` type alias, like so:
```coffee ```coffee
User : User : {
{
email : Str, email : Str,
firstName : Str, firstName : Str,
lastName : Str, lastName : Str,
@ -1661,8 +1660,7 @@ Since open records have a type variable (like `*` in `{ email : Str }*` or `a` i
type variable to the `User` type alias: type variable to the `User` type alias:
```coffee ```coffee
User a : User a : {
{
email : Str, email : Str,
firstName : Str, firstName : Str,
lastName : Str, lastName : Str,

View file

@ -154,12 +154,18 @@ fn canonicalize_field<'a>(
let (loc_can_expr, output) = let (loc_can_expr, output) =
expr_to_expr2(env, scope, &loc_expr.value, loc_expr.region); expr_to_expr2(env, scope, &loc_expr.value, loc_expr.region);
Ok(CanonicalField::LabelAndValue { match loc_can_expr {
Expr2::RuntimeError() => Ok(CanonicalField::InvalidLabelOnly {
label: label.value,
var: field_var,
}),
_ => Ok(CanonicalField::LabelAndValue {
label: label.value, label: label.value,
value_expr: loc_can_expr, value_expr: loc_can_expr,
value_output: output, value_output: output,
var: field_var, var: field_var,
}) }),
}
} }
OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue {

View file

@ -15,6 +15,24 @@ pub struct AST {
pub def_ids: Vec<DefId>, pub def_ids: Vec<DefId>,
} }
impl AST {
pub fn insert_def_at_index(&mut self, new_def_id: DefId, index: usize) {
self.def_ids.insert(index, new_def_id);
}
// TODO print in tree shape, similar to linux tree command
pub fn ast_to_string(&self, pool: &Pool) -> String {
let mut full_ast_string = String::new();
for def_id in self.def_ids.iter() {
full_ast_string.push_str(&def2_to_string(*def_id, pool));
full_ast_string.push_str("\n\n");
}
full_ast_string
}
}
#[derive(Debug, PartialEq, Copy, Clone)] #[derive(Debug, PartialEq, Copy, Clone)]
pub enum ASTNodeId { pub enum ASTNodeId {
ADefId(DefId), ADefId(DefId),

View file

@ -148,6 +148,9 @@ fn expr2_to_string_helper(
&Expr2::Var { .. } => { &Expr2::Var { .. } => {
out_string.push_str(&format!("{:?}", expr2,)); out_string.push_str(&format!("{:?}", expr2,));
} }
Expr2::RuntimeError { .. } => {
out_string.push_str("RuntimeError\n");
}
other => todo!("Implement for {:?}", other), other => todo!("Implement for {:?}", other),
} }

View file

@ -50,6 +50,7 @@ pub fn expr_to_expr2<'a>(
region: Region, region: Region,
) -> (Expr2, self::Output) { ) -> (Expr2, self::Output) {
use roc_parse::ast::Expr::*; use roc_parse::ast::Expr::*;
//dbg!("{:?}", parse_expr);
match parse_expr { match parse_expr {
Float(string) => { Float(string) => {

View file

@ -43,7 +43,8 @@ fn to_type2(
var_store: &mut VarStore, var_store: &mut VarStore,
) -> Type2 { ) -> Type2 {
match solved_type { match solved_type {
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual) => { // TODO(opaques): take opaques into account
SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual, _kind) => {
let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool);
for (type_variable_node_id, (lowercase, solved_arg)) in type_variables for (type_variable_node_id, (lowercase, solved_arg)) in type_variables

View file

@ -27,7 +27,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
Ok(x) => x, Ok(x) => x,
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => { Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
panic!( panic!(
"Failed to load module from src_file {:?}. Report: {:?}", "Failed to load module from src_file {:?}. Report: {}",
src_file, report src_file, report
); );
} }

View file

@ -25,7 +25,7 @@ pub fn parse_from_string<'a>(
) -> ASTResult<AST> { ) -> ASTResult<AST> {
let blank_line_indx = code_str let blank_line_indx = code_str
.find("\n\n") .find("\n\n")
.expect("I was expecting a double newline to split header and rest of code."); .expect("I was expecting two newline chars to split header and rest of code.");
let header_str = &code_str[0..blank_line_indx]; let header_str = &code_str[0..blank_line_indx];
let tail_str = &code_str[blank_line_indx..]; let tail_str = &code_str[blank_line_indx..];

View file

@ -12,7 +12,8 @@ use roc_types::subs::{
SubsSlice, UnionTags, Variable, VariableSubsSlice, SubsSlice, UnionTags, Variable, VariableSubsSlice,
}; };
use roc_types::types::{ use roc_types::types::{
gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField, gather_fields_unsorted_iter, Alias, AliasKind, Category, ErrorType, PatternCategory,
RecordField,
}; };
use roc_unify::unify::unify; use roc_unify::unify::unify;
use roc_unify::unify::Mode; use roc_unify::unify::Mode;
@ -892,7 +893,9 @@ fn type_to_variable<'a>(
let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []); 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 alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type);
let content = Content::Alias(*symbol, arg_vars, alias_var);
// TODO(opaques): take opaques into account
let content = Content::Alias(*symbol, arg_vars, alias_var, AliasKind::Structural);
let result = register(subs, rank, pools, content); let result = register(subs, rank, pools, content);
@ -1384,7 +1387,7 @@ fn adjust_rank_content(
} }
} }
Alias(_, args, real_var) => { Alias(_, args, real_var, _) => {
let mut rank = Rank::toplevel(); let mut rank = Rank::toplevel();
for var_index in args.variables() { for var_index in args.variables() {
@ -1544,7 +1547,7 @@ fn instantiate_rigids_help(
subs.set(copy, make_descriptor(FlexVar(Some(name)))); subs.set(copy, make_descriptor(FlexVar(Some(name))));
} }
Alias(_, args, real_type_var) => { Alias(_, args, real_type_var, _) => {
for var_index in args.variables() { for var_index in args.variables() {
let var = subs[var_index]; let var = subs[var_index];
instantiate_rigids_help(subs, max_rank, pools, var); instantiate_rigids_help(subs, max_rank, pools, var);
@ -1794,7 +1797,7 @@ fn deep_copy_var_help(
copy copy
} }
Alias(symbol, mut args, real_type_var) => { Alias(symbol, mut args, real_type_var, kind) => {
let mut new_args = Vec::with_capacity(args.variables().len()); let mut new_args = Vec::with_capacity(args.variables().len());
for var_index in args.variables() { for var_index in args.variables() {
@ -1806,7 +1809,7 @@ fn deep_copy_var_help(
args.replace_variables(subs, new_args); args.replace_variables(subs, new_args);
let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); 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); let new_content = Alias(symbol, args, new_real_type_var, kind);
subs.set(copy, make_descriptor(new_content)); subs.set(copy, make_descriptor(new_content));

View file

@ -64,13 +64,12 @@ pub fn expr2_to_markup<'a>(
Expr2::Str(text) => { Expr2::Str(text) => {
let content = format!("\"{}\"", text.as_str(env.pool)); let content = format!("\"{}\"", text.as_str(env.pool));
new_markup_node( string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
with_indent(indent_level, &content), }
ast_node_id, Expr2::SmallStr(array_str) => {
HighlightStyle::String, let content = format!("\"{}\"", array_str.as_str());
mark_node_pool,
indent_level, string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
)
} }
Expr2::GlobalTag { name, .. } => new_markup_node( Expr2::GlobalTag { name, .. } => new_markup_node(
with_indent(indent_level, &get_string(env, name)), with_indent(indent_level, &get_string(env, name)),
@ -387,3 +386,18 @@ fn with_indent(indent_level: usize, some_str: &str) -> String {
full_string full_string
} }
fn string_mark_node(
content: &str,
indent_level: usize,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
) -> MarkNodeId {
new_markup_node(
with_indent(indent_level, content),
ast_node_id,
HighlightStyle::String,
mark_node_pool,
indent_level,
)
}

View file

@ -468,3 +468,34 @@ pub fn join_mark_nodes_commas(
mark_nodes.into_iter().interleave(join_nodes).collect() mark_nodes.into_iter().interleave(join_nodes).collect()
} }
pub fn mark_nodes_to_string(markup_node_ids: &[MarkNodeId], mark_node_pool: &SlowPool) -> String {
let mut all_code_string = String::new();
for mark_node_id in markup_node_ids.iter() {
node_to_string_w_children(*mark_node_id, &mut all_code_string, mark_node_pool)
}
all_code_string
}
pub fn node_to_string_w_children(
node_id: MarkNodeId,
str_buffer: &mut String,
mark_node_pool: &SlowPool,
) {
let node = mark_node_pool.get(node_id);
if node.is_nested() {
for child_id in node.get_children_ids() {
node_to_string_w_children(child_id, str_buffer, mark_node_pool);
}
for _ in 0..node.get_newlines_at_end() {
str_buffer.push('\n')
}
} else {
let node_content_str = node.get_full_content();
str_buffer.push_str(&node_content_str);
}
}

View file

@ -103,8 +103,8 @@ fn generate_object_file(
println!("Moving zig object `{}` to: {}", zig_object, dest_obj); println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
// we store this .o file in rust's `target` folder // we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]); run_command(&bitcode_path, "cp", &[src_obj, dest_obj]);
} }
fn generate_bc_file( fn generate_bc_file(

View file

@ -392,27 +392,19 @@ toUtf32Le : Str -> List U8
# Parsing # Parsing
## If the string begins with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster), ## If the bytes begin with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster)
## return it along with the rest of the string after that grapheme. ## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
## ##
## If the string does not begin with a full grapheme, for example because it was ## If the bytes do not begin with a valid grapheme, for example because the list was
## empty, return `Err`. ## empty or began with an invalid grapheme, return `Err`.
parseGrapheme : Str -> Result { val : Str, rest : Str } [ Expected [ Grapheme ]* Str ]* parseUtf8Grapheme : List U8 -> Result { grapheme : Str, bytesParsed: Nat } [ InvalidGrapheme ]*
## If the string begins with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point), ## If the bytes begin with a valid [Unicode code point](http://www.unicode.org/glossary/#code_point)
## return it along with the rest of the string after that code point. ## encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), return it along with the number of bytes it took up.
## ##
## If the string does not begin with a valid code point, for example because it was ## If the string does not begin with a valid code point, for example because the list was
## empty, return `Err`. ## empty or began with an invalid code point, return an `Err`.
parseCodePt : Str -> Result { val : U32, rest : Str } [ Expected [ CodePt ]* Str ]* parseUtf8CodePt : List U8 -> Result { codePt : U32, bytesParsed: Nat } [ InvalidCodePt ]*
## If the first string begins with the second, return whatever comes
## after the second.
chomp : Str, Str -> Result Str [ Expected [ ExactStr Str ]* Str ]*
## If the string begins with a [Unicode code point](http://www.unicode.org/glossary/#code_point)
## equal to the given [U32], return whatever comes after that code point.
chompCodePt : Str, U32 -> Result Str [ Expected [ ExactCodePt U32 ]* Str ]*
## If the string represents a valid [U8] number, return that number. ## If the string represents a valid [U8] number, return that number.
## ##

View file

@ -312,9 +312,6 @@ fn can_annotation_help(
match scope.lookup_alias(symbol) { match scope.lookup_alias(symbol) {
Some(alias) => { Some(alias) => {
// use a known alias // use a known alias
let mut actual = alias.typ.clone();
let mut substitutions = ImMap::default();
let mut vars = Vec::new();
if alias.type_variables.len() != args.len() { if alias.type_variables.len() != args.len() {
let error = Type::Erroneous(Problem::BadTypeArguments { let error = Type::Erroneous(Problem::BadTypeArguments {
@ -326,40 +323,18 @@ fn can_annotation_help(
return error; return error;
} }
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args.into_iter()) { let (type_arguments, lambda_set_variables, actual) =
let name = loc_var.value.0.clone(); instantiate_and_freshen_alias_type(
let var = loc_var.value.1; var_store,
&alias.type_variables,
substitutions.insert(var, arg_ann.clone()); args,
vars.push((name.clone(), arg_ann)); &alias.lambda_set_variables,
} alias.typ.clone(),
);
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for typ in alias.lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// instantiate variables
actual.substitute(&substitutions);
Type::Alias { Type::Alias {
symbol, symbol,
type_arguments: vars, type_arguments,
lambda_set_variables, lambda_set_variables,
actual: Box::new(actual), actual: Box::new(actual),
kind: alias.kind, kind: alias.kind,
@ -652,6 +627,70 @@ fn can_annotation_help(
} }
} }
pub fn instantiate_and_freshen_alias_type(
var_store: &mut VarStore,
type_variables: &[Loc<(Lowercase, Variable)>],
type_arguments: Vec<Type>,
lambda_set_variables: &[LambdaSet],
mut actual_type: Type,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
let mut substitutions = ImMap::default();
let mut type_var_to_arg = Vec::new();
for (loc_var, arg_ann) in type_variables.iter().zip(type_arguments.into_iter()) {
let name = loc_var.value.0.clone();
let var = loc_var.value.1;
substitutions.insert(var, arg_ann.clone());
type_var_to_arg.push((name.clone(), arg_ann));
}
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual_type {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
let mut new_lambda_set_variables = Vec::with_capacity(lambda_set_variables.len());
for typ in lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// instantiate variables
actual_type.substitute(&substitutions);
(type_var_to_arg, new_lambda_set_variables, actual_type)
}
pub fn freshen_opaque_def(
var_store: &mut VarStore,
opaque: &Alias,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
debug_assert!(opaque.kind == AliasKind::Opaque);
let fresh_arguments = opaque
.type_variables
.iter()
.map(|_| Type::Variable(var_store.fresh()))
.collect();
instantiate_and_freshen_alias_type(
var_store,
&opaque.type_variables,
fresh_arguments,
&opaque.lambda_set_variables,
opaque.typ.clone(),
)
}
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F) fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)
where where
F: FnMut(&T, &T) -> std::cmp::Ordering, F: FnMut(&T, &T) -> std::cmp::Ordering,

View file

@ -11,7 +11,7 @@ use roc_types::{subs::Variable, types::VariableDetail};
/// constraints makes them behaviorally different from unification-based constraints. /// constraints makes them behaviorally different from unification-based constraints.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum PresenceConstraint { pub enum PresenceConstraint {
IncludesTag(TagName, Vec<Type>), IncludesTag(TagName, Vec<Type>, Region, PatternCategory),
IsOpen, IsOpen,
Pattern(Region, PatternCategory, PExpected<Type>), Pattern(Region, PatternCategory, PExpected<Type>),
} }
@ -159,7 +159,7 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia
Constraint::Present(typ, constr) => { Constraint::Present(typ, constr) => {
subtract(declared, &typ.variables_detail(), accum); subtract(declared, &typ.variables_detail(), accum);
match constr { match constr {
PresenceConstraint::IncludesTag(_, tys) => { PresenceConstraint::IncludesTag(_, tys, _, _) => {
for ty in tys { for ty in tys {
subtract(declared, &ty.variables_detail(), accum); subtract(declared, &ty.variables_detail(), accum);
} }

View file

@ -836,11 +836,10 @@ fn pattern_to_vars_by_symbol(
} }
UnwrappedOpaque { UnwrappedOpaque {
arguments, opaque, .. argument, opaque, ..
} => { } => {
for (var, nested) in arguments { let (var, nested) = &**argument;
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var); pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
}
vars_by_symbol.insert(*opaque, expr_var); vars_by_symbol.insert(*opaque, expr_var);
} }

View file

@ -1,4 +1,4 @@
use crate::annotation::IntroducedVariables; use crate::annotation::{freshen_opaque_def, IntroducedVariables};
use crate::builtins::builtin_defs_map; use crate::builtins::builtin_defs_map;
use crate::def::{can_defs_with_return, Def}; use crate::def::{can_defs_with_return, Def};
use crate::env::Env; use crate::env::Env;
@ -19,7 +19,7 @@ use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::Alias; use roc_types::types::{Alias, LambdaSet, Type};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::{char, u32}; use std::{char, u32};
@ -173,9 +173,29 @@ pub enum Expr {
arguments: Vec<(Variable, Loc<Expr>)>, arguments: Vec<(Variable, Loc<Expr>)>,
}, },
/// A wrapping of an opaque type, like `$Age 21`
// TODO(opaques): $->@ above when opaques land
OpaqueRef { OpaqueRef {
opaque_var: Variable,
name: Symbol, name: Symbol,
arguments: Vec<(Variable, Loc<Expr>)>, argument: Box<(Variable, Loc<Expr>)>,
// The following help us link this opaque reference to the type specified by its
// definition, which we then use during constraint generation. For example
// suppose we have
//
// Id n := [ Id U64 n ]
// @Id "sasha"
//
// Then `opaque` is "Id", `argument` is "sasha", but this is not enough for us to
// infer the type of the expression as "Id Str" - we need to link the specialized type of
// the variable "n".
// That's what `specialized_def_type` and `type_arguments` are for; they are specialized
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
lambda_set_variables: Vec<LambdaSet>,
}, },
// Test // Test
@ -388,29 +408,65 @@ pub fn canonicalize_expr<'a>(
// (foo) bar baz // (foo) bar baz
let fn_region = loc_fn.region; let fn_region = loc_fn.region;
// Canonicalize the function expression and its arguments
let (fn_expr, mut output) =
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
// The function's return type // The function's return type
let mut args = Vec::new(); let mut args = Vec::new();
let mut outputs = Vec::new(); let mut output = Output::default();
for loc_arg in loc_args.iter() { for loc_arg in loc_args.iter() {
let (arg_expr, arg_out) = let (arg_expr, arg_out) =
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value); canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
args.push((var_store.fresh(), arg_expr)); args.push((var_store.fresh(), arg_expr));
outputs.push(arg_out); output.references = output.references.union(arg_out.references);
} }
if let ast::Expr::OpaqueRef(name) = loc_fn.value {
// We treat opaques specially, since an opaque can wrap exactly one argument.
debug_assert!(!args.is_empty());
if args.len() > 1 {
let problem =
roc_problem::can::RuntimeError::OpaqueAppliedToMultipleArgs(region);
env.problem(Problem::RuntimeError(problem.clone()));
(RuntimeError(problem), output)
} else {
match scope.lookup_opaque_ref(name, loc_fn.region) {
Err(runtime_error) => {
env.problem(Problem::RuntimeError(runtime_error.clone()));
(RuntimeError(runtime_error), output)
}
Ok((name, opaque_def)) => {
let argument = Box::new(args.pop().unwrap());
output.references.referenced_type_defs.insert(name);
output.references.lookups.insert(name);
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
let opaque_ref = OpaqueRef {
opaque_var: var_store.fresh(),
name,
argument,
specialized_def_type: Box::new(specialized_def_type),
type_arguments,
lambda_set_variables,
};
(opaque_ref, output)
}
}
}
} else {
// Canonicalize the function expression and its arguments
let (fn_expr, fn_expr_output) =
canonicalize_expr(env, var_store, scope, fn_region, &loc_fn.value);
output.union(fn_expr_output);
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None; output.tail_call = None;
for arg_out in outputs {
output.references = output.references.union(arg_out.references);
}
let expr = match fn_expr.value { let expr = match fn_expr.value {
Var(symbol) => { Var(symbol) => {
output.references.calls.insert(symbol); output.references.calls.insert(symbol);
@ -447,10 +503,6 @@ pub fn canonicalize_expr<'a>(
name, name,
arguments: args, arguments: args,
}, },
OpaqueRef { name, .. } => OpaqueRef {
name,
arguments: args,
},
ZeroArgumentTag { ZeroArgumentTag {
variant_var, variant_var,
ext_var, ext_var,
@ -479,6 +531,7 @@ pub fn canonicalize_expr<'a>(
(expr, output) (expr, output)
} }
}
ast::Expr::Var { module_name, ident } => { ast::Expr::Var { module_name, ident } => {
canonicalize_lookup(env, scope, module_name, ident, region) canonicalize_lookup(env, scope, module_name, ident, region)
} }
@ -728,19 +781,16 @@ pub fn canonicalize_expr<'a>(
Output::default(), Output::default(),
) )
} }
ast::Expr::OpaqueRef(opaque_ref) => match scope.lookup_opaque_ref(opaque_ref, region) { ast::Expr::OpaqueRef(opaque_ref) => {
Ok(name) => ( // If we're here, the opaque reference is definitely not wrapping an argument - wrapped
OpaqueRef { // arguments are handled in the Apply branch.
name, let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at(
arguments: vec![], region,
}, (*opaque_ref).into(),
Output::default(), ));
), env.problem(Problem::RuntimeError(problem.clone()));
Err(runtime_error) => { (RuntimeError(problem), Output::default())
env.problem(Problem::RuntimeError(runtime_error.clone()));
(RuntimeError(runtime_error), Output::default())
} }
},
ast::Expr::Expect(condition, continuation) => { ast::Expr::Expect(condition, continuation) => {
let mut output = Output::default(); let mut output = Output::default();
@ -1519,18 +1569,28 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
); );
} }
OpaqueRef { name, arguments } => { OpaqueRef {
let arguments = arguments opaque_var,
.into_iter() name,
.map(|(var, loc_expr)| { argument,
( specialized_def_type,
type_arguments,
lambda_set_variables,
} => {
let (var, loc_expr) = *argument;
let argument = Box::new((
var, var,
loc_expr.map_owned(|expr| inline_calls(var_store, scope, expr)), loc_expr.map_owned(|expr| inline_calls(var_store, scope, expr)),
) ));
})
.collect();
OpaqueRef { name, arguments } OpaqueRef {
opaque_var,
name,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
}
} }
ZeroArgumentTag { ZeroArgumentTag {

View file

@ -543,15 +543,15 @@ fn fix_values_captured_in_closure_pattern(
AppliedTag { AppliedTag {
arguments: loc_args, arguments: loc_args,
.. ..
}
| UnwrappedOpaque {
arguments: loc_args,
..
} => { } => {
for (_, loc_arg) in loc_args.iter_mut() { for (_, loc_arg) in loc_args.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols); fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
} }
} }
UnwrappedOpaque { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
}
RecordDestructure { destructs, .. } => { RecordDestructure { destructs, .. } => {
for loc_destruct in destructs.iter_mut() { for loc_destruct in destructs.iter_mut() {
use crate::pattern::DestructType::*; use crate::pattern::DestructType::*;
@ -700,10 +700,14 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
} }
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } | OpaqueRef { arguments, .. } => { Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() { for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
} }
} }
OpaqueRef { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}
} }
} }

View file

@ -1,3 +1,4 @@
use crate::annotation::freshen_opaque_def;
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
use crate::num::{ use crate::num::{
@ -5,7 +6,6 @@ use crate::num::{
NumericBound, ParsedNumResult, NumericBound, ParsedNumResult,
}; };
use crate::scope::Scope; use crate::scope::Scope;
use roc_error_macros::todo_opaques;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::ast::{self, StrLiteral, StrSegment};
@ -13,6 +13,7 @@ use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{LambdaSet, Type};
/// A pattern, including possible problems (e.g. shadowing) so that /// A pattern, including possible problems (e.g. shadowing) so that
/// codegen can generate a runtime error if this pattern is reached. /// codegen can generate a runtime error if this pattern is reached.
@ -28,7 +29,26 @@ pub enum Pattern {
UnwrappedOpaque { UnwrappedOpaque {
whole_var: Variable, whole_var: Variable,
opaque: Symbol, opaque: Symbol,
arguments: Vec<(Variable, Loc<Pattern>)>, argument: Box<(Variable, Loc<Pattern>)>,
// The following help us link this opaque reference to the type specified by its
// definition, which we then use during constraint generation. For example
// suppose we have
//
// Id n := [ Id U64 n ]
// strToBool : Str -> Bool
//
// f = \@Id who -> strToBool who
//
// Then `opaque` is "Id", `argument` is "who", but this is not enough for us to
// infer the type of the expression as "Id Str" - we need to link the specialized type of
// the variable "n".
// That's what `specialized_def_type` and `type_arguments` are for; they are specialized
// for the expression from the opaque definition. `type_arguments` is something like
// [(n, fresh1)], and `specialized_def_type` becomes "[ Id U64 fresh1 ]".
specialized_def_type: Box<Type>,
type_arguments: Vec<(Lowercase, Type)>,
lambda_set_variables: Vec<LambdaSet>,
}, },
RecordDestructure { RecordDestructure {
whole_var: Variable, whole_var: Variable,
@ -87,13 +107,12 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
} }
} }
UnwrappedOpaque { UnwrappedOpaque {
opaque, arguments, .. opaque, argument, ..
} => { } => {
symbols.push(*opaque); symbols.push(*opaque);
for (_, nested) in arguments { let (_, nested) = &**argument;
symbols_from_pattern_help(&nested.value, symbols); symbols_from_pattern_help(&nested.value, symbols);
} }
}
RecordDestructure { destructs, .. } => { RecordDestructure { destructs, .. } => {
for destruct in destructs { for destruct in destructs {
// when a record field has a pattern guard, only symbols in the guard are introduced // when a record field has a pattern guard, only symbols in the guard are introduced
@ -171,7 +190,14 @@ pub fn canonicalize_pattern<'a>(
arguments: vec![], arguments: vec![],
} }
} }
OpaqueRef(..) => todo_opaques!(), OpaqueRef(name) => {
// If this opaque ref had an argument, we would be in the "Apply" branch.
let loc_name = Loc::at(region, (*name).into());
env.problem(Problem::RuntimeError(RuntimeError::OpaqueNotApplied(
loc_name,
)));
Pattern::UnsupportedPattern(region)
}
Apply(tag, patterns) => { Apply(tag, patterns) => {
let mut can_patterns = Vec::with_capacity(patterns.len()); let mut can_patterns = Vec::with_capacity(patterns.len());
for loc_pattern in *patterns { for loc_pattern in *patterns {
@ -212,11 +238,34 @@ pub fn canonicalize_pattern<'a>(
} }
OpaqueRef(name) => match scope.lookup_opaque_ref(name, tag.region) { OpaqueRef(name) => match scope.lookup_opaque_ref(name, tag.region) {
Ok(opaque) => Pattern::UnwrappedOpaque { Ok((opaque, opaque_def)) => {
debug_assert!(!can_patterns.is_empty());
if can_patterns.len() > 1 {
env.problem(Problem::RuntimeError(
RuntimeError::OpaqueAppliedToMultipleArgs(region),
));
Pattern::UnsupportedPattern(region)
} else {
let argument = Box::new(can_patterns.pop().unwrap());
let (type_arguments, lambda_set_variables, specialized_def_type) =
freshen_opaque_def(var_store, opaque_def);
output.references.referenced_type_defs.insert(opaque);
output.references.lookups.insert(opaque);
Pattern::UnwrappedOpaque {
whole_var: var_store.fresh(), whole_var: var_store.fresh(),
opaque, opaque,
arguments: can_patterns, argument,
}, specialized_def_type: Box::new(specialized_def_type),
type_arguments,
lambda_set_variables,
}
}
}
Err(runtime_error) => { Err(runtime_error) => {
env.problem(Problem::RuntimeError(runtime_error)); env.problem(Problem::RuntimeError(runtime_error));
@ -557,13 +606,10 @@ fn add_bindings_from_patterns(
} }
} }
UnwrappedOpaque { UnwrappedOpaque {
arguments: loc_args, argument, opaque, ..
opaque,
..
} => { } => {
for (_, loc_arg) in loc_args { let (_, loc_arg) = &**argument;
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer); add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
}
answer.push((*opaque, *region)); answer.push((*opaque, *region));
} }
RecordDestructure { destructs, .. } => { RecordDestructure { destructs, .. } => {

View file

@ -110,7 +110,7 @@ impl Scope {
&self, &self,
opaque_ref: &str, opaque_ref: &str,
lookup_region: Region, lookup_region: Region,
) -> Result<Symbol, RuntimeError> { ) -> Result<(Symbol, &Alias), RuntimeError> {
debug_assert!(opaque_ref.starts_with('$')); debug_assert!(opaque_ref.starts_with('$'));
let opaque = opaque_ref[1..].into(); let opaque = opaque_ref[1..].into();
@ -139,7 +139,7 @@ impl Scope {
Some(alias.header_region()), Some(alias.header_region()),
)), )),
// All is good // All is good
AliasKind::Opaque => Ok(*symbol), AliasKind::Opaque => Ok((*symbol, alias)),
}, },
} }
} }

View file

@ -12,13 +12,12 @@ use roc_can::expr::Expr::{self, *};
use roc_can::expr::{ClosureData, Field, WhenBranch}; use roc_can::expr::{ClosureData, Field, WhenBranch};
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, MutSet, SendMap}; use roc_collections::all::{ImMap, Index, MutSet, SendMap};
use roc_error_macros::todo_opaques;
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::Variable; use roc_types::subs::Variable;
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{AnnotationSource, Category, PReason, Reason, RecordField}; use roc_types::types::{AliasKind, AnnotationSource, Category, PReason, Reason, RecordField};
/// This is for constraining Defs /// This is for constraining Defs
#[derive(Default, Debug)] #[derive(Default, Debug)]
@ -917,7 +916,79 @@ pub fn constrain_expr(
exists(vars, And(arg_cons)) exists(vars, And(arg_cons))
} }
OpaqueRef { .. } => todo_opaques!(), OpaqueRef {
opaque_var,
name,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => {
let (arg_var, arg_loc_expr) = &**argument;
let arg_type = Type::Variable(*arg_var);
let opaque_type = Type::Alias {
symbol: *name,
type_arguments: type_arguments.clone(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_type.clone()),
kind: AliasKind::Opaque,
};
// Constrain the argument
let arg_con = constrain_expr(
env,
arg_loc_expr.region,
&arg_loc_expr.value,
Expected::NoExpectation(arg_type.clone()),
);
// Link the entire wrapped opaque type (with the now-constrained argument) to the
// expected type
let opaque_con = Eq(
opaque_type,
expected.clone(),
Category::OpaqueWrap(*name),
region,
);
// Link the entire wrapped opaque type (with the now-constrained argument) to the type
// variables of the opaque type
// TODO: better expectation here
let link_type_variables_con = Eq(
arg_type,
Expected::NoExpectation((**specialized_def_type).clone()),
Category::OpaqueArg,
arg_loc_expr.region,
);
// Store the entire wrapped opaque type in `opaque_var`
let storage_con = Eq(
Type::Variable(*opaque_var),
expected,
Category::Storage(std::file!(), std::line!()),
region,
);
let mut vars = vec![*arg_var, *opaque_var];
// Also add the fresh variables we created for the type argument and lambda sets
vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));
exists(
vars,
And(vec![
arg_con,
opaque_con,
link_type_variables_con,
storage_con,
]),
)
}
RunLowLevel { args, ret_var, op } => { RunLowLevel { args, ret_var, op } => {
// This is a modified version of what we do for function calls. // This is a modified version of what we do for function calls.

View file

@ -47,7 +47,7 @@ pub fn constrain_imported_values(
// an imported symbol can be either an alias or a value // an imported symbol can be either an alias or a value
match import.solved_type { match import.solved_type {
SolvedType::Alias(symbol, _, _, _) if symbol == loc_symbol.value => { SolvedType::Alias(symbol, _, _, _, _) if symbol == loc_symbol.value => {
// do nothing, in the future the alias definitions should not be in the list of imported values // do nothing, in the future the alias definitions should not be in the list of imported values
} }
_ => { _ => {

View file

@ -5,12 +5,11 @@ use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, RecordDestruct}; use roc_can::pattern::{DestructType, RecordDestruct};
use roc_collections::all::{Index, SendMap}; use roc_collections::all::{Index, SendMap};
use roc_error_macros::todo_opaques;
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::Variable; use roc_types::subs::Variable;
use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type}; use roc_types::types::{AliasKind, Category, PReason, PatternCategory, Reason, RecordField, Type};
#[derive(Default)] #[derive(Default)]
pub struct PatternState { pub struct PatternState {
@ -118,7 +117,35 @@ fn headers_from_annotation_help(
_ => false, _ => false,
}, },
UnwrappedOpaque { .. } => todo_opaques!(), UnwrappedOpaque {
whole_var: _,
opaque,
argument,
specialized_def_type: _,
type_arguments: pat_type_arguments,
lambda_set_variables: pat_lambda_set_variables,
} => match &annotation.value {
Type::Alias {
symbol,
kind: AliasKind::Opaque,
actual,
type_arguments,
lambda_set_variables,
} if symbol == opaque
&& type_arguments.len() == pat_type_arguments.len()
&& lambda_set_variables.len() == pat_lambda_set_variables.len() =>
{
headers.insert(*opaque, annotation.clone());
let (_, argument_pat) = &**argument;
headers_from_annotation_help(
&argument_pat.value,
&Loc::at(annotation.region, (**actual).clone()),
headers,
)
}
_ => false,
},
} }
} }
@ -393,18 +420,21 @@ pub fn constrain_pattern(
constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state); constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state);
} }
let pat_category = PatternCategory::Ctor(tag_name.clone());
let whole_con = Constraint::Present( let whole_con = Constraint::Present(
expected.clone().get_type(), expected.clone().get_type(),
PresenceConstraint::IncludesTag(tag_name.clone(), argument_types.clone()), PresenceConstraint::IncludesTag(
tag_name.clone(),
argument_types.clone(),
region,
pat_category.clone(),
),
); );
let tag_con = Constraint::Present( let tag_con = Constraint::Present(
Type::Variable(*whole_var), Type::Variable(*whole_var),
PresenceConstraint::Pattern( PresenceConstraint::Pattern(region, pat_category, expected),
region,
PatternCategory::Ctor(tag_name.clone()),
expected,
),
); );
state.vars.push(*whole_var); state.vars.push(*whole_var);
@ -413,6 +443,76 @@ pub fn constrain_pattern(
state.constraints.push(tag_con); state.constraints.push(tag_con);
} }
UnwrappedOpaque { .. } => todo_opaques!(), UnwrappedOpaque {
whole_var,
opaque,
argument,
specialized_def_type,
type_arguments,
lambda_set_variables,
} => {
// Suppose we are constraining the pattern \@Id who, where Id n := [ Id U64 n ]
let (arg_pattern_var, loc_arg_pattern) = &**argument;
let arg_pattern_type = Type::Variable(*arg_pattern_var);
let opaque_type = Type::Alias {
symbol: *opaque,
type_arguments: type_arguments.clone(),
lambda_set_variables: lambda_set_variables.clone(),
actual: Box::new(arg_pattern_type.clone()),
kind: AliasKind::Opaque,
};
// First, add a constraint for the argument "who"
let arg_pattern_expected = PExpected::NoExpectation(arg_pattern_type.clone());
constrain_pattern(
env,
&loc_arg_pattern.value,
loc_arg_pattern.region,
arg_pattern_expected,
state,
);
// Next, link `whole_var` to the opaque type of "@Id who"
let whole_con = Constraint::Eq(
Type::Variable(*whole_var),
Expected::NoExpectation(opaque_type),
Category::Storage(std::file!(), std::line!()),
region,
);
// Link the entire wrapped opaque type (with the now-constrained argument) to the type
// variables of the opaque type
// TODO: better expectation here
let link_type_variables_con = Constraint::Eq(
(**specialized_def_type).clone(),
Expected::NoExpectation(arg_pattern_type),
Category::OpaqueWrap(*opaque),
loc_arg_pattern.region,
);
// Next, link `whole_var` (the type of "@Id who") to the expected type
let opaque_pattern_con = Constraint::Present(
Type::Variable(*whole_var),
PresenceConstraint::Pattern(region, PatternCategory::Opaque(*opaque), expected),
);
state
.vars
.extend_from_slice(&[*arg_pattern_var, *whole_var]);
// Also add the fresh variables we created for the type argument and lambda sets
state.vars.extend(type_arguments.iter().map(|(_, t)| {
t.expect_variable("all type arguments should be fresh variables here")
}));
state.vars.extend(lambda_set_variables.iter().map(|v| {
v.0.expect_variable("all lambda sets should be fresh variables here")
}));
state.constraints.extend_from_slice(&[
whole_con,
link_type_variables_con,
opaque_pattern_con,
]);
}
} }
} }

View file

@ -0,0 +1,12 @@
[package]
name = "roc_exhaustive"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_std = { path = "../../roc_std", default-features = false }

View file

@ -0,0 +1,440 @@
use roc_collections::all::{Index, MutMap};
use roc_module::ident::{Lowercase, TagIdIntType, TagName};
use roc_region::all::Region;
use roc_std::RocDec;
use self::Pattern::*;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Union {
pub alternatives: Vec<Ctor>,
pub render_as: RenderAs,
}
impl Union {
pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self {
let alternatives = vec![Ctor {
name: tag_name,
tag_id: TagId(0),
arity,
}];
Union {
alternatives,
render_as: RenderAs::Tag,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum RenderAs {
Tag,
Opaque,
Record(Vec<Lowercase>),
Guard,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub struct TagId(pub TagIdIntType);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ctor {
pub name: TagName,
pub tag_id: TagId,
pub arity: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Anything,
Literal(Literal),
Ctor(Union, TagId, std::vec::Vec<Pattern>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
Int(i128),
U128(u128),
Bit(bool),
Byte(u8),
Float(u64),
Decimal(RocDec),
Str(Box<str>),
}
/// Error
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Incomplete(Region, Context, Vec<Pattern>),
Redundant {
overall_region: Region,
branch_region: Region,
index: Index,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum Context {
BadArg,
BadDestruct,
BadCase,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Guard {
HasGuard,
NoGuard,
}
/// Check
pub fn check(
region: Region,
context: Context,
matrix: Vec<Vec<Pattern>>,
) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
let bad_patterns = is_exhaustive(&matrix, 1);
if !bad_patterns.is_empty() {
// TODO i suspect this is like a concat in in practice? code below can panic
// if this debug_assert! ever fails, the theory is disproven
debug_assert!(bad_patterns.iter().map(|v| v.len()).sum::<usize>() == bad_patterns.len());
let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect();
errors.push(Error::Incomplete(region, context, heads));
return Err(errors);
}
Ok(())
}
/// EXHAUSTIVE PATTERNS
/// INVARIANTS:
///
/// The initial rows "matrix" are all of length 1
/// The initial count of items per row "n" is also 1
/// The resulting rows are examples of missing patterns
fn is_exhaustive(matrix: &RefPatternMatrix, n: usize) -> PatternMatrix {
if matrix.is_empty() {
vec![std::iter::repeat(Anything).take(n).collect()]
} else if n == 0 {
vec![]
} else {
let ctors = collect_ctors(matrix);
let num_seen = ctors.len();
if num_seen == 0 {
let new_matrix: Vec<_> = matrix
.iter()
.filter_map(|row| specialize_row_by_anything(row))
.collect();
let mut rest = is_exhaustive(&new_matrix, n - 1);
for row in rest.iter_mut() {
row.push(Anything);
}
rest
} else {
let alts = ctors.iter().next().unwrap().1;
let alt_list = &alts.alternatives;
let num_alts = alt_list.len();
if num_seen < num_alts {
let new_matrix: Vec<_> = matrix
.iter()
.filter_map(|row| specialize_row_by_anything(row))
.collect();
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, n - 1);
let last: _ = alt_list
.iter()
.filter_map(|r| is_missing(alts.clone(), &ctors, r));
let mut result = Vec::new();
for last_option in last {
for mut row in rest.clone() {
row.push(last_option.clone());
result.push(row);
}
}
result
} else {
let is_alt_exhaustive = |Ctor { arity, tag_id, .. }| {
let new_matrix: Vec<_> = matrix
.iter()
.filter_map(|r| specialize_row_by_ctor(tag_id, arity, r))
.collect();
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, arity + n - 1);
let mut result = Vec::with_capacity(rest.len());
for row in rest {
result.push(recover_ctor(alts.clone(), tag_id, arity, row));
}
result
};
alt_list
.iter()
.cloned()
.map(is_alt_exhaustive)
.flatten()
.collect()
}
}
}
}
fn is_missing<T>(union: Union, ctors: &MutMap<TagId, T>, ctor: &Ctor) -> Option<Pattern> {
let Ctor { arity, tag_id, .. } = ctor;
if ctors.contains_key(tag_id) {
None
} else {
let anythings = std::iter::repeat(Anything).take(*arity).collect();
Some(Pattern::Ctor(union, *tag_id, anythings))
}
}
fn recover_ctor(
union: Union,
tag_id: TagId,
arity: usize,
mut patterns: Vec<Pattern>,
) -> Vec<Pattern> {
let mut rest = patterns.split_off(arity);
let args = patterns;
rest.push(Ctor(union, tag_id, args));
rest
}
/// Check if a new row "vector" is useful given previous rows "matrix"
pub fn is_useful(mut old_matrix: PatternMatrix, mut vector: Row) -> bool {
let mut matrix = Vec::with_capacity(old_matrix.len());
// this loop ping-pongs the rows between old_matrix and matrix
'outer: loop {
match vector.pop() {
_ if old_matrix.is_empty() => {
// No rows are the same as the new vector! The vector is useful!
break true;
}
None => {
// There is nothing left in the new vector, but we still have
// rows that match the same things. This is not a useful vector!
break false;
}
Some(first_pattern) => {
// NOTE: if there are bugs in this code, look at the ordering of the row/matrix
match first_pattern {
// keep checking rows that start with this Ctor or Anything
Ctor(_, id, args) => {
specialize_row_by_ctor2(id, args.len(), &mut old_matrix, &mut matrix);
std::mem::swap(&mut old_matrix, &mut matrix);
vector.extend(args);
}
Anything => {
// check if all alternatives appear in matrix
match is_complete(&old_matrix) {
Complete::No => {
// This Anything is useful because some Ctors are missing.
// But what if a previous row has an Anything?
// If so, this one is not useful.
for mut row in old_matrix.drain(..) {
if let Some(Anything) = row.pop() {
matrix.push(row);
}
}
std::mem::swap(&mut old_matrix, &mut matrix);
}
Complete::Yes(alternatives) => {
// All Ctors are covered, so this Anything is not needed for any
// of those. But what if some of those Ctors have subpatterns
// that make them less general? If so, this actually is useful!
for alternative in alternatives {
let Ctor { arity, tag_id, .. } = alternative;
let mut old_matrix = old_matrix.clone();
let mut matrix = vec![];
specialize_row_by_ctor2(
tag_id,
arity,
&mut old_matrix,
&mut matrix,
);
let mut vector = vector.clone();
vector.extend(std::iter::repeat(Anything).take(arity));
if is_useful(matrix, vector) {
break 'outer true;
}
}
break false;
}
}
}
Literal(literal) => {
// keep checking rows that start with this Literal or Anything
for mut row in old_matrix.drain(..) {
let head = row.pop();
let patterns = row;
match head {
Some(Literal(lit)) => {
if lit == literal {
matrix.push(patterns);
} else {
// do nothing
}
}
Some(Anything) => matrix.push(patterns),
Some(Ctor(_, _, _)) => panic!(
r#"Compiler bug! After type checking, constructors and literals should never align in pattern match exhaustiveness checks."#
),
None => panic!(
"Compiler error! Empty matrices should not get specialized."
),
}
}
std::mem::swap(&mut old_matrix, &mut matrix);
}
}
}
}
}
}
/// INVARIANT: (length row == N) ==> (length result == arity + N - 1)
fn specialize_row_by_ctor2(
tag_id: TagId,
arity: usize,
old_matrix: &mut PatternMatrix,
matrix: &mut PatternMatrix,
) {
for mut row in old_matrix.drain(..) {
let head = row.pop();
let mut patterns = row;
match head {
Some(Ctor(_, id, args)) =>
if id == tag_id {
patterns.extend(args);
matrix.push(patterns);
} else {
// do nothing
}
Some(Anything) => {
// TODO order!
patterns.extend(std::iter::repeat(Anything).take(arity));
matrix.push(patterns);
}
Some(Literal(_)) => panic!( "Compiler bug! After type checking, constructors and literal should never align in pattern match exhaustiveness checks."),
None => panic!("Compiler error! Empty matrices should not get specialized."),
}
}
}
/// INVARIANT: (length row == N) ==> (length result == arity + N - 1)
fn specialize_row_by_ctor(tag_id: TagId, arity: usize, row: &RefRow) -> Option<Row> {
let mut row = row.to_vec();
let head = row.pop();
let patterns = row;
match head {
Some(Ctor(_, id, args)) => {
if id == tag_id {
// TODO order!
let mut new_patterns = Vec::new();
new_patterns.extend(args);
new_patterns.extend(patterns);
Some(new_patterns)
} else {
None
}
}
Some(Anything) => {
// TODO order!
let new_patterns = std::iter::repeat(Anything)
.take(arity)
.chain(patterns)
.collect();
Some(new_patterns)
}
Some(Literal(_)) => unreachable!(
r#"Compiler bug! After type checking, a constructor can never align with a literal: that should be a type error!"#
),
None => panic!("Compiler error! Empty matrices should not get specialized."),
}
}
/// INVARIANT: (length row == N) ==> (length result == N-1)
fn specialize_row_by_anything(row: &RefRow) -> Option<Row> {
let mut row = row.to_vec();
match row.pop() {
Some(Anything) => Some(row),
_ => None,
}
}
/// ALL CONSTRUCTORS ARE PRESENT?
pub enum Complete {
Yes(Vec<Ctor>),
No,
}
fn is_complete(matrix: &RefPatternMatrix) -> Complete {
let ctors = collect_ctors(matrix);
let length = ctors.len();
let mut it = ctors.into_iter();
match it.next() {
None => Complete::No,
Some((_, Union { alternatives, .. })) => {
if length == alternatives.len() {
Complete::Yes(alternatives)
} else {
Complete::No
}
}
}
}
/// COLLECT CTORS
type RefPatternMatrix = [Vec<Pattern>];
type PatternMatrix = Vec<Vec<Pattern>>;
type RefRow = [Pattern];
type Row = Vec<Pattern>;
fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap<TagId, Union> {
let mut ctors = MutMap::default();
for row in matrix {
if let Some(Ctor(union, id, _)) = row.get(row.len() - 1) {
ctors.insert(*id, union.clone());
}
}
ctors
}

View file

@ -6,7 +6,7 @@ The user needs to analyse the Wasm module's memory to decode the result.
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout, UnionLayout};
use roc_std::ReferenceCount; use roc_std::ReferenceCount;
use roc_target::TargetInfo; use roc_target::TargetInfo;
@ -42,6 +42,18 @@ pub fn insert_wrapper_for_layout<'a>(
main_fn_index: u32, main_fn_index: u32,
layout: &Layout<'a>, layout: &Layout<'a>,
) { ) {
let mut stack_data_structure = || {
let size = layout.stack_size(TargetInfo::default_wasm32());
if size == 0 {
<() as Wasm32Result>::insert_wrapper(arena, module, wrapper_name, main_fn_index);
} else {
insert_wrapper_metadata(arena, module, wrapper_name);
let mut code_builder = CodeBuilder::new(arena);
build_wrapper_body_stack_memory(&mut code_builder, main_fn_index, size as usize);
module.code.code_builders.push(code_builder);
}
};
match layout { match layout {
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => { Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
i8::insert_wrapper(arena, module, wrapper_name, main_fn_index); i8::insert_wrapper(arena, module, wrapper_name, main_fn_index);
@ -61,14 +73,14 @@ pub fn insert_wrapper_for_layout<'a>(
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
f64::insert_wrapper(arena, module, wrapper_name, main_fn_index); f64::insert_wrapper(arena, module, wrapper_name, main_fn_index);
} }
_ => { Layout::Builtin(Builtin::Bool) => {
// The result is not a Wasm primitive, it's an array of bytes in stack memory. bool::insert_wrapper(arena, module, wrapper_name, main_fn_index);
let size = layout.stack_size(TargetInfo::default_wasm32());
insert_wrapper_metadata(arena, module, wrapper_name);
let mut code_builder = CodeBuilder::new(arena);
build_wrapper_body_stack_memory(&mut code_builder, main_fn_index, size as usize);
module.code.code_builders.push(code_builder);
} }
Layout::Union(UnionLayout::NonRecursive(_)) => stack_data_structure(),
Layout::Union(_) => {
i32::insert_wrapper(arena, module, wrapper_name, main_fn_index);
}
_ => stack_data_structure(),
} }
} }

View file

@ -502,7 +502,7 @@ enum Msg<'a> {
}, },
FinishedAllTypeChecking { FinishedAllTypeChecking {
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
exposed_vars_by_symbol: MutMap<Symbol, Variable>, exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>, exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_values: Vec<Symbol>, exposed_values: Vec<Symbol>,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
@ -2131,7 +2131,7 @@ fn finish(
solved: Solved<Subs>, solved: Solved<Subs>,
exposed_values: Vec<Symbol>, exposed_values: Vec<Symbol>,
exposed_aliases_by_symbol: MutMap<Symbol, Alias>, exposed_aliases_by_symbol: MutMap<Symbol, Alias>,
exposed_vars_by_symbol: MutMap<Symbol, Variable>, exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
dep_idents: MutMap<ModuleId, IdentIds>, dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>, documentation: MutMap<ModuleId, ModuleDocumentation>,
) -> LoadedModule { ) -> LoadedModule {
@ -3102,20 +3102,21 @@ fn run_solve<'a>(
} }
let (solved_subs, solved_env, problems) = let (solved_subs, solved_env, problems) =
roc_solve::module::run_solve(aliases, rigid_variables, constraint, var_store); roc_solve::module::run_solve(rigid_variables, constraint, var_store);
let mut exposed_vars_by_symbol: MutMap<Symbol, Variable> = solved_env.vars_by_symbol.clone(); let exposed_vars_by_symbol: Vec<_> = solved_env
exposed_vars_by_symbol.retain(|k, _| exposed_symbols.contains(k)); .vars_by_symbol()
.filter(|(k, _)| exposed_symbols.contains(k))
.collect();
let solved_types = let solved_types = roc_solve::module::make_solved_types(&solved_subs, &exposed_vars_by_symbol);
roc_solve::module::make_solved_types(&solved_env, &solved_subs, &exposed_vars_by_symbol);
let solved_module = SolvedModule { let solved_module = SolvedModule {
exposed_vars_by_symbol, exposed_vars_by_symbol,
exposed_symbols: exposed_symbols.into_iter().collect::<Vec<_>>(), exposed_symbols: exposed_symbols.into_iter().collect::<Vec<_>>(),
solved_types, solved_types,
problems, problems,
aliases: solved_env.aliases, aliases,
}; };
// Record the final timings // Record the final timings

View file

@ -755,7 +755,7 @@ mod test_load {
err, err,
indoc!( indoc!(
r#" r#"
OPAQUE DECLARED OUTSIDE SCOPE OPAQUE TYPE DECLARED OUTSIDE SCOPE
The unwrapped opaque type Age referenced here: The unwrapped opaque type Age referenced here:
@ -769,7 +769,7 @@ mod test_load {
Note: Opaque types can only be wrapped and unwrapped in the module they are defined in! Note: Opaque types can only be wrapped and unwrapped in the module they are defined in!
OPAQUE DECLARED OUTSIDE SCOPE OPAQUE TYPE DECLARED OUTSIDE SCOPE
The unwrapped opaque type Age referenced here: The unwrapped opaque type Age referenced here:

View file

@ -40,6 +40,8 @@ pub struct Uppercase(IdentStr);
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct ForeignSymbol(IdentStr); pub struct ForeignSymbol(IdentStr);
pub type TagIdIntType = u16;
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TagName { pub enum TagName {
/// Global tags have no module, but tend to be short strings (since they're /// Global tags have no module, but tend to be short strings (since they're

View file

@ -580,6 +580,7 @@ impl IdentIds {
} }
// necessary when the name of a value is changed in the editor // necessary when the name of a value is changed in the editor
// TODO fix when same ident_name is present multiple times, see issue #2548
pub fn update_key( pub fn update_key(
&mut self, &mut self,
old_ident_name: &str, old_ident_name: &str,

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_exhaustive = { path = "../exhaustive" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_types = { path = "../types" } roc_types = { path = "../types" }

View file

@ -1,10 +1,10 @@
use crate::exhaustive::{Ctor, RenderAs, TagId, Union};
use crate::ir::{ use crate::ir::{
BranchInfo, DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern, Procs, Stmt, BranchInfo, DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern, Procs, Stmt,
}; };
use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout}; use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout};
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_exhaustive::{Ctor, RenderAs, TagId, Union};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -83,7 +83,7 @@ enum Test<'a> {
IsCtor { IsCtor {
tag_id: TagIdIntType, tag_id: TagIdIntType,
tag_name: TagName, tag_name: TagName,
union: crate::exhaustive::Union, union: roc_exhaustive::Union,
arguments: Vec<(Pattern<'a>, Layout<'a>)>, arguments: Vec<(Pattern<'a>, Layout<'a>)>,
}, },
IsInt(i128, IntWidth), IsInt(i128, IntWidth),
@ -565,6 +565,25 @@ fn test_at_path<'a>(
union: union.clone(), union: union.clone(),
arguments: arguments.to_vec(), arguments: arguments.to_vec(),
}, },
OpaqueUnwrap { opaque, argument } => {
let union = Union {
render_as: RenderAs::Tag,
alternatives: vec![Ctor {
tag_id: TagId(0),
name: TagName::Private(*opaque),
arity: 1,
}],
};
IsCtor {
tag_id: 0,
tag_name: TagName::Private(*opaque),
union,
arguments: vec![(**argument).clone()],
}
}
BitLiteral { value, .. } => IsBit(*value), BitLiteral { value, .. } => IsBit(*value),
EnumLiteral { tag_id, union, .. } => IsByte { EnumLiteral { tag_id, union, .. } => IsByte {
tag_id: *tag_id as _, tag_id: *tag_id as _,
@ -692,6 +711,33 @@ fn to_relevant_branch_help<'a>(
_ => None, _ => None,
}, },
OpaqueUnwrap { opaque, argument } => match test {
IsCtor {
tag_name: test_opaque_tag_name,
tag_id,
..
} => {
debug_assert_eq!(test_opaque_tag_name, &TagName::Private(opaque));
let (argument, _) = *argument;
let mut new_path = path.to_vec();
new_path.push(PathInstruction {
index: 0,
tag_id: *tag_id,
});
start.push((new_path, argument));
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
_ => None,
},
NewtypeDestructure { NewtypeDestructure {
tag_name, tag_name,
arguments, arguments,
@ -954,6 +1000,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
RecordDestructure(_, _) RecordDestructure(_, _)
| NewtypeDestructure { .. } | NewtypeDestructure { .. }
| AppliedTag { .. } | AppliedTag { .. }
| OpaqueUnwrap { .. }
| BitLiteral { .. } | BitLiteral { .. }
| EnumLiteral { .. } | EnumLiteral { .. }
| IntLiteral(_, _) | IntLiteral(_, _)
@ -1319,6 +1366,7 @@ fn test_to_equality<'a>(
_ => unreachable!("{:?}", (cond_layout, union)), _ => unreachable!("{:?}", (cond_layout, union)),
} }
} }
Test::IsInt(test_int, precision) => { Test::IsInt(test_int, precision) => {
// TODO don't downcast i128 here // TODO don't downcast i128 here
debug_assert!(test_int <= i64::MAX as i128); debug_assert!(test_int <= i64::MAX as i128);

View file

@ -1,66 +1,12 @@
use crate::{ir::DestructType, layout::TagIdIntType}; use crate::ir::DestructType;
use roc_collections::all::{Index, MutMap}; use roc_collections::all::Index;
use roc_module::ident::{Lowercase, TagName}; use roc_exhaustive::{
is_useful, Context, Ctor, Error, Guard, Literal, Pattern, RenderAs, TagId, Union,
};
use roc_module::ident::{TagIdIntType, TagName};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use self::Pattern::*; use Pattern::*;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Union {
pub alternatives: Vec<Ctor>,
pub render_as: RenderAs,
}
impl Union {
pub fn newtype_wrapper(tag_name: TagName, arity: usize) -> Self {
let alternatives = vec![Ctor {
name: tag_name,
tag_id: TagId(0),
arity,
}];
Union {
alternatives,
render_as: RenderAs::Tag,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum RenderAs {
Tag,
Record(Vec<Lowercase>),
Guard,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub struct TagId(pub TagIdIntType);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Ctor {
pub name: TagName,
pub tag_id: TagId,
pub arity: usize,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Pattern {
Anything,
Literal(Literal),
Ctor(Union, TagId, std::vec::Vec<Pattern>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Literal {
Int(i128),
U128(u128),
Bit(bool),
Byte(u8),
Float(u64),
Decimal(RocDec),
Str(Box<str>),
}
fn simplify(pattern: &crate::ir::Pattern) -> Pattern { fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
use crate::ir::Pattern::*; use crate::ir::Pattern::*;
@ -132,36 +78,26 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
arguments.iter().map(|v| simplify(&v.0)).collect(); arguments.iter().map(|v| simplify(&v.0)).collect();
Ctor(union.clone(), TagId(*tag_id), simplified_args) Ctor(union.clone(), TagId(*tag_id), simplified_args)
} }
OpaqueUnwrap { opaque, argument } => {
let (argument, _) = &(**argument);
let tag_id = TagId(0);
let union = Union {
render_as: RenderAs::Opaque,
alternatives: vec![Ctor {
name: TagName::Private(*opaque),
tag_id,
arity: 1,
}],
};
Ctor(union, tag_id, vec![simplify(argument)])
} }
} }
/// Error
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
Incomplete(Region, Context, Vec<Pattern>),
Redundant {
overall_region: Region,
branch_region: Region,
index: Index,
},
} }
#[derive(Clone, Debug, PartialEq)]
pub enum Context {
BadArg,
BadDestruct,
BadCase,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Guard {
HasGuard,
NoGuard,
}
/// Check
pub fn check( pub fn check(
region: Region, region: Region,
patterns: &[(Loc<crate::ir::Pattern>, Guard)], patterns: &[(Loc<crate::ir::Pattern>, Guard)],
@ -186,128 +122,13 @@ pub fn check_patterns<'a>(
match to_nonredundant_rows(region, patterns) { match to_nonredundant_rows(region, patterns) {
Err(err) => errors.push(err), Err(err) => errors.push(err),
Ok(matrix) => { Ok(matrix) => {
let bad_patterns = is_exhaustive(&matrix, 1); if let Err(err) = roc_exhaustive::check(region, context, matrix) {
if !bad_patterns.is_empty() { *errors = err;
// TODO i suspect this is like a concat in in practice? code below can panic
// if this debug_assert! ever fails, the theory is disproven
debug_assert!(
bad_patterns.iter().map(|v| v.len()).sum::<usize>() == bad_patterns.len()
);
let heads = bad_patterns.into_iter().map(|mut v| v.remove(0)).collect();
errors.push(Error::Incomplete(region, context, heads));
} }
} }
} }
} }
/// EXHAUSTIVE PATTERNS
/// INVARIANTS:
///
/// The initial rows "matrix" are all of length 1
/// The initial count of items per row "n" is also 1
/// The resulting rows are examples of missing patterns
fn is_exhaustive(matrix: &PatternMatrix, n: usize) -> PatternMatrix {
if matrix.is_empty() {
vec![std::iter::repeat(Anything).take(n).collect()]
} else if n == 0 {
vec![]
} else {
let ctors = collect_ctors(matrix);
let num_seen = ctors.len();
if num_seen == 0 {
let new_matrix = matrix
.iter()
.filter_map(specialize_row_by_anything)
.collect();
let mut rest = is_exhaustive(&new_matrix, n - 1);
for row in rest.iter_mut() {
row.push(Anything);
}
rest
} else {
let alts = ctors.iter().next().unwrap().1;
let alt_list = &alts.alternatives;
let num_alts = alt_list.len();
if num_seen < num_alts {
let new_matrix = matrix
.iter()
.filter_map(specialize_row_by_anything)
.collect();
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, n - 1);
let last: _ = alt_list
.iter()
.filter_map(|r| is_missing(alts.clone(), &ctors, r));
let mut result = Vec::new();
for last_option in last {
for mut row in rest.clone() {
row.push(last_option.clone());
result.push(row);
}
}
result
} else {
let is_alt_exhaustive = |Ctor { arity, tag_id, .. }| {
let new_matrix = matrix
.iter()
.filter_map(|r| specialize_row_by_ctor(tag_id, arity, r))
.collect();
let rest: Vec<Vec<Pattern>> = is_exhaustive(&new_matrix, arity + n - 1);
let mut result = Vec::with_capacity(rest.len());
for row in rest {
result.push(recover_ctor(alts.clone(), tag_id, arity, row));
}
result
};
alt_list
.iter()
.cloned()
.map(is_alt_exhaustive)
.flatten()
.collect()
}
}
}
}
fn is_missing<T>(union: Union, ctors: &MutMap<TagId, T>, ctor: &Ctor) -> Option<Pattern> {
let Ctor { arity, tag_id, .. } = ctor;
if ctors.contains_key(tag_id) {
None
} else {
let anythings = std::iter::repeat(Anything).take(*arity).collect();
Some(Pattern::Ctor(union, *tag_id, anythings))
}
}
fn recover_ctor(
union: Union,
tag_id: TagId,
arity: usize,
mut patterns: Vec<Pattern>,
) -> Vec<Pattern> {
let mut rest = patterns.split_off(arity);
let args = patterns;
rest.push(Ctor(union, tag_id, args));
rest
}
/// REDUNDANT PATTERNS /// REDUNDANT PATTERNS
/// INVARIANT: Produces a list of rows where (forall row. length row == 1) /// INVARIANT: Produces a list of rows where (forall row. length row == 1)
@ -375,226 +196,3 @@ fn to_nonredundant_rows(
Ok(checked_rows) Ok(checked_rows)
} }
/// Check if a new row "vector" is useful given previous rows "matrix"
fn is_useful(mut old_matrix: PatternMatrix, mut vector: Row) -> bool {
let mut matrix = Vec::with_capacity(old_matrix.len());
// this loop ping-pongs the rows between old_matrix and matrix
'outer: loop {
match vector.pop() {
_ if old_matrix.is_empty() => {
// No rows are the same as the new vector! The vector is useful!
break true;
}
None => {
// There is nothing left in the new vector, but we still have
// rows that match the same things. This is not a useful vector!
break false;
}
Some(first_pattern) => {
// NOTE: if there are bugs in this code, look at the ordering of the row/matrix
match first_pattern {
// keep checking rows that start with this Ctor or Anything
Ctor(_, id, args) => {
specialize_row_by_ctor2(id, args.len(), &mut old_matrix, &mut matrix);
std::mem::swap(&mut old_matrix, &mut matrix);
vector.extend(args);
}
Anything => {
// check if all alternatives appear in matrix
match is_complete(&old_matrix) {
Complete::No => {
// This Anything is useful because some Ctors are missing.
// But what if a previous row has an Anything?
// If so, this one is not useful.
for mut row in old_matrix.drain(..) {
if let Some(Anything) = row.pop() {
matrix.push(row);
}
}
std::mem::swap(&mut old_matrix, &mut matrix);
}
Complete::Yes(alternatives) => {
// All Ctors are covered, so this Anything is not needed for any
// of those. But what if some of those Ctors have subpatterns
// that make them less general? If so, this actually is useful!
for alternative in alternatives {
let Ctor { arity, tag_id, .. } = alternative;
let mut old_matrix = old_matrix.clone();
let mut matrix = vec![];
specialize_row_by_ctor2(
tag_id,
arity,
&mut old_matrix,
&mut matrix,
);
let mut vector = vector.clone();
vector.extend(std::iter::repeat(Anything).take(arity));
if is_useful(matrix, vector) {
break 'outer true;
}
}
break false;
}
}
}
Literal(literal) => {
// keep checking rows that start with this Literal or Anything
for mut row in old_matrix.drain(..) {
let head = row.pop();
let patterns = row;
match head {
Some(Literal(lit)) => {
if lit == literal {
matrix.push(patterns);
} else {
// do nothing
}
}
Some(Anything) => matrix.push(patterns),
Some(Ctor(_, _, _)) => panic!(
r#"Compiler bug! After type checking, constructors and literals should never align in pattern match exhaustiveness checks."#
),
None => panic!(
"Compiler error! Empty matrices should not get specialized."
),
}
}
std::mem::swap(&mut old_matrix, &mut matrix);
}
}
}
}
}
}
/// INVARIANT: (length row == N) ==> (length result == arity + N - 1)
fn specialize_row_by_ctor2(
tag_id: TagId,
arity: usize,
old_matrix: &mut PatternMatrix,
matrix: &mut PatternMatrix,
) {
for mut row in old_matrix.drain(..) {
let head = row.pop();
let mut patterns = row;
match head {
Some(Ctor(_, id, args)) =>
if id == tag_id {
patterns.extend(args);
matrix.push(patterns);
} else {
// do nothing
}
Some(Anything) => {
// TODO order!
patterns.extend(std::iter::repeat(Anything).take(arity));
matrix.push(patterns);
}
Some(Literal(_)) => panic!( "Compiler bug! After type checking, constructors and literal should never align in pattern match exhaustiveness checks."),
None => panic!("Compiler error! Empty matrices should not get specialized."),
}
}
}
/// INVARIANT: (length row == N) ==> (length result == arity + N - 1)
fn specialize_row_by_ctor(tag_id: TagId, arity: usize, row: &Row) -> Option<Row> {
let mut row = row.clone();
let head = row.pop();
let patterns = row;
match head {
Some(Ctor(_, id, args)) => {
if id == tag_id {
// TODO order!
let mut new_patterns = Vec::new();
new_patterns.extend(args);
new_patterns.extend(patterns);
Some(new_patterns)
} else {
None
}
}
Some(Anything) => {
// TODO order!
let new_patterns = std::iter::repeat(Anything)
.take(arity)
.chain(patterns)
.collect();
Some(new_patterns)
}
Some(Literal(_)) => unreachable!(
r#"Compiler bug! After type checking, a constructor can never align with a literal: that should be a type error!"#
),
None => panic!("Compiler error! Empty matrices should not get specialized."),
}
}
/// INVARIANT: (length row == N) ==> (length result == N-1)
fn specialize_row_by_anything(row: &Row) -> Option<Row> {
let mut row = row.clone();
match row.pop() {
Some(Anything) => Some(row),
_ => None,
}
}
/// ALL CONSTRUCTORS ARE PRESENT?
pub enum Complete {
Yes(Vec<Ctor>),
No,
}
fn is_complete(matrix: &PatternMatrix) -> Complete {
let ctors = collect_ctors(matrix);
let length = ctors.len();
let mut it = ctors.into_iter();
match it.next() {
None => Complete::No,
Some((_, Union { alternatives, .. })) => {
if length == alternatives.len() {
Complete::Yes(alternatives)
} else {
Complete::No
}
}
}
}
/// COLLECT CTORS
type RefPatternMatrix = [Vec<Pattern>];
type PatternMatrix = Vec<Vec<Pattern>>;
type Row = Vec<Pattern>;
fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap<TagId, Union> {
let mut ctors = MutMap::default();
for row in matrix {
if let Some(Ctor(union, id, _)) = row.get(row.len() - 1) {
ctors.insert(*id, union.clone());
}
}
ctors
}

View file

@ -1,6 +1,5 @@
#![allow(clippy::manual_map)] #![allow(clippy::manual_map)]
use crate::exhaustive::{Ctor, Guard, RenderAs, TagId};
use crate::layout::{ use crate::layout::{
Builtin, ClosureRepresentation, LambdaSet, Layout, LayoutCache, LayoutProblem, Builtin, ClosureRepresentation, LambdaSet, Layout, LayoutCache, LayoutProblem,
RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant, RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant,
@ -10,7 +9,7 @@ use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::expr::{ClosureData, IntValue}; use roc_can::expr::{ClosureData, IntValue};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_error_macros::todo_opaques; use roc_exhaustive::{Ctor, Guard, RenderAs, TagId};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
@ -44,8 +43,8 @@ roc_error_macros::assert_sizeof_aarch64!(CallType, 5 * 8);
roc_error_macros::assert_sizeof_wasm!(Literal, 24); roc_error_macros::assert_sizeof_wasm!(Literal, 24);
roc_error_macros::assert_sizeof_wasm!(Expr, 56); roc_error_macros::assert_sizeof_wasm!(Expr, 56);
roc_error_macros::assert_sizeof_wasm!(Stmt, 96); roc_error_macros::assert_sizeof_wasm!(Stmt, 120);
roc_error_macros::assert_sizeof_wasm!(ProcLayout, 24); roc_error_macros::assert_sizeof_wasm!(ProcLayout, 32);
roc_error_macros::assert_sizeof_wasm!(Call, 40); roc_error_macros::assert_sizeof_wasm!(Call, 40);
roc_error_macros::assert_sizeof_wasm!(CallType, 32); roc_error_macros::assert_sizeof_wasm!(CallType, 32);
@ -96,7 +95,7 @@ pub enum OptLevel {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum MonoProblem { pub enum MonoProblem {
PatternProblem(crate::exhaustive::Error), PatternProblem(roc_exhaustive::Error),
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -1896,7 +1895,7 @@ fn patterns_to_when<'a>(
// see https://github.com/rtfeldman/roc/issues/786 // see https://github.com/rtfeldman/roc/issues/786
// this must be fixed when moving exhaustiveness checking to the new canonical AST // this must be fixed when moving exhaustiveness checking to the new canonical AST
for (pattern_var, pattern) in patterns.into_iter() { for (pattern_var, pattern) in patterns.into_iter() {
let context = crate::exhaustive::Context::BadArg; let context = roc_exhaustive::Context::BadArg;
let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) { let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) {
Ok((pat, _assignments)) => { Ok((pat, _assignments)) => {
// Don't apply any assignments (e.g. to initialize optional variables) yet. // Don't apply any assignments (e.g. to initialize optional variables) yet.
@ -1922,7 +1921,7 @@ fn patterns_to_when<'a>(
pattern.region, pattern.region,
&[( &[(
Loc::at(pattern.region, mono_pattern), Loc::at(pattern.region, mono_pattern),
crate::exhaustive::Guard::NoGuard, roc_exhaustive::Guard::NoGuard,
)], )],
context, context,
) { ) {
@ -2021,7 +2020,7 @@ fn pattern_to_when<'a>(
(env.unique_symbol(), Loc::at_zero(RuntimeError(error))) (env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
} }
AppliedTag { .. } | RecordDestructure { .. } => { AppliedTag { .. } | RecordDestructure { .. } | UnwrappedOpaque { .. } => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
let wrapped_body = When { let wrapped_body = When {
@ -2039,7 +2038,6 @@ fn pattern_to_when<'a>(
(symbol, Loc::at_zero(wrapped_body)) (symbol, Loc::at_zero(wrapped_body))
} }
UnwrappedOpaque { .. } => todo_opaques!(),
IntLiteral(..) IntLiteral(..)
| NumLiteral(..) | NumLiteral(..)
| FloatLiteral(..) | FloatLiteral(..)
@ -3281,12 +3279,12 @@ pub fn with_hole<'a>(
} }
}; };
let context = crate::exhaustive::Context::BadDestruct; let context = roc_exhaustive::Context::BadDestruct;
match crate::exhaustive::check( match crate::exhaustive::check(
def.loc_pattern.region, def.loc_pattern.region,
&[( &[(
Loc::at(def.loc_pattern.region, mono_pattern.clone()), Loc::at(def.loc_pattern.region, mono_pattern.clone()),
crate::exhaustive::Guard::NoGuard, roc_exhaustive::Guard::NoGuard,
)], )],
context, context,
) { ) {
@ -3433,7 +3431,18 @@ pub fn with_hole<'a>(
} }
} }
OpaqueRef { .. } => todo_opaques!(), OpaqueRef { argument, .. } => {
let (arg_var, loc_arg_expr) = *argument;
with_hole(
env,
loc_arg_expr.value,
arg_var,
procs,
layout_cache,
assigned,
hole,
)
}
Record { Record {
record_var, record_var,
@ -5610,12 +5619,12 @@ pub fn from_can<'a>(
hole, hole,
) )
} else { } else {
let context = crate::exhaustive::Context::BadDestruct; let context = roc_exhaustive::Context::BadDestruct;
match crate::exhaustive::check( match crate::exhaustive::check(
def.loc_pattern.region, def.loc_pattern.region,
&[( &[(
Loc::at(def.loc_pattern.region, mono_pattern.clone()), Loc::at(def.loc_pattern.region, mono_pattern.clone()),
crate::exhaustive::Guard::NoGuard, roc_exhaustive::Guard::NoGuard,
)], )],
context, context,
) { ) {
@ -5741,11 +5750,11 @@ fn to_opt_branches<'a>(
// NOTE exhaustiveness is checked after the construction of all the branches // NOTE exhaustiveness is checked after the construction of all the branches
// In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive. // In contrast to elm (currently), we still do codegen even if a pattern is non-exhaustive.
// So we not only report exhaustiveness errors, but also correct them // So we not only report exhaustiveness errors, but also correct them
let context = crate::exhaustive::Context::BadCase; let context = roc_exhaustive::Context::BadCase;
match crate::exhaustive::check(region, &loc_branches, context) { match crate::exhaustive::check(region, &loc_branches, context) {
Ok(_) => {} Ok(_) => {}
Err(errors) => { Err(errors) => {
use crate::exhaustive::Error::*; use roc_exhaustive::Error::*;
let mut is_not_exhaustive = false; let mut is_not_exhaustive = false;
let mut overlapping_branches = std::vec::Vec::new(); let mut overlapping_branches = std::vec::Vec::new();
@ -6309,6 +6318,10 @@ fn store_pattern_help<'a>(
stmt, stmt,
); );
} }
OpaqueUnwrap { argument, .. } => {
return store_pattern_help(env, procs, layout_cache, &argument.0, outer_symbol, stmt);
}
RecordDestructure(destructs, [_single_field]) => { RecordDestructure(destructs, [_single_field]) => {
for destruct in destructs { for destruct in destructs {
match &destruct.typ { match &destruct.typ {
@ -7644,12 +7657,12 @@ pub enum Pattern<'a> {
BitLiteral { BitLiteral {
value: bool, value: bool,
tag_name: TagName, tag_name: TagName,
union: crate::exhaustive::Union, union: roc_exhaustive::Union,
}, },
EnumLiteral { EnumLiteral {
tag_id: u8, tag_id: u8,
tag_name: TagName, tag_name: TagName,
union: crate::exhaustive::Union, union: roc_exhaustive::Union,
}, },
StrLiteral(Box<str>), StrLiteral(Box<str>),
@ -7663,7 +7676,11 @@ pub enum Pattern<'a> {
tag_id: TagIdIntType, tag_id: TagIdIntType,
arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>, arguments: Vec<'a, (Pattern<'a>, Layout<'a>)>,
layout: UnionLayout<'a>, layout: UnionLayout<'a>,
union: crate::exhaustive::Union, union: roc_exhaustive::Union,
},
OpaqueUnwrap {
opaque: Symbol,
argument: Box<(Pattern<'a>, Layout<'a>)>,
}, },
} }
@ -7800,8 +7817,8 @@ fn from_can_pattern_help<'a>(
arguments, arguments,
.. ..
} => { } => {
use crate::exhaustive::Union;
use crate::layout::UnionVariant::*; use crate::layout::UnionVariant::*;
use roc_exhaustive::Union;
let res_variant = let res_variant =
crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.target_info) crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs, env.target_info)
@ -7866,7 +7883,7 @@ fn from_can_pattern_help<'a>(
}) })
} }
let union = crate::exhaustive::Union { let union = roc_exhaustive::Union {
render_as: RenderAs::Tag, render_as: RenderAs::Tag,
alternatives: ctors, alternatives: ctors,
}; };
@ -7957,7 +7974,7 @@ fn from_can_pattern_help<'a>(
}) })
} }
let union = crate::exhaustive::Union { let union = roc_exhaustive::Union {
render_as: RenderAs::Tag, render_as: RenderAs::Tag,
alternatives: ctors, alternatives: ctors,
}; };
@ -8009,7 +8026,7 @@ fn from_can_pattern_help<'a>(
}) })
} }
let union = crate::exhaustive::Union { let union = roc_exhaustive::Union {
render_as: RenderAs::Tag, render_as: RenderAs::Tag,
alternatives: ctors, alternatives: ctors,
}; };
@ -8052,7 +8069,7 @@ fn from_can_pattern_help<'a>(
arity: fields.len(), arity: fields.len(),
}); });
let union = crate::exhaustive::Union { let union = roc_exhaustive::Union {
render_as: RenderAs::Tag, render_as: RenderAs::Tag,
alternatives: ctors, alternatives: ctors,
}; };
@ -8122,7 +8139,7 @@ fn from_can_pattern_help<'a>(
}); });
} }
let union = crate::exhaustive::Union { let union = roc_exhaustive::Union {
render_as: RenderAs::Tag, render_as: RenderAs::Tag,
alternatives: ctors, alternatives: ctors,
}; };
@ -8177,7 +8194,7 @@ fn from_can_pattern_help<'a>(
arity: other_fields.len() - 1, arity: other_fields.len() - 1,
}); });
let union = crate::exhaustive::Union { let union = roc_exhaustive::Union {
render_as: RenderAs::Tag, render_as: RenderAs::Tag,
alternatives: ctors, alternatives: ctors,
}; };
@ -8218,7 +8235,20 @@ fn from_can_pattern_help<'a>(
Ok(result) Ok(result)
} }
UnwrappedOpaque { .. } => todo_opaques!(), UnwrappedOpaque {
opaque, argument, ..
} => {
let (arg_var, loc_arg_pattern) = &(**argument);
let arg_layout = layout_cache
.from_var(env.arena, *arg_var, env.subs)
.unwrap();
let mono_arg_pattern =
from_can_pattern_help(env, layout_cache, &loc_arg_pattern.value, assignments)?;
Ok(Pattern::OpaqueUnwrap {
opaque: *opaque,
argument: Box::new((mono_arg_pattern, arg_layout)),
})
}
RecordDestructure { RecordDestructure {
whole_var, whole_var,
@ -8389,7 +8419,7 @@ pub fn num_argument_to_int_or_float(
} }
Content::FlexVar(_) | Content::RigidVar(_) => IntOrFloat::Int(IntWidth::I64), // We default (Num *) to I64 Content::FlexVar(_) | Content::RigidVar(_) => IntOrFloat::Int(IntWidth::I64), // We default (Num *) to I64
Content::Alias(Symbol::NUM_INTEGER, args, _) => { Content::Alias(Symbol::NUM_INTEGER, args, _, _) => {
debug_assert!(args.len() == 1); debug_assert!(args.len() == 1);
// Recurse on the second argument // Recurse on the second argument
@ -8397,7 +8427,7 @@ pub fn num_argument_to_int_or_float(
num_argument_to_int_or_float(subs, target_info, var, false) num_argument_to_int_or_float(subs, target_info, var, false)
} }
other @ Content::Alias(symbol, args, _) => { other @ Content::Alias(symbol, args, _, _) => {
if let Some(int_width) = IntWidth::try_from_symbol(*symbol) { if let Some(int_width) = IntWidth::try_from_symbol(*symbol) {
return IntOrFloat::Int(int_width); return IntOrFloat::Int(int_width);
} }

View file

@ -19,10 +19,20 @@ use ven_pretty::{DocAllocator, DocBuilder};
// if your changes cause this number to go down, great! // if your changes cause this number to go down, great!
// please change it to the lower number. // please change it to the lower number.
// if it went up, maybe check that the change is really required // if it went up, maybe check that the change is really required
static_assertions::assert_eq_size!([usize; 3], Builtin); roc_error_macros::assert_sizeof_aarch64!(Builtin, 3 * 8);
static_assertions::assert_eq_size!([usize; 4], Layout); roc_error_macros::assert_sizeof_aarch64!(Layout, 4 * 8);
static_assertions::assert_eq_size!([usize; 3], UnionLayout); roc_error_macros::assert_sizeof_aarch64!(UnionLayout, 3 * 8);
static_assertions::assert_eq_size!([usize; 3], LambdaSet); roc_error_macros::assert_sizeof_aarch64!(LambdaSet, 3 * 8);
roc_error_macros::assert_sizeof_wasm!(Builtin, 3 * 4);
roc_error_macros::assert_sizeof_wasm!(Layout, 6 * 4);
roc_error_macros::assert_sizeof_wasm!(UnionLayout, 3 * 4);
roc_error_macros::assert_sizeof_wasm!(LambdaSet, 3 * 4);
roc_error_macros::assert_sizeof_default!(Builtin, 3 * 8);
roc_error_macros::assert_sizeof_default!(Layout, 4 * 8);
roc_error_macros::assert_sizeof_default!(UnionLayout, 3 * 8);
roc_error_macros::assert_sizeof_default!(LambdaSet, 3 * 8);
pub type TagIdIntType = u16; pub type TagIdIntType = u16;
pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<TagIdIntType>() * 8) as usize; pub const MAX_ENUM_SIZE: usize = (std::mem::size_of::<TagIdIntType>() * 8) as usize;
@ -70,70 +80,70 @@ impl<'a> RawFunctionLayout<'a> {
RangedNumber(typ, _) => Self::from_var(env, typ), RangedNumber(typ, _) => Self::from_var(env, typ),
// Ints // Ints
Alias(Symbol::NUM_I128, args, _) => { Alias(Symbol::NUM_I128, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::i128())) Ok(Self::ZeroArgumentThunk(Layout::i128()))
} }
Alias(Symbol::NUM_I64, args, _) => { Alias(Symbol::NUM_I64, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::i64())) Ok(Self::ZeroArgumentThunk(Layout::i64()))
} }
Alias(Symbol::NUM_I32, args, _) => { Alias(Symbol::NUM_I32, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::i32())) Ok(Self::ZeroArgumentThunk(Layout::i32()))
} }
Alias(Symbol::NUM_I16, args, _) => { Alias(Symbol::NUM_I16, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::i16())) Ok(Self::ZeroArgumentThunk(Layout::i16()))
} }
Alias(Symbol::NUM_I8, args, _) => { Alias(Symbol::NUM_I8, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::i8())) Ok(Self::ZeroArgumentThunk(Layout::i8()))
} }
// I think unsigned and signed use the same layout // I think unsigned and signed use the same layout
Alias(Symbol::NUM_U128, args, _) => { Alias(Symbol::NUM_U128, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::u128())) Ok(Self::ZeroArgumentThunk(Layout::u128()))
} }
Alias(Symbol::NUM_U64, args, _) => { Alias(Symbol::NUM_U64, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::u64())) Ok(Self::ZeroArgumentThunk(Layout::u64()))
} }
Alias(Symbol::NUM_U32, args, _) => { Alias(Symbol::NUM_U32, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::u32())) Ok(Self::ZeroArgumentThunk(Layout::u32()))
} }
Alias(Symbol::NUM_U16, args, _) => { Alias(Symbol::NUM_U16, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::u16())) Ok(Self::ZeroArgumentThunk(Layout::u16()))
} }
Alias(Symbol::NUM_U8, args, _) => { Alias(Symbol::NUM_U8, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::u8())) Ok(Self::ZeroArgumentThunk(Layout::u8()))
} }
// Floats // Floats
Alias(Symbol::NUM_F64, args, _) => { Alias(Symbol::NUM_F64, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::f64())) Ok(Self::ZeroArgumentThunk(Layout::f64()))
} }
Alias(Symbol::NUM_F32, args, _) => { Alias(Symbol::NUM_F32, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::f32())) Ok(Self::ZeroArgumentThunk(Layout::f32()))
} }
// Nat // Nat
Alias(Symbol::NUM_NAT, args, _) => { Alias(Symbol::NUM_NAT, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Self::ZeroArgumentThunk(Layout::usize(env.target_info))) Ok(Self::ZeroArgumentThunk(Layout::usize(env.target_info)))
} }
Alias(symbol, _, _) if symbol.is_builtin() => Ok(Self::ZeroArgumentThunk( Alias(symbol, _, _, _) if symbol.is_builtin() => Ok(Self::ZeroArgumentThunk(
Layout::new_help(env, var, content)?, Layout::new_help(env, var, content)?,
)), )),
Alias(_, _, var) => Self::from_var(env, var), Alias(_, _, var, _) => Self::from_var(env, var),
Error => Err(LayoutProblem::Erroneous), Error => Err(LayoutProblem::Erroneous),
} }
} }
@ -922,7 +932,7 @@ impl<'a> Layout<'a> {
} }
Structure(flat_type) => layout_from_flat_type(env, flat_type), Structure(flat_type) => layout_from_flat_type(env, flat_type),
Alias(symbol, _args, actual_var) => { Alias(symbol, _args, actual_var, _) => {
if let Some(int_width) = IntWidth::try_from_symbol(symbol) { if let Some(int_width) = IntWidth::try_from_symbol(symbol) {
return Ok(Layout::Builtin(Builtin::Int(int_width))); return Ok(Layout::Builtin(Builtin::Int(int_width)));
} }
@ -1980,7 +1990,7 @@ pub fn union_sorted_tags<'a>(
fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> { fn get_recursion_var(subs: &Subs, var: Variable) -> Option<Variable> {
match subs.get_content_without_compacting(var) { match subs.get_content_without_compacting(var) {
Content::Structure(FlatType::RecursiveTagUnion(rec_var, _, _)) => Some(*rec_var), Content::Structure(FlatType::RecursiveTagUnion(rec_var, _, _)) => Some(*rec_var),
Content::Alias(_, _, actual) => get_recursion_var(subs, *actual), Content::Alias(_, _, actual, _) => get_recursion_var(subs, *actual),
_ => None, _ => None,
} }
} }
@ -2623,7 +2633,7 @@ fn layout_from_num_content<'a>(
); );
} }
}, },
Alias(_, _, _) => { Alias(_, _, _, _) => {
todo!("TODO recursively resolve type aliases in num_from_content"); todo!("TODO recursively resolve type aliases in num_from_content");
} }
Structure(_) | RangedNumber(..) => { Structure(_) | RangedNumber(..) => {
@ -2639,7 +2649,7 @@ fn unwrap_num_tag<'a>(
target_info: TargetInfo, target_info: TargetInfo,
) -> Result<Layout<'a>, LayoutProblem> { ) -> Result<Layout<'a>, LayoutProblem> {
match subs.get_content_without_compacting(var) { match subs.get_content_without_compacting(var) {
Content::Alias(Symbol::NUM_INTEGER, args, _) => { Content::Alias(Symbol::NUM_INTEGER, args, _, _) => {
debug_assert!(args.len() == 1); debug_assert!(args.len() == 1);
let precision_var = subs[args.variables().into_iter().next().unwrap()]; let precision_var = subs[args.variables().into_iter().next().unwrap()];
@ -2647,7 +2657,7 @@ fn unwrap_num_tag<'a>(
let precision = subs.get_content_without_compacting(precision_var); let precision = subs.get_content_without_compacting(precision_var);
match precision { match precision {
Content::Alias(symbol, args, _) => { Content::Alias(symbol, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
let layout = match *symbol { let layout = match *symbol {
@ -2675,7 +2685,7 @@ fn unwrap_num_tag<'a>(
_ => unreachable!("not a valid int variant: {:?}", precision), _ => unreachable!("not a valid int variant: {:?}", precision),
} }
} }
Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => { Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _, _) => {
debug_assert!(args.len() == 1); debug_assert!(args.len() == 1);
let precision_var = subs[args.variables().into_iter().next().unwrap()]; let precision_var = subs[args.variables().into_iter().next().unwrap()];
@ -2683,17 +2693,17 @@ fn unwrap_num_tag<'a>(
let precision = subs.get_content_without_compacting(precision_var); let precision = subs.get_content_without_compacting(precision_var);
match precision { match precision {
Content::Alias(Symbol::NUM_BINARY32, args, _) => { Content::Alias(Symbol::NUM_BINARY32, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::f32()) Ok(Layout::f32())
} }
Content::Alias(Symbol::NUM_BINARY64, args, _) => { Content::Alias(Symbol::NUM_BINARY64, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::f64()) Ok(Layout::f64())
} }
Content::Alias(Symbol::NUM_DECIMAL, args, _) => { Content::Alias(Symbol::NUM_DECIMAL, args, _, _) => {
debug_assert!(args.is_empty()); debug_assert!(args.is_empty());
Ok(Layout::Builtin(Builtin::Decimal)) Ok(Layout::Builtin(Builtin::Decimal))

View file

@ -141,7 +141,7 @@ impl FunctionLayout {
Content::RigidVar(_) => Err(UnresolvedVariable(var)), Content::RigidVar(_) => Err(UnresolvedVariable(var)),
Content::RecursionVar { .. } => Err(TypeError(())), Content::RecursionVar { .. } => Err(TypeError(())),
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
@ -249,7 +249,7 @@ impl LambdaSet {
unreachable!("lambda sets cannot currently be recursive") unreachable!("lambda sets cannot currently be recursive")
} }
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual), Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual), Content::RangedNumber(actual, _) => Self::from_var_help(layouts, subs, *actual),
Content::Error => Err(TypeError(())), Content::Error => Err(TypeError(())),
} }
@ -660,7 +660,7 @@ impl Layout {
} }
} }
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type), Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(symbol, _, actual) => { Content::Alias(symbol, _, actual, _) => {
let symbol = *symbol; let symbol = *symbol;
if let Some(int_width) = IntWidth::try_from_symbol(symbol) { if let Some(int_width) = IntWidth::try_from_symbol(symbol) {

View file

@ -711,9 +711,8 @@ where
let cur_indent = INDENT.with(|i| *i.borrow()); let cur_indent = INDENT.with(|i| *i.borrow());
println!( println!(
"@{:>5}:{:<5}: {}{:<50}", "{:>5?}: {}{:<50}",
state.line, state.pos(),
state.column,
&indent_text[..cur_indent * 2], &indent_text[..cur_indent * 2],
self.message self.message
); );
@ -728,9 +727,8 @@ where
}; };
println!( println!(
"@{:>5}:{:<5}: {}{:<50} {:<15} {:?}", "{:<5?}: {}{:<50} {:<15} {:?}",
state.line, state.pos(),
state.column,
&indent_text[..cur_indent * 2], &indent_text[..cur_indent * 2],
self.message, self.message,
format!("{:?}", progress), format!("{:?}", progress),
@ -1217,7 +1215,11 @@ macro_rules! collection_trailing_sep_e {
$indent_problem $indent_problem
) )
), ),
$crate::blankspace::space0_e($min_indent, $indent_problem) $crate::blankspace::space0_e(
// we use min_indent=0 because we want to parse incorrectly indented closing braces
// and later fix these up in the formatter.
0 /* min_indent */,
$indent_problem)
).parse(arena, state)?; ).parse(arena, state)?;
let (_,_, state) = let (_,_, state) =
@ -1404,21 +1406,6 @@ where
} }
} }
pub fn check_indent<'a, TE, E>(min_indent: u32, to_problem: TE) -> impl Parser<'a, (), E>
where
TE: Fn(Position) -> E,
E: 'a,
{
move |_arena, state: State<'a>| {
dbg!(state.indent_column, min_indent);
if state.indent_column < min_indent {
Err((NoProgress, to_problem(state.pos()), state))
} else {
Ok((NoProgress, (), state))
}
}
}
#[macro_export] #[macro_export]
macro_rules! word1_check_indent { macro_rules! word1_check_indent {
($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => { ($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => {

View file

@ -0,0 +1,74 @@
Defs(
[
@0-58 Body(
@0-7 Malformed(
"my_list",
),
@10-58 List(
Collection {
items: [
@16-17 SpaceBefore(
Num(
"0",
),
[
Newline,
],
),
@23-48 SpaceBefore(
List(
Collection {
items: [
@33-34 SpaceBefore(
Var {
module_name: "",
ident: "a",
},
[
Newline,
],
),
@44-45 SpaceBefore(
Var {
module_name: "",
ident: "b",
},
[
Newline,
],
),
],
final_comments: [
Newline,
],
},
),
[
Newline,
],
),
@54-55 SpaceBefore(
Num(
"1",
),
[
Newline,
],
),
],
final_comments: [
Newline,
],
},
),
),
],
@59-61 SpaceBefore(
Num(
"42",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,9 @@
my_list = [
0,
[
a,
b,
],
1,
]
42

View file

@ -0,0 +1,42 @@
Defs(
[
@0-26 Body(
@0-7 Malformed(
"my_list",
),
@10-26 List(
Collection {
items: [
@16-17 SpaceBefore(
Num(
"0",
),
[
Newline,
],
),
@23-24 SpaceBefore(
Num(
"1",
),
[
Newline,
],
),
],
final_comments: [
Newline,
],
},
),
),
],
@27-29 SpaceBefore(
Num(
"42",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,5 @@
my_list = [
0,
1
]
42

View file

@ -0,0 +1,42 @@
Defs(
[
@0-27 Body(
@0-7 Malformed(
"my_list",
),
@10-27 List(
Collection {
items: [
@16-17 SpaceBefore(
Num(
"0",
),
[
Newline,
],
),
@23-24 SpaceBefore(
Num(
"1",
),
[
Newline,
],
),
],
final_comments: [
Newline,
],
},
),
),
],
@28-30 SpaceBefore(
Num(
"42",
),
[
Newline,
],
),
)

View file

@ -0,0 +1,5 @@
my_list = [
0,
1,
]
42

View file

@ -75,19 +75,32 @@ mod test_parse {
let mut base = std::path::PathBuf::from("tests"); let mut base = std::path::PathBuf::from("tests");
base.push("snapshots"); base.push("snapshots");
let pass_or_fail_names = list(&base); let pass_or_fail_names = list(&base);
let mut extra_test_files = std::collections::HashSet::new();
for res in pass_or_fail_names { for res in pass_or_fail_names {
assert!(res == "pass" || res == "fail"); assert!(res == "pass" || res == "fail");
let res_dir = base.join(&res); let res_dir = base.join(&res);
for file in list(&res_dir) { for file in list(&res_dir) {
if let Some(file) = file.strip_suffix(".roc") { let test = if let Some(test) = file.strip_suffix(".roc") {
assert!(tests.contains(format!("{}/{}", &res, file).as_str()), "{}", file); test
} else if let Some(file) = file.strip_suffix(".result-ast") { } else if let Some(test) = file.strip_suffix(".result-ast") {
assert!(tests.contains(format!("{}/{}", &res, file).as_str()), "{}", file); test
} else { } else {
panic!("unexpected test file found: {}", file); panic!("unexpected file found in tests/snapshots: {}", file);
};
let test_name = format!("{}/{}", &res, test);
if !tests.contains(test_name.as_str()) {
extra_test_files.insert(test_name);
} }
} }
} }
if extra_test_files.len() > 0 {
eprintln!("Found extra test files:");
for file in extra_test_files {
eprintln!("{}", file);
}
panic!("Add entries for these in the `snapshot_tests!` macro in test_parse.rs");
}
} }
$( $(
@ -109,6 +122,7 @@ mod test_parse {
snapshot_tests! { snapshot_tests! {
fail/type_argument_no_arrow.expr, fail/type_argument_no_arrow.expr,
fail/type_double_comma.expr, fail/type_double_comma.expr,
pass/list_closing_indent_not_enough.expr,
pass/add_var_with_spaces.expr, pass/add_var_with_spaces.expr,
pass/add_with_spaces.expr, pass/add_with_spaces.expr,
pass/annotated_record_destructure.expr, pass/annotated_record_destructure.expr,
@ -154,6 +168,8 @@ mod test_parse {
pass/int_with_underscore.expr, pass/int_with_underscore.expr,
pass/interface_with_newline.header, pass/interface_with_newline.header,
pass/lowest_float.expr, pass/lowest_float.expr,
pass/list_closing_same_indent_no_trailing_comma.expr,
pass/list_closing_same_indent_with_trailing_comma.expr,
pass/lowest_int.expr, pass/lowest_int.expr,
pass/malformed_ident_due_to_underscore.expr, pass/malformed_ident_due_to_underscore.expr,
pass/malformed_pattern_field_access.expr, // See https://github.com/rtfeldman/roc/issues/399 pass/malformed_pattern_field_access.expr, // See https://github.com/rtfeldman/roc/issues/399
@ -278,15 +294,11 @@ mod test_parse {
let result = func(&input); let result = func(&input);
let actual_result = if should_pass { let actual_result = if should_pass {
eprintln!("The source code for this test did not successfully parse!\n"); result.expect("The source code for this test did not successfully parse!")
result.unwrap()
} else { } else {
eprintln!( result.expect_err(
"The source code for this test successfully parsed, but it was not expected to!\n" "The source code for this test successfully parsed, but it was not expected to!",
); )
result.unwrap_err()
}; };
if std::env::var("ROC_PARSER_SNAPSHOT_TEST_OVERWRITE").is_ok() { if std::env::var("ROC_PARSER_SNAPSHOT_TEST_OVERWRITE").is_ok() {

View file

@ -165,6 +165,8 @@ pub enum RuntimeError {
referenced_region: Region, referenced_region: Region,
imported_region: Region, imported_region: Region,
}, },
OpaqueNotApplied(Loc<Ident>),
OpaqueAppliedToMultipleArgs(Region),
ValueNotExposed { ValueNotExposed {
module_name: ModuleName, module_name: ModuleName,
ident: Ident, ident: Ident,

View file

@ -12,20 +12,16 @@ pub struct SolvedModule {
pub solved_types: MutMap<Symbol, SolvedType>, pub solved_types: MutMap<Symbol, SolvedType>,
pub aliases: MutMap<Symbol, Alias>, pub aliases: MutMap<Symbol, Alias>,
pub exposed_symbols: Vec<Symbol>, pub exposed_symbols: Vec<Symbol>,
pub exposed_vars_by_symbol: MutMap<Symbol, Variable>, pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
pub problems: Vec<solve::TypeError>, pub problems: Vec<solve::TypeError>,
} }
pub fn run_solve( pub fn run_solve(
aliases: MutMap<Symbol, Alias>,
rigid_variables: MutMap<Variable, Lowercase>, rigid_variables: MutMap<Variable, Lowercase>,
constraint: Constraint, constraint: Constraint,
var_store: VarStore, var_store: VarStore,
) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) { ) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) {
let env = solve::Env { let env = solve::Env::default();
vars_by_symbol: MutMap::default(),
aliases,
};
let mut subs = Subs::new_from_varstore(var_store); let mut subs = Subs::new_from_varstore(var_store);
@ -44,35 +40,11 @@ pub fn run_solve(
} }
pub fn make_solved_types( pub fn make_solved_types(
solved_env: &solve::Env,
solved_subs: &Solved<Subs>, solved_subs: &Solved<Subs>,
exposed_vars_by_symbol: &MutMap<Symbol, Variable>, exposed_vars_by_symbol: &[(Symbol, Variable)],
) -> MutMap<Symbol, SolvedType> { ) -> MutMap<Symbol, SolvedType> {
let mut solved_types = MutMap::default(); let mut solved_types = MutMap::default();
for (symbol, alias) in solved_env.aliases.iter() {
let mut args = Vec::with_capacity(alias.type_variables.len());
for loc_named_var in alias.type_variables.iter() {
let (name, var) = &loc_named_var.value;
args.push((name.clone(), SolvedType::new(solved_subs, *var)));
}
let mut lambda_set_variables = Vec::with_capacity(alias.lambda_set_variables.len());
for set in alias.lambda_set_variables.iter() {
lambda_set_variables.push(roc_types::solved_types::SolvedLambdaSet(
SolvedType::from_type(solved_subs, &set.0),
));
}
let solved_type = SolvedType::from_type(solved_subs, &alias.typ);
let solved_alias =
SolvedType::Alias(*symbol, args, lambda_set_variables, Box::new(solved_type));
solved_types.insert(*symbol, solved_alias);
}
// exposed_vars_by_symbol contains the Variables for all the Symbols // exposed_vars_by_symbol contains the Variables for all the Symbols
// this module exposes. We want to convert those into flat SolvedType // this module exposes. We want to convert those into flat SolvedType
// annotations which are decoupled from our Subs, because that's how // annotations which are decoupled from our Subs, because that's how

View file

@ -1,3 +1,4 @@
use bumpalo::Bump;
use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::PresenceConstraint; use roc_can::constraint::PresenceConstraint;
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
@ -11,7 +12,9 @@ use roc_types::subs::{
SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
}; };
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory}; use roc_types::types::{
gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory,
};
use roc_unify::unify::{unify, Mode, Unified::*}; use roc_unify::unify::{unify, Mode, Unified::*};
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
@ -76,8 +79,37 @@ pub enum TypeError {
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Env { pub struct Env {
pub vars_by_symbol: MutMap<Symbol, Variable>, symbols: Vec<Symbol>,
pub aliases: MutMap<Symbol, Alias>, variables: Vec<Variable>,
}
impl Env {
pub fn vars_by_symbol(&self) -> impl Iterator<Item = (Symbol, Variable)> + '_ {
let it1 = self.symbols.iter().copied();
let it2 = self.variables.iter().copied();
it1.zip(it2)
}
fn get_var_by_symbol(&self, symbol: &Symbol) -> Option<Variable> {
self.symbols
.iter()
.position(|s| s == symbol)
.map(|index| self.variables[index])
}
fn insert_symbol_var_if_vacant(&mut self, symbol: Symbol, var: Variable) {
match self.symbols.iter().position(|s| *s == symbol) {
None => {
// symbol is not in vars_by_symbol yet; insert it
self.symbols.push(symbol);
self.variables.push(var);
}
Some(_) => {
// do nothing
}
}
}
} }
const DEFAULT_POOLS: usize = 8; const DEFAULT_POOLS: usize = 8;
@ -161,7 +193,11 @@ pub fn run_in_place(
mark: Mark::NONE.next(), mark: Mark::NONE.next(),
}; };
let rank = Rank::toplevel(); let rank = Rank::toplevel();
let arena = Bump::new();
let state = solve( let state = solve(
&arena,
env, env,
state, state,
rank, rank,
@ -175,10 +211,26 @@ pub fn run_in_place(
state.env state.env
} }
enum After {
CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc<Variable>)>),
}
enum Work<'a> {
Constraint {
env: &'a Env,
rank: Rank,
constraint: &'a Constraint,
after: Option<After>,
},
/// Something to be done after a constraint and all its dependencies are fully solved.
After(After),
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn solve( fn solve(
arena: &Bump,
env: &Env, env: &Env,
state: State, mut state: State,
rank: Rank, rank: Rank,
pools: &mut Pools, pools: &mut Pools,
problems: &mut Vec<TypeError>, problems: &mut Vec<TypeError>,
@ -186,7 +238,38 @@ fn solve(
subs: &mut Subs, subs: &mut Subs,
constraint: &Constraint, constraint: &Constraint,
) -> State { ) -> State {
match constraint { let mut stack = vec![Work::Constraint {
env,
rank,
constraint,
after: None,
}];
while let Some(work_item) = stack.pop() {
let (env, rank, constraint) = match work_item {
Work::After(After::CheckForInfiniteTypes(def_vars)) => {
for (symbol, loc_var) in def_vars.iter() {
check_for_infinite_type(subs, problems, *symbol, *loc_var);
}
// No constraint to be solved
continue;
}
Work::Constraint {
env,
rank,
constraint,
after,
} => {
// Push the `after` on first so that we look at it immediately after finishing all
// the children of this constraint.
if let Some(after) = after {
stack.push(Work::After(after));
}
(env, rank, constraint)
}
};
state = match constraint {
True => state, True => state,
SaveTheEnvironment => { SaveTheEnvironment => {
// NOTE deviation: elm only copies the env into the state on SaveTheEnvironment // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment
@ -264,7 +347,7 @@ fn solve(
} }
} }
Lookup(symbol, expectation, region) => { Lookup(symbol, expectation, region) => {
match env.vars_by_symbol.get(symbol) { match env.get_var_by_symbol(symbol) {
Some(var) => { Some(var) => {
// Deep copy the vars associated with this symbol before unifying them. // Deep copy the vars associated with this symbol before unifying them.
// Otherwise, suppose we have this: // Otherwise, suppose we have this:
@ -287,7 +370,7 @@ fn solve(
// then we copy from that module's Subs into our own. If the value // 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 // is being looked up in this module, then we use our Subs as both
// the source and destination. // the source and destination.
let actual = deep_copy_var(subs, rank, pools, *var); let actual = deep_copy_var(subs, rank, pools, var);
let expected = type_to_var( let expected = type_to_var(
subs, subs,
rank, rank,
@ -333,19 +416,13 @@ fn solve(
} }
} }
And(sub_constraints) => { And(sub_constraints) => {
let mut state = state; for sub_constraint in sub_constraints.iter().rev() {
stack.push(Work::Constraint {
for sub_constraint in sub_constraints.iter() {
state = solve(
env, env,
state,
rank, rank,
pools, constraint: sub_constraint,
problems, after: None,
cached_aliases, })
subs,
sub_constraint,
);
} }
state state
@ -402,19 +479,18 @@ fn solve(
// If the return expression is guaranteed to solve, // If the return expression is guaranteed to solve,
// solve the assignments themselves and move on. // solve the assignments themselves and move on.
solve( stack.push(Work::Constraint {
env, env,
state,
rank, rank,
pools, constraint: &let_con.defs_constraint,
problems, after: None,
cached_aliases, });
subs, state
&let_con.defs_constraint,
)
} }
ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => {
// TODO: make into `WorkItem` with `After`
let state = solve( let state = solve(
arena,
env, env,
state, state,
rank, rank,
@ -426,10 +502,12 @@ fn solve(
); );
// Add a variable for each def to new_vars_by_env. // Add a variable for each def to new_vars_by_env.
let mut local_def_vars = LocalDefVarsVec::with_length(let_con.def_types.len()); let mut local_def_vars =
LocalDefVarsVec::with_length(let_con.def_types.len());
for (symbol, loc_type) in let_con.def_types.iter() { for (symbol, loc_type) in let_con.def_types.iter() {
let var = type_to_var(subs, rank, pools, cached_aliases, &loc_type.value); let var =
type_to_var(subs, rank, pools, cached_aliases, &loc_type.value);
local_def_vars.push(( local_def_vars.push((
*symbol, *symbol,
@ -442,32 +520,17 @@ fn solve(
let mut new_env = env.clone(); let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() { for (symbol, loc_var) in local_def_vars.iter() {
match new_env.vars_by_symbol.entry(*symbol) { new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
Entry::Occupied(_) => {
// keep the existing value
}
Entry::Vacant(vacant) => {
vacant.insert(loc_var.value);
}
}
} }
let new_state = solve( stack.push(Work::Constraint {
&new_env, env: arena.alloc(new_env),
state,
rank, rank,
pools, constraint: ret_con,
problems, after: Some(After::CheckForInfiniteTypes(local_def_vars)),
cached_aliases, });
subs,
ret_con,
);
for (symbol, loc_var) in local_def_vars.iter() { state
check_for_infinite_type(subs, problems, *symbol, *loc_var);
}
new_state
} }
ret_con => { ret_con => {
let rigid_vars = &let_con.rigid_vars; let rigid_vars = &let_con.rigid_vars;
@ -482,17 +545,16 @@ fn solve(
} }
// determine the next pool // determine the next pool
let next_pools;
if next_rank.into_usize() < pools.len() { if next_rank.into_usize() < pools.len() {
next_pools = pools // Nothing to do, we already accounted for the next rank, no need to
// adjust the pools
} else { } else {
// we should be off by one at this point // we should be off by one at this point
debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); debug_assert_eq!(next_rank.into_usize(), 1 + pools.len());
pools.extend_to(next_rank.into_usize()); pools.extend_to(next_rank.into_usize());
next_pools = pools;
} }
let pool: &mut Vec<Variable> = next_pools.get_mut(next_rank); let pool: &mut Vec<Variable> = pools.get_mut(next_rank);
// Replace the contents of this pool with rigid_vars and flex_vars // Replace the contents of this pool with rigid_vars and flex_vars
pool.clear(); pool.clear();
@ -503,13 +565,13 @@ fn solve(
// run solver in next pool // run solver in next pool
// Add a variable for each def to local_def_vars. // Add a variable for each def to local_def_vars.
let mut local_def_vars = LocalDefVarsVec::with_length(let_con.def_types.len()); let mut local_def_vars =
LocalDefVarsVec::with_length(let_con.def_types.len());
for (symbol, loc_type) in let_con.def_types.iter() { for (symbol, loc_type) in let_con.def_types.iter() {
let def_type = &loc_type.value; let def_type = &loc_type.value;
let var = let var = type_to_var(subs, next_rank, pools, cached_aliases, def_type);
type_to_var(subs, next_rank, next_pools, cached_aliases, def_type);
local_def_vars.push(( local_def_vars.push((
*symbol, *symbol,
@ -521,14 +583,16 @@ fn solve(
} }
// Solve the assignments' constraints first. // Solve the assignments' constraints first.
// TODO: make into `WorkItem` with `After`
let State { let State {
env: saved_env, env: saved_env,
mark, mark,
} = solve( } = solve(
arena,
env, env,
state, state,
next_rank, next_rank,
next_pools, pools,
problems, problems,
cached_aliases, cached_aliases,
subs, subs,
@ -541,7 +605,7 @@ fn solve(
debug_assert_eq!( debug_assert_eq!(
{ {
let offenders = next_pools let offenders = pools
.get(next_rank) .get(next_rank)
.iter() .iter()
.filter(|var| { .filter(|var| {
@ -564,9 +628,9 @@ fn solve(
); );
// pop pool // pop pool
generalize(subs, young_mark, visit_mark, next_rank, next_pools); generalize(subs, young_mark, visit_mark, next_rank, pools);
next_pools.get_mut(next_rank).clear(); pools.get_mut(next_rank).clear();
// check that things went well // check that things went well
debug_assert!({ debug_assert!({
@ -591,41 +655,26 @@ fn solve(
let mut new_env = env.clone(); let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() { for (symbol, loc_var) in local_def_vars.iter() {
match new_env.vars_by_symbol.entry(*symbol) { new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
Entry::Occupied(_) => {
// keep the existing value
}
Entry::Vacant(vacant) => {
vacant.insert(loc_var.value);
}
}
} }
// Note that this vars_by_symbol is the one returned by the // Note that this vars_by_symbol is the one returned by the
// previous call to solve() // previous call to solve()
let temp_state = State { let state_for_ret_con = State {
env: saved_env, env: saved_env,
mark: final_mark, mark: final_mark,
}; };
// Now solve the body, using the new vars_by_symbol which includes // Now solve the body, using the new vars_by_symbol which includes
// the assignments' name-to-variable mappings. // the assignments' name-to-variable mappings.
let new_state = solve( stack.push(Work::Constraint {
&new_env, env: arena.alloc(new_env),
temp_state,
rank, rank,
next_pools, constraint: ret_con,
problems, after: Some(After::CheckForInfiniteTypes(local_def_vars)),
cached_aliases, });
subs,
ret_con,
);
for (symbol, loc_var) in local_def_vars.iter() { state_for_ret_con
check_for_infinite_type(subs, problems, *symbol, *loc_var);
}
new_state
} }
} }
} }
@ -651,7 +700,10 @@ fn solve(
} }
} }
} }
Present(typ, PresenceConstraint::IncludesTag(tag_name, tys)) => { Present(
typ,
PresenceConstraint::IncludesTag(tag_name, tys, region, pattern_category),
) => {
let actual = type_to_var(subs, rank, pools, cached_aliases, typ); let actual = type_to_var(subs, rank, pools, cached_aliases, typ);
let tag_ty = Type::TagUnion( let tag_ty = Type::TagUnion(
vec![(tag_name.clone(), tys.clone())], vec![(tag_name.clone(), tys.clone())],
@ -665,17 +717,15 @@ fn solve(
state state
} }
Failure(vars, actual_type, expected_type) => { Failure(vars, actual_type, expected_to_include_type) => {
introduce(subs, rank, pools, &vars); introduce(subs, rank, pools, &vars);
// TODO: do we need a better error type here? let problem = TypeError::BadPattern(
let problem = TypeError::BadExpr( *region,
Region::zero(), pattern_category.clone(),
Category::When, expected_to_include_type,
actual_type, PExpected::NoExpectation(actual_type),
Expected::NoExpectation(expected_type),
); );
problems.push(problem); problems.push(problem);
state state
@ -689,9 +739,13 @@ fn solve(
} }
} }
} }
} };
} }
state
}
#[derive(Debug)]
enum LocalDefVarsVec<T> { enum LocalDefVarsVec<T> {
Stack(arrayvec::ArrayVec<T, 32>), Stack(arrayvec::ArrayVec<T, 32>),
Heap(Vec<T>), Heap(Vec<T>),
@ -914,8 +968,7 @@ fn type_to_variable<'a>(
type_arguments, type_arguments,
actual, actual,
lambda_set_variables, lambda_set_variables,
// TODO(opaques): revisit kind kind,
kind: _,
} => { } => {
if let Some(reserved) = Variable::get_reserved(*symbol) { if let Some(reserved) = Variable::get_reserved(*symbol) {
if rank.is_none() { if rank.is_none() {
@ -941,7 +994,7 @@ fn type_to_variable<'a>(
} else { } else {
type_to_variable(subs, rank, pools, arena, actual) type_to_variable(subs, rank, pools, arena, actual)
}; };
let content = Content::Alias(*symbol, alias_variables, alias_variable); let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind);
register(subs, rank, pools, content) register(subs, rank, pools, content)
} }
@ -963,7 +1016,14 @@ fn type_to_variable<'a>(
); );
let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type); let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type);
let content = Content::Alias(*symbol, alias_variables, alias_variable); // TODO(opaques): I think host-exposed aliases should always be structural
// (when does it make sense to give a host an opaque type?)
let content = Content::Alias(
*symbol,
alias_variables,
alias_variable,
AliasKind::Structural,
);
let result = register(subs, rank, pools, content); let result = register(subs, rank, pools, content);
// We only want to unify the actual_var with the alias once // We only want to unify the actual_var with the alias once
@ -1587,7 +1647,7 @@ fn adjust_rank_content(
} }
} }
Alias(_, args, real_var) => { Alias(_, args, real_var, _) => {
let mut rank = Rank::toplevel(); let mut rank = Rank::toplevel();
for var_index in args.variables() { for var_index in args.variables() {
@ -1732,7 +1792,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
Erroneous(_) => (), Erroneous(_) => (),
}, },
Alias(_, args, var) => { Alias(_, args, var, _) => {
let var = *var; let var = *var;
let args = *args; let args = *args;
@ -1979,7 +2039,7 @@ fn deep_copy_var_help(
copy copy
} }
Alias(symbol, arguments, real_type_var) => { Alias(symbol, arguments, real_type_var, kind) => {
let new_variables = let new_variables =
SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _); SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _);
for (target_index, var_index) in (new_variables.indices()).zip(arguments.variables()) { for (target_index, var_index) in (new_variables.indices()).zip(arguments.variables()) {
@ -1995,7 +2055,7 @@ fn deep_copy_var_help(
let new_real_type_var = let new_real_type_var =
deep_copy_var_help(subs, max_rank, pools, visited, real_type_var); deep_copy_var_help(subs, max_rank, pools, visited, real_type_var);
let new_content = Alias(symbol, new_arguments, new_real_type_var); let new_content = Alias(symbol, new_arguments, new_real_type_var, kind);
subs.set(copy, make_descriptor(new_content)); subs.set(copy, make_descriptor(new_content));

View file

@ -5274,4 +5274,239 @@ mod solve_expr {
r#"{ toI128 : Int * -> I128, toI16 : Int * -> I16, toI32 : Int * -> I32, toI64 : Int * -> I64, toI8 : Int * -> I8, toU128 : Int * -> U128, toU16 : Int * -> U16, toU32 : Int * -> U32, toU64 : Int * -> U64, toU8 : Int * -> U8 }"#, r#"{ toI128 : Int * -> I128, toI16 : Int * -> I16, toI32 : Int * -> I32, toI64 : Int * -> I64, toI8 : Int * -> I8, toU128 : Int * -> U128, toU16 : Int * -> U16, toU32 : Int * -> U32, toU64 : Int * -> U64, toU8 : Int * -> U8 }"#,
) )
} }
#[test]
fn opaque_wrap_infer() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
$Age 21
"#
),
r#"Age"#,
)
}
#[test]
fn opaque_wrap_check() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
a : Age
a = $Age 21
a
"#
),
r#"Age"#,
)
}
#[test]
fn opaque_wrap_polymorphic_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
$Id (Id 21 "sasha")
"#
),
r#"Id Str"#,
)
}
#[test]
fn opaque_wrap_polymorphic_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
a : Id Str
a = $Id (Id 21 "sasha")
a
"#
),
r#"Id Str"#,
)
}
#[test]
fn opaque_wrap_polymorphic_from_multiple_branches_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
condition : Bool
if condition
then $Id (Id 21 (Y "sasha"))
else $Id (Id 21 (Z "felix"))
"#
),
r#"Id [ Y Str, Z Str ]*"#,
)
}
#[test]
fn opaque_wrap_polymorphic_from_multiple_branches_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
condition : Bool
v : Id [ Y Str, Z Str ]
v =
if condition
then $Id (Id 21 (Y "sasha"))
else $Id (Id 21 (Z "felix"))
v
"#
),
r#"Id [ Y Str, Z Str ]"#,
)
}
#[test]
fn opaque_unwrap_infer() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
\$Age n -> n
"#
),
r#"Age -> U32"#,
)
}
#[test]
fn opaque_unwrap_check() {
infer_eq_without_problem(
indoc!(
r#"
Age := U32
v : Age -> U32
v = \$Age n -> n
v
"#
),
r#"Age -> U32"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
\$Id (Id _ n) -> n
"#
),
r#"Id a -> a"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
v : Id a -> a
v = \$Id (Id _ n) -> n
v
"#
),
r#"Id a -> a"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_specialized_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
strToBool : Str -> Bool
\$Id (Id _ n) -> strToBool n
"#
),
r#"Id Str -> Bool"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_specialized_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
strToBool : Str -> Bool
v : Id Str -> Bool
v = \$Id (Id _ n) -> strToBool n
v
"#
),
r#"Id Str -> Bool"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_from_multiple_branches_infer() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
\id ->
when id is
$Id (Id _ A) -> ""
$Id (Id _ B) -> ""
$Id (Id _ (C { a: "" })) -> ""
"#
),
r#"Id [ A, B, C { a : Str }* ] -> Str"#,
)
}
#[test]
fn opaque_unwrap_polymorphic_from_multiple_branches_check() {
infer_eq_without_problem(
indoc!(
r#"
Id n := [ Id U32 n ]
f : Id [ A, B, C { a : Str }e ] -> Str
f = \id ->
when id is
$Id (Id _ A) -> ""
$Id (Id _ B) -> ""
$Id (Id _ (C { a: "" })) -> ""
f
"#
),
r#"Id [ A, B, C { a : Str }e ] -> Str"#,
)
}
} }

View file

@ -1,6 +1,6 @@
use crate::solved_types::{BuiltinAlias, SolvedType}; use crate::solved_types::{BuiltinAlias, SolvedType};
use crate::subs::VarId; use crate::subs::VarId;
use crate::types::RecordField; use crate::types::{AliasKind, RecordField};
use roc_collections::all::{default_hasher, MutMap}; use roc_collections::all::{default_hasher, MutMap};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -367,6 +367,7 @@ pub fn num_type(range: SolvedType) -> SolvedType {
vec![("range".into(), range.clone())], vec![("range".into(), range.clone())],
vec![], vec![],
Box::new(num_alias_content(range)), Box::new(num_alias_content(range)),
AliasKind::Structural,
) )
} }
@ -384,6 +385,7 @@ pub fn floatingpoint_type(range: SolvedType) -> SolvedType {
vec![("range".into(), range.clone())], vec![("range".into(), range.clone())],
vec![], vec![],
Box::new(floatingpoint_alias_content(range)), Box::new(floatingpoint_alias_content(range)),
AliasKind::Structural,
) )
} }
@ -401,6 +403,7 @@ pub fn float_type(range: SolvedType) -> SolvedType {
vec![("range".into(), range.clone())], vec![("range".into(), range.clone())],
vec![], vec![],
Box::new(float_alias_content(range)), Box::new(float_alias_content(range)),
AliasKind::Structural,
) )
} }
@ -418,6 +421,7 @@ pub fn f64_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(f64_alias_content()), Box::new(f64_alias_content()),
AliasKind::Structural,
) )
} }
@ -435,6 +439,7 @@ pub fn f32_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(f32_alias_content()), Box::new(f32_alias_content()),
AliasKind::Structural,
) )
} }
@ -452,6 +457,7 @@ pub fn nat_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(nat_alias_content()), Box::new(nat_alias_content()),
AliasKind::Structural,
) )
} }
@ -469,6 +475,7 @@ pub fn i128_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(i128_alias_content()), Box::new(i128_alias_content()),
AliasKind::Structural,
) )
} }
@ -486,6 +493,7 @@ pub fn u128_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(u128_alias_content()), Box::new(u128_alias_content()),
AliasKind::Structural,
) )
} }
@ -503,6 +511,7 @@ pub fn u64_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(u64_alias_content()), Box::new(u64_alias_content()),
AliasKind::Structural,
) )
} }
@ -520,6 +529,7 @@ pub fn i64_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(i64_alias_content()), Box::new(i64_alias_content()),
AliasKind::Structural,
) )
} }
@ -537,6 +547,7 @@ pub fn u32_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(u32_alias_content()), Box::new(u32_alias_content()),
AliasKind::Structural,
) )
} }
@ -554,6 +565,7 @@ pub fn i32_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(i32_alias_content()), Box::new(i32_alias_content()),
AliasKind::Structural,
) )
} }
@ -571,6 +583,7 @@ pub fn u16_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(u16_alias_content()), Box::new(u16_alias_content()),
AliasKind::Structural,
) )
} }
@ -588,6 +601,7 @@ pub fn i16_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(i16_alias_content()), Box::new(i16_alias_content()),
AliasKind::Structural,
) )
} }
@ -600,7 +614,13 @@ fn i16_alias_content() -> SolvedType {
#[inline(always)] #[inline(always)]
pub fn u8_type() -> SolvedType { pub fn u8_type() -> SolvedType {
SolvedType::Alias(Symbol::NUM_U8, vec![], vec![], Box::new(u8_alias_content())) SolvedType::Alias(
Symbol::NUM_U8,
vec![],
vec![],
Box::new(u8_alias_content()),
AliasKind::Structural,
)
} }
#[inline(always)] #[inline(always)]
@ -612,7 +632,13 @@ fn u8_alias_content() -> SolvedType {
#[inline(always)] #[inline(always)]
pub fn i8_type() -> SolvedType { pub fn i8_type() -> SolvedType {
SolvedType::Alias(Symbol::NUM_I8, vec![], vec![], Box::new(i8_alias_content())) SolvedType::Alias(
Symbol::NUM_I8,
vec![],
vec![],
Box::new(i8_alias_content()),
AliasKind::Structural,
)
} }
#[inline(always)] #[inline(always)]
@ -629,6 +655,7 @@ pub fn int_type(range: SolvedType) -> SolvedType {
vec![("range".into(), range.clone())], vec![("range".into(), range.clone())],
vec![], vec![],
Box::new(int_alias_content(range)), Box::new(int_alias_content(range)),
AliasKind::Structural,
) )
} }
@ -646,6 +673,7 @@ pub fn integer_type(range: SolvedType) -> SolvedType {
vec![("range".into(), range.clone())], vec![("range".into(), range.clone())],
vec![], vec![],
Box::new(integer_alias_content(range)), Box::new(integer_alias_content(range)),
AliasKind::Structural,
) )
} }
@ -661,6 +689,7 @@ pub fn binary64_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(binary64_alias_content()), Box::new(binary64_alias_content()),
AliasKind::Structural,
) )
} }
@ -676,6 +705,7 @@ pub fn binary32_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(binary32_alias_content()), Box::new(binary32_alias_content()),
AliasKind::Structural,
) )
} }
@ -691,6 +721,7 @@ pub fn natural_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(natural_alias_content()), Box::new(natural_alias_content()),
AliasKind::Structural,
) )
} }
@ -706,6 +737,7 @@ pub fn signed128_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(signed128_alias_content()), Box::new(signed128_alias_content()),
AliasKind::Structural,
) )
} }
@ -721,6 +753,7 @@ pub fn signed64_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(signed64_alias_content()), Box::new(signed64_alias_content()),
AliasKind::Structural,
) )
} }
@ -736,6 +769,7 @@ pub fn signed32_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(signed32_alias_content()), Box::new(signed32_alias_content()),
AliasKind::Structural,
) )
} }
@ -751,6 +785,7 @@ pub fn signed16_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(signed16_alias_content()), Box::new(signed16_alias_content()),
AliasKind::Structural,
) )
} }
@ -766,6 +801,7 @@ pub fn signed8_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(signed8_alias_content()), Box::new(signed8_alias_content()),
AliasKind::Structural,
) )
} }
@ -781,6 +817,7 @@ pub fn unsigned128_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(unsigned128_alias_content()), Box::new(unsigned128_alias_content()),
AliasKind::Structural,
) )
} }
@ -796,6 +833,7 @@ pub fn unsigned64_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(unsigned64_alias_content()), Box::new(unsigned64_alias_content()),
AliasKind::Structural,
) )
} }
@ -811,6 +849,7 @@ pub fn unsigned32_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(unsigned32_alias_content()), Box::new(unsigned32_alias_content()),
AliasKind::Structural,
) )
} }
@ -826,6 +865,7 @@ pub fn unsigned16_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(unsigned16_alias_content()), Box::new(unsigned16_alias_content()),
AliasKind::Structural,
) )
} }
@ -841,6 +881,7 @@ pub fn unsigned8_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(unsigned8_alias_content()), Box::new(unsigned8_alias_content()),
AliasKind::Structural,
) )
} }
@ -863,6 +904,7 @@ pub fn dec_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(dec_alias_content()), Box::new(dec_alias_content()),
AliasKind::Structural,
) )
} }
@ -878,6 +920,7 @@ pub fn decimal_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(decimal_alias_content()), Box::new(decimal_alias_content()),
AliasKind::Structural,
) )
} }
@ -888,6 +931,7 @@ pub fn bool_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(bool_alias_content()), Box::new(bool_alias_content()),
AliasKind::Structural,
) )
} }
@ -914,15 +958,6 @@ pub fn ordering_type() -> SolvedType {
) )
} }
#[inline(always)]
pub fn pair_type(t1: SolvedType, t2: SolvedType) -> SolvedType {
// [ Pair t1 t2 ]
SolvedType::TagUnion(
vec![(TagName::Global("Pair".into()), vec![t1, t2])],
Box::new(SolvedType::EmptyTagUnion),
)
}
#[inline(always)] #[inline(always)]
pub fn result_type(a: SolvedType, e: SolvedType) -> SolvedType { pub fn result_type(a: SolvedType, e: SolvedType) -> SolvedType {
SolvedType::Alias( SolvedType::Alias(
@ -930,6 +965,7 @@ pub fn result_type(a: SolvedType, e: SolvedType) -> SolvedType {
vec![("ok".into(), a.clone()), ("err".into(), e.clone())], vec![("ok".into(), a.clone()), ("err".into(), e.clone())],
vec![], vec![],
Box::new(result_alias_content(a, e)), Box::new(result_alias_content(a, e)),
AliasKind::Structural,
) )
} }
@ -961,6 +997,7 @@ pub fn str_utf8_problem_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(str_utf8_problem_alias_content()), Box::new(str_utf8_problem_alias_content()),
AliasKind::Structural,
) )
} }
@ -985,6 +1022,7 @@ pub fn str_utf8_byte_problem_type() -> SolvedType {
vec![], vec![],
vec![], vec![],
Box::new(str_utf8_byte_problem_alias_content()), Box::new(str_utf8_byte_problem_alias_content()),
AliasKind::Structural,
) )
} }

View file

@ -197,7 +197,7 @@ fn find_names_needed(
find_names_needed(*ext_var, subs, roots, root_appearances, names_taken); find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
find_names_needed(*rec_var, subs, roots, root_appearances, names_taken); find_names_needed(*rec_var, subs, roots, root_appearances, names_taken);
} }
Alias(_symbol, args, _actual) => { Alias(_symbol, args, _actual, _kind) => {
// only find names for named parameters! // only find names for named parameters!
for var_index in args.into_iter().take(args.len()) { for var_index in args.into_iter().take(args.len()) {
let var = subs[var_index]; let var = subs[var_index];
@ -319,14 +319,14 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
None => buf.push_str(WILDCARD), None => buf.push_str(WILDCARD),
}, },
Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens), Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens),
Alias(symbol, args, _actual) => { Alias(symbol, args, _actual, _kind) => {
let write_parens = parens == Parens::InTypeParam && !args.is_empty(); let write_parens = parens == Parens::InTypeParam && !args.is_empty();
match *symbol { match *symbol {
Symbol::NUM_NUM => { Symbol::NUM_NUM => {
let content = get_single_arg(subs, args); let content = get_single_arg(subs, args);
match *content { match *content {
Alias(nested, args, _actual) => match nested { Alias(nested, args, _actual, _kind) => match nested {
Symbol::NUM_INTEGER => { Symbol::NUM_INTEGER => {
write_integer( write_integer(
env, env,
@ -369,9 +369,9 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
let content = subs.get_content_without_compacting(arg_var); let content = subs.get_content_without_compacting(arg_var);
match content { match content {
Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"), Alias(Symbol::NUM_BINARY32, _, _, _) => buf.push_str("F32"),
Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"), Alias(Symbol::NUM_BINARY64, _, _, _) => buf.push_str("F64"),
Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"), Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"),
_ => write_parens!(write_parens, buf, { _ => write_parens!(write_parens, buf, {
buf.push_str("Float "); buf.push_str("Float ");
write_content(env, content, subs, buf, parens); write_content(env, content, subs, buf, parens);
@ -432,7 +432,7 @@ fn write_integer(
buf, buf,
match content { match content {
$( $(
&Alias($tag, _, _) => { &Alias($tag, _, _, _) => {
buf.push_str($lit) buf.push_str($lit)
}, },
)* )*
@ -755,7 +755,7 @@ pub fn chase_ext_tag_union<'a>(
chase_ext_tag_union(subs, *ext_var, fields) chase_ext_tag_union(subs, *ext_var, fields)
} }
Content::Alias(_, _, var) => chase_ext_tag_union(subs, *var, fields), Content::Alias(_, _, var, _) => chase_ext_tag_union(subs, *var, fields),
content => Err((var, content)), content => Err((var, content)),
} }

View file

@ -60,6 +60,7 @@ pub enum SolvedType {
Vec<(Lowercase, SolvedType)>, Vec<(Lowercase, SolvedType)>,
Vec<SolvedLambdaSet>, Vec<SolvedLambdaSet>,
Box<SolvedType>, Box<SolvedType>,
AliasKind,
), ),
HostExposedAlias { HostExposedAlias {
@ -181,7 +182,7 @@ impl SolvedType {
type_arguments, type_arguments,
lambda_set_variables, lambda_set_variables,
actual: box_type, actual: box_type,
.. kind,
} => { } => {
let solved_type = Self::from_type(solved_subs, box_type); let solved_type = Self::from_type(solved_subs, box_type);
let mut solved_args = Vec::with_capacity(type_arguments.len()); let mut solved_args = Vec::with_capacity(type_arguments.len());
@ -201,6 +202,7 @@ impl SolvedType {
solved_args, solved_args,
solved_lambda_sets, solved_lambda_sets,
Box::new(solved_type), Box::new(solved_type),
*kind,
) )
} }
HostExposedAlias { HostExposedAlias {
@ -257,7 +259,7 @@ impl SolvedType {
} }
RigidVar(name) => SolvedType::Rigid(name.clone()), RigidVar(name) => SolvedType::Rigid(name.clone()),
Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type), Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type),
Alias(symbol, args, actual_var) => { Alias(symbol, args, actual_var, kind) => {
let mut new_args = Vec::with_capacity(args.len()); let mut new_args = Vec::with_capacity(args.len());
for var_index in args.named_type_arguments() { for var_index in args.named_type_arguments() {
@ -283,7 +285,13 @@ impl SolvedType {
let aliased_to = Self::from_var_help(subs, recursion_vars, *actual_var); let aliased_to = Self::from_var_help(subs, recursion_vars, *actual_var);
SolvedType::Alias(*symbol, new_args, solved_lambda_sets, Box::new(aliased_to)) SolvedType::Alias(
*symbol,
new_args,
solved_lambda_sets,
Box::new(aliased_to),
*kind,
)
} }
RangedNumber(typ, _range_vars) => Self::from_var_help(subs, recursion_vars, *typ), RangedNumber(typ, _range_vars) => Self::from_var_help(subs, recursion_vars, *typ),
Error => SolvedType::Error, Error => SolvedType::Error,
@ -536,7 +544,7 @@ pub fn to_type(
Box::new(to_type(ext, free_vars, var_store)), Box::new(to_type(ext, free_vars, var_store)),
) )
} }
Alias(symbol, solved_type_variables, solved_lambda_sets, solved_actual) => { Alias(symbol, solved_type_variables, solved_lambda_sets, solved_actual, kind) => {
let mut type_variables = Vec::with_capacity(solved_type_variables.len()); let mut type_variables = Vec::with_capacity(solved_type_variables.len());
for (lowercase, solved_arg) in solved_type_variables { for (lowercase, solved_arg) in solved_type_variables {
@ -559,8 +567,7 @@ pub fn to_type(
type_arguments: type_variables, type_arguments: type_variables,
lambda_set_variables, lambda_set_variables,
actual: Box::new(actual), actual: Box::new(actual),
// TODO(opaques): revisit when opaques are in the solver kind: *kind,
kind: AliasKind::Structural,
} }
} }
HostExposedAlias { HostExposedAlias {

View file

@ -1,4 +1,4 @@
use crate::types::{name_type_var, ErrorType, Problem, RecordField, TypeExt}; use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt};
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -375,10 +375,14 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
opt_name, opt_name,
} => write!(f, "Recursion({:?}, {:?})", structure, opt_name), } => write!(f, "Recursion({:?}, {:?})", structure, opt_name),
Content::Structure(flat_type) => subs_fmt_flat_type(flat_type, subs, f), Content::Structure(flat_type) => subs_fmt_flat_type(flat_type, subs, f),
Content::Alias(name, arguments, actual) => { Content::Alias(name, arguments, actual, kind) => {
let slice = subs.get_subs_slice(arguments.variables()); let slice = subs.get_subs_slice(arguments.variables());
let wrap = match kind {
AliasKind::Structural => "Alias",
AliasKind::Opaque => "Opaque",
};
write!(f, "Alias({:?}, {:?}, {:?})", name, slice, actual) write!(f, "{}({:?}, {:?}, {:?})", wrap, name, slice, actual)
} }
Content::RangedNumber(typ, range) => { Content::RangedNumber(typ, range) => {
let slice = subs.get_subs_slice(*range); let slice = subs.get_subs_slice(*range);
@ -870,7 +874,12 @@ fn integer_type(
}); });
subs.set_content(signed64, { subs.set_content(signed64, {
Content::Alias(num_signed64, AliasVariables::default(), at_signed64) Content::Alias(
num_signed64,
AliasVariables::default(),
at_signed64,
AliasKind::Structural,
)
}); });
} }
@ -886,7 +895,12 @@ fn integer_type(
let vars = AliasVariables::insert_into_subs(subs, [signed64], []); let vars = AliasVariables::insert_into_subs(subs, [signed64], []);
subs.set_content(integer_signed64, { subs.set_content(integer_signed64, {
Content::Alias(Symbol::NUM_INTEGER, vars, at_signed64) Content::Alias(
Symbol::NUM_INTEGER,
vars,
at_signed64,
AliasKind::Structural,
)
}); });
} }
@ -902,11 +916,21 @@ fn integer_type(
let vars = AliasVariables::insert_into_subs(subs, [integer_signed64], []); let vars = AliasVariables::insert_into_subs(subs, [integer_signed64], []);
subs.set_content(num_integer_signed64, { subs.set_content(num_integer_signed64, {
Content::Alias(Symbol::NUM_NUM, vars, at_num_integer_signed64) Content::Alias(
Symbol::NUM_NUM,
vars,
at_num_integer_signed64,
AliasKind::Structural,
)
}); });
subs.set_content(var_i64, { subs.set_content(var_i64, {
Content::Alias(num_i64, AliasVariables::default(), num_integer_signed64) Content::Alias(
num_i64,
AliasVariables::default(),
num_integer_signed64,
AliasKind::Structural,
)
}); });
} }
} }
@ -1095,7 +1119,12 @@ fn float_type(
}); });
subs.set_content(binary64, { subs.set_content(binary64, {
Content::Alias(num_binary64, AliasVariables::default(), at_binary64) Content::Alias(
num_binary64,
AliasVariables::default(),
at_binary64,
AliasKind::Structural,
)
}); });
} }
@ -1111,7 +1140,12 @@ fn float_type(
let vars = AliasVariables::insert_into_subs(subs, [binary64], []); let vars = AliasVariables::insert_into_subs(subs, [binary64], []);
subs.set_content(float_binary64, { subs.set_content(float_binary64, {
Content::Alias(Symbol::NUM_FLOATINGPOINT, vars, at_binary64) Content::Alias(
Symbol::NUM_FLOATINGPOINT,
vars,
at_binary64,
AliasKind::Structural,
)
}); });
} }
@ -1127,11 +1161,21 @@ fn float_type(
let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []); let vars = AliasVariables::insert_into_subs(subs, [float_binary64], []);
subs.set_content(num_float_binary64, { subs.set_content(num_float_binary64, {
Content::Alias(Symbol::NUM_NUM, vars, at_num_float_binary64) Content::Alias(
Symbol::NUM_NUM,
vars,
at_num_float_binary64,
AliasKind::Structural,
)
}); });
subs.set_content(var_f64, { subs.set_content(var_f64, {
Content::Alias(num_f64, AliasVariables::default(), num_float_binary64) Content::Alias(
num_f64,
AliasVariables::default(),
num_float_binary64,
AliasKind::Structural,
)
}); });
} }
} }
@ -1249,6 +1293,7 @@ impl Subs {
Symbol::BOOL_BOOL, Symbol::BOOL_BOOL,
AliasVariables::default(), AliasVariables::default(),
Variable::BOOL_ENUM, Variable::BOOL_ENUM,
AliasKind::Structural,
) )
}); });
@ -1650,7 +1695,7 @@ pub enum Content {
opt_name: Option<Lowercase>, opt_name: Option<Lowercase>,
}, },
Structure(FlatType), Structure(FlatType),
Alias(Symbol, AliasVariables, Variable), Alias(Symbol, AliasVariables, Variable, AliasKind),
RangedNumber(Variable, VariableSubsSlice), RangedNumber(Variable, VariableSubsSlice),
Error, Error,
} }
@ -2107,7 +2152,7 @@ pub fn is_empty_tag_union(subs: &Subs, mut var: Variable) -> bool {
var = *sub_ext; var = *sub_ext;
} }
Alias(_, _, actual_var) => { Alias(_, _, actual_var, _) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here" // TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var; var = *actual_var;
} }
@ -2323,7 +2368,7 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool {
var = *sub_ext; var = *sub_ext;
} }
Alias(_, _, actual_var) => { Alias(_, _, actual_var, _) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here" // TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var; var = *actual_var;
} }
@ -2400,7 +2445,7 @@ fn occurs(
EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()), EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()),
} }
} }
Alias(_, args, _) => { Alias(_, args, _, _) => {
let mut new_seen = seen.clone(); let mut new_seen = seen.clone();
new_seen.insert(root_var); new_seen.insert(root_var);
@ -2596,7 +2641,7 @@ fn explicit_substitute(
in_var in_var
} }
Alias(symbol, args, actual) => { Alias(symbol, args, actual, kind) => {
for index in args.into_iter() { for index in args.into_iter() {
let var = subs[index]; let var = subs[index];
let new_var = explicit_substitute(subs, from, to, var, seen); let new_var = explicit_substitute(subs, from, to, var, seen);
@ -2605,7 +2650,7 @@ fn explicit_substitute(
let new_actual = explicit_substitute(subs, from, to, actual, seen); let new_actual = explicit_substitute(subs, from, to, actual, seen);
subs.set_content(in_var, Alias(symbol, args, new_actual)); subs.set_content(in_var, Alias(symbol, args, new_actual, kind));
in_var in_var
} }
@ -2667,7 +2712,7 @@ fn get_var_names(
RigidVar(name) => add_name(subs, 0, name, var, RigidVar, taken_names), RigidVar(name) => add_name(subs, 0, name, var, RigidVar, taken_names),
Alias(_, args, _) => args.into_iter().fold(taken_names, |answer, arg_var| { Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| {
get_var_names(subs, subs[arg_var], answer) get_var_names(subs, subs[arg_var], answer)
}), }),
@ -2874,7 +2919,7 @@ fn content_to_err_type(
ErrorType::FlexVar(name) ErrorType::FlexVar(name)
} }
Alias(symbol, args, aliased_to) => { Alias(symbol, args, aliased_to, kind) => {
let err_type = var_to_err_type(subs, state, aliased_to); let err_type = var_to_err_type(subs, state, aliased_to);
let mut err_args = Vec::with_capacity(args.len()); let mut err_args = Vec::with_capacity(args.len());
@ -2887,7 +2932,7 @@ fn content_to_err_type(
err_args.push(arg); err_args.push(arg);
} }
ErrorType::Alias(symbol, err_args, Box::new(err_type)) ErrorType::Alias(symbol, err_args, Box::new(err_type), kind)
} }
RangedNumber(typ, range) => { RangedNumber(typ, range) => {
@ -2968,7 +3013,7 @@ fn flat_type_to_err_type(
err_fields.insert(label, err_record_field); err_fields.insert(label, err_record_field);
} }
match var_to_err_type(subs, state, ext_var).unwrap_alias() { match var_to_err_type(subs, state, ext_var).unwrap_structural_alias() {
ErrorType::Record(sub_fields, sub_ext) => { ErrorType::Record(sub_fields, sub_ext) => {
ErrorType::Record(sub_fields.union(err_fields), sub_ext) ErrorType::Record(sub_fields.union(err_fields), sub_ext)
} }
@ -3002,7 +3047,7 @@ fn flat_type_to_err_type(
err_tags.insert(tag, err_vars); err_tags.insert(tag, err_vars);
} }
match var_to_err_type(subs, state, ext_var).unwrap_alias() { match var_to_err_type(subs, state, ext_var).unwrap_structural_alias() {
ErrorType::TagUnion(sub_tags, sub_ext) => { ErrorType::TagUnion(sub_tags, sub_ext) => {
ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext) ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext)
} }
@ -3030,7 +3075,7 @@ fn flat_type_to_err_type(
err_tags.insert(tag_name, vec![]); err_tags.insert(tag_name, vec![]);
match var_to_err_type(subs, state, ext_var).unwrap_alias() { match var_to_err_type(subs, state, ext_var).unwrap_structural_alias() {
ErrorType::TagUnion(sub_tags, sub_ext) => { ErrorType::TagUnion(sub_tags, sub_ext) => {
ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext) ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext)
} }
@ -3069,7 +3114,7 @@ fn flat_type_to_err_type(
let rec_error_type = Box::new(var_to_err_type(subs, state, rec_var)); let rec_error_type = Box::new(var_to_err_type(subs, state, rec_var));
match var_to_err_type(subs, state, ext_var).unwrap_alias() { match var_to_err_type(subs, state, ext_var).unwrap_structural_alias() {
ErrorType::RecursiveTagUnion(rec_var, sub_tags, sub_ext) => { ErrorType::RecursiveTagUnion(rec_var, sub_tags, sub_ext) => {
debug_assert!(rec_var == rec_error_type); debug_assert!(rec_var == rec_error_type);
ErrorType::RecursiveTagUnion(rec_error_type, sub_tags.union(err_tags), sub_ext) ErrorType::RecursiveTagUnion(rec_error_type, sub_tags.union(err_tags), sub_ext)
@ -3179,7 +3224,7 @@ fn restore_help(subs: &mut Subs, initial: Variable) {
Erroneous(_) => (), Erroneous(_) => (),
}, },
Alias(_, args, var) => { Alias(_, args, var, _) => {
stack.extend(var_slice(args.variables())); stack.extend(var_slice(args.variables()));
stack.push(*var); stack.push(*var);
@ -3343,10 +3388,11 @@ impl StorageSubs {
opt_name: opt_name.clone(), opt_name: opt_name.clone(),
}, },
Structure(flat_type) => Structure(Self::offset_flat_type(offsets, flat_type)), Structure(flat_type) => Structure(Self::offset_flat_type(offsets, flat_type)),
Alias(symbol, alias_variables, actual) => Alias( Alias(symbol, alias_variables, actual, kind) => Alias(
*symbol, *symbol,
Self::offset_alias_variables(offsets, *alias_variables), Self::offset_alias_variables(offsets, *alias_variables),
Self::offset_variable(offsets, *actual), Self::offset_variable(offsets, *actual),
*kind,
), ),
RangedNumber(typ, vars) => RangedNumber( RangedNumber(typ, vars) => RangedNumber(
Self::offset_variable(offsets, *typ), Self::offset_variable(offsets, *typ),
@ -3722,7 +3768,7 @@ fn deep_copy_var_to_help<'a>(
copy copy
} }
Alias(symbol, arguments, real_type_var) => { Alias(symbol, arguments, real_type_var, kind) => {
let new_variables = let new_variables =
SubsSlice::reserve_into_subs(target, arguments.all_variables_len as _); SubsSlice::reserve_into_subs(target, arguments.all_variables_len as _);
for (target_index, var_index) in (new_variables.indices()).zip(arguments.variables()) { for (target_index, var_index) in (new_variables.indices()).zip(arguments.variables()) {
@ -3738,7 +3784,7 @@ fn deep_copy_var_to_help<'a>(
let new_real_type_var = let new_real_type_var =
deep_copy_var_to_help(arena, visited, source, target, max_rank, real_type_var); deep_copy_var_to_help(arena, visited, source, target, max_rank, real_type_var);
let new_content = Alias(symbol, new_arguments, new_real_type_var); let new_content = Alias(symbol, new_arguments, new_real_type_var, kind);
target.set(copy, make_descriptor(new_content)); target.set(copy, make_descriptor(new_content));
@ -3830,7 +3876,7 @@ where
} }
Erroneous(_) | EmptyRecord | EmptyTagUnion => {} Erroneous(_) | EmptyRecord | EmptyTagUnion => {}
}, },
Alias(_, arguments, real_type_var) => { Alias(_, arguments, real_type_var, _) => {
push_var_slice!(arguments.variables()); push_var_slice!(arguments.variables());
stack.push(*real_type_var); stack.push(*real_type_var);
} }

View file

@ -924,6 +924,13 @@ impl Type {
_ => true, _ => true,
} }
} }
pub fn expect_variable(&self, reason: &'static str) -> Variable {
match self {
Type::Variable(v) => *v,
_ => internal_error!(reason),
}
}
} }
fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) { fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
@ -1286,6 +1293,8 @@ pub enum Category {
tag_name: TagName, tag_name: TagName,
args_count: usize, args_count: usize,
}, },
OpaqueWrap(Symbol),
OpaqueArg,
Lambda, Lambda,
Uniqueness, Uniqueness,
ClosureSize, ClosureSize,
@ -1322,6 +1331,7 @@ pub enum PatternCategory {
Set, Set,
Map, Map,
Ctor(TagName), Ctor(TagName),
Opaque(Symbol),
Str, Str,
Num, Num,
Int, Int,
@ -1329,7 +1339,7 @@ pub enum PatternCategory {
Character, Character,
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum AliasKind { pub enum AliasKind {
/// A structural alias is something like /// A structural alias is something like
/// List a : [ Nil, Cons a (List a) ] /// List a : [ Nil, Cons a (List a) ]
@ -1405,7 +1415,7 @@ pub enum ErrorType {
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt), TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt),
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt), RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
Function(Vec<ErrorType>, Box<ErrorType>, Box<ErrorType>), Function(Vec<ErrorType>, Box<ErrorType>, Box<ErrorType>),
Alias(Symbol, Vec<ErrorType>, Box<ErrorType>), Alias(Symbol, Vec<ErrorType>, Box<ErrorType>, AliasKind),
Range(Box<ErrorType>, Vec<ErrorType>), Range(Box<ErrorType>, Vec<ErrorType>),
Error, Error,
} }
@ -1418,9 +1428,9 @@ impl std::fmt::Debug for ErrorType {
} }
impl ErrorType { impl ErrorType {
pub fn unwrap_alias(self) -> ErrorType { pub fn unwrap_structural_alias(self) -> ErrorType {
match self { match self {
ErrorType::Alias(_, _, real) => real.unwrap_alias(), ErrorType::Alias(_, _, real, AliasKind::Structural) => real.unwrap_structural_alias(),
real => real, real => real,
} }
} }
@ -1459,7 +1469,7 @@ impl ErrorType {
capt.add_names(taken); capt.add_names(taken);
ret.add_names(taken); ret.add_names(taken);
} }
Alias(_, ts, t) => { Alias(_, ts, t, _) => {
ts.iter().for_each(|t| { ts.iter().for_each(|t| {
t.add_names(taken); t.add_names(taken);
}); });
@ -1515,7 +1525,7 @@ fn write_error_type_help(
buf.push(')'); buf.push(')');
} }
} }
Alias(Symbol::NUM_NUM, mut arguments, _actual) => { Alias(Symbol::NUM_NUM, mut arguments, _actual, _) => {
debug_assert!(arguments.len() == 1); debug_assert!(arguments.len() == 1);
let argument = arguments.remove(0); let argument = arguments.remove(0);
@ -1633,7 +1643,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
buf.push(')'); buf.push(')');
} }
} }
Alias(Symbol::NUM_NUM, mut arguments, _actual) => { Alias(Symbol::NUM_NUM, mut arguments, _actual, _) => {
debug_assert!(arguments.len() == 1); debug_assert!(arguments.len() == 1);
let argument = arguments.remove(0); let argument = arguments.remove(0);
@ -1660,7 +1670,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
} }
} }
} }
Alias(symbol, arguments, _actual) => { Alias(symbol, arguments, _actual, _) => {
let write_parens = parens == Parens::InTypeParam && !arguments.is_empty(); let write_parens = parens == Parens::InTypeParam && !arguments.is_empty();
if write_parens { if write_parens {
@ -1881,7 +1891,7 @@ pub fn gather_fields_unsorted_iter(
var = *sub_ext; var = *sub_ext;
} }
Alias(_, _, actual_var) => { Alias(_, _, actual_var, _) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here" // TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var; var = *actual_var;
} }
@ -1966,7 +1976,7 @@ pub fn gather_tags_unsorted_iter(
// var = *sub_ext; // var = *sub_ext;
} }
Alias(_, _, actual_var) => { Alias(_, _, actual_var, _) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here" // TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var; var = *actual_var;
} }

View file

@ -6,11 +6,11 @@ use roc_types::subs::{
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice, RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
}; };
use roc_types::types::{ErrorType, Mismatch, RecordField}; use roc_types::types::{AliasKind, ErrorType, Mismatch, RecordField};
macro_rules! mismatch { macro_rules! mismatch {
() => {{ () => {{
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_some() { if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() {
println!( println!(
"Mismatch in {} Line {} Column {}", "Mismatch in {} Line {} Column {}",
file!(), file!(),
@ -205,7 +205,9 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
Structure(flat_type) => { Structure(flat_type) => {
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
} }
Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, *args, *real_var), Alias(symbol, args, real_var, kind) => {
unify_alias(subs, pool, &ctx, *symbol, *args, *real_var, *kind)
}
&RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars), &RangedNumber(typ, range_vars) => unify_ranged_number(subs, pool, &ctx, typ, range_vars),
Error => { Error => {
// Error propagates. Whatever we're comparing it to doesn't matter! // Error propagates. Whatever we're comparing it to doesn't matter!
@ -294,17 +296,26 @@ fn unify_alias(
symbol: Symbol, symbol: Symbol,
args: AliasVariables, args: AliasVariables,
real_var: Variable, real_var: Variable,
kind: AliasKind,
) -> Outcome { ) -> Outcome {
let other_content = &ctx.second_desc.content; let other_content = &ctx.second_desc.content;
let either_is_opaque =
kind == AliasKind::Opaque || matches!(other_content, Alias(_, _, _, AliasKind::Opaque));
match other_content { match other_content {
FlexVar(_) => { FlexVar(_) => {
// Alias wins // Alias wins
merge(subs, ctx, Alias(symbol, args, real_var)) merge(subs, ctx, Alias(symbol, args, real_var, kind))
}
RecursionVar { structure, .. } if !either_is_opaque => {
unify_pool(subs, pool, real_var, *structure, ctx.mode)
} }
RecursionVar { structure, .. } => unify_pool(subs, pool, real_var, *structure, ctx.mode),
RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
Alias(other_symbol, other_args, other_real_var) => { Alias(other_symbol, other_args, other_real_var, _)
// Opaques types are only equal if the opaque symbols are equal!
if !either_is_opaque || symbol == *other_symbol =>
{
if symbol == *other_symbol { if symbol == *other_symbol {
if args.len() == other_args.len() { if args.len() == other_args.len() {
let mut problems = Vec::new(); let mut problems = Vec::new();
@ -334,8 +345,8 @@ fn unify_alias(
unify_pool(subs, pool, real_var, *other_real_var, ctx.mode) unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)
} }
} }
Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
RangedNumber(other_real_var, other_range_vars) => { RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => {
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode);
if outcome.is_empty() { if outcome.is_empty() {
check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode)
@ -344,6 +355,11 @@ fn unify_alias(
} }
} }
Error => merge(subs, ctx, Error), Error => merge(subs, ctx, Error),
other => {
// The type on the left is an alias, but the one on the right is not!
debug_assert!(either_is_opaque);
mismatch!("Cannot unify opaque {:?} with {:?}", symbol, other)
}
} }
} }
@ -409,11 +425,20 @@ fn unify_structure(
// Unify the two flat types // Unify the two flat types
unify_flat_type(subs, pool, ctx, flat_type, other_flat_type) unify_flat_type(subs, pool, ctx, flat_type, other_flat_type)
} }
Alias(_, _, real_var) => { Alias(sym, _, real_var, kind) => match kind {
AliasKind::Structural => {
// NB: not treating this as a presence constraint seems pivotal! I // NB: not treating this as a presence constraint seems pivotal! I
// can't quite figure out why, but it doesn't seem to impact other types. // can't quite figure out why, but it doesn't seem to impact other types.
unify_pool(subs, pool, ctx.first, *real_var, ctx.mode.as_eq()) unify_pool(subs, pool, ctx.first, *real_var, ctx.mode.as_eq())
} }
AliasKind::Opaque => {
mismatch!(
"Cannot unify structure {:?} with opaque {:?}",
&flat_type,
sym
)
}
},
RangedNumber(other_real_var, other_range_vars) => { RangedNumber(other_real_var, other_range_vars) => {
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode);
if outcome.is_empty() { if outcome.is_empty() {
@ -1122,17 +1147,6 @@ fn unify_shared_tags_merge_new(
merge(subs, ctx, Structure(flat_type)) merge(subs, ctx, Structure(flat_type))
} }
/// Is the given variable a structure. Does not consider Attr itself a structure, and instead looks
/// into it.
#[allow(dead_code)]
fn is_structure(var: Variable, subs: &mut Subs) -> bool {
match subs.get_content_without_compacting(var) {
Content::Alias(_, _, actual) => is_structure(*actual, subs),
Content::Structure(_) => true,
_ => false,
}
}
#[inline(always)] #[inline(always)]
fn unify_flat_type( fn unify_flat_type(
subs: &mut Subs, subs: &mut Subs,
@ -1343,7 +1357,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
// If the other is flex, rigid wins! // If the other is flex, rigid wins!
merge(subs, ctx, RigidVar(name.clone())) merge(subs, ctx, RigidVar(name.clone()))
} }
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) | RangedNumber(..) => { RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => {
if !ctx.mode.contains(Mode::RIGID_AS_FLEX) { if !ctx.mode.contains(Mode::RIGID_AS_FLEX) {
// Type mismatch! Rigid can only unify with flex, even if the // Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same. // rigid names are the same.
@ -1377,7 +1391,7 @@ fn unify_flex(
| RigidVar(_) | RigidVar(_)
| RecursionVar { .. } | RecursionVar { .. }
| Structure(_) | Structure(_)
| Alias(_, _, _) | Alias(_, _, _, _)
| RangedNumber(..) => { | RangedNumber(..) => {
// TODO special-case boolean here // TODO special-case boolean here
// In all other cases, if left is flex, defer to right. // In all other cases, if left is flex, defer to right.
@ -1431,7 +1445,15 @@ fn unify_recursion(
}, },
), ),
Alias(_, _, actual) => { Alias(opaque, _, _, AliasKind::Opaque) => {
mismatch!(
"RecursionVar {:?} cannot be equal to opaque {:?}",
ctx.first,
opaque
)
}
Alias(_, _, actual, _) => {
// look at the type the alias stands for // look at the type the alias stands for
unify_pool(subs, pool, ctx.first, *actual, ctx.mode) unify_pool(subs, pool, ctx.first, *actual, ctx.mode)

View file

@ -43,7 +43,7 @@ From roc to render:
- `ed_model` also contains an `EdModule`, which holds the parsed abstract syntax tree (AST). - `ed_model` also contains an `EdModule`, which holds the parsed abstract syntax tree (AST).
- In the `init_model` function: - In the `init_model` function:
+ The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as + The AST is converted into a tree of `MarkupNode`. The different types of `MarkupNode` are similar to the elements/nodes in HTML. A line of roc code is represented as a nested `MarkupNode` containing mostly text `MarkupNode`s. The line `foo = "bar"` is represented as
three text `MarkupNode` representing `foo`, ` = ` and `bar`. Multiple lines of roc code are represented as nested `MarkupNode` that contain other nested `MarkupNode`. three text `MarkupNode`; representing `foo`, ` = ` and `bar`. Multiple lines of roc code are represented as nested `MarkupNode` that contain other nested `MarkupNode`.
+ `CodeLines` holds a `Vec` of `String`, each line of code is a `String`. When saving the file, the content of `CodeLines` is written to disk. + `CodeLines` holds a `Vec` of `String`, each line of code is a `String`. When saving the file, the content of `CodeLines` is written to disk.
+ `GridNodeMap` maps every position of a char of roc code to a `MarkNodeId`, for easy interaction with the caret. + `GridNodeMap` maps every position of a char of roc code to a `MarkNodeId`, for easy interaction with the caret.
- Back in `editor/src/editor/main.rs` we convert the `EdModel` to `RenderedWgpu` by calling `model_to_wgpu`. - Back in `editor/src/editor/main.rs` we convert the `EdModel` to `RenderedWgpu` by calling `model_to_wgpu`.

View file

@ -1,10 +1,7 @@
use crate::ui::text::lines::Lines; use crate::ui::text::lines::Lines;
use crate::ui::text::selection::Selection;
use crate::ui::text::text_pos::TextPos; use crate::ui::text::text_pos::TextPos;
use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::ui_error::UIResult;
use crate::ui::util::slice_get; use crate::ui::util::slice_get;
use crate::ui::util::slice_get_mut;
use std::cmp::Ordering;
use std::fmt; use std::fmt;
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -14,134 +11,11 @@ pub struct CodeLines {
} }
impl CodeLines { impl CodeLines {
pub fn insert_between_line( pub fn from_str(code_str: &str) -> CodeLines {
&mut self, CodeLines {
line_nr: usize, lines: code_str.split('\n').map(|s| s.to_owned()).collect(),
index: usize, nr_of_chars: code_str.len(),
new_str: &str,
) -> UIResult<()> {
let nr_of_lines = self.lines.len();
if line_nr < nr_of_lines {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.insert_str(index, new_str);
} else if line_nr >= self.lines.len() {
for _ in 0..((line_nr - nr_of_lines) + 1) {
self.push_empty_line();
} }
self.insert_between_line(line_nr, index, new_str)?;
} else {
LineInsertionFailed {
line_nr,
nr_of_lines,
}
.fail()?;
}
self.nr_of_chars += new_str.len();
Ok(())
}
pub fn insert_empty_line(&mut self, line_nr: usize) -> UIResult<()> {
if line_nr <= self.lines.len() {
self.lines.insert(line_nr, String::new());
Ok(())
} else {
OutOfBounds {
index: line_nr,
collection_name: "code_lines.lines".to_owned(),
len: self.lines.len(),
}
.fail()
}
}
pub fn push_empty_line(&mut self) {
self.lines.push(String::new())
}
pub fn break_line(&mut self, line_nr: usize, col_nr: usize) -> UIResult<()> {
// clippy prefers this over if-else
match line_nr.cmp(&self.lines.len()) {
Ordering::Less => {
self.insert_empty_line(line_nr + 1)?;
let line_ref = self.lines.get_mut(line_nr).unwrap(); // safe because we checked line_nr
if col_nr < line_ref.len() {
let next_line_str: String = line_ref.drain(col_nr..).collect();
let next_line_ref = self.lines.get_mut(line_nr + 1).unwrap(); // safe because we just added the line
*next_line_ref = next_line_str;
}
Ok(())
}
Ordering::Equal => self.insert_empty_line(line_nr + 1),
Ordering::Greater => OutOfBounds {
index: line_nr,
collection_name: "code_lines.lines".to_owned(),
len: self.lines.len(),
}
.fail(),
}
}
pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
*line_ref = String::new();
Ok(())
}
pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> {
let line_len = self.line_len(line_nr)?;
self.lines.remove(line_nr);
self.nr_of_chars -= line_len;
Ok(())
}
pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.remove(index);
self.nr_of_chars -= 1;
Ok(())
}
pub fn del_range_at_line(
&mut self,
line_nr: usize,
col_range: std::ops::Range<usize>,
) -> UIResult<()> {
let line_ref = slice_get_mut(line_nr, &mut self.lines)?;
line_ref.drain(col_range);
Ok(())
}
pub fn del_selection(&mut self, selection: Selection) -> UIResult<()> {
if selection.is_on_same_line() {
let line_ref = slice_get_mut(selection.start_pos.line, &mut self.lines)?;
line_ref.drain(selection.start_pos.column..selection.end_pos.column);
} else {
// TODO support multiline selections
}
Ok(())
} }
// last column of last line // last column of last line

View file

@ -1,6 +1,4 @@
use roc_ast::lang::core::ast::ASTNodeId;
use roc_ast::lang::core::def::def2::Def2; 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::ed_error::EdResult;
use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::app_update::InputOutcome;
@ -30,24 +28,12 @@ pub fn break_line(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
&& ed_model.code_lines.line_len(new_blank_line_nr).unwrap() == 0) && ed_model.code_lines.line_len(new_blank_line_nr).unwrap() == 0)
{ {
// two blank lines between top level definitions // two blank lines between top level definitions
EdModel::insert_empty_line( EdModel::insert_empty_line(caret_line_nr + 1, &mut ed_model.grid_node_map)?;
caret_line_nr + 1, EdModel::insert_empty_line(caret_line_nr + 2, &mut ed_model.grid_node_map)?;
&mut ed_model.code_lines,
&mut ed_model.grid_node_map,
)?;
EdModel::insert_empty_line(
caret_line_nr + 2,
&mut ed_model.code_lines,
&mut ed_model.grid_node_map,
)?;
// third "empty" line will be filled by the blank // third "empty" line will be filled by the blank
EdModel::insert_empty_line( EdModel::insert_empty_line(caret_line_nr + 3, &mut ed_model.grid_node_map)?;
caret_line_nr + 3,
&mut ed_model.code_lines,
&mut ed_model.grid_node_map,
)?;
insert_new_blank(ed_model, caret_pos, caret_pos.line + 3)?; insert_new_blank(ed_model, caret_pos.line + 3)?;
} }
} }
} }
@ -57,39 +43,25 @@ pub fn break_line(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} }
pub fn insert_new_blank( pub fn insert_new_blank(ed_model: &mut EdModel, insert_on_line_nr: usize) -> EdResult<()> {
ed_model: &mut EdModel, println!(
caret_pos: &TextPos, "{}",
insert_on_line_nr: usize, ed_model.module.ast.ast_to_string(ed_model.module.env.pool)
) -> EdResult<()> { );
// find position of the previous ASTNode to figure out where to add this new Blank ASTNode
let def_mark_node_id = ed_model
.grid_node_map
.get_def_mark_node_id_before_line(insert_on_line_nr, &ed_model.mark_node_pool)?;
let new_line_blank = Def2::Blank; let new_line_blank = Def2::Blank;
let new_line_blank_id = ed_model.module.env.pool.add(new_line_blank); let new_line_blank_id = ed_model.module.env.pool.add(new_line_blank);
let prev_def_mn_id = ed_model let insertion_index = index_of(def_mark_node_id, &ed_model.markup_ids)?;
.grid_node_map
.get_def_mark_node_id_before_line(caret_pos.line + 1, &ed_model.mark_node_pool)?;
let prev_def_mn_id_indx = index_of(prev_def_mn_id, &ed_model.markup_ids)?;
ed_model ed_model
.module .module
.ast .ast
.def_ids .insert_def_at_index(new_line_blank_id, insertion_index);
.insert(prev_def_mn_id_indx, new_line_blank_id);
let blank_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls(
ASTNodeId::ADefId(new_line_blank_id),
None,
2,
));
ed_model
.markup_ids
.insert(prev_def_mn_id_indx + 1, blank_mn_id); // + 1 because first markup node is header
ed_model.insert_all_between_line(
insert_on_line_nr, // one blank line between top level definitions
0,
&[blank_mn_id],
)?;
Ok(()) Ok(())
} }

View file

@ -16,6 +16,7 @@ use roc_ast::lang::env::Env;
use roc_ast::mem_pool::pool_str::PoolStr; use roc_ast::mem_pool::pool_str::PoolStr;
use roc_ast::parse::parse_ast; use roc_ast::parse::parse_ast;
use roc_code_markup::markup::convert::from_ast::ast_to_mark_nodes; use roc_code_markup::markup::convert::from_ast::ast_to_mark_nodes;
use roc_code_markup::markup::nodes;
use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use roc_code_markup::slow_pool::{MarkNodeId, SlowPool};
use roc_load::file::LoadedModule; use roc_load::file::LoadedModule;
use roc_module::symbol::Interns; use roc_module::symbol::Interns;
@ -74,7 +75,8 @@ pub fn init_model<'a>(
)?) )?)
}?; }?;
let mut code_lines = CodeLines::default(); let code_lines =
CodeLines::from_str(&nodes::mark_nodes_to_string(&markup_ids, &mark_node_pool));
let mut grid_node_map = GridNodeMap::default(); let mut grid_node_map = GridNodeMap::default();
let mut line_nr = 0; let mut line_nr = 0;
@ -88,7 +90,6 @@ pub fn init_model<'a>(
&mut col_nr, &mut col_nr,
*mark_node_id, *mark_node_id,
&mut grid_node_map, &mut grid_node_map,
&mut code_lines,
&mark_node_pool, &mark_node_pool,
)? )?
} }

View file

@ -49,6 +49,7 @@ use roc_ast::mem_pool::pool_str::PoolStr;
use roc_ast::solve_type; use roc_ast::solve_type;
use roc_can::expected::Expected; use roc_can::expected::Expected;
use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::attribute::Attributes;
use roc_code_markup::markup::convert::from_ast::ast_to_mark_nodes;
use roc_code_markup::markup::nodes; use roc_code_markup::markup::nodes;
use roc_code_markup::markup::nodes::MarkupNode; use roc_code_markup::markup::nodes::MarkupNode;
use roc_code_markup::markup::nodes::EQUALS; use roc_code_markup::markup::nodes::EQUALS;
@ -187,10 +188,8 @@ impl<'a> EdModel<'a> {
new_str: &str, new_str: &str,
node_id: MarkNodeId, node_id: MarkNodeId,
grid_node_map: &mut GridNodeMap, grid_node_map: &mut GridNodeMap,
code_lines: &mut CodeLines,
) -> UIResult<()> { ) -> UIResult<()> {
grid_node_map.insert_between_line(line_nr, index, new_str.len(), node_id)?; grid_node_map.insert_between_line(line_nr, index, new_str.len(), node_id)
code_lines.insert_between_line(line_nr, index, new_str)
} }
pub fn insert_all_between_line( pub fn insert_all_between_line(
@ -218,9 +217,6 @@ impl<'a> EdModel<'a> {
node_id, node_id,
)?; )?;
self.code_lines
.insert_between_line(curr_line_nr, col_nr, line)?;
curr_line_nr += 1; curr_line_nr += 1;
col_nr = 0; col_nr = 0;
} }
@ -234,9 +230,6 @@ impl<'a> EdModel<'a> {
node_id, node_id,
)?; )?;
self.code_lines
.insert_between_line(line_nr, col_nr, &node_content)?;
col_nr += node_content.len(); col_nr += node_content.len();
} }
} }
@ -249,7 +242,6 @@ impl<'a> EdModel<'a> {
col_nr: &mut usize, col_nr: &mut usize,
mark_node_id: MarkNodeId, mark_node_id: MarkNodeId,
grid_node_map: &mut GridNodeMap, grid_node_map: &mut GridNodeMap,
code_lines: &mut CodeLines,
mark_node_pool: &SlowPool, mark_node_pool: &SlowPool,
) -> UIResult<()> { ) -> UIResult<()> {
let mark_node = mark_node_pool.get(mark_node_id); let mark_node = mark_node_pool.get(mark_node_id);
@ -265,7 +257,6 @@ impl<'a> EdModel<'a> {
col_nr, col_nr,
child_id, child_id,
grid_node_map, grid_node_map,
code_lines,
mark_node_pool, mark_node_pool,
)?; )?;
} }
@ -278,20 +269,19 @@ impl<'a> EdModel<'a> {
&node_content, &node_content,
mark_node_id, mark_node_id,
grid_node_map, grid_node_map,
code_lines,
)?; )?;
*col_nr += node_content.len(); *col_nr += node_content.len();
} }
if node_newlines > 0 { if node_newlines > 0 {
EdModel::break_line(*line_nr, *col_nr, code_lines, grid_node_map)?; EdModel::break_line(*line_nr, *col_nr, grid_node_map)?;
*line_nr += 1; *line_nr += 1;
*col_nr = 0; *col_nr = 0;
for _ in 1..node_newlines { for _ in 1..node_newlines {
EdModel::insert_empty_line(*line_nr, code_lines, grid_node_map)?; EdModel::insert_empty_line(*line_nr, grid_node_map)?;
*line_nr += 1; *line_nr += 1;
*col_nr = 0; *col_nr = 0;
@ -305,40 +295,29 @@ impl<'a> EdModel<'a> {
pub fn break_line( pub fn break_line(
line_nr: usize, line_nr: usize,
col_nr: usize, col_nr: usize,
code_lines: &mut CodeLines,
grid_node_map: &mut GridNodeMap, grid_node_map: &mut GridNodeMap,
) -> UIResult<()> { ) -> UIResult<()> {
code_lines.break_line(line_nr, col_nr)?;
grid_node_map.break_line(line_nr, col_nr) grid_node_map.break_line(line_nr, col_nr)
} }
pub fn insert_empty_line( pub fn insert_empty_line(line_nr: usize, grid_node_map: &mut GridNodeMap) -> UIResult<()> {
line_nr: usize,
code_lines: &mut CodeLines,
grid_node_map: &mut GridNodeMap,
) -> UIResult<()> {
code_lines.insert_empty_line(line_nr)?;
grid_node_map.insert_empty_line(line_nr) grid_node_map.insert_empty_line(line_nr)
} }
pub fn push_empty_line(code_lines: &mut CodeLines, grid_node_map: &mut GridNodeMap) { pub fn push_empty_line(grid_node_map: &mut GridNodeMap) {
code_lines.push_empty_line();
grid_node_map.push_empty_line(); grid_node_map.push_empty_line();
} }
pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> { pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> {
self.grid_node_map.clear_line(line_nr)?; self.grid_node_map.clear_line(line_nr)
self.code_lines.clear_line(line_nr)
} }
pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> { pub fn del_line(&mut self, line_nr: usize) {
self.grid_node_map.del_line(line_nr); self.grid_node_map.del_line(line_nr)
self.code_lines.del_line(line_nr)
} }
pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> { pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
self.grid_node_map.del_at_line(line_nr, index)?; self.grid_node_map.del_at_line(line_nr, index)
self.code_lines.del_at_line(line_nr, index)
} }
// updates grid_node_map and code_lines but nothing else. // updates grid_node_map and code_lines but nothing else.
@ -347,9 +326,7 @@ impl<'a> EdModel<'a> {
line_nr: usize, line_nr: usize,
col_range: std::ops::Range<usize>, col_range: std::ops::Range<usize>,
) -> UIResult<()> { ) -> UIResult<()> {
self.grid_node_map self.grid_node_map.del_range_at_line(line_nr, col_range)
.del_range_at_line(line_nr, col_range.clone())?;
self.code_lines.del_range_at_line(line_nr, col_range)
} }
pub fn del_blank_expr_node(&mut self, txt_pos: TextPos) -> UIResult<()> { pub fn del_blank_expr_node(&mut self, txt_pos: TextPos) -> UIResult<()> {
@ -602,7 +579,6 @@ impl<'a> EdModel<'a> {
let active_selection = self.get_selection().context(MissingSelection {})?; let active_selection = self.get_selection().context(MissingSelection {})?;
self.code_lines.del_selection(active_selection)?;
self.grid_node_map.del_selection(active_selection)?; self.grid_node_map.del_selection(active_selection)?;
match sel_block.ast_node_id { match sel_block.ast_node_id {
@ -624,7 +600,6 @@ impl<'a> EdModel<'a> {
nodes::BLANK_PLACEHOLDER, nodes::BLANK_PLACEHOLDER,
expr_mark_node_id, expr_mark_node_id,
&mut self.grid_node_map, &mut self.grid_node_map,
&mut self.code_lines,
)?; )?;
self.set_sel_none(); self.set_sel_none();
@ -678,6 +653,41 @@ impl<'a> EdModel<'a> {
Ok(()) Ok(())
} }
/// update MarkupNode's, grid_node_map, code_lines after the AST has been updated
fn post_process_ast_update(&mut self) -> EdResult<()> {
//dbg!("{}",self.module.ast.ast_to_string(self.module.env.pool));
self.markup_ids = ast_to_mark_nodes(
&mut self.module.env,
&self.module.ast,
&mut self.mark_node_pool,
&self.loaded_module.interns,
)?;
self.code_lines = CodeLines::from_str(&nodes::mark_nodes_to_string(
&self.markup_ids,
&self.mark_node_pool,
));
self.grid_node_map = GridNodeMap::default();
let mut line_nr = 0;
let mut col_nr = 0;
for mark_node_id in &self.markup_ids {
// for debugging:
//println!("{}", tree_as_string(*mark_node_id, &mark_node_pool));
EdModel::insert_mark_node_between_line(
&mut line_nr,
&mut col_nr,
*mark_node_id,
&mut self.grid_node_map,
&self.mark_node_pool,
)?
}
Ok(())
}
} }
impl<'a> SelectableLines for EdModel<'a> { impl<'a> SelectableLines for EdModel<'a> {
@ -1080,6 +1090,7 @@ pub fn handle_new_char_expr(
let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?; let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?;
// insert a Blank first, this results in cleaner code // insert a Blank first, this results in cleaner code
add_blank_child(new_child_index, new_ast_child_index, ed_model)?; add_blank_child(new_child_index, new_ast_child_index, ed_model)?;
ed_model.post_process_ast_update()?;
handle_new_char(received_char, ed_model)? handle_new_char(received_char, ed_model)?
} else { } else {
InputOutcome::Ignored InputOutcome::Ignored
@ -1162,6 +1173,7 @@ pub fn handle_new_char_diff_mark_nodes_prev_is_expr(
let new_ast_child_index = 0; let new_ast_child_index = 0;
// insert a Blank first, this results in cleaner code // insert a Blank first, this results in cleaner code
add_blank_child(new_child_index, new_ast_child_index, ed_model)?; add_blank_child(new_child_index, new_ast_child_index, ed_model)?;
ed_model.post_process_ast_update()?;
handle_new_char(received_char, ed_model)? handle_new_char(received_char, ed_model)?
} else { } else {
InputOutcome::Ignored InputOutcome::Ignored
@ -1182,6 +1194,8 @@ pub fn handle_new_char_diff_mark_nodes_prev_is_expr(
// updates the ed_model based on the char the user just typed if the result would be syntactically correct. // updates the ed_model based on the char the user just typed if the result would be syntactically correct.
pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<InputOutcome> { pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult<InputOutcome> {
//dbg!("{}", ed_model.module.ast.ast_to_string(ed_model.module.env.pool));
let input_outcome = match received_char { let input_outcome = match received_char {
'\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html '\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html
| '\u{f0000}'..='\u{ffffd}' // ^ | '\u{f0000}'..='\u{ffffd}' // ^
@ -1237,7 +1251,8 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult
for caret_pos in ed_model.get_carets() { for caret_pos in ed_model.get_carets() {
if caret_pos.line > 0 { if caret_pos.line > 0 {
insert_new_blank(ed_model, &caret_pos, caret_pos.line)?; insert_new_blank(ed_model, caret_pos.line)?;
ed_model.post_process_ast_update()?;
} }
} }
handle_new_char(received_char, ed_model)? handle_new_char(received_char, ed_model)?
@ -1260,6 +1275,7 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult
}; };
if let InputOutcome::Accepted = input_outcome { if let InputOutcome::Accepted = input_outcome {
ed_model.post_process_ast_update()?;
ed_model.dirty = true; ed_model.dirty = true;
} }
@ -1684,92 +1700,32 @@ pub mod test_ed_update {
fn test_record() -> Result<(), String> { fn test_record() -> Result<(), String> {
assert_insert_in_def_nls(ovec!["{ ┃ }"], '{')?; assert_insert_in_def_nls(ovec!["{ ┃ }"], '{')?;
assert_insert_nls(ovec!["val = { ┃ }"], ovec!["val = { a┃ }"], 'a')?; assert_insert_nls(ovec!["val = { ┃ }"], ovec!["val = { a┃ }"], 'a')?;
assert_insert_nls( assert_insert_nls(ovec!["val = { a┃ }"], ovec!["val = { ab┃ }"], 'b')?;
ovec!["val = { a┃ }"], assert_insert_nls(ovec!["val = { a┃ }"], ovec!["val = { a1┃ }"], '1')?;
ovec!["val = { ab┃: RunTimeError }"], assert_insert_nls(ovec!["val = { a1┃ }"], ovec!["val = { a1z┃ }"], 'z')?;
'b', assert_insert_nls(ovec!["val = { a1┃ }"], ovec!["val = { a15┃ }"], '5')?;
)?; // TODO: remove RunTimeError, see issue #1649 assert_insert_nls(ovec!["val = { ab┃ }"], ovec!["val = { abc┃ }"], 'c')?;
assert_insert_nls( assert_insert_nls(ovec!["val = { ┃abc }"], ovec!["val = { z┃abc }"], 'z')?;
ovec!["val = { a┃ }"], assert_insert_nls(ovec!["val = { a┃b }"], ovec!["val = { az┃b }"], 'z')?;
ovec!["val = { a1┃: RunTimeError }"], assert_insert_nls(ovec!["val = { a┃b }"], ovec!["val = { a9┃b }"], '9')?;
'1',
)?;
assert_insert_nls(
ovec!["val = { a1┃ }"],
ovec!["val = { a1z┃: RunTimeError }"],
'z',
)?;
assert_insert_nls(
ovec!["val = { a1┃ }"],
ovec!["val = { a15┃: RunTimeError }"],
'5',
)?;
assert_insert_nls(
ovec!["val = { ab┃ }"],
ovec!["val = { abc┃: RunTimeError }"],
'c',
)?;
assert_insert_nls(
ovec!["val = { ┃abc }"],
ovec!["val = { z┃abc: RunTimeError }"],
'z',
)?;
assert_insert_nls(
ovec!["val = { a┃b }"],
ovec!["val = { az┃b: RunTimeError }"],
'z',
)?;
assert_insert_nls(
ovec!["val = { a┃b }"],
ovec!["val = { a9┃b: RunTimeError }"],
'9',
)?;
assert_insert_nls( assert_insert_nls(ovec!["val = { a┃ }"], ovec!["val = { a: ┃ }"], ':')?;
ovec!["val = { a┃ }"], assert_insert_nls(ovec!["val = { abc┃ }"], ovec!["val = { abc: ┃ }"], ':')?;
ovec!["val = { a┃: RunTimeError }"], assert_insert_nls(ovec!["val = { aBc┃ }"], ovec!["val = { aBc: ┃ }"], ':')?;
':',
)?;
assert_insert_nls(
ovec!["val = { abc┃ }"],
ovec!["val = { abc┃: RunTimeError }"],
':',
)?;
assert_insert_nls(
ovec!["val = { aBc┃ }"],
ovec!["val = { aBc┃: RunTimeError }"],
':',
)?;
assert_insert_seq_nls( assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: \"\" }"], ":\"")?;
ovec!["val = { a┃ }"],
ovec!["val = { a┃: RunTimeError }"],
":\"",
)?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { abc┃ }"], ovec!["val = { abc┃ }"],
ovec!["val = { abc┃: RunTimeError }"], ovec!["val = { abc: \"\" }"],
":\"", ":\"",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: 0┃ }"], ":0")?;
ovec!["val = { a┃ }"], assert_insert_seq_nls(ovec!["val = { abc┃ }"], ovec!["val = { abc: 9┃ }"], ":9")?;
ovec!["val = { a0┃: RunTimeError }"], assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: 1000┃ }"], ":1000")?;
":0",
)?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { abc┃ }"], ovec!["val = { abc┃ }"],
ovec!["val = { abc9┃: RunTimeError }"], ovec!["val = { abc: 98761┃ }"],
":9",
)?;
assert_insert_seq_nls(
ovec!["val = { a┃ }"],
ovec!["val = { a1000┃: RunTimeError }"],
":1000",
)?;
assert_insert_seq_nls(
ovec!["val = { abc┃ }"],
ovec!["val = { abc98761┃: RunTimeError }"],
":98761", ":98761",
)?; )?;
@ -1919,19 +1875,11 @@ pub mod test_ed_update {
#[test] #[test]
fn test_nested_record() -> Result<(), String> { fn test_nested_record() -> Result<(), String> {
assert_insert_seq_nls( assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: { ┃ } }"], ":{")?;
ovec!["val = { a┃ }"], assert_insert_seq_nls(ovec!["val = { abc┃ }"], ovec!["val = { abc: { ┃ } }"], ":{")?;
ovec!["val = { a┃: RunTimeError }"],
":{",
)?;
assert_insert_seq_nls(
ovec!["val = { abc┃ }"],
ovec!["val = { abc┃: RunTimeError }"],
":{",
)?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { camelCase┃ }"], ovec!["val = { camelCase┃ }"],
ovec!["val = { camelCase┃: RunTimeError }"], ovec!["val = { camelCase: { ┃ } }"],
":{", ":{",
)?; )?;
@ -1953,49 +1901,49 @@ pub mod test_ed_update {
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { a: { zulu┃ } }"], ovec!["val = { a: { zulu┃ } }"],
ovec!["val = { a: { zulu┃: RunTimeError } }"], ovec!["val = { a: { zulu: ┃ } }"],
":", ":",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { abc: { camelCase┃ } }"], ovec!["val = { abc: { camelCase┃ } }"],
ovec!["val = { abc: { camelCase┃: RunTimeError } }"], ovec!["val = { abc: { camelCase: ┃ } }"],
":", ":",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { camelCase: { z┃ } }"], ovec!["val = { camelCase: { z┃ } }"],
ovec!["val = { camelCase: { z┃: RunTimeError } }"], ovec!["val = { camelCase: { z: ┃ } }"],
":", ":",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { a┃: { zulu } }"], ovec!["val = { a┃: { zulu } }"],
ovec!["val = { a0┃: { zulu: RunTimeError } }"], ovec!["val = { a0┃: { zulu } }"],
"0", "0",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { ab┃c: { camelCase } }"], ovec!["val = { ab┃c: { camelCase } }"],
ovec!["val = { abz┃c: { camelCase: RunTimeError } }"], ovec!["val = { abz┃c: { camelCase } }"],
"z", "z",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { ┃camelCase: { z } }"], ovec!["val = { ┃camelCase: { z } }"],
ovec!["val = { x┃camelCase: { z: RunTimeError } }"], ovec!["val = { x┃camelCase: { z } }"],
"x", "x",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { a: { zulu┃ } }"], ovec!["val = { a: { zulu┃ } }"],
ovec!["val = { a: { zulu┃: RunTimeError } }"], ovec!["val = { a: { zulu: \"\" } }"],
":\"", ":\"",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { abc: { camelCase┃ } }"], ovec!["val = { abc: { camelCase┃ } }"],
ovec!["val = { abc: { camelCase┃: RunTimeError } }"], ovec!["val = { abc: { camelCase: \"\" } }"],
":\"", ":\"",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { camelCase: { z┃ } }"], ovec!["val = { camelCase: { z┃ } }"],
ovec!["val = { camelCase: { z┃: RunTimeError } }"], ovec!["val = { camelCase: { z: \"\" } }"],
":\"", ":\"",
)?; )?;
@ -2012,17 +1960,17 @@ pub mod test_ed_update {
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { a: { zulu┃ } }"], ovec!["val = { a: { zulu┃ } }"],
ovec!["val = { a: { zulu1┃: RunTimeError } }"], ovec!["val = { a: { zulu: 1┃ } }"],
":1", ":1",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { abc: { camelCase┃ } }"], ovec!["val = { abc: { camelCase┃ } }"],
ovec!["val = { abc: { camelCase0┃: RunTimeError } }"], ovec!["val = { abc: { camelCase: 0┃ } }"],
":0", ":0",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { camelCase: { z┃ } }"], ovec!["val = { camelCase: { z┃ } }"],
ovec!["val = { camelCase: { z45┃: RunTimeError } }"], ovec!["val = { camelCase: { z: 45┃ } }"],
":45", ":45",
)?; )?;
@ -2039,17 +1987,17 @@ pub mod test_ed_update {
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { a: { zulu┃ } }"], ovec!["val = { a: { zulu┃ } }"],
ovec!["val = { a: { zulu┃: RunTimeError } }"], ovec!["val = { a: { zulu: { ┃ } } }"],
":{", ":{",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { abc: { camelCase┃ } }"], ovec!["val = { abc: { camelCase┃ } }"],
ovec!["val = { abc: { camelCase┃: RunTimeError } }"], ovec!["val = { abc: { camelCase: { ┃ } } }"],
":{", ":{",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { camelCase: { z┃ } }"], ovec!["val = { camelCase: { z┃ } }"],
ovec!["val = { camelCase: { z┃: RunTimeError } }"], ovec!["val = { camelCase: { z: { ┃ } } }"],
":{", ":{",
)?; )?;
@ -2076,17 +2024,17 @@ pub mod test_ed_update {
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { a┃: { bcD: { eFgHij: { k15 } } } }"], ovec!["val = { a┃: { bcD: { eFgHij: { k15 } } } }"],
ovec!["val = { a4┃: { bcD: { eFgHij: { k15: RunTimeError } } } }"], ovec!["val = { a4┃: { bcD: { eFgHij: { k15 } } } }"],
"4", "4",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { ┃a: { bcD: { eFgHij: { k15 } } } }"], ovec!["val = { ┃a: { bcD: { eFgHij: { k15 } } } }"],
ovec!["val = { y┃a: { bcD: { eFgHij: { k15: RunTimeError } } } }"], ovec!["val = { y┃a: { bcD: { eFgHij: { k15 } } } }"],
"y", "y",
)?; )?;
assert_insert_seq_nls( assert_insert_seq_nls(
ovec!["val = { a: { bcD: { eF┃gHij: { k15 } } } }"], ovec!["val = { a: { bcD: { eF┃gHij: { k15 } } } }"],
ovec!["val = { a: { bcD: { eFxyz┃gHij: { k15: RunTimeError } } } }"], ovec!["val = { a: { bcD: { eFxyz┃gHij: { k15 } } } }"],
"xyz", "xyz",
)?; )?;
@ -2099,6 +2047,14 @@ pub mod test_ed_update {
Ok(()) Ok(())
} }
fn concat_strings(str_a: &str, str_b: &str) -> String {
let mut string_a = str_a.to_owned();
string_a.push_str(str_b);
string_a
}
#[test] #[test]
fn test_ignore_record() -> Result<(), String> { fn test_ignore_record() -> Result<(), String> {
assert_insert_seq_ignore_nls(ovec!["val = ┃{ }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = ┃{ }"], IGNORE_CHARS)?;
@ -2107,23 +2063,37 @@ pub mod test_ed_update {
assert_insert_seq_ignore_nls(ovec!["val = { ┃}"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = { ┃}"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = { ┃ }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore_nls(ovec!["val = { ┃ }"], IGNORE_NO_LTR)?;
assert_insert_seq_ignore_nls(ovec!["val = { ┃a: RunTimeError }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore_nls(ovec!["val = { ┃a }"], IGNORE_NO_LTR)?;
assert_insert_seq_ignore_nls(ovec!["val = { ┃abc: RunTimeError }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore_nls(ovec!["val = { ┃abc }"], IGNORE_NO_LTR)?;
assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = ┃{ a }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = { a: ┃RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_nls(
assert_insert_seq_ignore_nls(ovec!["val = {┃ a: RunTimeError }"], IGNORE_CHARS)?; ovec!["val = { a┃ }"],
assert_insert_seq_ignore_nls(ovec!["val = { a:┃ RunTimeError }"], IGNORE_CHARS)?; ovec!["val = { a:┃ }"],
&concat_strings(":🡰", IGNORE_CHARS),
)?;
assert_insert_seq_nls(
ovec!["val = { a┃ }"],
ovec!["val = { a: ┃ }"],
&concat_strings(":🡲", IGNORE_CHARS),
)?;
assert_insert_seq_ignore_nls(ovec!["val = {┃ a }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = ┃{ a15: RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = ┃{ a15 }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = { a15: ┃RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = {┃ a15 }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = {┃ a15: RunTimeError }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = { a15:┃ RunTimeError }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: RunTimeError }"], IGNORE_CHARS)?; assert_insert_seq_nls(
assert_insert_seq_ignore_nls(ovec!["val = { camelCase:┃ RunTimeError }"], IGNORE_CHARS)?; ovec!["val = { camelCase┃ }"],
ovec!["val = { camelCase:┃ }"],
&concat_strings(":🡰", IGNORE_CHARS),
)?;
assert_insert_seq_nls(
ovec!["val = { camelCase┃ }"],
ovec!["val = { camelCase: ┃ }"],
&concat_strings(":🡲", IGNORE_CHARS),
)?;
assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: \"\" }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: \"\" }"], IGNORE_CHARS)?;
assert_insert_seq_ignore_nls(ovec!["val = {┃ a: \"\" }"], IGNORE_CHARS)?; assert_insert_seq_ignore_nls(ovec!["val = {┃ a: \"\" }"], IGNORE_CHARS)?;
@ -2198,44 +2168,23 @@ pub mod test_ed_update {
assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: { } }"], IGNORE_NO_LTR)?; assert_insert_seq_ignore_nls(ovec!["val = ┃{ a: { } }"], IGNORE_NO_LTR)?;
assert_insert_seq_ignore_nls(ovec!["val = { ┃a: { } }"], "1")?; assert_insert_seq_ignore_nls(ovec!["val = { ┃a: { } }"], "1")?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: {┃ z15a } }"], IGNORE_NO_LTR)?;
ovec!["val = { camelCaseB1: { z15a:┃ RunTimeError } }"], assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: ┃{ z15a } }"], IGNORE_NO_LTR)?;
IGNORE_NO_LTR, assert_insert_seq_nls(
ovec!["val = { camelCaseB1: { z15a┃ } }"],
ovec!["val = { camelCaseB1: { z15a:┃ } }"],
&concat_strings(":🡰", IGNORE_CHARS),
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_nls(
ovec!["val = { camelCaseB1: {┃ z15a: RunTimeError } }"], ovec!["val = { camelCaseB1: { z15a┃ } }"],
IGNORE_NO_LTR, ovec!["val = { camelCaseB1: { z15a: ┃ } }"],
&concat_strings(":🡲", IGNORE_CHARS),
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1:┃ { z15a } }"], IGNORE_NO_LTR)?;
ovec!["val = { camelCaseB1: ┃{ z15a: RunTimeError } }"], assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCaseB1: { z15a } }"], IGNORE_NO_LTR)?;
IGNORE_NO_LTR, assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCaseB1: { z15a } }"], IGNORE_NO_LTR)?;
)?; assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a } }"], "1")?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a } }"], "1")?;
ovec!["val = { camelCaseB1: { z15a: ┃RunTimeError } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = { camelCaseB1: { z15a: R┃unTimeError } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = { camelCaseB1: { z15a: Ru┃nTimeError } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = { camelCaseB1:┃ { z15a: RunTimeError } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = {┃ camelCaseB1: { z15a: RunTimeError } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = ┃{ camelCaseB1: { z15a: RunTimeError } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(ovec!["val = { ┃camelCaseB1: { z15a: RunTimeError } }"], "1")?;
assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: { ┃z15a: RunTimeError } }"], "1")?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(
ovec!["val = { camelCaseB1: { z15a: \"\"┃ } }"], ovec!["val = { camelCaseB1: { z15a: \"\"┃ } }"],
@ -2376,39 +2325,27 @@ pub mod test_ed_update {
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(
ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase:┃ RunTimeError } } } } } } } }"], ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"],
IGNORE_NO_LTR, IGNORE_NO_LTR,
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(
ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: R┃unTimeError } } } } } } } }"], ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"],
IGNORE_NO_LTR, IGNORE_NO_LTR,
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(
ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"],
IGNORE_NO_LTR, IGNORE_NO_LTR,
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(
ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeEr┃ror } } } } } } } }"], ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"],
IGNORE_NO_LTR, IGNORE_NO_LTR,
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(
ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"], ovec!["val = { g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"],
IGNORE_NO_LTR, IGNORE_NO_LTR,
)?; )?;
assert_insert_seq_ignore_nls( assert_insert_seq_ignore_nls(
ovec!["val = { g: { oi: { ng: { d: { e: { e:┃ { p: { camelCase: RunTimeError } } } } } } } }"], ovec!["val = { ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase } } } } } } } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = {┃ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = ┃{ g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"],
IGNORE_NO_LTR,
)?;
assert_insert_seq_ignore_nls(
ovec!["val = { ┃g: { oi: { ng: { d: { e: { e: { p: { camelCase: RunTimeError } } } } } } } }"],
"2", "2",
)?; )?;
Ok(()) Ok(())
@ -2619,7 +2556,8 @@ pub mod test_ed_update {
assert_insert_nls(ovec![""], ovec!["z┃ = "], 'z')?; assert_insert_nls(ovec![""], ovec!["z┃ = "], 'z')?;
assert_insert_seq_nls(ovec![""], ovec!["ab┃ = "], "ab")?; assert_insert_seq_nls(ovec![""], ovec!["ab┃ = "], "ab")?;
assert_insert_seq_nls(ovec![""], ovec!["mainVal┃ = "], "mainVal")?; // TODO see issue #2548
//assert_insert_seq_nls(ovec!["┃"], ovec!["mainVal┃ = "], "mainVal")?;
assert_insert_seq_nls(ovec![""], ovec!["camelCase123┃ = "], "camelCase123")?; assert_insert_seq_nls(ovec![""], ovec!["camelCase123┃ = "], "camelCase123")?;
assert_insert_seq_nls(ovec![""], ovec!["c137┃ = "], "c137")?; assert_insert_seq_nls(ovec![""], ovec!["c137┃ = "], "c137")?;
assert_insert_seq_nls(ovec![""], ovec!["c137Bb┃ = "], "c137Bb")?; assert_insert_seq_nls(ovec![""], ovec!["c137Bb┃ = "], "c137Bb")?;

View file

@ -2,10 +2,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::IntStyle;
use roc_ast::lang::core::expr::expr2::IntVal; use roc_ast::lang::core::expr::expr2::IntVal;
use roc_ast::mem_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; 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::EdResult;
use crate::editor::ed_error::StringParseError; use crate::editor::ed_error::StringParseError;
@ -18,15 +15,14 @@ use crate::ui::text::lines::SelectableLines;
// digit_char should be verified to be a digit before calling this function // digit_char should be verified to be a digit before calling this function
pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<InputOutcome> { pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos: _,
curr_mark_node_id, curr_mark_node_id: _,
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let int_var = ed_model.module.env.var_store.fresh(); let int_var = ed_model.module.env.var_store.fresh();
@ -45,36 +41,10 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
.pool .pool
.set(ast_node_id.to_expr_id()?, expr2_node); .set(ast_node_id.to_expr_id()?, expr2_node);
let int_node = MarkupNode::Text {
content: digit_string,
ast_node_id,
syn_high_style: HighlightStyle::Number,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: curr_mark_node_nls,
};
if is_blank_node { if is_blank_node {
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, int_node);
// remove data corresponding to Blank node
ed_model.del_at_line(old_caret_pos.line, old_caret_pos.column)?;
let char_len = 1; let char_len = 1;
ed_model.simple_move_carets_right(char_len); ed_model.simple_move_carets_right(char_len);
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
&digit_char.to_string(),
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)
@ -109,16 +79,6 @@ pub fn update_int(
let content_str = int_mark_node.get_content(); let content_str = int_mark_node.get_content();
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
&ch.to_string(),
int_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
// update ast // update ast
let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool); let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool);
let int_ast_node = ed_model let int_ast_node = ed_model

View file

@ -1,12 +1,6 @@
use roc_ast::lang::core::ast::ASTNodeId;
use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::expr::expr2::Expr2;
use roc_ast::lang::core::pattern::Pattern2; use roc_ast::lang::core::pattern::Pattern2;
use roc_ast::lang::core::val_def::ValueDef; 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 roc_module::symbol::Symbol;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
@ -17,26 +11,21 @@ use crate::editor::mvc::ed_update::NodeContext;
pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> { pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos: _,
curr_mark_node_id, curr_mark_node_id: _,
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let val_name_string = new_char.to_string(); let val_name_string = new_char.to_string();
// safe unwrap because our ArrString has a 30B capacity // safe unwrap because our ArrString has a 30B capacity
let val_expr2_node = Expr2::Blank; let val_expr2_node = Expr2::Blank;
let val_expr_id = ed_model.module.env.pool.add(val_expr2_node); let val_expr_id = ed_model.module.env.pool.add(val_expr2_node);
let ident_id = ed_model let ident_id = ed_model.module.env.ident_ids.add(val_name_string.into());
.module
.env
.ident_ids
.add(val_name_string.clone().into());
let var_symbol = Symbol::new(ed_model.module.env.home, ident_id); let var_symbol = Symbol::new(ed_model.module.env.home, ident_id);
let body = Expr2::Var(var_symbol); let body = Expr2::Var(var_symbol);
let body_id = ed_model.module.env.pool.add(body); let body_id = ed_model.module.env.pool.add(body);
@ -63,50 +52,10 @@ pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<
.pool .pool
.set(ast_node_id.to_expr_id()?, expr2_node); .set(ast_node_id.to_expr_id()?, expr2_node);
let val_name_mark_node = MarkupNode::Text {
content: val_name_string,
ast_node_id,
syn_high_style: HighlightStyle::Value,
attributes: Attributes::default(),
parent_id_opt: Some(curr_mark_node_id),
newlines_at_end: curr_mark_node_nls,
};
let val_name_mn_id = ed_model.add_mark_node(val_name_mark_node);
let equals_mn_id = ed_model.add_mark_node(new_equals_mn(ast_node_id, Some(curr_mark_node_id)));
let body_mn_id = ed_model.add_mark_node(new_blank_mn_w_nls(
ASTNodeId::AExprId(val_expr_id),
Some(curr_mark_node_id),
1,
));
let val_mark_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id],
parent_id_opt,
newlines_at_end: 1,
};
if is_blank_node { if is_blank_node {
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, val_mark_node);
// remove data corresponding to Blank node
ed_model.del_blank_expr_node(old_caret_pos)?;
let char_len = 1; let char_len = 1;
ed_model.simple_move_carets_right(char_len); ed_model.simple_move_carets_right(char_len);
// update GridNodeMap and CodeLines
ed_model.insert_all_between_line(
old_caret_pos.line,
old_caret_pos.column,
&[val_name_mn_id, equals_mn_id, body_mn_id],
)?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)

View file

@ -1,10 +1,7 @@
use roc_ast::lang::core::ast::{ast_node_to_string, ASTNodeId}; use roc_ast::lang::core::ast::ast_node_to_string;
use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; use roc_ast::lang::core::expr::expr2::{Expr2, ExprId};
use roc_ast::mem_pool::pool_vec::PoolVec; use roc_ast::mem_pool::pool_vec::PoolVec;
use roc_code_markup::markup::common_nodes::{ use roc_code_markup::markup::nodes::{self};
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 roc_code_markup::slow_pool::MarkNodeId;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
@ -13,19 +10,17 @@ use crate::editor::mvc::app_update::InputOutcome;
use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_model::EdModel;
use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::get_node_context;
use crate::editor::mvc::ed_update::NodeContext; use crate::editor::mvc::ed_update::NodeContext;
use crate::ui::text::text_pos::TextPos;
pub fn start_new_list(ed_model: &mut EdModel) -> EdResult<InputOutcome> { pub fn start_new_list(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos: _,
curr_mark_node_id, curr_mark_node_id: _,
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let expr2_node = Expr2::List { let expr2_node = Expr2::List {
elem_var: ed_model.module.env.var_store.fresh(), elem_var: ed_model.module.env.var_store.fresh(),
@ -38,51 +33,9 @@ pub fn start_new_list(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
.pool .pool
.set(ast_node_id.to_expr_id()?, expr2_node); .set(ast_node_id.to_expr_id()?, expr2_node);
let left_bracket_node_id = ed_model.add_mark_node(new_left_square_mn(
ast_node_id.to_expr_id()?,
Some(curr_mark_node_id),
));
let right_bracket_node_id = ed_model.add_mark_node(new_right_square_mn(
ast_node_id.to_expr_id()?,
Some(curr_mark_node_id),
));
let nested_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![left_bracket_node_id, right_bracket_node_id],
parent_id_opt,
newlines_at_end: curr_mark_node_nls,
};
if is_blank_node { if is_blank_node {
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, nested_node);
ed_model.del_blank_expr_node(old_caret_pos)?;
ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len()); ed_model.simple_move_carets_right(nodes::LEFT_SQUARE_BR.len());
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
nodes::LEFT_SQUARE_BR,
left_bracket_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column + nodes::LEFT_SQUARE_BR.len(),
nodes::RIGHT_SQUARE_BR,
right_bracket_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)
@ -96,7 +49,7 @@ pub fn add_blank_child(
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos: _,
curr_mark_node_id, curr_mark_node_id,
curr_mark_node: _, curr_mark_node: _,
parent_id_opt, parent_id_opt,
@ -133,7 +86,7 @@ pub fn add_blank_child(
.fail() .fail()
}; };
let (blank_elt_id, list_ast_node_id, parent_id) = trip_result?; let (blank_elt_id, list_ast_node_id, _parent_id) = trip_result?;
let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id); let list_ast_node = ed_model.module.env.pool.get(list_ast_node_id);
@ -164,75 +117,9 @@ pub fn add_blank_child(
.fail(), .fail(),
}?; }?;
let new_mark_children = update_mark_children( if new_child_index > 1 {
new_child_index, ed_model.simple_move_carets_right(nodes::COMMA.len());
blank_elt_id,
list_ast_node_id,
old_caret_pos,
parent_id_opt,
ed_model,
)?;
let parent = ed_model.mark_node_pool.get_mut(parent_id);
for (indx, child) in new_mark_children.iter().enumerate() {
parent.add_child_at_index(new_child_index + indx, *child)?;
} }
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} }
// add a Blank child to the Nested mark node and update the caret
pub fn update_mark_children(
new_child_index: usize,
blank_elt_id: ExprId,
list_ast_node_id: ExprId,
old_caret_pos: TextPos,
parent_id_opt: Option<MarkNodeId>,
ed_model: &mut EdModel,
) -> EdResult<Vec<MarkNodeId>> {
let blank_mark_node_id = ed_model.add_mark_node(new_blank_mn(
ASTNodeId::AExprId(blank_elt_id),
parent_id_opt,
));
let mut children: Vec<MarkNodeId> = vec![];
if new_child_index > 1 {
let comma_mark_node_id =
ed_model.add_mark_node(new_comma_mn(list_ast_node_id, parent_id_opt));
ed_model.simple_move_carets_right(nodes::COMMA.len());
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
nodes::COMMA,
comma_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
children.push(comma_mark_node_id);
}
children.push(blank_mark_node_id);
let comma_shift = if new_child_index == 1 {
0
} else {
nodes::COMMA.len()
};
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column + comma_shift,
nodes::BLANK_PLACEHOLDER,
blank_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
Ok(children)
}

View file

@ -15,7 +15,6 @@ pub fn update_invalid_lookup(
ed_model: &mut EdModel, ed_model: &mut EdModel,
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) { if input_str.chars().all(|ch| ch.is_ascii_alphanumeric()) {
let old_caret_pos = ed_model.get_caret();
let mut new_lookup_str = String::new(); let mut new_lookup_str = String::new();
new_lookup_str.push_str(old_pool_str.as_str(ed_model.module.env.pool)); new_lookup_str.push_str(old_pool_str.as_str(ed_model.module.env.pool));
@ -35,24 +34,9 @@ pub fn update_invalid_lookup(
.pool .pool
.set(expr_id, Expr2::InvalidLookup(new_pool_str)); .set(expr_id, Expr2::InvalidLookup(new_pool_str));
// update MarkupNode
let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?;
content_str_mut.insert_str(caret_offset, input_str);
// update caret // update caret
ed_model.simple_move_carets_right(input_str.len()); ed_model.simple_move_carets_right(input_str.len());
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
input_str,
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)

View file

@ -14,65 +14,32 @@ use roc_ast::lang::core::expr::record_field::RecordField;
use roc_ast::mem_pool::pool_str::PoolStr; use roc_ast::mem_pool::pool_str::PoolStr;
use roc_ast::mem_pool::pool_vec::PoolVec; use roc_ast::mem_pool::pool_vec::PoolVec;
use roc_code_markup::markup::attribute::Attributes; 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;
use roc_code_markup::markup::nodes::MarkupNode; use roc_code_markup::markup::nodes::MarkupNode;
use roc_code_markup::markup::nodes::COLON;
use roc_code_markup::slow_pool::MarkNodeId; use roc_code_markup::slow_pool::MarkNodeId;
use roc_code_markup::syntax_highlight::HighlightStyle; use roc_code_markup::syntax_highlight::HighlightStyle;
use snafu::OptionExt; use snafu::OptionExt;
pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> { pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos: _,
curr_mark_node_id, curr_mark_node_id: _,
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let is_blank_node = curr_mark_node.is_blank(); let is_blank_node = curr_mark_node.is_blank();
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
let ast_pool = &mut ed_model.module.env.pool; let ast_pool = &mut ed_model.module.env.pool;
let expr2_node = Expr2::EmptyRecord; let expr2_node = Expr2::EmptyRecord;
ast_pool.set(ast_node_id.to_expr_id()?, expr2_node); ast_pool.set(ast_node_id.to_expr_id()?, expr2_node);
let left_bracket_node_id = ed_model.add_mark_node(new_left_accolade_mn(
ast_node_id.to_expr_id()?,
Some(curr_mark_node_id),
));
let right_bracket_node_id = ed_model.add_mark_node(new_right_accolade_mn(
ast_node_id.to_expr_id()?,
Some(curr_mark_node_id),
));
let nested_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![left_bracket_node_id, right_bracket_node_id],
parent_id_opt,
newlines_at_end: curr_mark_node_nls,
};
if is_blank_node { if is_blank_node {
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, nested_node);
ed_model.del_blank_expr_node(old_caret_pos)?;
ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len()); ed_model.simple_move_carets_right(nodes::LEFT_ACCOLADE.len());
// update GridNodeMap and CodeLines
ed_model.insert_all_between_line(
old_caret_pos.line,
old_caret_pos.column,
&[left_bracket_node_id, right_bracket_node_id],
)?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)
@ -156,7 +123,6 @@ pub fn update_empty_record(
new_input, new_input,
record_field_node_id, record_field_node_id,
&mut ed_model.grid_node_map, &mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?; )?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
@ -174,12 +140,11 @@ pub fn update_record_colon(
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos,
curr_mark_node_id, curr_mark_node_id: _,
curr_mark_node, curr_mark_node: _,
parent_id_opt, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
if let Some(parent_id) = parent_id_opt {
let curr_ast_node = ed_model.module.env.pool.get(ast_node_id.to_expr_id()?); let curr_ast_node = ed_model.module.env.pool.get(ast_node_id.to_expr_id()?);
let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?; let prev_mark_node_id_opt = ed_model.get_prev_mark_node_id()?;
@ -195,10 +160,6 @@ pub fn update_record_colon(
if matches!(prev_expr, Expr2::Record { .. }) if matches!(prev_expr, Expr2::Record { .. })
&& matches!(curr_ast_node, Expr2::Record { .. }) && matches!(curr_ast_node, Expr2::Record { .. })
{ {
let sibling_ids = curr_mark_node.get_sibling_ids(&ed_model.mark_node_pool);
let new_child_index = index_of(curr_mark_node_id, &sibling_ids)?;
let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id); let ast_node_ref = ed_model.module.env.pool.get(record_ast_node_id);
match ast_node_ref { match ast_node_ref {
@ -209,8 +170,7 @@ pub fn update_record_colon(
if ed_model.node_exists_at_caret() { if ed_model.node_exists_at_caret() {
let next_mark_node_id = let next_mark_node_id =
ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?; ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?;
let next_mark_node = let next_mark_node = ed_model.mark_node_pool.get(next_mark_node_id);
ed_model.mark_node_pool.get(next_mark_node_id);
if next_mark_node.get_content() == nodes::RIGHT_ACCOLADE { if next_mark_node.get_content() == nodes::RIGHT_ACCOLADE {
// update AST node // update AST node
let new_field_val = Expr2::Blank; let new_field_val = Expr2::Blank;
@ -228,51 +188,8 @@ pub fn update_record_colon(
new_field_val_id, new_field_val_id,
); );
// update Markup
let record_colon = nodes::COLON;
let record_colon_node = MarkupNode::Text {
content: record_colon.to_owned(),
ast_node_id: ASTNodeId::AExprId(record_ast_node_id),
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::default(),
parent_id_opt: Some(parent_id),
newlines_at_end: 0,
};
let record_colon_node_id =
ed_model.add_mark_node(record_colon_node);
ed_model
.mark_node_pool
.get_mut(parent_id)
.add_child_at_index(
new_child_index,
record_colon_node_id,
)?;
let record_blank_node_id =
ed_model.add_mark_node(new_blank_mn(
ASTNodeId::AExprId(new_field_val_id),
Some(parent_id),
));
ed_model
.mark_node_pool
.get_mut(parent_id)
.add_child_at_index(
new_child_index + 1,
record_blank_node_id,
)?;
// update caret // update caret
ed_model.simple_move_carets_right(record_colon.len()); ed_model.simple_move_carets_right(COLON.len());
// update GridNodeMap and CodeLines
ed_model.insert_all_between_line(
old_caret_pos.line,
old_caret_pos.column,
&[record_colon_node_id, record_blank_node_id],
)?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} else { } else {
@ -292,12 +209,6 @@ pub fn update_record_colon(
} else { } else {
Ok(InputOutcome::Ignored) Ok(InputOutcome::Ignored)
} }
} else {
MissingParent {
node_id: curr_mark_node_id,
}
.fail()
}
} }
pub fn update_record_field( pub fn update_record_field(
@ -329,16 +240,6 @@ pub fn update_record_field(
// update caret // update caret
ed_model.simple_move_carets_right(new_input.len()); ed_model.simple_move_carets_right(new_input.len());
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
new_input,
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
// update AST Node // update AST Node
let first_field = record_fields let first_field = record_fields
.iter(ed_model.module.env.pool) .iter(ed_model.module.env.pool)

View file

@ -2,10 +2,6 @@ use roc_ast::lang::core::expr::expr2::ArrString;
use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::expr::expr2::Expr2;
use roc_ast::lang::core::str::update_str_expr; use roc_ast::lang::core::str::update_str_expr;
use roc_ast::mem_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;
use roc_code_markup::syntax_highlight::HighlightStyle;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::app_update::InputOutcome;
@ -21,21 +17,19 @@ pub fn update_small_string(
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos,
curr_mark_node_id, curr_mark_node_id,
curr_mark_node: _, curr_mark_node,
parent_id_opt: _, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
let new_input = &new_char.to_string(); let new_input = &new_char.to_string();
// update markup let content_str = curr_mark_node.get_content();
let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { if node_caret_offset != 0 && node_caret_offset < content_str.len() {
if old_array_str.len() < old_array_str.capacity() { if old_array_str.len() < old_array_str.capacity() {
if let Expr2::SmallStr(ref mut mut_array_str) = if let Expr2::SmallStr(ref mut mut_array_str) =
ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?) ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?)
@ -58,18 +52,6 @@ pub fn update_small_string(
.set(ast_node_id.to_expr_id()?, new_ast_node); .set(ast_node_id.to_expr_id()?, new_ast_node);
} }
content_str_mut.insert_str(node_caret_offset, new_input);
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
new_input,
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
// update caret // update caret
ed_model.simple_move_carets_right(new_input.len()); ed_model.simple_move_carets_right(new_input.len());
@ -83,31 +65,17 @@ pub fn update_string(new_char: char, ed_model: &mut EdModel) -> EdResult<InputOu
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos,
curr_mark_node_id, curr_mark_node_id,
curr_mark_node: _, curr_mark_node,
parent_id_opt: _, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
// update markup let content_str = curr_mark_node.get_content();
let curr_mark_node_mut = ed_model.mark_node_pool.get_mut(curr_mark_node_id);
let content_str_mut = curr_mark_node_mut.get_content_mut()?;
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?; .get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() { if node_caret_offset != 0 && node_caret_offset < content_str.len() {
content_str_mut.insert(node_caret_offset, new_char);
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
&new_char.to_string(),
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
// update ast // update ast
update_str_expr( update_str_expr(
ast_node_id.to_expr_id()?, ast_node_id.to_expr_id()?,
@ -127,16 +95,15 @@ pub fn update_string(new_char: char, ed_model: &mut EdModel) -> EdResult<InputOu
pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> { pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos: _,
curr_mark_node_id, curr_mark_node_id: _,
curr_mark_node, curr_mark_node,
parent_id_opt, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
if curr_mark_node.is_blank() { if curr_mark_node.is_blank() {
let new_expr2_node = Expr2::SmallStr(arrayvec::ArrayString::new()); let new_expr2_node = Expr2::SmallStr(arrayvec::ArrayString::new());
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
ed_model ed_model
.module .module
@ -144,32 +111,6 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
.pool .pool
.set(ast_node_id.to_expr_id()?, new_expr2_node); .set(ast_node_id.to_expr_id()?, new_expr2_node);
let new_string_node = MarkupNode::Text {
content: nodes::STRING_QUOTES.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::String,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: curr_mark_node_nls,
};
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, new_string_node);
// remove data corresponding to Blank node
ed_model.del_blank_expr_node(old_caret_pos)?;
// update GridNodeMap and CodeLines
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
nodes::STRING_QUOTES,
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
ed_model.simple_move_carets_right(1); ed_model.simple_move_carets_right(1);
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)

View file

@ -1,10 +1,5 @@
use roc_ast::lang::core::{ast::ASTNodeId, def::def2::Def2, expr::expr2::Expr2}; use roc_ast::lang::core::{def::def2::Def2, expr::expr2::Expr2};
use roc_code_markup::{ use roc_code_markup::slow_pool::MarkNodeId;
markup::{
common_nodes::new_blank_mn_w_nls, nodes::set_parent_for_all, top_level_def::tld_mark_node,
},
slow_pool::MarkNodeId,
};
use crate::{ use crate::{
editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound},
@ -20,19 +15,17 @@ use super::{
// Top Level Defined Value. example: `main = "Hello, World!"` // Top Level Defined Value. example: `main = "Hello, World!"`
pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> { pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> {
let NodeContext { let NodeContext {
old_caret_pos, old_caret_pos: _,
curr_mark_node_id, curr_mark_node_id: _,
curr_mark_node: _, curr_mark_node: _,
parent_id_opt: _, parent_id_opt: _,
ast_node_id, ast_node_id,
} = get_node_context(ed_model)?; } = get_node_context(ed_model)?;
// create new blank >> m = Blank
let val_expr_node = Expr2::Blank; let val_expr_node = Expr2::Blank;
let val_expr_id = ed_model.module.env.pool.add(val_expr_node); let val_expr_id = ed_model.module.env.pool.add(val_expr_node);
let val_expr_mn = new_blank_mn_w_nls(ASTNodeId::AExprId(val_expr_id), None, 0);
let val_expr_mn_id = ed_model.mark_node_pool.add(val_expr_mn);
let val_name_string = new_char.to_string(); let val_name_string = new_char.to_string();
let ident_id = ed_model let ident_id = ed_model
@ -57,14 +50,6 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<
.fail()? .fail()?
} }
let tld_mark_node = tld_mark_node(
ident_id,
val_expr_mn_id,
ast_node_id,
&mut ed_model.mark_node_pool,
&ed_model.module.env,
)?;
let new_ast_node = Def2::ValueDef { let new_ast_node = Def2::ValueDef {
identifier_id: ident_id, identifier_id: ident_id,
expr_id: val_expr_id, expr_id: val_expr_id,
@ -76,31 +61,9 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<
.pool .pool
.set(ast_node_id.to_def_id()?, new_ast_node); .set(ast_node_id.to_def_id()?, new_ast_node);
ed_model
.mark_node_pool
.replace_node(curr_mark_node_id, tld_mark_node);
set_parent_for_all(curr_mark_node_id, &mut ed_model.mark_node_pool);
// remove data corresponding to old Blank node
ed_model.del_line(old_caret_pos.line + 1)?;
ed_model.del_line(old_caret_pos.line)?;
let char_len = 1; let char_len = 1;
ed_model.simple_move_carets_right(char_len); ed_model.simple_move_carets_right(char_len);
let mut curr_line = old_caret_pos.line;
let mut curr_column = old_caret_pos.column;
EdModel::insert_mark_node_between_line(
&mut curr_line,
&mut curr_column,
curr_mark_node_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
&ed_model.mark_node_pool,
)?;
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)
} }
@ -112,37 +75,28 @@ pub fn update_tld_val_name(
) -> EdResult<InputOutcome> { ) -> EdResult<InputOutcome> {
if new_char.is_ascii_alphanumeric() { if new_char.is_ascii_alphanumeric() {
// update markup // update markup
let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id); let val_name_mn = ed_model.mark_node_pool.get(val_name_mn_id);
let content_str_mut = val_name_mn_mut.get_content_mut()?; let mut val_name_str = val_name_mn.get_content();
let old_val_name = content_str_mut.clone(); let old_val_name = val_name_str.clone();
let node_caret_offset = ed_model let node_caret_offset = ed_model
.grid_node_map .grid_node_map
.get_offset_to_node_id(old_caret_pos, val_name_mn_id)?; .get_offset_to_node_id(old_caret_pos, val_name_mn_id)?;
if node_caret_offset <= content_str_mut.len() { if node_caret_offset <= val_name_str.len() {
content_str_mut.insert(node_caret_offset, *new_char); val_name_str.insert(node_caret_offset, *new_char);
let update_val_name_res = ed_model let update_val_name_res = ed_model
.module .module
.env .env
.ident_ids .ident_ids
.update_key(&old_val_name, content_str_mut); .update_key(&old_val_name, &val_name_str);
if let Err(err_str) = update_val_name_res { if let Err(err_str) = update_val_name_res {
FailedToUpdateIdentIdName { err_str }.fail()?; FailedToUpdateIdentIdName { err_str }.fail()?;
} }
EdModel::insert_between_line(
old_caret_pos.line,
old_caret_pos.column,
&new_char.to_string(),
val_name_mn_id,
&mut ed_model.grid_node_map,
&mut ed_model.code_lines,
)?;
ed_model.simple_move_caret_right(old_caret_pos, 1); ed_model.simple_move_caret_right(old_caret_pos, 1);
Ok(InputOutcome::Accepted) Ok(InputOutcome::Accepted)

View file

@ -91,7 +91,7 @@ pub extern "C" fn rust_main() -> i32 {
0 0
} }
unsafe extern "C" fn call_the_closure(closure_data_ptr: *const u8) -> i64 { unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
let size = size_Fx_result() as usize; let size = size_Fx_result() as usize;
let layout = Layout::array::<u8>(size).unwrap(); let layout = Layout::array::<u8>(size).unwrap();
let buffer = std::alloc::alloc(layout) as *mut u8; let buffer = std::alloc::alloc(layout) as *mut u8;

View file

@ -24,15 +24,16 @@ target-lexicon = "0.12.2"
# TODO: make llvm optional # TODO: make llvm optional
roc_build = {path = "../compiler/build", features = ["llvm"]} roc_build = {path = "../compiler/build", features = ["llvm"]}
roc_builtins = {path = "../compiler/builtins"}
roc_collections = {path = "../compiler/collections"} roc_collections = {path = "../compiler/collections"}
roc_gen_llvm = {path = "../compiler/gen_llvm"} roc_gen_llvm = {path = "../compiler/gen_llvm"}
roc_load = {path = "../compiler/load"} roc_load = {path = "../compiler/load"}
roc_mono = {path = "../compiler/mono"} roc_mono = {path = "../compiler/mono"}
roc_parse = {path = "../compiler/parse"} roc_parse = {path = "../compiler/parse"}
roc_repl_eval = {path = "../repl_eval"} roc_repl_eval = {path = "../repl_eval"}
roc_std = {path = "../roc_std"}
roc_target = {path = "../compiler/roc_target"} roc_target = {path = "../compiler/roc_target"}
roc_types = {path = "../compiler/types"} roc_types = {path = "../compiler/types"}
roc_builtins = {path = "../compiler/builtins"}
[lib] [lib]
name = "roc_repl_cli" name = "roc_repl_cli"

View file

@ -20,6 +20,7 @@ use roc_parse::parser::{EExpr, ELambda, SyntaxError};
use roc_repl_eval::eval::jit_to_ast; use roc_repl_eval::eval::jit_to_ast;
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput}; use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
use roc_repl_eval::{ReplApp, ReplAppMemory}; use roc_repl_eval::{ReplApp, ReplAppMemory};
use roc_std::RocStr;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
@ -183,7 +184,8 @@ impl ReplAppMemory for CliMemory {
deref_number!(deref_f64, f64); deref_number!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str { fn deref_str(&self, addr: usize) -> &str {
unsafe { *(addr as *const &'static str) } let reference: &RocStr = unsafe { std::mem::transmute(addr) };
reference.as_str()
} }
} }

View file

@ -115,7 +115,7 @@ fn unroll_newtypes<'a>(
let field_var = *field.as_inner(); let field_var = *field.as_inner();
content = env.subs.get_content_without_compacting(field_var); content = env.subs.get_content_without_compacting(field_var);
} }
Content::Alias(_, _, real_var) => { Content::Alias(_, _, real_var, _) => {
// We need to pass through aliases too, because their underlying types may have // We need to pass through aliases too, because their underlying types may have
// unrolled newtypes. In such cases return the list of unrolled newtypes, but keep // unrolled newtypes. In such cases return the list of unrolled newtypes, but keep
// the content as the alias for readability. For example, // the content as the alias for readability. For example,
@ -162,7 +162,7 @@ fn apply_newtypes<'a>(
} }
fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content { fn unroll_aliases<'a>(env: &Env<'a, 'a>, mut content: &'a Content) -> &'a Content {
while let Content::Alias(_, _, real) = content { while let Content::Alias(_, _, real, _) = content {
content = env.subs.get_content_without_compacting(*real); content = env.subs.get_content_without_compacting(*real);
} }
content content
@ -333,10 +333,16 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
Ok(result) Ok(result)
} }
Layout::Builtin(Builtin::Str) => Ok(app Layout::Builtin(Builtin::Str) => {
.call_function(main_fn_name, |_, string: &'static str| { let size = layout.stack_size(env.target_info) as usize;
str_to_ast(env.arena, env.arena.alloc(string)) Ok(
})), app.call_function_dynamic_size(main_fn_name, size, |mem: &A::Memory, addr| {
let string = mem.deref_str(addr);
let arena_str = env.arena.alloc_str(string);
Expr::Str(StrLiteral::PlainLine(arena_str))
}),
)
}
Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function( Layout::Builtin(Builtin::List(elem_layout)) => Ok(app.call_function(
main_fn_name, main_fn_name,
|mem: &A::Memory, (addr, len): (usize, usize)| { |mem: &A::Memory, (addr, len): (usize, usize)| {
@ -523,9 +529,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
list_to_ast(env, mem, elem_addr, len, elem_layout, content) list_to_ast(env, mem, elem_addr, len, elem_layout, content)
} }
(_, Layout::Builtin(Builtin::Str)) => { (_, Layout::Builtin(Builtin::Str)) => {
let arena_str = mem.deref_str(addr); let string = mem.deref_str(addr);
let arena_str = env.arena.alloc_str(string);
str_to_ast(env.arena, arena_str) Expr::Str(StrLiteral::PlainLine(arena_str))
} }
(_, Layout::Struct{field_layouts, ..}) => match content { (_, Layout::Struct{field_layouts, ..}) => match content {
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
@ -1057,7 +1063,7 @@ fn bool_to_ast<'a, M: ReplAppMemory>(
} }
} }
} }
Alias(_, _, var) => { Alias(_, _, var, _) => {
let content = env.subs.get_content_without_compacting(*var); let content = env.subs.get_content_without_compacting(*var);
bool_to_ast(env, mem, value, content) bool_to_ast(env, mem, value, content)
@ -1154,7 +1160,7 @@ fn byte_to_ast<'a, M: ReplAppMemory>(
} }
} }
} }
Alias(_, _, var) => { Alias(_, _, var, _) => {
let content = env.subs.get_content_without_compacting(*var); let content = env.subs.get_content_without_compacting(*var);
byte_to_ast(env, mem, value, content) byte_to_ast(env, mem, value, content)
@ -1223,7 +1229,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
} }
} }
} }
Alias(_, _, var) => { Alias(_, _, var, _) => {
let content = env.subs.get_content_without_compacting(*var); let content = env.subs.get_content_without_compacting(*var);
num_to_ast(env, num_expr, content) num_to_ast(env, num_expr, content)
@ -1242,41 +1248,3 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_> { fn number_literal_to_ast<T: std::fmt::Display>(arena: &Bump, num: T) -> Expr<'_> {
Expr::Num(arena.alloc(format!("{}", num))) Expr::Num(arena.alloc(format!("{}", num)))
} }
#[cfg(target_endian = "little")]
/// NOTE: As of this writing, we don't have big-endian small strings implemented yet!
fn str_to_ast<'a>(arena: &'a Bump, string: &'a str) -> Expr<'a> {
const STR_SIZE: usize = 2 * std::mem::size_of::<usize>();
let bytes: [u8; STR_SIZE] = unsafe { std::mem::transmute(string) };
let is_small = (bytes[STR_SIZE - 1] & 0b1000_0000) != 0;
if is_small {
let len = (bytes[STR_SIZE - 1] & 0b0111_1111) as usize;
let mut string = bumpalo::collections::String::with_capacity_in(len, arena);
for byte in bytes.iter().take(len) {
string.push(*byte as char);
}
str_slice_to_ast(arena, arena.alloc(string))
} else {
// Roc string literals are stored inside the constant section of the program
// That means this memory is gone when the jit function is done
// (as opposed to heap memory, which we can leak and then still use after)
// therefore we must make an owned copy of the string here
let string = bumpalo::collections::String::from_str_in(string, arena).into_bump_str();
str_slice_to_ast(arena, string)
}
}
fn str_slice_to_ast<'a>(_arena: &'a Bump, string: &'a str) -> Expr<'a> {
if string.contains('\n') {
todo!(
"this string contains newlines, so render it as a multiline string: {:?}",
Expr::Str(StrLiteral::PlainLine(string))
);
} else {
Expr::Str(StrLiteral::PlainLine(string))
}
}

View file

@ -3,13 +3,12 @@ edition = "2021"
name = "repl_test" name = "repl_test"
version = "0.1.0" version = "0.1.0"
[[test]]
name = "repl_test"
path = "src/tests.rs"
[build-dependencies] [build-dependencies]
roc_cli = {path = "../cli"} roc_cli = {path = "../cli"}
[dependencies]
lazy_static = "1.4.0"
[dev-dependencies] [dev-dependencies]
indoc = "1.0.3" indoc = "1.0.3"
strip-ansi-escapes = "0.1.1" strip-ansi-escapes = "0.1.1"

View file

@ -1,6 +0,0 @@
# At the moment we are using this script instead of `cargo test`
# Cargo doesn't really have a good way to build two targets (host and wasm).
# We can try to make the build nicer later
cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --features wasmer --release
cargo test -p repl_test --features wasm

12
repl_test/src/lib.rs Normal file
View file

@ -0,0 +1,12 @@
#[allow(unused_imports)]
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
mod tests;
#[cfg(all(test, not(feature = "wasm")))]
mod cli;
#[cfg(all(test, feature = "wasm"))]
mod wasm;

View file

@ -1,12 +1,9 @@
#[allow(unused_imports)]
use indoc::indoc; use indoc::indoc;
#[cfg(not(feature = "wasm"))]
mod cli;
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))]
use crate::cli::{expect_failure, expect_success}; use crate::cli::{expect_failure, expect_success};
#[cfg(feature = "wasm")]
mod wasm;
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
#[allow(unused_imports)] #[allow(unused_imports)]
use crate::wasm::{expect_failure, expect_success}; use crate::wasm::{expect_failure, expect_success};
@ -83,7 +80,6 @@ fn num_ceil_division_success() {
expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*")
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn bool_in_record() { fn bool_in_record() {
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
@ -98,21 +94,18 @@ fn bool_in_record() {
); );
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn bool_basic_equality() { fn bool_basic_equality() {
expect_success("1 == 1", "True : Bool"); expect_success("1 == 1", "True : Bool");
expect_success("1 != 1", "False : Bool"); expect_success("1 != 1", "False : Bool");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn arbitrary_tag_unions() { fn arbitrary_tag_unions() {
expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*"); expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*");
expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*"); expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn tag_without_arguments() { fn tag_without_arguments() {
expect_success("True", "True : [ True ]*"); expect_success("True", "True : [ True ]*");
@ -132,7 +125,6 @@ fn byte_tag_union() {
); );
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn tag_in_record() { fn tag_in_record() {
expect_success( expect_success(
@ -152,13 +144,11 @@ fn single_element_tag_union() {
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*"); expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn newtype_of_unit() { fn newtype_of_unit() {
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn newtype_of_big_data() { fn newtype_of_big_data() {
expect_success( expect_success(
@ -174,7 +164,6 @@ fn newtype_of_big_data() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn newtype_nested() { fn newtype_nested() {
expect_success( expect_success(
@ -190,7 +179,6 @@ fn newtype_nested() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn newtype_of_big_of_newtype() { fn newtype_of_big_of_newtype() {
expect_success( expect_success(
@ -221,19 +209,16 @@ fn literal_empty_str() {
expect_success("\"\"", "\"\" : Str"); expect_success("\"\"", "\"\" : Str");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn literal_ascii_str() { fn literal_ascii_str() {
expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str"); expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn literal_utf8_str() { fn literal_utf8_str() {
expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str"); expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn str_concat() { fn str_concat() {
expect_success( expect_success(
@ -273,13 +258,11 @@ fn literal_float_list() {
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)"); expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)");
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn literal_string_list() { fn literal_string_list() {
expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#); expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#);
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn nested_string_list() { fn nested_string_list() {
expect_success( expect_success(
@ -432,7 +415,6 @@ fn list_last() {
); );
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn empty_record() { fn empty_record() {
expect_success("{}", "{} : {}"); expect_success("{}", "{} : {}");
@ -568,7 +550,7 @@ fn stdlib_function() {
expect_success("Num.abs", "<function> : Num a -> Num a"); expect_success("Num.abs", "<function> : Num a -> Num a");
} }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test] #[test]
fn too_few_args() { fn too_few_args() {
expect_failure( expect_failure(
@ -589,7 +571,7 @@ fn too_few_args() {
); );
} }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test] #[test]
fn type_problem() { fn type_problem() {
expect_failure( expect_failure(
@ -646,7 +628,6 @@ fn multiline_input() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn recursive_tag_union_flat_variant() { fn recursive_tag_union_flat_variant() {
expect_success( expect_success(
@ -662,7 +643,6 @@ fn recursive_tag_union_flat_variant() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn large_recursive_tag_union_flat_variant() { fn large_recursive_tag_union_flat_variant() {
expect_success( expect_success(
@ -679,7 +659,6 @@ fn large_recursive_tag_union_flat_variant() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn recursive_tag_union_recursive_variant() { fn recursive_tag_union_recursive_variant() {
expect_success( expect_success(
@ -695,7 +674,6 @@ fn recursive_tag_union_recursive_variant() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn large_recursive_tag_union_recursive_variant() { fn large_recursive_tag_union_recursive_variant() {
expect_success( expect_success(
@ -712,7 +690,6 @@ fn large_recursive_tag_union_recursive_variant() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn recursive_tag_union_into_flat_tag_union() { fn recursive_tag_union_into_flat_tag_union() {
expect_success( expect_success(
@ -728,7 +705,6 @@ fn recursive_tag_union_into_flat_tag_union() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn non_nullable_unwrapped_tag_union() { fn non_nullable_unwrapped_tag_union() {
expect_success( expect_success(
@ -748,7 +724,6 @@ fn non_nullable_unwrapped_tag_union() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn nullable_unwrapped_tag_union() { fn nullable_unwrapped_tag_union() {
expect_success( expect_success(
@ -768,7 +743,6 @@ fn nullable_unwrapped_tag_union() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn nullable_wrapped_tag_union() { fn nullable_wrapped_tag_union() {
expect_success( expect_success(
@ -792,7 +766,6 @@ fn nullable_wrapped_tag_union() {
) )
} }
#[cfg(not(feature = "wasm"))]
#[test] #[test]
fn large_nullable_wrapped_tag_union() { fn large_nullable_wrapped_tag_union() {
// > 7 non-empty variants so that to force tag storage alongside the data // > 7 non-empty variants so that to force tag storage alongside the data
@ -884,7 +857,7 @@ fn print_u8s() {
) )
} }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test] #[test]
fn parse_problem() { fn parse_problem() {
expect_failure( expect_failure(
@ -908,7 +881,7 @@ fn parse_problem() {
); );
} }
#[cfg(not(feature = "wasm"))] #[cfg(not(feature = "wasm"))] // TODO: mismatch is due to terminal control codes!
#[test] #[test]
fn mono_problem() { fn mono_problem() {
expect_failure( expect_failure(
@ -1020,3 +993,46 @@ fn issue_2588_record_with_function_and_nonfunction() {
r#"{ f: <function>, y: 2 } : { f : Num a -> Num a, y : Num * }"#, r#"{ f: <function>, y: 2 } : { f : Num a -> Num a, y : Num * }"#,
) )
} }
fn opaque_apply() {
expect_success(
indoc!(
r#"
Age := U32
$Age 23
"#
),
"23 : Age",
)
}
#[test]
fn opaque_apply_polymorphic() {
expect_success(
indoc!(
r#"
F t u := [ Package t u ]
$F (Package "" { a: "" })
"#
),
r#"Package "" { a: "" } : F Str { a : Str }"#,
)
}
#[test]
fn opaque_pattern_and_call() {
expect_success(
indoc!(
r#"
F t u := [ Package t u ]
f = \$F (Package A {}) -> $F (Package {} A)
f ($F (Package A {}))
"#
),
r#"Package {} A : F {} [ A ]*"#,
)
}

View file

@ -3,6 +3,7 @@ use std::{
fs, fs,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
path::Path, path::Path,
sync::Mutex,
thread_local, thread_local,
}; };
use wasmer::{imports, Function, Instance, Module, Store, Value}; use wasmer::{imports, Function, Instance, Module, Store, Value};
@ -11,11 +12,46 @@ use wasmer_wasi::WasiState;
const WASM_REPL_COMPILER_PATH: &str = "../target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm"; const WASM_REPL_COMPILER_PATH: &str = "../target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm";
thread_local! { thread_local! {
static COMPILER: RefCell<Option<Instance>> = RefCell::new(None) static REPL_STATE: RefCell<Option<ReplState>> = RefCell::new(None)
} }
thread_local! { // The compiler Wasm instance.
static REPL_STATE: RefCell<Option<ReplState>> = RefCell::new(None) // This takes several *seconds* to initialise, so we only want to do it once for all tests.
// Every test mutates compiler memory in `unsafe` ways, so we run them sequentially using a Mutex.
// Even if Cargo uses many threads, these tests won't go any faster. But that's fine, they're quick.
lazy_static! {
static ref COMPILER: Instance = init_compiler();
static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
}
/// Load the compiler .wasm file and get it ready to execute
/// THIS FUNCTION TAKES 4 SECONDS TO RUN
fn init_compiler() -> Instance {
let path = Path::new(WASM_REPL_COMPILER_PATH);
let wasm_module_bytes = match fs::read(&path) {
Ok(bytes) => bytes,
Err(e) => panic!("{}", format_compiler_load_error(e)),
};
let store = Store::default();
// This is the slow line. Skipping validation checks reduces module compilation time from 5s to 4s.
// Safety: We trust rustc to produce a valid module.
let wasmer_module =
unsafe { Module::from_binary_unchecked(&store, &wasm_module_bytes).unwrap() };
let import_object = imports! {
"env" => {
"wasmer_create_app" => Function::new_native(&store, wasmer_create_app),
"wasmer_run_app" => Function::new_native(&store, wasmer_run_app),
"wasmer_get_result_and_memory" => Function::new_native(&store, wasmer_get_result_and_memory),
"wasmer_copy_input_string" => Function::new_native(&store, wasmer_copy_input_string),
"wasmer_copy_output_string" => Function::new_native(&store, wasmer_copy_output_string),
"now" => Function::new_native(&store, dummy_system_time_now),
}
};
Instance::new(&wasmer_module, &import_object).unwrap()
} }
struct ReplState { struct ReplState {
@ -25,10 +61,9 @@ struct ReplState {
output: Option<String>, output: Option<String>,
} }
fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) { fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) -> u32 {
let app = COMPILER.with(|f| { let app: Instance = {
if let Some(compiler) = f.borrow().deref() { let memory = COMPILER.exports.get_memory("memory").unwrap();
let memory = compiler.exports.get_memory("memory").unwrap();
let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; let memory_bytes: &[u8] = unsafe { memory.data_unchecked() };
// Find the slice of bytes for the compiled Roc app // Find the slice of bytes for the compiled Roc app
@ -38,7 +73,18 @@ fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) {
// Parse the bytes into a Wasmer module // Parse the bytes into a Wasmer module
let store = Store::default(); let store = Store::default();
let wasmer_module = Module::new(&store, app_module_bytes).unwrap(); let wasmer_module = match Module::new(&store, app_module_bytes) {
Ok(m) => m,
Err(e) => {
println!("Failed to create Wasm module\n{:?}", e);
if false {
let path = "/tmp/roc_repl_test_invalid_app.wasm";
fs::write(path, app_module_bytes).unwrap();
println!("Wrote invalid wasm to {}", path);
}
return false.into();
}
};
// Get the WASI imports for the app // Get the WASI imports for the app
let mut wasi_env = WasiState::new("hello").finalize().unwrap(); let mut wasi_env = WasiState::new("hello").finalize().unwrap();
@ -46,12 +92,15 @@ fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) {
.import_object(&wasmer_module) .import_object(&wasmer_module)
.unwrap_or_else(|_| imports!()); .unwrap_or_else(|_| imports!());
// Create an executable instance. (Give it a stack & heap, etc. If this was ELF, it would be the OS's job.) // Create an executable instance
Instance::new(&wasmer_module, &import_object).unwrap() match Instance::new(&wasmer_module, &import_object) {
} else { Ok(instance) => instance,
unreachable!() Err(e) => {
println!("Failed to create Wasm instance {:?}", e);
return false.into();
} }
}); }
};
REPL_STATE.with(|f| { REPL_STATE.with(|f| {
if let Some(state) = f.borrow_mut().deref_mut() { if let Some(state) = f.borrow_mut().deref_mut() {
@ -60,6 +109,8 @@ fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) {
unreachable!() unreachable!()
} }
}); });
return true.into();
} }
fn wasmer_run_app() -> u32 { fn wasmer_run_app() -> u32 {
@ -97,16 +148,9 @@ fn wasmer_get_result_and_memory(buffer_alloc_addr: u32) -> u32 {
let buf_addr = buffer_alloc_addr as usize; let buf_addr = buffer_alloc_addr as usize;
let len = app_memory_bytes.len(); let len = app_memory_bytes.len();
COMPILER.with(|f| { let memory = COMPILER.exports.get_memory("memory").unwrap();
if let Some(compiler) = f.borrow().deref() { let compiler_memory_bytes: &mut [u8] = unsafe { memory.data_unchecked_mut() };
let memory = compiler.exports.get_memory("memory").unwrap();
let compiler_memory_bytes: &mut [u8] =
unsafe { memory.data_unchecked_mut() };
compiler_memory_bytes[buf_addr..][..len].copy_from_slice(app_memory_bytes); compiler_memory_bytes[buf_addr..][..len].copy_from_slice(app_memory_bytes);
} else {
unreachable!()
}
});
result_addr result_addr
} else { } else {
@ -127,24 +171,17 @@ fn wasmer_copy_input_string(src_buffer_addr: u32) {
} }
}); });
COMPILER.with(|c| { let memory = COMPILER.exports.get_memory("memory").unwrap();
if let Some(compiler) = c.borrow().deref() {
let memory = compiler.exports.get_memory("memory").unwrap();
let memory_bytes: &mut [u8] = unsafe { memory.data_unchecked_mut() }; let memory_bytes: &mut [u8] = unsafe { memory.data_unchecked_mut() };
let buf_addr = src_buffer_addr as usize; let buf_addr = src_buffer_addr as usize;
let len = src.len(); let len = src.len();
memory_bytes[buf_addr..][..len].copy_from_slice(src.as_bytes()); memory_bytes[buf_addr..][..len].copy_from_slice(src.as_bytes());
} else {
unreachable!()
}
})
} }
fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) { fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) {
let output: String = COMPILER.with(|c| { let output: String = {
if let Some(compiler) = c.borrow().deref() { let memory = COMPILER.exports.get_memory("memory").unwrap();
let memory = compiler.exports.get_memory("memory").unwrap();
let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; let memory_bytes: &[u8] = unsafe { memory.data_unchecked() };
// Find the slice of bytes for the output string // Find the slice of bytes for the output string
@ -155,10 +192,7 @@ fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) {
// Copy it out of the Wasm module // Copy it out of the Wasm module
let copied_bytes = output_bytes.to_vec(); let copied_bytes = output_bytes.to_vec();
unsafe { String::from_utf8_unchecked(copied_bytes) } unsafe { String::from_utf8_unchecked(copied_bytes) }
} else { };
unreachable!()
}
});
REPL_STATE.with(|f| { REPL_STATE.with(|f| {
if let Some(state) = f.borrow_mut().deref_mut() { if let Some(state) = f.borrow_mut().deref_mut() {
@ -167,75 +201,14 @@ fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) {
}) })
} }
fn init_compiler() -> Instance {
let path = Path::new(WASM_REPL_COMPILER_PATH);
let wasm_module_bytes = match fs::read(&path) {
Ok(bytes) => bytes,
Err(e) => panic!("{}", format_compiler_load_error(e)),
};
let store = Store::default();
let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap();
let import_object = imports! {
"env" => {
"wasmer_create_app" => Function::new_native(&store, wasmer_create_app),
"wasmer_run_app" => Function::new_native(&store, wasmer_run_app),
"wasmer_get_result_and_memory" => Function::new_native(&store, wasmer_get_result_and_memory),
"wasmer_copy_input_string" => Function::new_native(&store, wasmer_copy_input_string),
"wasmer_copy_output_string" => Function::new_native(&store, wasmer_copy_output_string),
"now" => Function::new_native(&store, dummy_system_time_now),
}
};
Instance::new(&wasmer_module, &import_object).unwrap()
}
fn run(src: &'static str) -> (bool, String) {
REPL_STATE.with(|rs| {
*rs.borrow_mut().deref_mut() = Some(ReplState {
src,
app: None,
result_addr: None,
output: None,
});
});
let ok = COMPILER.with(|c| {
*c.borrow_mut().deref_mut() = Some(init_compiler());
if let Some(compiler) = c.borrow().deref() {
let entrypoint = compiler
.exports
.get_function("entrypoint_from_test")
.unwrap();
let src_len = Value::I32(src.len() as i32);
let wasm_ok: i32 = entrypoint.call(&[src_len]).unwrap().deref()[0].unwrap_i32();
wasm_ok != 0
} else {
unreachable!()
}
});
let final_state: ReplState = REPL_STATE.with(|rs| rs.take()).unwrap();
let output: String = final_state.output.unwrap();
(ok, output)
}
fn format_compiler_load_error(e: std::io::Error) -> String { fn format_compiler_load_error(e: std::io::Error) -> String {
if matches!(e.kind(), std::io::ErrorKind::NotFound) { if matches!(e.kind(), std::io::ErrorKind::NotFound) {
format!( format!(
"\n\n {}\n\n", "\n\n {}\n\n",
[ [
"CANNOT BUILD WASM REPL TESTS", "ROC COMPILER WASM BINARY NOT FOUND",
"Please run these tests using repl_test/run_wasm.sh!", "Please run these tests using repl_test/run_wasm.sh!",
"", "It will build a .wasm binary for the compiler, and a native binary for the tests themselves",
"These tests combine two builds for two different targets,",
"which Cargo doesn't handle very well.",
"It probably requires a second target directory to avoid locks.",
"We'll get to it eventually but it's not done yet.",
] ]
.join("\n ") .join("\n ")
) )
@ -248,12 +221,45 @@ fn dummy_system_time_now() -> f64 {
0.0 0.0
} }
fn run(src: &'static str) -> (bool, String) {
REPL_STATE.with(|rs| {
*rs.borrow_mut().deref_mut() = Some(ReplState {
src,
app: None,
result_addr: None,
output: None,
});
});
let ok = if let Ok(_guard) = TEST_MUTEX.lock() {
let entrypoint = COMPILER
.exports
.get_function("entrypoint_from_test")
.unwrap();
let src_len = Value::I32(src.len() as i32);
let wasm_ok: i32 = entrypoint.call(&[src_len]).unwrap().deref()[0].unwrap_i32();
wasm_ok != 0
} else {
panic!(
"Failed to acquire test mutex! A previous test must have panicked while holding it, running Wasm"
)
};
let final_state: ReplState = REPL_STATE.with(|rs| rs.take()).unwrap();
let output: String = final_state.output.unwrap();
(ok, output)
}
#[allow(dead_code)]
pub fn expect_success(input: &'static str, expected: &str) { pub fn expect_success(input: &'static str, expected: &str) {
let (ok, output) = run(input); let (ok, output) = run(input);
assert_eq!(ok, true); assert_eq!(ok, true);
assert_eq!(output, expected); assert_eq!(output, expected);
} }
#[allow(dead_code)]
pub fn expect_failure(input: &'static str, expected: &str) { pub fn expect_failure(input: &'static str, expected: &str) {
let (ok, output) = run(input); let (ok, output) = run(input);
assert_eq!(ok, false); assert_eq!(ok, false);

10
repl_test/test_wasm.sh Executable file
View file

@ -0,0 +1,10 @@
set -eux
# Build the compiler for WebAssembly target
# We *could* write a build.rs to do this but we'd have nested cargo processes with different targets.
# That can be solved using two separate target direcories with --target-dir but there isn't a huge win.
# We need to clear RUSTFLAGS for this command, as CI sets normally some flags that are specific to CPU targets.
RUSTFLAGS="" cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --features wasmer --release
# Build & run the test code on *native* target, not WebAssembly
cargo test -p repl_test --features wasm -- --test-threads=1

View file

@ -1,3 +1,4 @@
use std::env;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
@ -11,6 +12,15 @@ fn main() {
println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=src/{}.c", PLATFORM_FILENAME); println!("cargo:rerun-if-changed=src/{}.c", PLATFORM_FILENAME);
// When we build on Netlify, zig is not installed (but also not used,
// since all we're doing is generating docs), so we can skip the steps
// that require having zig installed.
if env::var_os("NO_ZIG_INSTALLED").is_some() {
// We still need to do the other things before this point, because
// setting the env vars is needed for other parts of the build.
return;
}
std::fs::create_dir_all("./data").unwrap(); std::fs::create_dir_all("./data").unwrap();
// Build a pre-linked binary with platform, builtins and all their libc dependencies // Build a pre-linked binary with platform, builtins and all their libc dependencies

View file

@ -1,7 +1,7 @@
use futures::executor; use futures::executor;
extern "C" { extern "C" {
fn wasmer_create_app(app_bytes_ptr: *const u8, app_bytes_len: usize); fn wasmer_create_app(app_bytes_ptr: *const u8, app_bytes_len: usize) -> u32;
fn wasmer_run_app() -> usize; fn wasmer_run_app() -> usize;
fn wasmer_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; fn wasmer_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize;
fn wasmer_copy_input_string(src_buffer_addr: *mut u8); fn wasmer_copy_input_string(src_buffer_addr: *mut u8);
@ -10,10 +10,12 @@ extern "C" {
/// Async wrapper to match the equivalent JS function /// Async wrapper to match the equivalent JS function
pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), String> { pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), String> {
unsafe { let ok = unsafe { wasmer_create_app(wasm_module_bytes.as_ptr(), wasm_module_bytes.len()) } != 0;
wasmer_create_app(wasm_module_bytes.as_ptr(), wasm_module_bytes.len()); if ok {
}
Ok(()) Ok(())
} else {
Err("Compiler generated an invalid Wasm module".to_string())
}
} }
pub fn js_run_app() -> usize { pub fn js_run_app() -> usize {

View file

@ -4,16 +4,14 @@ mod repl;
// Interface with external JS in the browser // Interface with external JS in the browser
// //
#[cfg(not(feature = "wasmer"))] #[cfg(not(feature = "wasmer"))]
mod interface_js; mod externs_js;
#[cfg(not(feature = "wasmer"))] #[cfg(not(feature = "wasmer"))]
pub use interface_js::{entrypoint_from_js, js_create_app, js_get_result_and_memory, js_run_app}; pub use externs_js::{entrypoint_from_js, js_create_app, js_get_result_and_memory, js_run_app};
// //
// Interface with test code outside the Wasm module // Interface with test code outside the Wasm module
// //
#[cfg(feature = "wasmer")] #[cfg(feature = "wasmer")]
mod interface_test; mod externs_test;
#[cfg(feature = "wasmer")] #[cfg(feature = "wasmer")]
pub use interface_test::{ pub use externs_test::{entrypoint_from_test, js_create_app, js_get_result_and_memory, js_run_app};
entrypoint_from_test, js_create_app, js_get_result_and_memory, js_run_app,
};

View file

@ -64,10 +64,22 @@ impl<'a> ReplAppMemory for WasmMemory<'a> {
deref_number!(deref_f64, f64); deref_number!(deref_f64, f64);
fn deref_str(&self, addr: usize) -> &str { fn deref_str(&self, addr: usize) -> &str {
let elems_addr = self.deref_usize(addr); // We can't use RocStr, we need our own small/big string logic.
let len = self.deref_usize(addr + size_of::<usize>()); // The first field is *not* a pointer. We can calculate a pointer for it, but only for big strings.
let bytes = &self.copied_bytes[elems_addr..][..len]; // If changing this code, remember it also runs in wasm32, not just the app.
std::str::from_utf8(bytes).unwrap() let last_byte = self.copied_bytes[addr + 7] as i8;
let is_small = last_byte < 0;
let str_bytes = if is_small {
let len = (last_byte & 0x7f) as usize;
&self.copied_bytes[addr..][..len]
} else {
let chars_index = self.deref_usize(addr);
let len = self.deref_usize(addr + 4);
&self.copied_bytes[chars_index..][..len]
};
unsafe { std::str::from_utf8_unchecked(str_bytes) }
} }
} }

64
repl_www/README.md Normal file
View file

@ -0,0 +1,64 @@
# Web REPL
## Running locally
### 1. Run the build script
This builds the compiler as a `.wasm` file, and generates JS glue code.
It will `cargo install` the `wasm-pack` command line tool if you don't already have it.
You should run it from the project root directory.
```bash
./repl_www/build.sh
```
All the final assets for the web REPL will end up in `./repl_www/build/`
### 2. Run a local HTTP server
Browsers won't load .wasm files over the `file://` protocol, so you need to serve the files in `./repl_www/build/` from a local web server.
Any server will do, but this example should work on any system that has Python 3 installed:
```bash
cd repl_www/build
python3 -m http.server
```
### 3. Open your browser
You should be able to find the Roc REPL at http://127.0.0.1:8000 (or wherever your web server said when it started up.)
**Warning:** This is work in progress! Not all language features are implemented yet, error messages don't look nice yet, up/down arrows don't work for history, etc.
![Screenshot](./screenshot.png)
## How it works
- User types text into the HTML `<input />` tag
- JS detects the `onchange` event and passes the input text to the Roc compiler WebAssembly module
- Roc compiler WebAssembly module
- Parses the text (currently just a single line)
- Type checks
- Monomorphizes
- Generates WebAssembly using the development backend (not LLVM)
- Returns a slice of bytes to JavaScript
- JavaScript
- Takes the slice of bytes and creates a `WebAssembly.Instance`
- Runs the WebAssembly app
- Gets the memory address of the result and makes a copy of the app's entire memory buffer
- Passes the result address and the memory buffer to the compiler for analysis
- Roc compiler WebAssembly module
- Analyses the bytes of the result, based on the known return type from earlier
- Traverses the copied memory buffer to find any child values
- Produces a user-friendly String and passes it to JavaScript
- JavaScript
- Displays the input and output text on the web page
![High-level diagram](./architecture.png)
## Related crates
There are several directories/packages involved here:
- `repl_www`: The web page with its JavaScript and a build script
- `repl_wasm`: The Rust crate that becomes the "compiler" WebAssembly module
- `repl_eval`: REPL logic shared between `repl_cli` and `repl_wasm`

BIN
repl_www/architecture.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

View file

@ -1,6 +1,6 @@
#!/bin/bash #!/usr/bin/env bash
set -ex set -euxo pipefail
if [[ ! -d repl_www ]] if [[ ! -d repl_www ]]
then then
@ -10,17 +10,19 @@ fi
if ! which wasm-pack if ! which wasm-pack
then then
echo "Installing wasm-pack CLI"
cargo install wasm-pack cargo install wasm-pack
fi fi
WWW_DIR="repl_www/build" # output directory is first argument or default
WWW_DIR="${1:-repl_www/build}"
mkdir -p $WWW_DIR mkdir -p $WWW_DIR
cp repl_www/public/* $WWW_DIR cp repl_www/public/* $WWW_DIR
# For debugging, pass the --profiling option, which enables optimizations + debug info # When debugging the REPL, use `REPL_DEBUG=1 repl_www/build.sh`
# (We need optimizations to get rid of dead code that otherwise causes compile errors!) if [ -n "${REPL_DEBUG:-}" ]
if [ -n "$REPL_DEBUG" ]
then then
# Leave out wasm-opt since it takes too long when debugging, and provide some debug options
cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release
wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/ wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/
else else

View file

@ -15,6 +15,8 @@ const repl = {
inputQueue: [], inputQueue: [],
inputHistory: [], inputHistory: [],
inputHistoryIndex: 0,
inputStash: "", // stash the user input while we're toggling through history with up/down arrows
textDecoder: new TextDecoder(), textDecoder: new TextDecoder(),
textEncoder: new TextEncoder(), textEncoder: new TextEncoder(),
@ -28,6 +30,7 @@ const repl = {
// Initialise // Initialise
repl.elemSourceInput.addEventListener("change", onInputChange); repl.elemSourceInput.addEventListener("change", onInputChange);
repl.elemSourceInput.addEventListener("keyup", onInputKeyup);
roc_repl_wasm.default().then((instance) => { roc_repl_wasm.default().then((instance) => {
repl.compiler = instance; repl.compiler = instance;
}); });
@ -38,6 +41,8 @@ roc_repl_wasm.default().then((instance) => {
function onInputChange(event) { function onInputChange(event) {
const inputText = event.target.value; const inputText = event.target.value;
if (!inputText) return;
event.target.value = ""; event.target.value = "";
repl.inputQueue.push(inputText); repl.inputQueue.push(inputText);
@ -46,13 +51,60 @@ function onInputChange(event) {
} }
} }
function onInputKeyup(event) {
const UP = 38;
const DOWN = 40;
const ENTER = 13;
const { keyCode } = event;
const el = repl.elemSourceInput;
switch (keyCode) {
case UP:
if (repl.inputHistoryIndex == repl.inputHistory.length - 1) {
repl.inputStash = el.value;
}
setInput(repl.inputHistory[repl.inputHistoryIndex]);
if (repl.inputHistoryIndex > 0) {
repl.inputHistoryIndex--;
}
break;
case DOWN:
if (repl.inputHistoryIndex === repl.inputHistory.length - 1) {
setInput(repl.inputStash);
} else {
repl.inputHistoryIndex++;
setInput(repl.inputHistory[repl.inputHistoryIndex]);
}
break;
case ENTER:
onInputChange({ target: repl.elemSourceInput });
break;
default:
break;
}
}
function setInput(value) {
const el = repl.elemSourceInput;
el.value = value;
el.selectionStart = value.length;
el.selectionEnd = value.length;
}
// Use a queue just in case we somehow get inputs very fast // Use a queue just in case we somehow get inputs very fast
// We want the REPL to only process one at a time, since we're using some global state. // We want the REPL to only process one at a time, since we're using some global state.
// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste? // In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste?
async function processInputQueue() { async function processInputQueue() {
while (repl.inputQueue.length) { while (repl.inputQueue.length) {
const inputText = repl.inputQueue[0]; const inputText = repl.inputQueue[0];
const historyIndex = createHistoryEntry(inputText); repl.inputHistoryIndex = createHistoryEntry(inputText);
repl.inputStash = "";
let outputText; let outputText;
let ok = true; let ok = true;
@ -63,7 +115,7 @@ async function processInputQueue() {
ok = false; ok = false;
} }
updateHistoryEntry(historyIndex, ok, outputText); updateHistoryEntry(repl.inputHistoryIndex, ok, outputText);
repl.inputQueue.shift(); repl.inputQueue.shift();
} }
} }

BIN
repl_www/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies] [dependencies]
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
roc_exhaustive = { path = "../compiler/exhaustive" }
roc_region = { path = "../compiler/region" } roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_parse = { path = "../compiler/parse" } roc_parse = { path = "../compiler/parse" }

View file

@ -29,8 +29,10 @@ const NESTED_DATATYPE: &str = "NESTED DATATYPE";
const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX"; const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX";
const NUMBER_OVERFLOWS_SUFFIX: &str = "NUMBER OVERFLOWS SUFFIX"; const NUMBER_OVERFLOWS_SUFFIX: &str = "NUMBER OVERFLOWS SUFFIX";
const NUMBER_UNDERFLOWS_SUFFIX: &str = "NUMBER UNDERFLOWS SUFFIX"; const NUMBER_UNDERFLOWS_SUFFIX: &str = "NUMBER UNDERFLOWS SUFFIX";
const OPAQUE_NOT_DEFINED: &str = "OPAQUE NOT DEFINED"; const OPAQUE_NOT_DEFINED: &str = "OPAQUE TYPE NOT DEFINED";
const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE DECLARED OUTSIDE SCOPE"; const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE TYPE DECLARED OUTSIDE SCOPE";
const OPAQUE_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED";
const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS";
pub fn can_problem<'b>( pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
@ -1447,6 +1449,24 @@ fn pretty_runtime_error<'b>(
title = OPAQUE_DECLARED_OUTSIDE_SCOPE; title = OPAQUE_DECLARED_OUTSIDE_SCOPE;
} }
RuntimeError::OpaqueNotApplied(loc_ident) => {
doc = alloc.stack(vec![
alloc.reflow("This opaque type is not applied to an argument:"),
alloc.region(lines.convert_region(loc_ident.region)),
alloc.note("Opaque types always wrap exactly one argument!"),
]);
title = OPAQUE_NOT_APPLIED;
}
RuntimeError::OpaqueAppliedToMultipleArgs(region) => {
doc = alloc.stack(vec![
alloc.reflow("This opaque type is applied to multiple arguments:"),
alloc.region(lines.convert_region(region)),
alloc.note("Opaque types always wrap exactly one argument!"),
]);
title = OPAQUE_OVER_APPLIED;
}
} }
(doc, title) (doc, title)

View file

@ -1,4 +1,5 @@
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use roc_module::ident::TagName;
use roc_region::all::LineInfo; use roc_region::all::LineInfo;
use std::path::PathBuf; use std::path::PathBuf;
use ven_pretty::DocAllocator; use ven_pretty::DocAllocator;
@ -9,8 +10,8 @@ pub fn mono_problem<'b>(
filename: PathBuf, filename: PathBuf,
problem: roc_mono::ir::MonoProblem, problem: roc_mono::ir::MonoProblem,
) -> Report<'b> { ) -> Report<'b> {
use roc_mono::exhaustive::Context::*; use roc_exhaustive::Context::*;
use roc_mono::exhaustive::Error::*; use roc_exhaustive::Error::*;
use roc_mono::ir::MonoProblem::*; use roc_mono::ir::MonoProblem::*;
match problem { match problem {
@ -120,7 +121,7 @@ pub fn mono_problem<'b>(
pub fn unhandled_patterns_to_doc_block<'b>( pub fn unhandled_patterns_to_doc_block<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
patterns: Vec<roc_mono::exhaustive::Pattern>, patterns: Vec<roc_exhaustive::Pattern>,
) -> RocDocBuilder<'b> { ) -> RocDocBuilder<'b> {
alloc alloc
.vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v))) .vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v)))
@ -130,19 +131,19 @@ pub fn unhandled_patterns_to_doc_block<'b>(
fn pattern_to_doc<'b>( fn pattern_to_doc<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
pattern: roc_mono::exhaustive::Pattern, pattern: roc_exhaustive::Pattern,
) -> RocDocBuilder<'b> { ) -> RocDocBuilder<'b> {
pattern_to_doc_help(alloc, pattern, false) pattern_to_doc_help(alloc, pattern, false)
} }
fn pattern_to_doc_help<'b>( fn pattern_to_doc_help<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
pattern: roc_mono::exhaustive::Pattern, pattern: roc_exhaustive::Pattern,
in_type_param: bool, in_type_param: bool,
) -> RocDocBuilder<'b> { ) -> RocDocBuilder<'b> {
use roc_mono::exhaustive::Literal::*; use roc_exhaustive::Literal::*;
use roc_mono::exhaustive::Pattern::*; use roc_exhaustive::Pattern::*;
use roc_mono::exhaustive::RenderAs; use roc_exhaustive::RenderAs;
match pattern { match pattern {
Anything => alloc.text("_"), Anything => alloc.text("_"),
@ -184,19 +185,26 @@ fn pattern_to_doc_help<'b>(
.append(alloc.intersperse(arg_docs, alloc.reflow(", "))) .append(alloc.intersperse(arg_docs, alloc.reflow(", ")))
.append(" }") .append(" }")
} }
RenderAs::Tag => { RenderAs::Tag | RenderAs::Opaque => {
let has_args = !args.is_empty(); let has_args = !args.is_empty();
let arg_docs = args let arg_docs = args
.into_iter() .into_iter()
.map(|v| pattern_to_doc_help(alloc, v, true)); .map(|v| pattern_to_doc_help(alloc, v, true));
let tag = &union.alternatives[tag_id.0 as usize]; let tag = &union.alternatives[tag_id.0 as usize];
let tag_name = tag.name.clone(); let tag_name = match union.render_as {
RenderAs::Tag => alloc.tag_name(tag.name.clone()),
RenderAs::Opaque => match tag.name {
TagName::Private(opaque) => alloc.wrapped_opaque_name(opaque),
_ => unreachable!(),
},
_ => unreachable!(),
};
// We assume the alternatives are sorted. If not, this assert will trigger // We assume the alternatives are sorted. If not, this assert will trigger
debug_assert!(tag_id == tag.tag_id); debug_assert!(tag_id == tag.tag_id);
let docs = std::iter::once(alloc.tag_name(tag_name)).chain(arg_docs); let docs = std::iter::once(tag_name).chain(arg_docs);
if in_type_param && has_args { if in_type_param && has_args {
alloc alloc

View file

@ -6,7 +6,9 @@ use roc_module::symbol::Symbol;
use roc_region::all::{LineInfo, Loc, Region}; use roc_region::all::{LineInfo, Loc, Region};
use roc_solve::solve; use roc_solve::solve;
use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::pretty_print::{Parens, WILDCARD};
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; use roc_types::types::{
AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt,
};
use std::path::PathBuf; use std::path::PathBuf;
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
@ -700,7 +702,9 @@ fn to_expr_report<'b>(
You can achieve that with record literal syntax.", You can achieve that with record literal syntax.",
)), )),
), ),
Reason::RecordUpdateKeys(symbol, expected_fields) => match found.clone().unwrap_alias() Reason::RecordUpdateKeys(symbol, expected_fields) => match found
.clone()
.unwrap_structural_alias()
{ {
ErrorType::Record(actual_fields, ext) => { ErrorType::Record(actual_fields, ext) => {
let expected_set: MutSet<_> = expected_fields.keys().cloned().collect(); let expected_set: MutSet<_> = expected_fields.keys().cloned().collect();
@ -986,7 +990,7 @@ fn count_arguments(tipe: &ErrorType) -> usize {
match tipe { match tipe {
Function(args, _, _) => args.len(), Function(args, _, _) => args.len(),
Alias(_, _, actual) => count_arguments(actual), Alias(_, _, actual, _) => count_arguments(actual),
_ => 0, _ => 0,
} }
} }
@ -1139,6 +1143,23 @@ fn format_category<'b>(
alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]), alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]),
alloc.text(" of type:"), alloc.text(" of type:"),
), ),
OpaqueWrap(opaque) => (
alloc.concat(vec![
alloc.text(format!("{}his ", t)),
alloc.opaque_name(*opaque),
alloc.text(" opaque wrapping"),
]),
alloc.text(" has the type:"),
),
OpaqueArg => (
alloc.concat(vec![
alloc.text(format!("{}his argument to an opaque type", t))
]),
alloc.text(" has type:"),
),
TagApply { TagApply {
tag_name: TagName::Global(name), tag_name: TagName::Global(name),
args_count: 0, args_count: 0,
@ -1465,8 +1486,13 @@ fn add_pattern_category<'b>(
Set => alloc.reflow(" sets of type:"), Set => alloc.reflow(" sets of type:"),
Map => alloc.reflow(" maps of type:"), Map => alloc.reflow(" maps of type:"),
Ctor(tag_name) => alloc.concat(vec![ Ctor(tag_name) => alloc.concat(vec![
alloc.reflow(" a "),
alloc.tag_name(tag_name.clone()), alloc.tag_name(tag_name.clone()),
alloc.reflow(" values of type:"), alloc.reflow(" tag of type:"),
]),
Opaque(opaque) => alloc.concat(vec![
alloc.opaque_name(*opaque),
alloc.reflow(" unwrappings of type:"),
]), ]),
Str => alloc.reflow(" strings:"), Str => alloc.reflow(" strings:"),
Num => alloc.reflow(" numbers:"), Num => alloc.reflow(" numbers:"),
@ -1520,6 +1546,7 @@ pub enum Problem {
TagsMissing(Vec<TagName>), TagsMissing(Vec<TagName>),
BadRigidVar(Lowercase, ErrorType), BadRigidVar(Lowercase, ErrorType),
OptionalRequiredMismatch(Lowercase), OptionalRequiredMismatch(Lowercase),
OpaqueComparedToNonOpaque,
} }
fn problems_to_tip<'b>( fn problems_to_tip<'b>(
@ -1700,7 +1727,7 @@ pub fn to_doc<'b>(
.collect(), .collect(),
), ),
Alias(symbol, args, _) => report_text::apply( Alias(symbol, args, _, _) => report_text::apply(
alloc, alloc,
parens, parens,
alloc.symbol_foreign_qualified(symbol), alloc.symbol_foreign_qualified(symbol),
@ -1880,7 +1907,7 @@ fn to_diff<'b>(
} }
} }
(Alias(symbol1, args1, _), Alias(symbol2, args2, _)) if symbol1 == symbol2 => { (Alias(symbol1, args1, _, _), Alias(symbol2, args2, _, _)) if symbol1 == symbol2 => {
let args_diff = traverse(alloc, Parens::InTypeParam, args1, args2); let args_diff = traverse(alloc, Parens::InTypeParam, args1, args2);
let left = report_text::apply( let left = report_text::apply(
alloc, alloc,
@ -1902,12 +1929,27 @@ fn to_diff<'b>(
} }
} }
(Alias(symbol, _, actual), other) if !symbol.module_id().is_builtin() => { (Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => {
// when diffing an alias with a non-alias, de-alias let left = to_doc(alloc, Parens::InFn, type1);
let right = to_doc(alloc, Parens::InFn, type2);
Diff {
left,
right,
status: Status::Different(vec![Problem::OpaqueComparedToNonOpaque]),
}
}
(Alias(symbol, _, actual, AliasKind::Structural), other)
if !symbol.module_id().is_builtin() =>
{
// when diffing a structural alias with a non-alias, de-alias
to_diff(alloc, parens, *actual, other) to_diff(alloc, parens, *actual, other)
} }
(other, Alias(symbol, _, actual)) if !symbol.module_id().is_builtin() => { (other, Alias(symbol, _, actual, AliasKind::Structural))
// when diffing an alias with a non-alias, de-alias if !symbol.module_id().is_builtin() =>
{
// when diffing a structural alias with a non-alias, de-alias
to_diff(alloc, parens, other, *actual) to_diff(alloc, parens, other, *actual)
} }
@ -1938,41 +1980,41 @@ fn to_diff<'b>(
let is_int = |t: &ErrorType| match t { let is_int = |t: &ErrorType| match t {
ErrorType::Type(Symbol::NUM_INT, _) => true, ErrorType::Type(Symbol::NUM_INT, _) => true,
ErrorType::Alias(Symbol::NUM_INT, _, _) => true, ErrorType::Alias(Symbol::NUM_INT, _, _, _) => true,
ErrorType::Type(Symbol::NUM_NUM, args) => { ErrorType::Type(Symbol::NUM_NUM, args) => {
matches!( matches!(
&args.get(0), &args.get(0),
Some(ErrorType::Type(Symbol::NUM_INTEGER, _)) Some(ErrorType::Type(Symbol::NUM_INTEGER, _))
| Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _)) | Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _, _))
) )
} }
ErrorType::Alias(Symbol::NUM_NUM, args, _) => { ErrorType::Alias(Symbol::NUM_NUM, args, _, _) => {
matches!( matches!(
&args.get(0), &args.get(0),
Some(ErrorType::Type(Symbol::NUM_INTEGER, _)) Some(ErrorType::Type(Symbol::NUM_INTEGER, _))
| Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _)) | Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _, _))
) )
} }
_ => false, _ => false,
}; };
let is_float = |t: &ErrorType| match t { let is_float = |t: &ErrorType| match t {
ErrorType::Type(Symbol::NUM_FLOAT, _) => true, ErrorType::Type(Symbol::NUM_FLOAT, _) => true,
ErrorType::Alias(Symbol::NUM_FLOAT, _, _) => true, ErrorType::Alias(Symbol::NUM_FLOAT, _, _, _) => true,
ErrorType::Type(Symbol::NUM_NUM, args) => { ErrorType::Type(Symbol::NUM_NUM, args) => {
matches!( matches!(
&args.get(0), &args.get(0),
Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _)) Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _))
| Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _)) | Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _, _))
) )
} }
ErrorType::Alias(Symbol::NUM_NUM, args, _) => { ErrorType::Alias(Symbol::NUM_NUM, args, _, _) => {
matches!( matches!(
&args.get(0), &args.get(0),
Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _)) Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _))
| Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _)) | Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _, _))
) )
} }
_ => false, _ => false,
@ -2829,7 +2871,7 @@ fn type_problem_to_pretty<'b>(
TagUnion(_, _) | RecursiveTagUnion(_, _, _) => { TagUnion(_, _) | RecursiveTagUnion(_, _, _) => {
bad_rigid_var(x, alloc.reflow("a tag value")) bad_rigid_var(x, alloc.reflow("a tag value"))
} }
Alias(symbol, _, _) | Type(symbol, _) => bad_rigid_var( Alias(symbol, _, _, _) | Type(symbol, _) => bad_rigid_var(
x, x,
alloc.concat(vec![ alloc.concat(vec![
alloc.reflow("a "), alloc.reflow("a "),
@ -2902,5 +2944,17 @@ fn type_problem_to_pretty<'b>(
), ),
alloc.reflow("Learn more about optional fields at TODO."), alloc.reflow("Learn more about optional fields at TODO."),
])), ])),
(OpaqueComparedToNonOpaque, _) => alloc.tip().append(alloc.concat(vec![
alloc.reflow(
"Type comparisons between an opaque type are only ever \
equal if both types are the same opaque type. Did you mean \
to create an opaque type by wrapping it? If I have an opaque type ",
),
alloc.type_str("Age := U32"),
alloc.reflow(" I can create an instance of this opaque type by doing "),
alloc.type_str("@Age 23"),
alloc.reflow("."),
])),
} }
} }

View file

@ -136,6 +136,7 @@ pub struct Palette<'a> {
pub type_variable: &'a str, pub type_variable: &'a str,
pub structure: &'a str, pub structure: &'a str,
pub alias: &'a str, pub alias: &'a str,
pub opaque: &'a str,
pub error: &'a str, pub error: &'a str,
pub line_number: &'a str, pub line_number: &'a str,
pub header: &'a str, pub header: &'a str,
@ -155,6 +156,7 @@ pub const DEFAULT_PALETTE: Palette = Palette {
type_variable: YELLOW_CODE, type_variable: YELLOW_CODE,
structure: GREEN_CODE, structure: GREEN_CODE,
alias: YELLOW_CODE, alias: YELLOW_CODE,
opaque: YELLOW_CODE,
error: RED_CODE, error: RED_CODE,
line_number: CYAN_CODE, line_number: CYAN_CODE,
header: CYAN_CODE, header: CYAN_CODE,
@ -326,6 +328,29 @@ impl<'a> RocDocAllocator<'a> {
.annotate(Annotation::GlobalTag) .annotate(Annotation::GlobalTag)
} }
pub fn opaque_name(&'a self, opaque: Symbol) -> DocBuilder<'a, Self, Annotation> {
let fmt = if opaque.module_id() == self.home {
// Render it unqualified if it's in the current module.
format!("{}", opaque.ident_str(self.interns))
} else {
format!(
"{}.{}",
opaque.module_string(self.interns),
opaque.ident_str(self.interns),
)
};
self.text(fmt).annotate(Annotation::Opaque)
}
pub fn wrapped_opaque_name(&'a self, opaque: Symbol) -> DocBuilder<'a, Self, Annotation> {
debug_assert_eq!(opaque.module_id(), self.home, "Opaque wrappings can only be defined in the same module they're defined in, but this one is defined elsewhere: {:?}", opaque);
// TODO(opaques): $->@
self.text(format!("${}", opaque.ident_str(self.interns)))
.annotate(Annotation::Opaque)
}
pub fn record_field(&'a self, lowercase: Lowercase) -> DocBuilder<'a, Self, Annotation> { pub fn record_field(&'a self, lowercase: Lowercase) -> DocBuilder<'a, Self, Annotation> {
self.text(format!(".{}", lowercase)) self.text(format!(".{}", lowercase))
.annotate(Annotation::RecordField) .annotate(Annotation::RecordField)
@ -667,7 +692,7 @@ impl<'a> RocDocAllocator<'a> {
} }
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug)]
pub enum Annotation { pub enum Annotation {
Emphasized, Emphasized,
Url, Url,
@ -677,6 +702,7 @@ pub enum Annotation {
RecordField, RecordField,
TypeVariable, TypeVariable,
Alias, Alias,
Opaque,
Structure, Structure,
Symbol, Symbol,
BinOp, BinOp,
@ -849,6 +875,9 @@ where
Alias => { Alias => {
self.write_str(self.palette.alias)?; self.write_str(self.palette.alias)?;
} }
Opaque => {
self.write_str(self.palette.alias)?;
}
BinOp => { BinOp => {
self.write_str(self.palette.alias)?; self.write_str(self.palette.alias)?;
} }
@ -903,7 +932,7 @@ where
self.write_str(RESET_CODE)?; self.write_str(RESET_CODE)?;
} }
TypeBlock | GlobalTag | PrivateTag | RecordField => { /* nothing yet */ } TypeBlock | GlobalTag | PrivateTag | Opaque | RecordField => { /* nothing yet */ }
}, },
} }
Ok(()) Ok(())

View file

@ -31,10 +31,7 @@ pub fn infer_expr(
constraint: &Constraint, constraint: &Constraint,
expr_var: Variable, expr_var: Variable,
) -> (Content, Subs) { ) -> (Content, Subs) {
let env = solve::Env { let env = solve::Env::default();
aliases: MutMap::default(),
vars_by_symbol: MutMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint); let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved let content = solved

View file

@ -64,7 +64,7 @@ mod test_reporting {
var, var,
constraint, constraint,
home, home,
mut interns, interns,
problems: can_problems, problems: can_problems,
.. ..
} = can_expr(arena, expr_src)?; } = can_expr(arena, expr_src)?;
@ -92,7 +92,7 @@ mod test_reporting {
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let mut procs = Procs::new_in(&arena); let mut procs = Procs::new_in(&arena);
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); let mut ident_ids = interns.all_ident_ids.get(&home).unwrap().clone();
let mut update_mode_ids = UpdateModeIds::new(); let mut update_mode_ids = UpdateModeIds::new();
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr // Populate Procs and Subs, and get the low-level Expr from the canonical Expr
@ -4490,32 +4490,6 @@ mod test_reporting {
) )
} }
#[test]
fn record_type_indent_end() {
report_problem_as(
indoc!(
r#"
f : { a: Int
}
"#
),
indoc!(
r#"
NEED MORE INDENTATION
I am partway through parsing a record type, but I got stuck here:
1 f : { a: Int
2 }
^
I need this curly brace to be indented more. Try adding more spaces
before it!
"#
),
)
}
#[test] #[test]
fn record_type_keyword_field_name() { fn record_type_keyword_field_name() {
report_problem_as( report_problem_as(
@ -5451,36 +5425,6 @@ mod test_reporting {
) )
} }
#[test]
fn list_bad_indent() {
report_problem_as(
indoc!(
r#"
x = [ 1, 2,
]
x
"#
),
indoc!(
r#"
UNFINISHED LIST
I cannot find the end of this list:
1 x = [ 1, 2,
^
You could change it to something like [ 1, 2, 3 ] or even just [].
Anything where there is an open and a close square bracket, and where
the elements of the list are separated by commas.
Note: I may be confused by indentation
"#
),
)
}
#[test] #[test]
fn number_double_dot() { fn number_double_dot() {
report_problem_as( report_problem_as(
@ -6469,38 +6413,6 @@ I need all branches in an `if` to have the same type!
) )
} }
#[test]
fn outdented_alias() {
report_problem_as(
indoc!(
r#"
Box item : [
Box item,
Items item item
]
4
"#
),
indoc!(
r#"
NEED MORE INDENTATION
I am partway through parsing a tag union type, but I got stuck here:
1 Box item : [
2 Box item,
3 Items item item
4 ]
^
I need this square bracket to be indented more. Try adding more spaces
before it!
"#
),
)
}
#[test] #[test]
fn outdented_in_parens() { fn outdented_in_parens() {
report_problem_as( report_problem_as(
@ -6532,36 +6444,6 @@ I need all branches in an `if` to have the same type!
) )
} }
#[test]
fn outdented_record() {
report_problem_as(
indoc!(
r#"
Box : {
id: Str
}
4
"#
),
indoc!(
r#"
NEED MORE INDENTATION
I am partway through parsing a record type, but I got stuck here:
1 Box : {
2 id: Str
3 }
^
I need this curly brace to be indented more. Try adding more spaces
before it!
"#
),
)
}
#[test] #[test]
fn backpassing_type_error() { fn backpassing_type_error() {
report_problem_as( report_problem_as(
@ -8147,7 +8029,7 @@ I need all branches in an `if` to have the same type!
), ),
indoc!( indoc!(
r#" r#"
OPAQUE NOT DEFINED OPAQUE TYPE NOT DEFINED
The opaque type Age referenced here is not defined: The opaque type Age referenced here is not defined:
@ -8172,7 +8054,7 @@ I need all branches in an `if` to have the same type!
), ),
indoc!( indoc!(
r#" r#"
OPAQUE NOT DEFINED OPAQUE TYPE NOT DEFINED
The opaque type Age referenced here is not defined: The opaque type Age referenced here is not defined:
@ -8208,10 +8090,20 @@ I need all branches in an `if` to have the same type!
OtherModule.$Age 21 OtherModule.$Age 21
"# "#
), ),
// TODO: get rid of the second error. Consider parsing OtherModule.$Age to completion // TODO: get rid of the first error. Consider parsing OtherModule.$Age to completion
// and checking it during can. // and checking it during can. The reason the error appears is because it is parsed as
// Apply(Error(OtherModule), [ $Age, 21 ])
indoc!( indoc!(
r#" r#"
OPAQUE TYPE NOT APPLIED
This opaque type is not applied to an argument:
1 OtherModule.$Age 21
^^^^
Note: Opaque types always wrap exactly one argument!
SYNTAX PROBLEM SYNTAX PROBLEM
I am trying to parse a qualified name here: I am trying to parse a qualified name here:
@ -8221,15 +8113,6 @@ I need all branches in an `if` to have the same type!
I was expecting to see an identifier next, like height. A complete I was expecting to see an identifier next, like height. A complete
qualified name looks something like Json.Decode.string. qualified name looks something like Json.Decode.string.
OPAQUE NOT DEFINED
The opaque type Age referenced here is not defined:
1 OtherModule.$Age 21
^^^^
Note: It looks like there are no opaque types declared in this scope yet!
"# "#
), ),
) )
@ -8247,9 +8130,9 @@ I need all branches in an `if` to have the same type!
$Age age $Age age
"# "#
), ),
// TODO: there is a potential for a better error message here, if the usage of `$Age` can // TODO(opaques): there is a potential for a better error message here, if the usage of
// be linked to the declaration of `Age` inside `age`, and a suggestion to raise that // `$Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to
// declaration to the outer scope. // raise that declaration to the outer scope.
indoc!( indoc!(
r#" r#"
UNUSED DEFINITION UNUSED DEFINITION
@ -8262,7 +8145,7 @@ I need all branches in an `if` to have the same type!
If you didn't intend on using `Age` then remove it so future readers of If you didn't intend on using `Age` then remove it so future readers of
your code don't wonder why it is there. your code don't wonder why it is there.
OPAQUE NOT DEFINED OPAQUE TYPE NOT DEFINED
The opaque type Age referenced here is not defined: The opaque type Age referenced here is not defined:
@ -8305,4 +8188,256 @@ I need all branches in an `if` to have the same type!
), ),
) )
} }
#[test]
fn opaque_mismatch_check() {
report_problem_as(
indoc!(
r#"
Age := U8
n : Age
n = $Age ""
n
"#
),
// TODO(opaques): error could be improved by saying that the opaque definition demands
// that the argument be a U8, and linking to the definitin!
indoc!(
r#"
TYPE MISMATCH
This expression is used in an unexpected way:
4 n = $Age ""
^^
This argument to an opaque type has type:
Str
But you are trying to use it as:
U8
"#
),
)
}
#[test]
fn opaque_mismatch_infer() {
report_problem_as(
indoc!(
r#"
F n := n
if True
then $F ""
else $F {}
"#
),
indoc!(
r#"
TYPE MISMATCH
This expression is used in an unexpected way:
5 else $F {}
^^
This argument to an opaque type has type:
{}
But you are trying to use it as:
Str
"#
),
)
}
#[test]
fn opaque_creation_is_not_wrapped() {
report_problem_as(
indoc!(
r#"
F n := n
v : F Str
v = ""
v
"#
),
indoc!(
r#"
TYPE MISMATCH
Something is off with the body of the `v` definition:
3 v : F Str
4 v = ""
^^
The body is a string of type:
Str
But the type annotation on `v` says it should be:
F Str
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
"#
),
)
}
#[test]
fn opaque_mismatch_pattern_check() {
report_problem_as(
indoc!(
r#"
Age := U8
f : Age -> U8
f = \Age n -> n
f
"#
),
// TODO(opaques): error could be improved by saying that the user-provided pattern
// probably wants to change "Age" to "@Age"!
indoc!(
r#"
TYPE MISMATCH
This pattern is being used in an unexpected way:
4 f = \Age n -> n
^^^^^
It is a `Age` tag of type:
[ Age a ]
But it needs to match:
Age
Tip: Type comparisons between an opaque type are only ever equal if
both types are the same opaque type. Did you mean to create an opaque
type by wrapping it? If I have an opaque type Age := U32 I can create
an instance of this opaque type by doing @Age 23.
"#
),
)
}
#[test]
fn opaque_mismatch_pattern_infer() {
report_problem_as(
indoc!(
r#"
F n := n
\x ->
when x is
$F A -> ""
$F {} -> ""
"#
),
indoc!(
r#"
TYPE MISMATCH
The 2nd pattern in this `when` does not match the previous ones:
6 $F {} -> ""
^^^^^
The 2nd pattern is trying to matchF unwrappings of type:
F {}a
But all the previous branches match:
F [ A ]
"#
),
)
}
#[test]
fn opaque_pattern_match_not_exhaustive_tag() {
report_problem_as(
indoc!(
r#"
F n := n
v : F [ A, B, C ]
when v is
$F A -> ""
$F B -> ""
"#
),
indoc!(
r#"
UNSAFE PATTERN
This `when` does not cover all the possibilities:
5> when v is
6> $F A -> ""
7> $F B -> ""
Other possibilities include:
$F C
I would have to crash if I saw one of those! Add branches for them!
"#
),
)
}
#[test]
fn opaque_pattern_match_not_exhaustive_int() {
report_problem_as(
indoc!(
r#"
F n := n
v : F U8
when v is
$F 1 -> ""
$F 2 -> ""
"#
),
indoc!(
r#"
UNSAFE PATTERN
This `when` does not cover all the possibilities:
5> when v is
6> $F 1 -> ""
7> $F 2 -> ""
Other possibilities include:
$F _
I would have to crash if I saw one of those! Add branches for them!
"#
),
)
}
} }

View file

@ -183,8 +183,7 @@ is zero-configuration like `elm-format`) formats multi-line record literals (and
record types) with a comma at the end of each line, like so: record types) with a comma at the end of each line, like so:
```elm ```elm
user = user = {
{
firstName: "Sam", firstName: "Sam",
lastName: "Sample", lastName: "Sample",
email: "sam@example.com", email: "sam@example.com",
@ -456,22 +455,19 @@ The key is that each of the error types is a type alias for a Roc *tag union*.
Here's how those look: Here's how those look:
```elm ```elm
Http.Err a : Http.Err a : [
[
PageNotFound, PageNotFound,
Timeout, Timeout,
BadPayload Str, BadPayload Str,
]a ]a
File.ReadErr a : File.ReadErr a : [
[
FileNotFound, FileNotFound,
Corrupted, Corrupted,
BadFormat, BadFormat,
]a ]a
File.WriteErr a : File.WriteErr a : [
[
FileNotFound, FileNotFound,
DiskFull, DiskFull,
]a ]a
@ -758,86 +754,6 @@ Elm does permit overriding open imports - e.g. if you have
`import Foo exposing (bar)`, or `import Foo exposing (..)`, you can still define `import Foo exposing (bar)`, or `import Foo exposing (..)`, you can still define
`bar = ...` in the module. Roc treats this as shadowing and does not allow it. `bar = ...` in the module. Roc treats this as shadowing and does not allow it.
## Function equality
In Elm, if you write `(\val -> val) == (\val -> val)`, you currently get a runtime exception
which links to [the `==` docs](https://package.elm-lang.org/packages/elm/core/latest/Basics#==),
which explain why this is the current behavior and what the better version will look like.
> OCaml also has the "runtime exception if you compare functions for structural equality"
> behavior, but unlike Elm, in OCaml this appears to be the long-term design.
In Roc, function equality is a compile error, tracked explicitly in the type system.
Here's the type of Roc's equality function:
```elm
'val, 'val -> Bool
```
Whenever a named type variable in Roc has a `'` at the beginning, that means
it is a *functionless* type - a type which cannot involve functions.
If there are any functions in that type, you get a type mismatch. This is true
whether `val` itself is a function, or if it's a type that wraps a function,
like `{ predicate: (Str -> Bool) }` or `List (Bool -> Bool)`.
So if you write `(\a -> a) == (\a -> a)` in Roc, you'll get a type mismatch.
If you wrap both sides of that `==` in a record or list, you'll still get a
type mismatch.
If a named type variable has a `'` anywhere in a given type, then it must have a `'`
everywhere in that type. So it would be an error to have a type like `x, 'x -> Bool`
because `x` has a `'` in one place but not everywhere.
## Standard Data Structures
Elm has `List`, `Array`, `Set`, and `Dict` in the standard library.
Roc has all of these except `Array`, and there are some differences in how they work:
* `List` in Roc uses the term "list" the way Python does: to mean an ordered sequence of elements. Roc's `List` is more like an array, in that all the elements are sequential in memory and can be accessed in constant time. It still uses the `[` `]` syntax for list literals. Also there is no `::` operator because "cons" is not an efficient operation on an array like it is in a linked list.
* `Set` in Roc is like `Set` in Elm: it's shorthand for a `Dict` with keys but no value, and it has a slightly different API.
* `Dict` in Roc is like `Dict` in Elm, except it's backed by hashing rather than ordering. Roc silently computes hash values for any value that can be used with `==`, so instead of a `comparable` constraint on `Set` elements and `Dict` keys, in Roc they instead have the *functionless* constraint indicated with a `'`.
Roc also has a literal syntax for dictionaries and sets. Here's how to write a `Dict` literal:
```elm
{: "Sam" => True, "Ali" => False, firstName => False :}
```
This expression has the type `Dict Str Bool`, and the `firstName` variable would
necessarily be a `Str` as well.
The `Dict` literal syntax is for two reasons. First, Roc doesn't have tuples;
without tuples, initializing the above `Dict` would involve an API that looked
something like one of these:
```elm
Dict.fromList [ { k: "Sam", v: True }, { k: "Ali", v: False }, { k: firstName, v: False } ]
Dict.fromList [ KV "Sam" True, KV "Ali" False KV firstName False
```
This works, but is not nearly as nice to read.
Additionally, `Dict` literals can compile directly to efficient initialization code
without needing to (hopefully be able to) optimize away the intermediate
`List` involved in `fromList`.
`{::}` is an empty `Dict`.
You can write a `Set` literal like this:
```elm
[: "Sam", "Ali", firstName :]
```
The `Set` literal syntax is partly for the initialization benefit, and also
for symmetry with the `Dict` literal syntax.
`[::]` is an empty `Set`.
Roc does not have syntax for pattern matching on data structures - not even `[` `]` like Elm does.
## Operators ## Operators
In Elm, operators are functions. In Roc, all operators are syntax sugar. In Elm, operators are functions. In Roc, all operators are syntax sugar.
@ -1318,51 +1234,6 @@ If you put these into a hypothetical Roc REPL, here's what you'd see:
28 : Int * 28 : Int *
``` ```
## Phantom Types
[Phantom types](https://medium.com/@ckoster22/advanced-types-in-elm-phantom-types-808044c5946d)
exist in Elm but not in Roc. This is because phantom types can't be defined
using type aliases (in fact, there is a custom error message in Elm if you
try to do this), and Roc only has type aliases. However, in Roc, you can achieve
the same API and runtime performance characteristics as if you had phantom types,
by using *phantom values* instead.
A phantom value is one which affects types, but which holds no information at runtime.
As an example, let's say I wanted to define a [units library](https://package.elm-lang.org/packages/ianmackenzie/elm-units/latest/) -
a classic example of phantom types. I could do that in Roc like this:
```
Quantity units data : [ Quantity units data ]
km : Num a -> Quantity [ Km ] (Num a)
km = \num ->
Quantity Km num
cm : Num a -> Quantity [ Cm ] (Num a)
cm = \num ->
Quantity Cm num
mm : Num a -> Quantity [ Mm ] (Num a)
mm = \num ->
Quantity Mm num
add : Quantity u (Num a), Quantity u (Num a) -> Quantity u (Num a)
add = \Quantity units a, Quantity _ b ->
Quantity units (a + b)
```
From a performance perspective, it's relevant here that `[ Km ]`, `[ Cm ]`, and `[ Mm ]`
are all unions containing a single tag. That means they hold no information at runtime
(they would always destructure to the same tag), which means they can be "unboxed" away -
that is, discarded prior to code generation.
During code generation, Roc treats `Quantity [ Km ] Int` as equivalent to `Quantity Int`.
Then, because `Quantity Int` is an alias for `[ Quantity Int ]`, it will unbox again
and reduce that all the way down to to `Int`.
This means that, just like phantom *types*, phantom *values* affect type checking
only, and have no runtime overhead. Rust has a related concept called [phantom data](https://doc.rust-lang.org/nomicon/phantom-data.html).
## Standard library ## Standard library
`elm/core` has these modules: `elm/core` has these modules:

View file

@ -11,6 +11,12 @@ cd $SCRIPT_RELATIVE_DIR
rm -rf build/ rm -rf build/
cp -r public/ build/ cp -r public/ build/
# Build the repl page
mkdir -p build/repl
pushd ..
repl_www/build.sh www/build/repl
popd
# grab the source code and copy it to Netlify's server; if it's not there, fail the build. # grab the source code and copy it to Netlify's server; if it's not there, fail the build.
pushd build pushd build
wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip

16
www/netlify.sh Normal file
View file

@ -0,0 +1,16 @@
#!/bin/bash
# Runs on every Netlify build, to set up the Netlify server.
set -euxo pipefail
rustup update
rustup default stable
# TODO remove this once we actually build the web repl!
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
REPL_WASM_DATA=${SCRIPT_DIR}/../repl_wasm/data/
mkdir -p ${REPL_WASM_DATA}
touch ${REPL_WASM_DATA}/pre_linked_binary.o
bash build.sh

View file

@ -5,7 +5,7 @@
# https://docs.netlify.com/routing/headers/#syntax-for-the-netlify-configuration-file # https://docs.netlify.com/routing/headers/#syntax-for-the-netlify-configuration-file
[build] [build]
publish = "build/" publish = "build/"
command = "bash build.sh" command = "bash netlify.sh"
[[headers]] [[headers]]
for = "/*" for = "/*"

Some files were not shown because too many files have changed in this diff Show more