Merge branch 'fix/scope-when-backtracking'
This commit is contained in:
commit
a3c14d881d
|
@ -3918,7 +3918,7 @@ impl<'a> CodeGenerator<'a> {
|
||||||
.map(|(_, tipo)| get_generic_variant_name(tipo))
|
.map(|(_, tipo)| get_generic_variant_name(tipo))
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
*variant_name = variant.clone();
|
variant_name.clone_from(&variant);
|
||||||
|
|
||||||
if !dependency_functions
|
if !dependency_functions
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use chumsky::prelude::*;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast,
|
ast,
|
||||||
parser::{annotation, error::ParseError, token::Token, utils},
|
parser::{annotation, error::ParseError, token::Token, utils},
|
||||||
};
|
};
|
||||||
|
use chumsky::prelude::*;
|
||||||
|
|
||||||
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError> {
|
||||||
let unlabeled_constructor_type_args = annotation()
|
let unlabeled_constructor_type_args = annotation()
|
||||||
|
@ -67,7 +66,7 @@ pub fn parser() -> impl Parser<Token, ast::UntypedDefinition, Error = ParseError
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut constructor| {
|
.map(|mut constructor| {
|
||||||
if constructor.sugar {
|
if constructor.sugar {
|
||||||
constructor.name = name.clone();
|
constructor.name.clone_from(&name);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor
|
constructor
|
||||||
|
|
|
@ -2483,3 +2483,36 @@ fn not_indexable() {
|
||||||
Err((_, Error::NotIndexable { .. }))
|
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 super::Type;
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedPattern},
|
ast::{Annotation, BinOp, CallArg, LogicalOpChainKind, Span, UntypedFunction, UntypedPattern},
|
||||||
error::ExtraData,
|
error::ExtraData,
|
||||||
expr::{self, UntypedExpr},
|
expr::{self, UntypedExpr},
|
||||||
format::Formatter,
|
format::Formatter,
|
||||||
|
@ -1028,6 +1028,12 @@ The best thing to do from here is to remove it."#))]
|
||||||
#[label("unbound generic at boundary")]
|
#[label("unbound generic at boundary")]
|
||||||
location: Span,
|
location: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
#[error("Cannot infer caller without inferring callee first")]
|
||||||
|
MustInferFirst {
|
||||||
|
function: UntypedFunction,
|
||||||
|
location: Span,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtraData for Error {
|
impl ExtraData for Error {
|
||||||
|
@ -1085,7 +1091,8 @@ impl ExtraData for Error {
|
||||||
| Error::GenericLeftAtBoundary { .. }
|
| Error::GenericLeftAtBoundary { .. }
|
||||||
| Error::UnexpectedMultiPatternAssignment { .. }
|
| Error::UnexpectedMultiPatternAssignment { .. }
|
||||||
| Error::ExpectOnOpaqueType { .. }
|
| Error::ExpectOnOpaqueType { .. }
|
||||||
| Error::ValidatorMustReturnBool { .. } => None,
|
| Error::ValidatorMustReturnBool { .. }
|
||||||
|
| Error::MustInferFirst { .. } => None,
|
||||||
|
|
||||||
Error::UnknownType { name, .. }
|
Error::UnknownType { name, .. }
|
||||||
| Error::UnknownTypeConstructor { name, .. }
|
| Error::UnknownTypeConstructor { name, .. }
|
||||||
|
@ -1143,7 +1150,7 @@ impl Error {
|
||||||
rigid_type_names: ref mut annotated_names,
|
rigid_type_names: ref mut annotated_names,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
*annotated_names = new_names.clone();
|
annotated_names.clone_from(new_names);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
|
|
|
@ -26,7 +26,12 @@ use crate::{
|
||||||
line_numbers::LineNumbers,
|
line_numbers::LineNumbers,
|
||||||
tipo::{fields::FieldMap, PatternConstructor, TypeVar},
|
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;
|
use vec1::Vec1;
|
||||||
|
|
||||||
pub(crate) fn infer_function(
|
pub(crate) fn infer_function(
|
||||||
|
@ -66,9 +71,12 @@ pub(crate) fn infer_function(
|
||||||
.function_types()
|
.function_types()
|
||||||
.unwrap_or_else(|| panic!("Preregistered type for fn {name} was not a fn"));
|
.unwrap_or_else(|| panic!("Preregistered type for fn {name} was not a fn"));
|
||||||
|
|
||||||
// Infer the type using the preregistered args + return types as a starting point
|
let warnings = environment.warnings.clone();
|
||||||
let (tipo, arguments, body, safe_to_generalise) = environment.in_new_scope(|environment| {
|
|
||||||
let args = arguments
|
// ━━━ open new scope ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
let initial_scope = environment.open_new_scope();
|
||||||
|
|
||||||
|
let arguments = arguments
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&args_types)
|
.zip(&args_types)
|
||||||
.map(|(arg_name, tipo)| arg_name.to_owned().set_type(tipo.clone()))
|
.map(|(arg_name, tipo)| arg_name.to_owned().set_type(tipo.clone()))
|
||||||
|
@ -78,21 +86,57 @@ pub(crate) fn infer_function(
|
||||||
.remove(name)
|
.remove(name)
|
||||||
.unwrap_or_else(|| panic!("Could not find hydrator for fn {name}"));
|
.unwrap_or_else(|| panic!("Could not find hydrator for fn {name}"));
|
||||||
|
|
||||||
let mut expr_typer = ExprTyper::new(environment, hydrators, lines, tracing);
|
let mut expr_typer = ExprTyper::new(environment, lines, tracing);
|
||||||
|
|
||||||
expr_typer.hydrator = hydrator;
|
expr_typer.hydrator = hydrator;
|
||||||
|
expr_typer.not_yet_inferred = BTreeSet::from_iter(hydrators.keys().cloned());
|
||||||
|
|
||||||
let (args, body, return_type) =
|
// Infer the type using the preregistered args + return types as a starting point
|
||||||
expr_typer.infer_fn_with_known_types(args, body.to_owned(), Some(return_type))?;
|
let inferred =
|
||||||
|
expr_typer.infer_fn_with_known_types(arguments, body.to_owned(), Some(return_type));
|
||||||
|
|
||||||
let args_types = args.iter().map(|a| a.tipo.clone()).collect();
|
// 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 {
|
||||||
|
// Reset the environment & scope.
|
||||||
|
hydrators.insert(name.to_string(), expr_typer.hydrator);
|
||||||
|
environment.close_scope(initial_scope);
|
||||||
|
*environment.warnings = warnings;
|
||||||
|
|
||||||
|
// Backtrack and infer callee first.
|
||||||
|
infer_function(
|
||||||
|
&function,
|
||||||
|
environment.current_module,
|
||||||
|
hydrators,
|
||||||
|
environment,
|
||||||
|
lines,
|
||||||
|
tracing,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Then, try again the entire function definition.
|
||||||
|
return infer_function(fun, module_name, hydrators, environment, lines, tracing);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (arguments, body, return_type) = inferred?;
|
||||||
|
|
||||||
|
let args_types = arguments.iter().map(|a| a.tipo.clone()).collect();
|
||||||
|
|
||||||
let tipo = function(args_types, return_type);
|
let tipo = function(args_types, return_type);
|
||||||
|
|
||||||
let safe_to_generalise = !expr_typer.ungeneralised_function_used;
|
let safe_to_generalise = !expr_typer.ungeneralised_function_used;
|
||||||
|
|
||||||
Ok::<_, Error>((tipo, args, body, safe_to_generalise))
|
environment.close_scope(initial_scope);
|
||||||
})?;
|
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
|
|
||||||
// Assert that the inferred type matches the type of any recursive call
|
// Assert that the inferred type matches the type of any recursive call
|
||||||
environment.unify(preregistered_type, tipo.clone(), *location, false)?;
|
environment.unify(preregistered_type, tipo.clone(), *location, false)?;
|
||||||
|
@ -147,8 +191,6 @@ pub(crate) struct ExprTyper<'a, 'b> {
|
||||||
|
|
||||||
pub(crate) environment: &'a mut Environment<'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
|
// We tweak the tracing behavior during type-check. Traces are either kept or left out of the
|
||||||
// typed AST depending on this setting.
|
// typed AST depending on this setting.
|
||||||
pub(crate) tracing: Tracing,
|
pub(crate) tracing: Tracing,
|
||||||
|
@ -156,6 +198,9 @@ pub(crate) struct ExprTyper<'a, 'b> {
|
||||||
// Type hydrator for creating types from annotations
|
// Type hydrator for creating types from annotations
|
||||||
pub(crate) hydrator: Hydrator,
|
pub(crate) hydrator: Hydrator,
|
||||||
|
|
||||||
|
// A static set of remaining function names that are known but not yet inferred
|
||||||
|
pub(crate) not_yet_inferred: BTreeSet<String>,
|
||||||
|
|
||||||
// We keep track of whether any ungeneralised functions have been used
|
// We keep track of whether any ungeneralised functions have been used
|
||||||
// to determine whether it is safe to generalise this expression after
|
// to determine whether it is safe to generalise this expression after
|
||||||
// it has been inferred.
|
// it has been inferred.
|
||||||
|
@ -165,14 +210,13 @@ pub(crate) struct ExprTyper<'a, 'b> {
|
||||||
impl<'a, 'b> ExprTyper<'a, 'b> {
|
impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
environment: &'a mut Environment<'b>,
|
environment: &'a mut Environment<'b>,
|
||||||
hydrators: &'a mut HashMap<String, Hydrator>,
|
|
||||||
lines: &'a LineNumbers,
|
lines: &'a LineNumbers,
|
||||||
tracing: Tracing,
|
tracing: Tracing,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
hydrator: Hydrator::new(),
|
hydrator: Hydrator::new(),
|
||||||
|
not_yet_inferred: BTreeSet::new(),
|
||||||
environment,
|
environment,
|
||||||
hydrators,
|
|
||||||
tracing,
|
tracing,
|
||||||
ungeneralised_function_used: false,
|
ungeneralised_function_used: false,
|
||||||
lines,
|
lines,
|
||||||
|
@ -2346,15 +2390,11 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
|
||||||
// NOTE: Recursive functions should not run into this multiple time.
|
// NOTE: Recursive functions should not run into this multiple time.
|
||||||
// If we have no hydrator for this function, it means that we have already
|
// If we have no hydrator for this function, it means that we have already
|
||||||
// encountered it.
|
// encountered it.
|
||||||
if self.hydrators.get(&fun.name).is_some() {
|
if self.not_yet_inferred.contains(&fun.name) {
|
||||||
infer_function(
|
return Err(Error::MustInferFirst {
|
||||||
fun,
|
function: fun.clone(),
|
||||||
self.environment.current_module,
|
location: *location,
|
||||||
self.hydrators,
|
});
|
||||||
self.environment,
|
|
||||||
self.lines,
|
|
||||||
self.tracing,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -330,8 +330,8 @@ fn infer_definition(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let typed_via = ExprTyper::new(environment, hydrators, lines, tracing)
|
let typed_via =
|
||||||
.infer(arg.via.clone())?;
|
ExprTyper::new(environment, lines, tracing).infer(arg.via.clone())?;
|
||||||
|
|
||||||
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
|
let hydrator: &mut Hydrator = hydrators.get_mut(&f.name).unwrap();
|
||||||
|
|
||||||
|
@ -624,8 +624,8 @@ fn infer_definition(
|
||||||
value,
|
value,
|
||||||
tipo: _,
|
tipo: _,
|
||||||
}) => {
|
}) => {
|
||||||
let typed_expr = ExprTyper::new(environment, hydrators, lines, tracing)
|
let typed_expr =
|
||||||
.infer_const(&annotation, *value)?;
|
ExprTyper::new(environment, lines, tracing).infer_const(&annotation, *value)?;
|
||||||
|
|
||||||
let tipo = typed_expr.tipo();
|
let tipo = typed_expr.tipo();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue