Merge branch 'trunk' of github.com:rtfeldman/roc into wasm-linking-zig9

This commit is contained in:
Brian Carroll 2022-04-11 18:19:30 +01:00
commit c950f6d834
106 changed files with 4163 additions and 1740 deletions

View file

@ -72,3 +72,5 @@ Elliot Waite <1767836+elliotwaite@users.noreply.github.com>
zimt28 <1764689+zimt28@users.noreply.github.com> zimt28 <1764689+zimt28@users.noreply.github.com>
Ananda Umamil <zweimach@zweimach.org> Ananda Umamil <zweimach@zweimach.org>
SylvanSign <jake.d.bray@gmail.com> SylvanSign <jake.d.bray@gmail.com>
Nikita Mounier <36044205+nikitamounier@users.noreply.github.com>
Cai Bingjun <62678643+C-BJ@users.noreply.github.com>

View file

@ -39,6 +39,8 @@ Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`. To use the `repl` subcommand, execute `cargo run repl`.
Use `cargo build` to build the whole project. Use `cargo build` to build the whole project.
> When using `nix-shell`, make sure that if you start `nix-shell` and then run `echo "$PATH" | tr ':' '\n'`, you see the `usr/bin` path listed after all the `/nix/…` paths. Otherwise you might get some nasty rust compilation errors!
#### Extra tips #### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly! If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!

1
Cargo.lock generated
View file

@ -3370,6 +3370,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_error_macros",
"roc_gen_dev", "roc_gen_dev",
"roc_gen_llvm", "roc_gen_llvm",
"roc_gen_wasm", "roc_gen_wasm",

5
FAQ.md
View file

@ -1,5 +1,10 @@
# Frequently Asked Questions # Frequently Asked Questions
# Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
The Roc editor is one of the key areas where we want to innovate. Constraining ourselves to a plugin for existing editors would severely limit our possibilities for innovation.
A key part of our editor will be the use of plugins that are shipped with libraries. Think of a regex visualizer, parser debugger, or color picker. For library authors, it would be most convenient to write these plugins in Roc. Trying to dynamically load library plugins (written in Roc) in for example VSCode seems very difficult.
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP? ## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious

View file

@ -16,9 +16,9 @@ use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::{todo_abilities, todo_opaques}; use roc_error_macros::{todo_abilities, todo_opaques};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast::{self, TypeHeader}; use roc_parse::ast::{self, TypeDef, TypeHeader, ValueDef as AstValueDef};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError}; use roc_problem::can::{Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use std::collections::HashMap; use std::collections::HashMap;
@ -133,7 +133,7 @@ fn to_pending_def<'a>(
use roc_parse::ast::Def::*; use roc_parse::ast::Def::*;
match def { match def {
Annotation(loc_pattern, loc_ann) => { Value(AstValueDef::Annotation(loc_pattern, loc_ann)) => {
// This takes care of checking for shadowing and adding idents to scope. // This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = pattern::to_pattern_id( let (output, loc_can_pattern) = pattern::to_pattern_id(
env, env,
@ -148,7 +148,7 @@ fn to_pending_def<'a>(
PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann),
)) ))
} }
Body(loc_pattern, loc_expr) => { Value(AstValueDef::Body(loc_pattern, loc_expr)) => {
// This takes care of checking for shadowing and adding idents to scope. // This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = pattern::to_pattern_id( let (output, loc_can_pattern) = pattern::to_pattern_id(
env, env,
@ -164,13 +164,13 @@ fn to_pending_def<'a>(
)) ))
} }
AnnotatedBody { Value(AstValueDef::AnnotatedBody {
ann_pattern, ann_pattern,
ann_type, ann_type,
comment: _, comment: _,
body_pattern, body_pattern,
body_expr, body_expr,
} => { }) => {
if ann_pattern.value.equivalent(&body_pattern.value) { if ann_pattern.value.equivalent(&body_pattern.value) {
// NOTE: Pick the body pattern, picking the annotation one is // NOTE: Pick the body pattern, picking the annotation one is
// incorrect in the presence of optional record fields! // incorrect in the presence of optional record fields!
@ -199,10 +199,10 @@ fn to_pending_def<'a>(
} }
} }
roc_parse::ast::Def::Alias { Type(TypeDef::Alias {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
ann, ann,
} => { }) => {
let region = Region::span_across(&name.region, &ann.region); let region = Region::span_across(&name.region, &ann.region);
match scope.introduce( match scope.introduce(
@ -251,9 +251,10 @@ fn to_pending_def<'a>(
} }
Err((original_region, loc_shadowed_symbol)) => { Err((original_region, loc_shadowed_symbol)) => {
env.problem(Problem::ShadowingInAnnotation { env.problem(Problem::Shadowing {
original_region, original_region,
shadow: loc_shadowed_symbol, shadow: loc_shadowed_symbol,
kind: ShadowKind::Variable,
}); });
Some((Output::default(), PendingDef::InvalidAlias)) Some((Output::default(), PendingDef::InvalidAlias))
@ -261,10 +262,10 @@ fn to_pending_def<'a>(
} }
} }
Opaque { .. } => todo_opaques!(), Type(TypeDef::Opaque { .. }) => todo_opaques!(),
Ability { .. } => todo_abilities!(), Type(TypeDef::Ability { .. }) => todo_abilities!(),
Expect(_) => todo!(), Value(AstValueDef::Expect(_)) => todo!(),
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => {
to_pending_def(env, sub_def, scope, pattern_type) to_pending_def(env, sub_def, scope, pattern_type)
@ -608,7 +609,7 @@ fn canonicalize_pending_def<'a>(
pattern_id: loc_can_pattern, pattern_id: loc_can_pattern,
expr_id: env.pool.add(loc_can_expr), expr_id: env.pool.add(loc_can_expr),
type_id: annotation, type_id: annotation,
rigids: rigids, rigids,
expr_var: env.var_store.fresh(), expr_var: env.var_store.fresh(),
}; };

View file

@ -72,7 +72,7 @@ pub fn def_to_def2<'a>(
def_to_def2(arena, env, scope, inner_def, region) def_to_def2(arena, env, scope, inner_def, region)
} }
} }
Body(&loc_pattern, &loc_expr) => { Value(roc_parse::ast::ValueDef::Body(&loc_pattern, &loc_expr)) => {
let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0;
let expr_id = env.pool.add(expr2); let expr_id = env.pool.add(expr2);

View file

@ -12,7 +12,7 @@ use roc_error_macros::todo_opaques;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::subs::Variable; use roc_types::subs::Variable;
@ -161,6 +161,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
let name: &str = shadow.value.as_ref(); let name: &str = shadow.value.as_ref();
@ -364,6 +365,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// let shadowed = Pattern2::Shadowed { // let shadowed = Pattern2::Shadowed {
@ -443,6 +445,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// No matter what the other patterns // No matter what the other patterns

View file

@ -246,6 +246,11 @@ pub fn build_file<'a>(
todo!("gracefully handle failing to surgically link"); todo!("gracefully handle failing to surgically link");
})?; })?;
BuildOutcome::NoProblems BuildOutcome::NoProblems
} else if matches!(link_type, LinkType::None) {
// Just copy the object file to the output folder.
binary_path.set_extension(app_extension);
std::fs::copy(app_o_file, &binary_path).unwrap();
BuildOutcome::NoProblems
} else { } else {
let mut inputs = vec![ let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(), host_input_path.as_path().to_str().unwrap(),

View file

@ -10,8 +10,8 @@ use roc_fmt::module::fmt_module;
use roc_fmt::Buf; use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp}; use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{ use roc_parse::ast::{
AbilityDemand, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral, AbilityMember, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral,
StrSegment, Tag, TypeAnnotation, TypeHeader, WhenBranch, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
}; };
use roc_parse::header::{ use roc_parse::header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry, AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
@ -445,62 +445,80 @@ impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
} }
} }
impl<'a> RemoveSpaces<'a> for Def<'a> { impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self { fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self { match *self {
Def::Annotation(a, b) => { Alias {
Def::Annotation(a.remove_spaces(arena), b.remove_spaces(arena))
}
Def::Alias {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
ann, ann,
} => Def::Alias { } => Alias {
header: TypeHeader { header: TypeHeader {
name: name.remove_spaces(arena), name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena), vars: vars.remove_spaces(arena),
}, },
ann: ann.remove_spaces(arena), ann: ann.remove_spaces(arena),
}, },
Def::Opaque { Opaque {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
typ, typ,
} => Def::Opaque { } => Opaque {
header: TypeHeader { header: TypeHeader {
name: name.remove_spaces(arena), name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena), vars: vars.remove_spaces(arena),
}, },
typ: typ.remove_spaces(arena), typ: typ.remove_spaces(arena),
}, },
Def::Body(a, b) => Def::Body( Ability {
header: TypeHeader { name, vars },
loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_has: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)), arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)), arena.alloc(b.remove_spaces(arena)),
), ),
Def::AnnotatedBody { AnnotatedBody {
ann_pattern, ann_pattern,
ann_type, ann_type,
comment: _, comment: _,
body_pattern, body_pattern,
body_expr, body_expr,
} => Def::AnnotatedBody { } => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)), ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)), ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None, comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)), body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)), body_expr: arena.alloc(body_expr.remove_spaces(arena)),
}, },
Def::Ability { Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
header: TypeHeader { name, vars }, }
loc_has, }
demands, }
} => Def::Ability {
header: TypeHeader { impl<'a> RemoveSpaces<'a> for Def<'a> {
name: name.remove_spaces(arena), fn remove_spaces(&self, arena: &'a Bump) -> Self {
vars: vars.remove_spaces(arena), match *self {
}, Def::Type(def) => Def::Type(def.remove_spaces(arena)),
loc_has: loc_has.remove_spaces(arena), Def::Value(def) => Def::Value(def.remove_spaces(arena)),
demands: demands.remove_spaces(arena),
},
Def::Expect(a) => Def::Expect(arena.alloc(a.remove_spaces(arena))),
Def::NotYetImplemented(a) => Def::NotYetImplemented(a), Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena), Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
} }
@ -513,9 +531,9 @@ impl<'a> RemoveSpaces<'a> for Has<'a> {
} }
} }
impl<'a> RemoveSpaces<'a> for AbilityDemand<'a> { impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self { fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityDemand { AbilityMember {
name: self.name.remove_spaces(arena), name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena), typ: self.typ.remove_spaces(arena),
} }

View file

@ -34,6 +34,7 @@ pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize"; pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size"; pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib"; pub const FLAG_LIB: &str = "lib";
pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target"; pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker"; pub const FLAG_LINK: &str = "roc-linker";
@ -88,6 +89,12 @@ pub fn build_app<'a>() -> App<'a> {
.about("Build a C library instead of an executable.") .about("Build a C library instead of an executable.")
.required(false), .required(false),
) )
.arg(
Arg::new(FLAG_NO_LINK)
.long(FLAG_NO_LINK)
.about("Does not link. Instead just outputs the `.o` file")
.required(false),
)
.arg( .arg(
Arg::new(FLAG_DEBUG) Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG) .long(FLAG_DEBUG)
@ -291,10 +298,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let emit_debug_info = matches.is_present(FLAG_DEBUG); let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME); let emit_timings = matches.is_present(FLAG_TIME);
let link_type = if matches.is_present(FLAG_LIB) { let link_type = match (
LinkType::Dylib matches.is_present(FLAG_LIB),
} else { matches.is_present(FLAG_NO_LINK),
LinkType::Executable ) {
(true, false) => LinkType::Dylib,
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
}; };
let surgically_link = matches.is_present(FLAG_LINK); let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED); let precompiled = matches.is_present(FLAG_PRECOMPILED);

View file

@ -24,6 +24,7 @@ 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_error_macros = { path = "../../error_macros" }
roc_std = { path = "../../roc_std", default-features = false } roc_std = { path = "../../roc_std", default-features = false }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1" libloading = "0.7.1"

View file

@ -2,6 +2,7 @@ use crate::target::{arch_str, target_zig_str};
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use libloading::{Error, Library}; use libloading::{Error, Library};
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_error_macros::internal_error;
// #[cfg(feature = "llvm")] // #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use std::collections::HashMap; use std::collections::HashMap;
@ -20,10 +21,10 @@ fn zig_executable() -> String {
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType { pub enum LinkType {
// These numbers correspond to the --lib flag; if it's present // These numbers correspond to the --lib and --no-link flags
// (e.g. is_present returns `1 as bool`), this will be 1 as well.
Executable = 0, Executable = 0,
Dylib = 1, Dylib = 1,
None = 2,
} }
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"] /// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
@ -835,6 +836,7 @@ fn link_linux(
output_path, output_path,
) )
} }
LinkType::None => internal_error!("link_linux should not be called with link type of none"),
}; };
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string()); let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
@ -904,6 +906,7 @@ fn link_macos(
("-dylib", output_path) ("-dylib", output_path)
} }
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
}; };
let arch = match target.architecture { let arch = match target.architecture {

View file

@ -628,6 +628,15 @@ toU16 : Int * -> U16
toU32 : Int * -> U32 toU32 : Int * -> U32
toU64 : Int * -> U64 toU64 : Int * -> U64
toU128 : Int * -> U128 toU128 : Int * -> U128
## Convert a [Num] to a [F32]. If the given number can't be precisely represented in a [F32],
## there will be a loss of precision.
toF32 : Num * -> F32
## Convert a [Num] to a [F64]. If the given number can't be precisely represented in a [F64],
## there will be a loss of precision.
toF64 : Num * -> F64
## Convert any [Int] to a specifically-sized [Int], after checking validity. ## Convert any [Int] to a specifically-sized [Int], after checking validity.
## These are checked bitwise operations, ## These are checked bitwise operations,
## so if the source number is outside the target range, then these will ## so if the source number is outside the target range, then these will
@ -643,6 +652,9 @@ toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]* toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ OutOfBounds ]* toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
## Convert a number to a [Str]. ## Convert a number to a [Str].
## ##
## This is the same as calling `Num.format {}` - so for more details on ## This is the same as calling `Num.format {}` - so for more details on
@ -765,14 +777,6 @@ toU32 : Int * -> U32
toU64 : Int * -> U64 toU64 : Int * -> U64
toU128 : Int * -> U128 toU128 : Int * -> U128
## Convert a #Num to a #F32. If the given number can't be precisely represented in a #F32,
## there will be a loss of precision.
toF32 : Num * -> F32
## Convert a #Num to a #F64. If the given number can't be precisely represented in a #F64,
## there will be a loss of precision.
toF64 : Num * -> F64
## Convert a #Num to a #Dec. If the given number can't be precisely represented in a #Dec, ## Convert a #Num to a #Dec. If the given number can't be precisely represented in a #Dec,
## there will be a loss of precision. ## there will be a loss of precision.
toDec : Num * -> Dec toDec : Num * -> Dec

View file

@ -1,6 +1,6 @@
interface Num interface Num
exposes exposes
[ [
Num, Num,
Int, Int,
Float, Float,
@ -144,8 +144,8 @@ interface Num
imports [ ] imports [ ]
Num range : [ @Num range ] Num range : [ @Num range ]
Int range : Num (Integer range) Int range : Num (Integer range)
Float range : Num (FloatingPoint range) Float range : Num (FloatingPoint range)
Signed128 : [ @Signed128 ] Signed128 : [ @Signed128 ]
Signed64 : [ @Signed64 ] Signed64 : [ @Signed64 ]
@ -167,7 +167,7 @@ I128 : Num (Integer Signed128)
I64 : Num (Integer Signed64) I64 : Num (Integer Signed64)
I32 : Num (Integer Signed32) I32 : Num (Integer Signed32)
I16 : Num (Integer Signed16) I16 : Num (Integer Signed16)
I8 : Int Signed8 I8 : Int Signed8
U128 : Num (Integer Unsigned128) U128 : Num (Integer Unsigned128)
U64 : Num (Integer Unsigned64) U64 : Num (Integer Unsigned64)
@ -187,7 +187,7 @@ F64 : Num (FloatingPoint Binary64)
F32 : Num (FloatingPoint Binary32) F32 : Num (FloatingPoint Binary32)
Dec : Num (FloatingPoint Decimal) Dec : Num (FloatingPoint Decimal)
# ------- Functions # ------- Functions
toStr : Num * -> Str toStr : Num * -> Str
intCast : Int a -> Int b intCast : Int a -> Int b
@ -336,6 +336,9 @@ toU32 : Int * -> U32
toU64 : Int * -> U64 toU64 : Int * -> U64
toU128 : Int * -> U128 toU128 : Int * -> U128
toF32 : Num * -> F32
toF64 : Num * -> F64
toI8Checked : Int * -> Result I8 [ OutOfBounds ]* toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
toI16Checked : Int * -> Result I16 [ OutOfBounds ]* toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
toI32Checked : Int * -> Result I32 [ OutOfBounds ]* toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
@ -346,3 +349,5 @@ toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
toU32Checked : Int * -> Result U32 [ OutOfBounds ]* toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]* toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ OutOfBounds ]* toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
toF64Checked : Num * -> Result F64 [ OutOfBounds ]*

View file

@ -615,7 +615,35 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!( add_top_level_function_type!(
Symbol::NUM_TO_NAT_CHECKED, Symbol::NUM_TO_NAT_CHECKED,
vec![int_type(flex(TVAR1))], vec![int_type(flex(TVAR1))],
Box::new(result_type(nat_type(), out_of_bounds)), Box::new(result_type(nat_type(), out_of_bounds.clone())),
);
// toF32 : Num * -> F32
add_top_level_function_type!(
Symbol::NUM_TO_F32,
vec![num_type(flex(TVAR1))],
Box::new(f32_type()),
);
// toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_F32_CHECKED,
vec![num_type(flex(TVAR1))],
Box::new(result_type(f32_type(), out_of_bounds.clone())),
);
// toF64 : Num * -> F64
add_top_level_function_type!(
Symbol::NUM_TO_F64,
vec![num_type(flex(TVAR1))],
Box::new(f64_type()),
);
// toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_F64_CHECKED,
vec![num_type(flex(TVAR1))],
Box::new(result_type(f64_type(), out_of_bounds)),
); );
// toStr : Num a -> Str // toStr : Num a -> Str

View file

@ -0,0 +1,74 @@
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol;
use roc_types::types::Type;
use crate::annotation::HasClause;
/// Stores information about an ability member definition, including the parent ability, the
/// defining type, and what type variables need to be instantiated with instances of the ability.
#[derive(Debug)]
struct AbilityMemberData {
#[allow(unused)]
parent_ability: Symbol,
#[allow(unused)]
signature: Type,
#[allow(unused)]
bound_has_clauses: Vec<HasClause>,
}
/// Stores information about what abilities exist in a scope, what it means to implement an
/// ability, and what types implement them.
// TODO(abilities): this should probably go on the Scope, I don't put it there for now because we
// are only dealing with inter-module abilities for now.
#[derive(Default, Debug)]
pub struct AbilitiesStore {
/// Maps an ability to the members defining it.
#[allow(unused)]
members_of_ability: MutMap<Symbol, Vec<Symbol>>,
/// Information about all members composing abilities.
ability_members: MutMap<Symbol, AbilityMemberData>,
/// Tuples of (type, member) specifying that `type` declares an implementation of an ability
/// member `member`.
#[allow(unused)]
declared_implementations: MutSet<(Symbol, Symbol)>,
}
impl AbilitiesStore {
pub fn register_ability(
&mut self,
ability: Symbol,
members: Vec<(Symbol, Type, Vec<HasClause>)>,
) {
let mut members_vec = Vec::with_capacity(members.len());
for (member, signature, bound_has_clauses) in members.into_iter() {
members_vec.push(member);
let old_member = self.ability_members.insert(
member,
AbilityMemberData {
parent_ability: ability,
signature,
bound_has_clauses,
},
);
debug_assert!(old_member.is_none(), "Replacing existing member definition");
}
let old_ability = self.members_of_ability.insert(ability, members_vec);
debug_assert!(
old_ability.is_none(),
"Replacing existing ability definition"
);
}
pub fn register_implementation(&mut self, implementing_type: Symbol, ability_member: Symbol) {
let old_impl = self
.declared_implementations
.insert((implementing_type, ability_member));
debug_assert!(!old_impl, "Replacing existing implementation");
}
pub fn is_ability_member_name(&self, name: Symbol) -> bool {
self.ability_members.contains_key(&name)
}
}

View file

@ -1,10 +1,10 @@
use crate::env::Env; use crate::env::Env;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::todo_abilities;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader}; use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_problem::can::ShadowKind;
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{ use roc_types::types::{
@ -104,6 +104,10 @@ impl IntroducedVariables {
.find(|nv| nv.variable == var) .find(|nv| nv.variable == var)
.map(|nv| &nv.name) .map(|nv| &nv.name)
} }
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<&NamedVariable> {
self.named.iter().find(|nv| &nv.name == name)
}
} }
fn malformed(env: &mut Env, region: Region, name: &str) { fn malformed(env: &mut Env, region: Region, name: &str) {
@ -143,6 +147,78 @@ pub fn canonicalize_annotation(
} }
} }
#[derive(Clone, Debug)]
pub struct HasClause {
pub var_name: Lowercase,
pub var: Variable,
pub ability: Symbol,
}
pub fn canonicalize_annotation_with_possible_clauses(
env: &mut Env,
scope: &mut Scope,
annotation: &TypeAnnotation,
region: Region,
var_store: &mut VarStore,
abilities_in_scope: &[Symbol],
) -> (Annotation, Vec<Loc<HasClause>>) {
let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default();
let mut aliases = SendMap::default();
let (annotation, region, clauses) = match annotation {
TypeAnnotation::Where(annotation, clauses) => {
let mut can_clauses = Vec::with_capacity(clauses.len());
for clause in clauses.iter() {
match canonicalize_has_clause(
env,
scope,
var_store,
&mut introduced_variables,
clause,
abilities_in_scope,
&mut references,
) {
Ok(result) => can_clauses.push(Loc::at(clause.region, result)),
Err(err_type) => {
return (
Annotation {
typ: err_type,
introduced_variables,
references,
aliases,
},
can_clauses,
)
}
};
}
(&annotation.value, annotation.region, can_clauses)
}
annot => (annot, region, vec![]),
};
let typ = can_annotation_help(
env,
annotation,
region,
scope,
var_store,
&mut introduced_variables,
&mut aliases,
&mut references,
);
let annot = Annotation {
typ,
introduced_variables,
references,
aliases,
};
(annot, clauses)
}
fn make_apply_symbol( fn make_apply_symbol(
env: &mut Env, env: &mut Env,
region: Region, region: Region,
@ -271,7 +347,13 @@ pub fn find_type_def_symbols(
SpaceBefore(inner, _) | SpaceAfter(inner, _) => { SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
stack.push(inner); stack.push(inner);
} }
Where(..) => todo_abilities!(), Where(annotation, clauses) => {
stack.push(&annotation.value);
for has_clause in clauses.iter() {
stack.push(&has_clause.value.ability.value);
}
}
Inferred | Wildcard | Malformed(_) => {} Inferred | Wildcard | Malformed(_) => {}
} }
} }
@ -449,9 +531,10 @@ fn can_annotation_help(
Err((original_region, shadow, _new_symbol)) => { Err((original_region, shadow, _new_symbol)) => {
let problem = Problem::Shadowed(original_region, shadow.clone()); let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation { env.problem(roc_problem::can::Problem::Shadowing {
original_region, original_region,
shadow, shadow,
kind: ShadowKind::Variable,
}); });
return Type::Erroneous(problem); return Type::Erroneous(problem);
@ -685,7 +768,17 @@ fn can_annotation_help(
Type::Variable(var) Type::Variable(var)
} }
Where(..) => todo_abilities!(), Where(_annotation, clauses) => {
debug_assert!(!clauses.is_empty());
// Has clauses are allowed only on the top level of an ability member signature (for
// now), which we handle elsewhere.
env.problem(roc_problem::can::Problem::IllegalHasClause {
region: Region::across_all(clauses.iter().map(|clause| &clause.region)),
});
Type::Erroneous(Problem::CanonicalizationProblem)
}
Malformed(string) => { Malformed(string) => {
malformed(env, region, string); malformed(env, region, string);
@ -698,6 +791,72 @@ fn can_annotation_help(
} }
} }
fn canonicalize_has_clause(
env: &mut Env,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
clause: &Loc<roc_parse::ast::HasClause<'_>>,
abilities_in_scope: &[Symbol],
references: &mut MutSet<Symbol>,
) -> Result<HasClause, Type> {
let Loc {
region,
value: roc_parse::ast::HasClause { var, ability },
} = clause;
let region = *region;
let var_name = var.extract_spaces().item;
debug_assert!(
var_name.starts_with(char::is_lowercase),
"Vars should have been parsed as lowercase"
);
let var_name = Lowercase::from(var_name);
let ability = match ability.value {
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?;
if !abilities_in_scope.contains(&symbol) {
let region = ability.region;
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
}
symbol
}
_ => {
let region = ability.region;
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
}
};
references.insert(ability);
if let Some(shadowing) = introduced_variables.named_var_by_name(&var_name) {
let var_name_ident = var_name.to_string().into();
let shadow = Loc::at(region, var_name_ident);
env.problem(roc_problem::can::Problem::Shadowing {
original_region: shadowing.first_seen,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
});
return Err(Type::Erroneous(Problem::Shadowed(
shadowing.first_seen,
shadow,
)));
}
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), Loc::at(region, var));
Ok(HasClause {
var_name,
var,
ability,
})
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn can_extension_type<'a>( fn can_extension_type<'a>(
env: &mut Env, env: &mut Env,

View file

@ -266,6 +266,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_TO_U128_CHECKED => num_to_u128_checked, NUM_TO_U128_CHECKED => num_to_u128_checked,
NUM_TO_NAT => num_to_nat, NUM_TO_NAT => num_to_nat,
NUM_TO_NAT_CHECKED => num_to_nat_checked, NUM_TO_NAT_CHECKED => num_to_nat_checked,
NUM_TO_F32 => num_to_f32,
NUM_TO_F32_CHECKED => num_to_f32_checked,
NUM_TO_F64 => num_to_f64,
NUM_TO_F64_CHECKED => num_to_f64_checked,
NUM_TO_STR => num_to_str, NUM_TO_STR => num_to_str,
RESULT_MAP => result_map, RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err, RESULT_MAP_ERR => result_map_err,
@ -482,6 +486,18 @@ fn num_to_nat(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::NumIntCast, var_store) lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
} }
// Num.toF32 : Num * -> F32
fn num_to_f32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to NumToFloatCast
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
}
// Num.toF64 : Num * -> F64
fn num_to_f64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to NumToFloatCast
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
}
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def { fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh(); let num_var_1 = var_store.fresh();
@ -589,6 +605,8 @@ num_to_checked! {
num_to_u64_checked num_to_u64_checked
num_to_u128_checked num_to_u128_checked
num_to_nat_checked num_to_nat_checked
num_to_f32_checked
num_to_f64_checked
} }
// Num.toStr : Num a -> Str // Num.toStr : Num a -> Str

View file

@ -1,21 +1,25 @@
use crate::abilities::AbilitiesStore;
use crate::annotation::canonicalize_annotation; use crate::annotation::canonicalize_annotation;
use crate::annotation::canonicalize_annotation_with_possible_clauses;
use crate::annotation::IntroducedVariables; use crate::annotation::IntroducedVariables;
use crate::env::Env; use crate::env::Env;
use crate::expr::ClosureData; use crate::expr::ClosureData;
use crate::expr::Expr::{self, *}; use crate::expr::Expr::{self, *};
use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive}; use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive};
use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern};
use crate::procedure::References; use crate::procedure::References;
use crate::scope::create_alias; use crate::scope::create_alias;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::ImSet; use roc_collections::all::ImSet;
use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap}; use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::todo_abilities;
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast;
use roc_parse::ast::AbilityMember;
use roc_parse::ast::ExtractSpaces;
use roc_parse::ast::TypeHeader; use roc_parse::ast::TypeHeader;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::ShadowKind;
use roc_problem::can::{CycleEntry, Problem, RuntimeError}; use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -49,11 +53,12 @@ pub struct CanDefs {
pub can_defs_by_symbol: MutMap<Symbol, Def>, pub can_defs_by_symbol: MutMap<Symbol, Def>,
pub aliases: SendMap<Symbol, Alias>, pub aliases: SendMap<Symbol, Alias>,
} }
/// A Def that has had patterns and type annnotations canonicalized, /// A Def that has had patterns and type annnotations canonicalized,
/// but no Expr canonicalization has happened yet. Also, it has had spaces /// but no Expr canonicalization has happened yet. Also, it has had spaces
/// and nesting resolved, and knows whether annotations are standalone or not. /// and nesting resolved, and knows whether annotations are standalone or not.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
enum PendingDef<'a> { enum PendingValueDef<'a> {
/// A standalone annotation with no body /// A standalone annotation with no body
AnnotationOnly( AnnotationOnly(
&'a Loc<ast::Pattern<'a>>, &'a Loc<ast::Pattern<'a>>,
@ -73,7 +78,10 @@ enum PendingDef<'a> {
&'a Loc<ast::TypeAnnotation<'a>>, &'a Loc<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Expr<'a>>, &'a Loc<ast::Expr<'a>>,
), ),
}
#[derive(Debug, Clone, PartialEq)]
enum PendingTypeDef<'a> {
/// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively. /// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively.
Alias { Alias {
name: Loc<Symbol>, name: Loc<Symbol>,
@ -82,10 +90,19 @@ enum PendingDef<'a> {
kind: AliasKind, kind: AliasKind,
}, },
Ability {
name: Loc<Symbol>,
members: &'a [ast::AbilityMember<'a>],
},
/// An invalid alias, that is ignored in the rest of the pipeline /// An invalid alias, that is ignored in the rest of the pipeline
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
/// with an incorrect pattern /// with an incorrect pattern
InvalidAlias { kind: AliasKind }, InvalidAlias { kind: AliasKind },
/// An invalid ability, that is ignored in the rest of the pipeline.
/// E.g. a shadowed ability, or with a bad definition.
InvalidAbility,
} }
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
@ -206,56 +223,53 @@ pub fn canonicalize_defs<'a>(
let num_defs = loc_defs.len(); let num_defs = loc_defs.len();
let mut refs_by_symbol = MutMap::default(); let mut refs_by_symbol = MutMap::default();
let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher());
let mut pending = Vec::with_capacity(num_defs); // TODO bump allocate this!
// Canonicalize all the patterns, record shadowing problems, and store let mut type_defs = Vec::with_capacity(num_defs);
// the ast::Expr values in pending_exprs for further canonicalization let mut value_defs = Vec::with_capacity(num_defs);
// once we've finished assembling the entire scope.
for loc_def in loc_defs { for loc_def in loc_defs {
match to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type) { match loc_def.value.unroll_def() {
None => (), Ok(type_def) => type_defs.push(Loc::at(loc_def.region, type_def)),
Some((new_output, pending_def)) => { Err(value_def) => value_defs.push(Loc::at(loc_def.region, value_def)),
// store the top-level defs, used to ensure that closures won't capture them
if let PatternType::TopLevelDef = pattern_type {
match &pending_def {
PendingDef::AnnotationOnly(_, loc_can_pattern, _)
| PendingDef::Body(_, loc_can_pattern, _)
| PendingDef::TypedBody(_, loc_can_pattern, _, _) => {
env.top_level_symbols.extend(
bindings_from_patterns(std::iter::once(loc_can_pattern))
.iter()
.map(|t| t.0),
)
}
// Type definitions aren't value definitions, so we don't need to do
// anything for them here.
PendingDef::Alias { .. } | PendingDef::InvalidAlias { .. } => {}
}
}
// Record the ast::Expr for later. We'll do another pass through these
// once we have the entire scope assembled. If we were to canonicalize
// the exprs right now, they wouldn't have symbols in scope from defs
// that get would have gotten added later in the defs list!
pending.push(pending_def);
output.union(new_output);
}
} }
} }
// We need to canonicalize all the type defs first.
// Clippy is wrong - we do need the collect, otherwise "env" and "scope" are captured for
// longer than we'd like.
#[allow(clippy::needless_collect)]
let pending_type_defs = type_defs
.into_iter()
.filter_map(|loc_def| {
to_pending_type_def(env, loc_def.value, &mut scope).map(|(new_output, pending_def)| {
output.union(new_output);
pending_def
})
})
.collect::<Vec<_>>();
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
env.home.register_debug_idents(&env.ident_ids); env.home.register_debug_idents(&env.ident_ids);
} }
let mut aliases = SendMap::default(); enum TypeDef<'a> {
let mut value_defs = Vec::new(); AliasLike(
Loc<Symbol>,
Vec<Loc<Lowercase>>,
&'a Loc<ast::TypeAnnotation<'a>>,
AliasKind,
),
Ability(Loc<Symbol>, &'a [AbilityMember<'a>]),
}
let mut type_defs = MutMap::default();
let mut abilities_in_scope = Vec::new();
let mut alias_defs = MutMap::default();
let mut referenced_type_symbols = MutMap::default(); let mut referenced_type_symbols = MutMap::default();
for pending_def in pending.into_iter() { for pending_def in pending_type_defs.into_iter() {
match pending_def { match pending_def {
PendingDef::Alias { PendingTypeDef::Alias {
name, name,
vars, vars,
ann, ann,
@ -269,93 +283,137 @@ pub fn canonicalize_defs<'a>(
referenced_type_symbols.insert(name.value, referenced_symbols); referenced_type_symbols.insert(name.value, referenced_symbols);
alias_defs.insert(name.value, (name, vars, ann, kind)); type_defs.insert(name.value, TypeDef::AliasLike(name, vars, ann, kind));
}
PendingTypeDef::Ability { name, members } => {
let mut referenced_symbols = Vec::with_capacity(2);
for member in members.iter() {
// Add the referenced type symbols of each member function. We need to make
// sure those are processed first before we resolve the whole ability
// definition.
referenced_symbols.extend(crate::annotation::find_type_def_symbols(
env.home,
&mut env.ident_ids,
&member.typ.value,
));
}
referenced_type_symbols.insert(name.value, referenced_symbols);
type_defs.insert(name.value, TypeDef::Ability(name, members));
abilities_in_scope.push(name.value);
}
PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } => { /* ignore */
} }
other => value_defs.push(other),
} }
} }
let sorted = sort_type_defs_before_introduction(referenced_type_symbols); let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
let mut aliases = SendMap::default();
let mut abilities = MutMap::default();
for type_name in sorted { for type_name in sorted {
let (name, vars, ann, kind) = alias_defs.remove(&type_name).unwrap(); match type_defs.remove(&type_name).unwrap() {
TypeDef::AliasLike(name, vars, ann, kind) => {
let symbol = name.value;
let can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
let symbol = name.value; // Does this alias reference any abilities? For now, we don't permit that.
let can_ann = canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store); let ability_references = can_ann
.references
.iter()
.filter_map(|&ty_ref| abilities_in_scope.iter().find(|&&name| name == ty_ref))
.collect::<Vec<_>>();
// Record all the annotation's references in output.references.lookups if let Some(one_ability_ref) = ability_references.first() {
for symbol in can_ann.references { env.problem(Problem::AliasUsesAbility {
output.references.type_lookups.insert(symbol); loc_name: name,
output.references.referenced_type_defs.insert(symbol); ability: **one_ability_ref,
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
let mut named = can_ann.introduced_variables.named;
for loc_lowercase in vars.iter() {
match named.iter().position(|nv| nv.name == loc_lowercase.value) {
Some(index) => {
// This is a valid lowercase rigid var for the type def.
let named_variable = named.swap_remove(index);
can_vars.push(Loc {
value: (named_variable.name, named_variable.variable),
region: loc_lowercase.region,
}); });
} }
None => {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument { // Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
let mut named = can_ann.introduced_variables.named;
for loc_lowercase in vars.iter() {
match named.iter().position(|nv| nv.name == loc_lowercase.value) {
Some(index) => {
// This is a valid lowercase rigid var for the type def.
let named_variable = named.swap_remove(index);
can_vars.push(Loc {
value: (named_variable.name, named_variable.variable),
region: loc_lowercase.region,
});
}
None => {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
}
if is_phantom {
// Bail out
continue;
}
let IntroducedVariables {
wildcards,
inferred,
..
} = can_ann.introduced_variables;
let num_unbound = named.len() + wildcards.len() + inferred.len();
if num_unbound > 0 {
let one_occurrence = named
.iter()
.map(|nv| Loc::at(nv.first_seen, nv.variable))
.chain(wildcards)
.chain(inferred)
.next()
.unwrap()
.region;
env.problems.push(Problem::UnboundTypeVariable {
typ: symbol, typ: symbol,
variable_region: loc_lowercase.region, num_unbound,
variable_name: loc_lowercase.value.clone(), one_occurrence,
kind,
}); });
// Bail out
continue;
} }
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
}
TypeDef::Ability(name, members) => {
// For now we enforce that aliases cannot reference abilities, so let's wait to
// resolve ability definitions until aliases are resolved and in scope below.
abilities.insert(name.value, (name, members));
} }
} }
if is_phantom {
// Bail out
continue;
}
let IntroducedVariables {
wildcards,
inferred,
..
} = can_ann.introduced_variables;
let num_unbound = named.len() + wildcards.len() + inferred.len();
if num_unbound > 0 {
let one_occurrence = named
.iter()
.map(|nv| Loc::at(nv.first_seen, nv.variable))
.chain(wildcards)
.chain(inferred)
.next()
.unwrap()
.region;
env.problems.push(Problem::UnboundTypeVariable {
typ: symbol,
num_unbound,
one_occurrence,
kind,
});
// Bail out
continue;
}
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
} }
// Now that we know the alias dependency graph, we can try to insert recursion variables // Now that we know the alias dependency graph, we can try to insert recursion variables
@ -371,10 +429,139 @@ pub fn canonicalize_defs<'a>(
); );
} }
// Now we can go through and resolve all pending abilities, to add them to scope.
let mut abilities_store = AbilitiesStore::default();
for (loc_ability_name, members) in abilities.into_values() {
let mut can_members = Vec::with_capacity(members.len());
for member in members {
let (member_annot, clauses) = canonicalize_annotation_with_possible_clauses(
env,
&mut scope,
&member.typ.value,
member.typ.region,
var_store,
&abilities_in_scope,
);
// Record all the annotation's references in output.references.lookups
for symbol in member_annot.references {
output.references.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let member_name = member.name.extract_spaces().item;
let member_sym = match scope.introduce(
member_name.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
member.name.region,
) {
Ok(sym) => sym,
Err((original_region, shadow, _new_symbol)) => {
env.problem(roc_problem::can::Problem::Shadowing {
original_region,
shadow,
kind: ShadowKind::Variable,
});
// Pretend the member isn't a part of the ability
continue;
}
};
// What variables in the annotation are bound to the parent ability, and what variables
// are bound to some other ability?
let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) =
clauses
.into_iter()
.partition(|has_clause| has_clause.value.ability == loc_ability_name.value);
let mut bad_has_clauses = false;
if variables_bound_to_ability.is_empty() {
// There are no variables bound to the parent ability - then this member doesn't
// need to be a part of the ability.
env.problem(Problem::AbilityMemberMissingHasClause {
member: member_sym,
ability: loc_ability_name.value,
region: member.name.region,
});
bad_has_clauses = true;
}
if !variables_bound_to_other_abilities.is_empty() {
// Disallow variables bound to other abilities, for now.
for bad_clause in variables_bound_to_other_abilities.iter() {
env.problem(Problem::AbilityMemberBindsExternalAbility {
member: member_sym,
ability: loc_ability_name.value,
region: bad_clause.region,
});
}
bad_has_clauses = true;
}
if bad_has_clauses {
// Pretend the member isn't a part of the ability
continue;
}
let has_clauses = variables_bound_to_ability
.into_iter()
.map(|clause| clause.value)
.collect();
can_members.push((member_sym, member_annot.typ, has_clauses));
}
// Store what symbols a type must define implementations for to have this ability.
abilities_store.register_ability(loc_ability_name.value, can_members);
}
// Now that we have the scope completely assembled, and shadowing resolved, // Now that we have the scope completely assembled, and shadowing resolved,
// we're ready to canonicalize any body exprs. // we're ready to canonicalize any body exprs.
for pending_def in value_defs.into_iter() {
output = canonicalize_pending_def( // Canonicalize all the patterns, record shadowing problems, and store
// the ast::Expr values in pending_exprs for further canonicalization
// once we've finished assembling the entire scope.
let mut pending_value_defs = Vec::with_capacity(value_defs.len());
for loc_def in value_defs.into_iter() {
match to_pending_value_def(
env,
var_store,
loc_def.value,
&mut scope,
&abilities_store,
pattern_type,
) {
None => { /* skip */ }
Some((new_output, pending_def)) => {
// store the top-level defs, used to ensure that closures won't capture them
if let PatternType::TopLevelDef = pattern_type {
match &pending_def {
PendingValueDef::AnnotationOnly(_, loc_can_pattern, _)
| PendingValueDef::Body(_, loc_can_pattern, _)
| PendingValueDef::TypedBody(_, loc_can_pattern, _, _) => {
env.top_level_symbols.extend(
bindings_from_patterns(std::iter::once(loc_can_pattern))
.iter()
.map(|t| t.0),
)
}
}
}
// Record the ast::Expr for later. We'll do another pass through these
// once we have the entire scope assembled. If we were to canonicalize
// the exprs right now, they wouldn't have symbols in scope from defs
// that get would have gotten added later in the defs list!
pending_value_defs.push(pending_def);
output.union(new_output);
}
}
}
for pending_def in pending_value_defs.into_iter() {
output = canonicalize_pending_value_def(
env, env,
pending_def, pending_def,
output, output,
@ -833,6 +1020,13 @@ fn pattern_to_vars_by_symbol(
vars_by_symbol.insert(*symbol, expr_var); vars_by_symbol.insert(*symbol, expr_var);
} }
AbilityMemberSpecialization {
ident,
specializes: _,
} => {
vars_by_symbol.insert(*ident, expr_var);
}
AppliedTag { arguments, .. } => { AppliedTag { arguments, .. } => {
for (var, nested) in arguments { for (var, nested) in arguments {
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var); pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
@ -910,9 +1104,9 @@ fn add_annotation_aliases(
// TODO trim down these arguments! // TODO trim down these arguments!
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn canonicalize_pending_def<'a>( fn canonicalize_pending_value_def<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
pending_def: PendingDef<'a>, pending_def: PendingValueDef<'a>,
mut output: Output, mut output: Output,
scope: &mut Scope, scope: &mut Scope,
can_defs_by_symbol: &mut MutMap<Symbol, Def>, can_defs_by_symbol: &mut MutMap<Symbol, Def>,
@ -920,7 +1114,7 @@ fn canonicalize_pending_def<'a>(
refs_by_symbol: &mut MutMap<Symbol, (Region, References)>, refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
aliases: &mut ImMap<Symbol, Alias>, aliases: &mut ImMap<Symbol, Alias>,
) -> Output { ) -> Output {
use PendingDef::*; use PendingValueDef::*;
// Make types for the body expr, even if we won't end up having a body. // Make types for the body expr, even if we won't end up having a body.
let expr_var = var_store.fresh(); let expr_var = var_store.fresh();
@ -957,6 +1151,7 @@ fn canonicalize_pending_def<'a>(
Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing { Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: loc_ident.clone(), shadow: loc_ident.clone(),
kind: ShadowKind::Variable,
}, },
_ => RuntimeError::NoImplementation, _ => RuntimeError::NoImplementation,
}; };
@ -1047,11 +1242,6 @@ fn canonicalize_pending_def<'a>(
} }
} }
Alias { .. } => unreachable!("Aliases are handled in a separate pass"),
InvalidAlias { .. } => {
// invalid aliases and opaques (shadowed, incorrect patterns) get ignored
}
TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let type_annotation = let type_annotation =
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store);
@ -1446,85 +1636,14 @@ fn closure_recursivity(symbol: Symbol, closures: &MutMap<Symbol, References>) ->
Recursive::NotRecursive Recursive::NotRecursive
} }
fn to_pending_def<'a>( fn to_pending_type_def<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &mut VarStore, def: &'a ast::TypeDef<'a>,
def: &'a ast::Def<'a>,
scope: &mut Scope, scope: &mut Scope,
pattern_type: PatternType, ) -> Option<(Output, PendingTypeDef<'a>)> {
) -> Option<(Output, PendingDef<'a>)> { use ast::TypeDef::*;
use roc_parse::ast::Def::*;
match def { match def {
Annotation(loc_pattern, loc_ann) => {
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_pattern(
env,
var_store,
scope,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
Some((
output,
PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann),
))
}
Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_pattern(
env,
var_store,
scope,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
Some((
output,
PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr),
))
}
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => {
if ann_pattern.value.equivalent(&body_pattern.value) {
// NOTE: Pick the body pattern, picking the annotation one is
// incorrect in the presence of optional record fields!
//
// { x, y } : { x : Int, y ? Bool }*
// { x, y ? False } = rec
Some(pending_typed_body(
env,
body_pattern,
ann_type,
body_expr,
var_store,
scope,
pattern_type,
))
} else {
// the pattern of the annotation does not match the pattern of the body direc
env.problems.push(Problem::SignatureDefMismatch {
annotation_pattern: ann_pattern.region,
def_pattern: body_pattern.region,
});
// TODO: Should we instead build some PendingDef::InvalidAnnotatedBody ? This would
// remove the `Option` on this function (and be probably more reliable for further
// problem/error reporting)
None
}
}
Alias { Alias {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
ann, ann,
@ -1533,10 +1652,10 @@ fn to_pending_def<'a>(
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
typ: ann, typ: ann,
} => { } => {
let kind = if matches!(def, Alias { .. }) { let (kind, shadow_kind) = if matches!(def, Alias { .. }) {
AliasKind::Structural (AliasKind::Structural, ShadowKind::Alias)
} else { } else {
AliasKind::Opaque (AliasKind::Opaque, ShadowKind::Opaque)
}; };
let region = Region::span_across(&name.region, &ann.region); let region = Region::span_across(&name.region, &ann.region);
@ -1571,7 +1690,7 @@ fn to_pending_def<'a>(
return Some(( return Some((
Output::default(), Output::default(),
PendingDef::InvalidAlias { kind }, PendingTypeDef::InvalidAlias { kind },
)); ));
} }
} }
@ -1582,7 +1701,7 @@ fn to_pending_def<'a>(
value: symbol, value: symbol,
}; };
let pending_def = PendingDef::Alias { let pending_def = PendingTypeDef::Alias {
name, name,
vars: can_rigids, vars: can_rigids,
ann, ann,
@ -1593,51 +1712,152 @@ fn to_pending_def<'a>(
} }
Err((original_region, loc_shadowed_symbol, _new_symbol)) => { Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
env.problem(Problem::ShadowingInAnnotation { env.problem(Problem::Shadowing {
original_region, original_region,
shadow: loc_shadowed_symbol, shadow: loc_shadowed_symbol,
kind: shadow_kind,
}); });
Some((Output::default(), PendingDef::InvalidAlias { kind })) Some((Output::default(), PendingTypeDef::InvalidAlias { kind }))
} }
} }
} }
Ability { .. } => todo_abilities!(), Ability {
header: TypeHeader { name, vars },
members,
loc_has: _,
} => {
let name = match scope.introduce_without_shadow_symbol(
name.value.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
name.region,
) {
Ok(symbol) => Loc::at(name.region, symbol),
Err((original_region, shadowed_symbol)) => {
env.problem(Problem::Shadowing {
original_region,
shadow: shadowed_symbol,
kind: ShadowKind::Ability,
});
return Some((Output::default(), PendingTypeDef::InvalidAbility));
}
};
Expect(_condition) => todo!(), if !vars.is_empty() {
// Disallow ability type arguments, at least for now.
let variables_region = Region::across_all(vars.iter().map(|v| &v.region));
SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { env.problem(Problem::AbilityHasTypeVariables {
to_pending_def(env, var_store, sub_def, scope, pattern_type) name: name.value,
variables_region,
});
return Some((Output::default(), PendingTypeDef::InvalidAbility));
}
let pending_ability = PendingTypeDef::Ability {
name,
// We'll handle adding the member symbols later on when we do all value defs.
members,
};
Some((Output::default(), pending_ability))
} }
NotYetImplemented(s) => todo!("{}", s),
} }
} }
fn pending_typed_body<'a>( fn to_pending_value_def<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
loc_pattern: &'a Loc<ast::Pattern<'a>>,
loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
loc_expr: &'a Loc<ast::Expr<'a>>,
var_store: &mut VarStore, var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>,
scope: &mut Scope, scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType, pattern_type: PatternType,
) -> (Output, PendingDef<'a>) { ) -> Option<(Output, PendingValueDef<'a>)> {
// This takes care of checking for shadowing and adding idents to scope. use ast::ValueDef::*;
let (output, loc_can_pattern) = canonicalize_pattern(
env,
var_store,
scope,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
( match def {
output, Annotation(loc_pattern, loc_ann) => {
PendingDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr), // This takes care of checking for shadowing and adding idents to scope.
) let (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
abilities_store,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
Some((
output,
PendingValueDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann),
))
}
Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
abilities_store,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
Some((
output,
PendingValueDef::Body(loc_pattern, loc_can_pattern, loc_expr),
))
}
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => {
if ann_pattern.value.equivalent(&body_pattern.value) {
// NOTE: Pick the body pattern, picking the annotation one is
// incorrect in the presence of optional record fields!
//
// { x, y } : { x : Int, y ? Bool }*
// { x, y ? False } = rec
//
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
abilities_store,
pattern_type,
&body_pattern.value,
body_pattern.region,
);
Some((
output,
PendingValueDef::TypedBody(body_pattern, loc_can_pattern, ann_type, body_expr),
))
} else {
// the pattern of the annotation does not match the pattern of the body direc
env.problems.push(Problem::SignatureDefMismatch {
annotation_pattern: ann_pattern.region,
def_pattern: body_pattern.region,
});
// TODO: Should we instead build some PendingValueDef::InvalidAnnotatedBody ? This would
// remove the `Option` on this function (and be probably more reliable for further
// problem/error reporting)
None
}
}
Expect(_condition) => todo!(),
}
} }
/// Make aliases recursive /// Make aliases recursive

View file

@ -161,7 +161,6 @@ pub enum Expr {
variant_var: Variable, variant_var: Variable,
ext_var: Variable, ext_var: Variable,
name: TagName, name: TagName,
arguments: Vec<(Variable, Loc<Expr>)>,
}, },
/// A wrapping of an opaque type, like `$Age 21` /// A wrapping of an opaque type, like `$Age 21`
@ -813,7 +812,6 @@ pub fn canonicalize_expr<'a>(
( (
ZeroArgumentTag { ZeroArgumentTag {
name: TagName::Global((*tag).into()), name: TagName::Global((*tag).into()),
arguments: vec![],
variant_var, variant_var,
closure_name: symbol, closure_name: symbol,
ext_var, ext_var,
@ -831,7 +829,6 @@ pub fn canonicalize_expr<'a>(
( (
ZeroArgumentTag { ZeroArgumentTag {
name: TagName::Private(symbol), name: TagName::Private(symbol),
arguments: vec![],
variant_var, variant_var,
ext_var, ext_var,
closure_name: lambda_set_symbol, closure_name: lambda_set_symbol,
@ -1560,15 +1557,13 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
variant_var, variant_var,
ext_var, ext_var,
name, name,
arguments,
} => { } => {
todo!( todo!(
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}", "Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}",
closure_name, closure_name,
variant_var, variant_var,
ext_var, ext_var,
name, name,
arguments
); );
} }

View file

@ -1,6 +1,7 @@
#![warn(clippy::dbg_macro)] #![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)] #![allow(clippy::large_enum_variant)]
pub mod abilities;
pub mod annotation; pub mod annotation;
pub mod builtins; pub mod builtins;
pub mod constraint; pub mod constraint;

View file

@ -589,7 +589,8 @@ fn fix_values_captured_in_closure_pattern(
| Shadowed(..) | Shadowed(..)
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| OpaqueNotInScope(..) => (), | OpaqueNotInScope(..)
| AbilityMemberSpecialization { .. } => (),
} }
} }
@ -646,6 +647,7 @@ fn fix_values_captured_in_closure_expr(
| Var(_) | Var(_)
| EmptyRecord | EmptyRecord
| RuntimeError(_) | RuntimeError(_)
| ZeroArgumentTag { .. }
| Accessor { .. } => {} | Accessor { .. } => {}
List { loc_elems, .. } => { List { loc_elems, .. } => {
@ -712,7 +714,7 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
} }
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => { Tag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() { for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols); fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
} }

View file

@ -6,7 +6,7 @@ use roc_module::called_via::BinOp::Pizza;
use roc_module::called_via::{BinOp, CalledVia}; use roc_module::called_via::{BinOp, CalledVia};
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_parse::ast::Expr::{self, *}; use roc_parse::ast::Expr::{self, *};
use roc_parse::ast::{AssignedField, Def, WhenBranch}; use roc_parse::ast::{AssignedField, Def, TypeDef, ValueDef, WhenBranch};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
// BinOp precedence logic adapted from Gluon by Markus Westerlind // BinOp precedence logic adapted from Gluon by Markus Westerlind
@ -88,15 +88,21 @@ fn desugar_def_helps<'a>(
}) })
} }
pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> { fn desugar_type_def<'a>(def: &'a TypeDef<'a>) -> TypeDef<'a> {
use roc_parse::ast::Def::*; use TypeDef::*;
match def { match def {
Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)),
SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def),
alias @ Alias { .. } => *alias, alias @ Alias { .. } => *alias,
opaque @ Opaque { .. } => *opaque, opaque @ Opaque { .. } => *opaque,
ability @ Ability { .. } => *ability, ability @ Ability { .. } => *ability,
}
}
fn desugar_value_def<'a>(arena: &'a Bump, def: &'a ValueDef<'a>) -> ValueDef<'a> {
use ValueDef::*;
match def {
Body(loc_pattern, loc_expr) => Body(loc_pattern, desugar_expr(arena, loc_expr)),
ann @ Annotation(_, _) => *ann, ann @ Annotation(_, _) => *ann,
AnnotatedBody { AnnotatedBody {
ann_pattern, ann_pattern,
@ -115,6 +121,16 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
let desugared_condition = &*arena.alloc(desugar_expr(arena, condition)); let desugared_condition = &*arena.alloc(desugar_expr(arena, condition));
Expect(desugared_condition) Expect(desugared_condition)
} }
}
}
pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
use roc_parse::ast::Def::*;
match def {
Type(type_def) => Type(desugar_type_def(type_def)),
Value(value_def) => Value(desugar_value_def(arena, value_def)),
SpaceBefore(def, _) | SpaceAfter(def, _) => desugar_def(arena, def),
NotYetImplemented(s) => todo!("{}", s), NotYetImplemented(s) => todo!("{}", s),
} }
} }

View file

@ -1,3 +1,4 @@
use crate::abilities::AbilitiesStore;
use crate::annotation::freshen_opaque_def; use crate::annotation::freshen_opaque_def;
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
@ -10,7 +11,7 @@ use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{LambdaSet, Type}; use roc_types::types::{LambdaSet, Type};
@ -62,6 +63,17 @@ pub enum Pattern {
SingleQuote(char), SingleQuote(char),
Underscore, Underscore,
/// An identifier that marks a specialization of an ability member.
/// For example, given an ability member definition `hash : a -> U64 | a has Hash`,
/// there may be the specialization `hash : Bool -> U64`. In this case we generate a
/// new symbol for the specialized "hash" identifier.
AbilityMemberSpecialization {
/// The symbol for this specialization.
ident: Symbol,
/// The ability name being specialized.
specializes: Symbol,
},
// Runtime Exceptions // Runtime Exceptions
Shadowed(Region, Loc<Ident>, Symbol), Shadowed(Region, Loc<Ident>, Symbol),
OpaqueNotInScope(Loc<Ident>), OpaqueNotInScope(Loc<Ident>),
@ -101,6 +113,11 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
symbols.push(*symbol); symbols.push(*symbol);
} }
AbilityMemberSpecialization { ident, specializes } => {
symbols.push(*ident);
symbols.push(*specializes);
}
AppliedTag { arguments, .. } => { AppliedTag { arguments, .. } => {
for (_, nested) in arguments { for (_, nested) in arguments {
symbols_from_pattern_help(&nested.value, symbols); symbols_from_pattern_help(&nested.value, symbols);
@ -136,6 +153,56 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
} }
} }
pub fn canonicalize_def_header_pattern<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType,
pattern: &ast::Pattern<'a>,
region: Region,
) -> (Output, Loc<Pattern>) {
use roc_parse::ast::Pattern::*;
let mut output = Output::default();
match pattern {
// Identifiers that shadow ability members may appear (and may only appear) at the header of a def.
Identifier(name) => match scope.introduce_or_shadow_ability_member(
(*name).into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
abilities_store,
) {
Ok((symbol, shadowing_ability_member)) => {
output.references.bound_symbols.insert(symbol);
let can_pattern = match shadowing_ability_member {
// A fresh identifier.
None => Pattern::Identifier(symbol),
// Likely a specialization of an ability.
Some(ability_member_name) => Pattern::AbilityMemberSpecialization {
ident: symbol,
specializes: ability_member_name,
},
};
(output, Loc::at(region, can_pattern))
}
Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
output.references.bound_symbols.insert(new_symbol);
let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol);
(output, Loc::at(region, can_pattern))
}
},
_ => canonicalize_pattern(env, var_store, scope, pattern_type, pattern, region),
}
}
pub fn canonicalize_pattern<'a>( pub fn canonicalize_pattern<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &mut VarStore, var_store: &mut VarStore,
@ -164,6 +231,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
output.references.bound_symbols.insert(new_symbol); output.references.bound_symbols.insert(new_symbol);
@ -412,6 +480,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// No matter what the other patterns // No matter what the other patterns
@ -484,6 +553,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// No matter what the other patterns // No matter what the other patterns
@ -594,7 +664,12 @@ fn add_bindings_from_patterns(
use Pattern::*; use Pattern::*;
match pattern { match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => { Identifier(symbol)
| Shadowed(_, _, symbol)
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
answer.push((*symbol, *region)); answer.push((*symbol, *region));
} }
AppliedTag { AppliedTag {

View file

@ -6,6 +6,8 @@ use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, AliasKind, Type}; use roc_types::types::{Alias, AliasKind, Type};
use crate::abilities::AbilitiesStore;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Scope { pub struct Scope {
/// All the identifiers in scope, mapped to were they were defined and /// All the identifiers in scope, mapped to were they were defined and
@ -19,6 +21,9 @@ pub struct Scope {
/// The type aliases currently in scope /// The type aliases currently in scope
pub aliases: SendMap<Symbol, Alias>, pub aliases: SendMap<Symbol, Alias>,
/// The abilities currently in scope, and their implementors.
pub abilities: SendMap<Symbol, Region>,
/// The current module being processed. This will be used to turn /// The current module being processed. This will be used to turn
/// unqualified idents into Symbols. /// unqualified idents into Symbols.
home: ModuleId, home: ModuleId,
@ -62,6 +67,8 @@ impl Scope {
idents: Symbol::default_in_scope(), idents: Symbol::default_in_scope(),
symbols: SendMap::default(), symbols: SendMap::default(),
aliases, aliases,
// TODO(abilities): default abilities in scope
abilities: SendMap::default(),
} }
} }
@ -176,6 +183,11 @@ impl Scope {
/// ///
/// Returns Err if this would shadow an existing ident, including the /// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name. /// Symbol and Region of the ident we already had in scope under that name.
///
/// If this ident shadows an existing one, a new ident is allocated for the shadow. This is
/// done so that all identifiers have unique symbols, which is important in particular when
/// we generate code for value identifiers.
/// If this behavior is undesirable, use [`Self::introduce_without_shadow_symbol`].
pub fn introduce( pub fn introduce(
&mut self, &mut self,
ident: Ident, ident: Ident,
@ -198,25 +210,98 @@ impl Scope {
Err((original_region, shadow, symbol)) Err((original_region, shadow, symbol))
} }
None => { None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
// If this IdentId was already added previously }
// when the value was exposed in the module header, }
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) { /// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol.
Some(ident_id) => ident_id, pub fn introduce_without_shadow_symbol(
None => all_ident_ids.add(ident.clone()), &mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>)> {
match self.idents.get(&ident) {
Some(&(_, original_region)) => {
let shadow = Loc {
value: ident.clone(),
region,
}; };
Err((original_region, shadow))
}
None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
}
}
let symbol = Symbol::new(self.home, ident_id); /// Like [Self::introduce], but handles the case of when an ident matches an ability member
/// name. In such cases a new symbol is created for the ident (since it's expected to be a
/// specialization of the ability member), but the ident is not added to the ident->symbol map.
///
/// If the ident does not match an ability name, the behavior of this function is exactly that
/// of `introduce`.
#[allow(clippy::type_complexity)]
pub fn introduce_or_shadow_ability_member(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
abilities_store: &AbilitiesStore,
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
match self.idents.get(&ident) {
Some(&(original_symbol, original_region)) => {
let shadow_ident_id = all_ident_ids.add(ident.clone());
let shadow_symbol = Symbol::new(self.home, shadow_ident_id);
self.symbols.insert(symbol, region); self.symbols.insert(shadow_symbol, region);
self.idents.insert(ident, (symbol, region));
Ok(symbol) if abilities_store.is_ability_member_name(original_symbol) {
// Add a symbol for the shadow, but don't re-associate the member name.
Ok((shadow_symbol, Some(original_symbol)))
} else {
// This is an illegal shadow.
let shadow = Loc {
value: ident.clone(),
region,
};
self.idents.insert(ident, (shadow_symbol, region));
Err((original_region, shadow, shadow_symbol))
}
}
None => {
let new_symbol =
self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region);
Ok((new_symbol, None))
} }
} }
} }
fn commit_introduction(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Symbol {
// If this IdentId was already added previously
// when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone()),
};
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
symbol
}
/// Ignore an identifier. /// Ignore an identifier.
/// ///
/// Used for record guards like { x: Just _ } /// Used for record guards like { x: Just _ }

View file

@ -881,7 +881,9 @@ pub fn constrain_expr(
name, name,
arguments, arguments,
} => { } => {
let mut vars = Vec::with_capacity(arguments.len()); // +2 because we push all the arguments, plus variant_var and ext_var
let num_vars = arguments.len() + 2;
let mut vars = Vec::with_capacity(num_vars);
let mut types = Vec::with_capacity(arguments.len()); let mut types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len()); let mut arg_cons = Vec::with_capacity(arguments.len());
@ -923,27 +925,8 @@ pub fn constrain_expr(
variant_var, variant_var,
ext_var, ext_var,
name, name,
arguments,
closure_name, closure_name,
} => { } => {
let mut vars = Vec::with_capacity(arguments.len());
let mut types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len());
for (var, loc_expr) in arguments {
let arg_con = constrain_expr(
constraints,
env,
loc_expr.region,
&loc_expr.value,
Expected::NoExpectation(Type::Variable(*var)),
);
arg_cons.push(arg_con);
vars.push(*var);
types.push(Type::Variable(*var));
}
let union_con = constraints.equal_types_with_storage( let union_con = constraints.equal_types_with_storage(
Type::FunctionOrTagUnion( Type::FunctionOrTagUnion(
name.clone(), name.clone(),
@ -953,19 +936,14 @@ pub fn constrain_expr(
expected.clone(), expected.clone(),
Category::TagApply { Category::TagApply {
tag_name: name.clone(), tag_name: name.clone(),
args_count: arguments.len(), args_count: 0,
}, },
region, region,
*variant_var, *variant_var,
); );
vars.push(*variant_var); constraints.exists_many([*variant_var, *ext_var], [union_con])
vars.push(*ext_var);
arg_cons.push(union_con);
constraints.exists_many(vars, arg_cons)
} }
OpaqueRef { OpaqueRef {
opaque_var, opaque_var,
name, name,

View file

@ -50,7 +50,13 @@ fn headers_from_annotation_help(
headers: &mut SendMap<Symbol, Loc<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool { ) -> bool {
match pattern { match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => { Identifier(symbol)
| Shadowed(_, _, symbol)
// TODO(abilities): handle linking the member def to the specialization ident
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
let typ = Loc::at(annotation.region, annotation.value.clone()); let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*symbol, typ); headers.insert(*symbol, typ);
true true
@ -182,7 +188,12 @@ pub fn constrain_pattern(
// Erroneous patterns don't add any constraints. // Erroneous patterns don't add any constraints.
} }
Identifier(symbol) | Shadowed(_, _, symbol) => { Identifier(symbol) | Shadowed(_, _, symbol)
// TODO(abilities): handle linking the member def to the specialization ident
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
if could_be_a_tag_union(expected.get_type_ref()) { if could_be_a_tag_union(expected.get_type_ref()) {
state state
.constraints .constraints

View file

@ -2,27 +2,35 @@ use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, INDENT}; use crate::spaces::{fmt_spaces, INDENT};
use crate::Buf; use crate::Buf;
use roc_parse::ast::{AbilityDemand, Def, Expr, ExtractSpaces, Pattern, TypeHeader}; use roc_parse::ast::{AbilityMember, Def, Expr, ExtractSpaces, Pattern, TypeHeader};
use roc_region::all::Loc; use roc_region::all::Loc;
/// A Located formattable value is also formattable /// A Located formattable value is also formattable
impl<'a> Formattable for Def<'a> { impl<'a> Formattable for Def<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
use roc_parse::ast::Def::*; use roc_parse::ast::Def::*;
use roc_parse::ast::TypeDef::*;
use roc_parse::ast::ValueDef::*;
match self { match self {
Alias { ann, .. } => ann.is_multiline(), Type(def) => match def {
Opaque { typ, .. } => typ.is_multiline(), Alias { ann, .. } => ann.is_multiline(),
Annotation(loc_pattern, loc_annotation) => { Opaque { typ, .. } => typ.is_multiline(),
loc_pattern.is_multiline() || loc_annotation.is_multiline() Ability { members, .. } => members.iter().any(|d| d.is_multiline()),
} },
Body(loc_pattern, loc_expr) => loc_pattern.is_multiline() || loc_expr.is_multiline(), Value(def) => match def {
AnnotatedBody { .. } => true, Annotation(loc_pattern, loc_annotation) => {
Expect(loc_expr) => loc_expr.is_multiline(), loc_pattern.is_multiline() || loc_annotation.is_multiline()
}
Body(loc_pattern, loc_expr) => {
loc_pattern.is_multiline() || loc_expr.is_multiline()
}
AnnotatedBody { .. } => true,
Expect(loc_expr) => loc_expr.is_multiline(),
},
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => { SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline() spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
} }
Ability { demands, .. } => demands.iter().any(|d| d.is_multiline()),
NotYetImplemented(s) => todo!("{}", s), NotYetImplemented(s) => todo!("{}", s),
} }
} }
@ -35,104 +43,110 @@ impl<'a> Formattable for Def<'a> {
indent: u16, indent: u16,
) { ) {
use roc_parse::ast::Def::*; use roc_parse::ast::Def::*;
use roc_parse::ast::TypeDef::*;
use roc_parse::ast::ValueDef::*;
match self { match self {
Annotation(loc_pattern, loc_annotation) => { Type(def) => match def {
loc_pattern.format(buf, indent); Alias {
if loc_annotation.is_multiline() { header: TypeHeader { name, vars },
buf.push_str(" :"); ann,
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + INDENT,
);
} else {
buf.spaces(1);
buf.push_str(":");
buf.spaces(1);
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
} }
} | Opaque {
Alias { header: TypeHeader { name, vars },
header: TypeHeader { name, vars }, typ: ann,
ann, } => {
} buf.indent(indent);
| Opaque { buf.push_str(name.value);
header: TypeHeader { name, vars },
typ: ann,
} => {
buf.indent(indent);
buf.push_str(name.value);
for var in *vars { for var in *vars {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
buf.push_str(match def {
Alias { .. } => " :",
Opaque { .. } => " :=",
_ => unreachable!(),
});
buf.spaces(1); buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
ann.format(buf, indent + INDENT)
} }
Ability {
header: TypeHeader { name, vars },
loc_has: _,
members,
} => {
buf.indent(indent);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
buf.push_str(match self { buf.push_str(" has");
Alias { .. } => " :",
Opaque { .. } => " :=",
_ => unreachable!(),
});
buf.spaces(1);
ann.format(buf, indent + INDENT) if !self.is_multiline() {
} debug_assert_eq!(members.len(), 1);
Ability { buf.push_str(" ");
header: TypeHeader { name, vars }, members[0].format(buf, indent + INDENT);
loc_has: _, } else {
demands, for demand in members.iter() {
} => { buf.newline();
buf.indent(indent); buf.indent(indent + INDENT);
buf.push_str(name.value); demand.format(buf, indent + INDENT);
for var in *vars { }
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
buf.push_str(" has");
if !self.is_multiline() {
debug_assert_eq!(demands.len(), 1);
buf.push_str(" ");
demands[0].format(buf, indent + INDENT);
} else {
for demand in demands.iter() {
buf.newline();
buf.indent(indent + INDENT);
demand.format(buf, indent + INDENT);
} }
} }
} },
Body(loc_pattern, loc_expr) => { Value(def) => match def {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent); Annotation(loc_pattern, loc_annotation) => {
} loc_pattern.format(buf, indent);
Expect(condition) => fmt_expect(buf, condition, self.is_multiline(), indent), if loc_annotation.is_multiline() {
AnnotatedBody { buf.push_str(" :");
ann_pattern, loc_annotation.format_with_options(
ann_type, buf,
comment, Parens::NotNeeded,
body_pattern, Newlines::Yes,
body_expr, indent + INDENT,
} => { );
ann_pattern.format(buf, indent); } else {
buf.push_str(" :"); buf.spaces(1);
buf.spaces(1); buf.push_str(":");
ann_type.format(buf, indent); buf.spaces(1);
if let Some(comment_str) = comment { loc_annotation.format_with_options(
buf.push_str(" #"); buf,
buf.spaces(1); Parens::NotNeeded,
buf.push_str(comment_str.trim()); Newlines::No,
indent,
);
}
} }
buf.newline(); Body(loc_pattern, loc_expr) => {
fmt_body(buf, &body_pattern.value, &body_expr.value, indent); fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
} }
Expect(condition) => fmt_expect(buf, condition, self.is_multiline(), indent),
AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
} => {
ann_pattern.format(buf, indent);
buf.push_str(" :");
buf.spaces(1);
ann_type.format(buf, indent);
if let Some(comment_str) = comment {
buf.push_str(" #");
buf.spaces(1);
buf.push_str(comment_str.trim());
}
buf.newline();
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
}
},
SpaceBefore(sub_def, spaces) => { SpaceBefore(sub_def, spaces) => {
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
@ -195,7 +209,7 @@ pub fn fmt_body<'a, 'buf>(
} }
} }
impl<'a> Formattable for AbilityDemand<'a> { impl<'a> Formattable for AbilityMember<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
self.name.value.is_multiline() || self.typ.is_multiline() self.name.value.is_multiline() || self.typ.is_multiline()
} }

View file

@ -5878,6 +5878,48 @@ fn run_low_level<'a, 'ctx, 'env>(
.build_int_cast_sign_flag(arg, to, to_signed, "inc_cast") .build_int_cast_sign_flag(arg, to, to_signed, "inc_cast")
.into() .into()
} }
NumToFloatCast => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
match arg_layout {
Layout::Builtin(Builtin::Int(width)) => {
// Converting from int to float
let int_val = arg.into_int_value();
let dest = basic_type_from_layout(env, layout).into_float_type();
if width.is_signed() {
env.builder
.build_signed_int_to_float(int_val, dest, "signed_int_to_float")
.into()
} else {
env.builder
.build_unsigned_int_to_float(int_val, dest, "unsigned_int_to_float")
.into()
}
}
Layout::Builtin(Builtin::Float(_)) => {
// Converting from float to float - e.g. F64 to F32, or vice versa
let dest = basic_type_from_layout(env, layout).into_float_type();
env.builder
.build_float_cast(arg.into_float_value(), dest, "cast_float_to_float")
.into()
}
Layout::Builtin(Builtin::Decimal) => {
todo!("Support converting Dec values to floats.");
}
other => {
unreachable!("Tried to do a float cast to non-float layout {:?}", other);
}
}
}
NumToFloatChecked => {
// NOTE: There's a NumToIntChecked implementation above,
// which could be useful to look at when implementing this.
todo!("implement checked float conversion");
}
Eq => { Eq => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);

View file

@ -2,7 +2,7 @@ use bumpalo::{self, collections::Vec};
use std::fmt::Write; use std::fmt::Write;
use code_builder::Align; use code_builder::Align;
use roc_builtins::bitcode::IntWidth; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::low_level::{LowLevel, LowLevelWrapperType};
@ -17,10 +17,10 @@ use roc_error_macros::internal_error;
use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout};
use crate::layout::{CallConv, ReturnMethod, WasmLayout}; use crate::layout::{CallConv, ReturnMethod, WasmLayout};
use crate::low_level::LowLevelCall; use crate::low_level::{call_higher_order_lowlevel, LowLevelCall};
use crate::storage::{Storage, StoredValue, StoredValueKind}; use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol};
use crate::wasm_module::sections::{DataMode, DataSegment}; use crate::wasm_module::sections::{DataMode, DataSegment, Limits};
use crate::wasm_module::{ use crate::wasm_module::{
code_builder, CodeBuilder, ExportType, LocalId, Signature, SymInfo, ValueType, WasmModule, code_builder, CodeBuilder, ExportType, LocalId, Signature, SymInfo, ValueType, WasmModule,
}; };
@ -29,6 +29,22 @@ use crate::{
PTR_TYPE, TARGET_INFO, PTR_TYPE, TARGET_INFO,
}; };
#[derive(Clone, Copy, Debug)]
pub enum ProcSource {
Roc,
Helper,
/// Wrapper function for higher-order calls from Zig to Roc
HigherOrderWrapper(usize),
}
#[derive(Debug)]
pub struct ProcLookupData<'a> {
pub name: Symbol,
pub layout: ProcLayout<'a>,
pub linker_index: u32,
pub source: ProcSource,
}
pub struct WasmBackend<'a> { pub struct WasmBackend<'a> {
pub env: &'a Env<'a>, pub env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
@ -37,9 +53,9 @@ pub struct WasmBackend<'a> {
module: WasmModule<'a>, module: WasmModule<'a>,
layout_ids: LayoutIds<'a>, layout_ids: LayoutIds<'a>,
next_constant_addr: u32, next_constant_addr: u32,
fn_index_offset: u32, pub fn_index_offset: u32,
called_preload_fns: Vec<'a, u32>, called_preload_fns: Vec<'a, u32>,
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>, pub proc_lookup: Vec<'a, ProcLookupData<'a>>,
helper_proc_gen: CodeGenHelp<'a>, helper_proc_gen: CodeGenHelp<'a>,
// Function-level data // Function-level data
@ -56,7 +72,7 @@ impl<'a> WasmBackend<'a> {
env: &'a Env<'a>, env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
layout_ids: LayoutIds<'a>, layout_ids: LayoutIds<'a>,
proc_lookup: Vec<'a, (Symbol, ProcLayout<'a>, u32)>, proc_lookup: Vec<'a, ProcLookupData<'a>>,
mut module: WasmModule<'a>, mut module: WasmModule<'a>,
fn_index_offset: u32, fn_index_offset: u32,
helper_proc_gen: CodeGenHelp<'a>, helper_proc_gen: CodeGenHelp<'a>,
@ -109,31 +125,45 @@ impl<'a> WasmBackend<'a> {
} }
} }
pub fn generate_helpers(&mut self) -> Vec<'a, Proc<'a>> { pub fn get_helpers(&mut self) -> Vec<'a, Proc<'a>> {
self.helper_proc_gen.take_procs() self.helper_proc_gen.take_procs()
} }
fn register_helper_proc(&mut self, new_proc_info: (Symbol, ProcLayout<'a>)) { pub fn register_helper_proc(
let (new_proc_sym, new_proc_layout) = new_proc_info; &mut self,
let wasm_fn_index = self.proc_lookup.len() as u32; symbol: Symbol,
layout: ProcLayout<'a>,
source: ProcSource,
) -> u32 {
let proc_index = self.proc_lookup.len();
let wasm_fn_index = self.fn_index_offset + proc_index as u32;
let linker_sym_index = self.module.linking.symbol_table.len() as u32; let linker_sym_index = self.module.linking.symbol_table.len() as u32;
let name = self let name = self
.layout_ids .layout_ids
.get_toplevel(new_proc_sym, &new_proc_layout) .get_toplevel(symbol, &layout)
.to_symbol_string(new_proc_sym, self.interns); .to_symbol_string(symbol, self.interns);
self.proc_lookup.push(ProcLookupData {
name: symbol,
layout,
linker_index: linker_sym_index,
source,
});
self.proc_lookup
.push((new_proc_sym, new_proc_layout, linker_sym_index));
let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined { let linker_symbol = SymInfo::Function(WasmObjectSymbol::Defined {
flags: 0, flags: 0,
index: wasm_fn_index, index: wasm_fn_index,
name, name,
}); });
self.module.linking.symbol_table.push(linker_symbol); self.module.linking.symbol_table.push(linker_symbol);
wasm_fn_index
} }
pub fn finalize(self) -> (WasmModule<'a>, Vec<'a, u32>) { pub fn finalize(mut self) -> (WasmModule<'a>, Vec<'a, u32>) {
let fn_table_size = 1 + self.module.element.max_table_index();
self.module.table.function_table.limits = Limits::MinMax(fn_table_size, fn_table_size);
(self.module, self.called_preload_fns) (self.module, self.called_preload_fns)
} }
@ -150,8 +180,12 @@ impl<'a> WasmBackend<'a> {
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
pub fn register_symbol_debug_names(&self) {} pub fn register_symbol_debug_names(&self) {}
pub fn get_fn_table_index(&mut self, fn_index: u32) -> i32 {
self.module.element.get_fn_table_index(fn_index)
}
/// Create an IR Symbol for an anonymous value (such as ListLiteral) /// Create an IR Symbol for an anonymous value (such as ListLiteral)
fn create_symbol(&mut self, debug_name: &str) -> Symbol { pub fn create_symbol(&mut self, debug_name: &str) -> Symbol {
let ident_ids = self let ident_ids = self
.interns .interns
.all_ident_ids .all_ident_ids
@ -204,7 +238,7 @@ impl<'a> WasmBackend<'a> {
let ret_layout = WasmLayout::new(&proc.ret_layout); let ret_layout = WasmLayout::new(&proc.ret_layout);
let ret_type = match ret_layout.return_method(CallConv::C) { let ret_type = match ret_layout.return_method(CallConv::C) {
Primitive(ty) => Some(ty), Primitive(ty, _) => Some(ty),
NoReturnValue => None, NoReturnValue => None,
WriteToPointerArg => { WriteToPointerArg => {
self.storage.arg_types.push(PTR_TYPE); self.storage.arg_types.push(PTR_TYPE);
@ -251,20 +285,156 @@ impl<'a> WasmBackend<'a> {
); );
} }
fn append_proc_debug_name(&mut self, name: Symbol) { fn append_proc_debug_name(&mut self, sym: Symbol) {
let proc_index = self let proc_index = self
.proc_lookup .proc_lookup
.iter() .iter()
.position(|(n, _, _)| *n == name) .position(|ProcLookupData { name, .. }| *name == sym)
.unwrap(); .unwrap();
let wasm_fn_index = self.fn_index_offset + proc_index as u32; let wasm_fn_index = self.fn_index_offset + proc_index as u32;
let mut debug_name = bumpalo::collections::String::with_capacity_in(64, self.env.arena); let mut debug_name = bumpalo::collections::String::with_capacity_in(64, self.env.arena);
write!(debug_name, "{:?}", name).unwrap(); write!(debug_name, "{:?}", sym).unwrap();
let name_bytes = debug_name.into_bytes().into_bump_slice(); let name_bytes = debug_name.into_bytes().into_bump_slice();
self.module.names.append_function(wasm_fn_index, name_bytes); self.module.names.append_function(wasm_fn_index, name_bytes);
} }
/// Build a wrapper around a Roc procedure so that it can be called from our higher-order Zig builtins.
///
/// The generic Zig code passes *pointers* to all of the argument values (e.g. on the heap in a List).
/// Numbers up to 64 bits are passed by value, so we need to load them from the provided pointer.
/// Everything else is passed by reference, so we can just pass the pointer through.
///
/// NOTE: If the builtins expected the return pointer first and closure data last, we could eliminate the wrapper
/// when all args are pass-by-reference and non-zero size. But currently we need it to swap those around.
pub fn build_higher_order_wrapper(
&mut self,
wrapper_lookup_idx: usize,
inner_lookup_idx: usize,
) {
use Align::*;
use ValueType::*;
let ProcLookupData {
name: wrapper_name,
layout: wrapper_proc_layout,
..
} = self.proc_lookup[wrapper_lookup_idx];
let wrapper_arg_layouts = wrapper_proc_layout.arguments;
// Our convention is that the last arg of the wrapper is the heap return pointer
let heap_return_ptr_id = LocalId(wrapper_arg_layouts.len() as u32 - 1);
let inner_ret_layout = match wrapper_arg_layouts.last() {
Some(Layout::Boxed(inner)) => WasmLayout::new(inner),
x => internal_error!("Higher-order wrapper: invalid return layout {:?}", x),
};
let mut n_inner_wasm_args = 0;
let ret_type_and_size = match inner_ret_layout.return_method(CallConv::C) {
ReturnMethod::NoReturnValue => None,
ReturnMethod::Primitive(ty, size) => {
// If the inner function returns a primitive, load the address to store it at
// After the call, it will be under the call result in the value stack
self.code_builder.get_local(heap_return_ptr_id);
Some((ty, size))
}
ReturnMethod::WriteToPointerArg => {
// If the inner function writes to a return pointer, load its address
self.code_builder.get_local(heap_return_ptr_id);
n_inner_wasm_args += 1;
None
}
x => internal_error!("A Roc function should never use ReturnMethod {:?}", x),
};
// Load all the arguments for the inner function
for (i, wrapper_arg) in wrapper_arg_layouts.iter().enumerate() {
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner)
let is_return_pointer = i == wrapper_arg_layouts.len() - 1; // Skip return pointer (may not be an arg for inner. And if it is, swaps from end to start)
if is_closure_data || is_return_pointer || wrapper_arg.stack_size(TARGET_INFO) == 0 {
continue;
}
n_inner_wasm_args += 1;
// Load wrapper argument. They're all pointers.
self.code_builder.get_local(LocalId(i as u32));
// Dereference any primitive-valued arguments
match wrapper_arg {
Layout::Boxed(inner_arg) => match inner_arg {
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
self.code_builder.i32_load8_u(Bytes1, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => {
self.code_builder.i32_load16_u(Bytes2, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => {
self.code_builder.i32_load(Bytes4, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => {
self.code_builder.i64_load(Bytes8, 0);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
self.code_builder.f32_load(Bytes4, 0);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
self.code_builder.f64_load(Bytes8, 0);
}
Layout::Builtin(Builtin::Bool) => {
self.code_builder.i32_load8_u(Bytes1, 0);
}
_ => {
// Any other layout is a pointer, which we've already loaded. Nothing to do!
}
},
x => internal_error!("Higher-order wrapper: expected a Box layout, got {:?}", x),
}
}
// If the inner function has closure data, it's the last arg of the inner fn
let closure_data_layout = wrapper_arg_layouts[0];
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
self.code_builder.get_local(LocalId(0));
}
// Call the wrapped inner function
let lookup = &self.proc_lookup[inner_lookup_idx];
let inner_wasm_fn_index = self.fn_index_offset + inner_lookup_idx as u32;
let has_return_val = ret_type_and_size.is_some();
self.code_builder.call(
inner_wasm_fn_index,
lookup.linker_index,
n_inner_wasm_args,
has_return_val,
);
// If the inner function returns a primitive, store it to the address we loaded at the very beginning
if let Some((ty, size)) = ret_type_and_size {
match (ty, size) {
(I64, 8) => self.code_builder.i64_store(Bytes8, 0),
(I32, 4) => self.code_builder.i32_store(Bytes4, 0),
(I32, 2) => self.code_builder.i32_store16(Bytes2, 0),
(I32, 1) => self.code_builder.i32_store8(Bytes1, 0),
(F32, 4) => self.code_builder.f32_store(Bytes4, 0),
(F64, 8) => self.code_builder.f64_store(Bytes8, 0),
_ => {
internal_error!("Cannot store {:?} with alignment of {:?}", ty, size);
}
}
}
// Write empty function header (local variables array with zero length)
self.code_builder.build_fn_header_and_footer(&[], 0, None);
self.module.add_function_signature(Signature {
param_types: bumpalo::vec![in self.env.arena; I32; wrapper_arg_layouts.len()],
ret_type: None,
});
self.append_proc_debug_name(wrapper_name);
self.reset();
}
/********************************************************** /**********************************************************
STATEMENTS STATEMENTS
@ -551,8 +721,8 @@ impl<'a> WasmBackend<'a> {
} }
// If any new specializations were created, register their symbol data // If any new specializations were created, register their symbol data
for spec in new_specializations.into_iter() { for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec); self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
} }
self.stmt(rc_stmt); self.stmt(rc_stmt);
@ -827,11 +997,16 @@ impl<'a> WasmBackend<'a> {
ret_storage, ret_storage,
) )
} }
CallType::LowLevel { op: lowlevel, .. } => { CallType::LowLevel { op: lowlevel, .. } => {
self.expr_call_low_level(*lowlevel, arguments, ret_sym, ret_layout, ret_storage) self.expr_call_low_level(*lowlevel, arguments, ret_sym, ret_layout, ret_storage)
} }
x => todo!("call type {:?}", x), CallType::HigherOrder(higher_order_lowlevel) => {
call_higher_order_lowlevel(self, ret_sym, ret_layout, *higher_order_lowlevel)
}
CallType::Foreign { .. } => todo!("CallType::Foreign"),
} }
} }
@ -864,8 +1039,13 @@ impl<'a> WasmBackend<'a> {
); );
debug_assert!(!ret_zig_packed_struct); debug_assert!(!ret_zig_packed_struct);
let iter = self.proc_lookup.iter().enumerate(); for (roc_proc_index, lookup) in self.proc_lookup.iter().enumerate() {
for (roc_proc_index, (ir_sym, pl, linker_sym_index)) in iter { let ProcLookupData {
name: ir_sym,
layout: pl,
linker_index: linker_sym_index,
..
} = lookup;
if *ir_sym == func_sym && pl == proc_layout { if *ir_sym == func_sym && pl == proc_layout {
let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32; let wasm_fn_index = self.fn_index_offset + roc_proc_index as u32;
self.code_builder.call( self.code_builder.call(
@ -945,8 +1125,8 @@ impl<'a> WasmBackend<'a> {
.call_specialized_equals(ident_ids, arg_layout, arguments); .call_specialized_equals(ident_ids, arg_layout, arguments);
// If any new specializations were created, register their symbol data // If any new specializations were created, register their symbol data
for spec in new_specializations.into_iter() { for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec); self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
} }
// Generate Wasm code for the IR call expression // Generate Wasm code for the IR call expression
@ -1430,8 +1610,8 @@ impl<'a> WasmBackend<'a> {
.call_reset_refcount(ident_ids, layout, argument); .call_reset_refcount(ident_ids, layout, argument);
// If any new specializations were created, register their symbol data // If any new specializations were created, register their symbol data
for spec in new_specializations.into_iter() { for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec); self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
} }
// Generate Wasm code for the IR call expression // Generate Wasm code for the IR call expression
@ -1442,4 +1622,30 @@ impl<'a> WasmBackend<'a> {
ret_storage, ret_storage,
); );
} }
/// Generate a refcount increment procedure and return its Wasm function index
pub fn gen_refcount_inc_for_zig(&mut self, layout: Layout<'a>) -> u32 {
let ident_ids = self
.interns
.all_ident_ids
.get_mut(&self.env.module_id)
.unwrap();
let (proc_symbol, new_specializations) = self
.helper_proc_gen
.gen_refcount_inc_proc(ident_ids, layout);
// If any new specializations were created, register their symbol data
for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
}
let proc_index = self
.proc_lookup
.iter()
.position(|lookup| lookup.name == proc_symbol && lookup.layout.arguments[0] == layout)
.unwrap();
self.fn_index_offset + proc_index as u32
}
} }

View file

@ -7,7 +7,7 @@ use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReturnMethod { pub enum ReturnMethod {
/// This layout is returned from a Wasm function "normally" as a Primitive /// This layout is returned from a Wasm function "normally" as a Primitive
Primitive(ValueType), Primitive(ValueType, u32),
/// This layout is returned by writing to a pointer passed as the first argument /// This layout is returned by writing to a pointer passed as the first argument
WriteToPointerArg, WriteToPointerArg,
/// This layout is empty and requires no return value or argument (e.g. refcount helpers) /// This layout is empty and requires no return value or argument (e.g. refcount helpers)
@ -46,8 +46,7 @@ impl WasmLayout {
use UnionLayout::*; use UnionLayout::*;
use ValueType::*; use ValueType::*;
let size = layout.stack_size(TARGET_INFO); let (size, alignment_bytes) = layout.stack_size_and_alignment(TARGET_INFO);
let alignment_bytes = layout.alignment_bytes(TARGET_INFO);
match layout { match layout {
Layout::Builtin(Int(int_width)) => { Layout::Builtin(Int(int_width)) => {
@ -86,9 +85,10 @@ impl WasmLayout {
format: StackMemoryFormat::Decimal, format: StackMemoryFormat::Decimal,
}, },
Layout::LambdaSet(lambda_set) => WasmLayout::new(&lambda_set.runtime_representation()),
Layout::Builtin(Str | Dict(_, _) | Set(_) | List(_)) Layout::Builtin(Str | Dict(_, _) | Set(_) | List(_))
| Layout::Struct { .. } | Layout::Struct { .. }
| Layout::LambdaSet(_)
| Layout::Union(NonRecursive(_)) => Self::StackMemory { | Layout::Union(NonRecursive(_)) => Self::StackMemory {
size, size,
alignment_bytes, alignment_bytes,
@ -125,7 +125,7 @@ impl WasmLayout {
pub fn return_method(&self, conv: CallConv) -> ReturnMethod { pub fn return_method(&self, conv: CallConv) -> ReturnMethod {
match self { match self {
Self::Primitive(ty, _) => ReturnMethod::Primitive(*ty), Self::Primitive(ty, size) => ReturnMethod::Primitive(*ty, *size),
Self::StackMemory { size, format, .. } => { Self::StackMemory { size, format, .. } => {
conv.stack_memory_return_method(*size, *format) conv.stack_memory_return_method(*size, *format)
} }

View file

@ -18,7 +18,7 @@ use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::LayoutIds;
use roc_target::TargetInfo; use roc_target::TargetInfo;
use crate::backend::WasmBackend; use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
use crate::wasm_module::{ use crate::wasm_module::{
Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule, Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule,
}; };
@ -107,7 +107,12 @@ pub fn build_module_without_wrapper<'a>(
let linker_sym_index = linker_symbols.len() as u32; let linker_sym_index = linker_symbols.len() as u32;
// linker_sym_index is redundant for these procs from user code, but needed for generated helpers! // linker_sym_index is redundant for these procs from user code, but needed for generated helpers!
proc_lookup.push((sym, proc_layout, linker_sym_index)); proc_lookup.push(ProcLookupData {
name: sym,
layout: proc_layout,
linker_index: linker_sym_index,
source: ProcSource::Roc,
});
linker_symbols.push(linker_sym); linker_symbols.push(linker_sym);
fn_index += 1; fn_index += 1;
@ -134,7 +139,7 @@ pub fn build_module_without_wrapper<'a>(
println!("## procs"); println!("## procs");
for proc in procs.iter() { for proc in procs.iter() {
println!("{}", proc.to_pretty(200)); println!("{}", proc.to_pretty(200));
// println!("{:#?}", proc); // println!("{:?}", proc);
} }
} }
@ -144,7 +149,7 @@ pub fn build_module_without_wrapper<'a>(
} }
// Generate specialized helpers for refcounting & equality // Generate specialized helpers for refcounting & equality
let helper_procs = backend.generate_helpers(); let helper_procs = backend.get_helpers();
backend.register_symbol_debug_names(); backend.register_symbol_debug_names();
@ -156,9 +161,22 @@ pub fn build_module_without_wrapper<'a>(
} }
} }
// Generate Wasm for refcounting procs // Generate Wasm for helpers and Zig/Roc wrappers
for proc in helper_procs.iter() { let sources = Vec::from_iter_in(
backend.build_proc(proc); backend
.proc_lookup
.iter()
.map(|ProcLookupData { source, .. }| *source),
env.arena,
);
let mut helper_iter = helper_procs.iter();
for (idx, source) in sources.iter().enumerate() {
use ProcSource::*;
match source {
Roc => { /* already generated */ }
Helper => backend.build_proc(helper_iter.next().unwrap()),
HigherOrderWrapper(inner_idx) => backend.build_higher_order_wrapper(idx, *inner_idx),
}
} }
let (module, called_preload_fns) = backend.finalize(); let (module, called_preload_fns) = backend.finalize();

View file

@ -1,14 +1,17 @@
use bumpalo::collections::Vec;
use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout};
use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout};
use roc_mono::low_level::HigherOrder;
use crate::backend::WasmBackend; use crate::backend::{ProcLookupData, ProcSource, WasmBackend};
use crate::layout::CallConv; use crate::layout::{CallConv, StackMemoryFormat, WasmLayout};
use crate::layout::{StackMemoryFormat, WasmLayout};
use crate::storage::{StackMemoryLocation, StoredValue}; use crate::storage::{StackMemoryLocation, StoredValue};
use crate::wasm_module::{Align, ValueType}; use crate::wasm_module::{Align, ValueType};
use crate::TARGET_INFO;
/// Number types used for Wasm code gen /// Number types used for Wasm code gen
/// Unlike other enums, this contains no details about layout or storage. /// Unlike other enums, this contains no details about layout or storage.
@ -195,6 +198,7 @@ impl<'a> LowLevelCall<'a> {
/// Main entrypoint from WasmBackend /// Main entrypoint from WasmBackend
pub fn generate(&self, backend: &mut WasmBackend<'a>) { pub fn generate(&self, backend: &mut WasmBackend<'a>) {
use CodeGenNumType::*; use CodeGenNumType::*;
use LowLevel::*;
let panic_ret_type = || { let panic_ret_type = || {
internal_error!( internal_error!(
@ -286,14 +290,21 @@ impl<'a> LowLevelCall<'a> {
_ => internal_error!("invalid storage for List"), _ => internal_error!("invalid storage for List"),
}, },
ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| ListAny | ListAll | ListFindUnsafe | DictWalk => {
internal_error!("HigherOrder lowlevels should not be handled here")
}
ListGetUnsafe | ListReplaceUnsafe | ListSingle | ListRepeat | ListReverse ListGetUnsafe | ListReplaceUnsafe | ListSingle | ListRepeat | ListReverse
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange
| ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListSublist | ListDropAt | ListSwap => {
| ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs todo!("{:?}", self.lowlevel);
| ListSortWith | ListSublist | ListDropAt | ListSwap | ListAny | ListAll }
| ListFindUnsafe | DictSize | DictEmpty | DictInsert | DictRemove | DictContains
| DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe
| DictDifference | DictWalk | SetFromList => { | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference
| SetFromList => {
todo!("{:?}", self.lowlevel); todo!("{:?}", self.lowlevel);
} }
@ -687,9 +698,15 @@ impl<'a> LowLevelCall<'a> {
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
} }
} }
NumToFloatCast => {
todo!("implement toF32 and toF64");
}
NumToIntChecked => { NumToIntChecked => {
todo!() todo!()
} }
NumToFloatChecked => {
todo!("implement toF32Checked and toF64Checked");
}
And => { And => {
self.load_args(backend); self.load_args(backend);
backend.code_builder.i32_and(); backend.code_builder.i32_and();
@ -731,7 +748,7 @@ impl<'a> LowLevelCall<'a> {
"Cannot do `==` comparison on different types" "Cannot do `==` comparison on different types"
); );
let invert_result = matches!(self.lowlevel, NotEq); let invert_result = matches!(self.lowlevel, LowLevel::NotEq);
match arg_layout { match arg_layout {
Layout::Builtin( Layout::Builtin(
@ -942,3 +959,144 @@ fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) {
} }
} }
} }
pub fn call_higher_order_lowlevel<'a>(
backend: &mut WasmBackend<'a>,
return_sym: Symbol,
return_layout: &Layout<'a>,
higher_order: &'a HigherOrderLowLevel<'a>,
) {
use HigherOrder::*;
let HigherOrderLowLevel {
op,
passed_function,
..
} = higher_order;
let PassedFunction {
name: fn_name,
argument_layouts,
return_layout: result_layout,
owns_captured_environment,
captured_environment,
..
} = passed_function;
let closure_data_layout = match backend.storage.symbol_layouts[captured_environment] {
Layout::LambdaSet(lambda_set) => lambda_set.runtime_representation(),
Layout::Struct {
field_layouts: &[], ..
} => Layout::UNIT,
x => internal_error!("Closure data has an invalid layout\n{:?}", x),
};
let closure_data_exists: bool = closure_data_layout != Layout::UNIT;
// We create a wrapper around the passed function, which just unboxes the arguments.
// This allows Zig builtins to have a generic pointer-based interface.
let source = {
let passed_proc_layout = ProcLayout {
arguments: argument_layouts,
result: *result_layout,
};
let passed_proc_index = backend
.proc_lookup
.iter()
.position(|ProcLookupData { name, layout, .. }| {
name == fn_name && layout == &passed_proc_layout
})
.unwrap();
ProcSource::HigherOrderWrapper(passed_proc_index)
};
let wrapper_sym = backend.create_symbol(&format!("#wrap#{:?}", fn_name));
let wrapper_layout = {
let mut wrapper_arg_layouts: Vec<Layout<'a>> =
Vec::with_capacity_in(argument_layouts.len() + 1, backend.env.arena);
let n_non_closure_args = if closure_data_exists {
argument_layouts.len() - 1
} else {
argument_layouts.len()
};
wrapper_arg_layouts.push(closure_data_layout);
wrapper_arg_layouts.extend(
argument_layouts
.iter()
.take(n_non_closure_args)
.map(Layout::Boxed),
);
wrapper_arg_layouts.push(Layout::Boxed(result_layout));
ProcLayout {
arguments: wrapper_arg_layouts.into_bump_slice(),
result: Layout::UNIT,
}
};
let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source);
let inc_fn_idx = backend.gen_refcount_inc_for_zig(closure_data_layout);
let wrapper_fn_ptr = backend.get_fn_table_index(wrapper_fn_idx);
let inc_fn_ptr = backend.get_fn_table_index(inc_fn_idx);
match op {
// List.map : List elem_x, (elem_x -> elem_ret) -> List elem_ret
ListMap { xs } => {
let list_layout_in = backend.storage.symbol_layouts[xs];
let (elem_x, elem_ret) = match (list_layout_in, return_layout) {
(
Layout::Builtin(Builtin::List(elem_x)),
Layout::Builtin(Builtin::List(elem_ret)),
) => (elem_x, elem_ret),
_ => unreachable!("invalid layout for List.map arguments"),
};
let elem_x_size = elem_x.stack_size(TARGET_INFO);
let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO);
let cb = &mut backend.code_builder;
// Load return pointer & argument values
// Wasm signature: (i32, i64, i64, i32, i32, i32, i32, i32, i32, i32) -> nil
backend.storage.load_symbols(cb, &[return_sym]);
backend.storage.load_symbol_zig(cb, *xs); // list with capacity = 2 x i64 args
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[*captured_environment]);
} else {
// Normally, a zero-size arg would be eliminated in code gen, but Zig expects one!
cb.i32_const(0); // null pointer
}
cb.i32_const(inc_fn_ptr);
cb.i32_const(*owns_captured_environment as i32);
cb.i32_const(elem_ret_align as i32); // used for allocating the new list
cb.i32_const(elem_x_size as i32);
cb.i32_const(elem_ret_size as i32);
let num_wasm_args = 10; // 1 return pointer + 8 Zig args + list 2nd i64
let has_return_val = false;
backend.call_zig_builtin_after_loading_args(
bitcode::LIST_MAP,
num_wasm_args,
has_return_val,
);
}
ListMap2 { .. }
| ListMap3 { .. }
| ListMap4 { .. }
| ListMapWithIndex { .. }
| ListKeepIf { .. }
| ListWalk { .. }
| ListWalkUntil { .. }
| ListWalkBackwards { .. }
| ListKeepOks { .. }
| ListKeepErrs { .. }
| ListSortWith { .. }
| ListAny { .. }
| ListAll { .. }
| ListFindUnsafe { .. }
| DictWalk { .. } => todo!("{:?}", op),
}
}

View file

@ -299,7 +299,8 @@ impl<'a> Storage<'a> {
} }
} }
fn load_symbol_zig(&mut self, code_builder: &mut CodeBuilder, arg: Symbol) { // TODO: expose something higher level instead, shared among higher-order calls
pub fn load_symbol_zig(&mut self, code_builder: &mut CodeBuilder, arg: Symbol) {
if let StoredValue::StackMemory { if let StoredValue::StackMemory {
location, location,
size, size,
@ -402,7 +403,7 @@ impl<'a> Storage<'a> {
let return_method = return_layout.return_method(call_conv); let return_method = return_layout.return_method(call_conv);
let has_return_val = match return_method { let has_return_val = match return_method {
Primitive(_) => true, Primitive(..) => true,
NoReturnValue => false, NoReturnValue => false,
WriteToPointerArg => { WriteToPointerArg => {
num_wasm_args += 1; num_wasm_args += 1;
@ -508,7 +509,7 @@ impl<'a> Storage<'a> {
size size
); );
} }
}; }
size size
} }
} }

View file

@ -70,7 +70,7 @@ impl std::fmt::Debug for VmBlock<'_> {
/// Rust representation matches Wasm encoding. /// Rust representation matches Wasm encoding.
/// It's an error to specify alignment higher than the "natural" alignment of the instruction /// It's an error to specify alignment higher than the "natural" alignment of the instruction
#[repr(u8)] #[repr(u8)]
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum Align { pub enum Align {
Bytes1 = 0, Bytes1 = 0,
Bytes2 = 1, Bytes2 = 1,
@ -78,6 +78,23 @@ pub enum Align {
Bytes8 = 3, Bytes8 = 3,
} }
impl Align {
/// Calculate the largest possible alignment for a load/store at a given stack frame offset
/// Assumes the stack frame is aligned to at least 8 bytes
pub fn from_stack_offset(max_align: Align, offset: u32) -> Align {
if (max_align == Align::Bytes8) && (offset & 7 == 0) {
return Align::Bytes8;
}
if (max_align >= Align::Bytes4) && (offset & 3 == 0) {
return Align::Bytes4;
}
if (max_align >= Align::Bytes2) && (offset & 1 == 0) {
return Align::Bytes2;
}
Align::Bytes1
}
}
impl From<u32> for Align { impl From<u32> for Align {
fn from(x: u32) -> Align { fn from(x: u32) -> Align {
match x { match x {

View file

@ -14,7 +14,8 @@ pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature}
use self::linking::{LinkingSection, RelocationSection}; use self::linking::{LinkingSection, RelocationSection};
use self::sections::{ use self::sections::{
CodeSection, DataSection, ElementSection, ExportSection, FunctionSection, GlobalSection, CodeSection, DataSection, ElementSection, ExportSection, FunctionSection, GlobalSection,
ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TypeSection, ImportSection, MemorySection, NameSection, OpaqueSection, Section, SectionId, TableSection,
TypeSection,
}; };
use self::serialize::{SerialBuffer, Serialize}; use self::serialize::{SerialBuffer, Serialize};
@ -25,7 +26,7 @@ pub struct WasmModule<'a> {
pub types: TypeSection<'a>, pub types: TypeSection<'a>,
pub import: ImportSection<'a>, pub import: ImportSection<'a>,
pub function: FunctionSection<'a>, pub function: FunctionSection<'a>,
pub table: OpaqueSection<'a>, pub table: TableSection,
pub memory: MemorySection<'a>, pub memory: MemorySection<'a>,
pub global: GlobalSection<'a>, pub global: GlobalSection<'a>,
pub export: ExportSection<'a>, pub export: ExportSection<'a>,
@ -138,7 +139,7 @@ impl<'a> WasmModule<'a> {
let function = FunctionSection::preload(arena, bytes, &mut cursor); let function = FunctionSection::preload(arena, bytes, &mut cursor);
let defined_fn_signatures = function.parse(arena); let defined_fn_signatures = function.parse(arena);
let table = OpaqueSection::preload(SectionId::Table, arena, bytes, &mut cursor); let table = TableSection::preload(bytes, &mut cursor);
let memory = MemorySection::preload(arena, bytes, &mut cursor); let memory = MemorySection::preload(arena, bytes, &mut cursor);

View file

@ -276,33 +276,6 @@ impl<'a> Section<'a> for TypeSection<'a> {
* *
*******************************************************************/ *******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum RefType {
Func = 0x70,
Extern = 0x6f,
}
#[derive(Debug)]
pub struct TableType {
pub ref_type: RefType,
pub limits: Limits,
}
impl Serialize for TableType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(self.ref_type as u8);
self.limits.serialize(buffer);
}
}
impl SkipBytes for TableType {
fn skip_bytes(bytes: &[u8], cursor: &mut usize) {
u8::skip_bytes(bytes, cursor);
Limits::skip_bytes(bytes, cursor);
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum ImportDesc { pub enum ImportDesc {
Func { signature_index: u32 }, Func { signature_index: u32 },
@ -457,6 +430,104 @@ impl<'a> FunctionSection<'a> {
section_impl!(FunctionSection, SectionId::Function); section_impl!(FunctionSection, SectionId::Function);
/*******************************************************************
*
* Table section
*
* Defines tables used for indirect references to host memory.
* The table *contents* are elsewhere, in the ElementSection.
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum RefType {
Func = 0x70,
Extern = 0x6f,
}
#[derive(Debug)]
pub struct TableType {
pub ref_type: RefType,
pub limits: Limits,
}
impl Serialize for TableType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(self.ref_type as u8);
self.limits.serialize(buffer);
}
}
impl SkipBytes for TableType {
fn skip_bytes(bytes: &[u8], cursor: &mut usize) {
u8::skip_bytes(bytes, cursor);
Limits::skip_bytes(bytes, cursor);
}
}
#[derive(Debug)]
pub struct TableSection {
pub function_table: TableType,
}
impl TableSection {
const ID: SectionId = SectionId::Table;
pub fn preload(module_bytes: &[u8], mod_cursor: &mut usize) -> Self {
let (count, section_bytes) = parse_section(Self::ID, module_bytes, mod_cursor);
match count {
0 => TableSection {
function_table: TableType {
ref_type: RefType::Func,
limits: Limits::MinMax(0, 0),
},
},
1 => {
if section_bytes[0] != RefType::Func as u8 {
internal_error!("Only funcref tables are supported")
}
let mut section_cursor = 1;
let limits = Limits::parse(section_bytes, &mut section_cursor);
TableSection {
function_table: TableType {
ref_type: RefType::Func,
limits,
},
}
}
_ => internal_error!("Multiple tables are not supported"),
}
}
pub fn size(&self) -> usize {
let section_id_bytes = 1;
let section_length_bytes = 1;
let num_tables_bytes = 1;
let ref_type_bytes = 1;
let limits_bytes = match self.function_table.limits {
Limits::Min(_) => MAX_SIZE_ENCODED_U32,
Limits::MinMax(..) => 2 * MAX_SIZE_ENCODED_U32,
};
section_id_bytes + section_length_bytes + num_tables_bytes + ref_type_bytes + limits_bytes
}
}
impl Serialize for TableSection {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
let header_indices = write_section_header(buffer, Self::ID);
let num_tables: u32 = 1;
num_tables.serialize(buffer);
self.function_table.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
/******************************************************************* /*******************************************************************
* *
* Memory section * Memory section
@ -502,6 +573,21 @@ impl SkipBytes for Limits {
} }
} }
impl Limits {
fn parse(bytes: &[u8], cursor: &mut usize) -> Self {
let variant_id = bytes[*cursor];
*cursor += 1;
let min = parse_u32_or_panic(bytes, cursor);
if variant_id == LimitsId::MinMax as u8 {
let max = parse_u32_or_panic(bytes, cursor);
Limits::MinMax(min, max)
} else {
Limits::Min(min)
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct MemorySection<'a> { pub struct MemorySection<'a> {
pub count: u32, pub count: u32,
@ -584,6 +670,13 @@ impl ConstExpr {
value value
} }
fn unwrap_i32(&self) -> i32 {
match self {
Self::I32(x) => *x,
_ => internal_error!("Expected ConstExpr to be I32"),
}
}
} }
impl Serialize for ConstExpr { impl Serialize for ConstExpr {
@ -781,9 +874,10 @@ enum ElementSegmentFormatId {
ActiveImplicitTableIndex = 0x00, ActiveImplicitTableIndex = 0x00,
} }
/// A Segment initialises a subrange of elements in a table. Normally there's just one Segment.
#[derive(Debug)] #[derive(Debug)]
struct ElementSegment<'a> { struct ElementSegment<'a> {
offset: ConstExpr, offset: ConstExpr, // The starting table index for the segment
fn_indices: Vec<'a, u32>, fn_indices: Vec<'a, u32>,
} }
@ -834,6 +928,8 @@ impl<'a> Serialize for ElementSegment<'a> {
} }
} }
/// An Element is an entry in a Table (see TableSection)
/// The only currently supported Element type is a function reference, used for indirect calls.
#[derive(Debug)] #[derive(Debug)]
pub struct ElementSection<'a> { pub struct ElementSection<'a> {
segments: Vec<'a, ElementSegment<'a>>, segments: Vec<'a, ElementSegment<'a>>,
@ -845,17 +941,57 @@ impl<'a> ElementSection<'a> {
pub fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self { pub fn preload(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Self {
let (num_segments, body_bytes) = parse_section(Self::ID, module_bytes, cursor); let (num_segments, body_bytes) = parse_section(Self::ID, module_bytes, cursor);
let mut segments = Vec::with_capacity_in(num_segments as usize, arena); if num_segments == 0 {
let seg = ElementSegment {
offset: ConstExpr::I32(1),
fn_indices: bumpalo::vec![in arena],
};
ElementSection {
segments: bumpalo::vec![in arena; seg],
}
} else {
let mut segments = Vec::with_capacity_in(num_segments as usize, arena);
let mut body_cursor = 0; let mut body_cursor = 0;
for _ in 0..num_segments { for _ in 0..num_segments {
let seg = ElementSegment::parse(arena, body_bytes, &mut body_cursor); let seg = ElementSegment::parse(arena, body_bytes, &mut body_cursor);
segments.push(seg); segments.push(seg);
}
ElementSection { segments }
} }
ElementSection { segments }
} }
/// Get a table index for a function (equivalent to a function pointer)
/// The function will be inserted into the table if it's not already there.
/// This index is what the call_indirect instruction expects.
/// (This works mostly the same as function pointers, except hackers can't jump to arbitrary code)
pub fn get_fn_table_index(&mut self, fn_index: u32) -> i32 {
// In practice there is always one segment. We allow a bit more generality by using the last one.
let segment = self.segments.last_mut().unwrap();
let offset = segment.offset.unwrap_i32();
let pos = segment.fn_indices.iter().position(|f| *f == fn_index);
if let Some(existing_table_index) = pos {
offset + existing_table_index as i32
} else {
let new_table_index = segment.fn_indices.len();
segment.fn_indices.push(fn_index);
offset + new_table_index as i32
}
}
/// Number of elements in the table
pub fn max_table_index(&self) -> u32 {
let mut result = 0;
for s in self.segments.iter() {
let max_index = s.offset.unwrap_i32() + s.fn_indices.len() as i32;
if max_index > result {
result = max_index;
}
}
result as u32
}
/// Approximate serialized byte size (for buffer capacity)
pub fn size(&self) -> usize { pub fn size(&self) -> usize {
self.segments.iter().map(|seg| seg.size()).sum() self.segments.iter().map(|seg| seg.size()).sum()
} }

View file

@ -7,9 +7,9 @@ use roc_can::scope::Scope;
use roc_error_macros::todo_abilities; use roc_error_macros::todo_abilities;
use roc_module::ident::ModuleName; use roc_module::ident::ModuleName;
use roc_module::symbol::IdentIds; use roc_module::symbol::IdentIds;
use roc_parse::ast::CommentOrNewline;
use roc_parse::ast::{self, TypeHeader}; use roc_parse::ast::{self, TypeHeader};
use roc_parse::ast::{AssignedField, Def}; use roc_parse::ast::{AssignedField, Def};
use roc_parse::ast::{CommentOrNewline, TypeDef, ValueDef};
use roc_region::all::Loc; use roc_region::all::Loc;
// Documentation generation requirements // Documentation generation requirements
@ -166,98 +166,105 @@ fn generate_entry_doc<'a>(
(new_acc, Some(comments_or_new_lines)) (new_acc, Some(comments_or_new_lines))
} }
Def::Annotation(loc_pattern, loc_ann) => match loc_pattern.value { Def::Value(def) => match def {
Pattern::Identifier(identifier) => { ValueDef::Annotation(loc_pattern, loc_ann) => match loc_pattern.value {
// Check if the definition is exposed Pattern::Identifier(identifier) => {
if ident_ids.get_id(&identifier.into()).is_some() { // Check if the definition is exposed
let name = identifier.to_string(); if ident_ids.get_id(&identifier.into()).is_some() {
let doc_def = DocDef { let name = identifier.to_string();
name, let doc_def = DocDef {
type_annotation: type_to_docs(false, loc_ann.value), name,
type_vars: Vec::new(), type_annotation: type_to_docs(false, loc_ann.value),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), type_vars: Vec::new(),
}; docs: before_comments_or_new_lines
acc.push(DocEntry::DocDef(doc_def)); .and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
}
(acc, None)
} }
_ => (acc, None),
},
ValueDef::AnnotatedBody {
ann_pattern,
ann_type,
..
} => match ann_pattern.value {
Pattern::Identifier(identifier) => {
// Check if the definition is exposed
if ident_ids.get_id(&identifier.into()).is_some() {
let doc_def = DocDef {
name: identifier.to_string(),
type_annotation: type_to_docs(false, ann_type.value),
type_vars: Vec::new(),
docs: before_comments_or_new_lines
.and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
}
(acc, None)
}
_ => (acc, None),
},
ValueDef::Body(_, _) => (acc, None),
ValueDef::Expect(c) => todo!("documentation for tests {:?}", c),
},
Def::Type(def) => match def {
TypeDef::Alias {
header: TypeHeader { name, vars },
ann,
} => {
let mut type_vars = Vec::new();
for var in vars.iter() {
if let Pattern::Identifier(ident_name) = var.value {
type_vars.push(ident_name.to_string());
}
}
let doc_def = DocDef {
name: name.value.to_string(),
type_annotation: type_to_docs(false, ann.value),
type_vars,
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
(acc, None) (acc, None)
} }
_ => (acc, None), TypeDef::Opaque {
}, header: TypeHeader { name, vars },
Def::AnnotatedBody { ..
ann_pattern, } => {
ann_type, let mut type_vars = Vec::new();
..
} => match ann_pattern.value { for var in vars.iter() {
Pattern::Identifier(identifier) => { if let Pattern::Identifier(ident_name) = var.value {
// Check if the definition is exposed type_vars.push(ident_name.to_string());
if ident_ids.get_id(&identifier.into()).is_some() { }
let doc_def = DocDef {
name: identifier.to_string(),
type_annotation: type_to_docs(false, ann_type.value),
type_vars: Vec::new(),
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
} }
let doc_def = DocDef {
name: name.value.to_string(),
type_annotation: TypeAnnotation::NoTypeAnn,
type_vars,
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
(acc, None) (acc, None)
} }
_ => (acc, None), TypeDef::Ability { .. } => todo_abilities!(),
}, },
Def::Alias {
header: TypeHeader { name, vars },
ann,
} => {
let mut type_vars = Vec::new();
for var in vars.iter() {
if let Pattern::Identifier(ident_name) = var.value {
type_vars.push(ident_name.to_string());
}
}
let doc_def = DocDef {
name: name.value.to_string(),
type_annotation: type_to_docs(false, ann.value),
type_vars,
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
(acc, None)
}
Def::Opaque {
header: TypeHeader { name, vars },
..
} => {
let mut type_vars = Vec::new();
for var in vars.iter() {
if let Pattern::Identifier(ident_name) = var.value {
type_vars.push(ident_name.to_string());
}
}
let doc_def = DocDef {
name: name.value.to_string(),
type_annotation: TypeAnnotation::NoTypeAnn,
type_vars,
docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs),
};
acc.push(DocEntry::DocDef(doc_def));
(acc, None)
}
Def::Ability { .. } => todo_abilities!(),
Def::Body(_, _) => (acc, None),
Def::Expect(c) => todo!("documentation for tests {:?}", c),
Def::NotYetImplemented(s) => todo!("{}", s), Def::NotYetImplemented(s) => todo!("{}", s),
} }
} }

View file

@ -111,7 +111,9 @@ pub enum LowLevel {
NumShiftRightBy, NumShiftRightBy,
NumShiftRightZfBy, NumShiftRightZfBy,
NumIntCast, NumIntCast,
NumToFloatCast,
NumToIntChecked, NumToIntChecked,
NumToFloatChecked,
NumToStr, NumToStr,
Eq, Eq,
NotEq, NotEq,
@ -240,7 +242,7 @@ impl LowLevelWrapperType {
Symbol::LIST_PREPEND => CanBeReplacedBy(ListPrepend), Symbol::LIST_PREPEND => CanBeReplacedBy(ListPrepend),
Symbol::LIST_JOIN => CanBeReplacedBy(ListJoin), Symbol::LIST_JOIN => CanBeReplacedBy(ListJoin),
Symbol::LIST_RANGE => CanBeReplacedBy(ListRange), Symbol::LIST_RANGE => CanBeReplacedBy(ListRange),
Symbol::LIST_MAP => CanBeReplacedBy(ListMap), Symbol::LIST_MAP => WrapperIsRequired,
Symbol::LIST_MAP2 => CanBeReplacedBy(ListMap2), Symbol::LIST_MAP2 => CanBeReplacedBy(ListMap2),
Symbol::LIST_MAP3 => CanBeReplacedBy(ListMap3), Symbol::LIST_MAP3 => CanBeReplacedBy(ListMap3),
Symbol::LIST_MAP4 => CanBeReplacedBy(ListMap4), Symbol::LIST_MAP4 => CanBeReplacedBy(ListMap4),

View file

@ -1053,6 +1053,10 @@ define_builtins! {
144 NUM_TO_U128_CHECKED: "toU128Checked" 144 NUM_TO_U128_CHECKED: "toU128Checked"
145 NUM_TO_NAT: "toNat" 145 NUM_TO_NAT: "toNat"
146 NUM_TO_NAT_CHECKED: "toNatChecked" 146 NUM_TO_NAT_CHECKED: "toNatChecked"
147 NUM_TO_F32: "toF32"
148 NUM_TO_F32_CHECKED: "toF32Checked"
149 NUM_TO_F64: "toF64"
150 NUM_TO_F64_CHECKED: "toF64Checked"
} }
2 BOOL: "Bool" => { 2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
@ -1103,7 +1107,6 @@ define_builtins! {
32 STR_TO_I16: "toI16" 32 STR_TO_I16: "toI16"
33 STR_TO_U8: "toU8" 33 STR_TO_U8: "toU8"
34 STR_TO_I8: "toI8" 34 STR_TO_I8: "toI8"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias

View file

@ -1001,7 +1001,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos
| NumAsin | NumIntCast | NumToIntChecked => arena.alloc_slice_copy(&[irrelevant]), | NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => {
arena.alloc_slice_copy(&[irrelevant])
}
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),

View file

@ -182,6 +182,26 @@ impl<'a> CodeGenHelp<'a> {
(expr, ctx.new_linker_data) (expr, ctx.new_linker_data)
} }
/// Generate a refcount increment procedure, *without* a Call expression.
/// *This method should be rarely used* - only when the proc is to be called from Zig.
/// Otherwise you want to generate the Proc and the Call together, using another method.
/// This only supports the 'inc' operation, as it's the only real use case we have.
pub fn gen_refcount_inc_proc(
&mut self,
ident_ids: &mut IdentIds,
layout: Layout<'a>,
) -> (Symbol, Vec<'a, (Symbol, ProcLayout<'a>)>) {
let mut ctx = Context {
new_linker_data: Vec::new_in(self.arena),
recursive_union: None,
op: HelperOp::Inc,
};
let proc_name = self.find_or_create_proc(ident_ids, &mut ctx, layout);
(proc_name, ctx.new_linker_data)
}
/// Replace a generic `Lowlevel::Eq` call with a specialized helper proc. /// Replace a generic `Lowlevel::Eq` call with a specialized helper proc.
/// The helper procs themselves are to be generated later with `generate_procs` /// The helper procs themselves are to be generated later with `generate_procs`
pub fn call_specialized_equals( pub fn call_specialized_equals(
@ -512,24 +532,18 @@ fn layout_needs_helper_proc(layout: &Layout, op: HelperOp) -> bool {
Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => { Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => {
false false
} }
Layout::Builtin(Builtin::Str) => { Layout::Builtin(Builtin::Str) => {
// Str type can use either Zig functions or generated IR, since it's not generic. // Str type can use either Zig functions or generated IR, since it's not generic.
// Eq uses a Zig function, refcount uses generated IR. // Eq uses a Zig function, refcount uses generated IR.
// Both are fine, they were just developed at different times. // Both are fine, they were just developed at different times.
matches!(op, HelperOp::Inc | HelperOp::Dec | HelperOp::DecRef(_)) matches!(op, HelperOp::Inc | HelperOp::Dec | HelperOp::DecRef(_))
} }
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => true, Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => true,
Layout::Struct { .. } => true, // note: we do generate a helper for Unit, with just a Stmt::Ret
Layout::Struct { field_layouts, .. } => !field_layouts.is_empty(),
Layout::Union(UnionLayout::NonRecursive(tags)) => !tags.is_empty(), Layout::Union(UnionLayout::NonRecursive(tags)) => !tags.is_empty(),
Layout::Union(_) => true, Layout::Union(_) => true,
Layout::LambdaSet(_) => true,
Layout::LambdaSet(_) | Layout::RecursivePointer => false, Layout::RecursivePointer => false,
Layout::Boxed(_) => true, Layout::Boxed(_) => true,
} }
} }

View file

@ -13,7 +13,7 @@ use roc_exhaustive::{Ctor, Guard, RenderAs, TagId};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError; use roc_problem::can::{RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_std::RocDec; use roc_std::RocDec;
use roc_target::TargetInfo; use roc_target::TargetInfo;
@ -2037,6 +2037,7 @@ fn pattern_to_when<'a>(
let error = roc_problem::can::RuntimeError::Shadowing { let error = roc_problem::can::RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: loc_ident.clone(), shadow: loc_ident.clone(),
kind: ShadowKind::Variable,
}; };
(*new_symbol, Loc::at_zero(RuntimeError(error))) (*new_symbol, Loc::at_zero(RuntimeError(error)))
} }
@ -2088,6 +2089,13 @@ fn pattern_to_when<'a>(
// They should have been replaced with `UnsupportedPattern` during canonicalization // They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
} }
AbilityMemberSpecialization { .. } => {
unreachable!(
"Ability member specialization {:?} should never appear in a when!",
pattern.value
)
}
} }
} }
@ -3053,7 +3061,7 @@ fn specialize_naked_symbol<'a>(
let opt_fn_var = Some(variable); let opt_fn_var = Some(variable);
// if this is a function symbol, ensure that it's properly specialized! // if this is a function symbol, ensure that it's properly specialized!
reuse_function_symbol( specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -3432,7 +3440,6 @@ pub fn with_hole<'a>(
ZeroArgumentTag { ZeroArgumentTag {
variant_var, variant_var,
name: tag_name, name: tag_name,
arguments: args,
ext_var, ext_var,
closure_name, closure_name,
} => { } => {
@ -3466,7 +3473,7 @@ pub fn with_hole<'a>(
tag_name, tag_name,
procs, procs,
layout_cache, layout_cache,
args, std::vec::Vec::new(),
arena, arena,
) )
} }
@ -3558,7 +3565,7 @@ pub fn with_hole<'a>(
// this symbol is already defined; nothing to do // this symbol is already defined; nothing to do
} }
Field::Function(symbol, variable) => { Field::Function(symbol, variable) => {
stmt = reuse_function_symbol( stmt = specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -4114,7 +4121,7 @@ pub fn with_hole<'a>(
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt)); Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
if record_needs_specialization { if record_needs_specialization {
stmt = reuse_function_symbol( stmt = specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -4804,8 +4811,7 @@ fn construct_closure_data<'a>(
// symbols to be inlined when specializing the closure body elsewhere. // symbols to be inlined when specializing the closure body elsewhere.
for &&(symbol, var) in symbols { for &&(symbol, var) in symbols {
if procs.partial_exprs.contains(symbol) { if procs.partial_exprs.contains(symbol) {
result = result = specialize_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
reuse_function_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
} }
} }
@ -6318,6 +6324,20 @@ fn store_pattern_help<'a>(
match can_pat { match can_pat {
Identifier(symbol) => { Identifier(symbol) => {
if let Some((_, var)) = procs.partial_exprs.get(outer_symbol) {
// It might be the case that symbol we're storing hasn't been reified to a value
// yet, if it's polymorphic. Do that now.
stmt = specialize_symbol(
env,
procs,
layout_cache,
Some(var),
*symbol,
stmt,
outer_symbol,
);
}
substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol); substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
} }
Underscore => { Underscore => {
@ -6769,9 +6789,8 @@ fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> {
Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole) Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole)
} }
/// If the symbol is a function, make sure it is properly specialized /// If the symbol is a function or polymorphic value, make sure it is properly specialized
// TODO: rename this now that we handle polymorphic non-function expressions too fn specialize_symbol<'a>(
fn reuse_function_symbol<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
@ -6980,7 +6999,7 @@ fn assign_to_symbol<'a>(
match can_reuse_symbol(env, procs, &loc_arg.value) { match can_reuse_symbol(env, procs, &loc_arg.value) {
Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => { Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => {
// for functions we must make sure they are specialized correctly // for functions we must make sure they are specialized correctly
reuse_function_symbol( specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -7787,6 +7806,7 @@ fn from_can_pattern_help<'a>(
match can_pattern { match can_pattern {
Underscore => Ok(Pattern::Underscore), Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
IntLiteral(_, precision_var, _, int, _bound) => { IntLiteral(_, precision_var, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
IntOrFloat::Int(precision) => { IntOrFloat::Int(precision) => {
@ -7830,6 +7850,7 @@ fn from_can_pattern_help<'a>(
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: ident.clone(), shadow: ident.clone(),
kind: ShadowKind::Variable,
}), }),
UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)), UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
MalformedPattern(_problem, region) => { MalformedPattern(_problem, region) => {

View file

@ -468,7 +468,7 @@ impl<'a> UnionLayout<'a> {
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
let allocation = match self { let allocation = match self {
UnionLayout::NonRecursive(_) => unreachable!("not heap-allocated"), UnionLayout::NonRecursive(tags) => Self::tags_alignment_bytes(tags, target_info),
UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info), UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info),
UnionLayout::NonNullableUnwrapped(field_layouts) => { UnionLayout::NonNullableUnwrapped(field_layouts) => {
Layout::struct_no_name_order(field_layouts).alignment_bytes(target_info) Layout::struct_no_name_order(field_layouts).alignment_bytes(target_info)
@ -1150,9 +1150,11 @@ impl<'a> Layout<'a> {
} }
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
let ptr_width = target_info.ptr_width() as u32;
match self { match self {
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info), Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info),
Layout::Struct { .. } => unreachable!("not heap-allocated"), Layout::Struct { .. } => self.alignment_bytes(target_info).max(ptr_width),
Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info), Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info),
Layout::LambdaSet(lambda_set) => lambda_set Layout::LambdaSet(lambda_set) => lambda_set
.runtime_representation() .runtime_representation()
@ -1545,9 +1547,6 @@ impl<'a> Builtin<'a> {
let ptr_width = target_info.ptr_width() as u32; let ptr_width = target_info.ptr_width() as u32;
let allocation = match self { let allocation = match self {
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => {
unreachable!("not heap-allocated")
}
Builtin::Str => ptr_width, Builtin::Str => ptr_width,
Builtin::Dict(k, v) => k Builtin::Dict(k, v) => k
.alignment_bytes(target_info) .alignment_bytes(target_info)
@ -1555,6 +1554,11 @@ impl<'a> Builtin<'a> {
.max(ptr_width), .max(ptr_width),
Builtin::Set(k) => k.alignment_bytes(target_info).max(ptr_width), Builtin::Set(k) => k.alignment_bytes(target_info).max(ptr_width),
Builtin::List(e) => e.alignment_bytes(target_info).max(ptr_width), Builtin::List(e) => e.alignment_bytes(target_info).max(ptr_width),
// The following are usually not heap-allocated, but they might be when inside a Box.
Builtin::Int(int_width) => int_width.alignment_bytes(target_info).max(ptr_width),
Builtin::Float(float_width) => float_width.alignment_bytes(target_info).max(ptr_width),
Builtin::Bool => (core::mem::align_of::<bool>() as u32).max(ptr_width),
Builtin::Decimal => IntWidth::I128.alignment_bytes(target_info).max(ptr_width),
}; };
allocation.max(ptr_width) allocation.max(ptr_width)

View file

@ -170,6 +170,7 @@ enum FirstOrder {
NumBytesToU32, NumBytesToU32,
NumShiftRightZfBy, NumShiftRightZfBy,
NumIntCast, NumIntCast,
NumFloatCast,
Eq, Eq,
NotEq, NotEq,
And, And,

View file

@ -273,17 +273,13 @@ pub enum Has<'a> {
/// An ability demand is a value defining the ability; for example `hash : a -> U64 | a has Hash` /// An ability demand is a value defining the ability; for example `hash : a -> U64 | a has Hash`
/// for a `Hash` ability. /// for a `Hash` ability.
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct AbilityDemand<'a> { pub struct AbilityMember<'a> {
pub name: Loc<Spaced<'a, &'a str>>, pub name: Loc<Spaced<'a, &'a str>>,
pub typ: Loc<TypeAnnotation<'a>>, pub typ: Loc<TypeAnnotation<'a>>,
} }
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum Def<'a> { pub enum TypeDef<'a> {
// TODO in canonicalization, validate the pattern; only certain patterns
// are allowed in annotations.
Annotation(Loc<Pattern<'a>>, Loc<TypeAnnotation<'a>>),
/// A type alias. This is like a standalone annotation, except the pattern /// A type alias. This is like a standalone annotation, except the pattern
/// must be a capitalized Identifier, e.g. /// must be a capitalized Identifier, e.g.
/// ///
@ -305,8 +301,15 @@ pub enum Def<'a> {
Ability { Ability {
header: TypeHeader<'a>, header: TypeHeader<'a>,
loc_has: Loc<Has<'a>>, loc_has: Loc<Has<'a>>,
demands: &'a [AbilityDemand<'a>], members: &'a [AbilityMember<'a>],
}, },
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ValueDef<'a> {
// TODO in canonicalization, validate the pattern; only certain patterns
// are allowed in annotations.
Annotation(Loc<Pattern<'a>>, Loc<TypeAnnotation<'a>>),
// TODO in canonicalization, check to see if there are any newlines after the // TODO in canonicalization, check to see if there are any newlines after the
// annotation; if not, and if it's followed by a Body, then the annotation // annotation; if not, and if it's followed by a Body, then the annotation
@ -323,6 +326,12 @@ pub enum Def<'a> {
}, },
Expect(&'a Loc<Expr<'a>>), Expect(&'a Loc<Expr<'a>>),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Def<'a> {
Type(TypeDef<'a>),
Value(ValueDef<'a>),
// Blank Space (e.g. comments, spaces, newlines) before or after a def. // Blank Space (e.g. comments, spaces, newlines) before or after a def.
// We preserve this for the formatter; canonicalization ignores it. // We preserve this for the formatter; canonicalization ignores it.
@ -341,6 +350,30 @@ impl<'a> Def<'a> {
debug_assert!(!matches!(def, Def::SpaceBefore(_, _))); debug_assert!(!matches!(def, Def::SpaceBefore(_, _)));
(spaces, def) (spaces, def)
} }
pub fn unroll_def(&self) -> Result<&TypeDef<'a>, &ValueDef<'a>> {
let mut def = self;
loop {
match def {
Def::Type(type_def) => return Ok(type_def),
Def::Value(value_def) => return Err(value_def),
Def::SpaceBefore(def1, _) | Def::SpaceAfter(def1, _) => def = def1,
Def::NotYetImplemented(s) => todo!("{}", s),
}
}
}
}
impl<'a> From<TypeDef<'a>> for Def<'a> {
fn from(def: TypeDef<'a>) -> Self {
Self::Type(def)
}
}
impl<'a> From<ValueDef<'a>> for Def<'a> {
fn from(def: ValueDef<'a>) -> Self {
Self::Value(def)
}
} }
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]

View file

@ -1,6 +1,6 @@
use crate::ast::{ use crate::ast::{
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable, AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable,
TypeAnnotation, TypeHeader, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
}; };
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident, Ident}; use crate::ident::{lowercase_ident, parse_ident, Ident};
@ -10,7 +10,7 @@ use crate::parser::{
trailing_sep_by0, word1, word2, EExpect, EExpr, EIf, EInParens, ELambda, EList, ENumber, trailing_sep_by0, word1, word2, EExpect, EExpr, EIf, EInParens, ELambda, EList, ENumber,
EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser, EPattern, ERecord, EString, EType, EWhen, Either, ParseResult, Parser,
}; };
use crate::pattern::loc_closure_param; use crate::pattern::{loc_closure_param, loc_has_parser};
use crate::state::State; use crate::state::State;
use crate::type_annotation; use crate::type_annotation;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
@ -576,7 +576,7 @@ fn append_body_definition<'a>(
if spaces.len() <= 1 { if spaces.len() <= 1 {
let last = defs.pop(); let last = defs.pop();
match last.map(|d| d.value.unroll_spaces_before()) { match last.map(|d| d.value.unroll_spaces_before()) {
Some((before_ann_spaces, Def::Annotation(ann_pattern, ann_type))) => { Some((before_ann_spaces, Def::Value(ValueDef::Annotation(ann_pattern, ann_type)))) => {
return append_body_definition_help( return append_body_definition_help(
arena, arena,
defs, defs,
@ -591,10 +591,10 @@ fn append_body_definition<'a>(
} }
Some(( Some((
before_ann_spaces, before_ann_spaces,
Def::Alias { Def::Type(TypeDef::Alias {
header, header,
ann: ann_type, ann: ann_type,
}, }),
)) => { )) => {
// This is a case like // This is a case like
// UserId x : [ UserId Int ] // UserId x : [ UserId Int ]
@ -628,7 +628,10 @@ fn append_body_definition<'a>(
// the previous and current def can't be joined up // the previous and current def can't be joined up
let mut loc_def = Loc::at( let mut loc_def = Loc::at(
region, region,
Def::Body(arena.alloc(loc_pattern), &*arena.alloc(loc_def_body)), Def::Value(ValueDef::Body(
arena.alloc(loc_pattern),
&*arena.alloc(loc_def_body),
)),
); );
if !spaces.is_empty() { if !spaces.is_empty() {
@ -660,13 +663,13 @@ fn append_body_definition_help<'a>(
let mut loc_def = Loc::at( let mut loc_def = Loc::at(
region, region,
Def::AnnotatedBody { Def::Value(ValueDef::AnnotatedBody {
ann_pattern: loc_pattern_ann, ann_pattern: loc_pattern_ann,
ann_type: loc_ann, ann_type: loc_ann,
comment, comment,
body_pattern: arena.alloc(loc_pattern_body), body_pattern: arena.alloc(loc_pattern_body),
body_expr: &*arena.alloc(loc_def_body), body_expr: &*arena.alloc(loc_def_body),
}, }),
); );
if !before_ann_spaces.is_empty() { if !before_ann_spaces.is_empty() {
@ -717,7 +720,10 @@ fn append_annotation_definition<'a>(
kind, kind,
), ),
_ => { _ => {
let mut loc_def = Loc::at(region, Def::Annotation(loc_pattern, loc_ann)); let mut loc_def = Loc::at(
region,
Def::Value(ValueDef::Annotation(loc_pattern, loc_ann)),
);
if !spaces.is_empty() { if !spaces.is_empty() {
loc_def = arena loc_def = arena
.alloc(loc_def.value) .alloc(loc_def.value)
@ -736,7 +742,7 @@ fn append_expect_definition<'a>(
spaces: &'a [CommentOrNewline<'a>], spaces: &'a [CommentOrNewline<'a>],
loc_expect_body: Loc<Expr<'a>>, loc_expect_body: Loc<Expr<'a>>,
) { ) {
let def = Def::Expect(arena.alloc(loc_expect_body)); let def: Def = ValueDef::Expect(arena.alloc(loc_expect_body)).into();
let end = loc_expect_body.region.end(); let end = loc_expect_body.region.end();
let region = Region::new(start, end); let region = Region::new(start, end);
@ -768,16 +774,16 @@ fn append_type_definition<'a>(
vars: pattern_arguments, vars: pattern_arguments,
}; };
let def = match kind { let def = match kind {
TypeKind::Alias => Def::Alias { TypeKind::Alias => TypeDef::Alias {
header, header,
ann: loc_ann, ann: loc_ann,
}, },
TypeKind::Opaque => Def::Opaque { TypeKind::Opaque => TypeDef::Opaque {
header, header,
typ: loc_ann, typ: loc_ann,
}, },
}; };
let mut loc_def = Loc::at(region, def); let mut loc_def = Loc::at(region, Def::Type(def));
if !spaces.is_empty() { if !spaces.is_empty() {
loc_def = arena loc_def = arena
@ -858,55 +864,91 @@ fn parse_defs_end<'a>(
// a hacky way to get expression-based error messages. TODO fix this // a hacky way to get expression-based error messages. TODO fix this
Ok((NoProgress, def_state, initial)) Ok((NoProgress, def_state, initial))
} }
Ok((_, loc_pattern, state)) => match operator().parse(arena, state) { Ok((_, loc_pattern, state)) => {
Ok((_, BinOp::Assignment, state)) => { // First let's check whether this is an ability definition.
let parse_def_expr = space0_before_e( let opt_tag_and_args: Option<(&str, Region, &[Loc<Pattern>])> = match loc_pattern.value
move |a, s| parse_loc_expr(min_indent + 1, a, s), {
min_indent, Pattern::Apply(
EExpr::IndentEnd, Loc {
); value: Pattern::GlobalTag(name),
region,
let (_, loc_def_expr, state) = parse_def_expr.parse(arena, state)?;
append_body_definition(
arena,
&mut def_state.defs,
def_state.spaces_after,
loc_pattern,
loc_def_expr,
);
parse_defs_end(options, column, def_state, arena, state)
}
Ok((_, op @ (BinOp::IsAliasType | BinOp::IsOpaqueType), state)) => {
let (_, ann_type, state) = specialize(
EExpr::Type,
space0_before_e(
type_annotation::located_help(min_indent + 1, false),
min_indent + 1,
EType::TIndentStart,
),
)
.parse(arena, state)?;
append_annotation_definition(
arena,
&mut def_state.defs,
def_state.spaces_after,
loc_pattern,
ann_type,
match op {
BinOp::IsAliasType => TypeKind::Alias,
BinOp::IsOpaqueType => TypeKind::Opaque,
_ => unreachable!(),
}, },
); args,
) => Some((name, *region, args)),
Pattern::GlobalTag(name) => Some((name, loc_pattern.region, &[])),
_ => None,
};
parse_defs_end(options, column, def_state, arena, state) if let Some((name, name_region, args)) = opt_tag_and_args {
if let Ok((_, loc_has, state)) =
loc_has_parser(min_indent).parse(arena, state.clone())
{
let (_, loc_def, state) = finish_parsing_ability_def(
start_column,
Loc::at(name_region, name),
args,
loc_has,
arena,
state,
)?;
def_state.defs.push(loc_def);
return parse_defs_end(options, column, def_state, arena, state);
}
} }
_ => Ok((MadeProgress, def_state, initial)), // Otherwise, this is a def or alias.
}, match operator().parse(arena, state) {
Ok((_, BinOp::Assignment, state)) => {
let parse_def_expr = space0_before_e(
move |a, s| parse_loc_expr(min_indent + 1, a, s),
min_indent,
EExpr::IndentEnd,
);
let (_, loc_def_expr, state) = parse_def_expr.parse(arena, state)?;
append_body_definition(
arena,
&mut def_state.defs,
def_state.spaces_after,
loc_pattern,
loc_def_expr,
);
parse_defs_end(options, column, def_state, arena, state)
}
Ok((_, op @ (BinOp::IsAliasType | BinOp::IsOpaqueType), state)) => {
let (_, ann_type, state) = specialize(
EExpr::Type,
space0_before_e(
type_annotation::located_help(min_indent + 1, false),
min_indent + 1,
EType::TIndentStart,
),
)
.parse(arena, state)?;
append_annotation_definition(
arena,
&mut def_state.defs,
def_state.spaces_after,
loc_pattern,
ann_type,
match op {
BinOp::IsAliasType => TypeKind::Alias,
BinOp::IsOpaqueType => TypeKind::Opaque,
_ => unreachable!(),
},
);
parse_defs_end(options, column, def_state, arena, state)
}
_ => Ok((MadeProgress, def_state, initial)),
}
}
} }
} }
@ -1004,17 +1046,17 @@ fn finish_parsing_alias_or_opaque<'a>(
vars: type_arguments.into_bump_slice(), vars: type_arguments.into_bump_slice(),
}; };
let type_def = match kind { let type_def = match kind {
TypeKind::Alias => Def::Alias { TypeKind::Alias => TypeDef::Alias {
header, header,
ann: ann_type, ann: ann_type,
}, },
TypeKind::Opaque => Def::Opaque { TypeKind::Opaque => TypeDef::Opaque {
header, header,
typ: ann_type, typ: ann_type,
}, },
}; };
(&*arena.alloc(Loc::at(def_region, type_def)), state) (&*arena.alloc(Loc::at(def_region, type_def.into())), state)
} }
_ => { _ => {
@ -1043,9 +1085,9 @@ fn finish_parsing_alias_or_opaque<'a>(
let def_region = Region::span_across(&call.region, &ann_type.region); let def_region = Region::span_across(&call.region, &ann_type.region);
let alias = Def::Annotation(Loc::at(expr_region, good), ann_type); let alias = ValueDef::Annotation(Loc::at(expr_region, good), ann_type);
(&*arena.alloc(Loc::at(def_region, alias)), state) (&*arena.alloc(Loc::at(def_region, alias.into())), state)
} }
} }
} }
@ -1074,14 +1116,14 @@ fn finish_parsing_alias_or_opaque<'a>(
mod ability { mod ability {
use super::*; use super::*;
use crate::{ use crate::{
ast::{AbilityDemand, Spaceable, Spaced}, ast::{AbilityMember, Spaceable, Spaced},
parser::EAbility, parser::EAbility,
}; };
/// Parses a single ability demand line; see `parse_demand`. /// Parses a single ability demand line; see `parse_demand`.
fn parse_demand_help<'a>( fn parse_demand_help<'a>(
start_column: u32, start_column: u32,
) -> impl Parser<'a, AbilityDemand<'a>, EAbility<'a>> { ) -> impl Parser<'a, AbilityMember<'a>, EAbility<'a>> {
map!( map!(
and!( and!(
specialize(|_, pos| EAbility::DemandName(pos), loc!(lowercase_ident())), specialize(|_, pos| EAbility::DemandName(pos), loc!(lowercase_ident())),
@ -1099,7 +1141,7 @@ mod ability {
) )
), ),
|(name, typ): (Loc<&'a str>, Loc<TypeAnnotation<'a>>)| { |(name, typ): (Loc<&'a str>, Loc<TypeAnnotation<'a>>)| {
AbilityDemand { AbilityMember {
name: name.map_owned(Spaced::Item), name: name.map_owned(Spaced::Item),
typ, typ,
} }
@ -1117,7 +1159,7 @@ mod ability {
/// This is basically the same as parsing a free-floating annotation, but with stricter rules. /// This is basically the same as parsing a free-floating annotation, but with stricter rules.
pub fn parse_demand<'a>( pub fn parse_demand<'a>(
indent: IndentLevel, indent: IndentLevel,
) -> impl Parser<'a, (u32, AbilityDemand<'a>), EAbility<'a>> { ) -> impl Parser<'a, (u32, AbilityMember<'a>), EAbility<'a>> {
move |arena, state: State<'a>| { move |arena, state: State<'a>| {
let initial = state.clone(); let initial = state.clone();
@ -1190,15 +1232,14 @@ mod ability {
} }
} }
fn finish_parsing_ability<'a>( fn finish_parsing_ability_def<'a>(
start_column: u32, start_column: u32,
options: ExprParseOptions,
name: Loc<&'a str>, name: Loc<&'a str>,
args: &'a [Loc<Pattern<'a>>], args: &'a [Loc<Pattern<'a>>],
loc_has: Loc<Has<'a>>, loc_has: Loc<Has<'a>>,
arena: &'a Bump, arena: &'a Bump,
state: State<'a>, state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { ) -> ParseResult<'a, &'a Loc<Def<'a>>, EExpr<'a>> {
let mut demands = Vec::with_capacity_in(2, arena); let mut demands = Vec::with_capacity_in(2, arena);
let min_indent_for_demand = start_column + 1; let min_indent_for_demand = start_column + 1;
@ -1237,13 +1278,29 @@ fn finish_parsing_ability<'a>(
} }
let def_region = Region::span_across(&name.region, &demands.last().unwrap().typ.region); let def_region = Region::span_across(&name.region, &demands.last().unwrap().typ.region);
let def = Def::Ability { let def = TypeDef::Ability {
header: TypeHeader { name, vars: args }, header: TypeHeader { name, vars: args },
loc_has, loc_has,
demands: demands.into_bump_slice(), members: demands.into_bump_slice(),
}; }
.into();
let loc_def = &*(arena.alloc(Loc::at(def_region, def))); let loc_def = &*(arena.alloc(Loc::at(def_region, def)));
Ok((MadeProgress, loc_def, state))
}
fn finish_parsing_ability<'a>(
start_column: u32,
options: ExprParseOptions,
name: Loc<&'a str>,
args: &'a [Loc<Pattern<'a>>],
loc_has: Loc<Has<'a>>,
arena: &'a Bump,
state: State<'a>,
) -> ParseResult<'a, Expr<'a>, EExpr<'a>> {
let (_, loc_def, state) =
finish_parsing_ability_def(start_column, name, args, loc_has, arena, state)?;
let def_state = DefState { let def_state = DefState {
defs: bumpalo::vec![in arena; loc_def], defs: bumpalo::vec![in arena; loc_def],
spaces_after: &[], spaces_after: &[],
@ -1309,23 +1366,24 @@ fn parse_expr_operator<'a>(
let (loc_def, state) = { let (loc_def, state) = {
match expr_to_pattern_help(arena, &call.value) { match expr_to_pattern_help(arena, &call.value) {
Ok(good) => { Ok(good) => {
let (_, mut ann_type, state) = parse_loc_expr(indented_more, arena, state)?; let (_, mut body, state) = parse_loc_expr(indented_more, arena, state)?;
// put the spaces from after the operator in front of the call // put the spaces from after the operator in front of the call
if !spaces_after_operator.is_empty() { if !spaces_after_operator.is_empty() {
ann_type = arena body = arena
.alloc(ann_type.value) .alloc(body.value)
.with_spaces_before(spaces_after_operator, ann_type.region); .with_spaces_before(spaces_after_operator, body.region);
} }
let alias_region = Region::span_across(&call.region, &ann_type.region); let body_region = Region::span_across(&call.region, &body.region);
let alias = Def::Body( let alias = ValueDef::Body(
arena.alloc(Loc::at(expr_region, good)), arena.alloc(Loc::at(expr_region, good)),
arena.alloc(ann_type), arena.alloc(body),
); )
.into();
(&*arena.alloc(Loc::at(alias_region, alias)), state) (&*arena.alloc(Loc::at(body_region, alias)), state)
} }
Err(_) => { Err(_) => {
// this `=` likely occurred inline; treat it as an invalid operator // this `=` likely occurred inline; treat it as an invalid operator

View file

@ -1,9 +1,9 @@
use crate::ast::Pattern; use crate::ast::{Has, Pattern};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident, Ident}; use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::parser::Progress::{self, *}; use crate::parser::Progress::{self, *};
use crate::parser::{ use crate::parser::{
backtrackable, optional, specialize, specialize_ref, word1, EPattern, PInParens, PRecord, backtrackable, optional, specialize, specialize_ref, then, word1, EPattern, PInParens, PRecord,
ParseResult, Parser, ParseResult, Parser,
}; };
use crate::state::State; use crate::state::State;
@ -68,32 +68,63 @@ pub fn loc_pattern_help<'a>(min_indent: u32) -> impl Parser<'a, Loc<Pattern<'a>>
fn loc_tag_pattern_args_help<'a>( fn loc_tag_pattern_args_help<'a>(
min_indent: u32, min_indent: u32,
) -> impl Parser<'a, Vec<'a, Loc<Pattern<'a>>>, EPattern<'a>> { ) -> impl Parser<'a, Vec<'a, Loc<Pattern<'a>>>, EPattern<'a>> {
zero_or_more!(loc_tag_pattern_arg(min_indent)) zero_or_more!(loc_tag_pattern_arg(min_indent, false))
} }
fn loc_tag_pattern_arg<'a>(min_indent: u32) -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>> { /// Like `loc_tag_pattern_args_help`, but stops if a "has" keyword is seen (indicating an ability).
fn loc_type_def_tag_pattern_args_help<'a>(
min_indent: u32,
) -> impl Parser<'a, Vec<'a, Loc<Pattern<'a>>>, EPattern<'a>> {
zero_or_more!(loc_tag_pattern_arg(min_indent, true))
}
fn loc_tag_pattern_arg<'a>(
min_indent: u32,
stop_on_has_kw: bool,
) -> impl Parser<'a, Loc<Pattern<'a>>, EPattern<'a>> {
// Don't parse operators, because they have a higher precedence than function application. // Don't parse operators, because they have a higher precedence than function application.
// If we encounter one, we're done parsing function args! // If we encounter one, we're done parsing function args!
move |arena, state| { move |arena, original_state: State<'a>| {
let (_, spaces, state) = let (_, spaces, state) = backtrackable(space0_e(min_indent, EPattern::IndentStart))
backtrackable(space0_e(min_indent, EPattern::IndentStart)).parse(arena, state)?; .parse(arena, original_state.clone())?;
let (_, loc_pat, state) = loc_parse_tag_pattern_arg(min_indent, arena, state)?; let (_, loc_pat, state) = loc_parse_tag_pattern_arg(min_indent, arena, state)?;
let Loc { region, value } = loc_pat; let Loc { region, value } = loc_pat;
Ok(( if stop_on_has_kw && matches!(value, Pattern::Identifier("has")) {
MadeProgress, Err((
if spaces.is_empty() { NoProgress,
Loc::at(region, value) EPattern::End(original_state.pos()),
} else { original_state,
Loc::at(region, Pattern::SpaceBefore(arena.alloc(value), spaces)) ))
}, } else {
state, Ok((
)) MadeProgress,
if spaces.is_empty() {
Loc::at(region, value)
} else {
Loc::at(region, Pattern::SpaceBefore(arena.alloc(value), spaces))
},
state,
))
}
} }
} }
pub fn loc_has_parser<'a>(min_indent: u32) -> impl Parser<'a, Loc<Has<'a>>, EPattern<'a>> {
then(
loc_tag_pattern_arg(min_indent, false),
|_arena, state, progress, pattern| {
if matches!(pattern.value, Pattern::Identifier("has")) {
Ok((progress, Loc::at(pattern.region, Has::Has), state))
} else {
Err((progress, EPattern::End(state.pos()), state))
}
},
)
}
fn loc_parse_tag_pattern_arg<'a>( fn loc_parse_tag_pattern_arg<'a>(
min_indent: u32, min_indent: u32,
arena: &'a Bump, arena: &'a Bump,
@ -191,7 +222,7 @@ fn loc_ident_pattern_help<'a>(
// Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)` // Make sure `Foo Bar 1` is parsed as `Foo (Bar) 1`, and not `Foo (Bar 1)`
if can_have_arguments { if can_have_arguments {
let (_, loc_args, state) = let (_, loc_args, state) =
loc_tag_pattern_args_help(min_indent).parse(arena, state)?; loc_type_def_tag_pattern_args_help(min_indent).parse(arena, state)?;
if loc_args.is_empty() { if loc_args.is_empty() {
Ok((MadeProgress, loc_tag, state)) Ok((MadeProgress, loc_tag, state))

View file

@ -1,34 +1,36 @@
Defs( Defs(
[ [
@0-36 Ability { @0-36 Type(
header: TypeHeader { Ability {
name: @0-4 "Hash", header: TypeHeader {
vars: [], name: @0-4 "Hash",
}, vars: [],
loc_has: @5-8 Has,
demands: [
AbilityDemand {
name: @11-15 SpaceBefore(
"hash",
[
Newline,
],
),
typ: @33-36 Function(
[
@18-19 BoundVariable(
"a",
),
],
@33-36 Apply(
"",
"U64",
[],
),
),
}, },
], loc_has: @5-8 Has,
}, members: [
AbilityMember {
name: @11-15 SpaceBefore(
"hash",
[
Newline,
],
),
typ: @33-36 Function(
[
@18-19 BoundVariable(
"a",
),
],
@33-36 Apply(
"",
"U64",
[],
),
),
},
],
},
),
], ],
@38-39 SpaceBefore( @38-39 SpaceBefore(
Num( Num(

View file

@ -1,54 +1,56 @@
Defs( Defs(
[ [
@0-45 Ability { @0-45 Type(
header: TypeHeader { Ability {
name: @0-4 "Hash", header: TypeHeader {
vars: [], name: @0-4 "Hash",
vars: [],
},
loc_has: @5-8 Has,
members: [
AbilityMember {
name: @11-15 SpaceBefore(
"hash",
[
Newline,
],
),
typ: @23-26 Function(
[
@18-19 BoundVariable(
"a",
),
],
@23-26 Apply(
"",
"U64",
[],
),
),
},
AbilityMember {
name: @29-34 SpaceBefore(
"hash2",
[
Newline,
],
),
typ: @42-45 Function(
[
@37-38 BoundVariable(
"a",
),
],
@42-45 Apply(
"",
"U64",
[],
),
),
},
],
}, },
loc_has: @5-8 Has, ),
demands: [
AbilityDemand {
name: @11-15 SpaceBefore(
"hash",
[
Newline,
],
),
typ: @23-26 Function(
[
@18-19 BoundVariable(
"a",
),
],
@23-26 Apply(
"",
"U64",
[],
),
),
},
AbilityDemand {
name: @29-34 SpaceBefore(
"hash2",
[
Newline,
],
),
typ: @42-45 Function(
[
@37-38 BoundVariable(
"a",
),
],
@42-45 Apply(
"",
"U64",
[],
),
),
},
],
},
], ],
@47-48 SpaceBefore( @47-48 SpaceBefore(
Num( Num(

View file

@ -1,31 +1,45 @@
Defs( Defs(
[ [
@0-24 Ability { @0-37 Type(
header: TypeHeader { Ability {
name: @0-4 "Hash", header: TypeHeader {
vars: [], name: @0-4 "Hash",
}, vars: [],
loc_has: @5-8 Has,
demands: [
AbilityDemand {
name: @9-13 "hash",
typ: @21-24 Function(
[
@16-17 BoundVariable(
"a",
),
],
@21-24 Apply(
"",
"U64",
[],
),
),
}, },
], loc_has: @5-8 Has,
}, members: [
AbilityMember {
name: @9-13 "hash",
typ: @21-37 Where(
@21-24 Function(
[
@16-17 BoundVariable(
"a",
),
],
@21-24 Apply(
"",
"U64",
[],
),
),
[
@27-37 HasClause {
var: @27-28 "a",
ability: @33-37 Apply(
"",
"Hash",
[],
),
},
],
),
},
],
},
),
], ],
@26-27 SpaceBefore( @39-40 SpaceBefore(
Num( Num(
"1", "1",
), ),

View file

@ -1,3 +1,3 @@
Hash has hash : a -> U64 Hash has hash : a -> U64 | a has Hash
1 1

View file

@ -0,0 +1,87 @@
Defs(
[
@0-33 Type(
Ability {
header: TypeHeader {
name: @0-3 "Ab1",
vars: [],
},
loc_has: @4-7 Has,
members: [
AbilityMember {
name: @8-11 "ab1",
typ: @19-33 Where(
@19-21 Function(
[
@14-15 BoundVariable(
"a",
),
],
@19-21 Record {
fields: [],
ext: None,
},
),
[
@24-33 HasClause {
var: @24-25 "a",
ability: @30-33 Apply(
"",
"Ab1",
[],
),
},
],
),
},
],
},
),
@35-68 Type(
Ability {
header: TypeHeader {
name: @35-38 "Ab2",
vars: [],
},
loc_has: @39-42 Has,
members: [
AbilityMember {
name: @43-46 "ab2",
typ: @54-68 Where(
@54-56 Function(
[
@49-50 BoundVariable(
"a",
),
],
@54-56 Record {
fields: [],
ext: None,
},
),
[
@59-68 HasClause {
var: @59-60 "a",
ability: @65-68 Apply(
"",
"Ab2",
[],
),
},
],
),
},
],
},
),
],
@70-71 SpaceBefore(
Num(
"1",
),
[
Newline,
Newline,
],
),
)

View file

@ -0,0 +1,5 @@
Ab1 has ab1 : a -> {} | a has Ab1
Ab2 has ab2 : a -> {} | a has Ab2
1

View file

@ -1,53 +1,55 @@
Defs( Defs(
[ [
@15-49 AnnotatedBody { @15-49 Value(
ann_pattern: @0-8 RecordDestructure( AnnotatedBody {
[ ann_pattern: @0-8 RecordDestructure(
@2-3 Identifier( [
"x", @2-3 Identifier(
), "x",
@5-7 Identifier( ),
"y", @5-7 Identifier(
), "y",
], ),
), ],
ann_type: @11-14 Apply( ),
"", ann_type: @11-14 Apply(
"Foo", "",
[], "Foo",
), [],
comment: None, ),
body_pattern: @15-23 RecordDestructure( comment: None,
[ body_pattern: @15-23 RecordDestructure(
@17-18 Identifier( [
"x", @17-18 Identifier(
), "x",
@20-21 Identifier( ),
"y", @20-21 Identifier(
), "y",
], ),
), ],
body_expr: @26-49 Record( ),
[ body_expr: @26-49 Record(
@28-37 RequiredValue( [
@28-29 "x", @28-37 RequiredValue(
[], @28-29 "x",
@32-37 Str( [],
PlainLine( @32-37 Str(
"foo", PlainLine(
"foo",
),
), ),
), ),
), @39-47 RequiredValue(
@39-47 RequiredValue( @39-40 "y",
@39-40 "y", [],
[], @43-47 Float(
@43-47 Float( "3.14",
"3.14", ),
), ),
), ],
], ),
), },
}, ),
], ],
@51-52 SpaceBefore( @51-52 SpaceBefore(
Var { Var {

View file

@ -1,54 +1,56 @@
Defs( Defs(
[ [
@26-46 AnnotatedBody { @26-46 Value(
ann_pattern: @0-8 Apply( AnnotatedBody {
@0-6 GlobalTag( ann_pattern: @0-8 Apply(
"UserId", @0-6 GlobalTag(
), "UserId",
[
@7-8 Identifier(
"x",
), ),
], [
), @7-8 Identifier(
ann_type: @11-25 TagUnion { "x",
ext: None, ),
tags: [ ],
@13-23 Global { ),
name: @13-19 "UserId", ann_type: @11-25 TagUnion {
args: [ ext: None,
@20-23 Apply( tags: [
"", @13-23 Global {
"I64", name: @13-19 "UserId",
[], args: [
), @20-23 Apply(
], "",
}, "I64",
], [],
),
],
},
],
},
comment: None,
body_pattern: @26-34 Apply(
@26-32 GlobalTag(
"UserId",
),
[
@33-34 Identifier(
"x",
),
],
),
body_expr: @37-46 Apply(
@37-43 GlobalTag(
"UserId",
),
[
@44-46 Num(
"42",
),
],
Space,
),
}, },
comment: None, ),
body_pattern: @26-34 Apply(
@26-32 GlobalTag(
"UserId",
),
[
@33-34 Identifier(
"x",
),
],
),
body_expr: @37-46 Apply(
@37-43 GlobalTag(
"UserId",
),
[
@44-46 Num(
"42",
),
],
Space,
),
},
], ],
@48-49 SpaceBefore( @48-49 SpaceBefore(
Var { Var {

View file

@ -1,12 +1,14 @@
SpaceBefore( SpaceBefore(
Defs( Defs(
[ [
@107-112 Body( @107-112 Value(
@107-108 Identifier( Body(
"x", @107-108 Identifier(
), "x",
@111-112 Num( ),
"5", @111-112 Num(
"5",
),
), ),
), ),
], ],

View file

@ -1,12 +1,14 @@
[ [
@0-7 SpaceAfter( @0-7 SpaceAfter(
SpaceBefore( SpaceBefore(
Body( Value(
@0-3 Identifier( Body(
"foo", @0-3 Identifier(
), "foo",
@6-7 Num( ),
"1", @6-7 Num(
"1",
),
), ),
), ),
[], [],

View file

@ -1,28 +1,30 @@
Defs( Defs(
[ [
@0-36 Body( @0-36 Value(
@0-5 Apply( Body(
@0-5 GlobalTag( @0-5 Apply(
"Email", @0-5 GlobalTag(
), "Email",
[
@6-9 Identifier(
"str",
), ),
], [
), @6-9 Identifier(
@12-36 Apply( "str",
@12-17 GlobalTag(
"Email",
),
[
@18-36 Str(
PlainLine(
"blah@example.com",
), ),
],
),
@12-36 Apply(
@12-17 GlobalTag(
"Email",
), ),
], [
Space, @18-36 Str(
PlainLine(
"blah@example.com",
),
),
],
Space,
),
), ),
), ),
], ],

View file

@ -1,11 +1,13 @@
Defs( Defs(
[ [
@0-6 Body( @0-6 Value(
@0-4 Identifier( Body(
"iffy", @0-4 Identifier(
), "iffy",
@5-6 Num( ),
"5", @5-6 Num(
"5",
),
), ),
), ),
], ],

View file

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

View file

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

View file

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

View file

@ -1,12 +1,14 @@
SpaceBefore( SpaceBefore(
Defs( Defs(
[ [
@113-118 Body( @113-118 Value(
@113-114 Identifier( Body(
"x", @113-114 Identifier(
), "x",
@117-118 Num( ),
"5", @117-118 Num(
"5",
),
), ),
), ),
], ],

View file

@ -1,36 +1,40 @@
[ [
@0-24 SpaceAfter( @0-24 SpaceAfter(
SpaceBefore( SpaceBefore(
Body( Value(
@0-4 Identifier( Body(
"main", @0-4 Identifier(
), "main",
@11-24 SpaceBefore( ),
Defs( @11-24 SpaceBefore(
[ Defs(
@11-17 Body( [
@11-12 Identifier( @11-17 Value(
"i", Body(
), @11-12 Identifier(
@15-17 Num( "i",
"64", ),
), @15-17 Num(
), "64",
], ),
@23-24 SpaceBefore( ),
Var { ),
module_name: "", ],
ident: "i", @23-24 SpaceBefore(
}, Var {
[ module_name: "",
Newline, ident: "i",
Newline, },
], [
), Newline,
Newline,
],
),
),
[
Newline,
],
), ),
[
Newline,
],
), ),
), ),
[], [],

View file

@ -1,17 +1,19 @@
Defs( Defs(
[ [
@0-10 Annotation( @0-10 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@8-10 SpaceBefore( ),
Record { @8-10 SpaceBefore(
fields: [], Record {
ext: None, fields: [],
}, ext: None,
[ },
Newline, [
], Newline,
],
),
), ),
), ),
], ],

View file

@ -1,19 +1,21 @@
Defs( Defs(
[ [
@0-19 Annotation( @0-19 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@17-19 SpaceBefore( ),
Record { @17-19 SpaceBefore(
fields: [], Record {
ext: None, fields: [],
}, ext: None,
[ },
LineComment( [
" comment", LineComment(
), " comment",
], ),
],
),
), ),
), ),
], ],

View file

@ -1,93 +1,97 @@
[ [
@0-115 SpaceAfter( @0-115 SpaceAfter(
SpaceBefore( SpaceBefore(
Body( Value(
@0-4 Identifier( Body(
"main", @0-4 Identifier(
), "main",
@11-115 SpaceBefore( ),
Defs( @11-115 SpaceBefore(
[ Defs(
@43-93 AnnotatedBody { [
ann_pattern: @11-23 Identifier( @43-93 Value(
"wrappedNotEq", AnnotatedBody {
), ann_pattern: @11-23 Identifier(
ann_type: @34-38 Function( "wrappedNotEq",
[
@26-27 BoundVariable(
"a",
), ),
@29-30 BoundVariable( ann_type: @34-38 Function(
"a",
),
],
@34-38 Apply(
"",
"Bool",
[],
),
),
comment: None,
body_pattern: @43-55 Identifier(
"wrappedNotEq",
),
body_expr: @58-93 Closure(
[
@59-63 Identifier(
"num1",
),
@65-69 Identifier(
"num2",
),
],
@81-93 SpaceBefore(
BinOps(
[ [
( @26-27 BoundVariable(
@81-85 Var { "a",
module_name: "", ),
ident: "num1", @29-30 BoundVariable(
}, "a",
@86-88 NotEquals,
), ),
], ],
@89-93 Var { @34-38 Apply(
module_name: "", "",
ident: "num2", "Bool",
}, [],
),
), ),
[ comment: None,
Newline, body_pattern: @43-55 Identifier(
], "wrappedNotEq",
), ),
body_expr: @58-93 Closure(
[
@59-63 Identifier(
"num1",
),
@65-69 Identifier(
"num2",
),
],
@81-93 SpaceBefore(
BinOps(
[
(
@81-85 Var {
module_name: "",
ident: "num1",
},
@86-88 NotEquals,
),
],
@89-93 Var {
module_name: "",
ident: "num2",
},
),
[
Newline,
],
),
),
},
), ),
},
],
@99-115 SpaceBefore(
Apply(
@99-111 Var {
module_name: "",
ident: "wrappedNotEq",
},
[
@112-113 Num(
"2",
),
@114-115 Num(
"3",
),
],
Space,
),
[
Newline,
Newline,
], ],
@99-115 SpaceBefore(
Apply(
@99-111 Var {
module_name: "",
ident: "wrappedNotEq",
},
[
@112-113 Num(
"2",
),
@114-115 Num(
"3",
),
],
Space,
),
[
Newline,
Newline,
],
),
), ),
[
Newline,
],
), ),
[
Newline,
],
), ),
), ),
[], [],

View file

@ -1,16 +1,18 @@
Defs( Defs(
[ [
@0-9 Body( @0-9 Value(
@0-1 Identifier( Body(
"x", @0-1 Identifier(
), "x",
@8-9 SpaceBefore( ),
Num( @8-9 SpaceBefore(
"5", Num(
"5",
),
[
Newline,
],
), ),
[
Newline,
],
), ),
), ),
], ],

View file

@ -1,25 +1,27 @@
Defs( Defs(
[ [
@0-13 Body( @0-13 Value(
@0-1 Identifier( Body(
"x", @0-1 Identifier(
), "x",
@4-13 BinOps( ),
[ @4-13 BinOps(
( [
@4-5 SpaceAfter( (
Num( @4-5 SpaceAfter(
"1", Num(
"1",
),
[
Newline,
],
), ),
[ @10-11 LessThan,
Newline,
],
), ),
@10-11 LessThan, ],
@12-13 Num(
"2",
), ),
],
@12-13 Num(
"2",
), ),
), ),
), ),

View file

@ -1,12 +1,14 @@
SpaceBefore( SpaceBefore(
Defs( Defs(
[ [
@46-51 Body( @46-51 Value(
@46-47 Identifier( Body(
"x", @46-47 Identifier(
), "x",
@50-51 Num( ),
"5", @50-51 Num(
"5",
),
), ),
), ),
], ],

View file

@ -1,12 +1,14 @@
SpaceBefore( SpaceBefore(
Defs( Defs(
[ [
@18-21 Body( @18-21 Value(
@18-19 Identifier( Body(
"x", @18-19 Identifier(
), "x",
@20-21 Num( ),
"5", @20-21 Num(
"5",
),
), ),
), ),
], ],

View file

@ -1,12 +1,14 @@
SpaceBefore( SpaceBefore(
Defs( Defs(
[ [
@18-23 Body( @18-23 Value(
@18-19 Identifier( Body(
"x", @18-19 Identifier(
), "x",
@22-23 Num( ),
"5", @22-23 Num(
"5",
),
), ),
), ),
], ],

View file

@ -1,17 +1,19 @@
[ [
@0-9 SpaceAfter( @0-9 SpaceAfter(
SpaceBefore( SpaceBefore(
Opaque { Type(
header: TypeHeader { Opaque {
name: @0-3 "Age", header: TypeHeader {
vars: [], name: @0-3 "Age",
vars: [],
},
typ: @7-9 Apply(
"",
"U8",
[],
),
}, },
typ: @7-9 Apply( ),
"",
"U8",
[],
),
},
[], [],
), ),
[ [

View file

@ -1,46 +1,48 @@
[ [
@0-53 SpaceAfter( @0-53 SpaceAfter(
SpaceBefore( SpaceBefore(
Opaque { Type(
header: TypeHeader { Opaque {
name: @0-10 "Bookmark", header: TypeHeader {
vars: [ name: @0-10 "Bookmark",
@9-10 Identifier( vars: [
"a", @9-10 Identifier(
),
],
},
typ: @14-53 Record {
fields: [
@16-28 RequiredValue(
@16-23 "chapter",
[],
@25-28 Apply(
"",
"Str",
[],
),
),
@30-41 RequiredValue(
@30-36 "stanza",
[],
@38-41 Apply(
"",
"Str",
[],
),
),
@43-51 RequiredValue(
@43-48 "notes",
[],
@50-51 BoundVariable(
"a", "a",
), ),
), ],
], },
ext: None, typ: @14-53 Record {
fields: [
@16-28 RequiredValue(
@16-23 "chapter",
[],
@25-28 Apply(
"",
"Str",
[],
),
),
@30-41 RequiredValue(
@30-36 "stanza",
[],
@38-41 Apply(
"",
"Str",
[],
),
),
@43-51 RequiredValue(
@43-48 "notes",
[],
@50-51 BoundVariable(
"a",
),
),
],
ext: None,
},
}, },
}, ),
[], [],
), ),
[ [

View file

@ -1,30 +1,32 @@
Defs( Defs(
[ [
@0-26 Alias { @0-26 Type(
header: TypeHeader { Alias {
name: @0-4 "Blah", header: TypeHeader {
vars: [ name: @0-4 "Blah",
@5-6 Identifier( vars: [
"a", @5-6 Identifier(
), "a",
@7-8 Identifier( ),
"b", @7-8 Identifier(
), "b",
], ),
],
},
ann: @11-26 Apply(
"Foo.Bar",
"Baz",
[
@23-24 BoundVariable(
"x",
),
@25-26 BoundVariable(
"y",
),
],
),
}, },
ann: @11-26 Apply( ),
"Foo.Bar",
"Baz",
[
@23-24 BoundVariable(
"x",
),
@25-26 BoundVariable(
"y",
),
],
),
},
], ],
@28-30 SpaceBefore( @28-30 SpaceBefore(
Num( Num(

View file

@ -1,34 +1,36 @@
Defs( Defs(
[ [
@0-33 Annotation( @0-33 Value(
@0-3 Identifier( Annotation(
"foo", @0-3 Identifier(
), "foo",
@6-33 As( ),
@6-21 Apply( @6-33 As(
"Foo.Bar", @6-21 Apply(
"Baz", "Foo.Bar",
[ "Baz",
@18-19 BoundVariable( [
"x", @18-19 BoundVariable(
), "x",
@20-21 BoundVariable( ),
"y", @20-21 BoundVariable(
), "y",
], ),
],
),
[],
TypeHeader {
name: @25-29 "Blah",
vars: [
@30-31 Identifier(
"a",
),
@32-33 Identifier(
"b",
),
],
},
), ),
[],
TypeHeader {
name: @25-29 "Blah",
vars: [
@30-31 Identifier(
"a",
),
@32-33 Identifier(
"b",
),
],
},
), ),
), ),
], ],

View file

@ -1,28 +1,32 @@
SpaceBefore( SpaceBefore(
Defs( Defs(
[ [
@18-30 Body( @18-30 Value(
@18-26 RecordDestructure( Body(
[ @18-26 RecordDestructure(
@20-21 Identifier( [
"x", @20-21 Identifier(
), "x",
@23-25 Identifier( ),
"y", @23-25 Identifier(
), "y",
], ),
), ],
@29-30 Num( ),
"5", @29-30 Num(
"5",
),
), ),
), ),
@31-36 SpaceBefore( @31-36 SpaceBefore(
Body( Value(
@31-32 Identifier( Body(
"y", @31-32 Identifier(
), "y",
@35-36 Num( ),
"6", @35-36 Num(
"6",
),
), ),
), ),
[ [

View file

@ -1,102 +1,104 @@
Defs( Defs(
[ [
@0-122 Annotation( @0-122 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@8-122 SpaceBefore( ),
Record { @8-122 SpaceBefore(
fields: [ Record {
@18-38 SpaceBefore( fields: [
RequiredValue( @18-38 SpaceBefore(
@18-25 "getLine", RequiredValue(
[], @18-25 "getLine",
@28-38 Apply( [],
"", @28-38 Apply(
"Effect",
[
@35-38 Apply(
"",
"Str",
[],
),
],
),
),
[
Newline,
],
),
@48-75 SpaceBefore(
RequiredValue(
@48-55 "putLine",
[],
@65-75 Function(
[
@58-61 Apply(
"",
"Str",
[],
),
],
@65-75 Apply(
"", "",
"Effect", "Effect",
[ [
@72-75 Apply( @35-38 Apply(
"", "",
"Int", "Str",
[], [],
), ),
], ],
), ),
), ),
),
[
Newline,
],
),
@85-94 SpaceBefore(
RequiredValue(
@85-89 "text",
[],
@91-94 Apply(
"",
"Str",
[],
),
),
[
Newline,
],
),
@104-116 SpaceBefore(
SpaceAfter(
RequiredValue(
@104-109 "value",
[],
@111-116 Apply(
"",
"Int",
[
@115-116 Wildcard,
],
),
),
[ [
Newline, Newline,
], ],
), ),
[ @48-75 SpaceBefore(
Newline, RequiredValue(
], @48-55 "putLine",
), [],
@65-75 Function(
[
@58-61 Apply(
"",
"Str",
[],
),
],
@65-75 Apply(
"",
"Effect",
[
@72-75 Apply(
"",
"Int",
[],
),
],
),
),
),
[
Newline,
],
),
@85-94 SpaceBefore(
RequiredValue(
@85-89 "text",
[],
@91-94 Apply(
"",
"Str",
[],
),
),
[
Newline,
],
),
@104-116 SpaceBefore(
SpaceAfter(
RequiredValue(
@104-109 "value",
[],
@111-116 Apply(
"",
"Int",
[
@115-116 Wildcard,
],
),
),
[
Newline,
],
),
[
Newline,
],
),
],
ext: None,
},
[
Newline,
], ],
ext: None, ),
},
[
Newline,
],
), ),
), ),
], ],

View file

@ -1,11 +1,13 @@
[ [
@12-19 SpaceBefore( @12-19 SpaceBefore(
Body( Value(
@12-15 Identifier( Body(
"foo", @12-15 Identifier(
), "foo",
@18-19 Num( ),
"1", @18-19 Num(
"1",
),
), ),
), ),
[ [
@ -15,13 +17,15 @@
], ],
), ),
@33-43 SpaceBefore( @33-43 SpaceBefore(
Body( Value(
@33-36 Identifier( Body(
"bar", @33-36 Identifier(
), "bar",
@39-43 Str( ),
PlainLine( @39-43 Str(
"hi", PlainLine(
"hi",
),
), ),
), ),
), ),
@ -35,13 +39,15 @@
), ),
@44-57 SpaceAfter( @44-57 SpaceAfter(
SpaceBefore( SpaceBefore(
Body( Value(
@44-47 Identifier( Body(
"baz", @44-47 Identifier(
), "baz",
@50-57 Str( ),
PlainLine( @50-57 Str(
"stuff", PlainLine(
"stuff",
),
), ),
), ),
), ),

View file

@ -1,21 +1,25 @@
SpaceBefore( SpaceBefore(
Defs( Defs(
[ [
@18-23 Body( @18-23 Value(
@18-19 Identifier( Body(
"x", @18-19 Identifier(
), "x",
@22-23 Num( ),
"5", @22-23 Num(
"5",
),
), ),
), ),
@24-29 SpaceBefore( @24-29 SpaceBefore(
Body( Value(
@24-25 Identifier( Body(
"y", @24-25 Identifier(
), "y",
@28-29 Num( ),
"6", @28-29 Num(
"6",
),
), ),
), ),
[ [

View file

@ -1,28 +1,30 @@
Defs( Defs(
[ [
@0-30 Annotation( @0-30 Value(
@0-7 Identifier( Annotation(
"doStuff", @0-7 Identifier(
), "doStuff",
@20-30 Function( ),
[ @20-30 Function(
@10-16 Apply(
"",
"UserId",
[],
),
],
@20-30 Apply(
"",
"Task",
[ [
@25-28 Apply( @10-16 Apply(
"", "",
"Str", "UserId",
[], [],
), ),
@29-30 Inferred,
], ],
@20-30 Apply(
"",
"Task",
[
@25-28 Apply(
"",
"Str",
[],
),
@29-30 Inferred,
],
),
), ),
), ),
), ),

View file

@ -1,37 +1,39 @@
Defs( Defs(
[ [
@0-27 Annotation( @0-27 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@15-27 Where( ),
@15-16 Function( @15-27 Where(
[
@4-5 BoundVariable(
"a",
),
],
@15-16 Function( @15-16 Function(
[ [
@10-11 BoundVariable( @4-5 BoundVariable(
"b", "a",
), ),
], ],
@15-16 BoundVariable( @15-16 Function(
"c", [
@10-11 BoundVariable(
"b",
),
],
@15-16 BoundVariable(
"c",
),
), ),
), ),
[
@20-27 HasClause {
var: @20-21 "a",
ability: @26-27 Apply(
"",
"A",
[],
),
},
],
), ),
[
@20-27 HasClause {
var: @20-21 "a",
ability: @26-27 Apply(
"",
"A",
[],
),
},
],
), ),
), ),
], ],

View file

@ -1,53 +1,55 @@
Defs( Defs(
[ [
@0-48 Annotation( @0-48 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@15-48 Where( ),
@15-16 Function( @15-48 Where(
[
@4-5 BoundVariable(
"a",
),
],
@15-16 Function( @15-16 Function(
[ [
@10-11 BoundVariable( @4-5 BoundVariable(
"b", "a",
), ),
], ],
@15-16 BoundVariable( @15-16 Function(
"c", [
@10-11 BoundVariable(
"b",
),
],
@15-16 BoundVariable(
"c",
),
), ),
), ),
[
@20-27 HasClause {
var: @20-21 "a",
ability: @26-27 Apply(
"",
"A",
[],
),
},
@29-37 HasClause {
var: @29-30 "b",
ability: @35-37 Apply(
"",
"Eq",
[],
),
},
@39-48 HasClause {
var: @39-40 "c",
ability: @45-48 Apply(
"",
"Ord",
[],
),
},
],
), ),
[
@20-27 HasClause {
var: @20-21 "a",
ability: @26-27 Apply(
"",
"A",
[],
),
},
@29-37 HasClause {
var: @29-30 "b",
ability: @35-37 Apply(
"",
"Eq",
[],
),
},
@39-48 HasClause {
var: @39-40 "c",
ability: @45-48 Apply(
"",
"Ord",
[],
),
},
],
), ),
), ),
], ],

View file

@ -1,68 +1,70 @@
Defs( Defs(
[ [
@0-67 Annotation( @0-67 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@15-67 Where( ),
@15-16 SpaceBefore( @15-67 Where(
Function( @15-16 SpaceBefore(
[ Function(
@4-5 BoundVariable(
"a",
),
],
@15-16 Function(
[ [
@10-11 BoundVariable( @4-5 BoundVariable(
"b", "a",
), ),
], ],
@15-16 BoundVariable( @15-16 Function(
"c", [
@10-11 BoundVariable(
"b",
),
],
@15-16 BoundVariable(
"c",
),
), ),
), ),
[
Newline,
],
), ),
[ [
Newline, @24-34 HasClause {
var: @24-25 "a",
ability: @30-34 Apply(
"",
"Hash",
[],
),
},
@42-50 HasClause {
var: @42-43 SpaceBefore(
"b",
[
Newline,
],
),
ability: @48-50 Apply(
"",
"Eq",
[],
),
},
@58-67 HasClause {
var: @58-59 SpaceBefore(
"c",
[
Newline,
],
),
ability: @64-67 Apply(
"",
"Ord",
[],
),
},
], ],
), ),
[
@24-34 HasClause {
var: @24-25 "a",
ability: @30-34 Apply(
"",
"Hash",
[],
),
},
@42-50 HasClause {
var: @42-43 SpaceBefore(
"b",
[
Newline,
],
),
ability: @48-50 Apply(
"",
"Eq",
[],
),
},
@58-67 HasClause {
var: @58-59 SpaceBefore(
"c",
[
Newline,
],
),
ability: @64-67 Apply(
"",
"Ord",
[],
),
},
],
), ),
), ),
], ],

View file

@ -1,23 +1,25 @@
Defs( Defs(
[ [
@0-15 Annotation( @0-15 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@4-15 Where( ),
@4-5 BoundVariable( @4-15 Where(
"a", @4-5 BoundVariable(
"a",
),
[
@8-15 HasClause {
var: @8-9 "a",
ability: @14-15 Apply(
"",
"A",
[],
),
},
],
), ),
[
@8-15 HasClause {
var: @8-9 "a",
ability: @14-15 Apply(
"",
"A",
[],
),
},
],
), ),
), ),
], ],

View file

@ -1,37 +1,39 @@
Defs( Defs(
[ [
@0-29 Annotation( @0-29 Value(
@0-1 Identifier( Annotation(
"f", @0-1 Identifier(
), "f",
@9-29 Where( ),
@9-12 SpaceBefore( @9-29 Where(
Function( @9-12 SpaceBefore(
[ Function(
@4-5 BoundVariable( [
"a", @4-5 BoundVariable(
"a",
),
],
@9-12 Apply(
"",
"U64",
[],
), ),
],
@9-12 Apply(
"",
"U64",
[],
), ),
[
Newline,
],
), ),
[ [
Newline, @19-29 HasClause {
var: @19-20 "a",
ability: @25-29 Apply(
"",
"Hash",
[],
),
},
], ],
), ),
[
@19-29 HasClause {
var: @19-20 "a",
ability: @25-29 Apply(
"",
"Hash",
[],
),
},
],
), ),
), ),
], ],

View file

@ -126,6 +126,7 @@ mod test_parse {
pass/ability_single_line.expr, pass/ability_single_line.expr,
pass/ability_multi_line.expr, pass/ability_multi_line.expr,
pass/ability_demand_signature_is_multiline.expr, pass/ability_demand_signature_is_multiline.expr,
pass/ability_two_in_a_row.expr,
pass/add_var_with_spaces.expr, pass/add_var_with_spaces.expr,
pass/add_with_spaces.expr, pass/add_with_spaces.expr,
pass/annotated_record_destructure.expr, pass/annotated_record_destructure.expr,

View file

@ -20,6 +20,14 @@ pub enum BadPattern {
Unsupported(PatternType), Unsupported(PatternType),
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ShadowKind {
Variable,
Alias,
Opaque,
Ability,
}
/// Problems that can occur in the course of canonicalization. /// Problems that can occur in the course of canonicalization.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Problem { pub enum Problem {
@ -33,9 +41,10 @@ pub enum Problem {
PrecedenceProblem(PrecedenceProblem), PrecedenceProblem(PrecedenceProblem),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(BadPattern, Region), UnsupportedPattern(BadPattern, Region),
ShadowingInAnnotation { Shadowing {
original_region: Region, original_region: Region,
shadow: Loc<Ident>, shadow: Loc<Ident>,
kind: ShadowKind,
}, },
CyclicAlias(Symbol, Region, Vec<Symbol>), CyclicAlias(Symbol, Region, Vec<Symbol>),
BadRecursion(Vec<CycleEntry>), BadRecursion(Vec<CycleEntry>),
@ -95,6 +104,30 @@ pub enum Problem {
region: Region, region: Region,
kind: ExtensionTypeKind, kind: ExtensionTypeKind,
}, },
AbilityHasTypeVariables {
name: Symbol,
variables_region: Region,
},
HasClauseIsNotAbility {
region: Region,
},
IllegalHasClause {
region: Region,
},
AbilityMemberMissingHasClause {
member: Symbol,
ability: Symbol,
region: Region,
},
AbilityMemberBindsExternalAbility {
member: Symbol,
ability: Symbol,
region: Region,
},
AliasUsesAbility {
loc_name: Loc<Symbol>,
ability: Symbol,
},
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -157,6 +190,7 @@ pub enum RuntimeError {
Shadowing { Shadowing {
original_region: Region, original_region: Region,
shadow: Loc<Ident>, shadow: Loc<Ident>,
kind: ShadowKind,
}, },
InvalidOptionalValue { InvalidOptionalValue {
field_name: Lowercase, field_name: Lowercase,

View file

@ -5271,6 +5271,21 @@ mod solve_expr {
) )
} }
#[test]
fn to_float() {
infer_eq_without_problem(
indoc!(
r#"
{
toF32: Num.toF32,
toF64: Num.toF64,
}
"#
),
r#"{ toF32 : Num * -> F32, toF64 : Num * -> F64 }"#,
)
}
#[test] #[test]
fn opaque_wrap_infer() { fn opaque_wrap_infer() {
infer_eq_without_problem( infer_eq_without_problem(
@ -5359,7 +5374,7 @@ mod solve_expr {
condition : Bool condition : Bool
v : Id [ Y Str, Z Str ] v : Id [ Y Str, Z Str ]
v = v =
if condition if condition
then $Id (Id 21 (Y "sasha")) then $Id (Id 21 (Y "sasha"))
else $Id (Id 21 (Z "felix")) else $Id (Id 21 (Z "felix"))
@ -5622,4 +5637,29 @@ mod solve_expr {
r#"Outer"#, r#"Outer"#,
) )
} }
#[test]
fn issue_2583_specialize_errors_behind_unified_branches() {
infer_eq_without_problem(
indoc!(
r#"
if True then List.first [] else Str.toI64 ""
"#
),
"Result I64 [ InvalidNumStr, ListWasEmpty ]*",
)
}
#[test]
fn lots_of_type_variables() {
infer_eq_without_problem(
indoc!(
r#"
fun = \a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,bb -> {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,bb}
fun
"#
),
"a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb -> { a : a, aa : aa, b : b, bb : bb, c : c, d : d, e : e, f : f, g : g, h : h, i : i, j : j, k : k, l : l, m : m, n : n, o : o, p : p, q : q, r : r, s : s, t : t, u : u, v : v, w : w, x : x, y : y, z : z }",
)
}
} }

View file

@ -954,7 +954,7 @@ fn list_keep_if_str_is_hello() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_on_empty_list_with_int_layout() { fn list_map_on_empty_list_with_int_layout() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -972,7 +972,7 @@ fn list_map_on_empty_list_with_int_layout() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_on_non_empty_list() { fn list_map_on_non_empty_list() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -990,7 +990,7 @@ fn list_map_on_non_empty_list() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_changes_input() { fn list_map_changes_input() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1008,7 +1008,7 @@ fn list_map_changes_input() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_on_big_list() { fn list_map_on_big_list() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1028,7 +1028,7 @@ fn list_map_on_big_list() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_with_type_change() { fn list_map_with_type_change() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1047,7 +1047,7 @@ fn list_map_with_type_change() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_using_defined_function() { fn list_map_using_defined_function() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -1069,7 +1069,7 @@ fn list_map_using_defined_function() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_map_all_inline() { fn list_map_all_inline() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(

View file

@ -2160,7 +2160,7 @@ fn max_u8() {
); );
} }
macro_rules! to_int_tests { macro_rules! num_conversion_tests {
($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($( ($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($(
#[test] #[test]
#[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))] #[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))]
@ -2171,7 +2171,7 @@ macro_rules! to_int_tests {
)*)*} )*)*}
} }
to_int_tests! { num_conversion_tests! {
"Num.toI8", i8, ( "Num.toI8", i8, (
to_i8_same_width, "15u8", 15, ["gen-wasm"] to_i8_same_width, "15u8", 15, ["gen-wasm"]
to_i8_truncate, "115i32", 115, ["gen-wasm"] to_i8_truncate, "115i32", 115, ["gen-wasm"]
@ -2232,6 +2232,36 @@ to_int_tests! {
to_nat_truncate, "115i128", 115 to_nat_truncate, "115i128", 115
to_nat_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128 to_nat_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128
) )
"Num.toF32", f32, (
to_f32_from_i8, "15i8", 15.0
to_f32_from_i16, "15i16", 15.0
to_f32_from_i32, "15i32", 15.0
to_f32_from_i64, "15i64", 15.0
to_f32_from_i128, "15i128", 15.0
to_f32_from_u8, "15u8", 15.0
to_f32_from_u16, "15u16", 15.0
to_f32_from_u32, "15u32", 15.0
to_f32_from_u64, "15u64", 15.0
to_f32_from_u128, "15u128", 15.0
to_f32_from_nat, "15nat", 15.0
to_f32_from_f32, "1.5f32", 1.5
to_f32_from_f64, "1.5f64", 1.5
)
"Num.toF64", f64, (
to_f64_from_i8, "15i8", 15.0
to_f64_from_i16, "15i16", 15.0
to_f64_from_i32, "15i32", 15.0
to_f64_from_i64, "15i64", 15.0
to_f64_from_i128, "15i128", 15.0
to_f64_from_u8, "15u8", 15.0
to_f64_from_u16, "15u16", 15.0
to_f64_from_u32, "15u32", 15.0
to_f64_from_u64, "15u64", 15.0
to_f64_from_u128, "15u128", 15.0
to_f64_from_nat, "15nat", 15.0
to_f64_from_f32, "1.5f32", 1.5
to_f64_from_f64, "1.5f64", 1.5
)
} }
macro_rules! to_int_checked_tests { macro_rules! to_int_checked_tests {

View file

@ -1074,7 +1074,7 @@ fn closure_in_list() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn specialize_closure() { fn specialize_closure() {
use roc_std::RocList; use roc_std::RocList;
@ -2660,7 +2660,7 @@ fn pattern_match_unit_tag() {
// see for why this is disabled on wasm32 https://github.com/rtfeldman/roc/issues/1687 // see for why this is disabled on wasm32 https://github.com/rtfeldman/roc/issues/1687
#[cfg(not(feature = "wasm-cli-run"))] #[cfg(not(feature = "wasm-cli-run"))]
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn mirror_llvm_alignment_padding() { fn mirror_llvm_alignment_padding() {
// see https://github.com/rtfeldman/roc/issues/1569 // see https://github.com/rtfeldman/roc/issues/1569
assert_evals_to!( assert_evals_to!(
@ -2683,7 +2683,7 @@ fn mirror_llvm_alignment_padding() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_set_bool() { fn lambda_set_bool() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -2708,7 +2708,7 @@ fn lambda_set_bool() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_set_byte() { fn lambda_set_byte() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -2762,7 +2762,7 @@ fn lambda_set_struct_byte() {
} }
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_set_enum_byte_byte() { fn lambda_set_enum_byte_byte() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -3275,3 +3275,47 @@ fn box_and_unbox_string() {
RocStr RocStr
) )
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn box_and_unbox_num() {
assert_evals_to!(
indoc!(
r#"
Box.unbox (Box.box (123u8))
"#
),
123,
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn box_and_unbox_record() {
assert_evals_to!(
indoc!(
r#"
Box.unbox (Box.box { a: 15u8, b: 27u8 })
"#
),
(15, 27),
(u8, u8)
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn box_and_unbox_tag_union() {
assert_evals_to!(
indoc!(
r#"
v : [ A U8, B U8 ] # usually stack allocated
v = B 27u8
Box.unbox (Box.box v)
"#
),
(27, 1),
(u8, u8)
)
}

View file

@ -301,8 +301,8 @@ fn refcount_different_rosetrees_inc() {
(Pointer, Pointer), (Pointer, Pointer),
&[ &[
Live(2), // s Live(2), // s
Live(3), // i1
Live(2), // s1 Live(2), // s1
Live(3), // i1
Live(1), // [i1, i1] Live(1), // [i1, i1]
Live(1), // i2 Live(1), // i2
Live(1), // [s1, s1] Live(1), // [s1, s1]

View file

@ -256,3 +256,15 @@ fn roc_result_err() {
RocResult<i64, RocStr> RocResult<i64, RocStr>
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn issue_2583_specialize_errors_behind_unified_branches() {
assert_evals_to!(
r#"
if True then List.first [15] else Str.toI64 ""
"#,
RocResult::ok(15i64),
RocResult<i64, bool>
)
}

View file

@ -1587,3 +1587,19 @@ fn str_to_dec() {
RocDec RocDec
); );
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn issue_2811() {
assert_evals_to!(
indoc!(
r#"
x = Command { tool: "bash" }
Command c = x
c.tool
"#
),
RocStr::from("bash"),
RocStr
);
}

View file

@ -0,0 +1,40 @@
procedure List.10 (#Attr.2):
let Test.20 : U64 = 0i64;
let Test.21 : U64 = lowlevel ListLen #Attr.2;
let Test.16 : Int1 = lowlevel NotEq Test.20 Test.21;
if Test.16 then
let Test.19 : U64 = 0i64;
let Test.18 : I64 = lowlevel ListGetUnsafe #Attr.2 Test.19;
let Test.17 : [C Int1, C I64] = Ok Test.18;
ret Test.17;
else
let Test.15 : Int1 = true;
let Test.14 : [C Int1, C I64] = Err Test.15;
ret Test.14;
procedure Str.28 (#Attr.2):
let #Attr.3 : {I64, U8} = lowlevel StrToNum #Attr.2;
let Test.9 : U8 = StructAtIndex 1 #Attr.3;
let Test.10 : U8 = 0i64;
let Test.6 : Int1 = lowlevel NumGt Test.9 Test.10;
if Test.6 then
let Test.8 : Int1 = false;
let Test.7 : [C Int1, C I64] = Err Test.8;
ret Test.7;
else
let Test.5 : I64 = StructAtIndex 0 #Attr.3;
let Test.4 : [C Int1, C I64] = Ok Test.5;
ret Test.4;
procedure Test.0 ():
let Test.11 : Int1 = true;
if Test.11 then
let Test.13 : List I64 = Array [];
let Test.12 : [C Int1, C I64] = CallByName List.10 Test.13;
dec Test.13;
ret Test.12;
else
let Test.3 : Str = "";
let Test.2 : [C Int1, C I64] = CallByName Str.28 Test.3;
dec Test.3;
ret Test.2;

View file

@ -0,0 +1,3 @@
procedure Test.0 ():
let Test.6 : Str = "bash";
ret Test.6;

View file

@ -1273,6 +1273,26 @@ fn issue_2725_alias_polymorphic_lambda() {
) )
} }
#[mono_test]
fn issue_2583_specialize_errors_behind_unified_branches() {
indoc!(
r#"
if True then List.first [] else Str.toI64 ""
"#
)
}
#[mono_test]
fn issue_2811() {
indoc!(
r#"
x = Command { tool: "bash" }
Command c = x
c.tool
"#
)
}
// #[ignore] // #[ignore]
// #[mono_test] // #[mono_test]
// fn static_str_closure() { // fn static_str_closure() {

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