Skip to content

An object whose text dictionary holds its properties beside its class is saved as read, in every class - #145

Merged
ww-mw merged 4 commits into
mainfrom
inline-property-bags-replay-as-read
Oct 18, 2026
Merged

ww-mw merged 4 commits into
mainfrom
inline-property-bags-replay-as-read

Conversation

@ww-mw

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

Copy link
Copy Markdown
Member

One item in three commits on main 71378b5: 4415e5a (the fix), then cdb3442 and 9cc2373 (tests only, for the _elements: null spelling), and a merge of main (6ae067a: the 1.41.0 bump and #146, no conflicts).

G2-INLINEBAG: the inline property bag in the other Simulink object classes

Ruling. The agenda's follow-ups from group F's reviews, item 2:

The inline-bag shape in seven more classes (#136, SER-TE). A Signal, AliasType, NumericType, LookupTable, ValueType, Breakpoint or ConfigSet whose text dictionary holds its properties beside its class has a made-up _elements written on a text save, and accepts edits into it.

The maintainer answered "2. yes, yes" (group G, 2026-10-18). Items 1-4 are built under the rulings they already fall under. For this item that is group F's SER-TE ruling, option (a): "when _elements is absent, ... replay serial._rawVal". Theme 1 adds: "a save the writer cannot spell is refused rather than written wrong."

What was wrong. Take an entry {"_array_class":"Simulink.Signal","_array_type":"object","DataType":"single","Description":"d"}, with no _elements. It opens with every property at its default, and that part is unchanged. But then:

  • Saves were not the file. A copy and every save wrote a made-up "_elements":[{"_properties":{...}}] beside the keys as read, so an untouched save differed from the file. AliasType added BaseType:"" and ConfigSet added Name.
  • Edits were kept. An edit (Description, Min, BaseType, Storage Class, ...) was accepted, and saved into that element.
  • Editors were offered. The table and the Property Inspector offered editors for these properties.

This applied to the seven classes named, to mpt.Signal, and to every class that shares the writer: VariantExpression, VariantControl, VariantVariable, VariantBank, VariantBankCoderInfo, ConfigSetRef, VariantConfigurationData/VariantConfigurations and CustomObject. The XML save was already refused (#136).

Root cause.

  • The writer. DataNode._serializeSimulinkObject (src/datamodel/node/DataNode.ts) always wrote result._elements = [element] with the merged overrides. It is the text writer of SimulinkObjectNode.serializeValue and SignalNode.serializeValue, and those are its only two callers.
  • Edits. No class in this family asked DataNode._refuseInlineBagEdit, so DataNode.setProperty accepted the edit.
  • Editors. getPropInfo, valueEditable and descriptionEditable never asked _inlineObjectBag.

Fix.

  • The writer. _serializeSimulinkObject returns {...rawVal} when _inlineObjectBag(). This is one change at the root, so it covers every class above.
  • SimulinkObjectNode gains:
    • setProperty: _refuseInlineBagEdit ?? super, like BaseBusNode and EnumTypeNode, without their _isUnchangedCommit check, which cannot pass here (it needs getPropInfo().editable, which is false for every property of an inline bag but Name);
    • getPropInfo, through _inlineBagPropInfo;
    • valueEditable and descriptionEditable, which are false for an inline bag.
  • Overrides that answer first now ask _refuseInlineBagEdit too:
    • SignalNode, ValueTypeNode and VariantControlNode ask it after the encoded check, as ParameterNode does.
    • LookupTableNode, BreakpointNode, VariantBankNode and VariantBankCoderInfoNode ask it at the top.
  • SignalNode also gains descriptionEditable and getPropInfo, as ParameterNode has.
  • The XML refusal is unchanged.

MATLAB truth. Not needed. MATLAB never writes this shape, and option (a) is already the ruling.

Tests. test/inlineObjectBag.test.ts gains a block of 80 cases (53 in 4415e5a, 27 for the _elements: null spelling in the two review commits; 17 classes in 26 entries). It runs on its own synthetic dictionary, sparse_text.sldd with one inline-bag entry appended per class (17 classes). No corpus file is used.

  • The whole file. An untouched save is byte-identical to the bytes read. No entry offers an editor but its Name: getPropInfo().editable, valueEditable, descriptionEditable and row._descriptionEditable are all false.
  • Each class, through the real ingest, serialize and setProperty paths:
  • Before the change, all 80 fail, each for the right reason (80 failed and 35 passed at 71378b5):
    • 52 copy or rename cases, each writing a made-up _elements;
    • 16 edits accepted;
    • 10 Descriptions refused in other words;
    • the untouched save differs;
    • Signal's Min editor is offered.
  • Mutation check. Each of the 14 pieces of the change was removed in turn, and each removal fails at least one new case.

Existing tests. No existing test assertion changed. One comment above the InlineSignal case said a Signal's text save "is left as it was". It now points to the new block.

Left out, and why.

  • ObjectNode (an unregistered class) already saves this shape as read, but it accepts a Description edit and drops it. Any childless object does the same, whatever its shape. That is F2-4's ObjectNode half, and it is still the maintainer's call.
  • ConfigSet rename. An inline Name key is saved as read, with its old text. No reader reads that key, and this is option (a) as ruled.
  • An open-time warning for this shape (SER-TE deviation 4). It is still optional, and it touches SlddNode.parse, which other work shares.

A behaviour change, inline shape only. These edits were refused before, each with its own reason, and now get the inline-bag message, in both spellings: LookupTable's and Breakpoint's Description (was "Unrecognized property"); VariantControl's Description; VariantBank's Description, BankName and Value; VariantBankCoderInfo's Description, HeaderFile, DefinitionFile and Value. The element form is unchanged.

vscode. No change is needed; the pin bump brings this in.

  • An edit gets a SetPropertyResult refusal, which vscode already shows.
  • vscode shows an editor only where core reports one editable, and core now reports none but the Name for these entries.
  • A no-op save writes the document text.

Gates at 4415e5a (superseded; see the gates at the head below)

  • Build. rm -rf dist && npm run build leaves the tree clean, and dist is in the commit.
  • npm run verify (typecheck, build, smoke, test, check:pack, check:leak, check:browser): rc 0. 288 test files passed and 1 skipped; 9835 tests passed and 27 skipped.
  • Readbacks. DEX_READBACKS_CORPUS=<corpus>/parity npx vitest run test/parity/readbacks.truth.test.ts: 6 of 6 passed.
  • Live MATLAB. DEX_MATLAB_CMD="mw -using Bmain matlab": the live test/parity/matlab/writeback.live.test.ts passed 32 of 32, and the offline test/valueTypeValueProps.test.ts (it names DEX_MATLAB_CMD only in a comment) passed 36 of 36; 68 in all, rc 0. Only repo fixtures were opened.
  • Corpus census, read in place: the text dictionaries hold 6 inline-bag entries, all of them Parameter (fixed in Writers refuse what they cannot spell: an unspellable cell element or property, a function-handle paste into binary, a non-string object class; a Parameter with no elements saves #136). None is in a class this PR covers, and the census is the same before and after. One of the 142 text dictionaries cannot be opened at base or at head (it is larger than V8's string limit); a grep of it shows no inline bag.

Review rounds: the _elements: null spelling (cdb3442, 9cc2373)

_inlineObjectBag treats _elements: null the same as no _elements, and #136's NullElements case pins that for a Parameter. In 4415e5a every new case spelled the shape without the key, so the null spelling was unpinned at each guard:

  • cdb3442 adds NullBagSignal, NullBagAliasType, NullBagVariantControl and NullBagCustomObject (12 cases). It pins four guards: _serializeSimulinkObject (narrowed, it fails 30 cases), SON valueEditable, and SON's and SignalNode's descriptionEditable (1 each). With 4415e5a's test file, all four narrowings pass the whole suite.
  • 9cc2373 adds NullBagValueType (Min, Max), NullBagVariantBank, NullBagVariantBankCoderInfo, NullBagBreakpoint and NullBagLookupTable (15 cases). These five classes ask _refuseInlineBagEdit in their own setProperty, ahead of SimulinkObjectNode's. With cdb3442's file, a call site narrowed to skip the null spelling still passed. For a ValueType the review found the full suite green (9847): Min/Max was accepted, then dropped on save, and the entry's lastmod was restamped. In the other four the edit was refused with the wrong message. Now each narrowed call site fails its own class's refused-edit case, and 114 of 115 pass. The same holds for SignalNode and VariantControlNode.
  • Before the fix (src at 71378b5), all 27 null cases fail:
    • 18 copy or rename cases write a made-up element where the null was: [{"_properties":{}}], holding BaseType: "" for an AliasType and Value: "" for a VariantControl.
    • 4 Description edits are accepted: Signal, AliasType, CustomObject and ValueType.
    • 5 are refused in other words:
      • VariantControl: "has no Description in a dictionary";
      • VariantBank: "was read with no Description in its saved form";
      • VariantBankCoderInfo: "has no Description in a dictionary";
      • Breakpoint and LookupTable: "Unrecognized property".
  • No src change in either commit, and dist is unchanged.

Gates at the head (6ae067a)

  • Merge of main. 6ae067a merges main b8d7b59 (the 1.41.0 bump and A complex value with a -0 imaginary part shows +0i, and cell literals take signs and imaginary-first members as MATLAB does #146) with no conflicts; dist rebuilt from the merged src leaves the tree clean.
  • npm run verify: rc 0. 290 test files passed and 1 skipped; 10,045 tests passed and 27 skipped (main's 9,965 plus this PR's 80). Readbacks with DEX_READBACKS_CORPUS set: 6 of 6.
  • At 9cc2373, before the merge: verify 9,862 passed and 27 skipped; CI all green.
  • Review (round 3). Both lenses MERGE. Reverting the src fails exactly the 80 new cases. 55 mutants run; every deletion mutant is killed, and the survivors are equivalent. A base/head walk of 130 repo fixtures and 345 corpus files is identical apart from the synthetic inline file. The live MATLAB writeback.live (32/32) and fidelitySmoke (2/2) pass. vscode main (202dc3f) with this core passes verify (3,476) and integration (228 passing).

No existing assertion changed in any of the three commits.

Implements the maintainer's 2026-10-18 rulings (group G).

ww-mw added 4 commits October 18, 2026 10:07
… beside its class as it was read

A text dictionary can hold an object's properties beside its class, with
no _elements: {"_array_class": "Simulink.Signal", "DataType": "single",
...}. MATLAB never writes this shape. #136 made a Parameter, the buses and
the enumeration type save it as it was read, refuse every edit but a
rename, and refuse its XML. The same shape in a Signal (and an
mpt.Signal), AliasType, NumericType, LookupTable, ValueType, Breakpoint or
ConfigSet did not throw, but:

- every save, and a copy of the entry, wrote a made-up
  "_elements": [{"_properties": {...}}] beside the properties as read,
  holding what the node overrides (an AliasType's BaseType "", a
  ConfigSet's Name), so an untouched save of the file was not the file;
- an edit (Description, Min, BaseType, Storage Class, ...) was accepted
  and saved into that made-up element;
- the table and the Property Inspector offered editors for it.

The made-up element came from DataNode._serializeSimulinkObject, the text
writer of SimulinkObjectNode and SignalNode, so the fix is there and
reaches every class it serves. That includes the variant classes,
ConfigSetRef, VariantConfigurationData and CustomObject, which had the
same defect. This follows group F's ruling for SER-TE, option (a), as
agenda follow-up 2 extends it:

- _serializeSimulinkObject returns the value as it was read when
  DataNode._inlineObjectBag() is true.
- SimulinkObjectNode gains a setProperty that asks _refuseInlineBagEdit
  first. So do the overrides that answer before it: SignalNode,
  ValueTypeNode, LookupTableNode, BreakpointNode, VariantControlNode,
  VariantBankNode and VariantBankCoderInfoNode. Every edit but a rename
  is therefore refused with #136's message. That includes a LookupTable's
  or a Breakpoint's Description, which got MATLAB's "Unrecognized
  property" before.
- SimulinkObjectNode and SignalNode offer no editor for any property but
  the name (getPropInfo through _inlineBagPropInfo, valueEditable,
  descriptionEditable).
- The XML refusal (#136) is unchanged.

Not changed: an unregistered class (ObjectNode) already saves this shape
as read. It still accepts a Description edit and drops it, as any
childless object does. That is F2-4's ObjectNode half, which is still the
maintainer's call.

Tests: test/inlineObjectBag.test.ts gains a block with its own synthetic
dictionary (sparse_text.sldd plus one entry per class; no corpus file),
53 cases. An untouched save is byte-identical, and no entry offers an
editor but its name. For each class: the copy is the value as read; a
rename saves under the new name with the value as read; every edit tried
is refused with the message, and the save is unchanged. All 53 fail
before the change: a made-up _elements, an edit accepted (or refused in
other words), an editor offered. The existing cases are unchanged. One
comment above the existing InlineSignal case said a Signal's text save
"is left as it was", and now points to the new block.
The previous commit made every SimulinkObjectNode and SignalNode class
save an object holding its properties beside its class as it was read,
refuse every edit but a rename, and offer no editor but its name. Its
tests used only the spelling with no _elements key. The other spelling,
_elements: null, which a hand edit can leave, is the same shape:
DataNode._inlineObjectBag covers it, and #136's NullElements case pins
it for a Parameter. No test held it for these classes, so narrowing any
of the four new guards to "_elements is undefined" left the whole suite
green while the old behaviour came back for the null spelling:

- DataNode._serializeSimulinkObject: an untouched save wrote a made-up
  element over the null (a Signal's and a CustomObject's
  [{"_properties":{}}], an AliasType's {"BaseType":""}, a
  VariantControl's {"Value":""});
- SimulinkObjectNode.valueEditable: a VariantControl offered a Value
  editor;
- SimulinkObjectNode.descriptionEditable and
  SignalNode.descriptionEditable: an AliasType and a Signal offered a
  Description editor.

test/inlineObjectBag.test.ts adds four _elements: null entries to the
other classes' synthetic dictionary: a Signal, an AliasType, a
VariantControl and a CustomObject. Each runs the block's existing
cases (copy, rename, refused edits), and the whole-file cases (an
untouched save is the bytes read; no editor but the name) now cover
them too. That is 12 new cases. With src at 71378b5 all 12 fail: the
copy and the rename carry the made-up element in place of the null,
and the Description edit is accepted (or, for the VariantControl,
refused in other words). Each of the four narrowed guards fails at
least one of them. No src change, and dist is unchanged.
…emselves

ValueType, VariantBank, VariantBankCoderInfo, Breakpoint and LookupTable
ask _refuseInlineBagEdit in their own setProperty, ahead of
SimulinkObjectNode's. No test spelled their inline bag as
`_elements: null`, so each of those call sites could be narrowed to skip
that spelling with the suite still green. For a ValueType the edit was
then accepted and dropped on save; for the other four it was refused with
the wrong message (the encoded-stream text, or "Unrecognized property").

Add an `_elements: null` entry for each of the five to OTHER_CASES, with
the edits its `_elements`-less twin already makes. That is 15 new cases.
With src at 71378b5 all 15 fail: copy and rename write a made-up element
over the null, the ValueType Description edit is accepted, and the other
four refuse it in other words. Each narrowed call site now fails its own
class's refused-edit case. No src change; dist is unchanged.
@ww-mw
ww-mw merged commit cde901a into main Oct 18, 2026
4 checks passed
@ww-mw
ww-mw deleted the inline-property-bags-replay-as-read branch October 18, 2026 16:20
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