Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions ndc_analyser/src/analyser/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use ndc_parser::{
BindingPattern, BindingPatternLocation, ExpressionLocation, FunctionParameter, StructField,
TypeExpr,
};
use ndc_resolver::SymbolId;
use std::rc::Rc;

/// What a function declaration consists of, named like `fn f(x) { … }` or
Expand Down Expand Up @@ -201,12 +202,13 @@ impl Analyser {
self.vars.leave_level();
self.declaring = None;
self.declared_in_batch.insert(symbol);
self.generalize(
let typ = self.generalize(
symbol,
&signature,
id,
[first_use, first_literal, first_check],
)
);
self.within_size_limit(symbol, name, typ, span)
}
_ => signature,
};
Expand Down Expand Up @@ -264,6 +266,42 @@ impl Analyser {
}
}

/// `typ`, the type of the generic function `symbol`, unless it has more
/// parts than the analyser can work with, as when each function in a
/// chain doubles what the previous one returns: `fn f1(x) => f0(f0(x))`
/// with `fn f0(x) => (x, x)`. Then it's reported, and the function takes
/// and returns `Any`, so what calls it isn't built from its type.
fn within_size_limit(
&mut self,
symbol: SymbolId,
name: Option<&str>,
typ: StaticType,
span: Span,
) -> StaticType {
const LIMIT: usize = 10_000;
let parts = std::cell::Cell::new(0_usize);
let too_large = typ.contains(&|_| {
parts.set(parts.get() + 1);
parts.get() > LIMIT
});
if !too_large {
return typ;
}
self.emit(AnalysisError::type_too_large(
name.unwrap_or("?"),
LIMIT,
span,
));
self.schemes.remove(&symbol);
let StaticType::Function { parameters, .. } = typ else {
return StaticType::Any;
};
StaticType::Function {
parameters: parameters.map(|parameters| vec![StaticType::Any; parameters.len()]),
return_type: Box::new(StaticType::Any),
}
}

/// Declares a struct's type, constructor and field accessors.
pub(super) fn analyse_struct_declaration(
&mut self,
Expand Down
14 changes: 14 additions & 0 deletions ndc_analyser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ impl AnalysisError {
}
}

pub(crate) fn type_too_large(function: &str, limit: usize, span: Span) -> Self {
Self {
text: format!(
"The type of `{function}` has more than {limit} parts, too many to work with"
),
span,
help_text: Some(
"Each call through it makes a bigger type. Annotate a parameter or the return type with `Any` to stop it growing"
.to_string(),
),
notes: Vec::new(),
}
}

pub(crate) fn tuple_index_out_of_bounds(index: i64, length: usize, span: Span) -> Self {
Self {
text: format!("Index {index} is out of bounds for a tuple of {length} elements"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Each function doubles the type the one before it returns, so the types
// grow too large to work with, which is reported instead of running out of
// memory.
// expect-error: The type of `f4` has more than 10000 parts
fn f0(x) => (x, x);
fn f1(x) => f0(f0(x));
fn f2(x) => f1(f1(x));
fn f3(x) => f2(f2(x));
fn f4(x) => f3(f3(x));
fn f5(x) => f4(f4(x));
fn f6(x) => f5(f5(x));
if false { print(f6(1)); }
Loading