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
1 change: 1 addition & 0 deletions changes/unreleased/implicit-generals-memoized.fixed.md
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 16 additions & 2 deletions internal/semantic/semantics/implicit.go
Original file line number Diff line number Diff line change
Expand Up @@ -860,8 +860,22 @@ 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; 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 {
return slices.Contains(candidates, super)
Expand Down
3 changes: 3 additions & 0 deletions internal/semantic/semantics/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),

Expand Down
38 changes: 38 additions & 0 deletions internal/workspace/model/implicit_generals_memo_test.go
Original file line number Diff line number Diff line change
@@ -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])
}
}
Loading