mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
Using abilities as types is illegal, but we can still compile them
Closes #2881
This commit is contained in:
parent
c0dec1d5bc
commit
80dc50763e
7 changed files with 151 additions and 4 deletions
|
@ -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`.
|
/// 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
|
/// Entries via this function are considered a source of truth. It must be ensured that a
|
||||||
/// specialization is validated before being registered here.
|
/// specialization is validated before being registered here.
|
||||||
|
|
|
@ -8,7 +8,8 @@ use roc_problem::can::ShadowKind;
|
||||||
use roc_region::all::{Loc, Region};
|
use roc_region::all::{Loc, Region};
|
||||||
use roc_types::subs::{VarStore, Variable};
|
use roc_types::subs::{VarStore, Variable};
|
||||||
use roc_types::types::{
|
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)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -397,6 +398,16 @@ pub fn find_type_def_symbols(
|
||||||
result
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn can_annotation_help(
|
fn can_annotation_help(
|
||||||
env: &mut Env,
|
env: &mut Env,
|
||||||
|
@ -418,7 +429,7 @@ fn can_annotation_help(
|
||||||
let arg_ann = can_annotation_help(
|
let arg_ann = can_annotation_help(
|
||||||
env,
|
env,
|
||||||
&arg.value,
|
&arg.value,
|
||||||
region,
|
arg.region,
|
||||||
scope,
|
scope,
|
||||||
var_store,
|
var_store,
|
||||||
introduced_variables,
|
introduced_variables,
|
||||||
|
@ -456,6 +467,21 @@ fn can_annotation_help(
|
||||||
|
|
||||||
references.insert(symbol);
|
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 {
|
for arg in *type_arguments {
|
||||||
let arg_ann = can_annotation_help(
|
let arg_ann = can_annotation_help(
|
||||||
env,
|
env,
|
||||||
|
|
|
@ -138,6 +138,7 @@ pub enum Problem {
|
||||||
AbilityNotOnToplevel {
|
AbilityNotOnToplevel {
|
||||||
region: Region,
|
region: Region,
|
||||||
},
|
},
|
||||||
|
AbilityUsedAsType(Lowercase, Symbol, Region),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
|
|
@ -188,3 +188,31 @@ fn ability_constrained_in_non_member_multiple_specializations_inferred() {
|
||||||
u64
|
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::*;
|
use roc_problem::can::Problem::*;
|
||||||
for problem in can_problems.into_iter() {
|
for problem in can_problems.into_iter() {
|
||||||
// Ignore "unused" problems
|
dbg!(&problem);
|
||||||
match problem {
|
match problem {
|
||||||
|
// Ignore "unused" problems
|
||||||
UnusedDef(_, _)
|
UnusedDef(_, _)
|
||||||
| UnusedArgument(_, _, _)
|
| UnusedArgument(_, _, _)
|
||||||
| UnusedImport(_, _)
|
| UnusedImport(_, _)
|
||||||
|
@ -122,6 +123,8 @@ fn create_llvm_module<'a>(
|
||||||
delayed_errors.push(buf.clone());
|
delayed_errors.push(buf.clone());
|
||||||
lines.push(buf);
|
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 report = can_problem(&alloc, &line_info, module_path.clone(), problem);
|
||||||
let mut buf = String::new();
|
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_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_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULTIPLE VARIABLES";
|
||||||
const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL";
|
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>(
|
pub fn can_problem<'b>(
|
||||||
alloc: &'b RocDocAllocator<'b>,
|
alloc: &'b RocDocAllocator<'b>,
|
||||||
|
@ -750,6 +751,34 @@ pub fn can_problem<'b>(
|
||||||
title = ABILITY_NOT_ON_TOPLEVEL.to_string();
|
title = ABILITY_NOT_ON_TOPLEVEL.to_string();
|
||||||
severity = Severity::RuntimeError;
|
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 {
|
Report {
|
||||||
|
|
|
@ -8877,7 +8877,7 @@ I need all branches in an `if` to have the same type!
|
||||||
I cannot find a `UnknownType` value
|
I cannot find a `UnknownType` value
|
||||||
|
|
||||||
3│ insertHelper : UnknownType, Type -> Type
|
3│ insertHelper : UnknownType, Type -> Type
|
||||||
^^^^
|
^^^^^^^^^^^
|
||||||
|
|
||||||
Did you mean one of these?
|
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…
Add table
Add a link
Reference in a new issue