Basic canonicalization and error checking for abilities

This commit is contained in:
Ayaz Hafiz 2022-04-06 17:20:37 -04:00 committed by Ayaz Hafiz
parent 23bc8aab49
commit 66ec1b4a84
11 changed files with 1097 additions and 157 deletions

View file

@ -0,0 +1,64 @@
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.
#[allow(unused)]
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);
self.ability_members.insert(
member,
AbilityMemberData {
parent_ability: ability,
signature,
bound_has_clauses,
},
);
}
self.members_of_ability.insert(ability, members_vec);
}
pub fn register_implementation(&mut self, implementing_type: Symbol, ability_member: Symbol) {
self.declared_implementations
.insert((implementing_type, ability_member));
}
}

View file

@ -1,10 +1,9 @@
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_region::all::{Loc, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{
@ -104,6 +103,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 +146,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 +346,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(_) => {}
}
}
@ -685,7 +766,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)),
});
return Type::Erroneous(Problem::CanonicalizationProblem);
}
Malformed(string) => {
malformed(env, region, string);
@ -698,6 +789,71 @@ 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::ShadowingInAnnotation {
original_region: shadowing.first_seen,
shadow: shadow.clone(),
});
return Err(Type::Erroneous(Problem::Shadowed(
shadowing.first_seen,
shadow,
)));
}
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), Loc::at(region, var));
Ok(HasClause {
var_name,
var,
ability,
})
}
#[allow(clippy::too_many_arguments)]
fn can_extension_type<'a>(
env: &mut Env,

View file

@ -1,4 +1,6 @@
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;
@ -10,10 +12,11 @@ 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::{CycleEntry, Problem, RuntimeError};
@ -86,10 +89,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 +251,19 @@ pub fn canonicalize_defs<'a>(
env.home.register_debug_idents(&env.ident_ids);
}
let mut aliases = SendMap::default();
enum TypeDef<'a> {
AliasLike(
Loc<Symbol>,
Vec<Loc<Lowercase>>,
&'a Loc<ast::TypeAnnotation<'a>>,
AliasKind,
),
Ability(Loc<Symbol>, &'a [AbilityMember<'a>]),
}
let mut type_defs = MutMap::default();
let mut abilities_in_scope = Vec::new();
let mut alias_defs = MutMap::default();
let mut referenced_type_symbols = MutMap::default();
for pending_def in pending_type_defs.into_iter() {
@ -260,93 +282,137 @@ pub fn canonicalize_defs<'a>(
referenced_type_symbols.insert(name.value, referenced_symbols);
alias_defs.insert(name.value, (name, vars, ann, kind));
type_defs.insert(name.value, TypeDef::AliasLike(name, vars, ann, kind));
}
PendingTypeDef::Ability { name, members } => {
let mut referenced_symbols = Vec::with_capacity(2);
for member in members.iter() {
// Add the referenced type symbols of each member function. We need to make
// sure those are processed first before we resolve the whole ability
// definition.
referenced_symbols.extend(crate::annotation::find_type_def_symbols(
env.home,
&mut env.ident_ids,
&member.typ.value,
));
}
referenced_type_symbols.insert(name.value, referenced_symbols);
type_defs.insert(name.value, TypeDef::Ability(name, members));
abilities_in_scope.push(name.value);
}
PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } => { /* ignore */
}
PendingTypeDef::InvalidAlias { .. } => { /* ignore */ }
}
}
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
let mut aliases = SendMap::default();
let mut abilities = MutMap::default();
for type_name in sorted {
let (name, vars, ann, kind) = alias_defs.remove(&type_name).unwrap();
match type_defs.remove(&type_name).unwrap() {
TypeDef::AliasLike(name, vars, ann, kind) => {
let symbol = name.value;
let can_ann =
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
let symbol = name.value;
let can_ann = canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
// Does this alias reference any abilities? For now, we don't permit that.
let ability_references = can_ann
.references
.iter()
.filter_map(|&ty_ref| abilities_in_scope.iter().find(|&&name| name == ty_ref))
.collect::<Vec<_>>();
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
let mut named = can_ann.introduced_variables.named;
for loc_lowercase in vars.iter() {
match named.iter().position(|nv| nv.name == loc_lowercase.value) {
Some(index) => {
// This is a valid lowercase rigid var for the type def.
let named_variable = named.swap_remove(index);
can_vars.push(Loc {
value: (named_variable.name, named_variable.variable),
region: loc_lowercase.region,
if let Some(one_ability_ref) = ability_references.first() {
env.problem(Problem::AliasUsesAbility {
loc_name: name,
ability: **one_ability_ref,
});
}
None => {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false;
let mut named = can_ann.introduced_variables.named;
for loc_lowercase in vars.iter() {
match named.iter().position(|nv| nv.name == loc_lowercase.value) {
Some(index) => {
// This is a valid lowercase rigid var for the type def.
let named_variable = named.swap_remove(index);
can_vars.push(Loc {
value: (named_variable.name, named_variable.variable),
region: loc_lowercase.region,
});
}
None => {
is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
}
if is_phantom {
// Bail out
continue;
}
let IntroducedVariables {
wildcards,
inferred,
..
} = can_ann.introduced_variables;
let num_unbound = named.len() + wildcards.len() + inferred.len();
if num_unbound > 0 {
let one_occurrence = named
.iter()
.map(|nv| Loc::at(nv.first_seen, nv.variable))
.chain(wildcards)
.chain(inferred)
.next()
.unwrap()
.region;
env.problems.push(Problem::UnboundTypeVariable {
typ: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
num_unbound,
one_occurrence,
kind,
});
// Bail out
continue;
}
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
}
TypeDef::Ability(name, members) => {
// For now we enforce that aliases cannot reference abilities, so let's wait to
// resolve ability definitions until aliases are resolved and in scope below.
abilities.insert(name.value, (name, members));
}
}
if is_phantom {
// Bail out
continue;
}
let IntroducedVariables {
wildcards,
inferred,
..
} = can_ann.introduced_variables;
let num_unbound = named.len() + wildcards.len() + inferred.len();
if num_unbound > 0 {
let one_occurrence = named
.iter()
.map(|nv| Loc::at(nv.first_seen, nv.variable))
.chain(wildcards)
.chain(inferred)
.next()
.unwrap()
.region;
env.problems.push(Problem::UnboundTypeVariable {
typ: symbol,
num_unbound,
one_occurrence,
kind,
});
// Bail out
continue;
}
let alias = create_alias(
symbol,
name.region,
can_vars.clone(),
can_ann.typ.clone(),
kind,
);
aliases.insert(symbol, alias.clone());
}
// Now that we know the alias dependency graph, we can try to insert recursion variables
@ -362,6 +428,94 @@ 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::ShadowingInAnnotation {
original_region,
shadow,
});
// 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.
@ -1551,7 +1705,46 @@ 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::ShadowingInAnnotation {
original_region,
shadow: shadowed_symbol,
});
return Some((Output::default(), PendingTypeDef::InvalidAbility));
}
};
if !vars.is_empty() {
// Disallow ability type arguments, at least for now.
let variables_region = Region::across_all(vars.iter().map(|v| &v.region));
env.problem(Problem::AbilityHasTypeVariables {
name: name.value,
variables_region,
});
return Some((Output::default(), PendingTypeDef::InvalidAbility));
}
let pending_ability = PendingTypeDef::Ability {
name,
// We'll handle adding the member symbols later on when we do all value defs.
members,
};
Some((Output::default(), pending_ability))
}
}
}

View file

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

View file

@ -19,6 +19,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 +65,8 @@ impl Scope {
idents: Symbol::default_in_scope(),
symbols: SendMap::default(),
aliases,
// TODO(abilities): default abilities in scope
abilities: SendMap::default(),
}
}
@ -176,6 +181,11 @@ impl Scope {
///
/// Returns Err if this would shadow an existing ident, including the
/// Symbol and Region of the ident we already had in scope under that name.
///
/// If this ident shadows an existing one, a new ident is allocated for the shadow. This is
/// done so that all identifiers have unique symbols, which is important in particular when
/// we generate code for value identifiers.
/// If this behavior is undesirable, use [`Self::introduce_without_shadow_symbol`].
pub fn introduce(
&mut self,
ident: Ident,
@ -198,25 +208,53 @@ impl Scope {
Err((original_region, shadow, symbol))
}
None => {
// If this IdentId was already added previously
// when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone()),
};
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
Ok(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)),
}
}
fn commit_introduction(
&mut self,
ident: Ident,
exposed_ident_ids: &IdentIds,
all_ident_ids: &mut IdentIds,
region: Region,
) -> Symbol {
// If this IdentId was already added previously
// when the value was exposed in the module header,
// use that existing IdentId. Otherwise, create a fresh one.
let ident_id = match exposed_ident_ids.get_id(&ident) {
Some(ident_id) => ident_id,
None => all_ident_ids.add(ident.clone()),
};
let symbol = Symbol::new(self.home, ident_id);
self.symbols.insert(symbol, region);
self.idents.insert(ident, (symbol, region));
symbol
}
/// Ignore an identifier.
///
/// Used for record guards like { x: Just _ }

View file

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

View file

@ -33,61 +33,59 @@ Defs(
},
],
),
},
],
},
),
],
@35-71 SpaceBefore(
Defs(
[
@35-68 Type(
Ability {
header: TypeHeader {
name: @35-38 "Ab2",
vars: [],
},
loc_has: @39-42 Has,
members: [
AbilityMember {
name: @43-46 "ab2",
typ: @54-68 Where(
@54-56 Function(
[
@49-50 BoundVariable(
"a",
),
],
@54-56 Record {
fields: [],
ext: None,
},
),
[
@59-68 HasClause {
var: @59-60 "a",
ability: @65-68 Apply(
"",
"Ab2",
[],
),
},
],
[
@24-33 HasClause {
var: @24-25 "a",
ability: @30-33 Apply(
"",
"Ab1",
[],
),
},
],
},
),
),
},
],
@70-71 SpaceBefore(
Num(
"1",
),
[
Newline,
Newline,
],
),
},
@35-68 Ability {
header: TypeHeader {
name: @35-38 "Ab2",
vars: [],
},
loc_has: @39-42 Has,
members: [
AbilityMember {
name: @43-46 "ab2",
typ: @54-68 Where(
@54-56 Function(
[
@49-50 BoundVariable(
"a",
),
],
@54-56 Record {
fields: [],
ext: None,
},
),
[
@59-68 HasClause {
var: @59-60 "a",
ability: @65-68 Apply(
"",
"Ab2",
[],
),
},
],
),
},
],
},
],
@70-71 SpaceBefore(
Num(
"1",
),
[
Newline,

View file

@ -95,6 +95,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)]

View file

@ -1856,6 +1856,7 @@ pub enum Problem {
},
InvalidModule,
SolvedTypeError,
HasClauseIsNotAbility(Region),
}
#[derive(PartialEq, Eq, Debug, Clone)]