Skip to content

LookupTable, Breakpoint, VariantBank and Signal properties follow MATLAB - #124

Merged
ww-mw merged 8 commits into
mainfrom
properties-follow-matlab
Oct 16, 2026
Merged

ww-mw merged 8 commits into
mainfrom
properties-follow-matlab

Conversation

@ww-mw

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

Copy link
Copy Markdown
Member

Five items, one commit each. Each commit regenerates dist/ (rm -rf dist && npm run build) and passed npm run verify on its own tree.

The ruling (DECIDED 2026-10-16, group E, the backlog). The maintainer: "Agree your suggestions, use subagents, teammates, or workflow agents to handle all of them automatically. Add unit tests to lock down the fix. I pre-approve PR and merge as long as it passes all tests." All five items are under theme 2, follow MATLAB: "follow MATLAB, and measure it first (…) where it is unmeasured."

Measurements. MATLAB R2027a (27.1.0.3409468) was measured on 2026-10-16, before any code was written. Two dictionaries that MATLAB itself wrote then are now committed as test/fixtures/classes_r2027a_text.sldd and classes_r2027a_binary.sldd, together with their generator, test/fixtures/make_classes_r2027a.m. A re-run of the generator gives the same files apart from UUIDs and timestamps. The four new test files use these entries, so each fix is checked against MATLAB's own bytes, not against a hand-written expectation.

How the tests were checked. Every new test was run against the source as it was before its commit, with the test file present. Each one failed, and failed for the reason stated, apart from the controls marked below.

Item Commit New tests Failed before the fix
C-020 d0abc99 14 14 of 14
C-168 bbbb4f1 9, plus 1 contract row 8 (the 2 controls pass)
C-169 1d2be05 9, plus 1 contract row 8 (the 2 controls pass)
C-030 cca1e6e 18 18 of 18
C-086 71b3446 25 25 of 25

C-020: LookupTable and Breakpoint offered a Description that MATLAB's classes do not have (d0abc99)

The recommendation it adopts: "(a). It follows the FunctionElement ruling (ServiceBusNode.ts:26-30) and the MATLAB-measured default bags. Fix the doc's 'gate PASS' line in the same change." Option (a) is: "Mirror FunctionElement: drop PropDescription and the schema 'description' row for both classes, and keep any existing top-level key as a read-only pass-through."

What was wrong. In MATLAB, isprop(Simulink.LookupTable,'Description') is 0, and the same holds for Simulink.Breakpoint. Setting one fails with "Unrecognized property 'Description' for class 'Simulink.LookupTable'." A LookupTable or Breakpoint that MATLAB writes carries no top-level Description, in either format. Core still offered an editor in the table and in the Property Inspector, accepted the edit, and wrote it as a top-level key on both save paths. MATLAB opens such a file with no warning and ignores the key, so the edit was saved and had no effect. The class docs also claimed "MATLAB gate: Description edit -> MATLAB reads back. PASS.", which was not true.

Root cause. LookupTableNode.getProperties and BreakpointNode.getProperties returned [PropName, PropDataType, PropDescription]. Their _serializedOverrides wrote Description through _gatedProps. The General layouts in schema/classes/lookupTable.json and breakpoint.json listed description.

Fix. Both nodes now do what FunctionElement does:

  • getProperties is [PropName, PropDataType], and descriptionEditable is false.
  • setProperty('Description', …) is refused in MATLAB's words, "Unrecognized property 'Description' for class ''.", and leaves the entry unmodified.
  • _serializedOverrides is gone (the inherited {}), so a top-level Description key that a file already has passes through as read, on both save paths.
  • General is name, value, dataType, kind, class.

A key that is already present shows read-only, both in the Description column and in the inspector's Other group. Simulink.LookupTable.md and Simulink.Breakpoint.md now record the measurement and the refusal, and the false gate line is removed.

Tests. test/lutBreakpointNoDescription.test.ts has 14 cases. They cover both classes in both formats of the parity params.sldd (MyLUT and MyBkpt), plus a new entry, and run through createSession/ingest, setProperty, toRow, toPIObject and serializeSource:

  • The edit is refused with MATLAB's wording, and the entry is not Modified. After an unrelated StorageClass edit, neither save path writes a Description, and neither does the saved file once it is parsed again.
  • No editor is offered: Description is not in getProperties, _descriptionEditable is false, General has the 5 items, and the inspector has no editable Description.
  • A file with Description 'kept' written into its bytes shows the value read-only in the row and in Other, refuses an edit, and writes 'kept' back on both paths and in the re-parsed save.

Before the fix, all 14 failed: "expected true not to be true" (the edit was accepted), "expected [Name, DataType, Description] to not include 'Description'", and "expected true to be false" (_descriptionEditable).

Existing tests changed. Each one pinned exactly the Description editor that the ruling removes.

  • test/parity/fidelity/typedef.fidelity.test.ts: the LUT and Breakpoint cases "Description edit round-trips in-process" become "Description edit is refused; the file's key is unchanged", in both formats.
  • test/schema/piGeneralAllNodes.test.ts: the General check is split. CustomObject keeps Description, and LookupTable and Breakpoint have the five items.
  • test/lookupTableFlatProps.test.ts: 'Description' is dropped from both General expectations.
  • test/absentPropertyWriteBack.test.ts: the Breakpoint and LookupTable rows are removed from its CASES table, and the header's count goes from eleven classes to nine. Each row types a Description into the entry and checks how both save paths write it back. Neither class takes that edit now. The two states that still hold (a file without the key stays without it, and a file with the key keeps it) are covered by the new test.
  • test/schemaLayoutResolvable.test.ts: its layout-key floors go from 182 to 180 pairs and from 100 to 98 probe cases. The two pairs lost are exactly the two removed description layout keys.

Left out. setProperty('Value', …) still answers true on LookupTable, Breakpoint, Signal and ValueType, and the text is dropped on save. None of them has an editable Value. This is the kind of bug that #113 fixed for bus elements, but it is a separate item and not part of this ruling.

C-168: VariantBank offered and wrote a Value that MATLAB's class does not have (bbbb4f1)

The recommendation it adopts: "(a) now, together with C-169, as one deliberate test edit. Do (b) once a MATLAB-written fixture exists. Run one MATLAB load of a dictionary carrying the sibling Value first: if MATLAB refuses it, raise the priority." Option (a) is: "Override valueEditable to false and stop writing a non-empty Value, so _serializedOverrides writes nothing."

What was wrong. properties(Simulink.VariantBank) is Name, Description, VariantConditions, AllChoicesCoderInfo and ActiveChoiceCoderInfo. isprop(…, 'Value') is 0, and vb.Value = 'x' fails with "Unrecognized property 'Value' for class 'Simulink.VariantBank'." Core offered a Value editor anyway. setProperty('Value', …) was accepted, and both save paths wrote the text beside the saveobj envelope: _properties: {Value: 'x'} in text, and <P Name="Value" Class="char">x</P> after the saveobj in binary. Every bank that MATLAB wrote also showed '', a char value the class does not have.

The pre-check the recommendation asks for. A dictionary carrying our writer's sibling Value was loaded in MATLAB, in both formats. MATLAB opens it with no error and no warning, reads all 14 entries, and builds a bank with no Value. A MATLAB re-save drops the key. MATLAB does not refuse the file, so the priority was not raised. The edit was accepted, saved, and had no effect, which is what (a) removes.

Root cause. VariantBankNode.getProperties listed PropValue, with no valueEditable override, so BaseNode's default made the cell editable. _serializedOverrides wrote Value with no gate. The display quoted the empty field.

Fix. The node now does what a bus element does after #113:

  • valueEditable is false.
  • A Value edit is refused, with "'' is a Simulink.VariantBank, which has no Value, so none can be set on it.", after the existing encoded-edit refusal. The entry is not Modified.
  • _serializedOverrides returns {}, so no save writes a Value.
  • A Value key that a file already has (only ever this package's) is shown quoted and read-only, and is written back as it was read. A bank with no Value key shows an empty cell.
  • The Value column stays, read-only, for that key.

Simulink.VariantBank.md records the measurement. Two comments that described VariantBank's Value as written were corrected, in PropValue.ts and VariantConfigurationDataNode.ts.

Fixtures. test/fixtures/classes_r2027a_text.sldd and classes_r2027a_binary.sldd are the MATLAB-written dictionaries described above, with 14 entries each. They are used here (vbD) and by C-169, C-030 and C-086. The fixture sweeps (fixtureHygiene and check:leak) pass with them.

Tests. test/variantBankNoValue.test.ts has 9 cases over vbD, in text and binary, plus a new entry:

  • The bank MATLAB wrote shows '' in displayValue, in the row and in the inspector, and none of them is editable.
  • A Value edit is refused with the exact reason, for x, 'x' and the empty string. The entry is not Modified. Neither save path writes a Value, and neither does the saved file after a rename, both re-parsed and in the text entry and binary XML.
  • Controls, green before and after: a save with no edit is byte-equal, and a renamed entry is written back as read.
  • Value 'b1' written into the bytes where the old writer put it: shown as 'b1', read-only in the row and the inspector, edits refused, and written back as 'b1' on both paths and in the re-parsed save.
  • createDefault shows '', is not editable, refuses an edit, and writes no Value (the text bag, the binary bag and the XML).

Before the fix there were 8 failures: the 7 non-control cases, and the new contract row in variantLeafNodes. Each failed for the right reason: "expected '''' to be ''" (the display), "expected true not to be true" (the edit was accepted), "expected true to be false" (the cell was editable), and "expected true to match object { error: true }".

Existing tests changed. test/variantLeafNodes.test.ts pinned exactly the removed edit:

  • A READ_ONLY set is added. For the VariantBank row of the shared contract, "writes an edit back through the array wrapper" becomes "refuses an edit, and writes the property back as it was read".
  • The quoted-display regression's two VariantBank cases, which commit a Value, are removed.

The contract's other cases for this row are unchanged and green.

Left out. Option (b) is left for a follow-up: modelling the bank's real fields through classes/variantBank.json. The recommendation makes it the next step once a MATLAB-written fixture exists, and this PR commits that fixture. VariantConfigurationDataNode has the same Value contract. Whether Simulink.VariantConfigurations has a Value was not measured, so it is not changed.

C-169: VariantBankCoderInfo offered and wrote a Value that MATLAB's class does not have (1d2be05)

The recommendation it adopts: "(a) in the same change as C-168. (b) is a reasonable follow-up here sooner than for VariantBank, because every field is a plain char with a measured default." Option (a) is: "valueEditable false and no non-empty Value write."

What was wrong. properties(Simulink.VariantBankCoderInfo) is HeaderFile, DefinitionFile, PreStatement, PostStatement and Qualifier. All of them are char and default to '', except Qualifier, which defaults to 'None'. isprop(…, 'Value') is 0, and setting a Value fails with "Unrecognized property 'Value' for class 'Simulink.VariantBankCoderInfo'." The node behaved exactly as VariantBank did: the cell had an editor, setProperty('Value', …) was accepted, and both save paths wrote the text beside the envelope. MATLAB opens such a file in either format with no error and no warning. It builds an object with no Value, with Qualifier 'None', so the edit had no effect. The class doc's header said "Editable in our UI: no", which contradicted the code.

Root cause. This is the same as C-168, in VariantBankCoderInfoNode.ts. There was no valueEditable override, and _serializedOverrides wrote Value with no gate.

Fix. The node now mirrors C-168's VariantBankNode:

  • valueEditable is false.
  • A Value edit is refused with "'' is a Simulink.VariantBankCoderInfo, which has no Value, so none can be set on it."
  • _serializedOverrides returns {}.
  • A Value key that a file already has is shown quoted, read-only, and written back as it was read. An entry with no key shows an empty cell.

The two nodes are kept as copies of each other, as they were before, rather than sharing a new helper. Simulink.VariantBankCoderInfo.md is rewritten to match: its header is now true, it records the measurement, and it leaves (b) as the follow-up.

Tests. test/variantBankNoValue.test.ts now runs its cases over a table of both classes, which adds the same 9 cases for vbciD. That entry is MATLAB-written and comes from the same fixtures, in both formats. The VariantBank cases, their assertions and their titles are unchanged. A Value is injected into the binary bytes at the place where the old writer put it for this class: after the closing </P> of the saveobj.

Before the fix there were 8 failures: the 7 non-control vbciD cases, and the vbciD contract row in variantLeafNodes. They failed for the same reasons as in C-168. The VariantBank cases stayed green.

Existing tests changed. In test/variantLeafNodes.test.ts, each change pinned the removed edit:

  • 'Simulink.VariantBankCoderInfo' joins READ_ONLY. Its row's "writes an edit back through the array wrapper" becomes "refuses an edit, and writes the property back as it was read".
  • "VariantBank and VariantBankCoderInfo quote their Value like MATLAB" is retitled "… show a Value their file carries quoted". "Like MATLAB" no longer holds for a key that neither MATLAB class has. The assertions are unchanged.

Left out. Option (b) is left out: the five real char fields modelled through classes/variantBankCoderInfo.json. The recommendation calls (b) a follow-up. Its fixture, vbciD, is now committed.

C-030: Signal's Complexity and DimensionsMode become dropdowns over MATLAB's own sets (cca1e6e)

The recommendation it adopts: "(a). Confirm Signal's enum sets with set(Simulink.Signal,'Complexity') and set(Simulink.Signal,'DimensionsMode') in MATLAB first, then reuse the BusElement and ValueType mechanism with Signal's own options. Dimensions stays a label."

MATLAB, measured first.

  • set(Simulink.Signal,'Complexity') is auto|real|complex, and set(…,'DimensionsMode') is auto|Fixed|Variable. Both default to auto, and mpt.Signal is the same.
  • Any other value is refused with "There is no enumerated value named 'X'.", and so is ''.
  • A text .sldd writes a value other than auto as a top-level key and leaves the key out at auto. A binary one always writes <P Name="Complexity" Class="char">auto</P>.
  • Our proposed write reads back in MATLAB as complex and Variable, in both formats.

What was wrong. Both properties were read-only labels on a Signal, while BusElement and ValueType already offered the same two as dropdowns. A setProperty('complexity', …) also answered true and changed nothing. The text went onto a stray lowercase field that nothing shows or saves.

Root cause. schema/props/dataObject.json gives complexity and dimensionsMode the editor label, and SignalNode.setProperty routed only Min and Max specially, so there was no enum path. The shared atoms could not simply be reused, because PropComplexity offers real|complex and PropDimensionsMode offers Fixed|Variable. Neither has Signal's auto.

Fix.

  • Two new atoms, PropSignalComplexity (auto, real, complex) and PropSignalDimensionsMode (auto, Fixed, Variable), both select. A cleared cell reads as auto, which is what the save writes. PropComplexity and PropDimensionsMode keep their sets for BusElement and ValueType.
  • SignalNode gets Complexity and DimensionsMode fields, defaulting to auto. The two atoms replace the two schema descriptors in place: in the table columns, and in every group of the inspector layout. Both panes read the field that an edit writes. The column order and the schema's layout order are unchanged.
  • setProperty refuses any other value through DataNode._rejectUnknownEnumeral, in MATLAB's words.
  • Both save paths write the key when the file had it or when the value is not auto. So an untouched Signal, or one set back to auto, saves with no new key in a text dictionary, which is how MATLAB writes it. A binary dictionary always has the key. A clear is saved as auto, never as ''.
  • Dimensions stays a label, and so does Parameter's Complexity, which comes from its Value. dataObject.json is not touched.
  • Simulink.Signal.md is updated, including its "safe as read-only" note, which now covers Dimensions only. The two shared atoms' comments are updated too: "only BusElement uses this" was already stale, because ValueType uses them as well.

Deviation from the scoped change, with its reason. The enum check runs only for complexity and dimensionsMode. Run for every select, it also caught Signal's storageClass, whose refusal would have changed from "Invalid value for Storage Class" to MATLAB's enumeral wording. That broke two cases in test/parity/fidelity/schemaColumns.fidelity.test.ts, which pin nothing the ruling removes, so the check was narrowed and that test was left alone.

Tests. test/signalEnumProps.test.ts has 18 cases. They run through the session (ingest, editProperty, undo, serializeSource) and through the node, over the MATLAB-written sigC, sigR, sigA and sigNo in both formats. They also cover sig1 in params.sldd, an mpt.Signal on both save paths (re-parsed), and a new entry:

  • The select cells and the inspector agree.
  • sigNo edited to complex/Variable saves as MATLAB's sigC. The text bag is equal as a value: our new keys come last, where MATLAB sorts them alphabetically. The binary <Element> is identical character for character.
  • Values from the wrong set are refused in MATLAB's words, and the save is byte-unchanged.
  • Set back to auto, the text entry equals the untouched save, with no key, as MATLAB's sigA has none. The binary entry writes auto, as sigA does.
  • A clear shows and saves auto.
  • Undo works through the session.
  • The inspector's layout keeps the schema's groups and order, Other holds only LoggingInfo, and Dimensions stays a label.

Before the fix, all 18 failed: the cell was a label string (expected 'auto' to deeply equal { text: 'auto', editable: true, … }), the edit reached neither the display nor the save, a foreign value was accepted, and the inspector row was read-only.

Existing tests changed. Each pinned exactly the removed label.

  • test/schema/schemaColumns.test.ts: "read-only Dimensions Mode" now expects editor select and the select cell, and is retitled.
  • test/mptClasses.test.ts: row.complexity and row.dimensionsMode are now read through .text, because they are select cells.
  • test/propAtoms.test.ts: one comment only. Its assertion is unchanged.

Left out. MATLAB matches case-insensitively and stores the canonical spelling ('Complex' becomes complex). That is not adopted: the match stays exact, as it is for BusElement and ValueType, and a dropdown sends only canonical spellings. setProperty('Complexity', …), with the capitalised field name that no host sends, skips the enum check, and ValueType and BusElement have the same gap. Both points apply to all three classes, so they are left for one later change.

C-086: a Parameter's or Signal's Alignment can be edited in the table, by MATLAB's rule (71b3446)

The recommendation it adopts: "(b): the validation rule is already recorded, but the written form and defaults-stripping are exactly what the plan deferred. One MATLAB check turns this into a bounded core and vscode change." Option (b) is: "Measure first in MATLAB: how Alignment=8 is written in text and binary .sldd, whether a bare number reads back as Alignment, the upper bound, and whether -1 should be stripped. Then ship with defaults-stripping."

MATLAB, measured first.

  • The rule. The value must be -1 or a power of two from 1 to 2^30. The input is converted as int32 first, so 1.5 becomes 2, true becomes 1, and 2^31 saturates and is refused. 0, 3, -2 and 2.5 are refused. MATLAB's message cites a bound of "no more than 128", but MATLAB itself accepts 256 through 2^30.
  • The StorageClass gate, which the recorded rule left out. Under StorageClass 'Auto', the default, any value but -1 is refused: "Cannot set Alignment when StorageClass is 'Auto'." The value check comes first. Setting StorageClass back to 'Auto' resets Alignment to -1.
  • The written form. A text .sldd holds a value other than -1 as "Alignment": {"_type": "int32", "_value": "8"}, first in CoderInfo, and leaves the key out at -1. A binary one always holds <P Name="Alignment" Class="int32">8</P>.
  • Read-back. A typed int32, a typed double and a bare number all load as int32. A string "16", which is what the generic writer would have written, is silently read as -1, so the edit would be lost.

What was wrong. The user chose inline editing for Alignment, and it was never delivered. Alignment stayed a read-only label on Simulink.Parameter, Simulink.Signal and their mpt subclasses.

Root cause. In schema/props/codeGen.json, alignment has the editor label. trySetSchemaProperty declines every label prop and validates only selects. Making the cell editable alone would have written the cell's text as a string, which MATLAB reads as -1.

Fix.

  • parameter.json and signal.json give the alignment ref the editor text. codeGen.json still says label, so Bus's inspector-only row is unchanged.
  • There is a new optional hook, PropClass.editableFor(node), which BaseNode.getPropInfo honours. It can only take editability away. Alignment is editable unless the node's StorageClass is 'Auto'. Under 'Auto', the row holds a plain string.
  • schemaBridge applies MATLAB's rule, in MATLAB's order and words:
    • Text that is not a number is refused with "Value must be numeric or logical." A number here is a decimal literal, Inf, NaN, true or false. Empty text is refused the same way.
    • The value is converted as int32 (rounding half away from zero, saturating, NaN to 0) and must then be -1 or 2^k for k = 0..30. Otherwise the refusal is " is not a valid alignment boundary. The alignment boundary must be either -1 or a power of two." MATLAB's inaccurate "no more than 128" is left out.
    • Then the Auto gate is applied.
  • The write keeps the form the file already holds. A typed leaf keeps its _type, and a bare number stays bare. A new key is a typed int32, inserted where MATLAB's sorted text puts it. A string leaf is replaced by a typed one, and a string is never written. In a text dictionary -1 removes the key. Elsewhere it is written as int32 -1, and a key that is absent stays absent. Setting the value already held marks nothing as Modified.
  • A StorageClass edit to 'Auto' resets Alignment to -1 through the same write.
  • The inspector still shows Alignment read-only, as it shows Storage Class.
  • CodeGeneration.schema-columns.md and Simulink.Parameter.md describe the rule and the written form. Two helpers, sourceLeaf and isTypedScalar, are added to schema/index.ts, and writeSourcePath now uses them, with no change in its behaviour.

Deviations from the scoped change, with their reasons.

  • Order of checks. The scope put the Auto refusal first. This follows MATLAB's measured order instead. Under Auto, 3 gets the not-a-boundary message and -1 is accepted. A valid value under Auto (8 or 1.5) still gets the Auto message, as the scope expects.
  • Where a new key goes. It goes in sorted position, so the edit saves MATLAB's bytes exactly.
  • true, false, Inf and NaN are read the way MATLAB reads them. The scope did not mention them.

Tests. test/alignmentEdit.test.ts has 25 cases. They run through the session (ingest, editProperty, undo, redo, serializeSource, addEntry) and through the node, in both formats, over the MATLAB-written pSC, pAl8, pAlM1, pNo and sAl16:

  • Cells are editable text where StorageClass is not Auto, and a plain string under Auto. The inspector shows the same value, read-only.
  • pSC set to 8 saves pAl8's CoderInfo exactly. pAl8 set to -1 saves pAlM1's, and sAl16 does the same. pAl8 with StorageClass 'Auto' saves pNo's.
  • Every 2^0 to 2^30 is accepted, as are 1.5, -1.4, true, ' 64 ', '+16' and '3.2e1'.
  • These are refused, with nothing changed and the save unchanged: 3, 0, -2, 2.5, false, 2^31, 2147483647, -3e9, Inf, NaN, abc, '8 8', 0x8, '' and blank.
  • The Auto gate and its order. ImportedExtern keeps the value.
  • A new entry from addEntry: refused under Auto, then accepted after ExportedGlobal, saved and read back.
  • One undo step restores pSC exactly, and redo applies the edit again.
  • The stored forms, createDefault Parameter and Signal, and an mpt.Parameter.

Before the fix, all 25 failed: the cell was the label string, the edit was not written, or a refusal was answered true.

Existing tests changed. Each pinned the removed label.

  • test/schema/schemaBridge.test.ts and test/schema/schemaColumns.test.ts: alignment's editor is now text. Both are retitled.
  • test/mptClasses.test.ts: an ExportedGlobal mpt.Parameter's alignment is now an editable cell.
  • test/schemaBridgeEdit.test.ts: it checked that trySet('alignment') returns null, because alignment was a label. trySetSchemaProperty now handles alignment, and with no StorageClass refuses it as Auto. The checks that nothing changes are unchanged.

Only titles and comments changed in test/codegenEdit.test.ts and test/parity/fidelity/schemaColumns.fidelity.test.ts, which called Alignment read-only. Their assertions are unchanged, and they pass because their entries are Auto.

Left out.

  • A file holding an Alignment other than -1 under Auto shows that value, while MATLAB loads -1. This predates the item.
  • Core's StorageClass options include SimulinkGlobal and lack 'Model default', which MATLAB lists. That is a separate item.

For the vscode pin bump (not in this PR)

None of the five needs a vscode source change. The table already honours _descriptionEditable and _valueEditable, it already renders a select cell and a text cell for a schema column, and both providers send the edit to node.setProperty. This core was packed and installed into vscode v1.39.0 (3cd6a16), and the two suites were run:

  • npm run test:integration: 191 passing and 1 pending, the same as with vscode's own pinned core.
  • npm run verify: 3263 passed and 1 failed (3264 pass with vscode's own core). The failure is test/dimensionsMode.test.ts:39-47, "the Signal surfaces a read-only dimensionsMode column from real data", with expected 'select' to be 'label'. It pins exactly the label that C-030 removes. It is to be updated at the pin bump, where a lock test for the Alignment cell (C-086) also belongs.

Gate, at the head (71b3446)

Implements the maintainer's 2026-10-16 group E ruling.

Follow-up commit 8353f69: tests that lock the two parts the review found unlocked

This commit adds tests only. There is no src change. The correctness review found that two parts of this PR could be reverted with the suite staying green. Each now has tests that fail when it is reverted.

  • C-168 / C-169: a bank with no saveobj envelope saves with no Value.
    • The new cases are in test/variantBankNoValue.test.ts. They hold a VariantBank and a VariantBankCoderInfo in the form the corpus's R2026b text dictionaries use: plain, sorted _properties, with no _custom_save and no Value key. That form is copied field for field from mix/dd_json.sldd and textsldd/dd.sldd, and is built from the committed classes_r2027a_text.sldd, rewriting one entry per case.
    • Two things are checked for each class: an unedited save gives back the file byte for byte, with no "Value" in the entry, and a save after a rename writes no Value either.
    • The existing envelope cases could not catch this. Under an envelope, _mergeProps drops an empty override, so the old { Value: '' } never reached the file.
    • Putting { Value: this.Value } back in both nodes fails 4 of 8047 tests, and those 4 are exactly these cases. Reverting one node alone fails only that class's 2 cases.
  • C-086: Alignment rounds a negative half the way MATLAB's int32 does.
    • MATLAB was measured once for this follow-up: R2027a 27.1.0.3409468, on a Parameter's and a Signal's CoderInfo under ExportedGlobal.
      • -0.5 is accepted and stored as -1.
      • -1.5 and -1.6 are refused as -2, and -2.5 is refused as -3.
      • -0.4 is refused as 0.
      • 0.5 is accepted as 1.
    • Two new cases per format in test/alignmentEdit.test.ts drive pAl8 and sAl16 through setProperty and the save:
      • -1.5, -1.6, -2.5 and -0.4 are refused with the measured number, nothing changes, and the save is unchanged.
      • -0.5 is taken as -1. It is saved as no key in text and an int32 -1 in binary, and it reads back as -1.
    • Swapping toInt32 for Math.round fails all 4 of them, and nothing else.
  • npm run verify passes: 8023 passed and 26 skipped, which is 8 more than at 71b3446.

Merge of main (#123) and two reconciled tests: 49d51fa, f0d2b90

#123 (C-032, C-199, F2-4, F6-1, C-003) reached main as c3dee1a after this PR's last green CI. On the merge ref, CI then failed 4 tests. Neither side has a defect. Each failing test pinned something the other PR changes on purpose.

  • 49d51fa merges origin/main (c3dee1a) into this branch. The merge was clean, with no textual conflicts. After it, rm -rf dist && npm run build gives a dist that matches the committed one exactly (zero files changed), so no dist commit was needed.
  • f0d2b90 reconciles the two tests. It changes tests only, with no src change.

1. test/newEntryEnvelopeBothFormats.test.ts (from #123, C-199): the test needed a new vehicle; what it checks is the same

  • What broke. Three cases called setProperty('Value', 'x') on a new Simulink.VariantBank:

    • "Simulink.VariantBank.Value, binary: cleared after a save…"
    • the same case in json
    • "an undeclared field: the sibling goes, and the envelope is the same object"

    They used that Value to exercise one shape: a field the saveobj envelope does not declare, so the sibling <P> is its only copy. C-168 removes exactly that Value, because MATLAB's Simulink.VariantBank has none (measured in R2027a). The set is now refused with "'vNew' is a Simulink.VariantBank, which has no Value, so none can be set on it."

  • Why the change is authorized. The maintainer ruled for C-168 (group E, 2026-10-16, theme 2: follow MATLAB). That ruling removes the vehicle these cases used. C-199's ruled behaviour does not change: a cleared envelope property is written as MATLAB writes one, and an undeclared field's sibling goes.

  • The new vehicle. Simulink.VariantConfigurations' Value. Among new entries, it is the only field createDefault still writes as a sibling beside a _custom_save envelope that does not declare it. That is true in both formats: VariantBankCoderInfo's Value went with C-169, and VariantVariable's Specification is declared.

    • The table row is now { className: 'Simulink.VariantConfigurations', prop: 'Value', declared: false }, and the undeclared _mergeProps case uses the same class.
    • Every assertion is unchanged.
    • The undeclared case now also asserts its own premise: the envelope's _fields has no Value. If this vehicle ever becomes declared, or goes away, the case fails instead of passing vacuously.
  • A caveat, stated in the test's comment. Whether MATLAB's Simulink.VariantConfigurations has a Value has not been measured (scope-C-169 already noted this). It may be invented too. If a later ruling removes it, there is no vehicle left among new entries for the undeclared shape.

  • Proof that the intent is kept. I reverted Binary nested objects in Other, a cleared envelope property, VariantControl Description, model-reference ids, and a dist-freshness CI gate #123's C-199 change in DataNode._mergeProps (src only, then restored) and ran the file. All 5 C-199 cases failed, including the 3 moved ones. Each failure shows the old value still written:

    • binary: expected [ '<unnamed>', 'Value' ] to deeply equal [ '<unnamed>' ]
    • json: expected [ '_properties', '_custom_save' ] to deeply equal [ '_custom_save' ]
    • the undeclared case: the sibling is still present (expected true to be false, :419)

2. test/signalEnumProps.test.ts (this PR, C-030): the expectation now follows #123's layout

Gate

  • npm run verify on the merged tree passes: 8172 passed and 26 skipped (246 files passed, 1 skipped). That is the 8168 tests that passed on the old merge ref plus the 4 reconciled ones. check:pack, check:leak and check:browser are OK, and the tree is clean afterwards.
  • CI on the new merge ref passes: run 38973153828, build-and-test, checked out at 527a5d7 ("Merge f0d2b90 into c3dee1a"). It gives 8172 passed and 26 skipped, the same as locally. CodeQL passes too.

ww-mw added 8 commits October 16, 2026 15:48
…AB's classes do not have

MATLAB R2027a has no Description property on Simulink.LookupTable or
Simulink.Breakpoint: isprop is 0, and setting one fails with "Unrecognized
property 'Description' for class 'Simulink.LookupTable'." Both nodes listed
one anyway, so the table and the Property Inspector offered an editor, the
edit was accepted, and both save paths wrote it as a top-level key that
MATLAB opens without a warning and ignores.

Both nodes now do what FunctionElement does. Description is not in
getProperties or in the General layout, descriptionEditable is false, and
setProperty('Description', ...) is refused in MATLAB's words and leaves the
entry unmodified. A Description key the file already has is shown read-only
(in the Description column and in the inspector's Other group) and is
written back as read on both save paths.

test/lutBreakpointNoDescription.test.ts covers both classes in both formats
and a new entry. Existing tests changed only where they pinned the removed
editor: typedef.fidelity's Description round-trip (now: refused, file
unchanged), the General groups in piGeneralAllNodes and lookupTableFlatProps,
the Breakpoint and LookupTable rows in absentPropertyWriteBack's table, and
schemaLayoutResolvable's layout-key floors (182 to 180 and 100 to 98, the two
removed description keys). The two class docs no longer claim a passing
MATLAB gate for a Description edit.
…s does not have

MATLAB R2027a has no Value property on Simulink.VariantBank: its properties
are Name, Description, VariantConditions, AllChoicesCoderInfo and
ActiveChoiceCoderInfo, isprop(..., 'Value') is 0, and setting one fails with
"Unrecognized property 'Value' for class 'Simulink.VariantBank'." The node
offered one anyway: the cell took an editor, setProperty('Value', ...) was
accepted, and both save paths wrote the text beside the saveobj envelope.
MATLAB opens such a file with no error or warning, builds a bank with no
Value, and drops the key on its next save, so the edit had no effect. Every
bank MATLAB wrote also showed '', a char value the class does not have.

The node now does what a bus element does (#113). valueEditable is false, a
Value edit is refused ("'<name>' is a Simulink.VariantBank, 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 (only ever this package's) is shown quoted, read-only, and
written back as read. A bank with no Value key shows an empty cell. The Value
column stays, read-only, for that key.

test/variantBankNoValue.test.ts covers the bank MATLAB wrote, in both
formats, and a new entry. Its fixtures are
test/fixtures/classes_r2027a_text.sldd and classes_r2027a_binary.sldd,
written by MATLAB R2027a, with their generator, make_classes_r2027a.m. A
re-run gives the same files apart from UUIDs and timestamps.

Existing tests changed only where they pinned the removed edit. In
variantLeafNodes, the VariantBank row of the shared contract now checks that
an edit is refused and the bag is written back as read, instead of checking
that the edit is written. Its two cases in the quoted-display regression,
which commit a Value, are removed. The class doc records the measurement,
and two comments that cited VariantBank's Value as written are corrected.
…AB's class does not have

MATLAB R2027a has no Value property on Simulink.VariantBankCoderInfo. Its
properties are HeaderFile, DefinitionFile, PreStatement, PostStatement and
Qualifier, isprop(..., 'Value') is 0, and setting one fails with
"Unrecognized property 'Value' for class 'Simulink.VariantBankCoderInfo'."
The node offered one anyway: the cell took an editor, setProperty('Value',
...) was accepted, and both save paths wrote the text beside the saveobj
envelope. MATLAB opens such a file with no error or warning and builds an
object with no Value, so the edit had no effect. Every entry MATLAB wrote
also showed '', a char value the class does not have.

The node now does what VariantBank does (the previous commit) and a bus
element does (#113). valueEditable is false, a Value edit is refused
("'<name>' is a Simulink.VariantBankCoderInfo, 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 (only ever this package's) is shown quoted, read-only,
and written back as read. An entry with no Value key shows an empty cell.
The Value column stays, read-only, for that key.

test/variantBankNoValue.test.ts now runs its cases over a table of both
classes, adding vbciD (MATLAB-written, in both formats of the existing
classes_r2027a fixtures) and a new entry. The VariantBank cases and their
titles are unchanged.

Existing tests changed only where they pinned the removed edit. In
variantLeafNodes, the VariantBankCoderInfo row of the shared contract
joins VariantBank in READ_ONLY: it now checks that an edit is refused and
the bag is written back as read, instead of checking that the edit is
written. The quoted-display case for the two classes is retitled, since
"like MATLAB" no longer holds for a key MATLAB's classes do not have; its
assertions are unchanged. The class doc records the measurement.
…AB's own sets

MATLAB R2027a's Simulink.Signal (and mpt.Signal) takes Complexity from
auto|real|complex and DimensionsMode from auto|Fixed|Variable, both defaulting
to auto, and refuses anything else with "There is no enumerated value named
'X'." Both showed as read-only labels, while BusElement and ValueType already
offered the same two properties as dropdowns. A setProperty('complexity', ...)
also answered true and changed nothing: the text went onto a stray lowercase
field that nothing shows or saves.

SignalNode now uses BusElement's and ValueType's mechanism with the Signal's
sets. Two new atoms (PropSignalComplexity, PropSignalDimensionsMode) replace the
two schema descriptors in place, both in the table columns and in the Property
Inspector layout, so both panes read the node field an edit writes. The column
order and the schema's layout order are unchanged. setProperty refuses any
other value through DataNode._rejectUnknownEnumeral, but only for these two
properties, so Storage Class keeps its own refusal. Both save paths write the
key when the file had it or the value is not auto. An untouched Signal, or one
set back to auto, therefore saves with no new key in a text dictionary, which
is how MATLAB writes it. A binary dictionary always has the key. A cleared cell
shows auto and is saved as auto, never as ''. Dimensions stays a label, and so
does Parameter's Complexity, which comes from its Value.

test/signalEnumProps.test.ts drives the session and the node over Signals
MATLAB wrote (sigC, sigR, sigA and sigNo in classes_r2027a_{text,binary}.sldd,
and sig1 in params.sldd), plus an mpt.Signal and a new Signal. A default
Signal set to complex/Variable saves as what MATLAB wrote for sigC: the text
bag is equal as a value (the new keys come last, where MATLAB sorts them), and
the binary element matches character for character. Set back
to auto, it saves as sigA. The test also covers refusals, a clear, undo
through the session, and the Property Inspector layout.

Existing tests changed only where they pinned the removed label:
- schemaColumns.test's "read-only Dimensions Mode" now expects the select cell.
- mptClasses.test's typed columns now read the cells' text.
- propAtoms.test: one comment.

Simulink.Signal.md and the two shared atoms' comments now describe the new
behaviour.
…TLAB's rule

Alignment (CoderInfo.Alignment) was a read-only label on Simulink.Parameter,
Simulink.Signal and their mpt subclasses. MATLAB R2027a lets it be set, by a
rule measured on 2026-10-16:
- The value is converted as int32 first, so 1.5 becomes 2 and 2^31 saturates.
- It must then be -1 or a power of two from 1 to 2^30.
- Only after that check, under StorageClass 'Auto', every value but -1 is
  refused.
- Setting StorageClass back to 'Auto' resets Alignment to -1.

A text dictionary holds a non-default value as a typed int32, first in
CoderInfo, and leaves the key out at -1. A binary one always holds an int32.
MATLAB reads a string "16" back as -1, so writing the cell's text as it was
would have lost the edit.

The table's alignment column is now a text cell wherever MATLAB takes an
edit, and a plain string under 'Auto'. A new optional PropClass.editableFor
hook, which BaseNode.getPropInfo honours, gates the cell on the node's
StorageClass. trySetSchemaProperty applies the rule, refusing in MATLAB's
words. MATLAB's "no more than 128" is left out, because MATLAB does not apply
it.

An edit keeps the form the file already holds: a typed leaf keeps its _type
and a bare number stays bare. A new key is a typed int32, inserted where
MATLAB's sorted text puts it. A string leaf is replaced, and a string is never
written. -1 removes the key in a text dictionary, and a StorageClass edit to
'Auto' does the same. Setting the value already held marks nothing. The
Property Inspector still shows Alignment read-only, as it shows Storage Class.

test/alignmentEdit.test.ts drives the session and the node over the entries
MATLAB wrote in classes_r2027a_{text,binary}.sldd. pSC set to 8 saves pAl8's
CoderInfo exactly. pAl8 set to -1 saves pAlM1's, and pAl8 with StorageClass
'Auto' saves pNo's. The test also covers each power of two up to 2^30, the
int32 conversion, MATLAB's refusals with nothing changed, the Auto gate and
its order, undo and redo through the session, the stored forms, a new entry
in each format, and an mpt.Parameter.

Existing tests changed only where they pinned the removed label:
- schemaBridge.test and schemaColumns.test: alignment's editor is now 'text'.
- mptClasses.test: an ExportedGlobal mpt.Parameter's Alignment is now an
  editable cell.
- schemaBridgeEdit.test: trySetSchemaProperty now owns alignment and refuses
  it under Auto, instead of declining it as a label.
codegenEdit.test and schemaColumns.fidelity.test still pass unchanged, because
their entries are 'Auto'. Only their titles and comments changed, since those
called Alignment read-only.

CodeGeneration.schema-columns.md and Simulink.Parameter.md describe the rule
and the written form.
Two behaviours of this branch that no test locked (the correctness review put
`{ Value: this.Value }` back in both nodes, and swapped toInt32 for
Math.round, and the suite stayed green):

- C-168 and C-169: a Simulink.VariantBank or Simulink.VariantBankCoderInfo
  held with no saveobj envelope, as the corpus's R2026b text dictionaries
  hold one (plain sorted _properties, no Value key), saves with no Value.
  The envelope cases could not see this, because under an envelope
  _mergeProps drops an empty override. New cases in
  test/variantBankNoValue.test.ts build that form from the committed
  classes_r2027a_text.sldd, one entry at a time, and check that an unedited
  save gives the file back byte for byte and that a save after a rename
  writes no Value. Reverting either node's _serializedOverrides fails that
  class's two cases.

- C-086: a negative half is rounded away from zero, as MATLAB's int32 does.
  Measured in R2027a (27.1.0.3409468) on a Parameter's and a Signal's
  CoderInfo under ExportedGlobal: -0.5 is set to -1; -1.5 and -1.6 are
  refused as -2, -2.5 as -3, and -0.4 as 0; 0.5 is set to 1. Two new cases
  per format in test/alignmentEdit.test.ts drive pAl8 and sAl16 through
  setProperty and the save. Math.round fails all four.

Tests only; no src change.
main's #123 reached main after this branch's last green CI, and the merge
ref failed 4 tests. Neither failure is a defect in either side: each test
pinned something the other PR changed on purpose.

- test/newEntryEnvelopeBothFormats.test.ts (from #123, C-199). Three cases
  set a Value on a new Simulink.VariantBank to exercise a field the saveobj
  envelope does not declare. C-168 removes exactly that Value (MATLAB's
  class has none), so the set is now refused. The cases move to the one
  vehicle a new entry still has for that shape: Simulink.VariantConfigurations'
  Value, written by createDefault as a sibling beside an envelope that does
  not declare it, in both formats. The assertions are unchanged; the
  undeclared case also states its premise (the envelope's _fields has no
  Value), so a vehicle that stops being one fails rather than passing
  vacuously. That Value is unmeasured against MATLAB and may be invented
  too; the comment says so. With #123's _mergeProps clear reverted (src
  only), all five C-199 cases here fail with the old sibling still written.

- test/signalEnumProps.test.ts (this branch, C-030). The Property
  Inspector's Other group for sigC in a binary dictionary now lists
  LoggingInfo's seven properties, because #123's C-032 flattens a binary
  1x1 object one level as the same object out of a .mat. The text
  dictionary's LoggingInfo has no properties, so it stays one row. The
  expectation follows #123 per format. With C-030's src reverted the case
  still fails (at its first assertion, the PI row's edit); with C-032's
  piOther change reverted it fails at the updated line.

No src change; dist rebuilt clean after the merge with no difference.
@ww-mw
ww-mw merged commit 25717d3 into main Oct 16, 2026
4 checks passed
@ww-mw
ww-mw deleted the properties-follow-matlab branch October 16, 2026 22:12
ww-mw added a commit to mathworks/data-explorer-vscode that referenced this pull request Oct 17, 2026
v1.40.0 (f9acdf9) carries mathworks/data-explorer-core#124 (Signal
Complexity and DimensionsMode options; Alignment editing), #125 (mpt
entries admitted by paste and drop) and #127 (findNodeById and the
duplicate-id warning), the core halves of C-030, C-086, C-180 and F1-50.
It also carries #128 (MCOS numeric classes, per-element object classes).

The lockfile changes only data-explorer-core: its root spec and its
node_modules entry, which now resolves to f9acdf939aa726cb9ddd3162a6cac19fc4f4a5ca.

With the pin: npm run verify passes 3422 of 3422 tests in 175 files, and
check:leak is OK; npm run test:integration has 221 passing and 1 pending.
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