Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 28 additions & 17 deletions internal/exec/runtime/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "" {
Expand All @@ -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) {
Expand Down
90 changes: 53 additions & 37 deletions internal/exec/runtime/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand All @@ -1679,6 +1664,13 @@ 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 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.
id, isObject := value.Object()
if !isObject {
Expand Down Expand Up @@ -1721,27 +1713,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.
Expand All @@ -1759,6 +1731,50 @@ 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 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) {
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 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.
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 {
Expand Down
2 changes: 1 addition & 1 deletion internal/exec/runtime/subsetting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading