Skip to content

Commit

Permalink
feat(check): Let type aliases be referred via projection
Browse files Browse the repository at this point in the history
Since we want to provide the correct spans in the errors I had to hook into the ast type translation which was slightly awkward (need to clone `type_cache` to make lifetimes work).

## Example

```f#
type Test = Int
let module = { Test }
let x : module.Test = 1
x
```

Closes gluon-lang#192

Fixes gluon-lang#370
  • Loading branch information
Marwes committed Nov 2, 2017
1 parent bd43b8a commit 49a2a04
Show file tree
Hide file tree
Showing 6 changed files with 305 additions and 131 deletions.
40 changes: 26 additions & 14 deletions base/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2010,18 +2010,16 @@ where
}
}

pub fn translate_alias<Id, T, U>(
cache: &TypeCache<Id, U>,
alias: &AliasData<Id, T>,
) -> AliasData<Id, U>
pub fn translate_alias<Id, T, U, F>(alias: &AliasData<Id, T>, mut translate: F) -> AliasData<Id, U>
where
T: Deref<Target = Type<Id, T>>,
U: From<Type<Id, U>> + Clone,
Id: Clone,
F: FnMut(&T) -> U,
{
AliasData {
name: alias.name.clone(),
typ: translate_type(cache, &alias.typ),
typ: translate(&alias.typ),
}
}

Expand All @@ -2030,20 +2028,34 @@ where
T: Deref<Target = Type<Id, T>>,
U: From<Type<Id, U>> + Clone,
Id: Clone,
{
translate_type_with(cache, typ, |t| translate_type(cache, t))
}

pub fn translate_type_with<Id, T, U, F>(
cache: &TypeCache<Id, U>,
typ: &Type<Id, T>,
mut translate: F,
) -> U
where
T: Deref<Target = Type<Id, T>>,
U: From<Type<Id, U>> + Clone,
Id: Clone,
F: FnMut(&T) -> U,
{
match *typ {
Type::App(ref f, ref args) => Type::app(
translate_type(cache, f),
args.iter().map(|typ| translate_type(cache, typ)).collect(),
translate(f),
args.iter().map(|typ| translate(typ)).collect(),
),
Type::Record(ref row) => U::from(Type::Record(translate_type(cache, row))),
Type::Variant(ref row) => U::from(Type::Variant(translate_type(cache, row))),
Type::Record(ref row) => U::from(Type::Record(translate(row))),
Type::Variant(ref row) => U::from(Type::Variant(translate(row))),
Type::Forall(ref params, ref typ, ref skolem) => U::from(Type::Forall(
params.clone(),
translate_type(cache, typ),
translate(typ),
skolem
.as_ref()
.map(|ts| ts.iter().map(|t| translate_type(cache, t)).collect()),
.map(|ts| ts.iter().map(|t| translate(t)).collect()),
)),
Type::Skolem(ref skolem) => U::from(Type::Skolem(Skolem {
name: skolem.name.clone(),
Expand All @@ -2060,7 +2072,7 @@ where
.map(|field| {
Field {
name: field.name.clone(),
typ: Alias::from(translate_alias(cache, &field.typ)),
typ: Alias::from(translate_alias(&field.typ, &mut translate)),
}
})
.collect(),
Expand All @@ -2069,7 +2081,7 @@ where
.map(|field| {
Field {
name: field.name.clone(),
typ: translate_type(cache, &field.typ),
typ: translate(&field.typ),
}
})
.collect(),
Expand All @@ -2085,7 +2097,7 @@ where
// so this will not be used
Type::Alias(ref alias) => U::from(Type::Alias(AliasRef {
index: 0,
group: Arc::new(vec![translate_alias(cache, alias)]),
group: Arc::new(vec![translate_alias(alias, translate)]),
})),
Type::EmptyRow => cache.empty_row(),
}
Expand Down
50 changes: 48 additions & 2 deletions check/src/typecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1187,14 +1187,57 @@ impl<'a> Typecheck<'a> {
}
}

fn translate_projected_type(&mut self, id: &Symbol) -> TcResult<ArcType> {
let mut lookup_type: Option<ArcType> = None;
for component in id.name().module().components() {
let symbol = self.symbols.symbol(component);
lookup_type = match lookup_type {
Some(typ) => Some(self.remove_aliases(typ.clone())
.row_iter()
.find(|field| field.name.name_eq(&symbol))
.map(|field| field.typ.clone())
.ok_or_else(|| TypeError::UndefinedField(typ, symbol))?),
None => Some(self.find(&symbol)?),
};
}
let typ = lookup_type.unwrap();
let type_symbol = self.symbols.symbol(id.name().name());
self.remove_aliases(typ.clone())
.type_field_iter()
.find(|field| field.name.name_eq(&type_symbol))
.map(|field| field.typ.clone().into_type())
.ok_or_else(|| TypeError::UndefinedField(typ, type_symbol))
}

fn translate_ast_type(
&mut self,
type_cache: &TypeCache<Symbol, ArcType>,
ast_type: &AstType<Symbol>,
) -> ArcType {
use base::pos::HasSpan;

match **ast_type {
Type::Ident(ref id) if id.name().module().as_str() != "" => {
match self.translate_projected_type(id) {
Ok(typ) => typ,
Err(err) => self.error(ast_type.span(), err),
}
}
_ => types::translate_type_with(type_cache, ast_type, |typ| {
self.translate_ast_type(type_cache, typ)
}),
}
}

fn typecheck_bindings(&mut self, bindings: &mut [ValueBinding<Symbol>]) -> TcResult<()> {
self.enter_scope();
self.type_variables.enter_scope();
let level = self.subs.var_id();

for bind in bindings.iter_mut() {
if let Some(ref typ) = bind.typ {
bind.resolved_type = types::translate_type(&self.type_cache, typ);
let type_cache = self.type_cache.clone();
bind.resolved_type = self.translate_ast_type(&type_cache, typ);
}
}

Expand Down Expand Up @@ -1314,7 +1357,10 @@ impl<'a> Typecheck<'a> {
bind.alias.value.unresolved_type(),
);

let alias = types::translate_alias(&self.type_cache, &bind.alias.value);
let alias = types::translate_alias(&bind.alias.value, |typ| {
let type_cache = self.type_cache.clone();
self.translate_ast_type(&type_cache, typ)
});
resolved_aliases.push(alias);
}

Expand Down
116 changes: 1 addition & 115 deletions check/tests/fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,128 +8,14 @@ extern crate gluon_base as base;
extern crate gluon_check as check;
extern crate gluon_parser as parser;

use base::pos::Spanned;
use base::symbol::Symbol;
use base::types::{ArcType, Type};

use check::typecheck::TypeError;

#[macro_use]
mod support;

macro_rules! assert_err {
($e: expr, $($id: pat),+) => {{
#[allow(unused_imports)]
use check::typecheck::TypeError::*;
#[allow(unused_imports)]
use check::unify::Error::{TypeMismatch, Substitution, Other};
#[allow(unused_imports)]
use check::substitution::Error::{Occurs, Constraint};
#[allow(unused_imports)]
use check::unify_type::TypeError::FieldMismatch;

match $e {
Ok(x) => assert!(false, "Expected error, got {}", x),
Err(err) => {
let errors = err.errors();
let mut iter = (&errors).into_iter();
$(
match iter.next() {
Some(&Spanned { value: $id, .. }) => (),
_ => assert!(false, "Found errors:\n{}\nbut expected {}",
errors, stringify!($id)),
}
)+
assert!(iter.count() == 0, "Found more errors than expected\n{}", errors);
}
}
}}
}

macro_rules! count {
($e: pat) => {
1
};
($e: pat, $($id: pat),+) => {
1 + count!($($id),+)
}
}

macro_rules! assert_unify_err {
($e: expr, $($id: pat),+) => {
assert_multi_unify_err!($e, [$($id),+])
}
}
macro_rules! assert_multi_unify_err {
($e: expr, $( [ $( $id: pat ),+ ] ),+) => {{
use check::typecheck::TypeError::*;
#[allow(unused_imports)]
use check::unify::Error::{TypeMismatch, Substitution, Other};
#[allow(unused_imports)]
use check::substitution::Error::{Occurs, Constraint};
#[allow(unused_imports)]
use check::unify_type::TypeError::{FieldMismatch, SelfRecursive, MissingFields};

match $e {
Ok(x) => assert!(false, "Expected error, got {}", x),
Err(err) => {
let errors = err.errors();
let mut errors_iter = (&errors).into_iter().enumerate();
$(
match errors_iter.next() {
Some((i, error)) => {
match *error {
Spanned { value: Unification(_, _, ref errors), .. } => {
let mut iter = errors.iter();
let expected_count = count!($($id),+);
$(
match iter.next() {
Some(&$id) => (),
Some(error2) => {
assert!(false,
"Found errors at {}:\n{}\nExpected:\n{}\nFound\n:{:?}",
i,
error,
stringify!($id),
error2
);
}
None => {
assert!(false,
"Found {} less errors than expected at {}.\n\
Errors:\n{}\nbut expected {}",
expected_count - errors.len(),
i,
error,
stringify!($id)
);
}
}
)+
let count = iter.count();
assert!(count == 0,
"Found {} more errors than expected at {}\n{}",
count,
i,
error);
}
_ => assert!(false,
"Found errors at {}:\n\
{}\nbut expected an unification error",
i,
error)
}
}
None => ()
}
)+
assert!(errors_iter.count() == 0,
"Found more unification errors than expected\n{}",
errors);
}
}
}}
}

#[test]
fn record_missing_field() {
let _ = env_logger::init();
Expand Down
1 change: 1 addition & 0 deletions check/tests/pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use base::types::{Alias, AliasData, ArcType, Field, Generic, Type};
use support::{alias, intern, typ, MockEnv};

#[macro_use]
#[allow(unused_macros)]
mod support;

macro_rules! assert_pass {
Expand Down
Loading

0 comments on commit 49a2a04

Please sign in to comment.