Use signatures in type checking

For now only considers constants, e.g.

    foo : Int -> Int
    foo = \x -> x

But not yet rigid type variables, like `a -> a`

This kind of requires an additional variant of TypeAnnotation:

    TypedDef(
        &'a Loc<Pattern<'a>>,
        Loc<TypeAnnotation<'a>>,
        &'a Loc<Expr<'a>>,
    ),

Because otherwise combining the information from an annotation and its
succeeding body definition is hard. Currently, this variant is created
in an ad-hoc way. Perhaps this can be done during parsing?

Next, like previous commits, I've just copied the code for let, and
effectively added an additional constraint. There should be a way to
reduce the duplication again.
This commit is contained in:
Folkert 2019-12-14 13:52:48 +01:00
parent 4154805e23
commit 963033a1f8
5 changed files with 356 additions and 13 deletions

View file

@ -66,6 +66,8 @@ pub fn canonicalize_defs<'a>(
loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>, loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located<ast::Def<'a>>>,
flex_info: &mut Info, flex_info: &mut Info,
) -> CanDefs { ) -> CanDefs {
use crate::parse::ast::Def::*;
let mut refs_by_symbol = MutMap::default(); let mut refs_by_symbol = MutMap::default();
let mut can_defs_by_symbol = MutMap::default(); let mut can_defs_by_symbol = MutMap::default();
@ -80,20 +82,98 @@ pub fn canonicalize_defs<'a>(
scope.idents = union_pairs(scope.idents.clone(), defined_idents.iter()); scope.idents = union_pairs(scope.idents.clone(), defined_idents.iter());
for loc_def in loc_defs { let mut it = loc_defs.iter().peekable();
canonicalize_def(
rigids, while let Some(loc_def) = it.next() {
env, match &loc_def.value {
Located { Annotation(pattern, annotation) => match it.peek().map(|v| v.value.clone()) {
region: loc_def.region, Some(Body(body_pattern, body_expr)) if pattern == body_pattern => {
value: &loc_def.value, it.next();
let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
canonicalize_def(
rigids,
env,
Located {
region: loc_def.region,
value: &typed,
},
scope,
&mut can_defs_by_symbol,
flex_info,
var_store,
&mut refs_by_symbol,
);
}
_ => {
canonicalize_def(
rigids,
env,
Located {
region: loc_def.region,
value: &loc_def.value,
},
scope,
&mut can_defs_by_symbol,
flex_info,
var_store,
&mut refs_by_symbol,
);
}
}, },
scope,
&mut can_defs_by_symbol, Nested(Annotation(pattern, annotation)) => match it.peek().map(|v| v.value.clone()) {
flex_info, Some(Body(body_pattern, body_expr)) if pattern.value == body_pattern.value => {
var_store, it.next();
&mut refs_by_symbol,
); let typed = TypedDef(body_pattern, annotation.clone(), body_expr);
canonicalize_def(
rigids,
env,
Located {
region: loc_def.region,
value: &typed,
},
scope,
&mut can_defs_by_symbol,
flex_info,
var_store,
&mut refs_by_symbol,
);
}
_ => {
canonicalize_def(
rigids,
env,
Located {
region: loc_def.region,
value: &loc_def.value,
},
scope,
&mut can_defs_by_symbol,
flex_info,
var_store,
&mut refs_by_symbol,
);
}
},
_ => {
canonicalize_def(
rigids,
env,
Located {
region: loc_def.region,
value: &loc_def.value,
},
scope,
&mut can_defs_by_symbol,
flex_info,
var_store,
&mut refs_by_symbol,
);
}
}
} }
CanDefs { CanDefs {
@ -259,6 +339,37 @@ pub fn sort_can_defs(
} }
} }
fn canonicalize_annotation(annotation: &crate::parse::ast::TypeAnnotation) -> crate::types::Type {
use crate::parse::ast::TypeAnnotation::*;
match annotation {
Function(argument_types, return_type) => {
let mut args = Vec::new();
for arg in *argument_types {
args.push(canonicalize_annotation(&arg.value));
}
let ret = canonicalize_annotation(&return_type.value);
Type::Function(args, Box::new(ret))
}
Apply(module_name, name, type_arguments) => {
let mut args = Vec::new();
for arg in *type_arguments {
args.push(canonicalize_annotation(&arg.value));
}
Type::Apply {
module_name: module_name.join(".").into(),
name: (*name).into(),
args,
}
}
_ => panic!("TODO implement canonicalize annotation"),
}
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn canonicalize_def<'a>( fn canonicalize_def<'a>(
rigids: &Rigids, rigids: &Rigids,
@ -271,6 +382,7 @@ fn canonicalize_def<'a>(
refs_by_symbol: &mut MutMap<Symbol, (Located<Ident>, References)>, refs_by_symbol: &mut MutMap<Symbol, (Located<Ident>, References)>,
) { ) {
use crate::parse::ast::Def::*; use crate::parse::ast::Def::*;
use crate::types::AnnotationSource;
// Make types for the body expr, even if we won't end up having a body. // Make types for the body expr, even if we won't end up having a body.
let expr_var = var_store.fresh(); let expr_var = var_store.fresh();
@ -309,6 +421,202 @@ fn canonicalize_def<'a>(
(None, (loc_expr, Output::default())) (None, (loc_expr, Output::default()))
} }
TypedDef(loc_pattern, loc_annotation, loc_expr) => {
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
// (However, still include it in scope, because you *can* recursively refer to yourself.)
let mut shadowable_idents = scope.idents.clone();
remove_idents(&loc_pattern.value, &mut shadowable_idents);
let pattern_var = var_store.fresh();
let pattern_type = Type::Variable(pattern_var);
let pattern_expected = PExpected::NoExpectation(pattern_type);
let mut state = PatternState {
headers: SendMap::default(),
vars: Vec::with_capacity(1),
constraints: Vec::with_capacity(1),
};
let loc_can_pattern = canonicalize_pattern(
env,
&mut state,
var_store,
scope,
Assignment,
&loc_pattern.value,
loc_pattern.region,
&mut shadowable_idents,
pattern_expected,
);
flex_info.vars.push(pattern_var);
// Any time there's a lookup on this symbol in the outer Let,
// it should result in this expression's type. After all, this
// is the type to which this symbol is defined!
add_pattern_to_lookup_types(
&scope,
&loc_pattern,
&mut flex_info.def_types,
expr_type.clone(),
);
// bookkeeping for tail-call detection. If we're assigning to an
// identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called.
let outer_identifier = env.tailcallable_symbol.clone();
let mut fname = "invalid name".to_string();
if let (
&ast::Pattern::Identifier(ref name),
&Pattern::Identifier(_, ref defined_symbol),
) = (&loc_pattern.value, &loc_can_pattern.value)
{
fname = name.to_string();
env.tailcallable_symbol = Some(defined_symbol.clone());
variables_by_symbol.insert(defined_symbol.clone(), expr_var);
};
let annotation_expected = FromAnnotation(
fname,
0,
AnnotationSource::TypedBody,
canonicalize_annotation(&loc_annotation.value),
);
let (mut loc_can_expr, can_output, ret_constraint) = canonicalize_expr(
rigids,
env,
var_store,
scope,
loc_expr.region,
&loc_expr.value,
annotation_expected.clone(),
);
state
.constraints
.push(Eq(expr_type.clone(), annotation_expected, loc_def.region));
// reset the tailcallable_symbol
env.tailcallable_symbol = outer_identifier;
flex_info.constraints.push(Let(Box::new(LetConstraint {
rigid_vars: Vec::new(),
flex_vars: state.vars,
def_types: state.headers,
defs_constraint: And(state.constraints),
ret_constraint,
})));
// see below: a closure needs a fresh References!
let mut is_closure = false;
// First, make sure we are actually assigning an identifier instead of (for example) a tag.
//
// If we're assigning (UserId userId) = ... then this is certainly not a closure declaration,
// which also implies it's not a self tail call!
//
// Only defs of the form (foo = ...) can be closure declarations or self tail calls.
if let (
&ast::Pattern::Identifier(ref _name),
&Pattern::Identifier(_, ref defined_symbol),
&Closure(ref symbol, _, ref arguments, ref body),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
&loc_can_expr.value.clone(),
) {
is_closure = true;
// Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.)
let references = env.closures.remove(&symbol).unwrap_or_else(|| {
panic!(
"Tried to remove symbol {:?} from procedures, but it was not found: {:?}",
symbol, env.closures
)
});
// Re-insert the closure into the map, under its defined name.
// closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name.
env.closures.insert(defined_symbol.clone(), references);
// The closure is self tail recursive iff it tail calls itself (by defined name).
let is_recursive = match can_output.tail_call {
Some(ref symbol) if symbol == defined_symbol => Recursive::TailRecursive,
_ => Recursive::NotRecursive,
};
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol
.entry(defined_symbol.clone())
.and_modify(|(_, refs)| {
refs.locals = refs.locals.without(defined_symbol);
});
// renamed_closure_def = Some(&defined_symbol);
loc_can_expr.value = Closure(
symbol.clone(),
is_recursive,
arguments.clone(),
body.clone(),
);
}
let mut defined_symbols = Vec::new();
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other.
for (ident, (symbol, region)) in
idents_from_patterns(std::iter::once(*loc_pattern), &scope)
{
let refs =
// Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
if is_closure {
References::new()
} else {
can_output.references.clone()
};
refs_by_symbol.insert(
symbol.clone(),
(
Located {
value: ident,
region,
},
refs,
),
);
defined_symbols.push(symbol.clone());
}
for symbol in defined_symbols {
can_defs_by_symbol.insert(
symbol,
Def {
// TODO try to remove this .clone()!
pattern: loc_can_pattern.clone(),
expr: Located {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
},
variables_by_symbol: im::HashMap::clone(&variables_by_symbol),
},
);
}
(Some(loc_pattern), (loc_can_expr, can_output))
}
// If we have a pattern, then the def has a body (that is, it's not a // If we have a pattern, then the def has a body (that is, it's not a
// standalone annotation), so we need to canonicalize the pattern and expr. // standalone annotation), so we need to canonicalize the pattern and expr.
Body(loc_pattern, loc_expr) => { Body(loc_pattern, loc_expr) => {
@ -663,6 +971,7 @@ fn pattern_from_def<'a>(def: &'a ast::Def<'a>) -> Option<&'a Located<ast::Patter
match def { match def {
Annotation(_, _) => None, Annotation(_, _) => None,
Body(ref loc_pattern, _) => Some(loc_pattern), Body(ref loc_pattern, _) => Some(loc_pattern),
TypedDef(ref loc_pattern, _, _) => Some(loc_pattern),
SpaceBefore(def, _) | SpaceAfter(def, _) | Nested(def) => pattern_from_def(def), SpaceBefore(def, _) | SpaceAfter(def, _) | Nested(def) => pattern_from_def(def),
} }
} }

View file

@ -38,6 +38,12 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
Body(loc_pattern, loc_expr) | Nested(Body(loc_pattern, loc_expr)) => { Body(loc_pattern, loc_expr) | Nested(Body(loc_pattern, loc_expr)) => {
Body(loc_pattern, desugar_expr(arena, loc_expr)) Body(loc_pattern, desugar_expr(arena, loc_expr))
} }
TypedDef(loc_pattern, loc_annotation, loc_expr)
| Nested(TypedDef(loc_pattern, loc_annotation, loc_expr)) => TypedDef(
loc_pattern.clone(),
loc_annotation.clone(),
desugar_expr(arena, loc_expr),
),
SpaceBefore(def, _) SpaceBefore(def, _)
| SpaceAfter(def, _) | SpaceAfter(def, _)
| Nested(SpaceBefore(def, _)) | Nested(SpaceBefore(def, _))

View file

@ -14,6 +14,9 @@ pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) {
buf.push_str(" = "); buf.push_str(" = ");
fmt_expr(buf, &loc_expr.value, indent, false); fmt_expr(buf, &loc_expr.value, indent, false);
} }
TypedDef(_loc_pattern, _loc_annotation, _loc_expr) => {
panic!("TODO support Annotation in TypedDef");
}
SpaceBefore(sub_def, spaces) => { SpaceBefore(sub_def, spaces) => {
fmt_spaces(buf, spaces.iter(), indent); fmt_spaces(buf, spaces.iter(), indent);
fmt_def(buf, sub_def, indent); fmt_def(buf, sub_def, indent);

View file

@ -187,6 +187,12 @@ pub enum Def<'a> {
// No need to track that relationship in any data structure. // No need to track that relationship in any data structure.
Body(&'a Loc<Pattern<'a>>, &'a Loc<Expr<'a>>), Body(&'a Loc<Pattern<'a>>, &'a Loc<Expr<'a>>),
TypedDef(
&'a Loc<Pattern<'a>>,
Loc<TypeAnnotation<'a>>,
&'a Loc<Expr<'a>>,
),
// Blank Space (e.g. comments, spaces, newlines) before or after a def. // Blank Space (e.g. comments, spaces, newlines) before or after a def.
// We preserve this for the formatter; canonicalization ignores it. // We preserve this for the formatter; canonicalization ignores it.
SpaceBefore(&'a Def<'a>, &'a [CommentOrNewline<'a>]), SpaceBefore(&'a Def<'a>, &'a [CommentOrNewline<'a>]),

View file

@ -846,4 +846,23 @@ mod test_infer {
"{ x : a }b -> { x : a }b", "{ x : a }b -> { x : a }b",
); );
} }
#[test]
fn using_type_signature() {
infer_eq(
indoc!(
r#"
foo = \x -> x
bar : Int -> Int
bar = foo
baz : Bool -> Bool
baz = foo
bar
"#
),
"Int -> Int",
);
}
} }