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

@ -72,3 +72,5 @@ Elliot Waite <1767836+elliotwaite@users.noreply.github.com>
zimt28 <1764689+zimt28@users.noreply.github.com>
Ananda Umamil <zweimach@zweimach.org>
SylvanSign <jake.d.bray@gmail.com>
Nikita Mounier <36044205+nikitamounier@users.noreply.github.com>
Cai Bingjun <62678643+C-BJ@users.noreply.github.com>

View file

@ -39,6 +39,8 @@ Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`.
Use `cargo build` to build the whole project.
> When using `nix-shell`, make sure that if you start `nix-shell` and then run `echo "$PATH" | tr ':' '\n'`, you see the `usr/bin` path listed after all the `/nix/…` paths. Otherwise you might get some nasty rust compilation errors!
#### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!

1
Cargo.lock generated
View file

@ -3370,6 +3370,7 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_constrain",
"roc_error_macros",
"roc_gen_dev",
"roc_gen_llvm",
"roc_gen_wasm",

5
FAQ.md
View file

@ -1,5 +1,10 @@
# Frequently Asked Questions
# Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
The Roc editor is one of the key areas where we want to innovate. Constraining ourselves to a plugin for existing editors would severely limit our possibilities for innovation.
A key part of our editor will be the use of plugins that are shipped with libraries. Think of a regex visualizer, parser debugger, or color picker. For library authors, it would be most convenient to write these plugins in Roc. Trying to dynamically load library plugins (written in Roc) in for example VSCode seems very difficult.
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious

View file

@ -18,7 +18,7 @@ use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, TypeDef, TypeHeader, ValueDef as AstValueDef};
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_problem::can::{Problem, RuntimeError, ShadowKind};
use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use std::collections::HashMap;
@ -251,9 +251,10 @@ fn to_pending_def<'a>(
}
Err((original_region, loc_shadowed_symbol)) => {
env.problem(Problem::ShadowingInAnnotation {
env.problem(Problem::Shadowing {
original_region,
shadow: loc_shadowed_symbol,
kind: ShadowKind::Variable,
});
Some((Output::default(), PendingDef::InvalidAlias))

View file

@ -12,7 +12,7 @@ use roc_error_macros::todo_opaques;
use roc_module::symbol::{Interns, Symbol};
use roc_parse::ast::{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::Region;
use roc_types::subs::Variable;
@ -161,6 +161,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
let name: &str = shadow.value.as_ref();
@ -364,6 +365,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// let shadowed = Pattern2::Shadowed {
@ -443,6 +445,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region,
shadow: shadow.clone(),
kind: ShadowKind::Variable,
}));
// No matter what the other patterns

View file

@ -246,6 +246,11 @@ pub fn build_file<'a>(
todo!("gracefully handle failing to surgically link");
})?;
BuildOutcome::NoProblems
} else if matches!(link_type, LinkType::None) {
// Just copy the object file to the output folder.
binary_path.set_extension(app_extension);
std::fs::copy(app_o_file, &binary_path).unwrap();
BuildOutcome::NoProblems
} else {
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),

View file

@ -34,6 +34,7 @@ pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib";
pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker";
@ -88,6 +89,12 @@ pub fn build_app<'a>() -> App<'a> {
.about("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::new(FLAG_NO_LINK)
.long(FLAG_NO_LINK)
.about("Does not link. Instead just outputs the `.o` file")
.required(false),
)
.arg(
Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
@ -291,10 +298,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
let link_type = if matches.is_present(FLAG_LIB) {
LinkType::Dylib
} else {
LinkType::Executable
let link_type = match (
matches.is_present(FLAG_LIB),
matches.is_present(FLAG_NO_LINK),
) {
(true, false) => LinkType::Dylib,
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
(false, true) => LinkType::None,
(false, false) => LinkType::Executable,
};
let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED);

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

@ -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,19 +283,55 @@ 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 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<_>>();
if let Some(one_ability_ref) = ability_references.first() {
env.problem(Problem::AliasUsesAbility {
loc_name: name,
ability: **one_ability_ref,
});
}
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
@ -349,6 +408,14 @@ pub fn canonicalize_defs<'a>(
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));
}
}
}
// Now that we know the alias dependency graph, we can try to insert recursion variables
// where aliases are recursive tag unions, or detect illegal recursions.
let mut aliases = correct_mutual_recursive_type_alias(env, aliases, var_store);
@ -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,33 +1723,48 @@ 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));
}
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,
);
let pending_ability = PendingTypeDef::Ability {
name,
// We'll handle adding the member symbols later on when we do all value defs.
members,
};
(
output,
PendingValueDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr),
)
Some((Output::default(), pending_ability))
}
}
}
fn to_pending_value_def<'a>(
@ -1585,6 +1772,7 @@ fn to_pending_value_def<'a>(
var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>,
scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType,
) -> Option<(Output, PendingValueDef<'a>)> {
use ast::ValueDef::*;
@ -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,7 +210,82 @@ impl Scope {
Err((original_region, shadow, symbol))
}
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)),
}
}
/// 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(shadow_symbol, region);
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.
@ -212,9 +299,7 @@ impl Scope {
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Ok(symbol)
}
}
symbol
}
/// Ignore an identifier.

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:
let opt_tag_and_args: Option<(&str, Region, &[Loc<Pattern>])> = match loc_pattern.value
{
Pattern::Apply(
loc_name @ Loc {
Loc {
value: Pattern::GlobalTag(name),
..
region,
},
args,
),
..
} = loc_pattern
{
) => 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,10 +37,6 @@ Defs(
],
},
),
],
@35-71 SpaceBefore(
Defs(
[
@35-68 Type(
Ability {
header: TypeHeader {
@ -88,10 +84,4 @@ Defs(
Newline,
],
),
),
[
Newline,
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(
@ -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.

View file

@ -4,7 +4,7 @@
The Roc programming language is named after [a mythical bird](https://en.wikipedia.org/wiki/Roc_(mythology)).
Thats why the logo is a bird. Its specifically an [*origami* bird](https://youtu.be/9gni1t1k1uY) as a homage
Thats why the logo is a bird. Its specifically an [*origami* bird](https://youtu.be/9gni1t1k1uY) as an homage
to [Elm](https://elm-lang.org/)s tangram logo.
Roc is a direct descendant of Elm. The languages are similar, but not the same.

View file

@ -2,7 +2,7 @@ use roc_collections::all::MutSet;
use roc_module::ident::{Ident, Lowercase, ModuleName};
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{
BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError,
BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, ShadowKind,
};
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region};
use roc_types::types::AliasKind;
@ -38,6 +38,12 @@ const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE TYPE DECLARED OUTSIDE SCOPE"
const OPAQUE_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED";
const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS";
const INVALID_EXTENSION_TYPE: &str = "INVALID_EXTENSION_TYPE";
const ABILITY_HAS_TYPE_VARIABLES: &str = "ABILITY HAS TYPE VARIABLES";
const HAS_CLAUSE_IS_NOT_AN_ABILITY: &str = "HAS CLAUSE IS NOT AN ABILITY";
const ALIAS_USES_ABILITY: &str = "ALIAS USES ABILITY";
const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE";
const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAUSE";
const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE";
pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>,
@ -213,11 +219,12 @@ pub fn can_problem<'b>(
title = SYNTAX_PROBLEM.to_string();
severity = Severity::RuntimeError;
}
Problem::ShadowingInAnnotation {
Problem::Shadowing {
original_region,
shadow,
kind,
} => {
doc = report_shadowing(alloc, lines, original_region, shadow);
doc = report_shadowing(alloc, lines, original_region, shadow, kind);
title = DUPLICATE_NAME.to_string();
severity = Severity::RuntimeError;
@ -562,6 +569,144 @@ pub fn can_problem<'b>(
title = INVALID_EXTENSION_TYPE.to_string();
severity = Severity::RuntimeError;
}
Problem::AbilityHasTypeVariables {
name,
variables_region,
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The definition of the "),
alloc.symbol_unqualified(name),
alloc.reflow(" ability includes type variables:"),
]),
alloc.region(lines.convert_region(variables_region)),
alloc.reflow(
"Abilities cannot depend on type variables, but their member values can!",
),
]);
title = ABILITY_HAS_TYPE_VARIABLES.to_string();
severity = Severity::RuntimeError;
}
Problem::HasClauseIsNotAbility {
region: clause_region,
} => {
doc = alloc.stack(vec![
alloc.reflow(r#"The type referenced in this "has" clause is not an ability:"#),
alloc.region(lines.convert_region(clause_region)),
]);
title = HAS_CLAUSE_IS_NOT_AN_ABILITY.to_string();
severity = Severity::RuntimeError;
}
Problem::AliasUsesAbility {
loc_name: Loc {
region,
value: name,
},
ability,
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The definition of the "),
alloc.symbol_unqualified(name),
alloc.reflow(" aliases references the ability "),
alloc.symbol_unqualified(ability),
alloc.reflow(":"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat(vec![
alloc.reflow("Abilities are not types, but you can add an ability constraint to a type variable "),
alloc.type_variable("a".into()),
alloc.reflow(" by writing"),
]),
alloc.type_block(alloc.concat(vec![
alloc.reflow("| a has "),
alloc.symbol_unqualified(ability),
])),
alloc.reflow(" at the end of the type."),
]);
title = ALIAS_USES_ABILITY.to_string();
severity = Severity::RuntimeError;
}
Problem::IllegalHasClause { region } => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("A "),
alloc.keyword("has"),
alloc.reflow(" clause is not allowed here:"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat(vec![
alloc.keyword("has"),
alloc.reflow(" clauses can only be specified on the top-level type annotation of an ability member."),
]),
]);
title = ILLEGAL_HAS_CLAUSE.to_string();
severity = Severity::RuntimeError;
}
Problem::AbilityMemberMissingHasClause {
member,
ability,
region,
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The definition of the ability member "),
alloc.symbol_unqualified(member),
alloc.reflow(" does not include a "),
alloc.keyword("has"),
alloc.reflow(" clause binding a type variable to the ability "),
alloc.symbol_unqualified(ability),
alloc.reflow(":"),
]),
alloc.region(lines.convert_region(region)),
alloc.concat(vec![
alloc.reflow("Ability members must include a "),
alloc.keyword("has"),
alloc.reflow(" clause binding a type variable to an ability, like"),
]),
alloc.type_block(alloc.concat(vec![
alloc.type_variable("a".into()),
alloc.space(),
alloc.keyword("has"),
alloc.space(),
alloc.symbol_unqualified(ability),
])),
alloc.concat(vec![alloc.reflow(
"Otherwise, the function does not need to be part of the ability!",
)]),
]);
title = ABILITY_MEMBER_MISSING_HAS_CLAUSE.to_string();
severity = Severity::RuntimeError;
}
Problem::AbilityMemberBindsExternalAbility {
member,
ability,
region,
} => {
doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The definition of the ability member "),
alloc.symbol_unqualified(member),
alloc.reflow(" includes a has clause binding an ability it is not a part of:"),
]),
alloc.region(lines.convert_region(region)),
alloc.reflow("Currently, ability members can only bind variables to the ability they are a part of."),
alloc.concat(vec![
alloc.hint(""),
alloc.reflow("Did you mean to bind the "),
alloc.symbol_unqualified(ability),
alloc.reflow(" ability instead?"),
]),
]);
title = ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE.to_string();
severity = Severity::RuntimeError;
}
};
Report {
@ -940,8 +1085,14 @@ fn report_shadowing<'b>(
lines: &LineInfo,
original_region: Region,
shadow: Loc<Ident>,
kind: ShadowKind,
) -> RocDocBuilder<'b> {
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
let what = match kind {
ShadowKind::Variable => "variables",
ShadowKind::Alias => "aliases",
ShadowKind::Opaque => "opaques",
ShadowKind::Ability => "abilities",
};
alloc.stack(vec![
alloc
@ -951,7 +1102,11 @@ fn report_shadowing<'b>(
alloc.region(lines.convert_region(original_region)),
alloc.reflow("But then it's defined a second time here:"),
alloc.region(lines.convert_region(shadow.region)),
alloc.reflow(line),
alloc.concat(vec![
alloc.reflow("Since these "),
alloc.reflow(what),
alloc.reflow(" have the same name, it's easy to use the wrong one on accident. Give one of them a new name."),
]),
])
}
@ -978,8 +1133,9 @@ fn pretty_runtime_error<'b>(
RuntimeError::Shadowing {
original_region,
shadow,
kind,
} => {
doc = report_shadowing(alloc, lines, original_region, shadow);
doc = report_shadowing(alloc, lines, original_region, shadow, kind);
title = DUPLICATE_NAME;
}

View file

@ -430,8 +430,8 @@ mod test_reporting {
3 Booly : [ Yes, No, Maybe ]
^^^^^^^^^^^^^^^^^^^^^^^^^^
Since these variables have the same name, it's easy to use the wrong
one on accident. Give one of them a new name.
Since these aliases have the same name, it's easy to use the wrong one
on accident. Give one of them a new name.
UNUSED DEFINITION
@ -8896,4 +8896,340 @@ I need all branches in an `if` to have the same type!
),
)
}
#[test]
fn bad_type_parameter_in_ability() {
report_problem_as(
indoc!(
r#"
Hash a b c has
hash : a -> U64 | a has Hash
1
"#
),
indoc!(
r#"
ABILITY HAS TYPE VARIABLES
The definition of the `Hash` ability includes type variables:
1 Hash a b c has
^^^^^
Abilities cannot depend on type variables, but their member values
can!
UNUSED DEFINITION
`Hash` is not used anywhere in your code.
1 Hash a b c has
^^^^
If you didn't intend on using `Hash` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn alias_in_has_clause() {
report_problem_as(
indoc!(
r#"
Hash has hash : a, b -> U64 | a has Hash, b has Bool
1
"#
),
indoc!(
r#"
HAS CLAUSE IS NOT AN ABILITY
The type referenced in this "has" clause is not an ability:
1 Hash has hash : a, b -> U64 | a has Hash, b has Bool
^^^^
UNUSED DEFINITION
`hash` is not used anywhere in your code.
1 Hash has hash : a, b -> U64 | a has Hash, b has Bool
^^^^
If you didn't intend on using `hash` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn shadowed_type_variable_in_has_clause() {
report_problem_as(
indoc!(
r#"
Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
1
"#
),
indoc!(
r#"
DUPLICATE NAME
The `a` name is first defined here:
1 Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
^^^^^^^^^
But then it's defined a second time here:
1 Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
^^^^^^^^^
Since these variables have the same name, it's easy to use the wrong
one on accident. Give one of them a new name.
UNUSED DEFINITION
`ab1` is not used anywhere in your code.
1 Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
^^^
If you didn't intend on using `ab1` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn alias_using_ability() {
report_problem_as(
indoc!(
r#"
Ability has ab : a -> {} | a has Ability
Alias : Ability
a : Alias
a
"#
),
indoc!(
r#"
ALIAS USES ABILITY
The definition of the `Alias` aliases references the ability `Ability`:
3 Alias : Ability
^^^^^
Abilities are not types, but you can add an ability constraint to a
type variable `a` by writing
| a has Ability
at the end of the type.
UNUSED DEFINITION
`ab` is not used anywhere in your code.
1 Ability has ab : a -> {} | a has Ability
^^
If you didn't intend on using `ab` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn ability_shadows_ability() {
report_problem_as(
indoc!(
r#"
Ability has ab : a -> U64 | a has Ability
Ability has ab1 : a -> U64 | a has Ability
1
"#
),
indoc!(
r#"
DUPLICATE NAME
The `Ability` name is first defined here:
1 Ability has ab : a -> U64 | a has Ability
^^^^^^^
But then it's defined a second time here:
3 Ability has ab1 : a -> U64 | a has Ability
^^^^^^^
Since these abilities have the same name, it's easy to use the wrong
one on accident. Give one of them a new name.
UNUSED DEFINITION
`ab` is not used anywhere in your code.
1 Ability has ab : a -> U64 | a has Ability
^^
If you didn't intend on using `ab` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn ability_member_does_not_bind_ability() {
report_problem_as(
indoc!(
r#"
Ability has ab : {} -> {}
1
"#
),
indoc!(
r#"
ABILITY MEMBER MISSING HAS CLAUSE
The definition of the ability member `ab` does not include a `has` clause
binding a type variable to the ability `Ability`:
1 Ability has ab : {} -> {}
^^
Ability members must include a `has` clause binding a type variable to
an ability, like
a has Ability
Otherwise, the function does not need to be part of the ability!
UNUSED DEFINITION
`Ability` is not used anywhere in your code.
1 Ability has ab : {} -> {}
^^^^^^^
If you didn't intend on using `Ability` then remove it so future readers
of your code don't wonder why it is there.
UNUSED DEFINITION
`ab` is not used anywhere in your code.
1 Ability has ab : {} -> {}
^^
If you didn't intend on using `ab` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn ability_member_binds_extra_ability() {
report_problem_as(
indoc!(
r#"
Eq has eq : a, a -> Bool | a has Eq
Hash has hash : a, b -> U64 | a has Eq, b has Hash
1
"#
),
indoc!(
r#"
ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE
The definition of the ability member `hash` includes a has clause
binding an ability it is not a part of:
2 Hash has hash : a, b -> U64 | a has Eq, b has Hash
^^^^^^^^
Currently, ability members can only bind variables to the ability they
are a part of.
Hint: Did you mean to bind the `Hash` ability instead?
UNUSED DEFINITION
`eq` is not used anywhere in your code.
1 Eq has eq : a, a -> Bool | a has Eq
^^
If you didn't intend on using `eq` then remove it so future readers of
your code don't wonder why it is there.
UNUSED DEFINITION
`hash` is not used anywhere in your code.
2 Hash has hash : a, b -> U64 | a has Eq, b has Hash
^^^^
If you didn't intend on using `hash` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn has_clause_outside_of_ability() {
report_problem_as(
indoc!(
r#"
Hash has hash : a -> U64 | a has Hash
f : a -> U64 | a has Hash
f
"#
),
indoc!(
r#"
ILLEGAL HAS CLAUSE
A `has` clause is not allowed here:
3 f : a -> U64 | a has Hash
^^^^^^^^^^
`has` clauses can only be specified on the top-level type annotation of
an ability member.
UNUSED DEFINITION
`hash` is not used anywhere in your code.
1 Hash has hash : a -> U64 | a has Hash
^^^^
If you didn't intend on using `hash` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
}