Resolve a block parameter in the mask workspaces around it - #23
Merged
Conversation
A masked subsystem's parameters are a resolution scope, and this package knew three: model workspace, dictionary chain, MAT-file. So a `Gain = g1` inside a mask resolved to nothing and rendered as bare unlinked text — a name appearing nowhere in the workspace the reader was looking at — while the model workspace variable `g1_param` the mask actually reads collected no usage at all, because `<Mask>` was never read. Both halves are one fact seen from either end, and both are now implemented: - a mask parameter's VALUE is a parameter expression of the MASKED BLOCK, evaluated where that block sits, so `MulAdd` gains a row reading `g1 = g1_param` and `g1_param` gains the user `MulAdd`; - a mask parameter's NAME is a definition for the blocks INSIDE, so the inner `Gain`'s `g1` resolves to the enclosing mask — reported as `kind: 'mask'` with the masked block in `maskBlock`, and a `linkTarget` of that block's key. Four rules came out of `Simulink.findVars`, not out of the file format, and each is an arm of the new fixture (test/parity/matlab/gen_mask.m): - a value is credited even when nothing inside reads the parameter; - only expression-valued TYPES count — a popup's value is the option text the user picked, and the fixture plants a workspace variable spelled like one; - a name shadows outwards, so the model workspace's `shadowed` loses its usage and the inner mask's `o1` beats the outer's; - a value resolves in the mask block's ENCLOSING scope, never its own, so `Inner`'s `i1 = o1` credits `Outer`'s `o1`. `Evaluate="off"` is a fifth and separate gate: the value is the literal string the user typed. Both were measured on R2025a and R2027a, which agree exactly. The classic `.mdl` spells all of this in three flat properties read together — `MaskVariables` names each parameter and indexes into `MaskValueString`, with the type at the same index of `MaskStyleString`. Those properties used to fall through the ordinary parameter loop, so that flavour credited the masked block one row named `MaskValueString` carrying every value at once, where the `.slx` of the same diagram credited nothing. Both now read the same masks, pinned by a new cross-format pair built from one diagram by one MATLAB. Also: `isInsideBlockPath`, which the scope rule needs, because `/` is escaped by doubling and `A//B/C` is a block inside a block named `a/b` — a plain `startsWith` would let a mask on a sibling `A` swallow it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A masked subsystem's parameters are a resolution scope, and this package knew three: model workspace, dictionary chain, MAT-file. So a
Gain = g1inside a mask resolved to nothing and rendered as bare unlinked text — a name appearing nowhere in the workspace the reader was looking at — while the model workspace variableg1_paramthe mask actually reads collected no usage at all, because<Mask>was never read.Both halves
MulAddgains a row readingg1 = g1_param, andg1_paramgains the userMulAdd;Gain'sg1resolves to the enclosing mask, reported askind: 'mask'with the masked block inmaskBlockand alinkTargetof that block's key.The rules, measured rather than derived
Simulink.findVarson a purpose-built model (test/parity/matlab/gen_mask.m), one arm per rule:edit,slider,dial,spinbox,min,max, and notcheckbox,popup,combobox,listbox,radiobutton,unit,promote. A popup's value is the option text the user picked, and the fixture plants a workspace variable spelled exactly like one;shadowedloses its usage, and the inner mask'so1beats the outer's;Inner'si1 = o1creditsOuter'so1.Evaluate="off"is a fifth, separate gate (probe_evaluate.m): the value is the literal string the user typed. Everything above was measured on R2025a and R2027a, whose answers are byte-identical.The classic
.mdlThat format spells a mask in three flat properties read together —
MaskVariablesnames each parameter and indexes intoMaskValueString, with the type at the same index ofMaskStyleString. They used to fall through the ordinary parameter loop, so the classic flavour credited the masked block one row namedMaskValueStringcarrying every value at once where the.slxof the same diagram credited nothing. Both now read the same masks, pinned by a new cross-format pair built from one diagram by one MATLAB.Tests
test/maskWorkspace.test.ts— the parsers, the scope rule, the index, and the truth-derived parity read both ways round, so a variable MATLAB does not credit must have no users on our side either;test/usageEngines.test.ts— the same answers pinned on both usage engines, which reach the mask by unrelated routes (resolveNameagainst a summary,collectUsagesagainst aModelNode);drift.mjsnow regenerates the mask truth too, since these expectations are pure MATLAB behaviour and a release could change its mind silently.npm run verifygreen: 4317 tests, typecheck, build, smoke, pack and leak checks.