diff --git a/base/src/resolve.rs b/base/src/resolve.rs index 0e069a1fed..b5db3b5d67 100644 --- a/base/src/resolve.rs +++ b/base/src/resolve.rs @@ -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)] @@ -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) -> bool, + F: FnMut(&AliasRef) -> 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), } } diff --git a/check/src/typecheck.rs b/check/src/typecheck.rs index 5ec5f12052..38db8cb053 100644 --- a/check/src/typecheck.rs +++ b/check/src/typecheck.rs @@ -433,9 +433,36 @@ impl<'a> Typecheck<'a> { fn stack_type(&mut self, id: Symbol, alias: &Alias) { // 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( @@ -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); { diff --git a/check/tests/pass.rs b/check/tests/pass.rs index 5001480e15..28875fa1a9 100644 --- a/check/tests/pass.rs +++ b/check/tests/pass.rs @@ -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] @@ -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] @@ -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()); +} diff --git a/check/tests/support/mod.rs b/check/tests/support/mod.rs index e363af6bd1..3008f16751 100644 --- a/check/tests/support/mod.rs +++ b/check/tests/support/mod.rs @@ -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(),