Skip to content

perf(resolve): match names first under recursive imports - #1192

Closed
devin-ai-integration[bot] wants to merge 9 commits into
developfrom
perf/name-first-recursive-import-matching
Closed

devin-ai-integration[bot] wants to merge 9 commits into
developfrom
perf/name-first-recursive-import-matching

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Since the hiding of colliding imported memberships (KerML 7.2.5.4) landed, a lookup of one name through import X::** enumerated every element below X and ran the per-element hiding checks (admitsUnderName, reexportedUnder, hiddenOnRoute, withoutHiddenMembers) on each of them before comparing names. On models built from recursive imports (the OMG Vehicle Example in examples/pilot-corpora/sysml-examples) name resolution became about sixty percent slower, and the tests/model race shard sits near its timeout.

The enumeration in internal/semantic/resolve/filter.go now takes a memberSelect:

  • named: a lookup lists, per visited namespace, only the members bound to the name — the scope's LookupLocalAll plus derived short names (shortNamedMembers), the child imports asked for the same name, and the index entries bound to it (declared name, short name, or a derived short name for an entry the index alone holds). The hiding, re-export and visibility checks run on those alone.
  • namespaces: the recursive descent lists only the members that can be namespaces (own members or imports, or index entries under their name; never a body-local scope) and checks only those, so a namespace that is hidden or not admitted still hides its subtree as before.
  • the zero value lists every member as today; ImportedElements, completion, the Duplicate of imported member name audit and the distinguishability warnings are unchanged.

Around it, none changing an answer:

  • derivedShortName memoizes a settled answer per symbol (journaled to the symbol's document frame), skipped while a naming chain under way cut it short, while the lookup was cut by an import cycle, and while a filter condition resolves unfiltered or an import all lifts the visibility boundary.
  • A scope's derived-short-name chains are resolved for a name only when the scope has derived-named members at all. Gating them further on Index.ShortNamed was tried and dropped: its segment read made a recorded document's analysis stale as soon as another document re-exported its names, so the record was hydrated (TestWorkspacePortLintReadsRecordedDefinitions).
  • Index.GetFQN caches the built FQN per symbol for the index generation (FQNOf was ~27% of all allocation: the string was rebuilt for every namespace a lookup visited).
  • elementList dedups by a linear scan until it holds sixteen keys, then indexes.

memberCache.fqn joins the lazily filled caches TestSnapshotIndexMatchesFreshLoad skips when comparing a decoded snapshot with a fresh load.

Specification basis

No behaviour change. KerML 7.2.5.4 / 8.3.2.4.3 hiding of indistinguishable imported memberships (declared and derived short names, KerML 8.2.3.3) is applied exactly as before, to the candidates instead of to the whole subtree; Resolver.InCondition is untouched. tests/resolve gains TestDerivedShortNameFromADeclarationUnderAnUnnamedOwner: a redefinition borrowing a short name declared under an anonymous part, which no index entry holds.

How it was verified

Measured on one machine (8 cores), go test -count=1 -run 'TestInterfaceRecordDifferentialCorpora/sysml-examples' ./tests/model, back to back (wall):

non-race -race
develop before the hiding change (0af9fcb36) 69.3 s 737.5 s
develop at 6b81872 (before #1155, #1158 and the day's merges) 99.5–108.8 s 670.7 s
this branch on that develop 82.2 s 635.3 s
develop at e00ce72 (with #1158) 110.6–117.3 s —
this branch merged with that develop 114.2–116.3 s —

On current develop the costs this change removes no longer show in the profile (subtree enumeration per lookup, derived-short-name chains: under 1 s), but the subtest's time is now dominated by Model.generalizationTarget (41 s of 160 s CPU, through collectContributors and generalsOf) and by the captured collision sets of #1158 (importedCollisions → capturing → collectImported, 28 s, mostly import-target resolution and metadataBodyOwner ending in generalization resolution), so the end-to-end number does not move there. The -race column was taken in one sequence on the earlier develop.

Passing: internal/semantic/..., internal/check/..., internal/workspace/..., internal/frontend/..., tests/resolve, tests/export, tests/migrate, tests/parser, make lint, and tests/model TestIncrementalEqualsFresh, TestSnapshotIndexMatchesFreshLoad, TestInterfaceRecordDifferential$, TestPackagesImportingEachOtherInACycleAnalysePromptly.

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/26443c7e5db24d5c99c23cd94846f6a0
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/26443c7e5db24d5c99c23cd94846f6a0?variant=devin
Requested by: @HuiJun

devin-ai-integration Bot and others added 2 commits October 16, 2026 04:48
A lookup of one name through import X::** listed every element below X and
ran the KerML 7.2.5.4 hiding checks on each. The enumeration now takes a
selector: by name it tries each namespace's own members and the index entries
bound to the name, descends only into members that may be namespaces, and
applies the visibility, re-export and hiding checks to those alone. Full
listings keep the selector empty and are unchanged.

Also: a settled per-symbol memo for derived short names, a per-generation
FQN cache on the index, and a map-free elementList for small lists.

Co-Authored-By: jason.han <[email protected]>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration[bot]

This comment was marked as resolved.

@HuiJun
HuiJun marked this pull request as ready for review October 16, 2026 05:03
devin-ai-integration Bot and others added 2 commits October 16, 2026 05:03
A derived short name (KerML 8.2.3.3) is the declared short name of the feature
a naming chain ends at, so a scope's chains are resolved for a name only when
some declaration the index knows has that short name (Index.ShortNamed, read as
a segment), and only when the scope has derived-named members at all.

The derived-short-name memo is neither read nor written while a filter
condition resolves unfiltered or an import all lifts the visibility boundary.

Co-Authored-By: jason.han <[email protected]>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 5 commits October 16, 2026 05:19
…ed ones

The index walks only named symbols' scopes, so a short name declared under an
unnamed owner or in a body-local scope is in no index entry; the derived-short-
name gate now also consults the set of short names declared anywhere in the
scope's document, collected once per root scope.

Co-Authored-By: jason.han <[email protected]>
Asking Index.ShortNamed before resolving a scope's naming chains recorded a
segment read, and a recorded document whose analysis took that read turned
stale as soon as another document re-exported its names, so it was hydrated
(TestWorkspacePortLintReadsRecordedDefinitions). The chains are resolved only
for scopes that have derived-named members, which needs no read of its own.

Co-Authored-By: jason.han <[email protected]>
namespaceChildren listed every index child of the target before narrowing
the list to the name asked for; the narrowed lookup is now the only one.

Co-Authored-By: jason.han <[email protected]>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Closing: on current develop the subtest this change targeted (TestInterfaceRecordDifferentialCorpora/sysml-examples) measures the same with and without it (develop 110.6–117.3 s, branch merged with develop 114.2–116.3 s); the costs it removed no longer show in the profile, which is now dominated by Model.generalizationTarget and the captured collision sets. The export-shard promptness failure seen here is fixed separately in #1198.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant