Merge pull request #3037 from rtfeldman/disallow-nested-ability-specs

Disallow nested ability specializations
This commit is contained in:
Richard Feldman 2022-05-09 12:11:14 -04:00 committed by GitHub
commit 4f8273ba89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 72 additions and 76 deletions

View File

@ -505,6 +505,7 @@ pub(crate) fn canonicalize_defs<'a>(
output,
scope,
var_store,
pattern_type,
&mut aliases,
&abilities_in_scope,
);
@ -981,12 +982,13 @@ fn canonicalize_pending_value_def<'a>(
mut output: Output,
scope: &mut Scope,
var_store: &mut VarStore,
pattern_type: PatternType,
aliases: &mut VecMap<Symbol, Alias>,
abilities_in_scope: &[Symbol],
) -> DefOutput {
use PendingValueDef::*;
match pending_def {
let output = match pending_def {
AnnotationOnly(_, loc_can_pattern, loc_ann) => {
// Make types for the body expr, even if we won't end up having a body.
let expr_var = var_store.fresh();
@ -1129,7 +1131,21 @@ fn canonicalize_pending_value_def<'a>(
None,
)
}
};
// Disallow ability specializations that aren't on the toplevel (note: we might loosen this
// restriction later on).
if pattern_type != PatternType::TopLevelDef {
if let Loc {
value: Pattern::AbilityMemberSpecialization { specializes, .. },
region,
} = output.def.loc_pattern
{
env.problem(Problem::NestedSpecialization(specializes, region));
}
}
output
}
// TODO trim down these arguments!

View File

@ -125,20 +125,11 @@ pub enum Problem {
span_has_clauses: Region,
bound_var_names: Vec<Lowercase>,
},
// TODO(abilities): remove me when ability hierarchies are supported
AbilityMemberBindsExternalAbility {
member: Symbol,
ability: Symbol,
region: Region,
},
AliasUsesAbility {
loc_name: Loc<Symbol>,
ability: Symbol,
},
AbilityNotOnToplevel {
region: Region,
},
AbilityUsedAsType(Lowercase, Symbol, Region),
NestedSpecialization(Symbol, Region),
}
#[derive(Clone, Debug, PartialEq)]

View File

@ -40,12 +40,11 @@ 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";
const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES";
const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL";
const SPECIALIZATION_NOT_ON_TOPLEVEL: &str = "SPECIALIZATION NOT ON TOP-LEVEL";
const ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE";
pub fn can_problem<'b>(
@ -201,7 +200,7 @@ pub fn can_problem<'b>(
WhenBranch => unreachable!("all patterns are allowed in a When"),
};
let suggestion = vec![
let suggestion = [
alloc.reflow(
"Patterns like this don't cover all possible shapes of the input type. Use a ",
),
@ -603,37 +602,6 @@ pub fn can_problem<'b>(
severity = Severity::RuntimeError;
}
Problem::AliasUsesAbility {
loc_name: Loc {
region,
value: name,
},
ability,
} => {
doc = alloc.stack([
alloc.concat([
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([
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([
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([
alloc.concat([
@ -716,35 +684,11 @@ pub fn can_problem<'b>(
severity = Severity::RuntimeError;
}
Problem::AbilityMemberBindsExternalAbility {
member,
ability,
region,
} => {
doc = alloc.stack([
alloc.concat([
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([
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;
}
Problem::AbilityNotOnToplevel { region } => {
doc = alloc.stack(vec![
alloc.concat(vec![alloc.reflow(
"This ability definition is not on the top-level of a module:",
)]),
doc = alloc.stack([
alloc
.concat([alloc
.reflow("This ability definition is not on the top-level of a module:")]),
alloc.region(lines.convert_region(region)),
alloc.reflow("Abilities can only be defined on the top-level of a Roc module."),
]);
@ -753,8 +697,8 @@ pub fn can_problem<'b>(
}
Problem::AbilityUsedAsType(suggested_var_name, ability, region) => {
doc = alloc.stack(vec![
alloc.concat(vec![
doc = alloc.stack([
alloc.concat([
alloc.reflow("You are attempting to use the ability "),
alloc.symbol_unqualified(ability),
alloc.reflow(" as a type directly:"),
@ -768,7 +712,7 @@ pub fn can_problem<'b>(
.append(alloc.reflow("Perhaps you meant to include a "))
.append(alloc.keyword("has"))
.append(alloc.reflow(" annotation, like")),
alloc.type_block(alloc.concat(vec![
alloc.type_block(alloc.concat([
alloc.type_variable(suggested_var_name),
alloc.space(),
alloc.keyword("has"),
@ -779,6 +723,19 @@ pub fn can_problem<'b>(
title = ABILITY_USED_AS_TYPE.to_string();
severity = Severity::RuntimeError;
}
Problem::NestedSpecialization(member, region) => {
doc = alloc.stack([
alloc.concat([
alloc.reflow("This specialization of the "),
alloc.symbol_unqualified(member),
alloc.reflow(" ability member is in a nested scope:"),
]),
alloc.region(lines.convert_region(region)),
alloc.reflow("Specializations can only be defined on the top-level of a module."),
]);
title = SPECIALIZATION_NOT_ON_TOPLEVEL.to_string();
severity = Severity::Warning;
}
};
Report {

View File

@ -9764,4 +9764,36 @@ I need all branches in an `if` to have the same type!
"", // no problem
)
}
#[test]
fn nested_specialization() {
new_report_problem_as(
"nested_specialization",
indoc!(
r#"
app "test" provides [ main ] to "./platform"
Default has default : {} -> a | a has Default
main =
A := {}
default = \{} -> @A {}
default {}
"#
),
indoc!(
r#"
SPECIALIZATION NOT ON TOP-LEVEL /code/proj/Main.roc
This specialization of the `default` ability member is in a nested
scope:
7 default = \{} -> @A {}
^^^^^^^
Specializations can only be defined on the top-level of a module.
"#
),
)
}
}