Typecheck annotations with able variables outside ability members

This commit is contained in:
Ayaz Hafiz 2022-04-19 13:03:08 -04:00
parent f8156ffd53
commit a07323fb40
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
4 changed files with 88 additions and 40 deletions

View file

@ -187,37 +187,8 @@ fn malformed(env: &mut Env, region: Region, name: &str) {
env.problem(roc_problem::can::Problem::RuntimeError(problem)); env.problem(roc_problem::can::Problem::RuntimeError(problem));
} }
/// Canonicalizes a top-level type annotation.
pub fn canonicalize_annotation( pub fn canonicalize_annotation(
env: &mut Env,
scope: &mut Scope,
annotation: &roc_parse::ast::TypeAnnotation,
region: Region,
var_store: &mut VarStore,
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = VecSet::default();
let mut aliases = SendMap::default();
let typ = can_annotation_help(
env,
annotation,
region,
scope,
var_store,
&mut introduced_variables,
&mut aliases,
&mut references,
);
Annotation {
typ,
introduced_variables,
references,
aliases,
}
}
pub fn canonicalize_annotation_with_possible_clauses(
env: &mut Env, env: &mut Env,
scope: &mut Scope, scope: &mut Scope,
annotation: &TypeAnnotation, annotation: &TypeAnnotation,
@ -828,8 +799,7 @@ fn can_annotation_help(
Where(_annotation, clauses) => { Where(_annotation, clauses) => {
debug_assert!(!clauses.is_empty()); debug_assert!(!clauses.is_empty());
// Has clauses are allowed only on the top level of an ability member signature (for // Has clauses are allowed only on the top level of a signature, which we handle elsewhere.
// now), which we handle elsewhere.
env.problem(roc_problem::can::Problem::IllegalHasClause { env.problem(roc_problem::can::Problem::IllegalHasClause {
region: Region::across_all(clauses.iter().map(|clause| &clause.region)), region: Region::across_all(clauses.iter().map(|clause| &clause.region)),
}); });

View file

@ -1,6 +1,5 @@
use crate::abilities::MemberVariables; use crate::abilities::MemberVariables;
use crate::annotation::canonicalize_annotation; use crate::annotation::canonicalize_annotation;
use crate::annotation::canonicalize_annotation_with_possible_clauses;
use crate::annotation::IntroducedVariables; use crate::annotation::IntroducedVariables;
use crate::env::Env; use crate::env::Env;
use crate::expr::ClosureData; use crate::expr::ClosureData;
@ -318,8 +317,14 @@ pub fn canonicalize_defs<'a>(
match type_defs.remove(&type_name).unwrap() { match type_defs.remove(&type_name).unwrap() {
TypeDef::AliasLike(name, vars, ann, kind) => { TypeDef::AliasLike(name, vars, ann, kind) => {
let symbol = name.value; let symbol = name.value;
let can_ann = let can_ann = canonicalize_annotation(
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store); env,
&mut scope,
&ann.value,
ann.region,
var_store,
&abilities_in_scope,
);
// Does this alias reference any abilities? For now, we don't permit that. // Does this alias reference any abilities? For now, we don't permit that.
let ability_references = can_ann let ability_references = can_ann
@ -437,7 +442,7 @@ pub fn canonicalize_defs<'a>(
let mut can_members = Vec::with_capacity(members.len()); let mut can_members = Vec::with_capacity(members.len());
for member in members { for member in members {
let member_annot = canonicalize_annotation_with_possible_clauses( let member_annot = canonicalize_annotation(
env, env,
&mut scope, &mut scope,
&member.typ.value, &member.typ.value,
@ -605,6 +610,7 @@ pub fn canonicalize_defs<'a>(
var_store, var_store,
&mut refs_by_symbol, &mut refs_by_symbol,
&mut aliases, &mut aliases,
&abilities_in_scope,
); );
// TODO we should do something with these references; they include // TODO we should do something with these references; they include
@ -1148,6 +1154,7 @@ fn canonicalize_pending_value_def<'a>(
var_store: &mut VarStore, var_store: &mut VarStore,
refs_by_symbol: &mut MutMap<Symbol, (Region, References)>, refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
aliases: &mut ImMap<Symbol, Alias>, aliases: &mut ImMap<Symbol, Alias>,
abilities_in_scope: &[Symbol],
) -> Output { ) -> Output {
use PendingValueDef::*; use PendingValueDef::*;
@ -1159,8 +1166,14 @@ fn canonicalize_pending_value_def<'a>(
AnnotationOnly(_, loc_can_pattern, loc_ann) => { AnnotationOnly(_, loc_can_pattern, loc_ann) => {
// annotation sans body cannot introduce new rigids that are visible in other annotations // annotation sans body cannot introduce new rigids that are visible in other annotations
// but the rigids can show up in type error messages, so still register them // but the rigids can show up in type error messages, so still register them
let type_annotation = let type_annotation = canonicalize_annotation(
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); env,
scope,
&loc_ann.value,
loc_ann.region,
var_store,
abilities_in_scope,
);
// Record all the annotation's references in output.references.lookups // Record all the annotation's references in output.references.lookups
@ -1280,8 +1293,14 @@ fn canonicalize_pending_value_def<'a>(
} }
TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
let type_annotation = let type_annotation = canonicalize_annotation(
canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); env,
scope,
&loc_ann.value,
loc_ann.region,
var_store,
&abilities_in_scope,
);
// Record all the annotation's references in output.references.lookups // Record all the annotation's references in output.references.lookups
for symbol in type_annotation.references.iter() { for symbol in type_annotation.references.iter() {

View file

@ -5951,4 +5951,61 @@ mod solve_expr {
"{ tag : [ A, B ] }a -> { tag : [ A, B ] }a", "{ tag : [ A, B ] }a -> { tag : [ A, B ] }a",
) )
} }
#[test]
fn ability_constrained_in_non_member_check() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [ hashEq ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
hashEq : a, a -> Bool | a has Hash
hashEq = \x, y -> hash x == hash y
"#
),
"a, a -> Bool | a has Hash",
)
}
#[test]
fn ability_constrained_in_non_member_infer() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [ hashEq ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
hashEq = \x, y -> hash x == hash y
"#
),
"a, a -> Bool | a has Hash",
)
}
#[test]
fn ability_constrained_in_non_member_infer_usage() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [ result ] to "./platform"
Hash has
hash : a -> U64 | a has Hash
hashEq = \x, y -> hash x == hash y
Id := U64
hash = \$Id n -> n
result = hashEq ($Id 100) ($Id 101)
"#
),
"Bool",
)
}
} }

View file

@ -307,6 +307,8 @@ pub fn content_to_string(
write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary); write_content(&env, &mut ctx, content, subs, &mut buf, Parens::Unnecessary);
ctx.able_variables.sort();
ctx.able_variables.dedup();
for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() { for (i, (var, ability)) in ctx.able_variables.into_iter().enumerate() {
buf.push_str(if i == 0 { " | " } else { ", " }); buf.push_str(if i == 0 { " | " } else { ", " });
buf.push_str(var); buf.push_str(var);