mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-11 05:34:11 +03:00
Using abilities as types is illegal, but we can still compile them
Closes #2881
This commit is contained in:
parent
c0dec1d5bc
commit
80dc50763e
@ -85,6 +85,10 @@ impl AbilitiesStore {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn is_ability(&self, ability: Symbol) -> bool {
|
||||
self.members_of_ability.contains_key(&ability)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -8,7 +8,8 @@ use roc_problem::can::ShadowKind;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::{
|
||||
Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type, TypeExtension,
|
||||
name_type_var, Alias, AliasCommon, AliasKind, LambdaSet, Problem, RecordField, Type,
|
||||
TypeExtension,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -397,6 +398,16 @@ pub fn find_type_def_symbols(
|
||||
result
|
||||
}
|
||||
|
||||
/// Generates a fresh type variable name. PERF: not super performant, don't prefer using this in
|
||||
/// non-degenerate compilation paths!
|
||||
fn find_fresh_var_name(introduced_variables: &IntroducedVariables) -> Lowercase {
|
||||
let mut taken = introduced_variables
|
||||
.iter_named()
|
||||
.map(|v| v.name().clone())
|
||||
.collect();
|
||||
name_type_var(0, &mut taken).0
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn can_annotation_help(
|
||||
env: &mut Env,
|
||||
@ -418,7 +429,7 @@ fn can_annotation_help(
|
||||
let arg_ann = can_annotation_help(
|
||||
env,
|
||||
&arg.value,
|
||||
region,
|
||||
arg.region,
|
||||
scope,
|
||||
var_store,
|
||||
introduced_variables,
|
||||
@ -456,6 +467,21 @@ fn can_annotation_help(
|
||||
|
||||
references.insert(symbol);
|
||||
|
||||
if scope.abilities_store.is_ability(symbol) {
|
||||
let fresh_ty_var = find_fresh_var_name(introduced_variables);
|
||||
|
||||
env.problem(roc_problem::can::Problem::AbilityUsedAsType(
|
||||
fresh_ty_var.clone(),
|
||||
symbol,
|
||||
region,
|
||||
));
|
||||
|
||||
// Generate an variable bound to the ability so we can keep compiling.
|
||||
let var = var_store.fresh();
|
||||
introduced_variables.insert_able(fresh_ty_var, Loc::at(region, var), symbol);
|
||||
return Type::Variable(var);
|
||||
}
|
||||
|
||||
for arg in *type_arguments {
|
||||
let arg_ann = can_annotation_help(
|
||||
env,
|
||||
|
@ -138,6 +138,7 @@ pub enum Problem {
|
||||
AbilityNotOnToplevel {
|
||||
region: Region,
|
||||
},
|
||||
AbilityUsedAsType(Lowercase, Symbol, Region),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -188,3 +188,31 @@ fn ability_constrained_in_non_member_multiple_specializations_inferred() {
|
||||
u64
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn ability_used_as_type_still_compiles() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ result ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
mulHashes : Hash, Hash -> U64
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64
|
||||
hash = \$Id n -> n
|
||||
|
||||
Three := {}
|
||||
hash = \$Three _ -> 3
|
||||
|
||||
result = mulHashes ($Id 100) ($Three {})
|
||||
"#
|
||||
),
|
||||
300,
|
||||
u64
|
||||
)
|
||||
}
|
||||
|
@ -106,8 +106,9 @@ fn create_llvm_module<'a>(
|
||||
|
||||
use roc_problem::can::Problem::*;
|
||||
for problem in can_problems.into_iter() {
|
||||
// Ignore "unused" problems
|
||||
dbg!(&problem);
|
||||
match problem {
|
||||
// Ignore "unused" problems
|
||||
UnusedDef(_, _)
|
||||
| UnusedArgument(_, _, _)
|
||||
| UnusedImport(_, _)
|
||||
@ -122,6 +123,8 @@ fn create_llvm_module<'a>(
|
||||
delayed_errors.push(buf.clone());
|
||||
lines.push(buf);
|
||||
}
|
||||
// We should be able to compile even when abilities are used as types
|
||||
AbilityUsedAsType(..) => {}
|
||||
_ => {
|
||||
let report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||
let mut buf = String::new();
|
||||
|
@ -46,6 +46,7 @@ const ABILITY_MEMBER_MISSING_HAS_CLAUSE: &str = "ABILITY MEMBER MISSING HAS CLAU
|
||||
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 ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE";
|
||||
|
||||
pub fn can_problem<'b>(
|
||||
alloc: &'b RocDocAllocator<'b>,
|
||||
@ -750,6 +751,34 @@ pub fn can_problem<'b>(
|
||||
title = ABILITY_NOT_ON_TOPLEVEL.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
|
||||
Problem::AbilityUsedAsType(suggested_var_name, ability, region) => {
|
||||
doc = alloc.stack(vec![
|
||||
alloc.concat(vec![
|
||||
alloc.reflow("You are attempting to use the ability "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(" as a type directly:"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(region)),
|
||||
alloc.reflow(
|
||||
"Abilities can only be used in type annotations to constrain type variables.",
|
||||
),
|
||||
alloc
|
||||
.hint("")
|
||||
.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_variable(suggested_var_name),
|
||||
alloc.space(),
|
||||
alloc.keyword("has"),
|
||||
alloc.space(),
|
||||
alloc.symbol_unqualified(ability),
|
||||
])),
|
||||
]);
|
||||
title = ABILITY_USED_AS_TYPE.to_string();
|
||||
severity = Severity::RuntimeError;
|
||||
}
|
||||
};
|
||||
|
||||
Report {
|
||||
|
@ -8877,7 +8877,7 @@ I need all branches in an `if` to have the same type!
|
||||
I cannot find a `UnknownType` value
|
||||
|
||||
3│ insertHelper : UnknownType, Type -> Type
|
||||
^^^^
|
||||
^^^^^^^^^^^
|
||||
|
||||
Did you mean one of these?
|
||||
|
||||
@ -9873,4 +9873,60 @@ I need all branches in an `if` to have the same type!
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_value_annotations_are_an_error() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ result ] to "./platform"
|
||||
|
||||
Hash has
|
||||
hash : a -> U64 | a has Hash
|
||||
|
||||
mulHashes : Hash, Hash -> U64
|
||||
mulHashes = \x, y -> hash x * hash y
|
||||
|
||||
Id := U64
|
||||
hash = \$Id n -> n
|
||||
|
||||
Three := {}
|
||||
hash = \$Three _ -> 3
|
||||
|
||||
result = mulHashes ($Id 100) ($Three {})
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── ABILITY USED AS TYPE ────────────────────────────────────────────────────────
|
||||
|
||||
You are attempting to use the ability `Hash` as a type directly:
|
||||
|
||||
6│ mulHashes : Hash, Hash -> U64
|
||||
^^^^
|
||||
|
||||
Abilities can only be used in type annotations to constrain type
|
||||
variables.
|
||||
|
||||
Hint: Perhaps you meant to include a `has` annotation, like
|
||||
|
||||
a has Hash
|
||||
|
||||
── ABILITY USED AS TYPE ────────────────────────────────────────────────────────
|
||||
|
||||
You are attempting to use the ability `Hash` as a type directly:
|
||||
|
||||
6│ mulHashes : Hash, Hash -> U64
|
||||
^^^^
|
||||
|
||||
Abilities can only be used in type annotations to constrain type
|
||||
variables.
|
||||
|
||||
Hint: Perhaps you meant to include a `has` annotation, like
|
||||
|
||||
b has Hash
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user