1 | //! # proc-macro-error |
2 | //! |
3 | //! This crate aims to make error reporting in proc-macros simple and easy to use. |
4 | //! Migrate from `panic!`-based errors for as little effort as possible! |
5 | //! |
6 | //! (Also, you can explicitly [append a dummy token stream](dummy/index.html) to your errors). |
7 | //! |
8 | //! To achieve his, this crate serves as a tiny shim around `proc_macro::Diagnostic` and |
9 | //! `compile_error!`. It detects the best way of emitting available based on compiler's version. |
10 | //! When the underlying diagnostic type is finally stabilized, this crate will simply be |
11 | //! delegating to it requiring no changes in your code! |
12 | //! |
13 | //! So you can just use this crate and have *both* some of `proc_macro::Diagnostic` functionality |
14 | //! available on stable ahead of time *and* your error-reporting code future-proof. |
15 | //! |
16 | //! ## Cargo features |
17 | //! |
18 | //! This crate provides *enabled by default* `syn-error` feature that gates |
19 | //! `impl From<syn::Error> for Diagnostic` conversion. If you don't use `syn` and want |
20 | //! to cut off some of compilation time, you can disable it via |
21 | //! |
22 | //! ```toml |
23 | //! [dependencies] |
24 | //! proc-macro-error = { version = "1", default-features = false } |
25 | //! ``` |
26 | //! |
27 | //! ***Please note that disabling this feature makes sense only if you don't depend on `syn` |
28 | //! directly or indirectly, and you very likely do.** |
29 | //! |
30 | //! ## Real world examples |
31 | //! |
32 | //! * [`structopt-derive`](https://github.com/TeXitoi/structopt/tree/master/structopt-derive) |
33 | //! (abort-like usage) |
34 | //! * [`auto-impl`](https://github.com/auto-impl-rs/auto_impl/) (emit-like usage) |
35 | //! |
36 | //! ## Limitations |
37 | //! |
38 | //! - Warnings are emitted only on nightly, they are ignored on stable. |
39 | //! - "help" suggestions can't have their own span info on stable, |
40 | //! (essentially inheriting the parent span). |
41 | //! - If a panic occurs somewhere in your macro no errors will be displayed. This is not a |
42 | //! technical limitation but rather intentional design. `panic` is not for error reporting. |
43 | //! |
44 | //! ### `#[proc_macro_error]` attribute |
45 | //! |
46 | //! **This attribute MUST be present on the top level of your macro** (the function |
47 | //! annotated with any of `#[proc_macro]`, `#[proc_macro_derive]`, `#[proc_macro_attribute]`). |
48 | //! |
49 | //! This attribute performs the setup and cleanup necessary to make things work. |
50 | //! |
51 | //! In most cases you'll need the simple `#[proc_macro_error]` form without any |
52 | //! additional settings. Feel free to [skip the "Syntax" section](#macros). |
53 | //! |
54 | //! #### Syntax |
55 | //! |
56 | //! `#[proc_macro_error]` or `#[proc_macro_error(settings...)]`, where `settings...` |
57 | //! is a comma-separated list of: |
58 | //! |
59 | //! - `proc_macro_hack`: |
60 | //! |
61 | //! In order to correctly cooperate with `#[proc_macro_hack]`, `#[proc_macro_error]` |
62 | //! attribute must be placed *before* (above) it, like this: |
63 | //! |
64 | //! ```no_run |
65 | //! # use proc_macro2::TokenStream; |
66 | //! # const IGNORE: &str = " |
67 | //! #[proc_macro_error] |
68 | //! #[proc_macro_hack] |
69 | //! #[proc_macro] |
70 | //! # " ; |
71 | //! fn my_macro(input: TokenStream) -> TokenStream { |
72 | //! unimplemented!() |
73 | //! } |
74 | //! ``` |
75 | //! |
76 | //! If, for some reason, you can't place it like that you can use |
77 | //! `#[proc_macro_error(proc_macro_hack)]` instead. |
78 | //! |
79 | //! # Note |
80 | //! |
81 | //! If `proc-macro-hack` was detected (by any means) `allow_not_macro` |
82 | //! and `assert_unwind_safe` will be applied automatically. |
83 | //! |
84 | //! - `allow_not_macro`: |
85 | //! |
86 | //! By default, the attribute checks that it's applied to a proc-macro. |
87 | //! If none of `#[proc_macro]`, `#[proc_macro_derive]` nor `#[proc_macro_attribute]` are |
88 | //! present it will panic. It's the intention - this crate is supposed to be used only with |
89 | //! proc-macros. |
90 | //! |
91 | //! This setting is made to bypass the check, useful in certain circumstances. |
92 | //! |
93 | //! Pay attention: the function this attribute is applied to must return |
94 | //! `proc_macro::TokenStream`. |
95 | //! |
96 | //! This setting is implied if `proc-macro-hack` was detected. |
97 | //! |
98 | //! - `assert_unwind_safe`: |
99 | //! |
100 | //! By default, your code must be [unwind safe]. If your code is not unwind safe, |
101 | //! but you believe it's correct, you can use this setting to bypass the check. |
102 | //! You would need this for code that uses `lazy_static` or `thread_local` with |
103 | //! `Cell/RefCell` inside (and the like). |
104 | //! |
105 | //! This setting is implied if `#[proc_macro_error]` is applied to a function |
106 | //! marked as `#[proc_macro]`, `#[proc_macro_derive]` or `#[proc_macro_attribute]`. |
107 | //! |
108 | //! This setting is also implied if `proc-macro-hack` was detected. |
109 | //! |
110 | //! ## Macros |
111 | //! |
112 | //! Most of the time you want to use the macros. Syntax is described in the next section below. |
113 | //! |
114 | //! You'll need to decide how you want to emit errors: |
115 | //! |
116 | //! * Emit the error and abort. Very much panic-like usage. Served by [`abort!`] and |
117 | //! [`abort_call_site!`]. |
118 | //! * Emit the error but do not abort right away, looking for other errors to report. |
119 | //! Served by [`emit_error!`] and [`emit_call_site_error!`]. |
120 | //! |
121 | //! You **can** mix these usages. |
122 | //! |
123 | //! `abort` and `emit_error` take a "source span" as the first argument. This source |
124 | //! will be used to highlight the place the error originates from. It must be one of: |
125 | //! |
126 | //! * *Something* that implements [`ToTokens`] (most types in `syn` and `proc-macro2` do). |
127 | //! This source is the preferable one since it doesn't lose span information on multi-token |
128 | //! spans, see [this issue](https://gitlab.com/CreepySkeleton/proc-macro-error/-/issues/6) |
129 | //! for details. |
130 | //! * [`proc_macro::Span`] |
131 | //! * [`proc-macro2::Span`] |
132 | //! |
133 | //! The rest is your message in format-like style. |
134 | //! |
135 | //! See [the next section](#syntax-1) for detailed syntax. |
136 | //! |
137 | //! - [`abort!`]: |
138 | //! |
139 | //! Very much panic-like usage - abort right away and show the error. |
140 | //! Expands to [`!`] (never type). |
141 | //! |
142 | //! - [`abort_call_site!`]: |
143 | //! |
144 | //! Shortcut for `abort!(Span::call_site(), ...)`. Expands to [`!`] (never type). |
145 | //! |
146 | //! - [`emit_error!`]: |
147 | //! |
148 | //! [`proc_macro::Diagnostic`]-like usage - emit the error but keep going, |
149 | //! looking for other errors to report. |
150 | //! The compilation will fail nonetheless. Expands to [`()`] (unit type). |
151 | //! |
152 | //! - [`emit_call_site_error!`]: |
153 | //! |
154 | //! Shortcut for `emit_error!(Span::call_site(), ...)`. Expands to [`()`] (unit type). |
155 | //! |
156 | //! - [`emit_warning!`]: |
157 | //! |
158 | //! Like `emit_error!` but emit a warning instead of error. The compilation won't fail |
159 | //! because of warnings. |
160 | //! Expands to [`()`] (unit type). |
161 | //! |
162 | //! **Beware**: warnings are nightly only, they are completely ignored on stable. |
163 | //! |
164 | //! - [`emit_call_site_warning!`]: |
165 | //! |
166 | //! Shortcut for `emit_warning!(Span::call_site(), ...)`. Expands to [`()`] (unit type). |
167 | //! |
168 | //! - [`diagnostic`]: |
169 | //! |
170 | //! Build an instance of `Diagnostic` in format-like style. |
171 | //! |
172 | //! #### Syntax |
173 | //! |
174 | //! All the macros have pretty much the same syntax: |
175 | //! |
176 | //! 1. ```ignore |
177 | //! abort!(single_expr) |
178 | //! ``` |
179 | //! Shortcut for `Diagnostic::from(expr).abort()`. |
180 | //! |
181 | //! 2. ```ignore |
182 | //! abort!(span, message) |
183 | //! ``` |
184 | //! The first argument is an expression the span info should be taken from. |
185 | //! |
186 | //! The second argument is the error message, it must implement [`ToString`]. |
187 | //! |
188 | //! 3. ```ignore |
189 | //! abort!(span, format_literal, format_args...) |
190 | //! ``` |
191 | //! |
192 | //! This form is pretty much the same as 2, except `format!(format_literal, format_args...)` |
193 | //! will be used to for the message instead of [`ToString`]. |
194 | //! |
195 | //! That's it. `abort!`, `emit_warning`, `emit_error` share this exact syntax. |
196 | //! |
197 | //! `abort_call_site!`, `emit_call_site_warning`, `emit_call_site_error` lack 1 form |
198 | //! and do not take span in 2'th and 3'th forms. Those are essentially shortcuts for |
199 | //! `macro!(Span::call_site(), args...)`. |
200 | //! |
201 | //! `diagnostic!` requires a [`Level`] instance between `span` and second argument |
202 | //! (1'th form is the same). |
203 | //! |
204 | //! > **Important!** |
205 | //! > |
206 | //! > If you have some type from `proc_macro` or `syn` to point to, do not call `.span()` |
207 | //! > on it but rather use it directly: |
208 | //! > ```no_run |
209 | //! > # use proc_macro_error::abort; |
210 | //! > # let input = proc_macro2::TokenStream::new(); |
211 | //! > let ty: syn::Type = syn::parse2(input).unwrap(); |
212 | //! > abort!(ty, "BOOM" ); |
213 | //! > // ^^ <-- avoid .span() |
214 | //! > ``` |
215 | //! > |
216 | //! > `.span()` calls work too, but you may experience regressions in message quality. |
217 | //! |
218 | //! #### Note attachments |
219 | //! |
220 | //! 3. Every macro can have "note" attachments (only 2 and 3 form). |
221 | //! ```ignore |
222 | //! let opt_help = if have_some_info { Some("did you mean `this`?" ) } else { None }; |
223 | //! |
224 | //! abort!( |
225 | //! span, message; // <--- attachments start with `;` (semicolon) |
226 | //! |
227 | //! help = "format {} {}" , "arg1" , "arg2" ; // <--- every attachment ends with `;`, |
228 | //! // maybe except the last one |
229 | //! |
230 | //! note = "to_string" ; // <--- one arg uses `.to_string()` instead of `format!()` |
231 | //! |
232 | //! yay = "I see what {} did here" , "you" ; // <--- "help =" and "hint =" are mapped |
233 | //! // to Diagnostic::help, |
234 | //! // anything else is Diagnostic::note |
235 | //! |
236 | //! wow = note_span => "custom span" ; // <--- attachments can have their own span |
237 | //! // it takes effect only on nightly though |
238 | //! |
239 | //! hint =? opt_help; // <-- "optional" attachment, get displayed only if `Some` |
240 | //! // must be single `Option` expression |
241 | //! |
242 | //! note =? note_span => opt_help // <-- optional attachments can have custom spans too |
243 | //! ); |
244 | //! ``` |
245 | //! |
246 | |
247 | //! ### Diagnostic type |
248 | //! |
249 | //! [`Diagnostic`] type is intentionally designed to be API compatible with [`proc_macro::Diagnostic`]. |
250 | //! Not all API is implemented, only the part that can be reasonably implemented on stable. |
251 | //! |
252 | //! |
253 | //! [`abort!`]: macro.abort.html |
254 | //! [`abort_call_site!`]: macro.abort_call_site.html |
255 | //! [`emit_warning!`]: macro.emit_warning.html |
256 | //! [`emit_error!`]: macro.emit_error.html |
257 | //! [`emit_call_site_warning!`]: macro.emit_call_site_error.html |
258 | //! [`emit_call_site_error!`]: macro.emit_call_site_warning.html |
259 | //! [`diagnostic!`]: macro.diagnostic.html |
260 | //! [`Diagnostic`]: struct.Diagnostic.html |
261 | //! |
262 | //! [`proc_macro::Span`]: https://doc.rust-lang.org/proc_macro/struct.Span.html |
263 | //! [`proc_macro::Diagnostic`]: https://doc.rust-lang.org/proc_macro/struct.Diagnostic.html |
264 | //! |
265 | //! [unwind safe]: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html#what-is-unwind-safety |
266 | //! [`!`]: https://doc.rust-lang.org/std/primitive.never.html |
267 | //! [`()`]: https://doc.rust-lang.org/std/primitive.unit.html |
268 | //! [`ToString`]: https://doc.rust-lang.org/std/string/trait.ToString.html |
269 | //! |
270 | //! [`proc-macro2::Span`]: https://docs.rs/proc-macro2/1.0.10/proc_macro2/struct.Span.html |
271 | //! [`ToTokens`]: https://docs.rs/quote/1.0.3/quote/trait.ToTokens.html |
272 | //! |
273 | |
274 | #![cfg_attr (not(use_fallback), feature(proc_macro_diagnostic))] |
275 | #![forbid (unsafe_code)] |
276 | #![allow (clippy::needless_doctest_main)] |
277 | |
278 | extern crate proc_macro; |
279 | |
280 | pub use crate::{ |
281 | diagnostic::{Diagnostic, DiagnosticExt, Level}, |
282 | dummy::{append_dummy, set_dummy}, |
283 | }; |
284 | pub use proc_macro_error_attr::proc_macro_error; |
285 | |
286 | use proc_macro2::Span; |
287 | use quote::{quote, ToTokens}; |
288 | |
289 | use std::cell::Cell; |
290 | use std::panic::{catch_unwind, resume_unwind, UnwindSafe}; |
291 | |
292 | pub mod dummy; |
293 | |
294 | mod diagnostic; |
295 | mod macros; |
296 | mod sealed; |
297 | |
298 | #[cfg (use_fallback)] |
299 | #[path = "imp/fallback.rs" ] |
300 | mod imp; |
301 | |
302 | #[cfg (not(use_fallback))] |
303 | #[path = "imp/delegate.rs" ] |
304 | mod imp; |
305 | |
306 | #[derive (Debug, Clone, Copy)] |
307 | pub struct SpanRange { |
308 | pub first: Span, |
309 | pub last: Span, |
310 | } |
311 | |
312 | impl SpanRange { |
313 | /// Create a range with the `first` and `last` spans being the same. |
314 | pub fn single_span(span: Span) -> Self { |
315 | SpanRange { |
316 | first: span, |
317 | last: span, |
318 | } |
319 | } |
320 | |
321 | /// Create a `SpanRange` resolving at call site. |
322 | pub fn call_site() -> Self { |
323 | SpanRange::single_span(Span::call_site()) |
324 | } |
325 | |
326 | /// Construct span range from a `TokenStream`. This method always preserves all the |
327 | /// range. |
328 | /// |
329 | /// ### Note |
330 | /// |
331 | /// If the stream is empty, the result is `SpanRange::call_site()`. If the stream |
332 | /// consists of only one `TokenTree`, the result is `SpanRange::single_span(tt.span())` |
333 | /// that doesn't lose anything. |
334 | pub fn from_tokens(ts: &dyn ToTokens) -> Self { |
335 | let mut spans = ts.to_token_stream().into_iter().map(|tt| tt.span()); |
336 | let first = spans.next().unwrap_or_else(|| Span::call_site()); |
337 | let last = spans.last().unwrap_or(first); |
338 | |
339 | SpanRange { first, last } |
340 | } |
341 | |
342 | /// Join two span ranges. The resulting range will start at `self.first` and end at |
343 | /// `other.last`. |
344 | pub fn join_range(self, other: SpanRange) -> Self { |
345 | SpanRange { |
346 | first: self.first, |
347 | last: other.last, |
348 | } |
349 | } |
350 | |
351 | /// Collapse the range into single span, preserving as much information as possible. |
352 | pub fn collapse(self) -> Span { |
353 | self.first.join(self.last).unwrap_or(self.first) |
354 | } |
355 | } |
356 | |
357 | /// This traits expands `Result<T, Into<Diagnostic>>` with some handy shortcuts. |
358 | pub trait ResultExt { |
359 | type Ok; |
360 | |
361 | /// Behaves like `Result::unwrap`: if self is `Ok` yield the contained value, |
362 | /// otherwise abort macro execution via `abort!`. |
363 | fn unwrap_or_abort(self) -> Self::Ok; |
364 | |
365 | /// Behaves like `Result::expect`: if self is `Ok` yield the contained value, |
366 | /// otherwise abort macro execution via `abort!`. |
367 | /// If it aborts then resulting error message will be preceded with `message`. |
368 | fn expect_or_abort(self, msg: &str) -> Self::Ok; |
369 | } |
370 | |
371 | /// This traits expands `Option` with some handy shortcuts. |
372 | pub trait OptionExt { |
373 | type Some; |
374 | |
375 | /// Behaves like `Option::expect`: if self is `Some` yield the contained value, |
376 | /// otherwise abort macro execution via `abort_call_site!`. |
377 | /// If it aborts the `message` will be used for [`compile_error!`][compl_err] invocation. |
378 | /// |
379 | /// [compl_err]: https://doc.rust-lang.org/std/macro.compile_error.html |
380 | fn expect_or_abort(self, msg: &str) -> Self::Some; |
381 | } |
382 | |
383 | /// Abort macro execution and display all the emitted errors, if any. |
384 | /// |
385 | /// Does nothing if no errors were emitted (warnings do not count). |
386 | pub fn abort_if_dirty() { |
387 | imp::abort_if_dirty(); |
388 | } |
389 | |
390 | impl<T, E: Into<Diagnostic>> ResultExt for Result<T, E> { |
391 | type Ok = T; |
392 | |
393 | fn unwrap_or_abort(self) -> T { |
394 | match self { |
395 | Ok(res: T) => res, |
396 | Err(e: E) => e.into().abort(), |
397 | } |
398 | } |
399 | |
400 | fn expect_or_abort(self, message: &str) -> T { |
401 | match self { |
402 | Ok(res: T) => res, |
403 | Err(e: E) => { |
404 | let mut e = e.into(); |
405 | e.msg = format!(" {}: {}" , message, e.msg); |
406 | e.abort() |
407 | } |
408 | } |
409 | } |
410 | } |
411 | |
412 | impl<T> OptionExt for Option<T> { |
413 | type Some = T; |
414 | |
415 | fn expect_or_abort(self, message: &str) -> T { |
416 | match self { |
417 | Some(res: T) => res, |
418 | None => abort_call_site!(message), |
419 | } |
420 | } |
421 | } |
422 | |
423 | /// This is the entry point for a proc-macro. |
424 | /// |
425 | /// **NOT PUBLIC API, SUBJECT TO CHANGE WITHOUT ANY NOTICE** |
426 | #[doc (hidden)] |
427 | pub fn entry_point<F>(f: F, proc_macro_hack: bool) -> proc_macro::TokenStream |
428 | where |
429 | F: FnOnce() -> proc_macro::TokenStream + UnwindSafe, |
430 | { |
431 | ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() + 1)); |
432 | let caught = catch_unwind(f); |
433 | let dummy = dummy::cleanup(); |
434 | let err_storage = imp::cleanup(); |
435 | ENTERED_ENTRY_POINT.with(|flag| flag.set(flag.get() - 1)); |
436 | |
437 | let gen_error = || { |
438 | if proc_macro_hack { |
439 | quote! {{ |
440 | macro_rules! proc_macro_call { |
441 | () => ( unimplemented!() ) |
442 | } |
443 | |
444 | #(#err_storage)* |
445 | #dummy |
446 | |
447 | unimplemented!() |
448 | }} |
449 | } else { |
450 | quote!( #(#err_storage)* #dummy ) |
451 | } |
452 | }; |
453 | |
454 | match caught { |
455 | Ok(ts) => { |
456 | if err_storage.is_empty() { |
457 | ts |
458 | } else { |
459 | gen_error().into() |
460 | } |
461 | } |
462 | |
463 | Err(boxed) => match boxed.downcast::<AbortNow>() { |
464 | Ok(_) => gen_error().into(), |
465 | Err(boxed) => resume_unwind(boxed), |
466 | }, |
467 | } |
468 | } |
469 | |
470 | fn abort_now() -> ! { |
471 | check_correctness(); |
472 | panic!(AbortNow) |
473 | } |
474 | |
475 | thread_local! { |
476 | static ENTERED_ENTRY_POINT: Cell<usize> = Cell::new(0); |
477 | } |
478 | |
479 | struct AbortNow; |
480 | |
481 | fn check_correctness() { |
482 | if ENTERED_ENTRY_POINT.with(|flag: &Cell| flag.get()) == 0 { |
483 | panic!( |
484 | "proc-macro-error API cannot be used outside of `entry_point` invocation, \ |
485 | perhaps you forgot to annotate your #[proc_macro] function with `#[proc_macro_error]" |
486 | ); |
487 | } |
488 | } |
489 | |
490 | /// **ALL THE STUFF INSIDE IS NOT PUBLIC API!!!** |
491 | #[doc (hidden)] |
492 | pub mod __export { |
493 | // reexports for use in macros |
494 | pub extern crate proc_macro; |
495 | pub extern crate proc_macro2; |
496 | |
497 | use proc_macro2::Span; |
498 | use quote::ToTokens; |
499 | |
500 | use crate::SpanRange; |
501 | |
502 | // inspired by |
503 | // https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md#simple-application |
504 | |
505 | pub trait SpanAsSpanRange { |
506 | #[allow (non_snake_case)] |
507 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; |
508 | } |
509 | |
510 | pub trait Span2AsSpanRange { |
511 | #[allow (non_snake_case)] |
512 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; |
513 | } |
514 | |
515 | pub trait ToTokensAsSpanRange { |
516 | #[allow (non_snake_case)] |
517 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; |
518 | } |
519 | |
520 | pub trait SpanRangeAsSpanRange { |
521 | #[allow (non_snake_case)] |
522 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange; |
523 | } |
524 | |
525 | impl<T: ToTokens> ToTokensAsSpanRange for &T { |
526 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { |
527 | let mut ts = self.to_token_stream().into_iter(); |
528 | let first = ts |
529 | .next() |
530 | .map(|tt| tt.span()) |
531 | .unwrap_or_else(Span::call_site); |
532 | let last = ts.last().map(|tt| tt.span()).unwrap_or(first); |
533 | SpanRange { first, last } |
534 | } |
535 | } |
536 | |
537 | impl Span2AsSpanRange for Span { |
538 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { |
539 | SpanRange { |
540 | first: *self, |
541 | last: *self, |
542 | } |
543 | } |
544 | } |
545 | |
546 | impl SpanAsSpanRange for proc_macro::Span { |
547 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { |
548 | SpanRange { |
549 | first: self.clone().into(), |
550 | last: self.clone().into(), |
551 | } |
552 | } |
553 | } |
554 | |
555 | impl SpanRangeAsSpanRange for SpanRange { |
556 | fn FIRST_ARG_MUST_EITHER_BE_Span_OR_IMPLEMENT_ToTokens_OR_BE_SpanRange(&self) -> SpanRange { |
557 | *self |
558 | } |
559 | } |
560 | } |
561 | |