diff --git a/Cargo.toml b/Cargo.toml index e2bc8c4..aff4621 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,22 +5,23 @@ authors = ["Aleksey Kladov "] repository = "https://github.com/rust-analyzer/rowan" license = "MIT OR Apache-2.0" description = "Library for generic lossless syntax trees" -edition = "2021" -rust-version = "1.77.0" +edition = "2024" +rust-version = "1.85.0" exclude = [".github/", "bors.toml", "rustfmt.toml"] [workspace] members = ["xtask"] [dependencies] -rustc-hash = "1.0.1" -hashbrown = { version = "0.14.3", features = [ - "inline-more", +rustc-hash = "2.1.1" +hashbrown = { version = "0.15.2", features = [ + "inline-more", + "raw-entry", ], default-features = false } -text-size = "1.1.0" -countme = "3.0.0" +text-size = "1.1.1" +countme = "3.0.1" -serde = { version = "1.0.89", optional = true, default-features = false } +serde = { version = "1.0.218", optional = true, default-features = false } [dev-dependencies] m_lexer = "0.0.4" diff --git a/examples/math.rs b/examples/math.rs index e3c3505..3a451b6 100644 --- a/examples/math.rs +++ b/examples/math.rs @@ -32,6 +32,7 @@ enum SyntaxKind { OPERATION, ROOT, } + use SyntaxKind::*; impl From for rowan::SyntaxKind { diff --git a/examples/s_expressions.rs b/examples/s_expressions.rs index f3ceec3..34b3068 100644 --- a/examples/s_expressions.rs +++ b/examples/s_expressions.rs @@ -194,8 +194,10 @@ fn parse(text: &str) -> Parse { /// has identity semantics. type SyntaxNode = rowan::SyntaxNode; + #[allow(unused)] type SyntaxToken = rowan::SyntaxToken; + #[allow(unused)] type SyntaxElement = rowan::NodeOrToken; @@ -255,11 +257,7 @@ macro_rules! ast_node { impl $ast { #[allow(unused)] fn cast(node: SyntaxNode) -> Option { - if node.kind() == $kind { - Some(Self(node)) - } else { - None - } + if node.kind() == $kind { Some(Self(node)) } else { None } } } }; diff --git a/src/api.rs b/src/api.rs index a877fdd..a35d001 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,8 +1,8 @@ use std::{borrow::Cow, fmt, iter, marker::PhantomData, ops::Range}; use crate::{ - cursor, green::GreenTokenData, Direction, GreenNode, GreenNodeData, GreenToken, NodeOrToken, - SyntaxKind, SyntaxText, TextRange, TextSize, TokenAtOffset, WalkEvent, + Direction, GreenNode, GreenNodeData, GreenToken, NodeOrToken, SyntaxKind, SyntaxText, + TextRange, TextSize, TokenAtOffset, WalkEvent, cursor, green::GreenTokenData, }; pub trait Language: Sized + Copy + fmt::Debug + Eq + Ord + std::hash::Hash { @@ -446,10 +446,7 @@ impl Iterator for SyntaxNodeChildren { impl SyntaxNodeChildren { pub fn by_kind(self, matcher: impl Fn(L::Kind) -> bool) -> impl Iterator> { - self.raw - .by_kind(move |raw_kind| matcher(L::kind_from_raw(raw_kind))) - .into_iter() - .map(SyntaxNode::from) + self.raw.by_kind(move |raw_kind| matcher(L::kind_from_raw(raw_kind))).map(SyntaxNode::from) } } diff --git a/src/arc.rs b/src/arc.rs index cb6758e..fa3774f 100644 --- a/src/arc.rs +++ b/src/arc.rs @@ -4,7 +4,7 @@ use std::{ cmp::Ordering, hash::{Hash, Hasher}, marker::PhantomData, - mem::{self, offset_of, ManuallyDrop}, + mem::{self, ManuallyDrop, offset_of}, ops::Deref, ptr, sync::atomic::{ @@ -55,14 +55,17 @@ impl Arc { pub(crate) unsafe fn from_raw(ptr: *const T) -> Self { // To find the corresponding pointer to the `ArcInner` we need // to subtract the offset of the `data` field from the pointer. - let ptr = (ptr as *const u8).sub(offset_of!(ArcInner, data)); - Arc { p: ptr::NonNull::new_unchecked(ptr as *mut ArcInner), phantom: PhantomData } + unsafe { + let ptr = (ptr as *const u8).sub(offset_of!(ArcInner, data)); + Arc { p: ptr::NonNull::new_unchecked(ptr as *mut ArcInner), phantom: PhantomData } + } } } impl Arc { #[inline] fn inner(&self) -> &ArcInner { + // SAFETY: // This unsafety is ok because while this arc is alive we're guaranteed // that the inner pointer is valid. Furthermore, we know that the // `ArcInner` structure itself is `Sync` because the inner data is @@ -74,7 +77,9 @@ impl Arc { // Non-inlined part of `drop`. Just invokes the destructor. #[inline(never)] unsafe fn drop_slow(&mut self) { - let _ = Box::from_raw(self.ptr()); + unsafe { + let _ = Box::from_raw(self.ptr()); + } } /// Test pointer equality between the two Arcs, i.e. they must be the _same_ @@ -195,10 +200,6 @@ impl PartialEq for Arc { fn eq(&self, other: &Arc) -> bool { Self::ptr_eq(self, other) || *(*self) == *(*other) } - - fn ne(&self, other: &Arc) -> bool { - !Self::ptr_eq(self, other) && *(*self) != *(*other) - } } impl PartialOrd for Arc { @@ -312,10 +313,7 @@ impl ThinArc { }; // Expose the transient Arc to the callback, which may clone it if it wants. - let result = f(&transient); - - // Forward the result. - result + f(&transient) } /// Creates a `ThinArc` for a HeaderSlice using the given header struct and diff --git a/src/ast.rs b/src/ast.rs index e142395..292dba9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -156,7 +156,7 @@ impl AstPtr { /// Returns the underlying [`SyntaxNodePtr`]. pub fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.raw.clone() + self.raw } /// Casts this to an [`AstPtr`] to the given node type if possible. @@ -176,7 +176,7 @@ impl fmt::Debug for AstPtr { impl Clone for AstPtr { fn clone(&self) -> Self { - Self { raw: self.raw.clone() } + Self { raw: self.raw } } } diff --git a/src/cow_mut.rs b/src/cow_mut.rs index c50e25b..418d7ee 100644 --- a/src/cow_mut.rs +++ b/src/cow_mut.rs @@ -9,7 +9,7 @@ impl std::ops::Deref for CowMut<'_, T> { fn deref(&self) -> &T { match self { CowMut::Owned(it) => it, - CowMut::Borrowed(it) => *it, + CowMut::Borrowed(it) => it, } } } @@ -18,7 +18,7 @@ impl std::ops::DerefMut for CowMut<'_, T> { fn deref_mut(&mut self) -> &mut T { match self { CowMut::Owned(it) => it, - CowMut::Borrowed(it) => *it, + CowMut::Borrowed(it) => it, } } } diff --git a/src/cursor.rs b/src/cursor.rs index cb6ceeb..1288620 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -95,11 +95,11 @@ use std::{ use countme::Count; use crate::{ + Direction, GreenNode, GreenToken, NodeOrToken, SyntaxText, TextRange, TextSize, TokenAtOffset, + WalkEvent, green::{GreenChild, GreenElementRef, GreenNodeData, GreenTokenData, SyntaxKind}, sll, utility_types::Delta, - Direction, GreenNode, GreenToken, NodeOrToken, SyntaxText, TextRange, TextSize, TokenAtOffset, - WalkEvent, }; enum Green { @@ -188,32 +188,34 @@ impl Drop for SyntaxToken { #[inline(never)] unsafe fn free(mut data: ptr::NonNull) { - loop { - debug_assert_eq!(data.as_ref().rc.get(), 0); - debug_assert!(data.as_ref().first.get().is_null()); - let node = Box::from_raw(data.as_ptr()); - match node.parent.take() { - Some(parent) => { - debug_assert!(parent.as_ref().rc.get() > 0); - if node.mutable { - sll::unlink(&parent.as_ref().first, &*node) - } - if parent.as_ref().dec_rc() { - data = parent; - } else { - break; - } - } - None => { - match &node.green { - Green::Node { ptr } => { - let _ = GreenNode::from_raw(ptr.get()); + unsafe { + loop { + debug_assert_eq!(data.as_ref().rc.get(), 0); + debug_assert!(data.as_ref().first.get().is_null()); + let node = Box::from_raw(data.as_ptr()); + match node.parent.take() { + Some(parent) => { + debug_assert!(parent.as_ref().rc.get() > 0); + if node.mutable { + sll::unlink(&parent.as_ref().first, &*node) + } + if parent.as_ref().dec_rc() { + data = parent; + } else { + break; } - Green::Token { ptr } => { - let _ = GreenToken::from_raw(*ptr); + } + None => { + match &node.green { + Green::Node { ptr } => { + let _ = GreenNode::from_raw(ptr.get()); + } + Green::Token { ptr } => { + let _ = GreenToken::from_raw(*ptr); + } } + break; } - break; } } } @@ -321,7 +323,7 @@ impl NodeData { fn green(&self) -> GreenElementRef<'_> { match &self.green { Green::Node { ptr } => GreenElementRef::Node(unsafe { &*ptr.get().as_ptr() }), - Green::Token { ptr } => GreenElementRef::Token(unsafe { &*ptr.as_ref() }), + Green::Token { ptr } => GreenElementRef::Token(unsafe { ptr.as_ref() }), } } #[inline] @@ -342,11 +344,7 @@ impl NodeData { #[inline] fn offset(&self) -> TextSize { - if self.mutable { - self.offset_mut() - } else { - self.offset - } + if self.mutable { self.offset_mut() } else { self.offset } } #[cold] @@ -524,25 +522,27 @@ impl NodeData { } } unsafe fn respine(&self, mut new_green: GreenNode) { - let mut node = self; - loop { - let old_green = match &node.green { - Green::Node { ptr } => ptr.replace(ptr::NonNull::from(&*new_green)), - Green::Token { .. } => unreachable!(), - }; - match node.parent() { - Some(parent) => match parent.green() { - NodeOrToken::Node(parent_green) => { - new_green = - parent_green.replace_child(node.index() as usize, new_green.into()); - node = parent; + unsafe { + let mut node = self; + loop { + let old_green = match &node.green { + Green::Node { ptr } => ptr.replace(ptr::NonNull::from(&*new_green)), + Green::Token { .. } => unreachable!(), + }; + match node.parent() { + Some(parent) => match parent.green() { + NodeOrToken::Node(parent_green) => { + new_green = + parent_green.replace_child(node.index() as usize, new_green.into()); + node = parent; + } + _ => unreachable!(), + }, + None => { + mem::forget(new_green); + let _ = GreenNode::from_raw(old_green); + break; } - _ => unreachable!(), - }, - None => { - mem::forget(new_green); - let _ = GreenNode::from_raw(old_green); - break; } } } @@ -778,16 +778,13 @@ impl SyntaxNode { siblings .skip(index + 1) .find_map(|(index, child)| { - child - .as_ref() - .into_node() - .and_then(|green| Some((green, index as u32, child.rel_offset()))) + child.as_ref().into_node().map(|green| (green, index as u32, child.rel_offset())) }) - .and_then(|(green, index, rel_offset)| { + .map(|(green, index, rel_offset)| { data.index.set(index); data.offset = parent_offset + rel_offset; data.green = Green::Node { ptr: Cell::new(green.into()) }; - Some(SyntaxNode { ptr }) + SyntaxNode { ptr } }) .or_else(|| { data.dec_rc(); @@ -1121,12 +1118,8 @@ impl SyntaxElement { offset: TextSize, ) -> SyntaxElement { match element { - NodeOrToken::Node(node) => { - SyntaxNode::new_child(node, parent, index as u32, offset).into() - } - NodeOrToken::Token(token) => { - SyntaxToken::new(token, parent, index as u32, offset).into() - } + NodeOrToken::Node(node) => SyntaxNode::new_child(node, parent, index, offset).into(), + NodeOrToken::Token(token) => SyntaxToken::new(token, parent, index, offset).into(), } } @@ -1223,7 +1216,7 @@ impl SyntaxElement { siblings .skip(index + 1) - .find_map(|(index, green)| { + .map(|(index, green)| { data.index.set(index as u32); data.offset = parent_offset + green.rel_offset(); @@ -1238,6 +1231,8 @@ impl SyntaxElement { } } }) + .next() + .flatten() .or_else(|| { data.dec_rc(); unsafe { free(ptr) }; @@ -1411,9 +1406,8 @@ pub struct SyntaxNodeChildrenByKind bool> { impl bool> Iterator for SyntaxNodeChildrenByKind { type Item = SyntaxNode; fn next(&mut self) -> Option { - self.next.take().map(|next| { + self.next.take().inspect(|next| { self.next = next.next_sibling_by_kind(&self.matcher); - next }) } } @@ -1474,9 +1468,8 @@ pub struct SyntaxElementChildrenByKind bool> { impl bool> Iterator for SyntaxElementChildrenByKind { type Item = SyntaxElement; fn next(&mut self) -> Option { - self.next.take().map(|next| { + self.next.take().inspect(|next| { self.next = next.next_sibling_or_token_by_kind(&self.matcher); - next }) } } diff --git a/src/green/builder.rs b/src/green/builder.rs index 03d66df..a8becad 100644 --- a/src/green/builder.rs +++ b/src/green/builder.rs @@ -1,9 +1,9 @@ use std::num::NonZeroUsize; use crate::{ - cow_mut::CowMut, - green::{node_cache::NodeCache, GreenElement, GreenNode, SyntaxKind}, NodeOrToken, + cow_mut::CowMut, + green::{GreenElement, GreenNode, SyntaxKind, node_cache::NodeCache}, }; /// A checkpoint for maybe wrapping a node. See `GreenNodeBuilder::checkpoint` for details. diff --git a/src/green/element.rs b/src/green/element.rs index 2d1ce1f..2945157 100644 --- a/src/green/element.rs +++ b/src/green/element.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; use crate::{ - green::{GreenNode, GreenToken, SyntaxKind}, GreenNodeData, NodeOrToken, TextSize, + green::{GreenNode, GreenToken, SyntaxKind}, }; use super::GreenTokenData; diff --git a/src/green/node.rs b/src/green/node.rs index 9bd860c..832915b 100644 --- a/src/green/node.rs +++ b/src/green/node.rs @@ -9,10 +9,10 @@ use std::{ use countme::Count; use crate::{ + GreenToken, NodeOrToken, TextRange, TextSize, arc::{Arc, HeaderSlice, ThinArc}, green::{GreenElement, GreenElementRef, SyntaxKind}, utility_types::static_assert, - GreenToken, NodeOrToken, TextRange, TextSize, }; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -66,7 +66,7 @@ impl ToOwned for GreenNodeData { impl Borrow for GreenNode { #[inline] fn borrow(&self) -> &GreenNodeData { - &*self + self } } @@ -89,14 +89,14 @@ impl fmt::Debug for GreenNodeData { impl fmt::Debug for GreenNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let data: &GreenNodeData = &*self; + let data: &GreenNodeData = self; fmt::Debug::fmt(data, f) } } impl fmt::Display for GreenNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let data: &GreenNodeData = &*self; + let data: &GreenNodeData = self; fmt::Display::fmt(data, f) } } @@ -159,11 +159,7 @@ impl GreenNodeData { pub fn replace_child(&self, index: usize, new_child: GreenElement) -> GreenNode { let mut replacement = Some(new_child); let children = self.children().enumerate().map(|(i, child)| { - if i == index { - replacement.take().unwrap() - } else { - child.to_owned() - } + if i == index { replacement.take().unwrap() } else { child.to_owned() } }); GreenNode::new(self.kind(), children) } @@ -238,15 +234,17 @@ impl GreenNode { #[inline] pub(crate) fn into_raw(this: GreenNode) -> ptr::NonNull { let green = ManuallyDrop::new(this); - let green: &GreenNodeData = &*green; - ptr::NonNull::from(&*green) + let green: &GreenNodeData = &green; + ptr::NonNull::from(green) } #[inline] pub(crate) unsafe fn from_raw(ptr: ptr::NonNull) -> GreenNode { - let arc = Arc::from_raw(&ptr.as_ref().data as *const ReprThin); - let arc = mem::transmute::, ThinArc>(arc); - GreenNode { ptr: arc } + unsafe { + let arc = Arc::from_raw(&ptr.as_ref().data as *const ReprThin); + let arc = mem::transmute::, ThinArc>(arc); + GreenNode { ptr: arc } + } } } @@ -321,19 +319,19 @@ impl<'a> Iterator for Children<'a> { } #[inline] - fn fold(mut self, init: Acc, mut f: Fold) -> Acc + fn fold(self, init: Acc, mut f: Fold) -> Acc where Fold: FnMut(Acc, Self::Item) -> Acc, { let mut accum = init; - while let Some(x) = self.next() { + for x in self { accum = f(accum, x); } accum } } -impl<'a> DoubleEndedIterator for Children<'a> { +impl DoubleEndedIterator for Children<'_> { #[inline] fn next_back(&mut self) -> Option { self.raw.next_back().map(GreenChild::as_ref) diff --git a/src/green/node_cache.rs b/src/green/node_cache.rs index c73f3e6..89740bf 100644 --- a/src/green/node_cache.rs +++ b/src/green/node_cache.rs @@ -3,8 +3,8 @@ use rustc_hash::FxHasher; use std::hash::{BuildHasherDefault, Hash, Hasher}; use crate::{ - green::GreenElementRef, GreenNode, GreenNodeData, GreenToken, GreenTokenData, NodeOrToken, - SyntaxKind, + GreenNode, GreenNodeData, GreenToken, GreenTokenData, NodeOrToken, SyntaxKind, + green::GreenElementRef, }; use super::element::GreenElement; diff --git a/src/green/token.rs b/src/green/token.rs index 81e298c..9e6a1f0 100644 --- a/src/green/token.rs +++ b/src/green/token.rs @@ -8,9 +8,9 @@ use std::{ use countme::Count; use crate::{ + TextSize, arc::{Arc, HeaderSlice, ThinArc}, green::SyntaxKind, - TextSize, }; #[derive(PartialEq, Eq, Hash)] @@ -53,7 +53,7 @@ impl ToOwned for GreenTokenData { impl Borrow for GreenToken { #[inline] fn borrow(&self) -> &GreenTokenData { - &*self + self } } @@ -68,14 +68,14 @@ impl fmt::Debug for GreenTokenData { impl fmt::Debug for GreenToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let data: &GreenTokenData = &*self; + let data: &GreenTokenData = self; fmt::Debug::fmt(data, f) } } impl fmt::Display for GreenToken { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let data: &GreenTokenData = &*self; + let data: &GreenTokenData = self; fmt::Display::fmt(data, f) } } @@ -117,14 +117,25 @@ impl GreenToken { #[inline] pub(crate) fn into_raw(this: GreenToken) -> ptr::NonNull { let green = ManuallyDrop::new(this); - let green: &GreenTokenData = &*green; - ptr::NonNull::from(&*green) + let green: &GreenTokenData = &green; + ptr::NonNull::from(green) } + /// # Safety + /// + /// This function uses `unsafe` code to create an `Arc` from a raw pointer and then transmutes it into a `ThinArc`. + /// + /// - The raw pointer must be valid and correctly aligned for the type `ReprThin`. + /// - The lifetime of the raw pointer must outlive the lifetime of the `Arc` created from it. + /// - The transmute operation must be safe, meaning that the memory layout of `Arc` must be compatible with `ThinArc`. + /// + /// Failure to uphold these invariants can lead to undefined behavior. #[inline] pub(crate) unsafe fn from_raw(ptr: ptr::NonNull) -> GreenToken { - let arc = Arc::from_raw(&ptr.as_ref().data as *const ReprThin); - let arc = mem::transmute::, ThinArc>(arc); + let arc = unsafe { + let arc = Arc::from_raw(&ptr.as_ref().data as *const ReprThin); + mem::transmute::, ThinArc>(arc) + }; GreenToken { ptr: arc } } } diff --git a/src/serde_impls.rs b/src/serde_impls.rs index aea5d88..529303b 100644 --- a/src/serde_impls.rs +++ b/src/serde_impls.rs @@ -2,8 +2,8 @@ use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; use std::fmt; use crate::{ - api::{Language, SyntaxNode, SyntaxToken}, NodeOrToken, + api::{Language, SyntaxNode, SyntaxToken}, }; struct SerDisplay(T); diff --git a/src/sll.rs b/src/sll.rs index 4a29839..6947660 100644 --- a/src/sll.rs +++ b/src/sll.rs @@ -3,6 +3,15 @@ use std::{cell::Cell, cmp::Ordering, ptr}; use crate::utility_types::Delta; + +/// # Safety +/// +/// Implementors of this trait must ensure that the pointers returned by +/// `prev` and `next` are valid and properly initialized. The pointers must +/// point to valid instances of the implementing type or be null pointers. +/// Additionally, the `key` method must return a valid reference to a `Cell`. +/// +/// Failure to uphold these invariants can result in undefined behavior. pub(crate) unsafe trait Elem { fn prev(&self) -> &Cell<*const Self>; fn next(&self) -> &Cell<*const Self>; @@ -17,7 +26,7 @@ pub(crate) enum AddToSllResult<'a, E: Elem> { AlreadyInSll(*const E), } -impl<'a, E: Elem> AddToSllResult<'a, E> { +impl AddToSllResult<'_, E> { pub(crate) fn add_to_sll(&self, elem_ptr: *const E) { unsafe { (*elem_ptr).prev().set(elem_ptr); @@ -55,11 +64,7 @@ pub(crate) fn init<'a, E: Elem>( head: Option<&'a Cell<*const E>>, elem: &E, ) -> AddToSllResult<'a, E> { - if let Some(head) = head { - link(head, elem) - } else { - AddToSllResult::NoHead - } + if let Some(head) = head { link(head, elem) } else { AddToSllResult::NoHead } } #[cold] diff --git a/src/syntax_text.rs b/src/syntax_text.rs index 3ab3f6c..981df56 100644 --- a/src/syntax_text.rs +++ b/src/syntax_text.rs @@ -1,8 +1,8 @@ use std::fmt; use crate::{ - cursor::{SyntaxNode, SyntaxToken}, TextRange, TextSize, + cursor::{SyntaxNode, SyntaxToken}, }; #[derive(Clone)] @@ -43,7 +43,6 @@ impl SyntaxText { } pub fn char_at(&self, offset: TextSize) -> Option { - let offset = offset.into(); let mut start: TextSize = 0.into(); let res = self.try_for_each_chunk(|chunk| { let end = start + TextSize::of(chunk); @@ -97,7 +96,10 @@ impl SyntaxText { pub fn for_each_chunk(&self, mut f: F) { enum Void {} - match self.try_for_each_chunk(|chunk| Ok::<(), Void>(f(chunk))) { + match self.try_for_each_chunk(|chunk| { + f(chunk); + Ok::<(), Void>(()) + }) { Ok(()) => (), Err(void) => match void {}, } @@ -266,7 +268,7 @@ mod private { #[cfg(test)] mod tests { - use crate::{green::SyntaxKind, GreenNodeBuilder}; + use crate::{GreenNodeBuilder, green::SyntaxKind}; use super::*; @@ -274,7 +276,7 @@ mod tests { let mut builder = GreenNodeBuilder::new(); builder.start_node(SyntaxKind(62)); for &chunk in chunks.iter() { - builder.token(SyntaxKind(92), chunk.into()) + builder.token(SyntaxKind(92), chunk) } builder.finish_node(); SyntaxNode::new_root(builder.finish()) @@ -288,7 +290,7 @@ mod tests { let expected = t1.to_string() == t2.to_string(); let actual = t1 == t2; assert_eq!(expected, actual, "`{}` (SyntaxText) `{}` (SyntaxText)", t1, t2); - let actual = t1 == &*t2.to_string(); + let actual = t1 == *t2.to_string(); assert_eq!(expected, actual, "`{}` (SyntaxText) `{}` (&str)", t1, t2); } fn check(t1: &[&str], t2: &[&str]) { diff --git a/src/utility_types.rs b/src/utility_types.rs index 817add7..f26dabf 100644 --- a/src/utility_types.rs +++ b/src/utility_types.rs @@ -43,8 +43,8 @@ impl NodeOrToken { impl NodeOrToken { pub(crate) fn as_deref(&self) -> NodeOrToken<&N::Target, &T::Target> { match self { - NodeOrToken::Node(node) => NodeOrToken::Node(&*node), - NodeOrToken::Token(token) => NodeOrToken::Token(&*token), + NodeOrToken::Node(node) => NodeOrToken::Node(node), + NodeOrToken::Token(token) => NodeOrToken::Token(token), } } } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 6109c86..8a76f52 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -3,10 +3,8 @@ name = "xtask" version = "0.0.0" publish = false authors = ["Aleksey Kladov "] -edition = "2021" +edition = "2024" [dependencies] -xaction = "0.2" - -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(trick_rust_analyzer_into_highlighting_interpolated_bits)'] } +anyhow = "1.0.96" +xshell = "0.2.7" diff --git a/xtask/src/main.rs b/xtask/src/main.rs index d8ee44b..f14e1ea 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,6 +1,13 @@ -use std::env; +use std::{ + env, + path::PathBuf, + sync::atomic::{AtomicBool, Ordering}, + time::{Duration, Instant}, +}; -use xaction::{cargo_toml, cmd, git, section, Result}; +use anyhow::anyhow; +use xshell::{Shell, cmd}; +pub type Result = anyhow::Result; fn main() { if let Err(err) = try_main() { @@ -9,44 +16,163 @@ fn main() { } } +pub struct Section { + name: &'static str, + start: Instant, +} + +pub struct CargoToml { + path: PathBuf, + contents: String, +} + +impl CargoToml { + pub fn version(&self) -> Result<&str> { + self.get("version") + } + + fn get(&self, field: &str) -> Result<&str> { + for line in self.contents.lines() { + let words = line.split_ascii_whitespace().collect::>(); + match words.as_slice() { + [n, "=", v, ..] if n.trim() == field => { + assert!(v.starts_with('"') && v.ends_with('"')); + return Ok(&v[1..v.len() - 1]); + } + _ => (), + } + } + Err(anyhow!("can't find `{}` in {}", field, self.path.display()))? + } + + pub fn publish(&self, sh: &mut Shell) -> Result<()> { + let token = env::var("CRATES_IO_TOKEN").unwrap_or("no token".to_string()); + let dry_run = dry_run(); + cmd!(sh, "cargo publish --token {token} {dry_run...}").run()?; + Ok(()) + } + + pub fn publish_all(&self, dirs: &[&str], sh: &mut Shell) -> Result<()> { + let token = env::var("CRATES_IO_TOKEN").unwrap_or("no token".to_string()); + if dry_run().is_none() { + for &dir in dirs { + for _ in 0..20 { + std::thread::sleep(Duration::from_secs(10)); + if cmd!( + sh, + "cargo publish --manifest-path {dir}'/Cargo.toml' --token {token} --dry-run" + ) + .run() + .is_ok() + { + break; + } + } + cmd!(sh, "cargo publish --manifest-path {dir}'/Cargo.toml' --token {token}") + .run()?; + } + } + Ok(()) + } +} + +fn dry_run() -> Option<&'static str> { + let dry_run = DRY_RUN.load(Ordering::Relaxed); + if dry_run { Some("--dry-run") } else { None } +} + +pub fn section(name: &'static str) -> Section { + Section::new(name) +} + +pub fn cargo_toml() -> Result { + let manifest_dir = env::var("CARGO_MANIFEST_DIR")?; + let path = PathBuf::from(manifest_dir).join("Cargo.toml"); + let contents = std::fs::read_to_string(&path)?; + Ok(CargoToml { path, contents }) +} + +static DRY_RUN: AtomicBool = AtomicBool::new(false); +pub fn set_dry_run(yes: bool) { + DRY_RUN.store(yes, Ordering::Relaxed) +} + fn try_main() -> Result<()> { + let mut sh = Shell::new()?; let subcommand = std::env::args().nth(1); match subcommand { Some(it) if it == "ci" => (), _ => { print_usage(); - Err("invalid arguments")? + Err(anyhow!("invalid arguments"))? } } - let cargo_toml = cargo_toml()?; - - { - let _s = section("BUILD"); - cmd!("cargo test --workspace --no-run").run()?; - } - { let _s = section("TEST"); - cmd!("cargo test --workspace -- --nocapture").run()?; + for &release in &[None, Some("--release")] { + cmd!(sh, "cargo test {release...} --workspace -- --nocapture").run()?; + } } let version = cargo_toml.version()?; let tag = format!("v{}", version); - let dry_run = - env::var("CI").is_err() || git::has_tag(&tag)? || git::current_branch()? != "master"; - xaction::set_dry_run(dry_run); + let dry_run = env::var("CI").is_err() + || git::has_tag(&tag, &mut sh)? + || git::current_branch(&mut sh)? != "master"; + set_dry_run(dry_run); { let _s = section("PUBLISH"); - cargo_toml.publish()?; - git::tag(&tag)?; - git::push_tags()?; + cargo_toml.publish(&mut sh)?; + git::tag(&tag, &mut sh)?; + git::push_tags(&mut sh)?; } Ok(()) } +pub mod git { + use xshell::{Shell, cmd}; + + use super::{Result, dry_run}; + + pub fn current_branch(sh: &mut Shell) -> Result { + let res = cmd!(sh, "git branch --show-current").read()?; + Ok(res) + } + + pub fn tag_list(sh: &mut Shell) -> Result> { + let tags = cmd!(sh, "git tag --list").read()?; + let res = tags.lines().map(|it| it.trim().to_string()).collect(); + Ok(res) + } + + pub fn has_tag(tag: &str, sh: &mut Shell) -> Result { + let res = tag_list(sh)?.iter().any(|it| it == tag); + Ok(res) + } + + pub fn tag(tag: &str, sh: &mut Shell) -> Result<()> { + if dry_run().is_some() { + return Ok(()); + } + cmd!(sh, "git tag {tag}").run()?; + Ok(()) + } + + pub fn push_tags(sh: &mut Shell) -> Result<()> { + // `git push --tags --dry-run` exists, but it will fail with permissions + // error for forks. + if dry_run().is_some() { + return Ok(()); + } + + cmd!(sh, "git push --tags").run()?; + Ok(()) + } +} + fn print_usage() { eprintln!( "\ @@ -57,3 +183,18 @@ SUBCOMMANDS: " ) } + +impl Section { + fn new(name: &'static str) -> Section { + println!("::group::{}", name); + let start = Instant::now(); + Section { name, start } + } +} + +impl Drop for Section { + fn drop(&mut self) { + eprintln!("{}: {:.2?}", self.name, self.start.elapsed()); + println!("::endgroup::"); + } +} diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 97c423a..60579e6 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs @@ -1,6 +1,7 @@ -use xaction::cmd; +use xshell::{Shell, cmd}; #[test] fn test_formatting() { - cmd!("cargo fmt --all -- --check").run().unwrap() + let sh = Shell::new().unwrap(); + cmd!(sh, "cargo fmt --all -- --check").run().unwrap() }