From c74097e453ae82907c9757fc587edddfff2b8b94 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 07:06:21 +0000 Subject: [PATCH 1/2] refactor(runtime): split member chaining and bound-member collection into named steps Co-Authored-By: jason.han --- internal/exec/runtime/binding.go | 45 +++++++----- internal/exec/runtime/eval.go | 94 ++++++++++++++---------- internal/exec/runtime/subsetting_test.go | 2 +- 3 files changed, 86 insertions(+), 55 deletions(-) diff --git a/internal/exec/runtime/binding.go b/internal/exec/runtime/binding.go index d51a20d951..1b309e32de 100644 --- a/internal/exec/runtime/binding.go +++ b/internal/exec/runtime/binding.go @@ -776,24 +776,19 @@ func (ctx *Context) withBoundMembers(inst, owner *Instance, feature string, feat added := false path := feature for current := owner; current != nil; { - for _, typ := range current.types() { - for _, binding := range ctx.objectBindings(typ) { - for _, end := range binding.Ends { - name, ok := strings.CutPrefix(end.Path, path+".") - if !ok || strings.Contains(name, ".") || held[name] { - continue - } - member, typ := ctx.boundFrameMember(types, name) - if member == nil { - continue - } - if !added { - features, added = slices.Clone(features), true - } - held[name] = true - features = append(features, ctx.effectiveFeature(name, member, typ)) - } + for _, name := range ctx.membersBoundThrough(current, path) { + if held[name] { + continue + } + member, typ := ctx.boundFrameMember(types, name) + if member == nil { + continue + } + if !added { + features, added = slices.Clone(features), true } + held[name] = true + features = append(features, ctx.effectiveFeature(name, member, typ)) } next, nextFeature := current.Owner() if next == nil || nextFeature == "" { @@ -805,6 +800,22 @@ func (ctx *Context) withBoundMembers(inst, owner *Instance, feature string, feat return features } +// membersBoundThrough names the members of the object at path below owner that +// the bindings of owner's types reach directly, in binding order. +func (ctx *Context) membersBoundThrough(owner *Instance, path string) []string { + var names []string + for _, typ := range owner.types() { + for _, binding := range ctx.objectBindings(typ) { + for _, end := range binding.Ends { + if name, ok := strings.CutPrefix(end.Path, path+"."); ok && !strings.Contains(name, ".") { + names = append(names, name) + } + } + } + } + return names +} + // boundFrameMember finds the Kernel frame member name of the first of types // declaring it that the shape leaves out, with that type. func (ctx *Context) boundFrameMember(types []*symbols.Symbol, name string) (*symbols.Symbol, *symbols.Symbol) { diff --git a/internal/exec/runtime/eval.go b/internal/exec/runtime/eval.go index 13ce3e3228..a2120c74cc 100644 --- a/internal/exec/runtime/eval.go +++ b/internal/exec/runtime/eval.go @@ -1632,8 +1632,6 @@ func (ec *EvalContext) chainMemberValue(value Value, parts []ast.NameSegment, fr } return value, nil } - name, rest := parts[0].Text, parts[1:] - if literal := value.EnumerationLiteral(); literal != nil { // A literal is an occurrence of its enumeration, so its own features are // read from the object that literal stands for, whatever scalar it equals. @@ -1651,20 +1649,7 @@ func (ec *EvalContext) chainMemberValue(value Value, parts []ast.NameSegment, fr } return ec.chainOverElements(value, parts, from) case ValArray, ValVector, ValVectorQuantity, ValTensorQuantity, ValQuantity, ValMeasurementRef, ValCoordinateFrame, ValCoordinateTransformation: - // An array or vector read from an object keeps that object's members; a - // frame answers its own features from the value, then from its object. - if inst, ok := ec.ctx.structuredObject(value); ok && (isStructuredValue(&value) || value.Kind == ValQuantity) { - // A scalar quantity answers its own features (`num`, `mRef`) as the value it - // is; its object answers the members a specialization adds. - next, segments, label := Value{Kind: ValInstance, Instance: inst.ID}, parts, from - if value.Kind == ValQuantity { - if answer, ok, err := ec.ctx.quantityFeature(value, name); ok && err == nil { - next, segments, label = answer, rest, name - } - } - return ec.chainMemberValue(next, segments, label) - } - return ec.chainOwnFeature(value, parts, from) + return ec.chainThroughStructured(value, parts, from) case ValInstance, ValVariant: // handled below case ValMetaobject: @@ -1679,6 +1664,14 @@ func (ec *EvalContext) chainMemberValue(value Value, parts []ast.NameSegment, fr return Value{}, fmt.Errorf("cannot chain through non-instance member %s (%v)", from, value.Kind) } + return ec.chainObjectMember(value, parts, from) +} + +// chainObjectMember continues a chain through a member of the object an instance +// or selected variant value materialized: a reference value, shaped array or +// clock answers from what it is, then the feature value or variant named. +func (ec *EvalContext) chainObjectMember(value Value, parts []ast.NameSegment, from string) (Value, error) { + name, rest := parts[0].Text, parts[1:] // A selected variant is chained through the object it materialized. id, isObject := value.Object() if !isObject { @@ -1721,27 +1714,7 @@ func (ec *EvalContext) chainMemberValue(value Value, parts []ast.NameSegment, fr } fvDecl, ok := inst.FeatureValues[name] if !ok { - // A calc usage is an evaluation rather than a feature value, so its outputs are - // read from a run of it against this object. - if sym, found := ec.ctx.model.semantics.LookupMember(inst.Type, name); found && isCalcUsageSymbol(sym) { - return ec.calcUsageMemberValue(sym, inst, rest) - } - if ec.ctx.derivesKernelCollection(inst, name) { - return ec.chainFeatureValue(inst, name, rest, from) - } - // A member the shape leaves out, which the default the holder governs over - // states; else a Kernel member. - val, answered, err := ec.ctx.holderDefaultMember(inst, name) - if !answered { - val, answered, err = ec.kernelMemberValue(inst, name, from) - } - if answered { - if err != nil { - return Value{}, err - } - return ec.chainMemberValue(val, rest, name) - } - return Value{}, fmt.Errorf("%w: member %s not found in instance", ErrNoSuchFeature, name) + return ec.chainUnheldMember(inst, name, rest, from) } // A variant named through the variation feature it belongs to is the choice // itself, not a member of the variation's value. @@ -1759,6 +1732,53 @@ func (ec *EvalContext) chainMemberValue(value Value, parts []ast.NameSegment, fr return ec.chainFeatureValue(inst, name, rest, from) } +// chainThroughStructured continues a chain through an array, vector, quantity, +// measurement reference or frame value. An array or vector read from an object +// keeps that object's members; a frame answers its own features from the value, +// then from its object. +func (ec *EvalContext) chainThroughStructured(value Value, parts []ast.NameSegment, from string) (Value, error) { + inst, ok := ec.ctx.structuredObject(value) + if !ok || !(isStructuredValue(&value) || value.Kind == ValQuantity) { + return ec.chainOwnFeature(value, parts, from) + } + // A scalar quantity answers its own features (`num`, `mRef`) as the value it + // is; its object answers the members a specialization adds. + next, segments, label := Value{Kind: ValInstance, Instance: inst.ID}, parts, from + if value.Kind == ValQuantity { + if answer, ok, err := ec.ctx.quantityFeature(value, parts[0].Text); ok && err == nil { + next, segments, label = answer, parts[1:], parts[0].Text + } + } + return ec.chainMemberValue(next, segments, label) +} + +// chainUnheldMember continues a chain through a member the object holds no feature +// value for: a calc usage's run, a Kernel collection derived on demand, a default +// the holder governs, or a Kernel member. +func (ec *EvalContext) chainUnheldMember(inst *Instance, name string, rest []ast.NameSegment, from string) (Value, error) { + // A calc usage is an evaluation rather than a feature value, so its outputs are + // read from a run of it against this object. + if sym, found := ec.ctx.model.semantics.LookupMember(inst.Type, name); found && isCalcUsageSymbol(sym) { + return ec.calcUsageMemberValue(sym, inst, rest) + } + if ec.ctx.derivesKernelCollection(inst, name) { + return ec.chainFeatureValue(inst, name, rest, from) + } + // A member the shape leaves out, which the default the holder governs over + // states; else a Kernel member. + val, answered, err := ec.ctx.holderDefaultMember(inst, name) + if !answered { + val, answered, err = ec.kernelMemberValue(inst, name, from) + } + if answered { + if err != nil { + return Value{}, err + } + return ec.chainMemberValue(val, rest, name) + } + return Value{}, fmt.Errorf("%w: member %s not found in instance", ErrNoSuchFeature, name) +} + // chainFeatureValue continues a chain through the named feature value of inst. func (ec *EvalContext) chainFeatureValue(inst *Instance, name string, rest []ast.NameSegment, from string) (Value, error) { if val, ok, err := ec.constraintUsageRead(inst, name); ok { diff --git a/internal/exec/runtime/subsetting_test.go b/internal/exec/runtime/subsetting_test.go index 9a3afa20ec..28f9b9217d 100644 --- a/internal/exec/runtime/subsetting_test.go +++ b/internal/exec/runtime/subsetting_test.go @@ -390,7 +390,7 @@ func TestSubsetterObjectHasTheSubsettedType(t *testing.T) { if err != nil { t.Fatalf("mistyped.GetFeatureValue(engines): %v", err) } - if got := len(elementsOf(fv.HeldValue())); got != 1 { + if len(elementsOf(fv.HeldValue())) != 1 { t.Fatalf("mistyped.engines = %s, want the one wheel", FormatValue(fv.HeldValue())) } wheel, err := inst.GetFeatureValue(ctx, "wheel") From ed427d621bad92294e33f5327659ecfc18f29f1d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 07:13:56 +0000 Subject: [PATCH 2/2] refactor: keep the new helper comments to two lines Co-Authored-By: jason.han --- internal/exec/runtime/eval.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/internal/exec/runtime/eval.go b/internal/exec/runtime/eval.go index a2120c74cc..3666bce042 100644 --- a/internal/exec/runtime/eval.go +++ b/internal/exec/runtime/eval.go @@ -1667,9 +1667,8 @@ func (ec *EvalContext) chainMemberValue(value Value, parts []ast.NameSegment, fr return ec.chainObjectMember(value, parts, from) } -// chainObjectMember continues a chain through a member of the object an instance -// or selected variant value materialized: a reference value, shaped array or -// clock answers from what it is, then the feature value or variant named. +// chainObjectMember continues a chain through a member of the object an instance or +// selected variant materialized: what the object is answers first, then its features. func (ec *EvalContext) chainObjectMember(value Value, parts []ast.NameSegment, from string) (Value, error) { name, rest := parts[0].Text, parts[1:] // A selected variant is chained through the object it materialized. @@ -1732,10 +1731,8 @@ func (ec *EvalContext) chainObjectMember(value Value, parts []ast.NameSegment, f return ec.chainFeatureValue(inst, name, rest, from) } -// chainThroughStructured continues a chain through an array, vector, quantity, -// measurement reference or frame value. An array or vector read from an object -// keeps that object's members; a frame answers its own features from the value, -// then from its object. +// chainThroughStructured continues a chain through an array, vector, quantity or frame +// value: its own features answer from the value, the rest from the object it was read from. func (ec *EvalContext) chainThroughStructured(value Value, parts []ast.NameSegment, from string) (Value, error) { inst, ok := ec.ctx.structuredObject(value) if !ok || !(isStructuredValue(&value) || value.Kind == ValQuantity) { @@ -1752,9 +1749,8 @@ func (ec *EvalContext) chainThroughStructured(value Value, parts []ast.NameSegme return ec.chainMemberValue(next, segments, label) } -// chainUnheldMember continues a chain through a member the object holds no feature -// value for: a calc usage's run, a Kernel collection derived on demand, a default -// the holder governs, or a Kernel member. +// chainUnheldMember continues a chain through a member no feature value holds: a calc +// usage's run, a derived Kernel collection, a holder-governed default or a Kernel member. func (ec *EvalContext) chainUnheldMember(inst *Instance, name string, rest []ast.NameSegment, from string) (Value, error) { // A calc usage is an evaluation rather than a feature value, so its outputs are // read from a run of it against this object.