From 75bddb7fc6cc42fd2416229eea2db8818e135572 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 08:37:31 +0000 Subject: [PATCH 1/2] fix(semantics): derive a declaration's implied generals once per symbol Co-Authored-By: jason.han --- .../implicit-generals-memoized.fixed.md | 1 + internal/semantic/semantics/implicit.go | 19 +++++++++- internal/semantic/semantics/model.go | 3 ++ .../model/implicit_generals_memo_test.go | 38 +++++++++++++++++++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 changes/unreleased/implicit-generals-memoized.fixed.md create mode 100644 internal/workspace/model/implicit_generals_memo_test.go diff --git a/changes/unreleased/implicit-generals-memoized.fixed.md b/changes/unreleased/implicit-generals-memoized.fixed.md new file mode 100644 index 0000000000..113c23bf50 --- /dev/null +++ b/changes/unreleased/implicit-generals-memoized.fixed.md @@ -0,0 +1 @@ +- The generals a declaration's kind implies (the base usage it subsets, the definition it is typed by) are derived once per symbol and memoized, as its other supertype queries are; every type query of a feature re-derived them, which made model execution and the CLI about 40 % slower. diff --git a/internal/semantic/semantics/implicit.go b/internal/semantic/semantics/implicit.go index 161ffd7a3b..cbb0983f03 100644 --- a/internal/semantic/semantics/implicit.go +++ b/internal/semantic/semantics/implicit.go @@ -860,8 +860,23 @@ func (m *Model) ImplicitGenerals(sym *symbols.Symbol) []*symbols.Symbol { if sym.Recorded() { return m.recordedElements(sym, sym.Facts.ImpliedSupers) } - // The generals sym has otherwise are its direct supertypes less the base - // features its kind could imply, which DirectSupertypes appends last. + defer m.own(sym).LeaveDoc() + if cached, ok := m.implicitGenerals[sym]; ok { + return cached + } + m.resolver.Enter() + out := m.computeImplicitGenerals(sym) + if m.resolver.Leave() { + journal(m, m.implicitGenerals, sym, sym.Decl) + m.implicitGenerals[sym] = out + } + return out +} + +// computeImplicitGenerals derives ImplicitGenerals for a declared symbol: the +// generals sym has otherwise are its direct supertypes less the base features +// its kind could imply, which DirectSupertypes appends last. +func (m *Model) computeImplicitGenerals(sym *symbols.Symbol) []*symbols.Symbol { candidates := m.candidateBaseFeatures(sym) declared := slices.DeleteFunc(slices.Clone(m.DirectSupertypes(sym)), func(super *symbols.Symbol) bool { return slices.Contains(candidates, super) diff --git a/internal/semantic/semantics/model.go b/internal/semantic/semantics/model.go index bfdf8e7558..de55b7915d 100644 --- a/internal/semantic/semantics/model.go +++ b/internal/semantic/semantics/model.go @@ -62,6 +62,8 @@ type Model struct { referential map[*symbols.Symbol]bool // implicitBase memoizes each declaration's kind bases once settled (see implicit.go). implicitBase map[*symbols.Symbol][]*symbols.Symbol + // implicitGenerals memoizes ImplicitGenerals: the generals a declaration's kind implies, which every type query of a feature reads. + implicitGenerals map[*symbols.Symbol][]*symbols.Symbol // implicitSubsettings memoizes the owner feature each nested usage implicitly // subsets (see nested.go). implicitSubsettings map[*symbols.Symbol][]*symbols.Symbol @@ -178,6 +180,7 @@ func NewModel(resolver *resolve.Resolver) *Model { subtracting: make(map[*symbols.Symbol]bool), referential: make(map[*symbols.Symbol]bool), implicitBase: make(map[*symbols.Symbol][]*symbols.Symbol), + implicitGenerals: make(map[*symbols.Symbol][]*symbols.Symbol), implicitSubsettings: make(map[*symbols.Symbol][]*symbols.Symbol), computingUsageBase: make(map[*symbols.Symbol]bool), diff --git a/internal/workspace/model/implicit_generals_memo_test.go b/internal/workspace/model/implicit_generals_memo_test.go new file mode 100644 index 0000000000..edaabde0c6 --- /dev/null +++ b/internal/workspace/model/implicit_generals_memo_test.go @@ -0,0 +1,38 @@ +package model + +import ( + "testing" + + "github.com/Open-MBEE/OpenSysML/internal/semantic/resolve" + "github.com/Open-MBEE/OpenSysML/internal/semantic/semantics" +) + +// A feature's implied generals are read on every type query of it, so the model +// derives them once per symbol: a second query answers from the first's slice. +func TestImplicitGeneralsAreDerivedOncePerSymbol(t *testing.T) { + const uri = "file:///implicit-generals-memo.sysml" + ws := NewWorkspace() + ws.Open(uri, []byte("package P { part def Car; part car : Car; }"), 1) + defer ws.Close(uri) + + pkg, ok := ws.index.DocumentRoot(uri).LookupLocal("P") + if !ok { + t.Fatal("package P not found") + } + sym, ok := pkg.Scope.LookupLocal("car") + if !ok { + t.Fatal("usage car not found") + } + r := resolve.New(ws.index) + m := semantics.NewModel(r) + r.SetModel(m) + + first := m.ImplicitGenerals(sym) + if len(first) == 0 { + t.Fatal("part car implies no generals; want at least Parts::parts") + } + second := m.ImplicitGenerals(sym) + if len(second) != len(first) || &second[0] != &first[0] { + t.Fatalf("ImplicitGenerals derived the generals twice: %p then %p", &first[0], &second[0]) + } +} From 1ceae965cd07f8b9afb849da3b217d9018d9881f Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 08:48:31 +0000 Subject: [PATCH 2/2] fix(semantics): keep the derivation helper's comment to two lines Co-Authored-By: jason.han --- internal/semantic/semantics/implicit.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/semantic/semantics/implicit.go b/internal/semantic/semantics/implicit.go index cbb0983f03..5dd3c4fa40 100644 --- a/internal/semantic/semantics/implicit.go +++ b/internal/semantic/semantics/implicit.go @@ -873,9 +873,8 @@ func (m *Model) ImplicitGenerals(sym *symbols.Symbol) []*symbols.Symbol { return out } -// computeImplicitGenerals derives ImplicitGenerals for a declared symbol: the -// generals sym has otherwise are its direct supertypes less the base features -// its kind could imply, which DirectSupertypes appends last. +// computeImplicitGenerals derives ImplicitGenerals for a declared symbol; its other +// generals are its direct supertypes less the base features DirectSupertypes appends last. func (m *Model) computeImplicitGenerals(sym *symbols.Symbol) []*symbols.Symbol { candidates := m.candidateBaseFeatures(sym) declared := slices.DeleteFunc(slices.Clone(m.DirectSupertypes(sym)), func(super *symbols.Symbol) bool {