Fold inherited properties into the entity that inherits them - #15
Conversation
An entity found through its base was reported with only the properties it declares itself: PriorityOrder listed Rank and omitted the Name and Number it persists. The base walk already resolves every ancestor; keeping that edge lets each ancestor' properties fold into the entity, root down, marked with the class that declared them. A redeclared property stays the entity' own, and the abstract base is now marked abstract so a renderer can say which heading is a table. Closes peopleworks#13.
| foreach (var candidate in candidates) | ||
| { | ||
| if (!accepted.Contains((candidate.Namespace, candidate.Name))) | ||
| continue; | ||
|
|
||
| // Only one part of a partial class declares the base list; the parts that declare | ||
| // none must not erase the edge that part resolved. | ||
| if (ResolveBase(candidate.Declaration, candidate.Scopes, accepted) is { } parent) | ||
| parents.TryAdd((candidate.Namespace, candidate.Name), parent); | ||
| } |
| foreach (var property in parent.Properties) | ||
| { | ||
| if (own.Contains(property.Name)) | ||
| continue; | ||
|
|
||
| var copy = property.Clone(); | ||
| // The declarer, not the parent: what the parent itself inherited keeps its origin. | ||
| copy.InheritedFrom ??= parent.ClassName; | ||
| inherited.Add(copy); | ||
| } |
#15 landed without an entry, and it is the largest user-visible change in the batch: every entity's property list is different. The release notes for 0.13.0 are assembled from [Unreleased], so an omission here is an omission there. One bullet rather than two. The window where an entity's own columns fell out of a five-slot summary existed only between the fold landing and this branch -- it was never released, and a changelog that documents it is describing a version nobody can install. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Merged. The extraction half of this holds up under everything I could think to throw at it — throwaway fixtures rather than a reading of the diff, and the parents map survived all of them:
What the probe did find is one door down from where you were looking, and it is not a defect in this change — it is the consequence of the decision you handed over. Two summaries take the first few properties of the list. The full listings read root down, the way the class does, and that is right. But a five-slot table sharing its width with a six-column audit base spends all five on the base:
Not one of its own columns survived. Across a seven-entity fixture, six rows carried the identical five names — a table whose whole job is to tell entities apart, unable to. One entity kept a single column by accident, because a required property outranks the rest. You wrote that The part worth keeping is why your verification could not have caught it. Your three corpora fold about one property per entity ( One housekeeping note: this landed without a |
Fixes #13.
What this changes
Each entity now carries the properties it inherits from classes declared in the same project, folded in declaration order from the root down, each marked with the class that declared it. The abstract base itself is now marked
IsAbstract.Why
Your numbers from #13, on
mainand on this branch:PriorityOrder.NameandPriorityOrder.Numberare persisted, appear in views, and are readable from any code an agent writes; they now appear under the heading that presents the entity, instead of being a stated omission in an inventory that promises to be whole.How it decides
The walk from #9 already resolves every class's ancestry through the deriving file's scope — the fix is to stop throwing that edge away.
SelectPersistentClassesnow keeps the resolved parent per accepted class, computed after the fixed point, because a parent may be accepted rounds after the class deriving from it. Nothing is resolved a second way: a bare-name parent map would have been the mistake theContractsdecoys exist to catch.Folding runs after the partial merge, so a parent's property set is complete before it is folded into anyone, and folds the parent first, so a chain of any depth arrives complete. Each listing is a copy: the same declared property appears under every descendant, and each copy names its own declarer — on MainDemo,
Employee.PhotosaysParty, notPerson, even thoughEmployeereaches it throughPerson.A property the class redeclares is its own: the inherited one is not added beside it. A new fixture class,
LabeledOrder : OrderredeclaringNumber, pins that — oneNumber, the redeclaration.The two decisions #13 left open
ExtractedProperty.InheritedFrom,nullfor a property the entity declares itself. It is data only for now: no renderer prints it yet, because how "(from AuditedObject)" reads in three renderers and two languages felt like your call, and the field is everything a renderer needs whenever you make it.IsAbstractis read from syntax and survives the partial merge; the base's own heading is unchanged. Folding does make its duplication question sharper, and what an abstract base's heading should say is editorial.Also deliberately out of scope: rules and associations an entity inherits are still listed only under their declarer — that is #14, filed separately for the reason #13 was filed out of #9.
Verification
Against the 26.1 demos and the 196-entity legacy application from #1, comparing this branch to
mainper entity:FeatureCenter.NET.XPOMainDemo.NET.XPOEmployee27 = 15 own +Party's 5 +Person's 7, exact against sourceObjectSpacefield, no properties; a base holding no columns folds nothingOn every corpus the entity sets are identical to
mainand each entity's total minus its inherited count equalsmain's count — the fold only adds, never loses, never duplicates.295 tests, 0 warnings. The five new tests fail on
mainfor the stated reasons; the only existing test that moved was the README's test-count claim.