Merge branch 'trunk' into update_zig_09

This commit is contained in:
Brian Carroll 2022-04-15 21:17:25 +01:00 committed by GitHub
commit 9491d5fae9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 4010 additions and 797 deletions

View file

@ -5,7 +5,7 @@ use inkwell::{
};
#[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use target_lexicon::{Architecture, OperatingSystem, Triple};
use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
pub fn target_triple_str(target: &Triple) -> &'static str {
// Best guide I've found on how to determine these magic strings:
@ -57,11 +57,23 @@ pub fn target_zig_str(target: &Triple) -> &'static str {
// and an open proposal to unify them with the more typical "target triples":
// https://github.com/ziglang/zig/issues/4911
match target {
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
..
} => "x86_64-linux-musl",
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => "x86_64-linux-gnu",
Triple {
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
operating_system: OperatingSystem::Linux,
environment: Environment::Musl,
..
} => "i386-linux-musl",
Triple {
architecture: Architecture::X86_32(target_lexicon::X86_32Architecture::I386),
operating_system: OperatingSystem::Linux,

View file

@ -60,7 +60,7 @@ Its one thing to actually write these functions, its _another_ thing to let the
## Specifying how we pass args to the function
### builtins/mono/src/borrow.rs
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for your builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
## Testing it
### solve/tests/solve_expr.rs
@ -87,7 +87,7 @@ In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc
fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
}
```
```
But replace `Num.atan`, the return value, and the return type with your new builtin.
# Mistakes that are easy to make!!

View file

@ -28,12 +28,14 @@ pub fn build(b: *Builder) void {
// TODO allow for native target for maximum speed
},
});
const i386_target = makeI386Target();
const linux32_target = makeLinux32Target();
const linux64_target = makeLinux64Target();
const wasm32_target = makeWasm32Target();
// LLVM IR
generateLlvmIrFile(b, mode, host_target, main_path, "ir", "builtins-host");
generateLlvmIrFile(b, mode, i386_target, main_path, "ir-i386", "builtins-i386");
generateLlvmIrFile(b, mode, linux32_target, main_path, "ir-i386", "builtins-i386");
generateLlvmIrFile(b, mode, linux64_target, main_path, "ir-x86_64", "builtins-x86_64");
generateLlvmIrFile(b, mode, wasm32_target, main_path, "ir-wasm32", "builtins-wasm32");
// Generate Object Files
@ -88,7 +90,7 @@ fn generateObjectFile(
obj_step.dependOn(&obj.step);
}
fn makeI386Target() CrossTarget {
fn makeLinux32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.i386;
@ -98,6 +100,16 @@ fn makeI386Target() CrossTarget {
return target;
}
fn makeLinux64Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;
target.cpu_arch = std.Target.Cpu.Arch.x86_64;
target.os_tag = std.Target.Os.Tag.linux;
target.abi = std.Target.Abi.musl;
return target;
}
fn makeWasm32Target() CrossTarget {
var target = CrossTarget.parse(.{}) catch unreachable;

View file

@ -310,9 +310,7 @@ pub const RocDec = extern struct {
// (n / 0) is an error
if (denominator_i128 == 0) {
// The compiler frontend does the `denominator == 0` check for us,
// therefore this case is unreachable from roc user code
unreachable;
@panic("TODO runtime exception for dividing by 0!");
}
// If they're both negative, or if neither is negative, the final answer

View file

@ -102,7 +102,7 @@ pub fn exportRound(comptime T: type, comptime name: []const u8) void {
pub fn exportDivCeil(comptime T: type, comptime name: []const u8) void {
comptime var f = struct {
fn func(a: T, b: T) callconv(.C) T {
return math.divCeil(T, a, b) catch unreachable;
return math.divCeil(T, a, b) catch @panic("TODO runtime exception for dividing by 0!");
}
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });

View file

@ -468,8 +468,10 @@ fn strFromIntHelp(comptime T: type, int: T) RocStr {
const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters
var buf: [40]u8 = undefined;
var result = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
break :blk result.len;
var resultMin = std.fmt.bufPrint(&buf, "{}", .{std.math.minInt(T)}) catch unreachable;
var resultMax = std.fmt.bufPrint(&buf, "{}", .{std.math.maxInt(T)}) catch unreachable;
var result = if (resultMin.len > resultMax.len) resultMin.len else resultMax.len;
break :blk result;
};
var buf: [size]u8 = undefined;

View file

@ -44,6 +44,13 @@ fn main() {
generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
generate_bc_file(
&bitcode_path,
&build_script_dir_path,
"ir-x86_64",
"builtins-x86_64",
);
// OBJECT FILES
#[cfg(windows)]
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
@ -105,7 +112,12 @@ fn generate_object_file(
println!("Moving zig object `{}` to: {}", zig_object, dest_obj);
// we store this .o file in rust's `target` folder (for wasm we need to leave a copy here too)
fs::copy(src_obj, dest_obj).expect("Failed to copy object file.");
fs::copy(src_obj, dest_obj).unwrap_or_else(|err| {
panic!(
"Failed to copy object file {} to {}: {:?}",
src_obj, dest_obj, err
);
});
}
}

View file

@ -69,6 +69,7 @@ interface Num
isNegative,
rem,
div,
divChecked,
modInt,
modFloat,
sqrt,
@ -97,7 +98,9 @@ interface Num
bytesToU16,
bytesToU32,
divCeil,
divCeilChecked,
divFloor,
divFloorChecked,
toStr,
isMultipleOf,
minI8,
@ -229,10 +232,13 @@ atan : Float a -> Float a
sqrt : Float a -> Result (Float a) [ SqrtOfNegative ]*
log : Float a -> Result (Float a) [ LogNeedsPositive ]*
div : Float a, Float a -> Result (Float a) [ DivByZero ]*
div : Float a, Float a -> Float a
divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]*
divFloor: Int a, Int a -> Result (Int a) [ DivByZero ]*
divCeil : Int a, Int a -> Int a
divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
divFloor : Int a, Int a -> Int a
divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
# mod : Float a, Float a -> Result (Float a) [ DivByZero ]*
rem : Int a, Int a -> Result (Int a) [ DivByZero ]*

View file

@ -316,17 +316,31 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(SolvedType::Wildcard),
);
// divInt : Int a, Int a -> Result (Int a) [ DivByZero ]*
// divFloor : Int a, Int a -> Int a
add_top_level_function_type!(
Symbol::NUM_DIV_INT,
Symbol::NUM_DIV_FLOOR,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1)))
);
// divFloorChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_FLOOR_CHECKED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
);
//divCeil: Int a, Int a -> Result (Int a) [ DivByZero ]*
// divCeil : Int a, Int a -> Int a
add_top_level_function_type!(
Symbol::NUM_DIV_CEIL,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1)))
);
// divCeilChecked : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_CEIL_CHECKED,
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(result_type(int_type(flex(TVAR1)), div_by_zero.clone())),
);
@ -659,6 +673,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(float_type(flex(TVAR1)))
);
// divChecked : Float a, Float a -> Result (Float a) [ DivByZero ]*
add_top_level_function_type!(
Symbol::NUM_DIV_FLOAT_CHECKED,
vec![float_type(flex(TVAR1)), float_type(flex(TVAR1))],
Box::new(result_type(float_type(flex(TVAR1)), div_by_zero.clone())),
);

View file

@ -1,55 +1,77 @@
use roc_collections::all::{MutMap, MutSet};
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use roc_types::types::Type;
use roc_region::all::Region;
use roc_types::{subs::Variable, types::Type};
use crate::annotation::HasClause;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MemberVariables {
pub able_vars: Vec<Variable>,
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
/// Stores information about an ability member definition, including the parent ability, the
/// defining type, and what type variables need to be instantiated with instances of the ability.
#[derive(Debug)]
struct AbilityMemberData {
#[allow(unused)]
parent_ability: Symbol,
#[allow(unused)]
signature: Type,
#[allow(unused)]
bound_has_clauses: Vec<HasClause>,
// TODO: SoA and put me in an arena
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbilityMemberData {
pub parent_ability: Symbol,
pub signature: Type,
pub variables: MemberVariables,
pub region: Region,
}
/// A particular specialization of an ability member.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MemberSpecialization {
pub symbol: Symbol,
pub region: Region,
}
/// Stores information about what abilities exist in a scope, what it means to implement an
/// ability, and what types implement them.
// TODO(abilities): this should probably go on the Scope, I don't put it there for now because we
// are only dealing with inter-module abilities for now.
#[derive(Default, Debug)]
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct AbilitiesStore {
/// Maps an ability to the members defining it.
#[allow(unused)]
members_of_ability: MutMap<Symbol, Vec<Symbol>>,
/// Information about all members composing abilities.
ability_members: MutMap<Symbol, AbilityMemberData>,
/// Tuples of (type, member) specifying that `type` declares an implementation of an ability
/// member `member`.
#[allow(unused)]
declared_implementations: MutSet<(Symbol, Symbol)>,
/// Map of symbols that specialize an ability member to the root ability symbol name.
/// For example, for the program
/// Hash has hash : a -> U64 | a has Hash
/// ^^^^ gets the symbol "#hash"
/// hash = \@Id n -> n
/// ^^^^ gets the symbol "#hash1"
///
/// We keep the mapping #hash1->#hash
specialization_to_root: MutMap<Symbol, Symbol>,
/// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability
/// member `member`, to the exact symbol that implements the ability.
declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>,
}
impl AbilitiesStore {
/// Records the definition of an ability, including its members.
pub fn register_ability(
&mut self,
ability: Symbol,
members: Vec<(Symbol, Type, Vec<HasClause>)>,
members: Vec<(Symbol, Region, Type, MemberVariables)>,
) {
let mut members_vec = Vec::with_capacity(members.len());
for (member, signature, bound_has_clauses) in members.into_iter() {
for (member, region, signature, variables) in members.into_iter() {
members_vec.push(member);
let old_member = self.ability_members.insert(
member,
AbilityMemberData {
parent_ability: ability,
signature,
bound_has_clauses,
region,
variables,
},
);
debug_assert!(old_member.is_none(), "Replacing existing member definition");
@ -61,14 +83,83 @@ impl AbilitiesStore {
);
}
pub fn register_implementation(&mut self, implementing_type: Symbol, ability_member: Symbol) {
let old_impl = self
.declared_implementations
.insert((implementing_type, ability_member));
debug_assert!(!old_impl, "Replacing existing implementation");
/// Records a specialization of `ability_member` with specialized type `implementing_type`.
/// Entries via this function are considered a source of truth. It must be ensured that a
/// specialization is validated before being registered here.
pub fn register_specialization_for_type(
&mut self,
ability_member: Symbol,
implementing_type: Symbol,
specialization: MemberSpecialization,
) {
let old_spec = self
.declared_specializations
.insert((ability_member, implementing_type), specialization);
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
}
/// Checks if `name` is a root ability member symbol name.
/// Note that this will return `false` for specializations of an ability member, which have
/// different symbols from the root.
pub fn is_ability_member_name(&self, name: Symbol) -> bool {
self.ability_members.contains_key(&name)
}
/// Returns information about all known ability members and their root symbols.
pub fn root_ability_members(&self) -> &MutMap<Symbol, AbilityMemberData> {
&self.ability_members
}
/// Records that the symbol `specializing_symbol` claims to specialize `ability_member`; for
/// example the symbol of `hash : Id -> U64` specializing `hash : a -> U64 | a has Hash`.
pub fn register_specializing_symbol(
&mut self,
specializing_symbol: Symbol,
ability_member: Symbol,
) {
self.specialization_to_root
.insert(specializing_symbol, ability_member);
}
/// Returns whether a symbol is declared to specialize an ability member.
pub fn is_specialization_name(&self, symbol: Symbol) -> bool {
self.specialization_to_root.contains_key(&symbol)
}
/// Finds the symbol name and ability member definition for a symbol specializing the ability
/// member, if it specializes any.
/// For example, suppose `hash : Id -> U64` has symbol #hash1 and specializes
/// `hash : a -> U64 | a has Hash` with symbol #hash. Calling this with #hash1 would retrieve
/// the ability member data for #hash.
pub fn root_name_and_def(
&self,
specializing_symbol: Symbol,
) -> Option<(Symbol, &AbilityMemberData)> {
let root_symbol = self.specialization_to_root.get(&specializing_symbol)?;
debug_assert!(self.ability_members.contains_key(root_symbol));
let root_data = self.ability_members.get(root_symbol).unwrap();
Some((*root_symbol, root_data))
}
/// Finds the ability member definition for a member name.
pub fn member_def(&self, member: Symbol) -> Option<&AbilityMemberData> {
self.ability_members.get(&member)
}
/// Returns an iterator over pairs (ability member, type) specifying that
/// "ability member" has a specialization with type "type".
pub fn get_known_specializations(&self) -> impl Iterator<Item = (Symbol, Symbol)> + '_ {
self.declared_specializations.keys().copied()
}
/// Retrieves the specialization of `member` for `typ`, if it exists.
pub fn get_specialization(&self, member: Symbol, typ: Symbol) -> Option<MemberSpecialization> {
self.declared_specializations.get(&(member, typ)).copied()
}
/// Returns pairs of (type, ability member) specifying that "ability member" has a
/// specialization with type "type".
pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> {
self.members_of_ability.get(&ability).map(|v| v.as_ref())
}
}

View file

@ -19,6 +19,22 @@ pub struct Annotation {
pub aliases: SendMap<Symbol, Alias>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum NamedOrAbleVariable<'a> {
Named(&'a NamedVariable),
Able(&'a AbleVariable),
}
impl<'a> NamedOrAbleVariable<'a> {
pub fn first_seen(&self) -> Region {
match self {
NamedOrAbleVariable::Named(nv) => nv.first_seen,
NamedOrAbleVariable::Able(av) => av.first_seen,
}
}
}
/// A named type variable, not bound to an ability.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct NamedVariable {
pub variable: Variable,
@ -27,21 +43,40 @@ pub struct NamedVariable {
pub first_seen: Region,
}
/// A type variable bound to an ability, like "a has Hash".
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct AbleVariable {
pub variable: Variable,
pub name: Lowercase,
pub ability: Symbol,
// NB: there may be multiple occurrences of a variable
pub first_seen: Region,
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct IntroducedVariables {
pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Loc<Variable>>,
pub named: Vec<NamedVariable>,
pub able: Vec<AbleVariable>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
}
impl IntroducedVariables {
#[inline(always)]
fn debug_assert_not_already_present(&self, var: Variable) {
debug_assert!((self.wildcards.iter().map(|v| &v.value))
.chain(self.lambda_sets.iter())
.chain(self.inferred.iter().map(|v| &v.value))
.chain(self.named.iter().map(|nv| &nv.variable))
.chain(self.able.iter().map(|av| &av.variable))
.chain(self.host_exposed_aliases.values())
.all(|&v| v != var));
}
pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
debug_assert!(!self
.named
.iter()
.any(|nv| nv.name == name || nv.variable == var.value));
self.debug_assert_not_already_present(var.value);
let named_variable = NamedVariable {
name,
@ -52,19 +87,36 @@ impl IntroducedVariables {
self.named.push(named_variable);
}
pub fn insert_able(&mut self, name: Lowercase, var: Loc<Variable>, ability: Symbol) {
self.debug_assert_not_already_present(var.value);
let able_variable = AbleVariable {
name,
ability,
variable: var.value,
first_seen: var.region,
};
self.able.push(able_variable);
}
pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
self.debug_assert_not_already_present(var.value);
self.wildcards.push(var);
}
pub fn insert_inferred(&mut self, var: Loc<Variable>) {
self.debug_assert_not_already_present(var.value);
self.inferred.push(var);
}
fn insert_lambda_set(&mut self, var: Variable) {
self.debug_assert_not_already_present(var);
self.lambda_sets.push(var);
}
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
self.debug_assert_not_already_present(var);
self.host_exposed_aliases.insert(symbol, var);
}
@ -78,6 +130,10 @@ impl IntroducedVariables {
self.named.extend(other.named.iter().cloned());
self.named.sort();
self.named.dedup();
self.able.extend(other.able.iter().cloned());
self.able.sort();
self.able.dedup();
}
pub fn union_owned(&mut self, other: Self) {
@ -91,22 +147,42 @@ impl IntroducedVariables {
self.named.dedup();
}
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
self.named
pub fn var_by_name(&self, name: &Lowercase) -> Option<Variable> {
(self.named.iter().map(|nv| (&nv.name, nv.variable)))
.chain(self.able.iter().map(|av| (&av.name, av.variable)))
.find(|(cand, _)| cand == &name)
.map(|(_, var)| var)
}
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<NamedOrAbleVariable> {
if let Some(nav) = self
.named
.iter()
.find(|nv| &nv.name == name)
.map(|nv| &nv.variable)
}
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
self.named
.map(NamedOrAbleVariable::Named)
{
return Some(nav);
}
self.able
.iter()
.find(|nv| nv.variable == var)
.map(|nv| &nv.name)
.find(|av| &av.name == name)
.map(NamedOrAbleVariable::Able)
}
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<&NamedVariable> {
self.named.iter().find(|nv| &nv.name == name)
pub fn collect_able(&self) -> Vec<Variable> {
self.able.iter().map(|av| av.variable).collect()
}
pub fn collect_rigid(&self) -> Vec<Variable> {
(self.named.iter().map(|nv| nv.variable))
.chain(self.wildcards.iter().map(|wc| wc.value))
// For our purposes, lambda set vars are treated like rigids
.chain(self.lambda_sets.iter().copied())
.collect()
}
pub fn collect_flex(&self) -> Vec<Variable> {
self.inferred.iter().map(|iv| iv.value).collect()
}
}
@ -147,13 +223,6 @@ pub fn canonicalize_annotation(
}
}
#[derive(Clone, Debug)]
pub struct HasClause {
pub var_name: Lowercase,
pub var: Variable,
pub ability: Symbol,
}
pub fn canonicalize_annotation_with_possible_clauses(
env: &mut Env,
scope: &mut Scope,
@ -161,16 +230,17 @@ pub fn canonicalize_annotation_with_possible_clauses(
region: Region,
var_store: &mut VarStore,
abilities_in_scope: &[Symbol],
) -> (Annotation, Vec<Loc<HasClause>>) {
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default();
let mut aliases = SendMap::default();
let (annotation, region, clauses) = match annotation {
let (annotation, region) = match annotation {
TypeAnnotation::Where(annotation, clauses) => {
let mut can_clauses = Vec::with_capacity(clauses.len());
// Add each "has" clause. The association of a variable to an ability will be saved on
// `introduced_variables`, which we'll process later.
for clause in clauses.iter() {
match canonicalize_has_clause(
let opt_err = canonicalize_has_clause(
env,
scope,
var_store,
@ -178,24 +248,19 @@ pub fn canonicalize_annotation_with_possible_clauses(
clause,
abilities_in_scope,
&mut references,
) {
Ok(result) => can_clauses.push(Loc::at(clause.region, result)),
Err(err_type) => {
return (
Annotation {
typ: err_type,
introduced_variables,
references,
aliases,
},
can_clauses,
)
}
};
);
if let Err(err_type) = opt_err {
return Annotation {
typ: err_type,
introduced_variables,
references,
aliases,
};
}
}
(&annotation.value, annotation.region, can_clauses)
(&annotation.value, annotation.region)
}
annot => (annot, region, vec![]),
annot => (annot, region),
};
let typ = can_annotation_help(
@ -209,14 +274,12 @@ pub fn canonicalize_annotation_with_possible_clauses(
&mut references,
);
let annot = Annotation {
Annotation {
typ,
introduced_variables,
references,
aliases,
};
(annot, clauses)
}
}
fn make_apply_symbol(
@ -502,7 +565,7 @@ fn can_annotation_help(
let name = Lowercase::from(*v);
match introduced_variables.var_by_name(&name) {
Some(var) => Type::Variable(*var),
Some(var) => Type::Variable(var),
None => {
let var = var_store.fresh();
@ -566,8 +629,8 @@ fn can_annotation_help(
let var_name = Lowercase::from(var);
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var)));
vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
} else {
let var = var_store.fresh();
@ -799,7 +862,7 @@ fn canonicalize_has_clause(
clause: &Loc<roc_parse::ast::HasClause<'_>>,
abilities_in_scope: &[Symbol],
references: &mut MutSet<Symbol>,
) -> Result<HasClause, Type> {
) -> Result<(), Type> {
let Loc {
region,
value: roc_parse::ast::HasClause { var, ability },
@ -836,25 +899,21 @@ fn canonicalize_has_clause(
let var_name_ident = var_name.to_string().into();
let shadow = Loc::at(region, var_name_ident);
env.problem(roc_problem::can::Problem::Shadowing {
original_region: shadowing.first_seen,
original_region: shadowing.first_seen(),
shadow: shadow.clone(),
kind: ShadowKind::Variable,
});
return Err(Type::Erroneous(Problem::Shadowed(
shadowing.first_seen,
shadowing.first_seen(),
shadow,
)));
}
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), Loc::at(region, var));
introduced_variables.insert_able(var_name, Loc::at(region, var), ability);
Ok(HasClause {
var_name,
var,
ability,
})
Ok(())
}
#[allow(clippy::too_many_arguments)]
@ -1105,7 +1164,7 @@ fn can_assigned_fields<'a>(
let field_name = Lowercase::from(loc_field_name.value);
let field_type = {
if let Some(var) = introduced_variables.var_by_name(&field_name) {
Type::Variable(*var)
Type::Variable(var)
} else {
let field_var = var_store.fresh();
introduced_variables.insert_named(

View file

@ -195,8 +195,11 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_COS => num_cos,
NUM_TAN => num_tan,
NUM_DIV_FLOAT => num_div_float,
NUM_DIV_INT => num_div_int,
NUM_DIV_FLOAT_CHECKED => num_div_float_checked,
NUM_DIV_FLOOR => num_div_floor,
NUM_DIV_FLOOR_CHECKED => num_div_floor_checked,
NUM_DIV_CEIL => num_div_ceil,
NUM_DIV_CEIL_CHECKED => num_div_ceil_checked,
NUM_ABS => num_abs,
NUM_NEG => num_neg,
NUM_REM => num_rem,
@ -4295,8 +4298,13 @@ fn num_abs(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.div : Float, Float -> Result Float [ DivByZero ]*
/// Num.div : Float, Float -> Float
fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivUnchecked)
}
/// Num.divChecked : Float, Float -> Result Float [ DivByZero ]*
fn num_div_float_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -4361,8 +4369,13 @@ fn num_div_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.div : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
/// Num.divFloor : Int a, Int a -> Int a
fn num_div_floor(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivUnchecked)
}
/// Num.divFloorChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_floor_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();
@ -4432,8 +4445,13 @@ fn num_div_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Num.divCeil : Int a , Int a -> Result (Int a) [ DivByZero ]*
/// Num.divCeil : Int a, Int a -> Int a
fn num_div_ceil(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumDivCeilUnchecked)
}
/// Num.divCeilChecked : Int a , Int a -> Result (Int a) [ DivByZero ]*
fn num_div_ceil_checked(symbol: Symbol, var_store: &mut VarStore) -> Def {
let bool_var = var_store.fresh();
let num_var = var_store.fresh();
let unbound_zero_var = var_store.fresh();

View file

@ -1,4 +1,4 @@
use crate::abilities::AbilitiesStore;
use crate::abilities::MemberVariables;
use crate::annotation::canonicalize_annotation;
use crate::annotation::canonicalize_annotation_with_possible_clauses;
use crate::annotation::IntroducedVariables;
@ -430,12 +430,11 @@ pub fn canonicalize_defs<'a>(
}
// Now we can go through and resolve all pending abilities, to add them to scope.
let mut abilities_store = AbilitiesStore::default();
for (loc_ability_name, members) in abilities.into_values() {
let mut can_members = Vec::with_capacity(members.len());
for member in members {
let (member_annot, clauses) = canonicalize_annotation_with_possible_clauses(
let member_annot = canonicalize_annotation_with_possible_clauses(
env,
&mut scope,
&member.typ.value,
@ -450,13 +449,14 @@ pub fn canonicalize_defs<'a>(
output.references.referenced_type_defs.insert(symbol);
}
let name_region = member.name.region;
let member_name = member.name.extract_spaces().item;
let member_sym = match scope.introduce(
member_name.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
member.name.region,
name_region,
) {
Ok(sym) => sym,
Err((original_region, shadow, _new_symbol)) => {
@ -473,9 +473,11 @@ pub fn canonicalize_defs<'a>(
// What variables in the annotation are bound to the parent ability, and what variables
// are bound to some other ability?
let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) =
clauses
.into_iter()
.partition(|has_clause| has_clause.value.ability == loc_ability_name.value);
member_annot
.introduced_variables
.able
.iter()
.partition(|av| av.ability == loc_ability_name.value);
let mut bad_has_clauses = false;
@ -485,18 +487,38 @@ pub fn canonicalize_defs<'a>(
env.problem(Problem::AbilityMemberMissingHasClause {
member: member_sym,
ability: loc_ability_name.value,
region: member.name.region,
region: name_region,
});
bad_has_clauses = true;
}
if variables_bound_to_ability.len() > 1 {
// There is more than one variable bound to the member signature, so something like
// Eq has eq : a, b -> Bool | a has Eq, b has Eq
// We have no way of telling what type implements a particular instance of Eq in
// this case (a or b?), so disallow it.
let span_has_clauses =
Region::across_all(variables_bound_to_ability.iter().map(|v| &v.first_seen));
let bound_var_names = variables_bound_to_ability
.iter()
.map(|v| v.name.clone())
.collect();
env.problem(Problem::AbilityMemberMultipleBoundVars {
member: member_sym,
ability: loc_ability_name.value,
span_has_clauses,
bound_var_names,
});
bad_has_clauses = true;
}
if !variables_bound_to_other_abilities.is_empty() {
// Disallow variables bound to other abilities, for now.
for bad_clause in variables_bound_to_other_abilities.iter() {
for bad_variable in variables_bound_to_other_abilities.iter() {
env.problem(Problem::AbilityMemberBindsExternalAbility {
member: member_sym,
ability: loc_ability_name.value,
region: bad_clause.region,
region: bad_variable.first_seen,
});
}
bad_has_clauses = true;
@ -507,15 +529,26 @@ pub fn canonicalize_defs<'a>(
continue;
}
let has_clauses = variables_bound_to_ability
.into_iter()
.map(|clause| clause.value)
.collect();
can_members.push((member_sym, member_annot.typ, has_clauses));
// The introduced variables are good; add them to the output.
output
.introduced_variables
.union(&member_annot.introduced_variables);
let iv = member_annot.introduced_variables;
let variables = MemberVariables {
able_vars: iv.collect_able(),
rigid_vars: iv.collect_rigid(),
flex_vars: iv.collect_flex(),
};
can_members.push((member_sym, name_region, member_annot.typ, variables));
}
// Store what symbols a type must define implementations for to have this ability.
abilities_store.register_ability(loc_ability_name.value, can_members);
scope
.abilities_store
.register_ability(loc_ability_name.value, can_members);
}
// Now that we have the scope completely assembled, and shadowing resolved,
@ -526,14 +559,7 @@ pub fn canonicalize_defs<'a>(
// once we've finished assembling the entire scope.
let mut pending_value_defs = Vec::with_capacity(value_defs.len());
for loc_def in value_defs.into_iter() {
match to_pending_value_def(
env,
var_store,
loc_def.value,
&mut scope,
&abilities_store,
pattern_type,
) {
match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) {
None => { /* skip */ }
Some((new_output, pending_def)) => {
// store the top-level defs, used to ensure that closures won't capture them
@ -1201,7 +1227,9 @@ fn canonicalize_pending_value_def<'a>(
}
};
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let Pattern::Identifier(symbol)
| Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value
{
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
@ -1289,7 +1317,9 @@ fn canonicalize_pending_value_def<'a>(
// which also implies it's not a self tail call!
//
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let Pattern::Identifier(symbol)
| Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value
{
if let Closure(ClosureData {
function_type,
closure_type,
@ -1561,7 +1591,9 @@ pub fn can_defs_with_return<'a>(
// Now that we've collected all the references, check to see if any of the new idents
// we defined went unused by the return expression. If any were unused, report it.
for (symbol, region) in symbols_introduced {
if !output.references.has_value_lookup(symbol) && !output.references.has_type_lookup(symbol)
if !output.references.has_value_lookup(symbol)
&& !output.references.has_type_lookup(symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{
env.problem(Problem::UnusedDef(symbol, region));
}
@ -1772,7 +1804,6 @@ fn to_pending_value_def<'a>(
var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>,
scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType,
) -> Option<(Output, PendingValueDef<'a>)> {
use ast::ValueDef::*;
@ -1784,7 +1815,6 @@ fn to_pending_value_def<'a>(
env,
var_store,
scope,
abilities_store,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
@ -1801,7 +1831,6 @@ fn to_pending_value_def<'a>(
env,
var_store,
scope,
abilities_store,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
@ -1832,7 +1861,6 @@ fn to_pending_value_def<'a>(
env,
var_store,
scope,
abilities_store,
pattern_type,
&body_pattern.value,
body_pattern.region,

View file

@ -44,6 +44,15 @@ impl<T> PExpected<T> {
PExpected::ForReason(reason, _val, region) => PExpected::ForReason(reason, new, region),
}
}
pub fn replace_ref<U>(&self, new: U) -> PExpected<U> {
match self {
PExpected::NoExpectation(_val) => PExpected::NoExpectation(new),
PExpected::ForReason(reason, _val, region) => {
PExpected::ForReason(reason.clone(), new, *region)
}
}
}
}
impl<T> Expected<T> {

View file

@ -1083,6 +1083,7 @@ fn canonicalize_when_branch<'a>(
&& !branch_output.references.has_value_lookup(symbol)
&& !branch_output.references.has_type_lookup(symbol)
&& !original_scope.contains_symbol(symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{
env.problem(Problem::UnusedDef(symbol, *region));
}

View file

@ -1,3 +1,4 @@
use crate::abilities::AbilitiesStore;
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::effect_module::HostedGeneratedFunctions;
use crate::env::Env;
@ -28,11 +29,13 @@ pub struct Module {
/// all aliases. `bool` indicates whether it is exposed
pub aliases: MutMap<Symbol, (bool, Alias)>,
pub rigid_variables: RigidVariables,
pub abilities_store: AbilitiesStore,
}
#[derive(Debug, Default)]
pub struct RigidVariables {
pub named: MutMap<Variable, Lowercase>,
pub able: MutMap<Variable, (Lowercase, Symbol)>,
pub wildcards: MutSet<Variable>,
}
@ -250,6 +253,7 @@ pub fn canonicalize_module_defs<'a>(
if !output.references.has_value_lookup(symbol)
&& !output.references.has_type_lookup(symbol)
&& !exposed_symbols.contains(&symbol)
&& !scope.abilities_store.is_specialization_name(symbol)
{
env.problem(Problem::UnusedDef(symbol, region));
}
@ -259,6 +263,12 @@ pub fn canonicalize_module_defs<'a>(
rigid_variables.named.insert(named.variable, named.name);
}
for able in output.introduced_variables.able {
rigid_variables
.able
.insert(able.variable, (able.name, able.ability));
}
for var in output.introduced_variables.wildcards {
rigid_variables.wildcards.insert(var.value);
}
@ -444,6 +454,10 @@ pub fn canonicalize_module_defs<'a>(
aliases.insert(symbol, alias);
}
for member in scope.abilities_store.root_ability_members().keys() {
exposed_but_not_defined.remove(member);
}
// By this point, all exposed symbols should have been removed from
// exposed_symbols and added to exposed_vars_by_symbol. If any were
// not, that means they were declared as exposed but there was

View file

@ -1,4 +1,3 @@
use crate::abilities::AbilitiesStore;
use crate::annotation::freshen_opaque_def;
use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
@ -157,7 +156,6 @@ pub fn canonicalize_def_header_pattern<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType,
pattern: &ast::Pattern<'a>,
region: Region,
@ -172,7 +170,6 @@ pub fn canonicalize_def_header_pattern<'a>(
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
abilities_store,
) {
Ok((symbol, shadowing_ability_member)) => {
output.references.bound_symbols.insert(symbol);

View file

@ -22,7 +22,7 @@ pub struct Scope {
pub aliases: SendMap<Symbol, Alias>,
/// The abilities currently in scope, and their implementors.
pub abilities: SendMap<Symbol, Region>,
pub abilities_store: AbilitiesStore,
/// The current module being processed. This will be used to turn
/// unqualified idents into Symbols.
@ -68,7 +68,7 @@ impl Scope {
symbols: SendMap::default(),
aliases,
// TODO(abilities): default abilities in scope
abilities: SendMap::default(),
abilities_store: AbilitiesStore::default(),
}
}
@ -247,7 +247,6 @@ impl Scope {
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
abilities_store: &AbilitiesStore,
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
match self.idents.get(&ident) {
Some(&(original_symbol, original_region)) => {
@ -256,7 +255,9 @@ impl Scope {
self.symbols.insert(shadow_symbol, region);
if abilities_store.is_ability_member_name(original_symbol) {
if self.abilities_store.is_ability_member_name(original_symbol) {
self.abilities_store
.register_specializing_symbol(shadow_symbol, original_symbol);
// Add a symbol for the shadow, but don't re-associate the member name.
Ok((shadow_symbol, Some(original_symbol)))
} else {

View file

@ -1,4 +1,5 @@
use roc_builtins::std::StdLib;
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint, Constraints};
use roc_can::def::Declaration;
use roc_collections::all::MutMap;
@ -100,10 +101,32 @@ pub enum ExposedModuleTypes {
pub fn constrain_module(
constraints: &mut Constraints,
abilities_store: &AbilitiesStore,
declarations: &[Declaration],
home: ModuleId,
) -> Constraint {
crate::expr::constrain_decls(constraints, home, declarations)
let mut constraint = crate::expr::constrain_decls(constraints, home, declarations);
for (member_name, member_data) in abilities_store.root_ability_members().iter() {
let vars = &member_data.variables;
let rigids = (vars.rigid_vars.iter())
// For our purposes, in the let constraint, able vars are treated like rigids.
.chain(vars.able_vars.iter())
.copied();
let flex = vars.flex_vars.iter().copied();
constraint = constraints.let_constraint(
rigids,
flex,
[(*member_name, Loc::at_zero(member_data.signature.clone()))],
Constraint::True,
constraint,
);
}
// The module constraint should always save the environment at the end.
debug_assert!(constraints.contains_save_the_environment(&constraint));
constraint
}
#[derive(Debug, Clone)]

View file

@ -188,9 +188,23 @@ pub fn constrain_pattern(
// Erroneous patterns don't add any constraints.
}
Identifier(symbol) | Shadowed(_, _, symbol)
// TODO(abilities): handle linking the member def to the specialization ident
| AbilityMemberSpecialization {
Identifier(symbol) | Shadowed(_, _, symbol) => {
if could_be_a_tag_union(expected.get_type_ref()) {
state
.constraints
.push(constraints.is_open_type(expected.get_type_ref().clone()));
}
state.headers.insert(
*symbol,
Loc {
region,
value: expected.get_type(),
},
);
}
AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {

View file

@ -429,6 +429,13 @@ pub fn module_from_builtins<'ctx>(
} => {
include_bytes!("../../../builtins/bitcode/builtins-i386.bc")
}
Triple {
architecture: Architecture::X86_64,
operating_system: OperatingSystem::Linux,
..
} => {
include_bytes!("../../../builtins/bitcode/builtins-x86_64.bc")
}
_ => panic!(
"The zig builtins are not currently built for this target: {:?}",
target

View file

@ -13,10 +13,12 @@ roc_constrain= { path = "../constrain" }
roc_types = { path = "../types" }
roc_module = { path = "../module" }
roc_collections = { path = "../collections" }
roc_reporting = { path = "../../reporting" }
[build-dependencies]
roc_load_internal = { path = "../load_internal" }
roc_builtins = { path = "../builtins" }
roc_module = { path = "../module" }
roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" }
bumpalo = { version = "3.8.0", features = ["collections"] }

View file

@ -36,6 +36,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
&src_dir,
Default::default(),
target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
);
let module = res_module.unwrap();

View file

@ -2,6 +2,7 @@ use bumpalo::Bump;
use roc_collections::all::MutMap;
use roc_constrain::module::ExposedByModule;
use roc_module::symbol::{ModuleId, Symbol};
use roc_reporting::report::RenderTarget;
use roc_target::TargetInfo;
use roc_types::subs::{Subs, Variable};
use std::path::{Path, PathBuf};
@ -18,6 +19,7 @@ fn load<'a>(
exposed_types: ExposedByModule,
goal_phase: Phase,
target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let cached_subs = read_cached_subs();
@ -29,6 +31,7 @@ fn load<'a>(
goal_phase,
target_info,
cached_subs,
render,
)
}
@ -39,6 +42,7 @@ pub fn load_and_monomorphize_from_str<'a>(
src_dir: &Path,
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*;
@ -51,6 +55,7 @@ pub fn load_and_monomorphize_from_str<'a>(
exposed_types,
Phase::MakeSpecializations,
target_info,
render,
)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
@ -63,10 +68,11 @@ pub fn load_and_monomorphize<'a>(
src_dir: &Path,
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename)?;
let load_start = LoadStart::from_path(arena, filename, render)?;
match load(
arena,
@ -75,6 +81,7 @@ pub fn load_and_monomorphize<'a>(
exposed_types,
Phase::MakeSpecializations,
target_info,
render,
)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
@ -87,10 +94,11 @@ pub fn load_and_typecheck<'a>(
src_dir: &Path,
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename)?;
let load_start = LoadStart::from_path(arena, filename, render)?;
match load(
arena,
@ -99,6 +107,7 @@ pub fn load_and_typecheck<'a>(
exposed_types,
Phase::SolveTypes,
target_info,
render,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),

View file

@ -33,4 +33,3 @@ tempfile = "3.2.0"
pretty_assertions = "1.0.0"
maplit = "1.0.2"
indoc = "1.0.3"
strip-ansi-escapes = "0.1.1"

View file

@ -5,6 +5,7 @@ use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread;
use parking_lot::Mutex;
use roc_builtins::std::borrow_stdlib;
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
use roc_can::def::Declaration;
use roc_can::module::{canonicalize_module_defs, Module};
@ -31,6 +32,7 @@ use roc_parse::ident::UppercaseIdent;
use roc_parse::module::module_defs;
use roc_parse::parser::{FileError, Parser, SyntaxError};
use roc_region::all::{LineInfo, Loc, Region};
use roc_reporting::report::RenderTarget;
use roc_solve::module::SolvedModule;
use roc_solve::solve;
use roc_target::TargetInfo;
@ -347,6 +349,7 @@ pub struct LoadedModule {
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub timings: MutMap<ModuleId, ModuleTiming>,
pub documentation: MutMap<ModuleId, ModuleDocumentation>,
pub abilities_store: AbilitiesStore,
}
impl LoadedModule {
@ -508,6 +511,7 @@ enum Msg<'a> {
decls: Vec<Declaration>,
dep_idents: MutMap<ModuleId, IdentIds>,
module_timing: ModuleTiming,
abilities_store: AbilitiesStore,
},
FinishedAllTypeChecking {
solved_subs: Solved<Subs>,
@ -515,6 +519,7 @@ enum Msg<'a> {
exposed_aliases_by_symbol: MutMap<Symbol, (bool, Alias)>,
dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
},
FoundSpecializations {
module_id: ModuleId,
@ -604,6 +609,8 @@ struct State<'a> {
// (Granted, this has not been attempted or measured!)
pub layout_caches: std::vec::Vec<LayoutCache<'a>>,
pub render: RenderTarget,
// cached subs (used for builtin modules, could include packages in the future too)
cached_subs: CachedSubs,
}
@ -611,6 +618,7 @@ struct State<'a> {
type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>;
impl<'a> State<'a> {
#[allow(clippy::too_many_arguments)]
fn new(
root_id: ModuleId,
target_info: TargetInfo,
@ -619,6 +627,7 @@ impl<'a> State<'a> {
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Self {
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
@ -643,6 +652,7 @@ impl<'a> State<'a> {
timings: MutMap::default(),
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
cached_subs: Arc::new(Mutex::new(cached_subs)),
render,
}
}
}
@ -824,6 +834,7 @@ pub fn load_and_typecheck_str<'a>(
src_dir: &Path,
exposed_types: ExposedByModule,
target_info: TargetInfo,
render: RenderTarget,
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
@ -841,12 +852,19 @@ pub fn load_and_typecheck_str<'a>(
Phase::SolveTypes,
target_info,
cached_subs,
render,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),
}
}
#[derive(Clone, Copy)]
pub enum PrintTarget {
ColorTerminal,
Generic,
}
pub struct LoadStart<'a> {
arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
ident_ids_by_module: Arc<Mutex<MutMap<ModuleId, IdentIds>>>,
@ -855,7 +873,11 @@ pub struct LoadStart<'a> {
}
impl<'a> LoadStart<'a> {
pub fn from_path(arena: &'a Bump, filename: PathBuf) -> Result<Self, LoadingProblem<'a>> {
pub fn from_path(
arena: &'a Bump,
filename: PathBuf,
render: RenderTarget,
) -> Result<Self, LoadingProblem<'a>> {
let arc_modules = Arc::new(Mutex::new(PackageModuleIds::default()));
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let ident_ids_by_module = Arc::new(Mutex::new(root_exposed_ident_ids));
@ -887,7 +909,12 @@ impl<'a> LoadStart<'a> {
// if parsing failed, this module did not add any identifiers
let root_exposed_ident_ids = IdentIds::exposed_builtins(0);
let buf = to_parse_problem_report(problem, module_ids, root_exposed_ident_ids);
let buf = to_parse_problem_report(
problem,
module_ids,
root_exposed_ident_ids,
render,
);
return Err(LoadingProblem::FormattedReport(buf));
}
Err(LoadingProblem::FileProblem { filename, error }) => {
@ -995,6 +1022,7 @@ pub fn load<'a>(
goal_phase: Phase,
target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
// When compiling to wasm, we cannot spawn extra threads
// so we have a single-threaded implementation
@ -1007,6 +1035,7 @@ pub fn load<'a>(
goal_phase,
target_info,
cached_subs,
render,
)
} else {
load_multi_threaded(
@ -1017,6 +1046,7 @@ pub fn load<'a>(
goal_phase,
target_info,
cached_subs,
render,
)
}
}
@ -1031,12 +1061,14 @@ fn load_single_threaded<'a>(
goal_phase: Phase,
target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart {
arc_modules,
ident_ids_by_module,
root_id,
root_msg,
..
} = load_start;
let (msg_tx, msg_rx) = bounded(1024);
@ -1053,6 +1085,7 @@ fn load_single_threaded<'a>(
arc_modules,
ident_ids_by_module,
cached_subs,
render,
);
// We'll add tasks to this, and then worker threads will take tasks from it.
@ -1115,6 +1148,7 @@ fn state_thread_step<'a>(
exposed_aliases_by_symbol,
dep_idents,
documentation,
abilities_store,
} => {
// We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty());
@ -1131,6 +1165,7 @@ fn state_thread_step<'a>(
exposed_vars_by_symbol,
dep_idents,
documentation,
abilities_store,
);
Ok(ControlFlow::Break(LoadResult::TypeChecked(typechecked)))
@ -1153,8 +1188,12 @@ fn state_thread_step<'a>(
Msg::FailedToParse(problem) => {
let module_ids = (*state.arc_modules).lock().clone().into_module_ids();
let buf =
to_parse_problem_report(problem, module_ids, state.constrained_ident_ids);
let buf = to_parse_problem_report(
problem,
module_ids,
state.constrained_ident_ids,
state.render,
);
Err(LoadingProblem::FormattedReport(buf))
}
msg => {
@ -1164,6 +1203,8 @@ fn state_thread_step<'a>(
let constrained_ident_ids = state.constrained_ident_ids.clone();
let arc_modules = state.arc_modules.clone();
let render = state.render;
let res_state = update(
state,
msg,
@ -1185,8 +1226,12 @@ fn state_thread_step<'a>(
.into_inner()
.into_module_ids();
let buf =
to_parse_problem_report(problem, module_ids, constrained_ident_ids);
let buf = to_parse_problem_report(
problem,
module_ids,
constrained_ident_ids,
render,
);
Err(LoadingProblem::FormattedReport(buf))
}
Err(e) => Err(e),
@ -1210,12 +1255,14 @@ fn load_multi_threaded<'a>(
goal_phase: Phase,
target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart {
arc_modules,
ident_ids_by_module,
root_id,
root_msg,
..
} = load_start;
let mut state = State::new(
@ -1226,6 +1273,7 @@ fn load_multi_threaded<'a>(
arc_modules,
ident_ids_by_module,
cached_subs,
render,
);
let (msg_tx, msg_rx) = bounded(1024);
@ -1746,6 +1794,7 @@ fn update<'a>(
decls,
dep_idents,
mut module_timing,
abilities_store,
} => {
log!("solved types for {:?}", module_id);
module_timing.end_time = SystemTime::now();
@ -1798,6 +1847,7 @@ fn update<'a>(
exposed_aliases_by_symbol: solved_module.aliases,
dep_idents,
documentation,
abilities_store,
})
.map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -2126,6 +2176,7 @@ fn finish(
exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
dep_idents: MutMap<ModuleId, IdentIds>,
documentation: MutMap<ModuleId, ModuleDocumentation>,
abilities_store: AbilitiesStore,
) -> LoadedModule {
let module_ids = Arc::try_unwrap(state.arc_modules)
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
@ -2160,6 +2211,7 @@ fn finish(
sources,
timings: state.timings,
documentation,
abilities_store,
}
}
@ -3102,6 +3154,10 @@ fn add_imports(
rigid_vars.extend(copied_import.rigid);
rigid_vars.extend(copied_import.flex);
// Rigid vars bound to abilities are also treated like rigids.
rigid_vars.extend(copied_import.rigid_able);
rigid_vars.extend(copied_import.flex_able);
import_variables.extend(copied_import.registered);
def_types.push((
@ -3119,6 +3175,7 @@ fn add_imports(
import_variables
}
#[allow(clippy::complexity)]
fn run_solve_solve(
imported_builtins: Vec<Symbol>,
exposed_for_module: ExposedForModule,
@ -3126,11 +3183,17 @@ fn run_solve_solve(
constraint: ConstraintSoa,
mut var_store: VarStore,
module: Module,
) -> (Solved<Subs>, Vec<(Symbol, Variable)>, Vec<solve::TypeError>) {
) -> (
Solved<Subs>,
Vec<(Symbol, Variable)>,
Vec<solve::TypeError>,
AbilitiesStore,
) {
let Module {
exposed_symbols,
aliases,
rigid_variables,
abilities_store,
..
} = module;
@ -3155,12 +3218,13 @@ fn run_solve_solve(
solve_aliases.insert(*name, alias.clone());
}
let (solved_subs, solved_env, problems) = roc_solve::module::run_solve(
let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve(
&constraints,
actual_constraint,
rigid_variables,
subs,
solve_aliases,
abilities_store,
);
let solved_subs = if true {
@ -3179,7 +3243,12 @@ fn run_solve_solve(
.filter(|(k, _)| exposed_symbols.contains(k))
.collect();
(solved_subs, exposed_vars_by_symbol, problems)
(
solved_subs,
exposed_vars_by_symbol,
problems,
abilities_store,
)
}
#[allow(clippy::too_many_arguments)]
@ -3203,7 +3272,7 @@ fn run_solve<'a>(
// TODO remove when we write builtins in roc
let aliases = module.aliases.clone();
let (solved_subs, exposed_vars_by_symbol, problems) = {
let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = {
if module_id.is_builtin() {
match cached_subs.lock().remove(&module_id) {
None => {
@ -3217,9 +3286,13 @@ fn run_solve<'a>(
module,
)
}
Some((subs, exposed_vars_by_symbol)) => {
(Solved(subs), exposed_vars_by_symbol.to_vec(), vec![])
}
Some((subs, exposed_vars_by_symbol)) => (
Solved(subs),
exposed_vars_by_symbol.to_vec(),
vec![],
// TODO(abilities) replace when we have abilities for builtins
AbilitiesStore::default(),
),
}
} else {
run_solve_solve(
@ -3258,6 +3331,7 @@ fn run_solve<'a>(
dep_idents,
solved_module,
module_timing,
abilities_store,
}
}
@ -3385,8 +3459,12 @@ fn canonicalize_and_constrain<'a>(
let mut constraints = Constraints::new();
let constraint =
constrain_module(&mut constraints, &module_output.declarations, module_id);
let constraint = constrain_module(
&mut constraints,
&module_output.scope.abilities_store,
&module_output.declarations,
module_id,
);
let after = roc_types::types::get_type_clone_count();
@ -3426,6 +3504,7 @@ fn canonicalize_and_constrain<'a>(
referenced_types: module_output.referenced_types,
aliases,
rigid_variables: module_output.rigid_variables,
abilities_store: module_output.scope.abilities_store,
};
let constrained_module = ConstrainedModule {
@ -3859,6 +3938,7 @@ fn add_def_to_module<'a>(
// This is a top-level definition, so it cannot capture anything
captured_symbols: CapturedSymbols::None,
body,
body_var: def.expr_var,
// This is a 0-arity thunk, so it cannot be recursive
is_self_recursive: false,
};
@ -4068,6 +4148,7 @@ fn to_parse_problem_report<'a>(
problem: FileError<'a, SyntaxError<'a>>,
mut module_ids: ModuleIds,
all_ident_ids: MutMap<ModuleId, IdentIds>,
render: RenderTarget,
) -> String {
use roc_reporting::report::{parse_problem, RocDocAllocator, DEFAULT_PALETTE};
@ -4102,7 +4183,7 @@ fn to_parse_problem_report<'a>(
let mut buf = String::new();
let palette = DEFAULT_PALETTE;
report.render_color_terminal(&mut buf, &alloc, &palette);
report.render(render, &mut buf, &alloc, &palette);
buf
}

View file

@ -25,6 +25,7 @@ mod test_load {
use roc_problem::can::Problem;
use roc_region::all::LineInfo;
use roc_reporting::report::can_problem;
use roc_reporting::report::RenderTarget;
use roc_reporting::report::RocDocAllocator;
use roc_target::TargetInfo;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
@ -41,7 +42,7 @@ mod test_load {
) -> Result<LoadedModule, LoadingProblem<'a>> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename)?;
let load_start = LoadStart::from_path(arena, filename, RenderTarget::Generic)?;
match roc_load_internal::file::load(
arena,
@ -51,6 +52,7 @@ mod test_load {
Phase::SolveTypes,
target_info,
Default::default(), // these tests will re-compile the builtins
RenderTarget::Generic,
)? {
Monomorphized(_) => unreachable!(""),
TypeChecked(module) => Ok(module),
@ -424,12 +426,12 @@ mod test_load {
loaded_module,
hashmap! {
"floatTest" => "Float *",
"divisionFn" => "Float a, Float a -> Result (Float a) [ DivByZero ]*",
"divisionTest" => "Result (Float *) [ DivByZero ]*",
"divisionFn" => "Float a, Float a -> Float a",
"divisionTest" => "Float *",
"intTest" => "I64",
"x" => "Float *",
"constantNum" => "Num *",
"divDep1ByDep2" => "Result (Float *) [ DivByZero ]*",
"divDep1ByDep2" => "Float *",
"fromDep2" => "Float *",
},
);
@ -587,18 +589,18 @@ mod test_load {
report,
indoc!(
"
\u{1b}[36m UNFINISHED LIST \u{1b}[0m
UNFINISHED LIST
I cannot find the end of this list:
I cannot find the end of this list:
\u{1b}[36m3\u{1b}[0m\u{1b}[36m\u{1b}[0m \u{1b}[37mmain = [\u{1b}[0m
\u{1b}[31m^\u{1b}[0m
3 main = [
^
You could change it to something like \u{1b}[33m[ 1, 2, 3 ]\u{1b}[0m or even just \u{1b}[33m[]\u{1b}[0m.
Anything where there is an open and a close square bracket, and where
the elements of the list are separated by commas.
You could change it to something like [ 1, 2, 3 ] or even just [].
Anything where there is an open and a close square bracket, and where
the elements of the list are separated by commas.
\u{1b}[4mNote\u{1b}[0m: I may be confused by indentation"
Note: I may be confused by indentation"
)
),
Ok(_) => unreachable!("we expect failure here"),
@ -767,8 +769,6 @@ mod test_load {
];
let err = multiple_modules(modules).unwrap_err();
let err = strip_ansi_escapes::strip(err).unwrap();
let err = String::from_utf8(err).unwrap();
assert_eq!(
err,
indoc!(
@ -815,4 +815,65 @@ mod test_load {
err
);
}
#[test]
fn issue_2863_module_type_does_not_exist() {
let modules = vec![
(
"platform/Package-Config.roc",
indoc!(
r#"
platform "testplatform"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [ mainForHost ]
mainForHost : Str
mainForHost = main
"#
),
),
(
"Main",
indoc!(
r#"
app "test"
packages { pf: "platform" }
provides [ main ] to pf
main : DoesNotExist
main = 1
"#
),
),
];
match multiple_modules(modules) {
Err(report) => {
assert_eq!(
report,
indoc!(
"
UNRECOGNIZED NAME
I cannot find a `DoesNotExist` value
5 main : DoesNotExist
^^^^^^^^^^^^
Did you mean one of these?
Dict
Result
List
Nat
"
)
)
}
Ok(_) => unreachable!("we expect failure here"),
}
}
}

View file

@ -289,8 +289,10 @@ impl LowLevelWrapperType {
Symbol::NUM_LT => CanBeReplacedBy(NumLt),
Symbol::NUM_LTE => CanBeReplacedBy(NumLte),
Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare),
Symbol::NUM_DIV_FLOAT => WrapperIsRequired,
Symbol::NUM_DIV_CEIL => WrapperIsRequired,
Symbol::NUM_DIV_FLOAT => CanBeReplacedBy(NumDivUnchecked),
Symbol::NUM_DIV_FLOAT_CHECKED => WrapperIsRequired,
Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked),
Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired,
Symbol::NUM_REM => WrapperIsRequired,
Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf),
Symbol::NUM_ABS => CanBeReplacedBy(NumAbs),

View file

@ -945,118 +945,126 @@ define_builtins! {
36 NUM_IS_POSITIVE: "isPositive"
37 NUM_IS_NEGATIVE: "isNegative"
38 NUM_REM: "rem"
39 NUM_DIV_FLOAT: "div"
40 NUM_DIV_INT: "divFloor"
41 NUM_MOD_INT: "modInt"
42 NUM_MOD_FLOAT: "modFloat"
43 NUM_SQRT: "sqrt"
44 NUM_LOG: "log"
45 NUM_ROUND: "round"
46 NUM_COMPARE: "compare"
47 NUM_POW: "pow"
48 NUM_CEILING: "ceiling"
49 NUM_POW_INT: "powInt"
50 NUM_FLOOR: "floor"
51 NUM_ADD_WRAP: "addWrap"
52 NUM_ADD_CHECKED: "addChecked"
53 NUM_ADD_SATURATED: "addSaturated"
54 NUM_ATAN: "atan"
55 NUM_ACOS: "acos"
56 NUM_ASIN: "asin"
57 NUM_AT_SIGNED128: "@Signed128"
58 NUM_SIGNED128: "Signed128" imported
59 NUM_AT_SIGNED64: "@Signed64"
60 NUM_SIGNED64: "Signed64" imported
61 NUM_AT_SIGNED32: "@Signed32"
62 NUM_SIGNED32: "Signed32" imported
63 NUM_AT_SIGNED16: "@Signed16"
64 NUM_SIGNED16: "Signed16" imported
65 NUM_AT_SIGNED8: "@Signed8"
66 NUM_SIGNED8: "Signed8" imported
67 NUM_AT_UNSIGNED128: "@Unsigned128"
68 NUM_UNSIGNED128: "Unsigned128" imported
69 NUM_AT_UNSIGNED64: "@Unsigned64"
70 NUM_UNSIGNED64: "Unsigned64" imported
71 NUM_AT_UNSIGNED32: "@Unsigned32"
72 NUM_UNSIGNED32: "Unsigned32" imported
73 NUM_AT_UNSIGNED16: "@Unsigned16"
74 NUM_UNSIGNED16: "Unsigned16" imported
75 NUM_AT_UNSIGNED8: "@Unsigned8"
76 NUM_UNSIGNED8: "Unsigned8" imported
77 NUM_AT_BINARY64: "@Binary64"
78 NUM_BINARY64: "Binary64" imported
79 NUM_AT_BINARY32: "@Binary32"
80 NUM_BINARY32: "Binary32" imported
81 NUM_BITWISE_AND: "bitwiseAnd"
82 NUM_BITWISE_XOR: "bitwiseXor"
83 NUM_BITWISE_OR: "bitwiseOr"
84 NUM_SHIFT_LEFT: "shiftLeftBy"
85 NUM_SHIFT_RIGHT: "shiftRightBy"
86 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
87 NUM_SUB_WRAP: "subWrap"
88 NUM_SUB_CHECKED: "subChecked"
89 NUM_SUB_SATURATED: "subSaturated"
90 NUM_MUL_WRAP: "mulWrap"
91 NUM_MUL_CHECKED: "mulChecked"
92 NUM_INT: "Int" imported
93 NUM_FLOAT: "Float" imported
94 NUM_AT_NATURAL: "@Natural"
95 NUM_NATURAL: "Natural" imported
96 NUM_NAT: "Nat" imported
97 NUM_INT_CAST: "intCast"
98 NUM_IS_MULTIPLE_OF: "isMultipleOf"
99 NUM_AT_DECIMAL: "@Decimal"
100 NUM_DECIMAL: "Decimal" imported
101 NUM_DEC: "Dec" imported // the Num.Dectype alias
102 NUM_BYTES_TO_U16: "bytesToU16"
103 NUM_BYTES_TO_U32: "bytesToU32"
104 NUM_CAST_TO_NAT: "#castToNat"
105 NUM_DIV_CEIL: "divCeil"
106 NUM_TO_STR: "toStr"
107 NUM_MIN_I8: "minI8"
108 NUM_MAX_I8: "maxI8"
109 NUM_MIN_U8: "minU8"
110 NUM_MAX_U8: "maxU8"
111 NUM_MIN_I16: "minI16"
112 NUM_MAX_I16: "maxI16"
113 NUM_MIN_U16: "minU16"
114 NUM_MAX_U16: "maxU16"
115 NUM_MIN_I32: "minI32"
116 NUM_MAX_I32: "maxI32"
117 NUM_MIN_U32: "minU32"
118 NUM_MAX_U32: "maxU32"
119 NUM_MIN_I64: "minI64"
120 NUM_MAX_I64: "maxI64"
121 NUM_MIN_U64: "minU64"
122 NUM_MAX_U64: "maxU64"
123 NUM_MIN_I128: "minI128"
124 NUM_MAX_I128: "maxI128"
125 NUM_TO_I8: "toI8"
126 NUM_TO_I8_CHECKED: "toI8Checked"
127 NUM_TO_I16: "toI16"
128 NUM_TO_I16_CHECKED: "toI16Checked"
129 NUM_TO_I32: "toI32"
130 NUM_TO_I32_CHECKED: "toI32Checked"
131 NUM_TO_I64: "toI64"
132 NUM_TO_I64_CHECKED: "toI64Checked"
133 NUM_TO_I128: "toI128"
134 NUM_TO_I128_CHECKED: "toI128Checked"
135 NUM_TO_U8: "toU8"
136 NUM_TO_U8_CHECKED: "toU8Checked"
137 NUM_TO_U16: "toU16"
138 NUM_TO_U16_CHECKED: "toU16Checked"
139 NUM_TO_U32: "toU32"
140 NUM_TO_U32_CHECKED: "toU32Checked"
141 NUM_TO_U64: "toU64"
142 NUM_TO_U64_CHECKED: "toU64Checked"
143 NUM_TO_U128: "toU128"
144 NUM_TO_U128_CHECKED: "toU128Checked"
145 NUM_TO_NAT: "toNat"
146 NUM_TO_NAT_CHECKED: "toNatChecked"
147 NUM_TO_F32: "toF32"
148 NUM_TO_F32_CHECKED: "toF32Checked"
149 NUM_TO_F64: "toF64"
150 NUM_TO_F64_CHECKED: "toF64Checked"
39 NUM_REM_CHECKED: "remChecked"
40 NUM_DIV_FLOAT: "div"
41 NUM_DIV_FLOAT_CHECKED: "divChecked"
42 NUM_DIV_FLOOR: "divFloor"
43 NUM_DIV_FLOOR_CHECKED: "divFloorChecked"
44 NUM_MOD_INT: "modInt"
45 NUM_MOD_INT_CHECKED: "modIntChecked"
46 NUM_MOD_FLOAT: "modFloat"
47 NUM_MOD_FLOAT_CHECKED: "modFloatChecked"
48 NUM_SQRT: "sqrt"
49 NUM_SQRT_CHECKED: "sqrtChecked"
50 NUM_LOG: "log"
51 NUM_LOG_CHECKED: "logChecked"
52 NUM_ROUND: "round"
53 NUM_COMPARE: "compare"
54 NUM_POW: "pow"
55 NUM_CEILING: "ceiling"
56 NUM_POW_INT: "powInt"
57 NUM_FLOOR: "floor"
58 NUM_ADD_WRAP: "addWrap"
59 NUM_ADD_CHECKED: "addChecked"
60 NUM_ADD_SATURATED: "addSaturated"
61 NUM_ATAN: "atan"
62 NUM_ACOS: "acos"
63 NUM_ASIN: "asin"
64 NUM_AT_SIGNED128: "@Signed128"
65 NUM_SIGNED128: "Signed128" imported
66 NUM_AT_SIGNED64: "@Signed64"
67 NUM_SIGNED64: "Signed64" imported
68 NUM_AT_SIGNED32: "@Signed32"
69 NUM_SIGNED32: "Signed32" imported
70 NUM_AT_SIGNED16: "@Signed16"
71 NUM_SIGNED16: "Signed16" imported
72 NUM_AT_SIGNED8: "@Signed8"
73 NUM_SIGNED8: "Signed8" imported
74 NUM_AT_UNSIGNED128: "@Unsigned128"
75 NUM_UNSIGNED128: "Unsigned128" imported
76 NUM_AT_UNSIGNED64: "@Unsigned64"
77 NUM_UNSIGNED64: "Unsigned64" imported
78 NUM_AT_UNSIGNED32: "@Unsigned32"
79 NUM_UNSIGNED32: "Unsigned32" imported
80 NUM_AT_UNSIGNED16: "@Unsigned16"
81 NUM_UNSIGNED16: "Unsigned16" imported
82 NUM_AT_UNSIGNED8: "@Unsigned8"
83 NUM_UNSIGNED8: "Unsigned8" imported
84 NUM_AT_BINARY64: "@Binary64"
85 NUM_BINARY64: "Binary64" imported
86 NUM_AT_BINARY32: "@Binary32"
87 NUM_BINARY32: "Binary32" imported
88 NUM_BITWISE_AND: "bitwiseAnd"
89 NUM_BITWISE_XOR: "bitwiseXor"
90 NUM_BITWISE_OR: "bitwiseOr"
91 NUM_SHIFT_LEFT: "shiftLeftBy"
92 NUM_SHIFT_RIGHT: "shiftRightBy"
93 NUM_SHIFT_RIGHT_ZERO_FILL: "shiftRightZfBy"
94 NUM_SUB_WRAP: "subWrap"
95 NUM_SUB_CHECKED: "subChecked"
96 NUM_SUB_SATURATED: "subSaturated"
97 NUM_MUL_WRAP: "mulWrap"
98 NUM_MUL_CHECKED: "mulChecked"
99 NUM_INT: "Int" imported
100 NUM_FLOAT: "Float" imported
101 NUM_AT_NATURAL: "@Natural"
102 NUM_NATURAL: "Natural" imported
103 NUM_NAT: "Nat" imported
104 NUM_INT_CAST: "intCast"
105 NUM_IS_MULTIPLE_OF: "isMultipleOf"
106 NUM_AT_DECIMAL: "@Decimal"
107 NUM_DECIMAL: "Decimal" imported
108 NUM_DEC: "Dec" imported // the Num.Dectype alias
109 NUM_BYTES_TO_U16: "bytesToU16"
110 NUM_BYTES_TO_U32: "bytesToU32"
111 NUM_CAST_TO_NAT: "#castToNat"
112 NUM_DIV_CEIL: "divCeil"
113 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
114 NUM_TO_STR: "toStr"
115 NUM_MIN_I8: "minI8"
116 NUM_MAX_I8: "maxI8"
117 NUM_MIN_U8: "minU8"
118 NUM_MAX_U8: "maxU8"
119 NUM_MIN_I16: "minI16"
120 NUM_MAX_I16: "maxI16"
121 NUM_MIN_U16: "minU16"
122 NUM_MAX_U16: "maxU16"
123 NUM_MIN_I32: "minI32"
124 NUM_MAX_I32: "maxI32"
125 NUM_MIN_U32: "minU32"
126 NUM_MAX_U32: "maxU32"
127 NUM_MIN_I64: "minI64"
128 NUM_MAX_I64: "maxI64"
129 NUM_MIN_U64: "minU64"
130 NUM_MAX_U64: "maxU64"
131 NUM_MIN_I128: "minI128"
132 NUM_MAX_I128: "maxI128"
133 NUM_TO_I8: "toI8"
134 NUM_TO_I8_CHECKED: "toI8Checked"
135 NUM_TO_I16: "toI16"
136 NUM_TO_I16_CHECKED: "toI16Checked"
137 NUM_TO_I32: "toI32"
138 NUM_TO_I32_CHECKED: "toI32Checked"
139 NUM_TO_I64: "toI64"
140 NUM_TO_I64_CHECKED: "toI64Checked"
141 NUM_TO_I128: "toI128"
142 NUM_TO_I128_CHECKED: "toI128Checked"
143 NUM_TO_U8: "toU8"
144 NUM_TO_U8_CHECKED: "toU8Checked"
145 NUM_TO_U16: "toU16"
146 NUM_TO_U16_CHECKED: "toU16Checked"
147 NUM_TO_U32: "toU32"
148 NUM_TO_U32_CHECKED: "toU32Checked"
149 NUM_TO_U64: "toU64"
150 NUM_TO_U64_CHECKED: "toU64Checked"
151 NUM_TO_U128: "toU128"
152 NUM_TO_U128_CHECKED: "toU128Checked"
153 NUM_TO_NAT: "toNat"
154 NUM_TO_NAT_CHECKED: "toNatChecked"
155 NUM_TO_F32: "toF32"
156 NUM_TO_F32_CHECKED: "toF32Checked"
157 NUM_TO_F64: "toF64"
158 NUM_TO_F64_CHECKED: "toF64Checked"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -195,6 +195,7 @@ pub struct PartialProc<'a> {
pub pattern_symbols: &'a [Symbol],
pub captured_symbols: CapturedSymbols<'a>,
pub body: roc_can::expr::Expr,
pub body_var: Variable,
pub is_self_recursive: bool,
}
@ -224,6 +225,7 @@ impl<'a> PartialProc<'a> {
pattern_symbols,
captured_symbols,
body: body.value,
body_var: ret_var,
is_self_recursive,
}
}
@ -240,6 +242,7 @@ impl<'a> PartialProc<'a> {
pattern_symbols: pattern_symbols.into_bump_slice(),
captured_symbols: CapturedSymbols::None,
body: roc_can::expr::Expr::RuntimeError(error.value),
body_var: ret_var,
is_self_recursive: false,
}
}
@ -902,6 +905,7 @@ impl<'a> Procs<'a> {
pattern_symbols,
captured_symbols,
body: body.value,
body_var: ret_var,
is_self_recursive,
};
@ -939,6 +943,7 @@ impl<'a> Procs<'a> {
pattern_symbols,
captured_symbols,
body: body.value,
body_var: ret_var,
is_self_recursive,
};
@ -2476,7 +2481,7 @@ fn specialize_external<'a>(
};
let body = partial_proc.body.clone();
let mut specialized_body = from_can(env, fn_var, body, procs, layout_cache);
let mut specialized_body = from_can(env, partial_proc.body_var, body, procs, layout_cache);
match specialized {
SpecializedLayout::FunctionPointerBody {

View file

@ -3,6 +3,7 @@ use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{default_hasher, MutMap};
use roc_error_macros::todo_abilities;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol};
use roc_problem::can::RuntimeError;
@ -10,7 +11,7 @@ use roc_target::{PtrWidth, TargetInfo};
use roc_types::subs::{
Content, FlatType, RecordFields, Subs, UnionTags, UnsortedUnionTags, Variable,
};
use roc_types::types::{gather_fields_unsorted_iter, RecordField};
use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError};
use std::collections::hash_map::{DefaultHasher, Entry};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
@ -72,6 +73,7 @@ impl<'a> RawFunctionLayout<'a> {
use roc_types::subs::Content::*;
match content {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, *structure_content)
@ -952,6 +954,7 @@ impl<'a> Layout<'a> {
use roc_types::subs::Content::*;
match content {
FlexVar(_) | RigidVar(_) => Err(LayoutProblem::UnresolvedTypeVar(var)),
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
RecursionVar { structure, .. } => {
let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, *structure_content)
@ -1683,7 +1686,11 @@ fn layout_from_flat_type<'a>(
// extract any values from the ext_var
let mut pairs = Vec::with_capacity_in(fields.len(), arena);
for (label, field) in fields.unsorted_iterator(subs, ext_var) {
let it = match fields.unsorted_iterator(subs, ext_var) {
Ok(it) => it,
Err(RecordFieldsError) => return Err(LayoutProblem::Erroneous),
};
for (label, field) in it {
// drop optional fields
let var = match field {
RecordField::Optional(_) => continue,
@ -2657,6 +2664,7 @@ fn layout_from_num_content<'a>(
// (e.g. for (5 + 5) assume both 5s are 64-bit integers.)
Ok(Layout::default_integer())
}
FlexAbleVar(_, _) | RigidAbleVar(_, _) => todo_abilities!("Not reachable yet"),
Structure(Apply(symbol, args)) => match *symbol {
// Ints
Symbol::NUM_NAT => Ok(Layout::usize(target_info)),

View file

@ -137,8 +137,10 @@ impl FunctionLayout {
use LayoutError::*;
match content {
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
Content::RecursionVar { .. } => Err(TypeError(())),
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
Content::Alias(_, _, actual, _) => Self::from_var_help(layouts, subs, *actual),
@ -243,8 +245,10 @@ impl LambdaSet {
use LayoutError::*;
match content {
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
Content::RecursionVar { .. } => {
unreachable!("lambda sets cannot currently be recursive")
}
@ -627,8 +631,10 @@ impl Layout {
use LayoutError::*;
match content {
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(_, _)
| Content::RigidAbleVar(_, _) => Err(UnresolvedVariable(var)),
Content::RecursionVar {
structure,
opt_name: _,

View file

@ -2,7 +2,9 @@ use crate::ast::{
AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable,
TypeAnnotation, TypeDef, TypeHeader, ValueDef,
};
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
use crate::blankspace::{
space0_after_e, space0_around_ee, space0_before_e, space0_before_optional_after, space0_e,
};
use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::keyword;
use crate::parser::{
@ -2591,14 +2593,15 @@ fn record_help<'a>(
and!(
trailing_sep_by0(
word1(b',', ERecord::End),
space0_around_ee(
space0_before_optional_after(
loc!(record_field_help(min_indent)),
min_indent,
ERecord::IndentEnd,
ERecord::IndentEnd
),
),
space0_e(min_indent, ERecord::IndentEnd)
// Allow outdented closing braces
space0_e(0, ERecord::IndentEnd)
),
word1(b'}', ERecord::End)
)

View file

@ -0,0 +1,62 @@
Defs(
[
@0-29 Value(
Body(
@0-1 Identifier(
"x",
),
@4-29 Apply(
@4-7 Var {
module_name: "",
ident: "foo",
},
[
@9-28 ParensAround(
Apply(
@9-12 Var {
module_name: "",
ident: "baz",
},
[
@13-28 Record(
Collection {
items: [
@17-26 SpaceBefore(
RequiredValue(
@17-20 "bar",
[],
@22-26 Var {
module_name: "",
ident: "blah",
},
),
[
Newline,
],
),
],
final_comments: [
Newline,
],
},
),
],
Space,
),
),
],
Space,
),
),
),
],
@30-31 SpaceBefore(
Var {
module_name: "",
ident: "x",
},
[
Newline,
],
),
)

View file

@ -0,0 +1,4 @@
x = foo (baz {
bar: blah
})
x

View file

@ -0,0 +1,43 @@
Defs(
[
@0-17 Value(
Body(
@0-1 Identifier(
"a",
),
@4-17 List(
Collection {
items: [
@8-9 SpaceBefore(
Num(
"1",
),
[
Newline,
],
),
@11-12 Num(
"2",
),
@14-15 Num(
"3",
),
],
final_comments: [
Newline,
],
},
),
),
),
],
@18-19 SpaceBefore(
Var {
module_name: "",
ident: "a",
},
[
Newline,
],
),
)

View file

@ -0,0 +1,4 @@
a = [
1, 2, 3
]
a

View file

@ -0,0 +1,51 @@
Defs(
[
@0-23 Value(
Body(
@0-1 Identifier(
"x",
),
@4-23 Apply(
@4-7 Var {
module_name: "",
ident: "foo",
},
[
@8-23 Record(
Collection {
items: [
@12-21 SpaceBefore(
RequiredValue(
@12-15 "bar",
[],
@17-21 Var {
module_name: "",
ident: "blah",
},
),
[
Newline,
],
),
],
final_comments: [
Newline,
],
},
),
],
Space,
),
),
),
],
@24-25 SpaceBefore(
Var {
module_name: "",
ident: "x",
},
[
Newline,
],
),
)

View file

@ -0,0 +1,4 @@
x = foo {
bar: blah
}
x

View file

@ -218,6 +218,9 @@ mod test_parse {
pass/opaque_reference_pattern.expr,
pass/opaque_reference_pattern_with_arguments.expr,
pass/ops_with_newlines.expr,
pass/outdented_list.expr,
pass/outdented_record.expr,
pass/outdented_app_with_record.expr,
pass/packed_singleton_list.expr,
pass/parenthetical_apply.expr,
pass/parenthetical_basic_field.expr,

View file

@ -119,6 +119,13 @@ pub enum Problem {
ability: Symbol,
region: Region,
},
AbilityMemberMultipleBoundVars {
member: Symbol,
ability: Symbol,
span_has_clauses: Region,
bound_var_names: Vec<Lowercase>,
},
// TODO(abilities): remove me when ability hierarchies are supported
AbilityMemberBindsExternalAbility {
member: Symbol,
ability: Symbol,

View file

@ -7,6 +7,7 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_types = { path = "../types" }
@ -22,6 +23,7 @@ roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" }
pretty_assertions = "1.0.0"
indoc = "1.0.3"
tempfile = "3.2.0"

View file

@ -0,0 +1,156 @@
use roc_can::abilities::AbilitiesStore;
use roc_region::all::{Loc, Region};
use roc_types::subs::Subs;
use roc_types::subs::Variable;
use roc_types::types::{Category, PatternCategory};
use roc_unify::unify::MustImplementAbility;
use crate::solve::{IncompleteAbilityImplementation, TypeError};
#[derive(Debug, Clone)]
pub enum AbilityImplError {
/// Promote this to an error that the type does not fully implement an ability
IncompleteAbility,
/// Promote this error to a `TypeError::BadExpr` from elsewhere
BadExpr(Region, Category, Variable),
/// Promote this error to a `TypeError::BadPattern` from elsewhere
BadPattern(Region, PatternCategory, Variable),
}
#[derive(Default)]
pub struct DeferredMustImplementAbility(Vec<(Vec<MustImplementAbility>, AbilityImplError)>);
impl DeferredMustImplementAbility {
pub fn add(&mut self, must_implement: Vec<MustImplementAbility>, on_error: AbilityImplError) {
self.0.push((must_implement, on_error));
}
pub fn check(self, subs: &mut Subs, abilities_store: &AbilitiesStore) -> Vec<TypeError> {
// Two passes here. First up let's build up records of what types fully implement
// abilities, and what specializations are available/missing for the ones that don't.
// Use a vec since these lists should usually be pretty small.
let mut good = vec![];
let mut bad = vec![];
macro_rules! is_good {
($e:expr) => {
good.contains($e)
};
}
macro_rules! get_bad {
($e:expr) => {
bad.iter()
.find(|(cand, _)| $e == *cand)
.map(|(_, incomplete)| incomplete)
};
}
for (mias, _) in self.0.iter() {
for &mia @ MustImplementAbility { typ, ability } in mias {
if is_good!(&mia) || get_bad!(mia).is_some() {
continue;
}
let members_of_ability = abilities_store.members_of_ability(ability).unwrap();
let mut specialized_members = Vec::with_capacity(members_of_ability.len());
let mut missing_members = Vec::with_capacity(members_of_ability.len());
for &member in members_of_ability {
match abilities_store.get_specialization(member, typ) {
None => {
let root_data = abilities_store.member_def(member).unwrap();
missing_members.push(Loc::at(root_data.region, member));
}
Some(specialization) => {
specialized_members.push(Loc::at(specialization.region, member));
}
}
}
if missing_members.is_empty() {
good.push(mia);
} else {
bad.push((
mia,
IncompleteAbilityImplementation {
typ,
ability,
specialized_members,
missing_members,
},
));
}
}
}
// Now figure out what errors we need to report.
let mut problems = vec![];
// Keep track of which types that have an incomplete ability were reported as part of
// another type error (from an expression or pattern). If we reported an error for a type
// that doesn't implement an ability in that context, we don't want to repeat the error
// message.
let mut reported_in_context = vec![];
let mut incomplete_not_in_context = vec![];
for (must_implement, on_error) in self.0.into_iter() {
use AbilityImplError::*;
match on_error {
IncompleteAbility => {
incomplete_not_in_context.extend(must_implement);
}
BadExpr(region, category, var) => {
let incomplete_types = must_implement
.iter()
.filter_map(|e| get_bad!(*e))
.cloned()
.collect::<Vec<_>>();
if !incomplete_types.is_empty() {
// Demote the bad variable that exposed this problem to an error, both so
// that we have an ErrorType to report and so that codegen knows to deal
// with the error later.
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
problems.push(TypeError::BadExprMissingAbility(
region,
category,
error_type,
incomplete_types,
));
reported_in_context.extend(must_implement);
}
}
BadPattern(region, category, var) => {
let incomplete_types = must_implement
.iter()
.filter_map(|e| get_bad!(*e))
.cloned()
.collect::<Vec<_>>();
if !incomplete_types.is_empty() {
// Demote the bad variable that exposed this problem to an error, both so
// that we have an ErrorType to report and so that codegen knows to deal
// with the error later.
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
problems.push(TypeError::BadPatternMissingAbility(
region,
category,
error_type,
incomplete_types,
));
reported_in_context.extend(must_implement);
}
}
};
}
for mia in incomplete_not_in_context.into_iter() {
if let Some(must_implement) = get_bad!(mia) {
if !reported_in_context.contains(&mia) {
problems.push(TypeError::IncompleteAbilityImplementation(
must_implement.clone(),
));
}
}
}
problems
}
}

View file

@ -2,5 +2,6 @@
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
mod ability;
pub mod module;
pub mod solve;

View file

@ -1,4 +1,5 @@
use crate::solve::{self, Aliases};
use roc_can::abilities::AbilitiesStore;
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
use roc_can::module::RigidVariables;
use roc_collections::all::MutMap;
@ -32,13 +33,23 @@ pub fn run_solve(
rigid_variables: RigidVariables,
mut subs: Subs,
mut aliases: Aliases,
) -> (Solved<Subs>, solve::Env, Vec<solve::TypeError>) {
mut abilities_store: AbilitiesStore,
) -> (
Solved<Subs>,
solve::Env,
Vec<solve::TypeError>,
AbilitiesStore,
) {
let env = solve::Env::default();
for (var, name) in rigid_variables.named {
subs.rigid_var(var, name);
}
for (var, (name, ability)) in rigid_variables.able {
subs.rigid_able_var(var, name, ability);
}
for var in rigid_variables.wildcards {
subs.rigid_var(var, "*".into());
}
@ -55,9 +66,10 @@ pub fn run_solve(
subs,
&mut aliases,
&constraint,
&mut abilities_store,
);
(solved_subs, solved_env, problems)
(solved_subs, solved_env, problems, abilities_store)
}
pub fn exposed_types_storage_subs(

View file

@ -1,4 +1,6 @@
use crate::ability::{AbilityImplError, DeferredMustImplementAbility};
use bumpalo::Bump;
use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
use roc_can::constraint::Constraint::{self, *};
use roc_can::constraint::{Constraints, LetConstraint};
use roc_can::expected::{Expected, PExpected};
@ -14,7 +16,7 @@ use roc_types::subs::{
use roc_types::types::Type::{self, *};
use roc_types::types::{
gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, PatternCategory,
TypeExtension,
Reason, TypeExtension,
};
use roc_unify::unify::{unify, Mode, Unified::*};
@ -68,6 +70,15 @@ use roc_unify::unify::{unify, Mode, Unified::*};
// Ranks are used to limit the number of type variables considered for generalization. Only those inside
// of the let (so those used in inferring the type of `\x -> x`) are considered.
#[derive(PartialEq, Debug, Clone)]
pub struct IncompleteAbilityImplementation {
// TODO(abilities): have general types here, not just opaques
pub typ: Symbol,
pub ability: Symbol,
pub specialized_members: Vec<Loc<Symbol>>,
pub missing_members: Vec<Loc<Symbol>>,
}
#[derive(PartialEq, Debug, Clone)]
pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
@ -75,6 +86,19 @@ pub enum TypeError {
CircularType(Region, Symbol, ErrorType),
BadType(roc_types::types::Problem),
UnexposedLookup(Symbol),
IncompleteAbilityImplementation(IncompleteAbilityImplementation),
BadExprMissingAbility(
Region,
Category,
ErrorType,
Vec<IncompleteAbilityImplementation>,
),
BadPatternMissingAbility(
Region,
PatternCategory,
ErrorType,
Vec<IncompleteAbilityImplementation>,
),
}
use roc_types::types::Alias;
@ -515,8 +539,17 @@ pub fn run(
mut subs: Subs,
aliases: &mut Aliases,
constraint: &Constraint,
abilities_store: &mut AbilitiesStore,
) -> (Solved<Subs>, Env) {
let env = run_in_place(constraints, env, problems, &mut subs, aliases, constraint);
let env = run_in_place(
constraints,
env,
problems,
&mut subs,
aliases,
constraint,
abilities_store,
);
(Solved(subs), env)
}
@ -529,6 +562,7 @@ pub fn run_in_place(
subs: &mut Subs,
aliases: &mut Aliases,
constraint: &Constraint,
abilities_store: &mut AbilitiesStore,
) -> Env {
let mut pools = Pools::default();
@ -540,6 +574,8 @@ pub fn run_in_place(
let arena = Bump::new();
let mut deferred_must_implement_abilities = DeferredMustImplementAbility::default();
let state = solve(
&arena,
constraints,
@ -551,8 +587,14 @@ pub fn run_in_place(
aliases,
subs,
constraint,
abilities_store,
&mut deferred_must_implement_abilities,
);
// Now that the module has been solved, we can run through and check all
// types claimed to implement abilities.
problems.extend(deferred_must_implement_abilities.check(subs, abilities_store));
state.env
}
@ -604,6 +646,8 @@ fn solve(
aliases: &mut Aliases,
subs: &mut Subs,
constraint: &Constraint,
abilities_store: &mut AbilitiesStore,
deferred_must_implement_abilities: &mut DeferredMustImplementAbility,
) -> State {
let initial = Work::Constraint {
env,
@ -656,6 +700,19 @@ fn solve(
let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() {
check_ability_specialization(
arena,
subs,
&new_env,
pools,
rank,
abilities_store,
problems,
deferred_must_implement_abilities,
*symbol,
*loc_var,
);
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
}
@ -752,6 +809,19 @@ fn solve(
let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() {
check_ability_specialization(
arena,
subs,
&new_env,
pools,
rank,
abilities_store,
problems,
deferred_must_implement_abilities,
*symbol,
*loc_var,
);
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
}
@ -796,12 +866,21 @@ fn solve(
let expected = type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => {
Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars);
if !must_implement_ability.is_empty() {
deferred_must_implement_abilities.add(
must_implement_ability,
AbilityImplError::BadExpr(*region, category.clone(), actual),
);
}
state
}
Failure(vars, actual_type, expected_type) => {
Failure(vars, actual_type, expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
@ -838,12 +917,16 @@ fn solve(
let target = *target;
match unify(subs, actual, target, Mode::EQ) {
Success(vars) => {
Success {
vars,
// ERROR NOT REPORTED
must_implement_ability: _,
} => {
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, _actual_type, _expected_type) => {
Failure(vars, _actual_type, _expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars);
// ERROR NOT REPORTED
@ -890,13 +973,26 @@ fn solve(
type_to_var(subs, rank, pools, aliases, expectation.get_type_ref());
match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => {
Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars);
if !must_implement_ability.is_empty() {
deferred_must_implement_abilities.add(
must_implement_ability,
AbilityImplError::BadExpr(
*region,
Category::Lookup(*symbol),
actual,
),
);
}
state
}
Failure(vars, actual_type, expected_type) => {
Failure(vars, actual_type, expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
@ -954,12 +1050,21 @@ fn solve(
};
match unify(subs, actual, expected, mode) {
Success(vars) => {
Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars);
if !must_implement_ability.is_empty() {
deferred_must_implement_abilities.add(
must_implement_ability,
AbilityImplError::BadPattern(*region, category.clone(), actual),
);
}
state
}
Failure(vars, actual_type, expected_type) => {
Failure(vars, actual_type, expected_type, _bad_impls) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern(
@ -1123,12 +1228,25 @@ fn solve(
let includes = type_to_var(subs, rank, pools, aliases, &tag_ty);
match unify(subs, actual, includes, Mode::PRESENT) {
Success(vars) => {
Success {
vars,
must_implement_ability,
} => {
introduce(subs, rank, pools, &vars);
if !must_implement_ability.is_empty() {
deferred_must_implement_abilities.add(
must_implement_ability,
AbilityImplError::BadPattern(
*region,
pattern_category.clone(),
actual,
),
);
}
state
}
Failure(vars, actual_type, expected_to_include_type) => {
Failure(vars, actual_type, expected_to_include_type, _bad_impls) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern(
@ -1156,6 +1274,137 @@ fn solve(
state
}
/// If a symbol claims to specialize an ability member, check that its solved type in fact
/// does specialize the ability, and record the specialization.
#[allow(clippy::too_many_arguments)]
// Aggressive but necessary - there aren't many usages.
#[inline(always)]
fn check_ability_specialization(
arena: &Bump,
subs: &mut Subs,
env: &Env,
pools: &mut Pools,
rank: Rank,
abilities_store: &mut AbilitiesStore,
problems: &mut Vec<TypeError>,
deferred_must_implement_abilities: &mut DeferredMustImplementAbility,
symbol: Symbol,
symbol_loc_var: Loc<Variable>,
) {
// If the symbol specializes an ability member, we need to make sure that the
// inferred type for the specialization actually aligns with the expected
// implementation.
if let Some((root_symbol, root_data)) = abilities_store.root_name_and_def(symbol) {
let root_signature_var = env
.get_var_by_symbol(&root_symbol)
.expect("Ability should be registered in env by now!");
// Check if they unify - if they don't, then the claimed specialization isn't really one,
// and that's a type error!
// This also fixes any latent type variables that need to be specialized to exactly what
// the ability signature expects.
// We need to freshly instantiate the root signature so that all unifications are reflected
// in the specialization type, but not the original signature type.
let root_signature_var =
deep_copy_var_in(subs, Rank::toplevel(), pools, root_signature_var, arena);
let snapshot = subs.snapshot();
let unified = unify(subs, symbol_loc_var.value, root_signature_var, Mode::EQ);
match unified {
Success {
vars: _,
must_implement_ability,
} if must_implement_ability.is_empty() => {
// This can happen when every ability constriant on a type variable went
// through only another type variable. That means this def is not specialized
// for one type - for now, we won't admit this.
// Rollback the snapshot so we unlink the root signature with the specialization,
// so we can have two separate error types.
subs.rollback_to(snapshot);
let (expected_type, _problems) = subs.var_to_error_type(root_signature_var);
let (actual_type, _problems) = subs.var_to_error_type(symbol_loc_var.value);
let reason = Reason::GeneralizedAbilityMemberSpecialization {
member_name: root_symbol,
def_region: root_data.region,
};
let problem = TypeError::BadExpr(
symbol_loc_var.region,
Category::AbilityMemberSpecialization(root_symbol),
actual_type,
Expected::ForReason(reason, expected_type, symbol_loc_var.region),
);
problems.push(problem);
}
Success {
vars,
must_implement_ability,
} => {
subs.commit_snapshot(snapshot);
introduce(subs, rank, pools, &vars);
// First, figure out and register for what type does this symbol specialize
// the ability member.
let mut ability_implementations_for_specialization = must_implement_ability
.iter()
.filter(|mia| mia.ability == root_data.parent_ability)
.collect::<Vec<_>>();
ability_implementations_for_specialization.dedup();
debug_assert!(ability_implementations_for_specialization.len() == 1, "Multiple variables bound to an ability - this is ambiguous and should have been caught in canonicalization");
// This is a valid specialization! Record it.
let specialization_type = ability_implementations_for_specialization[0].typ;
let specialization = MemberSpecialization {
symbol,
region: symbol_loc_var.region,
};
abilities_store.register_specialization_for_type(
root_symbol,
specialization_type,
specialization,
);
// Store the checks for what abilities must be implemented to be checked after the
// whole module is complete.
deferred_must_implement_abilities
.add(must_implement_ability, AbilityImplError::IncompleteAbility);
}
Failure(vars, actual_type, expected_type, unimplemented_abilities) => {
subs.commit_snapshot(snapshot);
introduce(subs, rank, pools, &vars);
let reason = Reason::InvalidAbilityMemberSpecialization {
member_name: root_symbol,
def_region: root_data.region,
unimplemented_abilities,
};
let problem = TypeError::BadExpr(
symbol_loc_var.region,
Category::AbilityMemberSpecialization(root_symbol),
actual_type,
Expected::ForReason(reason, expected_type, symbol_loc_var.region),
);
problems.push(problem);
}
BadType(vars, problem) => {
subs.commit_snapshot(snapshot);
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
}
}
}
}
#[derive(Debug)]
enum LocalDefVarsVec<T> {
Stack(arrayvec::ArrayVec<T, 32>),
@ -1288,7 +1537,7 @@ impl RegisterVariable {
use RegisterVariable::*;
match typ {
Variable(var) => Direct(*var),
Type::Variable(var) => Direct(*var),
EmptyRec => Direct(Variable::EMPTY_RECORD),
EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION),
Type::DelayedAlias(AliasCommon { symbol, .. }) => {
@ -2180,7 +2429,7 @@ fn adjust_rank_content(
use roc_types::subs::FlatType::*;
match content {
FlexVar(_) | RigidVar(_) | Error => group_rank,
FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => group_rank,
RecursionVar { .. } => group_rank,
@ -2396,7 +2645,14 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
desc.mark = Mark::NONE;
desc.copy = OptVariable::NONE;
}
FlexVar(_) | Error => (),
&RigidAbleVar(name, ability) => {
// Same as `RigidVar` above
desc.content = FlexAbleVar(Some(name), ability);
desc.rank = max_rank;
desc.mark = Mark::NONE;
desc.copy = OptVariable::NONE;
}
FlexVar(_) | FlexAbleVar(_, _) | Error => (),
RecursionVar { structure, .. } => {
stack.push(*structure);
@ -2684,7 +2940,7 @@ fn deep_copy_var_help(
copy
}
FlexVar(_) | Error => copy,
FlexVar(_) | FlexAbleVar(_, _) | Error => copy,
RecursionVar {
opt_name,
@ -2709,6 +2965,12 @@ fn deep_copy_var_help(
copy
}
RigidAbleVar(name, ability) => {
subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability)));
copy
}
Alias(symbol, arguments, real_type_var, kind) => {
let new_variables =
SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _);

View file

@ -10,20 +10,12 @@ mod helpers;
#[cfg(test)]
mod solve_expr {
use crate::helpers::with_larger_debug_stack;
use roc_load::LoadedModule;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
// HELPERS
fn infer_eq_help(
src: &str,
) -> Result<
(
Vec<roc_solve::solve::TypeError>,
Vec<roc_problem::can::Problem>,
String,
),
std::io::Error,
> {
fn run_load_and_infer(src: &str) -> Result<LoadedModule, std::io::Error> {
use bumpalo::Bump;
use std::fs::File;
use std::io::Write;
@ -58,6 +50,7 @@ mod solve_expr {
dir.path(),
exposed_types,
roc_target::TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::Generic,
);
dir.close()?;
@ -66,8 +59,19 @@ mod solve_expr {
};
let loaded = loaded.expect("failed to load module");
Ok(loaded)
}
use roc_load::LoadedModule;
fn infer_eq_help(
src: &str,
) -> Result<
(
Vec<roc_solve::solve::TypeError>,
Vec<roc_problem::can::Problem>,
String,
),
std::io::Error,
> {
let LoadedModule {
module_id: home,
mut can_problems,
@ -76,7 +80,7 @@ mod solve_expr {
mut solved,
exposed_to_host,
..
} = loaded;
} = run_load_and_infer(src)?;
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
@ -155,6 +159,59 @@ mod solve_expr {
assert_eq!(actual, expected.to_string());
}
fn check_inferred_abilities<'a, I>(src: &'a str, expected_specializations: I)
where
I: IntoIterator<Item = (&'a str, &'a str)>,
{
let LoadedModule {
module_id: home,
mut can_problems,
mut type_problems,
interns,
abilities_store,
..
} = run_load_and_infer(src).unwrap();
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
assert_eq!(can_problems, Vec::new(), "Canonicalization problems: ");
if !type_problems.is_empty() {
eprintln!("{:?}", type_problems);
panic!();
}
let known_specializations = abilities_store.get_known_specializations();
use std::collections::HashSet;
let pretty_specializations = known_specializations
.into_iter()
.map(|(member, typ)| {
let member_data = abilities_store.member_def(member).unwrap();
let member_str = member.ident_str(&interns).as_str();
let ability_str = member_data.parent_ability.ident_str(&interns).as_str();
(
format!("{}:{}", ability_str, member_str),
typ.ident_str(&interns).as_str(),
)
})
.collect::<HashSet<_>>();
for (parent, specialization) in expected_specializations.into_iter() {
let has_the_one = pretty_specializations
.iter()
// references are annoying so we do this
.find(|(p, s)| p == parent && s == &specialization)
.is_some();
assert!(
has_the_one,
"{:#?} not in {:#?}",
(parent, specialization),
pretty_specializations,
);
}
}
#[test]
fn int_literal() {
infer_eq("5", "Num *");
@ -2340,7 +2397,7 @@ mod solve_expr {
{ numIdentity, x : numIdentity 42, y }
"#
),
"{ numIdentity : Num a -> Num a, x : Num a, y : F64 }",
"{ numIdentity : Num a -> Num a, x : Num a, y : Float * }",
);
}
@ -3296,6 +3353,30 @@ mod solve_expr {
);
}
#[test]
fn div() {
infer_eq_without_problem(
indoc!(
r#"
Num.div
"#
),
"Float a, Float a -> Float a",
)
}
#[test]
fn div_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divChecked
"#
),
"Float a, Float a -> Result (Float a) [ DivByZero ]*",
)
}
#[test]
fn div_ceil() {
infer_eq_without_problem(
@ -3304,22 +3385,46 @@ mod solve_expr {
Num.divCeil
"#
),
"Int a, Int a -> Int a",
);
}
#[test]
fn div_ceil_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divCeilChecked
"#
),
"Int a, Int a -> Result (Int a) [ DivByZero ]*",
);
}
#[test]
fn pow_int() {
fn div_floor() {
infer_eq_without_problem(
indoc!(
r#"
Num.powInt
Num.divFloor
"#
),
"Int a, Int a -> Int a",
);
}
#[test]
fn div_floor_checked() {
infer_eq_without_problem(
indoc!(
r#"
Num.divFloorChecked
"#
),
"Int a, Int a -> Result (Int a) [ DivByZero ]*",
);
}
#[test]
fn atan() {
infer_eq_without_problem(
@ -3715,7 +3820,7 @@ mod solve_expr {
negatePoint { x: 1, y: 2.1, z: 0x3 }
"#
),
"{ x : Num a, y : F64, z : Int * }",
"{ x : Num a, y : Float *, z : Int * }",
);
}
@ -3732,7 +3837,7 @@ mod solve_expr {
{ a, b }
"#
),
"{ a : { x : Num a, y : F64, z : c }, b : { blah : Str, x : Num a, y : F64, z : c } }",
"{ a : { x : Num a, y : Float *, z : c }, b : { blah : Str, x : Num a, y : Float *, z : c } }",
);
}
@ -5662,4 +5767,150 @@ mod solve_expr {
"a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb -> { a : a, aa : aa, b : b, bb : bb, c : c, d : d, e : e, f : f, g : g, h : h, i : i, j : j, k : k, l : l, m : m, n : n, o : o, p : p, q : q, r : r, s : s, t : t, u : u, v : v, w : w, x : x, y : y, z : z }",
)
}
#[test]
fn exposed_ability_name() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [ hash ] to "./platform"
Hash has hash : a -> U64 | a has Hash
"#
),
"a -> U64 | a has Hash",
)
}
#[test]
fn single_ability_single_member_specializations() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [ hash ] to "./platform"
Hash has hash : a -> U64 | a has Hash
Id := U64
hash = \$Id n -> n
"#
),
[("Hash:hash", "Id")],
)
}
#[test]
fn single_ability_multiple_members_specializations() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [ hash, hash32 ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
hash32 : a -> U32 | a has Hash
Id := U64
hash = \$Id n -> n
hash32 = \$Id n -> Num.toU32 n
"#
),
[("Hash:hash", "Id"), ("Hash:hash32", "Id")],
)
}
#[test]
fn multiple_abilities_multiple_members_specializations() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [ hash, hash32, eq, le ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
hash32 : a -> U32 | a has Hash
Ord has
eq : a, a -> Bool | a has Ord
le : a, a -> Bool | a has Ord
Id := U64
hash = \$Id n -> n
hash32 = \$Id n -> Num.toU32 n
eq = \$Id m, $Id n -> m == n
le = \$Id m, $Id n -> m < n
"#
),
[
("Hash:hash", "Id"),
("Hash:hash32", "Id"),
("Ord:eq", "Id"),
("Ord:le", "Id"),
],
)
}
#[test]
fn ability_checked_specialization_with_typed_body() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [ hash ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
Id := U64
hash : Id -> U64
hash = \$Id n -> n
"#
),
[("Hash:hash", "Id")],
)
}
#[test]
fn ability_checked_specialization_with_annotation_only() {
check_inferred_abilities(
indoc!(
r#"
app "test" provides [ hash ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
Id := U64
hash : Id -> U64
"#
),
[("Hash:hash", "Id")],
)
}
#[test]
fn ability_specialization_called() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [ zero ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
Id := U64
hash = \$Id n -> n
zero = hash ($Id 0)
"#
),
"U64",
)
}
}

View file

@ -723,11 +723,24 @@ fn gen_wrap_add_nums() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_f64() {
// FIXME this works with normal types, but fails when checking uniqueness types
assert_evals_to!(
indoc!(
r#"
when 48 / 2 is
48 / 2
"#
),
24.0,
f64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_f64() {
assert_evals_to!(
indoc!(
r#"
when Num.divChecked 48 2 is
Ok val -> val
Err _ -> -1
"#
@ -736,6 +749,23 @@ fn gen_div_f64() {
f64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_by_zero_f64() {
assert_evals_to!(
indoc!(
r#"
when Num.divChecked 47 0 is
Ok val -> val
Err _ -> -1
"#
),
-1.0,
f64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_dec() {
@ -748,7 +778,27 @@ fn gen_div_dec() {
y : Dec
y = 3
when x / y is
x / y
"#
),
RocDec::from_str_to_i128_unsafe("3.333333333333333333"),
i128
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 10
y : Dec
y = 3
when Num.divChecked x y is
Ok val -> val
Err _ -> -1
"#
@ -757,6 +807,27 @@ fn gen_div_dec() {
i128
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_by_zero_dec() {
assert_evals_to!(
indoc!(
r#"
x : Dec
x = 10
y : Dec
y = 0
when Num.divChecked x y is
Ok val -> val
Err _ -> -1
"#
),
RocDec::from_str_to_i128_unsafe("-1"),
i128
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
@ -965,7 +1036,21 @@ fn gen_div_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 10 is
1000 // 10
"#
),
100,
i64
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_checked_i64() {
assert_evals_to!(
indoc!(
r#"
when Num.divFloorChecked 1000 10 is
Ok val -> val
Err _ -> -1
"#
@ -977,11 +1062,11 @@ fn gen_div_i64() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn gen_div_by_zero_i64() {
fn gen_div_checked_by_zero_i64() {
assert_evals_to!(
indoc!(
r#"
when 1000 // 0 is
when Num.divFloorChecked 1000 0 is
Err DivByZero -> 99
_ -> -24
"#
@ -2660,6 +2745,166 @@ fn num_to_str() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_u8() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr 0u8"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1u8"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10u8"#, RocStr::from("10"), RocStr);
let max = format!("{}", u8::MAX);
assert_evals_to!(r#"Num.toStr Num.maxU8"#, RocStr::from(max.as_str()), RocStr);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_u16() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr 0u16"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1u16"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10u16"#, RocStr::from("10"), RocStr);
let max = format!("{}", u16::MAX);
assert_evals_to!(
r#"Num.toStr Num.maxU16"#,
RocStr::from(max.as_str()),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_u32() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr 0u32"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1u32"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10u32"#, RocStr::from("10"), RocStr);
let max = format!("{}", u32::MAX);
assert_evals_to!(
r#"Num.toStr Num.maxU32"#,
RocStr::from(max.as_str()),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_u64() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr 0u64"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1u64"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10u64"#, RocStr::from("10"), RocStr);
let max = format!("{}", u64::MAX);
assert_evals_to!(
r#"Num.toStr Num.maxU64"#,
RocStr::from(max.as_str()),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_i8() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr -10i8"#, RocStr::from("-10"), RocStr);
assert_evals_to!(r#"Num.toStr -1i8"#, RocStr::from("-1"), RocStr);
assert_evals_to!(r#"Num.toStr 0i8"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1i8"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10i8"#, RocStr::from("10"), RocStr);
let max = format!("{}", i8::MAX);
assert_evals_to!(r#"Num.toStr Num.maxI8"#, RocStr::from(max.as_str()), RocStr);
let max = format!("{}", i8::MIN);
assert_evals_to!(r#"Num.toStr Num.minI8"#, RocStr::from(max.as_str()), RocStr);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_i16() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr -10i16"#, RocStr::from("-10"), RocStr);
assert_evals_to!(r#"Num.toStr -1i16"#, RocStr::from("-1"), RocStr);
assert_evals_to!(r#"Num.toStr 0i16"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1i16"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10i16"#, RocStr::from("10"), RocStr);
let max = format!("{}", i16::MAX);
assert_evals_to!(
r#"Num.toStr Num.maxI16"#,
RocStr::from(max.as_str()),
RocStr
);
let max = format!("{}", i16::MIN);
assert_evals_to!(
r#"Num.toStr Num.minI16"#,
RocStr::from(max.as_str()),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_i32() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr -10i32"#, RocStr::from("-10"), RocStr);
assert_evals_to!(r#"Num.toStr -1i32"#, RocStr::from("-1"), RocStr);
assert_evals_to!(r#"Num.toStr 0i32"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1i32"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10i32"#, RocStr::from("10"), RocStr);
let max = format!("{}", i32::MAX);
assert_evals_to!(
r#"Num.toStr Num.maxI32"#,
RocStr::from(max.as_str()),
RocStr
);
let max = format!("{}", i32::MIN);
assert_evals_to!(
r#"Num.toStr Num.minI32"#,
RocStr::from(max.as_str()),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn num_to_str_i64() {
use roc_std::RocStr;
assert_evals_to!(r#"Num.toStr -10i64"#, RocStr::from("-10"), RocStr);
assert_evals_to!(r#"Num.toStr -1i64"#, RocStr::from("-1"), RocStr);
assert_evals_to!(r#"Num.toStr 0i64"#, RocStr::from("0"), RocStr);
assert_evals_to!(r#"Num.toStr 1i64"#, RocStr::from("1"), RocStr);
assert_evals_to!(r#"Num.toStr 10i64"#, RocStr::from("10"), RocStr);
let max = format!("{}", i64::MAX);
assert_evals_to!(
r#"Num.toStr Num.maxI64"#,
RocStr::from(max.as_str()),
RocStr
);
let max = format!("{}", i64::MIN);
assert_evals_to!(
r#"Num.toStr Num.minI64"#,
RocStr::from(max.as_str()),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn u8_addition_greater_than_i8() {

View file

@ -54,6 +54,7 @@ pub fn helper(
src_dir,
Default::default(),
roc_target::TargetInfo::default_x86_64(),
roc_reporting::report::RenderTarget::ColorTerminal,
);
let mut loaded = loaded.expect("failed to load module");

View file

@ -7,6 +7,7 @@ use roc_collections::all::MutSet;
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_mono::ir::OptLevel;
use roc_region::all::LineInfo;
use roc_reporting::report::RenderTarget;
use target_lexicon::Triple;
fn promote_expr_to_module(src: &str) -> String {
@ -57,6 +58,7 @@ fn create_llvm_module<'a>(
src_dir,
Default::default(),
target_info,
RenderTarget::ColorTerminal,
);
let mut loaded = match loaded {

View file

@ -91,6 +91,7 @@ fn compile_roc_to_wasm_bytes<'a, T: Wasm32Result>(
src_dir,
Default::default(),
roc_target::TargetInfo::default_wasm32(),
roc_reporting::report::RenderTarget::ColorTerminal,
);
let loaded = loaded.expect("failed to load module");

View file

@ -17,6 +17,7 @@ roc_load = { path = "../load" }
roc_can = { path = "../can" }
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"] }

View file

@ -101,6 +101,7 @@ fn compiles_to_ir(test_name: &str, src: &str) {
src_dir,
Default::default(),
TARGET_INFO,
roc_reporting::report::RenderTarget::Generic,
);
let mut loaded = match loaded {
@ -274,7 +275,7 @@ fn ir_round() {
#[mono_test]
fn ir_when_idiv() {
r#"
when 1000 // 10 is
when Num.divFloorChecked 1000 10 is
Ok val -> val
Err _ -> -1
"#

View file

@ -116,7 +116,7 @@ fn find_names_needed(
}
match &subs.get_content_without_compacting(variable).clone() {
RecursionVar { opt_name: None, .. } | FlexVar(None) => {
RecursionVar { opt_name: None, .. } | FlexVar(None) | FlexAbleVar(None, _) => {
let root = subs.get_root_key_without_compacting(variable);
// If this var is *not* its own root, then the
@ -139,7 +139,8 @@ fn find_names_needed(
opt_name: Some(name_index),
..
}
| FlexVar(Some(name_index)) => {
| FlexVar(Some(name_index))
| FlexAbleVar(Some(name_index), _) => {
// This root already has a name. Nothing more to do here!
// User-defined names are already taken.
@ -147,7 +148,7 @@ fn find_names_needed(
let name = subs.field_names[name_index.index as usize].clone();
names_taken.insert(name);
}
RigidVar(name_index) => {
RigidVar(name_index) | RigidAbleVar(name_index, _) => {
// User-defined names are already taken.
// We must not accidentally generate names that collide with them!
let name = subs.field_names[name_index.index as usize].clone();
@ -289,6 +290,11 @@ fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
}
}
#[derive(Default)]
struct Context<'a> {
able_variables: Vec<(&'a str, Symbol)>,
}
pub fn content_to_string(
content: &Content,
subs: &Subs,
@ -297,8 +303,16 @@ pub fn content_to_string(
) -> String {
let mut buf = String::new();
let env = Env { home, interns };
let mut ctx = Context::default();
write_content(&env, content, subs, &mut buf, Parens::Unnecessary);
write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary);
for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() {
buf.push_str(if i == 0 { " | " } else { ", " });
buf.push_str(var);
buf.push_str(" has ");
write_symbol(&env, ability, &mut buf);
}
buf
}
@ -314,7 +328,14 @@ pub fn get_single_arg<'a>(subs: &'a Subs, args: &'a AliasVariables) -> &'a Conte
subs.get_content_without_compacting(arg_var)
}
fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, parens: Parens) {
fn write_content<'a>(
env: &Env,
ctx: &mut Context<'a>,
content: &Content,
subs: &'a Subs,
buf: &mut String,
parens: Parens,
) {
use crate::subs::Content::*;
match content {
@ -327,6 +348,18 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
let name = &subs.field_names[name_index.index as usize];
buf.push_str(name.as_str())
}
FlexAbleVar(opt_name_index, ability) => {
let name = opt_name_index
.map(|name_index| subs.field_names[name_index.index as usize].as_str())
.unwrap_or(WILDCARD);
ctx.able_variables.push((name, *ability));
buf.push_str(name);
}
RigidAbleVar(name_index, ability) => {
let name = subs.field_names[name_index.index as usize].as_str();
ctx.able_variables.push((name, *ability));
buf.push_str(name);
}
RecursionVar { opt_name, .. } => match opt_name {
Some(name_index) => {
let name = &subs.field_names[name_index.index as usize];
@ -334,7 +367,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
}
None => buf.push_str(WILDCARD),
},
Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens),
Structure(flat_type) => write_flat_type(env, ctx, flat_type, subs, buf, parens),
Alias(symbol, args, _actual, _kind) => {
let write_parens = parens == Parens::InTypeParam && !args.is_empty();
@ -346,6 +379,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
Symbol::NUM_INTEGER => {
write_integer(
env,
ctx,
get_single_arg(subs, &args),
subs,
buf,
@ -353,17 +387,25 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
false,
);
}
Symbol::NUM_FLOATINGPOINT => buf.push_str("F64"),
Symbol::NUM_FLOATINGPOINT => write_float(
env,
ctx,
get_single_arg(subs, &args),
subs,
buf,
parens,
write_parens,
),
_ => write_parens!(write_parens, buf, {
buf.push_str("Num ");
write_content(env, content, subs, buf, parens);
write_content(env, ctx, content, subs, buf, parens);
}),
},
_ => write_parens!(write_parens, buf, {
buf.push_str("Num ");
write_content(env, content, subs, buf, parens);
write_content(env, ctx, content, subs, buf, parens);
}),
}
}
@ -371,29 +413,18 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
Symbol::NUM_INT => {
let content = get_single_arg(subs, args);
write_integer(env, content, subs, buf, parens, write_parens)
write_integer(env, ctx, content, subs, buf, parens, write_parens)
}
Symbol::NUM_FLOAT => {
debug_assert_eq!(args.len(), 1);
let arg_var_index = args
.into_iter()
.next()
.expect("Num was not applied to a type argument!");
let arg_var = subs[arg_var_index];
let content = subs.get_content_without_compacting(arg_var);
match content {
Alias(Symbol::NUM_BINARY32, _, _, _) => buf.push_str("F32"),
Alias(Symbol::NUM_BINARY64, _, _, _) => buf.push_str("F64"),
Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"),
_ => write_parens!(write_parens, buf, {
buf.push_str("Float ");
write_content(env, content, subs, buf, parens);
}),
}
}
Symbol::NUM_FLOAT => write_float(
env,
ctx,
get_single_arg(subs, args),
subs,
buf,
parens,
write_parens,
),
_ => write_parens!(write_parens, buf, {
write_symbol(env, *symbol, buf);
@ -403,6 +434,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
buf.push(' ');
write_content(
env,
ctx,
subs.get_content_without_compacting(var),
subs,
buf,
@ -414,7 +446,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
if false {
buf.push_str("[[ but really ");
let content = subs.get_content_without_compacting(*_actual);
write_content(env, content, subs, buf, parens);
write_content(env, ctx, content, subs, buf, parens);
buf.push_str("]]");
}
}),
@ -422,6 +454,7 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
}
RangedNumber(typ, _range_vars) => write_content(
env,
ctx,
subs.get_content_without_compacting(*typ),
subs,
buf,
@ -431,10 +464,32 @@ fn write_content(env: &Env, content: &Content, subs: &Subs, buf: &mut String, pa
}
}
fn write_integer(
fn write_float<'a>(
env: &Env,
ctx: &mut Context<'a>,
content: &Content,
subs: &Subs,
subs: &'a Subs,
buf: &mut String,
parens: Parens,
write_parens: bool,
) {
use crate::subs::Content::*;
match content {
Alias(Symbol::NUM_BINARY32, _, _, _) => buf.push_str("F32"),
Alias(Symbol::NUM_BINARY64, _, _, _) => buf.push_str("F64"),
Alias(Symbol::NUM_DECIMAL, _, _, _) => buf.push_str("Dec"),
_ => write_parens!(write_parens, buf, {
buf.push_str("Float ");
write_content(env, ctx, content, subs, buf, parens);
}),
}
}
fn write_integer<'a>(
env: &Env,
ctx: &mut Context<'a>,
content: &Content,
subs: &'a Subs,
buf: &mut String,
parens: Parens,
write_parens: bool,
@ -454,7 +509,7 @@ fn write_integer(
)*
actual => {
buf.push_str("Int ");
write_content(env, actual, subs, buf, parens);
write_content(env, ctx, actual, subs, buf, parens);
}
}
)
@ -497,6 +552,7 @@ impl<'a> ExtContent<'a> {
fn write_ext_content<'a>(
env: &Env,
ctx: &mut Context<'a>,
subs: &'a Subs,
buf: &mut String,
ext_content: ExtContent<'a>,
@ -508,12 +564,13 @@ fn write_ext_content<'a>(
//
// e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r`
write_content(env, content, subs, buf, parens)
write_content(env, ctx, content, subs, buf, parens)
}
}
fn write_sorted_tags2<'a>(
env: &Env,
ctx: &mut Context<'a>,
subs: &'a Subs,
buf: &mut String,
tags: &UnionTags,
@ -546,6 +603,7 @@ fn write_sorted_tags2<'a>(
buf.push(' ');
write_content(
env,
ctx,
subs.get_content_without_compacting(*var),
subs,
buf,
@ -559,6 +617,7 @@ fn write_sorted_tags2<'a>(
fn write_sorted_tags<'a>(
env: &Env,
ctx: &mut Context<'a>,
subs: &'a Subs,
buf: &mut String,
tags: &MutMap<TagName, Vec<Variable>>,
@ -603,6 +662,7 @@ fn write_sorted_tags<'a>(
buf.push(' ');
write_content(
env,
ctx,
subs.get_content_without_compacting(*var),
subs,
buf,
@ -614,18 +674,37 @@ fn write_sorted_tags<'a>(
ExtContent::from_var(subs, ext_var)
}
fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut String, parens: Parens) {
fn write_flat_type<'a>(
env: &Env,
ctx: &mut Context<'a>,
flat_type: &FlatType,
subs: &'a Subs,
buf: &mut String,
parens: Parens,
) {
use crate::subs::FlatType::*;
match flat_type {
Apply(symbol, args) => {
write_apply(env, *symbol, subs.get_subs_slice(*args), subs, buf, parens)
}
Apply(symbol, args) => write_apply(
env,
ctx,
*symbol,
subs.get_subs_slice(*args),
subs,
buf,
parens,
),
EmptyRecord => buf.push_str(EMPTY_RECORD),
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
Func(args, _closure, ret) => {
write_fn(env, subs.get_subs_slice(*args), *ret, subs, buf, parens)
}
Func(args, _closure, ret) => write_fn(
env,
ctx,
subs.get_subs_slice(*args),
*ret,
subs,
buf,
parens,
),
Record(fields, ext_var) => {
use crate::types::{gather_fields, RecordStructure};
@ -664,6 +743,7 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
write_content(
env,
ctx,
subs.get_content_without_compacting(var),
subs,
buf,
@ -684,18 +764,18 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
//
// e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r`
write_content(env, content, subs, buf, parens)
write_content(env, ctx, content, subs, buf, parens)
}
}
}
TagUnion(tags, ext_var) => {
buf.push_str("[ ");
let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var);
let ext_content = write_sorted_tags2(env, ctx, subs, buf, tags, *ext_var);
buf.push_str(" ]");
write_ext_content(env, subs, buf, ext_content, parens)
write_ext_content(env, ctx, subs, buf, ext_content, parens)
}
FunctionOrTagUnion(tag_name, _, ext_var) => {
@ -703,25 +783,26 @@ fn write_flat_type(env: &Env, flat_type: &FlatType, subs: &Subs, buf: &mut Strin
let mut tags: MutMap<TagName, _> = MutMap::default();
tags.insert(subs[*tag_name].clone(), vec![]);
let ext_content = write_sorted_tags(env, subs, buf, &tags, *ext_var);
let ext_content = write_sorted_tags(env, ctx, subs, buf, &tags, *ext_var);
buf.push_str(" ]");
write_ext_content(env, subs, buf, ext_content, parens)
write_ext_content(env, ctx, subs, buf, ext_content, parens)
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
buf.push_str("[ ");
let ext_content = write_sorted_tags2(env, subs, buf, tags, *ext_var);
let ext_content = write_sorted_tags2(env, ctx, subs, buf, tags, *ext_var);
buf.push_str(" ]");
write_ext_content(env, subs, buf, ext_content, parens);
write_ext_content(env, ctx, subs, buf, ext_content, parens);
buf.push_str(" as ");
write_content(
env,
ctx,
subs.get_content_without_compacting(*rec_var),
subs,
buf,
@ -777,11 +858,12 @@ pub fn chase_ext_tag_union<'a>(
}
}
fn write_apply(
fn write_apply<'a>(
env: &Env,
ctx: &mut Context<'a>,
symbol: Symbol,
args: &[Variable],
subs: &Subs,
subs: &'a Subs,
buf: &mut String,
parens: Parens,
) {
@ -805,7 +887,7 @@ fn write_apply(
buf.push('(');
}
write_content(env, content, subs, &mut arg_param, Parens::InTypeParam);
write_content(env, ctx, content, subs, &mut arg_param, Parens::InTypeParam);
buf.push_str("Num ");
buf.push_str(&arg_param);
@ -838,6 +920,7 @@ fn write_apply(
buf.push(' ');
write_content(
env,
ctx,
subs.get_content_without_compacting(*arg),
subs,
buf,
@ -852,11 +935,12 @@ fn write_apply(
}
}
fn write_fn(
fn write_fn<'a>(
env: &Env,
ctx: &mut Context<'a>,
args: &[Variable],
ret: Variable,
subs: &Subs,
subs: &'a Subs,
buf: &mut String,
parens: Parens,
) {
@ -876,6 +960,7 @@ fn write_fn(
write_content(
env,
ctx,
subs.get_content_without_compacting(*arg),
subs,
buf,
@ -886,6 +971,7 @@ fn write_fn(
buf.push_str(" -> ");
write_content(
env,
ctx,
subs.get_content_without_compacting(ret),
subs,
buf,

View file

@ -1,6 +1,9 @@
#![deny(unsafe_op_in_unsafe_fn)]
use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt};
use crate::types::{
name_type_var, AliasKind, ErrorType, Problem, RecordField, RecordFieldsError, TypeExt,
};
use roc_collections::all::{ImMap, ImSet, MutSet, SendMap};
use roc_error_macros::internal_error;
use roc_module::ident::{Lowercase, TagName, Uppercase};
use roc_module::symbol::Symbol;
use std::fmt;
@ -754,7 +757,9 @@ impl<'a> fmt::Debug for SubsFmtContent<'a> {
fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result {
match this {
Content::FlexVar(name) => write!(f, "Flex({:?})", name),
Content::FlexAbleVar(name, symbol) => write!(f, "FlexAble({:?}, {:?})", name, symbol),
Content::RigidVar(name) => write!(f, "Rigid({:?})", name),
Content::RigidAbleVar(name, symbol) => write!(f, "RigidAble({:?}, {:?})", name, symbol),
Content::RecursionVar {
structure,
opt_name,
@ -794,7 +799,19 @@ fn subs_fmt_flat_type(this: &FlatType, subs: &Subs, f: &mut fmt::Formatter) -> f
}
FlatType::Func(arguments, lambda_set, result) => {
let slice = subs.get_subs_slice(*arguments);
write!(f, "Func({:?}, {:?}, {:?})", slice, lambda_set, result)
write!(f, "Func([")?;
for var in slice {
let content = subs.get_content_without_compacting(*var);
write!(f, "<{:?}>{:?},", *var, SubsFmtContent(content, subs))?;
}
let result_content = subs.get_content_without_compacting(*result);
write!(
f,
"], {:?}, <{:?}>{:?})",
lambda_set,
*result,
SubsFmtContent(result_content, subs)
)
}
FlatType::Record(fields, ext) => {
write!(f, "{{ ")?;
@ -1737,6 +1754,14 @@ impl Subs {
self.set(var, desc);
}
pub fn rigid_able_var(&mut self, var: Variable, name: Lowercase, ability: Symbol) {
let name_index = SubsIndex::push_new(&mut self.field_names, name);
let content = Content::RigidAbleVar(name_index, ability);
let desc = Descriptor::from(content);
self.set(var, desc);
}
/// Unions two keys without the possibility of failure.
pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) {
let l_root = self.utable.inlined_get_root_key(left);
@ -2118,6 +2143,12 @@ pub enum Content {
FlexVar(Option<SubsIndex<Lowercase>>),
/// name given in a user-written annotation
RigidVar(SubsIndex<Lowercase>),
/// Like a [Self::FlexVar], but is also bound to an ability.
/// This can only happen when unified with a [Self::RigidAbleVar].
FlexAbleVar(Option<SubsIndex<Lowercase>>, Symbol),
/// Like a [Self::RigidVar], but is also bound to an ability.
/// For example, "a has Hash".
RigidAbleVar(SubsIndex<Lowercase>, Symbol),
/// name given to a recursion variable
RecursionVar {
structure: Variable,
@ -2713,11 +2744,11 @@ impl RecordFields {
&'a self,
subs: &'a Subs,
ext: Variable,
) -> impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a {
let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)
.expect("Something weird ended up in a record type");
) -> Result<impl Iterator<Item = (&Lowercase, RecordField<Variable>)> + 'a, RecordFieldsError>
{
let (it, _) = crate::types::gather_fields_unsorted_iter(subs, *self, ext)?;
it
Ok(it)
}
#[inline(always)]
@ -2838,7 +2869,12 @@ fn occurs(
Err((root_var, vec![]))
} else {
match subs.get_content_without_compacting(root_var) {
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => Ok(()),
FlexVar(_)
| RigidVar(_)
| FlexAbleVar(_, _)
| RigidAbleVar(_, _)
| RecursionVar { .. }
| Error => Ok(()),
Structure(flat_type) => {
let mut new_seen = seen.to_owned();
@ -2966,7 +3002,12 @@ fn explicit_substitute(
to
} else {
match subs.get(in_var).content {
FlexVar(_) | RigidVar(_) | RecursionVar { .. } | Error => in_var,
FlexVar(_)
| RigidVar(_)
| FlexAbleVar(_, _)
| RigidAbleVar(_, _)
| RecursionVar { .. }
| Error => in_var,
Structure(flat_type) => {
match flat_type {
@ -3134,9 +3175,9 @@ fn get_var_names(
subs.set_mark(var, Mark::GET_VAR_NAMES);
match desc.content {
Error | FlexVar(None) => taken_names,
Error | FlexVar(None) | FlexAbleVar(None, _) => taken_names,
FlexVar(Some(name_index)) => add_name(
FlexVar(Some(name_index)) | FlexAbleVar(Some(name_index), _) => add_name(
subs,
0,
name_index,
@ -3163,7 +3204,9 @@ fn get_var_names(
None => taken_names,
},
RigidVar(name_index) => add_name(subs, 0, name_index, var, RigidVar, taken_names),
RigidVar(name_index) | RigidAbleVar(name_index, _) => {
add_name(subs, 0, name_index, var, RigidVar, taken_names)
}
Alias(_, args, _, _) => args.into_iter().fold(taken_names, |answer, arg_var| {
get_var_names(subs, subs[arg_var], answer)
@ -3329,11 +3372,6 @@ fn content_to_err_type(
match content {
Structure(flat_type) => flat_type_to_err_type(subs, state, flat_type),
FlexVar(Some(name_index)) => {
let name = subs.field_names[name_index.index as usize].clone();
ErrorType::FlexVar(name)
}
FlexVar(opt_name) => {
let name = match opt_name {
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
@ -3356,6 +3394,28 @@ fn content_to_err_type(
ErrorType::RigidVar(name)
}
FlexAbleVar(opt_name, ability) => {
let name = match opt_name {
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
None => {
// set the name so when this variable occurs elsewhere in the type it gets the same name
let name = get_fresh_var_name(state);
let name_index = SubsIndex::push_new(&mut subs.field_names, name.clone());
subs.set_content(var, FlexVar(Some(name_index)));
name
}
};
ErrorType::FlexAbleVar(name, ability)
}
RigidAbleVar(name_index, ability) => {
let name = subs.field_names[name_index.index as usize].clone();
ErrorType::RigidAbleVar(name, ability)
}
RecursionVar { opt_name, .. } => {
let name = match opt_name {
Some(name_index) => subs.field_names[name_index.index as usize].clone(),
@ -3628,7 +3688,7 @@ fn restore_help(subs: &mut Subs, initial: Variable) {
use FlatType::*;
match &desc.content {
FlexVar(_) | RigidVar(_) | Error => (),
FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => (),
RecursionVar { structure, .. } => {
stack.push(*structure);
@ -3857,6 +3917,8 @@ impl StorageSubs {
match content {
FlexVar(opt_name) => FlexVar(*opt_name),
RigidVar(name) => RigidVar(*name),
FlexAbleVar(opt_name, ability) => FlexAbleVar(*opt_name, *ability),
RigidAbleVar(name, ability) => RigidAbleVar(*name, *ability),
RecursionVar {
structure,
opt_name,
@ -4253,6 +4315,29 @@ fn deep_copy_var_to_help(env: &mut DeepCopyVarToEnv<'_>, var: Variable) -> Varia
copy
}
FlexAbleVar(opt_name_index, ability) => {
let new_name_index = opt_name_index.map(|name_index| {
let name = env.source.field_names[name_index.index as usize].clone();
SubsIndex::push_new(&mut env.target.field_names, name)
});
let content = FlexAbleVar(new_name_index, ability);
env.target.set_content(copy, content);
copy
}
RigidAbleVar(name_index, ability) => {
let name = env.source.field_names[name_index.index as usize].clone();
let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name);
env.target.set(
copy,
make_descriptor(FlexAbleVar(Some(new_name_index), ability)),
);
copy
}
Alias(symbol, arguments, real_type_var, kind) => {
let new_variables =
SubsSlice::reserve_into_subs(env.target, arguments.all_variables_len as _);
@ -4312,6 +4397,8 @@ pub struct CopiedImport {
pub variable: Variable,
pub flex: Vec<Variable>,
pub rigid: Vec<Variable>,
pub flex_able: Vec<Variable>,
pub rigid_able: Vec<Variable>,
pub translations: Vec<(Variable, Variable)>,
pub registered: Vec<Variable>,
}
@ -4322,6 +4409,8 @@ struct CopyImportEnv<'a> {
target: &'a mut Subs,
flex: Vec<Variable>,
rigid: Vec<Variable>,
flex_able: Vec<Variable>,
rigid_able: Vec<Variable>,
translations: Vec<(Variable, Variable)>,
registered: Vec<Variable>,
}
@ -4343,6 +4432,8 @@ pub fn copy_import_to(
target,
flex: Vec::new(),
rigid: Vec::new(),
flex_able: Vec::new(),
rigid_able: Vec::new(),
translations: Vec::new(),
registered: Vec::new(),
};
@ -4354,6 +4445,8 @@ pub fn copy_import_to(
source,
flex,
rigid,
flex_able,
rigid_able,
translations,
registered,
target: _,
@ -4376,6 +4469,8 @@ pub fn copy_import_to(
variable: copy,
flex,
rigid,
flex_able,
rigid_able,
translations,
registered,
}
@ -4393,7 +4488,10 @@ pub fn copy_import_to(
/// standard variables
fn is_registered(content: &Content) -> bool {
match content {
Content::FlexVar(_) | Content::RigidVar(_) => false,
Content::FlexVar(_)
| Content::RigidVar(_)
| Content::FlexAbleVar(..)
| Content::RigidAbleVar(..) => false,
Content::Structure(FlatType::EmptyRecord | FlatType::EmptyTagUnion) => false,
Content::Structure(_)
@ -4454,6 +4552,13 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
// We have already marked the variable as copied, so we
// will not repeat this work or crawl this variable again.
match desc.content {
Structure(Erroneous(_)) => {
// Make this into a flex var so that we don't have to copy problems across module
// boundaries - the error will be reported locally.
env.target.set(copy, make_descriptor(FlexVar(None)));
copy
}
Structure(flat_type) => {
let new_flat_type = match flat_type {
Apply(symbol, arguments) => {
@ -4484,7 +4589,9 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
Func(new_arguments, new_closure_var, new_ret_var)
}
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
Erroneous(_) => internal_error!("I thought this was handled above"),
same @ EmptyRecord | same @ EmptyTagUnion => same,
Record(fields, ext_var) => {
let record_fields = {
@ -4631,6 +4738,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
copy
}
FlexAbleVar(opt_name_index, ability) => {
if let Some(name_index) = opt_name_index {
let name = env.source.field_names[name_index.index as usize].clone();
let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name);
let content = FlexAbleVar(Some(new_name_index), ability);
env.target.set_content(copy, content);
}
env.flex_able.push(copy);
copy
}
Error => {
// Open question: should this return Error, or a Flex var?
@ -4653,6 +4774,20 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
copy
}
RigidAbleVar(name_index, ability) => {
let name = env.source.field_names[name_index.index as usize].clone();
let new_name_index = SubsIndex::push_new(&mut env.target.field_names, name);
env.target
.set(copy, make_descriptor(RigidAbleVar(new_name_index, ability)));
env.rigid_able.push(copy);
env.translations.push((var, copy));
copy
}
RecursionVar {
opt_name,
structure,
@ -4746,7 +4881,7 @@ where
use Content::*;
use FlatType::*;
match content {
FlexVar(_) | RigidVar(_) => {}
FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) => {}
RecursionVar {
structure,
opt_name: _,

View file

@ -1744,6 +1744,15 @@ pub enum Reason {
RecordUpdateKeys(Symbol, SendMap<Lowercase, Region>),
RecordDefaultField(Lowercase),
NumericLiteralSuffix,
InvalidAbilityMemberSpecialization {
member_name: Symbol,
def_region: Region,
unimplemented_abilities: DoesNotImplementAbility,
},
GeneralizedAbilityMemberSpecialization {
member_name: Symbol,
def_region: Region,
},
}
#[derive(PartialEq, Debug, Clone)]
@ -1783,6 +1792,8 @@ pub enum Category {
Accessor(Lowercase),
Access(Lowercase),
DefaultValue(Lowercase), // for setting optional fields
AbilityMemberSpecialization(Symbol),
}
#[derive(Debug, Clone, PartialEq, Eq)]
@ -1867,14 +1878,19 @@ pub enum Mismatch {
InconsistentWhenBranches,
CanonicalizationProblem,
TypeNotInRange,
DoesNotImplementAbiity(Variable, Symbol),
}
pub type DoesNotImplementAbility = Vec<(ErrorType, Symbol)>;
#[derive(PartialEq, Eq, Clone, Hash)]
pub enum ErrorType {
Infinite,
Type(Symbol, Vec<ErrorType>),
FlexVar(Lowercase),
RigidVar(Lowercase),
FlexAbleVar(Lowercase, Symbol),
RigidAbleVar(Lowercase, Symbol),
Record(SendMap<Lowercase, RecordField<ErrorType>>, TypeExt),
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt),
RecursiveTagUnion(Box<ErrorType>, SendMap<TagName, Vec<ErrorType>>, TypeExt),
@ -1905,10 +1921,7 @@ impl ErrorType {
match self {
Infinite => {}
Type(_, ts) => ts.iter().for_each(|t| t.add_names(taken)),
FlexVar(v) => {
taken.insert(v.clone());
}
RigidVar(v) => {
FlexVar(v) | RigidVar(v) | FlexAbleVar(v, _) | RigidAbleVar(v, _) => {
taken.insert(v.clone());
}
Record(fields, ext) => {
@ -2087,8 +2100,18 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
match error_type {
Infinite => buf.push('∞'),
Error => buf.push('?'),
FlexVar(name) => buf.push_str(name.as_str()),
RigidVar(name) => buf.push_str(name.as_str()),
FlexVar(name) | RigidVar(name) => buf.push_str(name.as_str()),
FlexAbleVar(name, symbol) | RigidAbleVar(name, symbol) => {
let write_parens = parens == Parens::InTypeParam;
if write_parens {
buf.push('(');
}
buf.push_str(name.as_str());
buf.push_str(&format!(" has {:?}", symbol));
if write_parens {
buf.push(')');
}
}
Type(symbol, arguments) => {
let write_parens = parens == Parens::InTypeParam && !arguments.is_empty();

View file

@ -11,6 +11,9 @@ bitflags = "1.3.2"
[dependencies.roc_collections]
path = "../collections"
[dependencies.roc_error_macros]
path = "../../error_macros"
[dependencies.roc_module]
path = "../module"

View file

@ -1,4 +1,5 @@
use bitflags::bitflags;
use roc_error_macros::todo_abilities;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_types::subs::Content::{self, *};
@ -6,7 +7,7 @@ use roc_types::subs::{
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable,
RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::{AliasKind, ErrorType, Mismatch, RecordField};
use roc_types::types::{AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, RecordField};
macro_rules! mismatch {
() => {{
@ -19,7 +20,10 @@ macro_rules! mismatch {
);
}
vec![Mismatch::TypeMismatch]
Outcome {
mismatches: vec![Mismatch::TypeMismatch],
..Outcome::default()
}
}};
($msg:expr) => {{
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() {
@ -34,7 +38,10 @@ macro_rules! mismatch {
}
vec![Mismatch::TypeMismatch]
Outcome {
mismatches: vec![Mismatch::TypeMismatch],
..Outcome::default()
}
}};
($msg:expr,) => {{
mismatch!($msg)
@ -51,8 +58,28 @@ macro_rules! mismatch {
println!("");
}
vec![Mismatch::TypeMismatch]
Outcome {
mismatches: vec![Mismatch::TypeMismatch],
..Outcome::default()
}
}};
(%not_able, $var:expr, $ability:expr, $msg:expr, $($arg:tt)*) => {{
if cfg!(debug_assertions) && std::env::var("ROC_PRINT_MISMATCHES").is_ok() {
println!(
"Mismatch in {} Line {} Column {}",
file!(),
line!(),
column!()
);
println!($msg, $($arg)*);
println!("");
}
Outcome {
mismatches: vec![Mismatch::TypeMismatch, Mismatch::DoesNotImplementAbiity($var, $ability)],
..Outcome::default()
}
}}
}
type Pool = Vec<Variable>;
@ -105,20 +132,52 @@ pub struct Context {
#[derive(Debug)]
pub enum Unified {
Success(Pool),
Failure(Pool, ErrorType, ErrorType),
Success {
vars: Pool,
must_implement_ability: Vec<MustImplementAbility>,
},
Failure(Pool, ErrorType, ErrorType, DoesNotImplementAbility),
BadType(Pool, roc_types::types::Problem),
}
type Outcome = Vec<Mismatch>;
/// Specifies that `type` must implement the ability `ability`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MustImplementAbility {
// This only points to opaque type names currently.
// TODO(abilities) support structural types in general
pub typ: Symbol,
pub ability: Symbol,
}
#[derive(Debug, Default)]
pub struct Outcome {
mismatches: Vec<Mismatch>,
/// We defer these checks until the end of a solving phase.
/// NOTE: this vector is almost always empty!
must_implement_ability: Vec<MustImplementAbility>,
}
impl Outcome {
fn union(&mut self, other: Self) {
self.mismatches.extend(other.mismatches);
self.must_implement_ability
.extend(other.must_implement_ability);
}
}
#[inline(always)]
pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Unified {
let mut vars = Vec::new();
let mismatches = unify_pool(subs, &mut vars, var1, var2, mode);
let Outcome {
mismatches,
must_implement_ability,
} = unify_pool(subs, &mut vars, var1, var2, mode);
if mismatches.is_empty() {
Unified::Success(vars)
Unified::Success {
vars,
must_implement_ability,
}
} else {
let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) {
ErrorTypeContext::ExpandRanges
@ -136,7 +195,19 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable, mode: Mode) -> Uni
if !problems.is_empty() {
Unified::BadType(vars, problems.remove(0))
} else {
Unified::Failure(vars, type1, type2)
let do_not_implement_ability = mismatches
.into_iter()
.filter_map(|mismatch| match mismatch {
Mismatch::DoesNotImplementAbiity(var, ab) => {
let (err_type, _new_problems) =
subs.var_to_error_type_contextual(var, error_context);
Some((err_type, ab))
}
_ => None,
})
.collect();
Unified::Failure(vars, type1, type2, do_not_implement_ability)
}
}
}
@ -150,7 +221,7 @@ pub fn unify_pool(
mode: Mode,
) -> Outcome {
if subs.equivalent(var1, var2) {
Vec::new()
Outcome::default()
} else {
let ctx = Context {
first: var1,
@ -164,8 +235,10 @@ pub fn unify_pool(
}
}
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
if false {
#[cfg(debug_assertions)]
fn debug_print_unified_types(subs: &mut Subs, ctx: &Context, before_unified: bool) {
if std::env::var("ROC_PRINT_UNIFICATIONS").is_ok() {
let time = if before_unified { "START" } else { "END" };
// if true, print the types that are unified.
//
// NOTE: names are generated here (when creating an error type) and that modifies names
@ -181,8 +254,11 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
let content_1 = subs.get(ctx.first).content;
let content_2 = subs.get(ctx.second).content;
let mode = if ctx.mode.is_eq() { "~" } else { "+=" };
println!(
"{:?} {:?} {} {:?} {:?}",
eprintln!(
"{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}",
time,
ctx.first,
ctx.second,
ctx.first,
roc_types::subs::SubsFmtContent(&content_1, subs),
mode,
@ -190,8 +266,21 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
roc_types::subs::SubsFmtContent(&content_2, subs),
);
}
match &ctx.first_desc.content {
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, &ctx.second_desc.content),
}
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
#[cfg(debug_assertions)]
debug_print_unified_types(subs, &ctx, true);
let result = match &ctx.first_desc.content {
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content),
FlexAbleVar(opt_name, ability) => unify_flex(
subs,
&ctx,
opt_name,
Some(*ability),
&ctx.second_desc.content,
),
RecursionVar {
opt_name,
structure,
@ -203,7 +292,10 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
*structure,
&ctx.second_desc.content,
),
RigidVar(name) => unify_rigid(subs, &ctx, name, &ctx.second_desc.content),
RigidVar(name) => unify_rigid(subs, &ctx, name, None, &ctx.second_desc.content),
RigidAbleVar(name, ability) => {
unify_rigid(subs, &ctx, name, Some(*ability), &ctx.second_desc.content)
}
Structure(flat_type) => {
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
}
@ -215,7 +307,12 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
// Error propagates. Whatever we're comparing it to doesn't matter!
merge(subs, &ctx, Error)
}
}
};
#[cfg(debug_assertions)]
debug_print_unified_types(subs, &ctx, true);
result
}
#[inline(always)]
@ -238,7 +335,7 @@ fn unify_ranged_number(
}
&RangedNumber(other_real_var, other_range_vars) => {
let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode);
if outcome.is_empty() {
if outcome.mismatches.is_empty() {
check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode)
} else {
outcome
@ -246,9 +343,12 @@ fn unify_ranged_number(
// TODO: We should probably check that "range_vars" and "other_range_vars" intersect
}
Error => merge(subs, ctx, Error),
FlexAbleVar(..) | RigidAbleVar(..) => {
todo_abilities!("I don't think this can be reached yet")
}
};
if !outcome.is_empty() {
if !outcome.mismatches.is_empty() {
return outcome;
}
@ -269,11 +369,11 @@ fn check_valid_range(
let snapshot = subs.snapshot();
let old_pool = pool.clone();
let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX);
if outcome.is_empty() {
if outcome.mismatches.is_empty() {
// Okay, we matched some type in the range.
subs.rollback_to(snapshot);
*pool = old_pool;
return vec![];
return Outcome::default();
} else if it.peek().is_some() {
// We failed to match something in the range, but there are still things we can try.
subs.rollback_to(snapshot);
@ -283,7 +383,10 @@ fn check_valid_range(
}
}
return vec![Mismatch::TypeNotInRange];
Outcome {
mismatches: vec![Mismatch::TypeNotInRange],
..Outcome::default()
}
}
#[inline(always)]
@ -310,13 +413,19 @@ fn unify_alias(
unify_pool(subs, pool, real_var, *structure, ctx.mode)
}
RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
RigidAbleVar (_, ability) | FlexAbleVar(_, ability) if kind == AliasKind::Opaque && args.is_empty() => {
// Opaque type wins
let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind));
outcome.must_implement_ability.push(MustImplementAbility { typ: symbol, ability: *ability });
outcome
}
Alias(other_symbol, other_args, other_real_var, _)
// Opaques types are only equal if the opaque symbols are equal!
if !either_is_opaque || symbol == *other_symbol =>
{
if symbol == *other_symbol {
if args.len() == other_args.len() {
let mut problems = Vec::new();
let mut outcome = Outcome::default();
let it = args
.all_variables()
.into_iter()
@ -327,23 +436,23 @@ fn unify_alias(
for (l, r) in it {
let l_var = subs[l];
let r_var = subs[r];
problems.extend(unify_pool(subs, pool, l_var, r_var, ctx.mode));
outcome.union(unify_pool(subs, pool, l_var, r_var, ctx.mode));
}
if problems.is_empty() {
problems.extend(merge(subs, ctx, *other_content));
if outcome.mismatches.is_empty() {
outcome.union(merge(subs, ctx, *other_content));
}
let args_unification_had_changes = !subs.vars_since_snapshot(&args_unification_snapshot).is_empty();
subs.commit_snapshot(args_unification_snapshot);
if !args.is_empty() && args_unification_had_changes && problems.is_empty() {
if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() {
// We need to unify the real vars because unification of type variables
// may have made them larger, which then needs to be reflected in the `real_var`.
problems.extend(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode));
outcome.union(unify_pool(subs, pool, real_var, *other_real_var, ctx.mode));
}
problems
outcome
} else {
dbg!(args.len(), other_args.len());
mismatch!("{:?}", symbol)
@ -355,7 +464,7 @@ fn unify_alias(
Structure(_) if !either_is_opaque => unify_pool(subs, pool, real_var, ctx.second, ctx.mode),
RangedNumber(other_real_var, other_range_vars) if !either_is_opaque => {
let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode);
if outcome.is_empty() {
if outcome.mismatches.is_empty() {
check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode)
} else {
outcome
@ -448,13 +557,31 @@ fn unify_structure(
},
RangedNumber(other_real_var, other_range_vars) => {
let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode);
if outcome.is_empty() {
if outcome.mismatches.is_empty() {
check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode)
} else {
outcome
}
}
Error => merge(subs, ctx, Error),
FlexAbleVar(_, ability) => {
// TODO(abilities) support structural types in ability bounds
mismatch!(
%not_able, ctx.first, *ability,
"trying to unify {:?} with FlexAble {:?}",
&flat_type,
&other
)
}
RigidAbleVar(_, ability) => {
mismatch!(
%not_able, ctx.first, *ability,
"trying to unify {:?} with RigidAble {:?}",
&flat_type,
&other
)
}
}
}
@ -474,29 +601,29 @@ fn unify_record(
if separate.only_in_1.is_empty() {
if separate.only_in_2.is_empty() {
// these variable will be the empty record, but we must still unify them
let ext_problems = unify_pool(subs, pool, ext1, ext2, ctx.mode);
let ext_outcome = unify_pool(subs, pool, ext1, ext2, ctx.mode);
if !ext_problems.is_empty() {
return ext_problems;
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_problems =
let mut field_outcome =
unify_shared_fields(subs, pool, ctx, shared_fields, OtherFields::None, ext1);
field_problems.extend(ext_problems);
field_outcome.union(ext_outcome);
field_problems
field_outcome
} else {
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
let flat_type = FlatType::Record(only_in_2, ext2);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
if !ext_problems.is_empty() {
return ext_problems;
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_problems = unify_shared_fields(
let mut field_outcome = unify_shared_fields(
subs,
pool,
ctx,
@ -505,21 +632,21 @@ fn unify_record(
sub_record,
);
field_problems.extend(ext_problems);
field_outcome.union(ext_outcome);
field_problems
field_outcome
}
} else if separate.only_in_2.is_empty() {
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
let flat_type = FlatType::Record(only_in_1, ext1);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
if !ext_problems.is_empty() {
return ext_problems;
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_problems = unify_shared_fields(
let mut field_outcome = unify_shared_fields(
subs,
pool,
ctx,
@ -528,9 +655,9 @@ fn unify_record(
sub_record,
);
field_problems.extend(ext_problems);
field_outcome.union(ext_outcome);
field_problems
field_outcome
} else {
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
@ -544,24 +671,26 @@ fn unify_record(
let sub1 = fresh(subs, pool, ctx, Structure(flat_type1));
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
let rec1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode);
if !rec1_problems.is_empty() {
return rec1_problems;
let rec1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
if !rec1_outcome.mismatches.is_empty() {
return rec1_outcome;
}
let rec2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode);
if !rec2_problems.is_empty() {
return rec2_problems;
let rec2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
if !rec2_outcome.mismatches.is_empty() {
return rec2_outcome;
}
let mut field_problems =
let mut field_outcome =
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, ext);
field_problems.reserve(rec1_problems.len() + rec2_problems.len());
field_problems.extend(rec1_problems);
field_problems.extend(rec2_problems);
field_outcome
.mismatches
.reserve(rec1_outcome.mismatches.len() + rec2_outcome.mismatches.len());
field_outcome.union(rec1_outcome);
field_outcome.union(rec2_outcome);
field_problems
field_outcome
}
}
@ -584,7 +713,7 @@ fn unify_shared_fields(
let num_shared_fields = shared_fields.len();
for (name, (actual, expected)) in shared_fields {
let local_problems = unify_pool(
let local_outcome = unify_pool(
subs,
pool,
actual.into_inner(),
@ -592,7 +721,7 @@ fn unify_shared_fields(
ctx.mode,
);
if local_problems.is_empty() {
if local_outcome.mismatches.is_empty() {
use RecordField::*;
// Unification of optional fields
@ -856,18 +985,18 @@ fn unify_tag_union_new(
if separate.only_in_1.is_empty() {
if separate.only_in_2.is_empty() {
let ext_problems = if ctx.mode.is_eq() {
let ext_outcome = if ctx.mode.is_eq() {
unify_pool(subs, pool, ext1, ext2, ctx.mode)
} else {
// In a presence context, we don't care about ext2 being equal to ext1
vec![]
Outcome::default()
};
if !ext_problems.is_empty() {
return ext_problems;
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut tag_problems = unify_shared_tags_new(
let mut shared_tags_outcome = unify_shared_tags_new(
subs,
pool,
ctx,
@ -877,20 +1006,20 @@ fn unify_tag_union_new(
recursion_var,
);
tag_problems.extend(ext_problems);
shared_tags_outcome.union(ext_outcome);
tag_problems
shared_tags_outcome
} else {
let unique_tags2 = UnionTags::insert_slices_into_subs(subs, separate.only_in_2);
let flat_type = FlatType::TagUnion(unique_tags2, ext2);
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
let ext_problems = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
let ext_outcome = unify_pool(subs, pool, ext1, sub_record, ctx.mode);
if !ext_problems.is_empty() {
return ext_problems;
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut tag_problems = unify_shared_tags_new(
let mut shared_tags_outcome = unify_shared_tags_new(
subs,
pool,
ctx,
@ -900,9 +1029,9 @@ fn unify_tag_union_new(
recursion_var,
);
tag_problems.extend(ext_problems);
shared_tags_outcome.union(ext_outcome);
tag_problems
shared_tags_outcome
}
} else if separate.only_in_2.is_empty() {
let unique_tags1 = UnionTags::insert_slices_into_subs(subs, separate.only_in_1);
@ -911,10 +1040,10 @@ fn unify_tag_union_new(
// In a presence context, we don't care about ext2 being equal to tags1
if ctx.mode.is_eq() {
let ext_problems = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
let ext_outcome = unify_pool(subs, pool, sub_record, ext2, ctx.mode);
if !ext_problems.is_empty() {
return ext_problems;
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
}
@ -961,17 +1090,17 @@ fn unify_tag_union_new(
let snapshot = subs.snapshot();
let ext1_problems = unify_pool(subs, pool, ext1, sub2, ctx.mode);
if !ext1_problems.is_empty() {
let ext1_outcome = unify_pool(subs, pool, ext1, sub2, ctx.mode);
if !ext1_outcome.mismatches.is_empty() {
subs.rollback_to(snapshot);
return ext1_problems;
return ext1_outcome;
}
if ctx.mode.is_eq() {
let ext2_problems = unify_pool(subs, pool, sub1, ext2, ctx.mode);
if !ext2_problems.is_empty() {
let ext2_outcome = unify_pool(subs, pool, sub1, ext2, ctx.mode);
if !ext2_outcome.mismatches.is_empty() {
subs.rollback_to(snapshot);
return ext2_problems;
return ext2_outcome;
}
}
@ -1063,17 +1192,17 @@ fn unify_shared_tags_new(
maybe_mark_tag_union_recursive(subs, actual);
maybe_mark_tag_union_recursive(subs, expected);
let mut problems = Vec::new();
let mut outcome = Outcome::default();
problems.extend(unify_pool(subs, pool, actual, expected, ctx.mode));
outcome.union(unify_pool(subs, pool, actual, expected, ctx.mode));
// clearly, this is very suspicious: these variables have just been unified. And yet,
// not doing this leads to stack overflows
if let Rec::Right(_) = recursion_var {
if problems.is_empty() {
if outcome.mismatches.is_empty() {
matching_vars.push(expected);
}
} else if problems.is_empty() {
} else if outcome.mismatches.is_empty() {
matching_vars.push(actual);
}
}
@ -1215,39 +1344,43 @@ fn unify_flat_type(
debug_assert!(is_recursion_var(subs, *rec2));
let rec = Rec::Both(*rec1, *rec2);
let mut problems =
let mut outcome =
unify_tag_union_new(subs, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec);
problems.extend(unify_pool(subs, pool, *rec1, *rec2, ctx.mode));
outcome.union(unify_pool(subs, pool, *rec1, *rec2, ctx.mode));
problems
outcome
}
(Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => {
let problems = unify_zip_slices(subs, pool, *l_args, *r_args);
let mut outcome = unify_zip_slices(subs, pool, *l_args, *r_args);
if problems.is_empty() {
merge(subs, ctx, Structure(Apply(*r_symbol, *r_args)))
} else {
problems
if outcome.mismatches.is_empty() {
outcome.union(merge(subs, ctx, Structure(Apply(*r_symbol, *r_args))));
}
outcome
}
(Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret))
if l_args.len() == r_args.len() =>
{
let arg_problems = unify_zip_slices(subs, pool, *l_args, *r_args);
let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode);
let closure_problems = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode);
let arg_outcome = unify_zip_slices(subs, pool, *l_args, *r_args);
let ret_outcome = unify_pool(subs, pool, *l_ret, *r_ret, ctx.mode);
let closure_outcome = unify_pool(subs, pool, *l_closure, *r_closure, ctx.mode);
if arg_problems.is_empty() && closure_problems.is_empty() && ret_problems.is_empty() {
merge(subs, ctx, Structure(Func(*r_args, *r_closure, *r_ret)))
} else {
let mut problems = ret_problems;
let mut outcome = ret_outcome;
problems.extend(closure_problems);
problems.extend(arg_problems);
outcome.union(closure_outcome);
outcome.union(arg_outcome);
problems
if outcome.mismatches.is_empty() {
outcome.union(merge(
subs,
ctx,
Structure(Func(*r_args, *r_closure, *r_ret)),
));
}
outcome
}
(FunctionOrTagUnion(tag_name, tag_symbol, ext), Func(args, closure, ret)) => {
unify_function_or_tag_union_and_func(
@ -1282,12 +1415,12 @@ fn unify_flat_type(
let tag_name_2_ref = &subs[*tag_name_2];
if tag_name_1_ref == tag_name_2_ref {
let problems = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
if problems.is_empty() {
let outcome = unify_pool(subs, pool, *ext1, *ext2, ctx.mode);
if outcome.mismatches.is_empty() {
let content = *subs.get_content_without_compacting(ctx.second);
merge(subs, ctx, content)
} else {
problems
outcome
}
} else {
let tags1 = UnionTags::from_tag_name_index(*tag_name_1);
@ -1343,7 +1476,7 @@ fn unify_zip_slices(
left: SubsSlice<Variable>,
right: SubsSlice<Variable>,
) -> Outcome {
let mut problems = Vec::new();
let mut outcome = Outcome::default();
let it = left.into_iter().zip(right.into_iter());
@ -1351,10 +1484,10 @@ fn unify_zip_slices(
let l_var = subs[l_index];
let r_var = subs[r_index];
problems.extend(unify_pool(subs, pool, l_var, r_var, Mode::EQ));
outcome.union(unify_pool(subs, pool, l_var, r_var, Mode::EQ));
}
problems
outcome
}
#[inline(always)]
@ -1362,6 +1495,7 @@ fn unify_rigid(
subs: &mut Subs,
ctx: &Context,
name: &SubsIndex<Lowercase>,
opt_able_bound: Option<Symbol>,
other: &Content,
) -> Outcome {
match other {
@ -1369,16 +1503,76 @@ fn unify_rigid(
// If the other is flex, rigid wins!
merge(subs, ctx, RigidVar(*name))
}
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..) => {
if !ctx.mode.contains(Mode::RIGID_AS_FLEX) {
// Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same.
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
} else {
// We are treating rigid vars as flex vars; admit this
merge(subs, ctx, *other)
FlexAbleVar(_, other_ability) => {
match opt_able_bound {
Some(ability) => {
if ability == *other_ability {
// The ability bounds are the same, so rigid wins!
merge(subs, ctx, RigidAbleVar(*name, ability))
} else {
// Mismatch for now.
// TODO check ability hierarchies.
mismatch!(
%not_able, ctx.second, ability,
"RigidAble {:?} with ability {:?} not compatible with ability {:?}",
ctx.first,
ability,
other_ability
)
}
}
None => {
// Mismatch - Rigid can unify with FlexAble only when the Rigid has an ability
// bound as well, otherwise the user failed to correctly annotate the bound.
mismatch!(
%not_able, ctx.first, *other_ability,
"Rigid {:?} with FlexAble {:?}", ctx.first, other
)
}
}
}
RigidVar(_) | RecursionVar { .. } | Structure(_) | Alias(_, _, _, _) | RangedNumber(..)
if ctx.mode.contains(Mode::RIGID_AS_FLEX) =>
{
// Usually rigids can only unify with flex, but the mode indicates we are treating
// rigid vars as flex, so admit this.
match (opt_able_bound, other) {
(None, other) => merge(subs, ctx, *other),
(Some(ability), Alias(opaque_name, vars, _real_var, AliasKind::Opaque))
if vars.is_empty() =>
{
let mut output = merge(subs, ctx, *other);
let must_implement_ability = MustImplementAbility {
typ: *opaque_name,
ability,
};
output.must_implement_ability.push(must_implement_ability);
output
}
(Some(ability), other) => {
// For now, only allow opaque types with no type variables to implement abilities.
mismatch!(
%not_able, ctx.second, ability,
"RigidAble {:?} with non-opaque or opaque with type variables {:?}",
ctx.first,
&other
)
}
}
}
RigidVar(_)
| RigidAbleVar(..)
| RecursionVar { .. }
| Structure(_)
| Alias(..)
| RangedNumber(..) => {
// Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same.
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
}
Error => {
// Error propagates.
merge(subs, ctx, Error)
@ -1391,16 +1585,49 @@ fn unify_flex(
subs: &mut Subs,
ctx: &Context,
opt_name: &Option<SubsIndex<Lowercase>>,
opt_able_bound: Option<Symbol>,
other: &Content,
) -> Outcome {
match other {
FlexVar(None) => {
// If both are flex, and only left has a name, keep the name around.
merge(subs, ctx, FlexVar(*opt_name))
match opt_able_bound {
Some(ability) => merge(subs, ctx, FlexAbleVar(*opt_name, ability)),
None => merge(subs, ctx, FlexVar(*opt_name)),
}
}
FlexAbleVar(opt_other_name, other_ability) => {
// Prefer the right's name when possible.
let opt_name = (opt_other_name).or(*opt_name);
match opt_able_bound {
Some(ability) => {
if ability == *other_ability {
// The ability bounds are the same! Keep the name around if it exists.
merge(subs, ctx, FlexAbleVar(opt_name, ability))
} else {
// Ability names differ; mismatch for now.
// TODO check ability hierarchies.
mismatch!(
%not_able, ctx.second, ability,
"FlexAble {:?} with ability {:?} not compatible with ability {:?}",
ctx.first,
ability,
other_ability
)
}
}
None => {
// Right has an ability bound, but left might have the name. Combine them.
merge(subs, ctx, FlexAbleVar(opt_name, *other_ability))
}
}
}
FlexVar(Some(_))
| RigidVar(_)
| RigidAbleVar(_, _)
| RecursionVar { .. }
| Structure(_)
| Alias(_, _, _, _)
@ -1446,7 +1673,13 @@ fn unify_recursion(
// unify the structure variable with this Structure
unify_pool(subs, pool, structure, ctx.second, ctx.mode)
}
RigidVar(_) => mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other),
RigidVar(_) => {
mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other)
}
FlexAbleVar(..) | RigidAbleVar(..) => {
mismatch!("RecursionVar {:?} with able var {:?}", ctx.first, &other)
}
FlexVar(_) => merge(
subs,
@ -1492,7 +1725,7 @@ pub fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
subs.union(ctx.first, ctx.second, desc);
Vec::new()
Outcome::default()
}
fn register(subs: &mut Subs, desc: Descriptor, pool: &mut Pool) -> Variable {
@ -1543,7 +1776,7 @@ fn unify_function_or_tag_union_and_func(
let new_tag_union_var = fresh(subs, pool, ctx, content);
let mut problems = if left {
let mut outcome = if left {
unify_pool(subs, pool, new_tag_union_var, function_return, ctx.mode)
} else {
unify_pool(subs, pool, function_return, new_tag_union_var, ctx.mode)
@ -1567,16 +1800,16 @@ fn unify_function_or_tag_union_and_func(
pool,
);
let closure_problems = if left {
let closure_outcome = if left {
unify_pool(subs, pool, tag_lambda_set, function_lambda_set, ctx.mode)
} else {
unify_pool(subs, pool, function_lambda_set, tag_lambda_set, ctx.mode)
};
problems.extend(closure_problems);
outcome.union(closure_outcome);
}
if problems.is_empty() {
if outcome.mismatches.is_empty() {
let desc = if left {
subs.get(ctx.second)
} else {
@ -1586,5 +1819,5 @@ fn unify_function_or_tag_union_and_func(
subs.union(ctx.first, ctx.second, desc);
}
problems
outcome
}