1use rucc_base::Symbol;
16use rucc_diag::Span;
17
18use crate::ast::{AttrList, EnumeratorList, MemberList};
19use crate::decl::TypeNameId;
20use crate::expr::ExprId;
21
22pub type DeclSpecsId = rucc_base::Idx<DeclSpecs>;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct DeclSpecs {
28 pub storage: Option<StorageClass>,
30 pub thread_local: bool,
33 pub ty: TypeSpec,
35 pub quals: Quals,
37 pub func: FuncSpecs,
39 pub align: Option<AlignSpec>,
42 pub attrs: AttrList,
44 pub span: Span,
46}
47
48impl DeclSpecs {
49 #[must_use]
51 pub const fn empty(span: Span) -> DeclSpecs {
52 DeclSpecs {
53 storage: None,
54 thread_local: false,
55 ty: TypeSpec::None,
56 quals: Quals::NONE,
57 func: FuncSpecs::NONE,
58 align: None,
59 attrs: AttrList::EMPTY,
60 span,
61 }
62 }
63
64 #[must_use]
66 pub const fn is_typedef(&self) -> bool {
67 matches!(self.storage, Some(StorageClass::Typedef))
68 }
69
70 #[must_use]
72 pub const fn deduces(&self) -> Option<Deduction> {
73 match self.ty {
74 TypeSpec::Auto(which) => Some(which),
75 _ => None,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum StorageClass {
83 Typedef,
85 Extern,
87 Static,
89 Auto,
91 Register,
93 Constexpr,
95}
96
97impl StorageClass {
98 #[must_use]
100 pub const fn spelling(self) -> &'static str {
101 match self {
102 StorageClass::Typedef => "typedef",
103 StorageClass::Extern => "extern",
104 StorageClass::Static => "static",
105 StorageClass::Auto => "auto",
106 StorageClass::Register => "register",
107 StorageClass::Constexpr => "constexpr",
108 }
109 }
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
119pub struct Quals(u8);
120
121impl Quals {
122 pub const NONE: Quals = Quals(0);
124 pub const CONST: Quals = Quals(1);
126 pub const VOLATILE: Quals = Quals(2);
128 pub const RESTRICT: Quals = Quals(4);
130 pub const ATOMIC: Quals = Quals(8);
132
133 #[inline]
135 #[must_use]
136 pub const fn has(self, other: Quals) -> bool {
137 self.0 & other.0 == other.0
138 }
139
140 #[inline]
142 #[must_use]
143 pub const fn with(self, other: Quals) -> Quals {
144 Quals(self.0 | other.0)
145 }
146
147 #[inline]
149 #[must_use]
150 pub const fn is_none(self) -> bool {
151 self.0 == 0
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
157pub struct FuncSpecs(u8);
158
159impl FuncSpecs {
160 pub const NONE: FuncSpecs = FuncSpecs(0);
162 pub const INLINE: FuncSpecs = FuncSpecs(1);
164 pub const NORETURN: FuncSpecs = FuncSpecs(2);
167
168 #[inline]
170 #[must_use]
171 pub const fn has(self, other: FuncSpecs) -> bool {
172 self.0 & other.0 == other.0
173 }
174
175 #[inline]
177 #[must_use]
178 pub const fn with(self, other: FuncSpecs) -> FuncSpecs {
179 FuncSpecs(self.0 | other.0)
180 }
181
182 #[inline]
184 #[must_use]
185 pub const fn is_none(self) -> bool {
186 self.0 == 0
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum AlignSpec {
193 Type(TypeNameId),
195 Expr(ExprId),
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum TypeSpec {
202 None,
204 Builtin(Builtin),
206 Record {
208 kind: RecordKind,
210 tag: Option<Symbol>,
212 fields: Option<MemberList>,
216 attrs: AttrList,
218 },
219 Enum {
221 tag: Option<Symbol>,
223 enumerators: Option<EnumeratorList>,
225 underlying: Option<TypeNameId>,
228 attrs: AttrList,
230 },
231 Typedef(Symbol),
233 Typeof {
235 unqual: bool,
237 operand: TypeofArg,
240 },
241 Atomic(TypeNameId),
243 Auto(Deduction),
245 VaList,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub enum Deduction {
261 Auto,
263 AutoType,
265}
266
267impl Deduction {
268 #[must_use]
270 pub const fn spelling(self) -> &'static str {
271 match self {
272 Deduction::Auto => "auto",
273 Deduction::AutoType => "__auto_type",
274 }
275 }
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum RecordKind {
281 Struct,
283 Union,
285}
286
287impl RecordKind {
288 #[must_use]
290 pub const fn spelling(self) -> &'static str {
291 match self {
292 RecordKind::Struct => "struct",
293 RecordKind::Union => "union",
294 }
295 }
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq)]
300pub enum TypeofArg {
301 Expr(ExprId),
303 Type(TypeNameId),
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
314pub struct Builtin {
315 pub set: BuiltinSet,
317 pub longs: u8,
319 pub width: Option<ExprId>,
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
325pub struct BuiltinSet(u32);
326
327impl BuiltinSet {
328 pub const NONE: BuiltinSet = BuiltinSet(0);
330 pub const VOID: BuiltinSet = BuiltinSet(1 << 0);
332 pub const BOOL: BuiltinSet = BuiltinSet(1 << 1);
334 pub const CHAR: BuiltinSet = BuiltinSet(1 << 2);
336 pub const SHORT: BuiltinSet = BuiltinSet(1 << 3);
338 pub const INT: BuiltinSet = BuiltinSet(1 << 4);
340 pub const LONG: BuiltinSet = BuiltinSet(1 << 5);
342 pub const SIGNED: BuiltinSet = BuiltinSet(1 << 6);
344 pub const UNSIGNED: BuiltinSet = BuiltinSet(1 << 7);
346 pub const FLOAT: BuiltinSet = BuiltinSet(1 << 8);
348 pub const DOUBLE: BuiltinSet = BuiltinSet(1 << 9);
350 pub const COMPLEX: BuiltinSet = BuiltinSet(1 << 10);
352 pub const IMAGINARY: BuiltinSet = BuiltinSet(1 << 11);
354 pub const INT128: BuiltinSet = BuiltinSet(1 << 12);
356 pub const FLOAT16: BuiltinSet = BuiltinSet(1 << 13);
358 pub const FLOAT32: BuiltinSet = BuiltinSet(1 << 14);
360 pub const FLOAT64: BuiltinSet = BuiltinSet(1 << 15);
362 pub const FLOAT128: BuiltinSet = BuiltinSet(1 << 16);
364 pub const FLOAT32X: BuiltinSet = BuiltinSet(1 << 17);
366 pub const FLOAT64X: BuiltinSet = BuiltinSet(1 << 18);
368 pub const FLOAT128X: BuiltinSet = BuiltinSet(1 << 19);
370 pub const FLOAT80: BuiltinSet = BuiltinSet(1 << 20);
372 pub const DECIMAL32: BuiltinSet = BuiltinSet(1 << 21);
374 pub const DECIMAL64: BuiltinSet = BuiltinSet(1 << 22);
376 pub const DECIMAL128: BuiltinSet = BuiltinSet(1 << 23);
378 pub const BIT_INT: BuiltinSet = BuiltinSet(1 << 24);
380
381 const DECIMALS: BuiltinSet =
383 BuiltinSet(Self::DECIMAL32.0 | Self::DECIMAL64.0 | Self::DECIMAL128.0);
384 const EXTENDED: BuiltinSet = BuiltinSet(
386 Self::FLOAT16.0
387 | Self::FLOAT32.0
388 | Self::FLOAT64.0
389 | Self::FLOAT128.0
390 | Self::FLOAT32X.0
391 | Self::FLOAT64X.0
392 | Self::FLOAT128X.0
393 | Self::FLOAT80.0,
394 );
395 const INTEGER: BuiltinSet =
397 BuiltinSet(Self::SHORT.0 | Self::INT.0 | Self::LONG.0 | Self::SIGNED.0 | Self::UNSIGNED.0);
398
399 #[inline]
401 #[must_use]
402 pub const fn has(self, other: BuiltinSet) -> bool {
403 self.0 & other.0 == other.0
404 }
405
406 #[inline]
408 #[must_use]
409 pub const fn has_any(self, other: BuiltinSet) -> bool {
410 self.0 & other.0 != 0
411 }
412
413 #[inline]
415 #[must_use]
416 pub const fn with(self, other: BuiltinSet) -> BuiltinSet {
417 BuiltinSet(self.0 | other.0)
418 }
419
420 #[inline]
422 #[must_use]
423 pub const fn without(self, other: BuiltinSet) -> BuiltinSet {
424 BuiltinSet(self.0 & !other.0)
425 }
426
427 #[inline]
429 #[must_use]
430 pub const fn is_none(self) -> bool {
431 self.0 == 0
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq)]
437pub enum BuiltinError {
438 Duplicate,
440 TooManyLongs,
442}
443
444impl Builtin {
445 pub const NONE: Builtin = Builtin { set: BuiltinSet::NONE, longs: 0, width: None };
447
448 pub const fn add(self, which: BuiltinSet) -> Result<Builtin, BuiltinError> {
459 if which.0 == BuiltinSet::LONG.0 {
460 if self.longs >= 2 {
461 return Err(BuiltinError::TooManyLongs);
462 }
463 let set = self.set.with(which);
464 return Ok(Builtin { set, longs: self.longs + 1, width: self.width });
465 }
466 if self.set.has(which) {
467 return Err(BuiltinError::Duplicate);
468 }
469 Ok(Builtin { set: self.set.with(which), longs: self.longs, width: self.width })
470 }
471
472 pub const fn add_bit_int(self, width: ExprId) -> Result<Builtin, BuiltinError> {
483 if self.set.has(BuiltinSet::BIT_INT) {
484 return Err(BuiltinError::Duplicate);
485 }
486 let set = self.set.with(BuiltinSet::BIT_INT);
487 Ok(Builtin { set, longs: self.longs, width: Some(width) })
488 }
489
490 #[must_use]
492 pub const fn is_none(self) -> bool {
493 self.set.is_none()
494 }
495
496 #[must_use]
502 pub fn resolve(self) -> Option<Basic> {
503 let set = self.set;
504 let longs = self.longs;
505
506 let complexity = match (set.has(BuiltinSet::COMPLEX), set.has(BuiltinSet::IMAGINARY)) {
507 (true, true) => return None,
508 (true, false) => Complexity::Complex,
509 (false, true) => Complexity::Imaginary,
510 (false, false) => Complexity::Real,
511 };
512 let set = set.without(BuiltinSet::COMPLEX.with(BuiltinSet::IMAGINARY));
513 let basic = |scalar| Some(Basic { scalar, complexity });
514 let real = |scalar| {
515 if complexity == Complexity::Real { Some(Basic { scalar, complexity }) } else { None }
516 };
517
518 if set.has(BuiltinSet::SIGNED) && set.has(BuiltinSet::UNSIGNED) {
519 return None;
520 }
521 let unsigned = set.has(BuiltinSet::UNSIGNED);
522 let signs = BuiltinSet::SIGNED.with(BuiltinSet::UNSIGNED);
523
524 if set.has(BuiltinSet::VOID) {
527 return if set == BuiltinSet::VOID && longs == 0 { real(Scalar::Void) } else { None };
528 }
529 if set.has(BuiltinSet::BOOL) {
530 return if set == BuiltinSet::BOOL && longs == 0 { real(Scalar::Bool) } else { None };
531 }
532 if set.has(BuiltinSet::CHAR) {
533 if set.without(signs) != BuiltinSet::CHAR || longs != 0 {
534 return None;
535 }
536 return real(match (set.has(BuiltinSet::SIGNED), unsigned) {
537 (true, _) => Scalar::SignedChar,
538 (_, true) => Scalar::UnsignedChar,
539 _ => Scalar::Char,
540 });
541 }
542 if set.has(BuiltinSet::INT128) {
543 if set.without(signs) != BuiltinSet::INT128 || longs != 0 {
544 return None;
545 }
546 return real(if unsigned { Scalar::UnsignedInt128 } else { Scalar::Int128 });
547 }
548 if set.has(BuiltinSet::BIT_INT) {
549 if set.without(signs) != BuiltinSet::BIT_INT || longs != 0 {
550 return None;
551 }
552 let width = self.width?;
555 return real(Scalar::BitInt { width, unsigned });
556 }
557 if set.has_any(BuiltinSet::DECIMALS) {
558 if longs != 0 || complexity != Complexity::Real {
559 return None;
560 }
561 return match set {
562 s if s == BuiltinSet::DECIMAL32 => real(Scalar::Decimal32),
563 s if s == BuiltinSet::DECIMAL64 => real(Scalar::Decimal64),
564 s if s == BuiltinSet::DECIMAL128 => real(Scalar::Decimal128),
565 _ => None,
566 };
567 }
568 if set.has_any(BuiltinSet::EXTENDED) {
569 if longs != 0 {
570 return None;
571 }
572 return match set {
573 s if s == BuiltinSet::FLOAT16 => basic(Scalar::Float16),
574 s if s == BuiltinSet::FLOAT32 => basic(Scalar::Float32),
575 s if s == BuiltinSet::FLOAT64 => basic(Scalar::Float64),
576 s if s == BuiltinSet::FLOAT128 => basic(Scalar::Float128),
577 s if s == BuiltinSet::FLOAT32X => basic(Scalar::Float32x),
578 s if s == BuiltinSet::FLOAT64X => basic(Scalar::Float64x),
579 s if s == BuiltinSet::FLOAT128X => basic(Scalar::Float128x),
580 s if s == BuiltinSet::FLOAT80 => basic(Scalar::Float80),
581 _ => None,
582 };
583 }
584 if set.has(BuiltinSet::FLOAT) {
585 return if set == BuiltinSet::FLOAT && longs == 0 {
586 basic(Scalar::Float)
587 } else {
588 None
589 };
590 }
591 if set.has(BuiltinSet::DOUBLE) {
592 if set.without(BuiltinSet::LONG) != BuiltinSet::DOUBLE {
593 return None;
594 }
595 return match longs {
596 0 => basic(Scalar::Double),
597 1 => basic(Scalar::LongDouble),
598 _ => None,
599 };
600 }
601 if set.is_none() {
602 return None;
603 }
604 if !BuiltinSet::INTEGER.has(set) {
607 return None;
608 }
609 if set.has(BuiltinSet::SHORT) {
610 if longs != 0 {
611 return None;
612 }
613 return basic(if unsigned { Scalar::UnsignedShort } else { Scalar::Short });
614 }
615 match longs {
616 0 => basic(if unsigned { Scalar::UnsignedInt } else { Scalar::Int }),
617 1 => basic(if unsigned { Scalar::UnsignedLong } else { Scalar::Long }),
618 2 => basic(if unsigned { Scalar::UnsignedLongLong } else { Scalar::LongLong }),
619 _ => None,
620 }
621 }
622}
623
624#[derive(Debug, Clone, Copy, PartialEq, Eq)]
626pub struct Basic {
627 pub scalar: Scalar,
629 pub complexity: Complexity,
631}
632
633#[derive(Debug, Clone, Copy, PartialEq, Eq)]
635pub enum Complexity {
636 Real,
638 Complex,
640 Imaginary,
642}
643
644#[derive(Debug, Clone, Copy, PartialEq, Eq)]
649pub enum Scalar {
650 Void,
652 Bool,
654 Char,
656 SignedChar,
658 UnsignedChar,
660 Short,
662 UnsignedShort,
664 Int,
666 UnsignedInt,
668 Long,
670 UnsignedLong,
672 LongLong,
674 UnsignedLongLong,
676 Int128,
678 UnsignedInt128,
680 BitInt {
682 width: ExprId,
684 unsigned: bool,
687 },
688 Float,
690 Double,
692 LongDouble,
694 Float16,
696 Float32,
698 Float64,
700 Float128,
702 Float32x,
704 Float64x,
706 Float128x,
708 Float80,
710 Decimal32,
712 Decimal64,
714 Decimal128,
716}
717
718#[cfg(test)]
719mod tests {
720 use super::*;
721
722 fn resolve(keywords: &[BuiltinSet]) -> Option<Basic> {
723 let mut b = Builtin::NONE;
724 for &k in keywords {
725 b = b.add(k).expect("keyword rejected");
726 }
727 b.resolve()
728 }
729
730 fn real(keywords: &[BuiltinSet]) -> Option<Scalar> {
731 resolve(keywords).filter(|b| b.complexity == Complexity::Real).map(|b| b.scalar)
732 }
733
734 #[test]
735 fn the_plain_integer_spellings() {
736 assert_eq!(real(&[BuiltinSet::INT]), Some(Scalar::Int));
737 assert_eq!(real(&[BuiltinSet::SIGNED]), Some(Scalar::Int));
738 assert_eq!(real(&[BuiltinSet::UNSIGNED]), Some(Scalar::UnsignedInt));
739 assert_eq!(real(&[BuiltinSet::SIGNED, BuiltinSet::INT]), Some(Scalar::Int));
740 assert_eq!(real(&[BuiltinSet::SHORT]), Some(Scalar::Short));
741 assert_eq!(real(&[BuiltinSet::SHORT, BuiltinSet::INT]), Some(Scalar::Short));
742 assert_eq!(
743 real(&[BuiltinSet::UNSIGNED, BuiltinSet::SHORT, BuiltinSet::INT]),
744 Some(Scalar::UnsignedShort)
745 );
746 }
747
748 #[test]
749 fn long_counts_rather_than_repeats() {
750 assert_eq!(real(&[BuiltinSet::LONG]), Some(Scalar::Long));
751 assert_eq!(real(&[BuiltinSet::LONG, BuiltinSet::LONG]), Some(Scalar::LongLong));
752 assert_eq!(
753 real(&[BuiltinSet::UNSIGNED, BuiltinSet::LONG, BuiltinSet::LONG, BuiltinSet::INT]),
754 Some(Scalar::UnsignedLongLong)
755 );
756 let three = Builtin::NONE
757 .add(BuiltinSet::LONG)
758 .and_then(|b| b.add(BuiltinSet::LONG))
759 .and_then(|b| b.add(BuiltinSet::LONG));
760 assert_eq!(three, Err(BuiltinError::TooManyLongs));
761 }
762
763 #[test]
764 fn a_repeated_keyword_is_caught_where_it_is_written() {
765 let twice = Builtin::NONE.add(BuiltinSet::INT).and_then(|b| b.add(BuiltinSet::INT));
766 assert_eq!(twice, Err(BuiltinError::Duplicate));
767 }
768
769 #[test]
770 fn plain_char_is_its_own_type() {
771 assert_eq!(real(&[BuiltinSet::CHAR]), Some(Scalar::Char));
772 assert_eq!(real(&[BuiltinSet::SIGNED, BuiltinSet::CHAR]), Some(Scalar::SignedChar));
773 assert_eq!(real(&[BuiltinSet::UNSIGNED, BuiltinSet::CHAR]), Some(Scalar::UnsignedChar));
774 assert_eq!(real(&[BuiltinSet::CHAR, BuiltinSet::INT]), None);
775 assert_eq!(real(&[BuiltinSet::CHAR, BuiltinSet::LONG]), None);
776 }
777
778 #[test]
779 fn long_double_is_a_double_with_one_long() {
780 assert_eq!(real(&[BuiltinSet::DOUBLE]), Some(Scalar::Double));
781 assert_eq!(real(&[BuiltinSet::LONG, BuiltinSet::DOUBLE]), Some(Scalar::LongDouble));
782 assert_eq!(real(&[BuiltinSet::LONG, BuiltinSet::LONG, BuiltinSet::DOUBLE]), None);
783 assert_eq!(real(&[BuiltinSet::LONG, BuiltinSet::FLOAT]), None);
784 assert_eq!(real(&[BuiltinSet::DOUBLE, BuiltinSet::INT]), None);
785 }
786
787 #[test]
788 fn complex_is_a_modifier_and_not_a_type() {
789 assert_eq!(
790 resolve(&[BuiltinSet::COMPLEX, BuiltinSet::DOUBLE]),
791 Some(Basic { scalar: Scalar::Double, complexity: Complexity::Complex })
792 );
793 assert_eq!(
794 resolve(&[BuiltinSet::LONG, BuiltinSet::DOUBLE, BuiltinSet::IMAGINARY]),
795 Some(Basic { scalar: Scalar::LongDouble, complexity: Complexity::Imaginary })
796 );
797 assert_eq!(
799 resolve(&[BuiltinSet::COMPLEX, BuiltinSet::INT]),
800 Some(Basic { scalar: Scalar::Int, complexity: Complexity::Complex })
801 );
802 assert_eq!(resolve(&[BuiltinSet::COMPLEX, BuiltinSet::IMAGINARY, BuiltinSet::FLOAT]), None);
803 assert_eq!(resolve(&[BuiltinSet::COMPLEX, BuiltinSet::DECIMAL64]), None);
805 }
806
807 #[test]
808 fn the_extended_types_stand_alone_or_with_complex() {
809 assert_eq!(real(&[BuiltinSet::FLOAT128]), Some(Scalar::Float128));
810 assert_eq!(
811 resolve(&[BuiltinSet::COMPLEX, BuiltinSet::FLOAT128]),
812 Some(Basic { scalar: Scalar::Float128, complexity: Complexity::Complex })
813 );
814 assert_eq!(real(&[BuiltinSet::FLOAT32X]), Some(Scalar::Float32x));
815 assert_eq!(real(&[BuiltinSet::FLOAT128X]), Some(Scalar::Float128x));
816 assert_eq!(real(&[BuiltinSet::FLOAT16, BuiltinSet::INT]), None);
817 assert_eq!(real(&[BuiltinSet::FLOAT32, BuiltinSet::FLOAT64]), None);
818 }
819
820 #[test]
821 fn the_wide_integers_take_a_sign_and_nothing_else() {
822 assert_eq!(real(&[BuiltinSet::INT128]), Some(Scalar::Int128));
823 assert_eq!(real(&[BuiltinSet::UNSIGNED, BuiltinSet::INT128]), Some(Scalar::UnsignedInt128));
824 assert_eq!(real(&[BuiltinSet::INT128, BuiltinSet::INT]), None);
825 }
826
827 #[test]
828 fn void_and_bool_take_nothing() {
829 assert_eq!(real(&[BuiltinSet::VOID]), Some(Scalar::Void));
830 assert_eq!(real(&[BuiltinSet::BOOL]), Some(Scalar::Bool));
831 assert_eq!(real(&[BuiltinSet::VOID, BuiltinSet::INT]), None);
832 assert_eq!(real(&[BuiltinSet::UNSIGNED, BuiltinSet::BOOL]), None);
833 }
834
835 #[test]
836 fn two_signs_name_no_type() {
837 assert_eq!(real(&[BuiltinSet::SIGNED, BuiltinSet::UNSIGNED]), None);
838 }
839
840 #[test]
841 fn no_keywords_at_all_names_no_type() {
842 assert_eq!(Builtin::NONE.resolve(), None);
843 assert!(Builtin::NONE.is_none());
844 }
845
846 #[test]
847 fn short_and_long_do_not_go_together() {
848 assert_eq!(real(&[BuiltinSet::SHORT, BuiltinSet::LONG]), None);
849 }
850
851 #[test]
852 fn qualifier_sets_add_up() {
853 let q = Quals::NONE.with(Quals::CONST).with(Quals::VOLATILE);
854 assert!(q.has(Quals::CONST));
855 assert!(q.has(Quals::VOLATILE));
856 assert!(!q.has(Quals::RESTRICT));
857 assert!(Quals::NONE.is_none());
858 assert!(!q.is_none());
859 }
860
861 #[test]
862 fn function_specifier_sets_add_up() {
863 let f = FuncSpecs::NONE.with(FuncSpecs::INLINE);
864 assert!(f.has(FuncSpecs::INLINE));
865 assert!(!f.has(FuncSpecs::NORETURN));
866 assert!(FuncSpecs::NONE.is_none());
867 }
868}