Skip to content

perf(rust): return impl ExactSizeIterator from slice-backed accessors#24144

Merged
graphite-app[bot] merged 1 commit into
mainfrom
perf/exact-size-iterator-accessors
Jul 4, 2026
Merged

perf(rust): return impl ExactSizeIterator from slice-backed accessors#24144
graphite-app[bot] merged 1 commit into
mainfrom
perf/exact-size-iterator-accessors

Conversation

@Boshen

@Boshen Boshen commented Jul 4, 2026

Copy link
Copy Markdown
Member

These slice/Vec/IndexVec-backed accessors are already ExactSizeIterator internally (slice::iter().map(..) / .copied() / .rev(), iter_enumerated(), HashMap::values(), (0..len).map(..)) — the impl Iterator return type just erased it.

Widening to impl ExactSizeIterator lets callers pre-size collections from the known length:

let mut v = Vec::with_capacity(scoping.symbol_ids().len());
v.extend(scoping.symbol_ids().filter_map(..));

instead of growing a Vec through reallocs, and lets callers call .len() directly.

Backward-compatible: ExactSizeIterator: Iterator, so existing callers are unaffected.

Two ordering details make it compile:

  • The multi_index_vec! iter_ids ((0..len).map(..)) is widened first so the bound propagates to SymbolTable/ScopeTable, which is what lets symbol_ids/scope_descendants_from_root widen.
  • The delegating TraverseCtx::ancestors is widened after its TraverseAncestry::ancestors source — an opaque impl Iterator only re-exposes ExactSizeIterator if the inner signature declares it.

Widened accessors:

  • oxc_semantic: Scoping::{symbol_names, resolved_references, symbol_ids, scope_descendants_from_root, iter_bindings, iter_bindings_in}, AstNodes::{iter, iter_enumerated}, ClassTable::{iter_enumerated, iter_private_identifiers}, multi_index_vec! iter_ids
  • oxc_traverse / oxc_minifier: TraverseAncestry::ancestors, TraverseCtx::ancestors
  • oxc_formatter_core: SourceText::{bytes_from, bytes_to}
  • oxc_codegen: Codegen::current_class_ids

@github-actions github-actions Bot added A-semantic Area - Semantic A-minifier Area - Minifier A-codegen Area - Code Generation labels Jul 4, 2026
@Boshen Boshen changed the title perf: return impl ExactSizeIterator from slice-backed accessors perf(rust): return impl ExactSizeIterator from slice-backed accessors Jul 4, 2026
@Boshen Boshen added the 0-merge Merge with Graphite Merge Queue label Jul 4, 2026

Boshen commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

Merge activity

graphite-app Bot pushed a commit that referenced this pull request Jul 4, 2026
…#24144)

These slice/`Vec`/`IndexVec`-backed accessors are already `ExactSizeIterator` internally (`slice::iter().map(..)` / `.copied()` / `.rev()`, `iter_enumerated()`, `HashMap::values()`, `(0..len).map(..)`) — the `impl Iterator` return type just erased it.

Widening to `impl ExactSizeIterator` lets callers pre-size collections from the known length:

```rust
let mut v = Vec::with_capacity(scoping.symbol_ids().len());
v.extend(scoping.symbol_ids().filter_map(..));
```

instead of growing a `Vec` through reallocs, and lets callers call `.len()` directly.

Backward-compatible: `ExactSizeIterator: Iterator`, so existing callers are unaffected.

Two ordering details make it compile:
- The `multi_index_vec!` `iter_ids` (`(0..len).map(..)`) is widened first so the bound propagates to `SymbolTable`/`ScopeTable`, which is what lets `symbol_ids`/`scope_descendants_from_root` widen.
- The delegating `TraverseCtx::ancestors` is widened after its `TraverseAncestry::ancestors` source — an opaque `impl Iterator` only re-exposes `ExactSizeIterator` if the inner signature declares it.

Widened accessors:
- **oxc_semantic**: `Scoping::{symbol_names, resolved_references, symbol_ids, scope_descendants_from_root, iter_bindings, iter_bindings_in}`, `AstNodes::{iter, iter_enumerated}`, `ClassTable::{iter_enumerated, iter_private_identifiers}`, `multi_index_vec!` `iter_ids`
- **oxc_traverse** / **oxc_minifier**: `TraverseAncestry::ancestors`, `TraverseCtx::ancestors`
- **oxc_formatter_core**: `SourceText::{bytes_from, bytes_to}`
- **oxc_codegen**: `Codegen::current_class_ids`
@graphite-app
graphite-app Bot force-pushed the perf/exact-size-iterator-accessors branch from 2d2a9f0 to 2a6564b Compare July 4, 2026 02:44
@codspeed-hq

codspeed-hq Bot commented Jul 4, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 62 untouched benchmarks
⏩ 9 skipped benchmarks1


Comparing perf/exact-size-iterator-accessors (2d2a9f0) with main (b82c394)

Open in CodSpeed

Footnotes

  1. 9 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

…#24144)

These slice/`Vec`/`IndexVec`-backed accessors are already `ExactSizeIterator` internally (`slice::iter().map(..)` / `.copied()` / `.rev()`, `iter_enumerated()`, `HashMap::values()`, `(0..len).map(..)`) — the `impl Iterator` return type just erased it.

Widening to `impl ExactSizeIterator` lets callers pre-size collections from the known length:

```rust
let mut v = Vec::with_capacity(scoping.symbol_ids().len());
v.extend(scoping.symbol_ids().filter_map(..));
```

instead of growing a `Vec` through reallocs, and lets callers call `.len()` directly.

Backward-compatible: `ExactSizeIterator: Iterator`, so existing callers are unaffected.

Two ordering details make it compile:
- The `multi_index_vec!` `iter_ids` (`(0..len).map(..)`) is widened first so the bound propagates to `SymbolTable`/`ScopeTable`, which is what lets `symbol_ids`/`scope_descendants_from_root` widen.
- The delegating `TraverseCtx::ancestors` is widened after its `TraverseAncestry::ancestors` source — an opaque `impl Iterator` only re-exposes `ExactSizeIterator` if the inner signature declares it.

Widened accessors:
- **oxc_semantic**: `Scoping::{symbol_names, resolved_references, symbol_ids, scope_descendants_from_root, iter_bindings, iter_bindings_in}`, `AstNodes::{iter, iter_enumerated}`, `ClassTable::{iter_enumerated, iter_private_identifiers}`, `multi_index_vec!` `iter_ids`
- **oxc_traverse** / **oxc_minifier**: `TraverseAncestry::ancestors`, `TraverseCtx::ancestors`
- **oxc_formatter_core**: `SourceText::{bytes_from, bytes_to}`
- **oxc_codegen**: `Codegen::current_class_ids`
@graphite-app
graphite-app Bot force-pushed the perf/exact-size-iterator-accessors branch from 2a6564b to c59f2fe Compare July 4, 2026 02:46
@graphite-app
graphite-app Bot merged commit c59f2fe into main Jul 4, 2026
30 checks passed
@graphite-app graphite-app Bot removed the 0-merge Merge with Graphite Merge Queue label Jul 4, 2026
@graphite-app
graphite-app Bot deleted the perf/exact-size-iterator-accessors branch July 4, 2026 02:50
graphite-app Bot pushed a commit to rolldown/rolldown that referenced this pull request Jul 4, 2026
These accessors are already `ExactSizeIterator` internally — the `impl Iterator` return type just erased it. Widening to `impl ExactSizeIterator` lets callers pre-size collections from the known length (`Vec::with_capacity(iter.len())`) instead of growing through reallocs, and lets them call `.len()` directly. Backward-compatible: `ExactSizeIterator: Iterator`, so existing callers are unaffected.

| accessor | backing |
|---|---|
| `ChunkGraph::sorted_chunks` | `Vec<ChunkIdx>::iter().map(..)` |
| `PluginDriver::iter_plugin_with_context_by_order` | `&[PluginIdx]::iter().copied().map(..)` |
| `StmtInfos::iter_enumerated_without_namespace_stmt` | `IndexVec::iter_enumerated().skip(1)` (`Skip` preserves the exact len) |
| `HybridIndexVec::iter` | `Either<IndexVec::iter(), FxHashMap::values()>` (both arms `ExactSizeIterator`) |
| `ScanMode::iter` | `Either<iter::empty(), slice::iter()>` (both arms `ExactSizeIterator`) |
| `DependedRuntimeHelperMap::iter` | `[Vec<_>; N]::iter().enumerate().map(..)` |

The rest of the crate's `impl Iterator` accessors are correctly left alone — they use `filter_map` / `flat_map` / custom iterators that erase the length.

Same idea as oxc-project/oxc#24144.
Boshen added a commit that referenced this pull request Jul 6, 2026
# Oxlint
### 🚀 Features

- 7db7a29 allocator: Add `ReplaceWith` trait (#24012) (overlookmotel)
- a2c97f3 linter/unicorn: Implement `explicit-timer-delay` rule (#23612)
(Mikhail Baev)
- 85735cb linter/unicorn: Implement `no-confusing-array-with` rule
(#23638) (Shekhu☺️)
- cb4fbb9 linter/eslint: Implement no-unreachable-loop rule (#23975)
(Todor Andonov)
- dc32112 linter/eslint/no-constant-binary-expression: Check relational
comparisons (#24088) (camc314)
- 439c344 linter/jsdoc: Added missing options to `jsdoc/require-param`
rule (#23364) (kapobajza)
- 62af717 linter/unicorn/filename-case: Add `lowercase` and
`screamingSnakeCase` (#24045) (Boshen)
- d963967 linter/unicorn/no-array-sort: Add `allowAfterSpread` option
(#24043) (Boshen)
- 0a75682 linter: Add per-rule timings for type-aware linting (#22488)
(camchenry)
- 743e222 linter/react: Add `disallowedValues` option for
`forbid-dom-props` rule (#23970) (Mikhail Baev)

### 🐛 Bug Fixes

- 7b80010 linter: Use direct binding symbol ids (#24216) (camc314)
- 8f94b49 linter/import/no-duplicates: Don't flag a type-only import
beside a side-effect import (#24030) (Boshen)
- d8c3fee linter/react/rules-of-hooks: Flag `useEffectEvent` escapes
(#23764) (Rayan Salhab)
- 0a7312b linter/no-deprecated-functions: Map `require.requireActual` to
`jest.requireActual` (#23627) (Jerry Zhao)
- d9e3ab3 linter/eslint/no-useless-return: Handle switch case
continuation (#23984) (camc314)
- 0b25582 ast: Type binding node `typeAnnotation` as `TSTypeAnnotation |
null` (#23113) (Boshen)
- 122d112 linter/eslint/no-restricted-imports: Flag dynamic import()
expressions (#24029) (Boshen)
- 59b6b83 linter: Avoid `OnceLock` re-entry on cyclic `export *`
re-exports (#23632) (Jerry Zhao)
- dd09af0 linter/import/namespace: Avoid panic on destructuring of an
unresolvable namespace re-export (#23626) (Jerry Zhao)
- bdb51c7 linter/jest/prefer-ending-with-an-expect: Validate config
patterns (#24122) (camc314)
- e383843 linter/unicorn/prefer-modern-dom-apis: Skip fixer for non
identifier arguments (#23630) (Jerry Zhao)
- 0ac4c83 linter: Detect circular config extends (#24115) (camc314)
- bae1edf linter/import/namespace: Check namespace imports after named
imports (#24094) (camc314)
- cd8fdfe linter/eslint/no-eval: Recognize Array.from family thisArg
(#24091) (camc314)
- 851ee43 linter/eslint/no-eval: Resolve this binding for functions
returned from an IIFE (#23643) (Jerry Zhao)
- 002ab35 linter/unicorn: Avoid prefer-array-find rest destructuring
false positive (#23654) (ColemanDunn)
- 01c8775 linter/unicorn/filename-case: Keep digits attached in
screamingSnakeCase (#24056) (Boshen)
- f256941 linter: Recognize `@effect/vitest` as a vitest import source
(#24025) (Boshen)
- 73eeb1d linter/import/extensions: Honor per-extension `never` for
explicit extensions (#24031) (Boshen)
- d4ebe1f linter: Reject non-object oxlint config files (#24026)
(Boshen)
- 45d607d linter/react/forbid-component-props: Make allow/disallow lists
optional in schema (#24024) (Boshen)
- 54076ad linter/unicorn/no-array-for-each: Suggest entries loop for
index callbacks (#24004) (camc314)
- d057736 linter/jsdoc: Avoid param root underflow (#23945) (camc314)
- 29c76bf linter/unicorn/prefer-at: Skip object numeric-key access
(#23909) (Gaurav Dubey)

### ⚡ Performance

- 657a8fc linter/oxc/bad-array-method-on-arguments: Only run on member
expressions instead of all identifiers (#24164) (camchenry)
- 073d9e7 linter/eslint/prefer-rest-params: Run on functions instead of
all identifiers (#24163) (camchenry)
- e5a4162 linter/jest/no-confusing-set-timeout: Early exit fast path
(#24092) (camc314)
- bca7ce5 linter: Only run react-perf rules on JSX attribute nodes
(#24083) (camchenry)
- 6881bf6 linter: Compute `apply_overrides` rule set lazily (#23648)
(Jerry Zhao)
- 911c106 linter/eslint/no-obj-calls: Use resolved reference instead of
scope walk (#23895) (Marius Schulz)
- dc8fd9a linter/unicorn/prefer-dom-node-text-content: Change dispatch
to run only on less common node types (#23897) (Connor Shea)
- fdbd34d linter/eslint/no-useless-call: Fast-path static callees
(#24077) (camc314)
- b1be114 linter/import/extensions: Skip empty config and borrow
extensions (#24075) (camc314)
- 4781b2d linter/eslint/no-obj-calls: Use direct global matches (#24076)
(camc314)
- e6cee89 linter: Avoid node-chain allocation for non-Jest calls
(#23907) (Yagiz Nizipli)
- 30dc517 linter/typescript/no-restricted-types: O(1) banned-type
lookups (#23827) (Yagiz Nizipli)

### 📚 Documentation

- 6ca9125 linter/typescript: Clarify consistent-type-imports behavior
(#23972) (camc314)
# Oxfmt
### 🚀 Features

- 4f4313e formatter_css: Update oxc-css-parser 0.0.5 (#24120) (leaysgur)
- 0ccd8a1 formatter_graphql: Update oxc-graphql-parser 0.0.5 (#24106)
(leaysgur)
- 89ec3d9 formatter_core: Add literal line and root indention primitives
(#24051) (leaysgur)
- 213a96b formatter_core: Add no-expand-parent for multiline text
(#24050) (leaysgur)
- 0e5bcc9 formatter_graphql: Update oxc-graphql-parser 0.0.4 (#24039)
(leaysgur)
- e0b35a1 formatter_css: Update `[email protected]` (#23974)
(leaysgur)

### 🐛 Bug Fixes

- 1fe6546 formatter: Omit unneeded `;` for type members with `no-semi`
(#24212) (leaysgur)
- 0ad7316 formatter: Print space for `ForStatement`.`update` only if
exists (#24211) (leaysgur)
- 3abbed5 formatter: Print `;` before jsdoc type-cast parens with
no-semi (#24208) (leaysgur)
- 9af3833 formatter_css: Make scss formatter consistent (#24207)
(leaysgur)
- 46d7194 formatter_css: Use fill IR for `@forward` members (#24206)
(leaysgur)
- e31038f formatter_css: Keep comment inside sass config list (#24205)
(leaysgur)
- d3b9591 formatter: Add parens around `await/yield` with `<T>` (#24202)
(leaysgur)
- 2121a55 oxfmt: Reuse tinypool process during the same LSP process
(#24197) (leaysgur)
- 9bf4b4a formatter_css: Align CSS output to Prettier 3.9.1 (#24100)
(leaysgur)
- cd2452e formatter_css: Align SCSS output to Prettier 3.9.1 (#24097)
(leaysgur)
- 4ee8745 formatter_css: Keep selector value contain line-break without
breaking line (#24055) (leaysgur)
- e1ece97 formatter_graphql: Break `implements` list by print-width
(#23997) (leaysgur)
- 0a6b16c formatter_json: Preserve key and literal value for
json-stringify (#23996) (leaysgur)
- 903ab6e formatter_css: Preserve newlines in css-in-js selector list
(#23992) (leaysgur)
- ea5d095 oxfmt: Update `--migrate prettier` (#23963) (leaysgur)

### ⚡ Performance

- 468e1e3 formatter_core: Make printer queues cursor-based (#24098)
(Boshen)
- c59f2fe rust: Return impl ExactSizeIterator from slice-backed
accessors (#24144) (Boshen)
- c292fb2 formatter: Inline fits element dispatcher (#23982) (camc314)

Co-authored-by: Boshen <[email protected]>
Boshen added a commit that referenced this pull request Jul 6, 2026
### 🚀 Features

- 260425f semantic/examples: Include unresolved references (#24214)
(camc314)
- 2d9b0b3 minifier: Fold boolean-literal ternary branches in value
contexts (#24110) (Dunqing)
- 61fbf10 ast: Implement `ReplaceWith` on all AST types (#24013)
(overlookmotel)
- 7db7a29 allocator: Add `ReplaceWith` trait (#24012) (overlookmotel)
- 4eb074e mangler: Add `reserved` option for names that must not be
mangled (#24041) (Dunqing)
- 2e62012 data_structures: Add `StringExt` trait (#24006)
(overlookmotel)
- 60e7160 minifier: Drop side-effect-free IIFEs whose result is unused
(#23967) (Dunqing)
- 26dd9e2 ast: Add method to widen inherited enum ref to parent ref
(#23961) (overlookmotel)

### 🐛 Bug Fixes

- e8b50ee transformer: Clean up semantics for stripped TypeScript syntax
(#24180) (camc314)
- d966d0b react_compiler: Remove clippy allows (#24168) (Boshen)
- 854ef8d react_compiler: Compile generic functions instead of
over-bailing on type-param hoisting (#24158) (Boshen)
- 093586c react_compiler: Align memoization cache-slot allocation with
Babel (#24157) (Boshen)
- 09c8f59 react_compiler: Normalize snapshot fixture paths (#24142)
(camc314)
- f13df97 react_compiler: Drop stray empty statement from catch bindings
(#24133) (Boshen)
- cb2a505 react_compiler: Codegen destructuring reassignment targets
(#24131) (Boshen)
- b82c394 react_compiler: Propagate codegen invariants instead of
emitting empty bodies (#24128) (Boshen)
- 5771982 react_compiler: Render unchanged programs as source in fixture
snapshots (#24129) (Boshen)
- 4b16e1a transformer/async-to-generator: Preserve direct eval scope
flags (#24136) (camc314)
- 4e9194f react_compiler: Lower `delete obj.prop` to
Property/ComputedDelete (#24123) (Boshen)
- 0b25582 ast: Type binding node `typeAnnotation` as `TSTypeAnnotation |
null` (#23113) (Boshen)
- 018c0e5 transformer: Hoist lowered async declarations (#22770)
(camc314)
- 652fbaf mangler: Keep names of destructured exported bindings (#24036)
(Dunqing)
- e274415 minifier: Don't drop global calls that throw despite pure
arguments (#23917) (Dunqing)
- 59abb30 minifier: Only merge string literals in `try_fold_add` when
the inner operator is `+` (#23622) (Jerry Zhao)

### ⚡ Performance

- c5ca77b transformer: Avoid cloning refresh options (#24191) (camc314)
- bf1a151 react_compiler: Compile out debug printers (#24184) (Boshen)
- abb44a0 transformer: Build fixed object-rest arguments (#24190)
(camc314)
- a4db731 isolated_declarations: Use `ReplaceWith` instead of `TakeIn`
(#24016) (overlookmotel)
- ff10855 transformer: Use `ReplaceWith` instead of `TakeIn` (#24015)
(overlookmotel)
- bd49aff ecmascript: Avoid heap-allocating Math.min/max/imul operands
(#23941) (Lawrence Lin)
- e4b708b react_compiler: Skip compiled files before prefilters (#24171)
(Boshen)
- c59f2fe rust: Return impl ExactSizeIterator from slice-backed
accessors (#24144) (Boshen)
- 5d6d04a codegen: SWAR-skip boring byte runs in sourcemap line/column
scan (#24023) (Boshen)
- a55e0be traverse: Reduce string operations in `get_var_name_from_node`
(#24007) (overlookmotel)
- e6d48e1 transformer/nullish_coalescing: Move cold path into separate
function (#23989) (overlookmotel)
- c4e35b5 transformer/object_rest_spread: Pre-allocate capacity in `Vec`
(#23988) (overlookmotel)
- 527b8e5 transformer/decorators: Narrow type earlier (#23987)
(overlookmotel)

### 📚 Documentation

- 30d17f5 allocator: Clarify docs for `TakeIn::take_in_box` (#24093)
(overlookmotel)
- 675e6a8 ast: Correct doc comment for `PrivateFieldExpression` (#24008)
(overlookmotel)
- e4c30e6 minifier: Explain what `dce` mode means (#23994) (Dunqing)
- 37cbf88 ast_macros: Document fields of `StructDetails` (#23959)
(overlookmotel)
- 4de3e54 ast: Correct doc comment (#23948) (overlookmotel)

Co-authored-by: Boshen <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-codegen Area - Code Generation A-minifier Area - Minifier A-semantic Area - Semantic

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant