Summary
Replace the generated MixedTypeSet wrapper classes with a heterogeneous openminds.abstract.Schema root, restricting allowed types per property with a validator instead of a class declaration.
This is the alternative already named in the developer notes at the top of openminds.internal.abstract.MixedTypeSet, which record that the original blocker — enumerations for controlled instances in the ControlledTerm subtypes — has since been removed. This issue works that note up into a reviewable proposal, with the results of a prototype.
It is deliberately not part of the stacked series (#102 onwards). That series fixes defects and reworks the traversal shared by the serializer, resolver and deserializer, all within this repository. This proposal touches the generator and every generated type class, so it needs its own review.
See the comment below before reading further. The alternative this issue was originally weighed against — migrating the indexing overrides to matlab.mixin.indexing.RedefinesDot and keeping the wrappers — turns out not to be possible, because RedefinesDot does not intercept declared properties. Removing the wrappers is therefore the only way to remove the indexing layer.
What it would look like
A property declared today as:
author (1,:) openminds.internal.mixedtype.datasetversion.Author ...
{mustBeListOfUniqueItems(author)}
would become:
author (1,:) openminds.abstract.Schema ...
{mustBeAllowedInstance(author, ["openminds.core.actors.Consortium", ...
"openminds.core.actors.Organization", ...
"openminds.core.actors.Person"]), ...
mustBeListOfUniqueItems(author)}
with openminds.abstract.Schema gaining matlab.mixin.Heterogeneous.
What this buys
- The whole indexing layer disappears.
subsref, subsasgn and numArgumentsFromSubscript on Schema exist almost entirely to hide the wrappers. With no wrappers, instance.author(1) returns the concrete instance natively and there is nothing to override.
- 1520 generated wrapper classes are deleted (375 in
latest, across six version trees).
- Reference replacement becomes a positional element assignment.
instance.author(2) = resolved preserves the other entries and the index. This is the operation that is broken today, both for unresolved references generally and for multi-valued properties specifically.
- The reference representation can collapse to one form — see the scope question below.
Prototype results
A standalone prototype confirmed the mechanics and turned up four things the developer note does not anticipate.
1. mustBeA does not work. The note suggests mustBeA for property validation. It tests the class of the array, which for a heterogeneous array is the root, so a mixed array is rejected outright:
Error setting property 'author'. Value must be one of these types: 'HeteroA' or 'HeteroB'.
An element-wise validator is required instead.
2. Subclass-specific methods are blocked on a mixed array, with MATLAB:noSuchMethodOrField. Only methods that are Sealed or defined on the root can be called. Today MixedTypeSet forwards method calls to .Instance, so this may be a live regression rather than a theoretical constraint. Needs an audit of what is called on mixed property values.
3. An abstract root works, except for array expansion. Forming arrays, element access and .empty are all fine without getDefaultScalarElement. Padding (arr(5) = x on a 2-element array) fails with MATLAB:class:MissingGetDefaultScalarElement, and there is no sensible default openMINDS instance. Note the current subsasgn handles this same case poorly already — its own comment says empty slots "default to empty double, but should be empty object of correct instance type" — so this converts a silent wrong result into an explicit error.
4. Mixing becomes globally legal. [person, dataset] is a valid array anywhere; the restriction applies only at property assignment. This is the semantic the wrappers were protecting, and giving it up is a genuine trade rather than a bug.
Unresolved references still work, but the guarantee moves
An unknown-type stub remains representable: it is simply another concrete subclass of the root, and the property validator permits it alongside the allowed types. Verified in the prototype:
h.author = [realInstance, stub] -> root array, 2 elements
h.author(2) = resolved -> element 2 replaced, element 1 untouched
The cost is where the guarantee lives. Today a stub is accepted structurally: MixedTypeSet holds a MixedTypeReference by construction, so there is nothing to forget. Under this proposal every generated validator must explicitly exempt the stub type, which makes it generator discipline. One validator emitted without the exemption silently breaks references for that property. That risk lands in the same layer that produced the missing LINKED_PROPERTIES entry on TermSuggestion.addExistingTerminology and the untyped MRIScannerUsage.fieldOfView.
Scope question: minimal or full
Minimal — only properties that currently use a wrapper become Schema-typed. Single-type linked properties keep their concrete class, so an unresolved Person must still be a Person carrying an IsReference flag. Two reference representations remain.
Full — every linked property becomes Schema-typed. A reference is then always the same object regardless of context, because the allowed types come from the property rather than from the stub. The IsReference flag disappears, and with it the ambiguity between a typed instance that is a reference and a typed instance that is genuinely empty. See #12.
Full is the only version that reduces the reference representations to one. It is also the larger change: every linked property loses its class declaration, and type errors move from MATLAB's own message to a validator's.
Open questions
- Minimal or full.
- What editor tab-completion does for a
Schema-typed property. Generated classes exist partly to give users completion and inline documentation, and this has not been tested. It should be checked before the proposal is taken seriously.
- Whether any code calls a subclass-specific method on a mixed property value, per prototype finding 2.
- Migration mechanics. The change touches the generator, 744 type classes and 1520 wrapper classes across six version trees, so it needs coordination with the
pipeline branch. A mechanical transform of the existing trees plus a generator change is possible, but the generator change has to land or the next pipeline run reverts it.
Relationship to other work
🤖 Generated with Claude Code
Summary
Replace the generated
MixedTypeSetwrapper classes with a heterogeneousopenminds.abstract.Schemaroot, restricting allowed types per property with a validator instead of a class declaration.This is the alternative already named in the developer notes at the top of
openminds.internal.abstract.MixedTypeSet, which record that the original blocker — enumerations for controlled instances in theControlledTermsubtypes — has since been removed. This issue works that note up into a reviewable proposal, with the results of a prototype.It is deliberately not part of the stacked series (#102 onwards). That series fixes defects and reworks the traversal shared by the serializer, resolver and deserializer, all within this repository. This proposal touches the generator and every generated type class, so it needs its own review.
See the comment below before reading further. The alternative this issue was originally weighed against — migrating the indexing overrides to
matlab.mixin.indexing.RedefinesDotand keeping the wrappers — turns out not to be possible, becauseRedefinesDotdoes not intercept declared properties. Removing the wrappers is therefore the only way to remove the indexing layer.What it would look like
A property declared today as:
would become:
with
openminds.abstract.Schemagainingmatlab.mixin.Heterogeneous.What this buys
subsref,subsasgnandnumArgumentsFromSubscriptonSchemaexist almost entirely to hide the wrappers. With no wrappers,instance.author(1)returns the concrete instance natively and there is nothing to override.latest, across six version trees).instance.author(2) = resolvedpreserves the other entries and the index. This is the operation that is broken today, both for unresolved references generally and for multi-valued properties specifically.Prototype results
A standalone prototype confirmed the mechanics and turned up four things the developer note does not anticipate.
1.
mustBeAdoes not work. The note suggestsmustBeAfor property validation. It tests the class of the array, which for a heterogeneous array is the root, so a mixed array is rejected outright:An element-wise validator is required instead.
2. Subclass-specific methods are blocked on a mixed array, with
MATLAB:noSuchMethodOrField. Only methods that areSealedor defined on the root can be called. TodayMixedTypeSetforwards method calls to.Instance, so this may be a live regression rather than a theoretical constraint. Needs an audit of what is called on mixed property values.3. An abstract root works, except for array expansion. Forming arrays, element access and
.emptyare all fine withoutgetDefaultScalarElement. Padding (arr(5) = xon a 2-element array) fails withMATLAB:class:MissingGetDefaultScalarElement, and there is no sensible default openMINDS instance. Note the currentsubsasgnhandles this same case poorly already — its own comment says empty slots "default to empty double, but should be empty object of correct instance type" — so this converts a silent wrong result into an explicit error.4. Mixing becomes globally legal.
[person, dataset]is a valid array anywhere; the restriction applies only at property assignment. This is the semantic the wrappers were protecting, and giving it up is a genuine trade rather than a bug.Unresolved references still work, but the guarantee moves
An unknown-type stub remains representable: it is simply another concrete subclass of the root, and the property validator permits it alongside the allowed types. Verified in the prototype:
The cost is where the guarantee lives. Today a stub is accepted structurally:
MixedTypeSetholds aMixedTypeReferenceby construction, so there is nothing to forget. Under this proposal every generated validator must explicitly exempt the stub type, which makes it generator discipline. One validator emitted without the exemption silently breaks references for that property. That risk lands in the same layer that produced the missingLINKED_PROPERTIESentry onTermSuggestion.addExistingTerminologyand the untypedMRIScannerUsage.fieldOfView.Scope question: minimal or full
Minimal — only properties that currently use a wrapper become
Schema-typed. Single-type linked properties keep their concrete class, so an unresolvedPersonmust still be aPersoncarrying anIsReferenceflag. Two reference representations remain.Full — every linked property becomes
Schema-typed. A reference is then always the same object regardless of context, because the allowed types come from the property rather than from the stub. TheIsReferenceflag disappears, and with it the ambiguity between a typed instance that is a reference and a typed instance that is genuinely empty. See #12.Full is the only version that reduces the reference representations to one. It is also the larger change: every linked property loses its class declaration, and type errors move from MATLAB's own message to a validator's.
Open questions
Schema-typed property. Generated classes exist partly to give users completion and inline documentation, and this has not been tested. It should be checked before the proposal is taken seriously.pipelinebranch. A mechanical transform of the existing trees plus a generator change is possible, but the generator change has to land or the next pipeline run reverts it.Relationship to other work
🤖 Generated with Claude Code