From 0ebbffecaac571793001769dd6fe7625635294bf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Nov 2026 21:31:52 +0000 Subject: [PATCH] refactor(semantic): split the model's most involved derivations into one helper per concern Co-Authored-By: jason.han --- ...mantic-model-complexity-helpers.changed.md | 1 + .../metamodel/structure_parameters.go | 229 +++++++++++------- internal/semantic/semantics/annotations.go | 224 +++++++++-------- internal/semantic/semantics/nested.go | 21 +- internal/semantic/semantics/redefinition.go | 46 ++-- .../semantics/reflective_relationships.go | 10 +- 6 files changed, 323 insertions(+), 208 deletions(-) create mode 100644 changes/unreleased/semantic-model-complexity-helpers.changed.md diff --git a/changes/unreleased/semantic-model-complexity-helpers.changed.md b/changes/unreleased/semantic-model-complexity-helpers.changed.md new file mode 100644 index 0000000000..80bcf885b4 --- /dev/null +++ b/changes/unreleased/semantic-model-complexity-helpers.changed.md @@ -0,0 +1 @@ +- The semantic model's most involved derivations are split into one helper per concern with no change to what they derive: the parameter tables of `structureParameterTable`, `structureBehaviorGenerals` and `structureImplicitParameterRedefinitions` (`internal/semantic/metamodel`), the feature-chain, type-operand and expression readings of `ReflectiveElements`, the per-metaclass ends of `implicitRelationshipElements`, the direction reading of `implicitParameterTargets` and the family rules of `familyNestedFQN` (`internal/semantic/semantics`). diff --git a/internal/semantic/metamodel/structure_parameters.go b/internal/semantic/metamodel/structure_parameters.go index d1b93d4c94..c76d714b9e 100644 --- a/internal/semantic/metamodel/structure_parameters.go +++ b/internal/semantic/metamodel/structure_parameters.go @@ -86,7 +86,18 @@ func (e *Evaluator) structureBehaviorGenerals(owner Element) ([]Element, bool) { if !ok { return nil, false } - for _, base := range impliedFeatures { + generals, ok = e.structureImpliedBaseBehaviors(impliedFeatures, nested) + if !ok { + return nil, false + } + return uniqueStructureElements(generals, e.options.Structure), true +} + +// structureImpliedBaseBehaviors is the behaviors typing the implied base features, the one +// named by the nested subsetting FQN excepted. +func (e *Evaluator) structureImpliedBaseBehaviors(bases []Element, nested string) ([]Element, bool) { + var generals []Element + for _, base := range bases { symbol := e.structureElementSymbol(base) if symbol == nil || (nested != "" && e.model.QualifiedNameOf(symbol) == nested) { continue @@ -102,25 +113,14 @@ func (e *Evaluator) structureBehaviorGenerals(owner Element) ([]Element, bool) { } } } - return uniqueStructureElements(generals, e.options.Structure), true + return generals, true } // structureParameterTable mirrors the semantic parameter table: owner's own positional // parameters, then what each general leaves unclaimed by clause or position, and the result. func (e *Evaluator) structureParameterTable(owner Element, visiting map[ElementKey]bool) ([]structureParameter, *structureParameter, bool) { if symbol := e.structureElementSymbol(owner); symbol != nil && !e.structureKnows(owner) { - var positional []structureParameter - for i := 0; ; i++ { - sym, direction := e.model.PositionalParameter(symbol, i, false) - if sym == nil { - break - } - positional = append(positional, structureParameter{ElementOf(sym), direction}) - } - var result *structureParameter - if sym, direction := e.model.PositionalParameter(symbol, 0, true); sym != nil { - result = &structureParameter{ElementOf(sym), direction} - } + positional, result := e.structureSymbolParameters(symbol) return positional, result, true } if visiting[owner.Key()] { @@ -133,9 +133,67 @@ func (e *Evaluator) structureParameterTable(owner Element, visiting map[ElementK if !handled || !ok { return nil, nil, false } + positional, result, ok := e.structureOwnedParameters(owner, owned.Values) + if !ok { + return nil, nil, false + } + generals, ok := e.structureBehaviorGenerals(owner) + if !ok { + return nil, nil, false + } + declared := positional + for _, general := range generals { + inherited, inheritedResult, ok := e.structureParameterTable(general, visiting) + if !ok { + return nil, nil, false + } + claimed, ok := e.structureClaimedParameters(declared, result, inherited, inheritedResult) + if !ok { + return nil, nil, false + } + for _, p := range inherited { + if claimed[p.element.Key()] { + continue + } + listed, ok := e.structureListsParameter(positional[len(declared):], p.element) + if !ok { + return nil, nil, false + } + if !listed { + positional = append(positional, p) + } + } + if result == nil && inheritedResult != nil && !claimed[inheritedResult.element.Key()] { + result = inheritedResult + } + } + return positional, result, true +} + +// structureSymbolParameters is the semantic model's parameter table of a symbol-backed owner: +// its positional parameters in order, and its result parameter. +func (e *Evaluator) structureSymbolParameters(symbol *symbols.Symbol) ([]structureParameter, *structureParameter) { var positional []structureParameter + for i := 0; ; i++ { + sym, direction := e.model.PositionalParameter(symbol, i, false) + if sym == nil { + break + } + positional = append(positional, structureParameter{ElementOf(sym), direction}) + } var result *structureParameter - for _, value := range owned.Values { + if sym, direction := e.model.PositionalParameter(symbol, 0, true); sym != nil { + result = &structureParameter{ElementOf(sym), direction} + } + return positional, result +} + +// structureOwnedParameters is the directed features among owner's owned features: the +// positional ones in order, and the first result parameter. +func (e *Evaluator) structureOwnedParameters(owner Element, owned []Value) ([]structureParameter, *structureParameter, bool) { + var positional []structureParameter + var result *structureParameter + for _, value := range owned { if value.Kind != ElementValue { return nil, nil, false } @@ -159,61 +217,41 @@ func (e *Evaluator) structureParameterTable(owner Element, visiting map[ElementK } positional = append(positional, p) } - generals, ok := e.structureBehaviorGenerals(owner) - if !ok { - return nil, nil, false - } - declared := positional - for _, general := range generals { - inherited, inheritedResult, ok := e.structureParameterTable(general, visiting) + return positional, result, true +} + +// structureClaimedParameters is the set of a general's parameters the declared ones claim: +// by redefinition clause, else by position with an agreeing direction; the result by its role. +func (e *Evaluator) structureClaimedParameters(declared []structureParameter, result *structureParameter, inherited []structureParameter, inheritedResult *structureParameter) (map[ElementKey]bool, bool) { + claimed := make(map[ElementKey]bool) + for i, p := range declared { + explicit, ok := e.structureRedefinitionTargets(p.element) if !ok { - return nil, nil, false - } - claimed := make(map[ElementKey]bool) - for i, p := range declared { - explicit, ok := e.structureRedefinitionTargets(p.element) - if !ok { - return nil, nil, false - } - if len(explicit) > 0 { - for _, target := range explicit { - claimed[target.Key()] = true - } - continue - } - if i < len(inherited) && inherited[i].direction == p.direction { - claimed[inherited[i].element.Key()] = true - } + return nil, false } - if result != nil { - explicit, ok := e.structureRedefinitionTargets(result.element) - if !ok { - return nil, nil, false - } + if len(explicit) > 0 { for _, target := range explicit { claimed[target.Key()] = true } - if inheritedResult != nil { - claimed[inheritedResult.element.Key()] = true - } + continue } - for _, p := range inherited { - if claimed[p.element.Key()] { - continue - } - listed, ok := e.structureListsParameter(positional[len(declared):], p.element) - if !ok { - return nil, nil, false - } - if !listed { - positional = append(positional, p) - } + if i < len(inherited) && inherited[i].direction == p.direction { + claimed[inherited[i].element.Key()] = true } - if result == nil && inheritedResult != nil && !claimed[inheritedResult.element.Key()] { - result = inheritedResult + } + if result != nil { + explicit, ok := e.structureRedefinitionTargets(result.element) + if !ok { + return nil, false + } + for _, target := range explicit { + claimed[target.Key()] = true + } + if inheritedResult != nil { + claimed[inheritedResult.element.Key()] = true } } - return positional, result, true + return claimed, true } // structureListsParameter reports whether the parameters inherited so far carry feature, as @@ -264,25 +302,14 @@ func (e *Evaluator) structureImplicitParameterRedefinitions(feature Element, mat } return elementsOf(e.model.MaskedParameterRedefinitions(symbol)), true } - owner, handled, ok := e.structureOwningType(feature) - if !handled || !ok { - return nil, false - } - if owner.Kind != ElementValue { - return nil, true - } - isBehavior, known := e.isMetaclass(owner.Element, "Behavior") - if !known { - return nil, false - } - isStep, known := e.isMetaclass(owner.Element, "Step") - if !known { + owner, behaviorLike, ok := e.structureBehaviorOwner(feature) + if !ok { return nil, false } - if !isBehavior && !isStep { + if !behaviorLike { return nil, true } - direction, ok := e.structureEffectiveDirection(owner.Element, feature) + direction, ok := e.structureEffectiveDirection(owner, feature) if !ok { return nil, false } @@ -295,7 +322,7 @@ func (e *Evaluator) structureImplicitParameterRedefinitions(feature Element, mat } position := -1 if !isResult { - position, ok = e.structureParameterPosition(owner.Element, feature) + position, ok = e.structureParameterPosition(owner, feature) if !ok { return nil, false } @@ -303,23 +330,16 @@ func (e *Evaluator) structureImplicitParameterRedefinitions(feature Element, mat return nil, true } } - generals, ok := e.structureBehaviorGenerals(owner.Element) + generals, ok := e.structureBehaviorGenerals(owner) if !ok { return nil, false } var out []Element for _, general := range generals { - positional, result, ok := e.structureParameterTable(general, make(map[ElementKey]bool)) + target, ok := e.structureParameterAt(general, position, isResult) if !ok { return nil, false } - target := result - if !isResult { - target = nil - if position < len(positional) { - target = &positional[position] - } - } if target == nil || target.element.Key() == feature.Key() { continue } @@ -331,6 +351,43 @@ func (e *Evaluator) structureImplicitParameterRedefinitions(feature Element, mat return out, true } +// structureBehaviorOwner is the type owning feature; behaviorLike reports whether it is a +// Behavior or a Step, false too when feature has no owning type element. +func (e *Evaluator) structureBehaviorOwner(feature Element) (owner Element, behaviorLike, ok bool) { + owning, handled, ok := e.structureOwningType(feature) + if !handled || !ok { + return Element{}, false, false + } + if owning.Kind != ElementValue { + return Element{}, false, true + } + isBehavior, known := e.isMetaclass(owning.Element, "Behavior") + if !known { + return Element{}, false, false + } + isStep, known := e.isMetaclass(owning.Element, "Step") + if !known { + return Element{}, false, false + } + return owning.Element, isBehavior || isStep, true +} + +// structureParameterAt is general's result parameter, or the one at position, nil when it +// has none there. +func (e *Evaluator) structureParameterAt(general Element, position int, isResult bool) (*structureParameter, bool) { + positional, result, ok := e.structureParameterTable(general, make(map[ElementKey]bool)) + if !ok { + return nil, false + } + if isResult { + return result, true + } + if position < len(positional) { + return &positional[position], true + } + return nil, true +} + // structureMaskedParameterRedefinitions is what a parameter without a clause masks for its // owner: the general parameters at its position whatever their direction, as the semantic masking. func (e *Evaluator) structureMaskedParameterRedefinitions(feature Element) ([]Element, bool) { diff --git a/internal/semantic/semantics/annotations.go b/internal/semantic/semantics/annotations.go index cb78e166e9..a198d075ee 100644 --- a/internal/semantic/semantics/annotations.go +++ b/internal/semantic/semantics/annotations.go @@ -1467,6 +1467,15 @@ func (m *Model) ReflectiveElements(sym *symbols.Symbol, feature string) ([]*symb if elems, ok := m.sendElements(sym, feature); ok { return elems, true } + if elems, ok, handled := m.featureChainElements(sym, feature); handled { + return elems, ok + } + if elems, ok, handled := m.typeOperandElements(sym, feature); handled { + return elems, ok + } + if elems, ok, handled := m.expressionElements(sym, feature); handled { + return elems, ok + } switch feature { case "owningNamespace": if !m.reflectiveMetaclassConforms(sym, "Element") { @@ -1578,52 +1587,6 @@ func (m *Model) ReflectiveElements(sym *symbols.Symbol, feature string) ([]*symb } } return ends, true - case "ownedFeatureChaining": - if (sym.Expr != nil && sym.Expr.Role == symbols.ExpressionChainFeature) || sym.Chain != nil { - return m.OwnedRelationships(sym), true - } - if m.reflectiveMetaclassConforms(sym, "Feature") { - return []*symbols.Symbol{}, true - } - case "featureTarget": - // The last chaining feature of a chain, else the feature itself (KerML 1.1 §8.3.3.3.4). - if sym.Expr != nil && sym.Expr.Role == symbols.ExpressionChainFeature { - if chain := m.ChainingFeatures(sym); len(chain) > 0 { - return []*symbols.Symbol{chain[len(chain)-1]}, true - } - } - if !m.reflectiveMetaclassConforms(sym, "Feature") { - return nil, false - } - if chain := m.chainingFeaturesOf(sym); len(chain) > 0 { - return []*symbols.Symbol{chain[len(chain)-1]}, true - } - return []*symbols.Symbol{sym}, true - case "crossFeature": - if !m.reflectiveMetaclassConforms(sym, "Feature") { - return nil, false - } - // The second chaining feature of the crossed feature chain (`g crosses f.sub` → sub), else - // the end's owned cross feature (KerML 1.1 §8.3.3.3.4 deriveFeatureCrossFeature). - crossed := []*symbols.Symbol{} - relationships := m.ownedImplicitRelationships(sym, crossSubsetting) - for _, rel := range relationships { - targets, ok := m.ReflectiveElements(rel, "crossedFeature") - if !ok { - return nil, false - } - for _, target := range targets { - if chain, ok := m.ReflectiveElements(target, "chainingFeature"); ok && len(chain) >= 2 { - crossed = append(crossed, chain[1]) - } - } - } - if len(relationships) == 0 { - if owned := m.OwnedCrossFeature(sym); owned != nil { - crossed = append(crossed, owned) - } - } - return crossed, true case "ownedTypeFeaturing", "ownedFeatureInverting": if !m.reflectiveMetaclassConforms(sym, "Feature") { return nil, false @@ -1669,29 +1632,6 @@ func (m *Model) ReflectiveElements(sym *symbols.Symbol, feature string) ([]*symb } } return out, true - case "chainingFeature": - if sym.Expr != nil && sym.Expr.Role == symbols.ExpressionChainFeature { - return m.ChainingFeatures(sym), true - } - if !m.reflectiveMetaclassConforms(sym, "Feature") { - return nil, false - } - return m.chainingFeaturesOf(sym), true - case "unioningType": - if !m.reflectiveMetaclassConforms(sym, "Type") { - return nil, false - } - return m.UnioningTypes(sym), true - case "intersectingType": - if !m.reflectiveMetaclassConforms(sym, "Type") { - return nil, false - } - return m.IntersectingTypes(sym), true - case "differencingType": - if !m.reflectiveMetaclassConforms(sym, "Type") { - return nil, false - } - return m.DifferencingTypes(sym), true case "owner": if owner := ownerSymbol(sym); owner != nil { return []*symbols.Symbol{owner}, true @@ -1739,33 +1679,6 @@ func (m *Model) ReflectiveElements(sym *symbols.Symbol, feature string) ([]*symb return nil, false } return []*symbols.Symbol{}, true - case "argument": - if !isExpressionSymbol(sym) { - return nil, false - } - return m.ExpressionArguments(sym), true - case "referent", "function": - if !isExpressionSymbol(sym) { - return nil, false - } - if feature == "function" { - // Only an instantiation expression has a function; a metadata - // access files its referenced element at the same slot. - switch sym.Decl.(type) { - case *ast.InvocationExpr, *ast.ConstructorExpr: - default: - return nil, false - } - } - return m.expressionReferencedElements(sym, feature), true - case "referencedElement": - if !isExpressionSymbol(sym) { - return nil, false - } - if _, ok := sym.Decl.(*ast.MetadataAccessExpr); !ok { - return nil, false - } - return m.expressionReferencedElements(sym, "function"), true case "valuation": // KerML 8.3.3.3.5 Feature::valuation: the FeatureValue among the // feature's owned memberships, if its declaration has a value part. @@ -1825,6 +1738,125 @@ func (m *Model) ReflectiveElements(sym *symbols.Symbol, feature string) ([]*symb return nil, false } +// featureChainElements reads a Feature's ownedFeatureChaining, featureTarget, crossFeature and +// chainingFeature; handled is false for other features and for a non-Feature's ownedFeatureChaining. +func (m *Model) featureChainElements(sym *symbols.Symbol, feature string) (elems []*symbols.Symbol, ok, handled bool) { + switch feature { + case "ownedFeatureChaining": + if (sym.Expr != nil && sym.Expr.Role == symbols.ExpressionChainFeature) || sym.Chain != nil { + return m.OwnedRelationships(sym), true, true + } + if m.reflectiveMetaclassConforms(sym, "Feature") { + return []*symbols.Symbol{}, true, true + } + return nil, false, false + case "featureTarget": + // The last chaining feature of a chain, else the feature itself (KerML 1.1 §8.3.3.3.4). + if sym.Expr != nil && sym.Expr.Role == symbols.ExpressionChainFeature { + if chain := m.ChainingFeatures(sym); len(chain) > 0 { + return []*symbols.Symbol{chain[len(chain)-1]}, true, true + } + } + if !m.reflectiveMetaclassConforms(sym, "Feature") { + return nil, false, true + } + if chain := m.chainingFeaturesOf(sym); len(chain) > 0 { + return []*symbols.Symbol{chain[len(chain)-1]}, true, true + } + return []*symbols.Symbol{sym}, true, true + case "crossFeature": + if !m.reflectiveMetaclassConforms(sym, "Feature") { + return nil, false, true + } + elems, ok = m.crossFeatureElements(sym) + return elems, ok, true + case "chainingFeature": + if sym.Expr != nil && sym.Expr.Role == symbols.ExpressionChainFeature { + return m.ChainingFeatures(sym), true, true + } + if !m.reflectiveMetaclassConforms(sym, "Feature") { + return nil, false, true + } + return m.chainingFeaturesOf(sym), true, true + } + return nil, false, false +} + +// crossFeatureElements is the second chaining feature of the crossed feature chain +// (`g crosses f.sub` → sub), else the end's owned cross feature (KerML 1.1 §8.3.3.3.4). +func (m *Model) crossFeatureElements(sym *symbols.Symbol) ([]*symbols.Symbol, bool) { + crossed := []*symbols.Symbol{} + relationships := m.ownedImplicitRelationships(sym, crossSubsetting) + for _, rel := range relationships { + targets, ok := m.ReflectiveElements(rel, "crossedFeature") + if !ok { + return nil, false + } + for _, target := range targets { + if chain, ok := m.ReflectiveElements(target, "chainingFeature"); ok && len(chain) >= 2 { + crossed = append(crossed, chain[1]) + } + } + } + if len(relationships) == 0 { + if owned := m.OwnedCrossFeature(sym); owned != nil { + crossed = append(crossed, owned) + } + } + return crossed, true +} + +// typeOperandElements reads a Type's unioningType, intersectingType and differencingType; +// handled is false for any other feature. +func (m *Model) typeOperandElements(sym *symbols.Symbol, feature string) (elems []*symbols.Symbol, ok, handled bool) { + var operands func(*symbols.Symbol) []*symbols.Symbol + switch feature { + case "unioningType": + operands = m.UnioningTypes + case "intersectingType": + operands = m.IntersectingTypes + case "differencingType": + operands = m.DifferencingTypes + default: + return nil, false, false + } + if !m.reflectiveMetaclassConforms(sym, "Type") { + return nil, false, true + } + return operands(sym), true, true +} + +// expressionElements reads an Expression's argument, referent, function and +// referencedElement; handled is false for any other feature. +func (m *Model) expressionElements(sym *symbols.Symbol, feature string) (elems []*symbols.Symbol, ok, handled bool) { + switch feature { + case "argument", "referent", "function", "referencedElement": + default: + return nil, false, false + } + if !isExpressionSymbol(sym) { + return nil, false, true + } + switch feature { + case "argument": + return m.ExpressionArguments(sym), true, true + case "function": + // Only an instantiation expression has a function; a metadata + // access files its referenced element at the same slot. + switch sym.Decl.(type) { + case *ast.InvocationExpr, *ast.ConstructorExpr: + default: + return nil, false, true + } + case "referencedElement": + if _, isAccess := sym.Decl.(*ast.MetadataAccessExpr); !isAccess { + return nil, false, true + } + feature = "function" + } + return m.expressionReferencedElements(sym, feature), true, true +} + type reflectiveDefinitionFeature struct { owner string target string diff --git a/internal/semantic/semantics/nested.go b/internal/semantic/semantics/nested.go index 4376d94509..16495e0a88 100644 --- a/internal/semantic/semantics/nested.go +++ b/internal/semantic/semantics/nested.go @@ -244,6 +244,18 @@ func nestedRuleFQN(u NestedUsage, owner NestedOwner) (fqn string, step bool) { // familyNestedFQN is the feature a usage of one kind nested in an owner of its // own family subsets ahead of the general rule chains, "" when none applies. func familyNestedFQN(u NestedUsage, owner NestedOwner) string { + if fqn := familyRoleNestedFQN(u, owner); fqn != "" { + return fqn + } + if !u.Composite { + return "" + } + return familyCompositeNestedFQN(u, owner) +} + +// familyRoleNestedFQN is the family feature a usage subsets by the role its +// keyword or prefix states (performed, exhibited, event, actor, ...), composite or not. +func familyRoleNestedFQN(u NestedUsage, owner NestedOwner) string { switch u.Kind { case ast.UsageAction: if u.Performed && ownerInPartFamily(owner) { @@ -281,9 +293,12 @@ func familyNestedFQN(u NestedUsage, owner NestedOwner) string { return "VerificationCases::VerificationCase::obj::requirementVerifications" } } - if !u.Composite { - return "" - } + return "" +} + +// familyCompositeNestedFQN is the family feature a composite usage subsets by +// its kind alone (subitems, subports, subviews, ...). +func familyCompositeNestedFQN(u NestedUsage, owner NestedOwner) string { switch u.Kind { case ast.UsageViewpoint: if ownerInViewFamily(owner) { diff --git a/internal/semantic/semantics/redefinition.go b/internal/semantic/semantics/redefinition.go index 01a8f41086..02fb45a01e 100644 --- a/internal/semantic/semantics/redefinition.go +++ b/internal/semantic/semantics/redefinition.go @@ -454,27 +454,9 @@ func (m *Model) implicitParameterTargets(sym *symbols.Symbol, skipExplicit, matc if sym == nil { return nil } - var direction ast.FeatureDirection - var isResult bool - if sym.Recorded() { - if sym.Facts == nil || sym.Facts.Direction == ast.DirNone { - return nil - } - if skipExplicit && len(sym.Facts.Redefines) > 0 { - return nil - } - direction = sym.Facts.Direction - isResult = sym.Facts.Modifiers.Has(symbols.ModResult) - } else { - usage, ok := sym.Decl.(*ast.Usage) - if !ok || usage.Direction == ast.DirNone { - return nil - } - if skipExplicit && declaresRedefinitionAST(usage) { - return nil - } - direction = usage.Direction - isResult = usage.IsResult + direction, isResult, ok := parameterDirectionOf(sym, skipExplicit) + if !ok { + return nil } if sym.OwnerScope == nil { return nil @@ -520,6 +502,28 @@ func (m *Model) implicitParameterTargets(sym *symbols.Symbol, skipExplicit, matc return out } +// parameterDirectionOf is sym's direction and whether it is the result parameter; ok is +// false for a non-parameter and, with skipExplicit, for one redefining by clause. +func parameterDirectionOf(sym *symbols.Symbol, skipExplicit bool) (direction ast.FeatureDirection, isResult, ok bool) { + if sym.Recorded() { + if sym.Facts == nil || sym.Facts.Direction == ast.DirNone { + return ast.DirNone, false, false + } + if skipExplicit && len(sym.Facts.Redefines) > 0 { + return ast.DirNone, false, false + } + return sym.Facts.Direction, sym.Facts.Modifiers.Has(symbols.ModResult), true + } + usage, isUsage := sym.Decl.(*ast.Usage) + if !isUsage || usage.Direction == ast.DirNone { + return ast.DirNone, false, false + } + if skipExplicit && declaresRedefinitionAST(usage) { + return ast.DirNone, false, false + } + return usage.Direction, usage.IsResult, true +} + // positionalParameters returns the parameters that participate in redefinition // by position: every owned parameter except the result parameter, which is // redefined by name of its role rather than by position. diff --git a/internal/semantic/semantics/reflective_relationships.go b/internal/semantic/semantics/reflective_relationships.go index 8002cd0a14..4109c01171 100644 --- a/internal/semantic/semantics/reflective_relationships.go +++ b/internal/semantic/semantics/reflective_relationships.go @@ -461,8 +461,14 @@ func (m *Model) implicitRelationshipElements(sym *symbols.Symbol, feature string return nil, false } } - // Per-metaclass ends: the source end answers the owning element, the - // target end what the edge resolves to. + return m.implicitRelationshipEndElements(meta, feature, src, tgt, tgtOK) +} + +// implicitRelationshipEndElements answers the ends a reflected relationship's metaclass +// declares: the source end is the owning element, the target end what the edge resolves to. +func (m *Model) implicitRelationshipEndElements(meta, feature string, src, tgt *symbols.Symbol, tgtOK bool) ([]*symbols.Symbol, bool) { + source := []*symbols.Symbol{src} + empty := []*symbols.Symbol{} srcEnd, tgtEnd := relationshipEndFeature(meta, feature) switch { case feature == "specific" && specializationMetaclassNames[meta]: