mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-20 23:37:56 +03:00
Merge branch 'trunk' into cleanup-cli-example
This commit is contained in:
commit
48bd267892
@ -1,4 +1,4 @@
|
||||
[alias]
|
||||
test-gen-llvm = "test -p test_gen"
|
||||
test-gen-dev = "test -p roc_gen_dev -p test_gen --no-default-features --features gen-dev"
|
||||
test-gen-wasm = "test -p test_gen --no-default-features --features gen-wasm"
|
||||
test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --features gen-wasm"
|
||||
|
72
Cargo.lock
generated
72
Cargo.lock
generated
@ -3204,6 +3204,17 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157"
|
||||
|
||||
[[package]]
|
||||
name = "repl_test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"roc_cli",
|
||||
"roc_repl_cli",
|
||||
"roc_test_utils",
|
||||
"strip-ansi-escapes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv"
|
||||
version = "0.6.7"
|
||||
@ -3330,35 +3341,26 @@ dependencies = [
|
||||
"const_format",
|
||||
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
|
||||
"indoc",
|
||||
"inkwell 0.1.0",
|
||||
"libloading 0.7.1",
|
||||
"mimalloc",
|
||||
"pretty_assertions",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_docs",
|
||||
"roc_editor",
|
||||
"roc_error_macros",
|
||||
"roc_fmt",
|
||||
"roc_gen_llvm",
|
||||
"roc_linker",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_repl_cli",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"rustyline",
|
||||
"rustyline-derive",
|
||||
"serial_test",
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
@ -3672,6 +3674,56 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_repl_cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"const_format",
|
||||
"inkwell 0.1.0",
|
||||
"libloading 0.7.1",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_gen_llvm",
|
||||
"roc_load",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_repl_eval",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"rustyline",
|
||||
"rustyline-derive",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_repl_eval"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_fmt",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_repl_wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"roc_parse",
|
||||
"roc_repl_eval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_reporting"
|
||||
version = "0.1.0"
|
||||
|
@ -33,6 +33,10 @@ members = [
|
||||
"code_markup",
|
||||
"error_macros",
|
||||
"reporting",
|
||||
"repl_cli",
|
||||
"repl_eval",
|
||||
"repl_test",
|
||||
"repl_wasm",
|
||||
"roc_std",
|
||||
"test_utils",
|
||||
"utils",
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM rust:1.57.0-slim-bullseye
|
||||
FROM rust:1.57.0-slim-bullseye # make sure to update nixpkgs-unstable in sources.json too so that it uses the same rust version > search for cargo on unstable here: https://search.nixos.org/packages
|
||||
WORKDIR /earthbuild
|
||||
|
||||
prep-debian:
|
||||
@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
|
||||
|
||||
copy-dirs:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
||||
COPY --dir cli cli_utils compiler docs editor ast code_markup error_macros utils test_utils reporting repl_cli repl_eval repl_test repl_wasm roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
||||
|
||||
test-zig:
|
||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||
|
@ -1,6 +1,8 @@
|
||||
use bumpalo::Bump;
|
||||
use roc_can::expr::Recursive;
|
||||
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
||||
use roc_can::expr::{IntValue, Recursive};
|
||||
use roc_can::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
|
||||
};
|
||||
use roc_can::operator::desugar_expr;
|
||||
use roc_collections::all::MutSet;
|
||||
use roc_module::symbol::Symbol;
|
||||
@ -52,7 +54,7 @@ pub fn expr_to_expr2<'a>(
|
||||
match parse_expr {
|
||||
Float(string) => {
|
||||
match finish_parsing_float(string) {
|
||||
Ok(float) => {
|
||||
Ok((float, _bound)) => {
|
||||
let expr = Expr2::Float {
|
||||
number: FloatVal::F64(float),
|
||||
var: env.var_store.fresh(),
|
||||
@ -73,10 +75,13 @@ pub fn expr_to_expr2<'a>(
|
||||
}
|
||||
}
|
||||
Num(string) => {
|
||||
match finish_parsing_int(string) {
|
||||
Ok(int) => {
|
||||
match finish_parsing_num(string) {
|
||||
Ok(ParsedNumResult::UnknownNum(int) | ParsedNumResult::Int(int, _)) => {
|
||||
let expr = Expr2::SmallInt {
|
||||
number: IntVal::I64(int),
|
||||
number: IntVal::I64(match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => n as i64, // FIXME
|
||||
}),
|
||||
var: env.var_store.fresh(),
|
||||
// TODO non-hardcode
|
||||
style: IntStyle::Decimal,
|
||||
@ -85,6 +90,15 @@ pub fn expr_to_expr2<'a>(
|
||||
|
||||
(expr, Output::default())
|
||||
}
|
||||
Ok(ParsedNumResult::Float(float, _)) => {
|
||||
let expr = Expr2::Float {
|
||||
number: FloatVal::F64(float),
|
||||
var: env.var_store.fresh(),
|
||||
text: PoolStr::new(string, env.pool),
|
||||
};
|
||||
|
||||
(expr, Output::default())
|
||||
}
|
||||
Err((raw, error)) => {
|
||||
// emit runtime error
|
||||
let runtime_error = RuntimeError::InvalidInt(
|
||||
@ -107,9 +121,12 @@ pub fn expr_to_expr2<'a>(
|
||||
is_negative,
|
||||
} => {
|
||||
match finish_parsing_base(string, *base, *is_negative) {
|
||||
Ok(int) => {
|
||||
Ok((int, _bound)) => {
|
||||
let expr = Expr2::SmallInt {
|
||||
number: IntVal::I64(int),
|
||||
number: IntVal::I64(match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => n as i64, // FIXME
|
||||
}),
|
||||
var: env.var_store.fresh(),
|
||||
// TODO non-hardcode
|
||||
style: IntStyle::from_base(*base),
|
||||
|
@ -3,8 +3,10 @@
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use bumpalo::collections::Vec as BumpVec;
|
||||
use roc_can::expr::unescape_char;
|
||||
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
||||
use roc_can::expr::{unescape_char, IntValue};
|
||||
use roc_can::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, ParsedNumResult,
|
||||
};
|
||||
use roc_collections::all::BumpMap;
|
||||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_parse::ast::{StrLiteral, StrSegment};
|
||||
@ -183,18 +185,35 @@ pub fn to_pattern2<'a>(
|
||||
let problem = MalformedPatternProblem::MalformedFloat;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)),
|
||||
Ok((float, _bound)) => Pattern2::FloatLiteral(FloatVal::F64(float)),
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
NumLiteral(string) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_int(string) {
|
||||
WhenBranch => match finish_parsing_num(string) {
|
||||
Err(_error) => {
|
||||
let problem = MalformedPatternProblem::MalformedInt;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int),
|
||||
Ok(ParsedNumResult::UnknownNum(int)) => {
|
||||
Pattern2::NumLiteral(
|
||||
env.var_store.fresh(),
|
||||
match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => n as i64, // FIXME
|
||||
},
|
||||
)
|
||||
}
|
||||
Ok(ParsedNumResult::Int(int, _bound)) => {
|
||||
Pattern2::IntLiteral(IntVal::I64(match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => n as i64, // FIXME
|
||||
}))
|
||||
}
|
||||
Ok(ParsedNumResult::Float(int, _bound)) => {
|
||||
Pattern2::FloatLiteral(FloatVal::F64(int))
|
||||
}
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
@ -209,7 +228,11 @@ pub fn to_pattern2<'a>(
|
||||
let problem = MalformedPatternProblem::MalformedBase(*base);
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(int) => {
|
||||
Ok((int, _bound)) => {
|
||||
let int = match int {
|
||||
IntValue::U128(_) => todo!(),
|
||||
IntValue::I128(n) => n as i64, // FIXME
|
||||
};
|
||||
if *is_negative {
|
||||
Pattern2::IntLiteral(IntVal::I64(-int))
|
||||
} else {
|
||||
|
@ -15,14 +15,11 @@ test = false
|
||||
bench = false
|
||||
|
||||
[features]
|
||||
default = ["target-aarch64", "target-x86_64", "target-wasm32", "llvm", "editor"]
|
||||
default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
|
||||
|
||||
wasm32-cli-run = ["target-wasm32", "run-wasm32"]
|
||||
i386-cli-run = ["target-x86"]
|
||||
|
||||
# This is a separate feature because when we generate docs on Netlify,
|
||||
# it doesn't have LLVM installed. (Also, it doesn't need to do code gen.)
|
||||
llvm = ["inkwell", "roc_gen_llvm", "roc_build/llvm"]
|
||||
editor = ["roc_editor"]
|
||||
|
||||
run-wasm32 = ["wasmer", "wasmer-wasi"]
|
||||
@ -50,15 +47,9 @@ roc_docs = { path = "../docs" }
|
||||
roc_parse = { path = "../compiler/parse" }
|
||||
roc_region = { path = "../compiler/region" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_problem = { path = "../compiler/problem" }
|
||||
roc_types = { path = "../compiler/types" }
|
||||
roc_builtins = { path = "../compiler/builtins" }
|
||||
roc_constrain = { path = "../compiler/constrain" }
|
||||
roc_unify = { path = "../compiler/unify" }
|
||||
roc_solve = { path = "../compiler/solve" }
|
||||
roc_mono = { path = "../compiler/mono" }
|
||||
roc_load = { path = "../compiler/load" }
|
||||
roc_gen_llvm = { path = "../compiler/gen_llvm", optional = true }
|
||||
roc_build = { path = "../compiler/build", default-features = false }
|
||||
roc_fmt = { path = "../compiler/fmt" }
|
||||
roc_target = { path = "../compiler/roc_target" }
|
||||
@ -66,15 +57,12 @@ roc_reporting = { path = "../reporting" }
|
||||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_editor = { path = "../editor", optional = true }
|
||||
roc_linker = { path = "../linker" }
|
||||
roc_repl_cli = { path = "../repl_cli" }
|
||||
clap = { version = "= 3.0.0-beta.5", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
const_format = "0.2.22"
|
||||
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
|
||||
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "v9.1.1" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
mimalloc = { version = "0.1.26", default-features = false }
|
||||
|
||||
inkwell = { path = "../vendor/inkwell", optional = true }
|
||||
target-lexicon = "0.12.2"
|
||||
tempfile = "3.2.0"
|
||||
|
||||
@ -88,7 +76,6 @@ pretty_assertions = "1.0.0"
|
||||
roc_test_utils = { path = "../test_utils" }
|
||||
indoc = "1.0.3"
|
||||
serial_test = "0.5.1"
|
||||
tempfile = "3.2.0"
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
|
||||
cli_utils = { path = "../cli_utils" }
|
||||
|
||||
|
@ -12,7 +12,7 @@ use roc_parse::ast::{
|
||||
TypeAnnotation, WhenBranch,
|
||||
};
|
||||
use roc_parse::header::{
|
||||
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
};
|
||||
use roc_parse::{
|
||||
@ -199,14 +199,6 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
effects: Effects {
|
||||
spaces_before_effects_keyword: &[],
|
||||
spaces_after_effects_keyword: &[],
|
||||
spaces_after_type_name: &[],
|
||||
effect_shortname: header.effects.effect_shortname.remove_spaces(arena),
|
||||
effect_type_name: header.effects.effect_type_name.remove_spaces(arena),
|
||||
entries: header.effects.entries.remove_spaces(arena),
|
||||
},
|
||||
before_header: &[],
|
||||
after_platform_keyword: &[],
|
||||
before_requires: &[],
|
||||
@ -221,6 +213,25 @@ impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
after_provides: &[],
|
||||
},
|
||||
},
|
||||
Module::Hosted { header } => Module::Hosted {
|
||||
header: HostedHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
generates: header.generates.remove_spaces(arena),
|
||||
generates_with: header.generates_with.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_hosted_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_generates: &[],
|
||||
after_generates: &[],
|
||||
before_with: &[],
|
||||
after_with: &[],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
|
||||
|
||||
pub mod build;
|
||||
mod format;
|
||||
pub mod repl;
|
||||
pub use format::format;
|
||||
|
||||
pub const CMD_BUILD: &str = "build";
|
||||
|
@ -1,7 +1,7 @@
|
||||
use roc_cli::build::check_file;
|
||||
use roc_cli::{
|
||||
build_app, docs, format, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT,
|
||||
CMD_FORMAT, CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
|
||||
build_app, docs, format, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_FORMAT,
|
||||
CMD_REPL, CMD_VERSION, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
|
||||
};
|
||||
use roc_load::file::LoadingProblem;
|
||||
use std::fs::{self, FileType};
|
||||
@ -63,7 +63,7 @@ fn main() -> io::Result<()> {
|
||||
}
|
||||
}
|
||||
Some((CMD_REPL, _)) => {
|
||||
repl::main()?;
|
||||
roc_repl_cli::main()?;
|
||||
|
||||
// Exit 0 if the repl exited normally
|
||||
Ok(0)
|
||||
|
@ -1,159 +0,0 @@
|
||||
use std::mem::size_of;
|
||||
|
||||
pub trait AppMemory {
|
||||
fn deref_bool(&self, addr: usize) -> bool;
|
||||
|
||||
fn deref_u8(&self, addr: usize) -> u8;
|
||||
fn deref_u16(&self, addr: usize) -> u16;
|
||||
fn deref_u32(&self, addr: usize) -> u32;
|
||||
fn deref_u64(&self, addr: usize) -> u64;
|
||||
fn deref_u128(&self, addr: usize) -> u128;
|
||||
fn deref_usize(&self, addr: usize) -> usize;
|
||||
|
||||
fn deref_i8(&self, addr: usize) -> i8;
|
||||
fn deref_i16(&self, addr: usize) -> i16;
|
||||
fn deref_i32(&self, addr: usize) -> i32;
|
||||
fn deref_i64(&self, addr: usize) -> i64;
|
||||
fn deref_i128(&self, addr: usize) -> i128;
|
||||
fn deref_isize(&self, addr: usize) -> isize;
|
||||
|
||||
fn deref_f32(&self, addr: usize) -> f32;
|
||||
fn deref_f64(&self, addr: usize) -> f64;
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str;
|
||||
}
|
||||
|
||||
/// A block of app memory in the same address space as the compiler
|
||||
pub struct AppMemoryInternal;
|
||||
|
||||
macro_rules! internal_number_type {
|
||||
($name: ident, $t: ty) => {
|
||||
fn $name(&self, addr: usize) -> $t {
|
||||
let ptr = addr as *const _;
|
||||
unsafe { *ptr }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl AppMemory for AppMemoryInternal {
|
||||
internal_number_type!(deref_bool, bool);
|
||||
|
||||
internal_number_type!(deref_u8, u8);
|
||||
internal_number_type!(deref_u16, u16);
|
||||
internal_number_type!(deref_u32, u32);
|
||||
internal_number_type!(deref_u64, u64);
|
||||
internal_number_type!(deref_u128, u128);
|
||||
internal_number_type!(deref_usize, usize);
|
||||
|
||||
internal_number_type!(deref_i8, i8);
|
||||
internal_number_type!(deref_i16, i16);
|
||||
internal_number_type!(deref_i32, i32);
|
||||
internal_number_type!(deref_i64, i64);
|
||||
internal_number_type!(deref_i128, i128);
|
||||
internal_number_type!(deref_isize, isize);
|
||||
|
||||
internal_number_type!(deref_f32, f32);
|
||||
internal_number_type!(deref_f64, f64);
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str {
|
||||
unsafe { *(addr as *const &'static str) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A block of app memory copied from an exteral address space outside the compiler
|
||||
/// (e.g. compiler and app are in separate Wasm modules)
|
||||
pub struct AppMemoryExternal<'a> {
|
||||
bytes: &'a [u8],
|
||||
}
|
||||
|
||||
macro_rules! external_number_type {
|
||||
($name: ident, $t: ty) => {
|
||||
fn $name(&self, address: usize) -> $t {
|
||||
const N: usize = size_of::<$t>();
|
||||
let mut array = [0; N];
|
||||
array.copy_from_slice(&self.bytes[address..][..N]);
|
||||
<$t>::from_le_bytes(array)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a> AppMemory for AppMemoryExternal<'a> {
|
||||
fn deref_bool(&self, address: usize) -> bool {
|
||||
self.bytes[address] != 0
|
||||
}
|
||||
|
||||
external_number_type!(deref_u8, u8);
|
||||
external_number_type!(deref_u16, u16);
|
||||
external_number_type!(deref_u32, u32);
|
||||
external_number_type!(deref_u64, u64);
|
||||
external_number_type!(deref_u128, u128);
|
||||
external_number_type!(deref_usize, usize);
|
||||
|
||||
external_number_type!(deref_i8, i8);
|
||||
external_number_type!(deref_i16, i16);
|
||||
external_number_type!(deref_i32, i32);
|
||||
external_number_type!(deref_i64, i64);
|
||||
external_number_type!(deref_i128, i128);
|
||||
external_number_type!(deref_isize, isize);
|
||||
|
||||
external_number_type!(deref_f32, f32);
|
||||
external_number_type!(deref_f64, f64);
|
||||
|
||||
fn deref_str(&self, addr: usize) -> &str {
|
||||
let elems_addr = self.deref_usize(addr);
|
||||
let len = self.deref_usize(addr + size_of::<usize>());
|
||||
let bytes = &self.bytes[elems_addr..][..len];
|
||||
std::str::from_utf8(bytes).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn internal_u8() {
|
||||
let value: u8 = 123;
|
||||
let ptr = &value as *const u8;
|
||||
let addr = ptr as usize;
|
||||
let memory = AppMemoryInternal;
|
||||
let recovered: u8 = memory.deref_u8(addr);
|
||||
assert_eq!(value, recovered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_u8() {
|
||||
let value: u8 = 123;
|
||||
let memory = AppMemoryExternal {
|
||||
bytes: &[0, 0, value, 0, 0],
|
||||
};
|
||||
let addr = 2;
|
||||
let recovered: u8 = memory.deref_u8(addr);
|
||||
assert_eq!(value, recovered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_i64() {
|
||||
let value: i64 = -123 << 33;
|
||||
let ptr = &value as *const i64;
|
||||
let addr = ptr as usize;
|
||||
let memory = AppMemoryInternal;
|
||||
let recovered: i64 = memory.deref_i64(addr);
|
||||
assert_eq!(value, recovered);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_i64() {
|
||||
let value: i64 = -1 << 33;
|
||||
let memory = AppMemoryExternal {
|
||||
bytes: &[
|
||||
0, 0, //
|
||||
0, 0, 0, 0, 0xfe, 0xff, 0xff, 0xff, //
|
||||
0, 0,
|
||||
],
|
||||
};
|
||||
let addr = 2;
|
||||
let recovered: i64 = memory.deref_i64(addr);
|
||||
assert_eq!(value, recovered);
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
|
||||
// Check constraints
|
||||
//
|
||||
// Keep track of the used (in types or expectations) variables, and the declared variables (in
|
||||
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
|
||||
// and no variables that are used but not declared are allowed.
|
||||
//
|
||||
// There is one exception: the initial variable (that stores the type of the whole expression) is
|
||||
// never declared, but is used.
|
||||
pub fn assert_correct_variable_usage(constraint: &Constraint) {
|
||||
// variables declared in constraint (flex_vars or rigid_vars)
|
||||
// and variables actually used in constraints
|
||||
let (declared, used) = variable_usage(constraint);
|
||||
|
||||
let used: ImSet<Variable> = used.into();
|
||||
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
|
||||
|
||||
for var in declared.flex_vars.clone() {
|
||||
decl.insert(var);
|
||||
}
|
||||
|
||||
let diff = used.clone().relative_complement(decl);
|
||||
|
||||
// NOTE: this checks whether we're using variables that are not declared. For recursive type
|
||||
// definitions, their rigid types are declared twice, which is correct!
|
||||
if !diff.is_empty() {
|
||||
println!("VARIABLE USAGE PROBLEM");
|
||||
|
||||
println!("used: {:?}", &used);
|
||||
println!("rigids: {:?}", &declared.rigid_vars);
|
||||
println!("flexs: {:?}", &declared.flex_vars);
|
||||
|
||||
println!("difference: {:?}", &diff);
|
||||
|
||||
panic!("variable usage problem (see stdout for details)");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SeenVariables {
|
||||
pub rigid_vars: Vec<Variable>,
|
||||
pub flex_vars: Vec<Variable>,
|
||||
}
|
||||
|
||||
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
|
||||
let mut declared = SeenVariables::default();
|
||||
let mut used = ImSet::default();
|
||||
variable_usage_help(con, &mut declared, &mut used);
|
||||
|
||||
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
|
||||
|
||||
let mut used_vec: Vec<Variable> = used.into_iter().collect();
|
||||
used_vec.sort();
|
||||
|
||||
declared.rigid_vars.sort();
|
||||
declared.flex_vars.sort();
|
||||
|
||||
(declared, used_vec)
|
||||
}
|
||||
|
||||
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
|
||||
use Constraint::*;
|
||||
|
||||
match con {
|
||||
True | SaveTheEnvironment => (),
|
||||
Eq(tipe, expectation, _, _) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
for v in expectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Store(tipe, var, _, _) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
used.insert(*var);
|
||||
}
|
||||
Lookup(_, expectation, _) => {
|
||||
for v in expectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Pattern(_, _, tipe, pexpectation) => {
|
||||
for v in tipe.variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
||||
for v in pexpectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Let(letcon) => {
|
||||
declared.rigid_vars.extend(letcon.rigid_vars.clone());
|
||||
declared.flex_vars.extend(letcon.flex_vars.clone());
|
||||
|
||||
variable_usage_help(&letcon.defs_constraint, declared, used);
|
||||
variable_usage_help(&letcon.ret_constraint, declared, used);
|
||||
}
|
||||
And(constraints) => {
|
||||
for sub in constraints {
|
||||
variable_usage_help(sub, declared, used);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,277 +0,0 @@
|
||||
use crate::repl::app_memory::AppMemoryInternal;
|
||||
use crate::repl::eval;
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use inkwell::module::Linkage;
|
||||
use roc_build::link::module_to_dylib;
|
||||
use roc_build::program::FunctionIterator;
|
||||
use roc_can::builtins::builtin_defs_map;
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_fmt::annotation::Formattable;
|
||||
use roc_fmt::annotation::{Newlines, Parens};
|
||||
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
|
||||
use roc_load::file::LoadingProblem;
|
||||
use roc_mono::ir::OptLevel;
|
||||
use roc_parse::parser::SyntaxError;
|
||||
use roc_region::all::LineInfo;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::from_utf8_unchecked;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
pub enum ReplOutput {
|
||||
Problems(Vec<String>),
|
||||
NoProblems { expr: String, expr_type: String },
|
||||
}
|
||||
|
||||
pub fn gen_and_eval<'a>(
|
||||
src: &[u8],
|
||||
target: Triple,
|
||||
opt_level: OptLevel,
|
||||
) -> Result<ReplOutput, SyntaxError<'a>> {
|
||||
use roc_reporting::report::{
|
||||
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||
};
|
||||
|
||||
let arena = Bump::new();
|
||||
|
||||
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
|
||||
let src_str: &str = unsafe { from_utf8_unchecked(src) };
|
||||
|
||||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
let filename = PathBuf::from("REPL.roc");
|
||||
let src_dir = Path::new("fake/test/path");
|
||||
|
||||
let module_src = promote_expr_to_module(src_str);
|
||||
|
||||
let target_info = TargetInfo::from(&target);
|
||||
|
||||
let exposed_types = MutMap::default();
|
||||
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||
&arena,
|
||||
filename,
|
||||
&module_src,
|
||||
&stdlib,
|
||||
src_dir,
|
||||
exposed_types,
|
||||
target_info,
|
||||
builtin_defs_map,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
Ok(v) => v,
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
return Ok(ReplOutput::Problems(vec![report]));
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("error while loading module: {:?}", e)
|
||||
}
|
||||
};
|
||||
|
||||
use roc_load::file::MonomorphizedModule;
|
||||
let MonomorphizedModule {
|
||||
procedures,
|
||||
entry_point,
|
||||
interns,
|
||||
exposed_to_host,
|
||||
mut subs,
|
||||
module_id: home,
|
||||
sources,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let mut lines = Vec::new();
|
||||
|
||||
for (home, (module_path, src)) in sources {
|
||||
let can_problems = loaded.can_problems.remove(&home).unwrap_or_default();
|
||||
let type_problems = loaded.type_problems.remove(&home).unwrap_or_default();
|
||||
let mono_problems = loaded.mono_problems.remove(&home).unwrap_or_default();
|
||||
|
||||
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
|
||||
|
||||
if error_count == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let line_info = LineInfo::new(&module_src);
|
||||
let src_lines: Vec<&str> = src.split('\n').collect();
|
||||
let palette = DEFAULT_PALETTE;
|
||||
|
||||
// Report parsing and canonicalization problems
|
||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||
|
||||
for problem in can_problems.into_iter() {
|
||||
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
|
||||
for problem in type_problems {
|
||||
if let Some(report) = type_problem(&alloc, &line_info, module_path.clone(), problem) {
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
for problem in mono_problems {
|
||||
let report = mono_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||
|
||||
lines.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
if !lines.is_empty() {
|
||||
Ok(ReplOutput::Problems(lines))
|
||||
} else {
|
||||
let context = Context::create();
|
||||
let builder = context.create_builder();
|
||||
let module = arena.alloc(roc_gen_llvm::llvm::build::module_from_builtins(
|
||||
&target, &context, "",
|
||||
));
|
||||
|
||||
// mark our zig-defined builtins as internal
|
||||
for function in FunctionIterator::from_module(module) {
|
||||
let name = function.get_name().to_str().unwrap();
|
||||
if name.starts_with("roc_builtins") {
|
||||
function.set_linkage(Linkage::Internal);
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||
let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap();
|
||||
let main_fn_symbol = *main_fn_symbol;
|
||||
let main_fn_var = *main_fn_var;
|
||||
|
||||
// pretty-print the expr type string for later.
|
||||
name_all_type_vars(main_fn_var, &mut subs);
|
||||
let content = subs.get_content_without_compacting(main_fn_var);
|
||||
let expr_type_str = content_to_string(content, &subs, home, &interns);
|
||||
|
||||
let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
|
||||
Some(layout) => *layout,
|
||||
None => {
|
||||
return Ok(ReplOutput::NoProblems {
|
||||
expr: "<function>".to_string(),
|
||||
expr_type: expr_type_str,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let module = arena.alloc(module);
|
||||
let (module_pass, function_pass) =
|
||||
roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
|
||||
|
||||
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
|
||||
|
||||
// Compile and add all the Procs before adding main
|
||||
let env = roc_gen_llvm::llvm::build::Env {
|
||||
arena: &arena,
|
||||
builder: &builder,
|
||||
dibuilder: &dibuilder,
|
||||
compile_unit: &compile_unit,
|
||||
context: &context,
|
||||
interns,
|
||||
module,
|
||||
target_info,
|
||||
is_gen_test: true, // so roc_panic is generated
|
||||
// important! we don't want any procedures to get the C calling convention
|
||||
exposed_to_host: MutSet::default(),
|
||||
};
|
||||
|
||||
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
|
||||
// platform to provide them.
|
||||
add_default_roc_externs(&env);
|
||||
|
||||
let (main_fn_name, main_fn) = roc_gen_llvm::llvm::build::build_procedures_return_main(
|
||||
&env,
|
||||
opt_level,
|
||||
procedures,
|
||||
entry_point,
|
||||
);
|
||||
|
||||
env.dibuilder.finalize();
|
||||
|
||||
// we don't use the debug info, and it causes weird errors.
|
||||
module.strip_debug_info();
|
||||
|
||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
if main_fn.verify(true) {
|
||||
function_pass.run_on(&main_fn);
|
||||
} else {
|
||||
panic!("Main function {} failed LLVM verification in build. Uncomment things nearby to see more details.", main_fn_name);
|
||||
}
|
||||
|
||||
module_pass.run_on(env.module);
|
||||
|
||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
||||
// env.module.print_to_stderr();
|
||||
|
||||
// Verify the module
|
||||
if let Err(errors) = env.module.verify() {
|
||||
panic!(
|
||||
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
|
||||
errors.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let lib = module_to_dylib(env.module, &target, opt_level)
|
||||
.expect("Error loading compiled dylib for test");
|
||||
let res_answer = unsafe {
|
||||
eval::jit_to_ast(
|
||||
&arena,
|
||||
lib,
|
||||
main_fn_name,
|
||||
main_fn_layout,
|
||||
content,
|
||||
&env.interns,
|
||||
home,
|
||||
&subs,
|
||||
target_info,
|
||||
&AppMemoryInternal,
|
||||
)
|
||||
};
|
||||
let mut expr = roc_fmt::Buf::new_in(&arena);
|
||||
|
||||
use eval::ToAstProblem::*;
|
||||
match res_answer {
|
||||
Ok(answer) => {
|
||||
answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
}
|
||||
Err(FunctionLayout) => {
|
||||
expr.indent(0);
|
||||
expr.push_str("<function>");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ReplOutput::NoProblems {
|
||||
expr: expr.into_bump_str().to_string(),
|
||||
expr_type: expr_type_str,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn promote_expr_to_module(src: &str) -> String {
|
||||
let mut buffer =
|
||||
String::from("app \"app\" provides [ replOutput ] to \"./platform\"\n\nreplOutput =\n");
|
||||
|
||||
for line in src.lines() {
|
||||
// indent the body!
|
||||
buffer.push_str(" ");
|
||||
buffer.push_str(line);
|
||||
buffer.push('\n');
|
||||
}
|
||||
|
||||
buffer
|
||||
}
|
@ -4,7 +4,6 @@ platform "examples/multi-module"
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
||||
|
@ -4,7 +4,6 @@ platform "examples/multi-dep-thunk"
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
||||
|
@ -1,962 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
#[cfg(test)]
|
||||
mod repl_eval {
|
||||
use cli_utils::helpers;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
const ERROR_MESSAGE_START: char = '─';
|
||||
|
||||
fn expect_success(input: &str, expected: &str) {
|
||||
let out = helpers::repl_eval(input);
|
||||
|
||||
assert_multiline_str_eq!("", out.stderr.as_str());
|
||||
assert_multiline_str_eq!(expected, out.stdout.as_str());
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
||||
fn expect_failure(input: &str, expected: &str) {
|
||||
let out = helpers::repl_eval(input);
|
||||
|
||||
// there may be some other stuff printed (e.g. unification errors)
|
||||
// so skip till the header of the first error
|
||||
match out.stdout.find(ERROR_MESSAGE_START) {
|
||||
Some(index) => {
|
||||
assert_multiline_str_eq!("", out.stderr.as_str());
|
||||
assert_multiline_str_eq!(expected, &out.stdout[index..]);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
None => {
|
||||
assert_multiline_str_eq!("", out.stderr.as_str());
|
||||
assert!(out.status.success());
|
||||
panic!(
|
||||
"I expected a failure, but there is no error message in stdout:\n\n{}",
|
||||
&out.stdout
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_0() {
|
||||
expect_success("0", "0 : Num *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_42() {
|
||||
expect_success("42", "42 : Num *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_0x0() {
|
||||
expect_success("0x0", "0 : Int *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_0x42() {
|
||||
expect_success("0x42", "66 : Int *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_0point0() {
|
||||
expect_success("0.0", "0 : Float *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_4point2() {
|
||||
expect_success("4.2", "4.2 : Float *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_addition() {
|
||||
expect_success("1 + 2", "3 : Num *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int_addition() {
|
||||
expect_success("0x1 + 2", "3 : I64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_addition() {
|
||||
expect_success("1.1 + 2", "3.1 : F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_rem() {
|
||||
expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_floor_division_success() {
|
||||
expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_floor_division_divby_zero() {
|
||||
expect_success(
|
||||
"Num.divFloor 4 0",
|
||||
"Err DivByZero : Result (Int *) [ DivByZero ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_ceil_division_success() {
|
||||
expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_in_record() {
|
||||
expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }");
|
||||
expect_success(
|
||||
"{ z: { y: { x: 1 == 1 } } }",
|
||||
"{ z: { y: { x: True } } } : { z : { y : { x : Bool } } }",
|
||||
);
|
||||
expect_success("{ x: 1 != 1 }", "{ x: False } : { x : Bool }");
|
||||
expect_success(
|
||||
"{ x: 1 == 1, y: 1 != 1 }",
|
||||
"{ x: True, y: False } : { x : Bool, y : Bool }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool_basic_equality() {
|
||||
expect_success("1 == 1", "True : Bool");
|
||||
expect_success("1 != 1", "False : Bool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arbitrary_tag_unions() {
|
||||
expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*");
|
||||
expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_without_arguments() {
|
||||
expect_success("True", "True : [ True ]*");
|
||||
expect_success("False", "False : [ False ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn byte_tag_union() {
|
||||
expect_success(
|
||||
"if 1 == 1 then Red else if 1 == 1 then Green else Blue",
|
||||
"Red : [ Blue, Green, Red ]*",
|
||||
);
|
||||
|
||||
expect_success(
|
||||
"{ y: { x: if 1 == 1 then Red else if 1 == 1 then Green else Blue } }",
|
||||
"{ y: { x: Red } } : { y : { x : [ Blue, Green, Red ]* } }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_in_record() {
|
||||
expect_success(
|
||||
"{ x: Foo 1 2 3, y : 4 }",
|
||||
"{ x: Foo 1 2 3, y: 4 } : { x : [ Foo (Num *) (Num *) (Num *) ]*, y : Num * }",
|
||||
);
|
||||
expect_success(
|
||||
"{ x: Foo 1 2 3 }",
|
||||
"{ x: Foo 1 2 3 } : { x : [ Foo (Num *) (Num *) (Num *) ]* }",
|
||||
);
|
||||
expect_success("{ x: Unit }", "{ x: Unit } : { x : [ Unit ]* }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_element_tag_union() {
|
||||
expect_success("True 1", "True 1 : [ True (Num *) ]*");
|
||||
expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_of_unit() {
|
||||
expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_of_big_data() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Either a b : [ Left a, Right b ]
|
||||
lefty : Either Str Str
|
||||
lefty = Left "loosey"
|
||||
A lefty
|
||||
"#
|
||||
),
|
||||
r#"A (Left "loosey") : [ A (Either Str Str) ]*"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_nested() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Either a b : [ Left a, Right b ]
|
||||
lefty : Either Str Str
|
||||
lefty = Left "loosey"
|
||||
A (B (C lefty))
|
||||
"#
|
||||
),
|
||||
r#"A (B (C (Left "loosey"))) : [ A [ B [ C (Either Str Str) ]* ]* ]*"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_of_big_of_newtype() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Big a : [ Big a [ Wrapper [ Newtype a ] ] ]
|
||||
big : Big Str
|
||||
big = Big "s" (Wrapper (Newtype "t"))
|
||||
A big
|
||||
"#
|
||||
),
|
||||
r#"A (Big "s" (Wrapper (Newtype "t"))) : [ A (Big Str) ]*"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_with_arguments() {
|
||||
expect_success("True 1", "True 1 : [ True (Num *) ]*");
|
||||
|
||||
expect_success(
|
||||
"if 1 == 1 then True 3 else False 3.14",
|
||||
"True 3 : [ False (Float *), True (Num *) ]*",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_empty_str() {
|
||||
expect_success("\"\"", "\"\" : Str");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_ascii_str() {
|
||||
expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_utf8_str() {
|
||||
expect_success("\"👩👩👦👦\"", "\"👩👩👦👦\" : Str");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_concat() {
|
||||
expect_success(
|
||||
"Str.concat \"Hello, \" \"World!\"",
|
||||
"\"Hello, World!\" : Str",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn str_count_graphemes() {
|
||||
expect_success("Str.countGraphemes \"å🤔\"", "2 : Nat");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_empty_list() {
|
||||
expect_success("[]", "[] : List *");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_empty_list_empty_record() {
|
||||
expect_success("[ {} ]", "[ {} ] : List {}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_num_list() {
|
||||
expect_success("[ 1, 2, 3 ]", "[ 1, 2, 3 ] : List (Num *)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_int_list() {
|
||||
expect_success("[ 0x1, 0x2, 0x3 ]", "[ 1, 2, 3 ] : List (Int *)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_float_list() {
|
||||
expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn literal_string_list() {
|
||||
expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_string_list() {
|
||||
expect_success(
|
||||
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ]"#,
|
||||
r#"[ [ [ "a", "b", "cd" ], [ "y", "z" ] ], [ [] ], [] ] : List (List (List Str))"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_num_list() {
|
||||
expect_success(
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ]"#,
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List (Num *)))"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_int_list() {
|
||||
expect_success(
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0x0 ] ], [ [] ], [] ]"#,
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List I64))"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_float_list() {
|
||||
expect_success(
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0.0 ] ], [ [] ], [] ]"#,
|
||||
r#"[ [ [ 4, 3, 2 ], [ 1, 0 ] ], [ [] ], [] ] : List (List (List F64))"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_bitwise_and() {
|
||||
expect_success("Num.bitwiseAnd 20 20", "20 : Int *");
|
||||
|
||||
expect_success("Num.bitwiseAnd 25 10", "8 : Int *");
|
||||
|
||||
expect_success("Num.bitwiseAnd 200 0", "0 : Int *")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_bitwise_xor() {
|
||||
expect_success("Num.bitwiseXor 20 20", "0 : Int *");
|
||||
|
||||
expect_success("Num.bitwiseXor 15 14", "1 : Int *");
|
||||
|
||||
expect_success("Num.bitwiseXor 7 15", "8 : Int *");
|
||||
|
||||
expect_success("Num.bitwiseXor 200 0", "200 : Int *")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_add_wrap() {
|
||||
expect_success(
|
||||
"Num.addWrap Num.maxI64 1",
|
||||
"-9223372036854775808 : Int Signed64",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_sub_wrap() {
|
||||
expect_success(
|
||||
"Num.subWrap Num.minI64 1",
|
||||
"9223372036854775807 : Int Signed64",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_mul_wrap() {
|
||||
expect_success("Num.mulWrap Num.maxI64 2", "-2 : Int Signed64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_add_checked() {
|
||||
expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*");
|
||||
expect_success(
|
||||
"Num.addChecked Num.maxI64 1",
|
||||
"Err Overflow : Result I64 [ Overflow ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_sub_checked() {
|
||||
expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*");
|
||||
expect_success(
|
||||
"Num.subChecked Num.minI64 1",
|
||||
"Err Overflow : Result I64 [ Overflow ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_mul_checked() {
|
||||
expect_success(
|
||||
"Num.mulChecked 20 2",
|
||||
"Ok 40 : Result (Num *) [ Overflow ]*",
|
||||
);
|
||||
expect_success(
|
||||
"Num.mulChecked Num.maxI64 2",
|
||||
"Err Overflow : Result I64 [ Overflow ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_concat() {
|
||||
expect_success(
|
||||
"List.concat [ 1.1, 2.2 ] [ 3.3, 4.4, 5.5 ]",
|
||||
"[ 1.1, 2.2, 3.3, 4.4, 5.5 ] : List (Float *)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_contains() {
|
||||
expect_success("List.contains [] 0", "False : Bool");
|
||||
expect_success("List.contains [ 1, 2, 3 ] 2", "True : Bool");
|
||||
expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_sum() {
|
||||
expect_success("List.sum []", "0 : Num *");
|
||||
expect_success("List.sum [ 1, 2, 3 ]", "6 : Num *");
|
||||
expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_first() {
|
||||
expect_success(
|
||||
"List.first [ 12, 9, 6, 3 ]",
|
||||
"Ok 12 : Result (Num *) [ ListWasEmpty ]*",
|
||||
);
|
||||
expect_success(
|
||||
"List.first []",
|
||||
"Err ListWasEmpty : Result * [ ListWasEmpty ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_last() {
|
||||
expect_success(
|
||||
"List.last [ 12, 9, 6, 3 ]",
|
||||
"Ok 3 : Result (Num *) [ ListWasEmpty ]*",
|
||||
);
|
||||
|
||||
expect_success(
|
||||
"List.last []",
|
||||
"Err ListWasEmpty : Result * [ ListWasEmpty ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
expect_success("{}", "{} : {}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_1_field_i64_record() {
|
||||
// Even though this gets unwrapped at runtime, the repl should still
|
||||
// report it as a record
|
||||
expect_success("{ foo: 42 }", "{ foo: 42 } : { foo : Num * }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_1_field_f64_record() {
|
||||
// Even though this gets unwrapped at runtime, the repl should still
|
||||
// report it as a record
|
||||
expect_success("{ foo: 4.2 }", "{ foo: 4.2 } : { foo : Float * }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_1_field_i64_record() {
|
||||
// Even though this gets unwrapped at runtime, the repl should still
|
||||
// report it as a record
|
||||
expect_success(
|
||||
"{ foo: { bar: { baz: 42 } } }",
|
||||
"{ foo: { bar: { baz: 42 } } } : { foo : { bar : { baz : Num * } } }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_1_field_f64_record() {
|
||||
// Even though this gets unwrapped at runtime, the repl should still
|
||||
// report it as a record
|
||||
expect_success(
|
||||
"{ foo: { bar: { baz: 4.2 } } }",
|
||||
"{ foo: { bar: { baz: 4.2 } } } : { foo : { bar : { baz : Float * } } }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_2_field_i64_record() {
|
||||
expect_success(
|
||||
"{ foo: 0x4, bar: 0x2 }",
|
||||
"{ bar: 2, foo: 4 } : { bar : Int *, foo : Int * }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_2_field_f64_record() {
|
||||
expect_success(
|
||||
"{ foo: 4.1, bar: 2.3 }",
|
||||
"{ bar: 2.3, foo: 4.1 } : { bar : Float *, foo : Float * }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_2_field_mixed_record() {
|
||||
expect_success(
|
||||
"{ foo: 4.1, bar: 2 }",
|
||||
"{ bar: 2, foo: 4.1 } : { bar : Num *, foo : Float * }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_3_field_record() {
|
||||
expect_success(
|
||||
"{ foo: 4.1, bar: 2, baz: 0x5 }",
|
||||
"{ bar: 2, baz: 5, foo: 4.1 } : { bar : Num *, baz : Int *, foo : Float * }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_of_1_field_records() {
|
||||
// Even though these get unwrapped at runtime, the repl should still
|
||||
// report them as records
|
||||
expect_success("[ { foo: 42 } ]", "[ { foo: 42 } ] : List { foo : Num * }");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_of_2_field_records() {
|
||||
expect_success(
|
||||
"[ { foo: 4.1, bar: 2 } ]",
|
||||
"[ { bar: 2, foo: 4.1 } ] : List { bar : Num *, foo : Float * }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_element_record() {
|
||||
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
|
||||
expect_success(
|
||||
"{ a: 1, b: 2, c: 3 }",
|
||||
"{ a: 1, b: 2, c: 3 } : { a : Num *, b : Num *, c : Num * }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_element_record() {
|
||||
// if this tests turns out to fail on 32-bit platforms, look at jit_to_ast_help
|
||||
expect_success(
|
||||
"{ a: 1, b: 2, c: 3, d: 4 }",
|
||||
"{ a: 1, b: 2, c: 3, d: 4 } : { a : Num *, b : Num *, c : Num *, d : Num * }",
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn multiline_string() {
|
||||
// // If a string contains newlines, format it as a multiline string in the output
|
||||
// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\"");
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn list_of_3_field_records() {
|
||||
expect_success(
|
||||
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
|
||||
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int *, foo : Float * }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_lambda() {
|
||||
expect_success("\\x -> x", "<function> : a -> a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sum_lambda() {
|
||||
expect_success("\\x, y -> x + y", "<function> : Num a, Num a -> Num a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdlib_function() {
|
||||
expect_success("Num.abs", "<function> : Num a -> Num a");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn too_few_args() {
|
||||
expect_failure(
|
||||
"Num.add 2",
|
||||
indoc!(
|
||||
r#"
|
||||
── TOO FEW ARGS ────────────────────────────────────────────────────────────────
|
||||
|
||||
The add function expects 2 arguments, but it got only 1:
|
||||
|
||||
4│ Num.add 2
|
||||
^^^^^^^
|
||||
|
||||
Roc does not allow functions to be partially applied. Use a closure to
|
||||
make partial application explicit.
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_problem() {
|
||||
expect_failure(
|
||||
"1 + \"\"",
|
||||
indoc!(
|
||||
r#"
|
||||
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||
|
||||
The 2nd argument to add is not what I expect:
|
||||
|
||||
4│ 1 + ""
|
||||
^^
|
||||
|
||||
This argument is a string of type:
|
||||
|
||||
Str
|
||||
|
||||
But add needs the 2nd argument to be:
|
||||
|
||||
Num a
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_2149() {
|
||||
expect_success(r#"Str.toI8 "127""#, "Ok 127 : Result I8 [ InvalidNumStr ]*");
|
||||
expect_success(
|
||||
r#"Str.toI8 "128""#,
|
||||
"Err InvalidNumStr : Result I8 [ InvalidNumStr ]*",
|
||||
);
|
||||
expect_success(
|
||||
r#"Str.toI16 "32767""#,
|
||||
"Ok 32767 : Result I16 [ InvalidNumStr ]*",
|
||||
);
|
||||
expect_success(
|
||||
r#"Str.toI16 "32768""#,
|
||||
"Err InvalidNumStr : Result I16 [ InvalidNumStr ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_input() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
a : Str
|
||||
a = "123"
|
||||
a
|
||||
"#
|
||||
),
|
||||
r#""123" : Str"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_tag_union_flat_variant() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Expr : [ Sym Str, Add Expr Expr ]
|
||||
s : Expr
|
||||
s = Sym "levitating"
|
||||
s
|
||||
"#
|
||||
),
|
||||
r#"Sym "levitating" : Expr"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_recursive_tag_union_flat_variant() {
|
||||
expect_success(
|
||||
// > 7 variants so that to force tag storage alongside the data
|
||||
indoc!(
|
||||
r#"
|
||||
Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item ]
|
||||
s : Item
|
||||
s = H "woo"
|
||||
s
|
||||
"#
|
||||
),
|
||||
r#"H "woo" : Item"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_tag_union_recursive_variant() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Expr : [ Sym Str, Add Expr Expr ]
|
||||
s : Expr
|
||||
s = Add (Add (Sym "one") (Sym "two")) (Sym "four")
|
||||
s
|
||||
"#
|
||||
),
|
||||
r#"Add (Add (Sym "one") (Sym "two")) (Sym "four") : Expr"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_recursive_tag_union_recursive_variant() {
|
||||
expect_success(
|
||||
// > 7 variants so that to force tag storage alongside the data
|
||||
indoc!(
|
||||
r#"
|
||||
Item : [ A Str, B Str, C Str, D Str, E Str, F Str, G Str, H Str, I Str, J Str, K Item, L Item ]
|
||||
s : Item
|
||||
s = K (L (E "woo"))
|
||||
s
|
||||
"#
|
||||
),
|
||||
r#"K (L (E "woo")) : Item"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_tag_union_into_flat_tag_union() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Item : [ One [ A Str, B Str ], Deep Item ]
|
||||
i : Item
|
||||
i = Deep (One (A "woo"))
|
||||
i
|
||||
"#
|
||||
),
|
||||
r#"Deep (One (A "woo")) : Item"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_nullable_unwrapped_tag_union() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
RoseTree a : [ Tree a (List (RoseTree a)) ]
|
||||
e1 : RoseTree Str
|
||||
e1 = Tree "e1" []
|
||||
e2 : RoseTree Str
|
||||
e2 = Tree "e2" []
|
||||
combo : RoseTree Str
|
||||
combo = Tree "combo" [e1, e2]
|
||||
combo
|
||||
"#
|
||||
),
|
||||
r#"Tree "combo" [ Tree "e1" [], Tree "e2" [] ] : RoseTree Str"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nullable_unwrapped_tag_union() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||
c1 : LinkedList Str
|
||||
c1 = Cons "Red" Nil
|
||||
c2 : LinkedList Str
|
||||
c2 = Cons "Yellow" c1
|
||||
c3 : LinkedList Str
|
||||
c3 = Cons "Green" c2
|
||||
c3
|
||||
"#
|
||||
),
|
||||
r#"Cons "Green" (Cons "Yellow" (Cons "Red" Nil)) : LinkedList Str"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nullable_wrapped_tag_union() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Container a : [ Empty, Whole a, Halved (Container a) (Container a) ]
|
||||
|
||||
meats : Container Str
|
||||
meats = Halved (Whole "Brisket") (Whole "Ribs")
|
||||
|
||||
sides : Container Str
|
||||
sides = Halved (Whole "Coleslaw") Empty
|
||||
|
||||
bbqPlate : Container Str
|
||||
bbqPlate = Halved meats sides
|
||||
|
||||
bbqPlate
|
||||
"#
|
||||
),
|
||||
r#"Halved (Halved (Whole "Brisket") (Whole "Ribs")) (Halved (Whole "Coleslaw") Empty) : Container Str"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_nullable_wrapped_tag_union() {
|
||||
// > 7 non-empty variants so that to force tag storage alongside the data
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
Cont a : [ Empty, S1 a, S2 a, S3 a, S4 a, S5 a, S6 a, S7 a, Tup (Cont a) (Cont a) ]
|
||||
|
||||
fst : Cont Str
|
||||
fst = Tup (S1 "S1") (S2 "S2")
|
||||
|
||||
snd : Cont Str
|
||||
snd = Tup (S5 "S5") Empty
|
||||
|
||||
tup : Cont Str
|
||||
tup = Tup fst snd
|
||||
|
||||
tup
|
||||
"#
|
||||
),
|
||||
r#"Tup (Tup (S1 "S1") (S2 "S2")) (Tup (S5 "S5") Empty) : Cont Str"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_2300() {
|
||||
expect_success(
|
||||
r#"\Email str -> str == """#,
|
||||
r#"<function> : [ Email Str ] -> Bool"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_in_list() {
|
||||
expect_success(
|
||||
r#"[\x -> x + 1, \s -> s * 2]"#,
|
||||
r#"[ <function>, <function> ] : List (Num a -> Num a)"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_in_record() {
|
||||
expect_success(
|
||||
r#"{ n: 1, adder: \x -> x + 1 }"#,
|
||||
r#"{ adder: <function>, n: <function> } : { adder : Num a -> Num a, n : Num * }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_in_unwrapped_record() {
|
||||
expect_success(
|
||||
r#"{ adder: \x -> x + 1 }"#,
|
||||
r#"{ adder: <function> } : { adder : Num a -> Num a }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_in_tag() {
|
||||
expect_success(
|
||||
r#"Adder (\x -> x + 1)"#,
|
||||
r#"Adder <function> : [ Adder (Num a -> Num a) ]*"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newtype_of_record_of_tag_of_record_of_tag() {
|
||||
expect_success(
|
||||
r#"A {b: C {d: 1}}"#,
|
||||
r#"A { b: C { d: 1 } } : [ A { b : [ C { d : Num * } ]* } ]*"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print_u8s() {
|
||||
expect_success(
|
||||
indoc!(
|
||||
r#"
|
||||
x : U8
|
||||
x = 129
|
||||
x
|
||||
"#
|
||||
),
|
||||
"129 : U8",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_problem() {
|
||||
expect_failure(
|
||||
"add m n = m + n",
|
||||
indoc!(
|
||||
r#"
|
||||
── ARGUMENTS BEFORE EQUALS ─────────────────────────────────────────────────────
|
||||
|
||||
I am partway through parsing a definition, but I got stuck here:
|
||||
|
||||
1│ app "app" provides [ replOutput ] to "./platform"
|
||||
2│
|
||||
3│ replOutput =
|
||||
4│ add m n = m + n
|
||||
^^^
|
||||
|
||||
Looks like you are trying to define a function. In roc, functions are
|
||||
always written as a lambda, like increment = \n -> n + 1.
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mono_problem() {
|
||||
expect_failure(
|
||||
r#"
|
||||
t : [A, B, C]
|
||||
t = A
|
||||
|
||||
when t is
|
||||
A -> "a"
|
||||
"#,
|
||||
indoc!(
|
||||
r#"
|
||||
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
|
||||
|
||||
This when does not cover all the possibilities:
|
||||
|
||||
7│> when t is
|
||||
8│> A -> "a"
|
||||
|
||||
Other possibilities include:
|
||||
|
||||
B
|
||||
C
|
||||
|
||||
I would have to crash if I saw one of those! Add branches for them!
|
||||
|
||||
|
||||
Enter an expression, or :help, or :exit/:q."#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_2343_complete_mono_with_shadowed_vars() {
|
||||
expect_failure(
|
||||
indoc!(
|
||||
r#"
|
||||
b = False
|
||||
f = \b ->
|
||||
when b is
|
||||
True -> 5
|
||||
False -> 15
|
||||
f b
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||||
|
||||
The b name is first defined here:
|
||||
|
||||
4│ b = False
|
||||
^
|
||||
|
||||
But then it's defined a second time here:
|
||||
|
||||
5│ f = \b ->
|
||||
^
|
||||
|
||||
Since these variables have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name.
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
79
cli_utils/Cargo.lock
generated
79
cli_utils/Cargo.lock
generated
@ -2475,12 +2475,13 @@ dependencies = [
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"snafu",
|
||||
@ -2510,6 +2511,7 @@ dependencies = [
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"serde_json",
|
||||
@ -2524,6 +2526,7 @@ dependencies = [
|
||||
"roc_collections",
|
||||
"roc_module",
|
||||
"roc_region",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
]
|
||||
|
||||
@ -2549,31 +2552,24 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.0.0-beta.5",
|
||||
"const_format",
|
||||
"inkwell 0.1.0",
|
||||
"libloading 0.7.1",
|
||||
"mimalloc",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_docs",
|
||||
"roc_editor",
|
||||
"roc_error_macros",
|
||||
"roc_fmt",
|
||||
"roc_gen_llvm",
|
||||
"roc_linker",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_repl_cli",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"rustyline",
|
||||
"rustyline-derive",
|
||||
"roc_target",
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
]
|
||||
@ -2631,6 +2627,7 @@ dependencies = [
|
||||
"roc_module",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"snafu",
|
||||
]
|
||||
@ -2680,6 +2677,10 @@ dependencies = [
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_error_macros"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "roc_fmt"
|
||||
version = "0.1.0"
|
||||
@ -2700,12 +2701,13 @@ dependencies = [
|
||||
"packed_struct",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_problem",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"target-lexicon",
|
||||
@ -2720,10 +2722,11 @@ dependencies = [
|
||||
"morphic_lib",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_reporting",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
@ -2734,10 +2737,11 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_reporting",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2782,6 +2786,7 @@ dependencies = [
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_solve",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"ven_pretty",
|
||||
@ -2815,6 +2820,7 @@ dependencies = [
|
||||
"roc_region",
|
||||
"roc_solve",
|
||||
"roc_std",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"static_assertions",
|
||||
@ -2850,6 +2856,44 @@ dependencies = [
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_repl_cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"const_format",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_repl_eval",
|
||||
"rustyline",
|
||||
"rustyline-derive",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_repl_eval"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"inkwell 0.1.0",
|
||||
"libloading 0.7.1",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_fmt",
|
||||
"roc_gen_llvm",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
"roc_region",
|
||||
"roc_reporting",
|
||||
"roc_target",
|
||||
"roc_types",
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_reporting"
|
||||
version = "0.1.0"
|
||||
@ -2886,6 +2930,13 @@ dependencies = [
|
||||
name = "roc_std"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "roc_target"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"target-lexicon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_types"
|
||||
version = "0.1.0"
|
||||
|
@ -4,7 +4,6 @@ extern crate roc_load;
|
||||
extern crate roc_module;
|
||||
extern crate tempfile;
|
||||
|
||||
use roc_cli::repl::{INSTRUCTIONS, WELCOME_MESSAGE};
|
||||
use serde::Deserialize;
|
||||
use serde_xml_rs::from_str;
|
||||
use std::env;
|
||||
@ -306,88 +305,3 @@ pub fn known_bad_file(file_name: &str) -> PathBuf {
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn repl_eval(input: &str) -> Out {
|
||||
let mut cmd = Command::new(path_to_roc_binary());
|
||||
|
||||
cmd.arg("repl");
|
||||
|
||||
let mut child = cmd
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("failed to execute compiled `roc` binary in CLI test");
|
||||
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
|
||||
// Send the input expression
|
||||
stdin
|
||||
.write_all(input.as_bytes())
|
||||
.expect("Failed to write input to stdin");
|
||||
|
||||
// Evaluate the expression
|
||||
stdin
|
||||
.write_all(b"\n")
|
||||
.expect("Failed to write newline to stdin");
|
||||
|
||||
// Gracefully exit the repl
|
||||
stdin
|
||||
.write_all(b":exit\n")
|
||||
.expect("Failed to write :exit to stdin");
|
||||
}
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.expect("Error waiting for REPL child process to exit.");
|
||||
|
||||
// Remove the initial instructions from the output.
|
||||
|
||||
let expected_instructions = format!("{}{}", WELCOME_MESSAGE, INSTRUCTIONS);
|
||||
let stdout = String::from_utf8(output.stdout).unwrap();
|
||||
|
||||
assert!(
|
||||
stdout.starts_with(&expected_instructions),
|
||||
"Unexpected repl output: {}",
|
||||
stdout
|
||||
);
|
||||
|
||||
let (_, answer) = stdout.split_at(expected_instructions.len());
|
||||
let answer = if answer.is_empty() {
|
||||
// The repl crashed before completing the evaluation.
|
||||
// This is most likely due to a segfault.
|
||||
if output.status.to_string() == "signal: 11" {
|
||||
panic!(
|
||||
"repl segfaulted during the test. Stderr was {:?}",
|
||||
String::from_utf8(output.stderr).unwrap()
|
||||
);
|
||||
} else {
|
||||
panic!("repl exited unexpectedly before finishing evaluation. Exit status was {:?} and stderr was {:?}", output.status, String::from_utf8(output.stderr).unwrap());
|
||||
}
|
||||
} else {
|
||||
let expected_after_answer = "\n".to_string();
|
||||
|
||||
assert!(
|
||||
answer.ends_with(&expected_after_answer),
|
||||
"Unexpected repl output after answer: {}",
|
||||
answer
|
||||
);
|
||||
|
||||
// Use [1..] to trim the leading '\n'
|
||||
// and (len - 1) to trim the trailing '\n'
|
||||
let (answer, _) = answer[1..].split_at(answer.len() - expected_after_answer.len() - 1);
|
||||
|
||||
// Remove ANSI escape codes from the answer - for example:
|
||||
//
|
||||
// Before: "42 \u{1b}[35m:\u{1b}[0m Num *"
|
||||
// After: "42 : Num *"
|
||||
strip_ansi_escapes::strip(answer).unwrap()
|
||||
};
|
||||
|
||||
Out {
|
||||
stdout: String::from_utf8(answer).unwrap(),
|
||||
stderr: String::from_utf8(output.stderr).unwrap(),
|
||||
status: output.status,
|
||||
}
|
||||
}
|
||||
|
@ -856,11 +856,29 @@ fn link_macos(
|
||||
"-lSystem",
|
||||
"-lresolv",
|
||||
"-lpthread",
|
||||
// This `-F PATH` flag is needed for `-framework` flags to work
|
||||
"-F",
|
||||
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/",
|
||||
// These frameworks are needed for GUI examples to work
|
||||
"-framework",
|
||||
"AudioUnit",
|
||||
"-framework",
|
||||
"Cocoa",
|
||||
"-framework",
|
||||
"CoreAudio",
|
||||
"-framework",
|
||||
"CoreVideo",
|
||||
"-framework",
|
||||
"IOKit",
|
||||
"-framework",
|
||||
"Metal",
|
||||
"-framework",
|
||||
"QuartzCore",
|
||||
// "-lrt", // TODO shouldn't we need this?
|
||||
// "-lc_nonshared", // TODO shouldn't we need this?
|
||||
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
|
||||
// "-framework", // Uncomment this line & the following ro run the `rand` crate in examples/cli
|
||||
// "Security",
|
||||
"-framework",
|
||||
"Security",
|
||||
// Output
|
||||
"-o",
|
||||
output_path.to_str().unwrap(), // app
|
||||
|
@ -26,7 +26,7 @@ pub fn build(b: *Builder) void {
|
||||
.default_target = CrossTarget{
|
||||
.cpu_model = .baseline,
|
||||
// TODO allow for native target for maximum speed
|
||||
}
|
||||
},
|
||||
});
|
||||
const i386_target = makeI386Target();
|
||||
const wasm32_target = makeWasm32Target();
|
||||
|
@ -750,7 +750,8 @@ pub fn dictWalk(
|
||||
const alignment_u32 = alignment.toU32();
|
||||
// allocate space to write the result of the stepper into
|
||||
// experimentally aliasing the accum and output pointers is not a good idea
|
||||
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32);
|
||||
// TODO handle alloc failing!
|
||||
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32) orelse unreachable;
|
||||
var b1 = output orelse unreachable;
|
||||
var b2 = bytes_ptr;
|
||||
|
||||
|
162
compiler/builtins/bitcode/src/expect.zig
Normal file
162
compiler/builtins/bitcode/src/expect.zig
Normal file
@ -0,0 +1,162 @@
|
||||
const std = @import("std");
|
||||
const utils = @import("utils.zig");
|
||||
const CSlice = utils.CSlice;
|
||||
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
|
||||
|
||||
const Failure = struct {
|
||||
start_line: u32,
|
||||
end_line: u32,
|
||||
start_col: u16,
|
||||
end_col: u16,
|
||||
};
|
||||
|
||||
// BEGIN FAILURES GLOBALS ///////////////////
|
||||
var failures_mutex = std.Thread.Mutex{};
|
||||
var failures: [*]Failure = undefined;
|
||||
var failure_length: usize = 0;
|
||||
var failure_capacity: usize = 0;
|
||||
// END FAILURES GLOBALS /////////////////////
|
||||
|
||||
pub fn expectFailed(
|
||||
start_line: u32,
|
||||
end_line: u32,
|
||||
start_col: u16,
|
||||
end_col: u16,
|
||||
) void {
|
||||
const new_failure = Failure{ .start_line = start_line, .end_line = end_line, .start_col = start_col, .end_col = end_col };
|
||||
|
||||
// Lock the failures mutex before reading from any of the failures globals,
|
||||
// and then release the lock once we're done modifying things.
|
||||
|
||||
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
|
||||
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
|
||||
//
|
||||
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
|
||||
//
|
||||
// failures_mutex.lock();
|
||||
// defer failures_mutex.release();
|
||||
//
|
||||
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
|
||||
const held = failures_mutex.acquire();
|
||||
defer held.release();
|
||||
|
||||
// If we don't have enough capacity to add a failure, allocate a new failures pointer.
|
||||
if (failure_length >= failure_capacity) {
|
||||
if (failure_capacity > 0) {
|
||||
// We already had previous failures allocated, so try to realloc in order
|
||||
// to grow the size in-place without having to memcpy bytes over.
|
||||
const old_pointer = failures;
|
||||
const old_bytes = failure_capacity * @sizeOf(Failure);
|
||||
|
||||
failure_capacity *= 2;
|
||||
|
||||
const new_bytes = failure_capacity * @sizeOf(Failure);
|
||||
const failures_u8 = @ptrCast([*]u8, @alignCast(@alignOf(Failure), failures));
|
||||
const raw_pointer = utils.realloc(failures_u8, new_bytes, old_bytes, @alignOf(Failure));
|
||||
|
||||
failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer));
|
||||
|
||||
// If realloc wasn't able to expand in-place (that is, it returned a different pointer),
|
||||
// then copy the data into the new pointer and dealloc the old one.
|
||||
if (failures != old_pointer) {
|
||||
const old_pointer_u8 = @ptrCast([*]u8, old_pointer);
|
||||
utils.memcpy(@ptrCast([*]u8, failures), old_pointer_u8, old_bytes);
|
||||
utils.dealloc(old_pointer_u8, @alignOf(Failure));
|
||||
}
|
||||
} else {
|
||||
// We've never had any failures before, so allocate the failures for the first time.
|
||||
failure_capacity = 10;
|
||||
|
||||
const raw_pointer = utils.alloc(failure_capacity * @sizeOf(Failure), @alignOf(Failure));
|
||||
|
||||
failures = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_pointer));
|
||||
}
|
||||
}
|
||||
|
||||
failures[failure_length] = new_failure;
|
||||
failure_length += 1;
|
||||
}
|
||||
|
||||
pub fn expectFailedC(
|
||||
start_line: u32,
|
||||
end_line: u32,
|
||||
start_col: u16,
|
||||
end_col: u16,
|
||||
) callconv(.C) void {
|
||||
return @call(.{ .modifier = always_inline }, expectFailed, .{ start_line, end_line, start_col, end_col });
|
||||
}
|
||||
|
||||
pub fn getExpectFailures() []Failure {
|
||||
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
|
||||
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
|
||||
//
|
||||
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
|
||||
//
|
||||
// failures_mutex.lock();
|
||||
// defer failures_mutex.release();
|
||||
//
|
||||
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
|
||||
const held = failures_mutex.acquire();
|
||||
defer held.release();
|
||||
|
||||
if (failure_length > 0) {
|
||||
// defensively clone failures, in case someone modifies the originals after the mutex has been released.
|
||||
const num_bytes = failure_length * @sizeOf(Failure);
|
||||
// TODO handle the possibility of alloc failing
|
||||
const raw_clones = utils.alloc(num_bytes, @alignOf(Failure)) orelse unreachable;
|
||||
|
||||
utils.memcpy(raw_clones, @ptrCast([*]u8, failures), num_bytes);
|
||||
|
||||
const clones = @ptrCast([*]Failure, @alignCast(@alignOf(Failure), raw_clones));
|
||||
|
||||
return clones[0..failure_length];
|
||||
} else {
|
||||
return failures[0..0];
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getExpectFailuresC() callconv(.C) CSlice {
|
||||
var bytes = @ptrCast(*c_void, failures);
|
||||
|
||||
return .{ .pointer = bytes, .len = failure_length };
|
||||
}
|
||||
|
||||
pub fn deinitFailures() void {
|
||||
// TODO FOR ZIG 0.9: this API changed in https://github.com/ziglang/zig/commit/008b0ec5e58fc7e31f3b989868a7d1ea4df3f41d
|
||||
// to this: https://github.com/ziglang/zig/blob/c710d5eefe3f83226f1651947239730e77af43cb/lib/std/Thread/Mutex.zig
|
||||
//
|
||||
// ...so just use these two lines of code instead of the non-commented-out ones to make this work in Zig 0.9:
|
||||
//
|
||||
// failures_mutex.lock();
|
||||
// defer failures_mutex.release();
|
||||
//
|
||||
// 👆 👆 👆 IF UPGRADING TO ZIG 0.9, LOOK HERE! 👆 👆 👆
|
||||
const held = failures_mutex.acquire();
|
||||
defer held.release();
|
||||
|
||||
utils.dealloc(@ptrCast([*]u8, failures), @alignOf(Failure));
|
||||
failure_length = 0;
|
||||
}
|
||||
|
||||
pub fn deinitFailuresC() callconv(.C) void {
|
||||
return @call(.{ .modifier = always_inline }, deinitFailures, .{});
|
||||
}
|
||||
|
||||
test "expectFailure does something" {
|
||||
defer deinitFailures();
|
||||
|
||||
var fails = getExpectFailures();
|
||||
try std.testing.expectEqual(fails.len, 0);
|
||||
|
||||
expectFailed(1, 2, 3, 4);
|
||||
|
||||
fails = getExpectFailures();
|
||||
try std.testing.expectEqual(fails.len, 1);
|
||||
utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure));
|
||||
|
||||
const what_it_should_look_like = Failure{ .start_line = 1, .end_line = 2, .start_col = 3, .end_col = 4 };
|
||||
|
||||
fails = getExpectFailures();
|
||||
try std.testing.expectEqual(fails[0], what_it_should_look_like);
|
||||
utils.dealloc(@ptrCast([*]u8, fails.ptr), @alignOf([*]Failure));
|
||||
}
|
@ -550,7 +550,8 @@ pub fn listKeepResult(
|
||||
var output = RocList.allocate(alignment, list.len(), list.len() * after_width);
|
||||
const target_ptr = output.bytes orelse unreachable;
|
||||
|
||||
var temporary = @ptrCast([*]u8, utils.alloc(result_width, alignment));
|
||||
// TODO handle alloc failing!
|
||||
var temporary = utils.alloc(result_width, alignment) orelse unreachable;
|
||||
|
||||
if (data_is_owned) {
|
||||
inc_n_data(data, size);
|
||||
@ -614,7 +615,8 @@ pub fn listWalk(
|
||||
inc_n_data(data, list.len());
|
||||
}
|
||||
|
||||
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment);
|
||||
// TODO handle alloc failing!
|
||||
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable;
|
||||
var b1 = output orelse unreachable;
|
||||
var b2 = bytes_ptr;
|
||||
|
||||
@ -660,7 +662,8 @@ pub fn listWalkBackwards(
|
||||
inc_n_data(data, list.len());
|
||||
}
|
||||
|
||||
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment);
|
||||
// TODO handle alloc failing!
|
||||
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment) orelse unreachable;
|
||||
var b1 = output orelse unreachable;
|
||||
var b2 = bytes_ptr;
|
||||
|
||||
@ -708,7 +711,8 @@ pub fn listWalkUntil(
|
||||
return;
|
||||
}
|
||||
|
||||
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment);
|
||||
// TODO handle alloc failing!
|
||||
const bytes_ptr: [*]u8 = utils.alloc(continue_stop_width, alignment) orelse unreachable;
|
||||
|
||||
// NOTE: assumes data bytes are the first bytes in a tag
|
||||
@memcpy(bytes_ptr, accum orelse unreachable, accum_width);
|
||||
|
@ -1,6 +1,7 @@
|
||||
const std = @import("std");
|
||||
const math = std.math;
|
||||
const utils = @import("utils.zig");
|
||||
const expect = @import("expect.zig");
|
||||
|
||||
const ROC_BUILTINS = "roc_builtins";
|
||||
const NUM = "num";
|
||||
@ -141,12 +142,14 @@ comptime {
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
comptime {
|
||||
exportUtilsFn(utils.test_panic, "test_panic");
|
||||
exportUtilsFn(utils.increfC, "incref");
|
||||
exportUtilsFn(utils.decrefC, "decref");
|
||||
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||
exportExpectFn(expect.expectFailedC, "expect_failed");
|
||||
exportExpectFn(expect.getExpectFailuresC, "get_expect_failures");
|
||||
exportExpectFn(expect.deinitFailuresC, "deinit_failures");
|
||||
|
||||
@export(utils.panic, .{ .name = "roc_builtins.utils." ++ "panic", .linkage = .Weak });
|
||||
}
|
||||
@ -175,6 +178,10 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
exportBuiltinFn(func, "utils." ++ func_name);
|
||||
}
|
||||
|
||||
fn exportExpectFn(comptime func: anytype, comptime func_name: []const u8) void {
|
||||
exportBuiltinFn(func, "expect." ++ func_name);
|
||||
}
|
||||
|
||||
// Custom panic function, as builtin Zig version errors during LLVM verification
|
||||
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
|
||||
const builtin = @import("builtin");
|
||||
|
@ -18,14 +18,18 @@ extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
|
||||
// Signals to the host that the program has panicked
|
||||
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void;
|
||||
|
||||
// should work just like libc memcpy (we can't assume libc is present)
|
||||
extern fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
|
||||
|
||||
comptime {
|
||||
const builtin = @import("builtin");
|
||||
// During tetsts, use the testing allocators to satisfy these functions.
|
||||
// During tests, use the testing allocators to satisfy these functions.
|
||||
if (builtin.is_test) {
|
||||
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
|
||||
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
|
||||
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
|
||||
@export(testing_roc_panic, .{ .name = "roc_panic", .linkage = .Strong });
|
||||
@export(testing_roc_memcpy, .{ .name = "roc_memcpy", .linkage = .Strong });
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,8 +57,16 @@ fn testing_roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
|
||||
@panic("Roc panicked");
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize, alignment: u32) [*]u8 {
|
||||
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
|
||||
fn testing_roc_memcpy(dest: *c_void, src: *c_void, bytes: usize) callconv(.C) ?*c_void {
|
||||
const zig_dest = @ptrCast([*]u8, dest);
|
||||
const zig_src = @ptrCast([*]u8, src);
|
||||
|
||||
@memcpy(zig_dest, zig_src, bytes);
|
||||
return dest;
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize, alignment: u32) ?[*]u8 {
|
||||
return @ptrCast(?[*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
|
||||
}
|
||||
|
||||
pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 {
|
||||
@ -70,6 +82,10 @@ pub fn panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
return @call(.{ .modifier = always_inline }, roc_panic, .{ c_ptr, alignment });
|
||||
}
|
||||
|
||||
pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void {
|
||||
@call(.{ .modifier = always_inline }, roc_memcpy, .{ dst, src, size });
|
||||
}
|
||||
|
||||
// indirection because otherwise zig creates an alias to the panic function which our LLVM code
|
||||
// does not know how to deal with
|
||||
pub fn test_panic(c_ptr: *c_void, alignment: u32) callconv(.C) void {
|
||||
@ -173,7 +189,8 @@ pub fn allocateWithRefcount(
|
||||
|
||||
switch (alignment) {
|
||||
16 => {
|
||||
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment));
|
||||
// TODO handle alloc failing!
|
||||
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment) orelse unreachable);
|
||||
|
||||
var as_usize_array = @ptrCast([*]usize, new_bytes);
|
||||
as_usize_array[0] = 0;
|
||||
@ -185,7 +202,8 @@ pub fn allocateWithRefcount(
|
||||
return first_slot;
|
||||
},
|
||||
8 => {
|
||||
var raw = alloc(length, alignment);
|
||||
// TODO handle alloc failing!
|
||||
var raw = alloc(length, alignment) orelse unreachable;
|
||||
var new_bytes: [*]align(8) u8 = @alignCast(8, raw);
|
||||
|
||||
var as_isize_array = @ptrCast([*]isize, new_bytes);
|
||||
@ -197,7 +215,8 @@ pub fn allocateWithRefcount(
|
||||
return first_slot;
|
||||
},
|
||||
4 => {
|
||||
var raw = alloc(length, alignment);
|
||||
// TODO handle alloc failing!
|
||||
var raw = alloc(length, alignment) orelse unreachable;
|
||||
var new_bytes: [*]align(@alignOf(isize)) u8 = @alignCast(@alignOf(isize), raw);
|
||||
|
||||
var as_isize_array = @ptrCast([*]isize, new_bytes);
|
||||
@ -217,6 +236,11 @@ pub fn allocateWithRefcount(
|
||||
}
|
||||
}
|
||||
|
||||
pub const CSlice = extern struct {
|
||||
pointer: *c_void,
|
||||
len: usize,
|
||||
};
|
||||
|
||||
pub fn unsafeReallocate(
|
||||
source_ptr: [*]u8,
|
||||
alignment: u32,
|
||||
|
@ -1143,7 +1143,7 @@ shr : Int a, Int a -> Int a
|
||||
|
||||
## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right.
|
||||
##
|
||||
## This is called `shlWrap` because any bits shifted
|
||||
## This is called `shrWrap` because any bits shifted
|
||||
## off the end of the number will be wrapped around to
|
||||
## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.)
|
||||
shrWrap : Int a, Int a -> Int a
|
||||
|
@ -336,3 +336,6 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
||||
pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
|
||||
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||
pub const UTILS_EXPECT_FAILED: &str = "roc_builtins.expect.expect_failed";
|
||||
pub const UTILS_GET_EXPECT_FAILURES: &str = "roc_builtins.expect.get_expect_failures";
|
||||
pub const UTILS_DEINIT_FAILURES: &str = "roc_builtins.expect.deinit_failures";
|
||||
|
@ -357,7 +357,7 @@ fn can_annotation_help(
|
||||
actual: Box::new(actual),
|
||||
}
|
||||
}
|
||||
None => Type::Apply(symbol, args),
|
||||
None => Type::Apply(symbol, args, region),
|
||||
}
|
||||
}
|
||||
BoundVariable(v) => {
|
||||
@ -377,7 +377,8 @@ fn can_annotation_help(
|
||||
As(
|
||||
loc_inner,
|
||||
_spaces,
|
||||
AliasHeader {
|
||||
alias_header
|
||||
@ AliasHeader {
|
||||
name,
|
||||
vars: loc_vars,
|
||||
},
|
||||
@ -439,20 +440,43 @@ fn can_annotation_help(
|
||||
}
|
||||
}
|
||||
|
||||
let alias_args = vars.iter().map(|(_, v)| v.clone()).collect::<Vec<_>>();
|
||||
|
||||
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
|
||||
let rec_var = var_store.fresh();
|
||||
|
||||
let mut new_tags = Vec::with_capacity(tags.len());
|
||||
let mut is_nested_datatype = false;
|
||||
for (tag_name, args) in tags {
|
||||
let mut new_args = Vec::with_capacity(args.len());
|
||||
for arg in args {
|
||||
let mut new_arg = arg.clone();
|
||||
new_arg.substitute_alias(symbol, &Type::Variable(rec_var));
|
||||
let substitution_result =
|
||||
new_arg.substitute_alias(symbol, &alias_args, &Type::Variable(rec_var));
|
||||
|
||||
if let Err(differing_recursion_region) = substitution_result {
|
||||
env.problems
|
||||
.push(roc_problem::can::Problem::NestedDatatype {
|
||||
alias: symbol,
|
||||
def_region: alias_header.region(),
|
||||
differing_recursion_region,
|
||||
});
|
||||
is_nested_datatype = true;
|
||||
}
|
||||
|
||||
// Either way, add the argument; not doing so would only result in more
|
||||
// confusing error messages later on.
|
||||
new_args.push(new_arg);
|
||||
}
|
||||
new_tags.push((tag_name.clone(), new_args));
|
||||
}
|
||||
Type::RecursiveTagUnion(rec_var, new_tags, ext)
|
||||
if is_nested_datatype {
|
||||
// We don't have a way to represent nested data types; hence, we don't actually
|
||||
// use the recursion var in them, and should avoid marking them as such.
|
||||
Type::TagUnion(new_tags, ext)
|
||||
} else {
|
||||
Type::RecursiveTagUnion(rec_var, new_tags, ext)
|
||||
}
|
||||
} else {
|
||||
inner_type
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::def::Def;
|
||||
use crate::expr::{self, ClosureData, Expr::*};
|
||||
use crate::expr::{self, ClosureData, Expr::*, IntValue};
|
||||
use crate::expr::{Expr, Field, Recursive};
|
||||
use crate::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
|
||||
use crate::pattern::Pattern;
|
||||
use roc_collections::all::SendMap;
|
||||
use roc_module::called_via::CalledVia;
|
||||
@ -793,7 +794,7 @@ fn num_is_zero(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::Eq,
|
||||
args: vec![
|
||||
(arg_var, Var(Symbol::ARG_1)),
|
||||
(arg_var, num(unbound_zero_var, 0)),
|
||||
(arg_var, num(unbound_zero_var, 0, num_no_bound())),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
};
|
||||
@ -816,7 +817,7 @@ fn num_is_negative(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::NumGt,
|
||||
args: vec![
|
||||
(arg_var, num(unbound_zero_var, 0)),
|
||||
(arg_var, num(unbound_zero_var, 0, num_no_bound())),
|
||||
(arg_var, Var(Symbol::ARG_1)),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
@ -841,7 +842,7 @@ fn num_is_positive(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::NumGt,
|
||||
args: vec![
|
||||
(arg_var, Var(Symbol::ARG_1)),
|
||||
(arg_var, num(unbound_zero_var, 0)),
|
||||
(arg_var, num(unbound_zero_var, 0, num_no_bound())),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
};
|
||||
@ -866,7 +867,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
args: vec![
|
||||
(
|
||||
arg_var,
|
||||
int::<i128>(var_store.fresh(), var_store.fresh(), 1),
|
||||
int::<i128>(var_store.fresh(), var_store.fresh(), 1, num_no_bound()),
|
||||
),
|
||||
(
|
||||
arg_var,
|
||||
@ -874,7 +875,7 @@ fn num_is_odd(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::NumRemUnchecked,
|
||||
args: vec![
|
||||
(arg_var, Var(Symbol::ARG_1)),
|
||||
(arg_var, num(unbound_two_var, 2)),
|
||||
(arg_var, num(unbound_two_var, 2, num_no_bound())),
|
||||
],
|
||||
ret_var: arg_var,
|
||||
},
|
||||
@ -901,14 +902,14 @@ fn num_is_even(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::Eq,
|
||||
args: vec![
|
||||
(arg_var, num(arg_num_var, 0)),
|
||||
(arg_var, num(arg_num_var, 0, num_no_bound())),
|
||||
(
|
||||
arg_var,
|
||||
RunLowLevel {
|
||||
op: LowLevel::NumRemUnchecked,
|
||||
args: vec![
|
||||
(arg_var, Var(Symbol::ARG_1)),
|
||||
(arg_var, num(arg_num_var, 2)),
|
||||
(arg_var, num(arg_num_var, 2, num_no_bound())),
|
||||
],
|
||||
ret_var: arg_var,
|
||||
},
|
||||
@ -962,7 +963,10 @@ fn num_sqrt(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::NumGte,
|
||||
args: vec![
|
||||
(float_var, Var(Symbol::ARG_1)),
|
||||
(float_var, float(unbound_zero_var, precision_var, 0.0)),
|
||||
(
|
||||
float_var,
|
||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
}),
|
||||
@ -1008,7 +1012,10 @@ fn num_log(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::NumGt,
|
||||
args: vec![
|
||||
(float_var, Var(Symbol::ARG_1)),
|
||||
(float_var, float(unbound_zero_var, precision_var, 0.0)),
|
||||
(
|
||||
float_var,
|
||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
}),
|
||||
@ -1246,92 +1253,182 @@ fn num_int_cast(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
|
||||
/// Num.minI8: I8
|
||||
fn num_min_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i8>(symbol, var_store, i8::MIN)
|
||||
int_min_or_max::<i8>(
|
||||
symbol,
|
||||
var_store,
|
||||
i8::MIN,
|
||||
NumericBound::Exact(IntWidth::I8),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxI8: I8
|
||||
fn num_max_i8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i8>(symbol, var_store, i8::MAX)
|
||||
int_min_or_max::<i8>(
|
||||
symbol,
|
||||
var_store,
|
||||
i8::MAX,
|
||||
NumericBound::Exact(IntWidth::I8),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minU8: U8
|
||||
fn num_min_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u8>(symbol, var_store, u8::MIN)
|
||||
int_min_or_max::<u8>(
|
||||
symbol,
|
||||
var_store,
|
||||
u8::MIN,
|
||||
NumericBound::Exact(IntWidth::U8),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxU8: U8
|
||||
fn num_max_u8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u8>(symbol, var_store, u8::MAX)
|
||||
int_min_or_max::<u8>(
|
||||
symbol,
|
||||
var_store,
|
||||
u8::MAX,
|
||||
NumericBound::Exact(IntWidth::U8),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minI16: I16
|
||||
fn num_min_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i16>(symbol, var_store, i16::MIN)
|
||||
int_min_or_max::<i16>(
|
||||
symbol,
|
||||
var_store,
|
||||
i16::MIN,
|
||||
NumericBound::Exact(IntWidth::I16),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxI16: I16
|
||||
fn num_max_i16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i16>(symbol, var_store, i16::MAX)
|
||||
int_min_or_max::<i16>(
|
||||
symbol,
|
||||
var_store,
|
||||
i16::MAX,
|
||||
NumericBound::Exact(IntWidth::I16),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minU16: U16
|
||||
fn num_min_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u16>(symbol, var_store, u16::MIN)
|
||||
int_min_or_max::<u16>(
|
||||
symbol,
|
||||
var_store,
|
||||
u16::MIN,
|
||||
NumericBound::Exact(IntWidth::U16),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxU16: U16
|
||||
fn num_max_u16(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u16>(symbol, var_store, u16::MAX)
|
||||
int_min_or_max::<u16>(
|
||||
symbol,
|
||||
var_store,
|
||||
u16::MAX,
|
||||
NumericBound::Exact(IntWidth::U16),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minI32: I32
|
||||
fn num_min_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i32>(symbol, var_store, i32::MIN)
|
||||
int_min_or_max::<i32>(
|
||||
symbol,
|
||||
var_store,
|
||||
i32::MIN,
|
||||
NumericBound::Exact(IntWidth::I32),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxI32: I32
|
||||
fn num_max_i32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i32>(symbol, var_store, i32::MAX)
|
||||
int_min_or_max::<i32>(
|
||||
symbol,
|
||||
var_store,
|
||||
i32::MAX,
|
||||
NumericBound::Exact(IntWidth::I32),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minU32: U32
|
||||
fn num_min_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u32>(symbol, var_store, u32::MIN)
|
||||
int_min_or_max::<u32>(
|
||||
symbol,
|
||||
var_store,
|
||||
u32::MIN,
|
||||
NumericBound::Exact(IntWidth::U32),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxU32: U32
|
||||
fn num_max_u32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u32>(symbol, var_store, u32::MAX)
|
||||
int_min_or_max::<u32>(
|
||||
symbol,
|
||||
var_store,
|
||||
u32::MAX,
|
||||
NumericBound::Exact(IntWidth::U32),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minI64: I64
|
||||
fn num_min_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i64>(symbol, var_store, i64::MIN)
|
||||
int_min_or_max::<i64>(
|
||||
symbol,
|
||||
var_store,
|
||||
i64::MIN,
|
||||
NumericBound::Exact(IntWidth::I64),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxI64: I64
|
||||
fn num_max_i64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i64>(symbol, var_store, i64::MAX)
|
||||
int_min_or_max::<i64>(
|
||||
symbol,
|
||||
var_store,
|
||||
i64::MAX,
|
||||
NumericBound::Exact(IntWidth::I64),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minU64: U64
|
||||
fn num_min_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u64>(symbol, var_store, u64::MIN)
|
||||
int_min_or_max::<u64>(
|
||||
symbol,
|
||||
var_store,
|
||||
u64::MIN,
|
||||
NumericBound::Exact(IntWidth::U64),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxU64: U64
|
||||
fn num_max_u64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<u64>(symbol, var_store, u64::MAX)
|
||||
int_min_or_max::<u64>(
|
||||
symbol,
|
||||
var_store,
|
||||
u64::MAX,
|
||||
NumericBound::Exact(IntWidth::U64),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.minI128: I128
|
||||
fn num_min_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i128>(symbol, var_store, i128::MIN)
|
||||
int_min_or_max::<i128>(
|
||||
symbol,
|
||||
var_store,
|
||||
i128::MIN,
|
||||
NumericBound::Exact(IntWidth::I128),
|
||||
)
|
||||
}
|
||||
|
||||
/// Num.maxI128: I128
|
||||
fn num_max_i128(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
int_min_or_max::<i128>(symbol, var_store, i128::MAX)
|
||||
int_min_or_max::<i128>(
|
||||
symbol,
|
||||
var_store,
|
||||
i128::MAX,
|
||||
NumericBound::Exact(IntWidth::I128),
|
||||
)
|
||||
}
|
||||
|
||||
/// List.isEmpty : List * -> Bool
|
||||
@ -1344,7 +1441,7 @@ fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::Eq,
|
||||
args: vec![
|
||||
(len_var, num(unbound_zero_var, 0)),
|
||||
(len_var, num(unbound_zero_var, 0, num_no_bound())),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
@ -1458,7 +1555,12 @@ fn str_to_num(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
),
|
||||
(
|
||||
errorcode_var,
|
||||
int::<i128>(errorcode_var, Variable::UNSIGNED8, 0),
|
||||
int::<i128>(
|
||||
errorcode_var,
|
||||
Variable::UNSIGNED8,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::U8),
|
||||
),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
@ -2201,7 +2303,12 @@ fn list_swap(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
fn list_take_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let len_var = var_store.fresh();
|
||||
let zero = int::<i128>(len_var, Variable::NATURAL, 0);
|
||||
let zero = int::<i128>(
|
||||
len_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
|
||||
let body = RunLowLevel {
|
||||
op: LowLevel::ListSublist,
|
||||
@ -2227,7 +2334,12 @@ fn list_take_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let list_var = var_store.fresh();
|
||||
let len_var = var_store.fresh();
|
||||
|
||||
let zero = int::<i128>(len_var, Variable::NATURAL, 0);
|
||||
let zero = int::<i128>(
|
||||
len_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
let bool_var = var_store.fresh();
|
||||
|
||||
let get_list_len = RunLowLevel {
|
||||
@ -2337,7 +2449,12 @@ fn list_intersperse(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let clos_elem_sym = Symbol::ARG_4;
|
||||
|
||||
let int_var = var_store.fresh();
|
||||
let zero = int::<i128>(int_var, Variable::NATURAL, 0);
|
||||
let zero = int::<i128>(
|
||||
int_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
|
||||
// \acc, elem -> acc |> List.append sep |> List.append elem
|
||||
let clos = Closure(ClosureData {
|
||||
@ -2417,7 +2534,12 @@ fn list_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let clos_ret_var = var_store.fresh();
|
||||
|
||||
let ret_var = var_store.fresh();
|
||||
let zero = int::<i128>(index_var, Variable::NATURAL, 0);
|
||||
let zero = int::<i128>(
|
||||
index_var,
|
||||
Variable::NATURAL,
|
||||
0,
|
||||
NumericBound::Exact(IntWidth::Nat),
|
||||
);
|
||||
|
||||
let clos = Closure(ClosureData {
|
||||
function_type: clos_fun_var,
|
||||
@ -2579,7 +2701,10 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::ListDropAt,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(index_var, int::<i128>(num_var, num_precision_var, 0)),
|
||||
(
|
||||
index_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: list_var,
|
||||
};
|
||||
@ -2676,7 +2801,10 @@ fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
ret_var: len_var,
|
||||
},
|
||||
),
|
||||
(arg_var, int::<i128>(num_var, num_precision_var, 1)),
|
||||
(
|
||||
arg_var,
|
||||
int::<i128>(num_var, num_precision_var, 1, num_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: len_var,
|
||||
},
|
||||
@ -2874,7 +3002,10 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(len_var, int::<i128>(num_var, num_precision_var, 0)),
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
@ -2905,7 +3036,15 @@ fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::ListGetUnsafe,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(arg_var, int::<i128>(num_var, num_precision_var, 0)),
|
||||
(
|
||||
arg_var,
|
||||
int::<i128>(
|
||||
num_var,
|
||||
num_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
ret_var: list_elem_var,
|
||||
},
|
||||
@ -3004,7 +3143,10 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(len_var, int::<i128>(num_var, num_precision_var, 0)),
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
@ -3035,7 +3177,15 @@ fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::ListGetUnsafe,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(arg_var, int::<i128>(num_var, num_precision_var, 0)),
|
||||
(
|
||||
arg_var,
|
||||
int::<i128>(
|
||||
num_var,
|
||||
num_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
ret_var: list_elem_var,
|
||||
},
|
||||
@ -3129,7 +3279,10 @@ fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
Box::new(function),
|
||||
vec![
|
||||
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
|
||||
(num_var, Loc::at_zero(num(var_store.fresh(), 0))),
|
||||
(
|
||||
num_var,
|
||||
Loc::at_zero(num(var_store.fresh(), 0, num_no_bound())),
|
||||
),
|
||||
(closure_var, Loc::at_zero(Var(Symbol::NUM_ADD))),
|
||||
],
|
||||
CalledVia::Space,
|
||||
@ -3161,7 +3314,10 @@ fn list_product(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
Box::new(function),
|
||||
vec![
|
||||
(list_var, Loc::at_zero(Var(Symbol::ARG_1))),
|
||||
(num_var, Loc::at_zero(num(var_store.fresh(), 1))),
|
||||
(
|
||||
num_var,
|
||||
Loc::at_zero(num(var_store.fresh(), 1, num_no_bound())),
|
||||
),
|
||||
(closure_var, Loc::at_zero(Var(Symbol::NUM_MUL))),
|
||||
],
|
||||
CalledVia::Space,
|
||||
@ -3815,7 +3971,7 @@ fn num_rem(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(num_var, Var(Symbol::ARG_2)),
|
||||
(num_var, num(unbound_zero_var, 0)),
|
||||
(num_var, num(unbound_zero_var, 0, num_no_bound())),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
},
|
||||
@ -3918,7 +4074,10 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(num_var, Var(Symbol::ARG_2)),
|
||||
(num_var, float(unbound_zero_var, precision_var, 0.0)),
|
||||
(
|
||||
num_var,
|
||||
float(unbound_zero_var, precision_var, 0.0, num_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
},
|
||||
@ -3983,7 +4142,12 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
(num_var, Var(Symbol::ARG_2)),
|
||||
(
|
||||
num_var,
|
||||
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0),
|
||||
int::<i128>(
|
||||
unbound_zero_var,
|
||||
unbound_zero_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
@ -4049,7 +4213,12 @@ fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
(num_var, Var(Symbol::ARG_2)),
|
||||
(
|
||||
num_var,
|
||||
int::<i128>(unbound_zero_var, unbound_zero_precision_var, 0),
|
||||
int::<i128>(
|
||||
unbound_zero_var,
|
||||
unbound_zero_precision_var,
|
||||
0,
|
||||
num_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
ret_var: bool_var,
|
||||
@ -4119,7 +4288,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(len_var, int::<i128>(zero_var, zero_precision_var, 0)),
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
@ -4143,7 +4315,10 @@ fn list_first(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
op: LowLevel::ListGetUnsafe,
|
||||
args: vec![
|
||||
(list_var, Var(Symbol::ARG_1)),
|
||||
(len_var, int::<i128>(zero_var, zero_precision_var, 0)),
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(zero_var, zero_precision_var, 0, num_no_bound()),
|
||||
),
|
||||
],
|
||||
ret_var: list_elem_var,
|
||||
},
|
||||
@ -4200,7 +4375,10 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
RunLowLevel {
|
||||
op: LowLevel::NotEq,
|
||||
args: vec![
|
||||
(len_var, int::<i128>(num_var, num_precision_var, 0)),
|
||||
(
|
||||
len_var,
|
||||
int::<i128>(num_var, num_precision_var, 0, num_no_bound()),
|
||||
),
|
||||
(
|
||||
len_var,
|
||||
RunLowLevel {
|
||||
@ -4239,7 +4417,15 @@ fn list_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
ret_var: len_var,
|
||||
},
|
||||
),
|
||||
(arg_var, int::<i128>(num_var, num_precision_var, 1)),
|
||||
(
|
||||
arg_var,
|
||||
int::<i128>(
|
||||
num_var,
|
||||
num_precision_var,
|
||||
1,
|
||||
num_no_bound(),
|
||||
),
|
||||
),
|
||||
],
|
||||
ret_var: len_var,
|
||||
},
|
||||
@ -4867,7 +5053,10 @@ fn num_bytes_to(symbol: Symbol, var_store: &mut VarStore, offset: i64, low_level
|
||||
add_var,
|
||||
RunLowLevel {
|
||||
ret_var: cast_var,
|
||||
args: vec![(cast_var, num(var_store.fresh(), offset))],
|
||||
args: vec![(
|
||||
cast_var,
|
||||
num(var_store.fresh(), offset, num_no_bound()),
|
||||
)],
|
||||
op: LowLevel::NumIntCast,
|
||||
},
|
||||
),
|
||||
@ -4955,13 +5144,18 @@ fn defn_help(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn int_min_or_max<I128>(symbol: Symbol, var_store: &mut VarStore, i: I128) -> Def
|
||||
fn int_min_or_max<I128>(
|
||||
symbol: Symbol,
|
||||
var_store: &mut VarStore,
|
||||
i: I128,
|
||||
bound: NumericBound<IntWidth>,
|
||||
) -> Def
|
||||
where
|
||||
I128: Into<i128>,
|
||||
{
|
||||
let int_var = var_store.fresh();
|
||||
let int_precision_var = var_store.fresh();
|
||||
let body = int::<I128>(int_var, int_precision_var, i);
|
||||
let body = int::<I128>(int_var, int_precision_var, i, bound);
|
||||
|
||||
let std = roc_builtins::std::types();
|
||||
let solved = std.get(&symbol).unwrap();
|
||||
@ -4984,21 +5178,53 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn num_no_bound<W: Copy>() -> NumericBound<W> {
|
||||
NumericBound::None
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn int<I128>(num_var: Variable, precision_var: Variable, i: I128) -> Expr
|
||||
fn int<I128>(
|
||||
num_var: Variable,
|
||||
precision_var: Variable,
|
||||
i: I128,
|
||||
bound: NumericBound<IntWidth>,
|
||||
) -> Expr
|
||||
where
|
||||
I128: Into<i128>,
|
||||
{
|
||||
let ii = i.into();
|
||||
Int(num_var, precision_var, ii.to_string().into_boxed_str(), ii)
|
||||
Int(
|
||||
num_var,
|
||||
precision_var,
|
||||
ii.to_string().into_boxed_str(),
|
||||
IntValue::I128(ii),
|
||||
bound,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn float(num_var: Variable, precision_var: Variable, f: f64) -> Expr {
|
||||
Float(num_var, precision_var, f.to_string().into_boxed_str(), f)
|
||||
fn float(
|
||||
num_var: Variable,
|
||||
precision_var: Variable,
|
||||
f: f64,
|
||||
bound: NumericBound<FloatWidth>,
|
||||
) -> Expr {
|
||||
Float(
|
||||
num_var,
|
||||
precision_var,
|
||||
f.to_string().into_boxed_str(),
|
||||
f,
|
||||
bound,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn num(num_var: Variable, i: i64) -> Expr {
|
||||
Num(num_var, i.to_string().into_boxed_str(), i)
|
||||
fn num<I: Into<i128>>(num_var: Variable, i: I, bound: NumericBound<NumWidth>) -> Expr {
|
||||
let i = i.into();
|
||||
Num(
|
||||
num_var,
|
||||
i.to_string().into_boxed_str(),
|
||||
IntValue::I128(i),
|
||||
bound,
|
||||
)
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ pub fn canonicalize_defs<'a>(
|
||||
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
|
||||
let mut is_phantom = false;
|
||||
|
||||
for loc_lowercase in vars {
|
||||
for loc_lowercase in vars.iter() {
|
||||
if let Some(var) = can_ann
|
||||
.introduced_variables
|
||||
.var_by_name(&loc_lowercase.value)
|
||||
@ -303,10 +303,18 @@ pub fn canonicalize_defs<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut is_nested_datatype = false;
|
||||
if can_ann.typ.contains_symbol(symbol) {
|
||||
make_tag_union_recursive(
|
||||
let alias_args = can_vars
|
||||
.iter()
|
||||
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
|
||||
.collect::<Vec<_>>();
|
||||
let alias_region =
|
||||
Region::across_all([name.region].iter().chain(vars.iter().map(|l| &l.region)));
|
||||
|
||||
let made_recursive = make_tag_union_recursive(
|
||||
env,
|
||||
symbol,
|
||||
Loc::at(alias_region, (symbol, &alias_args)),
|
||||
name.region,
|
||||
vec![],
|
||||
&mut can_ann.typ,
|
||||
@ -315,6 +323,13 @@ pub fn canonicalize_defs<'a>(
|
||||
// recursion errors after the sorted introductions are complete.
|
||||
&mut false,
|
||||
);
|
||||
|
||||
is_nested_datatype = made_recursive.is_err();
|
||||
}
|
||||
|
||||
if is_nested_datatype {
|
||||
// Bail out
|
||||
continue;
|
||||
}
|
||||
|
||||
scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
|
||||
@ -825,9 +840,9 @@ fn pattern_to_vars_by_symbol(
|
||||
}
|
||||
}
|
||||
|
||||
NumLiteral(_, _, _)
|
||||
| IntLiteral(_, _, _)
|
||||
| FloatLiteral(_, _, _)
|
||||
NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
@ -1624,9 +1639,16 @@ fn correct_mutual_recursive_type_alias<'a>(
|
||||
var_store,
|
||||
&mut ImSet::default(),
|
||||
);
|
||||
make_tag_union_recursive(
|
||||
|
||||
let alias_args = &alias
|
||||
.type_variables
|
||||
.iter()
|
||||
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let _made_recursive = make_tag_union_recursive(
|
||||
env,
|
||||
*rec,
|
||||
Loc::at(alias.header_region(), (*rec, alias_args)),
|
||||
alias.region,
|
||||
others,
|
||||
&mut alias.typ,
|
||||
@ -1640,25 +1662,71 @@ fn correct_mutual_recursive_type_alias<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to make a tag union recursive at the position of `recursive_alias`; for example,
|
||||
///
|
||||
/// ```roc
|
||||
/// [ Cons a (ConsList a), Nil ] as ConsList a
|
||||
/// ```
|
||||
///
|
||||
/// can be made recursive at the position "ConsList a" with a fresh recursive variable, say r1:
|
||||
///
|
||||
/// ```roc
|
||||
/// [ Cons a r1, Nil ] as r1
|
||||
/// ```
|
||||
///
|
||||
/// Returns `Err` if the tag union is recursive, but there is no structure-preserving recursion
|
||||
/// variable for it. This can happen when the type is a nested datatype, for example in either of
|
||||
///
|
||||
/// ```roc
|
||||
/// Nested a : [ Chain a (Nested (List a)), Term ]
|
||||
/// DuoList a b : [ Cons a (DuoList b a), Nil ]
|
||||
/// ```
|
||||
///
|
||||
/// When `Err` is returned, a problem will be added to `env`.
|
||||
fn make_tag_union_recursive<'a>(
|
||||
env: &mut Env<'a>,
|
||||
symbol: Symbol,
|
||||
recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>,
|
||||
region: Region,
|
||||
others: Vec<Symbol>,
|
||||
typ: &mut Type,
|
||||
var_store: &mut VarStore,
|
||||
can_report_error: &mut bool,
|
||||
) {
|
||||
) -> Result<(), ()> {
|
||||
let Loc {
|
||||
value: (symbol, args),
|
||||
region: alias_region,
|
||||
} = recursive_alias;
|
||||
let vars = args.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>();
|
||||
match typ {
|
||||
Type::TagUnion(tags, ext) => {
|
||||
let rec_var = var_store.fresh();
|
||||
*typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
|
||||
typ.substitute_alias(symbol, &Type::Variable(rec_var));
|
||||
let mut pending_typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
|
||||
let substitution_result =
|
||||
pending_typ.substitute_alias(symbol, &vars, &Type::Variable(rec_var));
|
||||
match substitution_result {
|
||||
Ok(()) => {
|
||||
// We can substitute the alias presence for the variable exactly.
|
||||
*typ = pending_typ;
|
||||
Ok(())
|
||||
}
|
||||
Err(differing_recursion_region) => {
|
||||
env.problems.push(Problem::NestedDatatype {
|
||||
alias: symbol,
|
||||
def_region: alias_region,
|
||||
differing_recursion_region,
|
||||
});
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::RecursiveTagUnion(_, _, _) => {}
|
||||
Type::Alias { actual, .. } => make_tag_union_recursive(
|
||||
Type::RecursiveTagUnion(_, _, _) => Ok(()),
|
||||
Type::Alias {
|
||||
actual,
|
||||
type_arguments,
|
||||
..
|
||||
} => make_tag_union_recursive(
|
||||
env,
|
||||
symbol,
|
||||
Loc::at_zero((symbol, type_arguments)),
|
||||
region,
|
||||
others,
|
||||
actual,
|
||||
@ -1676,6 +1744,7 @@ fn make_tag_union_recursive<'a>(
|
||||
let problem = Problem::CyclicAlias(symbol, region, others);
|
||||
env.problems.push(problem);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
use roc_can::annotation::IntroducedVariables;
|
||||
use roc_can::def::{Declaration, Def};
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expr::{ClosureData, Expr, Recursive};
|
||||
use roc_can::pattern::Pattern;
|
||||
use roc_can::scope::Scope;
|
||||
use crate::annotation::IntroducedVariables;
|
||||
use crate::def::{Declaration, Def};
|
||||
use crate::env::Env;
|
||||
use crate::expr::{ClosureData, Expr, Recursive};
|
||||
use crate::pattern::Pattern;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{MutSet, SendMap};
|
||||
use roc_module::called_via::CalledVia;
|
||||
use roc_module::ident::TagName;
|
||||
@ -220,7 +220,7 @@ fn build_effect_always(
|
||||
)
|
||||
};
|
||||
|
||||
let def_annotation = roc_can::def::Annotation {
|
||||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
@ -432,7 +432,7 @@ fn build_effect_map(
|
||||
)
|
||||
};
|
||||
|
||||
let def_annotation = roc_can::def::Annotation {
|
||||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
@ -599,7 +599,7 @@ fn build_effect_after(
|
||||
)
|
||||
};
|
||||
|
||||
let def_annotation = roc_can::def::Annotation {
|
||||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
@ -852,7 +852,7 @@ fn build_effect_forever(
|
||||
)
|
||||
};
|
||||
|
||||
let def_annotation = roc_can::def::Annotation {
|
||||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
@ -1150,7 +1150,7 @@ fn build_effect_loop(
|
||||
)
|
||||
};
|
||||
|
||||
let def_annotation = roc_can::def::Annotation {
|
||||
let def_annotation = crate::def::Annotation {
|
||||
signature,
|
||||
introduced_variables,
|
||||
aliases: SendMap::default(),
|
||||
@ -1334,7 +1334,7 @@ fn build_effect_loop_inner_body(
|
||||
|
||||
let step_pattern = applied_tag_pattern(step_tag_name, &[new_state_symbol], var_store);
|
||||
|
||||
roc_can::expr::WhenBranch {
|
||||
crate::expr::WhenBranch {
|
||||
patterns: vec![Loc::at_zero(step_pattern)],
|
||||
value: Loc::at_zero(force_thunk2),
|
||||
guard: None,
|
||||
@ -1345,7 +1345,7 @@ fn build_effect_loop_inner_body(
|
||||
let done_tag_name = TagName::Global("Done".into());
|
||||
let done_pattern = applied_tag_pattern(done_tag_name, &[done_symbol], var_store);
|
||||
|
||||
roc_can::expr::WhenBranch {
|
||||
crate::expr::WhenBranch {
|
||||
patterns: vec![Loc::at_zero(done_pattern)],
|
||||
value: Loc::at_zero(Expr::Var(done_symbol)),
|
||||
guard: None,
|
||||
@ -1376,7 +1376,7 @@ pub fn build_host_exposed_def(
|
||||
ident: &str,
|
||||
effect_tag_name: TagName,
|
||||
var_store: &mut VarStore,
|
||||
annotation: roc_can::annotation::Annotation,
|
||||
annotation: crate::annotation::Annotation,
|
||||
) -> Def {
|
||||
let expr_var = var_store.fresh();
|
||||
let pattern = Pattern::Identifier(symbol);
|
||||
@ -1520,7 +1520,7 @@ pub fn build_host_exposed_def(
|
||||
}
|
||||
};
|
||||
|
||||
let def_annotation = roc_can::def::Annotation {
|
||||
let def_annotation = crate::def::Annotation {
|
||||
signature: annotation.typ,
|
||||
introduced_variables: annotation.introduced_variables,
|
||||
aliases: annotation.aliases,
|
@ -3,8 +3,8 @@ use crate::builtins::builtin_defs_map;
|
||||
use crate::def::{can_defs_with_return, Def};
|
||||
use crate::env::Env;
|
||||
use crate::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result,
|
||||
int_expr_from_result, num_expr_from_result,
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result,
|
||||
int_expr_from_result, num_expr_from_result, FloatWidth, IntWidth, NumWidth, NumericBound,
|
||||
};
|
||||
use crate::pattern::{canonicalize_pattern, Pattern};
|
||||
use crate::procedure::References;
|
||||
@ -20,7 +20,7 @@ use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::Alias;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::{char, u32};
|
||||
|
||||
#[derive(Clone, Default, Debug, PartialEq)]
|
||||
@ -46,17 +46,38 @@ impl Output {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum IntValue {
|
||||
I128(i128),
|
||||
U128(u128),
|
||||
}
|
||||
|
||||
impl Display for IntValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
IntValue::I128(n) => Display::fmt(&n, f),
|
||||
IntValue::U128(n) => Display::fmt(&n, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Expr {
|
||||
// Literals
|
||||
|
||||
// Num stores the `a` variable in `Num a`. Not the same as the variable
|
||||
// stored in Int and Float below, which is strictly for better error messages
|
||||
Num(Variable, Box<str>, i64),
|
||||
Num(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
|
||||
|
||||
// Int and Float store a variable to generate better error messages
|
||||
Int(Variable, Variable, Box<str>, i128),
|
||||
Float(Variable, Variable, Box<str>, f64),
|
||||
Int(
|
||||
Variable,
|
||||
Variable,
|
||||
Box<str>,
|
||||
IntValue,
|
||||
NumericBound<IntWidth>,
|
||||
),
|
||||
Float(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
||||
Str(Box<str>),
|
||||
List {
|
||||
elem_var: Variable,
|
||||
@ -208,20 +229,20 @@ pub fn canonicalize_expr<'a>(
|
||||
use Expr::*;
|
||||
|
||||
let (expr, output) = match expr {
|
||||
ast::Expr::Num(str) => {
|
||||
&ast::Expr::Num(str) => {
|
||||
let answer = num_expr_from_result(
|
||||
var_store,
|
||||
finish_parsing_int(*str).map(|int| (*str, int)),
|
||||
finish_parsing_num(str).map(|result| (str, result)),
|
||||
region,
|
||||
env,
|
||||
);
|
||||
|
||||
(answer, Output::default())
|
||||
}
|
||||
ast::Expr::Float(str) => {
|
||||
&ast::Expr::Float(str) => {
|
||||
let answer = float_expr_from_result(
|
||||
var_store,
|
||||
finish_parsing_float(str).map(|f| (*str, f)),
|
||||
finish_parsing_float(str).map(|(f, bound)| (str, f, bound)),
|
||||
region,
|
||||
env,
|
||||
);
|
||||
@ -790,21 +811,21 @@ pub fn canonicalize_expr<'a>(
|
||||
|
||||
(RuntimeError(problem), Output::default())
|
||||
}
|
||||
ast::Expr::NonBase10Int {
|
||||
&ast::Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => {
|
||||
// the minus sign is added before parsing, to get correct overflow/underflow behavior
|
||||
let answer = match finish_parsing_base(string, *base, *is_negative) {
|
||||
Ok(int) => {
|
||||
let answer = match finish_parsing_base(string, base, is_negative) {
|
||||
Ok((int, bound)) => {
|
||||
// Done in this kinda round about way with intermediate variables
|
||||
// to keep borrowed values around and make this compile
|
||||
let int_string = int.to_string();
|
||||
let int_str = int_string.as_str();
|
||||
int_expr_from_result(var_store, Ok((int_str, int as i128)), region, *base, env)
|
||||
int_expr_from_result(var_store, Ok((int_str, int, bound)), region, base, env)
|
||||
}
|
||||
Err(e) => int_expr_from_result(var_store, Err(e), region, *base, env),
|
||||
Err(e) => int_expr_from_result(var_store, Err(e), region, base, env),
|
||||
};
|
||||
|
||||
(answer, Output::default())
|
||||
@ -1226,9 +1247,9 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
||||
match expr {
|
||||
// Num stores the `a` variable in `Num a`. Not the same as the variable
|
||||
// stored in Int and Float below, which is strictly for better error messages
|
||||
other @ Num(_, _, _)
|
||||
| other @ Int(_, _, _, _)
|
||||
| other @ Float(_, _, _, _)
|
||||
other @ Num(..)
|
||||
| other @ Int(..)
|
||||
| other @ Float(..)
|
||||
| other @ Str { .. }
|
||||
| other @ RuntimeError(_)
|
||||
| other @ EmptyRecord
|
||||
|
@ -5,6 +5,7 @@ pub mod annotation;
|
||||
pub mod builtins;
|
||||
pub mod constraint;
|
||||
pub mod def;
|
||||
pub mod effect_module;
|
||||
pub mod env;
|
||||
pub mod expected;
|
||||
pub mod expr;
|
||||
|
@ -6,15 +6,16 @@ use crate::pattern::Pattern;
|
||||
use crate::scope::Scope;
|
||||
use bumpalo::Bump;
|
||||
use roc_collections::all::{MutMap, MutSet, SendMap};
|
||||
use roc_module::ident::Ident;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::ident::{Ident, TagName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||
use roc_parse::ast;
|
||||
use roc_parse::header::HeaderFor;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::Alias;
|
||||
use roc_types::types::{Alias, Type};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Module {
|
||||
@ -44,6 +45,7 @@ pub struct ModuleOutput {
|
||||
pub fn canonicalize_module_defs<'a, F>(
|
||||
arena: &Bump,
|
||||
loc_defs: &'a [Loc<ast::Def<'a>>],
|
||||
header_for: &roc_parse::header::HeaderFor,
|
||||
home: ModuleId,
|
||||
module_ids: &ModuleIds,
|
||||
exposed_ident_ids: IdentIds,
|
||||
@ -59,12 +61,49 @@ where
|
||||
{
|
||||
let mut can_exposed_imports = MutMap::default();
|
||||
let mut scope = Scope::new(home, var_store);
|
||||
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
|
||||
let num_deps = dep_idents.len();
|
||||
|
||||
for (name, alias) in aliases.into_iter() {
|
||||
scope.add_alias(name, alias.region, alias.type_variables, alias.typ);
|
||||
}
|
||||
|
||||
let effect_symbol = if let HeaderFor::Hosted { generates, .. } = header_for {
|
||||
// TODO extract effect name from the header
|
||||
let name: &str = generates.into();
|
||||
let effect_symbol = scope
|
||||
.introduce(
|
||||
name.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
Region::zero(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let effect_tag_name = TagName::Private(effect_symbol);
|
||||
|
||||
{
|
||||
let a_var = var_store.fresh();
|
||||
|
||||
let actual = crate::effect_module::build_effect_actual(
|
||||
effect_tag_name,
|
||||
Type::Variable(a_var),
|
||||
var_store,
|
||||
);
|
||||
|
||||
scope.add_alias(
|
||||
effect_symbol,
|
||||
Region::zero(),
|
||||
vec![Loc::at_zero(("a".into(), a_var))],
|
||||
actual,
|
||||
);
|
||||
}
|
||||
|
||||
Some(effect_symbol)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Desugar operators (convert them to Apply calls, taking into account
|
||||
// operator precedence and associativity rules), before doing other canonicalization.
|
||||
//
|
||||
@ -82,7 +121,6 @@ where
|
||||
}));
|
||||
}
|
||||
|
||||
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
|
||||
let mut lookups = Vec::with_capacity(num_deps);
|
||||
let mut rigid_variables = MutMap::default();
|
||||
|
||||
@ -143,7 +181,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let (defs, scope, output, symbols_introduced) = canonicalize_defs(
|
||||
let (defs, mut scope, output, symbols_introduced) = canonicalize_defs(
|
||||
&mut env,
|
||||
Output::default(),
|
||||
var_store,
|
||||
@ -208,7 +246,21 @@ where
|
||||
(Ok(mut declarations), output) => {
|
||||
use crate::def::Declaration::*;
|
||||
|
||||
for decl in declarations.iter() {
|
||||
if let Some(effect_symbol) = effect_symbol {
|
||||
let mut exposed_symbols = MutSet::default();
|
||||
|
||||
// NOTE this currently builds all functions, not just the ones that the user requested
|
||||
crate::effect_module::build_effect_builtins(
|
||||
&mut env,
|
||||
&mut scope,
|
||||
effect_symbol,
|
||||
var_store,
|
||||
&mut exposed_symbols,
|
||||
&mut declarations,
|
||||
);
|
||||
}
|
||||
|
||||
for decl in declarations.iter_mut() {
|
||||
match decl {
|
||||
Declare(def) => {
|
||||
for (symbol, _) in def.pattern_vars.iter() {
|
||||
@ -221,6 +273,59 @@ where
|
||||
exposed_but_not_defined.remove(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary hack: we don't know exactly what symbols are hosted symbols,
|
||||
// and which are meant to be normal definitions without a body. So for now
|
||||
// we just assume they are hosted functions (meant to be provided by the platform)
|
||||
if let Some(effect_symbol) = effect_symbol {
|
||||
macro_rules! make_hosted_def {
|
||||
() => {
|
||||
let symbol = def.pattern_vars.iter().next().unwrap().0;
|
||||
let ident_id = symbol.ident_id();
|
||||
let ident =
|
||||
env.ident_ids.get_name(ident_id).unwrap().to_string();
|
||||
let def_annotation = def.annotation.clone().unwrap();
|
||||
let annotation = crate::annotation::Annotation {
|
||||
typ: def_annotation.signature,
|
||||
introduced_variables: def_annotation.introduced_variables,
|
||||
references: Default::default(),
|
||||
aliases: Default::default(),
|
||||
};
|
||||
|
||||
let hosted_def = crate::effect_module::build_host_exposed_def(
|
||||
&mut env,
|
||||
&mut scope,
|
||||
*symbol,
|
||||
&ident,
|
||||
TagName::Private(effect_symbol),
|
||||
var_store,
|
||||
annotation,
|
||||
);
|
||||
|
||||
*def = hosted_def;
|
||||
};
|
||||
}
|
||||
|
||||
match &def.loc_expr.value {
|
||||
Expr::RuntimeError(RuntimeError::NoImplementationNamed {
|
||||
..
|
||||
}) => {
|
||||
make_hosted_def!();
|
||||
}
|
||||
Expr::Closure(closure_data)
|
||||
if matches!(
|
||||
closure_data.loc_body.value,
|
||||
Expr::RuntimeError(
|
||||
RuntimeError::NoImplementationNamed { .. }
|
||||
)
|
||||
) =>
|
||||
{
|
||||
make_hosted_def!();
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
DeclareRec(defs) => {
|
||||
for def in defs {
|
||||
@ -253,6 +358,18 @@ where
|
||||
|
||||
let mut aliases = MutMap::default();
|
||||
|
||||
if let Some(effect_symbol) = effect_symbol {
|
||||
// Remove this from exposed_symbols,
|
||||
// so that at the end of the process,
|
||||
// we can see if there were any
|
||||
// exposed symbols which did not have
|
||||
// corresponding defs.
|
||||
exposed_but_not_defined.remove(&effect_symbol);
|
||||
|
||||
let hosted_alias = scope.lookup_alias(effect_symbol).unwrap().clone();
|
||||
aliases.insert(effect_symbol, hosted_alias);
|
||||
}
|
||||
|
||||
for (symbol, alias) in output.aliases {
|
||||
// Remove this from exposed_symbols,
|
||||
// so that at the end of the process,
|
||||
@ -397,9 +514,9 @@ fn fix_values_captured_in_closure_pattern(
|
||||
}
|
||||
}
|
||||
Identifier(_)
|
||||
| NumLiteral(_, _, _)
|
||||
| IntLiteral(_, _, _)
|
||||
| FloatLiteral(_, _, _)
|
||||
| NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| Underscore
|
||||
| Shadowed(..)
|
||||
@ -453,9 +570,9 @@ fn fix_values_captured_in_closure_expr(
|
||||
fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
|
||||
}
|
||||
|
||||
Num(_, _, _)
|
||||
| Int(_, _, _, _)
|
||||
| Float(_, _, _, _)
|
||||
Num(..)
|
||||
| Int(..)
|
||||
| Float(..)
|
||||
| Str(_)
|
||||
| Var(_)
|
||||
| EmptyRecord
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::env::Env;
|
||||
use crate::expr::Expr;
|
||||
use crate::expr::{Expr, IntValue};
|
||||
use roc_parse::ast::Base;
|
||||
use roc_problem::can::Problem;
|
||||
use roc_problem::can::RuntimeError::*;
|
||||
@ -7,21 +7,33 @@ use roc_problem::can::{FloatErrorKind, IntErrorKind};
|
||||
use roc_region::all::Region;
|
||||
use roc_types::subs::VarStore;
|
||||
use std::i64;
|
||||
|
||||
// TODO use rust's integer parsing again
|
||||
//
|
||||
// We're waiting for libcore here, see https://github.com/rust-lang/rust/issues/22639
|
||||
// There is a nightly API for exposing the parse error.
|
||||
use std::str;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_expr_from_result(
|
||||
var_store: &mut VarStore,
|
||||
result: Result<(&str, i64), (&str, IntErrorKind)>,
|
||||
result: Result<(&str, ParsedNumResult), (&str, IntErrorKind)>,
|
||||
region: Region,
|
||||
env: &mut Env,
|
||||
) -> Expr {
|
||||
match result {
|
||||
Ok((str, num)) => Expr::Num(var_store.fresh(), (*str).into(), num),
|
||||
Ok((str, ParsedNumResult::UnknownNum(num))) => {
|
||||
Expr::Num(var_store.fresh(), (*str).into(), num, NumericBound::None)
|
||||
}
|
||||
Ok((str, ParsedNumResult::Int(num, bound))) => Expr::Int(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(*str).into(),
|
||||
num,
|
||||
bound,
|
||||
),
|
||||
Ok((str, ParsedNumResult::Float(num, bound))) => Expr::Float(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(*str).into(),
|
||||
num,
|
||||
bound,
|
||||
),
|
||||
Err((raw, error)) => {
|
||||
// (Num *) compiles to Int if it doesn't
|
||||
// get specialized to something else first,
|
||||
@ -38,14 +50,20 @@ pub fn num_expr_from_result(
|
||||
#[inline(always)]
|
||||
pub fn int_expr_from_result(
|
||||
var_store: &mut VarStore,
|
||||
result: Result<(&str, i128), (&str, IntErrorKind)>,
|
||||
result: Result<(&str, IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)>,
|
||||
region: Region,
|
||||
base: Base,
|
||||
env: &mut Env,
|
||||
) -> Expr {
|
||||
// Int stores a variable to generate better error messages
|
||||
match result {
|
||||
Ok((str, int)) => Expr::Int(var_store.fresh(), var_store.fresh(), (*str).into(), int),
|
||||
Ok((str, int, bound)) => Expr::Int(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(*str).into(),
|
||||
int,
|
||||
bound,
|
||||
),
|
||||
Err((raw, error)) => {
|
||||
let runtime_error = InvalidInt(error, base, region, raw.into());
|
||||
|
||||
@ -59,13 +77,19 @@ pub fn int_expr_from_result(
|
||||
#[inline(always)]
|
||||
pub fn float_expr_from_result(
|
||||
var_store: &mut VarStore,
|
||||
result: Result<(&str, f64), (&str, FloatErrorKind)>,
|
||||
result: Result<(&str, f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)>,
|
||||
region: Region,
|
||||
env: &mut Env,
|
||||
) -> Expr {
|
||||
// Float stores a variable to generate better error messages
|
||||
match result {
|
||||
Ok((str, float)) => Expr::Float(var_store.fresh(), var_store.fresh(), (*str).into(), float),
|
||||
Ok((str, float, bound)) => Expr::Float(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(*str).into(),
|
||||
float,
|
||||
bound,
|
||||
),
|
||||
Err((raw, error)) => {
|
||||
let runtime_error = InvalidFloat(error, region, raw.into());
|
||||
|
||||
@ -76,11 +100,32 @@ pub fn float_expr_from_result(
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ParsedNumResult {
|
||||
Int(IntValue, NumericBound<IntWidth>),
|
||||
Float(f64, NumericBound<FloatWidth>),
|
||||
UnknownNum(IntValue),
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn finish_parsing_int(raw: &str) -> Result<i64, (&str, IntErrorKind)> {
|
||||
pub fn finish_parsing_num(raw: &str) -> Result<ParsedNumResult, (&str, IntErrorKind)> {
|
||||
// Ignore underscores.
|
||||
let radix = 10;
|
||||
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e.kind))
|
||||
let (num, bound) =
|
||||
from_str_radix(raw.replace("_", "").as_str(), radix).map_err(|e| (raw, e))?;
|
||||
// Let's try to specialize the number
|
||||
Ok(match bound {
|
||||
NumericBound::None => ParsedNumResult::UnknownNum(num),
|
||||
NumericBound::Exact(NumWidth::Int(iw)) => {
|
||||
ParsedNumResult::Int(num, NumericBound::Exact(iw))
|
||||
}
|
||||
NumericBound::Exact(NumWidth::Float(fw)) => {
|
||||
let num = match num {
|
||||
IntValue::I128(n) => n as f64,
|
||||
IntValue::U128(n) => n as f64,
|
||||
};
|
||||
ParsedNumResult::Float(num, NumericBound::Exact(fw))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -88,7 +133,7 @@ pub fn finish_parsing_base(
|
||||
raw: &str,
|
||||
base: Base,
|
||||
is_negative: bool,
|
||||
) -> Result<i64, (&str, IntErrorKind)> {
|
||||
) -> Result<(IntValue, NumericBound<IntWidth>), (&str, IntErrorKind)> {
|
||||
let radix = match base {
|
||||
Base::Hex => 16,
|
||||
Base::Decimal => 10,
|
||||
@ -98,18 +143,36 @@ pub fn finish_parsing_base(
|
||||
|
||||
// Ignore underscores, insert - when negative to get correct underflow/overflow behavior
|
||||
(if is_negative {
|
||||
from_str_radix::<i64>(format!("-{}", raw.replace("_", "")).as_str(), radix)
|
||||
from_str_radix(format!("-{}", raw.replace("_", "")).as_str(), radix)
|
||||
} else {
|
||||
from_str_radix::<i64>(raw.replace("_", "").as_str(), radix)
|
||||
from_str_radix(raw.replace("_", "").as_str(), radix)
|
||||
})
|
||||
.map_err(|e| (raw, e.kind))
|
||||
.and_then(|(n, bound)| {
|
||||
let bound = match bound {
|
||||
NumericBound::None => NumericBound::None,
|
||||
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(iw),
|
||||
NumericBound::Exact(NumWidth::Float(_)) => return Err(IntErrorKind::FloatSuffix),
|
||||
};
|
||||
Ok((n, bound))
|
||||
})
|
||||
.map_err(|e| (raw, e))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
|
||||
pub fn finish_parsing_float(
|
||||
raw: &str,
|
||||
) -> Result<(f64, NumericBound<FloatWidth>), (&str, FloatErrorKind)> {
|
||||
let (opt_bound, raw_without_suffix) = parse_literal_suffix(raw);
|
||||
|
||||
let bound = match opt_bound {
|
||||
None => NumericBound::None,
|
||||
Some(NumWidth::Float(fw)) => NumericBound::Exact(fw),
|
||||
Some(NumWidth::Int(_)) => return Err((raw, FloatErrorKind::IntSuffix)),
|
||||
};
|
||||
|
||||
// Ignore underscores.
|
||||
match raw.replace("_", "").parse::<f64>() {
|
||||
Ok(float) if float.is_finite() => Ok(float),
|
||||
match raw_without_suffix.replace("_", "").parse::<f64>() {
|
||||
Ok(float) if float.is_finite() => Ok((float, bound)),
|
||||
Ok(float) => {
|
||||
if float.is_sign_positive() {
|
||||
Err((raw, FloatErrorKind::PositiveInfinity))
|
||||
@ -121,6 +184,35 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_literal_suffix(num_str: &str) -> (Option<NumWidth>, &str) {
|
||||
macro_rules! parse_num_suffix {
|
||||
($($suffix:expr, $width:expr)*) => {$(
|
||||
if num_str.ends_with($suffix) {
|
||||
return (Some($width), num_str.get(0..num_str.len() - $suffix.len()).unwrap());
|
||||
}
|
||||
)*}
|
||||
}
|
||||
|
||||
parse_num_suffix! {
|
||||
"u8", NumWidth::Int(IntWidth::U8)
|
||||
"u16", NumWidth::Int(IntWidth::U16)
|
||||
"u32", NumWidth::Int(IntWidth::U32)
|
||||
"u64", NumWidth::Int(IntWidth::U64)
|
||||
"u128", NumWidth::Int(IntWidth::U128)
|
||||
"i8", NumWidth::Int(IntWidth::I8)
|
||||
"i16", NumWidth::Int(IntWidth::I16)
|
||||
"i32", NumWidth::Int(IntWidth::I32)
|
||||
"i64", NumWidth::Int(IntWidth::I64)
|
||||
"i128", NumWidth::Int(IntWidth::I128)
|
||||
"nat", NumWidth::Int(IntWidth::Nat)
|
||||
"dec", NumWidth::Float(FloatWidth::Dec)
|
||||
"f32", NumWidth::Float(FloatWidth::F32)
|
||||
"f64", NumWidth::Float(FloatWidth::F64)
|
||||
}
|
||||
|
||||
(None, num_str)
|
||||
}
|
||||
|
||||
/// Integer parsing code taken from the rust libcore,
|
||||
/// pulled in so we can give custom error messages
|
||||
///
|
||||
@ -129,44 +221,11 @@ pub fn finish_parsing_float(raw: &str) -> Result<f64, (&str, FloatErrorKind)> {
|
||||
/// the LEGAL_DETAILS file in the root directory of this distribution.
|
||||
///
|
||||
/// Thanks to the Rust project and its contributors!
|
||||
trait FromStrRadixHelper: PartialOrd + Copy {
|
||||
fn min_value() -> Self;
|
||||
fn max_value() -> Self;
|
||||
fn from_u32(u: u32) -> Self;
|
||||
fn checked_mul(&self, other: u32) -> Option<Self>;
|
||||
fn checked_sub(&self, other: u32) -> Option<Self>;
|
||||
fn checked_add(&self, other: u32) -> Option<Self>;
|
||||
}
|
||||
|
||||
macro_rules! doit {
|
||||
($($t:ty)*) => ($(impl FromStrRadixHelper for $t {
|
||||
#[inline]
|
||||
fn min_value() -> Self { Self::min_value() }
|
||||
#[inline]
|
||||
fn max_value() -> Self { Self::max_value() }
|
||||
#[inline]
|
||||
fn from_u32(u: u32) -> Self { u as Self }
|
||||
#[inline]
|
||||
fn checked_mul(&self, other: u32) -> Option<Self> {
|
||||
Self::checked_mul(*self, other as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn checked_sub(&self, other: u32) -> Option<Self> {
|
||||
Self::checked_sub(*self, other as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn checked_add(&self, other: u32) -> Option<Self> {
|
||||
Self::checked_add(*self, other as Self)
|
||||
}
|
||||
})*)
|
||||
}
|
||||
// We only need the i64 implementation, but libcore defines
|
||||
// doit! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize }
|
||||
doit! { i64 }
|
||||
|
||||
fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, ParseIntError> {
|
||||
fn from_str_radix(
|
||||
src: &str,
|
||||
radix: u32,
|
||||
) -> Result<(IntValue, NumericBound<NumWidth>), IntErrorKind> {
|
||||
use self::IntErrorKind::*;
|
||||
use self::ParseIntError as PIE;
|
||||
|
||||
assert!(
|
||||
(2..=36).contains(&radix),
|
||||
@ -174,86 +233,260 @@ fn from_str_radix<T: FromStrRadixHelper>(src: &str, radix: u32) -> Result<T, Par
|
||||
radix
|
||||
);
|
||||
|
||||
if src.is_empty() {
|
||||
return Err(PIE { kind: Empty });
|
||||
}
|
||||
let (opt_exact_bound, src) = parse_literal_suffix(src);
|
||||
|
||||
let is_signed_ty = T::from_u32(0) > T::min_value();
|
||||
|
||||
// all valid digits are ascii, so we will just iterate over the utf8 bytes
|
||||
// and cast them to chars. .to_digit() will safely return None for anything
|
||||
// other than a valid ascii digit for the given radix, including the first-byte
|
||||
// of multi-byte sequences
|
||||
let src = src.as_bytes();
|
||||
|
||||
let (is_positive, digits) = match src[0] {
|
||||
b'+' => (true, &src[1..]),
|
||||
b'-' if is_signed_ty => (false, &src[1..]),
|
||||
_ => (true, src),
|
||||
use std::num::IntErrorKind as StdIEK;
|
||||
let result = match i128::from_str_radix(src, radix) {
|
||||
Ok(result) => IntValue::I128(result),
|
||||
Err(pie) => match pie.kind() {
|
||||
StdIEK::Empty => return Err(IntErrorKind::Empty),
|
||||
StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
|
||||
StdIEK::NegOverflow => return Err(IntErrorKind::Underflow),
|
||||
StdIEK::PosOverflow => {
|
||||
// try a u128
|
||||
match u128::from_str_radix(src, radix) {
|
||||
Ok(result) => IntValue::U128(result),
|
||||
Err(pie) => match pie.kind() {
|
||||
StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit),
|
||||
StdIEK::PosOverflow => return Err(IntErrorKind::Overflow),
|
||||
StdIEK::Empty | StdIEK::Zero | StdIEK::NegOverflow => unreachable!(),
|
||||
_ => unreachable!("I thought all possibilities were exhausted, but std::num added a new one")
|
||||
},
|
||||
}
|
||||
}
|
||||
StdIEK::Zero => unreachable!("Parsed a i128"),
|
||||
_ => unreachable!(
|
||||
"I thought all possibilities were exhausted, but std::num added a new one"
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
if digits.is_empty() {
|
||||
return Err(PIE { kind: Empty });
|
||||
}
|
||||
let (lower_bound, is_negative) = match result {
|
||||
IntValue::I128(num) => (lower_bound_of_int(num), num <= 0),
|
||||
IntValue::U128(_) => (IntWidth::U128, false),
|
||||
};
|
||||
|
||||
let mut result = T::from_u32(0);
|
||||
if is_positive {
|
||||
// The number is positive
|
||||
for &c in digits {
|
||||
let x = match (c as char).to_digit(radix) {
|
||||
Some(x) => x,
|
||||
None => return Err(PIE { kind: InvalidDigit }),
|
||||
};
|
||||
result = match result.checked_mul(radix) {
|
||||
Some(result) => result,
|
||||
None => return Err(PIE { kind: Overflow }),
|
||||
};
|
||||
result = match result.checked_add(x) {
|
||||
Some(result) => result,
|
||||
None => return Err(PIE { kind: Overflow }),
|
||||
};
|
||||
match opt_exact_bound {
|
||||
None => {
|
||||
// TODO: use the lower bound
|
||||
Ok((result, NumericBound::None))
|
||||
}
|
||||
Some(bound @ NumWidth::Float(_)) => {
|
||||
// For now, assume floats can represent all integers
|
||||
// TODO: this is somewhat incorrect, revisit
|
||||
Ok((result, NumericBound::Exact(bound)))
|
||||
}
|
||||
Some(NumWidth::Int(exact_width)) => {
|
||||
// We need to check if the exact bound >= lower bound.
|
||||
if exact_width.is_superset(&lower_bound, is_negative) {
|
||||
// Great! Use the exact bound.
|
||||
Ok((result, NumericBound::Exact(NumWidth::Int(exact_width))))
|
||||
} else {
|
||||
// This is something like 200i8; the lower bound is u8, which holds strictly more
|
||||
// ints on the positive side than i8 does. Report an error depending on which side
|
||||
// of the integers we checked.
|
||||
let err = if is_negative {
|
||||
UnderflowsSuffix {
|
||||
suffix_type: exact_width.type_str(),
|
||||
min_value: exact_width.min_value(),
|
||||
}
|
||||
} else {
|
||||
OverflowsSuffix {
|
||||
suffix_type: exact_width.type_str(),
|
||||
max_value: exact_width.max_value(),
|
||||
}
|
||||
};
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_bound_of_int(result: i128) -> IntWidth {
|
||||
use IntWidth::*;
|
||||
if result >= 0 {
|
||||
// Positive
|
||||
let result = result as u128;
|
||||
if result > U64.max_value() {
|
||||
I128
|
||||
} else if result > I64.max_value() {
|
||||
U64
|
||||
} else if result > U32.max_value() {
|
||||
I64
|
||||
} else if result > I32.max_value() {
|
||||
U32
|
||||
} else if result > U16.max_value() {
|
||||
I32
|
||||
} else if result > I16.max_value() {
|
||||
U16
|
||||
} else if result > U8.max_value() {
|
||||
I16
|
||||
} else if result > I8.max_value() {
|
||||
U8
|
||||
} else {
|
||||
I8
|
||||
}
|
||||
} else {
|
||||
// The number is negative
|
||||
for &c in digits {
|
||||
let x = match (c as char).to_digit(radix) {
|
||||
Some(x) => x,
|
||||
None => return Err(PIE { kind: InvalidDigit }),
|
||||
};
|
||||
result = match result.checked_mul(radix) {
|
||||
Some(result) => result,
|
||||
None => return Err(PIE { kind: Underflow }),
|
||||
};
|
||||
result = match result.checked_sub(x) {
|
||||
Some(result) => result,
|
||||
None => return Err(PIE { kind: Underflow }),
|
||||
};
|
||||
// Negative
|
||||
if result < I64.min_value() {
|
||||
I128
|
||||
} else if result < I32.min_value() {
|
||||
I64
|
||||
} else if result < I16.min_value() {
|
||||
I32
|
||||
} else if result < I8.min_value() {
|
||||
I16
|
||||
} else {
|
||||
I8
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// An error which can be returned when parsing an integer.
|
||||
///
|
||||
/// This error is used as the error type for the `from_str_radix()` functions
|
||||
/// on the primitive integer types, such as [`i8::from_str_radix`].
|
||||
///
|
||||
/// # Potential causes
|
||||
///
|
||||
/// Among other causes, `ParseIntError` can be thrown because of leading or trailing whitespace
|
||||
/// in the string e.g., when it is obtained from the standard input.
|
||||
/// Using the [`str.trim()`] method ensures that no whitespace remains before parsing.
|
||||
///
|
||||
/// [`str.trim()`]: ../../std/primitive.str.html#method.trim
|
||||
/// [`i8::from_str_radix`]: ../../std/primitive.i8.html#method.from_str_radix
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParseIntError {
|
||||
kind: IntErrorKind,
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
enum IntSign {
|
||||
Unsigned,
|
||||
Signed,
|
||||
}
|
||||
|
||||
impl ParseIntError {
|
||||
/// Outputs the detailed cause of parsing an integer failing.
|
||||
pub fn kind(&self) -> &IntErrorKind {
|
||||
&self.kind
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum IntWidth {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
U128,
|
||||
I8,
|
||||
I16,
|
||||
I32,
|
||||
I64,
|
||||
I128,
|
||||
Nat,
|
||||
}
|
||||
|
||||
impl IntWidth {
|
||||
/// Returns the `IntSign` and bit width of a variant.
|
||||
fn sign_and_width(&self) -> (IntSign, u32) {
|
||||
use IntSign::*;
|
||||
use IntWidth::*;
|
||||
match self {
|
||||
U8 => (Unsigned, 8),
|
||||
U16 => (Unsigned, 16),
|
||||
U32 => (Unsigned, 32),
|
||||
U64 => (Unsigned, 64),
|
||||
U128 => (Unsigned, 128),
|
||||
I8 => (Signed, 8),
|
||||
I16 => (Signed, 16),
|
||||
I32 => (Signed, 32),
|
||||
I64 => (Signed, 64),
|
||||
I128 => (Signed, 128),
|
||||
// TODO: this is platform specific!
|
||||
Nat => (Unsigned, 64),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_str(&self) -> &'static str {
|
||||
use IntWidth::*;
|
||||
match self {
|
||||
U8 => "U8",
|
||||
U16 => "U16",
|
||||
U32 => "U32",
|
||||
U64 => "U64",
|
||||
U128 => "U128",
|
||||
I8 => "I8",
|
||||
I16 => "I16",
|
||||
I32 => "I32",
|
||||
I64 => "I64",
|
||||
I128 => "I128",
|
||||
Nat => "Nat",
|
||||
}
|
||||
}
|
||||
|
||||
fn max_value(&self) -> u128 {
|
||||
use IntWidth::*;
|
||||
match self {
|
||||
U8 => u8::MAX as u128,
|
||||
U16 => u16::MAX as u128,
|
||||
U32 => u32::MAX as u128,
|
||||
U64 => u64::MAX as u128,
|
||||
U128 => u128::MAX,
|
||||
I8 => i8::MAX as u128,
|
||||
I16 => i16::MAX as u128,
|
||||
I32 => i32::MAX as u128,
|
||||
I64 => i64::MAX as u128,
|
||||
I128 => i128::MAX as u128,
|
||||
// TODO: this is platform specific!
|
||||
Nat => u64::MAX as u128,
|
||||
}
|
||||
}
|
||||
|
||||
fn min_value(&self) -> i128 {
|
||||
use IntWidth::*;
|
||||
match self {
|
||||
U8 | U16 | U32 | U64 | U128 | Nat => 0,
|
||||
I8 => i8::MIN as i128,
|
||||
I16 => i16::MIN as i128,
|
||||
I32 => i32::MIN as i128,
|
||||
I64 => i64::MIN as i128,
|
||||
I128 => i128::MIN,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular
|
||||
/// side of the integers relative to 0.
|
||||
///
|
||||
/// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked.
|
||||
pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool {
|
||||
use IntSign::*;
|
||||
|
||||
if is_negative {
|
||||
match (self.sign_and_width(), lower_bound.sign_and_width()) {
|
||||
((Signed, us), (Signed, lower_bound)) => us >= lower_bound,
|
||||
// Unsigned ints can never represent negative numbers; signed (non-zero width)
|
||||
// ints always can.
|
||||
((Unsigned, _), (Signed, _)) => false,
|
||||
((Signed, _), (Unsigned, _)) => true,
|
||||
// Trivially true; both can only express 0.
|
||||
((Unsigned, _), (Unsigned, _)) => true,
|
||||
}
|
||||
} else {
|
||||
match (self.sign_and_width(), lower_bound.sign_and_width()) {
|
||||
((Signed, us), (Signed, lower_bound))
|
||||
| ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound,
|
||||
|
||||
// Unsigned ints with the same bit width as their unsigned counterparts can always
|
||||
// express 2x more integers on the positive side as unsigned ints.
|
||||
((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound,
|
||||
|
||||
// ...but that means signed int widths can represent less than their unsigned
|
||||
// counterparts, so the below is true iff the bit width is strictly greater. E.g.
|
||||
// i16 is a superset of u8, but i16 is not a superset of u16.
|
||||
((Signed, us), (Unsigned, lower_bound)) => us > lower_bound,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum FloatWidth {
|
||||
Dec,
|
||||
F32,
|
||||
F64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum NumWidth {
|
||||
Int(IntWidth),
|
||||
Float(FloatWidth),
|
||||
}
|
||||
|
||||
/// Describes a bound on the width of a numeric literal.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum NumericBound<W>
|
||||
where
|
||||
W: Copy,
|
||||
{
|
||||
/// There is no bound on the width.
|
||||
None,
|
||||
/// Must have exactly the width `W`.
|
||||
Exact(W),
|
||||
}
|
||||
|
@ -121,8 +121,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
|
||||
/// then replace the BinOp nodes with Apply nodes. Also drop SpaceBefore and SpaceAfter nodes.
|
||||
pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc<Expr<'a>>) -> &'a Loc<Expr<'a>> {
|
||||
match &loc_expr.value {
|
||||
Float(_)
|
||||
| Num(_)
|
||||
Float(..)
|
||||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| Str(_)
|
||||
| AccessorFunction(_)
|
||||
|
@ -1,6 +1,9 @@
|
||||
use crate::env::Env;
|
||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, Output};
|
||||
use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
||||
use crate::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatWidth, IntWidth, NumWidth,
|
||||
NumericBound, ParsedNumResult,
|
||||
};
|
||||
use crate::scope::Scope;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
@ -9,6 +12,7 @@ use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
|
||||
/// A pattern, including possible problems (e.g. shadowing) so that
|
||||
/// codegen can generate a runtime error if this pattern is reached.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -25,9 +29,15 @@ pub enum Pattern {
|
||||
ext_var: Variable,
|
||||
destructs: Vec<Loc<RecordDestruct>>,
|
||||
},
|
||||
IntLiteral(Variable, Box<str>, i64),
|
||||
NumLiteral(Variable, Box<str>, i64),
|
||||
FloatLiteral(Variable, Box<str>, f64),
|
||||
NumLiteral(Variable, Box<str>, IntValue, NumericBound<NumWidth>),
|
||||
IntLiteral(
|
||||
Variable,
|
||||
Variable,
|
||||
Box<str>,
|
||||
IntValue,
|
||||
NumericBound<IntWidth>,
|
||||
),
|
||||
FloatLiteral(Variable, Variable, Box<str>, f64, NumericBound<FloatWidth>),
|
||||
StrLiteral(Box<str>),
|
||||
Underscore,
|
||||
|
||||
@ -85,9 +95,9 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||
}
|
||||
}
|
||||
|
||||
NumLiteral(_, _, _)
|
||||
| IntLiteral(_, _, _)
|
||||
| FloatLiteral(_, _, _)
|
||||
NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
@ -184,13 +194,19 @@ pub fn canonicalize_pattern<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
FloatLiteral(str) => match pattern_type {
|
||||
&FloatLiteral(str) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_float(str) {
|
||||
Err(_error) => {
|
||||
let problem = MalformedPatternProblem::MalformedFloat;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(float) => Pattern::FloatLiteral(var_store.fresh(), (*str).into(), float),
|
||||
Ok((float, bound)) => Pattern::FloatLiteral(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(str).into(),
|
||||
float,
|
||||
bound,
|
||||
),
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
@ -200,32 +216,58 @@ pub fn canonicalize_pattern<'a>(
|
||||
TopLevelDef | DefExpr => bad_underscore(env, region),
|
||||
},
|
||||
|
||||
NumLiteral(str) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_int(str) {
|
||||
&NumLiteral(str) => match pattern_type {
|
||||
WhenBranch => match finish_parsing_num(str) {
|
||||
Err(_error) => {
|
||||
let problem = MalformedPatternProblem::MalformedInt;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(int) => Pattern::NumLiteral(var_store.fresh(), (*str).into(), int),
|
||||
Ok(ParsedNumResult::UnknownNum(int)) => {
|
||||
Pattern::NumLiteral(var_store.fresh(), (str).into(), int, NumericBound::None)
|
||||
}
|
||||
Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(str).into(),
|
||||
int,
|
||||
bound,
|
||||
),
|
||||
Ok(ParsedNumResult::Float(float, bound)) => Pattern::FloatLiteral(
|
||||
var_store.fresh(),
|
||||
var_store.fresh(),
|
||||
(str).into(),
|
||||
float,
|
||||
bound,
|
||||
),
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
NonBase10Literal {
|
||||
&NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => match pattern_type {
|
||||
WhenBranch => match finish_parsing_base(string, *base, *is_negative) {
|
||||
WhenBranch => match finish_parsing_base(string, base, is_negative) {
|
||||
Err(_error) => {
|
||||
let problem = MalformedPatternProblem::MalformedBase(*base);
|
||||
let problem = MalformedPatternProblem::MalformedBase(base);
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok(int) => {
|
||||
let sign_str = if *is_negative { "-" } else { "" };
|
||||
Ok((IntValue::U128(_), _)) if is_negative => {
|
||||
// Can't negate a u128; that doesn't fit in any integer literal type we support.
|
||||
let problem = MalformedPatternProblem::MalformedInt;
|
||||
malformed_pattern(env, problem, region)
|
||||
}
|
||||
Ok((int, bound)) => {
|
||||
let sign_str = if is_negative { "-" } else { "" };
|
||||
let int_str = format!("{}{}", sign_str, int.to_string()).into_boxed_str();
|
||||
let i = if *is_negative { -int } else { int };
|
||||
Pattern::IntLiteral(var_store.fresh(), int_str, i)
|
||||
let i = match int {
|
||||
// Safety: this is fine because I128::MAX = |I128::MIN| - 1
|
||||
IntValue::I128(n) if is_negative => IntValue::I128(-n),
|
||||
IntValue::I128(n) => IntValue::I128(n),
|
||||
IntValue::U128(_) => unreachable!(),
|
||||
};
|
||||
Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound)
|
||||
}
|
||||
},
|
||||
ptype => unsupported_pattern(env, ptype, region),
|
||||
@ -473,9 +515,9 @@ fn add_bindings_from_patterns(
|
||||
answer.push((*symbol, *region));
|
||||
}
|
||||
}
|
||||
NumLiteral(_, _, _)
|
||||
| IntLiteral(_, _, _)
|
||||
| FloatLiteral(_, _, _)
|
||||
NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_)
|
||||
| Underscore
|
||||
| MalformedPattern(_, _)
|
||||
|
@ -15,7 +15,7 @@ mod test_can {
|
||||
use crate::helpers::{can_expr_with, test_home, CanExprOut};
|
||||
use bumpalo::Bump;
|
||||
use roc_can::expr::Expr::{self, *};
|
||||
use roc_can::expr::{ClosureData, Recursive};
|
||||
use roc_can::expr::{ClosureData, IntValue, Recursive};
|
||||
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
|
||||
use roc_region::all::{Position, Region};
|
||||
use std::{f64, i64};
|
||||
@ -32,7 +32,7 @@ mod test_can {
|
||||
let actual_out = can_expr_with(&arena, test_home(), input);
|
||||
|
||||
match actual_out.loc_expr.value {
|
||||
Expr::Float(_, _, _, actual) => {
|
||||
Expr::Float(_, _, _, actual, _) => {
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
actual => {
|
||||
@ -46,8 +46,8 @@ mod test_can {
|
||||
let actual_out = can_expr_with(&arena, test_home(), input);
|
||||
|
||||
match actual_out.loc_expr.value {
|
||||
Expr::Int(_, _, _, actual) => {
|
||||
assert_eq!(expected, actual);
|
||||
Expr::Int(_, _, _, actual, _) => {
|
||||
assert_eq!(IntValue::I128(expected), actual);
|
||||
}
|
||||
actual => {
|
||||
panic!("Expected an Int *, but got: {:?}", actual);
|
||||
@ -55,13 +55,13 @@ mod test_can {
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_can_num(input: &str, expected: i64) {
|
||||
fn assert_can_num(input: &str, expected: i128) {
|
||||
let arena = Bump::new();
|
||||
let actual_out = can_expr_with(&arena, test_home(), input);
|
||||
|
||||
match actual_out.loc_expr.value {
|
||||
Expr::Num(_, _, actual) => {
|
||||
assert_eq!(expected, actual);
|
||||
Expr::Num(_, _, actual, _) => {
|
||||
assert_eq!(IntValue::I128(expected), actual);
|
||||
}
|
||||
actual => {
|
||||
panic!("Expected a Num, but got: {:?}", actual);
|
||||
@ -79,7 +79,7 @@ mod test_can {
|
||||
fn int_too_large() {
|
||||
use roc_parse::ast::Base;
|
||||
|
||||
let string = (i64::MAX as i128 + 1).to_string();
|
||||
let string = "340_282_366_920_938_463_463_374_607_431_768_211_456".to_string();
|
||||
|
||||
assert_can(
|
||||
&string.clone(),
|
||||
@ -96,7 +96,7 @@ mod test_can {
|
||||
fn int_too_small() {
|
||||
use roc_parse::ast::Base;
|
||||
|
||||
let string = (i64::MIN as i128 - 1).to_string();
|
||||
let string = "-170_141_183_460_469_231_731_687_303_715_884_105_729".to_string();
|
||||
|
||||
assert_can(
|
||||
&string.clone(),
|
||||
@ -186,12 +186,12 @@ mod test_can {
|
||||
|
||||
#[test]
|
||||
fn num_max() {
|
||||
assert_can_num(&(i64::MAX.to_string()), i64::MAX);
|
||||
assert_can_num(&(i64::MAX.to_string()), i64::MAX.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_min() {
|
||||
assert_can_num(&(i64::MIN.to_string()), i64::MIN);
|
||||
assert_can_num(&(i64::MIN.to_string()), i64::MIN.into());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1,6 +1,7 @@
|
||||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::LetConstraint;
|
||||
use roc_can::expected::Expected::{self, *};
|
||||
use roc_can::num::{FloatWidth, IntWidth, NumWidth, NumericBound};
|
||||
use roc_collections::all::SendMap;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_module::symbol::Symbol;
|
||||
@ -10,28 +11,49 @@ use roc_types::types::Category;
|
||||
use roc_types::types::Reason;
|
||||
use roc_types::types::Type::{self, *};
|
||||
|
||||
pub fn add_numeric_bound_constr(
|
||||
constrs: &mut Vec<Constraint>,
|
||||
num_type: Type,
|
||||
bound: impl TypedNumericBound,
|
||||
region: Region,
|
||||
category: Category,
|
||||
) {
|
||||
if let Some(typ) = bound.concrete_num_type() {
|
||||
constrs.push(Eq(
|
||||
num_type,
|
||||
Expected::ForReason(Reason::NumericLiteralSuffix, typ, region),
|
||||
category,
|
||||
region,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn int_literal(
|
||||
num_var: Variable,
|
||||
precision_var: Variable,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
bound: NumericBound<IntWidth>,
|
||||
) -> Constraint {
|
||||
let num_type = Variable(num_var);
|
||||
let reason = Reason::IntLiteral;
|
||||
|
||||
exists(
|
||||
vec![num_var],
|
||||
And(vec![
|
||||
Eq(
|
||||
num_type.clone(),
|
||||
ForReason(reason, num_int(Type::Variable(precision_var)), region),
|
||||
Category::Int,
|
||||
region,
|
||||
),
|
||||
Eq(num_type, expected, Category::Int, region),
|
||||
]),
|
||||
)
|
||||
let mut constrs = Vec::with_capacity(3);
|
||||
// Always add the bound first; this improves the resolved type quality in case it's an alias
|
||||
// like "U8".
|
||||
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num);
|
||||
constrs.extend(vec![
|
||||
Eq(
|
||||
num_type.clone(),
|
||||
ForReason(reason, num_int(Type::Variable(precision_var)), region),
|
||||
Category::Int,
|
||||
region,
|
||||
),
|
||||
Eq(num_type, expected, Category::Int, region),
|
||||
]);
|
||||
|
||||
exists(vec![num_var], And(constrs))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -40,22 +62,46 @@ pub fn float_literal(
|
||||
precision_var: Variable,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
bound: NumericBound<FloatWidth>,
|
||||
) -> Constraint {
|
||||
let num_type = Variable(num_var);
|
||||
let reason = Reason::FloatLiteral;
|
||||
|
||||
exists(
|
||||
vec![num_var, precision_var],
|
||||
And(vec![
|
||||
Eq(
|
||||
num_type.clone(),
|
||||
ForReason(reason, num_float(Type::Variable(precision_var)), region),
|
||||
Category::Float,
|
||||
region,
|
||||
),
|
||||
Eq(num_type, expected, Category::Float, region),
|
||||
]),
|
||||
)
|
||||
let mut constrs = Vec::with_capacity(3);
|
||||
add_numeric_bound_constr(
|
||||
&mut constrs,
|
||||
num_type.clone(),
|
||||
bound,
|
||||
region,
|
||||
Category::Float,
|
||||
);
|
||||
constrs.extend(vec![
|
||||
Eq(
|
||||
num_type.clone(),
|
||||
ForReason(reason, num_float(Type::Variable(precision_var)), region),
|
||||
Category::Float,
|
||||
region,
|
||||
),
|
||||
Eq(num_type, expected, Category::Float, region),
|
||||
]);
|
||||
|
||||
exists(vec![num_var, precision_var], And(constrs))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_literal(
|
||||
num_var: Variable,
|
||||
expected: Expected<Type>,
|
||||
region: Region,
|
||||
bound: NumericBound<NumWidth>,
|
||||
) -> Constraint {
|
||||
let num_type = crate::builtins::num_num(Type::Variable(num_var));
|
||||
|
||||
let mut constrs = Vec::with_capacity(3);
|
||||
add_numeric_bound_constr(&mut constrs, num_type.clone(), bound, region, Category::Num);
|
||||
constrs.extend(vec![Eq(num_type, expected, Category::Num, region)]);
|
||||
|
||||
exists(vec![num_var], And(constrs))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -71,7 +117,7 @@ pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn builtin_type(symbol: Symbol, args: Vec<Type>) -> Type {
|
||||
Type::Apply(symbol, args)
|
||||
Type::Apply(symbol, args, Region::zero())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -148,6 +194,57 @@ pub fn num_int(range: Type) -> Type {
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! num_types {
|
||||
// Represent
|
||||
// num_u8 ~ U8 : Num Integer Unsigned8 = @Num (@Integer (@Unsigned8))
|
||||
// int_u8 ~ Integer Unsigned8 = @Integer (@Unsigned8)
|
||||
//
|
||||
// num_f32 ~ F32 : Num FloaingPoint Binary32 = @Num (@FloaingPoint (@Binary32))
|
||||
// float_f32 ~ FloatingPoint Binary32 = @FloatingPoint (@Binary32)
|
||||
// and so on, for all numeric types.
|
||||
($($num_fn:ident, $sub_fn:ident, $num_type:ident, $alias:path, $inner_alias:path, $inner_private_tag:path)*) => {
|
||||
$(
|
||||
#[inline(always)]
|
||||
fn $sub_fn() -> Type {
|
||||
builtin_alias(
|
||||
$inner_alias,
|
||||
vec![],
|
||||
Box::new(Type::TagUnion(
|
||||
vec![(TagName::Private($inner_private_tag), vec![])],
|
||||
Box::new(Type::EmptyTagUnion)
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn $num_fn() -> Type {
|
||||
builtin_alias(
|
||||
$alias,
|
||||
vec![],
|
||||
Box::new($num_type($sub_fn()))
|
||||
)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
num_types! {
|
||||
num_u8, int_u8, num_int, Symbol::NUM_U8, Symbol::NUM_UNSIGNED8, Symbol::NUM_AT_UNSIGNED8
|
||||
num_u16, int_u16, num_int, Symbol::NUM_U16, Symbol::NUM_UNSIGNED16, Symbol::NUM_AT_UNSIGNED16
|
||||
num_u32, int_u32, num_int, Symbol::NUM_U32, Symbol::NUM_UNSIGNED32, Symbol::NUM_AT_UNSIGNED32
|
||||
num_u64, int_u64, num_int, Symbol::NUM_U64, Symbol::NUM_UNSIGNED64, Symbol::NUM_AT_UNSIGNED64
|
||||
num_u128, int_u128, num_int, Symbol::NUM_U128, Symbol::NUM_UNSIGNED128, Symbol::NUM_AT_UNSIGNED128
|
||||
num_i8, int_i8, num_int, Symbol::NUM_I8, Symbol::NUM_SIGNED8, Symbol::NUM_AT_SIGNED8
|
||||
num_i16, int_i16, num_int, Symbol::NUM_I16, Symbol::NUM_SIGNED16, Symbol::NUM_AT_SIGNED16
|
||||
num_i32, int_i32, num_int, Symbol::NUM_I32, Symbol::NUM_SIGNED32, Symbol::NUM_AT_SIGNED32
|
||||
num_i64, int_i64, num_int, Symbol::NUM_I64, Symbol::NUM_SIGNED64, Symbol::NUM_AT_SIGNED64
|
||||
num_i128, int_i128, num_int, Symbol::NUM_I128, Symbol::NUM_SIGNED128, Symbol::NUM_AT_SIGNED128
|
||||
num_nat, int_nat, num_int, Symbol::NUM_NAT, Symbol::NUM_NATURAL, Symbol::NUM_AT_NATURAL
|
||||
num_dec, float_dec, num_float, Symbol::NUM_DEC, Symbol::NUM_DECIMAL, Symbol::NUM_AT_DECIMAL
|
||||
num_f32, float_f32, num_float, Symbol::NUM_F32, Symbol::NUM_BINARY32, Symbol::NUM_AT_BINARY32
|
||||
num_f64, float_f64, num_float, Symbol::NUM_F64, Symbol::NUM_BINARY64, Symbol::NUM_AT_BINARY64
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn num_signed64() -> Type {
|
||||
let alias_content = Type::TagUnion(
|
||||
@ -188,3 +285,55 @@ pub fn num_num(typ: Type) -> Type {
|
||||
Box::new(alias_content),
|
||||
)
|
||||
}
|
||||
|
||||
pub trait TypedNumericBound {
|
||||
/// Get a concrete type for this number, if one exists.
|
||||
/// Returns `None` e.g. if the bound is open, like `Int *`.
|
||||
fn concrete_num_type(&self) -> Option<Type>;
|
||||
}
|
||||
|
||||
impl TypedNumericBound for NumericBound<IntWidth> {
|
||||
fn concrete_num_type(&self) -> Option<Type> {
|
||||
match self {
|
||||
NumericBound::None => None,
|
||||
NumericBound::Exact(w) => Some(match w {
|
||||
IntWidth::U8 => num_u8(),
|
||||
IntWidth::U16 => num_u16(),
|
||||
IntWidth::U32 => num_u32(),
|
||||
IntWidth::U64 => num_u64(),
|
||||
IntWidth::U128 => num_u128(),
|
||||
IntWidth::I8 => num_i8(),
|
||||
IntWidth::I16 => num_i16(),
|
||||
IntWidth::I32 => num_i32(),
|
||||
IntWidth::I64 => num_i64(),
|
||||
IntWidth::I128 => num_i128(),
|
||||
IntWidth::Nat => num_nat(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedNumericBound for NumericBound<FloatWidth> {
|
||||
fn concrete_num_type(&self) -> Option<Type> {
|
||||
match self {
|
||||
NumericBound::None => None,
|
||||
NumericBound::Exact(w) => Some(match w {
|
||||
FloatWidth::Dec => num_dec(),
|
||||
FloatWidth::F32 => num_f32(),
|
||||
FloatWidth::F64 => num_f64(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TypedNumericBound for NumericBound<NumWidth> {
|
||||
fn concrete_num_type(&self) -> Option<Type> {
|
||||
match self {
|
||||
NumericBound::None => None,
|
||||
NumericBound::Exact(NumWidth::Int(iw)) => NumericBound::Exact(*iw).concrete_num_type(),
|
||||
NumericBound::Exact(NumWidth::Float(fw)) => {
|
||||
NumericBound::Exact(*fw).concrete_num_type()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::builtins::{empty_list_type, float_literal, int_literal, list_type, str_type};
|
||||
use crate::builtins::{
|
||||
empty_list_type, float_literal, int_literal, list_type, num_literal, str_type,
|
||||
};
|
||||
use crate::pattern::{constrain_pattern, PatternState};
|
||||
use roc_can::annotation::IntroducedVariables;
|
||||
use roc_can::constraint::Constraint::{self, *};
|
||||
@ -96,17 +98,11 @@ pub fn constrain_expr(
|
||||
expected: Expected<Type>,
|
||||
) -> Constraint {
|
||||
match expr {
|
||||
Int(var, precision, _, _) => int_literal(*var, *precision, expected, region),
|
||||
Num(var, _, _) => exists(
|
||||
vec![*var],
|
||||
Eq(
|
||||
crate::builtins::num_num(Type::Variable(*var)),
|
||||
expected,
|
||||
Category::Num,
|
||||
region,
|
||||
),
|
||||
),
|
||||
Float(var, precision, _, _) => float_literal(*var, *precision, expected, region),
|
||||
&Int(var, precision, _, _, bound) => int_literal(var, precision, expected, region, bound),
|
||||
&Num(var, _, _, bound) => num_literal(var, expected, region, bound),
|
||||
&Float(var, precision, _, _, bound) => {
|
||||
float_literal(var, precision, expected, region, bound)
|
||||
}
|
||||
EmptyRecord => constrain_empty_record(region, expected),
|
||||
Expr::Record { record_var, fields } => {
|
||||
if fields.is_empty() {
|
||||
|
@ -55,9 +55,9 @@ fn headers_from_annotation_help(
|
||||
Underscore
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
| NumLiteral(_, _, _)
|
||||
| IntLiteral(_, _, _)
|
||||
| FloatLiteral(_, _, _)
|
||||
| NumLiteral(..)
|
||||
| IntLiteral(..)
|
||||
| FloatLiteral(..)
|
||||
| StrLiteral(_) => true,
|
||||
|
||||
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
|
||||
@ -178,31 +178,83 @@ pub fn constrain_pattern(
|
||||
);
|
||||
}
|
||||
|
||||
NumLiteral(var, _, _) => {
|
||||
state.vars.push(*var);
|
||||
&NumLiteral(var, _, _, bound) => {
|
||||
state.vars.push(var);
|
||||
|
||||
let num_type = builtins::num_num(Type::Variable(var));
|
||||
|
||||
builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
num_type.clone(),
|
||||
bound,
|
||||
region,
|
||||
Category::Num,
|
||||
);
|
||||
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Num,
|
||||
builtins::num_num(Type::Variable(*var)),
|
||||
num_type,
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
IntLiteral(precision_var, _, _) => {
|
||||
&IntLiteral(num_var, precision_var, _, _, bound) => {
|
||||
// First constraint on the free num var; this improves the resolved type quality in
|
||||
// case the bound is an alias.
|
||||
builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
bound,
|
||||
region,
|
||||
Category::Int,
|
||||
);
|
||||
|
||||
// Link the free num var with the int var and our expectation.
|
||||
let int_type = builtins::num_int(Type::Variable(precision_var));
|
||||
|
||||
state.constraints.push(Constraint::Eq(
|
||||
Type::Variable(num_var),
|
||||
Expected::NoExpectation(int_type),
|
||||
Category::Int,
|
||||
region,
|
||||
));
|
||||
|
||||
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Int,
|
||||
builtins::num_int(Type::Variable(*precision_var)),
|
||||
Type::Variable(num_var),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
||||
FloatLiteral(precision_var, _, _) => {
|
||||
&FloatLiteral(num_var, precision_var, _, _, bound) => {
|
||||
// First constraint on the free num var; this improves the resolved type quality in
|
||||
// case the bound is an alias.
|
||||
builtins::add_numeric_bound_constr(
|
||||
&mut state.constraints,
|
||||
Type::Variable(num_var),
|
||||
bound,
|
||||
region,
|
||||
Category::Float,
|
||||
);
|
||||
|
||||
// Link the free num var with the float var and our expectation.
|
||||
let float_type = builtins::num_float(Type::Variable(precision_var));
|
||||
|
||||
state.constraints.push(Constraint::Eq(
|
||||
Type::Variable(num_var),
|
||||
Expected::NoExpectation(float_type),
|
||||
Category::Float,
|
||||
region,
|
||||
));
|
||||
|
||||
// Also constrain the pattern against the num var, again to reuse aliases if they're present.
|
||||
state.constraints.push(Constraint::Pattern(
|
||||
region,
|
||||
PatternCategory::Float,
|
||||
builtins::num_float(Type::Variable(*precision_var)),
|
||||
Type::Variable(num_var),
|
||||
expected,
|
||||
));
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::{
|
||||
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
|
||||
Buf,
|
||||
};
|
||||
use roc_parse::ast::{AliasHeader, AssignedField, Expr, Tag, TypeAnnotation};
|
||||
use roc_parse::ast::{AliasHeader, AssignedField, Collection, Expr, Tag, TypeAnnotation};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
@ -37,8 +37,8 @@ pub enum Parens {
|
||||
/// newlines are taken into account.
|
||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
||||
pub enum Newlines {
|
||||
Yes,
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
pub trait Formattable {
|
||||
@ -83,6 +83,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Formattable for Collection<'a, T>
|
||||
where
|
||||
T: Formattable,
|
||||
{
|
||||
fn is_multiline(&self) -> bool {
|
||||
// if there are any comments, they must go on their own line
|
||||
// because otherwise they'd comment out the closing delimiter
|
||||
!self.final_comments().is_empty() ||
|
||||
// if any of the items in the collection are multiline,
|
||||
// then the whole collection must be multiline
|
||||
self.items.iter().any(Formattable::is_multiline)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Located formattable value is also formattable
|
||||
impl<T> Formattable for Loc<T>
|
||||
where
|
||||
|
@ -16,11 +16,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||
) where
|
||||
<T as ExtractSpaces<'a>>::Item: Formattable,
|
||||
{
|
||||
buf.indent(indent);
|
||||
let is_multiline =
|
||||
items.iter().any(|item| item.is_multiline()) || !items.final_comments().is_empty();
|
||||
|
||||
if is_multiline {
|
||||
if items.is_multiline() {
|
||||
let braces_indent = indent;
|
||||
let item_indent = braces_indent + INDENT;
|
||||
if newline == Newlines::Yes {
|
||||
@ -52,9 +48,12 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||
item_indent,
|
||||
);
|
||||
buf.newline();
|
||||
buf.indent(braces_indent);
|
||||
buf.push(end);
|
||||
} else {
|
||||
// is_multiline == false
|
||||
// there is no comment to add
|
||||
buf.indent(indent);
|
||||
buf.push(start);
|
||||
let mut iter = items.iter().peekable();
|
||||
while let Some(item) = iter.next() {
|
||||
@ -68,7 +67,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
|
||||
if !items.is_empty() {
|
||||
buf.spaces(1);
|
||||
}
|
||||
|
||||
buf.push(end);
|
||||
}
|
||||
buf.indent(indent);
|
||||
buf.push(end);
|
||||
}
|
||||
|
@ -46,7 +46,9 @@ impl<'a> Formattable for Def<'a> {
|
||||
indent + INDENT,
|
||||
);
|
||||
} else {
|
||||
buf.push_str(" : ");
|
||||
buf.spaces(1);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
Parens::NotNeeded,
|
||||
|
@ -27,8 +27,8 @@ impl<'a> Formattable for Expr<'a> {
|
||||
}
|
||||
|
||||
// These expressions never have newlines
|
||||
Float(_)
|
||||
| Num(_)
|
||||
Float(..)
|
||||
| Num(..)
|
||||
| NonBase10Int { .. }
|
||||
| Access(_, _)
|
||||
| AccessorFunction(_)
|
||||
@ -196,17 +196,25 @@ impl<'a> Formattable for Expr<'a> {
|
||||
buf.push(')');
|
||||
}
|
||||
}
|
||||
Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => {
|
||||
&Num(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
&Float(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
GlobalTag(string) | PrivateTag(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string)
|
||||
}
|
||||
NonBase10Int {
|
||||
&NonBase10Int {
|
||||
base,
|
||||
string,
|
||||
is_negative,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
if *is_negative {
|
||||
if is_negative {
|
||||
buf.push('-');
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ pub mod spaces;
|
||||
|
||||
use bumpalo::{collections::String, Bump};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buf<'a> {
|
||||
text: String<'a>,
|
||||
spaces_to_flush: usize,
|
||||
|
@ -5,7 +5,7 @@ use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{Collection, Module, Spaced};
|
||||
use roc_parse::header::{
|
||||
AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
@ -22,6 +22,9 @@ pub fn fmt_module<'a, 'buf>(buf: &mut Buf<'buf>, module: &'a Module<'a>) {
|
||||
Module::Platform { header } => {
|
||||
fmt_platform_header(buf, header);
|
||||
}
|
||||
Module::Hosted { header } => {
|
||||
fmt_hosted_header(buf, header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +53,45 @@ pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a Interface
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
|
||||
buf.indent(0);
|
||||
buf.push_str("hosted");
|
||||
|
||||
// module name
|
||||
fmt_default_spaces(buf, header.after_hosted_keyword, indent);
|
||||
buf.push_str(header.name.value.as_str());
|
||||
|
||||
// exposes
|
||||
fmt_default_spaces(buf, header.before_exposes, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("exposes");
|
||||
fmt_default_spaces(buf, header.after_exposes, indent);
|
||||
fmt_exposes(buf, header.exposes, indent);
|
||||
|
||||
// imports
|
||||
fmt_default_spaces(buf, header.before_imports, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("imports");
|
||||
fmt_default_spaces(buf, header.after_imports, indent);
|
||||
fmt_imports(buf, header.imports, indent);
|
||||
|
||||
// generates
|
||||
fmt_default_spaces(buf, header.before_generates, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("generates");
|
||||
fmt_default_spaces(buf, header.after_generates, indent);
|
||||
buf.push_str(header.generates.into());
|
||||
|
||||
// with
|
||||
fmt_default_spaces(buf, header.before_with, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("with");
|
||||
fmt_default_spaces(buf, header.after_with, indent);
|
||||
fmt_exposes(buf, header.generates_with, indent);
|
||||
}
|
||||
|
||||
pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) {
|
||||
let indent = INDENT;
|
||||
buf.indent(0);
|
||||
@ -128,8 +170,6 @@ pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHe
|
||||
buf.push_str("provides");
|
||||
fmt_default_spaces(buf, header.after_provides, indent);
|
||||
fmt_provides(buf, header.provides, None, indent);
|
||||
|
||||
fmt_effects(buf, &header.effects, indent);
|
||||
}
|
||||
|
||||
fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) {
|
||||
@ -141,22 +181,6 @@ fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>,
|
||||
buf.push_str(" }");
|
||||
}
|
||||
|
||||
fn fmt_effects<'a, 'buf>(buf: &mut Buf<'buf>, effects: &Effects<'a>, indent: u16) {
|
||||
fmt_default_spaces(buf, effects.spaces_before_effects_keyword, indent);
|
||||
buf.indent(indent);
|
||||
buf.push_str("effects");
|
||||
fmt_default_spaces(buf, effects.spaces_after_effects_keyword, indent);
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str(effects.effect_shortname);
|
||||
buf.push('.');
|
||||
buf.push_str(effects.effect_type_name);
|
||||
|
||||
fmt_default_spaces(buf, effects.spaces_after_type_name, indent);
|
||||
|
||||
fmt_collection(buf, indent, '{', '}', effects.entries, Newlines::No)
|
||||
}
|
||||
|
||||
impl<'a> Formattable for TypedIdent<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
false
|
||||
@ -180,8 +204,14 @@ fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16)
|
||||
|
||||
impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
// TODO
|
||||
false
|
||||
use Spaced::*;
|
||||
|
||||
match self {
|
||||
Item(formattable) => formattable.is_multiline(),
|
||||
SpaceBefore(formattable, spaces) | SpaceAfter(formattable, spaces) => {
|
||||
!spaces.is_empty() || formattable.is_multiline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
@ -212,7 +242,7 @@ fn fmt_imports<'a, 'buf>(
|
||||
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
|
||||
fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No)
|
||||
}
|
||||
|
||||
fn fmt_provides<'a, 'buf>(
|
||||
@ -222,9 +252,9 @@ fn fmt_provides<'a, 'buf>(
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, '[', ']', loc_exposed_names, Newlines::No);
|
||||
if let Some(loc_provided_types) = loc_provided_types {
|
||||
if let Some(loc_provided) = loc_provided_types {
|
||||
fmt_default_spaces(buf, &[], indent);
|
||||
fmt_collection(buf, indent, '{', '}', loc_provided_types, Newlines::No);
|
||||
fmt_collection(buf, indent + INDENT, '{', '}', loc_provided, Newlines::No);
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,7 +272,7 @@ fn fmt_exposes<'buf, N: Formattable + Copy>(
|
||||
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_collection(buf, indent, '[', ']', loc_entries, Newlines::No)
|
||||
fmt_collection(buf, indent + INDENT, '[', ']', loc_entries, Newlines::No)
|
||||
}
|
||||
|
||||
pub trait FormatName {
|
||||
@ -276,7 +306,8 @@ impl<'a> Formattable for ExposedName<'a> {
|
||||
false
|
||||
}
|
||||
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
|
||||
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
|
||||
buf.indent(indent);
|
||||
buf.push_str(self.as_str());
|
||||
}
|
||||
}
|
||||
@ -324,6 +355,8 @@ fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, i
|
||||
fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) {
|
||||
use roc_parse::header::ImportsEntry::*;
|
||||
|
||||
buf.indent(indent);
|
||||
|
||||
match entry {
|
||||
Module(module, loc_exposes_entries) => {
|
||||
buf.push_str(module.as_str());
|
||||
|
@ -31,9 +31,9 @@ impl<'a> Formattable for Pattern<'a> {
|
||||
| Pattern::GlobalTag(_)
|
||||
| Pattern::PrivateTag(_)
|
||||
| Pattern::Apply(_, _)
|
||||
| Pattern::NumLiteral(_)
|
||||
| Pattern::NumLiteral(..)
|
||||
| Pattern::NonBase10Literal { .. }
|
||||
| Pattern::FloatLiteral(_)
|
||||
| Pattern::FloatLiteral(..)
|
||||
| Pattern::StrLiteral(_)
|
||||
| Pattern::Underscore(_)
|
||||
| Pattern::Malformed(_)
|
||||
@ -116,17 +116,17 @@ impl<'a> Formattable for Pattern<'a> {
|
||||
loc_pattern.format(buf, indent);
|
||||
}
|
||||
|
||||
NumLiteral(string) => {
|
||||
&NumLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
NonBase10Literal {
|
||||
&NonBase10Literal {
|
||||
base,
|
||||
string,
|
||||
is_negative,
|
||||
} => {
|
||||
buf.indent(indent);
|
||||
if *is_negative {
|
||||
if is_negative {
|
||||
buf.push('-');
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ impl<'a> Formattable for Pattern<'a> {
|
||||
|
||||
buf.push_str(string);
|
||||
}
|
||||
FloatLiteral(string) => {
|
||||
&FloatLiteral(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
|
@ -2640,6 +2640,25 @@ mod test_fmt {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_interface() {
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
interface Foo
|
||||
exposes
|
||||
[
|
||||
Stuff,
|
||||
Things,
|
||||
somethingElse,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Blah,
|
||||
Baz.{ stuff, things },
|
||||
]"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_app() {
|
||||
module_formats_same(indoc!(
|
||||
@ -2656,16 +2675,43 @@ mod test_fmt {
|
||||
exposes [] \
|
||||
packages {} \
|
||||
imports [ Task.{ Task } ] \
|
||||
provides [ mainForHost ] \
|
||||
effects fx.Effect \
|
||||
{ \
|
||||
putLine : Str -> Effect {}, \
|
||||
putInt : I64 -> Effect {}, \
|
||||
getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool } \
|
||||
}",
|
||||
provides [ mainForHost ]",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_hosted() {
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
hosted Foo exposes [] imports [] generates Bar with []"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_hosted() {
|
||||
module_formats_same(indoc!(
|
||||
r#"
|
||||
hosted Foo
|
||||
exposes
|
||||
[
|
||||
Stuff,
|
||||
Things,
|
||||
somethingElse,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Blah,
|
||||
Baz.{ stuff, things },
|
||||
]
|
||||
generates Bar with
|
||||
[
|
||||
map,
|
||||
after,
|
||||
loop,
|
||||
]"#
|
||||
));
|
||||
}
|
||||
|
||||
/// Annotations and aliases
|
||||
|
||||
#[test]
|
||||
|
@ -771,11 +771,13 @@ pub fn build_exp_literal<'a, 'ctx, 'env>(
|
||||
env.context.bool_type().const_int(*int as u64, false).into()
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(int_width)) => {
|
||||
int_with_precision(env, *int as i128, *int_width).into()
|
||||
int_with_precision(env, *int, *int_width).into()
|
||||
}
|
||||
_ => panic!("Invalid layout for int literal = {:?}", layout),
|
||||
},
|
||||
|
||||
U128(int) => const_u128(env, *int).into(),
|
||||
|
||||
Float(float) => match layout {
|
||||
Layout::Builtin(Builtin::Float(float_width)) => {
|
||||
float_with_precision(env, *float, *float_width)
|
||||
@ -3073,6 +3075,19 @@ fn const_i128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: i128) -> IntValu
|
||||
.const_int_arbitrary_precision(&[a, b])
|
||||
}
|
||||
|
||||
fn const_u128<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, value: u128) -> IntValue<'ctx> {
|
||||
// truncate the lower 64 bits
|
||||
let value = value as u128;
|
||||
let a = value as u64;
|
||||
|
||||
// get the upper 64 bits
|
||||
let b = (value >> 64) as u64;
|
||||
|
||||
env.context
|
||||
.i128_type()
|
||||
.const_int_arbitrary_precision(&[a, b])
|
||||
}
|
||||
|
||||
fn build_switch_ir<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
@ -5284,11 +5299,6 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix me! I should be different in tests vs. user code!
|
||||
fn expect_failed() {
|
||||
panic!("An expectation failed!");
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn run_low_level<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
@ -5299,7 +5309,6 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
op: LowLevel,
|
||||
args: &[Symbol],
|
||||
update_mode: UpdateMode,
|
||||
// expect_failed: *const (),
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use LowLevel::*;
|
||||
|
||||
@ -6063,21 +6072,28 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||
|
||||
match env.target_info.ptr_width() {
|
||||
roc_target::PtrWidth::Bytes8 => {
|
||||
let fn_ptr_type = context
|
||||
.void_type()
|
||||
.fn_type(&[], false)
|
||||
.ptr_type(AddressSpace::Generic);
|
||||
let fn_addr = env
|
||||
.ptr_int()
|
||||
.const_int(expect_failed as *const () as u64, false);
|
||||
let func: PointerValue<'ctx> = bd.build_int_to_ptr(
|
||||
fn_addr,
|
||||
fn_ptr_type,
|
||||
"cast_expect_failed_addr_to_ptr",
|
||||
);
|
||||
let func = env
|
||||
.module
|
||||
.get_function(bitcode::UTILS_EXPECT_FAILED)
|
||||
.unwrap();
|
||||
// TODO get the actual line info instead of
|
||||
// hardcoding as zero!
|
||||
let callable = CallableValue::try_from(func).unwrap();
|
||||
let start_line = context.i32_type().const_int(0, false);
|
||||
let end_line = context.i32_type().const_int(0, false);
|
||||
let start_col = context.i16_type().const_int(0, false);
|
||||
let end_col = context.i16_type().const_int(0, false);
|
||||
|
||||
bd.build_call(callable, &[], "call_expect_failed");
|
||||
bd.build_call(
|
||||
callable,
|
||||
&[
|
||||
start_line.into(),
|
||||
end_line.into(),
|
||||
start_col.into(),
|
||||
end_col.into(),
|
||||
],
|
||||
"call_expect_failed",
|
||||
);
|
||||
|
||||
bd.build_unconditional_branch(then_block);
|
||||
}
|
||||
|
@ -42,6 +42,43 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
|
||||
}
|
||||
}
|
||||
|
||||
// roc_memcpy
|
||||
{
|
||||
// The type of this function (but not the implementation) should have
|
||||
// already been defined by the builtins, which rely on it.
|
||||
let fn_val = module.get_function("roc_memcpy").unwrap();
|
||||
let mut params = fn_val.get_param_iter();
|
||||
let dest_arg = params.next().unwrap();
|
||||
let dest_alignment = 1;
|
||||
let src_arg = params.next().unwrap();
|
||||
let src_alignment = 1;
|
||||
let bytes_arg = params.next().unwrap();
|
||||
|
||||
debug_assert!(params.next().is_none());
|
||||
|
||||
// Add a basic block for the entry point
|
||||
let entry = ctx.append_basic_block(fn_val, "entry");
|
||||
|
||||
builder.position_at_end(entry);
|
||||
|
||||
// Call libc memcpy()
|
||||
let _retval = builder
|
||||
.build_memcpy(
|
||||
dest_arg.into_pointer_value(),
|
||||
dest_alignment,
|
||||
src_arg.into_pointer_value(),
|
||||
src_alignment,
|
||||
bytes_arg.into_int_value(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
builder.build_return(None);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
crate::llvm::build::verify_fn(fn_val);
|
||||
}
|
||||
}
|
||||
|
||||
// roc_realloc
|
||||
{
|
||||
let libc_realloc_val = {
|
||||
|
@ -38,10 +38,23 @@ macro_rules! run_jit_function {
|
||||
}};
|
||||
|
||||
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr) => {{
|
||||
run_jit_function!($lib, $main_fn_name, $ty, $transform, $errors, &[])
|
||||
}};
|
||||
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr, $errors:expr, $expect_failures:expr) => {{
|
||||
use inkwell::context::Context;
|
||||
use roc_builtins::bitcode;
|
||||
use roc_gen_llvm::run_roc::RocCallResult;
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C)]
|
||||
struct Failure {
|
||||
start_line: u32,
|
||||
end_line: u32,
|
||||
start_col: u16,
|
||||
end_col: u16,
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let main: libloading::Symbol<unsafe extern "C" fn(*mut RocCallResult<$ty>) -> ()> =
|
||||
$lib.get($main_fn_name.as_bytes())
|
||||
@ -49,11 +62,50 @@ macro_rules! run_jit_function {
|
||||
.ok_or(format!("Unable to JIT compile `{}`", $main_fn_name))
|
||||
.expect("errored");
|
||||
|
||||
let mut result = MaybeUninit::uninit();
|
||||
#[repr(C)]
|
||||
struct Failures {
|
||||
failures: *const Failure,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
main(result.as_mut_ptr());
|
||||
impl Drop for Failures {
|
||||
fn drop(&mut self) {
|
||||
use std::alloc::{dealloc, Layout};
|
||||
use std::mem;
|
||||
|
||||
match result.assume_init().into() {
|
||||
unsafe {
|
||||
let layout = Layout::from_size_align_unchecked(
|
||||
mem::size_of::<Failure>(),
|
||||
mem::align_of::<Failure>(),
|
||||
);
|
||||
|
||||
dealloc(self.failures as *mut u8, layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let get_expect_failures: libloading::Symbol<unsafe extern "C" fn() -> Failures> = $lib
|
||||
.get(bitcode::UTILS_GET_EXPECT_FAILURES.as_bytes())
|
||||
.ok()
|
||||
.ok_or(format!(
|
||||
"Unable to JIT compile `{}`",
|
||||
bitcode::UTILS_GET_EXPECT_FAILURES
|
||||
))
|
||||
.expect("errored");
|
||||
let mut main_result = MaybeUninit::uninit();
|
||||
|
||||
main(main_result.as_mut_ptr());
|
||||
let failures = get_expect_failures();
|
||||
|
||||
if failures.count > 0 {
|
||||
// TODO tell the user about the failures!
|
||||
let failures =
|
||||
unsafe { core::slice::from_raw_parts(failures.failures, failures.count) };
|
||||
|
||||
panic!("Failed with {} failures. Failures: ", failures.len());
|
||||
}
|
||||
|
||||
match main_result.assume_init().into() {
|
||||
Ok(success) => {
|
||||
// only if there are no exceptions thrown, check for errors
|
||||
assert!($errors.is_empty(), "Encountered errors:\n{}", $errors);
|
||||
|
@ -13,7 +13,7 @@ use roc_constrain::module::{
|
||||
constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
|
||||
};
|
||||
use roc_constrain::module::{constrain_module, ExposedModuleTypes, SubsByModule};
|
||||
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName, TagName};
|
||||
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
|
||||
use roc_module::symbol::{
|
||||
IdentIds, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds, PackageQualified,
|
||||
Symbol,
|
||||
@ -23,9 +23,9 @@ use roc_mono::ir::{
|
||||
UpdateModeIds,
|
||||
};
|
||||
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
|
||||
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
|
||||
use roc_parse::header::PackageName;
|
||||
use roc_parse::ast::{self, ExtractSpaces, Spaced, StrLiteral};
|
||||
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
|
||||
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_parse::module::module_defs;
|
||||
use roc_parse::parser::{FileError, Parser, SyntaxError};
|
||||
@ -35,7 +35,7 @@ use roc_solve::solve;
|
||||
use roc_target::TargetInfo;
|
||||
use roc_types::solved_types::Solved;
|
||||
use roc_types::subs::{Subs, VarStore, Variable};
|
||||
use roc_types::types::{Alias, Type};
|
||||
use roc_types::types::Alias;
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io;
|
||||
@ -165,37 +165,6 @@ impl<'a> Dependencies<'a> {
|
||||
output
|
||||
}
|
||||
|
||||
pub fn add_effect_module(
|
||||
&mut self,
|
||||
module_id: ModuleId,
|
||||
dependencies: &MutSet<ModuleId>,
|
||||
goal_phase: Phase,
|
||||
) -> MutSet<(ModuleId, Phase)> {
|
||||
// add dependencies for self
|
||||
// phase i + 1 of a file always depends on phase i being completed
|
||||
{
|
||||
let mut i = 2;
|
||||
|
||||
// platform modules should only start at CanonicalizeAndConstrain
|
||||
debug_assert!(PHASES[i] == Phase::CanonicalizeAndConstrain);
|
||||
while PHASES[i] < goal_phase {
|
||||
self.add_dependency_help(module_id, module_id, PHASES[i + 1], PHASES[i]);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.add_to_status(module_id, goal_phase);
|
||||
|
||||
let mut output = MutSet::default();
|
||||
|
||||
// all the dependencies can be loaded
|
||||
for dep in dependencies {
|
||||
output.insert((*dep, Phase::LoadHeader));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn add_to_status(&mut self, module_id: ModuleId, goal_phase: Phase) {
|
||||
for phase in PHASES.iter() {
|
||||
if *phase > goal_phase {
|
||||
@ -674,24 +643,7 @@ struct ModuleHeader<'a> {
|
||||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
parse_state: roc_parse::state::State<'a>,
|
||||
module_timing: ModuleTiming,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum HeaderFor<'a> {
|
||||
App {
|
||||
to_platform: To<'a>,
|
||||
},
|
||||
PkgConfig {
|
||||
/// usually `pf`
|
||||
config_shorthand: &'a str,
|
||||
/// the type scheme of the main function (required by the platform)
|
||||
/// (currently unused)
|
||||
#[allow(dead_code)]
|
||||
platform_main_type: TypedIdent<'a>,
|
||||
/// provided symbol to host (commonly `mainForHost`)
|
||||
main_for_host: Symbol,
|
||||
},
|
||||
Interface,
|
||||
header_for: HeaderFor<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -774,7 +726,6 @@ impl<'a> MonomorphizedModule<'a> {
|
||||
#[derive(Debug)]
|
||||
struct ParsedModule<'a> {
|
||||
module_id: ModuleId,
|
||||
module_name: ModuleNameEnum<'a>,
|
||||
module_path: PathBuf,
|
||||
src: &'a str,
|
||||
module_timing: ModuleTiming,
|
||||
@ -783,6 +734,8 @@ struct ParsedModule<'a> {
|
||||
exposed_ident_ids: IdentIds,
|
||||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
parsed_defs: &'a [Loc<roc_parse::ast::Def<'a>>],
|
||||
module_name: ModuleNameEnum<'a>,
|
||||
header_for: HeaderFor<'a>,
|
||||
}
|
||||
|
||||
/// A message sent out _from_ a worker thread,
|
||||
@ -790,19 +743,13 @@ struct ParsedModule<'a> {
|
||||
#[derive(Debug)]
|
||||
enum Msg<'a> {
|
||||
Many(Vec<Msg<'a>>),
|
||||
Header(ModuleHeader<'a>, HeaderFor<'a>),
|
||||
Header(ModuleHeader<'a>),
|
||||
Parsed(ParsedModule<'a>),
|
||||
CanonicalizedAndConstrained {
|
||||
constrained_module: ConstrainedModule,
|
||||
canonicalization_problems: Vec<roc_problem::can::Problem>,
|
||||
module_docs: Option<ModuleDocumentation>,
|
||||
},
|
||||
MadeEffectModule {
|
||||
type_shortname: &'a str,
|
||||
constrained_module: ConstrainedModule,
|
||||
canonicalization_problems: Vec<roc_problem::can::Problem>,
|
||||
module_docs: ModuleDocumentation,
|
||||
},
|
||||
SolvedTypes {
|
||||
module_id: ModuleId,
|
||||
ident_ids: IdentIds,
|
||||
@ -861,6 +808,7 @@ enum PlatformPath<'a> {
|
||||
NotSpecified,
|
||||
Valid(To<'a>),
|
||||
RootIsInterface,
|
||||
RootIsHosted,
|
||||
RootIsPkgConfig,
|
||||
}
|
||||
|
||||
@ -1217,6 +1165,10 @@ impl<'a> LoadStart<'a> {
|
||||
let buf = to_parse_problem_report(problem, module_ids, root_exposed_ident_ids);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(LoadingProblem::FileProblem { filename, error }) => {
|
||||
let buf = to_file_problem_report(&filename, error);
|
||||
return Err(LoadingProblem::FormattedReport(buf));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
};
|
||||
@ -1367,7 +1319,7 @@ where
|
||||
|
||||
// We need to allocate worker *queues* on the main thread and then move them
|
||||
// into the worker threads, because those workers' stealers need to be
|
||||
// shared bet,een all threads, and this coordination work is much easier
|
||||
// shared between all threads, and this coordination work is much easier
|
||||
// on the main thread.
|
||||
let mut worker_queues = bumpalo::collections::Vec::with_capacity_in(num_workers, arena);
|
||||
let mut stealers = bumpalo::collections::Vec::with_capacity_in(num_workers, arena);
|
||||
@ -1691,7 +1643,7 @@ fn update<'a>(
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
Header(header, header_extra) => {
|
||||
Header(header) => {
|
||||
use HeaderFor::*;
|
||||
|
||||
log!("loaded header for {:?}", header.module_id);
|
||||
@ -1708,13 +1660,13 @@ fn update<'a>(
|
||||
|
||||
if let PkgConfig {
|
||||
config_shorthand, ..
|
||||
} = header_extra
|
||||
} = header.header_for
|
||||
{
|
||||
work.extend(state.dependencies.notify_package(config_shorthand));
|
||||
}
|
||||
}
|
||||
|
||||
match header_extra {
|
||||
match header.header_for {
|
||||
App { to_platform } => {
|
||||
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
|
||||
state.platform_path = PlatformPath::Valid(to_platform);
|
||||
@ -1738,6 +1690,12 @@ fn update<'a>(
|
||||
state.platform_path = PlatformPath::RootIsInterface;
|
||||
}
|
||||
}
|
||||
Hosted { .. } => {
|
||||
if header.is_root_module {
|
||||
debug_assert!(matches!(state.platform_path, PlatformPath::NotSpecified));
|
||||
state.platform_path = PlatformPath::RootIsHosted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// store an ID to name mapping, so we know the file to read when fetching dependencies' headers
|
||||
@ -1745,7 +1703,7 @@ fn update<'a>(
|
||||
state.module_cache.module_names.insert(*id, name.clone());
|
||||
}
|
||||
|
||||
// This was a dependency. Write it down and keep processing messaages.
|
||||
// This was a dependency. Write it down and keep processing messages.
|
||||
let mut exposed_symbols: MutSet<Symbol> =
|
||||
HashSet::with_capacity_and_hasher(header.exposes.len(), default_hasher());
|
||||
|
||||
@ -1801,7 +1759,6 @@ fn update<'a>(
|
||||
//
|
||||
// e.g. for `app "blah"` we should generate an output file named "blah"
|
||||
match &parsed.module_name {
|
||||
ModuleNameEnum::PkgConfig => {}
|
||||
ModuleNameEnum::App(output_str) => match output_str {
|
||||
StrLiteral::PlainLine(path) => {
|
||||
state.output_path = Some(path);
|
||||
@ -1810,7 +1767,9 @@ fn update<'a>(
|
||||
todo!("TODO gracefully handle a malformed string literal after `app` keyword.");
|
||||
}
|
||||
},
|
||||
ModuleNameEnum::Interface(_) => {}
|
||||
ModuleNameEnum::PkgConfig
|
||||
| ModuleNameEnum::Interface(_)
|
||||
| ModuleNameEnum::Hosted(_) => {}
|
||||
}
|
||||
|
||||
let module_id = parsed.module_id;
|
||||
@ -1858,57 +1817,6 @@ fn update<'a>(
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
MadeEffectModule {
|
||||
constrained_module,
|
||||
canonicalization_problems,
|
||||
module_docs,
|
||||
type_shortname,
|
||||
} => {
|
||||
let module_id = constrained_module.module.module_id;
|
||||
|
||||
log!("made effect module for {:?}", module_id);
|
||||
state
|
||||
.module_cache
|
||||
.can_problems
|
||||
.insert(module_id, canonicalization_problems);
|
||||
|
||||
state
|
||||
.module_cache
|
||||
.documentation
|
||||
.insert(module_id, module_docs);
|
||||
|
||||
state
|
||||
.module_cache
|
||||
.aliases
|
||||
.insert(module_id, constrained_module.module.aliases.clone());
|
||||
|
||||
state
|
||||
.module_cache
|
||||
.constrained
|
||||
.insert(module_id, constrained_module);
|
||||
|
||||
let mut work = state.dependencies.add_effect_module(
|
||||
module_id,
|
||||
&MutSet::default(),
|
||||
state.goal_phase,
|
||||
);
|
||||
|
||||
work.extend(state.dependencies.notify_package(type_shortname));
|
||||
|
||||
work.extend(state.dependencies.notify(module_id, Phase::LoadHeader));
|
||||
|
||||
work.extend(state.dependencies.notify(module_id, Phase::Parse));
|
||||
|
||||
work.extend(
|
||||
state
|
||||
.dependencies
|
||||
.notify(module_id, Phase::CanonicalizeAndConstrain),
|
||||
);
|
||||
|
||||
start_tasks(arena, &mut state, work, injector, worker_listeners)?;
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
SolvedTypes {
|
||||
module_id,
|
||||
ident_ids,
|
||||
@ -2361,14 +2269,10 @@ fn load_pkg_config<'a>(
|
||||
|
||||
// Insert the first entries for this module's timings
|
||||
let mut pkg_module_timing = ModuleTiming::new(module_start_time);
|
||||
let mut effect_module_timing = ModuleTiming::new(module_start_time);
|
||||
|
||||
pkg_module_timing.read_roc_file = file_io_duration;
|
||||
pkg_module_timing.parse_header = parse_header_duration;
|
||||
|
||||
effect_module_timing.read_roc_file = file_io_duration;
|
||||
effect_module_timing.parse_header = parse_header_duration;
|
||||
|
||||
match parsed {
|
||||
Ok((ast::Module::Interface { header }, _parse_state)) => {
|
||||
Err(LoadingProblem::UnexpectedHeader(format!(
|
||||
@ -2391,23 +2295,19 @@ fn load_pkg_config<'a>(
|
||||
filename,
|
||||
parser_state,
|
||||
module_ids.clone(),
|
||||
ident_ids_by_module.clone(),
|
||||
ident_ids_by_module,
|
||||
&header,
|
||||
pkg_module_timing,
|
||||
)
|
||||
.1;
|
||||
|
||||
let effects_module_msg = fabricate_effects_module(
|
||||
arena,
|
||||
header.effects.effect_shortname,
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
header,
|
||||
effect_module_timing,
|
||||
)
|
||||
.1;
|
||||
|
||||
Ok(Msg::Many(vec![effects_module_msg, pkg_config_module_msg]))
|
||||
Ok(pkg_config_module_msg)
|
||||
}
|
||||
Ok((ast::Module::Hosted { header }, _parse_state)) => {
|
||||
Err(LoadingProblem::UnexpectedHeader(format!(
|
||||
"expected platform/package module, got Hosted module with header\n{:?}",
|
||||
header
|
||||
)))
|
||||
}
|
||||
Err(fail) => Err(LoadingProblem::ParsingFailed(
|
||||
fail.map_problem(SyntaxError::Header)
|
||||
@ -2540,7 +2440,33 @@ fn parse_header<'a>(
|
||||
packages: &[],
|
||||
exposes: unspace(arena, header.exposes.items),
|
||||
imports: unspace(arena, header.imports.items),
|
||||
to_platform: None,
|
||||
extra: HeaderFor::Interface,
|
||||
};
|
||||
|
||||
Ok(send_header(
|
||||
info,
|
||||
parse_state,
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
module_timing,
|
||||
))
|
||||
}
|
||||
Ok((ast::Module::Hosted { header }, parse_state)) => {
|
||||
let info = HeaderInfo {
|
||||
loc_name: Loc {
|
||||
region: header.name.region,
|
||||
value: ModuleNameEnum::Hosted(header.name.value),
|
||||
},
|
||||
filename,
|
||||
is_root_module,
|
||||
opt_shorthand,
|
||||
packages: &[],
|
||||
exposes: unspace(arena, header.exposes.items),
|
||||
imports: unspace(arena, header.imports.items),
|
||||
extra: HeaderFor::Hosted {
|
||||
generates: header.generates,
|
||||
generates_with: unspace(arena, header.generates_with.items),
|
||||
},
|
||||
};
|
||||
|
||||
Ok(send_header(
|
||||
@ -2582,7 +2508,9 @@ fn parse_header<'a>(
|
||||
packages,
|
||||
exposes,
|
||||
imports: unspace(arena, header.imports.items),
|
||||
to_platform: Some(header.to.value),
|
||||
extra: HeaderFor::App {
|
||||
to_platform: header.to.value,
|
||||
},
|
||||
};
|
||||
|
||||
let (module_id, app_module_header_msg) = send_header(
|
||||
@ -2638,7 +2566,10 @@ fn parse_header<'a>(
|
||||
Msg::Many(vec![app_module_header_msg, load_pkg_config_msg]),
|
||||
))
|
||||
} else {
|
||||
Ok((module_id, app_module_header_msg))
|
||||
Err(LoadingProblem::FileProblem {
|
||||
filename: pkg_config_roc,
|
||||
error: io::ErrorKind::NotFound,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
panic!("could not find base")
|
||||
@ -2647,14 +2578,13 @@ fn parse_header<'a>(
|
||||
To::NewPackage(_package_name) => Ok((module_id, app_module_header_msg)),
|
||||
}
|
||||
}
|
||||
Ok((ast::Module::Platform { header }, _parse_state)) => Ok(fabricate_effects_module(
|
||||
arena,
|
||||
"",
|
||||
module_ids,
|
||||
ident_ids_by_module,
|
||||
header,
|
||||
module_timing,
|
||||
)),
|
||||
Ok((ast::Module::Platform { header }, _parse_state)) => {
|
||||
Err(LoadingProblem::UnexpectedHeader(format!(
|
||||
"got an unexpected platform header\n{:?}",
|
||||
header
|
||||
)))
|
||||
}
|
||||
|
||||
Err(fail) => Err(LoadingProblem::ParsingFailed(
|
||||
fail.map_problem(SyntaxError::Header)
|
||||
.into_file_error(filename),
|
||||
@ -2723,14 +2653,6 @@ fn load_from_str<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ModuleNameEnum<'a> {
|
||||
/// A filename
|
||||
App(StrLiteral<'a>),
|
||||
Interface(roc_parse::header::ModuleName<'a>),
|
||||
PkgConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HeaderInfo<'a> {
|
||||
loc_name: Loc<ModuleNameEnum<'a>>,
|
||||
@ -2740,7 +2662,7 @@ struct HeaderInfo<'a> {
|
||||
packages: &'a [Loc<PackageEntry<'a>>],
|
||||
exposes: &'a [Loc<ExposedName<'a>>],
|
||||
imports: &'a [Loc<ImportsEntry<'a>>],
|
||||
to_platform: Option<To<'a>>,
|
||||
extra: HeaderFor<'a>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@ -2761,13 +2683,13 @@ fn send_header<'a>(
|
||||
packages,
|
||||
exposes,
|
||||
imports,
|
||||
to_platform,
|
||||
extra,
|
||||
} = info;
|
||||
|
||||
let declared_name: ModuleName = match &loc_name.value {
|
||||
PkgConfig => unreachable!(),
|
||||
App(_) => ModuleName::APP.into(),
|
||||
Interface(module_name) => {
|
||||
Interface(module_name) | Hosted(module_name) => {
|
||||
// TODO check to see if module_name is consistent with filename.
|
||||
// If it isn't, report a problem!
|
||||
|
||||
@ -2898,11 +2820,6 @@ fn send_header<'a>(
|
||||
// We always need to send these, even if deps is empty,
|
||||
// because the coordinator thread needs to receive this message
|
||||
// to decrement its "pending" count.
|
||||
let extra = match to_platform {
|
||||
Some(to_platform) => HeaderFor::App { to_platform },
|
||||
None => HeaderFor::Interface,
|
||||
};
|
||||
|
||||
let mut package_qualified_imported_modules = MutSet::default();
|
||||
for (pq_module_name, module_id) in &deps_by_name {
|
||||
match pq_module_name {
|
||||
@ -2919,24 +2836,22 @@ fn send_header<'a>(
|
||||
|
||||
(
|
||||
home,
|
||||
Msg::Header(
|
||||
ModuleHeader {
|
||||
module_id: home,
|
||||
module_path: filename,
|
||||
is_root_module,
|
||||
exposed_ident_ids: ident_ids,
|
||||
module_name: loc_name.value,
|
||||
packages: package_entries,
|
||||
imported_modules,
|
||||
package_qualified_imported_modules,
|
||||
deps_by_name,
|
||||
exposes: exposed,
|
||||
parse_state,
|
||||
exposed_imports: scope,
|
||||
module_timing,
|
||||
},
|
||||
extra,
|
||||
),
|
||||
Msg::Header(ModuleHeader {
|
||||
module_id: home,
|
||||
module_path: filename,
|
||||
is_root_module,
|
||||
exposed_ident_ids: ident_ids,
|
||||
module_name: loc_name.value,
|
||||
packages: package_entries,
|
||||
imported_modules,
|
||||
package_qualified_imported_modules,
|
||||
deps_by_name,
|
||||
exposes: exposed,
|
||||
parse_state,
|
||||
exposed_imports: scope,
|
||||
module_timing,
|
||||
header_for: extra,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@ -2985,7 +2900,6 @@ fn send_header_two<'a>(
|
||||
HashMap::with_capacity_and_hasher(num_exposes, default_hasher());
|
||||
|
||||
// add standard imports
|
||||
// TODO add Effect by default
|
||||
imported_modules.insert(app_module_id, Region::zero());
|
||||
deps_by_name.insert(
|
||||
PQModuleName::Unqualified(ModuleName::APP.into()),
|
||||
@ -3160,24 +3074,22 @@ fn send_header_two<'a>(
|
||||
|
||||
(
|
||||
home,
|
||||
Msg::Header(
|
||||
ModuleHeader {
|
||||
module_id: home,
|
||||
module_path: filename,
|
||||
is_root_module,
|
||||
exposed_ident_ids: ident_ids,
|
||||
module_name,
|
||||
packages: package_entries,
|
||||
imported_modules,
|
||||
package_qualified_imported_modules,
|
||||
deps_by_name,
|
||||
exposes: exposed,
|
||||
parse_state,
|
||||
exposed_imports: scope,
|
||||
module_timing,
|
||||
},
|
||||
extra,
|
||||
),
|
||||
Msg::Header(ModuleHeader {
|
||||
module_id: home,
|
||||
module_path: filename,
|
||||
is_root_module,
|
||||
exposed_ident_ids: ident_ids,
|
||||
module_name,
|
||||
packages: package_entries,
|
||||
imported_modules,
|
||||
package_qualified_imported_modules,
|
||||
deps_by_name,
|
||||
exposes: exposed,
|
||||
parse_state,
|
||||
exposed_imports: scope,
|
||||
module_timing,
|
||||
header_for: extra,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@ -3345,272 +3257,6 @@ fn fabricate_pkg_config_module<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn fabricate_effects_module<'a>(
|
||||
arena: &'a Bump,
|
||||
shorthand: &'a str,
|
||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
|
||||
header: PlatformHeader<'a>,
|
||||
module_timing: ModuleTiming,
|
||||
) -> (ModuleId, Msg<'a>) {
|
||||
let num_exposes = header.provides.len() + 1;
|
||||
let mut exposed: Vec<Symbol> = Vec::with_capacity(num_exposes);
|
||||
|
||||
let effects = header.effects;
|
||||
|
||||
let module_id: ModuleId;
|
||||
|
||||
let effect_entries = unpack_exposes_entries(arena, effects.entries.items);
|
||||
let name = effects.effect_type_name;
|
||||
let declared_name: ModuleName = name.into();
|
||||
|
||||
let hardcoded_effect_symbols = {
|
||||
let mut functions: Vec<_> = crate::effect_module::BUILTIN_EFFECT_FUNCTIONS
|
||||
.iter()
|
||||
.map(|x| x.0)
|
||||
.collect();
|
||||
functions.push(name);
|
||||
|
||||
functions
|
||||
};
|
||||
|
||||
{
|
||||
let mut module_ids = (*module_ids).lock();
|
||||
|
||||
for exposed in header.exposes.iter() {
|
||||
let module_name = exposed.value.extract_spaces().item;
|
||||
|
||||
module_ids.get_or_insert(&PQModuleName::Qualified(
|
||||
shorthand,
|
||||
module_name.as_str().into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let exposed_ident_ids = {
|
||||
// Lock just long enough to perform the minimal operations necessary.
|
||||
let mut module_ids = (*module_ids).lock();
|
||||
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
|
||||
|
||||
let name = PQModuleName::Qualified(shorthand, declared_name);
|
||||
module_id = module_ids.get_or_insert(&name);
|
||||
|
||||
// Ensure this module has an entry in the exposed_ident_ids map.
|
||||
ident_ids_by_module
|
||||
.entry(module_id)
|
||||
.or_insert_with(IdentIds::default);
|
||||
|
||||
let ident_ids = ident_ids_by_module.get_mut(&module_id).unwrap();
|
||||
|
||||
// Generate IdentIds entries for all values this module exposes.
|
||||
// This way, when we encounter them in Defs later, they already
|
||||
// have an IdentIds entry.
|
||||
//
|
||||
// We must *not* add them to scope yet, or else the Defs will
|
||||
// incorrectly think they're shadowing them!
|
||||
for (loc_exposed, _) in effect_entries.iter() {
|
||||
// Use get_or_insert here because the ident_ids may already
|
||||
// created an IdentId for this, when it was imported exposed
|
||||
// in a dependent module.
|
||||
//
|
||||
// For example, if module A has [ B.{ foo } ], then
|
||||
// when we get here for B, `foo` will already have
|
||||
// an IdentId. We must reuse that!
|
||||
let ident_id = ident_ids.get_or_insert(&loc_exposed.value.into());
|
||||
let symbol = Symbol::new(module_id, ident_id);
|
||||
|
||||
exposed.push(symbol);
|
||||
}
|
||||
|
||||
for hardcoded in hardcoded_effect_symbols {
|
||||
// Use get_or_insert here because the ident_ids may already
|
||||
// created an IdentId for this, when it was imported exposed
|
||||
// in a dependent module.
|
||||
//
|
||||
// For example, if module A has [ B.{ foo } ], then
|
||||
// when we get here for B, `foo` will already have
|
||||
// an IdentId. We must reuse that!
|
||||
let ident_id = ident_ids.get_or_insert(&hardcoded.into());
|
||||
let symbol = Symbol::new(module_id, ident_id);
|
||||
|
||||
exposed.push(symbol);
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
module_id.register_debug_idents(ident_ids);
|
||||
}
|
||||
|
||||
ident_ids.clone()
|
||||
};
|
||||
|
||||
// a platform module has no dependencies, hence empty
|
||||
let dep_idents: MutMap<ModuleId, IdentIds> = IdentIds::exposed_builtins(0);
|
||||
|
||||
let mut var_store = VarStore::default();
|
||||
|
||||
let module_ids = { (*module_ids).lock().clone() }.into_module_ids();
|
||||
|
||||
let mut scope = roc_can::scope::Scope::new(module_id, &mut var_store);
|
||||
let mut can_env =
|
||||
roc_can::env::Env::new(module_id, &dep_idents, &module_ids, exposed_ident_ids);
|
||||
|
||||
let effect_symbol = scope
|
||||
.introduce(
|
||||
name.into(),
|
||||
&can_env.exposed_ident_ids,
|
||||
&mut can_env.ident_ids,
|
||||
Region::zero(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let effect_tag_name = TagName::Private(effect_symbol);
|
||||
|
||||
let mut aliases = MutMap::default();
|
||||
let alias = {
|
||||
let a_var = var_store.fresh();
|
||||
|
||||
let actual = crate::effect_module::build_effect_actual(
|
||||
effect_tag_name,
|
||||
Type::Variable(a_var),
|
||||
&mut var_store,
|
||||
);
|
||||
|
||||
scope.add_alias(
|
||||
effect_symbol,
|
||||
Region::zero(),
|
||||
vec![Loc::at_zero(("a".into(), a_var))],
|
||||
actual,
|
||||
);
|
||||
|
||||
scope.lookup_alias(effect_symbol).unwrap().clone()
|
||||
};
|
||||
|
||||
aliases.insert(effect_symbol, alias);
|
||||
|
||||
let mut declarations = Vec::new();
|
||||
|
||||
let exposed_symbols: MutSet<Symbol> = {
|
||||
let mut exposed_symbols = MutSet::default();
|
||||
|
||||
{
|
||||
for (ident, ann) in effect_entries {
|
||||
let symbol = {
|
||||
scope
|
||||
.introduce(
|
||||
ident.value.into(),
|
||||
&can_env.exposed_ident_ids,
|
||||
&mut can_env.ident_ids,
|
||||
Region::zero(),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let annotation = roc_can::annotation::canonicalize_annotation(
|
||||
&mut can_env,
|
||||
&mut scope,
|
||||
&ann.value,
|
||||
Region::zero(),
|
||||
&mut var_store,
|
||||
);
|
||||
|
||||
let def = crate::effect_module::build_host_exposed_def(
|
||||
&mut can_env,
|
||||
&mut scope,
|
||||
symbol,
|
||||
ident.value,
|
||||
TagName::Private(effect_symbol),
|
||||
&mut var_store,
|
||||
annotation,
|
||||
);
|
||||
exposed_symbols.insert(symbol);
|
||||
|
||||
declarations.push(Declaration::Declare(def));
|
||||
}
|
||||
}
|
||||
|
||||
// define Effect.after, Effect.map etc.
|
||||
crate::effect_module::build_effect_builtins(
|
||||
&mut can_env,
|
||||
&mut scope,
|
||||
effect_symbol,
|
||||
&mut var_store,
|
||||
&mut exposed_symbols,
|
||||
&mut declarations,
|
||||
);
|
||||
|
||||
exposed_symbols
|
||||
};
|
||||
|
||||
use roc_can::module::ModuleOutput;
|
||||
let module_output = ModuleOutput {
|
||||
aliases,
|
||||
rigid_variables: MutMap::default(),
|
||||
declarations,
|
||||
exposed_imports: MutMap::default(),
|
||||
lookups: Vec::new(),
|
||||
problems: can_env.problems,
|
||||
ident_ids: can_env.ident_ids,
|
||||
references: MutSet::default(),
|
||||
scope,
|
||||
};
|
||||
|
||||
let constraint = constrain_module(&module_output.declarations, module_id);
|
||||
|
||||
let module = Module {
|
||||
module_id,
|
||||
exposed_imports: module_output.exposed_imports,
|
||||
exposed_symbols,
|
||||
references: module_output.references,
|
||||
aliases: module_output.aliases,
|
||||
rigid_variables: module_output.rigid_variables,
|
||||
};
|
||||
|
||||
let imported_modules = MutMap::default();
|
||||
|
||||
// Should a effect module ever have a ModuleDocumentation?
|
||||
let module_docs = ModuleDocumentation {
|
||||
name: String::from(name),
|
||||
entries: Vec::new(),
|
||||
scope: module_output.scope,
|
||||
};
|
||||
|
||||
let constrained_module = ConstrainedModule {
|
||||
module,
|
||||
declarations: module_output.declarations,
|
||||
imported_modules,
|
||||
var_store,
|
||||
constraint,
|
||||
ident_ids: module_output.ident_ids,
|
||||
dep_idents,
|
||||
module_timing,
|
||||
};
|
||||
|
||||
(
|
||||
module_id,
|
||||
Msg::MadeEffectModule {
|
||||
type_shortname: effects.effect_shortname,
|
||||
constrained_module,
|
||||
canonicalization_problems: module_output.problems,
|
||||
module_docs,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn unpack_exposes_entries<'a>(
|
||||
arena: &'a Bump,
|
||||
entries: &'a [Loc<Spaced<'a, TypedIdent<'a>>>],
|
||||
) -> bumpalo::collections::Vec<'a, (Loc<&'a str>, Loc<TypeAnnotation<'a>>)> {
|
||||
use bumpalo::collections::Vec;
|
||||
|
||||
let iter = entries.iter().map(|entry| {
|
||||
let entry: TypedIdent<'a> = entry.value.extract_spaces().item;
|
||||
(entry.ident, entry.ann)
|
||||
});
|
||||
|
||||
Vec::from_iter_in(iter, arena)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn canonicalize_and_constrain<'a, F>(
|
||||
@ -3630,6 +3276,7 @@ where
|
||||
let ParsedModule {
|
||||
module_id,
|
||||
module_name,
|
||||
header_for,
|
||||
exposed_ident_ids,
|
||||
parsed_defs,
|
||||
exposed_imports,
|
||||
@ -3642,6 +3289,7 @@ where
|
||||
let canonicalized = canonicalize_module_defs(
|
||||
arena,
|
||||
parsed_defs,
|
||||
&header_for,
|
||||
module_id,
|
||||
module_ids,
|
||||
exposed_ident_ids,
|
||||
@ -3663,12 +3311,16 @@ where
|
||||
let module_docs = match module_name {
|
||||
ModuleNameEnum::PkgConfig => None,
|
||||
ModuleNameEnum::App(_) => None,
|
||||
ModuleNameEnum::Interface(name) => Some(crate::docs::generate_module_docs(
|
||||
module_output.scope,
|
||||
name.as_str().into(),
|
||||
&module_output.ident_ids,
|
||||
parsed_defs,
|
||||
)),
|
||||
ModuleNameEnum::Interface(name) | ModuleNameEnum::Hosted(name) => {
|
||||
let docs = crate::docs::generate_module_docs(
|
||||
module_output.scope,
|
||||
name.as_str().into(),
|
||||
&module_output.ident_ids,
|
||||
parsed_defs,
|
||||
);
|
||||
|
||||
Some(docs)
|
||||
}
|
||||
};
|
||||
|
||||
let constraint = constrain_module(&module_output.declarations, module_id);
|
||||
@ -3744,6 +3396,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
|
||||
exposed_ident_ids,
|
||||
exposed_imports,
|
||||
module_path,
|
||||
header_for,
|
||||
..
|
||||
} = header;
|
||||
|
||||
@ -3758,6 +3411,7 @@ fn parse<'a>(arena: &'a Bump, header: ModuleHeader<'a>) -> Result<Msg<'a>, Loadi
|
||||
exposed_ident_ids,
|
||||
exposed_imports,
|
||||
parsed_defs,
|
||||
header_for,
|
||||
};
|
||||
|
||||
Ok(Msg::Parsed(parsed))
|
||||
@ -4390,7 +4044,23 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
|
||||
}
|
||||
RootIsInterface => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
|
||||
alloc.reflow(r"The input file is an interface module, but only app modules can be ran."),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
|
||||
alloc.reflow(r"but won't output any executable."),
|
||||
])
|
||||
]);
|
||||
|
||||
Report {
|
||||
filename: "UNKNOWN.roc".into(),
|
||||
doc,
|
||||
title: "NO PLATFORM".to_string(),
|
||||
severity: Severity::RuntimeError,
|
||||
}
|
||||
}
|
||||
RootIsHosted => {
|
||||
let doc = alloc.stack(vec![
|
||||
alloc.reflow(r"The input file is a hosted module, but only app modules can be ran."),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
|
||||
alloc.reflow(r"but won't output any executable."),
|
||||
|
@ -2,5 +2,4 @@
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
pub mod docs;
|
||||
pub mod effect_module;
|
||||
pub mod file;
|
||||
|
@ -1,7 +1,6 @@
|
||||
app "primary"
|
||||
packages { blah: "./blah" }
|
||||
interface Primary
|
||||
exposes [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ]
|
||||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Res ]
|
||||
provides [ blah2, blah3, str, alwaysThree, identity, z, w, succeed, withDefault, yay ] to blah
|
||||
|
||||
blah2 = Dep2.two
|
||||
blah3 = bar
|
||||
|
@ -82,11 +82,6 @@ mod test_load {
|
||||
let app_module = files.pop().unwrap();
|
||||
let interfaces = files;
|
||||
|
||||
debug_assert!(
|
||||
app_module.1.starts_with("app"),
|
||||
"The final module should be the application module"
|
||||
);
|
||||
|
||||
for (name, source) in interfaces {
|
||||
let mut filename = PathBuf::from(name);
|
||||
filename.set_extension("roc");
|
||||
@ -278,15 +273,10 @@ mod test_load {
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app "test-app"
|
||||
packages { blah: "./blah" }
|
||||
imports [ RBTree ]
|
||||
provides [ main ] to blah
|
||||
interface Other exposes [ empty ] imports [ RBTree ]
|
||||
|
||||
empty : RBTree.RedBlackTree I64 I64
|
||||
empty = RBTree.empty
|
||||
|
||||
main = empty
|
||||
"#
|
||||
),
|
||||
),
|
||||
@ -530,10 +520,10 @@ mod test_load {
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app "test-app" packages { blah: "./blah" } provides [ main ] to blah
|
||||
interface Main exposes [ main ] imports []
|
||||
|
||||
main = [
|
||||
"#
|
||||
main = [
|
||||
"#
|
||||
),
|
||||
)];
|
||||
|
||||
@ -561,9 +551,7 @@ mod test_load {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "FileProblem { filename: \"tests/fixtures/build/interface_with_deps/invalid$name.roc\", error: NotFound }"
|
||||
)]
|
||||
#[should_panic(expected = "FILE NOT FOUND")]
|
||||
fn file_not_found() {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module = load_fixture("interface_with_deps", "invalid$name", subs_by_module);
|
||||
@ -589,4 +577,29 @@ mod test_load {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_does_not_exist() {
|
||||
let modules = vec![(
|
||||
"Main",
|
||||
indoc!(
|
||||
r#"
|
||||
app "example"
|
||||
packages { pf: "./zzz-does-not-exist" }
|
||||
imports [ ]
|
||||
provides [ main ] to pf
|
||||
|
||||
main = ""
|
||||
"#
|
||||
),
|
||||
)];
|
||||
|
||||
match multiple_modules(modules) {
|
||||
Err(report) => {
|
||||
assert!(report.contains("FILE NOT FOUND"));
|
||||
assert!(report.contains("zzz-does-not-exist/Package-Config.roc"));
|
||||
}
|
||||
Ok(_) => unreachable!("we expect failure here"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1638,7 +1638,9 @@ fn literal_spec(
|
||||
|
||||
match literal {
|
||||
Str(_) => new_static_string(builder, block),
|
||||
Int(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]),
|
||||
Int(_) | U128(_) | Float(_) | Decimal(_) | Bool(_) | Byte(_) => {
|
||||
builder.add_make_tuple(block, &[])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,6 +87,7 @@ enum Test<'a> {
|
||||
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
|
||||
},
|
||||
IsInt(i128, IntWidth),
|
||||
IsU128(u128),
|
||||
IsFloat(u64, FloatWidth),
|
||||
IsDecimal(RocDec),
|
||||
IsStr(Box<str>),
|
||||
@ -136,6 +137,10 @@ impl<'a> Hash for Test<'a> {
|
||||
state.write_u8(6);
|
||||
v.0.hash(state);
|
||||
}
|
||||
IsU128(v) => {
|
||||
state.write_u8(7);
|
||||
v.hash(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,6 +316,7 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool {
|
||||
Test::IsByte { num_alts, .. } => number_of_tests == *num_alts,
|
||||
Test::IsBit(_) => number_of_tests == 2,
|
||||
Test::IsInt(_, _) => false,
|
||||
Test::IsU128(_) => false,
|
||||
Test::IsFloat(_, _) => false,
|
||||
Test::IsDecimal(_) => false,
|
||||
Test::IsStr(_) => false,
|
||||
@ -565,6 +571,7 @@ fn test_at_path<'a>(
|
||||
num_alts: union.alternatives.len(),
|
||||
},
|
||||
IntLiteral(v, precision) => IsInt(*v, *precision),
|
||||
U128Literal(v) => IsU128(*v),
|
||||
FloatLiteral(v, precision) => IsFloat(*v, *precision),
|
||||
DecimalLiteral(v) => IsDecimal(*v),
|
||||
StrLiteral(v) => IsStr(v.clone()),
|
||||
@ -823,6 +830,18 @@ fn to_relevant_branch_help<'a>(
|
||||
_ => None,
|
||||
},
|
||||
|
||||
U128Literal(int) => match test {
|
||||
IsU128(is_int) if int == *is_int => {
|
||||
start.extend(end);
|
||||
Some(Branch {
|
||||
goal: branch.goal,
|
||||
guard: branch.guard.clone(),
|
||||
patterns: start,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
|
||||
FloatLiteral(float, p1) => match test {
|
||||
IsFloat(test_float, p2) if float == *test_float => {
|
||||
debug_assert_eq!(p1, *p2);
|
||||
@ -934,6 +953,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
|
||||
| BitLiteral { .. }
|
||||
| EnumLiteral { .. }
|
||||
| IntLiteral(_, _)
|
||||
| U128Literal(_)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
| StrLiteral(_) => true,
|
||||
@ -1305,6 +1325,14 @@ fn test_to_equality<'a>(
|
||||
(stores, lhs_symbol, rhs_symbol, None)
|
||||
}
|
||||
|
||||
Test::IsU128(test_int) => {
|
||||
let lhs = Expr::Literal(Literal::U128(test_int));
|
||||
let lhs_symbol = env.unique_symbol();
|
||||
stores.push((lhs_symbol, Layout::int_width(IntWidth::U128), lhs));
|
||||
|
||||
(stores, lhs_symbol, rhs_symbol, None)
|
||||
}
|
||||
|
||||
Test::IsFloat(test_int, precision) => {
|
||||
// TODO maybe we can actually use i64 comparison here?
|
||||
let test_float = f64::from_bits(test_int as u64);
|
||||
|
@ -54,6 +54,7 @@ pub enum Pattern {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Literal {
|
||||
Int(i128),
|
||||
U128(u128),
|
||||
Bit(bool),
|
||||
Byte(u8),
|
||||
Float(u64),
|
||||
@ -66,6 +67,7 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern {
|
||||
|
||||
match pattern {
|
||||
IntLiteral(v, _) => Literal(Literal::Int(*v)),
|
||||
U128Literal(v) => Literal(Literal::U128(*v)),
|
||||
FloatLiteral(v, _) => Literal(Literal::Float(*v)),
|
||||
DecimalLiteral(v) => Literal(Literal::Decimal(*v)),
|
||||
StrLiteral(v) => Literal(Literal::Str(v.clone())),
|
||||
|
@ -8,7 +8,7 @@ use crate::layout::{
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_can::expr::ClosureData;
|
||||
use roc_can::expr::{ClosureData, IntValue};
|
||||
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
|
||||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
@ -1278,6 +1278,7 @@ impl ModifyRc {
|
||||
pub enum Literal<'a> {
|
||||
// Literals
|
||||
Int(i128),
|
||||
U128(u128),
|
||||
Float(f64),
|
||||
Decimal(RocDec),
|
||||
Str(&'a str),
|
||||
@ -1524,6 +1525,7 @@ impl<'a> Literal<'a> {
|
||||
|
||||
match self {
|
||||
Int(lit) => alloc.text(format!("{}i64", lit)),
|
||||
U128(lit) => alloc.text(format!("{}u128", lit)),
|
||||
Float(lit) => alloc.text(format!("{}f64", lit)),
|
||||
// TODO: Add proper Dec.to_str
|
||||
Decimal(lit) => alloc.text(format!("{}Dec", lit.0)),
|
||||
@ -2020,7 +2022,7 @@ fn pattern_to_when<'a>(
|
||||
(symbol, Loc::at_zero(wrapped_body))
|
||||
}
|
||||
|
||||
IntLiteral(_, _, _) | NumLiteral(_, _, _) | FloatLiteral(_, _, _) | StrLiteral(_) => {
|
||||
IntLiteral(..) | NumLiteral(..) | FloatLiteral(..) | StrLiteral(_) => {
|
||||
// These patters are refutable, and thus should never occur outside a `when` expression
|
||||
// They should have been replaced with `UnsupportedPattern` during canonicalization
|
||||
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
|
||||
@ -3009,14 +3011,17 @@ fn try_make_literal<'a>(
|
||||
use roc_can::expr::Expr::*;
|
||||
|
||||
match can_expr {
|
||||
Int(_, precision, _, int) => {
|
||||
Int(_, precision, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, false) {
|
||||
IntOrFloat::Int(_) => Some(Literal::Int(*int)),
|
||||
IntOrFloat::Int(_) => Some(match *int {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
_ => unreachable!("unexpected float precision for integer"),
|
||||
}
|
||||
}
|
||||
|
||||
Float(_, precision, float_str, float) => {
|
||||
Float(_, precision, float_str, float, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision, true) {
|
||||
IntOrFloat::Float(_) => Some(Literal::Float(*float)),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
@ -3036,11 +3041,17 @@ fn try_make_literal<'a>(
|
||||
|
||||
// TODO investigate lifetime trouble
|
||||
// Str(string) => Some(Literal::Str(env.arena.alloc(string))),
|
||||
Num(var, num_str, num) => {
|
||||
Num(var, num_str, num, _bound) => {
|
||||
// first figure out what kind of number this is
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
|
||||
IntOrFloat::Int(_) => Some(Literal::Int((*num).into())),
|
||||
IntOrFloat::Float(_) => Some(Literal::Float(*num as f64)),
|
||||
IntOrFloat::Int(_) => Some(match *num {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
IntOrFloat::Float(_) => Some(match *num {
|
||||
IntValue::I128(n) => Literal::Float(n as f64),
|
||||
IntValue::U128(n) => Literal::Float(n as f64),
|
||||
}),
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(num_str) {
|
||||
Some(d) => d,
|
||||
@ -3072,11 +3083,14 @@ pub fn with_hole<'a>(
|
||||
let arena = env.arena;
|
||||
|
||||
match can_expr {
|
||||
Int(_, precision, _, int) => {
|
||||
Int(_, precision, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, precision, false) {
|
||||
IntOrFloat::Int(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(int)),
|
||||
Expr::Literal(match int {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
Layout::Builtin(Builtin::Int(precision)),
|
||||
hole,
|
||||
),
|
||||
@ -3084,7 +3098,7 @@ pub fn with_hole<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
Float(_, precision, float_str, float) => {
|
||||
Float(_, precision, float_str, float, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, precision, true) {
|
||||
IntOrFloat::Float(precision) => Stmt::Let(
|
||||
assigned,
|
||||
@ -3115,18 +3129,24 @@ pub fn with_hole<'a>(
|
||||
hole,
|
||||
),
|
||||
|
||||
Num(var, num_str, num) => {
|
||||
Num(var, num_str, num, _bound) => {
|
||||
// first figure out what kind of number this is
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, var, false) {
|
||||
IntOrFloat::Int(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Int(num.into())),
|
||||
Expr::Literal(match num {
|
||||
IntValue::I128(n) => Literal::Int(n),
|
||||
IntValue::U128(n) => Literal::U128(n),
|
||||
}),
|
||||
Layout::int_width(precision),
|
||||
hole,
|
||||
),
|
||||
IntOrFloat::Float(precision) => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Float(num as f64)),
|
||||
Expr::Literal(match num {
|
||||
IntValue::I128(n) => Literal::Float(n as f64),
|
||||
IntValue::U128(n) => Literal::Float(n as f64),
|
||||
}),
|
||||
Layout::float_width(precision),
|
||||
hole,
|
||||
),
|
||||
@ -6211,6 +6231,7 @@ fn store_pattern_help<'a>(
|
||||
return StorePattern::NotProductive(stmt);
|
||||
}
|
||||
IntLiteral(_, _)
|
||||
| U128Literal(_)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
| EnumLiteral { .. }
|
||||
@ -7583,6 +7604,7 @@ fn call_specialized_proc<'a>(
|
||||
pub enum Pattern<'a> {
|
||||
Identifier(Symbol),
|
||||
Underscore,
|
||||
U128Literal(u128),
|
||||
IntLiteral(i128, IntWidth),
|
||||
FloatLiteral(u64, FloatWidth),
|
||||
DecimalLiteral(RocDec),
|
||||
@ -7662,9 +7684,15 @@ fn from_can_pattern_help<'a>(
|
||||
match can_pattern {
|
||||
Underscore => Ok(Pattern::Underscore),
|
||||
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
|
||||
IntLiteral(var, _, int) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
|
||||
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*int as i128, precision)),
|
||||
IntLiteral(_, precision_var, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
|
||||
IntOrFloat::Int(precision) => {
|
||||
let int = match *int {
|
||||
IntValue::I128(n) => Pattern::IntLiteral(n, precision),
|
||||
IntValue::U128(n) => Pattern::U128Literal(n),
|
||||
};
|
||||
Ok(int)
|
||||
}
|
||||
other => {
|
||||
panic!(
|
||||
"Invalid precision for int pattern: {:?} has {:?}",
|
||||
@ -7673,11 +7701,11 @@ fn from_can_pattern_help<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
FloatLiteral(var, float_str, float) => {
|
||||
FloatLiteral(_, precision_var, float_str, float, _bound) => {
|
||||
// TODO: Can I reuse num_argument_to_int_or_float here if I pass in true?
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, true) {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, true) {
|
||||
IntOrFloat::Int(_) => {
|
||||
panic!("Invalid precision for float pattern {:?}", var)
|
||||
panic!("Invalid precision for float pattern {:?}", precision_var)
|
||||
}
|
||||
IntOrFloat::Float(precision) => {
|
||||
Ok(Pattern::FloatLiteral(f64::to_bits(*float), precision))
|
||||
@ -7704,10 +7732,20 @@ fn from_can_pattern_help<'a>(
|
||||
// TODO preserve malformed problem information here?
|
||||
Err(RuntimeError::UnsupportedPattern(*region))
|
||||
}
|
||||
NumLiteral(var, num_str, num) => {
|
||||
NumLiteral(var, num_str, num, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) {
|
||||
IntOrFloat::Int(precision) => Ok(Pattern::IntLiteral(*num as i128, precision)),
|
||||
IntOrFloat::Float(precision) => Ok(Pattern::FloatLiteral(*num as u64, precision)),
|
||||
IntOrFloat::Int(precision) => Ok(match num {
|
||||
IntValue::I128(num) => Pattern::IntLiteral(*num, precision),
|
||||
IntValue::U128(num) => Pattern::U128Literal(*num),
|
||||
}),
|
||||
IntOrFloat::Float(precision) => {
|
||||
// TODO: this may be lossy
|
||||
let num = match *num {
|
||||
IntValue::I128(n) => f64::to_bits(n as f64),
|
||||
IntValue::U128(n) => f64::to_bits(n as f64),
|
||||
};
|
||||
Ok(Pattern::FloatLiteral(num, precision))
|
||||
}
|
||||
IntOrFloat::DecimalFloatType => {
|
||||
let dec = match RocDec::from_str(num_str) {
|
||||
Some(d) => d,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::header::{AppHeader, InterfaceHeader, PlatformHeader};
|
||||
use crate::header::{AppHeader, HostedHeader, InterfaceHeader, PlatformHeader};
|
||||
use crate::ident::Ident;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
use bumpalo::Bump;
|
||||
@ -70,6 +70,7 @@ pub enum Module<'a> {
|
||||
Interface { header: InterfaceHeader<'a> },
|
||||
App { header: AppHeader<'a> },
|
||||
Platform { header: PlatformHeader<'a> },
|
||||
Hosted { header: HostedHeader<'a> },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
@ -231,6 +232,16 @@ pub struct AliasHeader<'a> {
|
||||
pub vars: &'a [Loc<Pattern<'a>>],
|
||||
}
|
||||
|
||||
impl<'a> AliasHeader<'a> {
|
||||
pub fn region(&self) -> Region {
|
||||
Region::across_all(
|
||||
[self.name.region]
|
||||
.iter()
|
||||
.chain(self.vars.iter().map(|v| &v.region)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Def<'a> {
|
||||
// TODO in canonicalization, validate the pattern; only certain patterns
|
||||
|
@ -514,7 +514,7 @@ fn numeric_negate_expression<'a, T>(
|
||||
let start = state.pos();
|
||||
let region = Region::new(start, expr.region.end());
|
||||
|
||||
let new_expr = match &expr.value {
|
||||
let new_expr = match expr.value {
|
||||
Expr::Num(string) => {
|
||||
let new_string =
|
||||
unsafe { std::str::from_utf8_unchecked(&state.bytes()[..string.len() + 1]) };
|
||||
@ -536,7 +536,7 @@ fn numeric_negate_expression<'a, T>(
|
||||
Expr::NonBase10Int {
|
||||
is_negative: !is_negative,
|
||||
string,
|
||||
base: *base,
|
||||
base,
|
||||
}
|
||||
}
|
||||
_ => Expr::UnaryOp(arena.alloc(expr), Loc::at(loc_op.region, UnaryOp::Negate)),
|
||||
@ -1450,8 +1450,8 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
|
||||
Ok(Pattern::RecordDestructure(patterns))
|
||||
}
|
||||
|
||||
Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
|
||||
Expr::Num(string) => Ok(Pattern::NumLiteral(string)),
|
||||
&Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
|
||||
&Expr::Num(string) => Ok(Pattern::NumLiteral(string)),
|
||||
Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
|
@ -8,6 +8,28 @@ use crate::string_literal;
|
||||
use bumpalo::collections::Vec;
|
||||
use roc_region::all::Loc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum HeaderFor<'a> {
|
||||
App {
|
||||
to_platform: To<'a>,
|
||||
},
|
||||
Hosted {
|
||||
generates: UppercaseIdent<'a>,
|
||||
generates_with: &'a [Loc<ExposedName<'a>>],
|
||||
},
|
||||
PkgConfig {
|
||||
/// usually `pf`
|
||||
config_shorthand: &'a str,
|
||||
/// the type scheme of the main function (required by the platform)
|
||||
/// (currently unused)
|
||||
#[allow(dead_code)]
|
||||
platform_main_type: TypedIdent<'a>,
|
||||
/// provided symbol to host (commonly `mainForHost`)
|
||||
main_for_host: roc_module::symbol::Symbol,
|
||||
},
|
||||
Interface,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Version<'a> {
|
||||
Exact(&'a str),
|
||||
@ -47,6 +69,15 @@ impl<'a> ModuleName<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ModuleNameEnum<'a> {
|
||||
/// A filename
|
||||
App(StrLiteral<'a>),
|
||||
Interface(ModuleName<'a>),
|
||||
Hosted(ModuleName<'a>),
|
||||
PkgConfig,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
|
||||
pub struct ExposedName<'a>(&'a str);
|
||||
|
||||
@ -81,6 +112,27 @@ pub struct InterfaceHeader<'a> {
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct HostedHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
pub exposes: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
|
||||
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
|
||||
pub generates: UppercaseIdent<'a>,
|
||||
pub generates_with: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub before_header: &'a [CommentOrNewline<'a>],
|
||||
pub after_hosted_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub before_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub after_exposes: &'a [CommentOrNewline<'a>],
|
||||
pub before_imports: &'a [CommentOrNewline<'a>],
|
||||
pub after_imports: &'a [CommentOrNewline<'a>],
|
||||
pub before_generates: &'a [CommentOrNewline<'a>],
|
||||
pub after_generates: &'a [CommentOrNewline<'a>],
|
||||
pub before_with: &'a [CommentOrNewline<'a>],
|
||||
pub after_with: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum To<'a> {
|
||||
ExistingPackage(&'a str),
|
||||
@ -141,7 +193,6 @@ pub struct PlatformHeader<'a> {
|
||||
pub packages: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
|
||||
pub imports: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
|
||||
pub provides: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
|
||||
pub effects: Effects<'a>,
|
||||
|
||||
// Potential comments and newlines - these will typically all be empty.
|
||||
pub before_header: &'a [CommentOrNewline<'a>],
|
||||
@ -158,17 +209,6 @@ pub struct PlatformHeader<'a> {
|
||||
pub after_provides: &'a [CommentOrNewline<'a>],
|
||||
}
|
||||
|
||||
/// e.g. fx.Effects
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Effects<'a> {
|
||||
pub spaces_before_effects_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub spaces_after_effects_keyword: &'a [CommentOrNewline<'a>],
|
||||
pub spaces_after_type_name: &'a [CommentOrNewline<'a>],
|
||||
pub effect_shortname: &'a str,
|
||||
pub effect_type_name: &'a str,
|
||||
pub entries: Collection<'a, Loc<Spaced<'a, TypedIdent<'a>>>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum ImportsEntry<'a> {
|
||||
/// e.g. `Task` or `Task.{ Task, after }`
|
||||
|
@ -22,6 +22,12 @@ impl<'a> From<UppercaseIdent<'a>> for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a UppercaseIdent<'a>> for &'a str {
|
||||
fn from(ident: &'a UppercaseIdent<'a>) -> Self {
|
||||
ident.0
|
||||
}
|
||||
}
|
||||
|
||||
/// The parser accepts all of these in any position where any one of them could
|
||||
/// appear. This way, canonicalization can give more helpful error messages like
|
||||
/// "you can't redefine this tag!" if you wrote `Foo = ...` or
|
||||
|
@ -1,14 +1,15 @@
|
||||
use crate::ast::{Collection, CommentOrNewline, Def, Module, Spaced};
|
||||
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
|
||||
use crate::header::{
|
||||
package_entry, package_name, AppHeader, Effects, ExposedName, ImportsEntry, InterfaceHeader,
|
||||
ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
package_entry, package_name, AppHeader, ExposedName, HostedHeader, ImportsEntry,
|
||||
InterfaceHeader, ModuleName, PackageEntry, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
};
|
||||
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase_ident, UppercaseIdent};
|
||||
use crate::ident::{self, lowercase_ident, unqualified_ident, uppercase, UppercaseIdent};
|
||||
use crate::parser::Progress::{self, *};
|
||||
use crate::parser::{
|
||||
backtrackable, optional, specialize, specialize_region, word1, EEffects, EExposes, EHeader,
|
||||
EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser, SourceError, SyntaxError,
|
||||
backtrackable, optional, specialize, specialize_region, word1, EExposes, EGenerates,
|
||||
EGeneratesWith, EHeader, EImports, EPackages, EProvides, ERequires, ETypedIdent, Parser,
|
||||
SourceError, SyntaxError,
|
||||
};
|
||||
use crate::state::State;
|
||||
use crate::string_literal;
|
||||
@ -52,41 +53,52 @@ pub fn parse_header<'a>(
|
||||
fn header<'a>() -> impl Parser<'a, Module<'a>, EHeader<'a>> {
|
||||
use crate::parser::keyword_e;
|
||||
|
||||
one_of![
|
||||
map!(
|
||||
and!(
|
||||
space0_e(0, EHeader::Space, EHeader::IndentStart),
|
||||
skip_first!(keyword_e("app", EHeader::Start), app_header())
|
||||
),
|
||||
|(spaces, mut header): (&'a [CommentOrNewline], AppHeader<'a>)| {
|
||||
header.before_header = spaces;
|
||||
type Clos<'b> = Box<(dyn FnOnce(&'b [CommentOrNewline]) -> Module<'b> + 'b)>;
|
||||
|
||||
Module::App { header }
|
||||
}
|
||||
map!(
|
||||
and!(
|
||||
space0_e(0, EHeader::Space, EHeader::IndentStart),
|
||||
one_of![
|
||||
map!(
|
||||
skip_first!(keyword_e("interface", EHeader::Start), interface_header()),
|
||||
|mut header: InterfaceHeader<'a>| -> Clos<'a> {
|
||||
Box::new(|spaces| {
|
||||
header.before_header = spaces;
|
||||
Module::Interface { header }
|
||||
})
|
||||
}
|
||||
),
|
||||
map!(
|
||||
skip_first!(keyword_e("app", EHeader::Start), app_header()),
|
||||
|mut header: AppHeader<'a>| -> Clos<'a> {
|
||||
Box::new(|spaces| {
|
||||
header.before_header = spaces;
|
||||
Module::App { header }
|
||||
})
|
||||
}
|
||||
),
|
||||
map!(
|
||||
skip_first!(keyword_e("platform", EHeader::Start), platform_header()),
|
||||
|mut header: PlatformHeader<'a>| -> Clos<'a> {
|
||||
Box::new(|spaces| {
|
||||
header.before_header = spaces;
|
||||
Module::Platform { header }
|
||||
})
|
||||
}
|
||||
),
|
||||
map!(
|
||||
skip_first!(keyword_e("hosted", EHeader::Start), hosted_header()),
|
||||
|mut header: HostedHeader<'a>| -> Clos<'a> {
|
||||
Box::new(|spaces| {
|
||||
header.before_header = spaces;
|
||||
Module::Hosted { header }
|
||||
})
|
||||
}
|
||||
)
|
||||
]
|
||||
),
|
||||
map!(
|
||||
and!(
|
||||
space0_e(0, EHeader::Space, EHeader::IndentStart),
|
||||
skip_first!(keyword_e("platform", EHeader::Start), platform_header())
|
||||
),
|
||||
|(spaces, mut header): (&'a [CommentOrNewline], PlatformHeader<'a>)| {
|
||||
header.before_header = spaces;
|
||||
|
||||
Module::Platform { header }
|
||||
}
|
||||
),
|
||||
map!(
|
||||
and!(
|
||||
space0_e(0, EHeader::Space, EHeader::IndentStart),
|
||||
skip_first!(keyword_e("interface", EHeader::Start), interface_header())
|
||||
),
|
||||
|(spaces, mut header): (&'a [CommentOrNewline], InterfaceHeader<'a>)| {
|
||||
header.before_header = spaces;
|
||||
|
||||
Module::Interface { header }
|
||||
}
|
||||
)
|
||||
]
|
||||
|(spaces, make_header): (&'a [CommentOrNewline], Clos<'a>)| { make_header(spaces) }
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -119,6 +131,46 @@ fn interface_header<'a>() -> impl Parser<'a, InterfaceHeader<'a>, EHeader<'a>> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn hosted_header<'a>() -> impl Parser<'a, HostedHeader<'a>, EHeader<'a>> {
|
||||
|arena, state| {
|
||||
let min_indent = 1;
|
||||
|
||||
let (_, after_hosted_keyword, state) =
|
||||
space0_e(min_indent, EHeader::Space, EHeader::IndentStart).parse(arena, state)?;
|
||||
let (_, name, state) = loc!(module_name_help(EHeader::ModuleName)).parse(arena, state)?;
|
||||
|
||||
let (_, ((before_exposes, after_exposes), exposes), state) =
|
||||
specialize(EHeader::Exposes, exposes_values()).parse(arena, state)?;
|
||||
let (_, ((before_imports, after_imports), imports), state) =
|
||||
specialize(EHeader::Imports, imports()).parse(arena, state)?;
|
||||
let (_, ((before_generates, after_generates), generates), state) =
|
||||
specialize(EHeader::Generates, generates()).parse(arena, state)?;
|
||||
let (_, ((before_with, after_with), generates_with), state) =
|
||||
specialize(EHeader::GeneratesWith, generates_with()).parse(arena, state)?;
|
||||
|
||||
let header = HostedHeader {
|
||||
name,
|
||||
exposes,
|
||||
imports,
|
||||
generates,
|
||||
generates_with,
|
||||
before_header: &[] as &[_],
|
||||
after_hosted_keyword,
|
||||
before_exposes,
|
||||
after_exposes,
|
||||
before_imports,
|
||||
after_imports,
|
||||
before_generates,
|
||||
after_generates,
|
||||
before_with,
|
||||
after_with,
|
||||
};
|
||||
|
||||
Ok((MadeProgress, header, state))
|
||||
}
|
||||
}
|
||||
|
||||
fn chomp_module_name(buffer: &[u8]) -> Result<&str, Progress> {
|
||||
use encode_unicode::CharExt;
|
||||
|
||||
@ -269,8 +321,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
|
||||
let (_, ((before_provides, after_provides), (provides, _provides_type)), state) =
|
||||
specialize(EHeader::Provides, provides_without_to()).parse(arena, state)?;
|
||||
|
||||
let (_, effects, state) = specialize(EHeader::Effects, effects()).parse(arena, state)?;
|
||||
|
||||
let header = PlatformHeader {
|
||||
name,
|
||||
requires,
|
||||
@ -278,7 +328,6 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
|
||||
packages: packages.entries,
|
||||
imports,
|
||||
provides,
|
||||
effects,
|
||||
before_header: &[] as &[_],
|
||||
after_platform_keyword,
|
||||
before_requires,
|
||||
@ -676,6 +725,64 @@ fn packages<'a>() -> impl Parser<'a, Packages<'a>, EPackages<'a>> {
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn generates<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
UppercaseIdent<'a>,
|
||||
),
|
||||
EGenerates,
|
||||
> {
|
||||
let min_indent = 1;
|
||||
|
||||
and!(
|
||||
spaces_around_keyword(
|
||||
min_indent,
|
||||
"generates",
|
||||
EGenerates::Generates,
|
||||
EGenerates::Space,
|
||||
EGenerates::IndentGenerates,
|
||||
EGenerates::IndentTypeStart
|
||||
),
|
||||
specialize(|(), pos| EGenerates::Identifier(pos), uppercase())
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn generates_with<'a>() -> impl Parser<
|
||||
'a,
|
||||
(
|
||||
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
|
||||
Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
|
||||
),
|
||||
EGeneratesWith,
|
||||
> {
|
||||
let min_indent = 1;
|
||||
|
||||
and!(
|
||||
spaces_around_keyword(
|
||||
min_indent,
|
||||
"with",
|
||||
EGeneratesWith::With,
|
||||
EGeneratesWith::Space,
|
||||
EGeneratesWith::IndentWith,
|
||||
EGeneratesWith::IndentListStart
|
||||
),
|
||||
collection_trailing_sep_e!(
|
||||
word1(b'[', EGeneratesWith::ListStart),
|
||||
exposes_entry(EGeneratesWith::Identifier),
|
||||
word1(b',', EGeneratesWith::ListEnd),
|
||||
word1(b']', EGeneratesWith::ListEnd),
|
||||
min_indent,
|
||||
EGeneratesWith::Open,
|
||||
EGeneratesWith::Space,
|
||||
EGeneratesWith::IndentListEnd,
|
||||
Spaced::SpaceBefore
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn imports<'a>() -> impl Parser<
|
||||
'a,
|
||||
@ -710,63 +817,6 @@ fn imports<'a>() -> impl Parser<
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
|
||||
move |arena, state| {
|
||||
let min_indent = 1;
|
||||
|
||||
let (_, (spaces_before_effects_keyword, spaces_after_effects_keyword), state) =
|
||||
spaces_around_keyword(
|
||||
min_indent,
|
||||
"effects",
|
||||
EEffects::Effects,
|
||||
EEffects::Space,
|
||||
EEffects::IndentEffects,
|
||||
EEffects::IndentListStart,
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
// e.g. `fx.`
|
||||
let (_, type_shortname, state) = skip_second!(
|
||||
specialize(|_, pos| EEffects::Shorthand(pos), lowercase_ident()),
|
||||
word1(b'.', EEffects::ShorthandDot)
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
// the type name, e.g. Effects
|
||||
let (_, (type_name, spaces_after_type_name), state) = and!(
|
||||
specialize(|_, pos| EEffects::TypeName(pos), uppercase_ident()),
|
||||
space0_e(min_indent, EEffects::Space, EEffects::IndentListStart)
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
let (_, entries, state) = collection_trailing_sep_e!(
|
||||
word1(b'{', EEffects::ListStart),
|
||||
specialize(EEffects::TypedIdent, loc!(typed_ident())),
|
||||
word1(b',', EEffects::ListEnd),
|
||||
word1(b'}', EEffects::ListEnd),
|
||||
min_indent,
|
||||
EEffects::Open,
|
||||
EEffects::Space,
|
||||
EEffects::IndentListEnd,
|
||||
Spaced::SpaceBefore
|
||||
)
|
||||
.parse(arena, state)?;
|
||||
|
||||
Ok((
|
||||
MadeProgress,
|
||||
Effects {
|
||||
spaces_before_effects_keyword,
|
||||
spaces_after_effects_keyword,
|
||||
spaces_after_type_name,
|
||||
effect_shortname: type_shortname,
|
||||
effect_type_name: type_name,
|
||||
entries,
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent<'a>> {
|
||||
// e.g.
|
||||
|
@ -71,7 +71,8 @@ pub enum EHeader<'a> {
|
||||
Imports(EImports, Position),
|
||||
Requires(ERequires<'a>, Position),
|
||||
Packages(EPackages<'a>, Position),
|
||||
Effects(EEffects<'a>, Position),
|
||||
Generates(EGenerates, Position),
|
||||
GeneratesWith(EGeneratesWith, Position),
|
||||
|
||||
Space(BadInputError, Position),
|
||||
Start(Position),
|
||||
@ -165,22 +166,6 @@ pub enum EPackageEntry<'a> {
|
||||
Space(BadInputError, Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum EEffects<'a> {
|
||||
Space(BadInputError, Position),
|
||||
Effects(Position),
|
||||
Open(Position),
|
||||
IndentEffects(Position),
|
||||
ListStart(Position),
|
||||
ListEnd(Position),
|
||||
IndentListStart(Position),
|
||||
IndentListEnd(Position),
|
||||
TypedIdent(ETypedIdent<'a>, Position),
|
||||
ShorthandDot(Position),
|
||||
Shorthand(Position),
|
||||
TypeName(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EImports {
|
||||
Open(Position),
|
||||
@ -202,6 +187,30 @@ pub enum EImports {
|
||||
SetEnd(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EGenerates {
|
||||
Open(Position),
|
||||
Generates(Position),
|
||||
IndentGenerates(Position),
|
||||
Identifier(Position),
|
||||
Space(BadInputError, Position),
|
||||
IndentTypeStart(Position),
|
||||
IndentTypeEnd(Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EGeneratesWith {
|
||||
Open(Position),
|
||||
With(Position),
|
||||
IndentWith(Position),
|
||||
IndentListStart(Position),
|
||||
IndentListEnd(Position),
|
||||
ListStart(Position),
|
||||
ListEnd(Position),
|
||||
Identifier(Position),
|
||||
Space(BadInputError, Position),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BadInputError {
|
||||
HasTab,
|
||||
|
@ -45,7 +45,6 @@ impl<'a> State<'a> {
|
||||
#[must_use]
|
||||
pub(crate) fn advance(&self, offset: usize) -> State<'a> {
|
||||
let mut state = self.clone();
|
||||
debug_assert!(!state.bytes()[..offset].iter().any(|b| *b == b'\n'));
|
||||
state.offset += offset;
|
||||
state
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
Hosted {
|
||||
header: HostedHeader {
|
||||
name: @7-10 ModuleName(
|
||||
"Foo",
|
||||
),
|
||||
exposes: [],
|
||||
imports: [],
|
||||
generates: UppercaseIdent(
|
||||
"Bar",
|
||||
),
|
||||
generates_with: [],
|
||||
before_header: [],
|
||||
after_hosted_keyword: [],
|
||||
before_exposes: [],
|
||||
after_exposes: [],
|
||||
before_imports: [],
|
||||
after_imports: [],
|
||||
before_generates: [],
|
||||
after_generates: [],
|
||||
before_with: [],
|
||||
after_with: [],
|
||||
},
|
||||
}
|
@ -0,0 +1 @@
|
||||
hosted Foo exposes [] imports [] generates Bar with []
|
@ -18,14 +18,6 @@ Platform {
|
||||
packages: [],
|
||||
imports: [],
|
||||
provides: [],
|
||||
effects: Effects {
|
||||
spaces_before_effects_keyword: [],
|
||||
spaces_after_effects_keyword: [],
|
||||
spaces_after_type_name: [],
|
||||
effect_shortname: "fx",
|
||||
effect_type_name: "Blah",
|
||||
entries: [],
|
||||
},
|
||||
before_header: [],
|
||||
after_platform_keyword: [],
|
||||
before_requires: [],
|
||||
|
@ -1 +1 @@
|
||||
platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides [] effects fx.Blah {}
|
||||
platform "rtfeldman/blah" requires {} { main : {} } exposes [] packages {} imports [] provides []
|
||||
|
@ -43,105 +43,6 @@ Platform {
|
||||
"mainForHost",
|
||||
),
|
||||
],
|
||||
effects: Effects {
|
||||
spaces_before_effects_keyword: [
|
||||
Newline,
|
||||
],
|
||||
spaces_after_effects_keyword: [],
|
||||
spaces_after_type_name: [
|
||||
Newline,
|
||||
],
|
||||
effect_shortname: "fx",
|
||||
effect_type_name: "Effect",
|
||||
entries: [
|
||||
@208-228 SpaceBefore(
|
||||
TypedIdent {
|
||||
ident: @208-215 "getLine",
|
||||
spaces_before_colon: [],
|
||||
ann: @218-228 Apply(
|
||||
"",
|
||||
"Effect",
|
||||
[
|
||||
@225-228 Apply(
|
||||
"",
|
||||
"Str",
|
||||
[],
|
||||
),
|
||||
],
|
||||
),
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@242-268 SpaceBefore(
|
||||
TypedIdent {
|
||||
ident: @242-249 "putLine",
|
||||
spaces_before_colon: [],
|
||||
ann: @259-268 Function(
|
||||
[
|
||||
@252-255 Apply(
|
||||
"",
|
||||
"Str",
|
||||
[],
|
||||
),
|
||||
],
|
||||
@259-268 Apply(
|
||||
"",
|
||||
"Effect",
|
||||
[
|
||||
@266-268 Record {
|
||||
fields: [],
|
||||
ext: None,
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@282-318 SpaceBefore(
|
||||
SpaceAfter(
|
||||
TypedIdent {
|
||||
ident: @282-294 "twoArguments",
|
||||
spaces_before_colon: [],
|
||||
ann: @309-318 Function(
|
||||
[
|
||||
@297-300 Apply(
|
||||
"",
|
||||
"Int",
|
||||
[],
|
||||
),
|
||||
@302-305 Apply(
|
||||
"",
|
||||
"Int",
|
||||
[],
|
||||
),
|
||||
],
|
||||
@309-318 Apply(
|
||||
"",
|
||||
"Effect",
|
||||
[
|
||||
@316-318 Record {
|
||||
fields: [],
|
||||
ext: None,
|
||||
},
|
||||
],
|
||||
),
|
||||
),
|
||||
},
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
},
|
||||
before_header: [],
|
||||
after_platform_keyword: [],
|
||||
before_requires: [
|
||||
|
@ -4,9 +4,3 @@ platform "examples/cli"
|
||||
packages {}
|
||||
imports [ Task.{ Task } ]
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect
|
||||
{
|
||||
getLine : Effect Str,
|
||||
putLine : Str -> Effect {},
|
||||
twoArguments : Int, Int -> Effect {}
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
Interface {
|
||||
header: InterfaceHeader {
|
||||
name: @10-11 ModuleName(
|
||||
"T",
|
||||
),
|
||||
exposes: [],
|
||||
imports: [],
|
||||
before_header: [],
|
||||
after_interface_keyword: [],
|
||||
before_exposes: [],
|
||||
after_exposes: [],
|
||||
before_imports: [],
|
||||
after_imports: [],
|
||||
},
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
|
||||
interface T exposes [] imports []
|
@ -0,0 +1,130 @@
|
||||
Hosted {
|
||||
header: HostedHeader {
|
||||
name: @7-10 ModuleName(
|
||||
"Foo",
|
||||
),
|
||||
exposes: Collection {
|
||||
items: [
|
||||
@45-50 SpaceBefore(
|
||||
ExposedName(
|
||||
"Stuff",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@64-70 SpaceBefore(
|
||||
ExposedName(
|
||||
"Things",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@84-97 SpaceBefore(
|
||||
ExposedName(
|
||||
"somethingElse",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
final_comments: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
imports: Collection {
|
||||
items: [
|
||||
@143-147 SpaceBefore(
|
||||
Module(
|
||||
ModuleName(
|
||||
"Blah",
|
||||
),
|
||||
[],
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@161-182 SpaceBefore(
|
||||
Module(
|
||||
ModuleName(
|
||||
"Baz",
|
||||
),
|
||||
[
|
||||
@167-172 ExposedName(
|
||||
"stuff",
|
||||
),
|
||||
@174-180 ExposedName(
|
||||
"things",
|
||||
),
|
||||
],
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
final_comments: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
generates: UppercaseIdent(
|
||||
"Bar",
|
||||
),
|
||||
generates_with: Collection {
|
||||
items: [
|
||||
@239-242 SpaceBefore(
|
||||
ExposedName(
|
||||
"map",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@256-261 SpaceBefore(
|
||||
ExposedName(
|
||||
"after",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@275-279 SpaceBefore(
|
||||
ExposedName(
|
||||
"loop",
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
final_comments: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
before_header: [],
|
||||
after_hosted_keyword: [],
|
||||
before_exposes: [
|
||||
Newline,
|
||||
],
|
||||
after_exposes: [
|
||||
Newline,
|
||||
],
|
||||
before_imports: [
|
||||
Newline,
|
||||
],
|
||||
after_imports: [
|
||||
Newline,
|
||||
],
|
||||
before_generates: [
|
||||
Newline,
|
||||
],
|
||||
after_generates: [],
|
||||
before_with: [],
|
||||
after_with: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
hosted Foo
|
||||
exposes
|
||||
[
|
||||
Stuff,
|
||||
Things,
|
||||
somethingElse,
|
||||
]
|
||||
imports
|
||||
[
|
||||
Blah,
|
||||
Baz.{ stuff, things },
|
||||
]
|
||||
generates Bar with
|
||||
[
|
||||
map,
|
||||
after,
|
||||
loop,
|
||||
]
|
@ -34,16 +34,6 @@ Platform {
|
||||
"mainForHost",
|
||||
),
|
||||
],
|
||||
effects: Effects {
|
||||
spaces_before_effects_keyword: [
|
||||
Newline,
|
||||
],
|
||||
spaces_after_effects_keyword: [],
|
||||
spaces_after_type_name: [],
|
||||
effect_shortname: "fx",
|
||||
effect_type_name: "Effect",
|
||||
entries: [],
|
||||
},
|
||||
before_header: [],
|
||||
after_platform_keyword: [],
|
||||
before_requires: [
|
||||
|
@ -4,4 +4,3 @@ platform "foo/barbaz"
|
||||
packages { foo: "./foo" }
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
@ -0,0 +1,451 @@
|
||||
Record(
|
||||
Collection {
|
||||
items: [
|
||||
@4-15 SpaceBefore(
|
||||
RequiredValue(
|
||||
@4-6 "u8",
|
||||
[],
|
||||
@10-15 Num(
|
||||
"123u8",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@19-31 SpaceBefore(
|
||||
RequiredValue(
|
||||
@19-22 "u16",
|
||||
[],
|
||||
@25-31 Num(
|
||||
"123u16",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@35-47 SpaceBefore(
|
||||
RequiredValue(
|
||||
@35-38 "u32",
|
||||
[],
|
||||
@41-47 Num(
|
||||
"123u32",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@51-63 SpaceBefore(
|
||||
RequiredValue(
|
||||
@51-54 "u64",
|
||||
[],
|
||||
@57-63 Num(
|
||||
"123u64",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@67-80 SpaceBefore(
|
||||
RequiredValue(
|
||||
@67-71 "u128",
|
||||
[],
|
||||
@73-80 Num(
|
||||
"123u128",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@84-95 SpaceBefore(
|
||||
RequiredValue(
|
||||
@84-86 "i8",
|
||||
[],
|
||||
@90-95 Num(
|
||||
"123i8",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@99-111 SpaceBefore(
|
||||
RequiredValue(
|
||||
@99-102 "i16",
|
||||
[],
|
||||
@105-111 Num(
|
||||
"123i16",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@115-127 SpaceBefore(
|
||||
RequiredValue(
|
||||
@115-118 "i32",
|
||||
[],
|
||||
@121-127 Num(
|
||||
"123i32",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@131-143 SpaceBefore(
|
||||
RequiredValue(
|
||||
@131-134 "i64",
|
||||
[],
|
||||
@137-143 Num(
|
||||
"123i64",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@147-160 SpaceBefore(
|
||||
RequiredValue(
|
||||
@147-151 "i128",
|
||||
[],
|
||||
@153-160 Num(
|
||||
"123i128",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@164-176 SpaceBefore(
|
||||
RequiredValue(
|
||||
@164-167 "nat",
|
||||
[],
|
||||
@170-176 Num(
|
||||
"123nat",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@180-192 SpaceBefore(
|
||||
RequiredValue(
|
||||
@180-183 "dec",
|
||||
[],
|
||||
@186-192 Num(
|
||||
"123dec",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@196-211 SpaceBefore(
|
||||
RequiredValue(
|
||||
@196-201 "u8Neg",
|
||||
[],
|
||||
@205-211 Num(
|
||||
"-123u8",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@215-231 SpaceBefore(
|
||||
RequiredValue(
|
||||
@215-221 "u16Neg",
|
||||
[],
|
||||
@224-231 Num(
|
||||
"-123u16",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@235-251 SpaceBefore(
|
||||
RequiredValue(
|
||||
@235-241 "u32Neg",
|
||||
[],
|
||||
@244-251 Num(
|
||||
"-123u32",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@255-271 SpaceBefore(
|
||||
RequiredValue(
|
||||
@255-261 "u64Neg",
|
||||
[],
|
||||
@264-271 Num(
|
||||
"-123u64",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@275-292 SpaceBefore(
|
||||
RequiredValue(
|
||||
@275-282 "u128Neg",
|
||||
[],
|
||||
@284-292 Num(
|
||||
"-123u128",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@296-311 SpaceBefore(
|
||||
RequiredValue(
|
||||
@296-301 "i8Neg",
|
||||
[],
|
||||
@305-311 Num(
|
||||
"-123i8",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@315-331 SpaceBefore(
|
||||
RequiredValue(
|
||||
@315-321 "i16Neg",
|
||||
[],
|
||||
@324-331 Num(
|
||||
"-123i16",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@335-351 SpaceBefore(
|
||||
RequiredValue(
|
||||
@335-341 "i32Neg",
|
||||
[],
|
||||
@344-351 Num(
|
||||
"-123i32",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@355-371 SpaceBefore(
|
||||
RequiredValue(
|
||||
@355-361 "i64Neg",
|
||||
[],
|
||||
@364-371 Num(
|
||||
"-123i64",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@375-392 SpaceBefore(
|
||||
RequiredValue(
|
||||
@375-382 "i128Neg",
|
||||
[],
|
||||
@384-392 Num(
|
||||
"-123i128",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@396-412 SpaceBefore(
|
||||
RequiredValue(
|
||||
@396-402 "natNeg",
|
||||
[],
|
||||
@405-412 Num(
|
||||
"-123nat",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@416-432 SpaceBefore(
|
||||
RequiredValue(
|
||||
@416-422 "decNeg",
|
||||
[],
|
||||
@425-432 Num(
|
||||
"-123dec",
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@436-452 SpaceBefore(
|
||||
RequiredValue(
|
||||
@436-441 "u8Bin",
|
||||
[],
|
||||
@445-452 NonBase10Int {
|
||||
string: "101u8",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@456-473 SpaceBefore(
|
||||
RequiredValue(
|
||||
@456-462 "u16Bin",
|
||||
[],
|
||||
@465-473 NonBase10Int {
|
||||
string: "101u16",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@477-494 SpaceBefore(
|
||||
RequiredValue(
|
||||
@477-483 "u32Bin",
|
||||
[],
|
||||
@486-494 NonBase10Int {
|
||||
string: "101u32",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@498-515 SpaceBefore(
|
||||
RequiredValue(
|
||||
@498-504 "u64Bin",
|
||||
[],
|
||||
@507-515 NonBase10Int {
|
||||
string: "101u64",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@519-537 SpaceBefore(
|
||||
RequiredValue(
|
||||
@519-526 "u128Bin",
|
||||
[],
|
||||
@528-537 NonBase10Int {
|
||||
string: "101u128",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@541-557 SpaceBefore(
|
||||
RequiredValue(
|
||||
@541-546 "i8Bin",
|
||||
[],
|
||||
@550-557 NonBase10Int {
|
||||
string: "101i8",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@561-578 SpaceBefore(
|
||||
RequiredValue(
|
||||
@561-567 "i16Bin",
|
||||
[],
|
||||
@570-578 NonBase10Int {
|
||||
string: "101i16",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@582-599 SpaceBefore(
|
||||
RequiredValue(
|
||||
@582-588 "i32Bin",
|
||||
[],
|
||||
@591-599 NonBase10Int {
|
||||
string: "101i32",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@603-620 SpaceBefore(
|
||||
RequiredValue(
|
||||
@603-609 "i64Bin",
|
||||
[],
|
||||
@612-620 NonBase10Int {
|
||||
string: "101i64",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@624-642 SpaceBefore(
|
||||
RequiredValue(
|
||||
@624-631 "i128Bin",
|
||||
[],
|
||||
@633-642 NonBase10Int {
|
||||
string: "101i128",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
@646-663 SpaceBefore(
|
||||
RequiredValue(
|
||||
@646-652 "natBin",
|
||||
[],
|
||||
@655-663 NonBase10Int {
|
||||
string: "101nat",
|
||||
base: Binary,
|
||||
is_negative: false,
|
||||
},
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
],
|
||||
final_comments: [
|
||||
Newline,
|
||||
],
|
||||
},
|
||||
)
|
@ -0,0 +1,37 @@
|
||||
{
|
||||
u8: 123u8,
|
||||
u16: 123u16,
|
||||
u32: 123u32,
|
||||
u64: 123u64,
|
||||
u128: 123u128,
|
||||
i8: 123i8,
|
||||
i16: 123i16,
|
||||
i32: 123i32,
|
||||
i64: 123i64,
|
||||
i128: 123i128,
|
||||
nat: 123nat,
|
||||
dec: 123dec,
|
||||
u8Neg: -123u8,
|
||||
u16Neg: -123u16,
|
||||
u32Neg: -123u32,
|
||||
u64Neg: -123u64,
|
||||
u128Neg: -123u128,
|
||||
i8Neg: -123i8,
|
||||
i16Neg: -123i16,
|
||||
i32Neg: -123i32,
|
||||
i64Neg: -123i64,
|
||||
i128Neg: -123i128,
|
||||
natNeg: -123nat,
|
||||
decNeg: -123dec,
|
||||
u8Bin: 0b101u8,
|
||||
u16Bin: 0b101u16,
|
||||
u32Bin: 0b101u32,
|
||||
u64Bin: 0b101u64,
|
||||
u128Bin: 0b101u128,
|
||||
i8Bin: 0b101i8,
|
||||
i16Bin: 0b101i16,
|
||||
i32Bin: 0b101i32,
|
||||
i64Bin: 0b101i64,
|
||||
i128Bin: 0b101i128,
|
||||
natBin: 0b101nat,
|
||||
}
|
@ -41,16 +41,6 @@ Platform {
|
||||
"mainForHost",
|
||||
),
|
||||
],
|
||||
effects: Effects {
|
||||
spaces_before_effects_keyword: [
|
||||
Newline,
|
||||
],
|
||||
spaces_after_effects_keyword: [],
|
||||
spaces_after_type_name: [],
|
||||
effect_shortname: "fx",
|
||||
effect_type_name: "Effect",
|
||||
entries: [],
|
||||
},
|
||||
before_header: [],
|
||||
after_platform_keyword: [],
|
||||
before_requires: [
|
||||
|
@ -4,7 +4,6 @@ platform "test/types"
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
effects fx.Effect {}
|
||||
|
||||
mainForHost : App Flags Model
|
||||
mainForHost = main
|
||||
|
@ -133,6 +133,8 @@ mod test_parse {
|
||||
pass/destructure_tag_assignment.expr,
|
||||
pass/empty_app_header.header,
|
||||
pass/empty_interface_header.header,
|
||||
pass/empty_hosted_header.header,
|
||||
pass/nonempty_hosted_header.header,
|
||||
pass/empty_list.expr,
|
||||
pass/empty_platform_header.header,
|
||||
pass/empty_record.expr,
|
||||
@ -148,6 +150,7 @@ mod test_parse {
|
||||
pass/highest_int.expr,
|
||||
pass/if_def.expr,
|
||||
pass/int_with_underscore.expr,
|
||||
pass/interface_with_newline.header,
|
||||
pass/lowest_float.expr,
|
||||
pass/lowest_int.expr,
|
||||
pass/malformed_ident_due_to_underscore.expr,
|
||||
@ -179,6 +182,7 @@ mod test_parse {
|
||||
pass/newline_singleton_list.expr,
|
||||
pass/nonempty_platform_header.header,
|
||||
pass/not_docs.expr,
|
||||
pass/number_literal_suffixes.expr,
|
||||
pass/one_backpassing.expr,
|
||||
pass/one_char_string.expr,
|
||||
pass/one_def.expr,
|
||||
|
@ -78,6 +78,11 @@ pub enum Problem {
|
||||
InvalidInterpolation(Region),
|
||||
InvalidHexadecimal(Region),
|
||||
InvalidUnicodeCodePt(Region),
|
||||
NestedDatatype {
|
||||
alias: Symbol,
|
||||
def_region: Region,
|
||||
differing_recursion_region: Region,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -102,6 +107,18 @@ pub enum IntErrorKind {
|
||||
Overflow,
|
||||
/// Integer is too small to store in target integer type.
|
||||
Underflow,
|
||||
/// This is an integer, but it has a float numeric suffix.
|
||||
FloatSuffix,
|
||||
/// The integer literal overflows the width of the suffix associated with it.
|
||||
OverflowsSuffix {
|
||||
suffix_type: &'static str,
|
||||
max_value: u128,
|
||||
},
|
||||
/// The integer literal underflows the width of the suffix associated with it.
|
||||
UnderflowsSuffix {
|
||||
suffix_type: &'static str,
|
||||
min_value: i128,
|
||||
},
|
||||
}
|
||||
|
||||
/// Enum to store the various types of errors that can cause parsing a float to fail.
|
||||
@ -113,6 +130,8 @@ pub enum FloatErrorKind {
|
||||
NegativeInfinity,
|
||||
/// the literal is too large for f64
|
||||
PositiveInfinity,
|
||||
/// This is a float, but it has an integer numeric suffix.
|
||||
IntSuffix,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -771,7 +771,7 @@ fn type_to_variable<'a>(
|
||||
|
||||
match typ {
|
||||
Variable(var) => *var,
|
||||
Apply(symbol, arguments) => {
|
||||
Apply(symbol, arguments, _) => {
|
||||
let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len());
|
||||
for (target_index, var_index) in (new_arguments.indices()).zip(arguments) {
|
||||
let var = type_to_variable(subs, rank, pools, arena, var_index);
|
||||
|
@ -5044,4 +5044,156 @@ mod solve_expr {
|
||||
"[ Email Str ] -> Bool",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numeric_literal_suffixes() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
u8: 123u8,
|
||||
u16: 123u16,
|
||||
u32: 123u32,
|
||||
u64: 123u64,
|
||||
u128: 123u128,
|
||||
|
||||
i8: 123i8,
|
||||
i16: 123i16,
|
||||
i32: 123i32,
|
||||
i64: 123i64,
|
||||
i128: 123i128,
|
||||
|
||||
nat: 123nat,
|
||||
|
||||
bu8: 0b11u8,
|
||||
bu16: 0b11u16,
|
||||
bu32: 0b11u32,
|
||||
bu64: 0b11u64,
|
||||
bu128: 0b11u128,
|
||||
|
||||
bi8: 0b11i8,
|
||||
bi16: 0b11i16,
|
||||
bi32: 0b11i32,
|
||||
bi64: 0b11i64,
|
||||
bi128: 0b11i128,
|
||||
|
||||
bnat: 0b11nat,
|
||||
|
||||
dec: 123.0dec,
|
||||
f32: 123.0f32,
|
||||
f64: 123.0f64,
|
||||
|
||||
fdec: 123dec,
|
||||
ff32: 123f32,
|
||||
ff64: 123f64,
|
||||
}
|
||||
"#
|
||||
),
|
||||
r#"{ bi128 : I128, bi16 : I16, bi32 : I32, bi64 : I64, bi8 : I8, bnat : Nat, bu128 : U128, bu16 : U16, bu32 : U32, bu64 : U64, bu8 : U8, dec : Dec, f32 : F32, f64 : F64, fdec : Dec, ff32 : F32, ff64 : F64, i128 : I128, i16 : I16, i32 : I32, i64 : I64, i8 : I8, nat : Nat, u128 : U128, u16 : U16, u32 : U32, u64 : U64, u8 : U8 }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numeric_literal_suffixes_in_pattern() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
u8: (\n ->
|
||||
when n is
|
||||
123u8 -> n),
|
||||
u16: (\n ->
|
||||
when n is
|
||||
123u16 -> n),
|
||||
u32: (\n ->
|
||||
when n is
|
||||
123u32 -> n),
|
||||
u64: (\n ->
|
||||
when n is
|
||||
123u64 -> n),
|
||||
u128: (\n ->
|
||||
when n is
|
||||
123u128 -> n),
|
||||
|
||||
i8: (\n ->
|
||||
when n is
|
||||
123i8 -> n),
|
||||
i16: (\n ->
|
||||
when n is
|
||||
123i16 -> n),
|
||||
i32: (\n ->
|
||||
when n is
|
||||
123i32 -> n),
|
||||
i64: (\n ->
|
||||
when n is
|
||||
123i64 -> n),
|
||||
i128: (\n ->
|
||||
when n is
|
||||
123i128 -> n),
|
||||
|
||||
nat: (\n ->
|
||||
when n is
|
||||
123nat -> n),
|
||||
|
||||
bu8: (\n ->
|
||||
when n is
|
||||
0b11u8 -> n),
|
||||
bu16: (\n ->
|
||||
when n is
|
||||
0b11u16 -> n),
|
||||
bu32: (\n ->
|
||||
when n is
|
||||
0b11u32 -> n),
|
||||
bu64: (\n ->
|
||||
when n is
|
||||
0b11u64 -> n),
|
||||
bu128: (\n ->
|
||||
when n is
|
||||
0b11u128 -> n),
|
||||
|
||||
bi8: (\n ->
|
||||
when n is
|
||||
0b11i8 -> n),
|
||||
bi16: (\n ->
|
||||
when n is
|
||||
0b11i16 -> n),
|
||||
bi32: (\n ->
|
||||
when n is
|
||||
0b11i32 -> n),
|
||||
bi64: (\n ->
|
||||
when n is
|
||||
0b11i64 -> n),
|
||||
bi128: (\n ->
|
||||
when n is
|
||||
0b11i128 -> n),
|
||||
|
||||
bnat: (\n ->
|
||||
when n is
|
||||
0b11nat -> n),
|
||||
|
||||
dec: (\n ->
|
||||
when n is
|
||||
123.0dec -> n),
|
||||
f32: (\n ->
|
||||
when n is
|
||||
123.0f32 -> n),
|
||||
f64: (\n ->
|
||||
when n is
|
||||
123.0f64 -> n),
|
||||
|
||||
fdec: (\n ->
|
||||
when n is
|
||||
123dec -> n),
|
||||
ff32: (\n ->
|
||||
when n is
|
||||
123f32 -> n),
|
||||
ff64: (\n ->
|
||||
when n is
|
||||
123f64 -> n),
|
||||
}
|
||||
"#
|
||||
),
|
||||
r#"{ bi128 : I128 -> I128, bi16 : I16 -> I16, bi32 : I32 -> I32, bi64 : I64 -> I64, bi8 : I8 -> I8, bnat : Nat -> Nat, bu128 : U128 -> U128, bu16 : U16 -> U16, bu32 : U32 -> U32, bu64 : U64 -> U64, bu8 : U8 -> U8, dec : Dec -> Dec, f32 : F32 -> F32, f64 : F64 -> F64, fdec : Dec -> Dec, ff32 : F32 -> F32, ff64 : F64 -> F64, i128 : I128 -> I128, i16 : I16 -> I16, i32 : I32 -> I32, i64 : I64 -> I64, i8 : I8 -> I8, nat : Nat -> Nat, u128 : U128 -> U128, u16 : U16 -> U16, u32 : U32 -> U32, u64 : U64 -> U64, u8 : U8 -> U8 }"#,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_evals_to;
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_expect_failed;
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_llvm_evals_to;
|
||||
#[cfg(feature = "gen-llvm")]
|
||||
use crate::helpers::llvm::assert_non_opt_evals_to;
|
||||
@ -8,6 +10,8 @@ use crate::helpers::llvm::assert_non_opt_evals_to;
|
||||
#[cfg(feature = "gen-dev")]
|
||||
use crate::helpers::dev::assert_evals_to;
|
||||
// #[cfg(feature = "gen-dev")]
|
||||
// use crate::helpers::dev::assert_expect_failed;
|
||||
// #[cfg(feature = "gen-dev")]
|
||||
// use crate::helpers::dev::assert_evals_to as assert_llvm_evals_to;
|
||||
// #[cfg(feature = "gen-dev")]
|
||||
// use crate::helpers::dev::assert_evals_to as assert_non_opt_evals_to;
|
||||
@ -15,6 +19,8 @@ use crate::helpers::dev::assert_evals_to;
|
||||
#[cfg(feature = "gen-wasm")]
|
||||
use crate::helpers::wasm::assert_evals_to;
|
||||
// #[cfg(feature = "gen-wasm")]
|
||||
// use crate::helpers::dev::assert_expect_failed;
|
||||
// #[cfg(feature = "gen-wasm")]
|
||||
// use crate::helpers::wasm::assert_evals_to as assert_llvm_evals_to;
|
||||
// #[cfg(feature = "gen-wasm")]
|
||||
// use crate::helpers::wasm::assert_evals_to as assert_non_opt_evals_to;
|
||||
@ -2485,9 +2491,9 @@ fn call_invalid_layout() {
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[should_panic(expected = "An expectation failed!")]
|
||||
#[should_panic(expected = "Failed with 1 failures. Failures: ")]
|
||||
fn expect_fail() {
|
||||
assert_evals_to!(
|
||||
assert_expect_failed!(
|
||||
indoc!(
|
||||
r#"
|
||||
expect 1 == 2
|
||||
|
@ -257,5 +257,25 @@ macro_rules! assert_evals_to {
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! assert_expect_failed {
|
||||
($src:expr, $expected:expr, $ty:ty, $failures:expr) => {{
|
||||
use bumpalo::Bump;
|
||||
use roc_gen_dev::run_jit_function_raw;
|
||||
let stdlib = roc_builtins::std::standard_stdlib();
|
||||
|
||||
let arena = Bump::new();
|
||||
let (main_fn_name, errors, lib) =
|
||||
$crate::helpers::dev::helper(&arena, $src, stdlib, true, true);
|
||||
|
||||
let transform = |success| {
|
||||
let expected = $expected;
|
||||
assert_eq!(&success, &expected);
|
||||
};
|
||||
run_jit_function_raw!(lib, main_fn_name, $ty, transform, errors);
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_evals_to;
|
||||
pub(crate) use assert_expect_failed;
|
||||
|
@ -192,7 +192,11 @@ fn create_llvm_module<'a>(
|
||||
for function in FunctionIterator::from_module(module) {
|
||||
let name = function.get_name().to_str().unwrap();
|
||||
if name.starts_with("roc_builtins") {
|
||||
function.set_linkage(Linkage::Internal);
|
||||
if name.starts_with("roc_builtins.expect") {
|
||||
function.set_linkage(Linkage::External);
|
||||
} else {
|
||||
function.set_linkage(Linkage::Internal);
|
||||
}
|
||||
}
|
||||
|
||||
if name.starts_with("roc_builtins.dict") {
|
||||
@ -601,6 +605,46 @@ macro_rules! assert_evals_to {
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_macros)]
|
||||
macro_rules! assert_expect_failed {
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
use bumpalo::Bump;
|
||||
use inkwell::context::Context;
|
||||
use roc_gen_llvm::run_jit_function;
|
||||
|
||||
let arena = Bump::new();
|
||||
let context = Context::create();
|
||||
|
||||
// NOTE the stdlib must be in the arena; just taking a reference will segfault
|
||||
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
|
||||
|
||||
let is_gen_test = true;
|
||||
let (main_fn_name, errors, lib) =
|
||||
$crate::helpers::llvm::helper(&arena, $src, stdlib, is_gen_test, false, &context);
|
||||
|
||||
let transform = |success| {
|
||||
let expected = $expected;
|
||||
assert_eq!(&success, &expected, "LLVM test failed");
|
||||
};
|
||||
|
||||
run_jit_function!(lib, main_fn_name, $ty, transform, errors)
|
||||
};
|
||||
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
$crate::helpers::llvm::assert_llvm_evals_to!(
|
||||
$src,
|
||||
$expected,
|
||||
$ty,
|
||||
$crate::helpers::llvm::identity,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||
$crate::helpers::llvm::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! expect_runtime_error_panic {
|
||||
($src:expr) => {{
|
||||
#[cfg(feature = "wasm-cli-run")]
|
||||
@ -651,6 +695,8 @@ macro_rules! assert_non_opt_evals_to {
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_evals_to;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_expect_failed;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_llvm_evals_to;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use assert_non_opt_evals_to;
|
||||
|
@ -24,6 +24,11 @@ pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
|
||||
libc::malloc(size)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_memcpy(dest: *mut c_void, src: *const c_void, bytes: usize) -> *mut c_void {
|
||||
libc::memcpy(dest, src, bytes)
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe fn roc_realloc(
|
||||
c_ptr: *mut c_void,
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::subs::{Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable};
|
||||
use crate::subs::{AliasVariables, Content, FlatType, GetSubsSlice, Subs, UnionTags, Variable};
|
||||
use crate::types::{name_type_var, RecordField};
|
||||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
@ -289,6 +289,17 @@ pub fn content_to_string(
|
||||
buf
|
||||
}
|
||||
|
||||
pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Content {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let arg_var_index = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Num was not applied to a type argument!");
|
||||
let arg_var = subs[arg_var_index];
|
||||
subs.get_content_without_compacting(arg_var)
|
||||
}
|
||||
|
||||
fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) {
|
||||
use crate::subs::Content::*;
|
||||
|
||||
@ -306,18 +317,19 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
|
||||
match *symbol {
|
||||
Symbol::NUM_NUM => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let arg_var_index = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Num was not applied to a type argument!");
|
||||
let arg_var = subs[arg_var_index];
|
||||
let content = subs.get_content_without_compacting(arg_var);
|
||||
|
||||
match &content {
|
||||
Alias(nested, _, _) => match *nested {
|
||||
Symbol::NUM_INTEGER => buf.push_str("I64"),
|
||||
let content = get_single_arg(subs, args);
|
||||
match *content {
|
||||
Alias(nested, args, _actual) => match nested {
|
||||
Symbol::NUM_INTEGER => {
|
||||
write_integer(
|
||||
env,
|
||||
get_single_arg(subs, &args),
|
||||
subs,
|
||||
buf,
|
||||
parens,
|
||||
false,
|
||||
);
|
||||
}
|
||||
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
|
||||
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
@ -333,6 +345,33 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
}
|
||||
}
|
||||
|
||||
Symbol::NUM_INT => {
|
||||
let content = get_single_arg(subs, args);
|
||||
|
||||
write_integer(env, content, subs, buf, parens, write_parens)
|
||||
}
|
||||
|
||||
Symbol::NUM_FLOAT => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let arg_var_index = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect("Num was not applied to a type argument!");
|
||||
let arg_var = subs[arg_var_index];
|
||||
let content = subs.get_content_without_compacting(arg_var);
|
||||
|
||||
match content {
|
||||
Alias(Symbol::NUM_BINARY32, _, _) => buf.push_str("F32"),
|
||||
Alias(Symbol::NUM_BINARY64, _, _) => buf.push_str("F64"),
|
||||
Alias(Symbol::NUM_DECIMAL, _, _) => buf.push_str("Dec"),
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
buf.push_str("Float ");
|
||||
write_content(env, content, subs, buf, parens);
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
_ => write_parens!(write_parens, buf, {
|
||||
write_symbol(env, *symbol, buf);
|
||||
|
||||
@ -362,6 +401,51 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
|
||||
}
|
||||
}
|
||||
|
||||
fn write_integer(
|
||||
env: &Env,
|
||||
content: &Content,
|
||||
subs: &Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
write_parens: bool,
|
||||
) {
|
||||
use crate::subs::Content::*;
|
||||
|
||||
macro_rules! derive_num_writes {
|
||||
($($lit:expr, $tag:path)*) => {
|
||||
write_parens!(
|
||||
write_parens,
|
||||
buf,
|
||||
match content {
|
||||
$(
|
||||
&Alias($tag, _, _) => {
|
||||
buf.push_str($lit)
|
||||
},
|
||||
)*
|
||||
actual => {
|
||||
buf.push_str("Int ");
|
||||
write_content(env, actual, subs, buf, parens);
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
derive_num_writes! {
|
||||
"U8", Symbol::NUM_UNSIGNED8
|
||||
"U16", Symbol::NUM_UNSIGNED16
|
||||
"U32", Symbol::NUM_UNSIGNED32
|
||||
"U64", Symbol::NUM_UNSIGNED64
|
||||
"U128", Symbol::NUM_UNSIGNED128
|
||||
"I8", Symbol::NUM_SIGNED8
|
||||
"I16", Symbol::NUM_SIGNED16
|
||||
"I32", Symbol::NUM_SIGNED32
|
||||
"I64", Symbol::NUM_SIGNED64
|
||||
"I128", Symbol::NUM_SIGNED128
|
||||
"Nat", Symbol::NUM_NATURAL
|
||||
}
|
||||
}
|
||||
|
||||
enum ExtContent<'a> {
|
||||
Empty,
|
||||
Content(Variable, &'a Content),
|
||||
|
@ -85,7 +85,7 @@ impl SolvedType {
|
||||
match typ {
|
||||
EmptyRec => SolvedType::EmptyRecord,
|
||||
EmptyTagUnion => SolvedType::EmptyTagUnion,
|
||||
Apply(symbol, types) => {
|
||||
Apply(symbol, types, _) => {
|
||||
let mut solved_types = Vec::with_capacity(types.len());
|
||||
|
||||
for typ in types {
|
||||
@ -454,7 +454,7 @@ pub fn to_type(
|
||||
new_args.push(to_type(arg, free_vars, var_store));
|
||||
}
|
||||
|
||||
Type::Apply(*symbol, new_args)
|
||||
Type::Apply(*symbol, new_args, Region::zero())
|
||||
}
|
||||
Rigid(lowercase) => {
|
||||
if let Some(var) = free_vars.named_vars.get(lowercase) {
|
||||
|
@ -1626,6 +1626,25 @@ pub enum FlatType {
|
||||
EmptyTagUnion,
|
||||
}
|
||||
|
||||
impl FlatType {
|
||||
pub fn get_singleton_tag_union<'a>(&'a self, subs: &'a Subs) -> Option<&'a TagName> {
|
||||
match self {
|
||||
Self::TagUnion(tags, ext) => {
|
||||
let tags = tags.unsorted_tags_and_ext(subs, *ext).0.tags;
|
||||
if tags.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
let (tag_name, vars) = tags[0];
|
||||
if !vars.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(tag_name)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
pub enum Builtin {
|
||||
Str,
|
||||
|
@ -94,13 +94,18 @@ impl RecordField<Type> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) {
|
||||
pub fn substitute_alias(
|
||||
&mut self,
|
||||
rep_symbol: Symbol,
|
||||
rep_args: &[Type],
|
||||
actual: &Type,
|
||||
) -> Result<(), Region> {
|
||||
use RecordField::*;
|
||||
|
||||
match self {
|
||||
Optional(typ) => typ.substitute_alias(rep_symbol, actual),
|
||||
Required(typ) => typ.substitute_alias(rep_symbol, actual),
|
||||
Demanded(typ) => typ.substitute_alias(rep_symbol, actual),
|
||||
Optional(typ) => typ.substitute_alias(rep_symbol, rep_args, actual),
|
||||
Required(typ) => typ.substitute_alias(rep_symbol, rep_args, actual),
|
||||
Demanded(typ) => typ.substitute_alias(rep_symbol, rep_args, actual),
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,7 +194,7 @@ pub enum Type {
|
||||
},
|
||||
RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, Box<Type>),
|
||||
/// Applying a type to some arguments (e.g. Dict.Dict String Int)
|
||||
Apply(Symbol, Vec<Type>),
|
||||
Apply(Symbol, Vec<Type>, Region),
|
||||
Variable(Variable),
|
||||
/// A type error, which will code gen to a runtime error
|
||||
Erroneous(Problem),
|
||||
@ -220,7 +225,7 @@ impl fmt::Debug for Type {
|
||||
}
|
||||
Type::Variable(var) => write!(f, "<{:?}>", var),
|
||||
|
||||
Type::Apply(symbol, args) => {
|
||||
Type::Apply(symbol, args, _) => {
|
||||
write!(f, "({:?}", symbol)?;
|
||||
|
||||
for arg in args {
|
||||
@ -539,7 +544,7 @@ impl Type {
|
||||
}
|
||||
actual_type.substitute(substitutions);
|
||||
}
|
||||
Apply(_, args) => {
|
||||
Apply(_, args, _) => {
|
||||
for arg in args {
|
||||
arg.substitute(substitutions);
|
||||
}
|
||||
@ -549,62 +554,69 @@ impl Type {
|
||||
}
|
||||
}
|
||||
|
||||
// swap Apply with Alias if their module and tag match
|
||||
pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) {
|
||||
/// Swap Apply(rep_symbol, rep_args) with `actual`. Returns `Err` if there is an
|
||||
/// `Apply(rep_symbol, _)`, but the args don't match.
|
||||
pub fn substitute_alias(
|
||||
&mut self,
|
||||
rep_symbol: Symbol,
|
||||
rep_args: &[Type],
|
||||
actual: &Type,
|
||||
) -> Result<(), Region> {
|
||||
use Type::*;
|
||||
|
||||
match self {
|
||||
Function(args, closure, ret) => {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, actual);
|
||||
arg.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
closure.substitute_alias(rep_symbol, actual);
|
||||
ret.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
FunctionOrTagUnion(_, _, ext) => {
|
||||
ext.substitute_alias(rep_symbol, actual);
|
||||
closure.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
ret.substitute_alias(rep_symbol, rep_args, actual)
|
||||
}
|
||||
FunctionOrTagUnion(_, _, ext) => ext.substitute_alias(rep_symbol, rep_args, actual),
|
||||
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
|
||||
for (_, args) in tags {
|
||||
for x in args {
|
||||
x.substitute_alias(rep_symbol, actual);
|
||||
x.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
}
|
||||
ext.substitute_alias(rep_symbol, actual);
|
||||
ext.substitute_alias(rep_symbol, rep_args, actual)
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
for (_, x) in fields.iter_mut() {
|
||||
x.substitute_alias(rep_symbol, actual);
|
||||
x.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
ext.substitute_alias(rep_symbol, actual);
|
||||
ext.substitute_alias(rep_symbol, rep_args, actual)
|
||||
}
|
||||
Alias {
|
||||
actual: alias_actual,
|
||||
..
|
||||
} => {
|
||||
alias_actual.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
} => alias_actual.substitute_alias(rep_symbol, rep_args, actual),
|
||||
HostExposedAlias {
|
||||
actual: actual_type,
|
||||
..
|
||||
} => {
|
||||
actual_type.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
Apply(symbol, _) if *symbol == rep_symbol => {
|
||||
*self = actual.clone();
|
||||
} => actual_type.substitute_alias(rep_symbol, rep_args, actual),
|
||||
Apply(symbol, args, region) if *symbol == rep_symbol => {
|
||||
if args.len() == rep_args.len()
|
||||
&& args.iter().zip(rep_args.iter()).all(|(t1, t2)| t1 == t2)
|
||||
{
|
||||
*self = actual.clone();
|
||||
|
||||
if let Apply(_, args) = self {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, actual);
|
||||
if let Apply(_, args, _) = self {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Err(*region)
|
||||
}
|
||||
Apply(_, args) => {
|
||||
Apply(_, args, _) => {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_symbol, actual);
|
||||
arg.substitute_alias(rep_symbol, rep_args, actual)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
|
||||
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -639,8 +651,8 @@ impl Type {
|
||||
HostExposedAlias { name, actual, .. } => {
|
||||
name == &rep_symbol || actual.contains_symbol(rep_symbol)
|
||||
}
|
||||
Apply(symbol, _) if *symbol == rep_symbol => true,
|
||||
Apply(_, args) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
|
||||
Apply(symbol, _, _) if *symbol == rep_symbol => true,
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
|
||||
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false,
|
||||
}
|
||||
}
|
||||
@ -676,7 +688,7 @@ impl Type {
|
||||
..
|
||||
} => actual_type.contains_variable(rep_variable),
|
||||
HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable),
|
||||
Apply(_, args) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
|
||||
Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) => false,
|
||||
}
|
||||
}
|
||||
@ -753,7 +765,7 @@ impl Type {
|
||||
|
||||
actual_type.instantiate_aliases(region, aliases, var_store, introduced);
|
||||
}
|
||||
Apply(symbol, args) => {
|
||||
Apply(symbol, args, _) => {
|
||||
if let Some(alias) = aliases.get(symbol) {
|
||||
if args.len() != alias.type_variables.len() {
|
||||
*self = Type::Erroneous(Problem::BadTypeArguments {
|
||||
@ -882,7 +894,7 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
|
||||
accum.insert(*name);
|
||||
symbols_help(actual, accum);
|
||||
}
|
||||
Apply(symbol, args) => {
|
||||
Apply(symbol, args, _) => {
|
||||
accum.insert(*symbol);
|
||||
args.iter().for_each(|arg| symbols_help(arg, accum));
|
||||
}
|
||||
@ -967,7 +979,7 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
||||
}
|
||||
variables_help(actual, accum);
|
||||
}
|
||||
Apply(_, args) => {
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help(x, accum);
|
||||
}
|
||||
@ -1071,7 +1083,7 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
|
||||
}
|
||||
variables_help_detailed(actual, accum);
|
||||
}
|
||||
Apply(_, args) => {
|
||||
Apply(_, args, _) => {
|
||||
for x in args {
|
||||
variables_help_detailed(x, accum);
|
||||
}
|
||||
@ -1174,6 +1186,7 @@ pub enum Reason {
|
||||
RecordUpdateValue(Lowercase),
|
||||
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),
|
||||
RecordDefaultField(Lowercase),
|
||||
NumericLiteralSuffix,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
@ -1241,6 +1254,16 @@ pub struct Alias {
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
impl Alias {
|
||||
pub fn header_region(&self) -> Region {
|
||||
Region::across_all(
|
||||
[self.region]
|
||||
.iter()
|
||||
.chain(self.type_variables.iter().map(|tv| &tv.region)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
|
||||
pub enum Problem {
|
||||
CanonicalizationProblem,
|
||||
|
@ -285,7 +285,12 @@ fn unify_structure(
|
||||
// unify the structure with this unrecursive tag union
|
||||
unify_pool(subs, pool, ctx.first, *structure, ctx.mode)
|
||||
}
|
||||
_ => todo!("rec structure {:?}", &flat_type),
|
||||
// Only tag unions can be recursive; everything else is an error.
|
||||
_ => mismatch!(
|
||||
"trying to unify {:?} with recursive type var {:?}",
|
||||
&flat_type,
|
||||
structure
|
||||
),
|
||||
},
|
||||
|
||||
Structure(ref other_flat_type) => {
|
||||
|
@ -56,11 +56,7 @@ mod insert_doc_syntax_highlighting {
|
||||
}
|
||||
}
|
||||
|
||||
pub const HELLO_WORLD: &str = r#"
|
||||
app "test-app"
|
||||
packages { pf: "platform" }
|
||||
imports []
|
||||
provides [ main ] to pf
|
||||
pub const HELLO_WORLD: &str = r#"interface Test exposes [ ] imports [ ]
|
||||
|
||||
main = "Hello, world!"
|
||||
|
||||
|
@ -206,7 +206,7 @@ impl<'a> EdModule<'a> {
|
||||
pub mod test_ed_model {
|
||||
use crate::editor::ed_error::EdResult;
|
||||
use crate::editor::mvc::ed_model;
|
||||
use crate::editor::resources::strings::HELLO_WORLD;
|
||||
use crate::editor::resources::strings::{HELLO_WORLD, PLATFORM_STR};
|
||||
use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection;
|
||||
use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl;
|
||||
use crate::ui::text::caret_w_select::CaretPos;
|
||||
@ -222,6 +222,7 @@ pub mod test_ed_model {
|
||||
use roc_module::symbol::IdentIds;
|
||||
use roc_module::symbol::ModuleIds;
|
||||
use roc_types::subs::VarStore;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
@ -290,6 +291,15 @@ pub mod test_ed_model {
|
||||
*clean_code_str = full_code.join("\n");
|
||||
|
||||
let temp_dir = tempdir().expect("Failed to create temporary directory for test.");
|
||||
|
||||
let platform_dir = temp_dir.path().join("platform");
|
||||
fs::create_dir(platform_dir.clone()).expect("Failed to create platform directory");
|
||||
let package_config_path = platform_dir.join("Package-Config.roc");
|
||||
let mut package_config_file =
|
||||
File::create(package_config_path).expect("Failed to create Package-Config.roc");
|
||||
writeln!(package_config_file, "{}", PLATFORM_STR)
|
||||
.expect("Failed to write to Package-Config.roc");
|
||||
|
||||
let temp_file_path_buf =
|
||||
PathBuf::from([Uuid::new_v4().to_string(), ".roc".to_string()].join(""));
|
||||
let temp_file_full_path = temp_dir.path().join(temp_file_path_buf);
|
||||
|
@ -25,3 +25,15 @@ main = "Hello, world!"
|
||||
|
||||
|
||||
"#;
|
||||
|
||||
pub const PLATFORM_STR: &str = r#"
|
||||
platform "test-platform"
|
||||
requires {} { main : Str }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
|
||||
mainForHost : Str
|
||||
mainForHost = main
|
||||
"#;
|
||||
|
10
examples/benchmarks/platform/Effect.roc
Normal file
10
examples/benchmarks/platform/Effect.roc
Normal file
@ -0,0 +1,10 @@
|
||||
hosted Effect
|
||||
exposes [ Effect, after, map, always, forever, loop, putLine, putInt, getInt ]
|
||||
imports []
|
||||
generates Effect with [ after, map, always, forever, loop ]
|
||||
|
||||
putLine : Str -> Effect {}
|
||||
|
||||
putInt : I64 -> Effect {}
|
||||
|
||||
getInt : Effect { value : I64, errorCode : [ A, B ], isError : Bool }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user