Skip to content

war on Any: empty collection literals infer Any element type #149

@timfennis

Description

@timfennis

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions