diff --git a/crates/oxc_ast/src/ast/literal.rs b/crates/oxc_ast/src/ast/literal.rs index a48dedccdd8058..24ae7ad61c5889 100644 --- a/crates/oxc_ast/src/ast/literal.rs +++ b/crates/oxc_ast/src/ast/literal.rs @@ -33,7 +33,7 @@ pub struct BooleanLiteral { /// #[ast(visit)] #[derive(Debug, Clone)] -#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)] +#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] #[estree(type = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "value: null, raw: \"null\" | null")] pub struct NullLiteral { /// Node location in source code @@ -45,7 +45,7 @@ pub struct NullLiteral { /// #[ast(visit)] #[derive(Debug, Clone)] -#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ESTree)] +#[generate_derive(CloneIn, GetSpan, GetSpanMut, ESTree)] #[estree(type = "Literal", via = crate::serialize::ESTreeLiteral)] pub struct NumericLiteral<'a> { /// Node location in source code @@ -66,7 +66,7 @@ pub struct NumericLiteral<'a> { /// #[ast(visit)] #[derive(Debug, Clone)] -#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] +#[generate_derive(CloneIn, GetSpan, GetSpanMut, ESTree)] #[estree(type = "Literal", via = crate::serialize::ESTreeLiteral)] pub struct StringLiteral<'a> { /// Node location in source code @@ -85,7 +85,7 @@ pub struct StringLiteral<'a> { /// BigInt literal #[ast(visit)] #[derive(Debug, Clone)] -#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] +#[generate_derive(CloneIn, GetSpan, GetSpanMut, ESTree)] #[estree(type = "Literal", via = crate::serialize::ESTreeLiteral, add_ts = "value: null, bigint: string")] pub struct BigIntLiteral<'a> { /// Node location in source code @@ -103,7 +103,7 @@ pub struct BigIntLiteral<'a> { /// #[ast(visit)] #[derive(Debug)] -#[generate_derive(CloneIn, GetSpan, GetSpanMut, ContentEq, ContentHash, ESTree)] +#[generate_derive(CloneIn, GetSpan, GetSpanMut, ESTree)] #[estree( type = "Literal", via = crate::serialize::ESTreeLiteral, @@ -141,7 +141,7 @@ pub struct RegExp<'a> { /// This pattern may or may not be parsed. #[ast] #[derive(Debug)] -#[generate_derive(CloneIn, ContentEq, ContentHash, ESTree)] +#[generate_derive(CloneIn, ESTree)] pub enum RegExpPattern<'a> { /// Unparsed pattern. Contains string slice of the pattern. /// Pattern was not parsed, so may be valid or invalid. diff --git a/crates/oxc_ast/src/ast_impl/literal.rs b/crates/oxc_ast/src/ast_impl/literal.rs index 676a4734b5c985..4ce059977cda5e 100644 --- a/crates/oxc_ast/src/ast_impl/literal.rs +++ b/crates/oxc_ast/src/ast_impl/literal.rs @@ -30,13 +30,6 @@ impl fmt::Display for BooleanLiteral { } } -impl ContentHash for NullLiteral { - #[inline] - fn content_hash(&self, state: &mut H) { - Hash::hash(&Option::::None, state); - } -} - impl fmt::Display for NullLiteral { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -82,10 +75,45 @@ impl NumericLiteral<'_> { } } +impl ContentEq for NumericLiteral<'_> { + fn content_eq(&self, other: &Self) -> bool { + // Note: `f64::content_eq` uses `==` equality. + // `f64::NAN != f64::NAN` and `0.0 == -0.0`, so we follow the same here. + // If we change that behavior, we should alter `content_hash` too. + ContentEq::content_eq(&self.value, &other.value) + } +} + impl ContentHash for NumericLiteral<'_> { fn content_hash(&self, state: &mut H) { - ContentHash::content_hash(&self.base, state); - ContentHash::content_hash(&self.raw, state); + // `f64` does not implement `Hash` due to ambiguity over what is the right way to hash NaN + // and +/- zero values. + // + // # NaN + // IEEE 754 defines a whole range of NaN values. + // https://doc.rust-lang.org/std/primitive.f64.html#associatedconstant.NAN + // We can ignore that complication here as the rule is that 2 types which are equal (`==`) + // must hash the same. + // `ContentEq` uses `==` equality, and even the same NaN doesn't equal itself! + // `f64::NAN != f64::NAN` + // So it doesn't matter if two NaNs have the same hash or not. + // + // # Zero + // `ContentEq` uses `==` equality, which considers `0.0` and `-0.0` equal, so we must make + // them hash the same. + // But `(0.0).to_bits() != (-0.0).to_bits()`, so we need to convert `-0.0` to `0.0`. + // + // # Infinity + // `f64::INFINITY != -f64::INFINITY` and `f64::INFINITY.to_bits() != (-f64::INFINITY).to_bits()` + // so infinity needs no special handling. + // + // Whatever the value, we convert to `u64` using `to_bits`, and hash that. + let mut value = self.value; + if value == -0.0 { + value = 0.0; + } + let value = value.to_bits(); + std::hash::Hash::hash(&value, state); } } @@ -121,6 +149,18 @@ impl StringLiteral<'_> { } } +impl ContentEq for StringLiteral<'_> { + fn content_eq(&self, other: &Self) -> bool { + ContentEq::content_eq(&self.value, &other.value) + } +} + +impl ContentHash for StringLiteral<'_> { + fn content_hash(&self, state: &mut H) { + ContentHash::content_hash(&self.value, state); + } +} + impl AsRef for StringLiteral<'_> { #[inline] fn as_ref(&self) -> &str { @@ -142,12 +182,36 @@ impl BigIntLiteral<'_> { } } +impl ContentEq for BigIntLiteral<'_> { + fn content_eq(&self, other: &Self) -> bool { + ContentEq::content_eq(&self.raw, &other.raw) + } +} + +impl ContentHash for BigIntLiteral<'_> { + fn content_hash(&self, state: &mut H) { + ContentHash::content_hash(&self.raw, state); + } +} + impl fmt::Display for BigIntLiteral<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.raw.fmt(f) } } +impl ContentEq for RegExpLiteral<'_> { + fn content_eq(&self, other: &Self) -> bool { + ContentEq::content_eq(&self.regex, &other.regex) + } +} + +impl ContentHash for RegExpLiteral<'_> { + fn content_hash(&self, state: &mut H) { + ContentHash::content_hash(&self.regex, state); + } +} + impl fmt::Display for RegExp<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "/{}/{}", self.pattern, self.flags) @@ -203,6 +267,32 @@ impl<'a> RegExpPattern<'a> { } } +impl ContentEq for RegExpPattern<'_> { + fn content_eq(&self, other: &Self) -> bool { + let self_str = match self { + Self::Raw(s) | Self::Invalid(s) => *s, + Self::Pattern(p) => &p.to_string(), + }; + + let other_str = match other { + Self::Raw(s) | Self::Invalid(s) => *s, + Self::Pattern(p) => &p.to_string(), + }; + + self_str == other_str + } +} + +impl ContentHash for RegExpPattern<'_> { + fn content_hash(&self, state: &mut H) { + let self_str = match self { + Self::Raw(s) | Self::Invalid(s) => s, + Self::Pattern(p) => &&*p.to_string(), + }; + ContentHash::content_hash(self_str, state); + } +} + impl fmt::Display for RegExpPattern<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/crates/oxc_ast/src/generated/derive_content_eq.rs b/crates/oxc_ast/src/generated/derive_content_eq.rs index 476afcde805478..48cb9f4141f6cb 100644 --- a/crates/oxc_ast/src/generated/derive_content_eq.rs +++ b/crates/oxc_ast/src/generated/derive_content_eq.rs @@ -27,33 +27,6 @@ impl ContentEq for NullLiteral { } } -impl ContentEq for NumericLiteral<'_> { - fn content_eq(&self, other: &Self) -> bool { - ContentEq::content_eq(&self.value, &other.value) - && ContentEq::content_eq(&self.base, &other.base) - } -} - -impl ContentEq for StringLiteral<'_> { - fn content_eq(&self, other: &Self) -> bool { - ContentEq::content_eq(&self.value, &other.value) - } -} - -impl ContentEq for BigIntLiteral<'_> { - fn content_eq(&self, other: &Self) -> bool { - ContentEq::content_eq(&self.raw, &other.raw) - && ContentEq::content_eq(&self.base, &other.base) - } -} - -impl ContentEq for RegExpLiteral<'_> { - fn content_eq(&self, other: &Self) -> bool { - ContentEq::content_eq(&self.regex, &other.regex) - && ContentEq::content_eq(&self.raw, &other.raw) - } -} - impl ContentEq for RegExp<'_> { fn content_eq(&self, other: &Self) -> bool { ContentEq::content_eq(&self.pattern, &other.pattern) @@ -61,25 +34,6 @@ impl ContentEq for RegExp<'_> { } } -impl ContentEq for RegExpPattern<'_> { - fn content_eq(&self, other: &Self) -> bool { - match self { - Self::Raw(it) => match other { - Self::Raw(other) if ContentEq::content_eq(it, other) => true, - _ => false, - }, - Self::Invalid(it) => match other { - Self::Invalid(other) if ContentEq::content_eq(it, other) => true, - _ => false, - }, - Self::Pattern(it) => match other { - Self::Pattern(other) if ContentEq::content_eq(it, other) => true, - _ => false, - }, - } - } -} - impl ContentEq for Program<'_> { fn content_eq(&self, other: &Self) -> bool { ContentEq::content_eq(&self.source_type, &other.source_type) diff --git a/crates/oxc_ast/src/generated/derive_content_hash.rs b/crates/oxc_ast/src/generated/derive_content_hash.rs index ad73aef7e58c4d..a0ccda2e60ec2e 100644 --- a/crates/oxc_ast/src/generated/derive_content_hash.rs +++ b/crates/oxc_ast/src/generated/derive_content_hash.rs @@ -23,25 +23,8 @@ impl ContentHash for BooleanLiteral { } } -impl ContentHash for StringLiteral<'_> { - fn content_hash(&self, state: &mut H) { - ContentHash::content_hash(&self.value, state); - ContentHash::content_hash(&self.raw, state); - } -} - -impl ContentHash for BigIntLiteral<'_> { - fn content_hash(&self, state: &mut H) { - ContentHash::content_hash(&self.raw, state); - ContentHash::content_hash(&self.base, state); - } -} - -impl ContentHash for RegExpLiteral<'_> { - fn content_hash(&self, state: &mut H) { - ContentHash::content_hash(&self.regex, state); - ContentHash::content_hash(&self.raw, state); - } +impl ContentHash for NullLiteral { + fn content_hash(&self, _: &mut H) {} } impl ContentHash for RegExp<'_> { @@ -51,17 +34,6 @@ impl ContentHash for RegExp<'_> { } } -impl ContentHash for RegExpPattern<'_> { - fn content_hash(&self, state: &mut H) { - ContentHash::content_hash(&discriminant(self), state); - match self { - Self::Raw(it) => ContentHash::content_hash(it, state), - Self::Invalid(it) => ContentHash::content_hash(it, state), - Self::Pattern(it) => ContentHash::content_hash(it, state), - } - } -} - impl ContentHash for Program<'_> { fn content_hash(&self, state: &mut H) { ContentHash::content_hash(&self.source_type, state); diff --git a/tasks/ast_tools/src/derives/content_eq.rs b/tasks/ast_tools/src/derives/content_eq.rs index 69230c062e43fd..496a870403cfde 100644 --- a/tasks/ast_tools/src/derives/content_eq.rs +++ b/tasks/ast_tools/src/derives/content_eq.rs @@ -87,10 +87,6 @@ fn derive_struct(def: &StructDef) -> (&str, TokenStream) { .iter() .filter(|field| { let Some(name) = field.name.as_ref() else { return false }; - if name == "raw" && matches!(def.name.as_str(), "NumericLiteral" | "StringLiteral") - { - return false; - } !IGNORE_FIELDS .iter() .any(|it| name == it.0 && field.typ.name().inner_name() == it.1)