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> zimt28 <1764689+zimt28@users.noreply.github.com>
Ananda Umamil <zweimach@zweimach.org> Ananda Umamil <zweimach@zweimach.org>
SylvanSign <jake.d.bray@gmail.com> 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`. To use the `repl` subcommand, execute `cargo run repl`.
Use `cargo build` to build the whole project. 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 #### 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! 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_can",
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_error_macros",
"roc_gen_dev", "roc_gen_dev",
"roc_gen_llvm", "roc_gen_llvm",
"roc_gen_wasm", "roc_gen_wasm",

5
FAQ.md
View file

@ -1,5 +1,10 @@
# Frequently Asked Questions # 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? ## 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 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_module::symbol::Symbol;
use roc_parse::ast::{self, TypeDef, TypeHeader, ValueDef as AstValueDef}; use roc_parse::ast::{self, TypeDef, TypeHeader, ValueDef as AstValueDef};
use roc_parse::pattern::PatternType; 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_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use std::collections::HashMap; use std::collections::HashMap;
@ -251,9 +251,10 @@ fn to_pending_def<'a>(
} }
Err((original_region, loc_shadowed_symbol)) => { Err((original_region, loc_shadowed_symbol)) => {
env.problem(Problem::ShadowingInAnnotation { env.problem(Problem::Shadowing {
original_region, original_region,
shadow: loc_shadowed_symbol, shadow: loc_shadowed_symbol,
kind: ShadowKind::Variable,
}); });
Some((Output::default(), PendingDef::InvalidAlias)) 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_module::symbol::{Interns, Symbol};
use roc_parse::ast::{StrLiteral, StrSegment}; use roc_parse::ast::{StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; 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_region::all::Region;
use roc_types::subs::Variable; use roc_types::subs::Variable;
@ -161,6 +161,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
let name: &str = shadow.value.as_ref(); let name: &str = shadow.value.as_ref();
@ -364,6 +365,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// let shadowed = Pattern2::Shadowed { // let shadowed = Pattern2::Shadowed {
@ -443,6 +445,7 @@ pub fn to_pattern2<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// No matter what the other patterns // No matter what the other patterns

View file

@ -246,6 +246,11 @@ pub fn build_file<'a>(
todo!("gracefully handle failing to surgically link"); todo!("gracefully handle failing to surgically link");
})?; })?;
BuildOutcome::NoProblems 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 { } else {
let mut inputs = vec![ let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(), 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_OPTIMIZE: &str = "optimize";
pub const FLAG_OPT_SIZE: &str = "opt-size"; pub const FLAG_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib"; pub const FLAG_LIB: &str = "lib";
pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target"; pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker"; 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.") .about("Build a C library instead of an executable.")
.required(false), .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(
Arg::new(FLAG_DEBUG) Arg::new(FLAG_DEBUG)
.long(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_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME); let emit_timings = matches.is_present(FLAG_TIME);
let link_type = if matches.is_present(FLAG_LIB) { let link_type = match (
LinkType::Dylib matches.is_present(FLAG_LIB),
} else { matches.is_present(FLAG_NO_LINK),
LinkType::Executable ) {
(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 surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED); 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_wasm = { path = "../gen_wasm", optional = true }
roc_gen_dev = { path = "../gen_dev", default-features = false } roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
roc_error_macros = { path = "../../error_macros" }
roc_std = { path = "../../roc_std", default-features = false } roc_std = { path = "../../roc_std", default-features = false }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
libloading = "0.7.1" libloading = "0.7.1"

View file

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

View file

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

View file

@ -342,6 +342,9 @@ toU32 : Int * -> U32
toU64 : Int * -> U64 toU64 : Int * -> U64
toU128 : Int * -> U128 toU128 : Int * -> U128
toF32 : Num * -> F32
toF64 : Num * -> F64
toI8Checked : Int * -> Result I8 [ OutOfBounds ]* toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
toI16Checked : Int * -> Result I16 [ OutOfBounds ]* toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
toI32Checked : Int * -> Result I32 [ OutOfBounds ]* toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
@ -352,3 +355,5 @@ toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
toU32Checked : Int * -> Result U32 [ OutOfBounds ]* toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
toU64Checked : Int * -> Result U64 [ OutOfBounds ]* toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
toU128Checked : Int * -> Result U128 [ 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!( add_top_level_function_type!(
Symbol::NUM_TO_NAT_CHECKED, Symbol::NUM_TO_NAT_CHECKED,
vec![int_type(flex(TVAR1))], 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 // 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::env::Env;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap}; use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_error_macros::todo_abilities;
use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; 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_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{ use roc_types::types::{
@ -104,6 +104,10 @@ impl IntroducedVariables {
.find(|nv| nv.variable == var) .find(|nv| nv.variable == var)
.map(|nv| &nv.name) .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) { 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( fn make_apply_symbol(
env: &mut Env, env: &mut Env,
region: Region, region: Region,
@ -271,7 +347,13 @@ pub fn find_type_def_symbols(
SpaceBefore(inner, _) | SpaceAfter(inner, _) => { SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
stack.push(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(_) => {} Inferred | Wildcard | Malformed(_) => {}
} }
} }
@ -449,9 +531,10 @@ fn can_annotation_help(
Err((original_region, shadow, _new_symbol)) => { Err((original_region, shadow, _new_symbol)) => {
let problem = Problem::Shadowed(original_region, shadow.clone()); let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation { env.problem(roc_problem::can::Problem::Shadowing {
original_region, original_region,
shadow, shadow,
kind: ShadowKind::Variable,
}); });
return Type::Erroneous(problem); return Type::Erroneous(problem);
@ -685,7 +768,17 @@ fn can_annotation_help(
Type::Variable(var) 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(string) => {
malformed(env, region, 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)] #[allow(clippy::too_many_arguments)]
fn can_extension_type<'a>( fn can_extension_type<'a>(
env: &mut Env, 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_U128_CHECKED => num_to_u128_checked,
NUM_TO_NAT => num_to_nat, NUM_TO_NAT => num_to_nat,
NUM_TO_NAT_CHECKED => num_to_nat_checked, 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, NUM_TO_STR => num_to_str,
RESULT_MAP => result_map, RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err, 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) 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 { fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
let bool_var = var_store.fresh(); let bool_var = var_store.fresh();
let num_var_1 = var_store.fresh(); let num_var_1 = var_store.fresh();
@ -592,6 +608,8 @@ num_to_checked! {
num_to_u64_checked num_to_u64_checked
num_to_u128_checked num_to_u128_checked
num_to_nat_checked num_to_nat_checked
num_to_f32_checked
num_to_f64_checked
} }
// Num.toStr : Num a -> Str // 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;
use crate::annotation::canonicalize_annotation_with_possible_clauses;
use crate::annotation::IntroducedVariables; use crate::annotation::IntroducedVariables;
use crate::env::Env; use crate::env::Env;
use crate::expr::ClosureData; use crate::expr::ClosureData;
use crate::expr::Expr::{self, *}; use crate::expr::Expr::{self, *};
use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive}; 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::procedure::References;
use crate::scope::create_alias; use crate::scope::create_alias;
use crate::scope::Scope; use crate::scope::Scope;
use roc_collections::all::ImSet; use roc_collections::all::ImSet;
use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap}; 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::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_parse::ast; use roc_parse::ast;
use roc_parse::ast::AbilityMember;
use roc_parse::ast::ExtractSpaces;
use roc_parse::ast::TypeHeader; use roc_parse::ast::TypeHeader;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
use roc_problem::can::ShadowKind;
use roc_problem::can::{CycleEntry, Problem, RuntimeError}; use roc_problem::can::{CycleEntry, Problem, RuntimeError};
use roc_region::all::{Loc, Region}; use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
@ -86,10 +90,19 @@ enum PendingTypeDef<'a> {
kind: AliasKind, kind: AliasKind,
}, },
Ability {
name: Loc<Symbol>,
members: &'a [ast::AbilityMember<'a>],
},
/// An invalid alias, that is ignored in the rest of the pipeline /// An invalid alias, that is ignored in the rest of the pipeline
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
/// with an incorrect pattern /// with an incorrect pattern
InvalidAlias { kind: AliasKind }, 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. // 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); 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(); let mut referenced_type_symbols = MutMap::default();
for pending_def in pending_type_defs.into_iter() { 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); 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 sorted = sort_type_defs_before_introduction(referenced_type_symbols);
let mut aliases = SendMap::default();
let mut abilities = MutMap::default();
for type_name in sorted { 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 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 // Record all the annotation's references in output.references.lookups
for symbol in can_ann.references { for symbol in can_ann.references {
@ -349,6 +408,14 @@ pub fn canonicalize_defs<'a>(
aliases.insert(symbol, alias.clone()); 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 // 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. // where aliases are recursive tag unions, or detect illegal recursions.
let mut aliases = correct_mutual_recursive_type_alias(env, aliases, var_store); 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, // Now that we have the scope completely assembled, and shadowing resolved,
// we're ready to canonicalize any body exprs. // 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. // once we've finished assembling the entire scope.
let mut pending_value_defs = Vec::with_capacity(value_defs.len()); let mut pending_value_defs = Vec::with_capacity(value_defs.len());
for loc_def in value_defs.into_iter() { 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 */ } None => { /* skip */ }
Some((new_output, pending_def)) => { Some((new_output, pending_def)) => {
// store the top-level defs, used to ensure that closures won't capture them // 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); vars_by_symbol.insert(*symbol, expr_var);
} }
AbilityMemberSpecialization {
ident,
specializes: _,
} => {
vars_by_symbol.insert(*ident, expr_var);
}
AppliedTag { arguments, .. } => { AppliedTag { arguments, .. } => {
for (var, nested) in arguments { for (var, nested) in arguments {
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var); 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 { Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: loc_ident.clone(), shadow: loc_ident.clone(),
kind: ShadowKind::Variable,
}, },
_ => RuntimeError::NoImplementation, _ => RuntimeError::NoImplementation,
}; };
@ -1481,10 +1652,10 @@ fn to_pending_type_def<'a>(
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
typ: ann, typ: ann,
} => { } => {
let kind = if matches!(def, Alias { .. }) { let (kind, shadow_kind) = if matches!(def, Alias { .. }) {
AliasKind::Structural (AliasKind::Structural, ShadowKind::Alias)
} else { } else {
AliasKind::Opaque (AliasKind::Opaque, ShadowKind::Opaque)
}; };
let region = Region::span_across(&name.region, &ann.region); 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)) => { Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
env.problem(Problem::ShadowingInAnnotation { env.problem(Problem::Shadowing {
original_region, original_region,
shadow: loc_shadowed_symbol, shadow: loc_shadowed_symbol,
kind: shadow_kind,
}); });
Some((Output::default(), PendingTypeDef::InvalidAlias { 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>( let pending_ability = PendingTypeDef::Ability {
env: &mut Env<'a>, name,
loc_pattern: &'a Loc<ast::Pattern<'a>>, // We'll handle adding the member symbols later on when we do all value defs.
loc_ann: &'a Loc<ast::TypeAnnotation<'a>>, members,
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,
);
( Some((Output::default(), pending_ability))
output, }
PendingValueDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr), }
)
} }
fn to_pending_value_def<'a>( fn to_pending_value_def<'a>(
@ -1585,6 +1772,7 @@ fn to_pending_value_def<'a>(
var_store: &mut VarStore, var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>, def: &'a ast::ValueDef<'a>,
scope: &mut Scope, scope: &mut Scope,
abilities_store: &AbilitiesStore,
pattern_type: PatternType, pattern_type: PatternType,
) -> Option<(Output, PendingValueDef<'a>)> { ) -> Option<(Output, PendingValueDef<'a>)> {
use ast::ValueDef::*; use ast::ValueDef::*;
@ -1592,10 +1780,11 @@ fn to_pending_value_def<'a>(
match def { match def {
Annotation(loc_pattern, loc_ann) => { Annotation(loc_pattern, loc_ann) => {
// This takes care of checking for shadowing and adding idents to scope. // 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, env,
var_store, var_store,
scope, scope,
abilities_store,
pattern_type, pattern_type,
&loc_pattern.value, &loc_pattern.value,
loc_pattern.region, loc_pattern.region,
@ -1608,10 +1797,11 @@ fn to_pending_value_def<'a>(
} }
Body(loc_pattern, loc_expr) => { Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope. // 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, env,
var_store, var_store,
scope, scope,
abilities_store,
pattern_type, pattern_type,
&loc_pattern.value, &loc_pattern.value,
loc_pattern.region, loc_pattern.region,
@ -1636,14 +1826,21 @@ fn to_pending_value_def<'a>(
// //
// { x, y } : { x : Int, y ? Bool }* // { x, y } : { x : Int, y ? Bool }*
// { x, y ? False } = rec // { 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, env,
body_pattern,
ann_type,
body_expr,
var_store, var_store,
scope, scope,
abilities_store,
pattern_type, pattern_type,
&body_pattern.value,
body_pattern.region,
);
Some((
output,
PendingValueDef::TypedBody(body_pattern, loc_can_pattern, ann_type, body_expr),
)) ))
} else { } else {
// the pattern of the annotation does not match the pattern of the body direc // 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, variant_var: Variable,
ext_var: Variable, ext_var: Variable,
name: TagName, name: TagName,
arguments: Vec<(Variable, Loc<Expr>)>,
}, },
/// A wrapping of an opaque type, like `$Age 21` /// A wrapping of an opaque type, like `$Age 21`
@ -813,7 +812,6 @@ pub fn canonicalize_expr<'a>(
( (
ZeroArgumentTag { ZeroArgumentTag {
name: TagName::Global((*tag).into()), name: TagName::Global((*tag).into()),
arguments: vec![],
variant_var, variant_var,
closure_name: symbol, closure_name: symbol,
ext_var, ext_var,
@ -831,7 +829,6 @@ pub fn canonicalize_expr<'a>(
( (
ZeroArgumentTag { ZeroArgumentTag {
name: TagName::Private(symbol), name: TagName::Private(symbol),
arguments: vec![],
variant_var, variant_var,
ext_var, ext_var,
closure_name: lambda_set_symbol, closure_name: lambda_set_symbol,
@ -1560,15 +1557,13 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
variant_var, variant_var,
ext_var, ext_var,
name, name,
arguments,
} => { } => {
todo!( 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, closure_name,
variant_var, variant_var,
ext_var, ext_var,
name, name,
arguments
); );
} }

View file

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

View file

@ -589,7 +589,8 @@ fn fix_values_captured_in_closure_pattern(
| Shadowed(..) | Shadowed(..)
| MalformedPattern(_, _) | MalformedPattern(_, _)
| UnsupportedPattern(_) | UnsupportedPattern(_)
| OpaqueNotInScope(..) => (), | OpaqueNotInScope(..)
| AbilityMemberSpecialization { .. } => (),
} }
} }
@ -646,6 +647,7 @@ fn fix_values_captured_in_closure_expr(
| Var(_) | Var(_)
| EmptyRecord | EmptyRecord
| RuntimeError(_) | RuntimeError(_)
| ZeroArgumentTag { .. }
| Accessor { .. } => {} | Accessor { .. } => {}
List { loc_elems, .. } => { 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); 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() { for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols); 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::annotation::freshen_opaque_def;
use crate::env::Env; use crate::env::Env;
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; 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_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::ast::{self, StrLiteral, StrSegment};
use roc_parse::pattern::PatternType; 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_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{LambdaSet, Type}; use roc_types::types::{LambdaSet, Type};
@ -62,6 +63,17 @@ pub enum Pattern {
SingleQuote(char), SingleQuote(char),
Underscore, 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 // Runtime Exceptions
Shadowed(Region, Loc<Ident>, Symbol), Shadowed(Region, Loc<Ident>, Symbol),
OpaqueNotInScope(Loc<Ident>), OpaqueNotInScope(Loc<Ident>),
@ -101,6 +113,11 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
symbols.push(*symbol); symbols.push(*symbol);
} }
AbilityMemberSpecialization { ident, specializes } => {
symbols.push(*ident);
symbols.push(*specializes);
}
AppliedTag { arguments, .. } => { AppliedTag { arguments, .. } => {
for (_, nested) in arguments { for (_, nested) in arguments {
symbols_from_pattern_help(&nested.value, symbols); 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>( pub fn canonicalize_pattern<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &mut VarStore, var_store: &mut VarStore,
@ -164,6 +231,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
output.references.bound_symbols.insert(new_symbol); output.references.bound_symbols.insert(new_symbol);
@ -412,6 +480,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// No matter what the other patterns // No matter what the other patterns
@ -484,6 +553,7 @@ pub fn canonicalize_pattern<'a>(
env.problem(Problem::RuntimeError(RuntimeError::Shadowing { env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
original_region, original_region,
shadow: shadow.clone(), shadow: shadow.clone(),
kind: ShadowKind::Variable,
})); }));
// No matter what the other patterns // No matter what the other patterns
@ -594,7 +664,12 @@ fn add_bindings_from_patterns(
use Pattern::*; use Pattern::*;
match pattern { match pattern {
Identifier(symbol) | Shadowed(_, _, symbol) => { Identifier(symbol)
| Shadowed(_, _, symbol)
| AbilityMemberSpecialization {
ident: symbol,
specializes: _,
} => {
answer.push((*symbol, *region)); answer.push((*symbol, *region));
} }
AppliedTag { AppliedTag {

View file

@ -6,6 +6,8 @@ use roc_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, AliasKind, Type}; use roc_types::types::{Alias, AliasKind, Type};
use crate::abilities::AbilitiesStore;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Scope { pub struct Scope {
/// All the identifiers in scope, mapped to were they were defined and /// 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 /// The type aliases currently in scope
pub aliases: SendMap<Symbol, Alias>, 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 /// The current module being processed. This will be used to turn
/// unqualified idents into Symbols. /// unqualified idents into Symbols.
home: ModuleId, home: ModuleId,
@ -62,6 +67,8 @@ impl Scope {
idents: Symbol::default_in_scope(), idents: Symbol::default_in_scope(),
symbols: SendMap::default(), symbols: SendMap::default(),
aliases, 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 /// 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. /// 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( pub fn introduce(
&mut self, &mut self,
ident: Ident, ident: Ident,
@ -198,7 +210,82 @@ impl Scope {
Err((original_region, shadow, symbol)) 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 => { 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 // If this IdentId was already added previously
// when the value was exposed in the module header, // when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one. // use that existing IdentId. Otherwise, create a fresh one.
@ -212,9 +299,7 @@ impl Scope {
self.symbols.insert(symbol, region); self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region)); self.idents.insert(ident, (symbol, region));
Ok(symbol) symbol
}
}
} }
/// Ignore an identifier. /// Ignore an identifier.

View file

@ -881,7 +881,9 @@ pub fn constrain_expr(
name, name,
arguments, 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 types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len()); let mut arg_cons = Vec::with_capacity(arguments.len());
@ -923,27 +925,8 @@ pub fn constrain_expr(
variant_var, variant_var,
ext_var, ext_var,
name, name,
arguments,
closure_name, 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( let union_con = constraints.equal_types_with_storage(
Type::FunctionOrTagUnion( Type::FunctionOrTagUnion(
name.clone(), name.clone(),
@ -953,19 +936,14 @@ pub fn constrain_expr(
expected.clone(), expected.clone(),
Category::TagApply { Category::TagApply {
tag_name: name.clone(), tag_name: name.clone(),
args_count: arguments.len(), args_count: 0,
}, },
region, region,
*variant_var, *variant_var,
); );
vars.push(*variant_var); constraints.exists_many([*variant_var, *ext_var], [union_con])
vars.push(*ext_var);
arg_cons.push(union_con);
constraints.exists_many(vars, arg_cons)
} }
OpaqueRef { OpaqueRef {
opaque_var, opaque_var,
name, name,

View file

@ -50,7 +50,13 @@ fn headers_from_annotation_help(
headers: &mut SendMap<Symbol, Loc<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> bool { ) -> bool {
match pattern { 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()); let typ = Loc::at(annotation.region, annotation.value.clone());
headers.insert(*symbol, typ); headers.insert(*symbol, typ);
true true
@ -182,7 +188,12 @@ pub fn constrain_pattern(
// Erroneous patterns don't add any constraints. // 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()) { if could_be_a_tag_union(expected.get_type_ref()) {
state state
.constraints .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") .build_int_cast_sign_flag(arg, to, to_signed, "inc_cast")
.into() .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 => { Eq => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);

View file

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

View file

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

View file

@ -1115,7 +1115,6 @@ define_builtins! {
32 STR_TO_I16: "toI16" 32 STR_TO_I16: "toI16"
33 STR_TO_U8: "toU8" 33 STR_TO_U8: "toU8"
34 STR_TO_I8: "toI8" 34 STR_TO_I8: "toI8"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 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 NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | 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]), NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]), NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]), 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::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; 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_region::all::{Loc, Region};
use roc_std::RocDec; use roc_std::RocDec;
use roc_target::TargetInfo; use roc_target::TargetInfo;
@ -2037,6 +2037,7 @@ fn pattern_to_when<'a>(
let error = roc_problem::can::RuntimeError::Shadowing { let error = roc_problem::can::RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: loc_ident.clone(), shadow: loc_ident.clone(),
kind: ShadowKind::Variable,
}; };
(*new_symbol, Loc::at_zero(RuntimeError(error))) (*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 // They should have been replaced with `UnsupportedPattern` during canonicalization
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value) 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); let opt_fn_var = Some(variable);
// if this is a function symbol, ensure that it's properly specialized! // if this is a function symbol, ensure that it's properly specialized!
reuse_function_symbol( specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -3432,7 +3440,6 @@ pub fn with_hole<'a>(
ZeroArgumentTag { ZeroArgumentTag {
variant_var, variant_var,
name: tag_name, name: tag_name,
arguments: args,
ext_var, ext_var,
closure_name, closure_name,
} => { } => {
@ -3466,7 +3473,7 @@ pub fn with_hole<'a>(
tag_name, tag_name,
procs, procs,
layout_cache, layout_cache,
args, std::vec::Vec::new(),
arena, arena,
) )
} }
@ -3558,7 +3565,7 @@ pub fn with_hole<'a>(
// this symbol is already defined; nothing to do // this symbol is already defined; nothing to do
} }
Field::Function(symbol, variable) => { Field::Function(symbol, variable) => {
stmt = reuse_function_symbol( stmt = specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -4114,7 +4121,7 @@ pub fn with_hole<'a>(
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt)); Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
if record_needs_specialization { if record_needs_specialization {
stmt = reuse_function_symbol( stmt = specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -4804,8 +4811,7 @@ fn construct_closure_data<'a>(
// symbols to be inlined when specializing the closure body elsewhere. // symbols to be inlined when specializing the closure body elsewhere.
for &&(symbol, var) in symbols { for &&(symbol, var) in symbols {
if procs.partial_exprs.contains(symbol) { if procs.partial_exprs.contains(symbol) {
result = result = specialize_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
reuse_function_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
} }
} }
@ -6318,6 +6324,20 @@ fn store_pattern_help<'a>(
match can_pat { match can_pat {
Identifier(symbol) => { 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); substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
} }
Underscore => { 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) Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole)
} }
/// If the symbol is a function, make sure it is properly specialized /// If the symbol is a function or polymorphic value, make sure it is properly specialized
// TODO: rename this now that we handle polymorphic non-function expressions too fn specialize_symbol<'a>(
fn reuse_function_symbol<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
@ -6980,7 +6999,7 @@ fn assign_to_symbol<'a>(
match can_reuse_symbol(env, procs, &loc_arg.value) { match can_reuse_symbol(env, procs, &loc_arg.value) {
Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => { Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => {
// for functions we must make sure they are specialized correctly // for functions we must make sure they are specialized correctly
reuse_function_symbol( specialize_symbol(
env, env,
procs, procs,
layout_cache, layout_cache,
@ -7787,6 +7806,7 @@ fn from_can_pattern_help<'a>(
match can_pattern { match can_pattern {
Underscore => Ok(Pattern::Underscore), Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
IntLiteral(_, precision_var, _, int, _bound) => { IntLiteral(_, precision_var, _, int, _bound) => {
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
IntOrFloat::Int(precision) => { IntOrFloat::Int(precision) => {
@ -7830,6 +7850,7 @@ fn from_can_pattern_help<'a>(
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
original_region: *region, original_region: *region,
shadow: ident.clone(), shadow: ident.clone(),
kind: ShadowKind::Variable,
}), }),
UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)), UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
MalformedPattern(_problem, region) => { MalformedPattern(_problem, region) => {

View file

@ -468,7 +468,7 @@ impl<'a> UnionLayout<'a> {
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 { pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
let allocation = match self { 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::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info),
UnionLayout::NonNullableUnwrapped(field_layouts) => { UnionLayout::NonNullableUnwrapped(field_layouts) => {
Layout::struct_no_name_order(field_layouts).alignment_bytes(target_info) 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 { pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
let ptr_width = target_info.ptr_width() as u32;
match self { match self {
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info), 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::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info),
Layout::LambdaSet(lambda_set) => lambda_set Layout::LambdaSet(lambda_set) => lambda_set
.runtime_representation() .runtime_representation()
@ -1545,9 +1547,6 @@ impl<'a> Builtin<'a> {
let ptr_width = target_info.ptr_width() as u32; let ptr_width = target_info.ptr_width() as u32;
let allocation = match self { let allocation = match self {
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => {
unreachable!("not heap-allocated")
}
Builtin::Str => ptr_width, Builtin::Str => ptr_width,
Builtin::Dict(k, v) => k Builtin::Dict(k, v) => k
.alignment_bytes(target_info) .alignment_bytes(target_info)
@ -1555,6 +1554,11 @@ impl<'a> Builtin<'a> {
.max(ptr_width), .max(ptr_width),
Builtin::Set(k) => k.alignment_bytes(target_info).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), 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) allocation.max(ptr_width)

View file

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

View file

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

View file

@ -37,10 +37,6 @@ Defs(
], ],
}, },
), ),
],
@35-71 SpaceBefore(
Defs(
[
@35-68 Type( @35-68 Type(
Ability { Ability {
header: TypeHeader { header: TypeHeader {
@ -88,10 +84,4 @@ Defs(
Newline, Newline,
], ],
), ),
),
[
Newline,
Newline,
],
),
) )

View file

@ -20,6 +20,14 @@ pub enum BadPattern {
Unsupported(PatternType), Unsupported(PatternType),
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ShadowKind {
Variable,
Alias,
Opaque,
Ability,
}
/// Problems that can occur in the course of canonicalization. /// Problems that can occur in the course of canonicalization.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum Problem { pub enum Problem {
@ -33,9 +41,10 @@ pub enum Problem {
PrecedenceProblem(PrecedenceProblem), PrecedenceProblem(PrecedenceProblem),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(BadPattern, Region), UnsupportedPattern(BadPattern, Region),
ShadowingInAnnotation { Shadowing {
original_region: Region, original_region: Region,
shadow: Loc<Ident>, shadow: Loc<Ident>,
kind: ShadowKind,
}, },
CyclicAlias(Symbol, Region, Vec<Symbol>), CyclicAlias(Symbol, Region, Vec<Symbol>),
BadRecursion(Vec<CycleEntry>), BadRecursion(Vec<CycleEntry>),
@ -95,6 +104,30 @@ pub enum Problem {
region: Region, region: Region,
kind: ExtensionTypeKind, 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)] #[derive(Clone, Debug, PartialEq)]
@ -157,6 +190,7 @@ pub enum RuntimeError {
Shadowing { Shadowing {
original_region: Region, original_region: Region,
shadow: Loc<Ident>, shadow: Loc<Ident>,
kind: ShadowKind,
}, },
InvalidOptionalValue { InvalidOptionalValue {
field_name: Lowercase, 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] #[test]
fn opaque_wrap_infer() { fn opaque_wrap_infer() {
infer_eq_without_problem( infer_eq_without_problem(
@ -5682,4 +5697,17 @@ mod solve_expr {
"Result I64 [ InvalidNumStr, ListWasEmpty ]*", "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),* ])? )*))*) => {$($( ($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($(
#[test] #[test]
#[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))] #[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, ( "Num.toI8", i8, (
to_i8_same_width, "15u8", 15, ["gen-wasm"] to_i8_same_width, "15u8", 15, ["gen-wasm"]
to_i8_truncate, "115i32", 115, ["gen-wasm"] to_i8_truncate, "115i32", 115, ["gen-wasm"]
@ -2320,6 +2320,36 @@ to_int_tests! {
to_nat_truncate, "115i128", 115 to_nat_truncate, "115i128", 115
to_nat_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128 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 { macro_rules! to_int_checked_tests {

View file

@ -3275,3 +3275,47 @@ fn box_and_unbox_string() {
RocStr 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 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] // #[ignore]
// #[mono_test] // #[mono_test]
// fn static_str_closure() { // fn static_str_closure() {

View file

@ -1856,6 +1856,7 @@ pub enum Problem {
}, },
InvalidModule, InvalidModule,
SolvedTypeError, SolvedTypeError,
HasClauseIsNotAbility(Region),
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[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) { pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lowercase, u32) {
// TODO we should arena-allocate this String, // TODO we should arena-allocate this String,
// so all the strings in the entire pass only require ~1 allocation. // so all the strings in the entire pass only require ~1 allocation.
let generated_name = if letters_used < 26 { let mut generated_name = String::with_capacity((letters_used as usize) / 26 + 1);
// This should generate "a", then "b", etc.
std::char::from_u32(THE_LETTER_A + letters_used) let mut remaining = letters_used as i32;
.unwrap_or_else(|| panic!("Tried to convert {} to a char", THE_LETTER_A + letters_used)) while remaining >= 0 {
.to_string() generated_name.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap());
.into() remaining -= 26;
} else { }
panic!("TODO generate aa, ab, ac, ...");
}; let generated_name = generated_name.into();
if taken.contains(&generated_name) { if taken.contains(&generated_name) {
// If the generated name is already taken, try again. // 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)). 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. to [Elm](https://elm-lang.org/)s tangram logo.
Roc is a direct descendant of Elm. The languages are similar, but not the same. 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_module::ident::{Ident, Lowercase, ModuleName};
use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{ 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_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region};
use roc_types::types::AliasKind; 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_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED";
const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS"; const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS";
const INVALID_EXTENSION_TYPE: &str = "INVALID_EXTENSION_TYPE"; 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>( pub fn can_problem<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
@ -213,11 +219,12 @@ pub fn can_problem<'b>(
title = SYNTAX_PROBLEM.to_string(); title = SYNTAX_PROBLEM.to_string();
severity = Severity::RuntimeError; severity = Severity::RuntimeError;
} }
Problem::ShadowingInAnnotation { Problem::Shadowing {
original_region, original_region,
shadow, shadow,
kind,
} => { } => {
doc = report_shadowing(alloc, lines, original_region, shadow); doc = report_shadowing(alloc, lines, original_region, shadow, kind);
title = DUPLICATE_NAME.to_string(); title = DUPLICATE_NAME.to_string();
severity = Severity::RuntimeError; severity = Severity::RuntimeError;
@ -562,6 +569,144 @@ pub fn can_problem<'b>(
title = INVALID_EXTENSION_TYPE.to_string(); title = INVALID_EXTENSION_TYPE.to_string();
severity = Severity::RuntimeError; 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 { Report {
@ -940,8 +1085,14 @@ fn report_shadowing<'b>(
lines: &LineInfo, lines: &LineInfo,
original_region: Region, original_region: Region,
shadow: Loc<Ident>, shadow: Loc<Ident>,
kind: ShadowKind,
) -> RocDocBuilder<'b> { ) -> 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.stack(vec![
alloc alloc
@ -951,7 +1102,11 @@ fn report_shadowing<'b>(
alloc.region(lines.convert_region(original_region)), alloc.region(lines.convert_region(original_region)),
alloc.reflow("But then it's defined a second time here:"), alloc.reflow("But then it's defined a second time here:"),
alloc.region(lines.convert_region(shadow.region)), 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 { RuntimeError::Shadowing {
original_region, original_region,
shadow, shadow,
kind,
} => { } => {
doc = report_shadowing(alloc, lines, original_region, shadow); doc = report_shadowing(alloc, lines, original_region, shadow, kind);
title = DUPLICATE_NAME; title = DUPLICATE_NAME;
} }

View file

@ -430,8 +430,8 @@ mod test_reporting {
3 Booly : [ Yes, No, Maybe ] 3 Booly : [ Yes, No, Maybe ]
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
Since these variables have the same name, it's easy to use the wrong Since these aliases have the same name, it's easy to use the wrong one
one on accident. Give one of them a new name. on accident. Give one of them a new name.
UNUSED DEFINITION 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.
"#
),
)
}
} }