This PR contains the following updates:
| Package | Type | Update | Change |
|---|---|---|---|
| [rust](https://rust-lang.org/)
([source](https://redirect.github.com/rust-lang/rust),
[changelog](https://redirect.github.com/rust-lang/rust/blob/main/RELEASES.md))
| toolchain | minor | `1.97.1` → `1.98.0` |
---
### Configuration
📅 **Schedule**: (in timezone Asia/Shanghai)
- Branch creation
- "before 10am on monday"
- Automerge
- At any time (no schedule defined)
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/oxc-project/oxc).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zNS40IiwidXBkYXRlZEluVmVyIjoiNDQuMzUuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Cameron <cameron.clark@hey.com>
Introduce `exact_line_breaks(count)` IR builder.
This produces a line element that prints exactly `count` line breaks, exempt from the printer's hardline collapsing / empty-line capping.
Blank runs inside verbatim values (YAML block/flow scalars, GraphQL block strings) are the value itself and must not be normalized.
Previously this was emulated by emitting raw `\n` text followed by a re-arming `hard_line_break()`..., hack.
Also removed the printer behavior that trims end of line whitespaces.
Use `implements!` macro (introduced in #24721) in various tests, including adding some new ones.
The most valuable change is replacing a `compile_fail` doctest which checks that `Arena` is not `Sync`. Using `assert!(implements!(Arena: !Sync));` is more robust.
Add an `implements!` macro which answers "does type X implement trait Y?".
Primarily this is useful in tests, but can also be used in macros to produce specialized code paths.
```rust
assert!(implements!(Ident: Send));
assert!(implements!(Ident: Sync));
assert!(implements!(Allocator: !Sync));
```
Also works in const context:
```rust
const U32_IS_COPY: bool = implements!(u32: Copy);
const _: () = assert!(U32_IS_COPY);
```
Particularly useful for checking that a type does _not_ implement a trait. Without this macro, that requires a `compile_fail` doctest, which is fragile - when code changes, the doctest can easily start failing for _different_ reasons, which means it's no longer testing what it's meant to, and there's no way to spot that.
This is a primitive required for forthcoming `MultiVec` SoA vec type. Putting it in `oxc_data_structures` as it can be useful in other places too.
Add a `const_str_eq` function to `oxc_data_structures`, which allows comparing two strings in const context.
```rust
const IS_SAME: bool = const_str_eq("foo", "bar");
const _: () = assert!(!IS_SAME);
```
This is a primitive required for forthcoming `MultiVec` SoA vec type. Putting it in `oxc_data_structures` as it can be useful in other places too.
Introduce a `StringExt` trait which adds 2 unsafe methods to `String`:
- `push_unchecked`
- `push_str_unchecked`
Both push to the string without checking there's sufficient capacity for it. The caller guarantees the push cannot cause the `String`'s length to exceed its capacity.
Both methods call the corresponding safe methods of `String` internally, with `assert_unchecked!`s preceding those calls to allow compiler to remove the bounds check. The shape of these assertions matches the code in `String`'s methods exactly.
Examination of generated assembly confirms that the optimizations work.
Docs-only change. Document why `NonNullConst` and `NonNullMut`'s `as_ref` / `as_mut` methods take `self`, not `&self` / `&mut self` as `NonNull`'s methods do. See the comment for the rationale.
Most of our low-level data structures e.g. `Arena`, `Vec`, `Box`, and `Stack` types use `NonNull` pointers.
`NonNull` is fundamentally dangerous. A `NonNull` can be created from a `&T`, and so only has read permissions. Yet you can call `as_mut()` on such a pointer to gain a `&mut T`, and that's instant undefined behavior. The onus is on the author to manually track how pointers were created and so whether they have write permissions or not, to avoid this hazard.
In complex code like the arena allocator, pointers are created in various different places, stored in structs, and passed around, so it is not trivial to track their permissions, and neither the type system nor Clippy gives you any help.
In comparison, raw pointers `*const T` and `*mut T` *do* make such a distinction between read-only and write-enabled pointers, but they have other shortcomings - they lack the non-null guarantee of `NonNull` (which can improve perf in some cases), and `*mut T` is not covariant, so it is unsuitable for use in types like `Vec`.
This PR intoduces 2 new pointer types `NonNullConst` and `NonNullMut`. Both are just wrappers around `NonNull` and are equivalent to `NonNull` at runtime. What they add is type system-level guarantees of the read/write permissions that these pointers have. i.e. they bring together the const/mut distinction of raw `*const` / `*mut` pointers, and the advantages of `NonNull`.
This PR only introduces these new pointer types, but does not use them in the codebase yet. It is my intention to migrate `Arena` allocator over to these new pointer types, to make it harder to blunder into causing UB while reworking the implementation. Other types like `Vec`, `Box`, `Stack`, and lexer's `Source` could also be migrated over later on.
Follow on after #18172.
That PR made changes to comments to remove references to `bumpalo` from the codebase. Correct a few of those comments which were incorrect.
Add a `fieldless_enum!` macro. This macro generates a `VARIANTS` constant on the type that lists all the enum's variants.
```rs
fieldless_enum! {
enum Foo {
A,
B,
C,
}
}
assert_eq!(Foo::VARIANTS, [Foo::A, Foo::B, Foo::C]);
```
This is useful for code which interacts with the enum, and needs to take some action on each variant.
Add a method `CodeBuffer::print_strs_array`. This works much like `StringBuilder::from_strs_array_in` and `Atom::from_strs_array_in`. It takes an array of `&str`s, computes the total length required to print them all, reserves space for the total number of bytes for all together, and then writes all the strings one after another.
The result is that it's much faster than a series of `print_str` calls, which incur the cost of a bounds check and branch for each string. Instead you only do 1 bounds check for the whole batch.
Like `StringBuilder::from_strs_array_in` there have to be additional checks when computing the total length to ensure the sum of lengths does not exceed `usize::MAX` and wrap around. But when some of the strings are statically known (e.g. `buffer.print_strs_array(["foo", dynamic, "bar"])`) and so it's impossible for overflow to occur, these checks can usually be removed by compiler.
This doesn't seem to be an issue with native js/ts format paths, but it appears to cause problems when converting Prettier Docs, such as with gql-in-js.
Without this change,
```gql
type Mutation {
create__TYPE_NAME__(input: Create__TYPE_NAME__Input!): __TYPE_NAME__!
@skipAuth
```
will be formatted:
```gql
type Mutation {
create__TYPE_NAME__(input: Create__TYPE_NAME__Input!): __TYPE_NAME__!
@skipAuth
```
See trailing ` ` after `__TYPE_NAME__!`.
> This PR adds a new function get_offset_from_line_and_column that performs the inverse operation of the existing get_line_column function, converting UTF-16 line and column coordinates to a UTF-8 byte offset in source text.
## Summary
Now that bumpalo has been inlined into oxc_allocator (#18168), this PR cleans up comments and documentation that still reference bumpalo as an external dependency.
- Update module docs in `bump.rs` and `bumpalo_alloc.rs` to say "originally derived from" instead of "ported from"
- Remove obsolete comments about bumpalo version pinning in `from_raw_parts.rs`
- Update inline comments in `allocator.rs` from "delegates to bumpalo" to "it's a small function"
- Remove outdated TODO about replacing bumpalo in `tracking.rs`
- Update `ARCHITECTURE.md` and parser docs to reference `oxc_allocator` instead of bumpalo
- Clean up bumpalo references in other crates (`oxc_semantic`, `oxc_data_structures`, `apps/oxlint`, `napi/parser`, `tasks/ast_tools`)
## Test plan
- [x] `cargo check -p oxc_allocator` passes
- [x] `cargo test -p oxc_allocator` passes
- [x] Changes are documentation/comment only - no functional changes
🤖 Generated with [Claude Code](https://claude.ai/code)
Implement methods and traits for converting stacks to/from `Vec`s, and from iterators:
* `Stack::into_vec`
* `From<Stack> for Vec`
* `From<Vec> for Stack`
* `NonEmptyStack::into_vec`
* `From<NonEmptyStack> for Vec`
* `TryFrom<Vec> for NonEmptyStack`
* `FromIterator for Stack` (infallible)
* `FromIterator for NonEmptyStack` (fallible)
Implement `Send` and `Sync` for `Stack<T>` and `NonEmptyStack<T>` where `T` is `Send` / `Sync`.
This also makes `SparseStack<T>` implement `Send` / `Sync` with same bounds.
`Vec` also implements `Send` / `Sync` in the same way, and the stack types are all essentially the same as `Vec`, just with a different internal implementations, so it makes sense to follow `Vec`.
Data structures which own `T`s should contain a `PhantomData<T>` field to signal to compiler the ownership semantics. For example `std::vec::Vec` does this.
Add `PhantomData` field to `Stack` and `NonEmptyStack`.
It's unclear to me if this is actually required for soundness or not. In the case of `std::vec::Vec`, it definitely is, but `Vec` uses `#[may_dangle]` in its `Drop` impl. Ideally we would use `#[may_dangle]` in these `Stack` types too, to loosen the borrow-checker's restrictions, but we're not able to as it's not available in stable Rust.
Given that we're not using `#[may_dangle]`, maybe `PhantomData` is not required either, but it costs nothing, so it's better to be on safe side and add it.
Pure refactor. `std::mem::size_of` and `std::mem::align_of` are now part of the standard prelude. Shorten code by using them from the prelude, instead of importing from `std::mem`.
Add `advance_to_end` method to `SliceIter` trait. This allows calling `advance_to_end` on any slice iterator.
(this stack of 3 PRs is work I did as part of first attempt to build an ESTree walker, which I abandoned, but the APIs are useful for other tasks in `oxc_ast_tools`).
Coming soon: The Renovate bot (GitHub App) will be renamed to Mend. PRs from Renovate will soon appear from 'Mend'. Learn more [here](https://redirect.github.com/renovatebot/renovate/discussions/37842).
This PR contains the following updates:
| Package | Update | Change |
|---|---|---|
| [rust](https://redirect.github.com/rust-lang/rust) | minor | `1.89.0` -> `1.90.0` |
---
### Release Notes
<details>
<summary>rust-lang/rust (rust)</summary>
### [`v1.90.0`](https://redirect.github.com/rust-lang/rust/compare/1.89.0...1.90.0)
[Compare Source](https://redirect.github.com/rust-lang/rust/compare/1.89.0...1.90.0)
</details>
---
### Configuration
📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box
---
This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/oxc-project/oxc).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny4xMCIsInVwZGF0ZWRJblZlciI6IjQxLjk3LjEwIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->
Continuation from #13672.
Calling `NonEmptyStack::is_empty` is always a logic error. `NonEmptyStack` can never be empty, and `NonEmptyStack::is_exhausted` is very likely what was intended.
Make calling `NonEmptyStack::is_empty` a compile-time error, to prevent this erroneous usage.
Ideally, we'd remove the method entirely, but `&NonEmptyStack<T>` derefs to `&[T]`, so without defining `is_empty` method on `NonEmptyStack`, `stack.is_empty()` falls through to `[T]::is_empty`, which is not what we want.
When using stacks, typically you create a stack at start of AST traversal, and then `debug_assert!` that the stack has been emptied again at the end of traversal, to make sure that the stack has not got out of sync - that every `push` is followed by a corresponding `pop`.
With `NonEmptyStack` and `SparseStack` it's a little confusing how to do this, because these stacks can never be empty. `stack.is_empty()` is not correct. `stack.len() == 1` is correct, but it's unclear when reading that code what exactly this is testing.
Add `is_exhausted` methods to `NonEmptyStack` and `SparseStack` to fulfil this role.
Naming: `is_exhausted` is perhaps not the best name, but I couldn't come up with a better one. I considered `is_emptied`, which is more descriptive, but I think it's too close to `is_empty`.
(prompted by #13632)
Add `boxed_slice!` and `boxed_array!` macros, similar to std lib's `vec!` macro.
```rs
// Box<[u64]>
let box_slice = boxed_slice![0u64; 10];
// Box<[u64; 10]>
let box_array = boxed_array![0u64; 10];
```
Use `boxed_array!` in linter to shorten code.
These macros could probably be made more efficient by using `MaybeUninit` and emplacement, but can leave that until later.
Pure refactor. Rename:
* `SliceIterExt` -> `SliceIter`
* `SliceIterMutExt` -> `SliceIterMut`
* `slice_iter_ext` module -> `slice_iter`
* `slice_iter_ext` Cargo feature -> `slice_iter`
I don't think "ext" added any value. There is no `SliceIter` trait in std library, so no naming clash.
Use `as_mut_slice` method (introduced in #13437) to optimize `advance_unchecked` method on `slice::IterMut`. Previously it had a branch, now it's only 1 instruction.
Add `ptr` and `end_ptr` methods to `SliceIterExt`. These methods get pointers to the start and end of slice iterators, using the minimum possible number of instructions (1).
`end_ptr` also avoids unsafe code, unlike `iter.as_slice().as_ptr().add(iter.as_slice().len())`, which we used previously.
Use these methods in codegen.
Add `peek` and `peek_copy` methods to `SliceIterExt`.
This is just syntax sugar, but it makes code easier to understand - it's clear what `bytes.peek()` does, whereas `bytes.clone().next()` is a bit less obvious.
Use these methods in codegen.
Unclear to me why this has +4% perf impact on codegen. My best guess is it's pushing compiler to make better inlining decisions, as `peek` is marked `#[inline(always)]`.
`PointerExt` trait was introduced to allow using `NonNull::offset_from_unsigned` (and similar APIs) on Rust v1.87.0 and above, without breaking code on earlier Rust versions where these APIs did not exist.
Our MSRV is now v1.87.0 (#12900), so we can just use these methods directly, and `PointerExt` is not required any more. Remove it.
#12691 added the option to write indentation as either tabs or spaces.
This changed 2 things about writing indentation:
1. On x86_64, it now takes 3 SIMD instructions to "splat" the indent character across a 16-byte XMM register. Previously it was a 16-byte XMM read from a static.
2. If using spaces as indent, it'll usually be in width of 2 or 4. The indent printer has a fast path for writing less than 16 bytes of indentation. When using a single tab as indent, it's rare you'd write more than 16 bytes, because code is rarely that deeply nested. But if using 4 spaces as indent, depth only needs to get to 5 to exceed 16 bytes, which is not uncommon. This makes hitting the slow path far more likely.
Therefore, increase the size of chunks of indentation that's written on the fast path to 32 bytes. This adds only one more XMM write, because it can reuse the "splatted" XMM register, and makes it less likely to hit the slow path.
It's not possible to optimize for all cases because tabs and spaces indentation are quite different in this respect, but I think increasing the chunk size to 32 is probably a decent "middle of the road" option.
Seems to have no ill effects on our benchmarks, which use tabs for indentation. So this seems not to hurt the tabs case, but should help the 4 x spaces case significantly.
Add ability to control indentation style (tabs/spaces) and width (e.g. 2 / 4).
## Usage
```rust
use oxc_codegen::{Codegen, CodegenOptions, IndentChar};
// Tabs with default width
let options = CodegenOptions::default();
// Spaces with width 4
let options = CodegenOptions {
indent_char: IndentChar::Space,
indent_width: 4,
..CodegenOptions::default()
};
// Tabs with width 2
let options = CodegenOptions {
indent_char: IndentChar::Tab,
indent_width: 2,
..CodegenOptions::default()
};
```
All existing functionality is preserved with backward compatibility maintained. The updated system enables new use cases like multi-tab indentation.
---------
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>
Introduce `SliceIterExt` extension trait, to add methods to `std::slice::Iter` and `std::slice::IterMut` for advancing the iterators.
* `next_unchecked` - Get next item from iterator without checking if iterator is empty.
* `advance` - Move iterator forwards by a number of items.
* `advance_unchecked` - Move iterator forwards by a number of items, without bounds checks.
We have various implementations of these in various places in the codebase (and slightly different implementations of them each time), so it's better to centralize the implementation in one place.
Pure refactor. `expr_2021` fragment specifier was I assume introduced in these macros by a codemod when we updated to Rust 2024 edition. None of these macros are used with input where there's any difference between `expr_2021` and `expr`, so just use `expr`.
Rust v1.87.0 introduced new methods for pointers, notably `offset_from_unsigned` which calculates the distance between 2 pointers.
https://doc.rust-lang.org/std/primitive.pointer.html#method.offset_from_unsigned
`offset_from_unsigned` is the same as `offset_from`, except it require the source pointer to be before the target, and so returns a `usize` rather than an `isize`. In return for this restriction, the compiler is able to generate more efficient code in some cases.
We want to use these methods, but our MSRV won't hit 1.87.0 for 3 months.
This PR introduces a `PointerExt` trait which provides similar methods `offset_from_usize` and `byte_offset_from_usize`.
On versions of Rust >= 1.87.0, these delegate directly to the native Rust methods `offset_from_unsigned` and `byte_offset_from_unsigned`. On earlier versions of Rust, they use a fallback implementation.
This PR replaces all use of `offset_from` with `offset_from_usize` in our crates.
This will probably make little difference to perf, as we don't use these methods much, and the perf advantage is small anyway. But reason I'm making this PR now is that allocator will make liberal use of them on very hot paths.