Fix scope management issue when deep-inferring callee.
Fixes #941. However, this currently breaks the stdlib somehow with some FreeUnique on the shrinker step of the optimizer.
This commit is contained in:
parent
b546e42766
commit
eadf709411
|
@ -2483,3 +2483,36 @@ fn not_indexable() {
|
|||
Err((_, Error::NotIndexable { .. }))
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_scope_access() {
|
||||
let source_code = r#"
|
||||
pub fn a(x: Int) {
|
||||
b(x)
|
||||
}
|
||||
|
||||
fn b(y: Int) {
|
||||
x + y
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(matches!(
|
||||
dbg!(check_validator(parse(source_code))),
|
||||
Err((_, Error::UnknownVariable { .. }))
|
||||
))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutually_recursive_1() {
|
||||
let source_code = r#"
|
||||
pub fn foo(x) {
|
||||
bar(x)
|
||||
}
|
||||
|
||||
pub fn bar(y) {
|
||||
foo(y)
|
||||
}
|
||||
"#;
|
||||
|
||||
assert!(check(parse(source_code)).is_ok());
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::Type;
|
||||
use crate::{
|
||||
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedPattern},
|
||||
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedFunction, UntypedPattern},
|
||||
error::ExtraData,
|
||||
expr::{self, UntypedExpr},
|
||||
format::Formatter,
|
||||
|
@ -1028,6 +1028,12 @@ The best thing to do from here is to remove it."#))]
|
|||
#[label("unbound generic at boundary")]
|
||||
location: Span,
|
||||
},
|
||||
|
||||
#[error("Cannot infer caller without inferring callee first")]
|
||||
MustInferFirst {
|
||||
function: UntypedFunction,
|
||||
location: Span,
|
||||
},
|
||||
}
|
||||
|
||||
impl ExtraData for Error {
|
||||
|
@ -1085,7 +1091,8 @@ impl ExtraData for Error {
|
|||
| Error::GenericLeftAtBoundary { .. }
|
||||
| Error::UnexpectedMultiPatternAssignment { .. }
|
||||
| Error::ExpectOnOpaqueType { .. }
|
||||
| Error::ValidatorMustReturnBool { .. } => None,
|
||||
| Error::ValidatorMustReturnBool { .. }
|
||||
| Error::MustInferFirst { .. } => None,
|
||||
|
||||
Error::UnknownType { name, .. }
|
||||
| Error::UnknownTypeConstructor { name, .. }
|
||||
|
|
|
@ -26,7 +26,12 @@ use crate::{
|
|||
line_numbers::LineNumbers,
|
||||
tipo::{fields::FieldMap, PatternConstructor, TypeVar},
|
||||
};
|
||||
use std::{cmp::Ordering, collections::HashMap, ops::Deref, rc::Rc};
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeSet, HashMap},
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
};
|
||||
use vec1::Vec1;
|
||||
|
||||
pub(crate) fn infer_function(
|
||||
|
@ -66,33 +71,67 @@ pub(crate) fn infer_function(
|
|||
.function_types()
|
||||
.unwrap_or_else(|| panic!("Preregistered type for fn {name} was not a fn"));
|
||||
|
||||
// ━━━ open new scope ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
let initial_scope = environment.open_new_scope();
|
||||
|
||||
let arguments = arguments
|
||||
.iter()
|
||||
.zip(&args_types)
|
||||
.map(|(arg_name, tipo)| arg_name.to_owned().set_type(tipo.clone()))
|
||||
.collect();
|
||||
|
||||
let hydrator = hydrators
|
||||
.remove(name)
|
||||
.unwrap_or_else(|| panic!("Could not find hydrator for fn {name}"));
|
||||
|
||||
let mut expr_typer = ExprTyper::new(environment, lines, tracing);
|
||||
expr_typer.hydrator = hydrator;
|
||||
expr_typer.unseen = BTreeSet::from_iter(hydrators.keys().cloned());
|
||||
|
||||
// Infer the type using the preregistered args + return types as a starting point
|
||||
let (tipo, arguments, body, safe_to_generalise) = environment.in_new_scope(|environment| {
|
||||
let args = arguments
|
||||
.iter()
|
||||
.zip(&args_types)
|
||||
.map(|(arg_name, tipo)| arg_name.to_owned().set_type(tipo.clone()))
|
||||
.collect();
|
||||
let inferred =
|
||||
expr_typer.infer_fn_with_known_types(arguments, body.to_owned(), Some(return_type));
|
||||
|
||||
let hydrator = hydrators
|
||||
.remove(name)
|
||||
.unwrap_or_else(|| panic!("Could not find hydrator for fn {name}"));
|
||||
// We try to always perform a deep-first inferrence. So callee are inferred before callers,
|
||||
// since this provides better -- and necessary -- information in particular with regards to
|
||||
// generics.
|
||||
//
|
||||
// In principle, the compiler requires function definitions to be processed *in order*. So if
|
||||
// A calls B, we must have inferred B before A. This is detected during inferrence, and we
|
||||
// raise an error about it. Here however, we backtrack from that error and infer the caller
|
||||
// first. Then, re-attempt to infer the current function. It may takes multiple attempts, but
|
||||
// should eventually succeed.
|
||||
//
|
||||
// Note that we need to close the scope before backtracking to not mess with the scope of the
|
||||
// callee. Otherwise, identifiers present in the caller's scope may become available to the
|
||||
// callee.
|
||||
if let Err(Error::MustInferFirst { function, .. }) = inferred {
|
||||
hydrators.insert(name.to_string(), expr_typer.hydrator);
|
||||
|
||||
let mut expr_typer = ExprTyper::new(environment, hydrators, lines, tracing);
|
||||
environment.close_scope(initial_scope);
|
||||
|
||||
expr_typer.hydrator = hydrator;
|
||||
infer_function(
|
||||
&function,
|
||||
environment.current_module,
|
||||
hydrators,
|
||||
environment,
|
||||
lines,
|
||||
tracing,
|
||||
)?;
|
||||
|
||||
let (args, body, return_type) =
|
||||
expr_typer.infer_fn_with_known_types(args, body.to_owned(), Some(return_type))?;
|
||||
return infer_function(fun, module_name, hydrators, environment, lines, tracing);
|
||||
}
|
||||
|
||||
let args_types = args.iter().map(|a| a.tipo.clone()).collect();
|
||||
let (arguments, body, return_type) = inferred?;
|
||||
|
||||
let tipo = function(args_types, return_type);
|
||||
let args_types = arguments.iter().map(|a| a.tipo.clone()).collect();
|
||||
|
||||
let safe_to_generalise = !expr_typer.ungeneralised_function_used;
|
||||
let tipo = function(args_types, return_type);
|
||||
|
||||
Ok::<_, Error>((tipo, args, body, safe_to_generalise))
|
||||
})?;
|
||||
let safe_to_generalise = !expr_typer.ungeneralised_function_used;
|
||||
|
||||
environment.close_scope(initial_scope);
|
||||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
// Assert that the inferred type matches the type of any recursive call
|
||||
environment.unify(preregistered_type, tipo.clone(), *location, false)?;
|
||||
|
@ -147,8 +186,6 @@ pub(crate) struct ExprTyper<'a, 'b> {
|
|||
|
||||
pub(crate) environment: &'a mut Environment<'b>,
|
||||
|
||||
pub(crate) hydrators: &'a mut HashMap<String, Hydrator>,
|
||||
|
||||
// We tweak the tracing behavior during type-check. Traces are either kept or left out of the
|
||||
// typed AST depending on this setting.
|
||||
pub(crate) tracing: Tracing,
|
||||
|
@ -156,6 +193,9 @@ pub(crate) struct ExprTyper<'a, 'b> {
|
|||
// Type hydrator for creating types from annotations
|
||||
pub(crate) hydrator: Hydrator,
|
||||
|
||||
// A static set of remaining function names that are known but not yet inferred
|
||||
pub(crate) unseen: BTreeSet<String>,
|
||||
|
||||
// We keep track of whether any ungeneralised functions have been used
|
||||
// to determine whether it is safe to generalise this expression after
|
||||
// it has been inferred.
|
||||
|
@ -165,14 +205,13 @@ pub(crate) struct ExprTyper<'a, 'b> {
|
|||
impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||
pub fn new(
|
||||
environment: &'a mut Environment<'b>,
|
||||
hydrators: &'a mut HashMap<String, Hydrator>,
|
||||
lines: &'a LineNumbers,
|
||||
tracing: Tracing,
|
||||
) -> Self {
|
||||
Self {
|
||||
hydrator: Hydrator::new(),
|
||||
unseen: BTreeSet::new(),
|
||||
environment,
|
||||
hydrators,
|
||||
tracing,
|
||||
ungeneralised_function_used: false,
|
||||
lines,
|
||||
|
@ -2346,15 +2385,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
|||
// NOTE: Recursive functions should not run into this multiple time.
|
||||
// If we have no hydrator for this function, it means that we have already
|
||||
// encountered it.
|
||||
if self.hydrators.get(&fun.name).is_some() {
|
||||
infer_function(
|
||||
fun,
|
||||
self.environment.current_module,
|
||||
self.hydrators,
|
||||
self.environment,
|
||||
self.lines,
|
||||
self.tracing,
|
||||
)?;
|
||||
if self.unseen.contains(&fun.name) {
|
||||
return Err(Error::MustInferFirst {
|
||||
function: fun.clone(),
|
||||
location: *location,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,8 +330,8 @@ fn infer_definition(
|
|||
});
|
||||
}
|
||||
|
||||
let typed_via = ExprTyper::new(environment, hydrators, lines, tracing)
|
||||
.infer(arg.via.clone())?;
|
||||
let typed_via =
|
||||
ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?;
|
||||
|
||||
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
|
||||
|
||||
|
@ -624,8 +624,8 @@ fn infer_definition(
|
|||
value,
|
||||
tipo: _,
|
||||
}) => {
|
||||
let typed_expr = ExprTyper::new(environment, hydrators, lines, tracing)
|
||||
.infer_const(&annotation, *value)?;
|
||||
let typed_expr =
|
||||
ExprTyper::new(environment, lines, tracing).infer_const(&annotation, *value)?;
|
||||
|
||||
let tipo = typed_expr.tipo();
|
||||
|
||||
|
|
Loading…
Reference in New Issue