mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
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:
parent
4154805e23
commit
963033a1f8
5 changed files with 356 additions and 13 deletions
335
src/can/def.rs
335
src/can/def.rs
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, _))
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>]),
|
||||||
|
|
|
@ -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",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue