mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Report errors for duplicate bound abilities
This commit is contained in:
parent
231a72f2ee
commit
603160dae3
4 changed files with 56 additions and 6 deletions
|
@ -931,31 +931,37 @@ fn canonicalize_has_clause(
|
||||||
let var_name = Lowercase::from(var_name);
|
let var_name = Lowercase::from(var_name);
|
||||||
|
|
||||||
let mut can_abilities = Vec::with_capacity(abilities.len());
|
let mut can_abilities = Vec::with_capacity(abilities.len());
|
||||||
for ability in *abilities {
|
for &Loc {
|
||||||
let ability = match ability.value {
|
region,
|
||||||
|
value: ability,
|
||||||
|
} in *abilities
|
||||||
|
{
|
||||||
|
let ability = match ability {
|
||||||
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
|
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
|
||||||
let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?;
|
let symbol = make_apply_symbol(env, region, scope, module_name, ident)?;
|
||||||
|
|
||||||
// Ability defined locally, whose members we are constructing right now...
|
// Ability defined locally, whose members we are constructing right now...
|
||||||
if !pending_abilities_in_scope.contains_key(&symbol)
|
if !pending_abilities_in_scope.contains_key(&symbol)
|
||||||
// or an ability that was imported from elsewhere
|
// or an ability that was imported from elsewhere
|
||||||
&& !scope.abilities_store.is_ability(symbol)
|
&& !scope.abilities_store.is_ability(symbol)
|
||||||
{
|
{
|
||||||
let region = ability.region;
|
|
||||||
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
||||||
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
||||||
}
|
}
|
||||||
symbol
|
symbol
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let region = ability.region;
|
|
||||||
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
||||||
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
references.insert(ability);
|
references.insert(ability);
|
||||||
can_abilities.push(ability);
|
if can_abilities.contains(&ability) {
|
||||||
|
env.problem(roc_problem::can::Problem::DuplicateHasAbility { ability, region });
|
||||||
|
} else {
|
||||||
|
can_abilities.push(ability);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(shadowing) = introduced_variables.named_var_by_name(&var_name) {
|
if let Some(shadowing) = introduced_variables.named_var_by_name(&var_name) {
|
||||||
|
|
|
@ -117,6 +117,10 @@ pub enum Problem {
|
||||||
IllegalHasClause {
|
IllegalHasClause {
|
||||||
region: Region,
|
region: Region,
|
||||||
},
|
},
|
||||||
|
DuplicateHasAbility {
|
||||||
|
ability: Symbol,
|
||||||
|
region: Region,
|
||||||
|
},
|
||||||
AbilityMemberMissingHasClause {
|
AbilityMemberMissingHasClause {
|
||||||
member: Symbol,
|
member: Symbol,
|
||||||
ability: Symbol,
|
ability: Symbol,
|
||||||
|
|
|
@ -677,6 +677,24 @@ pub fn can_problem<'b>(
|
||||||
severity = Severity::RuntimeError;
|
severity = Severity::RuntimeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Problem::DuplicateHasAbility { ability, region } => {
|
||||||
|
doc = alloc.stack([
|
||||||
|
alloc.concat([
|
||||||
|
alloc.reflow("I already saw that this type variable is bound to the "),
|
||||||
|
alloc.symbol_foreign_qualified(ability),
|
||||||
|
alloc.reflow(" ability once before:"),
|
||||||
|
]),
|
||||||
|
alloc.region(lines.convert_region(region)),
|
||||||
|
alloc.concat([
|
||||||
|
alloc.reflow("Abilities only need to bound to a type variable once in a "),
|
||||||
|
alloc.keyword("has"),
|
||||||
|
alloc.reflow(" clause!"),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
title = "DUPLICATE BOUND ABILITY".to_string();
|
||||||
|
severity = Severity::Warning;
|
||||||
|
}
|
||||||
|
|
||||||
Problem::AbilityMemberMissingHasClause {
|
Problem::AbilityMemberMissingHasClause {
|
||||||
member,
|
member,
|
||||||
ability,
|
ability,
|
||||||
|
|
|
@ -11531,4 +11531,26 @@ All branches in an `if` must have the same type!
|
||||||
Tip: You can define a custom implementation of `Encoding` for `F`.
|
Tip: You can define a custom implementation of `Encoding` for `F`.
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test_report!(
|
||||||
|
duplicate_ability_in_has_clause,
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
f : a -> {} | a has Hash & Hash
|
||||||
|
|
||||||
|
f
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
@r###"
|
||||||
|
── DUPLICATE BOUND ABILITY ─────────────────────────────── /code/proj/Main.roc ─
|
||||||
|
|
||||||
|
I already saw that this type variable is bound to the `Hash` ability
|
||||||
|
once before:
|
||||||
|
|
||||||
|
4│ f : a -> {} | a has Hash & Hash
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
Abilities only need to bound to a type variable once in a `has` clause!
|
||||||
|
"###
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue