mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
Merge remote-tracking branch 'origin/trunk' into div-no-result
This commit is contained in:
commit
8206f345c7
46 changed files with 1685 additions and 291 deletions
2
AUTHORS
2
AUTHORS
|
@ -72,3 +72,5 @@ Elliot Waite <1767836+elliotwaite@users.noreply.github.com>
|
|||
zimt28 <1764689+zimt28@users.noreply.github.com>
|
||||
Ananda Umamil <zweimach@zweimach.org>
|
||||
SylvanSign <jake.d.bray@gmail.com>
|
||||
Nikita Mounier <36044205+nikitamounier@users.noreply.github.com>
|
||||
Cai Bingjun <62678643+C-BJ@users.noreply.github.com>
|
||||
|
|
|
@ -39,6 +39,8 @@ Use `cargo run help` to see all subcommands.
|
|||
To use the `repl` subcommand, execute `cargo run repl`.
|
||||
Use `cargo build` to build the whole project.
|
||||
|
||||
> When using `nix-shell`, make sure that if you start `nix-shell` and then run `echo "$PATH" | tr ':' '\n'`, you see the `usr/bin` path listed after all the `/nix/…` paths. Otherwise you might get some nasty rust compilation errors!
|
||||
|
||||
#### Extra tips
|
||||
|
||||
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependencies into your current shell, so you never have to run nix-shell directly!
|
||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3370,6 +3370,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_error_macros",
|
||||
"roc_gen_dev",
|
||||
"roc_gen_llvm",
|
||||
"roc_gen_wasm",
|
||||
|
|
5
FAQ.md
5
FAQ.md
|
@ -1,5 +1,10 @@
|
|||
# Frequently Asked Questions
|
||||
|
||||
# Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
|
||||
The Roc editor is one of the key areas where we want to innovate. Constraining ourselves to a plugin for existing editors would severely limit our possibilities for innovation.
|
||||
|
||||
A key part of our editor will be the use of plugins that are shipped with libraries. Think of a regex visualizer, parser debugger, or color picker. For library authors, it would be most convenient to write these plugins in Roc. Trying to dynamically load library plugins (written in Roc) in for example VSCode seems very difficult.
|
||||
|
||||
## Is there syntax highlighting for Vim/Emacs/VS Code or a LSP?
|
||||
|
||||
Not currently. Although they will presumably exist someday, while Roc is in the early days there's actually a conscious
|
||||
|
|
|
@ -18,7 +18,7 @@ use roc_module::ident::Lowercase;
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, TypeDef, TypeHeader, ValueDef as AstValueDef};
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{Problem, RuntimeError};
|
||||
use roc_problem::can::{Problem, RuntimeError, ShadowKind};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use std::collections::HashMap;
|
||||
|
@ -251,9 +251,10 @@ fn to_pending_def<'a>(
|
|||
}
|
||||
|
||||
Err((original_region, loc_shadowed_symbol)) => {
|
||||
env.problem(Problem::ShadowingInAnnotation {
|
||||
env.problem(Problem::Shadowing {
|
||||
original_region,
|
||||
shadow: loc_shadowed_symbol,
|
||||
kind: ShadowKind::Variable,
|
||||
});
|
||||
|
||||
Some((Output::default(), PendingDef::InvalidAlias))
|
||||
|
|
|
@ -12,7 +12,7 @@ use roc_error_macros::todo_opaques;
|
|||
use roc_module::symbol::{Interns, Symbol};
|
||||
use roc_parse::ast::{StrLiteral, StrSegment};
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
|
||||
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
|
||||
use roc_region::all::Region;
|
||||
use roc_types::subs::Variable;
|
||||
|
||||
|
@ -161,6 +161,7 @@ pub fn to_pattern2<'a>(
|
|||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
let name: &str = shadow.value.as_ref();
|
||||
|
@ -364,6 +365,7 @@ pub fn to_pattern2<'a>(
|
|||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
// let shadowed = Pattern2::Shadowed {
|
||||
|
@ -443,6 +445,7 @@ pub fn to_pattern2<'a>(
|
|||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
// No matter what the other patterns
|
||||
|
|
|
@ -246,6 +246,11 @@ pub fn build_file<'a>(
|
|||
todo!("gracefully handle failing to surgically link");
|
||||
})?;
|
||||
BuildOutcome::NoProblems
|
||||
} else if matches!(link_type, LinkType::None) {
|
||||
// Just copy the object file to the output folder.
|
||||
binary_path.set_extension(app_extension);
|
||||
std::fs::copy(app_o_file, &binary_path).unwrap();
|
||||
BuildOutcome::NoProblems
|
||||
} else {
|
||||
let mut inputs = vec![
|
||||
host_input_path.as_path().to_str().unwrap(),
|
||||
|
|
|
@ -34,6 +34,7 @@ pub const FLAG_DEV: &str = "dev";
|
|||
pub const FLAG_OPTIMIZE: &str = "optimize";
|
||||
pub const FLAG_OPT_SIZE: &str = "opt-size";
|
||||
pub const FLAG_LIB: &str = "lib";
|
||||
pub const FLAG_NO_LINK: &str = "no-link";
|
||||
pub const FLAG_TARGET: &str = "target";
|
||||
pub const FLAG_TIME: &str = "time";
|
||||
pub const FLAG_LINK: &str = "roc-linker";
|
||||
|
@ -88,6 +89,12 @@ pub fn build_app<'a>() -> App<'a> {
|
|||
.about("Build a C library instead of an executable.")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_NO_LINK)
|
||||
.long(FLAG_NO_LINK)
|
||||
.about("Does not link. Instead just outputs the `.o` file")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
|
@ -291,10 +298,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
|
|||
let emit_debug_info = matches.is_present(FLAG_DEBUG);
|
||||
let emit_timings = matches.is_present(FLAG_TIME);
|
||||
|
||||
let link_type = if matches.is_present(FLAG_LIB) {
|
||||
LinkType::Dylib
|
||||
} else {
|
||||
LinkType::Executable
|
||||
let link_type = match (
|
||||
matches.is_present(FLAG_LIB),
|
||||
matches.is_present(FLAG_NO_LINK),
|
||||
) {
|
||||
(true, false) => LinkType::Dylib,
|
||||
(true, true) => user_error!("build can only be one of `--lib` or `--no-link`"),
|
||||
(false, true) => LinkType::None,
|
||||
(false, false) => LinkType::Executable,
|
||||
};
|
||||
let surgically_link = matches.is_present(FLAG_LINK);
|
||||
let precompiled = matches.is_present(FLAG_PRECOMPILED);
|
||||
|
|
|
@ -24,6 +24,7 @@ roc_gen_llvm = { path = "../gen_llvm", optional = true }
|
|||
roc_gen_wasm = { path = "../gen_wasm", optional = true }
|
||||
roc_gen_dev = { path = "../gen_dev", default-features = false }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_std = { path = "../../roc_std", default-features = false }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
|
|
|
@ -2,6 +2,7 @@ use crate::target::{arch_str, target_zig_str};
|
|||
#[cfg(feature = "llvm")]
|
||||
use libloading::{Error, Library};
|
||||
use roc_builtins::bitcode;
|
||||
use roc_error_macros::internal_error;
|
||||
// #[cfg(feature = "llvm")]
|
||||
use roc_mono::ir::OptLevel;
|
||||
use std::collections::HashMap;
|
||||
|
@ -20,10 +21,10 @@ fn zig_executable() -> String {
|
|||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum LinkType {
|
||||
// These numbers correspond to the --lib flag; if it's present
|
||||
// (e.g. is_present returns `1 as bool`), this will be 1 as well.
|
||||
// These numbers correspond to the --lib and --no-link flags
|
||||
Executable = 0,
|
||||
Dylib = 1,
|
||||
None = 2,
|
||||
}
|
||||
|
||||
/// input_paths can include the host as well as the app. e.g. &["host.o", "roc_app.o"]
|
||||
|
@ -835,6 +836,7 @@ fn link_linux(
|
|||
output_path,
|
||||
)
|
||||
}
|
||||
LinkType::None => internal_error!("link_linux should not be called with link type of none"),
|
||||
};
|
||||
|
||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||
|
@ -904,6 +906,7 @@ fn link_macos(
|
|||
|
||||
("-dylib", output_path)
|
||||
}
|
||||
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
|
||||
};
|
||||
|
||||
let arch = match target.architecture {
|
||||
|
|
|
@ -628,6 +628,15 @@ toU16 : Int * -> U16
|
|||
toU32 : Int * -> U32
|
||||
toU64 : Int * -> U64
|
||||
toU128 : Int * -> U128
|
||||
|
||||
## Convert a [Num] to a [F32]. If the given number can't be precisely represented in a [F32],
|
||||
## there will be a loss of precision.
|
||||
toF32 : Num * -> F32
|
||||
|
||||
## Convert a [Num] to a [F64]. If the given number can't be precisely represented in a [F64],
|
||||
## there will be a loss of precision.
|
||||
toF64 : Num * -> F64
|
||||
|
||||
## Convert any [Int] to a specifically-sized [Int], after checking validity.
|
||||
## These are checked bitwise operations,
|
||||
## so if the source number is outside the target range, then these will
|
||||
|
@ -643,6 +652,9 @@ toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
|
|||
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
|
||||
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
|
||||
|
||||
toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
|
||||
toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
|
||||
|
||||
## Convert a number to a [Str].
|
||||
##
|
||||
## This is the same as calling `Num.format {}` - so for more details on
|
||||
|
@ -765,14 +777,6 @@ toU32 : Int * -> U32
|
|||
toU64 : Int * -> U64
|
||||
toU128 : Int * -> U128
|
||||
|
||||
## Convert a #Num to a #F32. If the given number can't be precisely represented in a #F32,
|
||||
## there will be a loss of precision.
|
||||
toF32 : Num * -> F32
|
||||
|
||||
## Convert a #Num to a #F64. If the given number can't be precisely represented in a #F64,
|
||||
## there will be a loss of precision.
|
||||
toF64 : Num * -> F64
|
||||
|
||||
## Convert a #Num to a #Dec. If the given number can't be precisely represented in a #Dec,
|
||||
## there will be a loss of precision.
|
||||
toDec : Num * -> Dec
|
||||
|
|
|
@ -342,6 +342,9 @@ toU32 : Int * -> U32
|
|||
toU64 : Int * -> U64
|
||||
toU128 : Int * -> U128
|
||||
|
||||
toF32 : Num * -> F32
|
||||
toF64 : Num * -> F64
|
||||
|
||||
toI8Checked : Int * -> Result I8 [ OutOfBounds ]*
|
||||
toI16Checked : Int * -> Result I16 [ OutOfBounds ]*
|
||||
toI32Checked : Int * -> Result I32 [ OutOfBounds ]*
|
||||
|
@ -352,3 +355,5 @@ toU16Checked : Int * -> Result U16 [ OutOfBounds ]*
|
|||
toU32Checked : Int * -> Result U32 [ OutOfBounds ]*
|
||||
toU64Checked : Int * -> Result U64 [ OutOfBounds ]*
|
||||
toU128Checked : Int * -> Result U128 [ OutOfBounds ]*
|
||||
toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
|
||||
toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
|
||||
|
|
|
@ -629,7 +629,35 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
|||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_NAT_CHECKED,
|
||||
vec![int_type(flex(TVAR1))],
|
||||
Box::new(result_type(nat_type(), out_of_bounds)),
|
||||
Box::new(result_type(nat_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toF32 : Num * -> F32
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_F32,
|
||||
vec![num_type(flex(TVAR1))],
|
||||
Box::new(f32_type()),
|
||||
);
|
||||
|
||||
// toF32Checked : Num * -> Result F32 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_F32_CHECKED,
|
||||
vec![num_type(flex(TVAR1))],
|
||||
Box::new(result_type(f32_type(), out_of_bounds.clone())),
|
||||
);
|
||||
|
||||
// toF64 : Num * -> F64
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_F64,
|
||||
vec![num_type(flex(TVAR1))],
|
||||
Box::new(f64_type()),
|
||||
);
|
||||
|
||||
// toF64Checked : Num * -> Result F64 [ OutOfBounds ]*
|
||||
add_top_level_function_type!(
|
||||
Symbol::NUM_TO_F64_CHECKED,
|
||||
vec![num_type(flex(TVAR1))],
|
||||
Box::new(result_type(f64_type(), out_of_bounds)),
|
||||
);
|
||||
|
||||
// toStr : Num a -> Str
|
||||
|
|
74
compiler/can/src/abilities.rs
Normal file
74
compiler/can/src/abilities.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_types::types::Type;
|
||||
|
||||
use crate::annotation::HasClause;
|
||||
|
||||
/// Stores information about an ability member definition, including the parent ability, the
|
||||
/// defining type, and what type variables need to be instantiated with instances of the ability.
|
||||
#[derive(Debug)]
|
||||
struct AbilityMemberData {
|
||||
#[allow(unused)]
|
||||
parent_ability: Symbol,
|
||||
#[allow(unused)]
|
||||
signature: Type,
|
||||
#[allow(unused)]
|
||||
bound_has_clauses: Vec<HasClause>,
|
||||
}
|
||||
|
||||
/// Stores information about what abilities exist in a scope, what it means to implement an
|
||||
/// ability, and what types implement them.
|
||||
// TODO(abilities): this should probably go on the Scope, I don't put it there for now because we
|
||||
// are only dealing with inter-module abilities for now.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct AbilitiesStore {
|
||||
/// Maps an ability to the members defining it.
|
||||
#[allow(unused)]
|
||||
members_of_ability: MutMap<Symbol, Vec<Symbol>>,
|
||||
|
||||
/// Information about all members composing abilities.
|
||||
ability_members: MutMap<Symbol, AbilityMemberData>,
|
||||
|
||||
/// Tuples of (type, member) specifying that `type` declares an implementation of an ability
|
||||
/// member `member`.
|
||||
#[allow(unused)]
|
||||
declared_implementations: MutSet<(Symbol, Symbol)>,
|
||||
}
|
||||
|
||||
impl AbilitiesStore {
|
||||
pub fn register_ability(
|
||||
&mut self,
|
||||
ability: Symbol,
|
||||
members: Vec<(Symbol, Type, Vec<HasClause>)>,
|
||||
) {
|
||||
let mut members_vec = Vec::with_capacity(members.len());
|
||||
for (member, signature, bound_has_clauses) in members.into_iter() {
|
||||
members_vec.push(member);
|
||||
let old_member = self.ability_members.insert(
|
||||
member,
|
||||
AbilityMemberData {
|
||||
parent_ability: ability,
|
||||
signature,
|
||||
bound_has_clauses,
|
||||
},
|
||||
);
|
||||
debug_assert!(old_member.is_none(), "Replacing existing member definition");
|
||||
}
|
||||
let old_ability = self.members_of_ability.insert(ability, members_vec);
|
||||
debug_assert!(
|
||||
old_ability.is_none(),
|
||||
"Replacing existing ability definition"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn register_implementation(&mut self, implementing_type: Symbol, ability_member: Symbol) {
|
||||
let old_impl = self
|
||||
.declared_implementations
|
||||
.insert((implementing_type, ability_member));
|
||||
debug_assert!(!old_impl, "Replacing existing implementation");
|
||||
}
|
||||
|
||||
pub fn is_ability_member_name(&self, name: Symbol) -> bool {
|
||||
self.ability_members.contains_key(&name)
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
use crate::env::Env;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::{Ident, Lowercase, TagName};
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_parse::ast::{AssignedField, Pattern, Tag, TypeAnnotation, TypeHeader};
|
||||
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
|
||||
use roc_problem::can::ShadowKind;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{
|
||||
|
@ -104,6 +104,10 @@ impl IntroducedVariables {
|
|||
.find(|nv| nv.variable == var)
|
||||
.map(|nv| &nv.name)
|
||||
}
|
||||
|
||||
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<&NamedVariable> {
|
||||
self.named.iter().find(|nv| &nv.name == name)
|
||||
}
|
||||
}
|
||||
|
||||
fn malformed(env: &mut Env, region: Region, name: &str) {
|
||||
|
@ -143,6 +147,78 @@ pub fn canonicalize_annotation(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HasClause {
|
||||
pub var_name: Lowercase,
|
||||
pub var: Variable,
|
||||
pub ability: Symbol,
|
||||
}
|
||||
|
||||
pub fn canonicalize_annotation_with_possible_clauses(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
annotation: &TypeAnnotation,
|
||||
region: Region,
|
||||
var_store: &mut VarStore,
|
||||
abilities_in_scope: &[Symbol],
|
||||
) -> (Annotation, Vec<Loc<HasClause>>) {
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
let mut references = MutSet::default();
|
||||
let mut aliases = SendMap::default();
|
||||
|
||||
let (annotation, region, clauses) = match annotation {
|
||||
TypeAnnotation::Where(annotation, clauses) => {
|
||||
let mut can_clauses = Vec::with_capacity(clauses.len());
|
||||
for clause in clauses.iter() {
|
||||
match canonicalize_has_clause(
|
||||
env,
|
||||
scope,
|
||||
var_store,
|
||||
&mut introduced_variables,
|
||||
clause,
|
||||
abilities_in_scope,
|
||||
&mut references,
|
||||
) {
|
||||
Ok(result) => can_clauses.push(Loc::at(clause.region, result)),
|
||||
Err(err_type) => {
|
||||
return (
|
||||
Annotation {
|
||||
typ: err_type,
|
||||
introduced_variables,
|
||||
references,
|
||||
aliases,
|
||||
},
|
||||
can_clauses,
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
(&annotation.value, annotation.region, can_clauses)
|
||||
}
|
||||
annot => (annot, region, vec![]),
|
||||
};
|
||||
|
||||
let typ = can_annotation_help(
|
||||
env,
|
||||
annotation,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
&mut introduced_variables,
|
||||
&mut aliases,
|
||||
&mut references,
|
||||
);
|
||||
|
||||
let annot = Annotation {
|
||||
typ,
|
||||
introduced_variables,
|
||||
references,
|
||||
aliases,
|
||||
};
|
||||
|
||||
(annot, clauses)
|
||||
}
|
||||
|
||||
fn make_apply_symbol(
|
||||
env: &mut Env,
|
||||
region: Region,
|
||||
|
@ -271,7 +347,13 @@ pub fn find_type_def_symbols(
|
|||
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
|
||||
stack.push(inner);
|
||||
}
|
||||
Where(..) => todo_abilities!(),
|
||||
Where(annotation, clauses) => {
|
||||
stack.push(&annotation.value);
|
||||
|
||||
for has_clause in clauses.iter() {
|
||||
stack.push(&has_clause.value.ability.value);
|
||||
}
|
||||
}
|
||||
Inferred | Wildcard | Malformed(_) => {}
|
||||
}
|
||||
}
|
||||
|
@ -449,9 +531,10 @@ fn can_annotation_help(
|
|||
Err((original_region, shadow, _new_symbol)) => {
|
||||
let problem = Problem::Shadowed(original_region, shadow.clone());
|
||||
|
||||
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
|
||||
env.problem(roc_problem::can::Problem::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
kind: ShadowKind::Variable,
|
||||
});
|
||||
|
||||
return Type::Erroneous(problem);
|
||||
|
@ -685,7 +768,17 @@ fn can_annotation_help(
|
|||
|
||||
Type::Variable(var)
|
||||
}
|
||||
Where(..) => todo_abilities!(),
|
||||
Where(_annotation, clauses) => {
|
||||
debug_assert!(!clauses.is_empty());
|
||||
|
||||
// Has clauses are allowed only on the top level of an ability member signature (for
|
||||
// now), which we handle elsewhere.
|
||||
env.problem(roc_problem::can::Problem::IllegalHasClause {
|
||||
region: Region::across_all(clauses.iter().map(|clause| &clause.region)),
|
||||
});
|
||||
|
||||
Type::Erroneous(Problem::CanonicalizationProblem)
|
||||
}
|
||||
Malformed(string) => {
|
||||
malformed(env, region, string);
|
||||
|
||||
|
@ -698,6 +791,72 @@ fn can_annotation_help(
|
|||
}
|
||||
}
|
||||
|
||||
fn canonicalize_has_clause(
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
var_store: &mut VarStore,
|
||||
introduced_variables: &mut IntroducedVariables,
|
||||
clause: &Loc<roc_parse::ast::HasClause<'_>>,
|
||||
abilities_in_scope: &[Symbol],
|
||||
references: &mut MutSet<Symbol>,
|
||||
) -> Result<HasClause, Type> {
|
||||
let Loc {
|
||||
region,
|
||||
value: roc_parse::ast::HasClause { var, ability },
|
||||
} = clause;
|
||||
let region = *region;
|
||||
|
||||
let var_name = var.extract_spaces().item;
|
||||
debug_assert!(
|
||||
var_name.starts_with(char::is_lowercase),
|
||||
"Vars should have been parsed as lowercase"
|
||||
);
|
||||
let var_name = Lowercase::from(var_name);
|
||||
|
||||
let ability = match ability.value {
|
||||
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
|
||||
let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?;
|
||||
if !abilities_in_scope.contains(&symbol) {
|
||||
let region = ability.region;
|
||||
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
||||
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
||||
}
|
||||
symbol
|
||||
}
|
||||
_ => {
|
||||
let region = ability.region;
|
||||
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
||||
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
||||
}
|
||||
};
|
||||
|
||||
references.insert(ability);
|
||||
|
||||
if let Some(shadowing) = introduced_variables.named_var_by_name(&var_name) {
|
||||
let var_name_ident = var_name.to_string().into();
|
||||
let shadow = Loc::at(region, var_name_ident);
|
||||
env.problem(roc_problem::can::Problem::Shadowing {
|
||||
original_region: shadowing.first_seen,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
});
|
||||
return Err(Type::Erroneous(Problem::Shadowed(
|
||||
shadowing.first_seen,
|
||||
shadow,
|
||||
)));
|
||||
}
|
||||
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named(var_name.clone(), Loc::at(region, var));
|
||||
|
||||
Ok(HasClause {
|
||||
var_name,
|
||||
var,
|
||||
ability,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn can_extension_type<'a>(
|
||||
env: &mut Env,
|
||||
|
|
|
@ -269,6 +269,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
|||
NUM_TO_U128_CHECKED => num_to_u128_checked,
|
||||
NUM_TO_NAT => num_to_nat,
|
||||
NUM_TO_NAT_CHECKED => num_to_nat_checked,
|
||||
NUM_TO_F32 => num_to_f32,
|
||||
NUM_TO_F32_CHECKED => num_to_f32_checked,
|
||||
NUM_TO_F64 => num_to_f64,
|
||||
NUM_TO_F64_CHECKED => num_to_f64_checked,
|
||||
NUM_TO_STR => num_to_str,
|
||||
RESULT_MAP => result_map,
|
||||
RESULT_MAP_ERR => result_map_err,
|
||||
|
@ -485,6 +489,18 @@ fn num_to_nat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
lowlevel_1(symbol, LowLevel::NumIntCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toF32 : Num * -> F32
|
||||
fn num_to_f32(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to NumToFloatCast
|
||||
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
|
||||
}
|
||||
|
||||
// Num.toF64 : Num * -> F64
|
||||
fn num_to_f64(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
// Defer to NumToFloatCast
|
||||
lowlevel_1(symbol, LowLevel::NumToFloatCast, var_store)
|
||||
}
|
||||
|
||||
fn to_num_checked(symbol: Symbol, var_store: &mut VarStore, lowlevel: LowLevel) -> Def {
|
||||
let bool_var = var_store.fresh();
|
||||
let num_var_1 = var_store.fresh();
|
||||
|
@ -592,6 +608,8 @@ num_to_checked! {
|
|||
num_to_u64_checked
|
||||
num_to_u128_checked
|
||||
num_to_nat_checked
|
||||
num_to_f32_checked
|
||||
num_to_f64_checked
|
||||
}
|
||||
|
||||
// Num.toStr : Num a -> Str
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
use crate::abilities::AbilitiesStore;
|
||||
use crate::annotation::canonicalize_annotation;
|
||||
use crate::annotation::canonicalize_annotation_with_possible_clauses;
|
||||
use crate::annotation::IntroducedVariables;
|
||||
use crate::env::Env;
|
||||
use crate::expr::ClosureData;
|
||||
use crate::expr::Expr::{self, *};
|
||||
use crate::expr::{canonicalize_expr, local_successors_with_duplicates, Output, Recursive};
|
||||
use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
|
||||
use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern};
|
||||
use crate::procedure::References;
|
||||
use crate::scope::create_alias;
|
||||
use crate::scope::Scope;
|
||||
use roc_collections::all::ImSet;
|
||||
use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap};
|
||||
use roc_error_macros::todo_abilities;
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast;
|
||||
use roc_parse::ast::AbilityMember;
|
||||
use roc_parse::ast::ExtractSpaces;
|
||||
use roc_parse::ast::TypeHeader;
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::ShadowKind;
|
||||
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
|
@ -86,10 +90,19 @@ enum PendingTypeDef<'a> {
|
|||
kind: AliasKind,
|
||||
},
|
||||
|
||||
Ability {
|
||||
name: Loc<Symbol>,
|
||||
members: &'a [ast::AbilityMember<'a>],
|
||||
},
|
||||
|
||||
/// An invalid alias, that is ignored in the rest of the pipeline
|
||||
/// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int`
|
||||
/// with an incorrect pattern
|
||||
InvalidAlias { kind: AliasKind },
|
||||
|
||||
/// An invalid ability, that is ignored in the rest of the pipeline.
|
||||
/// E.g. a shadowed ability, or with a bad definition.
|
||||
InvalidAbility,
|
||||
}
|
||||
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
|
@ -239,9 +252,19 @@ pub fn canonicalize_defs<'a>(
|
|||
env.home.register_debug_idents(&env.ident_ids);
|
||||
}
|
||||
|
||||
let mut aliases = SendMap::default();
|
||||
enum TypeDef<'a> {
|
||||
AliasLike(
|
||||
Loc<Symbol>,
|
||||
Vec<Loc<Lowercase>>,
|
||||
&'a Loc<ast::TypeAnnotation<'a>>,
|
||||
AliasKind,
|
||||
),
|
||||
Ability(Loc<Symbol>, &'a [AbilityMember<'a>]),
|
||||
}
|
||||
|
||||
let mut type_defs = MutMap::default();
|
||||
let mut abilities_in_scope = Vec::new();
|
||||
|
||||
let mut alias_defs = MutMap::default();
|
||||
let mut referenced_type_symbols = MutMap::default();
|
||||
|
||||
for pending_def in pending_type_defs.into_iter() {
|
||||
|
@ -260,19 +283,55 @@ pub fn canonicalize_defs<'a>(
|
|||
|
||||
referenced_type_symbols.insert(name.value, referenced_symbols);
|
||||
|
||||
alias_defs.insert(name.value, (name, vars, ann, kind));
|
||||
type_defs.insert(name.value, TypeDef::AliasLike(name, vars, ann, kind));
|
||||
}
|
||||
PendingTypeDef::Ability { name, members } => {
|
||||
let mut referenced_symbols = Vec::with_capacity(2);
|
||||
|
||||
for member in members.iter() {
|
||||
// Add the referenced type symbols of each member function. We need to make
|
||||
// sure those are processed first before we resolve the whole ability
|
||||
// definition.
|
||||
referenced_symbols.extend(crate::annotation::find_type_def_symbols(
|
||||
env.home,
|
||||
&mut env.ident_ids,
|
||||
&member.typ.value,
|
||||
));
|
||||
}
|
||||
|
||||
referenced_type_symbols.insert(name.value, referenced_symbols);
|
||||
type_defs.insert(name.value, TypeDef::Ability(name, members));
|
||||
abilities_in_scope.push(name.value);
|
||||
}
|
||||
PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } => { /* ignore */
|
||||
}
|
||||
PendingTypeDef::InvalidAlias { .. } => { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
|
||||
let mut aliases = SendMap::default();
|
||||
let mut abilities = MutMap::default();
|
||||
|
||||
for type_name in sorted {
|
||||
let (name, vars, ann, kind) = alias_defs.remove(&type_name).unwrap();
|
||||
|
||||
match type_defs.remove(&type_name).unwrap() {
|
||||
TypeDef::AliasLike(name, vars, ann, kind) => {
|
||||
let symbol = name.value;
|
||||
let can_ann = canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
|
||||
let can_ann =
|
||||
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
|
||||
|
||||
// Does this alias reference any abilities? For now, we don't permit that.
|
||||
let ability_references = can_ann
|
||||
.references
|
||||
.iter()
|
||||
.filter_map(|&ty_ref| abilities_in_scope.iter().find(|&&name| name == ty_ref))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Some(one_ability_ref) = ability_references.first() {
|
||||
env.problem(Problem::AliasUsesAbility {
|
||||
loc_name: name,
|
||||
ability: **one_ability_ref,
|
||||
});
|
||||
}
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in can_ann.references {
|
||||
|
@ -349,6 +408,14 @@ pub fn canonicalize_defs<'a>(
|
|||
aliases.insert(symbol, alias.clone());
|
||||
}
|
||||
|
||||
TypeDef::Ability(name, members) => {
|
||||
// For now we enforce that aliases cannot reference abilities, so let's wait to
|
||||
// resolve ability definitions until aliases are resolved and in scope below.
|
||||
abilities.insert(name.value, (name, members));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know the alias dependency graph, we can try to insert recursion variables
|
||||
// where aliases are recursive tag unions, or detect illegal recursions.
|
||||
let mut aliases = correct_mutual_recursive_type_alias(env, aliases, var_store);
|
||||
|
@ -362,6 +429,95 @@ pub fn canonicalize_defs<'a>(
|
|||
);
|
||||
}
|
||||
|
||||
// Now we can go through and resolve all pending abilities, to add them to scope.
|
||||
let mut abilities_store = AbilitiesStore::default();
|
||||
for (loc_ability_name, members) in abilities.into_values() {
|
||||
let mut can_members = Vec::with_capacity(members.len());
|
||||
|
||||
for member in members {
|
||||
let (member_annot, clauses) = canonicalize_annotation_with_possible_clauses(
|
||||
env,
|
||||
&mut scope,
|
||||
&member.typ.value,
|
||||
member.typ.region,
|
||||
var_store,
|
||||
&abilities_in_scope,
|
||||
);
|
||||
|
||||
// Record all the annotation's references in output.references.lookups
|
||||
for symbol in member_annot.references {
|
||||
output.references.type_lookups.insert(symbol);
|
||||
output.references.referenced_type_defs.insert(symbol);
|
||||
}
|
||||
|
||||
let member_name = member.name.extract_spaces().item;
|
||||
|
||||
let member_sym = match scope.introduce(
|
||||
member_name.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
member.name.region,
|
||||
) {
|
||||
Ok(sym) => sym,
|
||||
Err((original_region, shadow, _new_symbol)) => {
|
||||
env.problem(roc_problem::can::Problem::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
kind: ShadowKind::Variable,
|
||||
});
|
||||
// Pretend the member isn't a part of the ability
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// What variables in the annotation are bound to the parent ability, and what variables
|
||||
// are bound to some other ability?
|
||||
let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) =
|
||||
clauses
|
||||
.into_iter()
|
||||
.partition(|has_clause| has_clause.value.ability == loc_ability_name.value);
|
||||
|
||||
let mut bad_has_clauses = false;
|
||||
|
||||
if variables_bound_to_ability.is_empty() {
|
||||
// There are no variables bound to the parent ability - then this member doesn't
|
||||
// need to be a part of the ability.
|
||||
env.problem(Problem::AbilityMemberMissingHasClause {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
region: member.name.region,
|
||||
});
|
||||
bad_has_clauses = true;
|
||||
}
|
||||
|
||||
if !variables_bound_to_other_abilities.is_empty() {
|
||||
// Disallow variables bound to other abilities, for now.
|
||||
for bad_clause in variables_bound_to_other_abilities.iter() {
|
||||
env.problem(Problem::AbilityMemberBindsExternalAbility {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
region: bad_clause.region,
|
||||
});
|
||||
}
|
||||
bad_has_clauses = true;
|
||||
}
|
||||
|
||||
if bad_has_clauses {
|
||||
// Pretend the member isn't a part of the ability
|
||||
continue;
|
||||
}
|
||||
|
||||
let has_clauses = variables_bound_to_ability
|
||||
.into_iter()
|
||||
.map(|clause| clause.value)
|
||||
.collect();
|
||||
can_members.push((member_sym, member_annot.typ, has_clauses));
|
||||
}
|
||||
|
||||
// Store what symbols a type must define implementations for to have this ability.
|
||||
abilities_store.register_ability(loc_ability_name.value, can_members);
|
||||
}
|
||||
|
||||
// Now that we have the scope completely assembled, and shadowing resolved,
|
||||
// we're ready to canonicalize any body exprs.
|
||||
|
||||
|
@ -370,7 +526,14 @@ pub fn canonicalize_defs<'a>(
|
|||
// once we've finished assembling the entire scope.
|
||||
let mut pending_value_defs = Vec::with_capacity(value_defs.len());
|
||||
for loc_def in value_defs.into_iter() {
|
||||
match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) {
|
||||
match to_pending_value_def(
|
||||
env,
|
||||
var_store,
|
||||
loc_def.value,
|
||||
&mut scope,
|
||||
&abilities_store,
|
||||
pattern_type,
|
||||
) {
|
||||
None => { /* skip */ }
|
||||
Some((new_output, pending_def)) => {
|
||||
// store the top-level defs, used to ensure that closures won't capture them
|
||||
|
@ -857,6 +1020,13 @@ fn pattern_to_vars_by_symbol(
|
|||
vars_by_symbol.insert(*symbol, expr_var);
|
||||
}
|
||||
|
||||
AbilityMemberSpecialization {
|
||||
ident,
|
||||
specializes: _,
|
||||
} => {
|
||||
vars_by_symbol.insert(*ident, expr_var);
|
||||
}
|
||||
|
||||
AppliedTag { arguments, .. } => {
|
||||
for (var, nested) in arguments {
|
||||
pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var);
|
||||
|
@ -981,6 +1151,7 @@ fn canonicalize_pending_value_def<'a>(
|
|||
Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
|
||||
original_region: *region,
|
||||
shadow: loc_ident.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
},
|
||||
_ => RuntimeError::NoImplementation,
|
||||
};
|
||||
|
@ -1481,10 +1652,10 @@ fn to_pending_type_def<'a>(
|
|||
header: TypeHeader { name, vars },
|
||||
typ: ann,
|
||||
} => {
|
||||
let kind = if matches!(def, Alias { .. }) {
|
||||
AliasKind::Structural
|
||||
let (kind, shadow_kind) = if matches!(def, Alias { .. }) {
|
||||
(AliasKind::Structural, ShadowKind::Alias)
|
||||
} else {
|
||||
AliasKind::Opaque
|
||||
(AliasKind::Opaque, ShadowKind::Opaque)
|
||||
};
|
||||
|
||||
let region = Region::span_across(&name.region, &ann.region);
|
||||
|
@ -1541,9 +1712,10 @@ fn to_pending_type_def<'a>(
|
|||
}
|
||||
|
||||
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
|
||||
env.problem(Problem::ShadowingInAnnotation {
|
||||
env.problem(Problem::Shadowing {
|
||||
original_region,
|
||||
shadow: loc_shadowed_symbol,
|
||||
kind: shadow_kind,
|
||||
});
|
||||
|
||||
Some((Output::default(), PendingTypeDef::InvalidAlias { kind }))
|
||||
|
@ -1551,33 +1723,48 @@ fn to_pending_type_def<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
Ability { .. } => todo_abilities!(),
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
members,
|
||||
loc_has: _,
|
||||
} => {
|
||||
let name = match scope.introduce_without_shadow_symbol(
|
||||
name.value.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
name.region,
|
||||
) {
|
||||
Ok(symbol) => Loc::at(name.region, symbol),
|
||||
Err((original_region, shadowed_symbol)) => {
|
||||
env.problem(Problem::Shadowing {
|
||||
original_region,
|
||||
shadow: shadowed_symbol,
|
||||
kind: ShadowKind::Ability,
|
||||
});
|
||||
return Some((Output::default(), PendingTypeDef::InvalidAbility));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn pending_typed_body<'a>(
|
||||
env: &mut Env<'a>,
|
||||
loc_pattern: &'a Loc<ast::Pattern<'a>>,
|
||||
loc_ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
loc_expr: &'a Loc<ast::Expr<'a>>,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
pattern_type: PatternType,
|
||||
) -> (Output, PendingValueDef<'a>) {
|
||||
// This takes care of checking for shadowing and adding idents to scope.
|
||||
let (output, loc_can_pattern) = canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
);
|
||||
if !vars.is_empty() {
|
||||
// Disallow ability type arguments, at least for now.
|
||||
let variables_region = Region::across_all(vars.iter().map(|v| &v.region));
|
||||
|
||||
(
|
||||
output,
|
||||
PendingValueDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr),
|
||||
)
|
||||
env.problem(Problem::AbilityHasTypeVariables {
|
||||
name: name.value,
|
||||
variables_region,
|
||||
});
|
||||
return Some((Output::default(), PendingTypeDef::InvalidAbility));
|
||||
}
|
||||
|
||||
let pending_ability = PendingTypeDef::Ability {
|
||||
name,
|
||||
// We'll handle adding the member symbols later on when we do all value defs.
|
||||
members,
|
||||
};
|
||||
|
||||
Some((Output::default(), pending_ability))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn to_pending_value_def<'a>(
|
||||
|
@ -1585,6 +1772,7 @@ fn to_pending_value_def<'a>(
|
|||
var_store: &mut VarStore,
|
||||
def: &'a ast::ValueDef<'a>,
|
||||
scope: &mut Scope,
|
||||
abilities_store: &AbilitiesStore,
|
||||
pattern_type: PatternType,
|
||||
) -> Option<(Output, PendingValueDef<'a>)> {
|
||||
use ast::ValueDef::*;
|
||||
|
@ -1592,10 +1780,11 @@ fn to_pending_value_def<'a>(
|
|||
match def {
|
||||
Annotation(loc_pattern, loc_ann) => {
|
||||
// This takes care of checking for shadowing and adding idents to scope.
|
||||
let (output, loc_can_pattern) = canonicalize_pattern(
|
||||
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
|
@ -1608,10 +1797,11 @@ fn to_pending_value_def<'a>(
|
|||
}
|
||||
Body(loc_pattern, loc_expr) => {
|
||||
// This takes care of checking for shadowing and adding idents to scope.
|
||||
let (output, loc_can_pattern) = canonicalize_pattern(
|
||||
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
|
@ -1636,14 +1826,21 @@ fn to_pending_value_def<'a>(
|
|||
//
|
||||
// { x, y } : { x : Int, y ? Bool }*
|
||||
// { x, y ? False } = rec
|
||||
Some(pending_typed_body(
|
||||
//
|
||||
// This takes care of checking for shadowing and adding idents to scope.
|
||||
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
|
||||
env,
|
||||
body_pattern,
|
||||
ann_type,
|
||||
body_expr,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&body_pattern.value,
|
||||
body_pattern.region,
|
||||
);
|
||||
|
||||
Some((
|
||||
output,
|
||||
PendingValueDef::TypedBody(body_pattern, loc_can_pattern, ann_type, body_expr),
|
||||
))
|
||||
} else {
|
||||
// the pattern of the annotation does not match the pattern of the body direc
|
||||
|
|
|
@ -161,7 +161,6 @@ pub enum Expr {
|
|||
variant_var: Variable,
|
||||
ext_var: Variable,
|
||||
name: TagName,
|
||||
arguments: Vec<(Variable, Loc<Expr>)>,
|
||||
},
|
||||
|
||||
/// A wrapping of an opaque type, like `$Age 21`
|
||||
|
@ -813,7 +812,6 @@ pub fn canonicalize_expr<'a>(
|
|||
(
|
||||
ZeroArgumentTag {
|
||||
name: TagName::Global((*tag).into()),
|
||||
arguments: vec![],
|
||||
variant_var,
|
||||
closure_name: symbol,
|
||||
ext_var,
|
||||
|
@ -831,7 +829,6 @@ pub fn canonicalize_expr<'a>(
|
|||
(
|
||||
ZeroArgumentTag {
|
||||
name: TagName::Private(symbol),
|
||||
arguments: vec![],
|
||||
variant_var,
|
||||
ext_var,
|
||||
closure_name: lambda_set_symbol,
|
||||
|
@ -1560,15 +1557,13 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
|
|||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments,
|
||||
} => {
|
||||
todo!(
|
||||
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}",
|
||||
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}",
|
||||
closure_name,
|
||||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#![warn(clippy::dbg_macro)]
|
||||
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
|
||||
#![allow(clippy::large_enum_variant)]
|
||||
pub mod abilities;
|
||||
pub mod annotation;
|
||||
pub mod builtins;
|
||||
pub mod constraint;
|
||||
|
|
|
@ -589,7 +589,8 @@ fn fix_values_captured_in_closure_pattern(
|
|||
| Shadowed(..)
|
||||
| MalformedPattern(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
| OpaqueNotInScope(..) => (),
|
||||
| OpaqueNotInScope(..)
|
||||
| AbilityMemberSpecialization { .. } => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -646,6 +647,7 @@ fn fix_values_captured_in_closure_expr(
|
|||
| Var(_)
|
||||
| EmptyRecord
|
||||
| RuntimeError(_)
|
||||
| ZeroArgumentTag { .. }
|
||||
| Accessor { .. } => {}
|
||||
|
||||
List { loc_elems, .. } => {
|
||||
|
@ -712,7 +714,7 @@ fn fix_values_captured_in_closure_expr(
|
|||
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
|
||||
}
|
||||
|
||||
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
|
||||
Tag { arguments, .. } => {
|
||||
for (_, loc_arg) in arguments.iter_mut() {
|
||||
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::abilities::AbilitiesStore;
|
||||
use crate::annotation::freshen_opaque_def;
|
||||
use crate::env::Env;
|
||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
||||
|
@ -10,7 +11,7 @@ use roc_module::ident::{Ident, Lowercase, TagName};
|
|||
use roc_module::symbol::Symbol;
|
||||
use roc_parse::ast::{self, StrLiteral, StrSegment};
|
||||
use roc_parse::pattern::PatternType;
|
||||
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
|
||||
use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{LambdaSet, Type};
|
||||
|
@ -62,6 +63,17 @@ pub enum Pattern {
|
|||
SingleQuote(char),
|
||||
Underscore,
|
||||
|
||||
/// An identifier that marks a specialization of an ability member.
|
||||
/// For example, given an ability member definition `hash : a -> U64 | a has Hash`,
|
||||
/// there may be the specialization `hash : Bool -> U64`. In this case we generate a
|
||||
/// new symbol for the specialized "hash" identifier.
|
||||
AbilityMemberSpecialization {
|
||||
/// The symbol for this specialization.
|
||||
ident: Symbol,
|
||||
/// The ability name being specialized.
|
||||
specializes: Symbol,
|
||||
},
|
||||
|
||||
// Runtime Exceptions
|
||||
Shadowed(Region, Loc<Ident>, Symbol),
|
||||
OpaqueNotInScope(Loc<Ident>),
|
||||
|
@ -101,6 +113,11 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
symbols.push(*symbol);
|
||||
}
|
||||
|
||||
AbilityMemberSpecialization { ident, specializes } => {
|
||||
symbols.push(*ident);
|
||||
symbols.push(*specializes);
|
||||
}
|
||||
|
||||
AppliedTag { arguments, .. } => {
|
||||
for (_, nested) in arguments {
|
||||
symbols_from_pattern_help(&nested.value, symbols);
|
||||
|
@ -136,6 +153,56 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn canonicalize_def_header_pattern<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
scope: &mut Scope,
|
||||
abilities_store: &AbilitiesStore,
|
||||
pattern_type: PatternType,
|
||||
pattern: &ast::Pattern<'a>,
|
||||
region: Region,
|
||||
) -> (Output, Loc<Pattern>) {
|
||||
use roc_parse::ast::Pattern::*;
|
||||
|
||||
let mut output = Output::default();
|
||||
match pattern {
|
||||
// Identifiers that shadow ability members may appear (and may only appear) at the header of a def.
|
||||
Identifier(name) => match scope.introduce_or_shadow_ability_member(
|
||||
(*name).into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
abilities_store,
|
||||
) {
|
||||
Ok((symbol, shadowing_ability_member)) => {
|
||||
output.references.bound_symbols.insert(symbol);
|
||||
let can_pattern = match shadowing_ability_member {
|
||||
// A fresh identifier.
|
||||
None => Pattern::Identifier(symbol),
|
||||
// Likely a specialization of an ability.
|
||||
Some(ability_member_name) => Pattern::AbilityMemberSpecialization {
|
||||
ident: symbol,
|
||||
specializes: ability_member_name,
|
||||
},
|
||||
};
|
||||
(output, Loc::at(region, can_pattern))
|
||||
}
|
||||
Err((original_region, shadow, new_symbol)) => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
output.references.bound_symbols.insert(new_symbol);
|
||||
|
||||
let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol);
|
||||
(output, Loc::at(region, can_pattern))
|
||||
}
|
||||
},
|
||||
_ => canonicalize_pattern(env, var_store, scope, pattern_type, pattern, region),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonicalize_pattern<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &mut VarStore,
|
||||
|
@ -164,6 +231,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
output.references.bound_symbols.insert(new_symbol);
|
||||
|
||||
|
@ -412,6 +480,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
// No matter what the other patterns
|
||||
|
@ -484,6 +553,7 @@ pub fn canonicalize_pattern<'a>(
|
|||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}));
|
||||
|
||||
// No matter what the other patterns
|
||||
|
@ -594,7 +664,12 @@ fn add_bindings_from_patterns(
|
|||
use Pattern::*;
|
||||
|
||||
match pattern {
|
||||
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||
Identifier(symbol)
|
||||
| Shadowed(_, _, symbol)
|
||||
| AbilityMemberSpecialization {
|
||||
ident: symbol,
|
||||
specializes: _,
|
||||
} => {
|
||||
answer.push((*symbol, *region));
|
||||
}
|
||||
AppliedTag {
|
||||
|
|
|
@ -6,6 +6,8 @@ use roc_region::all::{Loc, Region};
|
|||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{Alias, AliasKind, Type};
|
||||
|
||||
use crate::abilities::AbilitiesStore;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Scope {
|
||||
/// All the identifiers in scope, mapped to were they were defined and
|
||||
|
@ -19,6 +21,9 @@ pub struct Scope {
|
|||
/// The type aliases currently in scope
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
|
||||
/// The abilities currently in scope, and their implementors.
|
||||
pub abilities: SendMap<Symbol, Region>,
|
||||
|
||||
/// The current module being processed. This will be used to turn
|
||||
/// unqualified idents into Symbols.
|
||||
home: ModuleId,
|
||||
|
@ -62,6 +67,8 @@ impl Scope {
|
|||
idents: Symbol::default_in_scope(),
|
||||
symbols: SendMap::default(),
|
||||
aliases,
|
||||
// TODO(abilities): default abilities in scope
|
||||
abilities: SendMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -176,6 +183,11 @@ impl Scope {
|
|||
///
|
||||
/// Returns Err if this would shadow an existing ident, including the
|
||||
/// Symbol and Region of the ident we already had in scope under that name.
|
||||
///
|
||||
/// If this ident shadows an existing one, a new ident is allocated for the shadow. This is
|
||||
/// done so that all identifiers have unique symbols, which is important in particular when
|
||||
/// we generate code for value identifiers.
|
||||
/// If this behavior is undesirable, use [`Self::introduce_without_shadow_symbol`].
|
||||
pub fn introduce(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
|
@ -198,7 +210,82 @@ impl Scope {
|
|||
|
||||
Err((original_region, shadow, symbol))
|
||||
}
|
||||
None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [Self::introduce], but does not introduce a new symbol for the shadowing symbol.
|
||||
pub fn introduce_without_shadow_symbol(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
exposed_ident_ids: &IdentIds,
|
||||
all_ident_ids: &mut IdentIds,
|
||||
region: Region,
|
||||
) -> Result<Symbol, (Region, Loc<Ident>)> {
|
||||
match self.idents.get(&ident) {
|
||||
Some(&(_, original_region)) => {
|
||||
let shadow = Loc {
|
||||
value: ident.clone(),
|
||||
region,
|
||||
};
|
||||
Err((original_region, shadow))
|
||||
}
|
||||
None => Ok(self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [Self::introduce], but handles the case of when an ident matches an ability member
|
||||
/// name. In such cases a new symbol is created for the ident (since it's expected to be a
|
||||
/// specialization of the ability member), but the ident is not added to the ident->symbol map.
|
||||
///
|
||||
/// If the ident does not match an ability name, the behavior of this function is exactly that
|
||||
/// of `introduce`.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn introduce_or_shadow_ability_member(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
exposed_ident_ids: &IdentIds,
|
||||
all_ident_ids: &mut IdentIds,
|
||||
region: Region,
|
||||
abilities_store: &AbilitiesStore,
|
||||
) -> Result<(Symbol, Option<Symbol>), (Region, Loc<Ident>, Symbol)> {
|
||||
match self.idents.get(&ident) {
|
||||
Some(&(original_symbol, original_region)) => {
|
||||
let shadow_ident_id = all_ident_ids.add(ident.clone());
|
||||
let shadow_symbol = Symbol::new(self.home, shadow_ident_id);
|
||||
|
||||
self.symbols.insert(shadow_symbol, region);
|
||||
|
||||
if abilities_store.is_ability_member_name(original_symbol) {
|
||||
// Add a symbol for the shadow, but don't re-associate the member name.
|
||||
Ok((shadow_symbol, Some(original_symbol)))
|
||||
} else {
|
||||
// This is an illegal shadow.
|
||||
let shadow = Loc {
|
||||
value: ident.clone(),
|
||||
region,
|
||||
};
|
||||
|
||||
self.idents.insert(ident, (shadow_symbol, region));
|
||||
|
||||
Err((original_region, shadow, shadow_symbol))
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let new_symbol =
|
||||
self.commit_introduction(ident, exposed_ident_ids, all_ident_ids, region);
|
||||
Ok((new_symbol, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn commit_introduction(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
exposed_ident_ids: &IdentIds,
|
||||
all_ident_ids: &mut IdentIds,
|
||||
region: Region,
|
||||
) -> Symbol {
|
||||
// If this IdentId was already added previously
|
||||
// when the value was exposed in the module header,
|
||||
// use that existing IdentId. Otherwise, create a fresh one.
|
||||
|
@ -212,9 +299,7 @@ impl Scope {
|
|||
self.symbols.insert(symbol, region);
|
||||
self.idents.insert(ident, (symbol, region));
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
}
|
||||
symbol
|
||||
}
|
||||
|
||||
/// Ignore an identifier.
|
||||
|
|
|
@ -881,7 +881,9 @@ pub fn constrain_expr(
|
|||
name,
|
||||
arguments,
|
||||
} => {
|
||||
let mut vars = Vec::with_capacity(arguments.len());
|
||||
// +2 because we push all the arguments, plus variant_var and ext_var
|
||||
let num_vars = arguments.len() + 2;
|
||||
let mut vars = Vec::with_capacity(num_vars);
|
||||
let mut types = Vec::with_capacity(arguments.len());
|
||||
let mut arg_cons = Vec::with_capacity(arguments.len());
|
||||
|
||||
|
@ -923,27 +925,8 @@ pub fn constrain_expr(
|
|||
variant_var,
|
||||
ext_var,
|
||||
name,
|
||||
arguments,
|
||||
closure_name,
|
||||
} => {
|
||||
let mut vars = Vec::with_capacity(arguments.len());
|
||||
let mut types = Vec::with_capacity(arguments.len());
|
||||
let mut arg_cons = Vec::with_capacity(arguments.len());
|
||||
|
||||
for (var, loc_expr) in arguments {
|
||||
let arg_con = constrain_expr(
|
||||
constraints,
|
||||
env,
|
||||
loc_expr.region,
|
||||
&loc_expr.value,
|
||||
Expected::NoExpectation(Type::Variable(*var)),
|
||||
);
|
||||
|
||||
arg_cons.push(arg_con);
|
||||
vars.push(*var);
|
||||
types.push(Type::Variable(*var));
|
||||
}
|
||||
|
||||
let union_con = constraints.equal_types_with_storage(
|
||||
Type::FunctionOrTagUnion(
|
||||
name.clone(),
|
||||
|
@ -953,19 +936,14 @@ pub fn constrain_expr(
|
|||
expected.clone(),
|
||||
Category::TagApply {
|
||||
tag_name: name.clone(),
|
||||
args_count: arguments.len(),
|
||||
args_count: 0,
|
||||
},
|
||||
region,
|
||||
*variant_var,
|
||||
);
|
||||
|
||||
vars.push(*variant_var);
|
||||
vars.push(*ext_var);
|
||||
arg_cons.push(union_con);
|
||||
|
||||
constraints.exists_many(vars, arg_cons)
|
||||
constraints.exists_many([*variant_var, *ext_var], [union_con])
|
||||
}
|
||||
|
||||
OpaqueRef {
|
||||
opaque_var,
|
||||
name,
|
||||
|
|
|
@ -50,7 +50,13 @@ fn headers_from_annotation_help(
|
|||
headers: &mut SendMap<Symbol, Loc<Type>>,
|
||||
) -> bool {
|
||||
match pattern {
|
||||
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||
Identifier(symbol)
|
||||
| Shadowed(_, _, symbol)
|
||||
// TODO(abilities): handle linking the member def to the specialization ident
|
||||
| AbilityMemberSpecialization {
|
||||
ident: symbol,
|
||||
specializes: _,
|
||||
} => {
|
||||
let typ = Loc::at(annotation.region, annotation.value.clone());
|
||||
headers.insert(*symbol, typ);
|
||||
true
|
||||
|
@ -182,7 +188,12 @@ pub fn constrain_pattern(
|
|||
// Erroneous patterns don't add any constraints.
|
||||
}
|
||||
|
||||
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
||||
Identifier(symbol) | Shadowed(_, _, symbol)
|
||||
// TODO(abilities): handle linking the member def to the specialization ident
|
||||
| AbilityMemberSpecialization {
|
||||
ident: symbol,
|
||||
specializes: _,
|
||||
} => {
|
||||
if could_be_a_tag_union(expected.get_type_ref()) {
|
||||
state
|
||||
.constraints
|
||||
|
|
|
@ -5865,6 +5865,48 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
.build_int_cast_sign_flag(arg, to, to_signed, "inc_cast")
|
||||
.into()
|
||||
}
|
||||
NumToFloatCast => {
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
||||
let (arg, arg_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||
|
||||
match arg_layout {
|
||||
Layout::Builtin(Builtin::Int(width)) => {
|
||||
// Converting from int to float
|
||||
let int_val = arg.into_int_value();
|
||||
let dest = basic_type_from_layout(env, layout).into_float_type();
|
||||
|
||||
if width.is_signed() {
|
||||
env.builder
|
||||
.build_signed_int_to_float(int_val, dest, "signed_int_to_float")
|
||||
.into()
|
||||
} else {
|
||||
env.builder
|
||||
.build_unsigned_int_to_float(int_val, dest, "unsigned_int_to_float")
|
||||
.into()
|
||||
}
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(_)) => {
|
||||
// Converting from float to float - e.g. F64 to F32, or vice versa
|
||||
let dest = basic_type_from_layout(env, layout).into_float_type();
|
||||
|
||||
env.builder
|
||||
.build_float_cast(arg.into_float_value(), dest, "cast_float_to_float")
|
||||
.into()
|
||||
}
|
||||
Layout::Builtin(Builtin::Decimal) => {
|
||||
todo!("Support converting Dec values to floats.");
|
||||
}
|
||||
other => {
|
||||
unreachable!("Tried to do a float cast to non-float layout {:?}", other);
|
||||
}
|
||||
}
|
||||
}
|
||||
NumToFloatChecked => {
|
||||
// NOTE: There's a NumToIntChecked implementation above,
|
||||
// which could be useful to look at when implementing this.
|
||||
todo!("implement checked float conversion");
|
||||
}
|
||||
Eq => {
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
|
|
|
@ -677,9 +677,15 @@ impl<'a> LowLevelCall<'a> {
|
|||
_ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type),
|
||||
}
|
||||
}
|
||||
NumToFloatCast => {
|
||||
todo!("implement toF32 and toF64");
|
||||
}
|
||||
NumToIntChecked => {
|
||||
todo!()
|
||||
}
|
||||
NumToFloatChecked => {
|
||||
todo!("implement toF32Checked and toF64Checked");
|
||||
}
|
||||
And => {
|
||||
self.load_args(backend);
|
||||
backend.code_builder.i32_and();
|
||||
|
|
|
@ -111,7 +111,9 @@ pub enum LowLevel {
|
|||
NumShiftRightBy,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
NumToFloatCast,
|
||||
NumToIntChecked,
|
||||
NumToFloatChecked,
|
||||
NumToStr,
|
||||
Eq,
|
||||
NotEq,
|
||||
|
|
|
@ -1115,7 +1115,6 @@ define_builtins! {
|
|||
32 STR_TO_I16: "toI16"
|
||||
33 STR_TO_U8: "toU8"
|
||||
34 STR_TO_I8: "toI8"
|
||||
|
||||
}
|
||||
4 LIST: "List" => {
|
||||
0 LIST_LIST: "List" imported // the List.List type alias
|
||||
|
|
|
@ -1001,7 +1001,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
|||
|
||||
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
|
||||
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos
|
||||
| NumAsin | NumIntCast | NumToIntChecked => arena.alloc_slice_copy(&[irrelevant]),
|
||||
| NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => {
|
||||
arena.alloc_slice_copy(&[irrelevant])
|
||||
}
|
||||
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||
|
|
|
@ -13,7 +13,7 @@ use roc_exhaustive::{Ctor, Guard, RenderAs, TagId};
|
|||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use roc_problem::can::RuntimeError;
|
||||
use roc_problem::can::{RuntimeError, ShadowKind};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_std::RocDec;
|
||||
use roc_target::TargetInfo;
|
||||
|
@ -2037,6 +2037,7 @@ fn pattern_to_when<'a>(
|
|||
let error = roc_problem::can::RuntimeError::Shadowing {
|
||||
original_region: *region,
|
||||
shadow: loc_ident.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
};
|
||||
(*new_symbol, Loc::at_zero(RuntimeError(error)))
|
||||
}
|
||||
|
@ -2088,6 +2089,13 @@ fn pattern_to_when<'a>(
|
|||
// They should have been replaced with `UnsupportedPattern` during canonicalization
|
||||
unreachable!("refutable pattern {:?} where irrefutable pattern is expected. This should never happen!", pattern.value)
|
||||
}
|
||||
|
||||
AbilityMemberSpecialization { .. } => {
|
||||
unreachable!(
|
||||
"Ability member specialization {:?} should never appear in a when!",
|
||||
pattern.value
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3053,7 +3061,7 @@ fn specialize_naked_symbol<'a>(
|
|||
let opt_fn_var = Some(variable);
|
||||
|
||||
// if this is a function symbol, ensure that it's properly specialized!
|
||||
reuse_function_symbol(
|
||||
specialize_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
|
@ -3432,7 +3440,6 @@ pub fn with_hole<'a>(
|
|||
ZeroArgumentTag {
|
||||
variant_var,
|
||||
name: tag_name,
|
||||
arguments: args,
|
||||
ext_var,
|
||||
closure_name,
|
||||
} => {
|
||||
|
@ -3466,7 +3473,7 @@ pub fn with_hole<'a>(
|
|||
tag_name,
|
||||
procs,
|
||||
layout_cache,
|
||||
args,
|
||||
std::vec::Vec::new(),
|
||||
arena,
|
||||
)
|
||||
}
|
||||
|
@ -3558,7 +3565,7 @@ pub fn with_hole<'a>(
|
|||
// this symbol is already defined; nothing to do
|
||||
}
|
||||
Field::Function(symbol, variable) => {
|
||||
stmt = reuse_function_symbol(
|
||||
stmt = specialize_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
|
@ -4114,7 +4121,7 @@ pub fn with_hole<'a>(
|
|||
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
|
||||
|
||||
if record_needs_specialization {
|
||||
stmt = reuse_function_symbol(
|
||||
stmt = specialize_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
|
@ -4804,8 +4811,7 @@ fn construct_closure_data<'a>(
|
|||
// symbols to be inlined when specializing the closure body elsewhere.
|
||||
for &&(symbol, var) in symbols {
|
||||
if procs.partial_exprs.contains(symbol) {
|
||||
result =
|
||||
reuse_function_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
|
||||
result = specialize_symbol(env, procs, layout_cache, Some(var), symbol, result, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6318,6 +6324,20 @@ fn store_pattern_help<'a>(
|
|||
|
||||
match can_pat {
|
||||
Identifier(symbol) => {
|
||||
if let Some((_, var)) = procs.partial_exprs.get(outer_symbol) {
|
||||
// It might be the case that symbol we're storing hasn't been reified to a value
|
||||
// yet, if it's polymorphic. Do that now.
|
||||
stmt = specialize_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
Some(var),
|
||||
*symbol,
|
||||
stmt,
|
||||
outer_symbol,
|
||||
);
|
||||
}
|
||||
|
||||
substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
|
||||
}
|
||||
Underscore => {
|
||||
|
@ -6769,9 +6789,8 @@ fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> {
|
|||
Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole)
|
||||
}
|
||||
|
||||
/// If the symbol is a function, make sure it is properly specialized
|
||||
// TODO: rename this now that we handle polymorphic non-function expressions too
|
||||
fn reuse_function_symbol<'a>(
|
||||
/// If the symbol is a function or polymorphic value, make sure it is properly specialized
|
||||
fn specialize_symbol<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
|
@ -6980,7 +6999,7 @@ fn assign_to_symbol<'a>(
|
|||
match can_reuse_symbol(env, procs, &loc_arg.value) {
|
||||
Imported(original) | LocalFunction(original) | UnspecializedExpr(original) => {
|
||||
// for functions we must make sure they are specialized correctly
|
||||
reuse_function_symbol(
|
||||
specialize_symbol(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
|
@ -7787,6 +7806,7 @@ fn from_can_pattern_help<'a>(
|
|||
match can_pattern {
|
||||
Underscore => Ok(Pattern::Underscore),
|
||||
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
|
||||
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
|
||||
IntLiteral(_, precision_var, _, int, _bound) => {
|
||||
match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) {
|
||||
IntOrFloat::Int(precision) => {
|
||||
|
@ -7830,6 +7850,7 @@ fn from_can_pattern_help<'a>(
|
|||
Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
|
||||
original_region: *region,
|
||||
shadow: ident.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
}),
|
||||
UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
|
||||
MalformedPattern(_problem, region) => {
|
||||
|
|
|
@ -468,7 +468,7 @@ impl<'a> UnionLayout<'a> {
|
|||
|
||||
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
|
||||
let allocation = match self {
|
||||
UnionLayout::NonRecursive(_) => unreachable!("not heap-allocated"),
|
||||
UnionLayout::NonRecursive(tags) => Self::tags_alignment_bytes(tags, target_info),
|
||||
UnionLayout::Recursive(tags) => Self::tags_alignment_bytes(tags, target_info),
|
||||
UnionLayout::NonNullableUnwrapped(field_layouts) => {
|
||||
Layout::struct_no_name_order(field_layouts).alignment_bytes(target_info)
|
||||
|
@ -1150,9 +1150,11 @@ impl<'a> Layout<'a> {
|
|||
}
|
||||
|
||||
pub fn allocation_alignment_bytes(&self, target_info: TargetInfo) -> u32 {
|
||||
let ptr_width = target_info.ptr_width() as u32;
|
||||
|
||||
match self {
|
||||
Layout::Builtin(builtin) => builtin.allocation_alignment_bytes(target_info),
|
||||
Layout::Struct { .. } => unreachable!("not heap-allocated"),
|
||||
Layout::Struct { .. } => self.alignment_bytes(target_info).max(ptr_width),
|
||||
Layout::Union(union_layout) => union_layout.allocation_alignment_bytes(target_info),
|
||||
Layout::LambdaSet(lambda_set) => lambda_set
|
||||
.runtime_representation()
|
||||
|
@ -1545,9 +1547,6 @@ impl<'a> Builtin<'a> {
|
|||
let ptr_width = target_info.ptr_width() as u32;
|
||||
|
||||
let allocation = match self {
|
||||
Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal => {
|
||||
unreachable!("not heap-allocated")
|
||||
}
|
||||
Builtin::Str => ptr_width,
|
||||
Builtin::Dict(k, v) => k
|
||||
.alignment_bytes(target_info)
|
||||
|
@ -1555,6 +1554,11 @@ impl<'a> Builtin<'a> {
|
|||
.max(ptr_width),
|
||||
Builtin::Set(k) => k.alignment_bytes(target_info).max(ptr_width),
|
||||
Builtin::List(e) => e.alignment_bytes(target_info).max(ptr_width),
|
||||
// The following are usually not heap-allocated, but they might be when inside a Box.
|
||||
Builtin::Int(int_width) => int_width.alignment_bytes(target_info).max(ptr_width),
|
||||
Builtin::Float(float_width) => float_width.alignment_bytes(target_info).max(ptr_width),
|
||||
Builtin::Bool => (core::mem::align_of::<bool>() as u32).max(ptr_width),
|
||||
Builtin::Decimal => IntWidth::I128.alignment_bytes(target_info).max(ptr_width),
|
||||
};
|
||||
|
||||
allocation.max(ptr_width)
|
||||
|
|
|
@ -170,6 +170,7 @@ enum FirstOrder {
|
|||
NumBytesToU32,
|
||||
NumShiftRightZfBy,
|
||||
NumIntCast,
|
||||
NumFloatCast,
|
||||
Eq,
|
||||
NotEq,
|
||||
And,
|
||||
|
|
|
@ -866,24 +866,26 @@ fn parse_defs_end<'a>(
|
|||
}
|
||||
Ok((_, loc_pattern, state)) => {
|
||||
// First let's check whether this is an ability definition.
|
||||
if let Loc {
|
||||
value:
|
||||
let opt_tag_and_args: Option<(&str, Region, &[Loc<Pattern>])> = match loc_pattern.value
|
||||
{
|
||||
Pattern::Apply(
|
||||
loc_name @ Loc {
|
||||
Loc {
|
||||
value: Pattern::GlobalTag(name),
|
||||
..
|
||||
region,
|
||||
},
|
||||
args,
|
||||
),
|
||||
..
|
||||
} = loc_pattern
|
||||
{
|
||||
) => Some((name, *region, args)),
|
||||
Pattern::GlobalTag(name) => Some((name, loc_pattern.region, &[])),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some((name, name_region, args)) = opt_tag_and_args {
|
||||
if let Ok((_, loc_has, state)) =
|
||||
loc_has_parser(min_indent).parse(arena, state.clone())
|
||||
{
|
||||
let (_, loc_def, state) = finish_parsing_ability_def(
|
||||
start_column,
|
||||
Loc::at(loc_name.region, name),
|
||||
Loc::at(name_region, name),
|
||||
args,
|
||||
loc_has,
|
||||
arena,
|
||||
|
|
|
@ -37,10 +37,6 @@ Defs(
|
|||
],
|
||||
},
|
||||
),
|
||||
],
|
||||
@35-71 SpaceBefore(
|
||||
Defs(
|
||||
[
|
||||
@35-68 Type(
|
||||
Ability {
|
||||
header: TypeHeader {
|
||||
|
@ -88,10 +84,4 @@ Defs(
|
|||
Newline,
|
||||
],
|
||||
),
|
||||
),
|
||||
[
|
||||
Newline,
|
||||
Newline,
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
|
@ -20,6 +20,14 @@ pub enum BadPattern {
|
|||
Unsupported(PatternType),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ShadowKind {
|
||||
Variable,
|
||||
Alias,
|
||||
Opaque,
|
||||
Ability,
|
||||
}
|
||||
|
||||
/// Problems that can occur in the course of canonicalization.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Problem {
|
||||
|
@ -33,9 +41,10 @@ pub enum Problem {
|
|||
PrecedenceProblem(PrecedenceProblem),
|
||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
UnsupportedPattern(BadPattern, Region),
|
||||
ShadowingInAnnotation {
|
||||
Shadowing {
|
||||
original_region: Region,
|
||||
shadow: Loc<Ident>,
|
||||
kind: ShadowKind,
|
||||
},
|
||||
CyclicAlias(Symbol, Region, Vec<Symbol>),
|
||||
BadRecursion(Vec<CycleEntry>),
|
||||
|
@ -95,6 +104,30 @@ pub enum Problem {
|
|||
region: Region,
|
||||
kind: ExtensionTypeKind,
|
||||
},
|
||||
AbilityHasTypeVariables {
|
||||
name: Symbol,
|
||||
variables_region: Region,
|
||||
},
|
||||
HasClauseIsNotAbility {
|
||||
region: Region,
|
||||
},
|
||||
IllegalHasClause {
|
||||
region: Region,
|
||||
},
|
||||
AbilityMemberMissingHasClause {
|
||||
member: Symbol,
|
||||
ability: Symbol,
|
||||
region: Region,
|
||||
},
|
||||
AbilityMemberBindsExternalAbility {
|
||||
member: Symbol,
|
||||
ability: Symbol,
|
||||
region: Region,
|
||||
},
|
||||
AliasUsesAbility {
|
||||
loc_name: Loc<Symbol>,
|
||||
ability: Symbol,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -157,6 +190,7 @@ pub enum RuntimeError {
|
|||
Shadowing {
|
||||
original_region: Region,
|
||||
shadow: Loc<Ident>,
|
||||
kind: ShadowKind,
|
||||
},
|
||||
InvalidOptionalValue {
|
||||
field_name: Lowercase,
|
||||
|
|
|
@ -5319,6 +5319,21 @@ mod solve_expr {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_float() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
toF32: Num.toF32,
|
||||
toF64: Num.toF64,
|
||||
}
|
||||
"#
|
||||
),
|
||||
r#"{ toF32 : Num * -> F32, toF64 : Num * -> F64 }"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opaque_wrap_infer() {
|
||||
infer_eq_without_problem(
|
||||
|
@ -5682,4 +5697,17 @@ mod solve_expr {
|
|||
"Result I64 [ InvalidNumStr, ListWasEmpty ]*",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lots_of_type_variables() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
fun = \a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,bb -> {a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,aa,bb}
|
||||
fun
|
||||
"#
|
||||
),
|
||||
"a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, aa, bb -> { a : a, aa : aa, b : b, bb : bb, c : c, d : d, e : e, f : f, g : g, h : h, i : i, j : j, k : k, l : l, m : m, n : n, o : o, p : p, q : q, r : r, s : s, t : t, u : u, v : v, w : w, x : x, y : y, z : z }",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2248,7 +2248,7 @@ fn max_u8() {
|
|||
);
|
||||
}
|
||||
|
||||
macro_rules! to_int_tests {
|
||||
macro_rules! num_conversion_tests {
|
||||
($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [ $($support_gen:literal),* ])? )*))*) => {$($(
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))]
|
||||
|
@ -2259,7 +2259,7 @@ macro_rules! to_int_tests {
|
|||
)*)*}
|
||||
}
|
||||
|
||||
to_int_tests! {
|
||||
num_conversion_tests! {
|
||||
"Num.toI8", i8, (
|
||||
to_i8_same_width, "15u8", 15, ["gen-wasm"]
|
||||
to_i8_truncate, "115i32", 115, ["gen-wasm"]
|
||||
|
@ -2320,6 +2320,36 @@ to_int_tests! {
|
|||
to_nat_truncate, "115i128", 115
|
||||
to_nat_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128
|
||||
)
|
||||
"Num.toF32", f32, (
|
||||
to_f32_from_i8, "15i8", 15.0
|
||||
to_f32_from_i16, "15i16", 15.0
|
||||
to_f32_from_i32, "15i32", 15.0
|
||||
to_f32_from_i64, "15i64", 15.0
|
||||
to_f32_from_i128, "15i128", 15.0
|
||||
to_f32_from_u8, "15u8", 15.0
|
||||
to_f32_from_u16, "15u16", 15.0
|
||||
to_f32_from_u32, "15u32", 15.0
|
||||
to_f32_from_u64, "15u64", 15.0
|
||||
to_f32_from_u128, "15u128", 15.0
|
||||
to_f32_from_nat, "15nat", 15.0
|
||||
to_f32_from_f32, "1.5f32", 1.5
|
||||
to_f32_from_f64, "1.5f64", 1.5
|
||||
)
|
||||
"Num.toF64", f64, (
|
||||
to_f64_from_i8, "15i8", 15.0
|
||||
to_f64_from_i16, "15i16", 15.0
|
||||
to_f64_from_i32, "15i32", 15.0
|
||||
to_f64_from_i64, "15i64", 15.0
|
||||
to_f64_from_i128, "15i128", 15.0
|
||||
to_f64_from_u8, "15u8", 15.0
|
||||
to_f64_from_u16, "15u16", 15.0
|
||||
to_f64_from_u32, "15u32", 15.0
|
||||
to_f64_from_u64, "15u64", 15.0
|
||||
to_f64_from_u128, "15u128", 15.0
|
||||
to_f64_from_nat, "15nat", 15.0
|
||||
to_f64_from_f32, "1.5f32", 1.5
|
||||
to_f64_from_f64, "1.5f64", 1.5
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! to_int_checked_tests {
|
||||
|
|
|
@ -3275,3 +3275,47 @@ fn box_and_unbox_string() {
|
|||
RocStr
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn box_and_unbox_num() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Box.unbox (Box.box (123u8))
|
||||
"#
|
||||
),
|
||||
123,
|
||||
u8
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn box_and_unbox_record() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
Box.unbox (Box.box { a: 15u8, b: 27u8 })
|
||||
"#
|
||||
),
|
||||
(15, 27),
|
||||
(u8, u8)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn box_and_unbox_tag_union() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
v : [ A U8, B U8 ] # usually stack allocated
|
||||
v = B 27u8
|
||||
Box.unbox (Box.box v)
|
||||
"#
|
||||
),
|
||||
(27, 1),
|
||||
(u8, u8)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1587,3 +1587,19 @@ fn str_to_dec() {
|
|||
RocDec
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn issue_2811() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
x = Command { tool: "bash" }
|
||||
Command c = x
|
||||
c.tool
|
||||
"#
|
||||
),
|
||||
RocStr::from("bash"),
|
||||
RocStr
|
||||
);
|
||||
}
|
||||
|
|
3
compiler/test_mono/generated/issue_2811.txt
Normal file
3
compiler/test_mono/generated/issue_2811.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
procedure Test.0 ():
|
||||
let Test.6 : Str = "bash";
|
||||
ret Test.6;
|
|
@ -1282,6 +1282,17 @@ fn issue_2583_specialize_errors_behind_unified_branches() {
|
|||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn issue_2811() {
|
||||
indoc!(
|
||||
r#"
|
||||
x = Command { tool: "bash" }
|
||||
Command c = x
|
||||
c.tool
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
// #[ignore]
|
||||
// #[mono_test]
|
||||
// fn static_str_closure() {
|
||||
|
|
|
@ -1856,6 +1856,7 @@ pub enum Problem {
|
|||
},
|
||||
InvalidModule,
|
||||
SolvedTypeError,
|
||||
HasClauseIsNotAbility(Region),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
|
@ -2307,15 +2308,15 @@ static THE_LETTER_A: u32 = 'a' as u32;
|
|||
pub fn name_type_var(letters_used: u32, taken: &mut MutSet<Lowercase>) -> (Lowercase, u32) {
|
||||
// TODO we should arena-allocate this String,
|
||||
// so all the strings in the entire pass only require ~1 allocation.
|
||||
let generated_name = if letters_used < 26 {
|
||||
// This should generate "a", then "b", etc.
|
||||
std::char::from_u32(THE_LETTER_A + letters_used)
|
||||
.unwrap_or_else(|| panic!("Tried to convert {} to a char", THE_LETTER_A + letters_used))
|
||||
.to_string()
|
||||
.into()
|
||||
} else {
|
||||
panic!("TODO generate aa, ab, ac, ...");
|
||||
};
|
||||
let mut generated_name = String::with_capacity((letters_used as usize) / 26 + 1);
|
||||
|
||||
let mut remaining = letters_used as i32;
|
||||
while remaining >= 0 {
|
||||
generated_name.push(std::char::from_u32(THE_LETTER_A + ((remaining as u32) % 26)).unwrap());
|
||||
remaining -= 26;
|
||||
}
|
||||
|
||||
let generated_name = generated_name.into();
|
||||
|
||||
if taken.contains(&generated_name) {
|
||||
// If the generated name is already taken, try again.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
The Roc programming language is named after [a mythical bird](https://en.wikipedia.org/wiki/Roc_(mythology)).
|
||||
|
||||
That’s why the logo is a bird. It’s specifically an [*origami* bird](https://youtu.be/9gni1t1k1uY) as a homage
|
||||
That’s why the logo is a bird. It’s specifically an [*origami* bird](https://youtu.be/9gni1t1k1uY) as an homage
|
||||
to [Elm](https://elm-lang.org/)’s tangram logo.
|
||||
|
||||
Roc is a direct descendant of Elm. The languages are similar, but not the same.
|
||||
|
|
|
@ -2,7 +2,7 @@ use roc_collections::all::MutSet;
|
|||
use roc_module::ident::{Ident, Lowercase, ModuleName};
|
||||
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
|
||||
use roc_problem::can::{
|
||||
BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError,
|
||||
BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, ShadowKind,
|
||||
};
|
||||
use roc_region::all::{LineColumn, LineColumnRegion, LineInfo, Loc, Region};
|
||||
use roc_types::types::AliasKind;
|
||||
|
@ -38,6 +38,12 @@ const OPAQUE_DECLARED_OUTSIDE_SCOPE: &str = "OPAQUE TYPE DECLARED OUTSIDE SCOPE"
|
|||
const OPAQUE_NOT_APPLIED: &str = "OPAQUE TYPE NOT APPLIED";
|
||||
const OPAQUE_OVER_APPLIED: &str = "OPAQUE TYPE APPLIED TO TOO MANY ARGS";
|
||||
const INVALID_EXTENSION_TYPE: &str = "INVALID_EXTENSION_TYPE";
|
||||
const ABILITY_HAS_TYPE_VARIABLES: &str = "ABILITY HAS TYPE VARIABLES";
|
||||
const HAS_CLAUSE_IS_NOT_AN_ABILITY: &str = "HAS CLAUSE IS NOT AN ABILITY";
|
||||
const ALIAS_USES_ABILITY: &str = "ALIAS USES ABILITY";
|
||||
const ILLEGAL_HAS_CLAUSE: &str = "ILLEGAL HAS CLAUSE";
|
||||
const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAUSE";
|
||||
const ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE: &str = "ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE";
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
|
@ -213,11 +219,12 @@ pub fn can_problem<'b>(
|
|||
title = SYNTAX_PROBLEM.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
Problem::ShadowingInAnnotation {
|
||||
Problem::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
kind,
|
||||
} => {
|
||||
doc = report_shadowing(alloc, lines, original_region, shadow);
|
||||
doc = report_shadowing(alloc, lines, original_region, shadow, kind);
|
||||
|
||||
title = DUPLICATE_NAME.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
|
@ -562,6 +569,144 @@ pub fn can_problem<'b>(
|
|||
title = INVALID_EXTENSION_TYPE.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::AbilityHasTypeVariables {
|
||||
name,
|
||||
variables_region,
|
||||
} => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The definition of the "),
|
||||
alloc.symbol_unqualified(name),
|
||||
alloc.reflow(" ability includes type variables:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(variables_region)),
|
||||
alloc.reflow(
|
||||
"Abilities cannot depend on type variables, but their member values can!",
|
||||
),
|
||||
]);
|
||||
title = ABILITY_HAS_TYPE_VARIABLES.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::HasClauseIsNotAbility {
|
||||
region: clause_region,
|
||||
} => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.reflow(r#"The type referenced in this "has" clause is not an ability:"#),
|
||||
alloc.region(lines.convert_region(clause_region)),
|
||||
]);
|
||||
title = HAS_CLAUSE_IS_NOT_AN_ABILITY.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::AliasUsesAbility {
|
||||
loc_name: Loc {
|
||||
region,
|
||||
value: name,
|
||||
},
|
||||
ability,
|
||||
} => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The definition of the "),
|
||||
alloc.symbol_unqualified(name),
|
||||
alloc.reflow(" aliases references the ability "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(":"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Abilities are not types, but you can add an ability constraint to a type variable "),
|
||||
alloc.type_variable("a".into()),
|
||||
alloc.reflow(" by writing"),
|
||||
]),
|
||||
alloc.type_block(alloc.concat(vec![
|
||||
alloc.reflow("| a has "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
])),
|
||||
alloc.reflow(" at the end of the type."),
|
||||
]);
|
||||
title = ALIAS_USES_ABILITY.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::IllegalHasClause { region } => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("A "),
|
||||
alloc.keyword("has"),
|
||||
alloc.reflow(" clause is not allowed here:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
alloc.concat(vec![
|
||||
alloc.keyword("has"),
|
||||
alloc.reflow(" clauses can only be specified on the top-level type annotation of an ability member."),
|
||||
]),
|
||||
]);
|
||||
title = ILLEGAL_HAS_CLAUSE.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::AbilityMemberMissingHasClause {
|
||||
member,
|
||||
ability,
|
||||
region,
|
||||
} => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The definition of the ability member "),
|
||||
alloc.symbol_unqualified(member),
|
||||
alloc.reflow(" does not include a "),
|
||||
alloc.keyword("has"),
|
||||
alloc.reflow(" clause binding a type variable to the ability "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(":"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Ability members must include a "),
|
||||
alloc.keyword("has"),
|
||||
alloc.reflow(" clause binding a type variable to an ability, like"),
|
||||
]),
|
||||
alloc.type_block(alloc.concat(vec![
|
||||
alloc.type_variable("a".into()),
|
||||
alloc.space(),
|
||||
alloc.keyword("has"),
|
||||
alloc.space(),
|
||||
alloc.symbol_unqualified(ability),
|
||||
])),
|
||||
alloc.concat(vec![alloc.reflow(
|
||||
"Otherwise, the function does not need to be part of the ability!",
|
||||
)]),
|
||||
]);
|
||||
title = ABILITY_MEMBER_MISSING_HAS_CLAUSE.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::AbilityMemberBindsExternalAbility {
|
||||
member,
|
||||
ability,
|
||||
region,
|
||||
} => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("The definition of the ability member "),
|
||||
alloc.symbol_unqualified(member),
|
||||
alloc.reflow(" includes a has clause binding an ability it is not a part of:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
alloc.reflow("Currently, ability members can only bind variables to the ability they are a part of."),
|
||||
alloc.concat(vec![
|
||||
alloc.hint(""),
|
||||
alloc.reflow("Did you mean to bind the "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(" ability instead?"),
|
||||
]),
|
||||
]);
|
||||
title = ABILITY_MEMBER_HAS_EXTRANEOUS_HAS_CLAUSE.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
};
|
||||
|
||||
Report {
|
||||
|
@ -940,8 +1085,14 @@ fn report_shadowing<'b>(
|
|||
lines: &LineInfo,
|
||||
original_region: Region,
|
||||
shadow: Loc<Ident>,
|
||||
kind: ShadowKind,
|
||||
) -> RocDocBuilder<'b> {
|
||||
let line = r#"Since these variables have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
|
||||
let what = match kind {
|
||||
ShadowKind::Variable => "variables",
|
||||
ShadowKind::Alias => "aliases",
|
||||
ShadowKind::Opaque => "opaques",
|
||||
ShadowKind::Ability => "abilities",
|
||||
};
|
||||
|
||||
alloc.stack(vec![
|
||||
alloc
|
||||
|
@ -951,7 +1102,11 @@ fn report_shadowing<'b>(
|
|||
alloc.region(lines.convert_region(original_region)),
|
||||
alloc.reflow("But then it's defined a second time here:"),
|
||||
alloc.region(lines.convert_region(shadow.region)),
|
||||
alloc.reflow(line),
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("Since these "),
|
||||
alloc.reflow(what),
|
||||
alloc.reflow(" have the same name, it's easy to use the wrong one on accident. Give one of them a new name."),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -978,8 +1133,9 @@ fn pretty_runtime_error<'b>(
|
|||
RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow,
|
||||
kind,
|
||||
} => {
|
||||
doc = report_shadowing(alloc, lines, original_region, shadow);
|
||||
doc = report_shadowing(alloc, lines, original_region, shadow, kind);
|
||||
title = DUPLICATE_NAME;
|
||||
}
|
||||
|
||||
|
|
|
@ -430,8 +430,8 @@ mod test_reporting {
|
|||
3│ Booly : [ Yes, No, Maybe ]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Since these variables have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name.
|
||||
Since these aliases have the same name, it's easy to use the wrong one
|
||||
on accident. Give one of them a new name.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
|
@ -8896,4 +8896,340 @@ I need all branches in an `if` to have the same type!
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_type_parameter_in_ability() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Hash a b c has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── ABILITY HAS TYPE VARIABLES ──────────────────────────────────────────────────
|
||||
|
||||
The definition of the `Hash` ability includes type variables:
|
||||
|
||||
1│ Hash a b c has
|
||||
^^^^^
|
||||
|
||||
Abilities cannot depend on type variables, but their member values
|
||||
can!
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`Hash` is not used anywhere in your code.
|
||||
|
||||
1│ Hash a b c has
|
||||
^^^^
|
||||
|
||||
If you didn't intend on using `Hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_in_has_clause() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Hash has hash : a, b -> U64 | a has Hash, b has Bool
|
||||
|
||||
1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── HAS CLAUSE IS NOT AN ABILITY ────────────────────────────────────────────────
|
||||
|
||||
The type referenced in this "has" clause is not an ability:
|
||||
|
||||
1│ Hash has hash : a, b -> U64 | a has Hash, b has Bool
|
||||
^^^^
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`hash` is not used anywhere in your code.
|
||||
|
||||
1│ Hash has hash : a, b -> U64 | a has Hash, b has Bool
|
||||
^^^^
|
||||
|
||||
If you didn't intend on using `hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shadowed_type_variable_in_has_clause() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
|
||||
1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||||
|
||||
The `a` name is first defined here:
|
||||
|
||||
1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
^^^^^^^^^
|
||||
|
||||
But then it's defined a second time here:
|
||||
|
||||
1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
^^^^^^^^^
|
||||
|
||||
Since these variables have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`ab1` is not used anywhere in your code.
|
||||
|
||||
1│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1
|
||||
^^^
|
||||
|
||||
If you didn't intend on using `ab1` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_using_ability() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ability has ab : a -> {} | a has Ability
|
||||
|
||||
Alias : Ability
|
||||
|
||||
a : Alias
|
||||
a
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── ALIAS USES ABILITY ──────────────────────────────────────────────────────────
|
||||
|
||||
The definition of the `Alias` aliases references the ability `Ability`:
|
||||
|
||||
3│ Alias : Ability
|
||||
^^^^^
|
||||
|
||||
Abilities are not types, but you can add an ability constraint to a
|
||||
type variable `a` by writing
|
||||
|
||||
| a has Ability
|
||||
|
||||
at the end of the type.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`ab` is not used anywhere in your code.
|
||||
|
||||
1│ Ability has ab : a -> {} | a has Ability
|
||||
^^
|
||||
|
||||
If you didn't intend on using `ab` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_shadows_ability() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ability has ab : a -> U64 | a has Ability
|
||||
|
||||
Ability has ab1 : a -> U64 | a has Ability
|
||||
|
||||
1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── DUPLICATE NAME ──────────────────────────────────────────────────────────────
|
||||
|
||||
The `Ability` name is first defined here:
|
||||
|
||||
1│ Ability has ab : a -> U64 | a has Ability
|
||||
^^^^^^^
|
||||
|
||||
But then it's defined a second time here:
|
||||
|
||||
3│ Ability has ab1 : a -> U64 | a has Ability
|
||||
^^^^^^^
|
||||
|
||||
Since these abilities have the same name, it's easy to use the wrong
|
||||
one on accident. Give one of them a new name.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`ab` is not used anywhere in your code.
|
||||
|
||||
1│ Ability has ab : a -> U64 | a has Ability
|
||||
^^
|
||||
|
||||
If you didn't intend on using `ab` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_member_does_not_bind_ability() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Ability has ab : {} -> {}
|
||||
|
||||
1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────────────────────────────
|
||||
|
||||
The definition of the ability member `ab` does not include a `has` clause
|
||||
binding a type variable to the ability `Ability`:
|
||||
|
||||
1│ Ability has ab : {} -> {}
|
||||
^^
|
||||
|
||||
Ability members must include a `has` clause binding a type variable to
|
||||
an ability, like
|
||||
|
||||
a has Ability
|
||||
|
||||
Otherwise, the function does not need to be part of the ability!
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`Ability` is not used anywhere in your code.
|
||||
|
||||
1│ Ability has ab : {} -> {}
|
||||
^^^^^^^
|
||||
|
||||
If you didn't intend on using `Ability` then remove it so future readers
|
||||
of your code don't wonder why it is there.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`ab` is not used anywhere in your code.
|
||||
|
||||
1│ Ability has ab : {} -> {}
|
||||
^^
|
||||
|
||||
If you didn't intend on using `ab` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_member_binds_extra_ability() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Eq has eq : a, a -> Bool | a has Eq
|
||||
Hash has hash : a, b -> U64 | a has Eq, b has Hash
|
||||
|
||||
1
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── ABILITY MEMBER HAS EXTRANEOUS HAS CLAUSE ────────────────────────────────────
|
||||
|
||||
The definition of the ability member `hash` includes a has clause
|
||||
binding an ability it is not a part of:
|
||||
|
||||
2│ Hash has hash : a, b -> U64 | a has Eq, b has Hash
|
||||
^^^^^^^^
|
||||
|
||||
Currently, ability members can only bind variables to the ability they
|
||||
are a part of.
|
||||
|
||||
Hint: Did you mean to bind the `Hash` ability instead?
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`eq` is not used anywhere in your code.
|
||||
|
||||
1│ Eq has eq : a, a -> Bool | a has Eq
|
||||
^^
|
||||
|
||||
If you didn't intend on using `eq` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`hash` is not used anywhere in your code.
|
||||
|
||||
2│ Hash has hash : a, b -> U64 | a has Eq, b has Hash
|
||||
^^^^
|
||||
|
||||
If you didn't intend on using `hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_clause_outside_of_ability() {
|
||||
report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
Hash has hash : a -> U64 | a has Hash
|
||||
|
||||
f : a -> U64 | a has Hash
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── ILLEGAL HAS CLAUSE ──────────────────────────────────────────────────────────
|
||||
|
||||
A `has` clause is not allowed here:
|
||||
|
||||
3│ f : a -> U64 | a has Hash
|
||||
^^^^^^^^^^
|
||||
|
||||
`has` clauses can only be specified on the top-level type annotation of
|
||||
an ability member.
|
||||
|
||||
── UNUSED DEFINITION ───────────────────────────────────────────────────────────
|
||||
|
||||
`hash` is not used anywhere in your code.
|
||||
|
||||
1│ Hash has hash : a -> U64 | a has Hash
|
||||
^^^^
|
||||
|
||||
If you didn't intend on using `hash` then remove it so future readers of
|
||||
your code don't wonder why it is there.
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue