Skip to content

Commit

Permalink
refactor: make CompactStr immutable (#2620)
Browse files Browse the repository at this point in the history
First step towards #2516.

This replaces `compact_str::CompactString` with an immutable interface `CompactStr`.

Currently just implemented as a wrapper around `CompactString` which hides all its mutation methods. A more optimized implementation to follow, which shrinks size of `CompactStr` to 16 bytes by removing the `capacity` field.

The rationale for the change of name is: `CompactString` is like `String` in that it's mutable. `CompactStr` is more like `str` - immutable - so its name mirrors `str`.
  • Loading branch information
overlookmotel committed Mar 6, 2024
1 parent 0646bf3 commit 8001b2f
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 18 deletions.
2 changes: 1 addition & 1 deletion crates/oxc_linter/src/rules/unicorn/catch_error_name.rs
Expand Up @@ -36,7 +36,7 @@ impl std::ops::Deref for CatchErrorName {

impl Default for CatchErrorNameConfig {
fn default() -> Self {
Self { ignore: vec![], name: CompactStr::new_inline("error") }
Self { ignore: vec![], name: CompactStr::new_const("error") }
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_minifier/src/mangler/mod.rs
Expand Up @@ -234,5 +234,5 @@ fn base54(n: usize) -> CompactStr {
ret.push(BASE54_CHARS[num % base] as char);
num /= base;
}
CompactStr::new(ret)
CompactStr::new(&ret)
}
8 changes: 2 additions & 6 deletions crates/oxc_semantic/src/builder.rs
Expand Up @@ -5,7 +5,7 @@ use std::{cell::RefCell, path::PathBuf, rc::Rc, sync::Arc};
#[allow(clippy::wildcard_imports)]
use oxc_ast::{ast::*, AstKind, Trivias, TriviasMap, Visit};
use oxc_diagnostics::Error;
use oxc_span::{Atom, CompactStr, SourceType, Span};
use oxc_span::{Atom, SourceType, Span};
use oxc_syntax::{
module_record::{ExportLocalName, ModuleRecord},
operator::AssignmentOperator,
Expand Down Expand Up @@ -296,11 +296,7 @@ impl<'a> SemanticBuilder<'a> {
pub fn declare_reference(&mut self, reference: Reference) -> ReferenceId {
let reference_name = reference.name().clone();
let reference_id = self.symbols.create_reference(reference);
self.scope.add_unresolved_reference(
self.current_scope_id,
CompactStr::new(reference_name),
reference_id,
);
self.scope.add_unresolved_reference(self.current_scope_id, reference_name, reference_id);
reference_id
}

Expand Down
164 changes: 156 additions & 8 deletions crates/oxc_span/src/atom.rs
Expand Up @@ -19,12 +19,13 @@ export type Atom = string;
export type CompactStr = string;
"#;

/// Maximum length for inline string, which can be created with `CompactStr::new_const`.
pub const MAX_INLINE_LEN: usize = 16;

/// An inlinable string for oxc_allocator.
///
/// Use [CompactStr] with [Atom::to_compact_str] or [Atom::into_compact_str] for the
/// lifetimeless form.
///
/// [CompactStr]: crate::CompactStr
#[derive(Clone, Eq)]
pub enum Atom<'a> {
Arena(&'a str),
Expand Down Expand Up @@ -59,18 +60,18 @@ impl<'a> Atom<'a> {
}

#[inline]
pub fn into_compact_str(self) -> CompactString {
pub fn into_compact_str(self) -> CompactStr {
match self {
Self::Arena(s) => CompactString::new(s),
Self::Compact(s) => s,
Self::Arena(s) => CompactStr::new(s),
Self::Compact(s) => CompactStr::from(s),
}
}

#[inline]
pub fn to_compact_str(&self) -> CompactString {
pub fn to_compact_str(&self) -> CompactStr {
match &self {
Self::Arena(s) => CompactString::new(s),
Self::Compact(s) => s.clone(),
Self::Arena(s) => CompactStr::new(s),
Self::Compact(s) => CompactStr::from(s.clone()),
}
}
}
Expand Down Expand Up @@ -145,3 +146,150 @@ impl<'a> fmt::Display for Atom<'a> {
fmt::Display::fmt(self.as_str(), f)
}
}

/// Lifetimeless version of `Atom<'_>` which owns its own string data allocation.
///
/// `CompactStr` is immutable. Use `CompactStr::into_string` for a mutable `String`.
///
/// Currently implemented as just a wrapper around `compact_str::CompactString`,
/// but will be reduced in size with a custom implementation later.
#[derive(Clone, Eq)]
pub struct CompactStr(CompactString);

impl CompactStr {
/// Create a new `CompactStr`.
///
/// If `&str` is `'static` and no more than `MAX_INLINE_LEN` bytes,
/// prefer `CompactStr::new_const` which creates the `CompactStr` at compile time.
///
/// # Examples
/// ```
/// let s = CompactStr::new("long string which can't use new_const for");
/// ```
#[inline]
pub fn new(s: &str) -> Self {
Self(CompactString::new(s))
}

/// Create a `CompactStr` at compile time.
///
/// String must be no longer than `MAX_INLINE_LEN` bytes.
///
/// Prefer this over `CompactStr::new` or `CompactStr::from` where string
/// is `'static` and not longer than `MAX_INLINE_LEN` bytes.
///
/// # Panics
/// Panics if string is longer than `MAX_INLINE_LEN` bytes.
///
/// # Examples
/// ```
/// const S: CompactStr = CompactStr::new_const("short");
/// ```
#[inline]
pub const fn new_const(s: &'static str) -> Self {
assert!(s.len() <= MAX_INLINE_LEN);
Self(CompactString::new_inline(s))
}

/// Get string content as a `&str` slice.
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}

/// Convert a `CompactStr` into a `String`.
#[inline]
pub fn into_string(self) -> String {
self.0.into_string()
}

/// Get length of `CompactStr`.
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}

/// Check if a `CompactStr` is empty (0 length).
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}

impl From<&str> for CompactStr {
fn from(s: &str) -> Self {
Self(CompactString::from(s))
}
}

impl From<String> for CompactStr {
fn from(s: String) -> Self {
Self(CompactString::from(s))
}
}

impl From<CompactString> for CompactStr {
fn from(s: CompactString) -> Self {
Self(s)
}
}

impl Deref for CompactStr {
type Target = str;

fn deref(&self) -> &Self::Target {
self.as_str()
}
}

impl AsRef<str> for CompactStr {
fn as_ref(&self) -> &str {
self.as_str()
}
}

impl Borrow<str> for CompactStr {
fn borrow(&self) -> &str {
self.as_str()
}
}

impl<T: AsRef<str>> PartialEq<T> for CompactStr {
fn eq(&self, other: &T) -> bool {
self.as_str() == other.as_ref()
}
}

impl PartialEq<CompactStr> for &str {
fn eq(&self, other: &CompactStr) -> bool {
*self == other.as_str()
}
}

impl hash::Hash for CompactStr {
fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
self.as_str().hash(hasher);
}
}

impl fmt::Debug for CompactStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.as_str(), f)
}
}

impl fmt::Display for CompactStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}

#[cfg(feature = "serde")]
impl Serialize for CompactStr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
3 changes: 1 addition & 2 deletions crates/oxc_span/src/lib.rs
Expand Up @@ -7,8 +7,7 @@ mod source_type;
mod span;

pub use crate::{
atom::Atom,
atom::{Atom, CompactStr, MAX_INLINE_LEN as ATOM_MAX_INLINE_LEN},
source_type::{Language, LanguageVariant, ModuleKind, SourceType, VALID_EXTENSIONS},
span::{GetSpan, Span, SPAN},
};
pub use compact_str::CompactString as CompactStr;

0 comments on commit 8001b2f

Please sign in to comment.