Skip to content

Commit

Permalink
fix(check): Don't shadow more general variant constructors
Browse files Browse the repository at this point in the history
  • Loading branch information
Marwes committed Jun 25, 2018
1 parent b7c88ed commit 182c396
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 14 deletions.
22 changes: 14 additions & 8 deletions base/src/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::borrow::Cow;

use fnv::FnvMap;
use symbol::Symbol;
use types::{AliasData, AliasRef, ArcType, Type, TypeEnv};
use types::{AliasRef, ArcType, Type, TypeEnv};

quick_error! {
#[derive(Debug)]
Expand Down Expand Up @@ -79,16 +79,22 @@ pub fn remove_aliases_cow<'t>(env: &TypeEnv, typ: &'t ArcType) -> Cow<'t, ArcTyp
}
}

pub fn canonical_alias<'t, F>(env: &TypeEnv, typ: &'t ArcType, canonical: F) -> Cow<'t, ArcType>
/// Resolves aliases until `canonical` returns `true` for an alias in which case it returns the
/// type that directly contains that alias
pub fn canonical_alias<'t, F>(env: &TypeEnv, typ: &'t ArcType, mut canonical: F) -> Cow<'t, ArcType>
where
F: Fn(&AliasData<Symbol, ArcType>) -> bool,
F: FnMut(&AliasRef<Symbol, ArcType>) -> bool,
{
match peek_alias(env, typ) {
Ok(Some(alias)) if !canonical(alias) => alias
.typ()
.apply_args(&typ.unapplied_args())
.map(|typ| Cow::Owned(canonical_alias(env, &typ, canonical).into_owned()))
.unwrap_or(Cow::Borrowed(typ)),
Ok(Some(alias)) => if canonical(alias) {
Cow::Borrowed(typ)
} else {
alias
.typ()
.apply_args(&typ.unapplied_args())
.map(|typ| Cow::Owned(canonical_alias(env, &typ, canonical).into_owned()))
.unwrap_or(Cow::Borrowed(typ))
},
_ => Cow::Borrowed(typ),
}
}
Expand Down
34 changes: 31 additions & 3 deletions check/src/typecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,9 +433,36 @@ impl<'a> Typecheck<'a> {

fn stack_type(&mut self, id: Symbol, alias: &Alias<Symbol, ArcType>) {
// Insert variant constructors into the local scope
let aliased_type = resolve::remove_aliases(&self.environment, alias.typ().into_owned());
if let Type::Variant(ref row) = **aliased_type.remove_forall() {
for field in row.row_iter().cloned() {

// We want to prevent the constructors of more specialized aliases from shadowing the more
// general ones so we get the canonical alias and then take its inner type without applying
// any types to always get the most general type of the constructors
//
// ```
// type Option a = | None | Some a
// type OptionInt = Option Int
// // Should work
// Some ""
// ```
let aliased_type = alias.typ();
let canonical_alias = resolve::canonical_alias(&self.environment, &aliased_type, |alias| {
match **alias.unresolved_type().remove_forall() {
Type::Variant(_) => true,
_ => false,
}
});

let canonical_alias = match **canonical_alias.remove_forall() {
Type::App(ref func, _) => match **func {
Type::Alias(ref alias) => alias.typ(),
_ => Cow::Borrowed(func),
},
Type::Alias(ref alias) => alias.typ(),
_ => Cow::Borrowed(canonical_alias.remove_forall()),
};

if let Type::Variant(ref variants) = **canonical_alias {
for field in variants.row_iter().cloned() {
let symbol = self.symbols.symbol(field.name.as_ref());
self.original_symbols.insert(symbol, field.name.clone());
self.stack_var(
Expand All @@ -444,6 +471,7 @@ impl<'a> Typecheck<'a> {
);
}
}

let generic_args = alias.params().iter().cloned().map(Type::generic).collect();
let typ = Type::<_, ArcType>::app(alias.as_ref().clone(), generic_args);
{
Expand Down
34 changes: 32 additions & 2 deletions check/tests/pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ Test 1
let result = support::typecheck(text);
let expected = Ok(support::typ_a("Test", vec![typ("Int")]));

assert_eq!(result.map(make_ident_type), expected);
assert_req!(result.map(make_ident_type), expected);
}

#[test]
Expand Down Expand Up @@ -644,7 +644,7 @@ in Node { value = 1, tree = Empty } rhs
let result = support::typecheck(text);
let expected = Ok(support::typ_a("Tree", vec![typ("Int")]));

assert_eq!(result.map(make_ident_type), expected);
assert_req!(result.map(make_ident_type), expected);
}

#[test]
Expand Down Expand Up @@ -869,3 +869,33 @@ A 1

assert!(result.is_ok(), "{}", result.unwrap_err());
}

#[test]
fn dont_shadow_more_generalize_variant_issue_548() {
let _ = env_logger::try_init();

let text = r#"
type Test a = | Test a
type TestInt = Test Int
Test ""
"#;
let result = support::typecheck(text);

assert!(result.is_ok(), "{}", result.unwrap_err());
}

#[test]
fn dont_shadow_more_generalize_variant_2_issue_548() {
let _ = env_logger::try_init();

let text = r#"
type Test a b = | Test a b
type TestInt b = Test Int b
Test "" 1
"#;
let result = support::typecheck(text);

assert!(result.is_ok(), "{}", result.unwrap_err());
}
1 change: 0 additions & 1 deletion check/tests/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ pub fn typecheck_expr_expected(
);
let (_, mut metadata) = metadata::metadata(&env, &expr);
reparse_infix(&metadata, &*interner, &mut expr).unwrap_or_else(|err| panic!("{}", err));
eprintln!("{:?}", expr);

let mut tc = Typecheck::new(
"test".into(),
Expand Down

0 comments on commit 182c396

Please sign in to comment.