diff --git a/crates/oxc_ast/src/generated/assert_layouts.rs b/crates/oxc_ast/src/generated/assert_layouts.rs index 8e5bf9b0fa026..45e1b8d6e59cf 100644 --- a/crates/oxc_ast/src/generated/assert_layouts.rs +++ b/crates/oxc_ast/src/generated/assert_layouts.rs @@ -9,13 +9,13 @@ use crate::ast::*; #[cfg(target_pointer_width = "64")] const _: () = { - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(BooleanLiteral, span) == 0usize); assert!(offset_of!(BooleanLiteral, value) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(NullLiteral, span) == 0usize); assert!(size_of::() == 40usize); @@ -88,7 +88,7 @@ const _: () = { assert!(offset_of!(LabelIdentifier, name) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(ThisExpression, span) == 0usize); assert!(size_of::() == 56usize); @@ -101,7 +101,7 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Elision, span) == 0usize); assert!(size_of::() == 56usize); @@ -312,7 +312,7 @@ const _: () = { assert!(offset_of!(SequenceExpression, expressions) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Super, span) == 0usize); assert!(size_of::() == 24usize); @@ -375,7 +375,7 @@ const _: () = { assert!(offset_of!(VariableDeclarator, definite) == 64usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(EmptyStatement, span) == 0usize); assert!(size_of::() == 24usize); @@ -499,7 +499,7 @@ const _: () = { assert!(offset_of!(CatchParameter, pattern) == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(DebuggerStatement, span) == 0usize); assert!(size_of::() == 32usize); @@ -898,59 +898,59 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSAnyKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSStringKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSBooleanKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSNumberKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSNeverKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSIntrinsicKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSUnknownKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSNullKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSUndefinedKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSVoidKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSSymbolKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSThisType, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSObjectKeyword, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(TSBigIntKeyword, span) == 0usize); assert!(size_of::() == 32usize); @@ -1272,7 +1272,7 @@ const _: () = { assert!(offset_of!(JSDocNonNullableType, postfix) == 24usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSDocUnknownType, span) == 0usize); assert!(size_of::() == 56usize); @@ -1303,11 +1303,11 @@ const _: () = { assert!(offset_of!(JSXFragment, children) == 24usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSXOpeningFragment, span) == 0usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSXClosingFragment, span) == 0usize); assert!(size_of::() == 16usize); @@ -1337,7 +1337,7 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(JSXEmptyExpression, span) == 0usize); assert!(size_of::() == 16usize); @@ -1385,7 +1385,7 @@ const _: () = { assert!(align_of::() == 1usize); assert!(size_of::() == 16usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Comment, span) == 0usize); assert!(offset_of!(Comment, attached_to) == 8usize); assert!(offset_of!(Comment, kind) == 12usize); @@ -1415,7 +1415,7 @@ const _: () = { assert!(align_of::() == 1usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Span, start) == 0usize); assert!(offset_of!(Span, end) == 4usize); @@ -1449,8 +1449,8 @@ const _: () = { assert!(size_of::() == 16usize); assert!(align_of::() == 8usize); - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(BoundaryAssertion, span) == 0usize); assert!(offset_of!(BoundaryAssertion, kind) == 8usize); @@ -1475,7 +1475,7 @@ const _: () = { assert!(offset_of!(Quantifier, body) == 40usize); assert!(size_of::() == 16usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Character, span) == 0usize); assert!(offset_of!(Character, kind) == 8usize); assert!(offset_of!(Character, value) == 12usize); @@ -1483,8 +1483,8 @@ const _: () = { assert!(size_of::() == 1usize); assert!(align_of::() == 1usize); - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(CharacterClassEscape, span) == 0usize); assert!(offset_of!(CharacterClassEscape, kind) == 8usize); @@ -1500,7 +1500,7 @@ const _: () = { assert!(offset_of!(UnicodePropertyEscape, value) == 32usize); assert!(size_of::() == 8usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Dot, span) == 0usize); assert!(size_of::() == 48usize); @@ -1518,7 +1518,7 @@ const _: () = { assert!(align_of::() == 8usize); assert!(size_of::() == 40usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(CharacterClassRange, span) == 0usize); assert!(offset_of!(CharacterClassRange, min) == 8usize); assert!(offset_of!(CharacterClassRange, max) == 24usize); @@ -1548,7 +1548,7 @@ const _: () = { assert!(offset_of!(IgnoreGroup, body) == 24usize); assert!(size_of::() == 16usize); - assert!(align_of::() == 4usize); + assert!(align_of::() == 8usize); assert!(offset_of!(Modifiers, span) == 0usize); assert!(offset_of!(Modifiers, enabling) == 8usize); assert!(offset_of!(Modifiers, disabling) == 11usize); @@ -1559,8 +1559,8 @@ const _: () = { assert!(offset_of!(Modifier, multiline) == 1usize); assert!(offset_of!(Modifier, sticky) == 2usize); - assert!(size_of::() == 12usize); - assert!(align_of::() == 4usize); + assert!(size_of::() == 16usize); + assert!(align_of::() == 8usize); assert!(offset_of!(IndexedReference, span) == 0usize); assert!(offset_of!(IndexedReference, index) == 8usize); diff --git a/crates/oxc_span/src/span/mod.rs b/crates/oxc_span/src/span/mod.rs index 4d66624b04a9d..fe7e024d59cfa 100644 --- a/crates/oxc_span/src/span/mod.rs +++ b/crates/oxc_span/src/span/mod.rs @@ -1,4 +1,8 @@ -use std::ops::{Index, IndexMut, Range}; +use std::{ + fmt::{self, Debug}, + hash::{Hash, Hasher}, + ops::{Index, IndexMut, Range}, +}; use miette::{LabeledSpan, SourceOffset, SourceSpan}; @@ -9,6 +13,18 @@ pub use types::Span; /// An Empty span useful for creating AST nodes. pub const SPAN: Span = Span::new(0, 0); +/// Zero-sized type which has pointer alignment (8 on 64-bit, 4 on 32-bit). +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +struct PointerAlign([usize; 0]); + +impl PointerAlign { + #[inline] + const fn new() -> Self { + Self([]) + } +} + impl Span { /// Create a new [`Span`] from a start and end position. /// @@ -19,7 +35,7 @@ impl Span { /// #[inline] pub const fn new(start: u32, end: u32) -> Self { - Self { start, end } + Self { start, end, _align: PointerAlign::new() } } /// Create a new empty [`Span`] that starts and ends at an offset position. @@ -34,7 +50,7 @@ impl Span { /// assert_eq!(fifth, Span::new(5, 5)); /// ``` pub fn empty(at: u32) -> Self { - Self { start: at, end: at } + Self::new(at, at) } /// Create a new [`Span`] starting at `start` and covering `size` bytes. @@ -362,6 +378,23 @@ impl From for LabeledSpan { } } +// Skip hashing `_align` field +impl Hash for Span { + #[inline] // We exclusively use `FxHasher`, which produces small output hashing `u32`s + fn hash(&self, hasher: &mut H) { + self.start.hash(hasher); + self.end.hash(hasher); + } +} + +// Skip `_align` field in `Debug` output +#[expect(clippy::missing_fields_in_debug)] +impl Debug for Span { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Span").field("start", &self.start).field("end", &self.end).finish() + } +} + /// Get the span for an AST node pub trait GetSpan { /// Get the [`Span`] for an AST node @@ -413,13 +446,29 @@ mod test { } #[test] + #[expect(clippy::items_after_statements)] fn test_hash() { use std::hash::{DefaultHasher, Hash, Hasher}; - let mut first = DefaultHasher::new(); - let mut second = DefaultHasher::new(); - Span::new(0, 5).hash(&mut first); - Span::new(0, 5).hash(&mut second); - assert_eq!(first.finish(), second.finish()); + fn hash(value: T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() + } + + let first_hash = hash(Span::new(0, 5)); + let second_hash = hash(Span::new(0, 5)); + assert_eq!(first_hash, second_hash); + + // Check `_align` field does not alter hash + #[derive(Hash)] + #[repr(C)] + struct PlainSpan { + start: u32, + end: u32, + } + + let plain_hash = hash(PlainSpan { start: 0, end: 5 }); + assert_eq!(plain_hash, first_hash); } #[test] @@ -481,3 +530,18 @@ mod test { let _ = span.shrink(5); } } + +#[cfg(test)] +mod size_asserts { + use std::mem::{align_of, size_of}; + + use super::Span; + + const _: () = assert!(size_of::() == 8); + + #[cfg(target_pointer_width = "64")] + const _: () = assert!(align_of::() == 8); + + #[cfg(not(target_pointer_width = "64"))] + const _: () = assert!(align_of::() == 4); +} diff --git a/crates/oxc_span/src/span/types.rs b/crates/oxc_span/src/span/types.rs index 9260b9f45d8e2..def2d8619434d 100644 --- a/crates/oxc_span/src/span/types.rs +++ b/crates/oxc_span/src/span/types.rs @@ -1,6 +1,8 @@ use oxc_ast_macros::ast; use oxc_estree::ESTree; +use super::PointerAlign; + /// A range in text, represented by a zero-indexed start and end offset. /// /// It is a logical error for `end` to be less than `start`. @@ -57,9 +59,8 @@ use oxc_estree::ESTree; /// [`expand`]: Span::expand /// [`shrink`]: Span::shrink #[ast(visit)] -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[generate_derive(ESTree)] -#[non_exhaustive] // Disallow struct expression constructor `Span {}` #[estree(no_type, always_flatten)] pub struct Span { /// The zero-based start offset of the span @@ -67,6 +68,9 @@ pub struct Span { /// The zero-based end offset of the span. This may be equal to [`start`](Span::start) if /// the span is empty, but should not be less than it. pub end: u32, + /// Align `Span` on 8 on 64-bit platforms + #[estree(skip)] + pub(super) _align: PointerAlign, } #[cfg(test)] diff --git a/tasks/ast_tools/src/passes/calc_layout.rs b/tasks/ast_tools/src/passes/calc_layout.rs index d113caef693d9..d59f4198c17ef 100644 --- a/tasks/ast_tools/src/passes/calc_layout.rs +++ b/tasks/ast_tools/src/passes/calc_layout.rs @@ -300,5 +300,7 @@ lazy_static! { ("Cell>", PlatformLayout::of::()), // Unsupported: this is a `bitflags` generated type, we don't expand macros ("RegExpFlags", PlatformLayout::of::()), + // `PointerAlign` is a field of `Span`. ZST with pointer alignment. + ("PointerAlign", PlatformLayout(Layout::known(0, 8, 0), Layout::known(0, 4, 0))), ]); }