Merge remote-tracking branch 'origin/trunk' into div-no-result

This commit is contained in:
Nikita Mounier 2022-04-11 11:45:06 +00:00
commit 8206f345c7
46 changed files with 1685 additions and 291 deletions

View file

@ -24,6 +24,7 @@ roc_gen_llvm = { path = "../gen_llvm", optional = true }
roc_gen_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../../reporting" }
roc_error_macros = { path = "../../error_macros" }
roc_std = { path = "../../roc_std", default-features = false }
bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1"

View file

@ -2,6 +2,7 @@ use crate::target::{arch_str, target_zig_str};
#[cfg(feature = "llvm")]
use libloading::{Error, Library};
use roc_builtins::bitcode;
use roc_error_macros::internal_error;
// #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel;
use std::collections::HashMap;
@ -20,10 +21,10 @@ fn zig_executable() -> String {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkType {
// These numbers correspond to the --lib flag; if it's present
// (e.g. is_present returns `1 as bool`), this will be 1 as well.
// These numbers correspond to the --lib and --no-link flags
Executable = 0,
Dylib = 1,
None = 2,
}
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
@ -835,6 +836,7 @@ fn link_linux(
output_path,
)
}
LinkType::None => internal_error!("link_linux should not be called with link type of none"),
};
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
@ -904,6 +906,7 @@ fn link_macos(
("-dylib", output_path)
}
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
};
let arch = match target.architecture {

View file

@ -628,6 +628,15 @@ toU16 : Int * -> U16
toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
## Convert a [Num] to a [F32]. If the given number can't be precisely represented in a [F32],
## there will be a loss of precision.
toF32 : Num * -> F32
## Convert a [Num] to a [F64]. If the given number can't be precisely represented in a [F64],
## there will be a loss of precision.
toF64 : Num * -> F64
## Convert any [Int] to a specifically-sized [Int], after checking validity.
## These are checked bitwise operations,
## so if the source number is outside the target range, then these will
@ -643,6 +652,9 @@ toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
## Convert a number to a [Str].
##
## This is the same as calling `Num.format {}` - so for more details on
@ -765,14 +777,6 @@ toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
## Convert a #Num to a #F32. If the given number can't be precisely represented in a #F32,
## there will be a loss of precision.
toF32 : Num * -> F32
## Convert a #Num to a #F64. If the given number can't be precisely represented in a #F64,
## there will be a loss of precision.
toF64 : Num * -> F64
## Convert a #Num to a #Dec. If the given number can't be precisely represented in a #Dec,
## there will be a loss of precision.
toDec : Num * -> Dec

View file

@ -1,6 +1,6 @@
interface Num
exposes
[
exposes
[
Num,
Int,
Float,
@ -147,8 +147,8 @@ interface Num
imports [ ]
Num range : [ @Num range ]
Int range : Num (Integer range)
Float range : Num (FloatingPoint range)
Int range : Num (Integer range)
Float range : Num (FloatingPoint range)
Signed128 : [ @Signed128 ]
Signed64 : [ @Signed64 ]
@ -170,7 +170,7 @@ I128 : Num (Integer Signed128)
I64 : Num (Integer Signed64)
I32 : Num (Integer Signed32)
I16 : Num (Integer Signed16)
I8 : Int Signed8
I8 : Int Signed8
U128 : Num (Integer Unsigned128)
U64 : Num (Integer Unsigned64)
@ -190,7 +190,7 @@ F64 : Num (FloatingPoint Binary64)
F32 : Num (FloatingPoint Binary32)
Dec : Num (FloatingPoint Decimal)
# ------- Functions
# ------- Functions
toStr : Num * -> Str
intCast : Int a -> Int b
@ -342,6 +342,9 @@ toU32 : Int * -> U32
toU64 : Int * -> U64
toU128 : Int * -> U128
toF32 : Num * -> F32
toF64 : Num * -> F64
toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
@ -352,3 +355,5 @@ toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
toF64Checked : Num * -> Result F64 [ OutOfBounds ]*

View file

@ -629,7 +629,35 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::NUM_TO_NAT_CHECKED,
vec![int_type(flex(TVAR1))],
Box::new(result_type(nat_type(), out_of_bounds)),
Box::new(result_type(nat_type(), out_of_bounds.clone())),
);
// toF32 : Num * -> F32
add_top_level_function_type!(
Symbol::NUM_TO_F32,
vec![num_type(flex(TVAR1))],
Box::new(f32_type()),
);
// toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_F32_CHECKED,
vec![num_type(flex(TVAR1))],
Box::new(result_type(f32_type(), out_of_bounds.clone())),
);
// toF64 : Num * -> F64
add_top_level_function_type!(
Symbol::NUM_TO_F64,
vec![num_type(flex(TVAR1))],
Box::new(f64_type()),
);
// toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
add_top_level_function_type!(
Symbol::NUM_TO_F64_CHECKED,
vec![num_type(flex(TVAR1))],
Box::new(result_type(f64_type(), out_of_bounds)),
);
// toStr : Num a -> Str

View file

@ -0,0 +1,74 @@
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol;
use roc_types::types::Type;
use crate::annotation::HasClause;
/// 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>,
}
/// 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)]
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)>,
}
impl AbilitiesStore {
pub fn register_ability(
&mut self,
ability: Symbol,
members: Vec<(Symbol, Type, Vec<HasClause>)>,
) {
let mut members_vec = Vec::with_capacity(members.len());
for (member, signature, bound_has_clauses) in members.into_iter() {
members_vec.push(member);
let old_member = self.ability_members.insert(
member,
AbilityMemberData {
parent_ability: ability,
signature,
bound_has_clauses,
},
);
debug_assert!(old_member.is_none(), "Replacing existing member definition");
}
let old_ability = self.members_of_ability.insert(ability, members_vec);
debug_assert!(
old_ability.is_none(),
"Replacing existing ability definition"
);
}
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");
}
pub fn is_ability_member_name(&self, name: Symbol) -> bool {
self.ability_members.contains_key(&name)
}
}

View file

@ -1,10 +1,10 @@
use crate::env::Env;
use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::todo_abilities;
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
use roc_problem::can::ShadowKind;
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{
@ -104,6 +104,10 @@ impl IntroducedVariables {
.find(|nv| nv.variable == var)
.map(|nv| &nv.name)
}
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<&NamedVariable> {
self.named.iter().find(|nv| &nv.name == name)
}
}
fn malformed(env: &mut Env, region: Region, name: &str) {
@ -143,6 +147,78 @@ 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,
annotation: &TypeAnnotation,
region: Region,
var_store: &mut VarStore,
abilities_in_scope: &[Symbol],
) -> (Annotation, Vec<Loc<HasClause>>) {
let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default();
let mut aliases = SendMap::default();
let (annotation, region, clauses) = match annotation {
TypeAnnotation::Where(annotation, clauses) => {
let mut can_clauses = Vec::with_capacity(clauses.len());
for clause in clauses.iter() {
match canonicalize_has_clause(
env,
scope,
var_store,
&mut introduced_variables,
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,
)
}
};
}
(&annotation.value, annotation.region, can_clauses)
}
annot => (annot, region, vec![]),
};
let typ = can_annotation_help(
env,
annotation,
region,
scope,
var_store,
&mut introduced_variables,
&mut aliases,
&mut references,
);
let annot = Annotation {
typ,
introduced_variables,
references,
aliases,
};
(annot, clauses)
}
fn make_apply_symbol(
env: &mut Env,
region: Region,
@ -271,7 +347,13 @@ pub fn find_type_def_symbols(
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
stack.push(inner);
}
Where(..) => todo_abilities!(),
Where(annotation, clauses) => {
stack.push(&annotation.value);
for has_clause in clauses.iter() {
stack.push(&has_clause.value.ability.value);
}
}
Inferred | Wildcard | Malformed(_) => {}
}
}
@ -449,9 +531,10 @@ fn can_annotation_help(
Err((original_region, shadow, _new_symbol)) => {
let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
env.problem(roc_problem::can::Problem::Shadowing {
original_region,
shadow,
kind: ShadowKind::Variable,
});
return Type::Erroneous(problem);
@ -685,7 +768,17 @@ fn can_annotation_help(
Type::Variable(var)
}
Where(..) => todo_abilities!(),
Where(_annotation, clauses) => {
debug_assert!(!clauses.is_empty());
// Has clauses are allowed only on the top level of an ability member signature (for
// now), which we handle elsewhere.
env.problem(roc_problem::can::Problem::IllegalHasClause {
region: Region::across_all(clauses.iter().map(|clause| &clause.region)),
});
Type::Erroneous(Problem::CanonicalizationProblem)
}
Malformed(string) => {
malformed(env, region, string);
@ -698,6 +791,72 @@ fn can_annotation_help(
}
}
fn canonicalize_has_clause(
env: &mut Env,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
clause: &Loc<roc_parse::ast::HasClause<'_>>,
abilities_in_scope: &[Symbol],
references: &mut MutSet<Symbol>,
) -> Result<HasClause, Type> {
let Loc {
region,
value: roc_parse::ast::HasClause { var, ability },
} = clause;
let region = *region;
let var_name = var.extract_spaces().item;
debug_assert!(
var_name.starts_with(char::is_lowercase),
"Vars should have been parsed as lowercase"
);
let var_name = Lowercase::from(var_name);
let ability = match ability.value {
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?;
if !abilities_in_scope.contains(&symbol) {
let region = ability.region;
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
}
symbol
}
_ => {
let region = ability.region;
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
}
};
references.insert(ability);
if let Some(shadowing) = introduced_variables.named_var_by_name(&var_name) {
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,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
});
return Err(Type::Erroneous(Problem::Shadowed(
shadowing.first_seen,
shadow,
)));
}
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), Loc::at(region, var));
Ok(HasClause {
var_name,
var,
ability,
})
}
#[allow(clippy::too_many_arguments)]
fn can_extension_type<'a>(
env: &mut Env,

View file

@ -269,6 +269,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_TO_U128_CHECKED => num_to_u128_checked,
NUM_TO_NAT => num_to_nat,
NUM_TO_NAT_CHECKED => num_to_nat_checked,
NUM_TO_F32 => num_to_f32,
NUM_TO_F32_CHECKED => num_to_f32_checked,
NUM_TO_F64 => num_to_f64,
NUM_TO_F64_CHECKED => num_to_f64_checked,
NUM_TO_STR => num_to_str,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
@ -485,6 +489,18 @@ fn num_to_nat(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
}
// Num.toF32 : Num * -> F32
fn num_to_f32(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to NumToFloatCast
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
}
// Num.toF64 : Num * -> F64
fn num_to_f64(symbol: Symbol, var_store: &mut VarStore) -> Def {
// Defer to NumToFloatCast
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
}
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh();
@ -592,6 +608,8 @@ num_to_checked! {
num_to_u64_checked
num_to_u128_checked
num_to_nat_checked
num_to_f32_checked
num_to_f64_checked
}
// Num.toStr : Num a -> Str

View file

@ -1,21 +1,25 @@
use crate::abilities::AbilitiesStore;
use crate::annotation::canonicalize_annotation;
use crate::annotation::canonicalize_annotation_with_possible_clauses;
use crate::annotation::IntroducedVariables;
use crate::env::Env;
use crate::expr::ClosureData;
use crate::expr::Expr::{self, *};
use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive};
use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern};
use crate::procedure::References;
use crate::scope::create_alias;
use crate::scope::Scope;
use roc_collections::all::ImSet;
use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::todo_abilities;
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast;
use roc_parse::ast::AbilityMember;
use roc_parse::ast::ExtractSpaces;
use roc_parse::ast::TypeHeader;
use roc_parse::pattern::PatternType;
use roc_problem::can::ShadowKind;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
@ -86,10 +90,19 @@ enum PendingTypeDef<'a> {
kind: AliasKind,
},
Ability {
name: Loc<Symbol>,
members: &'a [ast::AbilityMember<'a>],
},
/// An invalid alias, that is ignored in the rest of the pipeline
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
/// with an incorrect pattern
InvalidAlias { kind: AliasKind },
/// An invalid ability, that is ignored in the rest of the pipeline.
/// E.g. a shadowed ability, or with a bad definition.
InvalidAbility,
}
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
@ -239,9 +252,19 @@ pub fn canonicalize_defs<'a>(
env.home.register_debug_idents(&env.ident_ids);
}
let mut aliases = SendMap::default();
enum TypeDef<'a> {
AliasLike(
Loc<Symbol>,
Vec<Loc<Lowercase>>,
&'a Loc<ast::TypeAnnotation<'a>>,
AliasKind,
),
Ability(Loc<Symbol>, &'a [AbilityMember<'a>]),
}
let mut type_defs = MutMap::default();
let mut abilities_in_scope = Vec::new();
let mut alias_defs = MutMap::default();
let mut referenced_type_symbols = MutMap::default();
for pending_def in pending_type_defs.into_iter() {
@ -260,93 +283,137 @@ pub fn canonicalize_defs<'a>(
referenced_type_symbols.insert(name.value, referenced_symbols);
alias_defs.insert(name.value, (name, vars, ann, kind));
type_defs.insert(name.value, TypeDef::AliasLike(name, vars, ann, kind));
}
PendingTypeDef::Ability { name, members } => {
let mut referenced_symbols = Vec::with_capacity(2);
for member in members.iter() {
// Add the referenced type symbols of each member function. We need to make
// sure those are processed first before we resolve the whole ability
// definition.
referenced_symbols.extend(crate::annotation::find_type_def_symbols(
env.home,
&mut env.ident_ids,
&member.typ.value,
));
}
referenced_type_symbols.insert(name.value, referenced_symbols);
type_defs.insert(name.value, TypeDef::Ability(name, members));
abilities_in_scope.push(name.value);
}
PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } => { /* ignore */
}
PendingTypeDef::InvalidAlias { .. } => { /* ignore */ }
}
}
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
let mut aliases = SendMap::default();
let mut abilities = MutMap::default();
for type_name in sorted {
let (name, vars, ann, kind) = alias_defs.remove(&type_name).unwrap();
match type_defs.remove(&type_name).unwrap() {
TypeDef::AliasLike(name, vars, ann, kind) => {
let symbol = name.value;
let can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
let symbol = name.value;
let can_ann = canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
// Does this alias reference any abilities? For now, we don't permit that.
let ability_references = can_ann
.references
.iter()
.filter_map(|&ty_ref| abilities_in_scope.iter().find(|&&name| name == ty_ref))
.collect::<Vec<_>>();
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
let mut named = can_ann.introduced_variables.named;
for loc_lowercase in vars.iter() {
match named.iter().position(|nv| nv.name == loc_lowercase.value) {
Some(index) => {
// This is a valid lowercase rigid var for the type def.
let named_variable = named.swap_remove(index);
can_vars.push(Loc {
value: (named_variable.name, named_variable.variable),
region: loc_lowercase.region,
if let Some(one_ability_ref) = ability_references.first() {
env.problem(Problem::AliasUsesAbility {
loc_name: name,
ability: **one_ability_ref,
});
}
None => {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
let mut named = can_ann.introduced_variables.named;
for loc_lowercase in vars.iter() {
match named.iter().position(|nv| nv.name == loc_lowercase.value) {
Some(index) => {
// This is a valid lowercase rigid var for the type def.
let named_variable = named.swap_remove(index);
can_vars.push(Loc {
value: (named_variable.name, named_variable.variable),
region: loc_lowercase.region,
});
}
None => {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
}
if is_phantom {
// Bail out
continue;
}
let IntroducedVariables {
wildcards,
inferred,
..
} = can_ann.introduced_variables;
let num_unbound = named.len() + wildcards.len() + inferred.len();
if num_unbound > 0 {
let one_occurrence = named
.iter()
.map(|nv| Loc::at(nv.first_seen, nv.variable))
.chain(wildcards)
.chain(inferred)
.next()
.unwrap()
.region;
env.problems.push(Problem::UnboundTypeVariable {
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
num_unbound,
one_occurrence,
kind,
});
// Bail out
continue;
}
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
}
TypeDef::Ability(name, members) => {
// For now we enforce that aliases cannot reference abilities, so let's wait to
// resolve ability definitions until aliases are resolved and in scope below.
abilities.insert(name.value, (name, members));
}
}
if is_phantom {
// Bail out
continue;
}
let IntroducedVariables {
wildcards,
inferred,
..
} = can_ann.introduced_variables;
let num_unbound = named.len() + wildcards.len() + inferred.len();
if num_unbound > 0 {
let one_occurrence = named
.iter()
.map(|nv| Loc::at(nv.first_seen, nv.variable))
.chain(wildcards)
.chain(inferred)
.next()
.unwrap()
.region;
env.problems.push(Problem::UnboundTypeVariable {
typ: symbol,
num_unbound,
one_occurrence,
kind,
});
// Bail out
continue;
}
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
}
// Now that we know the alias dependency graph, we can try to insert recursion variables
@ -362,6 +429,95 @@ 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(
env,
&mut scope,
&member.typ.value,
member.typ.region,
var_store,
&abilities_in_scope,
);
// Record all the annotation's references in output.references.lookups
for symbol in member_annot.references {
output.references.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
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,
) {
Ok(sym) => sym,
Err((original_region, shadow, _new_symbol)) => {
env.problem(roc_problem::can::Problem::Shadowing {
original_region,
shadow,
kind: ShadowKind::Variable,
});
// Pretend the member isn't a part of the ability
continue;
}
};
// 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);
let mut bad_has_clauses = false;
if variables_bound_to_ability.is_empty() {
// There are no variables bound to the parent ability - then this member doesn't
// need to be a part of the ability.
env.problem(Problem::AbilityMemberMissingHasClause {
member: member_sym,
ability: loc_ability_name.value,
region: member.name.region,
});
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() {
env.problem(Problem::AbilityMemberBindsExternalAbility {
member: member_sym,
ability: loc_ability_name.value,
region: bad_clause.region,
});
}
bad_has_clauses = true;
}
if bad_has_clauses {
// Pretend the member isn't a part of the ability
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));
}
// Store what symbols a type must define implementations for to have this ability.
abilities_store.register_ability(loc_ability_name.value, can_members);
}
// Now that we have the scope completely assembled, and shadowing resolved,
// we're ready to canonicalize any body exprs.
@ -370,7 +526,14 @@ 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, pattern_type) {
match to_pending_value_def(
env,
var_store,
loc_def.value,
&mut scope,
&abilities_store,
pattern_type,
) {
None => { /* skip */ }
Some((new_output, pending_def)) => {
// store the top-level defs, used to ensure that closures won't capture them
@ -857,6 +1020,13 @@ fn pattern_to_vars_by_symbol(
vars_by_symbol.insert(*symbol, expr_var);
}
AbilityMemberSpecialization {
ident,
specializes: _,
} => {
vars_by_symbol.insert(*ident, expr_var);
}
AppliedTag { arguments, .. } => {
for (var, nested) in arguments {
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
@ -981,6 +1151,7 @@ fn canonicalize_pending_value_def<'a>(
Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
original_region: *region,
shadow: loc_ident.clone(),
kind: ShadowKind::Variable,
},
_ => RuntimeError::NoImplementation,
};
@ -1481,10 +1652,10 @@ fn to_pending_type_def<'a>(
header: TypeHeader { name, vars },
typ: ann,
} => {
let kind = if matches!(def, Alias { .. }) {
AliasKind::Structural
let (kind, shadow_kind) = if matches!(def, Alias { .. }) {
(AliasKind::Structural, ShadowKind::Alias)
} else {
AliasKind::Opaque
(AliasKind::Opaque, ShadowKind::Opaque)
};
let region = Region::span_across(&name.region, &ann.region);
@ -1541,9 +1712,10 @@ fn to_pending_type_def<'a>(
}
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
env.problem(Problem::ShadowingInAnnotation {
env.problem(Problem::Shadowing {
original_region,
shadow: loc_shadowed_symbol,
kind: shadow_kind,
});
Some((Output::default(), PendingTypeDef::InvalidAlias { kind }))
@ -1551,40 +1723,56 @@ fn to_pending_type_def<'a>(
}
}
Ability { .. } => todo_abilities!(),
Ability {
header: TypeHeader { name, vars },
members,
loc_has: _,
} => {
let name = match scope.introduce_without_shadow_symbol(
name.value.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
name.region,
) {
Ok(symbol) => Loc::at(name.region, symbol),
Err((original_region, shadowed_symbol)) => {
env.problem(Problem::Shadowing {
original_region,
shadow: shadowed_symbol,
kind: ShadowKind::Ability,
});
return Some((Output::default(), PendingTypeDef::InvalidAbility));
}
};
if !vars.is_empty() {
// Disallow ability type arguments, at least for now.
let variables_region = Region::across_all(vars.iter().map(|v| &v.region));
env.problem(Problem::AbilityHasTypeVariables {
name: name.value,
variables_region,
});
return Some((Output::default(), PendingTypeDef::InvalidAbility));
}
let pending_ability = PendingTypeDef::Ability {
name,
// We'll handle adding the member symbols later on when we do all value defs.
members,
};
Some((Output::default(), pending_ability))
}
}
}
fn pending_typed_body<'a>(
env: &mut Env<'a>,
loc_pattern: &'a Loc<ast::Pattern<'a>>,
loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
loc_expr: &'a Loc<ast::Expr<'a>>,
var_store: &mut VarStore,
scope: &mut Scope,
pattern_type: PatternType,
) -> (Output, PendingValueDef<'a>) {
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_pattern(
env,
var_store,
scope,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
(
output,
PendingValueDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr),
)
}
fn to_pending_value_def<'a>(
env: &mut Env<'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::*;
@ -1592,10 +1780,11 @@ fn to_pending_value_def<'a>(
match def {
Annotation(loc_pattern, loc_ann) => {
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_pattern(
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
abilities_store,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
@ -1608,10 +1797,11 @@ fn to_pending_value_def<'a>(
}
Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_pattern(
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
abilities_store,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
@ -1636,14 +1826,21 @@ fn to_pending_value_def<'a>(
//
// { x, y } : { x : Int, y ? Bool }*
// { x, y ? False } = rec
Some(pending_typed_body(
//
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
body_pattern,
ann_type,
body_expr,
var_store,
scope,
abilities_store,
pattern_type,
&body_pattern.value,
body_pattern.region,
);
Some((
output,
PendingValueDef::TypedBody(body_pattern, loc_can_pattern, ann_type, body_expr),
))
} else {
// the pattern of the annotation does not match the pattern of the body direc

View file

@ -161,7 +161,6 @@ pub enum Expr {
variant_var: Variable,
ext_var: Variable,
name: TagName,
arguments: Vec<(Variable, Loc<Expr>)>,
},
/// A wrapping of an opaque type, like `$Age 21`
@ -813,7 +812,6 @@ pub fn canonicalize_expr<'a>(
(
ZeroArgumentTag {
name: TagName::Global((*tag).into()),
arguments: vec![],
variant_var,
closure_name: symbol,
ext_var,
@ -831,7 +829,6 @@ pub fn canonicalize_expr<'a>(
(
ZeroArgumentTag {
name: TagName::Private(symbol),
arguments: vec![],
variant_var,
ext_var,
closure_name: lambda_set_symbol,
@ -1560,15 +1557,13 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
variant_var,
ext_var,
name,
arguments,
} => {
todo!(
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}",
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}",
closure_name,
variant_var,
ext_var,
name,
arguments
);
}

View file

@ -1,6 +1,7 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod abilities;
pub mod annotation;
pub mod builtins;
pub mod constraint;

View file

@ -589,7 +589,8 @@ fn fix_values_captured_in_closure_pattern(
| Shadowed(..)
| MalformedPattern(_, _)
| UnsupportedPattern(_)
| OpaqueNotInScope(..) => (),
| OpaqueNotInScope(..)
| AbilityMemberSpecialization { .. } => (),
}
}
@ -646,6 +647,7 @@ fn fix_values_captured_in_closure_expr(
| Var(_)
| EmptyRecord
| RuntimeError(_)
| ZeroArgumentTag { .. }
| Accessor { .. } => {}
List { loc_elems, .. } => {
@ -712,7 +714,7 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
Tag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}

View file

@ -1,3 +1,4 @@
use crate::abilities::AbilitiesStore;
use crate::annotation::freshen_opaque_def;
use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
@ -10,7 +11,7 @@ use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType;
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{LambdaSet, Type};
@ -62,6 +63,17 @@ pub enum Pattern {
SingleQuote(char),
Underscore,
/// An identifier that marks a specialization of an ability member.
/// For example, given an ability member definition `hash : a -> U64 | a has Hash`,
/// there may be the specialization `hash : Bool -> U64`. In this case we generate a
/// new symbol for the specialized "hash" identifier.
AbilityMemberSpecialization {
/// The symbol for this specialization.
ident: Symbol,
/// The ability name being specialized.
specializes: Symbol,
},
// Runtime Exceptions
Shadowed(Region, Loc<Ident>, Symbol),
OpaqueNotInScope(Loc<Ident>),
@ -101,6 +113,11 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
symbols.push(*symbol);
}
AbilityMemberSpecialization { ident, specializes } => {
symbols.push(*ident);
symbols.push(*specializes);
}
AppliedTag { arguments, .. } => {
for (_, nested) in arguments {
symbols_from_pattern_help(&nested.value, symbols);
@ -136,6 +153,56 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
}
}
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,
) -> (Output, Loc<Pattern>) {
use roc_parse::ast::Pattern::*;
let mut output = Output::default();
match pattern {
// Identifiers that shadow ability members may appear (and may only appear) at the header of a def.
Identifier(name) => match scope.introduce_or_shadow_ability_member(
(*name).into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
abilities_store,
) {
Ok((symbol, shadowing_ability_member)) => {
output.references.bound_symbols.insert(symbol);
let can_pattern = match shadowing_ability_member {
// A fresh identifier.
None => Pattern::Identifier(symbol),
// Likely a specialization of an ability.
Some(ability_member_name) => Pattern::AbilityMemberSpecialization {
ident: symbol,
specializes: ability_member_name,
},
};
(output, Loc::at(region, can_pattern))
}
Err((original_region, shadow, new_symbol)) => {
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
output.references.bound_symbols.insert(new_symbol);
let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol);
(output, Loc::at(region, can_pattern))
}
},
_ => canonicalize_pattern(env, var_store, scope, pattern_type, pattern, region),
}
}
pub fn canonicalize_pattern<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
@ -164,6 +231,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
output.references.bound_symbols.insert(new_symbol);
@ -412,6 +480,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// No matter what the other patterns
@ -484,6 +553,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// No matter what the other patterns
@ -594,7 +664,12 @@ fn add_bindings_from_patterns(
use Pattern::*;
match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => {
Identifier(symbol)
| Shadowed(_, _, symbol)
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
answer.push((*symbol, *region));
}
AppliedTag {

View file

@ -6,6 +6,8 @@ use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, AliasKind, Type};
use crate::abilities::AbilitiesStore;
#[derive(Clone, Debug, PartialEq)]
pub struct Scope {
/// All the identifiers in scope, mapped to were they were defined and
@ -19,6 +21,9 @@ pub struct Scope {
/// The type aliases currently in scope
pub aliases: SendMap<Symbol, Alias>,
/// The abilities currently in scope, and their implementors.
pub abilities: SendMap<Symbol, Region>,
/// The current module being processed. This will be used to turn
/// unqualified idents into Symbols.
home: ModuleId,
@ -62,6 +67,8 @@ impl Scope {
idents: Symbol::default_in_scope(),
symbols: SendMap::default(),
aliases,
// TODO(abilities): default abilities in scope
abilities: SendMap::default(),
}
}
@ -176,6 +183,11 @@ impl Scope {
///
/// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name.
///
/// If this ident shadows an existing one, a new ident is allocated for the shadow. This is
/// done so that all identifiers have unique symbols, which is important in particular when
/// we generate code for value identifiers.
/// If this behavior is undesirable, use [`Self::introduce_without_shadow_symbol`].
pub fn introduce(
&mut self,
ident: Ident,
@ -198,25 +210,98 @@ impl Scope {
Err((original_region, shadow, symbol))
}
None => {
// If this IdentId was already added previously
// when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone()),
None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
}
}
/// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol.
pub fn introduce_without_shadow_symbol(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Result<Symbol, (Region, Loc<Ident>)> {
match self.idents.get(&ident) {
Some(&(_, original_region)) => {
let shadow = Loc {
value: ident.clone(),
region,
};
Err((original_region, shadow))
}
None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
}
}
let symbol = Symbol::new(self.home, ident_id);
/// Like [Self::introduce], but handles the case of when an ident matches an ability member
/// name. In such cases a new symbol is created for the ident (since it's expected to be a
/// specialization of the ability member), but the ident is not added to the ident->symbol map.
///
/// If the ident does not match an ability name, the behavior of this function is exactly that
/// of `introduce`.
#[allow(clippy::type_complexity)]
pub fn introduce_or_shadow_ability_member(
&mut self,
ident: Ident,
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)) => {
let shadow_ident_id = all_ident_ids.add(ident.clone());
let shadow_symbol = Symbol::new(self.home, shadow_ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
self.symbols.insert(shadow_symbol, region);
Ok(symbol)
if abilities_store.is_ability_member_name(original_symbol) {
// Add a symbol for the shadow, but don't re-associate the member name.
Ok((shadow_symbol, Some(original_symbol)))
} else {
// This is an illegal shadow.
let shadow = Loc {
value: ident.clone(),
region,
};
self.idents.insert(ident, (shadow_symbol, region));
Err((original_region, shadow, shadow_symbol))
}
}
None => {
let new_symbol =
self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region);
Ok((new_symbol, None))
}
}
}
fn commit_introduction(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Symbol {
// If this IdentId was already added previously
// when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone()),
};
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
symbol
}
/// Ignore an identifier.
///
/// Used for record guards like { x: Just _ }

View file

@ -881,7 +881,9 @@ pub fn constrain_expr(
name,
arguments,
} => {
let mut vars = Vec::with_capacity(arguments.len());
// +2 because we push all the arguments, plus variant_var and ext_var
let num_vars = arguments.len() + 2;
let mut vars = Vec::with_capacity(num_vars);
let mut types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len());
@ -923,27 +925,8 @@ pub fn constrain_expr(
variant_var,
ext_var,
name,
arguments,
closure_name,
} => {
let mut vars = Vec::with_capacity(arguments.len());
let mut types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len());
for (var, loc_expr) in arguments {
let arg_con = constrain_expr(
constraints,
env,
loc_expr.region,
&loc_expr.value,
Expected::NoExpectation(Type::Variable(*var)),
);
arg_cons.push(arg_con);
vars.push(*var);
types.push(Type::Variable(*var));
}
let union_con = constraints.equal_types_with_storage(
Type::FunctionOrTagUnion(
name.clone(),
@ -953,19 +936,14 @@ pub fn constrain_expr(
expected.clone(),
Category::TagApply {
tag_name: name.clone(),
args_count: arguments.len(),
args_count: 0,
},
region,
*variant_var,
);
vars.push(*variant_var);
vars.push(*ext_var);
arg_cons.push(union_con);
constraints.exists_many(vars, arg_cons)
constraints.exists_many([*variant_var, *ext_var], [union_con])
}
OpaqueRef {
opaque_var,
name,

View file

@ -50,7 +50,13 @@ fn headers_from_annotation_help(
headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool {
match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => {
Identifier(symbol)
| Shadowed(_, _, symbol)
// TODO(abilities): handle linking the member def to the specialization ident
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*symbol, typ);
true
@ -182,7 +188,12 @@ pub fn constrain_pattern(
// Erroneous patterns don't add any constraints.
}
Identifier(symbol) | Shadowed(_, _, symbol) => {
Identifier(symbol) | Shadowed(_, _, symbol)
// TODO(abilities): handle linking the member def to the specialization ident
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
if could_be_a_tag_union(expected.get_type_ref()) {
state
.constraints

View file

@ -5865,6 +5865,48 @@ fn run_low_level<'a, 'ctx, 'env>(
.build_int_cast_sign_flag(arg, to, to_signed, "inc_cast")
.into()
}
NumToFloatCast => {
debug_assert_eq!(args.len(), 1);
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
match arg_layout {
Layout::Builtin(Builtin::Int(width)) => {
// Converting from int to float
let int_val = arg.into_int_value();
let dest = basic_type_from_layout(env, layout).into_float_type();
if width.is_signed() {
env.builder
.build_signed_int_to_float(int_val, dest, "signed_int_to_float")
.into()
} else {
env.builder
.build_unsigned_int_to_float(int_val, dest, "unsigned_int_to_float")
.into()
}
}
Layout::Builtin(Builtin::Float(_)) => {
// Converting from float to float - e.g. F64 to F32, or vice versa
let dest = basic_type_from_layout(env, layout).into_float_type();
env.builder
.build_float_cast(arg.into_float_value(), dest, "cast_float_to_float")
.into()
}
Layout::Builtin(Builtin::Decimal) => {
todo!("Support converting Dec values to floats.");
}
other => {
unreachable!("Tried to do a float cast to non-float layout {:?}", other);
}
}
}
NumToFloatChecked => {
// NOTE: There's a NumToIntChecked implementation above,
// which could be useful to look at when implementing this.
todo!("implement checked float conversion");
}
Eq => {
debug_assert_eq!(args.len(), 2);

View file

@ -677,9 +677,15 @@ impl<'a> LowLevelCall<'a> {
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
}
}
NumToFloatCast => {
todo!("implement toF32 and toF64");
}
NumToIntChecked => {
todo!()
}
NumToFloatChecked => {
todo!("implement toF32Checked and toF64Checked");
}
And => {
self.load_args(backend);
backend.code_builder.i32_and();

View file

@ -111,7 +111,9 @@ pub enum LowLevel {
NumShiftRightBy,
NumShiftRightZfBy,
NumIntCast,
NumToFloatCast,
NumToIntChecked,
NumToFloatChecked,
NumToStr,
Eq,
NotEq,

View file

@ -1115,7 +1115,6 @@ define_builtins! {
32 STR_TO_I16: "toI16"
33 STR_TO_U8: "toU8"
34 STR_TO_I8: "toI8"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias

View file

@ -1001,7 +1001,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos
| NumAsin | NumIntCast | NumToIntChecked => arena.alloc_slice_copy(&[irrelevant]),
| NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => {
arena.alloc_slice_copy(&[irrelevant])
}
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),

View file

@ -13,7 +13,7 @@ use roc_exhaustive::{Ctor, Guard, RenderAs, TagId};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
use roc_problem::can::{RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_std::RocDec;
use roc_target::TargetInfo;
@ -2037,6 +2037,7 @@ fn pattern_to_when<'a>(
let error = roc_problem::can::RuntimeError::Shadowing {
original_region: *region,
shadow: loc_ident.clone(),
kind: ShadowKind::Variable,
};
(*new_symbol, Loc::at_zero(RuntimeError(error)))
}
@ -2088,6 +2089,13 @@ fn pattern_to_when<'a>(
// They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
}
AbilityMemberSpecialization { .. } => {
unreachable!(
"Ability member specialization {:?} should never appear in a when!",
pattern.value
)
}
}
}
@ -3053,7 +3061,7 @@ fn specialize_naked_symbol<'a>(
let opt_fn_var = Some(variable);
// if this is a function symbol, ensure that it's properly specialized!
reuse_function_symbol(
specialize_symbol(
env,
procs,
layout_cache,
@ -3432,7 +3440,6 @@ pub fn with_hole<'a>(
ZeroArgumentTag {
variant_var,
name: tag_name,
arguments: args,
ext_var,
closure_name,
} => {
@ -3466,7 +3473,7 @@ pub fn with_hole<'a>(
tag_name,
procs,
layout_cache,
args,
std::vec::Vec::new(),
arena,
)
}
@ -3558,7 +3565,7 @@ pub fn with_hole<'a>(
// this symbol is already defined; nothing to do
}
Field::Function(symbol, variable) => {
stmt = reuse_function_symbol(
stmt = specialize_symbol(
env,
procs,
layout_cache,
@ -4114,7 +4121,7 @@ pub fn with_hole<'a>(
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
if record_needs_specialization {
stmt = reuse_function_symbol(
stmt = specialize_symbol(
env,
procs,
layout_cache,
@ -4804,8 +4811,7 @@ fn construct_closure_data<'a>(
// symbols to be inlined when specializing the closure body elsewhere.
for &&(symbol, var) in symbols {
if procs.partial_exprs.contains(symbol) {
result =
reuse_function_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
result = specialize_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
}
}
@ -6318,6 +6324,20 @@ fn store_pattern_help<'a>(
match can_pat {
Identifier(symbol) => {
if let Some((_, var)) = procs.partial_exprs.get(outer_symbol) {
// It might be the case that symbol we're storing hasn't been reified to a value
// yet, if it's polymorphic. Do that now.
stmt = specialize_symbol(
env,
procs,
layout_cache,
Some(var),
*symbol,
stmt,
outer_symbol,
);
}
substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
}
Underscore => {
@ -6769,9 +6789,8 @@ fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> {
Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole)
}
/// If the symbol is a function, make sure it is properly specialized
// TODO: rename this now that we handle polymorphic non-function expressions too
fn reuse_function_symbol<'a>(
/// If the symbol is a function or polymorphic value, make sure it is properly specialized
fn specialize_symbol<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
@ -6980,7 +6999,7 @@ fn assign_to_symbol<'a>(
match can_reuse_symbol(env, procs, &loc_arg.value) {
Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => {
// for functions we must make sure they are specialized correctly
reuse_function_symbol(
specialize_symbol(
env,
procs,
layout_cache,
@ -7787,6 +7806,7 @@ fn from_can_pattern_help<'a>(
match can_pattern {
Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
IntLiteral(_, precision_var, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
IntOrFloat::Int(precision) => {
@ -7830,6 +7850,7 @@ fn from_can_pattern_help<'a>(
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
original_region: *region,
shadow: ident.clone(),
kind: ShadowKind::Variable,
}),
UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
MalformedPattern(_problem, region) => {

View file

@ -468,7 +468,7 @@ impl<'a> UnionLayout<'a> {
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
let allocation = match self {
UnionLayout::NonRecursive(_) => unreachable!("not heap-allocated"),
UnionLayout::NonRecursive(tags) => Self::tags_alignment_bytes(tags, target_info),
UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info),
UnionLayout::NonNullableUnwrapped(field_layouts) => {
Layout::struct_no_name_order(field_layouts).alignment_bytes(target_info)
@ -1150,9 +1150,11 @@ impl<'a> Layout<'a> {
}
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
let ptr_width = target_info.ptr_width() as u32;
match self {
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info),
Layout::Struct { .. } => unreachable!("not heap-allocated"),
Layout::Struct { .. } => self.alignment_bytes(target_info).max(ptr_width),
Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info),
Layout::LambdaSet(lambda_set) => lambda_set
.runtime_representation()
@ -1545,9 +1547,6 @@ impl<'a> Builtin<'a> {
let ptr_width = target_info.ptr_width() as u32;
let allocation = match self {
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => {
unreachable!("not heap-allocated")
}
Builtin::Str => ptr_width,
Builtin::Dict(k, v) => k
.alignment_bytes(target_info)
@ -1555,6 +1554,11 @@ impl<'a> Builtin<'a> {
.max(ptr_width),
Builtin::Set(k) => k.alignment_bytes(target_info).max(ptr_width),
Builtin::List(e) => e.alignment_bytes(target_info).max(ptr_width),
// The following are usually not heap-allocated, but they might be when inside a Box.
Builtin::Int(int_width) => int_width.alignment_bytes(target_info).max(ptr_width),
Builtin::Float(float_width) => float_width.alignment_bytes(target_info).max(ptr_width),
Builtin::Bool => (core::mem::align_of::<bool>() as u32).max(ptr_width),
Builtin::Decimal => IntWidth::I128.alignment_bytes(target_info).max(ptr_width),
};
allocation.max(ptr_width)

View file

@ -170,6 +170,7 @@ enum FirstOrder {
NumBytesToU32,
NumShiftRightZfBy,
NumIntCast,
NumFloatCast,
Eq,
NotEq,
And,

View file

@ -866,24 +866,26 @@ fn parse_defs_end<'a>(
}
Ok((_, loc_pattern, state)) => {
// First let's check whether this is an ability definition.
if let Loc {
value:
Pattern::Apply(
loc_name @ Loc {
value: Pattern::GlobalTag(name),
..
},
args,
),
..
} = loc_pattern
let opt_tag_and_args: Option<(&str, Region, &[Loc<Pattern>])> = match loc_pattern.value
{
Pattern::Apply(
Loc {
value: Pattern::GlobalTag(name),
region,
},
args,
) => Some((name, *region, args)),
Pattern::GlobalTag(name) => Some((name, loc_pattern.region, &[])),
_ => None,
};
if let Some((name, name_region, args)) = opt_tag_and_args {
if let Ok((_, loc_has, state)) =
loc_has_parser(min_indent).parse(arena, state.clone())
{
let (_, loc_def, state) = finish_parsing_ability_def(
start_column,
Loc::at(loc_name.region, name),
Loc::at(name_region, name),
args,
loc_has,
arena,

View file

@ -37,57 +37,47 @@ Defs(
],
},
),
],
@35-71 SpaceBefore(
Defs(
[
@35-68 Type(
Ability {
header: TypeHeader {
name: @35-38 "Ab2",
vars: [],
},
loc_has: @39-42 Has,
members: [
AbilityMember {
name: @43-46 "ab2",
typ: @54-68 Where(
@54-56 Function(
[
@49-50 BoundVariable(
"a",
),
],
@54-56 Record {
fields: [],
ext: None,
},
@35-68 Type(
Ability {
header: TypeHeader {
name: @35-38 "Ab2",
vars: [],
},
loc_has: @39-42 Has,
members: [
AbilityMember {
name: @43-46 "ab2",
typ: @54-68 Where(
@54-56 Function(
[
@49-50 BoundVariable(
"a",
),
[
@59-68 HasClause {
var: @59-60 "a",
ability: @65-68 Apply(
"",
"Ab2",
[],
),
},
],
),
},
],
],
@54-56 Record {
fields: [],
ext: None,
},
),
[
@59-68 HasClause {
var: @59-60 "a",
ability: @65-68 Apply(
"",
"Ab2",
[],
),
},
],
),
},
),
],
@70-71 SpaceBefore(
Num(
"1",
),
[
Newline,
Newline,
],
),
},
),
],
@70-71 SpaceBefore(
Num(
"1",
),
[
Newline,

View file

@ -20,6 +20,14 @@ pub enum BadPattern {
Unsupported(PatternType),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ShadowKind {
Variable,
Alias,
Opaque,
Ability,
}
/// Problems that can occur in the course of canonicalization.
#[derive(Clone, Debug, PartialEq)]
pub enum Problem {
@ -33,9 +41,10 @@ pub enum Problem {
PrecedenceProblem(PrecedenceProblem),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(BadPattern, Region),
ShadowingInAnnotation {
Shadowing {
original_region: Region,
shadow: Loc<Ident>,
kind: ShadowKind,
},
CyclicAlias(Symbol, Region, Vec<Symbol>),
BadRecursion(Vec<CycleEntry>),
@ -95,6 +104,30 @@ pub enum Problem {
region: Region,
kind: ExtensionTypeKind,
},
AbilityHasTypeVariables {
name: Symbol,
variables_region: Region,
},
HasClauseIsNotAbility {
region: Region,
},
IllegalHasClause {
region: Region,
},
AbilityMemberMissingHasClause {
member: Symbol,
ability: Symbol,
region: Region,
},
AbilityMemberBindsExternalAbility {
member: Symbol,
ability: Symbol,
region: Region,
},
AliasUsesAbility {
loc_name: Loc<Symbol>,
ability: Symbol,
},
}
#[derive(Clone, Debug, PartialEq)]
@ -157,6 +190,7 @@ pub enum RuntimeError {
Shadowing {
original_region: Region,
shadow: Loc<Ident>,
kind: ShadowKind,
},
InvalidOptionalValue {
field_name: Lowercase,

View file

@ -5319,6 +5319,21 @@ mod solve_expr {
)
}
#[test]
fn to_float() {
infer_eq_without_problem(
indoc!(
r#"
{
toF32: Num.toF32,
toF64: Num.toF64,
}
"#
),
r#"{ toF32 : Num * -> F32, toF64 : Num * -> F64 }"#,
)
}
#[test]
fn opaque_wrap_infer() {
infer_eq_without_problem(
@ -5407,7 +5422,7 @@ mod solve_expr {
condition : Bool
v : Id [ Y Str, Z Str ]
v =
v =
if condition
then $Id (Id 21 (Y "sasha"))
else $Id (Id 21 (Z "felix"))
@ -5682,4 +5697,17 @@ mod solve_expr {
"Result I64 [ InvalidNumStr, ListWasEmpty ]*",
)
}
#[test]
fn lots_of_type_variables() {
infer_eq_without_problem(
indoc!(
r#"
fun = \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,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}
fun
"#
),
"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 }",
)
}
}

View file

@ -2248,7 +2248,7 @@ fn max_u8() {
);
}
macro_rules! to_int_tests {
macro_rules! num_conversion_tests {
($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($(
#[test]
#[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))]
@ -2259,7 +2259,7 @@ macro_rules! to_int_tests {
)*)*}
}
to_int_tests! {
num_conversion_tests! {
"Num.toI8", i8, (
to_i8_same_width, "15u8", 15, ["gen-wasm"]
to_i8_truncate, "115i32", 115, ["gen-wasm"]
@ -2320,6 +2320,36 @@ to_int_tests! {
to_nat_truncate, "115i128", 115
to_nat_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128
)
"Num.toF32", f32, (
to_f32_from_i8, "15i8", 15.0
to_f32_from_i16, "15i16", 15.0
to_f32_from_i32, "15i32", 15.0
to_f32_from_i64, "15i64", 15.0
to_f32_from_i128, "15i128", 15.0
to_f32_from_u8, "15u8", 15.0
to_f32_from_u16, "15u16", 15.0
to_f32_from_u32, "15u32", 15.0
to_f32_from_u64, "15u64", 15.0
to_f32_from_u128, "15u128", 15.0
to_f32_from_nat, "15nat", 15.0
to_f32_from_f32, "1.5f32", 1.5
to_f32_from_f64, "1.5f64", 1.5
)
"Num.toF64", f64, (
to_f64_from_i8, "15i8", 15.0
to_f64_from_i16, "15i16", 15.0
to_f64_from_i32, "15i32", 15.0
to_f64_from_i64, "15i64", 15.0
to_f64_from_i128, "15i128", 15.0
to_f64_from_u8, "15u8", 15.0
to_f64_from_u16, "15u16", 15.0
to_f64_from_u32, "15u32", 15.0
to_f64_from_u64, "15u64", 15.0
to_f64_from_u128, "15u128", 15.0
to_f64_from_nat, "15nat", 15.0
to_f64_from_f32, "1.5f32", 1.5
to_f64_from_f64, "1.5f64", 1.5
)
}
macro_rules! to_int_checked_tests {

View file

@ -3275,3 +3275,47 @@ fn box_and_unbox_string() {
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn box_and_unbox_num() {
assert_evals_to!(
indoc!(
r#"
Box.unbox (Box.box (123u8))
"#
),
123,
u8
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn box_and_unbox_record() {
assert_evals_to!(
indoc!(
r#"
Box.unbox (Box.box { a: 15u8, b: 27u8 })
"#
),
(15, 27),
(u8, u8)
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn box_and_unbox_tag_union() {
assert_evals_to!(
indoc!(
r#"
v : [ A U8, B U8 ] # usually stack allocated
v = B 27u8
Box.unbox (Box.box v)
"#
),
(27, 1),
(u8, u8)
)
}

View file

@ -1587,3 +1587,19 @@ fn str_to_dec() {
RocDec
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn issue_2811() {
assert_evals_to!(
indoc!(
r#"
x = Command { tool: "bash" }
Command c = x
c.tool
"#
),
RocStr::from("bash"),
RocStr
);
}

View file

@ -0,0 +1,3 @@
procedure Test.0 ():
let Test.6 : Str = "bash";
ret Test.6;

View file

@ -1282,6 +1282,17 @@ fn issue_2583_specialize_errors_behind_unified_branches() {
)
}
#[mono_test]
fn issue_2811() {
indoc!(
r#"
x = Command { tool: "bash" }
Command c = x
c.tool
"#
)
}
// #[ignore]
// #[mono_test]
// fn static_str_closure() {

View file

@ -1856,6 +1856,7 @@ pub enum Problem {
},
InvalidModule,
SolvedTypeError,
HasClauseIsNotAbility(Region),
}
#[derive(PartialEq, Eq, Debug, Clone)]
@ -2307,15 +2308,15 @@ static THE_LETTER_A: u32 = 'a' as u32;
pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lowercase, u32) {
// TODO we should arena-allocate this String,
// so all the strings in the entire pass only require ~1 allocation.
let generated_name = if letters_used < 26 {
// This should generate "a", then "b", etc.
std::char::from_u32(THE_LETTER_A + letters_used)
.unwrap_or_else(|| panic!("Tried to convert {} to a char", THE_LETTER_A + letters_used))
.to_string()
.into()
} else {
panic!("TODO generate aa, ab, ac, ...");
};
let mut generated_name = String::with_capacity((letters_used as usize) / 26 + 1);
let mut remaining = letters_used as i32;
while remaining >= 0 {
generated_name.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap());
remaining -= 26;
}
let generated_name = generated_name.into();
if taken.contains(&generated_name) {
// If the generated name is already taken, try again.