Problem
The single largest source of Any in the analyzer. When a function is declared without parameter type annotations, every parameter is given type TypeBinding::Inferred(StaticType::Any) (ndc_analyser/src/analyser.rs:700–705). This cascades through the entire function body: arithmetic on Any-typed parameters dispatches through the all-by-name fallback, operations return Any, and the inferred return type ends up as Any too.
fn add(x, y) { x + y }
// x: Any, y: Any → x + y dispatches dynamically → return type: Any
// caller: let z = add(1, 2) → z: Any
Since most user-defined functions are unannotated, this is the root cause of most Any in practice.
Root cause
resolve_parameters_declarative in ndc_analyser/src/analyser.rs checks param.type_name != StaticType::Any to detect explicit annotations. Parameters without annotations come from the parser with type_name = StaticType::Any. No inference is attempted — the Any stands for the duration of analysis.
Impact
Severity: Critical. Every operation inside an unannotated function body produces Any. Callers of those functions also get Any. In practice this means the analyzer gives no useful type information for the majority of user code.
Approaches
Partial fix (low effort, high payoff)
Register all stdlib functions with explicit, narrow parameter types where possible. This won't help user-defined functions but immediately improves let z = add(1, 2) where add is a stdlib function.
Medium fix (medium effort)
After the function body is fully analyzed, re-examine all call sites within the same translation unit and narrow parameter types from the LUB of argument types at all call sites. This is a single backward pass over the call graph.
Full fix (high effort)
Implement proper bidirectional / Hindley-Milner–style inference with constraint generation and unification. This would handle mutual recursion, higher-order functions, and cross-module calls. This is a significant architectural change.
Notes
- The parser represents unannotated params as
type_name = StaticType::Any; this conflates "annotated as Any" with "unannotated." A first step would be distinguishing these two states (e.g., Option<StaticType> where None means unannotated).
- Fixing this class eliminates the downstream
Any in for-loop variables, map comprehensions, and any expression depending on a call to a user-defined function.
Problem
The single largest source of
Anyin the analyzer. When a function is declared without parameter type annotations, every parameter is given typeTypeBinding::Inferred(StaticType::Any)(ndc_analyser/src/analyser.rs:700–705). This cascades through the entire function body: arithmetic onAny-typed parameters dispatches through the all-by-name fallback, operations returnAny, and the inferred return type ends up asAnytoo.Since most user-defined functions are unannotated, this is the root cause of most
Anyin practice.Root cause
resolve_parameters_declarativeinndc_analyser/src/analyser.rschecksparam.type_name != StaticType::Anyto detect explicit annotations. Parameters without annotations come from the parser withtype_name = StaticType::Any. No inference is attempted — theAnystands for the duration of analysis.Impact
Severity: Critical. Every operation inside an unannotated function body produces
Any. Callers of those functions also getAny. In practice this means the analyzer gives no useful type information for the majority of user code.Approaches
Partial fix (low effort, high payoff)
Register all stdlib functions with explicit, narrow parameter types where possible. This won't help user-defined functions but immediately improves
let z = add(1, 2)whereaddis a stdlib function.Medium fix (medium effort)
After the function body is fully analyzed, re-examine all call sites within the same translation unit and narrow parameter types from the LUB of argument types at all call sites. This is a single backward pass over the call graph.
Full fix (high effort)
Implement proper bidirectional / Hindley-Milner–style inference with constraint generation and unification. This would handle mutual recursion, higher-order functions, and cross-module calls. This is a significant architectural change.
Notes
type_name = StaticType::Any; this conflates "annotated as Any" with "unannotated." A first step would be distinguishing these two states (e.g.,Option<StaticType>whereNonemeans unannotated).Anyin for-loop variables, map comprehensions, and any expression depending on a call to a user-defined function.