mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
Merge remote-tracking branch 'origin/trunk' into hostgen
This commit is contained in:
commit
36f64d8496
45 changed files with 1801 additions and 1368 deletions
|
@ -106,6 +106,9 @@ comptime {
|
|||
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
|
||||
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
|
||||
}
|
||||
|
||||
num.exportRoundF32(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32.");
|
||||
num.exportRoundF64(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64.");
|
||||
}
|
||||
|
||||
inline for (FLOATS) |T| {
|
||||
|
@ -114,7 +117,6 @@ comptime {
|
|||
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
|
||||
|
||||
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
|
||||
num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,10 +90,19 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
|
|||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
|
||||
pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: T) callconv(.C) i64 {
|
||||
return @floatToInt(i64, (@round(input)));
|
||||
fn func(input: f32) callconv(.C) T {
|
||||
return @floatToInt(T, (@round(input)));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportRoundF64(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: f64) callconv(.C) T {
|
||||
return @floatToInt(T, (@round(input)));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
|
|
|
@ -279,7 +279,9 @@ pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
|
|||
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
|
||||
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
|
||||
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
|
||||
pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round");
|
||||
|
||||
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
|
||||
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
|
||||
|
||||
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
|
||||
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
|
||||
|
|
|
@ -14,8 +14,6 @@ roc_module = { path = "../module" }
|
|||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
ven_graph = { path = "../../vendor/pathfinding" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
static_assertions = "1.1.0"
|
||||
bitvec = "1"
|
||||
|
|
|
@ -192,17 +192,12 @@ fn sort_type_defs_before_introduction(
|
|||
}
|
||||
|
||||
// find the strongly connected components and their relations
|
||||
let nodes: Vec<_> = (0..capacity as u32).collect();
|
||||
|
||||
let mut output = Vec::with_capacity(capacity);
|
||||
|
||||
for group in matrix.strongly_connected_components(&nodes).groups() {
|
||||
for index in group.iter_ones() {
|
||||
output.push(symbols[index])
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
matrix
|
||||
.strongly_connected_components_all()
|
||||
.groups()
|
||||
.flat_map(|group| group.iter_ones())
|
||||
.map(|index| symbols[index])
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -790,14 +785,10 @@ pub(crate) fn sort_can_defs(
|
|||
};
|
||||
}
|
||||
|
||||
let nodes: Vec<_> = (0..defs.len() as u32).collect();
|
||||
|
||||
// We first perform SCC based on any reference, both variable usage and calls
|
||||
// considering both value definitions and function bodies. This will spot any
|
||||
// recursive relations between any 2 definitions.
|
||||
let sccs = def_ordering
|
||||
.references
|
||||
.strongly_connected_components(&nodes);
|
||||
let sccs = def_ordering.references.strongly_connected_components_all();
|
||||
|
||||
let mut declarations = Vec::new();
|
||||
|
||||
|
@ -838,10 +829,9 @@ pub(crate) fn sort_can_defs(
|
|||
// boom = \{} -> boom {}
|
||||
//
|
||||
// In general we cannot spot faulty recursion (halting problem) so this is our best attempt
|
||||
let nodes: Vec<_> = group.iter_ones().map(|v| v as u32).collect();
|
||||
let direct_sccs = def_ordering
|
||||
.direct_references
|
||||
.strongly_connected_components(&nodes);
|
||||
.strongly_connected_components_subset(group);
|
||||
|
||||
let declaration = if direct_sccs.groups().count() == 1 {
|
||||
// all defs are part of the same direct cycle, that is invalid!
|
||||
|
@ -1571,8 +1561,7 @@ fn correct_mutual_recursive_type_alias<'a>(
|
|||
|
||||
let mut solved_aliases = bitvec::vec::BitVec::<usize>::repeat(false, capacity);
|
||||
|
||||
let group: Vec<_> = (0u32..capacity as u32).collect();
|
||||
let sccs = matrix.strongly_connected_components(&group);
|
||||
let sccs = matrix.strongly_connected_components_all();
|
||||
|
||||
// scratchpad to store aliases that are modified in the current iteration.
|
||||
// Only used when there is are more than one alias in a group. See below why
|
||||
|
|
|
@ -129,8 +129,14 @@ impl ReferenceMatrix {
|
|||
TopologicalSort::Groups { groups }
|
||||
}
|
||||
|
||||
/// Get the strongly-connected components of the set of input nodes.
|
||||
pub fn strongly_connected_components(&self, nodes: &[u32]) -> Sccs {
|
||||
/// Get the strongly-connected components all nodes in the matrix
|
||||
pub fn strongly_connected_components_all(&self) -> Sccs {
|
||||
let bitvec = BitVec::repeat(true, self.length);
|
||||
self.strongly_connected_components_subset(&bitvec)
|
||||
}
|
||||
|
||||
/// Get the strongly-connected components of a set of input nodes.
|
||||
pub fn strongly_connected_components_subset(&self, nodes: &BitSlice) -> Sccs {
|
||||
let mut params = Params::new(self.length, nodes);
|
||||
|
||||
'outer: loop {
|
||||
|
@ -176,15 +182,15 @@ struct Params {
|
|||
p: Vec<u32>,
|
||||
s: Vec<u32>,
|
||||
scc: Sccs,
|
||||
scca: Vec<u32>,
|
||||
scca: BitVec,
|
||||
}
|
||||
|
||||
impl Params {
|
||||
fn new(length: usize, group: &[u32]) -> Self {
|
||||
fn new(length: usize, group: &BitSlice) -> Self {
|
||||
let mut preorders = vec![Preorder::Removed; length];
|
||||
|
||||
for value in group {
|
||||
preorders[*value as usize] = Preorder::Empty;
|
||||
for index in group.iter_ones() {
|
||||
preorders[index] = Preorder::Empty;
|
||||
}
|
||||
|
||||
Self {
|
||||
|
@ -196,7 +202,7 @@ impl Params {
|
|||
matrix: ReferenceMatrix::new(length),
|
||||
components: 0,
|
||||
},
|
||||
scca: Vec::new(),
|
||||
scca: BitVec::repeat(false, length),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +216,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
|
|||
params.p.push(v as u32);
|
||||
|
||||
for w in bitvec[v * length..][..length].iter_ones() {
|
||||
if !params.scca.contains(&(w as u32)) {
|
||||
if !params.scca[w] {
|
||||
match params.preorders[w] {
|
||||
Preorder::Filled(pw) => loop {
|
||||
let index = *params.p.last().unwrap();
|
||||
|
@ -241,7 +247,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
|
|||
.scc
|
||||
.matrix
|
||||
.set_row_col(params.scc.components, node as usize, true);
|
||||
params.scca.push(node);
|
||||
params.scca.set(node as usize, true);
|
||||
params.preorders[node as usize] = Preorder::Removed;
|
||||
if node as usize == v {
|
||||
break;
|
||||
|
|
|
@ -13,6 +13,13 @@ impl<K, V> Default for VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<K, V> VecMap<K, V> {
|
||||
pub fn len(&self) -> usize {
|
||||
debug_assert_eq!(self.keys.len(), self.values.len());
|
||||
self.keys.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: PartialEq, V> VecMap<K, V> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
|
@ -21,11 +28,6 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
debug_assert_eq!(self.keys.len(), self.values.len());
|
||||
self.keys.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
debug_assert_eq!(self.keys.len(), self.values.len());
|
||||
self.keys.is_empty()
|
||||
|
@ -58,15 +60,9 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
self.keys.contains(key)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &K) {
|
||||
match self.keys.iter().position(|x| x == key) {
|
||||
None => {
|
||||
// just do nothing
|
||||
}
|
||||
Some(index) => {
|
||||
self.swap_remove(index);
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, key: &K) -> Option<(K, V)> {
|
||||
let index = self.keys.iter().position(|x| x == key)?;
|
||||
Some(self.swap_remove(index))
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &K) -> Option<&V> {
|
||||
|
@ -83,7 +79,7 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_or_insert(&mut self, key: K, default_value: impl Fn() -> V) -> &mut V {
|
||||
pub fn get_or_insert(&mut self, key: K, default_value: impl FnOnce() -> V) -> &mut V {
|
||||
match self.keys.iter().position(|x| x == &key) {
|
||||
Some(index) => &mut self.values[index],
|
||||
None => {
|
||||
|
@ -97,15 +93,15 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
|
||||
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&K, &V)> {
|
||||
self.keys.iter().zip(self.values.iter())
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> impl Iterator<Item = &K> {
|
||||
pub fn keys(&self) -> impl ExactSizeIterator<Item = &K> {
|
||||
self.keys.iter()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
pub fn values(&self) -> impl ExactSizeIterator<Item = &V> {
|
||||
self.values.iter()
|
||||
}
|
||||
|
||||
|
@ -159,6 +155,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
|
|||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter {
|
||||
len: self.len(),
|
||||
keys: self.keys.into_iter(),
|
||||
values: self.values.into_iter(),
|
||||
}
|
||||
|
@ -166,6 +163,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
|
|||
}
|
||||
|
||||
pub struct IntoIter<K, V> {
|
||||
len: usize,
|
||||
keys: std::vec::IntoIter<K>,
|
||||
values: std::vec::IntoIter<V>,
|
||||
}
|
||||
|
@ -180,3 +178,9 @@ impl<K, V> Iterator for IntoIter<K, V> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> ExactSizeIterator for IntoIter<K, V> {
|
||||
fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,14 @@ pub mod pattern;
|
|||
pub mod spaces;
|
||||
|
||||
use bumpalo::{collections::String, Bump};
|
||||
use roc_parse::ast::{Def, Module};
|
||||
use roc_region::all::Loc;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Ast<'a> {
|
||||
pub module: Module<'a>,
|
||||
pub defs: bumpalo::collections::vec::Vec<'a, Loc<Def<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buf<'a> {
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
use roc_parse::ast::CommentOrNewline;
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::{
|
||||
ast::{
|
||||
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Expr, Has, HasClause,
|
||||
Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader,
|
||||
ValueDef, WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
|
||||
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
},
|
||||
ident::UppercaseIdent,
|
||||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
use crate::Buf;
|
||||
use crate::{Ast, Buf};
|
||||
|
||||
/// The number of spaces to indent.
|
||||
pub const INDENT: u16 = 4;
|
||||
|
@ -149,3 +164,575 @@ fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
|
|||
}
|
||||
buf.push_str(docs);
|
||||
}
|
||||
|
||||
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
|
||||
///
|
||||
/// Currently this consists of:
|
||||
/// * Removing newlines
|
||||
/// * Removing comments
|
||||
/// * Removing parens in Exprs
|
||||
///
|
||||
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
|
||||
/// - but there are currently several bugs where they're _not_ preserved.
|
||||
/// TODO: ensure formatting retains comments
|
||||
pub trait RemoveSpaces<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self;
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Ast<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
Ast {
|
||||
module: self.module.remove_spaces(arena),
|
||||
defs: {
|
||||
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
|
||||
for d in &self.defs {
|
||||
defs.push(d.remove_spaces(arena))
|
||||
}
|
||||
defs
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match self {
|
||||
Module::Interface { header } => Module::Interface {
|
||||
header: InterfaceHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_interface_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
},
|
||||
},
|
||||
Module::App { header } => Module::App {
|
||||
header: AppHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
|
||||
to: header.to.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_app_keyword: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
before_to: &[],
|
||||
after_to: &[],
|
||||
},
|
||||
},
|
||||
Module::Platform { header } => Module::Platform {
|
||||
header: PlatformHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
requires: header.requires.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_platform_keyword: &[],
|
||||
before_requires: &[],
|
||||
after_requires: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
},
|
||||
},
|
||||
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: &[],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for &'a str {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
|
||||
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for To<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
To::ExistingPackage(a) => To::ExistingPackage(a),
|
||||
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
TypedIdent {
|
||||
ident: self.ident.remove_spaces(arena),
|
||||
spaces_before_colon: &[],
|
||||
ann: self.ann.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PlatformRequires {
|
||||
rigids: self.rigids.remove_spaces(arena),
|
||||
signature: self.signature.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PackageEntry {
|
||||
shorthand: self.shorthand,
|
||||
spaces_after_shorthand: &[],
|
||||
package_name: self.package_name.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
|
||||
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
self.as_ref().map(|a| a.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let res = self.value.remove_spaces(arena);
|
||||
Loc::at(Region::zero(), res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.items.len(), arena);
|
||||
for item in self.items {
|
||||
items.push(item.remove_spaces(arena));
|
||||
}
|
||||
Collection::with_items(items.into_bump_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.len(), arena);
|
||||
for item in *self {
|
||||
let res = item.remove_spaces(arena);
|
||||
items.push(res);
|
||||
}
|
||||
items.into_bump_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UnaryOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for BinOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
arena.alloc((*self).remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use TypeDef::*;
|
||||
|
||||
match *self {
|
||||
Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => Alias {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
ann: ann.remove_spaces(arena),
|
||||
},
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ,
|
||||
} => Opaque {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
typ: typ.remove_spaces(arena),
|
||||
},
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
loc_has,
|
||||
members,
|
||||
} => Ability {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
loc_has: loc_has.remove_spaces(arena),
|
||||
members: members.remove_spaces(arena),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use ValueDef::*;
|
||||
|
||||
match *self {
|
||||
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
|
||||
Body(a, b) => Body(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment: _,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => AnnotatedBody {
|
||||
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
|
||||
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
|
||||
comment: None,
|
||||
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
|
||||
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
|
||||
},
|
||||
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Def<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
|
||||
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
|
||||
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
|
||||
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Has<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Has::Has
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
AbilityMember {
|
||||
name: self.name.remove_spaces(arena),
|
||||
typ: self.typ.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
WhenBranch {
|
||||
patterns: self.patterns.remove_spaces(arena),
|
||||
value: self.value.remove_spaces(arena),
|
||||
guard: self.guard.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
|
||||
AssignedField::Malformed(a) => AssignedField::Malformed(a),
|
||||
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
|
||||
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
|
||||
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
|
||||
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
|
||||
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
|
||||
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Expr::Float(a) => Expr::Float(a),
|
||||
Expr::Num(a) => Expr::Num(a),
|
||||
Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
|
||||
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
|
||||
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
|
||||
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
|
||||
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
|
||||
update: arena.alloc(update.remove_spaces(arena)),
|
||||
fields: fields.remove_spaces(arena),
|
||||
},
|
||||
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
|
||||
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
|
||||
Expr::Underscore(a) => Expr::Underscore(a),
|
||||
Expr::Tag(a) => Expr::Tag(a),
|
||||
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
|
||||
Expr::Closure(a, b) => Expr::Closure(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Defs(a, b) => {
|
||||
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::Backpassing(a, b, c) => Expr::Backpassing(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Expect(a, b) => Expr::Expect(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Apply(a, b, c) => Expr::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
b.remove_spaces(arena),
|
||||
c,
|
||||
),
|
||||
Expr::BinOps(a, b) => {
|
||||
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::UnaryOp(a, b) => {
|
||||
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
|
||||
Expr::When(a, b) => {
|
||||
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::ParensAround(a) => {
|
||||
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
|
||||
a.remove_spaces(arena)
|
||||
}
|
||||
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
|
||||
Expr::MalformedClosure => Expr::MalformedClosure,
|
||||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Pattern::Identifier(a) => Pattern::Identifier(a),
|
||||
Pattern::Tag(a) => Pattern::Tag(a),
|
||||
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
|
||||
Pattern::Apply(a, b) => Pattern::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
|
||||
Pattern::RequiredField(a, b) => {
|
||||
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::OptionalField(a, b) => {
|
||||
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
|
||||
Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
|
||||
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
|
||||
Pattern::Underscore(a) => Pattern::Underscore(a),
|
||||
Pattern::Malformed(a) => Pattern::Malformed(a),
|
||||
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
|
||||
Pattern::QualifiedIdentifier { module_name, ident } => {
|
||||
Pattern::QualifiedIdentifier { module_name, ident }
|
||||
}
|
||||
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
|
||||
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
|
||||
TypeAnnotation::As(a, _, c) => {
|
||||
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
|
||||
}
|
||||
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
|
||||
fields: fields.remove_spaces(arena),
|
||||
ext: ext.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
|
||||
ext: ext.remove_spaces(arena),
|
||||
tags: tags.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
|
||||
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
|
||||
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
|
||||
arena.alloc(annot.remove_spaces(arena)),
|
||||
arena.alloc(has_clauses.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
HasClause {
|
||||
var: self.var.remove_spaces(arena),
|
||||
ability: self.ability.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Tag<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Tag::Apply { name, args } => Tag::Apply {
|
||||
name: name.remove_spaces(arena),
|
||||
args: args.remove_spaces(arena),
|
||||
},
|
||||
Tag::Malformed(a) => Tag::Malformed(a),
|
||||
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,60 +10,153 @@ mod test_fmt {
|
|||
use roc_fmt::def::fmt_def;
|
||||
use roc_fmt::module::fmt_module;
|
||||
use roc_fmt::Buf;
|
||||
use roc_parse::ast::Module;
|
||||
use roc_parse::module::{self, module_defs};
|
||||
use roc_parse::parser::Parser;
|
||||
use roc_parse::state::State;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
|
||||
fn expect_format_expr_helper(input: &str, expected: &str) {
|
||||
fn expr_formats_to(input: &str, expected: &str) {
|
||||
let arena = Bump::new();
|
||||
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
|
||||
let input = input.trim();
|
||||
let expected = expected.trim();
|
||||
|
||||
match roc_parse::test_helpers::parse_expr_with(&arena, input) {
|
||||
Ok(actual) => {
|
||||
use roc_fmt::spaces::RemoveSpaces;
|
||||
|
||||
let mut buf = Buf::new_in(&arena);
|
||||
|
||||
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
assert_multiline_str_eq!(expected, buf.as_str());
|
||||
let output = buf.as_str();
|
||||
|
||||
assert_multiline_str_eq!(expected, output);
|
||||
|
||||
let reparsed_ast = roc_parse::test_helpers::parse_expr_with(&arena, output).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
|
||||
err, output
|
||||
);
|
||||
});
|
||||
|
||||
let ast_normalized = actual.remove_spaces(&arena);
|
||||
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
|
||||
|
||||
// HACK!
|
||||
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
||||
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
||||
// I don't have the patience to debug this right now, so let's leave it for another day...
|
||||
// TODO: fix PartialEq impl on ast types
|
||||
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
|
||||
panic!(
|
||||
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
|
||||
* * * Source code before formatting:\n{}\n\n\
|
||||
* * * Source code after formatting:\n{}\n\n",
|
||||
input,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
|
||||
let mut reformatted_buf = Buf::new_in(&arena);
|
||||
reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
if output != reformatted_buf.as_str() {
|
||||
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
|
||||
|
||||
assert_multiline_str_eq!(output, reformatted_buf.as_str());
|
||||
}
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
|
||||
};
|
||||
}
|
||||
|
||||
fn expr_formats_to(input: &str, expected: &str) {
|
||||
let input = input.trim_end();
|
||||
let expected = expected.trim_end();
|
||||
|
||||
// First check that input formats to the expected version
|
||||
expect_format_expr_helper(input, expected);
|
||||
|
||||
// Parse the expected result format it, asserting that it doesn't change
|
||||
// It's important that formatting be stable / idempotent
|
||||
expect_format_expr_helper(expected, expected);
|
||||
}
|
||||
|
||||
fn expr_formats_same(input: &str) {
|
||||
expr_formats_to(input, input);
|
||||
}
|
||||
|
||||
fn fmt_module_and_defs<'a>(
|
||||
arena: &Bump,
|
||||
src: &str,
|
||||
module: &Module<'a>,
|
||||
state: State<'a>,
|
||||
buf: &mut Buf<'_>,
|
||||
) {
|
||||
fmt_module(buf, module);
|
||||
|
||||
match module_defs().parse(&arena, state) {
|
||||
Ok((_, loc_defs, _)) => {
|
||||
for loc_def in loc_defs {
|
||||
fmt_def(buf, arena.alloc(loc_def.value), 0);
|
||||
}
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Not intended to be used directly in tests; please use module_formats_to or module_formats_same
|
||||
fn expect_format_module_helper(src: &str, expected: &str) {
|
||||
let arena = Bump::new();
|
||||
let src = src.trim();
|
||||
let expected = expected.trim();
|
||||
|
||||
match module::parse_header(&arena, State::new(src.as_bytes())) {
|
||||
Ok((actual, state)) => {
|
||||
use roc_fmt::spaces::RemoveSpaces;
|
||||
|
||||
let mut buf = Buf::new_in(&arena);
|
||||
|
||||
fmt_module(&mut buf, &actual);
|
||||
fmt_module_and_defs(&arena, src, &actual, state, &mut buf);
|
||||
|
||||
match module_defs().parse(&arena, state) {
|
||||
Ok((_, loc_defs, _)) => {
|
||||
for loc_def in loc_defs {
|
||||
fmt_def(&mut buf, arena.alloc(loc_def.value), 0);
|
||||
}
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||
let output = buf.as_str().trim();
|
||||
|
||||
let (reparsed_ast, state) = module::parse_header(&arena, State::new(output.as_bytes())).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
|
||||
err, output
|
||||
);
|
||||
});
|
||||
|
||||
let ast_normalized = actual.remove_spaces(&arena);
|
||||
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
|
||||
|
||||
// HACK!
|
||||
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
||||
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
||||
// I don't have the patience to debug this right now, so let's leave it for another day...
|
||||
// TODO: fix PartialEq impl on ast types
|
||||
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
|
||||
panic!(
|
||||
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
|
||||
* * * Source code before formatting:\n{}\n\n\
|
||||
* * * Source code after formatting:\n{}\n\n",
|
||||
src,
|
||||
output
|
||||
);
|
||||
}
|
||||
assert_multiline_str_eq!(expected, buf.as_str())
|
||||
|
||||
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
|
||||
let mut reformatted_buf = Buf::new_in(&arena);
|
||||
|
||||
fmt_module_and_defs(&arena, output, &reparsed_ast, state, &mut reformatted_buf);
|
||||
|
||||
let reformatted = reformatted_buf.as_str().trim();
|
||||
|
||||
if output != reformatted {
|
||||
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
|
||||
|
||||
assert_multiline_str_eq!(output, reformatted);
|
||||
}
|
||||
|
||||
// If everything was idempotent re-parsing worked, finally assert
|
||||
// that the formatted code was what we expected it to be.
|
||||
//
|
||||
// Do this last because if there were any serious problems with the
|
||||
// formatter (e.g. it wasn't idempotent), we want to know about
|
||||
// those more than we want to know that the expectation failed!
|
||||
assert_multiline_str_eq!(expected, output);
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||
};
|
||||
|
@ -680,7 +773,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -693,7 +786,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -706,7 +799,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -720,7 +813,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -737,7 +830,7 @@ mod test_fmt {
|
|||
# comment 3
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -753,7 +846,7 @@ mod test_fmt {
|
|||
# comment 3
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
expr_formats_to(
|
||||
|
@ -767,7 +860,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -779,7 +872,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -795,7 +888,7 @@ mod test_fmt {
|
|||
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -807,7 +900,7 @@ mod test_fmt {
|
|||
# comment
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -823,7 +916,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -835,7 +928,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -851,7 +944,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -863,7 +956,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -881,7 +974,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -894,7 +987,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -1444,7 +1537,7 @@ mod test_fmt {
|
|||
3,
|
||||
]
|
||||
|
||||
toList
|
||||
toList
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -3939,6 +4032,39 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_tui_package_config() {
|
||||
// At one point this failed to reformat.
|
||||
module_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
platform "tui"
|
||||
requires { Model } { main : { init : ({} -> Model), update : (Model, Str -> Model), view : (Model -> Str) } }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
|
||||
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
|
||||
mainForHost = main
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
platform "tui"
|
||||
requires { Model } { main : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
|
||||
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
|
||||
mainForHost = main
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_hosted() {
|
||||
module_formats_same(indoc!(
|
||||
|
|
|
@ -554,7 +554,7 @@ trait Backend<'a> {
|
|||
}
|
||||
LowLevel::NumRound => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_ROUND[FloatWidth::F64].to_string(),
|
||||
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
|
|
|
@ -602,6 +602,7 @@ static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin");
|
|||
static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos");
|
||||
static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil");
|
||||
static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor");
|
||||
static LLVM_ROUND: IntrinsicName = float_intrinsic!("llvm.round");
|
||||
|
||||
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
|
||||
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
|
||||
|
@ -7403,20 +7404,67 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
NumCeiling => env.builder.build_cast(
|
||||
InstructionOpcode::FPToSI,
|
||||
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
|
||||
env.context.i64_type(),
|
||||
"num_ceiling",
|
||||
),
|
||||
NumFloor => env.builder.build_cast(
|
||||
InstructionOpcode::FPToSI,
|
||||
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
|
||||
env.context.i64_type(),
|
||||
"num_floor",
|
||||
),
|
||||
NumCeiling => {
|
||||
let (return_signed, return_type) = match layout {
|
||||
Layout::Builtin(Builtin::Int(int_width)) => (
|
||||
int_width.is_signed(),
|
||||
convert::int_type_from_int_width(env, *int_width),
|
||||
),
|
||||
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
||||
};
|
||||
let opcode = if return_signed {
|
||||
InstructionOpcode::FPToSI
|
||||
} else {
|
||||
InstructionOpcode::FPToUI
|
||||
};
|
||||
env.builder.build_cast(
|
||||
opcode,
|
||||
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
|
||||
return_type,
|
||||
"num_ceiling",
|
||||
)
|
||||
}
|
||||
NumFloor => {
|
||||
let (return_signed, return_type) = match layout {
|
||||
Layout::Builtin(Builtin::Int(int_width)) => (
|
||||
int_width.is_signed(),
|
||||
convert::int_type_from_int_width(env, *int_width),
|
||||
),
|
||||
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
||||
};
|
||||
let opcode = if return_signed {
|
||||
InstructionOpcode::FPToSI
|
||||
} else {
|
||||
InstructionOpcode::FPToUI
|
||||
};
|
||||
env.builder.build_cast(
|
||||
opcode,
|
||||
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
|
||||
return_type,
|
||||
"num_floor",
|
||||
)
|
||||
}
|
||||
NumRound => {
|
||||
let (return_signed, return_type) = match layout {
|
||||
Layout::Builtin(Builtin::Int(int_width)) => (
|
||||
int_width.is_signed(),
|
||||
convert::int_type_from_int_width(env, *int_width),
|
||||
),
|
||||
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
||||
};
|
||||
let opcode = if return_signed {
|
||||
InstructionOpcode::FPToSI
|
||||
} else {
|
||||
InstructionOpcode::FPToUI
|
||||
};
|
||||
env.builder.build_cast(
|
||||
opcode,
|
||||
env.call_intrinsic(&LLVM_ROUND[float_width], &[arg.into()]),
|
||||
return_type,
|
||||
"num_round",
|
||||
)
|
||||
}
|
||||
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]),
|
||||
NumRound => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND[float_width]),
|
||||
|
||||
// trigonometry
|
||||
NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]),
|
||||
|
|
|
@ -561,18 +561,6 @@ impl<'a> LowLevelCall<'a> {
|
|||
NumCos => todo!("{:?}", self.lowlevel),
|
||||
NumSqrtUnchecked => todo!("{:?}", self.lowlevel),
|
||||
NumLogUnchecked => todo!("{:?}", self.lowlevel),
|
||||
NumRound => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
|
||||
F32 => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32])
|
||||
}
|
||||
F64 => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64])
|
||||
}
|
||||
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
|
||||
}
|
||||
}
|
||||
NumToFloat => {
|
||||
self.load_args(backend);
|
||||
let ret_type = CodeGenNumType::from(self.ret_layout);
|
||||
|
@ -592,35 +580,54 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
}
|
||||
NumPow => todo!("{:?}", self.lowlevel),
|
||||
NumCeiling => {
|
||||
NumRound => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::from(self.ret_layout) {
|
||||
I32 => {
|
||||
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
|
||||
let ret_type = CodeGenNumType::from(self.ret_layout);
|
||||
|
||||
let width = match ret_type {
|
||||
CodeGenNumType::I32 => IntWidth::I32,
|
||||
CodeGenNumType::I64 => IntWidth::I64,
|
||||
CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel),
|
||||
_ => internal_error!("Invalid return type for round: {:?}", ret_type),
|
||||
};
|
||||
|
||||
match arg_type {
|
||||
F32 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F32[width]),
|
||||
F64 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F64[width]),
|
||||
_ => internal_error!("Invalid argument type for round: {:?}", arg_type),
|
||||
}
|
||||
}
|
||||
NumCeiling | NumFloor => {
|
||||
self.load_args(backend);
|
||||
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
|
||||
let ret_type = CodeGenNumType::from(self.ret_layout);
|
||||
match (arg_type, self.lowlevel) {
|
||||
(F32, NumCeiling) => {
|
||||
backend.code_builder.f32_ceil();
|
||||
backend.code_builder.i32_trunc_s_f32()
|
||||
}
|
||||
I64 => {
|
||||
(F64, NumCeiling) => {
|
||||
backend.code_builder.f64_ceil();
|
||||
backend.code_builder.i64_trunc_s_f64()
|
||||
}
|
||||
(F32, NumFloor) => {
|
||||
backend.code_builder.f32_floor();
|
||||
}
|
||||
(F64, NumFloor) => {
|
||||
backend.code_builder.f64_floor();
|
||||
}
|
||||
_ => internal_error!("Invalid argument type for ceiling: {:?}", arg_type),
|
||||
}
|
||||
match (ret_type, arg_type) {
|
||||
// TODO: unsigned truncation
|
||||
(I32, F32) => backend.code_builder.i32_trunc_s_f32(),
|
||||
(I32, F64) => backend.code_builder.i32_trunc_s_f64(),
|
||||
(I64, F32) => backend.code_builder.i64_trunc_s_f32(),
|
||||
(I64, F64) => backend.code_builder.i64_trunc_s_f64(),
|
||||
(I128, _) => todo!("{:?} for I128", self.lowlevel),
|
||||
_ => panic_ret_type(),
|
||||
}
|
||||
}
|
||||
NumPowInt => todo!("{:?}", self.lowlevel),
|
||||
NumFloor => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::from(self.ret_layout) {
|
||||
I32 => {
|
||||
backend.code_builder.f32_floor();
|
||||
backend.code_builder.i32_trunc_s_f32()
|
||||
}
|
||||
I64 => {
|
||||
backend.code_builder.f64_floor();
|
||||
backend.code_builder.i64_trunc_s_f64()
|
||||
}
|
||||
_ => panic_ret_type(),
|
||||
}
|
||||
}
|
||||
NumIsFinite => num_is_finite(backend, self.arguments[0]),
|
||||
|
||||
NumAtan => match self.ret_layout {
|
||||
|
|
|
@ -4,6 +4,3 @@ version = "0.1.0"
|
|||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.2"
|
||||
|
|
|
@ -38,7 +38,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
|
|||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::Multi,
|
||||
Threading::AllAvailable,
|
||||
);
|
||||
|
||||
let module = res_module.unwrap();
|
||||
|
|
|
@ -22,15 +22,12 @@ roc_mono = { path = "../mono" }
|
|||
roc_target = { path = "../roc_target" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
parking_lot = "0.12"
|
||||
crossbeam = "0.8.1"
|
||||
num_cpus = "1.13.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.2.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
maplit = "1.0.2"
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -747,6 +747,7 @@ impl<'a> State<'a> {
|
|||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
number_of_workers: usize,
|
||||
) -> Self {
|
||||
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
|
||||
|
||||
|
@ -770,7 +771,7 @@ impl<'a> State<'a> {
|
|||
declarations_by_id: MutMap::default(),
|
||||
exposed_symbols_by_module: MutMap::default(),
|
||||
timings: MutMap::default(),
|
||||
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
|
||||
layout_caches: std::vec::Vec::with_capacity(number_of_workers),
|
||||
cached_subs: Arc::new(Mutex::new(cached_subs)),
|
||||
render,
|
||||
}
|
||||
|
@ -1099,7 +1100,8 @@ pub enum LoadResult<'a> {
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Threading {
|
||||
Single,
|
||||
Multi,
|
||||
AllAvailable,
|
||||
AtMost(usize),
|
||||
}
|
||||
|
||||
/// The loading process works like this, starting from the given filename (e.g. "main.roc"):
|
||||
|
@ -1157,10 +1159,32 @@ pub fn load<'a>(
|
|||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
// When compiling to wasm, we cannot spawn extra threads
|
||||
// so we have a single-threaded implementation
|
||||
if threading == Threading::Single || cfg!(target_family = "wasm") {
|
||||
load_single_threaded(
|
||||
enum Threads {
|
||||
Single,
|
||||
Many(usize),
|
||||
}
|
||||
|
||||
let threads = {
|
||||
if cfg!(target_family = "wasm") {
|
||||
// When compiling to wasm, we cannot spawn extra threads
|
||||
// so we have a single-threaded implementation
|
||||
Threads::Single
|
||||
} else {
|
||||
match std::thread::available_parallelism().map(|v| v.get()) {
|
||||
Err(_) => Threads::Single,
|
||||
Ok(0) => unreachable!("NonZeroUsize"),
|
||||
Ok(1) => Threads::Single,
|
||||
Ok(reported) => match threading {
|
||||
Threading::Single => Threads::Single,
|
||||
Threading::AllAvailable => Threads::Many(reported),
|
||||
Threading::AtMost(at_most) => Threads::Many(Ord::min(reported, at_most)),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match threads {
|
||||
Threads::Single => load_single_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
src_dir,
|
||||
|
@ -1169,9 +1193,8 @@ pub fn load<'a>(
|
|||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
} else {
|
||||
load_multi_threaded(
|
||||
),
|
||||
Threads::Many(threads) => load_multi_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
src_dir,
|
||||
|
@ -1180,7 +1203,8 @@ pub fn load<'a>(
|
|||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
threads,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1210,6 +1234,7 @@ pub fn load_single_threaded<'a>(
|
|||
.send(root_msg)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
let number_of_workers = 1;
|
||||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
|
@ -1219,6 +1244,7 @@ pub fn load_single_threaded<'a>(
|
|||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
number_of_workers,
|
||||
);
|
||||
|
||||
// We'll add tasks to this, and then worker threads will take tasks from it.
|
||||
|
@ -1390,6 +1416,7 @@ fn load_multi_threaded<'a>(
|
|||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
available_threads: usize,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
|
@ -1399,6 +1426,28 @@ fn load_multi_threaded<'a>(
|
|||
..
|
||||
} = load_start;
|
||||
|
||||
let (msg_tx, msg_rx) = bounded(1024);
|
||||
msg_tx
|
||||
.send(root_msg)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
// Reserve one CPU for the main thread, and let all the others be eligible
|
||||
// to spawn workers.
|
||||
let available_workers = available_threads - 1;
|
||||
|
||||
let num_workers = match env::var("ROC_NUM_WORKERS") {
|
||||
Ok(env_str) => env_str
|
||||
.parse::<usize>()
|
||||
.unwrap_or(available_workers)
|
||||
.min(available_workers),
|
||||
Err(_) => available_workers,
|
||||
};
|
||||
|
||||
assert!(
|
||||
num_workers >= 1,
|
||||
"`load_multi_threaded` needs at least one worker"
|
||||
);
|
||||
|
||||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
|
@ -1408,28 +1457,9 @@ fn load_multi_threaded<'a>(
|
|||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
num_workers,
|
||||
);
|
||||
|
||||
let (msg_tx, msg_rx) = bounded(1024);
|
||||
msg_tx
|
||||
.send(root_msg)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
// Reserve one CPU for the main thread, and let all the others be eligible
|
||||
// to spawn workers. We use .max(2) to enforce that we always
|
||||
// end up with at least 1 worker - since (.max(2) - 1) will
|
||||
// always return a number that's at least 1. Using
|
||||
// .max(2) on the initial number of CPUs instead of
|
||||
// doing .max(1) on the entire expression guards against
|
||||
// num_cpus returning 0, while also avoiding wrapping
|
||||
// unsigned subtraction overflow.
|
||||
let default_num_workers = num_cpus::get().max(2) - 1;
|
||||
|
||||
let num_workers = match env::var("ROC_NUM_WORKERS") {
|
||||
Ok(env_str) => env_str.parse::<usize>().unwrap_or(default_num_workers),
|
||||
Err(_) => default_num_workers,
|
||||
};
|
||||
|
||||
// an arena for every worker, stored in an arena-allocated bumpalo vec to make the lifetimes work
|
||||
let arenas = std::iter::repeat_with(Bump::new).take(num_workers);
|
||||
let worker_arenas = arena.alloc(bumpalo::collections::Vec::from_iter_in(arenas, arena));
|
||||
|
|
|
@ -14,4 +14,3 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
|||
lazy_static = "1.4.0"
|
||||
static_assertions = "1.1.0"
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
arrayvec = "0.7.2"
|
||||
|
|
|
@ -21,7 +21,6 @@ roc_target = { path = "../roc_target" }
|
|||
roc_error_macros = {path="../../error_macros"}
|
||||
roc_debug_flags = {path="../debug_flags"}
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
|
||||
ven_graph = { path = "../../vendor/pathfinding" }
|
||||
|
|
|
@ -1210,15 +1210,8 @@ pub fn optimize_when<'a>(
|
|||
// bind the fields referenced in the pattern. For guards this happens separately, so
|
||||
// the pattern variables are defined when evaluating the guard.
|
||||
if !has_guard {
|
||||
branch = crate::ir::store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&pattern,
|
||||
cond_layout,
|
||||
cond_symbol,
|
||||
branch,
|
||||
);
|
||||
branch =
|
||||
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
|
||||
}
|
||||
|
||||
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
|
||||
|
@ -1730,15 +1723,7 @@ fn decide_to_branching<'a>(
|
|||
body: arena.alloc(decide),
|
||||
};
|
||||
|
||||
crate::ir::store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&pattern,
|
||||
cond_layout,
|
||||
cond_symbol,
|
||||
join,
|
||||
)
|
||||
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
|
||||
}
|
||||
Chain {
|
||||
test_chain,
|
||||
|
|
|
@ -10,10 +10,12 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
|||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::expr::{AnnotatedMark, ClosureData, IntValue};
|
||||
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
|
||||
use roc_collections::VecMap;
|
||||
use roc_debug_flags::{
|
||||
dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE,
|
||||
ROC_PRINT_IR_AFTER_SPECIALIZATION,
|
||||
};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId};
|
||||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
|
@ -743,6 +745,157 @@ impl<'a> Specialized<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Uniquely determines the specialization of a polymorphic (non-proc) value symbol.
|
||||
/// Two specializations are equivalent if their [`SpecializationMark`]s are equal.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
struct SpecializationMark<'a> {
|
||||
/// The layout of the symbol itself.
|
||||
layout: Layout<'a>,
|
||||
|
||||
/// If this symbol is a closure def, we must also keep track of what function it specializes,
|
||||
/// because the [`layout`] field will only keep track of its closure and lambda set - which can
|
||||
/// be the same for two different function specializations. For example,
|
||||
///
|
||||
/// id = if True then \x -> x else \y -> y
|
||||
/// { a: id "", b: id 1u8 }
|
||||
///
|
||||
/// The lambda set and captures of `id` is the same in both usages inside the record, but the
|
||||
/// reified specializations of `\x -> x` and `\y -> y` must be for Str and U8.
|
||||
///
|
||||
/// Note that this field is not relevant for anything that is not a function.
|
||||
function_mark: Option<RawFunctionLayout<'a>>,
|
||||
}
|
||||
|
||||
/// When walking a function body, we may encounter specialized usages of polymorphic symbols. For
|
||||
/// example
|
||||
///
|
||||
/// myTag = A
|
||||
/// use1 : [A, B]
|
||||
/// use1 = myTag
|
||||
/// use2 : [A, B, C]
|
||||
/// use2 = myTag
|
||||
///
|
||||
/// We keep track of the specializations of `myTag` and create fresh symbols when there is more
|
||||
/// than one, so that a unique def can be created for each.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct SymbolSpecializations<'a>(
|
||||
// THEORY:
|
||||
// 1. the number of symbols in a def is very small
|
||||
// 2. the number of specializations of a symbol in a def is even smaller (almost always only one)
|
||||
// So, a linear VecMap is preferrable. Use a two-layered one to make (1) extraction of defs easy
|
||||
// and (2) reads of a certain symbol be determined by its first occurrence, not its last.
|
||||
VecMap<Symbol, VecMap<SpecializationMark<'a>, (Variable, Symbol)>>,
|
||||
);
|
||||
|
||||
impl<'a> SymbolSpecializations<'a> {
|
||||
/// Gets a specialization for a symbol, or creates a new one.
|
||||
#[inline(always)]
|
||||
fn get_or_insert(
|
||||
&mut self,
|
||||
env: &mut Env<'a, '_>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
symbol: Symbol,
|
||||
specialization_var: Variable,
|
||||
) -> Symbol {
|
||||
let arena = env.arena;
|
||||
let subs: &Subs = env.subs;
|
||||
|
||||
let layout = match layout_cache.from_var(arena, specialization_var, subs) {
|
||||
Ok(layout) => layout,
|
||||
// This can happen when the def symbol has a type error. In such cases just use the
|
||||
// def symbol, which is erroring.
|
||||
Err(_) => return symbol,
|
||||
};
|
||||
|
||||
let is_closure = matches!(
|
||||
subs.get_content_without_compacting(specialization_var),
|
||||
Content::Structure(FlatType::Func(..))
|
||||
);
|
||||
let function_mark = if is_closure {
|
||||
let fn_layout = match layout_cache.raw_from_var(arena, specialization_var, subs) {
|
||||
Ok(layout) => layout,
|
||||
// This can happen when the def symbol has a type error. In such cases just use the
|
||||
// def symbol, which is erroring.
|
||||
Err(_) => return symbol,
|
||||
};
|
||||
Some(fn_layout)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let specialization_mark = SpecializationMark {
|
||||
layout,
|
||||
function_mark,
|
||||
};
|
||||
|
||||
let symbol_specializations = self.0.get_or_insert(symbol, Default::default);
|
||||
|
||||
// For the first specialization, always reuse the current symbol. The vast majority of defs
|
||||
// only have one instance type, so this preserves readability of the IR.
|
||||
// TODO: turn me off and see what breaks.
|
||||
let needs_fresh_symbol = !symbol_specializations.is_empty();
|
||||
|
||||
let mut make_specialized_symbol = || {
|
||||
if needs_fresh_symbol {
|
||||
env.unique_symbol()
|
||||
} else {
|
||||
symbol
|
||||
}
|
||||
};
|
||||
|
||||
let (_var, specialized_symbol) = symbol_specializations
|
||||
.get_or_insert(specialization_mark, || {
|
||||
(specialization_var, make_specialized_symbol())
|
||||
});
|
||||
|
||||
*specialized_symbol
|
||||
}
|
||||
|
||||
/// Inserts a known specialization for a symbol. Returns the overwritten specialization, if any.
|
||||
pub fn get_or_insert_known(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
mark: SpecializationMark<'a>,
|
||||
specialization_var: Variable,
|
||||
specialization_symbol: Symbol,
|
||||
) -> Option<(Variable, Symbol)> {
|
||||
self.0
|
||||
.get_or_insert(symbol, Default::default)
|
||||
.insert(mark, (specialization_var, specialization_symbol))
|
||||
}
|
||||
|
||||
/// Removes all specializations for a symbol, returning the type and symbol of each specialization.
|
||||
pub fn remove(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
) -> impl ExactSizeIterator<Item = (SpecializationMark<'a>, (Variable, Symbol))> {
|
||||
self.0
|
||||
.remove(&symbol)
|
||||
.map(|(_, specializations)| specializations)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
/// Expects and removes at most a single specialization symbol for the given requested symbol.
|
||||
/// A symbol may have no specializations if it is never referenced in a body, so it is possible
|
||||
/// for this to return None.
|
||||
pub fn remove_single(&mut self, symbol: Symbol) -> Option<Symbol> {
|
||||
let mut specializations = self.remove(symbol);
|
||||
|
||||
debug_assert!(
|
||||
specializations.len() < 2,
|
||||
"Symbol {:?} has multiple specializations",
|
||||
symbol
|
||||
);
|
||||
|
||||
specializations.next().map(|(_, (_, symbol))| symbol)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Procs<'a> {
|
||||
pub partial_procs: PartialProcs<'a>,
|
||||
|
@ -753,7 +906,7 @@ pub struct Procs<'a> {
|
|||
specialized: Specialized<'a>,
|
||||
pub runtime_errors: BumpMap<Symbol, &'a str>,
|
||||
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations>,
|
||||
pub needed_symbol_specializations: BumpMap<(Symbol, Layout<'a>), (Variable, Symbol)>,
|
||||
symbol_specializations: SymbolSpecializations<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Procs<'a> {
|
||||
|
@ -767,38 +920,9 @@ impl<'a> Procs<'a> {
|
|||
specialized: Specialized::default(),
|
||||
runtime_errors: BumpMap::new_in(arena),
|
||||
externals_we_need: BumpMap::new_in(arena),
|
||||
needed_symbol_specializations: BumpMap::new_in(arena),
|
||||
symbol_specializations: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expects and removes a single specialization symbol for the given requested symbol.
|
||||
/// In debug builds, we assert that the layout of the specialization is the layout expected by
|
||||
/// the requested symbol.
|
||||
fn remove_single_symbol_specialization(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
layout: Layout,
|
||||
) -> Option<Symbol> {
|
||||
let mut specialized_symbols = self
|
||||
.needed_symbol_specializations
|
||||
.drain_filter(|(sym, _), _| sym == &symbol);
|
||||
|
||||
let specialization_symbol = specialized_symbols
|
||||
.next()
|
||||
.map(|((_, specialized_layout), (_, specialized_symbol))| {
|
||||
debug_assert_eq!(specialized_layout, layout, "Requested the single specialization of {:?}, but the specialization layout ({:?}) doesn't match the expected layout ({:?})", symbol, specialized_layout, layout);
|
||||
specialized_symbol
|
||||
});
|
||||
|
||||
debug_assert_eq!(
|
||||
specialized_symbols.count(),
|
||||
0,
|
||||
"Symbol {:?} has multiple specializations",
|
||||
symbol
|
||||
);
|
||||
|
||||
specialization_symbol
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -2166,9 +2290,9 @@ pub fn specialize_all<'a>(
|
|||
specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host);
|
||||
|
||||
debug_assert!(
|
||||
procs.needed_symbol_specializations.is_empty(),
|
||||
procs.symbol_specializations.is_empty(),
|
||||
"{:?}",
|
||||
&procs.needed_symbol_specializations
|
||||
&procs.symbol_specializations
|
||||
);
|
||||
|
||||
procs
|
||||
|
@ -2503,11 +2627,10 @@ fn specialize_external<'a>(
|
|||
// An argument from the closure list may have taken on a specialized symbol
|
||||
// name during the evaluation of the def body. If this is the case, load the
|
||||
// specialized name rather than the original captured name!
|
||||
let mut get_specialized_name = |symbol, layout| {
|
||||
let mut get_specialized_name = |symbol| {
|
||||
procs
|
||||
.needed_symbol_specializations
|
||||
.remove(&(symbol, layout))
|
||||
.map(|(_, specialized)| specialized)
|
||||
.symbol_specializations
|
||||
.remove_single(symbol)
|
||||
.unwrap_or(symbol)
|
||||
};
|
||||
|
||||
|
@ -2545,7 +2668,7 @@ fn specialize_external<'a>(
|
|||
union_layout,
|
||||
};
|
||||
|
||||
let symbol = get_specialized_name(**symbol, **layout);
|
||||
let symbol = get_specialized_name(**symbol);
|
||||
|
||||
specialized_body = Stmt::Let(
|
||||
symbol,
|
||||
|
@ -2588,7 +2711,7 @@ fn specialize_external<'a>(
|
|||
structure: Symbol::ARG_CLOSURE,
|
||||
};
|
||||
|
||||
let symbol = get_specialized_name(**symbol, **layout);
|
||||
let symbol = get_specialized_name(**symbol);
|
||||
|
||||
specialized_body = Stmt::Let(
|
||||
symbol,
|
||||
|
@ -2633,11 +2756,10 @@ fn specialize_external<'a>(
|
|||
let proc_args: Vec<_> = proc_args
|
||||
.iter()
|
||||
.map(|&(layout, symbol)| {
|
||||
// Grab the specialization symbol, if it exists.
|
||||
let symbol = procs
|
||||
.needed_symbol_specializations
|
||||
// We can remove the specialization since this is the definition site.
|
||||
.remove(&(symbol, layout))
|
||||
.map(|(_, specialized_symbol)| specialized_symbol)
|
||||
.symbol_specializations
|
||||
.remove_single(symbol)
|
||||
.unwrap_or(symbol);
|
||||
|
||||
(layout, symbol)
|
||||
|
@ -3351,18 +3473,7 @@ pub fn with_hole<'a>(
|
|||
);
|
||||
|
||||
let outer_symbol = env.unique_symbol();
|
||||
let pattern_layout = layout_cache
|
||||
.from_var(env.arena, def.expr_var, env.subs)
|
||||
.expect("Pattern has no layout");
|
||||
stmt = store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&mono_pattern,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
|
||||
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
|
@ -3405,7 +3516,9 @@ pub fn with_hole<'a>(
|
|||
can_reuse_symbol(env, procs, &roc_can::expr::Expr::Var(symbol))
|
||||
{
|
||||
let real_symbol =
|
||||
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, variable);
|
||||
procs
|
||||
.symbol_specializations
|
||||
.get_or_insert(env, layout_cache, symbol, variable);
|
||||
symbol = real_symbol;
|
||||
}
|
||||
|
||||
|
@ -3484,8 +3597,12 @@ pub fn with_hole<'a>(
|
|||
match can_reuse_symbol(env, procs, &loc_arg_expr.value) {
|
||||
// Opaques decay to their argument.
|
||||
ReuseSymbol::Value(symbol) => {
|
||||
let real_name =
|
||||
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, arg_var);
|
||||
let real_name = procs.symbol_specializations.get_or_insert(
|
||||
env,
|
||||
layout_cache,
|
||||
symbol,
|
||||
arg_var,
|
||||
);
|
||||
let mut result = hole.clone();
|
||||
substitute_in_exprs(arena, &mut result, assigned, real_name);
|
||||
result
|
||||
|
@ -3538,9 +3655,8 @@ pub fn with_hole<'a>(
|
|||
can_fields.push(Field::Function(symbol, variable));
|
||||
}
|
||||
Value(symbol) => {
|
||||
let reusable = reuse_symbol_or_specialize(
|
||||
let reusable = procs.symbol_specializations.get_or_insert(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
field.var,
|
||||
|
@ -4353,25 +4469,38 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
Value(function_symbol) => match full_layout {
|
||||
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
|
||||
let closure_data_symbol = function_symbol;
|
||||
Value(function_symbol) => {
|
||||
let function_symbol = procs.symbol_specializations.get_or_insert(
|
||||
env,
|
||||
layout_cache,
|
||||
function_symbol,
|
||||
fn_var,
|
||||
);
|
||||
|
||||
result = match_on_lambda_set(
|
||||
env,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
match full_layout {
|
||||
RawFunctionLayout::Function(
|
||||
arg_layouts,
|
||||
lambda_set,
|
||||
ret_layout,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
) => {
|
||||
let closure_data_symbol = function_symbol;
|
||||
|
||||
result = match_on_lambda_set(
|
||||
env,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
unreachable!("calling a non-closure layout")
|
||||
}
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
unreachable!("calling a non-closure layout")
|
||||
}
|
||||
},
|
||||
}
|
||||
UnspecializedExpr(symbol) => {
|
||||
match procs.ability_member_aliases.get(symbol).unwrap() {
|
||||
&AbilityMember(member) => {
|
||||
|
@ -5521,7 +5650,6 @@ pub fn from_can<'a>(
|
|||
}
|
||||
LetNonRec(def, cont, outer_annotation) => {
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
|
||||
// dbg!(symbol, &def.loc_expr.value);
|
||||
match def.loc_expr.value {
|
||||
roc_can::expr::Expr::Closure(closure_data) => {
|
||||
register_capturing_closure(env, procs, layout_cache, *symbol, closure_data);
|
||||
|
@ -5653,12 +5781,14 @@ pub fn from_can<'a>(
|
|||
_ => {
|
||||
let rest = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
|
||||
let needs_def_specializations = procs
|
||||
.needed_symbol_specializations
|
||||
.keys()
|
||||
.any(|(s, _)| s == symbol);
|
||||
// Remove all the requested symbol specializations now, since this is the
|
||||
// def site and hence we won't need them any higher up.
|
||||
let mut needed_specializations =
|
||||
procs.symbol_specializations.remove(*symbol);
|
||||
|
||||
if !needs_def_specializations {
|
||||
if needed_specializations.len() == 0 {
|
||||
// We don't need any specializations, that means this symbol is never
|
||||
// referenced.
|
||||
return with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
|
@ -5674,16 +5804,9 @@ pub fn from_can<'a>(
|
|||
|
||||
let mut stmt = rest;
|
||||
|
||||
// Remove all the requested symbol specializations now, since this is the
|
||||
// def site and hence we won't need them any higher up.
|
||||
let mut needed_specializations = procs
|
||||
.needed_symbol_specializations
|
||||
.drain_filter(|(s, _), _| s == symbol)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
|
||||
if needed_specializations.len() == 1 {
|
||||
let ((_, _wanted_layout), (var, specialized_symbol)) =
|
||||
needed_specializations.pop().unwrap();
|
||||
let (_specialization_mark, (var, specialized_symbol)) =
|
||||
needed_specializations.next().unwrap();
|
||||
|
||||
// Unify the expr_var with the requested specialization once.
|
||||
let _res =
|
||||
|
@ -5700,7 +5823,7 @@ pub fn from_can<'a>(
|
|||
);
|
||||
} else {
|
||||
// Need to eat the cost and create a specialized version of the body for each specialization.
|
||||
for ((_original_symbol, _wanted_layout), (var, specialized_symbol)) in
|
||||
for (_specialization_mark, (var, specialized_symbol)) in
|
||||
needed_specializations
|
||||
{
|
||||
use crate::copy::deep_copy_type_vars_into_expr;
|
||||
|
@ -5744,89 +5867,50 @@ pub fn from_can<'a>(
|
|||
Err(_) => todo!(),
|
||||
};
|
||||
|
||||
if let Pattern::Identifier(symbol) = mono_pattern {
|
||||
let mut hole =
|
||||
env.arena
|
||||
.alloc(from_can(env, variable, cont.value, procs, layout_cache));
|
||||
if let Pattern::Identifier(_symbol) = mono_pattern {
|
||||
internal_error!("Identifier patterns should be handled in a higher code pass!")
|
||||
}
|
||||
|
||||
for (symbol, variable, expr) in assignments {
|
||||
let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole);
|
||||
// convert the continuation
|
||||
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
|
||||
hole = env.arena.alloc(stmt);
|
||||
}
|
||||
// layer on any default record fields
|
||||
for (symbol, variable, expr) in assignments {
|
||||
let specialization_symbol = procs
|
||||
.symbol_specializations
|
||||
.remove_single(symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(symbol);
|
||||
|
||||
let hole = env.arena.alloc(stmt);
|
||||
stmt = with_hole(
|
||||
env,
|
||||
expr,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
specialization_symbol,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
|
||||
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
|
||||
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
|
||||
} else {
|
||||
let outer_symbol = env.unique_symbol();
|
||||
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
|
||||
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
hole,
|
||||
outer_symbol,
|
||||
env.arena.alloc(stmt),
|
||||
)
|
||||
} else {
|
||||
// convert the continuation
|
||||
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
|
||||
// layer on any default record fields
|
||||
for (symbol, variable, expr) in assignments {
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, variable, env.subs)
|
||||
.expect("Default field has no layout");
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(symbol, layout)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(symbol);
|
||||
|
||||
let hole = env.arena.alloc(stmt);
|
||||
stmt = with_hole(
|
||||
env,
|
||||
expr,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
specialization_symbol,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
|
||||
let pattern_layout = layout_cache
|
||||
.from_var(env.arena, def.expr_var, env.subs)
|
||||
.expect("Pattern has no layout");
|
||||
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
|
||||
store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&mono_pattern,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
)
|
||||
} else {
|
||||
let outer_symbol = env.unique_symbol();
|
||||
stmt = store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&mono_pattern,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
outer_symbol,
|
||||
env.arena.alloc(stmt),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6380,19 +6464,10 @@ pub fn store_pattern<'a>(
|
|||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
can_pat: &Pattern<'a>,
|
||||
pattern_layout: Layout,
|
||||
outer_symbol: Symbol,
|
||||
stmt: Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
can_pat,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
|
||||
StorePattern::Productive(new) => new,
|
||||
StorePattern::NotProductive(new) => new,
|
||||
}
|
||||
|
@ -6412,7 +6487,6 @@ fn store_pattern_help<'a>(
|
|||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
can_pat: &Pattern<'a>,
|
||||
pattern_layout: Layout,
|
||||
outer_symbol: Symbol,
|
||||
mut stmt: Stmt<'a>,
|
||||
) -> StorePattern<'a> {
|
||||
|
@ -6423,7 +6497,8 @@ fn store_pattern_help<'a>(
|
|||
// An identifier in a pattern can define at most one specialization!
|
||||
// Remove any requested specializations for this name now, since this is the definition site.
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, pattern_layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6444,16 +6519,8 @@ fn store_pattern_help<'a>(
|
|||
return StorePattern::NotProductive(stmt);
|
||||
}
|
||||
NewtypeDestructure { arguments, .. } => match arguments.as_slice() {
|
||||
[(pattern, layout)] => {
|
||||
return store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
pattern,
|
||||
*layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
[(pattern, _layout)] => {
|
||||
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
|
||||
}
|
||||
_ => {
|
||||
let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
|
@ -6490,16 +6557,8 @@ fn store_pattern_help<'a>(
|
|||
);
|
||||
}
|
||||
OpaqueUnwrap { argument, .. } => {
|
||||
let (pattern, layout) = &**argument;
|
||||
return store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
pattern,
|
||||
*layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
let (pattern, _layout) = &**argument;
|
||||
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
|
||||
}
|
||||
|
||||
RecordDestructure(destructs, [_single_field]) => {
|
||||
|
@ -6507,7 +6566,8 @@ fn store_pattern_help<'a>(
|
|||
match &destruct.typ {
|
||||
DestructType::Required(symbol) => {
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, destruct.layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6525,7 +6585,6 @@ fn store_pattern_help<'a>(
|
|||
procs,
|
||||
layout_cache,
|
||||
guard_pattern,
|
||||
destruct.layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
|
@ -6598,7 +6657,8 @@ fn store_tag_pattern<'a>(
|
|||
Identifier(symbol) => {
|
||||
// Pattern can define only one specialization
|
||||
let symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, arg_layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
.unwrap_or(*symbol);
|
||||
|
||||
// store immediately in the given symbol
|
||||
|
@ -6619,15 +6679,7 @@ fn store_tag_pattern<'a>(
|
|||
let symbol = env.unique_symbol();
|
||||
|
||||
// first recurse, continuing to unpack symbol
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
argument,
|
||||
arg_layout,
|
||||
symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
|
||||
StorePattern::Productive(new) => {
|
||||
is_productive = true;
|
||||
stmt = new;
|
||||
|
@ -6687,7 +6739,8 @@ fn store_newtype_pattern<'a>(
|
|||
Identifier(symbol) => {
|
||||
// store immediately in the given symbol, removing it specialization if it had any
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, arg_layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6714,15 +6767,7 @@ fn store_newtype_pattern<'a>(
|
|||
let symbol = env.unique_symbol();
|
||||
|
||||
// first recurse, continuing to unpack symbol
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
argument,
|
||||
arg_layout,
|
||||
symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
|
||||
StorePattern::Productive(new) => {
|
||||
is_productive = true;
|
||||
stmt = new;
|
||||
|
@ -6770,7 +6815,8 @@ fn store_record_destruct<'a>(
|
|||
// A destructure can define at most one specialization!
|
||||
// Remove any requested specializations for this name now, since this is the definition site.
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, destruct.layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6785,7 +6831,8 @@ fn store_record_destruct<'a>(
|
|||
DestructType::Guard(guard_pattern) => match &guard_pattern {
|
||||
Identifier(symbol) => {
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, destruct.layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6823,15 +6870,7 @@ fn store_record_destruct<'a>(
|
|||
_ => {
|
||||
let symbol = env.unique_symbol();
|
||||
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
guard_pattern,
|
||||
destruct.layout,
|
||||
symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
|
||||
StorePattern::Productive(new) => {
|
||||
stmt = new;
|
||||
stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
|
||||
|
@ -6894,45 +6933,6 @@ fn can_reuse_symbol<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Reuses the specialized symbol for a given symbol and instance type. If no specialization symbol
|
||||
/// yet exists, one is created.
|
||||
fn reuse_symbol_or_specialize<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
symbol: Symbol,
|
||||
var: Variable,
|
||||
) -> Symbol {
|
||||
let wanted_layout = match layout_cache.from_var(env.arena, var, env.subs) {
|
||||
Ok(layout) => layout,
|
||||
// This can happen when the def symbol has a type error. In such cases just use the
|
||||
// def symbol, which is erroring.
|
||||
Err(_) => return symbol,
|
||||
};
|
||||
|
||||
// For the first specialization, always reuse the current symbol. The vast majority of defs
|
||||
// only have one instance type, so this preserves readability of the IR.
|
||||
let needs_fresh_symbol = procs
|
||||
.needed_symbol_specializations
|
||||
.keys()
|
||||
.any(|(s, _)| *s == symbol);
|
||||
|
||||
let mut make_specialized_symbol = || {
|
||||
if needs_fresh_symbol {
|
||||
env.unique_symbol()
|
||||
} else {
|
||||
symbol
|
||||
}
|
||||
};
|
||||
|
||||
let (_, specialized_symbol) = procs
|
||||
.needed_symbol_specializations
|
||||
.entry((symbol, wanted_layout))
|
||||
.or_insert_with(|| (var, make_specialized_symbol()));
|
||||
|
||||
*specialized_symbol
|
||||
}
|
||||
|
||||
fn possible_reuse_symbol_or_specialize<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
|
@ -6942,7 +6942,9 @@ fn possible_reuse_symbol_or_specialize<'a>(
|
|||
) -> Symbol {
|
||||
match can_reuse_symbol(env, procs, expr) {
|
||||
ReuseSymbol::Value(symbol) => {
|
||||
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, var)
|
||||
procs
|
||||
.symbol_specializations
|
||||
.get_or_insert(env, layout_cache, symbol, var)
|
||||
}
|
||||
_ => env.unique_symbol(),
|
||||
}
|
||||
|
@ -6987,16 +6989,13 @@ where
|
|||
let result = build_rest(env, procs, layout_cache);
|
||||
|
||||
// The specializations we wanted of the symbol on the LHS of this alias.
|
||||
let needed_specializations_of_left = procs
|
||||
.needed_symbol_specializations
|
||||
.drain_filter(|(s, _), _| s == &left)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let needed_specializations_of_left = procs.symbol_specializations.remove(left);
|
||||
|
||||
if procs.is_imported_module_thunk(right) {
|
||||
// if this is an imported symbol, then we must make sure it is
|
||||
// specialized, and wrap the original in a function pointer.
|
||||
let mut result = result;
|
||||
for (_, (variable, left)) in needed_specializations_of_left.into_iter() {
|
||||
for (_, (variable, left)) in needed_specializations_of_left {
|
||||
add_needed_external(procs, env, variable, right);
|
||||
|
||||
let res_layout = layout_cache.from_var(env.arena, variable, env.subs);
|
||||
|
@ -7016,14 +7015,17 @@ where
|
|||
// We need to lift all specializations of "left" to be specializations of "right".
|
||||
let mut scratchpad_update_specializations = std::vec::Vec::new();
|
||||
|
||||
let left_had_specialization_symbols = !needed_specializations_of_left.is_empty();
|
||||
let left_had_specialization_symbols = needed_specializations_of_left.len() > 0;
|
||||
|
||||
for ((_, layout), (specialized_var, specialized_sym)) in
|
||||
needed_specializations_of_left.into_iter()
|
||||
for (specialization_mark, (specialized_var, specialized_sym)) in
|
||||
needed_specializations_of_left
|
||||
{
|
||||
let old_specialized_sym = procs
|
||||
.needed_symbol_specializations
|
||||
.insert((right, layout), (specialized_var, specialized_sym));
|
||||
let old_specialized_sym = procs.symbol_specializations.get_or_insert_known(
|
||||
right,
|
||||
specialization_mark,
|
||||
specialized_var,
|
||||
specialized_sym,
|
||||
);
|
||||
|
||||
if let Some((_, old_specialized_sym)) = old_specialized_sym {
|
||||
scratchpad_update_specializations.push((old_specialized_sym, specialized_sym));
|
||||
|
|
|
@ -476,10 +476,13 @@ impl Pools {
|
|||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn split_last(&self) -> (&Vec<Variable>, &[Vec<Variable>]) {
|
||||
self.0
|
||||
.split_last()
|
||||
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools"))
|
||||
pub fn split_last(mut self) -> (Vec<Variable>, Vec<Vec<Variable>>) {
|
||||
let last = self
|
||||
.0
|
||||
.pop()
|
||||
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools"));
|
||||
|
||||
(last, self.0)
|
||||
}
|
||||
|
||||
pub fn extend_to(&mut self, n: usize) {
|
||||
|
@ -737,8 +740,7 @@ fn solve(
|
|||
|
||||
// pop pool
|
||||
generalize(subs, young_mark, visit_mark, next_rank, pools);
|
||||
|
||||
pools.get_mut(next_rank).clear();
|
||||
debug_assert!(pools.get(next_rank).is_empty());
|
||||
|
||||
// check that things went well
|
||||
dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, {
|
||||
|
@ -2426,7 +2428,7 @@ fn generalize(
|
|||
young_rank: Rank,
|
||||
pools: &mut Pools,
|
||||
) {
|
||||
let young_vars = pools.get(young_rank);
|
||||
let young_vars = std::mem::take(pools.get_mut(young_rank));
|
||||
let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars);
|
||||
|
||||
// Get the ranks right for each entry.
|
||||
|
@ -2437,12 +2439,12 @@ fn generalize(
|
|||
}
|
||||
}
|
||||
|
||||
let (last_pool, all_but_last_pool) = rank_table.split_last();
|
||||
let (mut last_pool, all_but_last_pool) = rank_table.split_last();
|
||||
|
||||
// For variables that have rank lowerer than young_rank, register them in
|
||||
// the appropriate old pool if they are not redundant.
|
||||
for vars in all_but_last_pool {
|
||||
for &var in vars {
|
||||
for var in vars {
|
||||
if !subs.redundant(var) {
|
||||
let rank = subs.get_rank(var);
|
||||
|
||||
|
@ -2453,7 +2455,7 @@ fn generalize(
|
|||
|
||||
// For variables with rank young_rank, if rank < young_rank: register in old pool,
|
||||
// otherwise generalize
|
||||
for &var in last_pool {
|
||||
for var in last_pool.drain(..) {
|
||||
if !subs.redundant(var) {
|
||||
let desc_rank = subs.get_rank(var);
|
||||
|
||||
|
@ -2464,32 +2466,38 @@ fn generalize(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// re-use the last_vector (which likely has a good capacity for future runs
|
||||
*pools.get_mut(young_rank) = last_pool;
|
||||
}
|
||||
|
||||
/// Sort the variables into buckets by rank.
|
||||
#[inline]
|
||||
fn pool_to_rank_table(
|
||||
subs: &mut Subs,
|
||||
young_mark: Mark,
|
||||
young_rank: Rank,
|
||||
young_vars: &[Variable],
|
||||
mut young_vars: Vec<Variable>,
|
||||
) -> Pools {
|
||||
let mut pools = Pools::new(young_rank.into_usize() + 1);
|
||||
|
||||
// the vast majority of young variables have young_rank
|
||||
// using `retain` here prevents many `pools.get_mut(young_rank)` lookups
|
||||
let mut young_vars = young_vars.to_vec();
|
||||
young_vars.retain(|var| {
|
||||
let rank = subs.get_rank_set_mark(*var, young_mark);
|
||||
let mut i = 0;
|
||||
while i < young_vars.len() {
|
||||
let var = young_vars[i];
|
||||
let rank = subs.get_rank_set_mark(var, young_mark);
|
||||
|
||||
if rank != young_rank {
|
||||
debug_assert!(rank.into_usize() < young_rank.into_usize() + 1);
|
||||
|
||||
pools.get_mut(rank).push(*var);
|
||||
false
|
||||
pools.get_mut(rank).push(var);
|
||||
|
||||
// swap an element in; don't increment i
|
||||
young_vars.swap_remove(i);
|
||||
} else {
|
||||
true
|
||||
i += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::mem::swap(pools.get_mut(young_rank), &mut young_vars);
|
||||
|
||||
|
|
|
@ -6234,4 +6234,22 @@ mod solve_expr {
|
|||
"F b -> b",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_in_opaque() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ foo ] to "./platform"
|
||||
|
||||
MyError : [ Error ]
|
||||
|
||||
MyResult := Result U8 MyError
|
||||
|
||||
foo = @MyResult (Err Error)
|
||||
"#
|
||||
),
|
||||
"MyResult",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3270,3 +3270,51 @@ fn dec_float_suffix() {
|
|||
i128
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn ceiling_to_u32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
n : U32
|
||||
n = Num.ceiling 124.5
|
||||
n
|
||||
"#
|
||||
),
|
||||
125,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn floor_to_u32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
n : U32
|
||||
n = Num.floor 124.5
|
||||
n
|
||||
"#
|
||||
),
|
||||
124,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn round_to_u32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
n : U32
|
||||
n = Num.round 124.49
|
||||
n
|
||||
"#
|
||||
),
|
||||
124,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3267,7 +3267,6 @@ fn polymophic_expression_captured_inside_closure() {
|
|||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[ignore = "Compile polymorphic functions"]
|
||||
fn issue_2322() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3421,7 +3420,6 @@ fn polymorphic_def_used_in_closure() {
|
|||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[ignore = "This still doesn't work... yet"]
|
||||
fn polymorphic_lambda_set_usage() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3429,6 +3427,7 @@ fn polymorphic_lambda_set_usage() {
|
|||
id1 = \x -> x
|
||||
id2 = \y -> y
|
||||
id = if True then id1 else id2
|
||||
|
||||
id 9u8
|
||||
"#
|
||||
),
|
||||
|
@ -3436,3 +3435,21 @@ fn polymorphic_lambda_set_usage() {
|
|||
u8
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn polymorphic_lambda_set_multiple_specializations() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
id1 = \x -> x
|
||||
id2 = \y -> y
|
||||
id = if True then id1 else id2
|
||||
|
||||
(id 9u8) + Num.toU8 (id 16u16)
|
||||
"#
|
||||
),
|
||||
25,
|
||||
u8
|
||||
)
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ fn create_llvm_module<'a>(
|
|||
Default::default(),
|
||||
target_info,
|
||||
RenderTarget::ColorTerminal,
|
||||
Threading::Multi,
|
||||
Threading::AllAvailable,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
|
|
@ -19,6 +19,5 @@ roc_mono = { path = "../mono" }
|
|||
roc_target = { path = "../roc_target" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
test_mono_macros = { path = "../test_mono_macros" }
|
||||
pretty_assertions = "1.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -1123,8 +1123,17 @@ impl Type {
|
|||
ext.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
|
||||
}
|
||||
}
|
||||
DelayedAlias(AliasCommon { .. }) => {
|
||||
// do nothing, yay
|
||||
DelayedAlias(AliasCommon {
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
symbol: _,
|
||||
}) => {
|
||||
debug_assert!(lambda_set_variables
|
||||
.iter()
|
||||
.all(|lambda_set| matches!(lambda_set.0, Type::Variable(..))));
|
||||
type_arguments.iter_mut().for_each(|t| {
|
||||
t.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables)
|
||||
});
|
||||
}
|
||||
HostExposedAlias {
|
||||
type_arguments: type_args,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue