mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 23:04:49 +00:00
Merge pull request #2838 from rtfeldman/abilities-typechecking
Inference and checking for abilities
This commit is contained in:
commit
4ea4aa4708
46 changed files with 2432 additions and 530 deletions
|
@ -1,55 +1,63 @@
|
|||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
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>,
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AbilityMemberData {
|
||||
pub parent_ability: Symbol,
|
||||
pub signature: Type,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
/// A particular specialization of an ability member.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MemberSpecialization {
|
||||
pub symbol: Symbol,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq)]
|
||||
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)>,
|
||||
/// Map of symbols that specialize an ability member to the root ability symbol name.
|
||||
/// For example, for the program
|
||||
/// Hash has hash : a -> U64 | a has Hash
|
||||
/// ^^^^ gets the symbol "#hash"
|
||||
/// hash = \@Id n -> n
|
||||
/// ^^^^ gets the symbol "#hash1"
|
||||
///
|
||||
/// We keep the mapping #hash1->#hash
|
||||
specialization_to_root: MutMap<Symbol, Symbol>,
|
||||
|
||||
/// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability
|
||||
/// member `member`, to the exact symbol that implements the ability.
|
||||
declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>,
|
||||
}
|
||||
|
||||
impl AbilitiesStore {
|
||||
pub fn register_ability(
|
||||
&mut self,
|
||||
ability: Symbol,
|
||||
members: Vec<(Symbol, Type, Vec<HasClause>)>,
|
||||
) {
|
||||
/// Records the definition of an ability, including its members.
|
||||
pub fn register_ability(&mut self, ability: Symbol, members: Vec<(Symbol, Region, Type)>) {
|
||||
let mut members_vec = Vec::with_capacity(members.len());
|
||||
for (member, signature, bound_has_clauses) in members.into_iter() {
|
||||
for (member, region, signature) in members.into_iter() {
|
||||
members_vec.push(member);
|
||||
let old_member = self.ability_members.insert(
|
||||
member,
|
||||
AbilityMemberData {
|
||||
parent_ability: ability,
|
||||
signature,
|
||||
bound_has_clauses,
|
||||
region,
|
||||
},
|
||||
);
|
||||
debug_assert!(old_member.is_none(), "Replacing existing member definition");
|
||||
|
@ -61,14 +69,83 @@ impl AbilitiesStore {
|
|||
);
|
||||
}
|
||||
|
||||
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");
|
||||
/// Records a specialization of `ability_member` with specialized type `implementing_type`.
|
||||
/// Entries via this function are considered a source of truth. It must be ensured that a
|
||||
/// specialization is validated before being registered here.
|
||||
pub fn register_specialization_for_type(
|
||||
&mut self,
|
||||
ability_member: Symbol,
|
||||
implementing_type: Symbol,
|
||||
specialization: MemberSpecialization,
|
||||
) {
|
||||
let old_spec = self
|
||||
.declared_specializations
|
||||
.insert((ability_member, implementing_type), specialization);
|
||||
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
|
||||
}
|
||||
|
||||
/// Checks if `name` is a root ability member symbol name.
|
||||
/// Note that this will return `false` for specializations of an ability member, which have
|
||||
/// different symbols from the root.
|
||||
pub fn is_ability_member_name(&self, name: Symbol) -> bool {
|
||||
self.ability_members.contains_key(&name)
|
||||
}
|
||||
|
||||
/// Returns information about all known ability members and their root symbols.
|
||||
pub fn root_ability_members(&self) -> &MutMap<Symbol, AbilityMemberData> {
|
||||
&self.ability_members
|
||||
}
|
||||
|
||||
/// Records that the symbol `specializing_symbol` claims to specialize `ability_member`; for
|
||||
/// example the symbol of `hash : Id -> U64` specializing `hash : a -> U64 | a has Hash`.
|
||||
pub fn register_specializing_symbol(
|
||||
&mut self,
|
||||
specializing_symbol: Symbol,
|
||||
ability_member: Symbol,
|
||||
) {
|
||||
self.specialization_to_root
|
||||
.insert(specializing_symbol, ability_member);
|
||||
}
|
||||
|
||||
/// Returns whether a symbol is declared to specialize an ability member.
|
||||
pub fn is_specialization_name(&self, symbol: Symbol) -> bool {
|
||||
self.specialization_to_root.contains_key(&symbol)
|
||||
}
|
||||
|
||||
/// Finds the symbol name and ability member definition for a symbol specializing the ability
|
||||
/// member, if it specializes any.
|
||||
/// For example, suppose `hash : Id -> U64` has symbol #hash1 and specializes
|
||||
/// `hash : a -> U64 | a has Hash` with symbol #hash. Calling this with #hash1 would retrieve
|
||||
/// the ability member data for #hash.
|
||||
pub fn root_name_and_def(
|
||||
&self,
|
||||
specializing_symbol: Symbol,
|
||||
) -> Option<(Symbol, &AbilityMemberData)> {
|
||||
let root_symbol = self.specialization_to_root.get(&specializing_symbol)?;
|
||||
debug_assert!(self.ability_members.contains_key(root_symbol));
|
||||
let root_data = self.ability_members.get(root_symbol).unwrap();
|
||||
Some((*root_symbol, root_data))
|
||||
}
|
||||
|
||||
/// Finds the ability member definition for a member name.
|
||||
pub fn member_def(&self, member: Symbol) -> Option<&AbilityMemberData> {
|
||||
self.ability_members.get(&member)
|
||||
}
|
||||
|
||||
/// Returns an iterator over pairs (ability member, type) specifying that
|
||||
/// "ability member" has a specialization with type "type".
|
||||
pub fn get_known_specializations(&self) -> impl Iterator<Item = (Symbol, Symbol)> + '_ {
|
||||
self.declared_specializations.keys().copied()
|
||||
}
|
||||
|
||||
/// Retrieves the specialization of `member` for `typ`, if it exists.
|
||||
pub fn get_specialization(&self, member: Symbol, typ: Symbol) -> Option<MemberSpecialization> {
|
||||
self.declared_specializations.get(&(member, typ)).copied()
|
||||
}
|
||||
|
||||
/// Returns pairs of (type, ability member) specifying that "ability member" has a
|
||||
/// specialization with type "type".
|
||||
pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> {
|
||||
self.members_of_ability.get(&ability).map(|v| v.as_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,22 @@ pub struct Annotation {
|
|||
pub aliases: SendMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum NamedOrAbleVariable<'a> {
|
||||
Named(&'a NamedVariable),
|
||||
Able(&'a AbleVariable),
|
||||
}
|
||||
|
||||
impl<'a> NamedOrAbleVariable<'a> {
|
||||
pub fn first_seen(&self) -> Region {
|
||||
match self {
|
||||
NamedOrAbleVariable::Named(nv) => nv.first_seen,
|
||||
NamedOrAbleVariable::Able(av) => av.first_seen,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A named type variable, not bound to an ability.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct NamedVariable {
|
||||
pub variable: Variable,
|
||||
|
@ -27,21 +43,40 @@ pub struct NamedVariable {
|
|||
pub first_seen: Region,
|
||||
}
|
||||
|
||||
/// A type variable bound to an ability, like "a has Hash".
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct AbleVariable {
|
||||
pub variable: Variable,
|
||||
pub name: Lowercase,
|
||||
pub ability: Symbol,
|
||||
// NB: there may be multiple occurrences of a variable
|
||||
pub first_seen: Region,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Default)]
|
||||
pub struct IntroducedVariables {
|
||||
pub wildcards: Vec<Loc<Variable>>,
|
||||
pub lambda_sets: Vec<Variable>,
|
||||
pub inferred: Vec<Loc<Variable>>,
|
||||
pub named: Vec<NamedVariable>,
|
||||
pub able: Vec<AbleVariable>,
|
||||
pub host_exposed_aliases: MutMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
impl IntroducedVariables {
|
||||
#[inline(always)]
|
||||
fn debug_assert_not_already_present(&self, var: Variable) {
|
||||
debug_assert!((self.wildcards.iter().map(|v| &v.value))
|
||||
.chain(self.lambda_sets.iter())
|
||||
.chain(self.inferred.iter().map(|v| &v.value))
|
||||
.chain(self.named.iter().map(|nv| &nv.variable))
|
||||
.chain(self.able.iter().map(|av| &av.variable))
|
||||
.chain(self.host_exposed_aliases.values())
|
||||
.all(|&v| v != var));
|
||||
}
|
||||
|
||||
pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
|
||||
debug_assert!(!self
|
||||
.named
|
||||
.iter()
|
||||
.any(|nv| nv.name == name || nv.variable == var.value));
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
|
||||
let named_variable = NamedVariable {
|
||||
name,
|
||||
|
@ -52,19 +87,36 @@ impl IntroducedVariables {
|
|||
self.named.push(named_variable);
|
||||
}
|
||||
|
||||
pub fn insert_able(&mut self, name: Lowercase, var: Loc<Variable>, ability: Symbol) {
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
|
||||
let able_variable = AbleVariable {
|
||||
name,
|
||||
ability,
|
||||
variable: var.value,
|
||||
first_seen: var.region,
|
||||
};
|
||||
|
||||
self.able.push(able_variable);
|
||||
}
|
||||
|
||||
pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
self.wildcards.push(var);
|
||||
}
|
||||
|
||||
pub fn insert_inferred(&mut self, var: Loc<Variable>) {
|
||||
self.debug_assert_not_already_present(var.value);
|
||||
self.inferred.push(var);
|
||||
}
|
||||
|
||||
fn insert_lambda_set(&mut self, var: Variable) {
|
||||
self.debug_assert_not_already_present(var);
|
||||
self.lambda_sets.push(var);
|
||||
}
|
||||
|
||||
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
|
||||
self.debug_assert_not_already_present(var);
|
||||
self.host_exposed_aliases.insert(symbol, var);
|
||||
}
|
||||
|
||||
|
@ -78,6 +130,10 @@ impl IntroducedVariables {
|
|||
self.named.extend(other.named.iter().cloned());
|
||||
self.named.sort();
|
||||
self.named.dedup();
|
||||
|
||||
self.able.extend(other.able.iter().cloned());
|
||||
self.able.sort();
|
||||
self.able.dedup();
|
||||
}
|
||||
|
||||
pub fn union_owned(&mut self, other: Self) {
|
||||
|
@ -91,22 +147,26 @@ impl IntroducedVariables {
|
|||
self.named.dedup();
|
||||
}
|
||||
|
||||
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
|
||||
self.named
|
||||
pub fn var_by_name(&self, name: &Lowercase) -> Option<Variable> {
|
||||
(self.named.iter().map(|nv| (&nv.name, nv.variable)))
|
||||
.chain(self.able.iter().map(|av| (&av.name, av.variable)))
|
||||
.find(|(cand, _)| cand == &name)
|
||||
.map(|(_, var)| var)
|
||||
}
|
||||
|
||||
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<NamedOrAbleVariable> {
|
||||
if let Some(nav) = self
|
||||
.named
|
||||
.iter()
|
||||
.find(|nv| &nv.name == name)
|
||||
.map(|nv| &nv.variable)
|
||||
}
|
||||
|
||||
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
|
||||
self.named
|
||||
.map(NamedOrAbleVariable::Named)
|
||||
{
|
||||
return Some(nav);
|
||||
}
|
||||
self.able
|
||||
.iter()
|
||||
.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)
|
||||
.find(|av| &av.name == name)
|
||||
.map(NamedOrAbleVariable::Able)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,13 +207,6 @@ 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,
|
||||
|
@ -161,16 +214,17 @@ pub fn canonicalize_annotation_with_possible_clauses(
|
|||
region: Region,
|
||||
var_store: &mut VarStore,
|
||||
abilities_in_scope: &[Symbol],
|
||||
) -> (Annotation, Vec<Loc<HasClause>>) {
|
||||
) -> Annotation {
|
||||
let mut introduced_variables = IntroducedVariables::default();
|
||||
let mut references = MutSet::default();
|
||||
let mut aliases = SendMap::default();
|
||||
|
||||
let (annotation, region, clauses) = match annotation {
|
||||
let (annotation, region) = match annotation {
|
||||
TypeAnnotation::Where(annotation, clauses) => {
|
||||
let mut can_clauses = Vec::with_capacity(clauses.len());
|
||||
// Add each "has" clause. The association of a variable to an ability will be saved on
|
||||
// `introduced_variables`, which we'll process later.
|
||||
for clause in clauses.iter() {
|
||||
match canonicalize_has_clause(
|
||||
let opt_err = canonicalize_has_clause(
|
||||
env,
|
||||
scope,
|
||||
var_store,
|
||||
|
@ -178,24 +232,19 @@ pub fn canonicalize_annotation_with_possible_clauses(
|
|||
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,
|
||||
)
|
||||
}
|
||||
};
|
||||
);
|
||||
if let Err(err_type) = opt_err {
|
||||
return Annotation {
|
||||
typ: err_type,
|
||||
introduced_variables,
|
||||
references,
|
||||
aliases,
|
||||
};
|
||||
}
|
||||
}
|
||||
(&annotation.value, annotation.region, can_clauses)
|
||||
(&annotation.value, annotation.region)
|
||||
}
|
||||
annot => (annot, region, vec![]),
|
||||
annot => (annot, region),
|
||||
};
|
||||
|
||||
let typ = can_annotation_help(
|
||||
|
@ -209,14 +258,12 @@ pub fn canonicalize_annotation_with_possible_clauses(
|
|||
&mut references,
|
||||
);
|
||||
|
||||
let annot = Annotation {
|
||||
Annotation {
|
||||
typ,
|
||||
introduced_variables,
|
||||
references,
|
||||
aliases,
|
||||
};
|
||||
|
||||
(annot, clauses)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_apply_symbol(
|
||||
|
@ -502,7 +549,7 @@ fn can_annotation_help(
|
|||
let name = Lowercase::from(*v);
|
||||
|
||||
match introduced_variables.var_by_name(&name) {
|
||||
Some(var) => Type::Variable(*var),
|
||||
Some(var) => Type::Variable(var),
|
||||
None => {
|
||||
let var = var_store.fresh();
|
||||
|
||||
|
@ -566,8 +613,8 @@ fn can_annotation_help(
|
|||
let var_name = Lowercase::from(var);
|
||||
|
||||
if let Some(var) = introduced_variables.var_by_name(&var_name) {
|
||||
vars.push((var_name.clone(), Type::Variable(*var)));
|
||||
lowercase_vars.push(Loc::at(loc_var.region, (var_name, *var)));
|
||||
vars.push((var_name.clone(), Type::Variable(var)));
|
||||
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
|
||||
} else {
|
||||
let var = var_store.fresh();
|
||||
|
||||
|
@ -799,7 +846,7 @@ fn canonicalize_has_clause(
|
|||
clause: &Loc<roc_parse::ast::HasClause<'_>>,
|
||||
abilities_in_scope: &[Symbol],
|
||||
references: &mut MutSet<Symbol>,
|
||||
) -> Result<HasClause, Type> {
|
||||
) -> Result<(), Type> {
|
||||
let Loc {
|
||||
region,
|
||||
value: roc_parse::ast::HasClause { var, ability },
|
||||
|
@ -836,25 +883,21 @@ fn canonicalize_has_clause(
|
|||
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,
|
||||
original_region: shadowing.first_seen(),
|
||||
shadow: shadow.clone(),
|
||||
kind: ShadowKind::Variable,
|
||||
});
|
||||
return Err(Type::Erroneous(Problem::Shadowed(
|
||||
shadowing.first_seen,
|
||||
shadowing.first_seen(),
|
||||
shadow,
|
||||
)));
|
||||
}
|
||||
|
||||
let var = var_store.fresh();
|
||||
|
||||
introduced_variables.insert_named(var_name.clone(), Loc::at(region, var));
|
||||
introduced_variables.insert_able(var_name, Loc::at(region, var), ability);
|
||||
|
||||
Ok(HasClause {
|
||||
var_name,
|
||||
var,
|
||||
ability,
|
||||
})
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -1105,7 +1148,7 @@ fn can_assigned_fields<'a>(
|
|||
let field_name = Lowercase::from(loc_field_name.value);
|
||||
let field_type = {
|
||||
if let Some(var) = introduced_variables.var_by_name(&field_name) {
|
||||
Type::Variable(*var)
|
||||
Type::Variable(var)
|
||||
} else {
|
||||
let field_var = var_store.fresh();
|
||||
introduced_variables.insert_named(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::abilities::AbilitiesStore;
|
||||
use crate::annotation::canonicalize_annotation;
|
||||
use crate::annotation::canonicalize_annotation_with_possible_clauses;
|
||||
use crate::annotation::IntroducedVariables;
|
||||
|
@ -430,12 +429,11 @@ 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(
|
||||
let member_annot = canonicalize_annotation_with_possible_clauses(
|
||||
env,
|
||||
&mut scope,
|
||||
&member.typ.value,
|
||||
|
@ -450,13 +448,14 @@ pub fn canonicalize_defs<'a>(
|
|||
output.references.referenced_type_defs.insert(symbol);
|
||||
}
|
||||
|
||||
let name_region = member.name.region;
|
||||
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,
|
||||
name_region,
|
||||
) {
|
||||
Ok(sym) => sym,
|
||||
Err((original_region, shadow, _new_symbol)) => {
|
||||
|
@ -473,9 +472,11 @@ pub fn canonicalize_defs<'a>(
|
|||
// 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);
|
||||
member_annot
|
||||
.introduced_variables
|
||||
.able
|
||||
.iter()
|
||||
.partition(|av| av.ability == loc_ability_name.value);
|
||||
|
||||
let mut bad_has_clauses = false;
|
||||
|
||||
|
@ -485,18 +486,38 @@ pub fn canonicalize_defs<'a>(
|
|||
env.problem(Problem::AbilityMemberMissingHasClause {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
region: member.name.region,
|
||||
region: name_region,
|
||||
});
|
||||
bad_has_clauses = true;
|
||||
}
|
||||
|
||||
if variables_bound_to_ability.len() > 1 {
|
||||
// There is more than one variable bound to the member signature, so something like
|
||||
// Eq has eq : a, b -> Bool | a has Eq, b has Eq
|
||||
// We have no way of telling what type implements a particular instance of Eq in
|
||||
// this case (a or b?), so disallow it.
|
||||
let span_has_clauses =
|
||||
Region::across_all(variables_bound_to_ability.iter().map(|v| &v.first_seen));
|
||||
let bound_var_names = variables_bound_to_ability
|
||||
.iter()
|
||||
.map(|v| v.name.clone())
|
||||
.collect();
|
||||
env.problem(Problem::AbilityMemberMultipleBoundVars {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
span_has_clauses,
|
||||
bound_var_names,
|
||||
});
|
||||
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() {
|
||||
for bad_variable in variables_bound_to_other_abilities.iter() {
|
||||
env.problem(Problem::AbilityMemberBindsExternalAbility {
|
||||
member: member_sym,
|
||||
ability: loc_ability_name.value,
|
||||
region: bad_clause.region,
|
||||
region: bad_variable.first_seen,
|
||||
});
|
||||
}
|
||||
bad_has_clauses = true;
|
||||
|
@ -507,15 +528,18 @@ pub fn canonicalize_defs<'a>(
|
|||
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));
|
||||
// The introduced variables are good; add them to the output.
|
||||
output
|
||||
.introduced_variables
|
||||
.union(&member_annot.introduced_variables);
|
||||
|
||||
can_members.push((member_sym, name_region, member_annot.typ));
|
||||
}
|
||||
|
||||
// Store what symbols a type must define implementations for to have this ability.
|
||||
abilities_store.register_ability(loc_ability_name.value, can_members);
|
||||
scope
|
||||
.abilities_store
|
||||
.register_ability(loc_ability_name.value, can_members);
|
||||
}
|
||||
|
||||
// Now that we have the scope completely assembled, and shadowing resolved,
|
||||
|
@ -526,14 +550,7 @@ 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,
|
||||
&abilities_store,
|
||||
pattern_type,
|
||||
) {
|
||||
match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) {
|
||||
None => { /* skip */ }
|
||||
Some((new_output, pending_def)) => {
|
||||
// store the top-level defs, used to ensure that closures won't capture them
|
||||
|
@ -1561,7 +1578,9 @@ pub fn can_defs_with_return<'a>(
|
|||
// Now that we've collected all the references, check to see if any of the new idents
|
||||
// we defined went unused by the return expression. If any were unused, report it.
|
||||
for (symbol, region) in symbols_introduced {
|
||||
if !output.references.has_value_lookup(symbol) && !output.references.has_type_lookup(symbol)
|
||||
if !output.references.has_value_lookup(symbol)
|
||||
&& !output.references.has_type_lookup(symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
|
@ -1772,7 +1791,6 @@ 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::*;
|
||||
|
@ -1784,7 +1802,6 @@ fn to_pending_value_def<'a>(
|
|||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
|
@ -1801,7 +1818,6 @@ fn to_pending_value_def<'a>(
|
|||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
|
@ -1832,7 +1848,6 @@ fn to_pending_value_def<'a>(
|
|||
env,
|
||||
var_store,
|
||||
scope,
|
||||
abilities_store,
|
||||
pattern_type,
|
||||
&body_pattern.value,
|
||||
body_pattern.region,
|
||||
|
|
|
@ -1083,6 +1083,7 @@ fn canonicalize_when_branch<'a>(
|
|||
&& !branch_output.references.has_value_lookup(symbol)
|
||||
&& !branch_output.references.has_type_lookup(symbol)
|
||||
&& !original_scope.contains_symbol(symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, *region));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::abilities::AbilitiesStore;
|
||||
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
|
||||
use crate::effect_module::HostedGeneratedFunctions;
|
||||
use crate::env::Env;
|
||||
|
@ -28,11 +29,13 @@ pub struct Module {
|
|||
/// all aliases. `bool` indicates whether it is exposed
|
||||
pub aliases: MutMap<Symbol, (bool, Alias)>,
|
||||
pub rigid_variables: RigidVariables,
|
||||
pub abilities_store: AbilitiesStore,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RigidVariables {
|
||||
pub named: MutMap<Variable, Lowercase>,
|
||||
pub able: MutMap<Variable, (Lowercase, Symbol)>,
|
||||
pub wildcards: MutSet<Variable>,
|
||||
}
|
||||
|
||||
|
@ -250,6 +253,7 @@ pub fn canonicalize_module_defs<'a>(
|
|||
if !output.references.has_value_lookup(symbol)
|
||||
&& !output.references.has_type_lookup(symbol)
|
||||
&& !exposed_symbols.contains(&symbol)
|
||||
&& !scope.abilities_store.is_specialization_name(symbol)
|
||||
{
|
||||
env.problem(Problem::UnusedDef(symbol, region));
|
||||
}
|
||||
|
@ -259,6 +263,12 @@ pub fn canonicalize_module_defs<'a>(
|
|||
rigid_variables.named.insert(named.variable, named.name);
|
||||
}
|
||||
|
||||
for able in output.introduced_variables.able {
|
||||
rigid_variables
|
||||
.able
|
||||
.insert(able.variable, (able.name, able.ability));
|
||||
}
|
||||
|
||||
for var in output.introduced_variables.wildcards {
|
||||
rigid_variables.wildcards.insert(var.value);
|
||||
}
|
||||
|
@ -444,6 +454,10 @@ pub fn canonicalize_module_defs<'a>(
|
|||
aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
for member in scope.abilities_store.root_ability_members().keys() {
|
||||
exposed_but_not_defined.remove(member);
|
||||
}
|
||||
|
||||
// By this point, all exposed symbols should have been removed from
|
||||
// exposed_symbols and added to exposed_vars_by_symbol. If any were
|
||||
// not, that means they were declared as exposed but there was
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use crate::abilities::AbilitiesStore;
|
||||
use crate::annotation::freshen_opaque_def;
|
||||
use crate::env::Env;
|
||||
use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output};
|
||||
|
@ -157,7 +156,6 @@ 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,
|
||||
|
@ -172,7 +170,6 @@ pub fn canonicalize_def_header_pattern<'a>(
|
|||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
abilities_store,
|
||||
) {
|
||||
Ok((symbol, shadowing_ability_member)) => {
|
||||
output.references.bound_symbols.insert(symbol);
|
||||
|
|
|
@ -22,7 +22,7 @@ pub struct Scope {
|
|||
pub aliases: SendMap<Symbol, Alias>,
|
||||
|
||||
/// The abilities currently in scope, and their implementors.
|
||||
pub abilities: SendMap<Symbol, Region>,
|
||||
pub abilities_store: AbilitiesStore,
|
||||
|
||||
/// The current module being processed. This will be used to turn
|
||||
/// unqualified idents into Symbols.
|
||||
|
@ -68,7 +68,7 @@ impl Scope {
|
|||
symbols: SendMap::default(),
|
||||
aliases,
|
||||
// TODO(abilities): default abilities in scope
|
||||
abilities: SendMap::default(),
|
||||
abilities_store: AbilitiesStore::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,7 +247,6 @@ impl Scope {
|
|||
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)) => {
|
||||
|
@ -256,7 +255,9 @@ impl Scope {
|
|||
|
||||
self.symbols.insert(shadow_symbol, region);
|
||||
|
||||
if abilities_store.is_ability_member_name(original_symbol) {
|
||||
if self.abilities_store.is_ability_member_name(original_symbol) {
|
||||
self.abilities_store
|
||||
.register_specializing_symbol(shadow_symbol, original_symbol);
|
||||
// Add a symbol for the shadow, but don't re-associate the member name.
|
||||
Ok((shadow_symbol, Some(original_symbol)))
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue