From 0a5fd4452a5db506f78320387d4aa9ab0ff96273 Mon Sep 17 00:00:00 2001 From: Tim Fennis Date: Fri, 13 Nov 2026 17:39:46 +0100 Subject: [PATCH] =?UTF-8?q?fix(analyser):=20report=20a=20generic=20functio?= =?UTF-8?q?n=20whose=20type=20grows=20too=20large=20=F0=9F=93=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- ndc_analyser/src/analyser/declarations.rs | 42 ++++++++++++++++++- ndc_analyser/src/error.rs | 14 +++++++ ..._type_that_grows_too_large_is_an_error.ndc | 12 ++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/functional/programs/005_functions/097_a_type_that_grows_too_large_is_an_error.ndc diff --git a/ndc_analyser/src/analyser/declarations.rs b/ndc_analyser/src/analyser/declarations.rs index fe170e93..4ea90f38 100644 --- a/ndc_analyser/src/analyser/declarations.rs +++ b/ndc_analyser/src/analyser/declarations.rs @@ -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 @@ -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, }; @@ -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, diff --git a/ndc_analyser/src/error.rs b/ndc_analyser/src/error.rs index e90df755..dc7c7168 100644 --- a/ndc_analyser/src/error.rs +++ b/ndc_analyser/src/error.rs @@ -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"), diff --git a/tests/functional/programs/005_functions/097_a_type_that_grows_too_large_is_an_error.ndc b/tests/functional/programs/005_functions/097_a_type_that_grows_too_large_is_an_error.ndc new file mode 100644 index 00000000..334a9872 --- /dev/null +++ b/tests/functional/programs/005_functions/097_a_type_that_grows_too_large_is_an_error.ndc @@ -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)); }