Merge branch 'trunk' into constrain_closure

This commit is contained in:
Lucas 2021-08-07 09:30:54 -04:00 committed by GitHub
commit f9d1010bfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 692 additions and 443 deletions

View File

@ -6,7 +6,7 @@
To build the compiler, you need these installed:
* `libunwind` (macOS should already have this one installed)
* `libc++-dev`
* `libc++-dev` and `libc++abi-dev`
* Python 2.7 (Windows only), `python-is-python3` (Ubuntu)
* [Zig](https://ziglang.org/), see below for version
* LLVM, see below for version
@ -21,7 +21,7 @@ For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir).
### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be done with `sudo apt-get install libunwind-dev`).
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev libc++abi-dev`.)
### libcxb libraries
@ -151,6 +151,8 @@ That will help us improve this document for everyone who reads it in the future!
### LLVM installation on Linux
For a current list of all dependency versions and their names in apt, see the Earthfile.
On some Linux systems we've seen the error "failed to run custom build command for x11".
On Ubuntu, running `sudo apt install pkg-config cmake libx11-dev` fixed this.
@ -205,8 +207,8 @@ Create `~/.cargo/config.toml` if it does not exist and add this to it:
rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "target-cpu=native"]
```
Then install `lld` version 9 (e.g. with `$ sudo apt-get install lld-9`)
Then install `lld` version 12 (e.g. with `$ sudo apt-get install lld-12`)
and add make sure there's a `ld.lld` executable on your `PATH` which
is symlinked to `lld-9`.
is symlinked to `lld-12`.
That's it! Enjoy the faster builds.

View File

@ -10,9 +10,8 @@ Check [Build from source](BUILDING_FROM_SOURCE.md) for instructions.
## Running Tests
To run all tests as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
To run all tests and checks as they are run on CI, [install earthly](https://earthly.dev/get-earthly) and run:
```
mkdir -p sccache_dir
earthly +test-all
```
@ -22,6 +21,7 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com/join/rz7n4d42v7tfilp3njzbm5eg/) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
- It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review!
- Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security.
## Can we do better?

View File

@ -25,7 +25,7 @@ Run examples as follows:
1. Navigate to `/examples`
2. Run with:
```
cargo run run hello-world/Hello.roc
cargo run hello-world/Hello.roc
```
Some examples like `examples/benchmarks/NQueens.roc` require input after running.
For NQueens, input 10 in the terminal and press enter.

View File

@ -57,7 +57,7 @@ roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor", optional = true }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
const_format = "0.2.8"
const_format = "0.2"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
im = "14" # im and im-rc should always have the same version!

View File

@ -1,6 +1,9 @@
#[macro_use]
extern crate clap;
#[macro_use]
extern crate const_format;
use build::{BuildOutcome, BuiltFile};
use bumpalo::Bump;
use clap::{App, AppSettings, Arg, ArgMatches};
@ -32,9 +35,10 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc")
.version(crate_version!())
.version(concatcp!(crate_version!(), "\n"))
.about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!")
.subcommand(App::new(CMD_BUILD)
.about("Build a program")
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file to build")
@ -43,7 +47,7 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
@ -60,7 +64,7 @@ pub fn build_app<'a>() -> App<'a> {
)
)
.subcommand(App::new(CMD_RUN)
.about("Build and run a program")
.about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`")
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
@ -76,7 +80,7 @@ pub fn build_app<'a>() -> App<'a> {
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to build and run")
.help("The .roc file of an app to run")
.required(true),
)
.arg(
@ -98,6 +102,32 @@ pub fn build_app<'a>() -> App<'a> {
.help("The directory or files to build documentation for")
)
)
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to build and run")
.required(false),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
.help("Arguments to pass into the app being run")
.requires(ROC_FILE)
.multiple(true),
);
if cfg!(feature = "editor") {
@ -215,7 +245,7 @@ pub fn build(target: &Triple, matches: &ArgMatches, config: BuildConfig) -> io::
// Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like:
//
// roc run app.roc foo bar baz
// roc app.roc foo bar baz
//
// ...and have it so that app.roc will receive only `foo`,
// `bar`, and `baz` as its arguments.
@ -263,16 +293,16 @@ fn roc_run(cmd: &mut Command) -> io::Result<i32> {
.spawn()
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
.wait()
.expect("TODO gracefully handle block_on failing when roc run spawns a subprocess for the compiled app");
.expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
// `roc run` exits with the same status code as the app it ran.
// `roc [FILE]` exits with the same status code as the app it ran.
//
// If you want to know whether there were compilation problems
// via status code, use either `roc build` or `roc check` instead!
match exit_status.code() {
Some(code) => Ok(code),
None => {
todo!("TODO gracefully handle the roc run subprocess terminating with a signal.");
todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal.");
}
}
}

View File

@ -21,10 +21,23 @@ fn main() -> io::Result<()> {
let exit_code = match matches.subcommand_name() {
None => {
launch_editor(&[])?;
match matches.index_of(ROC_FILE) {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
// rustc couldn't infer the error type here
Result::<i32, io::Error>::Ok(0)
build(
&Triple::host(),
&matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
)
}
None => {
launch_editor(&[])?;
Ok(0)
}
}
}
Some(CMD_BUILD) => Ok(build(
&Triple::host(),
@ -32,14 +45,10 @@ fn main() -> io::Result<()> {
BuildConfig::BuildOnly,
)?),
Some(CMD_RUN) => {
let subcmd_matches = matches.subcommand_matches(CMD_RUN).unwrap();
let roc_file_arg_index = subcmd_matches.index_of(ROC_FILE).unwrap() + 1; // Not sure why this +1 is necessary, but it is!
// TODO remove CMD_RUN altogether if it is currently September 2021 or later.
println!("`roc run` is deprecated! (You no longer need the `run` - just do `roc [FILE]` instead of `roc run [FILE]` like before.");
Ok(build(
&Triple::host(),
subcmd_matches,
BuildConfig::BuildAndRun { roc_file_arg_index },
)?)
Ok(1)
}
Some(CMD_REPL) => {
repl::main()?;

View File

@ -151,13 +151,13 @@ fn jit_to_ast_help<'a>(
Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => {
Ok(struct_to_ast(env, ptr, field_layouts, fields))
Ok(struct_to_ast(env, ptr, field_layouts, *fields))
}
Content::Structure(FlatType::EmptyRecord) => Ok(struct_to_ast(
env,
ptr,
field_layouts,
&RecordFields::with_capacity(0),
RecordFields::empty(),
)),
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1);
@ -426,7 +426,7 @@ fn ptr_to_ast<'a>(
}
Layout::Struct(field_layouts) => match content {
Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, ptr, field_layouts, fields)
struct_to_ast(env, ptr, field_layouts, *fields)
}
Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1);
@ -438,7 +438,7 @@ fn ptr_to_ast<'a>(
single_tag_union_to_ast(env, ptr, field_layouts, *tag_name.clone(), &[])
}
Content::Structure(FlatType::EmptyRecord) => {
struct_to_ast(env, ptr, &[], &RecordFields::with_capacity(0))
struct_to_ast(env, ptr, &[], RecordFields::empty())
}
other => {
unreachable!(
@ -557,12 +557,17 @@ fn struct_to_ast<'a>(
env: &Env<'a, '_>,
ptr: *const u8,
field_layouts: &'a [Layout<'a>],
sorted_fields: &RecordFields,
record_fields: RecordFields,
) -> Expr<'a> {
let arena = env.arena;
let subs = env.subs;
let mut output = Vec::with_capacity_in(field_layouts.len(), arena);
let sorted_fields: Vec<_> = Vec::from_iter_in(
record_fields.sorted_iterator(env.subs, Variable::EMPTY_RECORD),
env.arena,
);
if sorted_fields.len() == 1 {
// this is a 1-field wrapper record around another record or 1-tag tag union
let (label, field) = sorted_fields.into_iter().next().unwrap();
@ -595,8 +600,10 @@ fn struct_to_ast<'a>(
// We'll advance this as we iterate through the fields
let mut field_ptr = ptr;
for ((label, field), field_layout) in sorted_fields.iter().zip(field_layouts.iter()) {
let content = subs.get_content_without_compacting(*field.as_inner());
for ((label, field), field_layout) in sorted_fields.into_iter().zip(field_layouts.iter()) {
let var = field.into_inner();
let content = subs.get_content_without_compacting(var);
let loc_expr = &*arena.alloc(Located {
value: ptr_to_ast(env, field_ptr, field_layout, content),
region: Region::zero(),
@ -638,7 +645,11 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1);
let (label, field) = fields.iter().next().unwrap();
let (label, field) = fields
.sorted_iterator(env.subs, Variable::EMPTY_RECORD)
.next()
.unwrap();
let loc_label = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
@ -750,7 +761,11 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
FlatType::Record(fields, _) => {
debug_assert_eq!(fields.len(), 1);
let (label, field) = fields.iter().next().unwrap();
let (label, field) = fields
.sorted_iterator(env.subs, Variable::EMPTY_RECORD)
.next()
.unwrap();
let loc_label = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),
@ -866,7 +881,11 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
// Its type signature will tell us that.
debug_assert_eq!(fields.len(), 1);
let (label, field) = fields.iter().next().unwrap();
let (label, field) = fields
.sorted_iterator(env.subs, Variable::EMPTY_RECORD)
.next()
.unwrap();
let loc_label = Located {
value: &*arena.alloc_str(label.as_str()),
region: Region::zero(),

View File

@ -1209,15 +1209,35 @@ fn layout_from_flat_type<'a>(
}
Record(fields, ext_var) => {
// extract any values from the ext_var
let mut fields_map = MutMap::default();
fields_map.extend(fields);
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut fields_map) {
Ok(()) | Err((_, Content::FlexVar(_))) => {}
Err(_) => unreachable!("this would have been a type error"),
}
// discard optional fields
let mut layouts = sort_stored_record_fields(env, fields_map);
let pairs_it = fields
.unsorted_iterator(subs, ext_var)
.filter_map(|(label, field)| {
// drop optional fields
let var = match field {
RecordField::Optional(_) => return None,
RecordField::Required(var) => var,
RecordField::Demanded(var) => var,
};
Some((
label,
Layout::from_var(env, var).expect("invalid layout from var"),
))
});
let mut pairs = Vec::from_iter_in(pairs_it, arena);
pairs.sort_by(|(label1, layout1), (label2, layout2)| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1).then(label1.cmp(label2))
});
let mut layouts = Vec::from_iter_in(pairs.into_iter().map(|t| t.1), arena);
if layouts.len() == 1 {
// If the record has only one field that isn't zero-sized,
@ -1396,43 +1416,6 @@ fn sort_record_fields_help<'a>(
sorted_fields
}
// drops optional fields
fn sort_stored_record_fields<'a>(
env: &mut Env<'a, '_>,
fields_map: MutMap<Lowercase, RecordField<Variable>>,
) -> Vec<'a, Layout<'a>> {
// Sort the fields by label
let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), env.arena);
for (label, field) in fields_map {
let var = match field {
RecordField::Demanded(v) => v,
RecordField::Required(v) => v,
RecordField::Optional(_) => {
continue;
}
};
let layout = Layout::from_var(env, var).expect("invalid layout from var");
sorted_fields.push((label, layout));
}
sorted_fields.sort_by(|(label1, layout1), (label2, layout2)| {
let ptr_bytes = 8;
let size1 = layout1.alignment_bytes(ptr_bytes);
let size2 = layout2.alignment_bytes(ptr_bytes);
size2.cmp(&size1).then(label1.cmp(label2))
});
let mut result = Vec::with_capacity_in(sorted_fields.len(), env.arena);
result.extend(sorted_fields.into_iter().map(|t| t.1));
result
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum UnionVariant<'a> {
Never,

View File

@ -5,7 +5,7 @@ use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::solved_types::Solved;
use roc_types::subs::{
Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, SubsSlice, Variable,
Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, SubsSlice, Variable,
};
use roc_types::types::Type::{self, *};
use roc_types::types::{Alias, Category, ErrorType, PatternCategory};
@ -698,7 +698,11 @@ fn type_to_variable(
Err((new, _)) => new,
};
let record_fields = field_vars.into_iter().collect();
let mut all_fields: Vec<_> = field_vars.into_iter().collect();
all_fields.sort_unstable_by(RecordFields::compare);
let record_fields = RecordFields::insert_into_subs(subs, all_fields);
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
register(subs, rank, pools, content)
@ -1089,9 +1093,9 @@ fn adjust_rank_content(
Record(fields, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for var in fields.iter_variables() {
rank =
rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
for index in fields.iter_variables() {
let var = subs[index];
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
}
rank
@ -1239,8 +1243,9 @@ fn instantiate_rigids_help(
EmptyRecord | EmptyTagUnion | Erroneous(_) => {}
Record(fields, ext_var) => {
for var in fields.iter_variables() {
instantiate_rigids_help(subs, max_rank, pools, *var);
for index in fields.iter_variables() {
let var = subs[index];
instantiate_rigids_help(subs, max_rank, pools, var);
}
instantiate_rigids_help(subs, max_rank, pools, ext_var);
@ -1386,12 +1391,30 @@ fn deep_copy_var_help(
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(mut fields, ext_var) => {
for var in fields.iter_variables_mut() {
*var = deep_copy_var_help(subs, max_rank, pools, *var);
Record(fields, ext_var) => {
let mut new_vars = Vec::with_capacity(fields.len());
for index in fields.iter_variables() {
let var = subs[index];
let copy_var = deep_copy_var_help(subs, max_rank, pools, var);
new_vars.push(copy_var);
}
Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var))
let it = fields.iter_all().zip(new_vars).map(|((i1, _, i3), var)| {
let label = subs[i1].clone();
let record_field = subs[i3].map(|_| var);
(label, record_field)
});
// lifetime troubles
let vec: Vec<_> = it.collect();
let record_fields = RecordFields::insert_into_subs(subs, vec);
Record(
record_fields,
deep_copy_var_help(subs, max_rank, pools, ext_var),
)
}
TagUnion(tags, ext_var) => {

View File

@ -81,7 +81,7 @@ fn fn_record() {
assert_evals_to!(
indoc!(
r#"
getRec = \x -> { y: 17, x, z: 19 }
getRec = \x -> { y: "foo", x, z: 19 }
(getRec 15).x
"#
@ -761,14 +761,19 @@ fn return_nested_record() {
}
#[test]
fn accessor() {
fn accessor_twice() {
assert_evals_to!(".foo { foo: 4 } + .foo { bar: 6.28, foo: 3 } ", 7, i64);
}
#[test]
fn accessor_multi_element_record() {
assert_evals_to!(
indoc!(
r#"
.foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
.foo { foo: 4, bar: "foo" }
"#
),
7,
4,
i64
);
}

View File

@ -154,8 +154,9 @@ fn find_names_needed(
find_names_needed(*ret_var, subs, roots, root_appearances, names_taken);
}
Structure(Record(sorted_fields, ext_var)) => {
for var in sorted_fields.iter_variables() {
find_names_needed(*var, subs, roots, root_appearances, names_taken);
for index in sorted_fields.iter_variables() {
let var = subs[index];
find_names_needed(var, subs, roots, root_appearances, names_taken);
}
find_names_needed(*ext_var, subs, roots, root_appearances, names_taken);
@ -418,7 +419,7 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
let RecordStructure {
fields: sorted_fields,
ext,
} = gather_fields(subs, fields.clone(), *ext_var);
} = gather_fields(subs, *fields, *ext_var);
let ext_var = ext;
if fields.is_empty() {
@ -428,9 +429,11 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
let mut any_written_yet = false;
for (label, field_var) in sorted_fields {
for (label, record_field) in sorted_fields {
use RecordField::*;
let var = *record_field.as_inner();
if any_written_yet {
buf.push_str(", ");
} else {
@ -438,19 +441,10 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
}
buf.push_str(label.as_str());
let var = match field_var {
Optional(var) => {
buf.push_str(" ? ");
var
}
Required(var) => {
buf.push_str(" : ");
var
}
Demanded(var) => {
buf.push_str(" : ");
var
}
match record_field {
Optional(_) => buf.push_str(" ? "),
Required(_) => buf.push_str(" : "),
Demanded(_) => buf.push_str(" : "),
};
write_content(
@ -583,8 +577,12 @@ pub fn chase_ext_record(
match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => {
for (field_name, record_field) in sub_fields {
fields.insert(field_name.clone(), record_field);
for (i1, i2, i3) in sub_fields.iter_all() {
let label = &subs[i1];
let var = subs[i2];
let record_field = subs[i3].map(|_| var);
fields.insert(label.clone(), record_field);
}
chase_ext_record(subs, *sub_ext, fields)

View File

@ -399,11 +399,15 @@ impl SolvedType {
Record(fields, ext_var) => {
let mut new_fields = Vec::with_capacity(fields.len());
for (label, field) in fields {
let solved_type =
field.map(|var| Self::from_var_help(subs, recursion_vars, *var));
for (i1, i2, i3) in fields.iter_all() {
let field_name: Lowercase = subs[i1].clone();
let variable: Variable = subs[i2];
let record_field: RecordField<()> = subs[i3];
new_fields.push((label.clone(), solved_type));
let solved_type =
record_field.map(|_| Self::from_var_help(subs, recursion_vars, variable));
new_fields.push((field_name, solved_type));
}
let ext = Self::from_var_help(subs, recursion_vars, *ext_var);

View File

@ -2,17 +2,16 @@ use crate::types::{name_type_var, ErrorType, Problem, RecordField, TypeExt};
use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use std::cmp::Ordering;
use std::fmt;
use std::iter::{once, Extend, FromIterator, Iterator, Map, Zip};
use std::iter::{once, Iterator, Map};
use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
// if your changes cause this number to go down, great!
// please change it to the lower number.
// if it went up, maybe check that the change is really required
static_assertions::assert_eq_size!([u8; 104], Descriptor);
static_assertions::assert_eq_size!([u8; 88], Content);
static_assertions::assert_eq_size!([u8; 80], FlatType);
static_assertions::assert_eq_size!([u8; 72], Descriptor);
static_assertions::assert_eq_size!([u8; 56], Content);
static_assertions::assert_eq_size!([u8; 48], FlatType);
static_assertions::assert_eq_size!([u8; 48], Problem);
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
@ -54,22 +53,30 @@ struct ErrorTypeState {
pub struct Subs {
utable: UnificationTable<InPlace<Variable>>,
pub variables: Vec<Variable>,
tag_names: Vec<TagName>,
field_names: Vec<Lowercase>,
record_fields: Vec<RecordField<()>>,
pub tag_names: Vec<TagName>,
pub field_names: Vec<Lowercase>,
pub record_fields: Vec<RecordField<()>>,
}
/// A slice into the Vec<T> of subs
///
/// The starting position is a u32 which should be plenty
/// We limit slices to u16::MAX = 65535 elements
pub struct SubsSlice<T> {
start: u32,
length: u16,
_marker: std::marker::PhantomData<T>,
}
/// An index into the Vec<T> of subs
pub struct SubsIndex<T> {
start: u32,
_marker: std::marker::PhantomData<T>,
}
// make `subs[some_index]` work. The types/trait resolution make sure we get the
// element from the right vector
impl std::ops::Index<SubsIndex<Variable>> for Subs {
type Output = Variable;
@ -84,6 +91,47 @@ impl std::ops::IndexMut<SubsIndex<Variable>> for Subs {
}
}
impl std::ops::Index<SubsIndex<Lowercase>> for Subs {
type Output = Lowercase;
fn index(&self, index: SubsIndex<Lowercase>) -> &Self::Output {
&self.field_names[index.start as usize]
}
}
impl std::ops::IndexMut<SubsIndex<Lowercase>> for Subs {
fn index_mut(&mut self, index: SubsIndex<Lowercase>) -> &mut Self::Output {
&mut self.field_names[index.start as usize]
}
}
impl std::ops::Index<SubsIndex<RecordField<()>>> for Subs {
type Output = RecordField<()>;
fn index(&self, index: SubsIndex<RecordField<()>>) -> &Self::Output {
&self.record_fields[index.start as usize]
}
}
impl std::ops::IndexMut<SubsIndex<RecordField<()>>> for Subs {
fn index_mut(&mut self, index: SubsIndex<RecordField<()>>) -> &mut Self::Output {
&mut self.record_fields[index.start as usize]
}
}
// custom debug
impl<T> std::fmt::Debug for SubsIndex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"SubsIndex<{}>({})",
std::any::type_name::<T>(),
self.start
)
}
}
impl<T> std::fmt::Debug for SubsSlice<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
@ -94,6 +142,8 @@ impl<T> std::fmt::Debug for SubsSlice<T> {
}
}
// derive of copy and clone does not play well with PhantomData
impl<T> Copy for SubsIndex<T> {}
impl<T> Clone for SubsIndex<T> {
@ -153,6 +203,15 @@ impl<T> SubsSlice<T> {
}
}
impl<T> SubsIndex<T> {
pub fn new(start: u32) -> Self {
Self {
start,
_marker: std::marker::PhantomData,
}
}
}
impl<T> IntoIterator for SubsSlice<T> {
type Item = SubsIndex<T>;
@ -173,17 +232,23 @@ fn u32_to_index<T>(i: u32) -> SubsIndex<T> {
pub trait GetSubsSlice<T> {
fn get_subs_slice(&self, subs_slice: SubsSlice<T>) -> &[T];
fn get_subs_slice_mut(&mut self, subs_slice: SubsSlice<T>) -> &mut [T];
}
impl GetSubsSlice<Variable> for Subs {
fn get_subs_slice(&self, subs_slice: SubsSlice<Variable>) -> &[Variable] {
subs_slice.get_slice(&self.variables)
}
}
fn get_subs_slice_mut(&mut self, subs_slice: SubsSlice<Variable>) -> &mut [Variable] {
subs_slice.get_slice_mut(&mut self.variables)
impl GetSubsSlice<RecordField<()>> for Subs {
fn get_subs_slice(&self, subs_slice: SubsSlice<RecordField<()>>) -> &[RecordField<()>] {
subs_slice.get_slice(&self.record_fields)
}
}
impl GetSubsSlice<Lowercase> for Subs {
fn get_subs_slice(&self, subs_slice: SubsSlice<Lowercase>) -> &[Lowercase] {
subs_slice.get_slice(&self.field_names)
}
}
@ -782,232 +847,182 @@ pub enum Builtin {
EmptyRecord,
}
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug)]
pub struct RecordFields {
field_names: Vec<Lowercase>,
variables: Vec<Variable>,
field_type: Vec<RecordField<()>>,
pub length: u16,
pub field_names_start: u32,
pub variables_start: u32,
pub field_types_start: u32,
}
fn first<K: Ord, V>(x: &(K, V), y: &(K, V)) -> std::cmp::Ordering {
x.0.cmp(&y.0)
}
pub type SortedIterator<'a> = Box<dyn Iterator<Item = (Lowercase, RecordField<Variable>)> + 'a>;
impl RecordFields {
pub fn with_capacity(capacity: usize) -> Self {
Self {
field_names: Vec::with_capacity(capacity),
variables: Vec::with_capacity(capacity),
field_type: Vec::with_capacity(capacity),
}
}
pub fn len(&self) -> usize {
let answer = self.field_names.len();
debug_assert_eq!(answer, self.variables.len());
debug_assert_eq!(answer, self.field_type.len());
answer
self.length as usize
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter_variables(&self) -> impl Iterator<Item = &Variable> {
self.variables.iter()
pub fn empty() -> Self {
Self {
length: 0,
field_names_start: 0,
variables_start: 0,
field_types_start: 0,
}
}
pub fn iter_variables_mut(&mut self) -> impl Iterator<Item = &mut Variable> {
self.variables.iter_mut()
pub fn iter_variables(&self) -> impl Iterator<Item = SubsIndex<Variable>> {
let slice = SubsSlice::new(self.variables_start, self.length);
slice.into_iter()
}
pub fn iter(&self) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> {
self.into_iter()
}
pub fn has_only_optional_fields(&self, subs: &Subs) -> bool {
let slice: SubsSlice<RecordField<()>> = SubsSlice::new(self.field_types_start, self.length);
pub fn has_only_optional_fields(&self) -> bool {
self.field_type
subs.get_subs_slice(slice)
.iter()
.all(|field| matches!(field, RecordField::Optional(_)))
}
pub fn from_vec(mut vec: Vec<(Lowercase, RecordField<Variable>)>) -> Self {
// we assume there are no duplicate field names in there
vec.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2));
Self::from_sorted_vec(vec)
pub fn compare(
x: &(Lowercase, RecordField<Variable>),
y: &(Lowercase, RecordField<Variable>),
) -> std::cmp::Ordering {
first(x, y)
}
pub fn from_sorted_vec(vec: Vec<(Lowercase, RecordField<Variable>)>) -> Self {
let mut result = RecordFields::with_capacity(vec.len());
pub fn insert_into_subs<I>(subs: &mut Subs, input: I) -> Self
where
I: IntoIterator<Item = (Lowercase, RecordField<Variable>)>,
{
let field_names_start = subs.field_names.len() as u32;
let variables_start = subs.variables.len() as u32;
let field_types_start = subs.record_fields.len() as u32;
result.extend(vec);
let mut length = 0;
for (k, v) in input {
let var = *v.as_inner();
let record_field = v.map(|_| ());
result
}
subs.field_names.push(k);
subs.variables.push(var);
subs.record_fields.push(record_field);
pub fn merge(self, other: Self) -> Self {
if other.is_empty() {
return self;
length += 1;
}
// maximum final size (if there is no overlap at all)
let final_size = self.len() + other.len();
let mut result = Self::with_capacity(final_size);
let mut it1 = self.into_iter().peekable();
let mut it2 = other.into_iter().peekable();
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
let next_element = match which {
Some(Ordering::Less) => it1.next(),
Some(Ordering::Equal) => {
let _ = it2.next();
it1.next()
}
Some(Ordering::Greater) => it2.next(),
None => break,
};
result.extend([next_element.unwrap()]);
}
result
}
pub fn separate(self, other: Self) -> SeparateRecordFields {
let max_common = self.len().min(other.len());
let mut result = SeparateRecordFields {
only_in_1: RecordFields::with_capacity(self.len()),
only_in_2: RecordFields::with_capacity(other.len()),
in_both: Vec::with_capacity(max_common),
};
let mut it1 = self.into_iter().peekable();
let mut it2 = other.into_iter().peekable();
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
match which {
Some(Ordering::Less) => result.only_in_1.extend(it1.next()),
Some(Ordering::Equal) => {
let (label, field1) = it1.next().unwrap();
let (_, field2) = it2.next().unwrap();
result.in_both.push((label, field1, field2));
}
Some(Ordering::Greater) => result.only_in_2.extend(it2.next()),
None => break,
};
}
result
}
}
pub struct SeparateRecordFields {
pub only_in_1: RecordFields,
pub only_in_2: RecordFields,
pub in_both: Vec<(Lowercase, RecordField<Variable>, RecordField<Variable>)>,
}
impl Extend<(Lowercase, RecordField<Variable>)> for RecordFields {
fn extend<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(&mut self, iter: T) {
for (name, record_field) in iter.into_iter() {
self.field_names.push(name);
self.field_type.push(record_field.map(|_| ()));
self.variables.push(record_field.into_inner());
RecordFields {
length,
field_names_start,
variables_start,
field_types_start,
}
}
}
#[inline(always)]
pub fn unsorted_iterator<'a>(
&'a self,
subs: &'a Subs,
ext: Variable,
) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a {
let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext);
impl FromIterator<(Lowercase, RecordField<Variable>)> for RecordFields {
fn from_iter<T: IntoIterator<Item = (Lowercase, RecordField<Variable>)>>(iter: T) -> Self {
let vec: Vec<_> = iter.into_iter().collect();
Self::from_vec(vec)
it
}
}
impl<'a> FromIterator<(&'a Lowercase, RecordField<Variable>)> for RecordFields {
fn from_iter<T: IntoIterator<Item = (&'a Lowercase, RecordField<Variable>)>>(iter: T) -> Self {
let vec: Vec<_> = iter.into_iter().map(|(a, b)| (a.clone(), b)).collect();
Self::from_vec(vec)
/// Get a sorted iterator over the fields of this record type
///
/// Implementation: When the record has an `ext` variable that is the empty record, then
/// we read the (assumed sorted) fields directly from Subs. Otherwise we have to chase the
/// ext var, then sort the fields.
///
/// Hopefully the inline will get rid of the Box in practice
#[inline(always)]
pub fn sorted_iterator<'a>(&'_ self, subs: &'a Subs, ext: Variable) -> SortedIterator<'a> {
self.sorted_iterator_and_ext(subs, ext).0
}
}
impl IntoIterator for RecordFields {
type Item = (Lowercase, RecordField<Variable>);
#[inline(always)]
pub fn sorted_iterator_and_ext<'a>(
&'_ self,
subs: &'a Subs,
ext: Variable,
) -> (SortedIterator<'a>, Variable) {
if is_empty_record(subs, ext) {
(
Box::new(self.iter_all().map(move |(i1, i2, i3)| {
let field_name: Lowercase = subs[i1].clone();
let variable = subs[i2];
let record_field: RecordField<Variable> = subs[i3].map(|_| variable);
#[allow(clippy::type_complexity)]
type IntoIter = Map<
Zip<
Zip<std::vec::IntoIter<Lowercase>, std::vec::IntoIter<Variable>>,
std::vec::IntoIter<RecordField<()>>,
>,
fn(((Lowercase, Variable), RecordField<()>)) -> (Lowercase, RecordField<Variable>),
>;
(field_name, record_field)
})),
ext,
)
} else {
let record_structure = crate::types::gather_fields(subs, *self, ext);
fn into_iter(self) -> Self::IntoIter {
self.field_names
(
Box::new(record_structure.fields.into_iter()),
record_structure.ext,
)
}
}
pub fn iter_all(
&self,
) -> impl Iterator<
Item = (
SubsIndex<Lowercase>,
SubsIndex<Variable>,
SubsIndex<RecordField<()>>,
),
> {
let range1 = self.field_names_start..self.field_names_start + self.length as u32;
let range2 = self.variables_start..self.variables_start + self.length as u32;
let range3 = self.field_types_start..self.field_types_start + self.length as u32;
let it = range1
.into_iter()
.zip(self.variables.into_iter())
.zip(self.field_type.into_iter())
.map(record_fields_into_iterator_help)
.zip(range2.into_iter())
.zip(range3.into_iter());
it.map(|((i1, i2), i3)| (SubsIndex::new(i1), SubsIndex::new(i2), SubsIndex::new(i3)))
}
}
fn record_fields_into_iterator_help(
arg: ((Lowercase, Variable), RecordField<()>),
) -> (Lowercase, RecordField<Variable>) {
let ((name, var), field_type) = arg;
fn is_empty_record(subs: &Subs, mut var: Variable) -> bool {
use crate::subs::Content::*;
use crate::subs::FlatType::*;
(name, field_type.map(|_| var))
}
loop {
match subs.get_content_without_compacting(var) {
Structure(EmptyRecord) => return true,
Structure(Record(sub_fields, sub_ext)) => {
if !sub_fields.is_empty() {
return false;
}
impl<'a> IntoIterator for &'a RecordFields {
type Item = (&'a Lowercase, RecordField<Variable>);
var = *sub_ext;
}
#[allow(clippy::type_complexity)]
type IntoIter = Map<
Zip<
Zip<std::slice::Iter<'a, Lowercase>, std::slice::Iter<'a, Variable>>,
std::slice::Iter<'a, RecordField<()>>,
>,
fn(
((&'a Lowercase, &Variable), &RecordField<()>),
) -> (&'a Lowercase, RecordField<Variable>),
>;
Alias(_, _, actual_var) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var;
}
fn into_iter(self) -> Self::IntoIter {
self.field_names
.iter()
.zip(self.variables.iter())
.zip(self.field_type.iter())
.map(ref_record_fields_into_iterator_help)
_ => return false,
}
}
}
fn ref_record_fields_into_iterator_help<'a>(
arg: ((&'a Lowercase, &Variable), &RecordField<()>),
) -> (&'a Lowercase, RecordField<Variable>) {
let ((name, var), field_type) = arg;
(name, field_type.map(|_| *var))
}
fn occurs(
subs: &Subs,
seen: &ImSet<Variable>,
@ -1038,7 +1053,9 @@ fn occurs(
short_circuit(subs, root_var, &new_seen, it)
}
Record(vars_by_field, ext_var) => {
let it = once(ext_var).chain(vars_by_field.iter_variables());
let slice =
SubsSlice::new(vars_by_field.variables_start, vars_by_field.length);
let it = once(ext_var).chain(subs.get_subs_slice(slice).iter());
short_circuit(subs, root_var, &new_seen, it)
}
TagUnion(tags, ext_var) => {
@ -1161,11 +1178,13 @@ fn explicit_substitute(
Structure(RecursiveTagUnion(rec_var, tags, new_ext_var)),
);
}
Record(mut vars_by_field, ext_var) => {
Record(vars_by_field, ext_var) => {
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
for var in vars_by_field.variables.iter_mut() {
*var = explicit_substitute(subs, from, to, *var, seen);
for index in vars_by_field.iter_variables() {
let var = subs[index];
let new_var = explicit_substitute(subs, from, to, var, seen);
subs[index] = new_var;
}
subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var)));
@ -1262,13 +1281,15 @@ fn get_var_names(
}
FlatType::Record(vars_by_field, ext_var) => {
let taken_names = get_var_names(subs, ext_var, taken_names);
let mut accum = get_var_names(subs, ext_var, taken_names);
vars_by_field
.into_iter()
.fold(taken_names, |answer, (_, field)| {
get_var_names(subs, field.into_inner(), answer)
})
for var_index in vars_by_field.iter_variables() {
let arg_var = subs[var_index];
accum = get_var_names(subs, arg_var, accum)
}
accum
}
FlatType::TagUnion(tags, ext_var) => {
let mut taken_names = get_var_names(subs, ext_var, taken_names);
@ -1477,16 +1498,21 @@ fn flat_type_to_err_type(
Record(vars_by_field, ext_var) => {
let mut err_fields = SendMap::default();
for (field, field_var) in vars_by_field.into_iter() {
use RecordField::*;
for (i1, i2, i3) in vars_by_field.iter_all() {
let label = subs[i1].clone();
let var = subs[i2];
let record_field = subs[i3];
let err_type = match field_var {
Optional(var) => Optional(var_to_err_type(subs, state, var)),
Required(var) => Required(var_to_err_type(subs, state, var)),
Demanded(var) => Demanded(var_to_err_type(subs, state, var)),
let error_type = var_to_err_type(subs, state, var);
use RecordField::*;
let err_record_field = match record_field {
Optional(_) => Optional(error_type),
Required(_) => Required(error_type),
Demanded(_) => Demanded(error_type),
};
err_fields.insert(field, err_type);
err_fields.insert(label, err_record_field);
}
match var_to_err_type(subs, state, ext_var).unwrap_alias() {
@ -1651,8 +1677,9 @@ fn restore_content(subs: &mut Subs, content: &Content) {
EmptyTagUnion => (),
Record(fields, ext_var) => {
for var in fields.iter_variables() {
subs.restore(*var);
for index in fields.iter_variables() {
let var = subs[index];
subs.restore(var);
}
subs.restore(*ext_var);

View File

@ -978,8 +978,10 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
}
}
#[derive(Debug)]
pub struct RecordStructure {
pub fields: RecordFields,
/// Invariant: these should be sorted!
pub fields: Vec<(Lowercase, RecordField<Variable>)>,
pub ext: Variable,
}
@ -1519,20 +1521,23 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lower
}
}
pub fn gather_fields(
pub fn gather_fields_unsorted_iter(
subs: &Subs,
other_fields: RecordFields,
mut var: Variable,
) -> RecordStructure {
) -> (
impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + '_,
Variable,
) {
use crate::subs::Content::*;
use crate::subs::FlatType::*;
let mut result = other_fields;
let mut stack = vec![other_fields];
loop {
match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => {
result = RecordFields::merge(result, sub_fields.clone());
stack.push(*sub_fields);
var = *sub_ext;
}
@ -1546,51 +1551,32 @@ pub fn gather_fields(
}
}
let it = stack
.into_iter()
.map(|fields| fields.iter_all())
.flatten()
.map(move |(i1, i2, i3)| {
let field_name: &Lowercase = &subs[i1];
let variable = subs[i2];
let record_field: RecordField<Variable> = subs[i3].map(|_| variable);
(field_name, record_field)
});
(it, var)
}
pub fn gather_fields(subs: &Subs, other_fields: RecordFields, var: Variable) -> RecordStructure {
let (it, ext) = gather_fields_unsorted_iter(subs, other_fields, var);
let mut result: Vec<_> = it
.map(|(ref_label, field)| (ref_label.clone(), field))
.collect();
result.sort_by(|(a, _), (b, _)| a.cmp(b));
RecordStructure {
fields: result,
ext: var,
}
}
pub fn gather_fields_ref(
subs: &Subs,
other_fields: &RecordFields,
mut var: Variable,
) -> RecordStructure {
use crate::subs::Content::*;
use crate::subs::FlatType::*;
let mut from_ext = Vec::new();
loop {
match subs.get_content_without_compacting(var) {
Structure(Record(sub_fields, sub_ext)) => {
from_ext.extend(sub_fields.into_iter());
var = *sub_ext;
}
Alias(_, _, actual_var) => {
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
var = *actual_var;
}
_ => break,
}
}
if from_ext.is_empty() {
RecordStructure {
fields: other_fields.clone(),
ext: var,
}
} else {
RecordStructure {
fields: other_fields
.into_iter()
.chain(from_ext.into_iter())
.collect(),
ext: var,
}
ext,
}
}

View File

@ -5,7 +5,7 @@ use roc_types::subs::Content::{self, *};
use roc_types::subs::{
Descriptor, FlatType, GetSubsSlice, Mark, OptVariable, RecordFields, Subs, SubsSlice, Variable,
};
use roc_types::types::{gather_fields_ref, ErrorType, Mismatch, RecordField, RecordStructure};
use roc_types::types::{ErrorType, Mismatch, RecordField};
macro_rules! mismatch {
() => {{
@ -259,34 +259,35 @@ fn unify_record(
subs: &mut Subs,
pool: &mut Pool,
ctx: &Context,
rec1: RecordStructure,
rec2: RecordStructure,
fields1: RecordFields,
ext1: Variable,
fields2: RecordFields,
ext2: Variable,
) -> Outcome {
let fields1 = rec1.fields;
let fields2 = rec2.fields;
let separate = RecordFields::separate(fields1, fields2);
let (separate, ext1, ext2) = separate_record_fields(subs, fields1, ext1, fields2, ext2);
let shared_fields = separate.in_both;
if separate.only_in_1.is_empty() {
if separate.only_in_2.is_empty() {
let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
// these variable will be the empty record, but we must still unify them
let ext_problems = unify_pool(subs, pool, ext1, ext2);
if !ext_problems.is_empty() {
return ext_problems;
}
let mut field_problems =
unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, rec1.ext);
unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, ext1);
field_problems.extend(ext_problems);
field_problems
} else {
let flat_type = FlatType::Record(separate.only_in_2, rec2.ext);
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
let flat_type = FlatType::Record(only_in_2, ext2);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record);
let ext_problems = unify_pool(subs, pool, ext1, sub_record);
if !ext_problems.is_empty() {
return ext_problems;
@ -306,9 +307,10 @@ fn unify_record(
field_problems
}
} else if separate.only_in_2.is_empty() {
let flat_type = FlatType::Record(separate.only_in_1, rec1.ext);
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
let flat_type = FlatType::Record(only_in_1, ext1);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext);
let ext_problems = unify_pool(subs, pool, sub_record, ext2);
if !ext_problems.is_empty() {
return ext_problems;
@ -327,26 +329,24 @@ fn unify_record(
field_problems
} else {
let it = (&separate.only_in_1)
.into_iter()
.chain((&separate.only_in_2).into_iter());
let other: RecordFields = it.collect();
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
let other_fields = OtherFields::Other(other);
let other_fields = OtherFields::Other(only_in_1, only_in_2);
let ext = fresh(subs, pool, ctx, Content::FlexVar(None));
let flat_type1 = FlatType::Record(separate.only_in_1, ext);
let flat_type2 = FlatType::Record(separate.only_in_2, ext);
let flat_type1 = FlatType::Record(only_in_1, ext);
let flat_type2 = FlatType::Record(only_in_2, ext);
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
let rec1_problems = unify_pool(subs, pool, rec1.ext, sub2);
let rec1_problems = unify_pool(subs, pool, ext1, sub2);
if !rec1_problems.is_empty() {
return rec1_problems;
}
let rec2_problems = unify_pool(subs, pool, sub1, rec2.ext);
let rec2_problems = unify_pool(subs, pool, sub1, ext2);
if !rec2_problems.is_empty() {
return rec2_problems;
}
@ -364,21 +364,23 @@ fn unify_record(
enum OtherFields {
None,
Other(RecordFields),
Other(RecordFields, RecordFields),
}
type SharedFields = Vec<(Lowercase, (RecordField<Variable>, RecordField<Variable>))>;
fn unify_shared_fields(
subs: &mut Subs,
pool: &mut Pool,
ctx: &Context,
shared_fields: Vec<(Lowercase, RecordField<Variable>, RecordField<Variable>)>,
shared_fields: SharedFields,
other_fields: OtherFields,
ext: Variable,
) -> Outcome {
let mut matching_fields = Vec::with_capacity(shared_fields.len());
let num_shared_fields = shared_fields.len();
for (name, actual, expected) in shared_fields {
for (name, (actual, expected)) in shared_fields {
let local_problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner());
if local_problems.is_empty() {
@ -418,22 +420,44 @@ fn unify_shared_fields(
Err((new, _)) => new,
};
let mut ext_fields: Vec<_> = ext_fields.into_iter().collect();
ext_fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
let fields: RecordFields = match other_fields {
OtherFields::None => {
if ext_fields.is_empty() {
RecordFields::from_sorted_vec(matching_fields)
RecordFields::insert_into_subs(subs, matching_fields)
} else {
matching_fields
.into_iter()
.chain(ext_fields.into_iter())
.collect()
let all_fields = merge_sorted(matching_fields, ext_fields);
RecordFields::insert_into_subs(subs, all_fields)
}
}
OtherFields::Other(other_fields) => matching_fields
.into_iter()
.chain(other_fields.into_iter())
.chain(ext_fields.into_iter())
.collect(),
OtherFields::Other(other1, other2) => {
let mut all_fields = merge_sorted(matching_fields, ext_fields);
all_fields = merge_sorted(
all_fields,
other1.iter_all().map(|(i1, i2, i3)| {
let field_name: Lowercase = subs[i1].clone();
let variable = subs[i2];
let record_field: RecordField<Variable> = subs[i3].map(|_| variable);
(field_name, record_field)
}),
);
all_fields = merge_sorted(
all_fields,
other2.iter_all().map(|(i1, i2, i3)| {
let field_name: Lowercase = subs[i1].clone();
let variable = subs[i2];
let record_field: RecordField<Variable> = subs[i3].map(|_| variable);
(field_name, record_field)
}),
);
RecordFields::insert_into_subs(subs, all_fields)
}
};
let flat_type = FlatType::Record(fields, new_ext_var);
@ -444,13 +468,132 @@ fn unify_shared_fields(
}
}
fn separate_record_fields(
subs: &Subs,
fields1: RecordFields,
ext1: Variable,
fields2: RecordFields,
ext2: Variable,
) -> (
Separate<Lowercase, RecordField<Variable>>,
Variable,
Variable,
) {
let (it1, new_ext1) = fields1.sorted_iterator_and_ext(subs, ext1);
let (it2, new_ext2) = fields2.sorted_iterator_and_ext(subs, ext2);
let it1 = it1.collect::<Vec<_>>();
let it2 = it2.collect::<Vec<_>>();
(separate(it1, it2), new_ext1, new_ext2)
}
#[derive(Debug)]
struct Separate<K, V> {
only_in_1: Vec<(K, V)>,
only_in_2: Vec<(K, V)>,
in_both: Vec<(K, (V, V))>,
}
fn merge_sorted<K, V, I1, I2>(input1: I1, input2: I2) -> Vec<(K, V)>
where
K: Ord,
I1: IntoIterator<Item = (K, V)>,
I2: IntoIterator<Item = (K, V)>,
{
use std::cmp::Ordering;
let mut it1 = input1.into_iter().peekable();
let mut it2 = input2.into_iter().peekable();
let input1_len = it1.size_hint().0;
let input2_len = it2.size_hint().0;
let mut result = Vec::with_capacity(input1_len + input2_len);
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
match which {
Some(Ordering::Less) => {
result.push(it1.next().unwrap());
}
Some(Ordering::Equal) => {
let (k, v) = it1.next().unwrap();
let (_, _) = it2.next().unwrap();
result.push((k, v));
}
Some(Ordering::Greater) => {
result.push(it2.next().unwrap());
}
None => break,
}
}
result
}
fn separate<K, V, I1, I2>(input1: I1, input2: I2) -> Separate<K, V>
where
K: Ord,
I1: IntoIterator<Item = (K, V)>,
I2: IntoIterator<Item = (K, V)>,
{
use std::cmp::Ordering;
let mut it1 = input1.into_iter().peekable();
let mut it2 = input2.into_iter().peekable();
let input1_len = it1.size_hint().0;
let input2_len = it2.size_hint().0;
let max_common = input1_len.min(input2_len);
let mut result = Separate {
only_in_1: Vec::with_capacity(input1_len),
only_in_2: Vec::with_capacity(input2_len),
in_both: Vec::with_capacity(max_common),
};
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
match which {
Some(Ordering::Less) => {
result.only_in_1.push(it1.next().unwrap());
}
Some(Ordering::Equal) => {
let (k, v1) = it1.next().unwrap();
let (_, v2) = it2.next().unwrap();
result.in_both.push((k, (v1, v2)));
}
Some(Ordering::Greater) => {
result.only_in_2.push(it2.next().unwrap());
}
None => break,
}
}
result
}
struct SeparateTags<K, V> {
only_in_1: MutMap<K, V>,
only_in_2: MutMap<K, V>,
in_both: MutMap<K, (V, V)>,
}
fn separate<K, V>(tags1: MutMap<K, V>, mut tags2: MutMap<K, V>) -> Separate<K, V>
fn separate_tags<K, V>(tags1: MutMap<K, V>, mut tags2: MutMap<K, V>) -> SeparateTags<K, V>
where
K: Ord + std::hash::Hash,
{
@ -470,7 +613,7 @@ where
}
}
Separate {
SeparateTags {
only_in_1,
only_in_2: tags2,
in_both,
@ -505,11 +648,11 @@ fn unify_tag_union(
return unify_shared_tags_merge(subs, ctx, tags1, rec1.ext, recursion_var);
}
let Separate {
let SeparateTags {
only_in_1: unique_tags1,
only_in_2: unique_tags2,
in_both: shared_tags,
} = separate(tags1, tags2);
} = separate_tags(tags1, tags2);
if unique_tags1.is_empty() {
if unique_tags2.is_empty() {
@ -985,19 +1128,16 @@ fn unify_flat_type(
match (left, right) {
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())),
(Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields() => {
(Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields(subs) => {
unify_pool(subs, pool, *ext, ctx.second)
}
(EmptyRecord, Record(fields, ext)) if fields.has_only_optional_fields() => {
(EmptyRecord, Record(fields, ext)) if fields.has_only_optional_fields(subs) => {
unify_pool(subs, pool, ctx.first, *ext)
}
(Record(fields1, ext1), Record(fields2, ext2)) => {
let rec1 = gather_fields_ref(subs, fields1, *ext1);
let rec2 = gather_fields_ref(subs, fields2, *ext2);
unify_record(subs, pool, ctx, rec1, rec2)
unify_record(subs, pool, ctx, *fields1, *ext1, *fields2, *ext2)
}
(EmptyTagUnion, EmptyTagUnion) => merge(subs, ctx, Structure(left.clone())),

View File

@ -280,3 +280,4 @@ Thoughts and ideas possibly taken from above inspirations or separate.
* "Error mode" where the editor jumps you to the next error
* Similar in theory to diff tools that jump you to the next merge conflict
* dependency recommendation
* Command to change the file to put all exposed functions at the top of the file, private functions below. Other alternative; ability to show a "file explorer" that shows exposed functions first, followed by private functions.

View File

@ -10,7 +10,7 @@ use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::solved_types::Solved;
use roc_types::subs::{
Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, SubsSlice, Variable,
Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, SubsSlice, Variable,
};
use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField};
use roc_unify::unify::unify;
@ -762,7 +762,10 @@ fn type_to_variable<'a>(
Err((new, _)) => new,
};
let record_fields = field_vars.into_iter().collect();
let mut all_fields: Vec<_> = field_vars.into_iter().collect();
all_fields.sort_unstable_by(RecordFields::compare);
let record_fields = RecordFields::insert_into_subs(subs, all_fields);
let content = Content::Structure(FlatType::Record(record_fields, new_ext_var));
@ -1217,9 +1220,9 @@ fn adjust_rank_content(
Record(fields, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
for var in fields.iter_variables() {
rank =
rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, *var));
for index in fields.iter_variables() {
let var = subs[index];
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
}
rank
@ -1367,8 +1370,9 @@ fn instantiate_rigids_help(
EmptyRecord | EmptyTagUnion | Erroneous(_) => {}
Record(fields, ext_var) => {
for var in fields.iter_variables() {
instantiate_rigids_help(subs, max_rank, pools, *var);
for index in fields.iter_variables() {
let var = subs[index];
instantiate_rigids_help(subs, max_rank, pools, var);
}
instantiate_rigids_help(subs, max_rank, pools, ext_var);
@ -1514,12 +1518,30 @@ fn deep_copy_var_help(
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Record(mut fields, ext_var) => {
for var in fields.iter_variables_mut() {
*var = deep_copy_var_help(subs, max_rank, pools, *var);
Record(fields, ext_var) => {
let mut new_vars = Vec::with_capacity(fields.len());
for index in fields.iter_variables() {
let var = subs[index];
let copy_var = deep_copy_var_help(subs, max_rank, pools, var);
new_vars.push(copy_var);
}
Record(fields, deep_copy_var_help(subs, max_rank, pools, ext_var))
let it = fields.iter_all().zip(new_vars).map(|((i1, _, i3), var)| {
let label = subs[i1].clone();
let record_field = subs[i3].map(|_| var);
(label, record_field)
});
// lifetime troubles
let vec: Vec<_> = it.collect();
let record_fields = RecordFields::insert_into_subs(subs, vec);
Record(
record_fields,
deep_copy_var_help(subs, max_rank, pools, ext_var),
)
}
TagUnion(tags, ext_var) => {

View File

@ -7,7 +7,7 @@ Run examples as follows:
1. Navigate to `/examples`
2. Run with:
```
cargo run run hello-world/Hello.roc
cargo run hello-world/Hello.roc
```
Some examples like `examples/benchmarks/NQueens.roc` require input after running.
For NQueens, input 10 in the terminal and press enter.
For NQueens, input 10 in the terminal and press enter.

View File

@ -3,13 +3,13 @@
To run, `cd` into this directory and run:
```bash
$ cargo run run Hello.roc
$ cargo run Hello.roc
```
To run in release mode instead, do:
```bash
$ cargo run --release run Hello.roc
$ cargo run --release Hello.roc
```
## Troubleshooting

View File

@ -3,13 +3,13 @@
To run, `cd` into this directory and run:
```bash
$ cargo run run Hello.roc
$ cargo run Hello.roc
```
To run in release mode instead, do:
```bash
$ cargo run --release run Hello.roc
$ cargo run --release Hello.roc
```
## Troubleshooting

View File

@ -3,13 +3,13 @@
To run:
```bash
$ cargo run run Quicksort.roc
$ cargo run Quicksort.roc
```
To run in release mode instead, do:
```bash
$ cargo run --release run Quicksort.roc
$ cargo run --release Quicksort.roc
```
## Troubleshooting