mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 00:01:16 +00:00
Merge remote-tracking branch 'origin/trunk' into gen-dev/quicksort2
This commit is contained in:
commit
3bada97067
102 changed files with 3628 additions and 2851 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -3222,6 +3222,7 @@ name = "repl_test"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"lazy_static",
|
||||
"roc_cli",
|
||||
"roc_repl_cli",
|
||||
"roc_test_utils",
|
||||
|
@ -3501,6 +3502,16 @@ dependencies = [
|
|||
name = "roc_error_macros"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "roc_exhaustive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
"roc_std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_fmt"
|
||||
version = "0.1.0"
|
||||
|
@ -3650,6 +3661,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_exhaustive",
|
||||
"roc_module",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
|
@ -3713,6 +3725,7 @@ dependencies = [
|
|||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_repl_eval",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"rustyline",
|
||||
|
@ -3770,6 +3783,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_exhaustive",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
|
|
|
@ -3,6 +3,7 @@ members = [
|
|||
"compiler/ident",
|
||||
"compiler/region",
|
||||
"compiler/collections",
|
||||
"compiler/exhaustive",
|
||||
"compiler/module",
|
||||
"compiler/parse",
|
||||
"compiler/can",
|
||||
|
|
|
@ -33,12 +33,15 @@ install-zig-llvm-valgrind-clippy-rustfmt:
|
|||
RUN rustup component add clippy
|
||||
# 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
|
||||
RUN cargo install cargo-criterion
|
||||
# editor
|
||||
RUN apt -y install libxkbcommon-dev
|
||||
# sccache
|
||||
RUN apt -y install libssl-dev
|
||||
RUN cargo install sccache
|
||||
RUN sccache -V
|
||||
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
|
||||
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
|
||||
# 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 echo "4" | cargo run --locked --release --features="target-x86" -- --backend=x86_32 examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
|
|
|
@ -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:
|
||||
|
||||
```coffee
|
||||
User :
|
||||
{
|
||||
User : {
|
||||
email : Str,
|
||||
firstName : 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:
|
||||
|
||||
```coffee
|
||||
User a :
|
||||
{
|
||||
User a : {
|
||||
email : Str,
|
||||
firstName : Str,
|
||||
lastName : Str,
|
||||
|
|
|
@ -154,12 +154,18 @@ fn canonicalize_field<'a>(
|
|||
let (loc_can_expr, output) =
|
||||
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,
|
||||
value_expr: loc_can_expr,
|
||||
value_output: output,
|
||||
var: field_var,
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue {
|
||||
|
|
|
@ -15,6 +15,24 @@ pub struct AST {
|
|||
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)]
|
||||
pub enum ASTNodeId {
|
||||
ADefId(DefId),
|
||||
|
|
|
@ -148,6 +148,9 @@ fn expr2_to_string_helper(
|
|||
&Expr2::Var { .. } => {
|
||||
out_string.push_str(&format!("{:?}", expr2,));
|
||||
}
|
||||
Expr2::RuntimeError { .. } => {
|
||||
out_string.push_str("RuntimeError\n");
|
||||
}
|
||||
other => todo!("Implement for {:?}", other),
|
||||
}
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ pub fn expr_to_expr2<'a>(
|
|||
region: Region,
|
||||
) -> (Expr2, self::Output) {
|
||||
use roc_parse::ast::Expr::*;
|
||||
//dbg!("{:?}", parse_expr);
|
||||
|
||||
match parse_expr {
|
||||
Float(string) => {
|
||||
|
|
|
@ -43,7 +43,8 @@ fn to_type2(
|
|||
var_store: &mut VarStore,
|
||||
) -> Type2 {
|
||||
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);
|
||||
|
||||
for (type_variable_node_id, (lowercase, solved_arg)) in type_variables
|
||||
|
|
|
@ -27,7 +27,7 @@ pub fn load_module(src_file: &Path) -> LoadedModule {
|
|||
Ok(x) => x,
|
||||
Err(roc_load::file::LoadingProblem::FormattedReport(report)) => {
|
||||
panic!(
|
||||
"Failed to load module from src_file {:?}. Report: {:?}",
|
||||
"Failed to load module from src_file {:?}. Report: {}",
|
||||
src_file, report
|
||||
);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ pub fn parse_from_string<'a>(
|
|||
) -> ASTResult<AST> {
|
||||
let blank_line_indx = code_str
|
||||
.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 tail_str = &code_str[blank_line_indx..];
|
||||
|
|
|
@ -12,7 +12,8 @@ use roc_types::subs::{
|
|||
SubsSlice, UnionTags, Variable, VariableSubsSlice,
|
||||
};
|
||||
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::Mode;
|
||||
|
@ -892,7 +893,9 @@ fn type_to_variable<'a>(
|
|||
let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []);
|
||||
|
||||
let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type);
|
||||
let content = Content::Alias(*symbol, arg_vars, alias_var);
|
||||
|
||||
// TODO(opaques): take opaques into account
|
||||
let content = Content::Alias(*symbol, arg_vars, alias_var, AliasKind::Structural);
|
||||
|
||||
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();
|
||||
|
||||
for var_index in args.variables() {
|
||||
|
@ -1544,7 +1547,7 @@ fn instantiate_rigids_help(
|
|||
subs.set(copy, make_descriptor(FlexVar(Some(name))));
|
||||
}
|
||||
|
||||
Alias(_, args, real_type_var) => {
|
||||
Alias(_, args, real_type_var, _) => {
|
||||
for var_index in args.variables() {
|
||||
let var = subs[var_index];
|
||||
instantiate_rigids_help(subs, max_rank, pools, var);
|
||||
|
@ -1794,7 +1797,7 @@ fn deep_copy_var_help(
|
|||
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());
|
||||
|
||||
for var_index in args.variables() {
|
||||
|
@ -1806,7 +1809,7 @@ fn deep_copy_var_help(
|
|||
args.replace_variables(subs, new_args);
|
||||
|
||||
let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var);
|
||||
let new_content = Alias(symbol, args, new_real_type_var);
|
||||
let new_content = Alias(symbol, args, new_real_type_var, kind);
|
||||
|
||||
subs.set(copy, make_descriptor(new_content));
|
||||
|
||||
|
|
|
@ -64,13 +64,12 @@ pub fn expr2_to_markup<'a>(
|
|||
Expr2::Str(text) => {
|
||||
let content = format!("\"{}\"", text.as_str(env.pool));
|
||||
|
||||
new_markup_node(
|
||||
with_indent(indent_level, &content),
|
||||
ast_node_id,
|
||||
HighlightStyle::String,
|
||||
mark_node_pool,
|
||||
indent_level,
|
||||
)
|
||||
string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
|
||||
}
|
||||
Expr2::SmallStr(array_str) => {
|
||||
let content = format!("\"{}\"", array_str.as_str());
|
||||
|
||||
string_mark_node(&content, indent_level, ast_node_id, mark_node_pool)
|
||||
}
|
||||
Expr2::GlobalTag { name, .. } => new_markup_node(
|
||||
with_indent(indent_level, &get_string(env, name)),
|
||||
|
@ -387,3 +386,18 @@ fn with_indent(indent_level: usize, some_str: &str) -> 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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -468,3 +468,34 @@ pub fn join_mark_nodes_commas(
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,8 +103,8 @@ fn generate_object_file(
|
|||
|
||||
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
|
||||
|
||||
// we store this .o file in rust's `target` folder
|
||||
run_command(&bitcode_path, "mv", &[src_obj, dest_obj]);
|
||||
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
|
||||
run_command(&bitcode_path, "cp", &[src_obj, dest_obj]);
|
||||
}
|
||||
|
||||
fn generate_bc_file(
|
||||
|
|
|
@ -392,27 +392,19 @@ toUtf32Le : Str -> List U8
|
|||
|
||||
# Parsing
|
||||
|
||||
## If the string begins 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.
|
||||
## If the bytes begin with a valid [extended grapheme cluster](http://www.unicode.org/glossary/#extended_grapheme_cluster)
|
||||
## 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
|
||||
## empty, return `Err`.
|
||||
parseGrapheme : Str -> Result { val : Str, rest : Str } [ Expected [ Grapheme ]* Str ]*
|
||||
## If the bytes do not begin with a valid grapheme, for example because the list was
|
||||
## empty or began with an invalid grapheme, return `Err`.
|
||||
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),
|
||||
## return it along with the rest of the string after that code point.
|
||||
## If the bytes begin with a valid [Unicode code point](http://www.unicode.org/glossary/#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
|
||||
## empty, return `Err`.
|
||||
parseCodePt : Str -> Result { val : U32, rest : Str } [ Expected [ CodePt ]* Str ]*
|
||||
|
||||
## 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 does not begin with a valid code point, for example because the list was
|
||||
## empty or began with an invalid code point, return an `Err`.
|
||||
parseUtf8CodePt : List U8 -> Result { codePt : U32, bytesParsed: Nat } [ InvalidCodePt ]*
|
||||
|
||||
## If the string represents a valid [U8] number, return that number.
|
||||
##
|
||||
|
|
|
@ -312,9 +312,6 @@ fn can_annotation_help(
|
|||
match scope.lookup_alias(symbol) {
|
||||
Some(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() {
|
||||
let error = Type::Erroneous(Problem::BadTypeArguments {
|
||||
|
@ -326,40 +323,18 @@ fn can_annotation_help(
|
|||
return error;
|
||||
}
|
||||
|
||||
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args.into_iter()) {
|
||||
let name = loc_var.value.0.clone();
|
||||
let var = loc_var.value.1;
|
||||
|
||||
substitutions.insert(var, arg_ann.clone());
|
||||
vars.push((name.clone(), arg_ann));
|
||||
}
|
||||
|
||||
// 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);
|
||||
let (type_arguments, lambda_set_variables, actual) =
|
||||
instantiate_and_freshen_alias_type(
|
||||
var_store,
|
||||
&alias.type_variables,
|
||||
args,
|
||||
&alias.lambda_set_variables,
|
||||
alias.typ.clone(),
|
||||
);
|
||||
|
||||
Type::Alias {
|
||||
symbol,
|
||||
type_arguments: vars,
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
actual: Box::new(actual),
|
||||
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)
|
||||
where
|
||||
F: FnMut(&T, &T) -> std::cmp::Ordering,
|
||||
|
|
|
@ -11,7 +11,7 @@ use roc_types::{subs::Variable, types::VariableDetail};
|
|||
/// constraints makes them behaviorally different from unification-based constraints.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PresenceConstraint {
|
||||
IncludesTag(TagName, Vec<Type>),
|
||||
IncludesTag(TagName, Vec<Type>, Region, PatternCategory),
|
||||
IsOpen,
|
||||
Pattern(Region, PatternCategory, PExpected<Type>),
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ fn validate_help(constraint: &Constraint, declared: &Declared, accum: &mut Varia
|
|||
Constraint::Present(typ, constr) => {
|
||||
subtract(declared, &typ.variables_detail(), accum);
|
||||
match constr {
|
||||
PresenceConstraint::IncludesTag(_, tys) => {
|
||||
PresenceConstraint::IncludesTag(_, tys, _, _) => {
|
||||
for ty in tys {
|
||||
subtract(declared, &ty.variables_detail(), accum);
|
||||
}
|
||||
|
|
|
@ -836,11 +836,10 @@ fn pattern_to_vars_by_symbol(
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
vars_by_symbol.insert(*opaque, expr_var);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::annotation::IntroducedVariables;
|
||||
use crate::annotation::{freshen_opaque_def, IntroducedVariables};
|
||||
use crate::builtins::builtin_defs_map;
|
||||
use crate::def::{can_defs_with_return, Def};
|
||||
use crate::env::Env;
|
||||
|
@ -19,7 +19,7 @@ use roc_parse::pattern::PatternType::*;
|
|||
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
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::{char, u32};
|
||||
|
||||
|
@ -173,9 +173,29 @@ pub enum Expr {
|
|||
arguments: Vec<(Variable, Loc<Expr>)>,
|
||||
},
|
||||
|
||||
/// A wrapping of an opaque type, like `$Age 21`
|
||||
// TODO(opaques): $->@ above when opaques land
|
||||
OpaqueRef {
|
||||
opaque_var: Variable,
|
||||
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
|
||||
|
@ -388,29 +408,65 @@ pub fn canonicalize_expr<'a>(
|
|||
// (foo) bar baz
|
||||
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
|
||||
let mut args = Vec::new();
|
||||
let mut outputs = Vec::new();
|
||||
let mut output = Output::default();
|
||||
|
||||
for loc_arg in loc_args.iter() {
|
||||
let (arg_expr, arg_out) =
|
||||
canonicalize_expr(env, var_store, scope, loc_arg.region, &loc_arg.value);
|
||||
|
||||
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.
|
||||
output.tail_call = None;
|
||||
|
||||
for arg_out in outputs {
|
||||
output.references = output.references.union(arg_out.references);
|
||||
}
|
||||
|
||||
let expr = match fn_expr.value {
|
||||
Var(symbol) => {
|
||||
output.references.calls.insert(symbol);
|
||||
|
@ -447,10 +503,6 @@ pub fn canonicalize_expr<'a>(
|
|||
name,
|
||||
arguments: args,
|
||||
},
|
||||
OpaqueRef { name, .. } => OpaqueRef {
|
||||
name,
|
||||
arguments: args,
|
||||
},
|
||||
ZeroArgumentTag {
|
||||
variant_var,
|
||||
ext_var,
|
||||
|
@ -479,6 +531,7 @@ pub fn canonicalize_expr<'a>(
|
|||
|
||||
(expr, output)
|
||||
}
|
||||
}
|
||||
ast::Expr::Var { module_name, ident } => {
|
||||
canonicalize_lookup(env, scope, module_name, ident, region)
|
||||
}
|
||||
|
@ -728,19 +781,16 @@ pub fn canonicalize_expr<'a>(
|
|||
Output::default(),
|
||||
)
|
||||
}
|
||||
ast::Expr::OpaqueRef(opaque_ref) => match scope.lookup_opaque_ref(opaque_ref, region) {
|
||||
Ok(name) => (
|
||||
OpaqueRef {
|
||||
name,
|
||||
arguments: vec![],
|
||||
},
|
||||
Output::default(),
|
||||
),
|
||||
Err(runtime_error) => {
|
||||
env.problem(Problem::RuntimeError(runtime_error.clone()));
|
||||
(RuntimeError(runtime_error), Output::default())
|
||||
ast::Expr::OpaqueRef(opaque_ref) => {
|
||||
// If we're here, the opaque reference is definitely not wrapping an argument - wrapped
|
||||
// arguments are handled in the Apply branch.
|
||||
let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at(
|
||||
region,
|
||||
(*opaque_ref).into(),
|
||||
));
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
(RuntimeError(problem), Output::default())
|
||||
}
|
||||
},
|
||||
ast::Expr::Expect(condition, continuation) => {
|
||||
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 } => {
|
||||
let arguments = arguments
|
||||
.into_iter()
|
||||
.map(|(var, loc_expr)| {
|
||||
(
|
||||
OpaqueRef {
|
||||
opaque_var,
|
||||
name,
|
||||
argument,
|
||||
specialized_def_type,
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
} => {
|
||||
let (var, loc_expr) = *argument;
|
||||
let argument = Box::new((
|
||||
var,
|
||||
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 {
|
||||
|
|
|
@ -543,15 +543,15 @@ fn fix_values_captured_in_closure_pattern(
|
|||
AppliedTag {
|
||||
arguments: loc_args,
|
||||
..
|
||||
}
|
||||
| UnwrappedOpaque {
|
||||
arguments: loc_args,
|
||||
..
|
||||
} => {
|
||||
for (_, loc_arg) in loc_args.iter_mut() {
|
||||
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, .. } => {
|
||||
for loc_destruct in destructs.iter_mut() {
|
||||
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);
|
||||
}
|
||||
|
||||
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } | OpaqueRef { arguments, .. } => {
|
||||
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
|
||||
for (_, loc_arg) in arguments.iter_mut() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::annotation::freshen_opaque_def;
|
||||
use crate::env::Env;
|
||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
||||
use crate::num::{
|
||||
|
@ -5,7 +6,6 @@ use crate::num::{
|
|||
NumericBound, ParsedNumResult,
|
||||
};
|
||||
use crate::scope::Scope;
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
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_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{LambdaSet, Type};
|
||||
|
||||
/// A pattern, including possible problems (e.g. shadowing) so that
|
||||
/// codegen can generate a runtime error if this pattern is reached.
|
||||
|
@ -28,7 +29,26 @@ pub enum Pattern {
|
|||
UnwrappedOpaque {
|
||||
whole_var: Variable,
|
||||
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 {
|
||||
whole_var: Variable,
|
||||
|
@ -87,13 +107,12 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
}
|
||||
}
|
||||
UnwrappedOpaque {
|
||||
opaque, arguments, ..
|
||||
opaque, argument, ..
|
||||
} => {
|
||||
symbols.push(*opaque);
|
||||
for (_, nested) in arguments {
|
||||
let (_, nested) = &**argument;
|
||||
symbols_from_pattern_help(&nested.value, symbols);
|
||||
}
|
||||
}
|
||||
RecordDestructure { destructs, .. } => {
|
||||
for destruct in destructs {
|
||||
// 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![],
|
||||
}
|
||||
}
|
||||
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) => {
|
||||
let mut can_patterns = Vec::with_capacity(patterns.len());
|
||||
for loc_pattern in *patterns {
|
||||
|
@ -212,11 +238,34 @@ pub fn canonicalize_pattern<'a>(
|
|||
}
|
||||
|
||||
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(),
|
||||
opaque,
|
||||
arguments: can_patterns,
|
||||
},
|
||||
argument,
|
||||
specialized_def_type: Box::new(specialized_def_type),
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(runtime_error) => {
|
||||
env.problem(Problem::RuntimeError(runtime_error));
|
||||
|
||||
|
@ -557,13 +606,10 @@ fn add_bindings_from_patterns(
|
|||
}
|
||||
}
|
||||
UnwrappedOpaque {
|
||||
arguments: loc_args,
|
||||
opaque,
|
||||
..
|
||||
argument, opaque, ..
|
||||
} => {
|
||||
for (_, loc_arg) in loc_args {
|
||||
let (_, loc_arg) = &**argument;
|
||||
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, answer);
|
||||
}
|
||||
answer.push((*opaque, *region));
|
||||
}
|
||||
RecordDestructure { destructs, .. } => {
|
||||
|
|
|
@ -110,7 +110,7 @@ impl Scope {
|
|||
&self,
|
||||
opaque_ref: &str,
|
||||
lookup_region: Region,
|
||||
) -> Result<Symbol, RuntimeError> {
|
||||
) -> Result<(Symbol, &Alias), RuntimeError> {
|
||||
debug_assert!(opaque_ref.starts_with('$'));
|
||||
let opaque = opaque_ref[1..].into();
|
||||
|
||||
|
@ -139,7 +139,7 @@ impl Scope {
|
|||
Some(alias.header_region()),
|
||||
)),
|
||||
// All is good
|
||||
AliasKind::Opaque => Ok(*symbol),
|
||||
AliasKind::Opaque => Ok((*symbol, alias)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,13 +12,12 @@ use roc_can::expr::Expr::{self, *};
|
|||
use roc_can::expr::{ClosureData, Field, WhenBranch};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_collections::all::{ImMap, Index, MutSet, SendMap};
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::{ModuleId, Symbol};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Variable;
|
||||
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
|
||||
#[derive(Default, Debug)]
|
||||
|
@ -917,7 +916,79 @@ pub fn constrain_expr(
|
|||
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 } => {
|
||||
// This is a modified version of what we do for function calls.
|
||||
|
|
|
@ -47,7 +47,7 @@ pub fn constrain_imported_values(
|
|||
|
||||
// an imported symbol can be either an alias or a value
|
||||
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
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -5,12 +5,11 @@ use roc_can::expected::{Expected, PExpected};
|
|||
use roc_can::pattern::Pattern::{self, *};
|
||||
use roc_can::pattern::{DestructType, RecordDestruct};
|
||||
use roc_collections::all::{Index, SendMap};
|
||||
use roc_error_macros::todo_opaques;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
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)]
|
||||
pub struct PatternState {
|
||||
|
@ -118,7 +117,35 @@ fn headers_from_annotation_help(
|
|||
_ => 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);
|
||||
}
|
||||
|
||||
let pat_category = PatternCategory::Ctor(tag_name.clone());
|
||||
|
||||
let whole_con = Constraint::Present(
|
||||
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(
|
||||
Type::Variable(*whole_var),
|
||||
PresenceConstraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Ctor(tag_name.clone()),
|
||||
expected,
|
||||
),
|
||||
PresenceConstraint::Pattern(region, pat_category, expected),
|
||||
);
|
||||
|
||||
state.vars.push(*whole_var);
|
||||
|
@ -413,6 +443,76 @@ pub fn constrain_pattern(
|
|||
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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
compiler/exhaustive/Cargo.toml
Normal file
12
compiler/exhaustive/Cargo.toml
Normal 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 }
|
440
compiler/exhaustive/src/lib.rs
Normal file
440
compiler/exhaustive/src/lib.rs
Normal 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
|
||||
}
|
|
@ -6,7 +6,7 @@ The user needs to analyse the Wasm module's memory to decode the result.
|
|||
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
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_target::TargetInfo;
|
||||
|
||||
|
@ -42,6 +42,18 @@ pub fn insert_wrapper_for_layout<'a>(
|
|||
main_fn_index: u32,
|
||||
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 {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
|
||||
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)) => {
|
||||
f64::insert_wrapper(arena, module, wrapper_name, main_fn_index);
|
||||
}
|
||||
_ => {
|
||||
// The result is not a Wasm primitive, it's an array of bytes in stack memory.
|
||||
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::Builtin(Builtin::Bool) => {
|
||||
bool::insert_wrapper(arena, module, wrapper_name, main_fn_index);
|
||||
}
|
||||
Layout::Union(UnionLayout::NonRecursive(_)) => stack_data_structure(),
|
||||
Layout::Union(_) => {
|
||||
i32::insert_wrapper(arena, module, wrapper_name, main_fn_index);
|
||||
}
|
||||
_ => stack_data_structure(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -502,7 +502,7 @@ enum Msg<'a> {
|
|||
},
|
||||
FinishedAllTypeChecking {
|
||||
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_values: Vec<Symbol>,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
|
@ -2131,7 +2131,7 @@ fn finish(
|
|||
solved: Solved<Subs>,
|
||||
exposed_values: Vec<Symbol>,
|
||||
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>,
|
||||
documentation: MutMap<ModuleId, ModuleDocumentation>,
|
||||
) -> LoadedModule {
|
||||
|
@ -3102,20 +3102,21 @@ fn run_solve<'a>(
|
|||
}
|
||||
|
||||
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();
|
||||
exposed_vars_by_symbol.retain(|k, _| exposed_symbols.contains(k));
|
||||
let exposed_vars_by_symbol: Vec<_> = solved_env
|
||||
.vars_by_symbol()
|
||||
.filter(|(k, _)| exposed_symbols.contains(k))
|
||||
.collect();
|
||||
|
||||
let solved_types =
|
||||
roc_solve::module::make_solved_types(&solved_env, &solved_subs, &exposed_vars_by_symbol);
|
||||
let solved_types = roc_solve::module::make_solved_types(&solved_subs, &exposed_vars_by_symbol);
|
||||
|
||||
let solved_module = SolvedModule {
|
||||
exposed_vars_by_symbol,
|
||||
exposed_symbols: exposed_symbols.into_iter().collect::<Vec<_>>(),
|
||||
solved_types,
|
||||
problems,
|
||||
aliases: solved_env.aliases,
|
||||
aliases,
|
||||
};
|
||||
|
||||
// Record the final timings
|
||||
|
|
|
@ -755,7 +755,7 @@ mod test_load {
|
|||
err,
|
||||
indoc!(
|
||||
r#"
|
||||
── OPAQUE DECLARED OUTSIDE SCOPE ───────────────────────────────────────────────
|
||||
── OPAQUE TYPE DECLARED OUTSIDE SCOPE ──────────────────────────────────────────
|
||||
|
||||
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!
|
||||
|
||||
── OPAQUE DECLARED OUTSIDE SCOPE ───────────────────────────────────────────────
|
||||
── OPAQUE TYPE DECLARED OUTSIDE SCOPE ──────────────────────────────────────────
|
||||
|
||||
The unwrapped opaque type Age referenced here:
|
||||
|
||||
|
|
|
@ -40,6 +40,8 @@ pub struct Uppercase(IdentStr);
|
|||
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||
pub struct ForeignSymbol(IdentStr);
|
||||
|
||||
pub type TagIdIntType = u16;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum TagName {
|
||||
/// Global tags have no module, but tend to be short strings (since they're
|
||||
|
|
|
@ -580,6 +580,7 @@ impl IdentIds {
|
|||
}
|
||||
|
||||
// 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(
|
||||
&mut self,
|
||||
old_ident_name: &str,
|
||||
|
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_exhaustive = { path = "../exhaustive" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_types = { path = "../types" }
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::exhaustive::{Ctor, RenderAs, TagId, Union};
|
||||
use crate::ir::{
|
||||
BranchInfo, DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern, Procs, Stmt,
|
||||
};
|
||||
use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout};
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_exhaustive::{Ctor, RenderAs, TagId, Union};
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -83,7 +83,7 @@ enum Test<'a> {
|
|||
IsCtor {
|
||||
tag_id: TagIdIntType,
|
||||
tag_name: TagName,
|
||||
union: crate::exhaustive::Union,
|
||||
union: roc_exhaustive::Union,
|
||||
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
|
||||
},
|
||||
IsInt(i128, IntWidth),
|
||||
|
@ -565,6 +565,25 @@ fn test_at_path<'a>(
|
|||
union: union.clone(),
|
||||
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),
|
||||
EnumLiteral { tag_id, union, .. } => IsByte {
|
||||
tag_id: *tag_id as _,
|
||||
|
@ -692,6 +711,33 @@ fn to_relevant_branch_help<'a>(
|
|||
_ => 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 {
|
||||
tag_name,
|
||||
arguments,
|
||||
|
@ -954,6 +1000,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
|
|||
RecordDestructure(_, _)
|
||||
| NewtypeDestructure { .. }
|
||||
| AppliedTag { .. }
|
||||
| OpaqueUnwrap { .. }
|
||||
| BitLiteral { .. }
|
||||
| EnumLiteral { .. }
|
||||
| IntLiteral(_, _)
|
||||
|
@ -1319,6 +1366,7 @@ fn test_to_equality<'a>(
|
|||
_ => unreachable!("{:?}", (cond_layout, union)),
|
||||
}
|
||||
}
|
||||
|
||||
Test::IsInt(test_int, precision) => {
|
||||
// TODO don't downcast i128 here
|
||||
debug_assert!(test_int <= i64::MAX as i128);
|
||||
|
|
|
@ -1,66 +1,12 @@
|
|||
use crate::{ir::DestructType, layout::TagIdIntType};
|
||||
use roc_collections::all::{Index, MutMap};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use crate::ir::DestructType;
|
||||
use roc_collections::all::Index;
|
||||
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_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,
|
||||
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>),
|
||||
}
|
||||
use Pattern::*;
|
||||
|
||||
fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
|
||||
use crate::ir::Pattern::*;
|
||||
|
@ -132,36 +78,26 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
|
|||
arguments.iter().map(|v| simplify(&v.0)).collect();
|
||||
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(
|
||||
region: Region,
|
||||
patterns: &[(Loc<crate::ir::Pattern>, Guard)],
|
||||
|
@ -186,128 +122,13 @@ pub fn check_patterns<'a>(
|
|||
match to_nonredundant_rows(region, patterns) {
|
||||
Err(err) => errors.push(err),
|
||||
Ok(matrix) => {
|
||||
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));
|
||||
if let Err(err) = roc_exhaustive::check(region, context, matrix) {
|
||||
*errors = err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
/// INVARIANT: Produces a list of rows where (forall row. length row == 1)
|
||||
|
@ -375,226 +196,3 @@ fn to_nonredundant_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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#![allow(clippy::manual_map)]
|
||||
|
||||
use crate::exhaustive::{Ctor, Guard, RenderAs, TagId};
|
||||
use crate::layout::{
|
||||
Builtin, ClosureRepresentation, LambdaSet, Layout, LayoutCache, LayoutProblem,
|
||||
RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant,
|
||||
|
@ -10,7 +9,7 @@ use bumpalo::Bump;
|
|||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_can::expr::{ClosureData, IntValue};
|
||||
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::low_level::LowLevel;
|
||||
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!(Expr, 56);
|
||||
roc_error_macros::assert_sizeof_wasm!(Stmt, 96);
|
||||
roc_error_macros::assert_sizeof_wasm!(ProcLayout, 24);
|
||||
roc_error_macros::assert_sizeof_wasm!(Stmt, 120);
|
||||
roc_error_macros::assert_sizeof_wasm!(ProcLayout, 32);
|
||||
roc_error_macros::assert_sizeof_wasm!(Call, 40);
|
||||
roc_error_macros::assert_sizeof_wasm!(CallType, 32);
|
||||
|
||||
|
@ -96,7 +95,7 @@ pub enum OptLevel {
|
|||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum MonoProblem {
|
||||
PatternProblem(crate::exhaustive::Error),
|
||||
PatternProblem(roc_exhaustive::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -1896,7 +1895,7 @@ fn patterns_to_when<'a>(
|
|||
// see https://github.com/rtfeldman/roc/issues/786
|
||||
// this must be fixed when moving exhaustiveness checking to the new canonical AST
|
||||
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) {
|
||||
Ok((pat, _assignments)) => {
|
||||
// Don't apply any assignments (e.g. to initialize optional variables) yet.
|
||||
|
@ -1922,7 +1921,7 @@ fn patterns_to_when<'a>(
|
|||
pattern.region,
|
||||
&[(
|
||||
Loc::at(pattern.region, mono_pattern),
|
||||
crate::exhaustive::Guard::NoGuard,
|
||||
roc_exhaustive::Guard::NoGuard,
|
||||
)],
|
||||
context,
|
||||
) {
|
||||
|
@ -2021,7 +2020,7 @@ fn pattern_to_when<'a>(
|
|||
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
|
||||
}
|
||||
|
||||
AppliedTag { .. } | RecordDestructure { .. } => {
|
||||
AppliedTag { .. } | RecordDestructure { .. } | UnwrappedOpaque { .. } => {
|
||||
let symbol = env.unique_symbol();
|
||||
|
||||
let wrapped_body = When {
|
||||
|
@ -2039,7 +2038,6 @@ fn pattern_to_when<'a>(
|
|||
(symbol, Loc::at_zero(wrapped_body))
|
||||
}
|
||||
|
||||
UnwrappedOpaque { .. } => todo_opaques!(),
|
||||
IntLiteral(..)
|
||||
| NumLiteral(..)
|
||||
| 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(
|
||||
def.loc_pattern.region,
|
||||
&[(
|
||||
Loc::at(def.loc_pattern.region, mono_pattern.clone()),
|
||||
crate::exhaustive::Guard::NoGuard,
|
||||
roc_exhaustive::Guard::NoGuard,
|
||||
)],
|
||||
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_var,
|
||||
|
@ -5610,12 +5619,12 @@ pub fn from_can<'a>(
|
|||
hole,
|
||||
)
|
||||
} else {
|
||||
let context = crate::exhaustive::Context::BadDestruct;
|
||||
let context = roc_exhaustive::Context::BadDestruct;
|
||||
match crate::exhaustive::check(
|
||||
def.loc_pattern.region,
|
||||
&[(
|
||||
Loc::at(def.loc_pattern.region, mono_pattern.clone()),
|
||||
crate::exhaustive::Guard::NoGuard,
|
||||
roc_exhaustive::Guard::NoGuard,
|
||||
)],
|
||||
context,
|
||||
) {
|
||||
|
@ -5741,11 +5750,11 @@ fn to_opt_branches<'a>(
|
|||
// 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.
|
||||
// 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) {
|
||||
Ok(_) => {}
|
||||
Err(errors) => {
|
||||
use crate::exhaustive::Error::*;
|
||||
use roc_exhaustive::Error::*;
|
||||
let mut is_not_exhaustive = false;
|
||||
let mut overlapping_branches = std::vec::Vec::new();
|
||||
|
||||
|
@ -6309,6 +6318,10 @@ fn store_pattern_help<'a>(
|
|||
stmt,
|
||||
);
|
||||
}
|
||||
OpaqueUnwrap { argument, .. } => {
|
||||
return store_pattern_help(env, procs, layout_cache, &argument.0, outer_symbol, stmt);
|
||||
}
|
||||
|
||||
RecordDestructure(destructs, [_single_field]) => {
|
||||
for destruct in destructs {
|
||||
match &destruct.typ {
|
||||
|
@ -7644,12 +7657,12 @@ pub enum Pattern<'a> {
|
|||
BitLiteral {
|
||||
value: bool,
|
||||
tag_name: TagName,
|
||||
union: crate::exhaustive::Union,
|
||||
union: roc_exhaustive::Union,
|
||||
},
|
||||
EnumLiteral {
|
||||
tag_id: u8,
|
||||
tag_name: TagName,
|
||||
union: crate::exhaustive::Union,
|
||||
union: roc_exhaustive::Union,
|
||||
},
|
||||
StrLiteral(Box<str>),
|
||||
|
||||
|
@ -7663,7 +7676,11 @@ pub enum Pattern<'a> {
|
|||
tag_id: TagIdIntType,
|
||||
arguments: Vec<'a, (Pattern<'a>, Layout<'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,
|
||||
..
|
||||
} => {
|
||||
use crate::exhaustive::Union;
|
||||
use crate::layout::UnionVariant::*;
|
||||
use roc_exhaustive::Union;
|
||||
|
||||
let res_variant =
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
alternatives: ctors,
|
||||
};
|
||||
|
@ -8052,7 +8069,7 @@ fn from_can_pattern_help<'a>(
|
|||
arity: fields.len(),
|
||||
});
|
||||
|
||||
let union = crate::exhaustive::Union {
|
||||
let union = roc_exhaustive::Union {
|
||||
render_as: RenderAs::Tag,
|
||||
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,
|
||||
alternatives: ctors,
|
||||
};
|
||||
|
@ -8177,7 +8194,7 @@ fn from_can_pattern_help<'a>(
|
|||
arity: other_fields.len() - 1,
|
||||
});
|
||||
|
||||
let union = crate::exhaustive::Union {
|
||||
let union = roc_exhaustive::Union {
|
||||
render_as: RenderAs::Tag,
|
||||
alternatives: ctors,
|
||||
};
|
||||
|
@ -8218,7 +8235,20 @@ fn from_can_pattern_help<'a>(
|
|||
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 {
|
||||
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::Alias(Symbol::NUM_INTEGER, args, _) => {
|
||||
Content::Alias(Symbol::NUM_INTEGER, args, _, _) => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
other @ Content::Alias(symbol, args, _) => {
|
||||
other @ Content::Alias(symbol, args, _, _) => {
|
||||
if let Some(int_width) = IntWidth::try_from_symbol(*symbol) {
|
||||
return IntOrFloat::Int(int_width);
|
||||
}
|
||||
|
|
|
@ -19,10 +19,20 @@ use ven_pretty::{DocAllocator, DocBuilder};
|
|||
// if your changes cause this number to go down, great!
|
||||
// please change it to the lower number.
|
||||
// if it went up, maybe check that the change is really required
|
||||
static_assertions::assert_eq_size!([usize; 3], Builtin);
|
||||
static_assertions::assert_eq_size!([usize; 4], Layout);
|
||||
static_assertions::assert_eq_size!([usize; 3], UnionLayout);
|
||||
static_assertions::assert_eq_size!([usize; 3], LambdaSet);
|
||||
roc_error_macros::assert_sizeof_aarch64!(Builtin, 3 * 8);
|
||||
roc_error_macros::assert_sizeof_aarch64!(Layout, 4 * 8);
|
||||
roc_error_macros::assert_sizeof_aarch64!(UnionLayout, 3 * 8);
|
||||
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 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),
|
||||
|
||||
// Ints
|
||||
Alias(Symbol::NUM_I128, args, _) => {
|
||||
Alias(Symbol::NUM_I128, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::i128()))
|
||||
}
|
||||
Alias(Symbol::NUM_I64, args, _) => {
|
||||
Alias(Symbol::NUM_I64, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::i64()))
|
||||
}
|
||||
Alias(Symbol::NUM_I32, args, _) => {
|
||||
Alias(Symbol::NUM_I32, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::i32()))
|
||||
}
|
||||
Alias(Symbol::NUM_I16, args, _) => {
|
||||
Alias(Symbol::NUM_I16, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::i16()))
|
||||
}
|
||||
Alias(Symbol::NUM_I8, args, _) => {
|
||||
Alias(Symbol::NUM_I8, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::i8()))
|
||||
}
|
||||
|
||||
// I think unsigned and signed use the same layout
|
||||
Alias(Symbol::NUM_U128, args, _) => {
|
||||
Alias(Symbol::NUM_U128, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::u128()))
|
||||
}
|
||||
Alias(Symbol::NUM_U64, args, _) => {
|
||||
Alias(Symbol::NUM_U64, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::u64()))
|
||||
}
|
||||
Alias(Symbol::NUM_U32, args, _) => {
|
||||
Alias(Symbol::NUM_U32, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::u32()))
|
||||
}
|
||||
Alias(Symbol::NUM_U16, args, _) => {
|
||||
Alias(Symbol::NUM_U16, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::u16()))
|
||||
}
|
||||
Alias(Symbol::NUM_U8, args, _) => {
|
||||
Alias(Symbol::NUM_U8, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::u8()))
|
||||
}
|
||||
|
||||
// Floats
|
||||
Alias(Symbol::NUM_F64, args, _) => {
|
||||
Alias(Symbol::NUM_F64, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::f64()))
|
||||
}
|
||||
Alias(Symbol::NUM_F32, args, _) => {
|
||||
Alias(Symbol::NUM_F32, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(Self::ZeroArgumentThunk(Layout::f32()))
|
||||
}
|
||||
|
||||
// Nat
|
||||
Alias(Symbol::NUM_NAT, args, _) => {
|
||||
Alias(Symbol::NUM_NAT, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
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)?,
|
||||
)),
|
||||
|
||||
Alias(_, _, var) => Self::from_var(env, var),
|
||||
Alias(_, _, var, _) => Self::from_var(env, var),
|
||||
Error => Err(LayoutProblem::Erroneous),
|
||||
}
|
||||
}
|
||||
|
@ -922,7 +932,7 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
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) {
|
||||
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> {
|
||||
match subs.get_content_without_compacting(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,
|
||||
}
|
||||
}
|
||||
|
@ -2623,7 +2633,7 @@ fn layout_from_num_content<'a>(
|
|||
);
|
||||
}
|
||||
},
|
||||
Alias(_, _, _) => {
|
||||
Alias(_, _, _, _) => {
|
||||
todo!("TODO recursively resolve type aliases in num_from_content");
|
||||
}
|
||||
Structure(_) | RangedNumber(..) => {
|
||||
|
@ -2639,7 +2649,7 @@ fn unwrap_num_tag<'a>(
|
|||
target_info: TargetInfo,
|
||||
) -> Result<Layout<'a>, LayoutProblem> {
|
||||
match subs.get_content_without_compacting(var) {
|
||||
Content::Alias(Symbol::NUM_INTEGER, args, _) => {
|
||||
Content::Alias(Symbol::NUM_INTEGER, args, _, _) => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
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);
|
||||
|
||||
match precision {
|
||||
Content::Alias(symbol, args, _) => {
|
||||
Content::Alias(symbol, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
|
||||
let layout = match *symbol {
|
||||
|
@ -2675,7 +2685,7 @@ fn unwrap_num_tag<'a>(
|
|||
_ => unreachable!("not a valid int variant: {:?}", precision),
|
||||
}
|
||||
}
|
||||
Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _) => {
|
||||
Content::Alias(Symbol::NUM_FLOATINGPOINT, args, _, _) => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
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);
|
||||
|
||||
match precision {
|
||||
Content::Alias(Symbol::NUM_BINARY32, args, _) => {
|
||||
Content::Alias(Symbol::NUM_BINARY32, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
|
||||
Ok(Layout::f32())
|
||||
}
|
||||
Content::Alias(Symbol::NUM_BINARY64, args, _) => {
|
||||
Content::Alias(Symbol::NUM_BINARY64, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
|
||||
Ok(Layout::f64())
|
||||
}
|
||||
Content::Alias(Symbol::NUM_DECIMAL, args, _) => {
|
||||
Content::Alias(Symbol::NUM_DECIMAL, args, _, _) => {
|
||||
debug_assert!(args.is_empty());
|
||||
|
||||
Ok(Layout::Builtin(Builtin::Decimal))
|
||||
|
|
|
@ -141,7 +141,7 @@ impl FunctionLayout {
|
|||
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
|
||||
Content::RecursionVar { .. } => Err(TypeError(())),
|
||||
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::Error => Err(TypeError(())),
|
||||
}
|
||||
|
@ -249,7 +249,7 @@ impl LambdaSet {
|
|||
unreachable!("lambda sets cannot currently be recursive")
|
||||
}
|
||||
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::Error => Err(TypeError(())),
|
||||
}
|
||||
|
@ -660,7 +660,7 @@ impl Layout {
|
|||
}
|
||||
}
|
||||
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
|
||||
Content::Alias(symbol, _, actual) => {
|
||||
Content::Alias(symbol, _, actual, _) => {
|
||||
let symbol = *symbol;
|
||||
|
||||
if let Some(int_width) = IntWidth::try_from_symbol(symbol) {
|
||||
|
|
|
@ -711,9 +711,8 @@ where
|
|||
let cur_indent = INDENT.with(|i| *i.borrow());
|
||||
|
||||
println!(
|
||||
"@{:>5}:{:<5}: {}{:<50}",
|
||||
state.line,
|
||||
state.column,
|
||||
"{:>5?}: {}{:<50}",
|
||||
state.pos(),
|
||||
&indent_text[..cur_indent * 2],
|
||||
self.message
|
||||
);
|
||||
|
@ -728,9 +727,8 @@ where
|
|||
};
|
||||
|
||||
println!(
|
||||
"@{:>5}:{:<5}: {}{:<50} {:<15} {:?}",
|
||||
state.line,
|
||||
state.column,
|
||||
"{:<5?}: {}{:<50} {:<15} {:?}",
|
||||
state.pos(),
|
||||
&indent_text[..cur_indent * 2],
|
||||
self.message,
|
||||
format!("{:?}", progress),
|
||||
|
@ -1217,7 +1215,11 @@ macro_rules! collection_trailing_sep_e {
|
|||
$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)?;
|
||||
|
||||
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_rules! word1_check_indent {
|
||||
($word:expr, $word_problem:expr, $min_indent:expr, $indent_problem:expr) => {
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
my_list = [
|
||||
0,
|
||||
[
|
||||
a,
|
||||
b,
|
||||
],
|
||||
1,
|
||||
]
|
||||
42
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
my_list = [
|
||||
0,
|
||||
1
|
||||
]
|
||||
42
|
|
@ -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,
|
||||
],
|
||||
),
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
my_list = [
|
||||
0,
|
||||
1,
|
||||
]
|
||||
42
|
|
@ -75,19 +75,32 @@ mod test_parse {
|
|||
let mut base = std::path::PathBuf::from("tests");
|
||||
base.push("snapshots");
|
||||
let pass_or_fail_names = list(&base);
|
||||
let mut extra_test_files = std::collections::HashSet::new();
|
||||
for res in pass_or_fail_names {
|
||||
assert!(res == "pass" || res == "fail");
|
||||
let res_dir = base.join(&res);
|
||||
for file in list(&res_dir) {
|
||||
if let Some(file) = file.strip_suffix(".roc") {
|
||||
assert!(tests.contains(format!("{}/{}", &res, file).as_str()), "{}", file);
|
||||
} else if let Some(file) = file.strip_suffix(".result-ast") {
|
||||
assert!(tests.contains(format!("{}/{}", &res, file).as_str()), "{}", file);
|
||||
let test = if let Some(test) = file.strip_suffix(".roc") {
|
||||
test
|
||||
} else if let Some(test) = file.strip_suffix(".result-ast") {
|
||||
test
|
||||
} 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! {
|
||||
fail/type_argument_no_arrow.expr,
|
||||
fail/type_double_comma.expr,
|
||||
pass/list_closing_indent_not_enough.expr,
|
||||
pass/add_var_with_spaces.expr,
|
||||
pass/add_with_spaces.expr,
|
||||
pass/annotated_record_destructure.expr,
|
||||
|
@ -154,6 +168,8 @@ mod test_parse {
|
|||
pass/int_with_underscore.expr,
|
||||
pass/interface_with_newline.header,
|
||||
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/malformed_ident_due_to_underscore.expr,
|
||||
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 actual_result = if should_pass {
|
||||
eprintln!("The source code for this test did not successfully parse!\n");
|
||||
|
||||
result.unwrap()
|
||||
result.expect("The source code for this test did not successfully parse!")
|
||||
} else {
|
||||
eprintln!(
|
||||
"The source code for this test successfully parsed, but it was not expected to!\n"
|
||||
);
|
||||
|
||||
result.unwrap_err()
|
||||
result.expect_err(
|
||||
"The source code for this test successfully parsed, but it was not expected to!",
|
||||
)
|
||||
};
|
||||
|
||||
if std::env::var("ROC_PARSER_SNAPSHOT_TEST_OVERWRITE").is_ok() {
|
||||
|
|
|
@ -165,6 +165,8 @@ pub enum RuntimeError {
|
|||
referenced_region: Region,
|
||||
imported_region: Region,
|
||||
},
|
||||
OpaqueNotApplied(Loc<Ident>),
|
||||
OpaqueAppliedToMultipleArgs(Region),
|
||||
ValueNotExposed {
|
||||
module_name: ModuleName,
|
||||
ident: Ident,
|
||||
|
|
|
@ -12,20 +12,16 @@ pub struct SolvedModule {
|
|||
pub solved_types: MutMap<Symbol, SolvedType>,
|
||||
pub aliases: MutMap<Symbol, Alias>,
|
||||
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 fn run_solve(
|
||||
aliases: MutMap<Symbol, Alias>,
|
||||
rigid_variables: MutMap<Variable, Lowercase>,
|
||||
constraint: Constraint,
|
||||
var_store: VarStore,
|
||||
) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) {
|
||||
let env = solve::Env {
|
||||
vars_by_symbol: MutMap::default(),
|
||||
aliases,
|
||||
};
|
||||
let env = solve::Env::default();
|
||||
|
||||
let mut subs = Subs::new_from_varstore(var_store);
|
||||
|
||||
|
@ -44,35 +40,11 @@ pub fn run_solve(
|
|||
}
|
||||
|
||||
pub fn make_solved_types(
|
||||
solved_env: &solve::Env,
|
||||
solved_subs: &Solved<Subs>,
|
||||
exposed_vars_by_symbol: &MutMap<Symbol, Variable>,
|
||||
exposed_vars_by_symbol: &[(Symbol, Variable)],
|
||||
) -> MutMap<Symbol, SolvedType> {
|
||||
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
|
||||
// this module exposes. We want to convert those into flat SolvedType
|
||||
// annotations which are decoupled from our Subs, because that's how
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use bumpalo::Bump;
|
||||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::PresenceConstraint;
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
|
@ -11,7 +12,9 @@ use roc_types::subs::{
|
|||
SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
|
||||
};
|
||||
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 std::collections::hash_map::Entry;
|
||||
|
||||
|
@ -76,8 +79,37 @@ pub enum TypeError {
|
|||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Env {
|
||||
pub vars_by_symbol: MutMap<Symbol, Variable>,
|
||||
pub aliases: MutMap<Symbol, Alias>,
|
||||
symbols: Vec<Symbol>,
|
||||
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;
|
||||
|
@ -161,7 +193,11 @@ pub fn run_in_place(
|
|||
mark: Mark::NONE.next(),
|
||||
};
|
||||
let rank = Rank::toplevel();
|
||||
|
||||
let arena = Bump::new();
|
||||
|
||||
let state = solve(
|
||||
&arena,
|
||||
env,
|
||||
state,
|
||||
rank,
|
||||
|
@ -175,10 +211,26 @@ pub fn run_in_place(
|
|||
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)]
|
||||
fn solve(
|
||||
arena: &Bump,
|
||||
env: &Env,
|
||||
state: State,
|
||||
mut state: State,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Vec<TypeError>,
|
||||
|
@ -186,7 +238,38 @@ fn solve(
|
|||
subs: &mut Subs,
|
||||
constraint: &Constraint,
|
||||
) -> 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,
|
||||
SaveTheEnvironment => {
|
||||
// NOTE deviation: elm only copies the env into the state on SaveTheEnvironment
|
||||
|
@ -264,7 +347,7 @@ fn solve(
|
|||
}
|
||||
}
|
||||
Lookup(symbol, expectation, region) => {
|
||||
match env.vars_by_symbol.get(symbol) {
|
||||
match env.get_var_by_symbol(symbol) {
|
||||
Some(var) => {
|
||||
// Deep copy the vars associated with this symbol before unifying them.
|
||||
// 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
|
||||
// is being looked up in this module, then we use our Subs as both
|
||||
// the source and destination.
|
||||
let actual = deep_copy_var(subs, rank, pools, *var);
|
||||
let actual = deep_copy_var(subs, rank, pools, var);
|
||||
let expected = type_to_var(
|
||||
subs,
|
||||
rank,
|
||||
|
@ -333,19 +416,13 @@ fn solve(
|
|||
}
|
||||
}
|
||||
And(sub_constraints) => {
|
||||
let mut state = state;
|
||||
|
||||
for sub_constraint in sub_constraints.iter() {
|
||||
state = solve(
|
||||
for sub_constraint in sub_constraints.iter().rev() {
|
||||
stack.push(Work::Constraint {
|
||||
env,
|
||||
state,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
sub_constraint,
|
||||
);
|
||||
constraint: sub_constraint,
|
||||
after: None,
|
||||
})
|
||||
}
|
||||
|
||||
state
|
||||
|
@ -402,19 +479,18 @@ fn solve(
|
|||
|
||||
// If the return expression is guaranteed to solve,
|
||||
// solve the assignments themselves and move on.
|
||||
solve(
|
||||
stack.push(Work::Constraint {
|
||||
env,
|
||||
state,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
&let_con.defs_constraint,
|
||||
)
|
||||
constraint: &let_con.defs_constraint,
|
||||
after: None,
|
||||
});
|
||||
state
|
||||
}
|
||||
ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => {
|
||||
// TODO: make into `WorkItem` with `After`
|
||||
let state = solve(
|
||||
arena,
|
||||
env,
|
||||
state,
|
||||
rank,
|
||||
|
@ -426,10 +502,12 @@ fn solve(
|
|||
);
|
||||
|
||||
// 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() {
|
||||
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((
|
||||
*symbol,
|
||||
|
@ -442,32 +520,17 @@ fn solve(
|
|||
|
||||
let mut new_env = env.clone();
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
match new_env.vars_by_symbol.entry(*symbol) {
|
||||
Entry::Occupied(_) => {
|
||||
// keep the existing value
|
||||
}
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(loc_var.value);
|
||||
}
|
||||
}
|
||||
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
|
||||
}
|
||||
|
||||
let new_state = solve(
|
||||
&new_env,
|
||||
state,
|
||||
stack.push(Work::Constraint {
|
||||
env: arena.alloc(new_env),
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
ret_con,
|
||||
);
|
||||
constraint: ret_con,
|
||||
after: Some(After::CheckForInfiniteTypes(local_def_vars)),
|
||||
});
|
||||
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
check_for_infinite_type(subs, problems, *symbol, *loc_var);
|
||||
}
|
||||
|
||||
new_state
|
||||
state
|
||||
}
|
||||
ret_con => {
|
||||
let rigid_vars = &let_con.rigid_vars;
|
||||
|
@ -482,17 +545,16 @@ fn solve(
|
|||
}
|
||||
|
||||
// determine the next pool
|
||||
let next_pools;
|
||||
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 {
|
||||
// we should be off by one at this point
|
||||
debug_assert_eq!(next_rank.into_usize(), 1 + pools.len());
|
||||
pools.extend_to(next_rank.into_usize());
|
||||
next_pools = pools;
|
||||
}
|
||||
|
||||
let pool: &mut Vec<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
|
||||
pool.clear();
|
||||
|
@ -503,13 +565,13 @@ fn solve(
|
|||
// run solver in next pool
|
||||
|
||||
// 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() {
|
||||
let def_type = &loc_type.value;
|
||||
|
||||
let var =
|
||||
type_to_var(subs, next_rank, next_pools, cached_aliases, def_type);
|
||||
let var = type_to_var(subs, next_rank, pools, cached_aliases, def_type);
|
||||
|
||||
local_def_vars.push((
|
||||
*symbol,
|
||||
|
@ -521,14 +583,16 @@ fn solve(
|
|||
}
|
||||
|
||||
// Solve the assignments' constraints first.
|
||||
// TODO: make into `WorkItem` with `After`
|
||||
let State {
|
||||
env: saved_env,
|
||||
mark,
|
||||
} = solve(
|
||||
arena,
|
||||
env,
|
||||
state,
|
||||
next_rank,
|
||||
next_pools,
|
||||
pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
|
@ -541,7 +605,7 @@ fn solve(
|
|||
|
||||
debug_assert_eq!(
|
||||
{
|
||||
let offenders = next_pools
|
||||
let offenders = pools
|
||||
.get(next_rank)
|
||||
.iter()
|
||||
.filter(|var| {
|
||||
|
@ -564,9 +628,9 @@ fn solve(
|
|||
);
|
||||
|
||||
// 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
|
||||
debug_assert!({
|
||||
|
@ -591,41 +655,26 @@ fn solve(
|
|||
|
||||
let mut new_env = env.clone();
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
match new_env.vars_by_symbol.entry(*symbol) {
|
||||
Entry::Occupied(_) => {
|
||||
// keep the existing value
|
||||
}
|
||||
Entry::Vacant(vacant) => {
|
||||
vacant.insert(loc_var.value);
|
||||
}
|
||||
}
|
||||
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
|
||||
}
|
||||
|
||||
// Note that this vars_by_symbol is the one returned by the
|
||||
// previous call to solve()
|
||||
let temp_state = State {
|
||||
let state_for_ret_con = State {
|
||||
env: saved_env,
|
||||
mark: final_mark,
|
||||
};
|
||||
|
||||
// Now solve the body, using the new vars_by_symbol which includes
|
||||
// the assignments' name-to-variable mappings.
|
||||
let new_state = solve(
|
||||
&new_env,
|
||||
temp_state,
|
||||
stack.push(Work::Constraint {
|
||||
env: arena.alloc(new_env),
|
||||
rank,
|
||||
next_pools,
|
||||
problems,
|
||||
cached_aliases,
|
||||
subs,
|
||||
ret_con,
|
||||
);
|
||||
constraint: ret_con,
|
||||
after: Some(After::CheckForInfiniteTypes(local_def_vars)),
|
||||
});
|
||||
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
check_for_infinite_type(subs, problems, *symbol, *loc_var);
|
||||
}
|
||||
|
||||
new_state
|
||||
state_for_ret_con
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 tag_ty = Type::TagUnion(
|
||||
vec![(tag_name.clone(), tys.clone())],
|
||||
|
@ -665,17 +717,15 @@ fn solve(
|
|||
|
||||
state
|
||||
}
|
||||
Failure(vars, actual_type, expected_type) => {
|
||||
Failure(vars, actual_type, expected_to_include_type) => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
// TODO: do we need a better error type here?
|
||||
let problem = TypeError::BadExpr(
|
||||
Region::zero(),
|
||||
Category::When,
|
||||
actual_type,
|
||||
Expected::NoExpectation(expected_type),
|
||||
let problem = TypeError::BadPattern(
|
||||
*region,
|
||||
pattern_category.clone(),
|
||||
expected_to_include_type,
|
||||
PExpected::NoExpectation(actual_type),
|
||||
);
|
||||
|
||||
problems.push(problem);
|
||||
|
||||
state
|
||||
|
@ -689,9 +739,13 @@ fn solve(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LocalDefVarsVec<T> {
|
||||
Stack(arrayvec::ArrayVec<T, 32>),
|
||||
Heap(Vec<T>),
|
||||
|
@ -914,8 +968,7 @@ fn type_to_variable<'a>(
|
|||
type_arguments,
|
||||
actual,
|
||||
lambda_set_variables,
|
||||
// TODO(opaques): revisit kind
|
||||
kind: _,
|
||||
kind,
|
||||
} => {
|
||||
if let Some(reserved) = Variable::get_reserved(*symbol) {
|
||||
if rank.is_none() {
|
||||
|
@ -941,7 +994,7 @@ fn type_to_variable<'a>(
|
|||
} else {
|
||||
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)
|
||||
}
|
||||
|
@ -963,7 +1016,14 @@ fn type_to_variable<'a>(
|
|||
);
|
||||
|
||||
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);
|
||||
|
||||
// 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();
|
||||
|
||||
for var_index in args.variables() {
|
||||
|
@ -1732,7 +1792,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
|
|||
|
||||
Erroneous(_) => (),
|
||||
},
|
||||
Alias(_, args, var) => {
|
||||
Alias(_, args, var, _) => {
|
||||
let var = *var;
|
||||
let args = *args;
|
||||
|
||||
|
@ -1979,7 +2039,7 @@ fn deep_copy_var_help(
|
|||
copy
|
||||
}
|
||||
|
||||
Alias(symbol, arguments, real_type_var) => {
|
||||
Alias(symbol, arguments, real_type_var, kind) => {
|
||||
let new_variables =
|
||||
SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _);
|
||||
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 =
|
||||
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));
|
||||
|
||||
|
|
|
@ -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 }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[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"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::solved_types::{BuiltinAlias, SolvedType};
|
||||
use crate::subs::VarId;
|
||||
use crate::types::RecordField;
|
||||
use crate::types::{AliasKind, RecordField};
|
||||
use roc_collections::all::{default_hasher, MutMap};
|
||||
use roc_module::ident::TagName;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -367,6 +367,7 @@ pub fn num_type(range: SolvedType) -> SolvedType {
|
|||
vec![("range".into(), range.clone())],
|
||||
vec![],
|
||||
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![],
|
||||
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![],
|
||||
Box::new(float_alias_content(range)),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -418,6 +421,7 @@ pub fn f64_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(f64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -435,6 +439,7 @@ pub fn f32_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(f32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -452,6 +457,7 @@ pub fn nat_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(nat_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -469,6 +475,7 @@ pub fn i128_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(i128_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -486,6 +493,7 @@ pub fn u128_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(u128_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -503,6 +511,7 @@ pub fn u64_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(u64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -520,6 +529,7 @@ pub fn i64_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(i64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -537,6 +547,7 @@ pub fn u32_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(u32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -554,6 +565,7 @@ pub fn i32_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(i32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -571,6 +583,7 @@ pub fn u16_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(u16_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -588,6 +601,7 @@ pub fn i16_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(i16_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -600,7 +614,13 @@ fn i16_alias_content() -> SolvedType {
|
|||
|
||||
#[inline(always)]
|
||||
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)]
|
||||
|
@ -612,7 +632,13 @@ fn u8_alias_content() -> SolvedType {
|
|||
|
||||
#[inline(always)]
|
||||
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)]
|
||||
|
@ -629,6 +655,7 @@ pub fn int_type(range: SolvedType) -> SolvedType {
|
|||
vec![("range".into(), range.clone())],
|
||||
vec![],
|
||||
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![],
|
||||
Box::new(integer_alias_content(range)),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -661,6 +689,7 @@ pub fn binary64_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(binary64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -676,6 +705,7 @@ pub fn binary32_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(binary32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -691,6 +721,7 @@ pub fn natural_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(natural_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -706,6 +737,7 @@ pub fn signed128_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(signed128_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -721,6 +753,7 @@ pub fn signed64_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(signed64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -736,6 +769,7 @@ pub fn signed32_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(signed32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -751,6 +785,7 @@ pub fn signed16_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(signed16_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -766,6 +801,7 @@ pub fn signed8_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(signed8_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -781,6 +817,7 @@ pub fn unsigned128_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned128_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -796,6 +833,7 @@ pub fn unsigned64_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned64_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -811,6 +849,7 @@ pub fn unsigned32_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned32_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -826,6 +865,7 @@ pub fn unsigned16_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned16_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -841,6 +881,7 @@ pub fn unsigned8_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(unsigned8_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -863,6 +904,7 @@ pub fn dec_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(dec_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -878,6 +920,7 @@ pub fn decimal_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(decimal_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -888,6 +931,7 @@ pub fn bool_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
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)]
|
||||
pub fn result_type(a: SolvedType, e: SolvedType) -> SolvedType {
|
||||
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![],
|
||||
Box::new(result_alias_content(a, e)),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -961,6 +997,7 @@ pub fn str_utf8_problem_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(str_utf8_problem_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -985,6 +1022,7 @@ pub fn str_utf8_byte_problem_type() -> SolvedType {
|
|||
vec![],
|
||||
vec![],
|
||||
Box::new(str_utf8_byte_problem_alias_content()),
|
||||
AliasKind::Structural,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -197,7 +197,7 @@ fn find_names_needed(
|
|||
find_names_needed(*ext_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!
|
||||
for var_index in args.into_iter().take(args.len()) {
|
||||
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),
|
||||
},
|
||||
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();
|
||||
|
||||
match *symbol {
|
||||
Symbol::NUM_NUM => {
|
||||
let content = get_single_arg(subs, args);
|
||||
match *content {
|
||||
Alias(nested, args, _actual) => match nested {
|
||||
Alias(nested, args, _actual, _kind) => match nested {
|
||||
Symbol::NUM_INTEGER => {
|
||||
write_integer(
|
||||
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);
|
||||
|
||||
match content {
|
||||
Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"),
|
||||
Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"),
|
||||
Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"),
|
||||
Alias(Symbol::NUM_BINARY32, _, _, _) => buf.push_str("F32"),
|
||||
Alias(Symbol::NUM_BINARY64, _, _, _) => buf.push_str("F64"),
|
||||
Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"),
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Float ");
|
||||
write_content(env, content, subs, buf, parens);
|
||||
|
@ -432,7 +432,7 @@ fn write_integer(
|
|||
buf,
|
||||
match content {
|
||||
$(
|
||||
&Alias($tag, _, _) => {
|
||||
&Alias($tag, _, _, _) => {
|
||||
buf.push_str($lit)
|
||||
},
|
||||
)*
|
||||
|
@ -755,7 +755,7 @@ pub fn chase_ext_tag_union<'a>(
|
|||
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)),
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ pub enum SolvedType {
|
|||
Vec<(Lowercase, SolvedType)>,
|
||||
Vec<SolvedLambdaSet>,
|
||||
Box<SolvedType>,
|
||||
AliasKind,
|
||||
),
|
||||
|
||||
HostExposedAlias {
|
||||
|
@ -181,7 +182,7 @@ impl SolvedType {
|
|||
type_arguments,
|
||||
lambda_set_variables,
|
||||
actual: box_type,
|
||||
..
|
||||
kind,
|
||||
} => {
|
||||
let solved_type = Self::from_type(solved_subs, box_type);
|
||||
let mut solved_args = Vec::with_capacity(type_arguments.len());
|
||||
|
@ -201,6 +202,7 @@ impl SolvedType {
|
|||
solved_args,
|
||||
solved_lambda_sets,
|
||||
Box::new(solved_type),
|
||||
*kind,
|
||||
)
|
||||
}
|
||||
HostExposedAlias {
|
||||
|
@ -257,7 +259,7 @@ impl SolvedType {
|
|||
}
|
||||
RigidVar(name) => SolvedType::Rigid(name.clone()),
|
||||
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());
|
||||
|
||||
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);
|
||||
|
||||
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),
|
||||
Error => SolvedType::Error,
|
||||
|
@ -536,7 +544,7 @@ pub fn to_type(
|
|||
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());
|
||||
|
||||
for (lowercase, solved_arg) in solved_type_variables {
|
||||
|
@ -559,8 +567,7 @@ pub fn to_type(
|
|||
type_arguments: type_variables,
|
||||
lambda_set_variables,
|
||||
actual: Box::new(actual),
|
||||
// TODO(opaques): revisit when opaques are in the solver
|
||||
kind: AliasKind::Structural,
|
||||
kind: *kind,
|
||||
}
|
||||
}
|
||||
HostExposedAlias {
|
||||
|
|
|
@ -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_module::ident::{Lowercase, TagName, Uppercase};
|
||||
use roc_module::symbol::Symbol;
|
||||
|
@ -375,10 +375,14 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
|
|||
opt_name,
|
||||
} => write!(f, "Recursion({:?}, {:?})", structure, opt_name),
|
||||
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 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) => {
|
||||
let slice = subs.get_subs_slice(*range);
|
||||
|
@ -870,7 +874,12 @@ fn integer_type(
|
|||
});
|
||||
|
||||
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], []);
|
||||
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], []);
|
||||
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, {
|
||||
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, {
|
||||
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], []);
|
||||
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], []);
|
||||
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, {
|
||||
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,
|
||||
AliasVariables::default(),
|
||||
Variable::BOOL_ENUM,
|
||||
AliasKind::Structural,
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -1650,7 +1695,7 @@ pub enum Content {
|
|||
opt_name: Option<Lowercase>,
|
||||
},
|
||||
Structure(FlatType),
|
||||
Alias(Symbol, AliasVariables, Variable),
|
||||
Alias(Symbol, AliasVariables, Variable, AliasKind),
|
||||
RangedNumber(Variable, VariableSubsSlice),
|
||||
Error,
|
||||
}
|
||||
|
@ -2107,7 +2152,7 @@ pub fn is_empty_tag_union(subs: &Subs, mut var: Variable) -> bool {
|
|||
var = *sub_ext;
|
||||
}
|
||||
|
||||
Alias(_, _, actual_var) => {
|
||||
Alias(_, _, actual_var, _) => {
|
||||
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||
var = *actual_var;
|
||||
}
|
||||
|
@ -2323,7 +2368,7 @@ fn is_empty_record(subs: &Subs, mut var: Variable) -> bool {
|
|||
var = *sub_ext;
|
||||
}
|
||||
|
||||
Alias(_, _, actual_var) => {
|
||||
Alias(_, _, actual_var, _) => {
|
||||
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||
var = *actual_var;
|
||||
}
|
||||
|
@ -2400,7 +2445,7 @@ fn occurs(
|
|||
EmptyRecord | EmptyTagUnion | Erroneous(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
Alias(_, args, _) => {
|
||||
Alias(_, args, _, _) => {
|
||||
let mut new_seen = seen.clone();
|
||||
new_seen.insert(root_var);
|
||||
|
||||
|
@ -2596,7 +2641,7 @@ fn explicit_substitute(
|
|||
|
||||
in_var
|
||||
}
|
||||
Alias(symbol, args, actual) => {
|
||||
Alias(symbol, args, actual, kind) => {
|
||||
for index in args.into_iter() {
|
||||
let var = subs[index];
|
||||
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);
|
||||
|
||||
subs.set_content(in_var, Alias(symbol, args, new_actual));
|
||||
subs.set_content(in_var, Alias(symbol, args, new_actual, kind));
|
||||
|
||||
in_var
|
||||
}
|
||||
|
@ -2667,7 +2712,7 @@ fn get_var_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)
|
||||
}),
|
||||
|
||||
|
@ -2874,7 +2919,7 @@ fn content_to_err_type(
|
|||
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 mut err_args = Vec::with_capacity(args.len());
|
||||
|
@ -2887,7 +2932,7 @@ fn content_to_err_type(
|
|||
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) => {
|
||||
|
@ -2968,7 +3013,7 @@ fn flat_type_to_err_type(
|
|||
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.union(err_fields), sub_ext)
|
||||
}
|
||||
|
@ -3002,7 +3047,7 @@ fn flat_type_to_err_type(
|
|||
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.union(err_tags), sub_ext)
|
||||
}
|
||||
|
@ -3030,7 +3075,7 @@ fn flat_type_to_err_type(
|
|||
|
||||
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.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));
|
||||
|
||||
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) => {
|
||||
debug_assert!(rec_var == rec_error_type);
|
||||
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(_) => (),
|
||||
},
|
||||
Alias(_, args, var) => {
|
||||
Alias(_, args, var, _) => {
|
||||
stack.extend(var_slice(args.variables()));
|
||||
|
||||
stack.push(*var);
|
||||
|
@ -3343,10 +3388,11 @@ impl StorageSubs {
|
|||
opt_name: opt_name.clone(),
|
||||
},
|
||||
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,
|
||||
Self::offset_alias_variables(offsets, *alias_variables),
|
||||
Self::offset_variable(offsets, *actual),
|
||||
*kind,
|
||||
),
|
||||
RangedNumber(typ, vars) => RangedNumber(
|
||||
Self::offset_variable(offsets, *typ),
|
||||
|
@ -3722,7 +3768,7 @@ fn deep_copy_var_to_help<'a>(
|
|||
copy
|
||||
}
|
||||
|
||||
Alias(symbol, arguments, real_type_var) => {
|
||||
Alias(symbol, arguments, real_type_var, kind) => {
|
||||
let new_variables =
|
||||
SubsSlice::reserve_into_subs(target, arguments.all_variables_len as _);
|
||||
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 =
|
||||
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));
|
||||
|
||||
|
@ -3830,7 +3876,7 @@ where
|
|||
}
|
||||
Erroneous(_) | EmptyRecord | EmptyTagUnion => {}
|
||||
},
|
||||
Alias(_, arguments, real_type_var) => {
|
||||
Alias(_, arguments, real_type_var, _) => {
|
||||
push_var_slice!(arguments.variables());
|
||||
stack.push(*real_type_var);
|
||||
}
|
||||
|
|
|
@ -924,6 +924,13 @@ impl Type {
|
|||
_ => 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>) {
|
||||
|
@ -1286,6 +1293,8 @@ pub enum Category {
|
|||
tag_name: TagName,
|
||||
args_count: usize,
|
||||
},
|
||||
OpaqueWrap(Symbol),
|
||||
OpaqueArg,
|
||||
Lambda,
|
||||
Uniqueness,
|
||||
ClosureSize,
|
||||
|
@ -1322,6 +1331,7 @@ pub enum PatternCategory {
|
|||
Set,
|
||||
Map,
|
||||
Ctor(TagName),
|
||||
Opaque(Symbol),
|
||||
Str,
|
||||
Num,
|
||||
Int,
|
||||
|
@ -1329,7 +1339,7 @@ pub enum PatternCategory {
|
|||
Character,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum AliasKind {
|
||||
/// A structural alias is something like
|
||||
/// List a : [ Nil, Cons a (List a) ]
|
||||
|
@ -1405,7 +1415,7 @@ pub enum ErrorType {
|
|||
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt),
|
||||
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
|
||||
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>),
|
||||
Error,
|
||||
}
|
||||
|
@ -1418,9 +1428,9 @@ impl std::fmt::Debug for ErrorType {
|
|||
}
|
||||
|
||||
impl ErrorType {
|
||||
pub fn unwrap_alias(self) -> ErrorType {
|
||||
pub fn unwrap_structural_alias(self) -> ErrorType {
|
||||
match self {
|
||||
ErrorType::Alias(_, _, real) => real.unwrap_alias(),
|
||||
ErrorType::Alias(_, _, real, AliasKind::Structural) => real.unwrap_structural_alias(),
|
||||
real => real,
|
||||
}
|
||||
}
|
||||
|
@ -1459,7 +1469,7 @@ impl ErrorType {
|
|||
capt.add_names(taken);
|
||||
ret.add_names(taken);
|
||||
}
|
||||
Alias(_, ts, t) => {
|
||||
Alias(_, ts, t, _) => {
|
||||
ts.iter().for_each(|t| {
|
||||
t.add_names(taken);
|
||||
});
|
||||
|
@ -1515,7 +1525,7 @@ fn write_error_type_help(
|
|||
buf.push(')');
|
||||
}
|
||||
}
|
||||
Alias(Symbol::NUM_NUM, mut arguments, _actual) => {
|
||||
Alias(Symbol::NUM_NUM, mut arguments, _actual, _) => {
|
||||
debug_assert!(arguments.len() == 1);
|
||||
|
||||
let argument = arguments.remove(0);
|
||||
|
@ -1633,7 +1643,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
|||
buf.push(')');
|
||||
}
|
||||
}
|
||||
Alias(Symbol::NUM_NUM, mut arguments, _actual) => {
|
||||
Alias(Symbol::NUM_NUM, mut arguments, _actual, _) => {
|
||||
debug_assert!(arguments.len() == 1);
|
||||
|
||||
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();
|
||||
|
||||
if write_parens {
|
||||
|
@ -1881,7 +1891,7 @@ pub fn gather_fields_unsorted_iter(
|
|||
var = *sub_ext;
|
||||
}
|
||||
|
||||
Alias(_, _, actual_var) => {
|
||||
Alias(_, _, actual_var, _) => {
|
||||
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||
var = *actual_var;
|
||||
}
|
||||
|
@ -1966,7 +1976,7 @@ pub fn gather_tags_unsorted_iter(
|
|||
// var = *sub_ext;
|
||||
}
|
||||
|
||||
Alias(_, _, actual_var) => {
|
||||
Alias(_, _, actual_var, _) => {
|
||||
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||
var = *actual_var;
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ use roc_types::subs::{
|
|||
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
|
||||
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 {
|
||||
() => {{
|
||||
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!(
|
||||
"Mismatch in {} Line {} Column {}",
|
||||
file!(),
|
||||
|
@ -205,7 +205,9 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
|||
Structure(flat_type) => {
|
||||
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),
|
||||
Error => {
|
||||
// Error propagates. Whatever we're comparing it to doesn't matter!
|
||||
|
@ -294,17 +296,26 @@ fn unify_alias(
|
|||
symbol: Symbol,
|
||||
args: AliasVariables,
|
||||
real_var: Variable,
|
||||
kind: AliasKind,
|
||||
) -> Outcome {
|
||||
let other_content = &ctx.second_desc.content;
|
||||
|
||||
let either_is_opaque =
|
||||
kind == AliasKind::Opaque || matches!(other_content, Alias(_, _, _, AliasKind::Opaque));
|
||||
|
||||
match other_content {
|
||||
FlexVar(_) => {
|
||||
// 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),
|
||||
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 args.len() == other_args.len() {
|
||||
let mut problems = Vec::new();
|
||||
|
@ -334,8 +345,8 @@ fn unify_alias(
|
|||
unify_pool(subs, pool, real_var, *other_real_var, ctx.mode)
|
||||
}
|
||||
}
|
||||
Structure(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
RangedNumber(other_real_var, other_range_vars) => {
|
||||
Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
|
||||
RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => {
|
||||
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode)
|
||||
|
@ -344,6 +355,11 @@ fn unify_alias(
|
|||
}
|
||||
}
|
||||
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_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
|
||||
// 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())
|
||||
}
|
||||
AliasKind::Opaque => {
|
||||
mismatch!(
|
||||
"Cannot unify structure {:?} with opaque {:?}",
|
||||
&flat_type,
|
||||
sym
|
||||
)
|
||||
}
|
||||
},
|
||||
RangedNumber(other_real_var, other_range_vars) => {
|
||||
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode);
|
||||
if outcome.is_empty() {
|
||||
|
@ -1122,17 +1147,6 @@ fn unify_shared_tags_merge_new(
|
|||
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)]
|
||||
fn unify_flat_type(
|
||||
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!
|
||||
merge(subs, ctx, RigidVar(name.clone()))
|
||||
}
|
||||
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _) | RangedNumber(..) => {
|
||||
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => {
|
||||
if !ctx.mode.contains(Mode::RIGID_AS_FLEX) {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
|
@ -1377,7 +1391,7 @@ fn unify_flex(
|
|||
| RigidVar(_)
|
||||
| RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(_, _, _)
|
||||
| Alias(_, _, _, _)
|
||||
| RangedNumber(..) => {
|
||||
// TODO special-case boolean here
|
||||
// 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
|
||||
|
||||
unify_pool(subs, pool, ctx.first, *actual, ctx.mode)
|
||||
|
|
|
@ -43,7 +43,7 @@ From roc to render:
|
|||
- `ed_model` also contains an `EdModule`, which holds the parsed abstract syntax tree (AST).
|
||||
- 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
|
||||
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.
|
||||
+ `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`.
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use crate::ui::text::lines::Lines;
|
||||
use crate::ui::text::selection::Selection;
|
||||
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_mut;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -14,134 +11,11 @@ pub struct CodeLines {
|
|||
}
|
||||
|
||||
impl CodeLines {
|
||||
pub fn insert_between_line(
|
||||
&mut self,
|
||||
line_nr: usize,
|
||||
index: usize,
|
||||
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();
|
||||
pub fn from_str(code_str: &str) -> CodeLines {
|
||||
CodeLines {
|
||||
lines: code_str.split('\n').map(|s| s.to_owned()).collect(),
|
||||
nr_of_chars: code_str.len(),
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use roc_ast::lang::core::ast::ASTNodeId;
|
||||
use roc_ast::lang::core::def::def2::Def2;
|
||||
use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::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)
|
||||
{
|
||||
// two blank lines between top level definitions
|
||||
EdModel::insert_empty_line(
|
||||
caret_line_nr + 1,
|
||||
&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,
|
||||
)?;
|
||||
EdModel::insert_empty_line(caret_line_nr + 1, &mut ed_model.grid_node_map)?;
|
||||
EdModel::insert_empty_line(caret_line_nr + 2, &mut ed_model.grid_node_map)?;
|
||||
// third "empty" line will be filled by the blank
|
||||
EdModel::insert_empty_line(
|
||||
caret_line_nr + 3,
|
||||
&mut ed_model.code_lines,
|
||||
&mut ed_model.grid_node_map,
|
||||
)?;
|
||||
EdModel::insert_empty_line(caret_line_nr + 3, &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)
|
||||
}
|
||||
|
||||
pub fn insert_new_blank(
|
||||
ed_model: &mut EdModel,
|
||||
caret_pos: &TextPos,
|
||||
insert_on_line_nr: usize,
|
||||
) -> EdResult<()> {
|
||||
pub fn insert_new_blank(ed_model: &mut EdModel, insert_on_line_nr: usize) -> EdResult<()> {
|
||||
println!(
|
||||
"{}",
|
||||
ed_model.module.ast.ast_to_string(ed_model.module.env.pool)
|
||||
);
|
||||
|
||||
// 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_id = ed_model.module.env.pool.add(new_line_blank);
|
||||
|
||||
let prev_def_mn_id = ed_model
|
||||
.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)?;
|
||||
let insertion_index = index_of(def_mark_node_id, &ed_model.markup_ids)?;
|
||||
ed_model
|
||||
.module
|
||||
.ast
|
||||
.def_ids
|
||||
.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],
|
||||
)?;
|
||||
.insert_def_at_index(new_line_blank_id, insertion_index);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use roc_ast::lang::env::Env;
|
|||
use roc_ast::mem_pool::pool_str::PoolStr;
|
||||
use roc_ast::parse::parse_ast;
|
||||
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_load::file::LoadedModule;
|
||||
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 line_nr = 0;
|
||||
|
@ -88,7 +90,6 @@ pub fn init_model<'a>(
|
|||
&mut col_nr,
|
||||
*mark_node_id,
|
||||
&mut grid_node_map,
|
||||
&mut code_lines,
|
||||
&mark_node_pool,
|
||||
)?
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ use roc_ast::mem_pool::pool_str::PoolStr;
|
|||
use roc_ast::solve_type;
|
||||
use roc_can::expected::Expected;
|
||||
use roc_code_markup::markup::attribute::Attributes;
|
||||
use roc_code_markup::markup::convert::from_ast::ast_to_mark_nodes;
|
||||
use roc_code_markup::markup::nodes;
|
||||
use roc_code_markup::markup::nodes::MarkupNode;
|
||||
use roc_code_markup::markup::nodes::EQUALS;
|
||||
|
@ -187,10 +188,8 @@ impl<'a> EdModel<'a> {
|
|||
new_str: &str,
|
||||
node_id: MarkNodeId,
|
||||
grid_node_map: &mut GridNodeMap,
|
||||
code_lines: &mut CodeLines,
|
||||
) -> UIResult<()> {
|
||||
grid_node_map.insert_between_line(line_nr, index, new_str.len(), node_id)?;
|
||||
code_lines.insert_between_line(line_nr, index, new_str)
|
||||
grid_node_map.insert_between_line(line_nr, index, new_str.len(), node_id)
|
||||
}
|
||||
|
||||
pub fn insert_all_between_line(
|
||||
|
@ -218,9 +217,6 @@ impl<'a> EdModel<'a> {
|
|||
node_id,
|
||||
)?;
|
||||
|
||||
self.code_lines
|
||||
.insert_between_line(curr_line_nr, col_nr, line)?;
|
||||
|
||||
curr_line_nr += 1;
|
||||
col_nr = 0;
|
||||
}
|
||||
|
@ -234,9 +230,6 @@ impl<'a> EdModel<'a> {
|
|||
node_id,
|
||||
)?;
|
||||
|
||||
self.code_lines
|
||||
.insert_between_line(line_nr, col_nr, &node_content)?;
|
||||
|
||||
col_nr += node_content.len();
|
||||
}
|
||||
}
|
||||
|
@ -249,7 +242,6 @@ impl<'a> EdModel<'a> {
|
|||
col_nr: &mut usize,
|
||||
mark_node_id: MarkNodeId,
|
||||
grid_node_map: &mut GridNodeMap,
|
||||
code_lines: &mut CodeLines,
|
||||
mark_node_pool: &SlowPool,
|
||||
) -> UIResult<()> {
|
||||
let mark_node = mark_node_pool.get(mark_node_id);
|
||||
|
@ -265,7 +257,6 @@ impl<'a> EdModel<'a> {
|
|||
col_nr,
|
||||
child_id,
|
||||
grid_node_map,
|
||||
code_lines,
|
||||
mark_node_pool,
|
||||
)?;
|
||||
}
|
||||
|
@ -278,20 +269,19 @@ impl<'a> EdModel<'a> {
|
|||
&node_content,
|
||||
mark_node_id,
|
||||
grid_node_map,
|
||||
code_lines,
|
||||
)?;
|
||||
|
||||
*col_nr += node_content.len();
|
||||
}
|
||||
|
||||
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;
|
||||
*col_nr = 0;
|
||||
|
||||
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;
|
||||
*col_nr = 0;
|
||||
|
@ -305,40 +295,29 @@ impl<'a> EdModel<'a> {
|
|||
pub fn break_line(
|
||||
line_nr: usize,
|
||||
col_nr: usize,
|
||||
code_lines: &mut CodeLines,
|
||||
grid_node_map: &mut GridNodeMap,
|
||||
) -> UIResult<()> {
|
||||
code_lines.break_line(line_nr, col_nr)?;
|
||||
grid_node_map.break_line(line_nr, col_nr)
|
||||
}
|
||||
|
||||
pub fn insert_empty_line(
|
||||
line_nr: usize,
|
||||
code_lines: &mut CodeLines,
|
||||
grid_node_map: &mut GridNodeMap,
|
||||
) -> UIResult<()> {
|
||||
code_lines.insert_empty_line(line_nr)?;
|
||||
pub fn insert_empty_line(line_nr: usize, grid_node_map: &mut GridNodeMap) -> UIResult<()> {
|
||||
grid_node_map.insert_empty_line(line_nr)
|
||||
}
|
||||
|
||||
pub fn push_empty_line(code_lines: &mut CodeLines, grid_node_map: &mut GridNodeMap) {
|
||||
code_lines.push_empty_line();
|
||||
pub fn push_empty_line(grid_node_map: &mut GridNodeMap) {
|
||||
grid_node_map.push_empty_line();
|
||||
}
|
||||
|
||||
pub fn clear_line(&mut self, line_nr: usize) -> UIResult<()> {
|
||||
self.grid_node_map.clear_line(line_nr)?;
|
||||
self.code_lines.clear_line(line_nr)
|
||||
self.grid_node_map.clear_line(line_nr)
|
||||
}
|
||||
|
||||
pub fn del_line(&mut self, line_nr: usize) -> UIResult<()> {
|
||||
self.grid_node_map.del_line(line_nr);
|
||||
self.code_lines.del_line(line_nr)
|
||||
pub fn del_line(&mut self, line_nr: usize) {
|
||||
self.grid_node_map.del_line(line_nr)
|
||||
}
|
||||
|
||||
pub fn del_at_line(&mut self, line_nr: usize, index: usize) -> UIResult<()> {
|
||||
self.grid_node_map.del_at_line(line_nr, index)?;
|
||||
self.code_lines.del_at_line(line_nr, index)
|
||||
self.grid_node_map.del_at_line(line_nr, index)
|
||||
}
|
||||
|
||||
// updates grid_node_map and code_lines but nothing else.
|
||||
|
@ -347,9 +326,7 @@ impl<'a> EdModel<'a> {
|
|||
line_nr: usize,
|
||||
col_range: std::ops::Range<usize>,
|
||||
) -> UIResult<()> {
|
||||
self.grid_node_map
|
||||
.del_range_at_line(line_nr, col_range.clone())?;
|
||||
self.code_lines.del_range_at_line(line_nr, col_range)
|
||||
self.grid_node_map.del_range_at_line(line_nr, col_range)
|
||||
}
|
||||
|
||||
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 {})?;
|
||||
|
||||
self.code_lines.del_selection(active_selection)?;
|
||||
self.grid_node_map.del_selection(active_selection)?;
|
||||
|
||||
match sel_block.ast_node_id {
|
||||
|
@ -624,7 +600,6 @@ impl<'a> EdModel<'a> {
|
|||
nodes::BLANK_PLACEHOLDER,
|
||||
expr_mark_node_id,
|
||||
&mut self.grid_node_map,
|
||||
&mut self.code_lines,
|
||||
)?;
|
||||
|
||||
self.set_sel_none();
|
||||
|
@ -678,6 +653,41 @@ impl<'a> EdModel<'a> {
|
|||
|
||||
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> {
|
||||
|
@ -1080,6 +1090,7 @@ pub fn handle_new_char_expr(
|
|||
let (new_child_index, new_ast_child_index) = ed_model.get_curr_child_indices()?;
|
||||
// insert a Blank first, this results in cleaner code
|
||||
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)?
|
||||
} else {
|
||||
InputOutcome::Ignored
|
||||
|
@ -1162,6 +1173,7 @@ pub fn handle_new_char_diff_mark_nodes_prev_is_expr(
|
|||
let new_ast_child_index = 0;
|
||||
// insert a Blank first, this results in cleaner code
|
||||
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)?
|
||||
} else {
|
||||
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.
|
||||
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 {
|
||||
'\u{e000}'..='\u{f8ff}' // http://www.unicode.org/faq/private_use.html
|
||||
| '\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() {
|
||||
|
||||
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)?
|
||||
|
@ -1260,6 +1275,7 @@ pub fn handle_new_char(received_char: &char, ed_model: &mut EdModel) -> EdResult
|
|||
};
|
||||
|
||||
if let InputOutcome::Accepted = input_outcome {
|
||||
ed_model.post_process_ast_update()?;
|
||||
ed_model.dirty = true;
|
||||
}
|
||||
|
||||
|
@ -1684,92 +1700,32 @@ pub mod test_ed_update {
|
|||
fn test_record() -> Result<(), String> {
|
||||
assert_insert_in_def_nls(ovec!["{ ┃ }"], '{')?;
|
||||
assert_insert_nls(ovec!["val = { ┃ }"], ovec!["val = { a┃ }"], 'a')?;
|
||||
assert_insert_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
ovec!["val = { ab┃: RunTimeError }"],
|
||||
'b',
|
||||
)?; // TODO: remove RunTimeError, see issue #1649
|
||||
assert_insert_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
ovec!["val = { a1┃: RunTimeError }"],
|
||||
'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(ovec!["val = { a┃ }"], ovec!["val = { ab┃ }"], 'b')?;
|
||||
assert_insert_nls(ovec!["val = { a┃ }"], ovec!["val = { a1┃ }"], '1')?;
|
||||
assert_insert_nls(ovec!["val = { a1┃ }"], ovec!["val = { a1z┃ }"], 'z')?;
|
||||
assert_insert_nls(ovec!["val = { a1┃ }"], ovec!["val = { a15┃ }"], '5')?;
|
||||
assert_insert_nls(ovec!["val = { ab┃ }"], ovec!["val = { abc┃ }"], 'c')?;
|
||||
assert_insert_nls(ovec!["val = { ┃abc }"], ovec!["val = { z┃abc }"], 'z')?;
|
||||
assert_insert_nls(ovec!["val = { a┃b }"], ovec!["val = { az┃b }"], 'z')?;
|
||||
assert_insert_nls(ovec!["val = { a┃b }"], ovec!["val = { a9┃b }"], '9')?;
|
||||
|
||||
assert_insert_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
ovec!["val = { a┃: RunTimeError }"],
|
||||
':',
|
||||
)?;
|
||||
assert_insert_nls(
|
||||
ovec!["val = { abc┃ }"],
|
||||
ovec!["val = { abc┃: RunTimeError }"],
|
||||
':',
|
||||
)?;
|
||||
assert_insert_nls(
|
||||
ovec!["val = { aBc┃ }"],
|
||||
ovec!["val = { aBc┃: RunTimeError }"],
|
||||
':',
|
||||
)?;
|
||||
assert_insert_nls(ovec!["val = { a┃ }"], ovec!["val = { a: ┃ }"], ':')?;
|
||||
assert_insert_nls(ovec!["val = { abc┃ }"], ovec!["val = { abc: ┃ }"], ':')?;
|
||||
assert_insert_nls(ovec!["val = { aBc┃ }"], ovec!["val = { aBc: ┃ }"], ':')?;
|
||||
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
ovec!["val = { a┃: RunTimeError }"],
|
||||
":\"",
|
||||
)?;
|
||||
assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: \"┃\" }"], ":\"")?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc┃ }"],
|
||||
ovec!["val = { abc┃: RunTimeError }"],
|
||||
ovec!["val = { abc: \"┃\" }"],
|
||||
":\"",
|
||||
)?;
|
||||
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
ovec!["val = { a0┃: RunTimeError }"],
|
||||
":0",
|
||||
)?;
|
||||
assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: 0┃ }"], ":0")?;
|
||||
assert_insert_seq_nls(ovec!["val = { abc┃ }"], ovec!["val = { abc: 9┃ }"], ":9")?;
|
||||
assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: 1000┃ }"], ":1000")?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc┃ }"],
|
||||
ovec!["val = { abc9┃: RunTimeError }"],
|
||||
":9",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
ovec!["val = { a1000┃: RunTimeError }"],
|
||||
":1000",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc┃ }"],
|
||||
ovec!["val = { abc98761┃: RunTimeError }"],
|
||||
ovec!["val = { abc: 98761┃ }"],
|
||||
":98761",
|
||||
)?;
|
||||
|
||||
|
@ -1919,19 +1875,11 @@ pub mod test_ed_update {
|
|||
|
||||
#[test]
|
||||
fn test_nested_record() -> Result<(), String> {
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
ovec!["val = { a┃: RunTimeError }"],
|
||||
":{",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc┃ }"],
|
||||
ovec!["val = { abc┃: RunTimeError }"],
|
||||
":{",
|
||||
)?;
|
||||
assert_insert_seq_nls(ovec!["val = { a┃ }"], ovec!["val = { a: { ┃ } }"], ":{")?;
|
||||
assert_insert_seq_nls(ovec!["val = { abc┃ }"], ovec!["val = { abc: { ┃ } }"], ":{")?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { camelCase┃ }"],
|
||||
ovec!["val = { camelCase┃: RunTimeError }"],
|
||||
ovec!["val = { camelCase: { ┃ } }"],
|
||||
":{",
|
||||
)?;
|
||||
|
||||
|
@ -1953,49 +1901,49 @@ pub mod test_ed_update {
|
|||
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a: { zulu┃ } }"],
|
||||
ovec!["val = { a: { zulu┃: RunTimeError } }"],
|
||||
ovec!["val = { a: { zulu: ┃ } }"],
|
||||
":",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc: { camelCase┃ } }"],
|
||||
ovec!["val = { abc: { camelCase┃: RunTimeError } }"],
|
||||
ovec!["val = { abc: { camelCase: ┃ } }"],
|
||||
":",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { camelCase: { z┃ } }"],
|
||||
ovec!["val = { camelCase: { z┃: RunTimeError } }"],
|
||||
ovec!["val = { camelCase: { z: ┃ } }"],
|
||||
":",
|
||||
)?;
|
||||
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a┃: { zulu } }"],
|
||||
ovec!["val = { a0┃: { zulu: RunTimeError } }"],
|
||||
ovec!["val = { a0┃: { zulu } }"],
|
||||
"0",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { ab┃c: { camelCase } }"],
|
||||
ovec!["val = { abz┃c: { camelCase: RunTimeError } }"],
|
||||
ovec!["val = { abz┃c: { camelCase } }"],
|
||||
"z",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { ┃camelCase: { z } }"],
|
||||
ovec!["val = { x┃camelCase: { z: RunTimeError } }"],
|
||||
ovec!["val = { x┃camelCase: { z } }"],
|
||||
"x",
|
||||
)?;
|
||||
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a: { zulu┃ } }"],
|
||||
ovec!["val = { a: { zulu┃: RunTimeError } }"],
|
||||
ovec!["val = { a: { zulu: \"┃\" } }"],
|
||||
":\"",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc: { camelCase┃ } }"],
|
||||
ovec!["val = { abc: { camelCase┃: RunTimeError } }"],
|
||||
ovec!["val = { abc: { camelCase: \"┃\" } }"],
|
||||
":\"",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
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(
|
||||
ovec!["val = { a: { zulu┃ } }"],
|
||||
ovec!["val = { a: { zulu1┃: RunTimeError } }"],
|
||||
ovec!["val = { a: { zulu: 1┃ } }"],
|
||||
":1",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc: { camelCase┃ } }"],
|
||||
ovec!["val = { abc: { camelCase0┃: RunTimeError } }"],
|
||||
ovec!["val = { abc: { camelCase: 0┃ } }"],
|
||||
":0",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { camelCase: { z┃ } }"],
|
||||
ovec!["val = { camelCase: { z45┃: RunTimeError } }"],
|
||||
ovec!["val = { camelCase: { z: 45┃ } }"],
|
||||
":45",
|
||||
)?;
|
||||
|
||||
|
@ -2039,17 +1987,17 @@ pub mod test_ed_update {
|
|||
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a: { zulu┃ } }"],
|
||||
ovec!["val = { a: { zulu┃: RunTimeError } }"],
|
||||
ovec!["val = { a: { zulu: { ┃ } } }"],
|
||||
":{",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { abc: { camelCase┃ } }"],
|
||||
ovec!["val = { abc: { camelCase┃: RunTimeError } }"],
|
||||
ovec!["val = { abc: { camelCase: { ┃ } } }"],
|
||||
":{",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
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(
|
||||
ovec!["val = { a┃: { bcD: { eFgHij: { k15 } } } }"],
|
||||
ovec!["val = { a4┃: { bcD: { eFgHij: { k15: RunTimeError } } } }"],
|
||||
ovec!["val = { a4┃: { bcD: { eFgHij: { k15 } } } }"],
|
||||
"4",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { ┃a: { bcD: { eFgHij: { k15 } } } }"],
|
||||
ovec!["val = { y┃a: { bcD: { eFgHij: { k15: RunTimeError } } } }"],
|
||||
ovec!["val = { y┃a: { bcD: { eFgHij: { k15 } } } }"],
|
||||
"y",
|
||||
)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a: { bcD: { eF┃gHij: { k15 } } } }"],
|
||||
ovec!["val = { a: { bcD: { eFxyz┃gHij: { k15: RunTimeError } } } }"],
|
||||
ovec!["val = { a: { bcD: { eFxyz┃gHij: { k15 } } } }"],
|
||||
"xyz",
|
||||
)?;
|
||||
|
||||
|
@ -2099,6 +2047,14 @@ pub mod test_ed_update {
|
|||
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]
|
||||
fn test_ignore_record() -> Result<(), String> {
|
||||
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_NO_LTR)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = { ┃a: RunTimeError }"], IGNORE_NO_LTR)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = { ┃abc: RunTimeError }"], IGNORE_NO_LTR)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = { ┃a }"], 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: ┃RunTimeError }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = {┃ a: RunTimeError }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = { a:┃ RunTimeError }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = ┃{ a }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { a┃ }"],
|
||||
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: ┃RunTimeError }"], 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 = ┃{ a15 }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = {┃ a15 }"], IGNORE_CHARS)?;
|
||||
|
||||
assert_insert_seq_ignore_nls(ovec!["val = ┃{ camelCase: RunTimeError }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = { camelCase: ┃RunTimeError }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCase: 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 }"], IGNORE_CHARS)?;
|
||||
assert_insert_seq_nls(
|
||||
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)?;
|
||||
|
@ -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: { } }"], "1")?;
|
||||
|
||||
assert_insert_seq_ignore_nls(
|
||||
ovec!["val = { camelCaseB1: { z15a:┃ RunTimeError } }"],
|
||||
IGNORE_NO_LTR,
|
||||
assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: {┃ z15a } }"], IGNORE_NO_LTR)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = { camelCaseB1: ┃{ z15a } }"], IGNORE_NO_LTR)?;
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { camelCaseB1: { z15a┃ } }"],
|
||||
ovec!["val = { camelCaseB1: { z15a:┃ } }"],
|
||||
&concat_strings(":🡰", IGNORE_CHARS),
|
||||
)?;
|
||||
assert_insert_seq_ignore_nls(
|
||||
ovec!["val = { camelCaseB1: {┃ z15a: RunTimeError } }"],
|
||||
IGNORE_NO_LTR,
|
||||
assert_insert_seq_nls(
|
||||
ovec!["val = { camelCaseB1: { z15a┃ } }"],
|
||||
ovec!["val = { camelCaseB1: { z15a: ┃ } }"],
|
||||
&concat_strings(":🡲", IGNORE_CHARS),
|
||||
)?;
|
||||
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: 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(ovec!["val = { camelCaseB1:┃ { z15a } }"], IGNORE_NO_LTR)?;
|
||||
assert_insert_seq_ignore_nls(ovec!["val = {┃ camelCaseB1: { z15a } }"], 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(ovec!["val = { camelCaseB1: { ┃z15a } }"], "1")?;
|
||||
|
||||
assert_insert_seq_ignore_nls(
|
||||
ovec!["val = { camelCaseB1: { z15a: \"\"┃ } }"],
|
||||
|
@ -2376,39 +2325,27 @@ pub mod test_ed_update {
|
|||
)?;
|
||||
|
||||
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: R┃unTimeError } } } } } } } }"],
|
||||
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 } } } } } } } }┃"],
|
||||
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: RunTimeEr┃ror } } } } } } } }"],
|
||||
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 } } } } } } } }"],
|
||||
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 } } } } } } } }"],
|
||||
IGNORE_NO_LTR,
|
||||
)?;
|
||||
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 } } } } } } } }"],
|
||||
"2",
|
||||
)?;
|
||||
Ok(())
|
||||
|
@ -2619,7 +2556,8 @@ pub mod test_ed_update {
|
|||
assert_insert_nls(ovec!["┃"], ovec!["z┃ = "], 'z')?;
|
||||
|
||||
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!["c137┃ = "], "c137")?;
|
||||
assert_insert_seq_nls(ovec!["┃"], ovec!["c137Bb┃ = "], "c137Bb")?;
|
||||
|
|
|
@ -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::IntVal;
|
||||
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::syntax_highlight::HighlightStyle;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
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
|
||||
pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
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();
|
||||
|
||||
|
@ -45,36 +41,10 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult<Inpu
|
|||
.pool
|
||||
.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 {
|
||||
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;
|
||||
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)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
|
@ -109,16 +79,6 @@ pub fn update_int(
|
|||
|
||||
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
|
||||
let new_pool_str = PoolStr::new(&content_str, ed_model.module.env.pool);
|
||||
let int_ast_node = ed_model
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
use roc_ast::lang::core::ast::ASTNodeId;
|
||||
use roc_ast::lang::core::expr::expr2::Expr2;
|
||||
use roc_ast::lang::core::pattern::Pattern2;
|
||||
use roc_ast::lang::core::val_def::ValueDef;
|
||||
use roc_code_markup::markup::attribute::Attributes;
|
||||
use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls;
|
||||
use roc_code_markup::markup::common_nodes::new_equals_mn;
|
||||
use roc_code_markup::markup::nodes::MarkupNode;
|
||||
use roc_code_markup::syntax_highlight::HighlightStyle;
|
||||
use roc_module::symbol::Symbol;
|
||||
|
||||
use crate::editor::ed_error::EdResult;
|
||||
|
@ -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> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
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();
|
||||
// safe unwrap because our ArrString has a 30B capacity
|
||||
let val_expr2_node = Expr2::Blank;
|
||||
let val_expr_id = ed_model.module.env.pool.add(val_expr2_node);
|
||||
|
||||
let ident_id = ed_model
|
||||
.module
|
||||
.env
|
||||
.ident_ids
|
||||
.add(val_name_string.clone().into());
|
||||
let ident_id = ed_model.module.env.ident_ids.add(val_name_string.into());
|
||||
let var_symbol = Symbol::new(ed_model.module.env.home, ident_id);
|
||||
let body = Expr2::Var(var_symbol);
|
||||
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
|
||||
.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 {
|
||||
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;
|
||||
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)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
|
|
|
@ -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::mem_pool::pool_vec::PoolVec;
|
||||
use roc_code_markup::markup::common_nodes::{
|
||||
new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn,
|
||||
};
|
||||
use roc_code_markup::markup::nodes::{self, MarkupNode};
|
||||
use roc_code_markup::markup::nodes::{self};
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
|
||||
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_update::get_node_context;
|
||||
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> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
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 {
|
||||
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
|
||||
.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 {
|
||||
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());
|
||||
|
||||
// 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)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
|
@ -96,7 +49,7 @@ pub fn add_blank_child(
|
|||
ed_model: &mut EdModel,
|
||||
) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id,
|
||||
curr_mark_node: _,
|
||||
parent_id_opt,
|
||||
|
@ -133,7 +86,7 @@ pub fn add_blank_child(
|
|||
.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);
|
||||
|
||||
|
@ -164,75 +117,9 @@ pub fn add_blank_child(
|
|||
.fail(),
|
||||
}?;
|
||||
|
||||
let new_mark_children = update_mark_children(
|
||||
new_child_index,
|
||||
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)?;
|
||||
if new_child_index > 1 {
|
||||
ed_model.simple_move_carets_right(nodes::COMMA.len());
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ pub fn update_invalid_lookup(
|
|||
ed_model: &mut EdModel,
|
||||
) -> EdResult<InputOutcome> {
|
||||
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();
|
||||
|
||||
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
|
||||
.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
|
||||
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)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
|
|
|
@ -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_vec::PoolVec;
|
||||
use roc_code_markup::markup::attribute::Attributes;
|
||||
use roc_code_markup::markup::common_nodes::new_blank_mn;
|
||||
use roc_code_markup::markup::common_nodes::new_left_accolade_mn;
|
||||
use roc_code_markup::markup::common_nodes::new_right_accolade_mn;
|
||||
use roc_code_markup::markup::nodes;
|
||||
use roc_code_markup::markup::nodes::MarkupNode;
|
||||
use roc_code_markup::markup::nodes::COLON;
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
use roc_code_markup::syntax_highlight::HighlightStyle;
|
||||
use snafu::OptionExt;
|
||||
|
||||
pub fn start_new_record(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
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 expr2_node = Expr2::EmptyRecord;
|
||||
|
||||
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 {
|
||||
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());
|
||||
|
||||
// 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)
|
||||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
|
@ -156,7 +123,6 @@ pub fn update_empty_record(
|
|||
new_input,
|
||||
record_field_node_id,
|
||||
&mut ed_model.grid_node_map,
|
||||
&mut ed_model.code_lines,
|
||||
)?;
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
|
@ -174,12 +140,11 @@ pub fn update_record_colon(
|
|||
) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
curr_mark_node,
|
||||
parent_id_opt,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node: _,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = 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 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 { .. })
|
||||
&& 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);
|
||||
|
||||
match ast_node_ref {
|
||||
|
@ -209,8 +170,7 @@ pub fn update_record_colon(
|
|||
if ed_model.node_exists_at_caret() {
|
||||
let next_mark_node_id =
|
||||
ed_model.grid_node_map.get_id_at_row_col(old_caret_pos)?;
|
||||
let next_mark_node =
|
||||
ed_model.mark_node_pool.get(next_mark_node_id);
|
||||
let next_mark_node = ed_model.mark_node_pool.get(next_mark_node_id);
|
||||
if next_mark_node.get_content() == nodes::RIGHT_ACCOLADE {
|
||||
// update AST node
|
||||
let new_field_val = Expr2::Blank;
|
||||
|
@ -228,51 +188,8 @@ pub fn update_record_colon(
|
|||
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
|
||||
ed_model.simple_move_carets_right(record_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],
|
||||
)?;
|
||||
ed_model.simple_move_carets_right(COLON.len());
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
} else {
|
||||
|
@ -292,12 +209,6 @@ pub fn update_record_colon(
|
|||
} else {
|
||||
Ok(InputOutcome::Ignored)
|
||||
}
|
||||
} else {
|
||||
MissingParent {
|
||||
node_id: curr_mark_node_id,
|
||||
}
|
||||
.fail()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_record_field(
|
||||
|
@ -329,16 +240,6 @@ pub fn update_record_field(
|
|||
// update caret
|
||||
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
|
||||
let first_field = record_fields
|
||||
.iter(ed_model.module.env.pool)
|
||||
|
|
|
@ -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::str::update_str_expr;
|
||||
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::mvc::app_update::InputOutcome;
|
||||
|
@ -21,21 +17,19 @@ pub fn update_small_string(
|
|||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
curr_mark_node: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
let new_input = &new_char.to_string();
|
||||
|
||||
// update markup
|
||||
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 content_str = curr_mark_node.get_content();
|
||||
let node_caret_offset = ed_model
|
||||
.grid_node_map
|
||||
.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 let Expr2::SmallStr(ref mut mut_array_str) =
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
curr_mark_node: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
// update markup
|
||||
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 content_str = curr_mark_node.get_content();
|
||||
let node_caret_offset = ed_model
|
||||
.grid_node_map
|
||||
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
|
||||
|
||||
if node_caret_offset != 0 && node_caret_offset < content_str_mut.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,
|
||||
)?;
|
||||
|
||||
if node_caret_offset != 0 && node_caret_offset < content_str.len() {
|
||||
// update ast
|
||||
update_str_expr(
|
||||
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> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node,
|
||||
parent_id_opt,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
if curr_mark_node.is_blank() {
|
||||
let new_expr2_node = Expr2::SmallStr(arrayvec::ArrayString::new());
|
||||
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
|
||||
|
||||
ed_model
|
||||
.module
|
||||
|
@ -144,32 +111,6 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
|
|||
.pool
|
||||
.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);
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
use roc_ast::lang::core::{ast::ASTNodeId, def::def2::Def2, expr::expr2::Expr2};
|
||||
use roc_code_markup::{
|
||||
markup::{
|
||||
common_nodes::new_blank_mn_w_nls, nodes::set_parent_for_all, top_level_def::tld_mark_node,
|
||||
},
|
||||
slow_pool::MarkNodeId,
|
||||
};
|
||||
use roc_ast::lang::core::{def::def2::Def2, expr::expr2::Expr2};
|
||||
use roc_code_markup::slow_pool::MarkNodeId;
|
||||
|
||||
use crate::{
|
||||
editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound},
|
||||
|
@ -20,19 +15,17 @@ use super::{
|
|||
// Top Level Defined Value. example: `main = "Hello, World!"`
|
||||
pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<InputOutcome> {
|
||||
let NodeContext {
|
||||
old_caret_pos,
|
||||
curr_mark_node_id,
|
||||
old_caret_pos: _,
|
||||
curr_mark_node_id: _,
|
||||
curr_mark_node: _,
|
||||
parent_id_opt: _,
|
||||
ast_node_id,
|
||||
} = get_node_context(ed_model)?;
|
||||
|
||||
// create new blank >> m = Blank
|
||||
let val_expr_node = Expr2::Blank;
|
||||
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 ident_id = ed_model
|
||||
|
@ -57,14 +50,6 @@ pub fn start_new_tld_value(ed_model: &mut EdModel, new_char: &char) -> EdResult<
|
|||
.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 {
|
||||
identifier_id: ident_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
|
||||
.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;
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -112,37 +75,28 @@ pub fn update_tld_val_name(
|
|||
) -> EdResult<InputOutcome> {
|
||||
if new_char.is_ascii_alphanumeric() {
|
||||
// update markup
|
||||
let val_name_mn_mut = ed_model.mark_node_pool.get_mut(val_name_mn_id);
|
||||
let content_str_mut = val_name_mn_mut.get_content_mut()?;
|
||||
let val_name_mn = ed_model.mark_node_pool.get(val_name_mn_id);
|
||||
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
|
||||
.grid_node_map
|
||||
.get_offset_to_node_id(old_caret_pos, val_name_mn_id)?;
|
||||
|
||||
if node_caret_offset <= content_str_mut.len() {
|
||||
content_str_mut.insert(node_caret_offset, *new_char);
|
||||
if node_caret_offset <= val_name_str.len() {
|
||||
val_name_str.insert(node_caret_offset, *new_char);
|
||||
|
||||
let update_val_name_res = ed_model
|
||||
.module
|
||||
.env
|
||||
.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 {
|
||||
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);
|
||||
|
||||
Ok(InputOutcome::Accepted)
|
||||
|
|
|
@ -91,7 +91,7 @@ pub extern "C" fn rust_main() -> i32 {
|
|||
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 layout = Layout::array::<u8>(size).unwrap();
|
||||
let buffer = std::alloc::alloc(layout) as *mut u8;
|
||||
|
|
|
@ -24,15 +24,16 @@ target-lexicon = "0.12.2"
|
|||
|
||||
# TODO: make llvm optional
|
||||
roc_build = {path = "../compiler/build", features = ["llvm"]}
|
||||
roc_builtins = {path = "../compiler/builtins"}
|
||||
roc_collections = {path = "../compiler/collections"}
|
||||
roc_gen_llvm = {path = "../compiler/gen_llvm"}
|
||||
roc_load = {path = "../compiler/load"}
|
||||
roc_mono = {path = "../compiler/mono"}
|
||||
roc_parse = {path = "../compiler/parse"}
|
||||
roc_repl_eval = {path = "../repl_eval"}
|
||||
roc_std = {path = "../roc_std"}
|
||||
roc_target = {path = "../compiler/roc_target"}
|
||||
roc_types = {path = "../compiler/types"}
|
||||
roc_builtins = {path = "../compiler/builtins"}
|
||||
|
||||
[lib]
|
||||
name = "roc_repl_cli"
|
||||
|
|
|
@ -20,6 +20,7 @@ use roc_parse::parser::{EExpr, ELambda, SyntaxError};
|
|||
use roc_repl_eval::eval::jit_to_ast;
|
||||
use roc_repl_eval::gen::{compile_to_mono, format_answer, ReplOutput};
|
||||
use roc_repl_eval::{ReplApp, ReplAppMemory};
|
||||
use roc_std::RocStr;
|
||||
use roc_target::TargetInfo;
|
||||
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);
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str {
|
||||
unsafe { *(addr as *const &'static str) }
|
||||
let reference: &RocStr = unsafe { std::mem::transmute(addr) };
|
||||
reference.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ fn unroll_newtypes<'a>(
|
|||
let field_var = *field.as_inner();
|
||||
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
|
||||
// unrolled newtypes. In such cases return the list of unrolled newtypes, but keep
|
||||
// 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 {
|
||||
while let Content::Alias(_, _, real) = content {
|
||||
while let Content::Alias(_, _, real, _) = content {
|
||||
content = env.subs.get_content_without_compacting(*real);
|
||||
}
|
||||
content
|
||||
|
@ -333,10 +333,16 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
|
|||
|
||||
Ok(result)
|
||||
}
|
||||
Layout::Builtin(Builtin::Str) => Ok(app
|
||||
.call_function(main_fn_name, |_, string: &'static str| {
|
||||
str_to_ast(env.arena, env.arena.alloc(string))
|
||||
})),
|
||||
Layout::Builtin(Builtin::Str) => {
|
||||
let size = layout.stack_size(env.target_info) as usize;
|
||||
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(
|
||||
main_fn_name,
|
||||
|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)
|
||||
}
|
||||
(_, Layout::Builtin(Builtin::Str)) => {
|
||||
let arena_str = mem.deref_str(addr);
|
||||
|
||||
str_to_ast(env.arena, arena_str)
|
||||
let string = mem.deref_str(addr);
|
||||
let arena_str = env.arena.alloc_str(string);
|
||||
Expr::Str(StrLiteral::PlainLine(arena_str))
|
||||
}
|
||||
(_, Layout::Struct{field_layouts, ..}) => match content {
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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<'_> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,12 @@ edition = "2021"
|
|||
name = "repl_test"
|
||||
version = "0.1.0"
|
||||
|
||||
[[test]]
|
||||
name = "repl_test"
|
||||
path = "src/tests.rs"
|
||||
|
||||
[build-dependencies]
|
||||
roc_cli = {path = "../cli"}
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.3"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
|
|
|
@ -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
12
repl_test/src/lib.rs
Normal 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;
|
|
@ -1,12 +1,9 @@
|
|||
#[allow(unused_imports)]
|
||||
use indoc::indoc;
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
mod cli;
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
use crate::cli::{expect_failure, expect_success};
|
||||
|
||||
#[cfg(feature = "wasm")]
|
||||
mod wasm;
|
||||
#[cfg(feature = "wasm")]
|
||||
#[allow(unused_imports)]
|
||||
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 ]*")
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn bool_in_record() {
|
||||
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
|
||||
|
@ -98,21 +94,18 @@ fn bool_in_record() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn bool_basic_equality() {
|
||||
expect_success("1 == 1", "True : Bool");
|
||||
expect_success("1 != 1", "False : Bool");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
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", "Green : [ Green, Red ]*");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn tag_without_arguments() {
|
||||
expect_success("True", "True : [ True ]*");
|
||||
|
@ -132,7 +125,6 @@ fn byte_tag_union() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn tag_in_record() {
|
||||
expect_success(
|
||||
|
@ -152,13 +144,11 @@ fn single_element_tag_union() {
|
|||
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn newtype_of_unit() {
|
||||
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn newtype_of_big_data() {
|
||||
expect_success(
|
||||
|
@ -174,7 +164,6 @@ fn newtype_of_big_data() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn newtype_nested() {
|
||||
expect_success(
|
||||
|
@ -190,7 +179,6 @@ fn newtype_nested() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn newtype_of_big_of_newtype() {
|
||||
expect_success(
|
||||
|
@ -221,19 +209,16 @@ fn literal_empty_str() {
|
|||
expect_success("\"\"", "\"\" : Str");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn literal_ascii_str() {
|
||||
expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn literal_utf8_str() {
|
||||
expect_success("\"👩👩👦👦\"", "\"👩👩👦👦\" : Str");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn str_concat() {
|
||||
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 *)");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn literal_string_list() {
|
||||
expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn nested_string_list() {
|
||||
expect_success(
|
||||
|
@ -432,7 +415,6 @@ fn list_last() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
expect_success("{}", "{} : {}");
|
||||
|
@ -568,7 +550,7 @@ fn stdlib_function() {
|
|||
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]
|
||||
fn too_few_args() {
|
||||
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]
|
||||
fn type_problem() {
|
||||
expect_failure(
|
||||
|
@ -646,7 +628,6 @@ fn multiline_input() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn recursive_tag_union_flat_variant() {
|
||||
expect_success(
|
||||
|
@ -662,7 +643,6 @@ fn recursive_tag_union_flat_variant() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn large_recursive_tag_union_flat_variant() {
|
||||
expect_success(
|
||||
|
@ -679,7 +659,6 @@ fn large_recursive_tag_union_flat_variant() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn recursive_tag_union_recursive_variant() {
|
||||
expect_success(
|
||||
|
@ -695,7 +674,6 @@ fn recursive_tag_union_recursive_variant() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn large_recursive_tag_union_recursive_variant() {
|
||||
expect_success(
|
||||
|
@ -712,7 +690,6 @@ fn large_recursive_tag_union_recursive_variant() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn recursive_tag_union_into_flat_tag_union() {
|
||||
expect_success(
|
||||
|
@ -728,7 +705,6 @@ fn recursive_tag_union_into_flat_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn non_nullable_unwrapped_tag_union() {
|
||||
expect_success(
|
||||
|
@ -748,7 +724,6 @@ fn non_nullable_unwrapped_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn nullable_unwrapped_tag_union() {
|
||||
expect_success(
|
||||
|
@ -768,7 +743,6 @@ fn nullable_unwrapped_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn nullable_wrapped_tag_union() {
|
||||
expect_success(
|
||||
|
@ -792,7 +766,6 @@ fn nullable_wrapped_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn large_nullable_wrapped_tag_union() {
|
||||
// > 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]
|
||||
fn parse_problem() {
|
||||
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]
|
||||
fn mono_problem() {
|
||||
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 * }"#,
|
||||
)
|
||||
}
|
||||
|
||||
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 ]*"#,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::{
|
|||
fs,
|
||||
ops::{Deref, DerefMut},
|
||||
path::Path,
|
||||
sync::Mutex,
|
||||
thread_local,
|
||||
};
|
||||
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";
|
||||
|
||||
thread_local! {
|
||||
static COMPILER: RefCell<Option<Instance>> = RefCell::new(None)
|
||||
static REPL_STATE: RefCell<Option<ReplState>> = RefCell::new(None)
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static REPL_STATE: RefCell<Option<ReplState>> = RefCell::new(None)
|
||||
// The compiler Wasm instance.
|
||||
// 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 {
|
||||
|
@ -25,10 +61,9 @@ struct ReplState {
|
|||
output: Option<String>,
|
||||
}
|
||||
|
||||
fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) {
|
||||
let app = COMPILER.with(|f| {
|
||||
if let Some(compiler) = f.borrow().deref() {
|
||||
let memory = compiler.exports.get_memory("memory").unwrap();
|
||||
fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) -> u32 {
|
||||
let app: Instance = {
|
||||
let memory = COMPILER.exports.get_memory("memory").unwrap();
|
||||
let memory_bytes: &[u8] = unsafe { memory.data_unchecked() };
|
||||
|
||||
// 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
|
||||
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
|
||||
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)
|
||||
.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.)
|
||||
Instance::new(&wasmer_module, &import_object).unwrap()
|
||||
} else {
|
||||
unreachable!()
|
||||
// Create an executable instance
|
||||
match Instance::new(&wasmer_module, &import_object) {
|
||||
Ok(instance) => instance,
|
||||
Err(e) => {
|
||||
println!("Failed to create Wasm instance {:?}", e);
|
||||
return false.into();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
REPL_STATE.with(|f| {
|
||||
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!()
|
||||
}
|
||||
});
|
||||
|
||||
return true.into();
|
||||
}
|
||||
|
||||
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 len = app_memory_bytes.len();
|
||||
|
||||
COMPILER.with(|f| {
|
||||
if let Some(compiler) = f.borrow().deref() {
|
||||
let memory = compiler.exports.get_memory("memory").unwrap();
|
||||
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);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
|
||||
result_addr
|
||||
} else {
|
||||
|
@ -127,24 +171,17 @@ fn wasmer_copy_input_string(src_buffer_addr: u32) {
|
|||
}
|
||||
});
|
||||
|
||||
COMPILER.with(|c| {
|
||||
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: &mut [u8] = unsafe { memory.data_unchecked_mut() };
|
||||
|
||||
let buf_addr = src_buffer_addr as usize;
|
||||
let len = src.len();
|
||||
memory_bytes[buf_addr..][..len].copy_from_slice(src.as_bytes());
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) {
|
||||
let output: String = COMPILER.with(|c| {
|
||||
if let Some(compiler) = c.borrow().deref() {
|
||||
let memory = compiler.exports.get_memory("memory").unwrap();
|
||||
let output: String = {
|
||||
let memory = COMPILER.exports.get_memory("memory").unwrap();
|
||||
let memory_bytes: &[u8] = unsafe { memory.data_unchecked() };
|
||||
|
||||
// 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
|
||||
let copied_bytes = output_bytes.to_vec();
|
||||
unsafe { String::from_utf8_unchecked(copied_bytes) }
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
REPL_STATE.with(|f| {
|
||||
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 {
|
||||
if matches!(e.kind(), std::io::ErrorKind::NotFound) {
|
||||
format!(
|
||||
"\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!",
|
||||
"",
|
||||
"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.",
|
||||
"It will build a .wasm binary for the compiler, and a native binary for the tests themselves",
|
||||
]
|
||||
.join("\n ")
|
||||
)
|
||||
|
@ -248,12 +221,45 @@ fn dummy_system_time_now() -> f64 {
|
|||
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) {
|
||||
let (ok, output) = run(input);
|
||||
assert_eq!(ok, true);
|
||||
assert_eq!(output, expected);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn expect_failure(input: &'static str, expected: &str) {
|
||||
let (ok, output) = run(input);
|
||||
assert_eq!(ok, false);
|
||||
|
|
10
repl_test/test_wasm.sh
Executable file
10
repl_test/test_wasm.sh
Executable 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
|
|
@ -1,3 +1,4 @@
|
|||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
@ -11,6 +12,15 @@ fn main() {
|
|||
println!("cargo:rerun-if-changed=build.rs");
|
||||
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();
|
||||
|
||||
// Build a pre-linked binary with platform, builtins and all their libc dependencies
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use futures::executor;
|
||||
|
||||
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_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize;
|
||||
fn wasmer_copy_input_string(src_buffer_addr: *mut u8);
|
||||
|
@ -10,10 +10,12 @@ extern "C" {
|
|||
|
||||
/// Async wrapper to match the equivalent JS function
|
||||
pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), String> {
|
||||
unsafe {
|
||||
wasmer_create_app(wasm_module_bytes.as_ptr(), wasm_module_bytes.len());
|
||||
}
|
||||
let ok = unsafe { wasmer_create_app(wasm_module_bytes.as_ptr(), wasm_module_bytes.len()) } != 0;
|
||||
if ok {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Compiler generated an invalid Wasm module".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn js_run_app() -> usize {
|
|
@ -4,16 +4,14 @@ mod repl;
|
|||
// Interface with external JS in the browser
|
||||
//
|
||||
#[cfg(not(feature = "wasmer"))]
|
||||
mod interface_js;
|
||||
mod externs_js;
|
||||
#[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
|
||||
//
|
||||
#[cfg(feature = "wasmer")]
|
||||
mod interface_test;
|
||||
mod externs_test;
|
||||
#[cfg(feature = "wasmer")]
|
||||
pub use interface_test::{
|
||||
entrypoint_from_test, js_create_app, js_get_result_and_memory, js_run_app,
|
||||
};
|
||||
pub use externs_test::{entrypoint_from_test, js_create_app, js_get_result_and_memory, js_run_app};
|
||||
|
|
|
@ -64,10 +64,22 @@ impl<'a> ReplAppMemory for WasmMemory<'a> {
|
|||
deref_number!(deref_f64, f64);
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str {
|
||||
let elems_addr = self.deref_usize(addr);
|
||||
let len = self.deref_usize(addr + size_of::<usize>());
|
||||
let bytes = &self.copied_bytes[elems_addr..][..len];
|
||||
std::str::from_utf8(bytes).unwrap()
|
||||
// We can't use RocStr, we need our own small/big string logic.
|
||||
// The first field is *not* a pointer. We can calculate a pointer for it, but only for big strings.
|
||||
// If changing this code, remember it also runs in wasm32, not just the app.
|
||||
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
64
repl_www/README.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
## 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
BIN
repl_www/architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 139 KiB |
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
set -euxo pipefail
|
||||
|
||||
if [[ ! -d repl_www ]]
|
||||
then
|
||||
|
@ -10,17 +10,19 @@ fi
|
|||
|
||||
if ! which wasm-pack
|
||||
then
|
||||
echo "Installing wasm-pack CLI"
|
||||
cargo install wasm-pack
|
||||
fi
|
||||
|
||||
WWW_DIR="repl_www/build"
|
||||
# output directory is first argument or default
|
||||
WWW_DIR="${1:-repl_www/build}"
|
||||
mkdir -p $WWW_DIR
|
||||
cp repl_www/public/* $WWW_DIR
|
||||
|
||||
# For debugging, pass the --profiling option, which enables optimizations + debug info
|
||||
# (We need optimizations to get rid of dead code that otherwise causes compile errors!)
|
||||
if [ -n "$REPL_DEBUG" ]
|
||||
# When debugging the REPL, use `REPL_DEBUG=1 repl_www/build.sh`
|
||||
if [ -n "${REPL_DEBUG:-}" ]
|
||||
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
|
||||
wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/
|
||||
else
|
||||
|
|
|
@ -15,6 +15,8 @@ const repl = {
|
|||
|
||||
inputQueue: [],
|
||||
inputHistory: [],
|
||||
inputHistoryIndex: 0,
|
||||
inputStash: "", // stash the user input while we're toggling through history with up/down arrows
|
||||
|
||||
textDecoder: new TextDecoder(),
|
||||
textEncoder: new TextEncoder(),
|
||||
|
@ -28,6 +30,7 @@ const repl = {
|
|||
|
||||
// Initialise
|
||||
repl.elemSourceInput.addEventListener("change", onInputChange);
|
||||
repl.elemSourceInput.addEventListener("keyup", onInputKeyup);
|
||||
roc_repl_wasm.default().then((instance) => {
|
||||
repl.compiler = instance;
|
||||
});
|
||||
|
@ -38,6 +41,8 @@ roc_repl_wasm.default().then((instance) => {
|
|||
|
||||
function onInputChange(event) {
|
||||
const inputText = event.target.value;
|
||||
if (!inputText) return;
|
||||
|
||||
event.target.value = "";
|
||||
|
||||
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
|
||||
// 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?
|
||||
async function processInputQueue() {
|
||||
while (repl.inputQueue.length) {
|
||||
const inputText = repl.inputQueue[0];
|
||||
const historyIndex = createHistoryEntry(inputText);
|
||||
repl.inputHistoryIndex = createHistoryEntry(inputText);
|
||||
repl.inputStash = "";
|
||||
|
||||
let outputText;
|
||||
let ok = true;
|
||||
|
@ -63,7 +115,7 @@ async function processInputQueue() {
|
|||
ok = false;
|
||||
}
|
||||
|
||||
updateHistoryEntry(historyIndex, ok, outputText);
|
||||
updateHistoryEntry(repl.inputHistoryIndex, ok, outputText);
|
||||
repl.inputQueue.shift();
|
||||
}
|
||||
}
|
||||
|
|
BIN
repl_www/screenshot.png
Normal file
BIN
repl_www/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../compiler/collections" }
|
||||
roc_exhaustive = { path = "../compiler/exhaustive" }
|
||||
roc_region = { path = "../compiler/region" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
|
|
|
@ -29,8 +29,10 @@ const NESTED_DATATYPE: &str = "NESTED DATATYPE";
|
|||
const CONFLICTING_NUMBER_SUFFIX: &str = "CONFLICTING NUMBER SUFFIX";
|
||||
const NUMBER_OVERFLOWS_SUFFIX: &str = "NUMBER OVERFLOWS SUFFIX";
|
||||
const NUMBER_UNDERFLOWS_SUFFIX: &str = "NUMBER UNDERFLOWS SUFFIX";
|
||||
const OPAQUE_NOT_DEFINED: &str = "OPAQUE NOT DEFINED";
|
||||
const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE DECLARED OUTSIDE SCOPE";
|
||||
const OPAQUE_NOT_DEFINED: &str = "OPAQUE TYPE NOT DEFINED";
|
||||
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>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
|
@ -1447,6 +1449,24 @@ fn pretty_runtime_error<'b>(
|
|||
|
||||
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)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
|
||||
use roc_module::ident::TagName;
|
||||
use roc_region::all::LineInfo;
|
||||
use std::path::PathBuf;
|
||||
use ven_pretty::DocAllocator;
|
||||
|
@ -9,8 +10,8 @@ pub fn mono_problem<'b>(
|
|||
filename: PathBuf,
|
||||
problem: roc_mono::ir::MonoProblem,
|
||||
) -> Report<'b> {
|
||||
use roc_mono::exhaustive::Context::*;
|
||||
use roc_mono::exhaustive::Error::*;
|
||||
use roc_exhaustive::Context::*;
|
||||
use roc_exhaustive::Error::*;
|
||||
use roc_mono::ir::MonoProblem::*;
|
||||
|
||||
match problem {
|
||||
|
@ -120,7 +121,7 @@ pub fn mono_problem<'b>(
|
|||
|
||||
pub fn unhandled_patterns_to_doc_block<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
patterns: Vec<roc_mono::exhaustive::Pattern>,
|
||||
patterns: Vec<roc_exhaustive::Pattern>,
|
||||
) -> RocDocBuilder<'b> {
|
||||
alloc
|
||||
.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>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
pattern: roc_mono::exhaustive::Pattern,
|
||||
pattern: roc_exhaustive::Pattern,
|
||||
) -> RocDocBuilder<'b> {
|
||||
pattern_to_doc_help(alloc, pattern, false)
|
||||
}
|
||||
|
||||
fn pattern_to_doc_help<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
pattern: roc_mono::exhaustive::Pattern,
|
||||
pattern: roc_exhaustive::Pattern,
|
||||
in_type_param: bool,
|
||||
) -> RocDocBuilder<'b> {
|
||||
use roc_mono::exhaustive::Literal::*;
|
||||
use roc_mono::exhaustive::Pattern::*;
|
||||
use roc_mono::exhaustive::RenderAs;
|
||||
use roc_exhaustive::Literal::*;
|
||||
use roc_exhaustive::Pattern::*;
|
||||
use roc_exhaustive::RenderAs;
|
||||
|
||||
match pattern {
|
||||
Anything => alloc.text("_"),
|
||||
|
@ -184,19 +185,26 @@ fn pattern_to_doc_help<'b>(
|
|||
.append(alloc.intersperse(arg_docs, alloc.reflow(", ")))
|
||||
.append(" }")
|
||||
}
|
||||
RenderAs::Tag => {
|
||||
RenderAs::Tag | RenderAs::Opaque => {
|
||||
let has_args = !args.is_empty();
|
||||
let arg_docs = args
|
||||
.into_iter()
|
||||
.map(|v| pattern_to_doc_help(alloc, v, true));
|
||||
|
||||
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
|
||||
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 {
|
||||
alloc
|
||||
|
|
|
@ -6,7 +6,9 @@ use roc_module::symbol::Symbol;
|
|||
use roc_region::all::{LineInfo, Loc, Region};
|
||||
use roc_solve::solve;
|
||||
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 crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
|
||||
|
@ -700,7 +702,9 @@ fn to_expr_report<'b>(
|
|||
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) => {
|
||||
let expected_set: MutSet<_> = expected_fields.keys().cloned().collect();
|
||||
|
@ -986,7 +990,7 @@ fn count_arguments(tipe: &ErrorType) -> usize {
|
|||
|
||||
match tipe {
|
||||
Function(args, _, _) => args.len(),
|
||||
Alias(_, _, actual) => count_arguments(actual),
|
||||
Alias(_, _, actual, _) => count_arguments(actual),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
@ -1139,6 +1143,23 @@ fn format_category<'b>(
|
|||
alloc.concat(vec![this_is, alloc.text(" the closure size of a function")]),
|
||||
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 {
|
||||
tag_name: TagName::Global(name),
|
||||
args_count: 0,
|
||||
|
@ -1465,8 +1486,13 @@ fn add_pattern_category<'b>(
|
|||
Set => alloc.reflow(" sets of type:"),
|
||||
Map => alloc.reflow(" maps of type:"),
|
||||
Ctor(tag_name) => alloc.concat(vec![
|
||||
alloc.reflow(" a "),
|
||||
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:"),
|
||||
Num => alloc.reflow(" numbers:"),
|
||||
|
@ -1520,6 +1546,7 @@ pub enum Problem {
|
|||
TagsMissing(Vec<TagName>),
|
||||
BadRigidVar(Lowercase, ErrorType),
|
||||
OptionalRequiredMismatch(Lowercase),
|
||||
OpaqueComparedToNonOpaque,
|
||||
}
|
||||
|
||||
fn problems_to_tip<'b>(
|
||||
|
@ -1700,7 +1727,7 @@ pub fn to_doc<'b>(
|
|||
.collect(),
|
||||
),
|
||||
|
||||
Alias(symbol, args, _) => report_text::apply(
|
||||
Alias(symbol, args, _, _) => report_text::apply(
|
||||
alloc,
|
||||
parens,
|
||||
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 left = report_text::apply(
|
||||
alloc,
|
||||
|
@ -1902,12 +1929,27 @@ fn to_diff<'b>(
|
|||
}
|
||||
}
|
||||
|
||||
(Alias(symbol, _, actual), other) if !symbol.module_id().is_builtin() => {
|
||||
// when diffing an alias with a non-alias, de-alias
|
||||
(Alias(_, _, _, AliasKind::Opaque), _) | (_, Alias(_, _, _, AliasKind::Opaque)) => {
|
||||
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)
|
||||
}
|
||||
(other, Alias(symbol, _, actual)) if !symbol.module_id().is_builtin() => {
|
||||
// when diffing an alias with a non-alias, de-alias
|
||||
(other, Alias(symbol, _, actual, AliasKind::Structural))
|
||||
if !symbol.module_id().is_builtin() =>
|
||||
{
|
||||
// when diffing a structural alias with a non-alias, de-alias
|
||||
to_diff(alloc, parens, other, *actual)
|
||||
}
|
||||
|
||||
|
@ -1938,41 +1980,41 @@ fn to_diff<'b>(
|
|||
|
||||
let is_int = |t: &ErrorType| match t {
|
||||
ErrorType::Type(Symbol::NUM_INT, _) => true,
|
||||
ErrorType::Alias(Symbol::NUM_INT, _, _) => true,
|
||||
ErrorType::Alias(Symbol::NUM_INT, _, _, _) => true,
|
||||
|
||||
ErrorType::Type(Symbol::NUM_NUM, args) => {
|
||||
matches!(
|
||||
&args.get(0),
|
||||
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!(
|
||||
&args.get(0),
|
||||
Some(ErrorType::Type(Symbol::NUM_INTEGER, _))
|
||||
| Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _))
|
||||
| Some(ErrorType::Alias(Symbol::NUM_INTEGER, _, _, _))
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
let is_float = |t: &ErrorType| match t {
|
||||
ErrorType::Type(Symbol::NUM_FLOAT, _) => true,
|
||||
ErrorType::Alias(Symbol::NUM_FLOAT, _, _) => true,
|
||||
ErrorType::Alias(Symbol::NUM_FLOAT, _, _, _) => true,
|
||||
|
||||
ErrorType::Type(Symbol::NUM_NUM, args) => {
|
||||
matches!(
|
||||
&args.get(0),
|
||||
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!(
|
||||
&args.get(0),
|
||||
Some(ErrorType::Type(Symbol::NUM_FLOATINGPOINT, _))
|
||||
| Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _))
|
||||
| Some(ErrorType::Alias(Symbol::NUM_FLOATINGPOINT, _, _, _))
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
|
@ -2829,7 +2871,7 @@ fn type_problem_to_pretty<'b>(
|
|||
TagUnion(_, _) | RecursiveTagUnion(_, _, _) => {
|
||||
bad_rigid_var(x, alloc.reflow("a tag value"))
|
||||
}
|
||||
Alias(symbol, _, _) | Type(symbol, _) => bad_rigid_var(
|
||||
Alias(symbol, _, _, _) | Type(symbol, _) => bad_rigid_var(
|
||||
x,
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("a "),
|
||||
|
@ -2902,5 +2944,17 @@ fn type_problem_to_pretty<'b>(
|
|||
),
|
||||
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("."),
|
||||
])),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,6 +136,7 @@ pub struct Palette<'a> {
|
|||
pub type_variable: &'a str,
|
||||
pub structure: &'a str,
|
||||
pub alias: &'a str,
|
||||
pub opaque: &'a str,
|
||||
pub error: &'a str,
|
||||
pub line_number: &'a str,
|
||||
pub header: &'a str,
|
||||
|
@ -155,6 +156,7 @@ pub const DEFAULT_PALETTE: Palette = Palette {
|
|||
type_variable: YELLOW_CODE,
|
||||
structure: GREEN_CODE,
|
||||
alias: YELLOW_CODE,
|
||||
opaque: YELLOW_CODE,
|
||||
error: RED_CODE,
|
||||
line_number: CYAN_CODE,
|
||||
header: CYAN_CODE,
|
||||
|
@ -326,6 +328,29 @@ impl<'a> RocDocAllocator<'a> {
|
|||
.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> {
|
||||
self.text(format!(".{}", lowercase))
|
||||
.annotate(Annotation::RecordField)
|
||||
|
@ -667,7 +692,7 @@ impl<'a> RocDocAllocator<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Annotation {
|
||||
Emphasized,
|
||||
Url,
|
||||
|
@ -677,6 +702,7 @@ pub enum Annotation {
|
|||
RecordField,
|
||||
TypeVariable,
|
||||
Alias,
|
||||
Opaque,
|
||||
Structure,
|
||||
Symbol,
|
||||
BinOp,
|
||||
|
@ -849,6 +875,9 @@ where
|
|||
Alias => {
|
||||
self.write_str(self.palette.alias)?;
|
||||
}
|
||||
Opaque => {
|
||||
self.write_str(self.palette.alias)?;
|
||||
}
|
||||
BinOp => {
|
||||
self.write_str(self.palette.alias)?;
|
||||
}
|
||||
|
@ -903,7 +932,7 @@ where
|
|||
self.write_str(RESET_CODE)?;
|
||||
}
|
||||
|
||||
TypeBlock | GlobalTag | PrivateTag | RecordField => { /* nothing yet */ }
|
||||
TypeBlock | GlobalTag | PrivateTag | Opaque | RecordField => { /* nothing yet */ }
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -31,10 +31,7 @@ pub fn infer_expr(
|
|||
constraint: &Constraint,
|
||||
expr_var: Variable,
|
||||
) -> (Content, Subs) {
|
||||
let env = solve::Env {
|
||||
aliases: MutMap::default(),
|
||||
vars_by_symbol: MutMap::default(),
|
||||
};
|
||||
let env = solve::Env::default();
|
||||
let (solved, _) = solve::run(&env, problems, subs, constraint);
|
||||
|
||||
let content = solved
|
||||
|
|
|
@ -64,7 +64,7 @@ mod test_reporting {
|
|||
var,
|
||||
constraint,
|
||||
home,
|
||||
mut interns,
|
||||
interns,
|
||||
problems: can_problems,
|
||||
..
|
||||
} = can_expr(arena, expr_src)?;
|
||||
|
@ -92,7 +92,7 @@ mod test_reporting {
|
|||
|
||||
// Compile and add all the Procs before adding main
|
||||
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();
|
||||
|
||||
// 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]
|
||||
fn record_type_keyword_field_name() {
|
||||
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]
|
||||
fn number_double_dot() {
|
||||
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]
|
||||
fn outdented_in_parens() {
|
||||
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]
|
||||
fn backpassing_type_error() {
|
||||
report_problem_as(
|
||||
|
@ -8147,7 +8029,7 @@ I need all branches in an `if` to have the same type!
|
|||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── OPAQUE NOT DEFINED ──────────────────────────────────────────────────────────
|
||||
── OPAQUE TYPE 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!(
|
||||
r#"
|
||||
── OPAQUE NOT DEFINED ──────────────────────────────────────────────────────────
|
||||
── OPAQUE TYPE 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
|
||||
"#
|
||||
),
|
||||
// TODO: get rid of the second error. Consider parsing OtherModule.$Age to completion
|
||||
// and checking it during can.
|
||||
// TODO: get rid of the first error. Consider parsing OtherModule.$Age to completion
|
||||
// and checking it during can. The reason the error appears is because it is parsed as
|
||||
// Apply(Error(OtherModule), [ $Age, 21 ])
|
||||
indoc!(
|
||||
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 ──────────────────────────────────────────────────────────────
|
||||
|
||||
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
|
||||
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
|
||||
"#
|
||||
),
|
||||
// TODO: there is a potential for a better error message here, if the usage of `$Age` can
|
||||
// be linked to the declaration of `Age` inside `age`, and a suggestion to raise that
|
||||
// declaration to the outer scope.
|
||||
// TODO(opaques): there is a potential for a better error message here, if the usage of
|
||||
// `$Age` can be linked to the declaration of `Age` inside `age`, and a suggestion to
|
||||
// raise that declaration to the outer scope.
|
||||
indoc!(
|
||||
r#"
|
||||
── 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
|
||||
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:
|
||||
|
||||
|
@ -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!
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
||||
```elm
|
||||
user =
|
||||
{
|
||||
user = {
|
||||
firstName: "Sam",
|
||||
lastName: "Sample",
|
||||
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:
|
||||
|
||||
```elm
|
||||
Http.Err a :
|
||||
[
|
||||
Http.Err a : [
|
||||
PageNotFound,
|
||||
Timeout,
|
||||
BadPayload Str,
|
||||
]a
|
||||
|
||||
File.ReadErr a :
|
||||
[
|
||||
File.ReadErr a : [
|
||||
FileNotFound,
|
||||
Corrupted,
|
||||
BadFormat,
|
||||
]a
|
||||
|
||||
File.WriteErr a :
|
||||
[
|
||||
File.WriteErr a : [
|
||||
FileNotFound,
|
||||
DiskFull,
|
||||
]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
|
||||
`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
|
||||
|
||||
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 *
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
`elm/core` has these modules:
|
||||
|
|
|
@ -11,6 +11,12 @@ cd $SCRIPT_RELATIVE_DIR
|
|||
rm -rf 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.
|
||||
pushd build
|
||||
wget https://github.com/rtfeldman/elm-css/files/8037422/roc-source-code.zip
|
||||
|
|
16
www/netlify.sh
Normal file
16
www/netlify.sh
Normal 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
|
|
@ -5,7 +5,7 @@
|
|||
# https://docs.netlify.com/routing/headers/#syntax-for-the-netlify-configuration-file
|
||||
[build]
|
||||
publish = "build/"
|
||||
command = "bash build.sh"
|
||||
command = "bash netlify.sh"
|
||||
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue