Summary
A Vensim macro whose formal parameter is named exactly like a Vensim control variable (Final Time, Time Step, Initial Time, Saveper) is silently dropped during MDL import. No port variable is synthesized for that parameter, and no MacroSpec.parameters-vs-body mismatch is detected.
This is a latent correctness gap: currently unobservable (Phase 2 only imports macros, it does not compile or simulate them), but it becomes a real correctness bug once Phase 3+ compiles macros as modules. A macro with a control-var-named parameter would mis-compile or lose an input port.
Root cause (verified in code review)
src/simlin-engine/src/mdl/convert/macros.rs:172-189 (synthetic_param_equation) prepends a synthetic <param> = 0 equation per formal parameter before the scoped conversion. This placeholder is what later becomes the macro's port/input variable.
- The synthetic equation's LHS is canonicalized via
to_lower_space (src/simlin-engine/src/builtins.rs, the _-to-space collapse), so a parameter canonicalized to final_time keys as "final time".
src/simlin-engine/src/mdl/convert/stocks.rs:66-72 (mark_variable_types, the "second pass: mark control vars as unwanted" loop) marks the symbol whose key is "final time" (etc.) as unwanted.
- The synthetic param equation therefore collides with the control-variable key, gets marked
unwanted, and is dropped -- so no port variable is created for that formal parameter, and nothing flags the resulting parameters-vs-body mismatch.
Why it matters
- Correctness: once macros are compiled as modules (Phase 3+), a macro with a control-var-named formal parameter loses an input port or mis-compiles, with no diagnostic. Silent wrong results, not a hard error.
- Diagnostics: the parameter is dropped without any
MacroSpec.parameters-vs-body mismatch warning, so the failure is invisible at import time.
Components affected
src/simlin-engine/src/mdl/convert/macros.rs (synthetic_param_equation, macro sub-context conversion)
src/simlin-engine/src/mdl/convert/stocks.rs (mark_variable_types control-variable marking)
src/simlin-engine/src/builtins.rs (to_lower_space canonicalization -- root of the key collapse)
Scope / observability
- Pathological: no fixture in the 6-fixture macro corpus exercises this, and it is not in the Phase 2 plan scope.
- Currently unobservable because Phase 2 of the Vensim macro support work only imports macros; it does not compile or simulate them.
- Becomes a real correctness gap in Phase 3+, where macros are first compiled as modules. See
docs/implementation-plans/2026-05-13-macros/.
Recommended remediation (to be addressed in Phase 3+, not now)
In the macro sub-context conversion, prevent formal-parameter synthetic equations from being treated as control variables. Two viable approaches:
- Exclude the macro sub-context from control-variable marking in
mark_variable_types (don't run the "mark control vars as unwanted" pass over a macro body's symbol table), or
- Use a collision-proof keying/marking path for synthesized macro port parameters so the synthetic param equation's key cannot collide with a control-variable key.
This should be handled in/by Phase 3 of the Vensim macro support work (docs/implementation-plans/2026-05-13-macros/), which is where macros are first compiled as modules.
Discovery context
Identified during Phase 2 code review of the Vensim macro support implementation (branch macros; design plan docs/implementation-plans/2026-05-13-macros/, design doc commit 86cc7fc). The Phase 2 code reviewer explicitly recommended filing this via track-issue rather than blocking Phase 2, and that Phase 3+ should handle control-var-named parameters. Out of scope for Phase 2 -- tracked now, to be fixed in Phase 3+.
Summary
A Vensim macro whose formal parameter is named exactly like a Vensim control variable (
Final Time,Time Step,Initial Time,Saveper) is silently dropped during MDL import. No port variable is synthesized for that parameter, and noMacroSpec.parameters-vs-body mismatch is detected.This is a latent correctness gap: currently unobservable (Phase 2 only imports macros, it does not compile or simulate them), but it becomes a real correctness bug once Phase 3+ compiles macros as modules. A macro with a control-var-named parameter would mis-compile or lose an input port.
Root cause (verified in code review)
src/simlin-engine/src/mdl/convert/macros.rs:172-189(synthetic_param_equation) prepends a synthetic<param> = 0equation per formal parameter before the scoped conversion. This placeholder is what later becomes the macro's port/input variable.to_lower_space(src/simlin-engine/src/builtins.rs, the_-to-space collapse), so a parameter canonicalized tofinal_timekeys as"final time".src/simlin-engine/src/mdl/convert/stocks.rs:66-72(mark_variable_types, the "second pass: mark control vars as unwanted" loop) marks the symbol whose key is"final time"(etc.) asunwanted.unwanted, and is dropped -- so no port variable is created for that formal parameter, and nothing flags the resulting parameters-vs-body mismatch.Why it matters
MacroSpec.parameters-vs-body mismatch warning, so the failure is invisible at import time.Components affected
src/simlin-engine/src/mdl/convert/macros.rs(synthetic_param_equation, macro sub-context conversion)src/simlin-engine/src/mdl/convert/stocks.rs(mark_variable_typescontrol-variable marking)src/simlin-engine/src/builtins.rs(to_lower_spacecanonicalization -- root of the key collapse)Scope / observability
docs/implementation-plans/2026-05-13-macros/.Recommended remediation (to be addressed in Phase 3+, not now)
In the macro sub-context conversion, prevent formal-parameter synthetic equations from being treated as control variables. Two viable approaches:
mark_variable_types(don't run the "mark control vars as unwanted" pass over a macro body's symbol table), orThis should be handled in/by Phase 3 of the Vensim macro support work (
docs/implementation-plans/2026-05-13-macros/), which is where macros are first compiled as modules.Discovery context
Identified during Phase 2 code review of the Vensim macro support implementation (branch
macros; design plandocs/implementation-plans/2026-05-13-macros/, design doc commit 86cc7fc). The Phase 2 code reviewer explicitly recommended filing this viatrack-issuerather than blocking Phase 2, and that Phase 3+ should handle control-var-named parameters. Out of scope for Phase 2 -- tracked now, to be fixed in Phase 3+.