aozora_syntax/borrowed/non_empty.rs
1//! Non-emptiness invariant for [`Content<'src>`].
2//!
3//! AST variants like [`Ruby`](super::Ruby) and [`Bouten`](super::Bouten)
4//! semantically require non-empty content payloads (an empty ruby base
5//! is a parse bug, not a valid state).
6//!
7//! [`NonEmpty<Content<'src>>`] makes the invariant a build-time fact:
8//! the `NonEmpty::new` constructor returns `Option`, so empty content
9//! cannot enter the AST without a deliberate `unwrap` / `expect`. The
10//! allocator (`aozora_syntax::alloc`) does the `expect` exactly once
11//! per node variant — Phase 3's classifier guarantees the input is
12//! non-empty, and an empty payload at allocation time signals a
13//! pipeline-internal bug rather than valid input.
14//!
15//! Read access is via [`Deref`] so existing
16//! consumers of [`Content`] inherent methods (`as_plain`, `iter`)
17//! work unchanged on `NonEmpty<Content>`.
18
19use core::ops::Deref;
20
21use super::types::Content;
22
23/// Non-emptiness wrapper for an AST payload.
24///
25/// Only constructable through [`Self::new`] (returns `Option`) or
26/// [`Self::new_unchecked`] (caller-asserted, used only by the
27/// allocator after Phase 3 classification has guaranteed the
28/// non-emptiness). Auto-derefs to the inner payload for read access.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct NonEmpty<T>(T);
31
32impl<T> NonEmpty<T> {
33 /// Construct without checking. Caller must guarantee the value is
34 /// non-empty by some other means (typically: this is a pipeline
35 /// allocator that just classified the payload as non-empty).
36 ///
37 /// Marked `#[doc(hidden)]` to discourage casual use; the typed
38 /// constructor [`Self::new`] is the supported path for outside
39 /// callers.
40 #[doc(hidden)]
41 #[must_use]
42 pub const fn new_unchecked(value: T) -> Self {
43 Self(value)
44 }
45
46 /// Consume the wrapper and return the inner payload.
47 #[must_use]
48 pub const fn into_inner(self) -> T
49 where
50 T: Copy,
51 {
52 self.0
53 }
54
55 /// Borrow the inner payload directly. Equivalent to dereferencing
56 /// through [`Deref`].
57 #[must_use]
58 pub const fn as_inner(&self) -> &T {
59 &self.0
60 }
61}
62
63impl<T> Deref for NonEmpty<T> {
64 type Target = T;
65 fn deref(&self) -> &T {
66 &self.0
67 }
68}
69
70impl<'src> NonEmpty<Content<'src>> {
71 /// Construct, returning `None` if the content is empty.
72 ///
73 /// `Plain("")` and `Segments(&[])` are both rejected; everything
74 /// else (including a single segment that happens to carry empty
75 /// text) is accepted.
76 #[must_use]
77 pub fn new(content: Content<'src>) -> Option<Self> {
78 match content {
79 Content::Plain(s) if !s.is_empty() => Some(Self(content)),
80 Content::Segments(segs) if !segs.is_empty() => Some(Self(content)),
81 _ => None,
82 }
83 }
84
85 /// Underlying [`Content`]. Convenience accessor for callers that
86 /// want a `Copy`-style move rather than a deref-borrow.
87 #[must_use]
88 pub const fn get(self) -> Content<'src> {
89 self.0
90 }
91}
92
93/// Non-empty `&'src str` newtype.
94///
95/// Used by AST fields where an empty string would never represent a
96/// valid input — e.g. [`super::Sashie::file`] (illustration path),
97/// [`super::HeadingHint::target`] (forward-reference target),
98/// [`super::Annotation::raw`] (annotation body bytes), and
99/// [`super::Kaeriten::mark`] (kanbun mark text).
100///
101/// Constructable through [`Self::new`] (returns `Option`) or
102/// [`Self::new_unchecked`] (caller-asserted). Auto-derefs to `str`
103/// for read access — `s.len()` / `s.starts_with(...)` etc. work
104/// unchanged.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
106pub struct NonEmptyStr<'src>(&'src str);
107
108impl<'src> NonEmptyStr<'src> {
109 /// Construct, returning `None` if `s` is empty.
110 #[must_use]
111 pub const fn new(s: &'src str) -> Option<Self> {
112 if s.is_empty() { None } else { Some(Self(s)) }
113 }
114
115 /// Construct without checking. Caller must guarantee the string
116 /// is non-empty. Marked `#[doc(hidden)]` to discourage casual use;
117 /// the typed constructor [`Self::new`] is the supported path.
118 #[doc(hidden)]
119 #[must_use]
120 pub const fn new_unchecked(s: &'src str) -> Self {
121 Self(s)
122 }
123
124 /// Underlying `&str`. Equivalent to dereferencing through
125 /// [`Deref`].
126 #[must_use]
127 pub const fn as_str(self) -> &'src str {
128 self.0
129 }
130}
131
132impl Deref for NonEmptyStr<'_> {
133 type Target = str;
134 fn deref(&self) -> &str {
135 self.0
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::super::types::Segment;
142 use super::*;
143
144 #[test]
145 fn new_rejects_plain_empty_string() {
146 assert!(NonEmpty::new(Content::Plain("")).is_none());
147 }
148
149 #[test]
150 fn new_rejects_empty_segments_slice() {
151 assert!(NonEmpty::new(Content::Segments(&[])).is_none());
152 }
153
154 #[test]
155 fn new_accepts_plain_non_empty() {
156 let ne = NonEmpty::new(Content::Plain("text")).expect("non-empty");
157 assert!(matches!(ne.get(), Content::Plain("text")));
158 }
159
160 #[test]
161 fn new_accepts_non_empty_segments() {
162 static SEGS: &[Segment<'static>] = &[Segment::Text("a")];
163 let ne = NonEmpty::new(Content::Segments(SEGS)).expect("non-empty");
164 assert!(matches!(ne.get(), Content::Segments(s) if s.len() == 1));
165 }
166
167 #[test]
168 fn deref_gives_inner_methods() {
169 let ne = NonEmpty::new(Content::Plain("abc")).expect("non-empty");
170 // Deref to Content lets us call inherent methods directly.
171 assert_eq!(ne.as_plain(), Some("abc"));
172 }
173
174 #[test]
175 fn empty_content_const_is_rejected() {
176 assert!(NonEmpty::new(Content::EMPTY).is_none());
177 }
178}