Merge branch 'trunk' into builtins-list-intersperse

This commit is contained in:
satotake 2021-11-18 11:16:27 +00:00 committed by GitHub
commit ce8a88416d
319 changed files with 4413 additions and 3654 deletions

View file

@ -52,3 +52,5 @@ Ayaz Hafiz <ayaz.hafiz.1@gmail.com>
Johannes Maas <github@j-maas.de> Johannes Maas <github@j-maas.de>
Takeshi Sato <doublequotation@gmail.com> Takeshi Sato <doublequotation@gmail.com>
Joost Baas <joost@joostbaas.eu> Joost Baas <joost@joostbaas.eu>
Callum Dunster <cdunster@users.noreply.github.com>
Martin Stewart <MartinSStewart@gmail.com>

2
Cargo.lock generated
View file

@ -3476,7 +3476,9 @@ dependencies = [
name = "roc_parse" name = "roc_parse"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ansi_term",
"bumpalo", "bumpalo",
"diff",
"encode_unicode", "encode_unicode",
"indoc", "indoc",
"pretty_assertions", "pretty_assertions",

View file

@ -12,7 +12,6 @@ members = [
"compiler/constrain", "compiler/constrain",
"compiler/unify", "compiler/unify",
"compiler/solve", "compiler/solve",
"compiler/reporting",
"compiler/fmt", "compiler/fmt",
"compiler/mono", "compiler/mono",
"compiler/test_mono", "compiler/test_mono",
@ -31,6 +30,7 @@ members = [
"ast", "ast",
"cli", "cli",
"code_markup", "code_markup",
"reporting",
"roc_std", "roc_std",
"utils", "utils",
"docs", "docs",

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli cli_utils compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./ COPY --dir cli cli_utils compiler docs editor ast code_markup utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt

View file

@ -20,6 +20,7 @@ use crate::{
expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, expr2::{ClosureExtra, Expr2, ExprId, WhenBranch},
record_field::RecordField, record_field::RecordField,
}, },
fun_def::FunctionDef,
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
types::{Type2, TypeId}, types::{Type2, TypeId},
val_def::ValueDef, val_def::ValueDef,
@ -818,6 +819,126 @@ pub fn constrain_expr<'a>(
} }
} }
} }
// In an expression like
// id = \x -> x
//
// id 1
// The `def_id` refers to the definition `id = \x -> x`,
// and the body refers to `id 1`.
Expr2::LetFunction {
def_id,
body_id,
body_var: _,
} => {
let body = env.pool.get(*body_id);
let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region);
let function_def = env.pool.get(*def_id);
let (name, arguments, body_id, rigid_vars, args_constrs) = match function_def {
FunctionDef::WithAnnotation {
name,
arguments,
body_id,
rigids,
return_type: _,
} => {
// The annotation gives us arguments with proper Type2s, but the constraints we
// generate below args bound to type variables. Create fresh ones and bind them
// to the types we already know.
let mut args_constrs = BumpVec::with_capacity_in(arguments.len(), arena);
let args_vars = PoolVec::with_capacity(arguments.len() as u32, env.pool);
for (arg_ty_node_id, arg_var_node_id) in
arguments.iter_node_ids().zip(args_vars.iter_node_ids())
{
let (ty, pattern) = env.pool.get(arg_ty_node_id);
let arg_var = env.var_store.fresh();
let ty = env.pool.get(*ty);
args_constrs.push(Eq(
Type2::Variable(arg_var),
Expected::NoExpectation(ty.shallow_clone()),
Category::Storage(std::file!(), std::line!()),
// TODO: should be the actual region of the argument
region,
));
env.pool[arg_var_node_id] = (arg_var, *pattern);
}
let rigids = env.pool.get(*rigids);
let rigid_vars: BumpVec<Variable> =
BumpVec::from_iter_in(rigids.names.iter(env.pool).map(|&(_, v)| v), arena);
(name, args_vars, body_id, rigid_vars, args_constrs)
}
FunctionDef::NoAnnotation {
name,
arguments,
body_id,
return_var: _,
} => {
(
name,
arguments.shallow_clone(),
body_id,
BumpVec::new_in(arena), // The function is unannotated, so there are no rigid type vars
BumpVec::new_in(arena), // No extra constraints to generate for arguments
)
}
};
// A function definition is equivalent to a named value definition, where the
// value is a closure. So, we create a closure definition in correspondence
// with the function definition, generate type constraints for it, and demand
// that type of the function is just the type of the resolved closure.
let fn_var = env.var_store.fresh();
let fn_ty = Type2::Variable(fn_var);
let extra = ClosureExtra {
return_type: env.var_store.fresh(),
captured_symbols: PoolVec::empty(env.pool),
closure_type: env.var_store.fresh(),
closure_ext_var: env.var_store.fresh(),
};
let clos = Expr2::Closure {
args: arguments.shallow_clone(),
uniq_symbol: *name,
body_id: *body_id,
function_type: env.var_store.fresh(),
extra: env.pool.add(extra),
recursive: roc_can::expr::Recursive::Recursive,
};
let clos_con = constrain_expr(
arena,
env,
&clos,
Expected::NoExpectation(fn_ty.shallow_clone()),
region,
);
// This is the `foo` part in `foo = \...`. We want to bind the name of the
// function with its type, whose constraints we generated above.
let mut def_pattern_state = PatternState2 {
headers: BumpMap::new_in(arena),
vars: BumpVec::new_in(arena),
constraints: args_constrs,
};
def_pattern_state.headers.insert(*name, fn_ty);
def_pattern_state.vars.push(fn_var);
Let(arena.alloc(LetConstraint {
rigid_vars,
flex_vars: def_pattern_state.vars,
def_types: def_pattern_state.headers, // Binding function name -> its type
defs_constraint: Let(arena.alloc(LetConstraint {
rigid_vars: BumpVec::new_in(arena), // always empty
flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments
def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments
defs_constraint: And(def_pattern_state.constraints),
ret_constraint: clos_con,
})),
ret_constraint: body_con,
}))
}
Expr2::Update { Expr2::Update {
symbol, symbol,
updates, updates,
@ -1031,7 +1152,6 @@ pub fn constrain_expr<'a>(
exists(arena, vars, And(and_constraints)) exists(arena, vars, And(and_constraints))
} }
Expr2::LetRec { .. } => todo!(), Expr2::LetRec { .. } => todo!(),
Expr2::LetFunction { .. } => todo!(),
} }
} }
@ -1765,7 +1885,7 @@ pub mod test_constrain {
use roc_parse::parser::SyntaxError; use roc_parse::parser::SyntaxError;
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::{ use roc_types::{
pretty_print::content_to_string, pretty_print::{content_to_string, name_all_type_vars},
solved_types::Solved, solved_types::Solved,
subs::{Subs, VarStore, Variable}, subs::{Subs, VarStore, Variable},
}; };
@ -1845,7 +1965,7 @@ pub mod test_constrain {
let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region);
match expr2_result { match expr2_result {
Ok((expr, _)) => { Ok((expr, output)) => {
let constraint = constrain_expr( let constraint = constrain_expr(
&code_arena, &code_arena,
&mut env, &mut env,
@ -1865,17 +1985,22 @@ pub mod test_constrain {
let mut var_store = VarStore::default(); let mut var_store = VarStore::default();
std::mem::swap(ref_var_store, &mut var_store); std::mem::swap(ref_var_store, &mut var_store);
let rigids = output.introduced_variables.name_by_var;
let (mut solved, _, _) = run_solve( let (mut solved, _, _) = run_solve(
&code_arena, &code_arena,
pool, pool,
Default::default(), Default::default(),
Default::default(), rigids,
constraint, constraint,
var_store, var_store,
); );
let subs = solved.inner_mut(); let subs = solved.inner_mut();
// name type vars
name_all_type_vars(var, subs);
let content = subs.get_content_without_compacting(var); let content = subs.get_content_without_compacting(var);
// Connect the ModuleId to it's IdentIds // Connect the ModuleId to it's IdentIds
@ -2132,6 +2257,102 @@ pub mod test_constrain {
) )
} }
#[test]
fn dual_arity_lambda() {
infer_eq(
indoc!(
r#"
\a, b -> Pair a b
"#
),
"a, b -> [ Pair a b ]*",
);
}
#[test]
fn anonymous_identity() {
infer_eq(
indoc!(
r#"
(\a -> a) 3.14
"#
),
"Float *",
);
}
#[test]
fn identity_of_identity() {
infer_eq(
indoc!(
r#"
(\val -> val) (\val -> val)
"#
),
"a -> a",
);
}
#[test]
fn identity_function() {
infer_eq(
indoc!(
r#"
\val -> val
"#
),
"a -> a",
);
}
#[test]
fn apply_function() {
infer_eq(
indoc!(
r#"
\f, x -> f x
"#
),
"(a -> b), a -> b",
);
}
#[test]
fn flip_function() {
infer_eq(
indoc!(
r#"
\f -> (\a, b -> f b a)
"#
),
"(a, b -> c) -> (b, a -> c)",
);
}
#[test]
fn always_function() {
infer_eq(
indoc!(
r#"
\val -> \_ -> val
"#
),
"a -> (* -> a)",
);
}
#[test]
fn pass_a_function() {
infer_eq(
indoc!(
r#"
\f -> f {}
"#
),
"({} -> a) -> a",
);
}
#[test] #[test]
fn constrain_closure() { fn constrain_closure() {
infer_eq( infer_eq(
@ -2145,4 +2366,130 @@ pub mod test_constrain {
"{}* -> Num *", "{}* -> Num *",
) )
} }
#[test]
fn recursive_identity() {
infer_eq(
indoc!(
r#"
identity = \val -> val
identity
"#
),
"a -> a",
);
}
#[test]
fn use_apply() {
infer_eq(
indoc!(
r#"
identity = \a -> a
apply = \f, x -> f x
apply identity 5
"#
),
"Num *",
);
}
#[test]
fn nested_let_function() {
infer_eq(
indoc!(
r#"
curryPair = \a ->
getB = \b -> Pair a b
getB
curryPair
"#
),
"a -> (b -> [ Pair a b ]*)",
);
}
#[test]
fn record_with_bound_var() {
infer_eq(
indoc!(
r#"
fn = \rec ->
x = rec.x
rec
fn
"#
),
"{ x : a }b -> { x : a }b",
);
}
#[test]
fn using_type_signature() {
infer_eq(
indoc!(
r#"
bar : custom -> custom
bar = \x -> x
bar
"#
),
"custom -> custom",
);
}
#[ignore = "Currently panics at 'Invalid Cycle', ast/src/lang/core/def/def.rs:1212:21"]
#[test]
fn using_type_signature2() {
infer_eq(
indoc!(
r#"
id1 : tya -> tya
id1 = \x -> x
id2 : tyb -> tyb
id2 = id1
id2
"#
),
"tyb -> tyb",
);
}
#[ignore = "Implement annotation-only decls"]
#[test]
fn type_signature_without_body() {
infer_eq(
indoc!(
r#"
foo: Str -> {}
foo "hi"
"#
),
"{}",
);
}
#[ignore = "Implement annotation-only decls"]
#[test]
fn type_signature_without_body_rigid() {
infer_eq(
indoc!(
r#"
foo : Num * -> custom
foo 2
"#
),
"custom",
);
}
} }

View file

@ -37,7 +37,11 @@ use crate::{
rigids::Rigids, rigids::Rigids,
scope::Scope, scope::Scope,
}, },
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, mem_pool::{
pool::{NodeId, Pool},
pool_vec::PoolVec,
shallow_clone::ShallowClone,
},
}; };
#[derive(Debug)] #[derive(Debug)]
@ -316,7 +320,7 @@ fn from_pending_alias<'a>(
} }
for loc_lowercase in vars { for loc_lowercase in vars {
if !named_rigids.contains_key(loc_lowercase.value.as_str()) { if !named_rigids.contains_key(&loc_lowercase.value) {
env.problem(Problem::PhantomTypeArgument { env.problem(Problem::PhantomTypeArgument {
alias: symbol, alias: symbol,
variable_region: loc_lowercase.region, variable_region: loc_lowercase.region,
@ -454,6 +458,10 @@ fn canonicalize_pending_def<'a>(
output.references.referenced_aliases.insert(symbol); output.references.referenced_aliases.insert(symbol);
} }
// Ensure rigid type vars and their names are known in the output.
for (name, &var) in named_rigids.iter() {
output.introduced_variables.insert_named(name.clone(), var);
}
let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool);
// bookkeeping for tail-call detection. If we're assigning to an // bookkeeping for tail-call detection. If we're assigning to an
@ -521,7 +529,7 @@ fn canonicalize_pending_def<'a>(
// parent commit for the bug this fixed! // parent commit for the bug this fixed!
let refs = References::new(); let refs = References::new();
let arguments: PoolVec<(PatternId, Type2)> = let arguments: PoolVec<(NodeId<Type2>, PatternId)> =
PoolVec::with_capacity(closure_args.len() as u32, env.pool); PoolVec::with_capacity(closure_args.len() as u32, env.pool);
let return_type: TypeId; let return_type: TypeId;
@ -558,7 +566,8 @@ fn canonicalize_pending_def<'a>(
for (node_id, ((_, pattern_id), typ)) in for (node_id, ((_, pattern_id), typ)) in
arguments.iter_node_ids().zip(it.into_iter()) arguments.iter_node_ids().zip(it.into_iter())
{ {
env.pool[node_id] = (pattern_id, typ); let typ = env.pool.add(typ);
env.pool[node_id] = (typ, pattern_id);
} }
return_type = return_type_id; return_type = return_type_id;
@ -689,14 +698,14 @@ fn canonicalize_pending_def<'a>(
// parent commit for the bug this fixed! // parent commit for the bug this fixed!
let refs = References::new(); let refs = References::new();
let arguments: PoolVec<(PatternId, Variable)> = let arguments: PoolVec<(Variable, PatternId)> =
PoolVec::with_capacity(closure_args.len() as u32, env.pool); PoolVec::with_capacity(closure_args.len() as u32, env.pool);
let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect(); let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect();
for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter()) for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter())
{ {
env.pool[node_id] = (pattern_id, env.var_store.fresh()); env.pool[node_id] = (env.var_store.fresh(), pattern_id);
} }
let function_def = FunctionDef::NoAnnotation { let function_def = FunctionDef::NoAnnotation {

View file

@ -14,15 +14,15 @@ use super::{
#[derive(Debug)] #[derive(Debug)]
pub enum FunctionDef { pub enum FunctionDef {
WithAnnotation { WithAnnotation {
name: Symbol, // 8B name: Symbol, // 8B
arguments: PoolVec<(PatternId, Type2)>, // 8B arguments: PoolVec<(NodeId<Type2>, PatternId)>, // 8B
rigids: NodeId<Rigids>, // 4B rigids: NodeId<Rigids>, // 4B
return_type: TypeId, // 4B return_type: TypeId, // 4B
body_id: ExprId, // 4B body_id: ExprId, // 4B
}, },
NoAnnotation { NoAnnotation {
name: Symbol, // 8B name: Symbol, // 8B
arguments: PoolVec<(PatternId, Variable)>, // 8B arguments: PoolVec<(Variable, PatternId)>, // 8B
return_var: Variable, // 4B return_var: Variable, // 4B
body_id: ExprId, // 4B body_id: ExprId, // 4B
}, },

View file

@ -2,7 +2,7 @@ pub mod ast;
mod declaration; mod declaration;
pub mod def; pub mod def;
pub mod expr; pub mod expr;
mod fun_def; pub mod fun_def;
pub mod header; pub mod header;
pub mod pattern; pub mod pattern;
pub mod str; pub mod str;

View file

@ -3,7 +3,7 @@
#![allow(unused_imports)] #![allow(unused_imports)]
// use roc_can::expr::Output; // use roc_can::expr::Output;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::types::{Problem, RecordField}; use roc_types::types::{Problem, RecordField};
@ -20,29 +20,34 @@ pub type TypeId = NodeId<Type2>;
#[derive(Debug)] #[derive(Debug)]
pub enum Type2 { pub enum Type2 {
Variable(Variable), Variable(Variable), // 4B
Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 8B + 4B + pad
// 32B // 24B
HostExposedAlias { HostExposedAlias {
name: Symbol, // 8B name: Symbol, // 8B
arguments: PoolVec<(PoolStr, TypeId)>, // 12B arguments: PoolVec<(PoolStr, TypeId)>, // 8B
actual_var: Variable, // 4B actual_var: Variable, // 4B
actual: TypeId, // 4B actual: TypeId, // 4B
}, },
EmptyTagUnion, EmptyTagUnion,
TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 12B + 4B TagUnion(PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 12B = 8B + 4B
RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 20B = 4B + 12B + 4B RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec<Type2>)>, TypeId), // 16B = 4B + 8B + 4B
EmptyRec, EmptyRec,
Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 16B = 12B + 4B Record(PoolVec<(PoolStr, RecordField<TypeId>)>, TypeId), // 12B = 8B + 4B
Function(PoolVec<Type2>, TypeId, TypeId), // 20B = 12B + 4B + 4B Function(PoolVec<Type2>, TypeId, TypeId), // 16B = 8B + 4B + 4B
Apply(Symbol, PoolVec<Type2>), // 20B = 8B + 12B Apply(Symbol, PoolVec<Type2>), // 16B = 8B + 8B
Erroneous(Problem2), Erroneous(Problem2), // 24B
}
#[test]
fn type2_size() {
assert_eq!(std::mem::size_of::<Type2>(), 32); // 24B + pad
} }
#[derive(Debug)] #[derive(Debug)]
@ -171,9 +176,9 @@ pub enum Signature {
}, },
} }
pub enum Annotation2<'a> { pub enum Annotation2 {
Annotation { Annotation {
named_rigids: MutMap<&'a str, Variable>, named_rigids: MutMap<Lowercase, Variable>,
unnamed_rigids: MutSet<Variable>, unnamed_rigids: MutSet<Variable>,
symbols: MutSet<Symbol>, symbols: MutSet<Symbol>,
signature: Signature, signature: Signature,
@ -186,7 +191,7 @@ pub fn to_annotation2<'a>(
scope: &mut Scope, scope: &mut Scope,
annotation: &'a roc_parse::ast::TypeAnnotation<'a>, annotation: &'a roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
) -> Annotation2<'a> { ) -> Annotation2 {
let mut references = References::default(); let mut references = References::default();
let annotation = to_type2(env, scope, &mut references, annotation, region); let annotation = to_type2(env, scope, &mut references, annotation, region);
@ -240,11 +245,7 @@ pub fn to_annotation2<'a>(
} }
} }
fn shallow_dealias<'a>( fn shallow_dealias<'a>(env: &mut Env, references: References, annotation: Type2) -> Annotation2 {
env: &mut Env,
references: References<'a>,
annotation: Type2,
) -> Annotation2<'a> {
let References { let References {
named, named,
unnamed, unnamed,
@ -288,8 +289,8 @@ fn shallow_dealias<'a>(
} }
#[derive(Default)] #[derive(Default)]
pub struct References<'a> { pub struct References {
named: MutMap<&'a str, Variable>, named: MutMap<Lowercase, Variable>,
unnamed: MutSet<Variable>, unnamed: MutSet<Variable>,
hidden: MutSet<Variable>, hidden: MutSet<Variable>,
symbols: MutSet<Symbol>, symbols: MutSet<Symbol>,
@ -298,7 +299,7 @@ pub struct References<'a> {
pub fn to_type_id<'a>( pub fn to_type_id<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
annotation: &roc_parse::ast::TypeAnnotation<'a>, annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
) -> TypeId { ) -> TypeId {
@ -310,7 +311,7 @@ pub fn to_type_id<'a>(
pub fn as_type_id<'a>( pub fn as_type_id<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
type_id: TypeId, type_id: TypeId,
annotation: &roc_parse::ast::TypeAnnotation<'a>, annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
@ -324,7 +325,7 @@ pub fn as_type_id<'a>(
pub fn to_type2<'a>( pub fn to_type2<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
references: &mut References<'a>, references: &mut References,
annotation: &roc_parse::ast::TypeAnnotation<'a>, annotation: &roc_parse::ast::TypeAnnotation<'a>,
region: Region, region: Region,
) -> Type2 { ) -> Type2 {
@ -375,8 +376,9 @@ pub fn to_type2<'a>(
Type2::Function(arguments, closure_type_id, return_type_id) Type2::Function(arguments, closure_type_id, return_type_id)
} }
BoundVariable(v) => { BoundVariable(v) => {
// a rigid type variable // A rigid type variable. The parser should have already ensured that the name is indeed a lowercase.
match references.named.get(v) { let v = Lowercase::from(*v);
match references.named.get(&v) {
Some(var) => Type2::Variable(*var), Some(var) => Type2::Variable(*var),
None => { None => {
let var = env.var_store.fresh(); let var = env.var_store.fresh();
@ -387,6 +389,9 @@ pub fn to_type2<'a>(
} }
} }
} }
Inferred => {
unimplemented!();
}
Wildcard | Malformed(_) => { Wildcard | Malformed(_) => {
let var = env.var_store.fresh(); let var = env.var_store.fresh();
@ -401,7 +406,7 @@ pub fn to_type2<'a>(
let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool); let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool);
for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) { for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) {
let poolstr = PoolStr::new(label, env.pool); let poolstr = PoolStr::new(label.as_str(), env.pool);
let rec_field = match field { let rec_field = match field {
RecordField::Optional(_) => { RecordField::Optional(_) => {
@ -480,10 +485,10 @@ pub fn to_type2<'a>(
{ {
match loc_var.value { match loc_var.value {
BoundVariable(ident) => { BoundVariable(ident) => {
let var_name = ident; let var_name = Lowercase::from(ident);
if let Some(var) = references.named.get(&var_name) { if let Some(var) = references.named.get(&var_name) {
let poolstr = PoolStr::new(var_name, env.pool); let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(*var)); let type_id = env.pool.add(Type2::Variable(*var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id); env.pool[var_id] = (poolstr.shallow_clone(), type_id);
@ -494,7 +499,7 @@ pub fn to_type2<'a>(
let var = env.var_store.fresh(); let var = env.var_store.fresh();
references.named.insert(var_name.clone(), var); references.named.insert(var_name.clone(), var);
let poolstr = PoolStr::new(var_name, env.pool); let poolstr = PoolStr::new(var_name.as_str(), env.pool);
let type_id = env.pool.add(Type2::Variable(var)); let type_id = env.pool.add(Type2::Variable(var));
env.pool[var_id] = (poolstr.shallow_clone(), type_id); env.pool[var_id] = (poolstr.shallow_clone(), type_id);
@ -576,10 +581,10 @@ pub fn to_type2<'a>(
fn can_assigned_fields<'a>( fn can_assigned_fields<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
fields: &&[Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>], fields: &&[Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::TypeAnnotation<'a>>>],
region: Region, region: Region,
) -> MutMap<&'a str, RecordField<Type2>> { ) -> MutMap<Lowercase, RecordField<Type2>> {
use roc_parse::ast::AssignedField::*; use roc_parse::ast::AssignedField::*;
use roc_types::types::RecordField::*; use roc_types::types::RecordField::*;
@ -602,8 +607,8 @@ fn can_assigned_fields<'a>(
let field_type = let field_type =
to_type2(env, scope, rigids, &annotation.value, annotation.region); to_type2(env, scope, rigids, &annotation.value, annotation.region);
let label = field_name.value; let label = Lowercase::from(field_name.value);
field_types.insert(label, Required(field_type)); field_types.insert(label.clone(), Required(field_type));
break 'inner label; break 'inner label;
} }
@ -611,20 +616,20 @@ fn can_assigned_fields<'a>(
let field_type = let field_type =
to_type2(env, scope, rigids, &annotation.value, annotation.region); to_type2(env, scope, rigids, &annotation.value, annotation.region);
let label = field_name.value; let label = Lowercase::from(field_name.value);
field_types.insert(label.clone(), Optional(field_type)); field_types.insert(label.clone(), Optional(field_type));
break 'inner label; break 'inner label;
} }
LabelOnly(loc_field_name) => { LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b } // Interpret { a, b } as { a : a, b : b }
let field_name = loc_field_name.value; let field_name = Lowercase::from(loc_field_name.value);
let field_type = { let field_type = {
if let Some(var) = rigids.named.get(&field_name) { if let Some(var) = rigids.named.get(&field_name) {
Type2::Variable(*var) Type2::Variable(*var)
} else { } else {
let field_var = env.var_store.fresh(); let field_var = env.var_store.fresh();
rigids.named.insert(field_name, field_var); rigids.named.insert(field_name.clone(), field_var);
Type2::Variable(field_var) Type2::Variable(field_var)
} }
}; };
@ -664,7 +669,7 @@ fn can_assigned_fields<'a>(
fn can_tags<'a>( fn can_tags<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
tags: &'a [Located<roc_parse::ast::Tag<'a>>], tags: &'a [Located<roc_parse::ast::Tag<'a>>],
region: Region, region: Region,
) -> Vec<(TagName, PoolVec<Type2>)> { ) -> Vec<(TagName, PoolVec<Type2>)> {
@ -748,7 +753,7 @@ enum TypeApply {
fn to_type_apply<'a>( fn to_type_apply<'a>(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
rigids: &mut References<'a>, rigids: &mut References,
module_name: &str, module_name: &str,
ident: &str, ident: &str,
type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>], type_arguments: &[Located<roc_parse::ast::TypeAnnotation<'a>>],

View file

@ -7,6 +7,7 @@ use crate::mem_pool::{
pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone, pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone,
}; };
use roc_collections::all::WyHash; use roc_collections::all::WyHash;
use roc_module::ident::Lowercase;
use roc_types::subs::Variable; use roc_types::subs::Variable;
#[derive(Debug)] #[derive(Debug)]
@ -18,7 +19,7 @@ pub struct Rigids {
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]
impl Rigids { impl Rigids {
pub fn new( pub fn new(
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>, named: HashMap<Lowercase, Variable, BuildHasherDefault<WyHash>>,
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>, unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
pool: &mut Pool, pool: &mut Pool,
) -> Self { ) -> Self {
@ -26,7 +27,7 @@ impl Rigids {
let mut temp_names = Vec::new(); let mut temp_names = Vec::new();
temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); temp_names.extend(named.iter().map(|(name, var)| (Some(name.as_str()), *var)));
temp_names.extend(unnamed.iter().map(|var| (None, *var))); temp_names.extend(unnamed.iter().map(|var| (None, *var)));

View file

@ -61,7 +61,7 @@ roc_load = { path = "../compiler/load" }
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true } roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
roc_build = { path = "../compiler/build", default-features = false } roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" } roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" } roc_reporting = { path = "../reporting" }
roc_editor = { path = "../editor", optional = true } roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" } roc_linker = { path = "../linker" }
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] } clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }

View file

@ -22,7 +22,7 @@ roc_load = { path = "../load" }
roc_gen_llvm = { path = "../gen_llvm", optional = true } roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true } roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false } roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../../reporting" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1" libloading = "0.7.1"

View file

@ -145,6 +145,8 @@ pub fn build_zig_host_native(
_target: &str, _target: &str,
opt_level: OptLevel, opt_level: OptLevel,
shared_lib_path: Option<&Path>, shared_lib_path: Option<&Path>,
// For compatibility with the non-macOS def above. Keep these in sync.
_target_valgrind: bool,
) -> Output { ) -> Output {
use serde_json::Value; use serde_json::Value;

View file

@ -870,6 +870,9 @@ pub fn listSublist(
len: usize, len: usize,
dec: Dec, dec: Dec,
) callconv(.C) RocList { ) callconv(.C) RocList {
if (len == 0) {
return RocList.empty();
}
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();

View file

@ -1015,6 +1015,25 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))), Box::new(list_type(flex(TVAR1))),
); );
// split : List elem, Nat -> { before: List elem, others: List elem }
add_top_level_function_type!(
Symbol::LIST_SPLIT,
vec![list_type(flex(TVAR1)), nat_type(),],
Box::new(SolvedType::Record {
fields: vec![
(
"before".into(),
RecordField::Required(list_type(flex(TVAR1)))
),
(
"others".into(),
RecordField::Required(list_type(flex(TVAR1)))
),
],
ext: Box::new(SolvedType::EmptyRecord),
},),
);
// drop : List elem, Nat -> List elem // drop : List elem, Nat -> List elem
add_top_level_function_type!( add_top_level_function_type!(
Symbol::LIST_DROP, Symbol::LIST_DROP,

View file

@ -459,6 +459,9 @@ fn can_annotation_help(
Type::Variable(var) Type::Variable(var)
} }
Inferred => {
unimplemented!();
}
Malformed(string) => { Malformed(string) => {
malformed(env, region, string); malformed(env, region, string);

View file

@ -1,9 +1,9 @@
use crate::def::Def; use crate::def::Def;
use crate::expr::{ClosureData, Expr::*}; use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Recursive, WhenBranch}; use crate::expr::{Expr, Field, Recursive, WhenBranch};
use crate::pattern::Pattern; use crate::pattern::Pattern;
use roc_collections::all::SendMap; use roc_collections::all::SendMap;
use roc_module::ident::TagName; use roc_module::ident::{Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia; use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -96,6 +96,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_TAKE_FIRST => list_take_first, LIST_TAKE_FIRST => list_take_first,
LIST_TAKE_LAST => list_take_last, LIST_TAKE_LAST => list_take_last,
LIST_SUBLIST => list_sublist, LIST_SUBLIST => list_sublist,
LIST_SPLIT => list_split,
LIST_INTERSPERSE => list_intersperse,
LIST_DROP => list_drop, LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at, LIST_DROP_AT => list_drop_at,
LIST_DROP_FIRST => list_drop_first, LIST_DROP_FIRST => list_drop_first,
@ -111,7 +113,6 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_WALK_UNTIL => list_walk_until, LIST_WALK_UNTIL => list_walk_until,
LIST_SORT_WITH => list_sort_with, LIST_SORT_WITH => list_sort_with,
LIST_ANY => list_any, LIST_ANY => list_any,
LIST_INTERSPERSE => list_intersperse,
LIST_FIND => list_find, LIST_FIND => list_find,
DICT_LEN => dict_len, DICT_LEN => dict_len,
DICT_EMPTY => dict_empty, DICT_EMPTY => dict_empty,
@ -2226,6 +2227,117 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// List.split : List elem, Nat -> { before: List elem, others: List elem }
fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let list_sym = Symbol::ARG_1;
let index_sym = Symbol::ARG_2;
let clos_sym = Symbol::LIST_SPLIT_CLOS;
let clos_start_sym = Symbol::ARG_3;
let clos_len_sym = Symbol::ARG_4;
let clos_fun_var = var_store.fresh();
let clos_start_var = var_store.fresh();
let clos_len_var = var_store.fresh();
let clos_ret_var = var_store.fresh();
let ret_var = var_store.fresh();
let zero = int(index_var, Variable::NATURAL, 0);
let clos = Closure(ClosureData {
function_type: clos_fun_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
return_type: clos_ret_var,
name: clos_sym,
recursive: Recursive::NotRecursive,
captured_symbols: vec![(list_sym, clos_ret_var)],
arguments: vec![
(
clos_start_var,
no_region(Pattern::Identifier(clos_start_sym)),
),
(clos_len_var, no_region(Pattern::Identifier(clos_len_sym))),
],
loc_body: {
Box::new(no_region(RunLowLevel {
op: LowLevel::ListSublist,
args: vec![
(clos_ret_var, Var(list_sym)),
(clos_start_var, Var(clos_start_sym)),
(clos_len_var, Var(clos_len_sym)),
],
ret_var: clos_ret_var,
}))
},
});
let fun = Box::new((
clos_fun_var,
no_region(clos),
var_store.fresh(),
clos_ret_var,
));
let get_before = Call(
fun.clone(),
vec![
(index_var, no_region(zero)),
(index_var, no_region(Var(index_sym))),
],
CalledVia::Space,
);
let get_list_len = RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(list_sym))],
ret_var: index_var,
};
let get_others_len = RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![(index_var, get_list_len), (index_var, Var(index_sym))],
ret_var: index_var,
};
let get_others = Call(
fun,
vec![
(index_var, no_region(Var(index_sym))),
(index_var, no_region(get_others_len)),
],
CalledVia::Space,
);
let before = Field {
var: clos_ret_var,
region: Region::zero(),
loc_expr: Box::new(no_region(get_before)),
};
let others = Field {
var: clos_ret_var,
region: Region::zero(),
loc_expr: Box::new(no_region(get_others)),
};
let body = record(
vec![("before".into(), before), ("others".into(), others)],
var_store,
);
defn(
symbol,
vec![(list_var, list_sym), (index_var, index_sym)],
var_store,
body,
ret_var,
)
}
/// List.drop : List elem, Nat -> List elem /// List.drop : List elem, Nat -> List elem
fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();
@ -4388,17 +4500,17 @@ fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
} }
} }
// #[inline(always)] #[inline(always)]
// fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr { fn record(fields: Vec<(Lowercase, Field)>, var_store: &mut VarStore) -> Expr {
// let mut send_map = SendMap::default(); let mut send_map = SendMap::default();
// for (k, v) in fields { for (k, v) in fields {
// send_map.insert(k, v); send_map.insert(k, v);
// } }
// Expr::Record { Expr::Record {
// record_var: var_store.fresh(), record_var: var_store.fresh(),
// fields: send_map, fields: send_map,
// } }
// } }
#[inline(always)] #[inline(always)]
fn defn( fn defn(

View file

@ -180,7 +180,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
true true
} }
Wildcard | BoundVariable(_) | Malformed(_) => false, Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => { Function(args, result) => {
(&result.value).is_multiline() (&result.value).is_multiline()
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline()) || args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
@ -279,6 +279,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
} }
BoundVariable(v) => buf.push_str(v), BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'), Wildcard => buf.push('*'),
Inferred => buf.push('_'),
TagUnion { tags, ext } => { TagUnion { tags, ext } => {
format_sequence!(buf, indent, '[', ']', tags, newlines, Tag); format_sequence!(buf, indent, '[', ']', tags, newlines, Tag);

View file

@ -70,7 +70,7 @@ This is the general procedure I follow with some helpful links:
A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/rtfeldman/roc/tree/trunk/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend. A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/rtfeldman/roc/tree/trunk/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend.
1. Pick/write the simplest test case you can find for the new feature. 1. Pick/write the simplest test case you can find for the new feature.
Just add `feature = "gen-dev"` to the `cfg` line for the test case. Just add `feature = "gen-dev"` to the `cfg` line for the test case.
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/tests/helpers/eval.rs) and run the test. 1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/b03ed18553569314a420d5bf1fb0ead4b6b5ecda/compiler/test_gen/src/helpers/dev.rs#L76) and run the test.
It should fail and print out the mono ir for this test case. It should fail and print out the mono ir for this test case.
Seeing the actual mono ir tends to be very helpful for complex additions. Seeing the actual mono ir tends to be very helpful for complex additions.
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait. 1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.

View file

@ -446,6 +446,10 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
unimplemented!("stack offsets over 32k are not yet implement for AArch64"); unimplemented!("stack offsets over 32k are not yet implement for AArch64");
} }
} }
#[inline(always)]
fn neg_reg64_reg64(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _src: AArch64GeneralReg) {
unimplemented!("neg is not yet implement for AArch64");
}
#[inline(always)] #[inline(always)]
fn sub_reg64_reg64_imm32( fn sub_reg64_reg64_imm32(

View file

@ -149,6 +149,7 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn imul_reg64_reg64_reg64( fn imul_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
dst: GeneralReg, dst: GeneralReg,
@ -786,6 +787,23 @@ impl<
} }
} }
fn build_num_neg(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String> {
match layout {
Layout::Builtin(Builtin::Int64) => {
let dst_reg = self.claim_general_reg(dst)?;
let src_reg = self.load_to_general_reg(src)?;
ASM::neg_reg64_reg64(&mut self.buf, dst_reg, src_reg);
Ok(())
}
x => Err(format!("NumNeg: layout, {:?}, not implemented yet", x)),
}
}
fn build_num_sub( fn build_num_sub(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,

View file

@ -1053,6 +1053,11 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src) mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src)
} }
#[inline(always)]
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
mov_reg64_reg64(buf, dst, src);
neg_reg64(buf, dst);
}
#[inline(always)] #[inline(always)]
fn sub_reg64_reg64_imm32( fn sub_reg64_reg64_imm32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,

View file

@ -368,6 +368,18 @@ where
); );
self.build_num_mul(sym, &args[0], &args[1], ret_layout) self.build_num_mul(sym, &args[0], &args[1], ret_layout)
} }
LowLevel::NumNeg => {
debug_assert_eq!(
1,
args.len(),
"NumNeg: expected to have exactly one argument"
);
debug_assert_eq!(
arg_layouts[0], *ret_layout,
"NumNeg: expected to have the same argument and return layout"
);
self.build_num_neg(sym, &args[0], ret_layout)
}
LowLevel::NumPowInt => self.build_fn_call( LowLevel::NumPowInt => self.build_fn_call(
sym, sym,
bitcode::NUM_POW_INT[IntWidth::I64].to_string(), bitcode::NUM_POW_INT[IntWidth::I64].to_string(),
@ -459,6 +471,14 @@ where
layout: &Layout<'a>, layout: &Layout<'a>,
) -> Result<(), String>; ) -> Result<(), String>;
/// build_num_neg stores the negated value of src into dst.
fn build_num_neg(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
/// build_num_sub stores the `src1 - src2` difference into dst. /// build_num_sub stores the `src1 - src2` difference into dst.
fn build_num_sub( fn build_num_sub(
&mut self, &mut self,

Binary file not shown.

View file

@ -444,6 +444,13 @@ impl<'a> WasmBackend<'a> {
Ok(()) Ok(())
} }
Stmt::Refcounting(_modify, following) => {
// TODO: actually deal with refcounting. For hello world, we just skipped it.
self.build_stmt(following, ret_layout)?;
Ok(())
}
x => Err(format!("statement not yet implemented: {:?}", x)), x => Err(format!("statement not yet implemented: {:?}", x)),
} }
} }
@ -472,7 +479,7 @@ impl<'a> WasmBackend<'a> {
CallType::ByName { name: func_sym, .. } => { CallType::ByName { name: func_sym, .. } => {
// If this function is just a lowlevel wrapper, then inline it // If this function is just a lowlevel wrapper, then inline it
if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) { if let Some(lowlevel) = LowLevel::from_inlined_wrapper(*func_sym) {
return self.build_low_level(lowlevel, arguments, wasm_layout); return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
} }
let mut wasm_args_tmp: Vec<Symbol>; let mut wasm_args_tmp: Vec<Symbol>;
@ -517,7 +524,7 @@ impl<'a> WasmBackend<'a> {
} }
CallType::LowLevel { op: lowlevel, .. } => { CallType::LowLevel { op: lowlevel, .. } => {
self.build_low_level(*lowlevel, arguments, wasm_layout) self.build_low_level(*lowlevel, arguments, *sym, wasm_layout)
} }
x => Err(format!("the call type, {:?}, is not yet implemented", x)), x => Err(format!("the call type, {:?}, is not yet implemented", x)),
@ -556,9 +563,18 @@ impl<'a> WasmBackend<'a> {
&mut self, &mut self,
lowlevel: LowLevel, lowlevel: LowLevel,
arguments: &'a [Symbol], arguments: &'a [Symbol],
return_sym: Symbol,
return_layout: WasmLayout, return_layout: WasmLayout,
) -> Result<(), String> { ) -> Result<(), String> {
self.storage.load_symbols(&mut self.code_builder, arguments); // Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use.
// It's only different from the C ABI for small structs, and we are using Zig for all of those cases.
// This is a workaround for a bug in Zig. If later versions fix it, we can change to the C ABI.
self.storage.load_symbols_fastcc(
&mut self.code_builder,
arguments,
return_sym,
&return_layout,
);
let build_result = decode_low_level( let build_result = decode_low_level(
&mut self.code_builder, &mut self.code_builder,
@ -572,7 +588,7 @@ impl<'a> WasmBackend<'a> {
match build_result { match build_result {
Done => Ok(()), Done => Ok(()),
BuiltinCall(name) => { BuiltinCall(name) => {
self.call_imported_builtin(name, arguments, &return_layout); self.call_zig_builtin(name, arguments, &return_layout);
Ok(()) Ok(())
} }
NotImplemented => Err(format!( NotImplemented => Err(format!(
@ -626,8 +642,8 @@ impl<'a> WasmBackend<'a> {
self.lookup_string_constant(string, sym, layout); self.lookup_string_constant(string, sym, layout);
self.code_builder.get_local(local_id); self.code_builder.get_local(local_id);
self.code_builder.insert_memory_relocation(linker_sym_index); self.code_builder
self.code_builder.i32_const(elements_addr as i32); .i32_const_mem_addr(elements_addr, linker_sym_index);
self.code_builder.i32_store(Align::Bytes4, offset); self.code_builder.i32_store(Align::Bytes4, offset);
self.code_builder.get_local(local_id); self.code_builder.get_local(local_id);
@ -749,12 +765,10 @@ impl<'a> WasmBackend<'a> {
Ok(()) Ok(())
} }
fn call_imported_builtin( /// Generate a call instruction to a Zig builtin function.
&mut self, /// And if we haven't seen it before, add an Import and linker data for it.
name: &'a str, /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
arguments: &[Symbol], fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) {
ret_layout: &WasmLayout,
) {
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) { let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
Some(sym_idx) => match &self.linker_symbols[*sym_idx] { Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => { SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
@ -764,11 +778,29 @@ impl<'a> WasmBackend<'a> {
}, },
None => { None => {
let mut param_types = Vec::with_capacity_in(arguments.len(), self.env.arena); let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
param_types.extend(arguments.iter().map(|a| self.storage.get(a).value_type()));
let ret_type = if ret_layout.is_stack_memory() {
param_types.push(ValueType::I32);
None
} else {
Some(ret_layout.value_type())
};
// Zig's "fast calling convention" packs structs into CPU registers (stack machine slots) if possible.
// If they're small enough they can go into an I32 or I64. If they're big, they're pointers (I32).
for arg in arguments {
param_types.push(match self.storage.get(arg) {
StoredValue::StackMemory { size, .. } if *size > 4 && *size <= 8 => {
ValueType::I64
}
stored => stored.value_type(),
});
}
let signature_index = self.module.types.insert(Signature { let signature_index = self.module.types.insert(Signature {
param_types, param_types,
ret_type: Some(ret_layout.value_type()), // TODO: handle builtins with no return value ret_type,
}); });
let import_index = self.module.import.entries.len() as u32; let import_index = self.module.import.entries.len() as u32;

View file

@ -7,6 +7,7 @@ pub mod wasm_module;
use bumpalo::{self, collections::Vec, Bump}; use bumpalo::{self, collections::Vec, Bump};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
@ -36,7 +37,7 @@ pub fn build_module<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<std::vec::Vec<u8>, String> { ) -> Result<std::vec::Vec<u8>, String> {
let mut wasm_module = build_module_help(env, procedures)?; let (mut wasm_module, _) = build_module_help(env, procedures)?;
let mut buffer = std::vec::Vec::with_capacity(4096); let mut buffer = std::vec::Vec::with_capacity(4096);
wasm_module.serialize_mut(&mut buffer); wasm_module.serialize_mut(&mut buffer);
Ok(buffer) Ok(buffer)
@ -45,36 +46,54 @@ pub fn build_module<'a>(
pub fn build_module_help<'a>( pub fn build_module_help<'a>(
env: &'a Env, env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<WasmModule<'a>, String> { ) -> Result<(WasmModule<'a>, u32), String> {
let mut layout_ids = LayoutIds::default(); let mut layout_ids = LayoutIds::default();
let mut proc_symbols = Vec::with_capacity_in(procedures.len(), env.arena); let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
let mut exports = Vec::with_capacity_in(procedures.len(), env.arena); let mut exports = Vec::with_capacity_in(4, env.arena);
let mut main_fn_index = None;
// Collect the symbols & names for the procedures // Collect the symbols & names for the procedures,
for (i, (sym, layout)) in procedures.keys().enumerate() { // and filter out procs we're going to inline
proc_symbols.push(*sym); let mut fn_index: u32 = 0;
for ((sym, layout), proc) in procedures.into_iter() {
if LowLevel::from_inlined_wrapper(sym).is_some() {
continue;
}
generated_procs.push(proc);
generated_symbols.push(sym);
let fn_name = layout_ids let fn_name = layout_ids
.get_toplevel(*sym, layout) .get_toplevel(sym, &layout)
.to_symbol_string(*sym, &env.interns); .to_symbol_string(sym, &env.interns);
if env.exposed_to_host.contains(sym) { if env.exposed_to_host.contains(&sym) {
main_fn_index = Some(fn_index);
exports.push(Export { exports.push(Export {
name: fn_name.clone(), name: fn_name.clone(),
ty: ExportType::Func, ty: ExportType::Func,
index: i as u32, index: fn_index,
}); });
} }
let linker_sym = SymInfo::for_function(i as u32, fn_name); let linker_sym = SymInfo::for_function(fn_index, fn_name);
linker_symbols.push(linker_sym); linker_symbols.push(linker_sym);
fn_index += 1;
} }
// Main loop: Build the Wasm module // Build the Wasm module
let (mut module, linker_symbols) = { let (mut module, linker_symbols) = {
let mut backend = WasmBackend::new(env, layout_ids, proc_symbols, linker_symbols, exports); let mut backend = WasmBackend::new(
for ((sym, _), proc) in procedures.into_iter() { env,
layout_ids,
generated_symbols.clone(),
linker_symbols,
exports,
);
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
backend.build_proc(proc, sym)?; backend.build_proc(proc, sym)?;
} }
(backend.module, backend.linker_symbols) (backend.module, backend.linker_symbols)
@ -83,7 +102,7 @@ pub fn build_module_help<'a>(
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols); let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
module.linking.subsections.push(symbol_table); module.linking.subsections.push(symbol_table);
Ok(module) Ok((module, main_fn_index.unwrap()))
} }
pub struct CopyMemoryConfig { pub struct CopyMemoryConfig {

View file

@ -27,16 +27,18 @@ pub fn decode_low_level<'a>(
let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout); let panic_ret_type = || panic!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout);
match lowlevel { match lowlevel {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt StrConcat => return BuiltinCall(bitcode::STR_CONCAT),
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft
| StrTrimRight | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit
| ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft | StrTrimRight
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
| ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
| ListSublist | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | DictEmpty | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
| DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => { | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | DictEmpty | DictInsert
| DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | DictUnion
| DictIntersection | DictDifference | DictWalk | SetFromList => {
return NotImplemented; return NotImplemented;
} }

View file

@ -5,7 +5,7 @@ use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use crate::layout::WasmLayout; use crate::layout::WasmLayout;
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VmSymbolState}; use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE}; use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
pub enum StoredValueKind { pub enum StoredValueKind {
@ -194,6 +194,63 @@ impl<'a> Storage<'a> {
}) })
} }
/// Load a single symbol using the C Calling Convention
/// *Private* because external code should always load symbols in bulk (see load_symbols)
fn load_symbol_ccc(&mut self, code_builder: &mut CodeBuilder, sym: Symbol) {
let storage = self.get(&sym).to_owned();
match storage {
StoredValue::VirtualMachineStack {
vm_state,
value_type,
size,
} => {
let next_local_id = self.get_next_local_id();
let maybe_next_vm_state = code_builder.load_symbol(sym, vm_state, next_local_id);
match maybe_next_vm_state {
// The act of loading the value changed the VM state, so update it
Some(next_vm_state) => {
self.symbol_storage_map.insert(
sym,
StoredValue::VirtualMachineStack {
vm_state: next_vm_state,
value_type,
size,
},
);
}
None => {
// Loading the value required creating a new local, because
// it was not in a convenient position in the VM stack.
self.local_types.push(value_type);
self.symbol_storage_map.insert(
sym,
StoredValue::Local {
local_id: next_local_id,
value_type,
size,
},
);
}
}
}
StoredValue::Local { local_id, .. } => {
code_builder.get_local(local_id);
code_builder.set_top_symbol(sym);
}
StoredValue::StackMemory { location, .. } => {
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
code_builder.get_local(local_id);
if offset != 0 {
code_builder.i32_const(offset as i32);
code_builder.i32_add();
}
code_builder.set_top_symbol(sym);
}
}
}
/// Load symbols to the top of the VM stack /// Load symbols to the top of the VM stack
/// Avoid calling this method in a loop with one symbol at a time! It will work, /// Avoid calling this method in a loop with one symbol at a time! It will work,
/// but it generates very inefficient Wasm code. /// but it generates very inefficient Wasm code.
@ -204,61 +261,60 @@ impl<'a> Storage<'a> {
return; return;
} }
for sym in symbols.iter() { for sym in symbols.iter() {
let storage = self.get(sym).to_owned(); self.load_symbol_ccc(code_builder, *sym);
match storage { }
StoredValue::VirtualMachineStack { }
vm_state,
value_type, /// Load symbols in a way compatible with LLVM's "fast calling convention"
size, /// A bug in Zig means it always uses this for Wasm even when we specify C calling convention.
} => { /// It squashes small structs into primitive values where possible, avoiding stack memory
let next_local_id = self.get_next_local_id(); /// in favour of CPU registers (or VM stack values, which eventually become CPU registers).
let maybe_next_vm_state = /// We need to convert some of our structs from our internal C-like representation to work with Zig.
code_builder.load_symbol(*sym, vm_state, next_local_id); /// We are sticking to C ABI for better compatibility on the platform side.
match maybe_next_vm_state { pub fn load_symbols_fastcc(
// The act of loading the value changed the VM state, so update it &mut self,
Some(next_vm_state) => { code_builder: &mut CodeBuilder,
self.symbol_storage_map.insert( symbols: &[Symbol],
*sym, return_symbol: Symbol,
StoredValue::VirtualMachineStack { return_layout: &WasmLayout,
vm_state: next_vm_state, ) {
value_type, // Note: we are not doing verify_stack_match in this case so we may generate more code.
size, // We would need more bookkeeping in CodeBuilder to track which representation is on the stack!
},
); if return_layout.is_stack_memory() {
} // Load the address where the return value should be written
None => { // Apparently for return values we still use a pointer to stack memory
// Loading the value required creating a new local, because self.load_symbol_ccc(code_builder, return_symbol);
// it was not in a convenient position in the VM stack. };
self.local_types.push(value_type);
self.symbol_storage_map.insert( for sym in symbols {
*sym, if let StoredValue::StackMemory {
StoredValue::Local { location,
local_id: next_local_id, size,
value_type, alignment_bytes,
size, } = self.get(sym)
}, {
); if *size == 0 {
} unimplemented!("Passing zero-sized values is not implemented yet");
} } else if *size > 8 {
} return self.load_symbol_ccc(code_builder, *sym);
StoredValue::Local { local_id, .. }
| StoredValue::StackMemory {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
code_builder.get_local(local_id);
code_builder.set_top_symbol(*sym);
} }
StoredValue::StackMemory { let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
location: StackMemoryLocation::FrameOffset(offset), code_builder.get_local(local_id);
.. let align = Align::from(*alignment_bytes);
} => {
code_builder.get_local(self.stack_frame_pointer.unwrap()); if *size == 1 {
code_builder.i32_const(offset as i32); code_builder.i32_load8_u(align, offset);
code_builder.i32_add(); } else if *size == 2 {
code_builder.set_top_symbol(*sym); code_builder.i32_load16_u(align, offset);
} else if *size <= 4 {
code_builder.i32_load(align, offset);
} else {
code_builder.i64_load(align, offset);
} }
} else {
self.load_symbol_ccc(code_builder, *sym);
} }
} }
} }

View file

@ -9,7 +9,7 @@ use super::opcodes::{OpCode, OpCode::*};
use super::serialize::{SerialBuffer, Serialize}; use super::serialize::{SerialBuffer, Serialize};
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID}; use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
const ENABLE_DEBUG_LOG: bool = true; const ENABLE_DEBUG_LOG: bool = false;
macro_rules! log_instruction { macro_rules! log_instruction {
($($x: expr),+) => { ($($x: expr),+) => {
if ENABLE_DEBUG_LOG { println!($($x,)*); } if ENABLE_DEBUG_LOG { println!($($x,)*); }
@ -572,11 +572,14 @@ impl<'a> CodeBuilder<'a> {
); );
} }
/// Insert a linker relocation for a memory address /// Insert a const reference to a memory address
pub fn insert_memory_relocation(&mut self, symbol_index: u32) { pub fn i32_const_mem_addr(&mut self, addr: u32, symbol_index: u32) {
self.inst_base(I32CONST, 0, true);
let offset = self.code.len() as u32;
self.code.encode_padded_u32(addr);
self.relocations.push(RelocationEntry::Offset { self.relocations.push(RelocationEntry::Offset {
type_id: OffsetRelocType::MemoryAddrLeb, type_id: OffsetRelocType::MemoryAddrLeb,
offset: self.code.len() as u32, offset,
symbol_index, symbol_index,
addend: 0, addend: 0,
}); });

View file

@ -29,6 +29,8 @@ pub enum SectionId {
Element = 9, Element = 9,
Code = 10, Code = 10,
Data = 11, Data = 11,
/// DataCount section is unused. Only needed for single-pass validation of
/// memory.init and data.drop, which we don't use
DataCount = 12, DataCount = 12,
} }
@ -239,8 +241,7 @@ impl<'a> Serialize for ImportSection<'a> {
#[derive(Debug)] #[derive(Debug)]
pub struct FunctionSection<'a> { pub struct FunctionSection<'a> {
/// Private. See WasmModule::add_function_signature pub signature_indices: Vec<'a, u32>,
signature_indices: Vec<'a, u32>,
} }
impl<'a> FunctionSection<'a> { impl<'a> FunctionSection<'a> {
@ -525,42 +526,6 @@ impl Serialize for DataSection<'_> {
} }
} }
/*******************************************************************
*
* Data Count section
*
* Pre-declares the number of segments in the Data section.
* This helps the runtime to validate the module in a single pass.
* The order of sections is DataCount -> Code -> Data
*
*******************************************************************/
#[derive(Debug)]
struct DataCountSection {
count: u32,
}
impl DataCountSection {
fn new(data_section: &DataSection<'_>) -> Self {
let count = data_section
.segments
.iter()
.filter(|seg| !seg.init.is_empty())
.count() as u32;
DataCountSection { count }
}
}
impl Serialize for DataCountSection {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if self.count > 0 {
let header_indices = write_section_header(buffer, SectionId::DataCount);
buffer.encode_u32(self.count);
update_section_size(buffer, header_indices);
}
}
}
/******************************************************************* /*******************************************************************
* *
* Module * Module
@ -658,11 +623,6 @@ impl<'a> WasmModule<'a> {
counter.serialize_and_count(buffer, &self.start); counter.serialize_and_count(buffer, &self.start);
counter.serialize_and_count(buffer, &self.element); counter.serialize_and_count(buffer, &self.element);
// Data Count section forward-declares the size of the Data section
// so that Code section can be validated in one pass
let data_count_section = DataCountSection::new(&self.data);
counter.serialize_and_count(buffer, &data_count_section);
// Code section is the only one with relocations so we can stop counting // Code section is the only one with relocations so we can stop counting
let code_section_index = counter.section_index; let code_section_index = counter.section_index;
let code_section_body_index = self let code_section_body_index = self

View file

@ -18,7 +18,7 @@ roc_unify = { path = "../unify" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../../reporting" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -1074,6 +1074,8 @@ define_builtins! {
49 LIST_SUBLIST: "sublist" 49 LIST_SUBLIST: "sublist"
50 LIST_INTERSPERSE: "intersperse" 50 LIST_INTERSPERSE: "intersperse"
51 LIST_INTERSPERSE_CLOS: "#intersperseClos" 51 LIST_INTERSPERSE_CLOS: "#intersperseClos"
52 LIST_SPLIT: "split"
53 LIST_SPLIT_CLOS: "#splitClos"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -17,3 +17,5 @@ pretty_assertions = "1.0.0"
indoc = "1.0.3" indoc = "1.0.3"
quickcheck = "1.0.3" quickcheck = "1.0.3"
quickcheck_macros = "1.0.0" quickcheck_macros = "1.0.0"
diff = "0.1.12"
ansi_term = "0.12.1"

View file

@ -240,6 +240,9 @@ pub enum TypeAnnotation<'a> {
tags: Collection<'a, Loc<Tag<'a>>>, tags: Collection<'a, Loc<Tag<'a>>>,
}, },
/// '_', indicating the compiler should infer the type
Inferred,
/// The `*` type variable, e.g. in (List *) /// The `*` type variable, e.g. in (List *)
Wildcard, Wildcard,

View file

@ -626,6 +626,7 @@ pub enum EType<'a> {
TApply(ETypeApply, Row, Col), TApply(ETypeApply, Row, Col),
TBadTypeVariable(Row, Col), TBadTypeVariable(Row, Col),
TWildcard(Row, Col), TWildcard(Row, Col),
TInferred(Row, Col),
/// ///
TStart(Row, Col), TStart(Row, Col),
TEnd(Row, Col), TEnd(Row, Col),

View file

@ -55,6 +55,7 @@ fn term<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, ETy
and!( and!(
one_of!( one_of!(
loc_wildcard(), loc_wildcard(),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens(min_indent)), specialize(EType::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(EType::TRecord, record_type(min_indent))), loc!(specialize(EType::TRecord, record_type(min_indent))),
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))), loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),
@ -111,6 +112,15 @@ fn loc_wildcard<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>>
}) })
} }
/// The `_` indicating an inferred type, e.g. in (List _)
fn loc_inferred<'a>() -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
map!(loc!(word1(b'_', EType::TInferred)), |loc_val: Located<
(),
>| {
loc_val.map(|_| TypeAnnotation::Inferred)
})
}
fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> { fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotation<'a>>, EType<'a>> {
use crate::ast::Spaceable; use crate::ast::Spaceable;
@ -119,6 +129,7 @@ fn loc_applied_arg<'a>(min_indent: u16) -> impl Parser<'a, Located<TypeAnnotatio
backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentStart)), backtrackable(space0_e(min_indent, EType::TSpace, EType::TIndentStart)),
one_of!( one_of!(
loc_wildcard(), loc_wildcard(),
loc_inferred(),
specialize(EType::TInParens, loc_type_in_parens(min_indent)), specialize(EType::TInParens, loc_type_in_parens(min_indent)),
loc!(specialize(EType::TRecord, record_type(min_indent))), loc!(specialize(EType::TRecord, record_type(min_indent))),
loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))), loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))),

View file

@ -0,0 +1,14 @@
BinOps(
[
(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "x",
},
|L 0-0, C 2-3| Plus,
),
],
|L 0-0, C 4-5| Num(
"2",
),
)

View file

@ -0,0 +1 @@
x + 2

View file

@ -0,0 +1,13 @@
BinOps(
[
(
|L 0-0, C 0-1| Num(
"1",
),
|L 0-0, C 3-4| Plus,
),
],
|L 0-0, C 7-8| Num(
"2",
),
)

View file

@ -0,0 +1 @@
1 + 2

View file

@ -0,0 +1,14 @@
Apply(
|L 0-0, C 0-4| GlobalTag(
"Whee",
),
[
|L 0-0, C 5-7| Num(
"12",
),
|L 0-0, C 8-10| Num(
"34",
),
],
Space,
)

View file

@ -0,0 +1 @@
Whee 12 34

View file

@ -0,0 +1,18 @@
Apply(
|L 0-0, C 0-4| GlobalTag(
"Whee",
),
[
|L 0-0, C 6-8| ParensAround(
Num(
"12",
),
),
|L 0-0, C 11-13| ParensAround(
Num(
"34",
),
),
],
Space,
)

View file

@ -0,0 +1 @@
Whee (12) (34)

View file

@ -0,0 +1,14 @@
Apply(
|L 0-0, C 0-5| PrivateTag(
"@Whee",
),
[
|L 0-0, C 6-8| Num(
"12",
),
|L 0-0, C 9-11| Num(
"34",
),
],
Space,
)

View file

@ -0,0 +1 @@
@Whee 12 34

View file

@ -0,0 +1,21 @@
Apply(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "a",
},
[
|L 0-0, C 2-3| Var {
module_name: "",
ident: "b",
},
|L 0-0, C 4-5| Var {
module_name: "",
ident: "c",
},
|L 0-0, C 6-7| Var {
module_name: "",
ident: "d",
},
],
Space,
)

View file

@ -0,0 +1 @@
a b c d

View file

@ -0,0 +1,15 @@
Apply(
|L 0-0, C 0-4| Var {
module_name: "",
ident: "whee",
},
[
|L 0-0, C 6-8| Num(
"12",
),
|L 0-0, C 10-12| Num(
"34",
),
],
Space,
)

View file

@ -0,0 +1 @@
whee 12 34

View file

@ -0,0 +1,19 @@
Apply(
|L 0-0, C 0-5| UnaryOp(
|L 0-0, C 1-5| Var {
module_name: "",
ident: "whee",
},
|L 0-0, C 0-1| Negate,
),
[
|L 0-0, C 7-9| Num(
"12",
),
|L 0-0, C 10-13| Var {
module_name: "",
ident: "foo",
},
],
Space,
)

View file

@ -0,0 +1 @@
-whee 12 foo

View file

@ -0,0 +1,19 @@
Apply(
|L 0-0, C 0-5| UnaryOp(
|L 0-0, C 1-5| Var {
module_name: "",
ident: "whee",
},
|L 0-0, C 0-1| Not,
),
[
|L 0-0, C 7-9| Num(
"12",
),
|L 0-0, C 10-13| Var {
module_name: "",
ident: "foo",
},
],
Space,
)

View file

@ -0,0 +1 @@
!whee 12 foo

View file

@ -0,0 +1,12 @@
Apply(
|L 0-0, C 0-4| Var {
module_name: "",
ident: "whee",
},
[
|L 0-0, C 5-6| Num(
"1",
),
],
Space,
)

View file

@ -0,0 +1 @@
whee 1

View file

@ -0,0 +1,43 @@
SpaceBefore(
Defs(
[
|L 6-6, C 0-5| Body(
|L 6-6, C 0-1| Identifier(
"x",
),
|L 6-6, C 4-5| Num(
"5",
),
),
],
|L 8-8, C 0-2| SpaceBefore(
Num(
"42",
),
[
Newline,
Newline,
],
),
),
[
DocComment(
"first line of docs",
),
DocComment(
" second line",
),
DocComment(
" third line",
),
DocComment(
"fourth line",
),
DocComment(
"",
),
DocComment(
"sixth line after doc new line",
),
],
)

View file

@ -0,0 +1,9 @@
## first line of docs
## second line
## third line
## fourth line
##
## sixth line after doc new line
x = 5
42

View file

@ -0,0 +1,7 @@
Access(
Var {
module_name: "",
ident: "rec",
},
"field",
)

View file

@ -0,0 +1 @@
rec.field

View file

@ -0,0 +1,3 @@
GlobalTag(
"Whee",
)

View file

@ -0,0 +1 @@
Whee

View file

@ -0,0 +1,3 @@
PrivateTag(
"@Whee",
)

View file

@ -0,0 +1 @@
@Whee

View file

@ -0,0 +1,4 @@
Var {
module_name: "",
ident: "whee",
}

View file

@ -0,0 +1 @@
whee

View file

@ -0,0 +1,13 @@
Closure(
[
|L 0-0, C 1-2| Underscore(
"",
),
|L 0-0, C 4-9| Underscore(
"name",
),
],
|L 0-0, C 13-15| Num(
"42",
),
)

View file

@ -0,0 +1 @@
\_, _name -> 42

View file

@ -0,0 +1,20 @@
BinOps(
[
(
|L 0-0, C 0-2| Num(
"12",
),
|L 0-0, C 4-5| Star,
),
],
|L 1-1, C 1-3| SpaceBefore(
Num(
"92",
),
[
LineComment(
" test!",
),
],
),
)

View file

@ -0,0 +1,2 @@
12 * # test!
92

View file

@ -0,0 +1,20 @@
BinOps(
[
(
|L 0-0, C 0-1| SpaceAfter(
Num(
"3",
),
[
LineComment(
" test!",
),
],
),
|L 1-1, C 0-1| Plus,
),
],
|L 1-1, C 2-3| Num(
"4",
),
)

View file

@ -0,0 +1,2 @@
3 # test!
+ 4

View file

@ -0,0 +1,10 @@
List(
Collection {
items: [],
final_comments: [
LineComment(
"comment",
),
],
},
)

View file

@ -0,0 +1,2 @@
[#comment
]

View file

@ -0,0 +1,20 @@
BinOps(
[
(
|L 0-0, C 0-1| SpaceAfter(
Num(
"3",
),
[
LineComment(
" 2 × 2",
),
],
),
|L 1-1, C 0-1| Plus,
),
],
|L 1-1, C 2-3| Num(
"4",
),
)

View file

@ -0,0 +1,2 @@
3 # 2 × 2
+ 4

View file

@ -0,0 +1,23 @@
App {
header: AppHeader {
name: |L 0-0, C 4-14| PlainLine(
"test-app",
),
packages: [],
imports: [],
provides: [],
to: |L 0-0, C 53-57| ExistingPackage(
"blah",
),
before_header: [],
after_app_keyword: [],
before_packages: [],
after_packages: [],
before_imports: [],
after_imports: [],
before_provides: [],
after_provides: [],
before_to: [],
after_to: [],
},
}

View file

@ -0,0 +1 @@
app "test-app" packages {} imports [] provides [] to blah

View file

@ -0,0 +1,15 @@
Interface {
header: InterfaceHeader {
name: |L 0-0, C 10-13| ModuleName(
"Foo",
),
exposes: [],
imports: [],
before_header: [],
after_interface_keyword: [],
before_exposes: [],
after_exposes: [],
before_imports: [],
after_imports: [],
},
}

View file

@ -0,0 +1 @@
interface Foo exposes [] imports []

View file

@ -0,0 +1,3 @@
List(
[],
)

View file

@ -0,0 +1 @@
[]

View file

@ -0,0 +1,43 @@
Platform {
header: PlatformHeader {
name: |L 0-0, C 9-23| PackageName {
account: "rtfeldman",
pkg: "blah",
},
requires: PlatformRequires {
rigids: [],
signature: |L 0-0, C 38-47| Entry {
ident: |L 0-0, C 38-42| "main",
spaces_before_colon: [],
ann: |L 0-0, C 45-47| Record {
fields: [],
ext: None,
},
},
},
exposes: [],
packages: [],
imports: [],
provides: [],
effects: Effects {
spaces_before_effects_keyword: [],
spaces_after_effects_keyword: [],
spaces_after_type_name: [],
effect_shortname: "fx",
effect_type_name: "Blah",
entries: [],
},
before_header: [],
after_platform_keyword: [],
before_requires: [],
after_requires: [],
before_exposes: [],
after_exposes: [],
before_packages: [],
after_packages: [],
before_imports: [],
after_imports: [],
before_provides: [],
after_provides: [],
},
}

View file

@ -0,0 +1 @@
platform rtfeldman/blah requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {}

View file

@ -0,0 +1,3 @@
Record(
[],
)

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,5 @@
Str(
PlainLine(
"",
),
)

View file

@ -0,0 +1 @@
""

View file

@ -0,0 +1,15 @@
BinOps(
[
(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "x",
},
|L 0-0, C 1-3| Equals,
),
],
|L 0-0, C 3-4| Var {
module_name: "",
ident: "y",
},
)

View file

@ -0,0 +1 @@
x==y

View file

@ -0,0 +1,15 @@
BinOps(
[
(
|L 0-0, C 0-1| Var {
module_name: "",
ident: "x",
},
|L 0-0, C 2-4| Equals,
),
],
|L 0-0, C 5-6| Var {
module_name: "",
ident: "y",
},
)

View file

@ -0,0 +1 @@
x == y

View file

@ -0,0 +1,24 @@
Expect(
|L 0-0, C 7-13| BinOps(
[
(
|L 0-0, C 7-8| Num(
"1",
),
|L 0-0, C 9-11| Equals,
),
],
|L 0-0, C 12-13| Num(
"1",
),
),
|L 2-2, C 0-1| SpaceBefore(
Num(
"4",
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,3 @@
expect 1 == 1
4

View file

@ -0,0 +1,3 @@
Float(
"-1_23_456.0_1_23_456",
)

View file

@ -0,0 +1 @@
-1_23_456.0_1_23_456

View file

@ -0,0 +1,51 @@
App {
header: AppHeader {
name: |L 0-0, C 4-15| PlainLine(
"quicksort",
),
packages: [
|L 1-1, C 15-33| Entry {
shorthand: "base",
spaces_after_shorthand: [],
package_or_path: |L 1-1, C 21-33| Path(
PlainLine(
"./platform",
),
),
},
],
imports: [
|L 2-2, C 14-25| Package(
"foo",
ModuleName(
"Bar.Baz",
),
[],
),
],
provides: [
|L 3-3, C 15-24| Exposed(
"quicksort",
),
],
to: |L 3-3, C 30-34| ExistingPackage(
"base",
),
before_header: [],
after_app_keyword: [],
before_packages: [
Newline,
],
after_packages: [],
before_imports: [
Newline,
],
after_imports: [],
before_provides: [
Newline,
],
after_provides: [],
before_to: [],
after_to: [],
},
}

View file

@ -0,0 +1,4 @@
app "quicksort"
packages { base: "./platform" }
imports [ foo.Bar.Baz ]
provides [ quicksort ] to base

View file

@ -0,0 +1,76 @@
App {
header: AppHeader {
name: |L 0-0, C 4-15| PlainLine(
"quicksort",
),
packages: [
|L 1-1, C 15-33| Entry {
shorthand: "base",
spaces_after_shorthand: [],
package_or_path: |L 1-1, C 21-33| Path(
PlainLine(
"./platform",
),
),
},
],
imports: [
|L 2-6, C 14-5| Package(
"foo",
ModuleName(
"Bar",
),
Collection {
items: [
|L 3-3, C 8-11| SpaceBefore(
Exposed(
"Baz",
),
[
Newline,
],
),
|L 4-4, C 8-16| SpaceBefore(
Exposed(
"FortyTwo",
),
[
Newline,
],
),
],
final_comments: [
Newline,
LineComment(
" I'm a happy comment",
),
],
},
),
],
provides: [
|L 7-7, C 15-24| Exposed(
"quicksort",
),
],
to: |L 7-7, C 31-35| ExistingPackage(
"base",
),
before_header: [],
after_app_keyword: [],
before_packages: [
Newline,
],
after_packages: [],
before_imports: [
Newline,
],
after_imports: [],
before_provides: [
Newline,
],
after_provides: [],
before_to: [],
after_to: [],
},
}

View file

@ -0,0 +1,8 @@
app "quicksort"
packages { base: "./platform", }
imports [ foo.Bar.{
Baz,
FortyTwo,
# I'm a happy comment
} ]
provides [ quicksort, ] to base

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