mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 06:14:46 +00:00
Merge remote-tracking branch 'origin/trunk' into div-no-result
This commit is contained in:
commit
8206f345c7
46 changed files with 1685 additions and 291 deletions
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ]*
|
||||
|
|
|
@ -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
|
||||
|
|
74
compiler/can/src/abilities.rs
Normal file
74
compiler/can/src/abilities.rs
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 _ }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -111,7 +111,9 @@ pub enum LowLevel {
|
|||
NumShiftRightBy,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
NumToFloatCast,
|
||||
NumToIntChecked,
|
||||
NumToFloatChecked,
|
||||
NumToStr,
|
||||
Eq,
|
||||
NotEq,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]),
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -170,6 +170,7 @@ enum FirstOrder {
|
|||
NumBytesToU32,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
NumFloatCast,
|
||||
Eq,
|
||||
NotEq,
|
||||
And,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
3
compiler/test_mono/generated/issue_2811.txt
Normal file
3
compiler/test_mono/generated/issue_2811.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
procedure Test.0 ():
|
||||
let Test.6 : Str = "bash";
|
||||
ret Test.6;
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue