From 50138da0ef71df1a1daf18d47fee44505924cd10 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:30:18 +0000 Subject: [PATCH 1/2] perf(semantic): conform to a named type by the element it names Co-Authored-By: jason.han --- changes/unreleased/conform-by-symbol.fixed.md | 8 ++++ .../semantics/conforms_by_name_allocs_test.go | 42 +++++++++++++++++++ internal/semantic/semantics/filter.go | 13 ++++++ 3 files changed, 63 insertions(+) create mode 100644 changes/unreleased/conform-by-symbol.fixed.md create mode 100644 internal/semantic/semantics/conforms_by_name_allocs_test.go diff --git a/changes/unreleased/conform-by-symbol.fixed.md b/changes/unreleased/conform-by-symbol.fixed.md new file mode 100644 index 0000000000..a629f59a2e --- /dev/null +++ b/changes/unreleased/conform-by-symbol.fixed.md @@ -0,0 +1,8 @@ +- A conformance check against a named library type — the ones every redefinition's derived + `isVariable`/`isConstant` ask through `Usage::mayTimeVary` (`Occurrences::Occurrence`, + `Links::SelfLink`, `Occurrences::HappensLink`, `Actions::Action`) — compares the candidate and + its memoized supertypes to the one element registered under the name instead of building the + qualified name of each supertype on every check, which the 0.11.0 performance record found + costing the connector-dense `ImportValues` benchmarks 29% more allocations (135.5 k → 175.2 k + per load) and a synthetic document's analysis 10% more. A name no or several elements carry + is still compared by name. diff --git a/internal/semantic/semantics/conforms_by_name_allocs_test.go b/internal/semantic/semantics/conforms_by_name_allocs_test.go new file mode 100644 index 0000000000..124c4334fe --- /dev/null +++ b/internal/semantic/semantics/conforms_by_name_allocs_test.go @@ -0,0 +1,42 @@ +package semantics + +import ( + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/semantic/resolve" + "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" +) + +// A conformance check against a named type compares the candidate and its +// memoized supertypes to the element registered under the name: no names are built. +func TestConformsByNameDoesNotBuildNames(t *testing.T) { + idx := symbols.NewIndex() + addTestDoc(t, idx, "d.sysml", `package T { + part def Base; + part def Mid :> Base; + part def Leaf :> Mid; + part def Other; + }`) + idx.ExpandWildcardImports() + r := resolve.New(idx) + m := NewModel(r) + r.SetModel(m) + leaf := idx.LookupQualified("T::Leaf")[0] + + cases := []struct { + fqn string + want bool + }{{"T::Base", true}, {"T::Mid", true}, {"T::Leaf", true}, {"T::Other", false}} + for _, c := range cases { + if got := m.conformsByName(leaf, c.fqn); got != c.want { + t.Fatalf("conformsByName(Leaf, %s) = %v, want %v", c.fqn, got, c.want) + } + if allocs := testing.AllocsPerRun(20, func() { m.conformsByName(leaf, c.fqn) }); allocs != 0 { + t.Errorf("a repeated conformsByName(Leaf, %s) allocates %.0f objects, want 0", c.fqn, allocs) + } + } + // A name nothing is registered under is compared by name, and conforms to nothing here. + if m.conformsByName(leaf, "T::Absent") { + t.Error("Leaf conforms to the unregistered T::Absent") + } +} diff --git a/internal/semantic/semantics/filter.go b/internal/semantic/semantics/filter.go index c2154cb3f8..5b428c0d83 100644 --- a/internal/semantic/semantics/filter.go +++ b/internal/semantic/semantics/filter.go @@ -985,6 +985,19 @@ func (m *Model) conformsByName(sym *symbols.Symbol, typeFQN string) bool { if sym == nil || typeFQN == "" { return false } + // The one element registered under the name is compared by identity; a + // name no or several elements carry falls back to the names themselves. + if target := m.symbolByFQN(typeFQN); target != nil { + if symbols.SameElement(sym, target) { + return true + } + for _, sup := range m.AllSupertypes(sym) { + if symbols.SameElement(sup, target) { + return true + } + } + return false + } if m.fqnOf(sym) == typeFQN { return true } From 55b542a1389f2c106aa44d2d4c20b9fc3f6ad9ae 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:49:39 +0000 Subject: [PATCH 2/2] perf(semantic): compare a named type by its owner chain, not the registered element Co-Authored-By: jason.han --- .../semantics/conforms_by_name_allocs_test.go | 10 +++------- internal/semantic/semantics/filter.go | 18 +++++++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/semantic/semantics/conforms_by_name_allocs_test.go b/internal/semantic/semantics/conforms_by_name_allocs_test.go index 124c4334fe..124750b9bd 100644 --- a/internal/semantic/semantics/conforms_by_name_allocs_test.go +++ b/internal/semantic/semantics/conforms_by_name_allocs_test.go @@ -7,8 +7,8 @@ import ( "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" ) -// A conformance check against a named type compares the candidate and its -// memoized supertypes to the element registered under the name: no names are built. +// A conformance check against a named type compares the candidate's and its +// memoized supertypes' owner chains to the name: no qualified name is built. func TestConformsByNameDoesNotBuildNames(t *testing.T) { idx := symbols.NewIndex() addTestDoc(t, idx, "d.sysml", `package T { @@ -26,7 +26,7 @@ func TestConformsByNameDoesNotBuildNames(t *testing.T) { cases := []struct { fqn string want bool - }{{"T::Base", true}, {"T::Mid", true}, {"T::Leaf", true}, {"T::Other", false}} + }{{"T::Base", true}, {"T::Mid", true}, {"T::Leaf", true}, {"T::Other", false}, {"T::Absent", false}, {"Base", false}} for _, c := range cases { if got := m.conformsByName(leaf, c.fqn); got != c.want { t.Fatalf("conformsByName(Leaf, %s) = %v, want %v", c.fqn, got, c.want) @@ -35,8 +35,4 @@ func TestConformsByNameDoesNotBuildNames(t *testing.T) { t.Errorf("a repeated conformsByName(Leaf, %s) allocates %.0f objects, want 0", c.fqn, allocs) } } - // A name nothing is registered under is compared by name, and conforms to nothing here. - if m.conformsByName(leaf, "T::Absent") { - t.Error("Leaf conforms to the unregistered T::Absent") - } } diff --git a/internal/semantic/semantics/filter.go b/internal/semantic/semantics/filter.go index 5b428c0d83..91cd3885bb 100644 --- a/internal/semantic/semantics/filter.go +++ b/internal/semantic/semantics/filter.go @@ -985,24 +985,28 @@ func (m *Model) conformsByName(sym *symbols.Symbol, typeFQN string) bool { if sym == nil || typeFQN == "" { return false } - // The one element registered under the name is compared by identity; a - // name no or several elements carry falls back to the names themselves. - if target := m.symbolByFQN(typeFQN); target != nil { - if symbols.SameElement(sym, target) { + if m.resolver == nil || m.resolver.Index() == nil { + if sym.Name == typeFQN { return true } for _, sup := range m.AllSupertypes(sym) { - if symbols.SameElement(sup, target) { + if sup.Name == typeFQN { return true } } return false } - if m.fqnOf(sym) == typeFQN { + // The names are compared segment by segment against the owner chain, so + // no qualified name is built for the candidate or any supertype. + parent, leaf := "", typeFQN + if i := strings.LastIndex(typeFQN, "::"); i >= 0 { + parent, leaf = typeFQN[:i], typeFQN[i+2:] + } + if symbols.FQNIs(sym, parent, leaf) { return true } for _, sup := range m.AllSupertypes(sym) { - if m.fqnOf(sup) == typeFQN { + if symbols.FQNIs(sup, parent, leaf) { return true } }