From 963033a1f8fea20a7d7198711d6988c821c08985 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 14 Dec 2019 13:52:48 +0100 Subject: [PATCH] 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>, Loc>, &'a Loc>, ), 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. --- src/can/def.rs | 335 ++++++++++++++++++++++++++++++++++++++++++-- src/can/operator.rs | 6 + src/fmt/def.rs | 3 + src/parse/ast.rs | 6 + tests/test_infer.rs | 19 +++ 5 files changed, 356 insertions(+), 13 deletions(-) diff --git a/src/can/def.rs b/src/can/def.rs index 84a661befc..feab24dda7 100644 --- a/src/can/def.rs +++ b/src/can/def.rs @@ -66,6 +66,8 @@ pub fn canonicalize_defs<'a>( loc_defs: &'a bumpalo::collections::Vec<'a, &'a Located>>, flex_info: &mut Info, ) -> CanDefs { + use crate::parse::ast::Def::*; + let mut refs_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()); - for loc_def in loc_defs { - canonicalize_def( - rigids, - env, - Located { - region: loc_def.region, - value: &loc_def.value, + let mut it = loc_defs.iter().peekable(); + + while let Some(loc_def) = it.next() { + match &loc_def.value { + Annotation(pattern, annotation) => match it.peek().map(|v| v.value.clone()) { + Some(Body(body_pattern, body_expr)) if pattern == body_pattern => { + 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, - flex_info, - var_store, - &mut refs_by_symbol, - ); + + Nested(Annotation(pattern, annotation)) => match it.peek().map(|v| v.value.clone()) { + Some(Body(body_pattern, body_expr)) if pattern.value == body_pattern.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, + ); + } + }, + + _ => { + 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 { @@ -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)] fn canonicalize_def<'a>( rigids: &Rigids, @@ -271,6 +382,7 @@ fn canonicalize_def<'a>( refs_by_symbol: &mut MutMap, References)>, ) { 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. let expr_var = var_store.fresh(); @@ -309,6 +421,202 @@ fn canonicalize_def<'a>( (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 // standalone annotation), so we need to canonicalize the pattern and expr. Body(loc_pattern, loc_expr) => { @@ -663,6 +971,7 @@ fn pattern_from_def<'a>(def: &'a ast::Def<'a>) -> Option<&'a Located None, Body(ref loc_pattern, _) => Some(loc_pattern), + TypedDef(ref loc_pattern, _, _) => Some(loc_pattern), SpaceBefore(def, _) | SpaceAfter(def, _) | Nested(def) => pattern_from_def(def), } } diff --git a/src/can/operator.rs b/src/can/operator.rs index 4bd52ccb79..8330357958 100644 --- a/src/can/operator.rs +++ b/src/can/operator.rs @@ -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, 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, _) | SpaceAfter(def, _) | Nested(SpaceBefore(def, _)) diff --git a/src/fmt/def.rs b/src/fmt/def.rs index 2d854636ef..0ffc768a72 100644 --- a/src/fmt/def.rs +++ b/src/fmt/def.rs @@ -14,6 +14,9 @@ pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) { buf.push_str(" = "); 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) => { fmt_spaces(buf, spaces.iter(), indent); fmt_def(buf, sub_def, indent); diff --git a/src/parse/ast.rs b/src/parse/ast.rs index 30560f713e..65135e297a 100644 --- a/src/parse/ast.rs +++ b/src/parse/ast.rs @@ -187,6 +187,12 @@ pub enum Def<'a> { // No need to track that relationship in any data structure. Body(&'a Loc>, &'a Loc>), + TypedDef( + &'a Loc>, + Loc>, + &'a Loc>, + ), + // Blank Space (e.g. comments, spaces, newlines) before or after a def. // We preserve this for the formatter; canonicalization ignores it. SpaceBefore(&'a Def<'a>, &'a [CommentOrNewline<'a>]), diff --git a/tests/test_infer.rs b/tests/test_infer.rs index 7f544737ed..01e5a586b9 100644 --- a/tests/test_infer.rs +++ b/tests/test_infer.rs @@ -846,4 +846,23 @@ mod test_infer { "{ 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", + ); + } }