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
8 changes: 8 additions & 0 deletions changes/unreleased/conform-by-symbol.fixed.md
Original file line number Diff line number Diff line change
@@ -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.
38 changes: 38 additions & 0 deletions internal/semantic/semantics/conforms_by_name_allocs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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'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 {
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}, {"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)
}
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)
}
}
}
21 changes: 19 additions & 2 deletions internal/semantic/semantics/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,11 +985,28 @@ func (m *Model) conformsByName(sym *symbols.Symbol, typeFQN string) bool {
if sym == nil || typeFQN == "" {
return false
}
if m.fqnOf(sym) == typeFQN {
if m.resolver == nil || m.resolver.Index() == nil {
if sym.Name == typeFQN {
return true
}
for _, sup := range m.AllSupertypes(sym) {
if sup.Name == typeFQN {
return true
}
}
return false
}
// 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) {
Comment thread
HuiJun marked this conversation as resolved.
Comment thread
HuiJun marked this conversation as resolved.
return true
}
}
Expand Down
Loading