Skip to content

Cell literals refuse unspaced arithmetic and read 0i as MATLAB does; VariantBank and VariantConfigurations follow MATLAB in every spelling - #151

Merged
ww-mw merged 12 commits into
mainfrom
cell-literals-and-variantbank-follow-matlab
Oct 19, 2026
Merged

ww-mw merged 12 commits into
mainfrom
cell-literals-and-variantbank-follow-matlab

Conversation

@ww-mw

@ww-mw ww-mw commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Seven items from group H, one commit each, on main 38c20de. Three fix rounds added four commits, and round 3 merged main 17b610c in. Each src commit carries its regenerated dist. MATLAB is R2027a 27.1.0.3409468. Every probe opens only repo fixtures copied to /tmp, or files it generates under /tmp. Each probe is committed under test/parity/matlab/ with its truth file and a test.

B-01 (d4975c3): refuse unspaced arithmetic in a cell literal

  • Ruling: group G item 6: "accept the sign and i + 1 forms as MATLAB does, and keep refusing arithmetic."
  • What was wrong: {1+2}, {a+b}, {2i+3i}, {-a} and {2*3} were each held as one char member, and editProperty(..., 'Value', '{1+2}') was accepted. MATLAB computes these values. Core already refused the spaced form {1 + 2}.
  • Root cause: in the tokenizeCellElements fallback (MatlabValueParser.ts), a bare span that no literal parser could read fell through to elements.push(span).
  • Fix: such a span is now refused when it holds + - * / \ ^, the same way a joined span is. A bare word ({abc}) stays text. A sign that belongs to one literal is still read: {-2} {1e+3} {1e-3} {1+2i} {-2i} {i+1}.
  • MATLAB: probe_cell_sign_literals.m gains 24 rows. Their output is quoted in test/cellSignMember.test.ts, and a script checked every quoted line against the log.
  • Tests: 18 unspaced rows were added to MATLAB_COMPUTES, plus {-} and {+}. The real editProperty path on cellarr_text/binary.sldd now types {1+2}, {a+b}, {-a} and {2i+3i}. Before the fix, 21 tests failed (expected { type: 'cell', value: ['1+2'] } to be null; expected true to match { error: true, ... }). The controls passed before and after. No existing assertion changed. Fix round 1 pinned that the check runs on the member, not the whole row (50896dd).
  • Left out: other bare expressions that are not arithmetic ({1:3}, {a==b}, {f(x)}) are still text, as cellElementShape.test.ts pins on purpose. Changing that needs its own call.
  • Side effect on typed edits: now that any bare member holding + - * / \ ^ is refused, some bare members that used to be accepted are refused:
    • In a VariantBank's conditions, {V==-1} and {V>-2} were written as 'V == -1' and 'V > -2'. They are now refused as "must be a cell array". {V+1==2} was refused before too, but by the condition check.
    • Other bare spans that used to be held as text are now refused as well: {@(x)x+1}, {1:-1:0}, {-int8(1)}, {-0x1F}, {e-3}.
    • {V==1} and {1:3} are still text, and the quoted {'V==-1'} is still taken.
    • MATLAB never makes any of these chars from the literal as typed, so this stays within "keep refusing arithmetic". It is disclosed here, not changed.
  • Side effect on a file that holds such a member: none. Fix round 3 checked this. Every file still opens, with nothing new refused; see that section.

B-02 (df73f30): a cell member whose imaginary part is zero is a real double

  • Ruling: group G item 6 ("as MATLAB does"), and theme 2: "follow MATLAB, and measure it first".
  • What was wrong: {0i} was held as complex 0+0i, and {1 + 0i}, {1+0i} and {0i + 1} as 1+0i. MATLAB makes each of these a real double.
  • Root cause: tokenizeCellElements kept the complex bag from parseComplex / parseImaginaryFirst even when its imaginary part was 0.
  • Fix: a new zeroImaginaryAsReal(span) returns MATLAB's real part, with IEEE signed zeros ({-0 - 0i} and {-0i} give -0, {0i - 0} gives 0). This applies to cell members only, so nested cells follow. The top level, complex-array elements and _setConstrainedValue are untouched.
  • MATLAB: the probe gains 69 rows: zero forms, real-first and imaginary-first forms, signed zeros, Inf, NaN, nested cells, arrays inside cells, and controls. The 84 earlier rows show no drift. (The commit body says 68 rows. The correct number is 69.)
  • Tests: a table of 59 measured rows, 2 of them non-zero controls, runs through parse and through editProperty on both fixtures. There is also a nested-cell test and a save/reopen in both formats: binary writes Class="double" 1.0 / -0.0, and text writes a bare 1. Before the fix, 174 failed (expected [ { re: 1, im: +0 } ] to deeply equal [ 1 ]) and the controls passed. No existing assertion changed.
  • Left out:
    • MATLAB also makes the top-level 0i / 1 + 0i real. Changing that would break "an element of a complex array stays complex", so it needs its own call.
    • A file's complex cell member with a zero imaginary part is shown as {1+0i, 5}. If you change only the 5, the save now writes that member as a real 1, where before this PR it stayed complex.
      • Both formats were reproduced: written by main, reopened on this branch, 1+0i:complex becomes 1:double.
      • This is what MATLAB makes of {1+0i, 6}. But the top level stays complex for exactly this round-trip reason, and _literalRoundTrips does not catch it for a cell member.
      • Follow-up: _literalRoundTrips could return false for such a member, so the element is edited instead.

B-03 (25ffd3f): pin the VariantBank refusal of {i+1}, {i + 1}, {- 2}

  • Ruling: the group G follow-up "a VariantBank conditions edit of {i+1} is now refused, matching MATLAB, but unpinned".
  • Fix (test only): three REFUSED rows in variantBankFields.test.ts expect MATLAB's measured "must be a scalar string." message, in both formats.
  • Fail-before: two mutants were applied and then restored. Dropping parseImaginaryFirst from the cell path fails the {i+1} and {i + 1} rows (4 failures). Disabling the unary-sign normalization fails {- 2} (2 failures). cellSignMember.test.ts already killed both mutants (121 and 84 failures); what no test held before was the VariantBank-level message. No existing test changed, and dist is unchanged.

B-06 (ab2880a): read and edit a VariantBank held as plain properties

  • Ruling: C-168 under theme 2 ("follow MATLAB, and measure it first"), using the fix shape Simulink.VariantBank and VariantBankCoderInfo show and edit their real fields, as MATLAB does #141 named ("the same fix shape C-169B uses"). Theme 1 covers hand-edited input: "warn and show what was read; never refuse the file and never crash."
  • What was wrong: a bank in R2026b's plain spelling showed defaults. Its values appeared only as Other rows, and every edit was refused with "nowhere to be saved".
  • Root cause: the VariantBankNode constructor read fields only through readSaveobjField, and edits wrote only into the envelope.
  • Fix: fieldOf reads the envelope, or else the plain property, as VariantBankCoderInfoNode does. It is used by the constructor, by the "nowhere to be saved" check and by unreadFields. In a plain bag, _mergeProps writes into the plain property and does not invent an envelope. Warnings for a plain bag name MATLAB's per-property reading. The four bank props gain their plain source keys, and the docs gain a section on R2026b's spelling.
  • MATLAB: variantbank_plain.mjs respells the repo's R2027a fixture into 30 hand cases and 4 core saves, all under /tmp. probe_variantbank_plain.m writes variantbank_plain.truth.json. MATLAB reads such a bag property by property, with no warning. A value a setter refuses becomes that property's default alone. All 4 core saves were read back as written.
  • Tests: a new describe in variantBankFields.test.ts covers display, edit and undo, grades 28 hand cases against the truth, and checks the 4 save cases. The fixture text is pinned by sha256. Before the fix, 35 failed (cells showed defaults, and edits were refused as "nowhere to be saved"). Three mutants gave 10, 5 and 1 failures. No existing test changed.
  • Left out: a condition past int32 gets no warning in either spelling (follow-up). No R2026b-written plain bank was available to compare against, so the key order and the presence of BankCoderInfo are not confirmed. MATLAB was measured both with and without the key and in both orders, and its reading does not depend on either.

B-07 (99f6d9f): pin that a 302-character DefinitionFile is taken

  • Ruling: the test gap of C-169B. Simulink.VariantBank and VariantBankCoderInfo show and edit their real fields, as MATLAB does #141 measured that a 302-character name is accepted (make_variantbank_r2027a.m:211).
  • Fix (test only): one CI_EDITS row, 'a'.repeat(300) + '.c', in both formats.
  • Fail-before: a mutant gives DefinitionFile the HeaderFile 255-character rule. The whole suite stayed green under it before this row. With the row, it fails twice, receiving "Check specified header file name ...". No existing test changed, and dist is unchanged.

B-08 (269f897): warn of a VariantBankCoderInfo held under a property named undefined

  • Ruling: theme 1 ("warn and show what was read; never refuse the file"), plus theme 2.
  • What was wrong: a coder info whose properties sat under <P Name="undefined"> showed five blank cells, plus an Other.undefined row, with no warning.
  • Root cause: fieldOf reads only named fields, and nothing reported the bag it could not read.
  • Fix: a new heldUnderUndefined(props) covers the case with no envelope, an own undefined key and none of the five fields. In that case unreadFields adds one part-unreadable clause, worded per format. Display, save-as-read and the edit refusal are unchanged, and are now pinned.
  • MATLAB: coderinfo_undefined.mjs builds 15 /tmp files from the repo fixtures (5 at first; the controls were added in the fix rounds), and probe_coderinfo_undefined.m writes the truth. In binary, the dictionary opens, but that entry's getValue fails ("unable to create an object of class 'Simulink.VariantBankCoderInfo'"). In text, MATLAB reads every default. When some of the five properties sit beside undefined, MATLAB reads those and ignores the undefined. MATLAB gave no warning in any case.
  • Tests: a new describe with 15 cases, each file pinned by sha256: 3 damaged, 1 "beside", 1 as written, and 10 controls. Before the fix, the 3 damaged cases failed at the warning assertion (expected [] to deeply equal [['ciA','part-unreadable']]). The controls and the "beside" case pass by design: MATLAB warns of none of them, and neither does core. The mutants of the check are listed under the fix rounds; at head every one is killed. No existing test changed.
  • Left out:
    • Another saveobj class whose envelope was written as undefined still gets no warning; it needs its own measurement. A repair action would be a new design decision.
    • The partial-bag controls are text only. heldUnderUndefined does not depend on the format, so a binary coder info with no envelope, an undefined and some of the five is not measured, and is not warned of. MATLAB does not write a binary plain bag, and the old package wrote undefined with none of the five beside it, so that case is hypothetical. A follow-up could measure it.

B-19 (03abbc2): VariantConfigurations has no Value

  • Ruling: theme 2 ("follow MATLAB, and measure it first"), as the twin of C-168/C-169. Group E recorded: "VariantConfigurations' Value is probably invented too; measure it in MATLAB."
  • What was wrong: the node defaulted Value to '' and accepted a Value edit. Both save paths wrote that Value beside the envelope, and an unedited plain-bag save added "Value": "".
  • MATLAB: probe_variantconfigurations_value.m opens no file. Under both class names, the object built is Simulink.VariantConfigurations and isprop(...,'Value') is 0. Its metaclass lists 10 properties and none is Value. Setting one fails: "Unrecognized property 'Value' ...".
  • Fix: a Value edit is refused, in VariantBankNode's words, and _serializedOverrides() returns {}. A Value key a file already carries is written back as read.
  • Tests: the new test/variantConfigurationsNoValue.test.ts has 7 tests, and 6 of the 7 failed against main's node. The MATLAB-truth plus createDefault fields test passes on main by design. The 7th failure in the before-run was the changed objectArrayElementClass pin.
  • Changed existing tests, each of which pinned exactly the removed behaviour:
    • variantLeafNodes.test.ts: the class joins READ_ONLY, because its "writes an edit back" case was the removed Value edit.
    • objectArrayElementClass.test.ts: the exemption for configurations ("its node writes a Value") is removed.
    • newEntryEnvelopeBothFormats.test.ts (C-199): the declared:false row and the "undeclared field" test used VCD's Value as their vehicle. They move to a test-local custom saver, Test.UndeclaredSibling, which still goes through the real writers and readers. A mutant that drops delete stored[key] in DataNode._mergeProps fails all 3 of its tests.
  • Left out: a Value already in a file is kept but not shown (displayValue stays '').

Gates at 03abbc2 (before the fix rounds)

Gates at the current head are under Fix round 3.

  • rm -rf dist && npm run build leaves the tree clean.
  • npm run verify: exit 0. 296 test files passed and 1 skipped; 10435 tests passed and 27 skipped. The smoke, pack, leak and browser checks are OK.
  • DEX_READBACKS_CORPUS=... npx vitest run test/parity/readbacks.truth.test.ts test/parity/m9MergedSaves.truth.test.ts: 45 of 45 passed.
  • Live DEX_MATLAB_CMD tests were not run. None of them reaches these paths: the writeback/fidelity live tests type scalars, arrays and a char, and none covers VariantBank or VariantConfigurations. variable.fidelity's {1, 2} cell edit reaches no changed branch either: it is refused before and after.
  • Every commit was built and verified green when it was made.

Fix round 1

Two new commits on top of 03abbc2. No force-push, and no earlier commit was changed. main was 31b8ac3; #150 changes no src or dist. The branch merged into it with no conflict, so main was not merged in.

  • 50896dd (B-01, test only): the "keeps a bare word" test in cellSignMember.test.ts now also parses {abc, -2}, {abc, 1e-3}, {abc, 'x-y'}, {abc, [1 -2]} and {-2, abc}. It expects the word to stay text and the other member to be read as before. Main and this branch both give that. If the operator check runs on the whole row (.test(rowStr)) instead of on the member (.test(span)), every one of these comes back null, and the test fails (expected null to deeply equal { type: 'cell', … }). No other test changed, and dist is unchanged.
  • 4fed791 (B-08): measured first, then pinned. The same probe run (R2027a 27.1.0.3409468, on /tmp files made from the repo fixtures) gained four control cases:
    • an undefined struct holding other values beside the envelope MATLAB wrote, in binary (b_ciA_envelope_beside) and in text (t_ciA_envelope_beside). MATLAB reads the envelope and ignores the undefined.
    • a text plain bag with no property (t_ciA_empty_bag), and one holding only Foo: 'x' (t_ciA_other_bag). MATLAB reads every default.
    • MATLAB gave no warning for any of the four, so core gives none either. The five earlier truth rows did not change; only the date did.
    • variantBankFields.test.ts grades each control against the truth: no warning, shown as read, its Other. rows listed, and saved as read.
    • If own(UNDEFINED_KEY) is dropped, the two bag controls fail. If !(SAVEOBJ_KEY in props) is dropped, the two envelope controls fail. Each failure is the 'undefined' warning that MATLAB does not give.
    • The only src change is the comment above heldUnderUndefined, and dist was regenerated. The coder info doc, the probe header and the README row name the controls.
  • Gates at 4fed791:
    • rm -rf dist && npm run build leaves the tree clean.
    • npm run verify: exit 0. 296 files passed and 1 skipped; 10439 tests passed and 27 skipped (4 more than before).
    • DEX_READBACKS_CORPUS readbacks plus m9MergedSaves: 45/45.
    • Live MATLAB tests: none reaches these changes.
    • vscode was not re-run this round. The reviewers ran it at 03abbc2 against vscode main b09b07f: verify 3539/3539, integration 274 passing. Since then dist differs only in one comment and its source maps.

Fix round 2

One new commit on top of 4fed791. There was no force-push, and no earlier commit changed. main was still 31b8ac3, and the trial merge was clean, so main was not merged in.

  • 1c0a169 (B-08): measured first, then pinned. In heldUnderUndefined, the part !FIELDS.some(own) was not held by any test. Two mutants passed the full suite: .every in place of .some, and a check of HeaderFile alone. Under either, a text plain bag with an undefined beside only some of the five properties got a false part-unreadable warning, saying that MATLAB reads the coder info with every property at its default.
    • What MATLAB does (R2027a 27.1.0.3409468, on /tmp files made from test/fixtures/variantbank_r2027a_text.sldd): two new control cases.

      • t_ciA_partial_header is ciA's HeaderFile alone, beside an undefined struct of other values. MATLAB reads h.h, with every other property at its default.
      • t_ciA_partial_definition is ciA's DefinitionFile and Qualifier, beside the same struct. MATLAB reads d.c and Const, with every other property at its default.
      • In both cases MATLAB ignores the undefined and gives no warning. Core already did the same, so there is no behaviour change. The measurement is text only (see B-08, Left out).
    • coderinfo_undefined.mjs makes the two files. The truth gains the two cases and their rows. The 9 earlier rows are byte-identical, and so is the header (date 2026-10-19).

    • variantBankFields.test.ts grades both cases as controls: no warning, shown and listed (Other.undefined) as read, and saved as read.

    • The two bags hold no field in common, so every single-field form of the check is killed:

      mutant tests that fail
      .every both controls
      !own('HeaderFile') t_ciA_partial_definition
      !own('DefinitionFile') t_ciA_partial_header
      !own('Qualifier') t_ciA_partial_header
      !own('PreStatement') both controls
      !own('PostStatement') both controls

      Each failure is expected [ { code: 'part-unreadable', … } ] to be undefined. A check of a larger subset of the five could still pass; round 3 closes that.

    • The only src change is the comment above heldUnderUndefined ("any of the five", and what was measured). dist was regenerated, and differs in that comment and its maps only. The coder info doc, the probe header and the README row name the new controls.

    • No existing test changed. The controls were added to B-08's own CONTROLS list, which this PR added.

  • Gates at 1c0a169:
    • rm -rf dist && npm run build leaves the tree clean.
    • npm run verify: exit 0. 296 files passed and 1 skipped. 10441 tests passed and 27 skipped (2 more than at 4fed791). Smoke, pack (659 files), leak and browser checks are OK.
    • DEX_READBACKS_CORPUS readbacks plus m9MergedSaves: 45/45.
    • Live MATLAB tests: none reaches this change, which is a comment and tests.
    • vscode: the reviewers ran it at 1c0a169 against vscode main 4209e6d, with core from npm pack: verify 3539/3539, integration 275 passing and 1 pending.

Fix round 3

Main had moved to 17b610c, so this round merged it in, then closed the last gap in B-08's controls and checked B-01's side effect on files. There was no force-push, rebase or amend, and no earlier commit changed.

  • da27394: merge of main 17b610c (Tests for S2 and the node ceiling, the group B deviations recorded, and MATLAB read-backs of old core saves (M4, M7) #150, tests and probes; Nodes keep what they read and check every spelling of a property: JSON re-reads, one-element arrays, struct fields, bus elements, Complexity, Parameter shape #152, node fixes in DataNode, BaseBusNode, ParameterNode, SectionNode, EnumTypeNode and others).
    • One conflict, in test/parity/matlab/README.md: both sides added probe rows at the same place in the table. Both sides' rows are kept.
    • No src conflict. dist was regenerated from the merged sources (rm -rf dist && npm run build), and it equals the dist git merged.
    • On the merge: npm run verify exit 0, 301 files passed and 1 skipped, 10750 tests passed and 27 skipped. DEX_READBACKS_CORPUS readbacks plus m9MergedSaves: 45/45.
  • 1e08fca (B-08): measured first, then pinned. A check of only some of the five still passed every test, because the two partial controls from round 2 left gaps. The surviving mutants of !FIELDS.some(own) were !FIELDS.filter((f) => f !== X).some(own) for X = DefinitionFile, Qualifier, PreStatement or PostStatement, and !own('HeaderFile') && !own('DefinitionFile') (the same as !FIELDS.slice(0, 2).some(own)). Under any of them, a bag that holds only a field the check skips gets a false part-unreadable warning. Display and save are unchanged.
    • What MATLAB does (R2027a 27.1.0.3409468, re-running the committed probe on the /tmp files the committed generator makes from test/fixtures/variantbank_r2027a_text.sldd): four new control cases. t_ciA_partial_definition_alone, t_ciA_partial_qualifier, t_ciA_partial_pre and t_ciA_partial_post each hold one of DefinitionFile, Qualifier, PreStatement or PostStatement alone, beside an undefined struct of other values. MATLAB reads each from that one field (d.c, Const, pre, post), with every other property at its default, ignores the undefined, and gives no warning. Core already did the same, so there is no behaviour change.

    • The truth gains the four cases and their rows. The 11 earlier cases and rows are unchanged, and so is the header (date 2026-10-19).

    • variantBankFields.test.ts grades the four as controls, as before: no warning, shown and listed (Other.undefined) as read, and saved as read. With t_ciA_partial_header, each of the five is now held alone by one control. A check that leaves out any field therefore warns of that control, and no subset of the five can pass.

    • Each mutant was applied to src, variantBankFields.test.ts (611 tests) was run, and the file was restored byte for byte. None was committed.

      mutant, in place of !FIELDS.some(own) tests that fail
      !FIELDS.filter((f) => f !== 'HeaderFile').some(own) t_ciA_partial_header
      !FIELDS.filter((f) => f !== 'DefinitionFile').some(own) t_ciA_partial_definition_alone
      !FIELDS.filter((f) => f !== 'Qualifier').some(own) t_ciA_partial_qualifier
      !FIELDS.filter((f) => f !== 'PreStatement').some(own) t_ciA_partial_pre
      !FIELDS.filter((f) => f !== 'PostStatement').some(own) t_ciA_partial_post
      !own('HeaderFile') && !own('DefinitionFile') t_ciA_partial_qualifier, t_ciA_partial_pre, t_ciA_partial_post
      !FIELDS.every(own) all six partial controls

      Each failure is expected [ { code: 'part-unreadable', … } ] to be undefined.

    • src gains only comments: the comment above heldUnderUndefined names what was measured, and so does the coder info doc. dist differs in that comment and its maps only. The generator, the probe header and the README row name the new controls.

    • No existing test changed. The controls were added to B-08's own CONTROLS list.

  • B-01's side effect, checked on files (no change). B-01 changed only the cell-literal parser, and no read path calls it. Only typed edits do: a Parameter's, a Constant's or a variable's Value, and a bank's VariantConditions. The host does not call it either. A text dictionary holds a cell member as JSON, so a hand-edited V==-1, V>-2, @(x)x+1, 1:-1:0, -int8(1), -0x1F or e-3 is a char member.
    • Checked on 42 /tmp files made from the repo's text fixtures. Each of the seven members was placed:
      • in a bank's VariantConditions, as a cell of char (in the envelope, and in R2026b's plain bag) and as a char {…};
      • in a Simulink.Parameter's Value, as a cell of char and as a char {…};
      • in a plain variable's cell.
    • Every file opens, with no warning and no throw. The member is shown quoted, as the char it is ({'V == 1','V==-1'}, {'@(x)x+1'}). Every other entry is shown as it is without the edit, and the file is saved byte-identical. Typing the shown text back unchanged is taken as no change, as on main.
    • main's dist gives the same result, except that it shows the plain-bag bank's conditions as {}. That difference is the B-06 fix.
    • So theme 1 holds, and nothing was changed. Whether MATLAB's bank reads such a condition as written was not measured here; core opens these files as it did before this PR.
    • What B-01 does change is a typed edit. A bare member is refused with "must be a cell array" in a bank's conditions; main took {V==-1} and {V>-2}, and refused the other five through the condition check. It is refused with "Invalid MATLAB expression" in a plain variable's cell, where main held each one as a char. It is also refused with "Invalid MATLAB expression" in a Parameter's Value, where main refused it as a cell Value.
  • Gates at 1e08fca:
    • rm -rf dist && npm run build leaves the tree clean.
    • npm run verify: exit 0. 301 files passed and 1 skipped. 10754 tests passed and 27 skipped (4 more than on the merge). Smoke, pack (659 files), leak and browser checks are OK.
    • DEX_READBACKS_CORPUS readbacks plus m9MergedSaves: 45/45.
    • Live MATLAB tests: none reaches this round's changes, which are comments, tests and main's merged work.
    • vscode was not re-run at 1e08fca.
    • CI at 1e08fca: build-and-test and CodeQL pass. GitHub reports MERGEABLE/CLEAN.
  • Commits: 11, plus the merge da27394. The fix rounds added 50896dd and 4fed791 (round 1), 1c0a169 (round 2) and 1e08fca (round 3).

Mutants re-checked by the orchestrator at 1e08fca

In a fresh clone, each applied alone to src, run against variantBankFields.test.ts and cellSignMember.test.ts, then restored: .every fails 6; dropping any one of HeaderFile, DefinitionFile, Qualifier, PreStatement or PostStatement from the check fails 1 each (its own partial control); !own('HeaderFile') && !own('DefinitionFile') fails 3; dropping own(UNDEFINED_KEY) fails 2 (the empty and the other-field bags); dropping !(SAVEOBJ_KEY in props) fails 2 (the envelope beside, binary and text); B-01's operator check on the whole row (rowStr) instead of the member fails 1 (the bare-word control).

Noted for later (not changed)

  • In VariantConfigurationDataNode.setProperty, the Value edit is still refused if _refuseEncodedEdit or _refuseInlineBagEdit is dropped. Only the reason changes, to the generic "has no Value". Neither reason is pinned yet. Nothing is written in either case.
  • Simulink.VariantBankCoderInfo.md (lines 129-132, from before this PR) names two corpus paths. This PR edits that file but not those lines. A separate follow-up should remove them, as B-06 did for Simulink.VariantBank.md.

Implements the maintainer's rulings, as audited in group H (2026-10-19).

ww-mw added 12 commits October 18, 2026 22:20
A cell member typed without spaces, such as {1+2}, {a+b}, {2i+3i}, {2*3}
or {-a}, was held as one char member ('1+2', '-a'). MATLAB computes each
of them ({3}, a sum, {0+5i}, {6}, a negated name), so the char was a value
MATLAB never makes, while the spaced {1 + 2} and {- a} were already
refused. Under the group G ruling (accept the sign and i + 1 forms, keep
refusing arithmetic), a bare member that holds + - * / \ or ^ and is not
one literal is now refused, and the edit leaves the value as it was.

A bare word such as {abc} or {a_b, c1} is still held as text, and a sign
that belongs to one literal is still read: {-2}, {1e+3}, {1e-3}, {1+2i},
{-2i}, {i+1}, {2i-3}.

probe_cell_sign_literals.m gains the unspaced rows, and the refusal table
in cellSignMember.test.ts quotes what MATLAB R2027a (27.1.0.3409468) made
of each. The edit-path test now types {1+2}, {a+b}, {-a} and {2i+3i} into
a text and a binary dictionary and expects the refusal.
…TLAB makes

A cell member typed with a zero imaginary part, such as {0i}, {1 + 0i},
{1+0i} or {0i + 1}, was held as a complex member (0+0i, 1+0i) and saved
as one. MATLAB R2027a holds each as a real double: its arithmetic drops an
imaginary part that is all zero, and a lone 0i is real too. Under the
group G ruling (accept these forms as MATLAB does) and theme 2 (follow
MATLAB, measured first), such a member is now that double, with its real
part as MATLAB sums it, -0 included: {-0 + 0i} is 0, {-0 - 0i} and {-0i}
are -0, and {-0i + 0} is 0. A nested cell's member is read the same way.

Only a cell's member changes. The top level is what an element of a complex
array takes, which must stay complex, so 0i and 1 + 0i typed as a whole
value are read as before, although MATLAB makes doubles of them too. An
array literal such as {[0i 1]} holds no complex element in this parser and
is still refused.

probe_cell_sign_literals.m gains 68 rows (the zero forms in a cell, nested,
in an array and at the top level), and cellSignMember.test.ts quotes what
MATLAB made of each. The new tests drive the parser and the edit path into
a text and a binary dictionary, and a save and reopen in both formats.
… {- 2}

A VariantConditions edit of {i+1}, {i + 1} or {- 2} is refused with
MATLAB's message for a member that is not text ("Conditions defined in
'VariantConditions' property must be a scalar string."), because the
parser reads each as the one number MATLAB makes of it (1+1i, 1+1i, -2),
as for {1,2}. No VariantBank test held that: with the imaginary-first
form or the spaced unary sign dropped from a cell member, the edit was
refused as "must be a cell array" instead, and those tests stayed green.

Test only. The three cases join the refusal list in
variantBankFields.test.ts, run against a text and a binary dictionary.
The two i + 1 cases fail with the imaginary-first form dropped, and
{- 2} with the spaced sign dropped; all pass on the code as it is.
… one

A text dictionary R2026b wrote holds a Simulink.VariantBank as plain
properties, with no saveobj envelope, as it holds a coder info. The bank
was read out of the envelope alone, so there its Bank Name, Variant
Conditions and coder info cells showed their defaults, the file's values
showed only as Other rows, and every edit was refused as having nowhere to
be saved. Under C-168 (follow MATLAB) with the fix shape C-169B gave the
coder info, the bank now reads its fields from, and writes an edit into,
the plain properties when there is no envelope, and invents no envelope.

What R2027a reads of that spelling was measured first, on synthetic
files made from the R2027a text fixture with its banks and coder infos
respelled so and hand-edited one field at a time. MATLAB reads the bag
property by property, through the setters, with no warning: a value a
setter refuses is read as that property's default alone, not the whole
bank as from an envelope; a char VariantConditions is read as no
conditions; BankCoderInfo is read independently of the two coder infos;
and a missing field is read as its default. MATLAB read core's own saves
of such a bank back as written. Under theme 1 a hand-edited bank so
spelled is shown and saved as read, and its warning says what MATLAB
does there. A field the bag does not have still takes no edit. The
Variant Bank rows also claim their plain keys, so Other no longer repeats
them; the Hidden BankCoderInfo, which has no row, is listed under Other.

The probe (variantbank_plain.mjs and probe_variantbank_plain.m) and its
truth are committed, and variantBankFields.test.ts grades core's reading
of every case against what MATLAB read. A condition past int32, which
neither spelling warns of, is recorded and left for a follow-up.
…aken

MATLAB R2027a's setter takes a DefinitionFile of 302 characters (300
a's and .c), while it refuses a HeaderFile longer than 255 characters in
a part: the length rule is the header file's alone. The editor takes
that edit, but no test held it. With the length check moved out of the
header file branch, so a DefinitionFile got the 255 rule too, the whole
suite stayed green, and the edit was refused as "Check specified header
file name ... invalid file name length".

Test only. The 302-character name joins the coder info edit table in
variantBankFields.test.ts, run against a text and a binary dictionary.
Both cases fail with that change and pass on the code as it is.
An earlier version of this package wrote a saveobj envelope back as
<P Name="undefined" Class="struct"> in place of MATLAB's
<P Source="saveobj" PropertyType="any" Class="struct">, and a binary
dictionary saved so still holds a coder info that way. The node reads its
five properties from the envelope or from R2026b's plain properties, found
neither, and showed the coder info blank, its state only as an
Other.undefined row, with no warning.

What R2027a does with it was measured first, on synthetic files made from
the R2027a fixtures. In a binary dictionary MATLAB cannot make the coder
info at all: the dictionary and its other entries open, but that entry's
value fails with "Data dictionary unable to create an object of class
'Simulink.VariantBankCoderInfo'." A text dictionary's plain bag holding
only that struct is read as every default, and one holding the five
properties too is read from them, the 'undefined' ignored. MATLAB warned of
none of them.

Under theme 1 such a coder info is still shown and saved as read, and the
dictionary's warnings now carry one part-unreadable for it that names
'undefined' and says what MATLAB does with it in that format. A bag with
the five beside its 'undefined' is not reported, and an edit of one of the
five still has nowhere to be saved.

The probe (coderinfo_undefined.mjs and probe_coderinfo_undefined.m) and
its truth are committed, and variantBankFields.test.ts grades core's
reading of each case against what MATLAB read.
…LAB's class does not have

MATLAB R2027a has no Value property on Simulink.VariantConfigurations.
Under either name an entry of this kind goes by,
Simulink.VariantConfigurationData or Simulink.VariantConfigurations, the
object built is a Simulink.VariantConfigurations, isprop(..., 'Value') is
0, none of the ten properties its metaclass lists (seven hidden) is a
Value, and setting one fails with "Unrecognized property 'Value' for class
'Simulink.VariantConfigurations'." The probe opens no file:
test/parity/matlab/probe_variantconfigurations_value.m, with what it
measured in variantconfigurations_value.truth.json.

The node had a Value anyway. setProperty('Value', ...) fell through to the
generic tail, which took the text and marked the entry Modified, and both
save paths wrote it beside the saveobj envelope. An entry held as plain
properties, with no envelope, also gained Value '' on every save, an
unedited one included.

The node now does what VariantBank and VariantBankCoderInfo do (C-168,
C-169). A Value edit is refused ("'<name>' is a
Simulink.VariantConfigurations, which has no Value, so none can be set on
it.") and leaves the entry unmodified, and _serializedOverrides is empty,
so no save writes a Value. A Value key a file already carries is written
back as read.

test/variantConfigurationsNoValue.test.ts covers a new entry in both
formats, a Value already in the file, and an entry held as plain
properties. Three existing tests pinned the removed behaviour and change:
variantLeafNodes.test.ts now lists the class among those that refuse an
edit of their one property, and objectArrayElementClass.test.ts no longer
exempts it from the untouched-save check. In newEntryEnvelopeBothFormats,
C-199's undeclared-field case rode on this Value. It now rides on a
custom-saving class defined in the test, written and read by the real
writers and readers.
The unspaced-arithmetic refusal (d4975c3) checks one bare member for an
operator. Nothing held it to that member: checking the whole row instead
passed the full suite, and under that change {abc, -2}, {abc, 1e-3},
{abc, 'x-y'}, {abc, [1 -2]} and {-2, abc} were all refused, where main
and this branch read them as ['abc', -2] and so on.

The "keeps a bare word" test in cellSignMember.test.ts now parses each of
them and expects the word as text beside the member read as before. Under
the row-wide check each comes back null, so the test fails.
The 'undefined' warning (269f897) is for a coder info with no envelope,
an own 'undefined' property and none of the five. Two parts of that test
were not held by any test: dropping the 'undefined' check, or the
envelope check, passed the full suite. Without the first, a plain bag
with none of the five, such as {} or {Foo: 'x'}, was warned of as held
in a property named 'undefined', which it does not have. Without the
second, an envelope with an 'undefined' beside it was warned of too.

What R2027a (27.1.0.3409468) makes of each was measured first, in the
same probe run as the damaged cases, on files made from the R2027a
fixtures. The envelope with an 'undefined' struct of other values beside
it is read from the envelope in both formats, the 'undefined' ignored. A
text dictionary's plain bag with no property, or with only Foo, is read
as every default. MATLAB warned of none of them, so none is reported
here.

coderinfo_undefined.mjs makes the four control files, and the truth now
holds them (the five earlier rows are as before). variantBankFields.test.ts
grades each against what MATLAB read: no warning, shown and listed as
read, and saved as read. Under either change above two of the four fail,
each with the 'undefined' warning MATLAB does not give. The comment above
heldUnderUndefined and the coder info doc name the controls.
…of the five

The 'undefined' warning (269f897) is for a coder info whose plain bag
holds an 'undefined' and none of the five properties. Which of the five
it looks for was not held by any test: checking that all five are there
(every in place of some), or checking HeaderFile alone, passed the full
suite. Under either, a text dictionary's plain bag with an 'undefined'
beside only some of the five was warned of as one MATLAB reads with
every property at its default.

What R2027a (27.1.0.3409468) makes of two such bags was measured first,
on files made from the R2027a text fixture: ciA's HeaderFile alone
beside an 'undefined' struct of other values, and ciA's DefinitionFile
and Qualifier beside one. MATLAB reads each from the properties it
holds, every other at its default, ignores the 'undefined', and gives no
warning. Core already does the same, so the only src change is the
comment above heldUnderUndefined.

coderinfo_undefined.mjs makes the two files, and the truth now holds
them (its nine earlier rows are as before). variantBankFields.test.ts
grades both as controls: no warning, shown and listed as read, and saved
as read. The two bags hold no field in common, so a check of any one
field alone fails at least one of them: every in place of some fails
both, HeaderFile alone fails the second, DefinitionFile or Qualifier
alone the first, and PreStatement or PostStatement alone both. The coder
info doc, the probe header and the README row name the new controls.
Brings in #150 and #152. The parity README keeps both sides' new probe rows; dist is rebuilt from the merged sources.
…e five alone

A check of only some of the five properties still passed every test: the
two partial controls, HeaderFile alone and DefinitionFile with Qualifier,
left a check that skipped DefinitionFile, Qualifier, PreStatement or
PostStatement, or read only HeaderFile and DefinitionFile, unseen. Such a
check warns falsely of a bag that holds only a field it skips.

coderinfo_undefined.mjs now also writes a text bag of DefinitionFile,
Qualifier, PreStatement or PostStatement alone, each beside an 'undefined'
struct of other values. MATLAB R2027a (27.1.0.3409468) reads each from that
one field, every other at its default, with no warning, and so does core.
The four are added to the controls, so each of the five is held alone by
one, and any check that leaves a field out warns of that one. The 11
earlier rows of the truth are unchanged.

No behaviour changes: src gains only comments, and dist only those
comments and its maps.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant