Skip to content

engine: duplicate_unit diagnostics emitted once per stdlib module instance #456

Description

@bpowers

Description

Each custom unit (ghectare, hectare, pollution_unit, resource_unit) in World3 is reported as duplicate_unit ~20 times -- once per stdlib module instance that gets expanded during compilation.

Representative CLI output:

units error in model '' variable 'ghectare': duplicate_unit
units error in model '' variable 'hectare': duplicate_unit
units error in model '' variable 'pollution_unit': duplicate_unit
units error in model '' variable 'resource_unit': duplicate_unit

Note the empty model name -- the diagnostic is not attributed to any user model, which strongly suggests the duplicate-unit check is not model-scoped. The repetition count (20 instances for each of the 4 units) matches the number of stdlib module expansions in the model (smth1/smth3/delay1/delay3 sites).

Why it matters

  • User-facing noise: ~80 duplicate_unit errors for a model whose units are actually declared exactly once. Obscures real diagnostics.
  • Suggests an architectural layering bug in how stdlib modules share the parent project's unit list. If the parent's unit list is re-registered each time a stdlib model is built, the duplicate-detection check trivially fires -- which means the check is running at the wrong scope.
  • Empty model '' attribution breaks error reporting downstream: a UI can't navigate to a variable in an unnamed model.

Components affected

  • src/simlin-engine/src/db.rs (likely units_check tracked function and how it iterates across stdlib models)
  • src/simlin-engine/src/units.rs (unit-context construction, duplicate detection)
  • src/simlin-engine/src/stdlib.gen.rs (stdlib model expansion)

Possible approaches

  1. Build the project-level unit context once and share it with stdlib module instances (via Arc, aligning with Make UnitMap cheaply cloneable via Arc to reduce per-context allocation overhead #318). Do not re-register user-declared units when compiling stdlib models.
  2. Make duplicate detection idempotent: if a unit is re-declared with identical definition, accept silently; only flag conflicting redeclarations.
  3. Scope duplicate_unit diagnostics to the model where the unit was declared; never emit them against stdlib model instances, which should inherit rather than declare.

Option 1 is cleanest and also reduces allocation overhead.

Context

Discovered while fixing the delay3 conflation bug in commit 6d48816. The real CLI surfaces 80 of these diagnostics on a model that declares each unit exactly once, making it a first-impression correctness problem on any model that (a) declares custom units and (b) uses stdlib smooth/delay modules.

Related: #35 (general unit checking), #318 (UnitMap Arc sharing).

Activity

  1. added
    engineIssues with the rust-based simulation engine
    on Jun 8, 2026
  2. bpowers commented on Jul 11, 2026

    @bpowers
    OwnerAuthor

    Additional evidence: this is not specific to duplicate_unit. The same empty-model + N-fold duplication reproduces with bad_binary_op_in_units on a different fixture:

    cargo run -p simlin-cli -- simulate --ltm test/conveyors/covid19_severity.stmx 2>&1 | grep bad_binary_op_in_units
    

    emits each of these 10 times:

    units error in model '' variable 'euros_per_year_per_person': bad_binary_op_in_units
    units error in model '' variable 'dollars_per_month_per_worker': bad_binary_op_in_units
    units error in model '' variable 'jobs_per_month_per_worker': bad_binary_op_in_units
    

    The original report guessed at db.rs/stdlib.gen.rs. The actual emission site is src/simlin-engine/src/db/query.rs:62-74, in the salsa-tracked project_units_context:

    • model: String::new() is hardcoded, which is where the in model '' comes from.
    • variable: Some(unit_name.clone()) puts a unit definition name into the variable slot. euros_per_year_per_person et al. are project-level unit aliases, not model variables -- so a project-level unit-definition error is being reported through the per-variable diagnostic channel. That confirms the "not model-scoped" hypothesis in the original description, and explains why no UI can navigate to it.
    • The duplication is the accumulator drain: project_units_context accumulates CompilationDiagnostic once, but collect_all_diagnostics drains per model, so a project with N models/module instances re-reports every project-level unit diagnostic N times. The count tracks models/instances, matching the ~20x observed on World3 and the 10x here.

    So both halves of this issue live in one place, and the fix has to give project-scoped diagnostics a channel of their own (or at minimum an attribution field distinct from (model, variable)) plus a single drain. Approach 1 in the description still looks right for the duplicate_unit half, but it will not by itself fix bad_binary_op_in_units, which is a genuine syntax error in a user unit declaration and must still be reported -- exactly once, attributed to the declaration rather than to a nonexistent model variable.

    Discovered while fixing #919 (which only touches the severity word and the Error-severity gating of has_model_errors/has_variable_errors; the attribution and dedup bug here is orthogonal and untouched by that PR).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    engineIssues with the rust-based simulation engine

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions