Merge branch 'trunk' into cleanup-cli-example

This commit is contained in:
Richard Feldman 2022-02-05 20:53:55 -05:00 committed by GitHub
commit 48bd267892
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
145 changed files with 6549 additions and 3240 deletions

View File

@ -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
View File

@ -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"

View File

@ -33,6 +33,10 @@ members = [
"code_markup",
"error_macros",
"reporting",
"repl_cli",
"repl_eval",
"repl_test",
"repl_wasm",
"roc_std",
"test_utils",
"utils",

View File

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

View File

@ -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),

View File

@ -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 {

View File

@ -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" }

View File

@ -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: &[],
},
},
}
}
}

View File

@ -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";

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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
}

View File

@ -4,7 +4,6 @@ platform "examples/multi-module"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View File

@ -4,7 +4,6 @@ platform "examples/multi-dep-thunk"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View File

@ -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
View File

@ -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"

View File

@ -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,
}
}

View File

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

View File

@ -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();

View File

@ -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;

View 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));
}

View File

@ -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);

View File

@ -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");

View File

@ -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,

View File

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

View File

@ -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";

View File

@ -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
};

View File

@ -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,
)
}

View File

@ -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(())
}
}
}

View File

@ -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,

View File

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

View File

@ -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;

View File

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

View File

@ -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),
}

View File

@ -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(_)

View File

@ -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(_, _)

View File

@ -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]

View File

@ -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()
}
}
}
}

View File

@ -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() {

View File

@ -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,
));
}

View File

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

View File

@ -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);
}

View File

@ -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,

View File

@ -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('-');
}

View File

@ -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,

View File

@ -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());

View File

@ -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);
}

View File

@ -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]

View File

@ -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);
}

View File

@ -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 = {

View File

@ -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);

View File

@ -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."),

View File

@ -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;

View 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

View File

@ -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"),
}
}
}

View File

@ -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, &[])
}
}
}

View File

@ -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);

View File

@ -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())),

View File

@ -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,

View File

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

View File

@ -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,

View File

@ -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 }`

View File

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

View File

@ -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.

View File

@ -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,

View File

@ -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
}

View File

@ -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: [],
},
}

View File

@ -0,0 +1 @@
hosted Foo exposes [] imports [] generates Bar with []

View File

@ -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: [],

View File

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

View File

@ -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: [

View File

@ -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 {}
}

View File

@ -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: [],
},
}

View File

@ -0,0 +1,2 @@
interface T exposes [] imports []

View File

@ -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,
],
},
}

View File

@ -0,0 +1,18 @@
hosted Foo
exposes
[
Stuff,
Things,
somethingElse,
]
imports
[
Blah,
Baz.{ stuff, things },
]
generates Bar with
[
map,
after,
loop,
]

View File

@ -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: [

View File

@ -4,4 +4,3 @@ platform "foo/barbaz"
packages { foo: "./foo" }
imports []
provides [ mainForHost ]
effects fx.Effect {}

View File

@ -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,
],
},
)

View File

@ -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,
}

View File

@ -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: [

View File

@ -4,7 +4,6 @@ platform "test/types"
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : App Flags Model
mainForHost = main

View File

@ -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,

View File

@ -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)]

View File

@ -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);

View File

@ -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 }"#,
)
}
}

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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),

View File

@ -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) {

View File

@ -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,

View File

@ -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,

View File

@ -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) => {

View File

@ -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!"

View File

@ -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);

View File

@ -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
"#;

View 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