Problem
Empty list and map literals produce container types with Any element types (ndc_analyser/src/analyser.rs:429, 447):
let xs = [] // xs: List(Any)
let m = {} // m: Map(Any, ())
Any subsequent push, index, or iteration on these variables propagates Any downstream.
Root cause
In analyse_inner for Expression::List, element type is computed as analyse_multiple_expression_with_same_type(values), which returns None for an empty values vec. The None is unwrapped as StaticType::Any:
// ndc_analyser/src/analyser.rs:428-430
Ok(StaticType::List(Box::new(
element_type.unwrap_or(StaticType::Any),
)))
The same pattern applies to the map key type (line 447).
Impact
Severity: Medium. Empty collection literals are a very common pattern — initializing before a loop, collecting results, etc. In all these cases the variable gets Any element type and subsequent operations lose precision.
Current partial behavior
When there is an explicit type annotation, resolve_lvalue_declarative uses the annotation as a type constraint, so let xs: List(Int) = [] does assign xs the type List(Int). The Any only appears when there is no annotation.
Approaches
Partial fix (low effort)
When an empty literal is on the RHS of a variable declaration that has a type annotation (let xs: List(Int) = []), validate that the annotation is consistent and use it. This already works via the assignment path but it's worth verifying end-to-end coverage in the analyzer.
Full fix (high effort)
Demand-driven / bidirectional inference: when an empty list is passed as an argument to a function with a known parameter type, or when it's used in an operation with a known type constraint, propagate that constraint back to narrow the empty literal's type. This requires a two-pass or constraint-based analysis.
Related
- Once unannotated function parameters (see related issue) are fixed, many call sites that currently return
Any will produce concrete types, which in turn allows the declaration type to be inferred from RHS even for non-empty literals.
Problem
Empty list and map literals produce container types with
Anyelement types (ndc_analyser/src/analyser.rs:429, 447):Any subsequent push, index, or iteration on these variables propagates
Anydownstream.Root cause
In
analyse_innerforExpression::List, element type is computed asanalyse_multiple_expression_with_same_type(values), which returnsNonefor an emptyvaluesvec. TheNoneis unwrapped asStaticType::Any:The same pattern applies to the map key type (line 447).
Impact
Severity: Medium. Empty collection literals are a very common pattern — initializing before a loop, collecting results, etc. In all these cases the variable gets
Anyelement type and subsequent operations lose precision.Current partial behavior
When there is an explicit type annotation,
resolve_lvalue_declarativeuses the annotation as a type constraint, solet xs: List(Int) = []does assignxsthe typeList(Int). TheAnyonly appears when there is no annotation.Approaches
Partial fix (low effort)
When an empty literal is on the RHS of a variable declaration that has a type annotation (
let xs: List(Int) = []), validate that the annotation is consistent and use it. This already works via the assignment path but it's worth verifying end-to-end coverage in the analyzer.Full fix (high effort)
Demand-driven / bidirectional inference: when an empty list is passed as an argument to a function with a known parameter type, or when it's used in an operation with a known type constraint, propagate that constraint back to narrow the empty literal's type. This requires a two-pass or constraint-based analysis.
Related
Anywill produce concrete types, which in turn allows the declaration type to be inferred from RHS even for non-empty literals.