Skip to content

engine: single-parameter Vensim macro invocation is silently rewritten to LOOKUP on MDL import #553

Description

@bpowers

Summary

In the MDL importer, a NAME(arg) call with exactly one argument is rewritten by the MDL/XMILE-compat converter into LOOKUP(NAME, arg) (a graphical-function lookup) before the call ever reaches the new macro resolver / BuiltinVisitor.

Consequence: a single-parameter Vensim macro cannot be invoked via a .mdl file -- the invocation is silently reinterpreted as a graphical-function lookup instead of a macro expansion. There is no error and no diagnostic; it just silently does the wrong thing.

Root cause (verified in code review)

src/simlin-engine/src/mdl/xmile_compat.rs:475:

// Check for lookup invocation (Symbol call with 1 arg)
if kind == CallKind::Symbol && args.len() == 1 {
    let table_name = self.format_var_ctx(name, subscripts, ctx);
    return format!(
        "LOOKUP({}, {})",
        table_name,
        self.format_expr_ctx(&args[0], ctx)
    );
}

A CallKind::Symbol call with exactly one argument is unconditionally formatted as LOOKUP(<name>, <arg>). This rewrite is macro-unaware: it has no knowledge of whether NAME resolves to a registered macro, so a legitimate one-parameter macro invocation is consumed here and never reaches the macro resolver / BuiltinVisitor that would otherwise expand it.

Why it matters

  • Correctness (silent): a user-authored MDL model that defines and invokes a legitimate single-parameter Vensim macro will compile and simulate, but with the macro call silently replaced by a graphical-function lookup -- wrong results, no error, no warning. This is the worst failure mode (silent miscompile, not a hard error).
  • Round-trip lossiness: round-tripping a 1-parameter macro through .mdl (Phase 6 of the macros plan -- MDL export/round-trip) would currently be lossy, since the invocation is destroyed on import.
  • Test-authoring constraint: this pre-existing behavior forced all newly-authored recursion/arity macro test fixtures to use 2-argument macros, since a 1-arg macro invocation cannot survive MDL import. It is a latent constraint on the macro test surface, not just on user models.

Scope / observability

  • This is pre-existing MDL-converter behavior: the 1-arg-call -> LOOKUP rewrite predates macro support and was not introduced by the macro work.
  • It is out of scope for Phase 3 of the Vensim macro support implementation (docs/implementation-plans/2026-05-13-macros/). All six bundled test/test-models/tests/macro_* fixtures use 2-parameter macros, so Phase 3 itself is unaffected.
  • It is a real latent correctness limitation for any user-authored MDL model that defines and invokes a legitimate single-parameter macro.

Components affected

  • src/simlin-engine/src/mdl/xmile_compat.rs (format_* call formatting; the CallKind::Symbol && args.len() == 1 -> LOOKUP rewrite at line ~475)
  • The macro resolver / BuiltinVisitor path (it never sees these calls)
  • MDL round-trip / export (Phase 6 of the macros plan)

Recommended remediation (to be addressed in the macros epic / Phase 6, not now)

Make the 1-arg NAME(arg) -> LOOKUP rewrite macro-aware: if NAME resolves to a registered macro (consult the macro registry / MacroSpec table available to the converter), expand it as a macro invocation rather than rewriting it to LOOKUP. Only fall back to the LOOKUP rewrite when NAME is not a known macro (i.e., it is genuinely a graphical-function lookup).

This belongs in the macros epic and should be considered alongside Phase 6 (MDL export / round-trip) of docs/implementation-plans/2026-05-13-macros/, since round-tripping a 1-parameter macro through .mdl is currently lossy on import.

Relationship to existing issues

Distinct from #552 (a macro formal parameter named like a control variable being silently dropped via the synthetic_param_equation / mark_variable_types control-var keying collision). That is a different code path, root cause, and symptom; this issue is the 1-arg-call -> LOOKUP rewrite in xmile_compat.rs preempting macro resolution entirely.

Discovery context

Identified during Phase 3 implementation of the Vensim macro support work (branch macros; design plan docs/implementation-plans/2026-05-13-macros/, design doc commit 86cc7fc). Out of scope for Phase 3; tracked now, to be addressed later in the macros epic / Phase 6.

Activity

  1. added
    engineIssues with the rust-based simulation engine
    compatCompatibility (XMILE, Vensim, etc) problem or enhancement
    on Jun 8, 2026
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

    compatCompatibility (XMILE, Vensim, etc) problem or enhancementengineIssues with the rust-based simulation engine

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions