Skip to main content

hwpforge_core/
error.rs

1//! Error types for the HwpForge Core crate.
2//!
3//! All validation and structural errors produced by Core live here.
4//! Error codes occupy the **2000-2999** range, extending the Foundation
5//! convention (1000-1999).
6//!
7//! # Error Hierarchy
8//!
9//! [`CoreError`] is the top-level error. It wraps:
10//! - [`ValidationError`] -- document structure validation failures
11//! - [`FoundationError`] -- propagated Foundation errors
12//! - `InvalidStructure` -- catch-all for structural issues
13//!
14//! # Examples
15//!
16//! ```
17//! use hwpforge_core::error::{CoreError, ValidationError};
18//!
19//! let err = CoreError::from(ValidationError::EmptyDocument);
20//! assert!(err.to_string().contains("section"));
21//! ```
22
23use hwpforge_foundation::FoundationError;
24
25/// Top-level error type for the Core crate.
26///
27/// Every fallible operation in Core returns `Result<T, CoreError>`.
28/// Use the `?` operator freely -- both [`ValidationError`] and
29/// [`FoundationError`] convert via `#[from]`.
30///
31/// # Examples
32///
33/// ```
34/// use hwpforge_core::error::{CoreError, ValidationError};
35///
36/// fn example() -> Result<(), CoreError> {
37///     Err(ValidationError::EmptyDocument)?
38/// }
39/// assert!(example().is_err());
40/// ```
41#[derive(Debug, thiserror::Error)]
42#[non_exhaustive]
43pub enum CoreError {
44    /// Document validation failed.
45    #[error("Document validation failed: {0}")]
46    Validation(#[from] ValidationError),
47
48    /// A Foundation-layer error propagated upward.
49    #[error("Foundation error: {0}")]
50    Foundation(#[from] FoundationError),
51
52    /// Structural issue that is not a validation failure.
53    #[error("Invalid document structure in {context}: {reason}")]
54    InvalidStructure {
55        /// Where in the document the issue was found.
56        context: String,
57        /// What went wrong.
58        reason: String,
59    },
60}
61
62/// Specific validation failures with precise location context.
63///
64/// Every variant carries enough information to pinpoint the
65/// exact location of the problem (section index, paragraph index, etc.).
66///
67/// Marked `#[non_exhaustive]` so future phases can add variants
68/// without a breaking change.
69///
70/// # Examples
71///
72/// ```
73/// use hwpforge_core::error::ValidationError;
74///
75/// let err = ValidationError::EmptySection { section_index: 2 };
76/// assert!(err.to_string().contains("Section 2"));
77/// ```
78#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
79#[non_exhaustive]
80pub enum ValidationError {
81    /// The document contains zero sections.
82    #[error("Empty document: at least 1 section required")]
83    EmptyDocument,
84
85    /// A section contains zero paragraphs.
86    #[error("Section {section_index} has no paragraphs")]
87    EmptySection {
88        /// Zero-based index of the offending section.
89        section_index: usize,
90    },
91
92    /// A paragraph contains zero runs.
93    #[error("Paragraph has no runs (section {section_index}, paragraph {paragraph_index})")]
94    EmptyParagraph {
95        /// Zero-based section index.
96        section_index: usize,
97        /// Zero-based paragraph index within the section.
98        paragraph_index: usize,
99    },
100
101    /// A table contains zero rows.
102    #[error(
103        "Table has no rows (section {section_index}, paragraph {paragraph_index}, run {run_index})"
104    )]
105    EmptyTable {
106        /// Zero-based section index.
107        section_index: usize,
108        /// Zero-based paragraph index.
109        paragraph_index: usize,
110        /// Zero-based run index.
111        run_index: usize,
112    },
113
114    /// A table row contains zero cells.
115    #[error("Table row has no cells (section {section_index}, paragraph {paragraph_index}, run {run_index}, row {row_index})")]
116    EmptyTableRow {
117        /// Zero-based section index.
118        section_index: usize,
119        /// Zero-based paragraph index.
120        paragraph_index: usize,
121        /// Zero-based run index.
122        run_index: usize,
123        /// Zero-based row index within the table.
124        row_index: usize,
125    },
126
127    /// A span value (col_span or row_span) is zero.
128    #[error("Invalid span: {field} = {value} (section {section_index}, paragraph {paragraph_index}, run {run_index}, row {row_index}, cell {cell_index})")]
129    InvalidSpan {
130        /// Which span field failed ("col_span" or "row_span").
131        field: &'static str,
132        /// The invalid value.
133        value: u16,
134        /// Zero-based section index.
135        section_index: usize,
136        /// Zero-based paragraph index.
137        paragraph_index: usize,
138        /// Zero-based run index.
139        run_index: usize,
140        /// Zero-based row index.
141        row_index: usize,
142        /// Zero-based cell index.
143        cell_index: usize,
144    },
145
146    /// A TextBox control contains zero paragraphs.
147    #[error("TextBox has no paragraphs (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
148    EmptyTextBox {
149        /// Zero-based section index.
150        section_index: usize,
151        /// Zero-based paragraph index.
152        paragraph_index: usize,
153        /// Zero-based run index.
154        run_index: usize,
155    },
156
157    /// A Group control contains a child that is not a drawing shape.
158    #[error("Group has a non-shape child (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
159    InvalidGroupChild {
160        /// Zero-based section index.
161        section_index: usize,
162        /// Zero-based paragraph index.
163        paragraph_index: usize,
164        /// Zero-based run index.
165        run_index: usize,
166    },
167
168    /// A Footnote control contains zero paragraphs.
169    #[error("Footnote has no paragraphs (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
170    EmptyFootnote {
171        /// Zero-based section index.
172        section_index: usize,
173        /// Zero-based paragraph index.
174        paragraph_index: usize,
175        /// Zero-based run index.
176        run_index: usize,
177    },
178
179    /// An Endnote control contains zero paragraphs.
180    #[error("Endnote has no paragraphs (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
181    EmptyEndnote {
182        /// Zero-based section index.
183        section_index: usize,
184        /// Zero-based paragraph index.
185        paragraph_index: usize,
186        /// Zero-based run index.
187        run_index: usize,
188    },
189
190    /// A Polygon control has fewer than 3 vertices.
191    #[error("Polygon has invalid vertex count: {vertex_count} (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
192    InvalidPolygon {
193        /// Zero-based section index.
194        section_index: usize,
195        /// Zero-based paragraph index.
196        paragraph_index: usize,
197        /// Zero-based run index.
198        run_index: usize,
199        /// Number of vertices found.
200        vertex_count: usize,
201    },
202
203    /// A shape (Ellipse or Polygon) has zero width or height.
204    #[error("Shape {shape_type} has zero dimension (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
205    InvalidShapeDimension {
206        /// Zero-based section index.
207        section_index: usize,
208        /// Zero-based paragraph index.
209        paragraph_index: usize,
210        /// Zero-based run index.
211        run_index: usize,
212        /// Type of shape ("Ellipse" or "Polygon").
213        shape_type: &'static str,
214    },
215
216    /// A Chart control has no data series.
217    #[error("Chart has empty data (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
218    EmptyChartData {
219        /// Zero-based section index.
220        section_index: usize,
221        /// Zero-based paragraph index.
222        paragraph_index: usize,
223        /// Zero-based run index.
224        run_index: usize,
225    },
226
227    /// An Equation control has an empty script.
228    #[error("Equation has empty script (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
229    EmptyEquation {
230        /// Zero-based section index.
231        section_index: usize,
232        /// Zero-based paragraph index.
233        paragraph_index: usize,
234        /// Zero-based run index.
235        run_index: usize,
236    },
237
238    /// A Category chart has an empty categories list.
239    #[error("Chart has empty category labels (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
240    EmptyCategoryLabels {
241        /// Zero-based section index.
242        section_index: usize,
243        /// Zero-based paragraph index.
244        paragraph_index: usize,
245        /// Zero-based run index.
246        run_index: usize,
247    },
248
249    /// An XY series has mismatched x/y value lengths.
250    #[error("XY series '{series_name}' has mismatched lengths: x={x_len}, y={y_len} (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
251    MismatchedSeriesLengths {
252        /// Zero-based section index.
253        section_index: usize,
254        /// Zero-based paragraph index.
255        paragraph_index: usize,
256        /// Zero-based run index.
257        run_index: usize,
258        /// Name of the offending series.
259        series_name: String,
260        /// Length of x_values.
261        x_len: usize,
262        /// Length of y_values.
263        y_len: usize,
264    },
265
266    /// A table cell contains zero paragraphs.
267    #[error("Table cell has no paragraphs (section {section_index}, paragraph {paragraph_index}, run {run_index}, row {row_index}, cell {cell_index})")]
268    EmptyTableCell {
269        /// Zero-based section index.
270        section_index: usize,
271        /// Zero-based paragraph index.
272        paragraph_index: usize,
273        /// Zero-based run index.
274        run_index: usize,
275        /// Zero-based row index.
276        row_index: usize,
277        /// Zero-based cell index.
278        cell_index: usize,
279    },
280
281    /// A header row appears after a non-header row.
282    #[error("Table header row is not part of the leading header block (section {section_index}, paragraph {paragraph_index}, run {run_index}, row {row_index})")]
283    NonLeadingTableHeaderRow {
284        /// Zero-based section index.
285        section_index: usize,
286        /// Zero-based paragraph index.
287        paragraph_index: usize,
288        /// Zero-based run index.
289        run_index: usize,
290        /// Zero-based row index.
291        row_index: usize,
292    },
293
294    /// A SUMMERY token (`UnknownSummery.token`) is empty or does not start with `$`.
295    /// HWPX SUMMERY `Command` strings always have the `$word` shape (Wave 12n).
296    #[error("Invalid SUMMERY token {token:?} (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
297    InvalidSummeryToken {
298        /// Zero-based section index.
299        section_index: usize,
300        /// Zero-based paragraph index.
301        paragraph_index: usize,
302        /// Zero-based run index.
303        run_index: usize,
304        /// The offending token.
305        token: String,
306    },
307
308    /// `DateCodeField.is_time_mode` does not match the `T` prefix convention
309    /// of the raw command, or the raw command is empty (Wave 12n).
310    #[error("DateCodeField mismatch: is_time_mode={is_time_mode}, raw_command={raw_command:?} (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
311    DateCodeFieldMismatch {
312        /// Zero-based section index.
313        section_index: usize,
314        /// Zero-based paragraph index.
315        paragraph_index: usize,
316        /// Zero-based run index.
317        run_index: usize,
318        /// The claimed time-mode flag.
319        is_time_mode: bool,
320        /// The raw HWP5 command string.
321        raw_command: String,
322    },
323
324    /// `Control::InlinePageNumber` carries a known `kind` whose canonical
325    /// wire `raw_flag` does not match the actual `raw_flag` field. The
326    /// encoder ignores `raw_flag` for known kinds, so a mismatch is silent
327    /// drift that validation must catch (Wave 12n architect review).
328    #[error("InlinePageNumber mismatch: kind {kind} expects raw_flag {expected:#X}, found {actual:#X} (section {section_index}, paragraph {paragraph_index}, run {run_index})")]
329    InlinePageNumberMismatch {
330        /// Zero-based section index.
331        section_index: usize,
332        /// Zero-based paragraph index.
333        paragraph_index: usize,
334        /// Zero-based run index.
335        run_index: usize,
336        /// Display name of the typed kind variant (e.g. `"CurrentPage"`).
337        kind: &'static str,
338        /// The canonical wire flag for the typed kind.
339        expected: u32,
340        /// The actual `raw_flag` value carried by the control.
341        actual: u32,
342    },
343}
344
345// ---------------------------------------------------------------------------
346// ErrorCode integration
347// ---------------------------------------------------------------------------
348
349/// Core validation error codes (2000-2099).
350///
351/// Extends Foundation's [`ErrorCode`](hwpforge_foundation::ErrorCode) convention into the Core range.
352///
353/// # Examples
354///
355/// ```
356/// use hwpforge_core::error::CoreErrorCode;
357///
358/// assert_eq!(CoreErrorCode::EmptyDocument as u32, 2000);
359/// ```
360#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
361#[repr(u32)]
362pub enum CoreErrorCode {
363    /// Empty document (no sections).
364    EmptyDocument = 2000,
365    /// Empty section (no paragraphs).
366    EmptySection = 2001,
367    /// Empty paragraph (no runs).
368    EmptyParagraph = 2002,
369    /// Empty table (no rows).
370    EmptyTable = 2003,
371    /// Empty table row (no cells).
372    EmptyTableRow = 2004,
373    /// Invalid span value (zero).
374    InvalidSpan = 2005,
375    /// Empty TextBox (no paragraphs).
376    EmptyTextBox = 2006,
377    /// Empty Footnote (no paragraphs).
378    EmptyFootnote = 2007,
379    /// Empty table cell (no paragraphs).
380    EmptyTableCell = 2008,
381    /// Empty Endnote (no paragraphs).
382    EmptyEndnote = 2009,
383    /// Invalid Polygon (fewer than 3 vertices).
384    InvalidPolygon = 2010,
385    /// Invalid shape dimension (zero width or height).
386    InvalidShapeDimension = 2011,
387    /// Empty Equation (empty script).
388    EmptyEquation = 2012,
389    /// Empty Chart data (no series).
390    EmptyChartData = 2013,
391    /// Empty category labels in a Category chart.
392    EmptyCategoryLabels = 2014,
393    /// Mismatched x/y value lengths in an XY series.
394    MismatchedSeriesLengths = 2015,
395    /// Non-leading table header row.
396    NonLeadingTableHeaderRow = 2016,
397    /// Invalid SUMMERY `$token` (Wave 12n).
398    InvalidSummeryToken = 2017,
399    /// `DateCodeField.is_time_mode` ↔ raw command `T`-prefix mismatch (Wave 12n).
400    DateCodeFieldMismatch = 2018,
401    /// `InlinePageNumber.kind` ↔ `raw_flag` mismatch (Wave 12n).
402    InlinePageNumberMismatch = 2019,
403    /// A Group control has a non-shape child.
404    InvalidGroupChild = 2020,
405    /// Invalid document structure.
406    InvalidStructure = 2100,
407}
408
409impl std::fmt::Display for CoreErrorCode {
410    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
411        write!(f, "E{:04}", *self as u32)
412    }
413}
414
415impl ValidationError {
416    /// Returns the numeric error code for this validation error.
417    pub fn code(&self) -> CoreErrorCode {
418        match self {
419            Self::EmptyDocument => CoreErrorCode::EmptyDocument,
420            Self::EmptySection { .. } => CoreErrorCode::EmptySection,
421            Self::EmptyParagraph { .. } => CoreErrorCode::EmptyParagraph,
422            Self::EmptyTable { .. } => CoreErrorCode::EmptyTable,
423            Self::EmptyTableRow { .. } => CoreErrorCode::EmptyTableRow,
424            Self::InvalidSpan { .. } => CoreErrorCode::InvalidSpan,
425            Self::NonLeadingTableHeaderRow { .. } => CoreErrorCode::NonLeadingTableHeaderRow,
426            Self::EmptyTextBox { .. } => CoreErrorCode::EmptyTextBox,
427            Self::EmptyFootnote { .. } => CoreErrorCode::EmptyFootnote,
428            Self::InvalidGroupChild { .. } => CoreErrorCode::InvalidGroupChild,
429            Self::EmptyTableCell { .. } => CoreErrorCode::EmptyTableCell,
430            Self::EmptyEndnote { .. } => CoreErrorCode::EmptyEndnote,
431            Self::InvalidPolygon { .. } => CoreErrorCode::InvalidPolygon,
432            Self::InvalidShapeDimension { .. } => CoreErrorCode::InvalidShapeDimension,
433            Self::EmptyChartData { .. } => CoreErrorCode::EmptyChartData,
434            Self::EmptyCategoryLabels { .. } => CoreErrorCode::EmptyCategoryLabels,
435            Self::MismatchedSeriesLengths { .. } => CoreErrorCode::MismatchedSeriesLengths,
436            Self::EmptyEquation { .. } => CoreErrorCode::EmptyEquation,
437            Self::InvalidSummeryToken { .. } => CoreErrorCode::InvalidSummeryToken,
438            Self::DateCodeFieldMismatch { .. } => CoreErrorCode::DateCodeFieldMismatch,
439            Self::InlinePageNumberMismatch { .. } => CoreErrorCode::InlinePageNumberMismatch,
440        }
441    }
442}
443
444/// Convenience type alias for Core operations.
445///
446/// # Examples
447///
448/// ```
449/// use hwpforge_core::error::CoreResult;
450///
451/// fn always_ok() -> CoreResult<i32> {
452///     Ok(42)
453/// }
454/// assert_eq!(always_ok().unwrap(), 42);
455/// ```
456pub type CoreResult<T> = Result<T, CoreError>;
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    // === Variant construction ===
463
464    #[test]
465    fn empty_document_displays_message() {
466        let err = ValidationError::EmptyDocument;
467        let msg = err.to_string();
468        assert!(msg.contains("section"), "msg: {msg}");
469        assert!(msg.contains("at least 1"), "msg: {msg}");
470    }
471
472    #[test]
473    fn empty_section_displays_index() {
474        let err = ValidationError::EmptySection { section_index: 3 };
475        let msg = err.to_string();
476        assert!(msg.contains("3"), "msg: {msg}");
477        assert!(msg.contains("no paragraphs"), "msg: {msg}");
478    }
479
480    #[test]
481    fn empty_paragraph_displays_location() {
482        let err = ValidationError::EmptyParagraph { section_index: 1, paragraph_index: 5 };
483        let msg = err.to_string();
484        assert!(msg.contains("section 1"), "msg: {msg}");
485        assert!(msg.contains("paragraph 5"), "msg: {msg}");
486    }
487
488    #[test]
489    fn empty_table_displays_location() {
490        let err =
491            ValidationError::EmptyTable { section_index: 0, paragraph_index: 2, run_index: 0 };
492        let msg = err.to_string();
493        assert!(msg.contains("no rows"), "msg: {msg}");
494    }
495
496    #[test]
497    fn empty_table_row_displays_location() {
498        let err = ValidationError::EmptyTableRow {
499            section_index: 0,
500            paragraph_index: 0,
501            run_index: 0,
502            row_index: 1,
503        };
504        let msg = err.to_string();
505        assert!(msg.contains("row 1"), "msg: {msg}");
506        assert!(msg.contains("no cells"), "msg: {msg}");
507    }
508
509    #[test]
510    fn invalid_span_displays_all_context() {
511        let err = ValidationError::InvalidSpan {
512            field: "col_span",
513            value: 0,
514            section_index: 0,
515            paragraph_index: 1,
516            run_index: 0,
517            row_index: 0,
518            cell_index: 2,
519        };
520        let msg = err.to_string();
521        assert!(msg.contains("col_span"), "msg: {msg}");
522        assert!(msg.contains("= 0"), "msg: {msg}");
523        assert!(msg.contains("cell 2"), "msg: {msg}");
524    }
525
526    #[test]
527    fn empty_text_box_displays_location() {
528        let err =
529            ValidationError::EmptyTextBox { section_index: 0, paragraph_index: 0, run_index: 1 };
530        let msg = err.to_string();
531        assert!(msg.contains("TextBox"), "msg: {msg}");
532    }
533
534    #[test]
535    fn empty_footnote_displays_location() {
536        let err =
537            ValidationError::EmptyFootnote { section_index: 0, paragraph_index: 0, run_index: 0 };
538        let msg = err.to_string();
539        assert!(msg.contains("Footnote"), "msg: {msg}");
540    }
541
542    #[test]
543    fn empty_table_cell_displays_location() {
544        let err = ValidationError::EmptyTableCell {
545            section_index: 0,
546            paragraph_index: 0,
547            run_index: 0,
548            row_index: 0,
549            cell_index: 0,
550        };
551        let msg = err.to_string();
552        assert!(msg.contains("cell"), "msg: {msg}");
553    }
554
555    // === CoreError wrapping ===
556
557    #[test]
558    fn core_error_from_validation() {
559        let ve = ValidationError::EmptyDocument;
560        let ce: CoreError = ve.into();
561        match ce {
562            CoreError::Validation(v) => assert_eq!(v, ValidationError::EmptyDocument),
563            other => panic!("expected Validation, got: {other}"),
564        }
565    }
566
567    #[test]
568    fn core_error_from_foundation() {
569        let fe =
570            FoundationError::InvalidField { field: "test".to_string(), reason: "bad".to_string() };
571        let ce: CoreError = fe.into();
572        assert!(matches!(ce, CoreError::Foundation(_)));
573    }
574
575    #[test]
576    fn core_error_invalid_structure() {
577        let ce = CoreError::InvalidStructure {
578            context: "document".to_string(),
579            reason: "circular reference".to_string(),
580        };
581        let msg = ce.to_string();
582        assert!(msg.contains("document"), "msg: {msg}");
583        assert!(msg.contains("circular"), "msg: {msg}");
584    }
585
586    // === Error codes ===
587
588    #[test]
589    fn error_codes_in_core_range() {
590        assert_eq!(CoreErrorCode::EmptyDocument as u32, 2000);
591        assert_eq!(CoreErrorCode::EmptySection as u32, 2001);
592        assert_eq!(CoreErrorCode::EmptyParagraph as u32, 2002);
593        assert_eq!(CoreErrorCode::EmptyTable as u32, 2003);
594        assert_eq!(CoreErrorCode::EmptyTableRow as u32, 2004);
595        assert_eq!(CoreErrorCode::InvalidSpan as u32, 2005);
596        assert_eq!(CoreErrorCode::EmptyTextBox as u32, 2006);
597        assert_eq!(CoreErrorCode::EmptyFootnote as u32, 2007);
598        assert_eq!(CoreErrorCode::EmptyTableCell as u32, 2008);
599        assert_eq!(CoreErrorCode::InvalidStructure as u32, 2100);
600    }
601
602    #[test]
603    fn error_code_display_format() {
604        assert_eq!(CoreErrorCode::EmptyDocument.to_string(), "E2000");
605        assert_eq!(CoreErrorCode::InvalidStructure.to_string(), "E2100");
606    }
607
608    #[test]
609    fn validation_error_code_mapping() {
610        assert_eq!(ValidationError::EmptyDocument.code(), CoreErrorCode::EmptyDocument);
611        assert_eq!(
612            ValidationError::EmptySection { section_index: 0 }.code(),
613            CoreErrorCode::EmptySection
614        );
615        assert_eq!(
616            ValidationError::EmptyParagraph { section_index: 0, paragraph_index: 0 }.code(),
617            CoreErrorCode::EmptyParagraph
618        );
619    }
620
621    // === CoreResult alias ===
622
623    #[test]
624    fn core_result_alias_works() {
625        fn ok_example() -> CoreResult<i32> {
626            Ok(42)
627        }
628        fn err_example() -> CoreResult<i32> {
629            Err(ValidationError::EmptyDocument)?
630        }
631        assert_eq!(ok_example().unwrap(), 42);
632        assert!(err_example().is_err());
633    }
634
635    // === Send + Sync ===
636
637    #[test]
638    fn errors_are_send_and_sync() {
639        fn assert_send<T: Send>() {}
640        fn assert_sync<T: Sync>() {}
641        assert_send::<CoreError>();
642        assert_sync::<CoreError>();
643        assert_send::<ValidationError>();
644        assert_sync::<ValidationError>();
645    }
646
647    // === std::error::Error ===
648
649    #[test]
650    fn core_error_implements_std_error() {
651        let err = CoreError::from(ValidationError::EmptyDocument);
652        let _: &dyn std::error::Error = &err;
653    }
654
655    // === ValidationError PartialEq ===
656
657    #[test]
658    fn validation_error_eq() {
659        let a = ValidationError::EmptyDocument;
660        let b = ValidationError::EmptyDocument;
661        let c = ValidationError::EmptySection { section_index: 0 };
662        assert_eq!(a, b);
663        assert_ne!(a, c);
664    }
665}