Skip to main content

rd_rds/package/
namespace.rs

1//! Typed access to declarations stored in `Meta/nsInfo.rds`.
2//!
3//! These declarations describe namespace metadata written from a package's
4//! `NAMESPACE`; they are not a runtime export set, a set of stored lazy-load
5//! bindings, or the result of evaluating export patterns. In particular, this
6//! view does not resolve regular expressions, synthesize S4 exports into
7//! regular exports, or inspect `.onLoad` behavior.
8
9use std::fmt;
10
11use super::{
12    InstalledMetadataError, ValueKindName, ViewError, decode_optional, decode_required,
13    expect_list, invalid_dimensions, named_values, unexpected_length, unexpected_type,
14};
15use crate::{RObject, RStr, RValue};
16
17/// The state of one known namespace metadata field.
18///
19/// `Missing` means that the field was absent from the metadata object.
20/// `Present` includes an empty collection. `Invalid` means the field belongs
21/// to the supported schema but contains invalid data. `UnsupportedSchema`
22/// means that the shape was understood but is outside the supported profile.
23#[derive(Debug, Clone, PartialEq, Eq)]
24#[non_exhaustive]
25pub enum MetadataField<T> {
26    Missing,
27    Present(T),
28    Invalid(ViewError),
29    UnsupportedSchema { description: String },
30}
31
32impl<T> MetadataField<T> {
33    /// Borrows a present value while preserving the field state.
34    pub fn as_ref(&self) -> MetadataField<&T> {
35        match self {
36            Self::Missing => MetadataField::Missing,
37            Self::Present(value) => MetadataField::Present(value),
38            Self::Invalid(error) => MetadataField::Invalid(error.clone()),
39            Self::UnsupportedSchema { description } => MetadataField::UnsupportedSchema {
40                description: description.clone(),
41            },
42        }
43    }
44
45    /// Returns the owned value when the field is present and valid.
46    pub fn present(&self) -> Option<&T> {
47        match self {
48            Self::Present(value) => Some(value),
49            Self::Missing | Self::Invalid(_) | Self::UnsupportedSchema { .. } => None,
50        }
51    }
52}
53
54/// One imported binding, retaining the source name and local alias.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ImportedName {
57    source_name: String,
58    local_name: String,
59}
60
61/// One statically declared export, retaining source and exported names.
62///
63/// For an ordinary `export(name)` declaration both names are identical. An
64/// assignment-shaped declaration such as `export(public_name = internal_name)`
65/// retains the source binding as `source_name` and the namespace binding as
66/// `exported_name`.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct NamespaceExport {
69    source_name: String,
70    exported_name: String,
71}
72
73impl NamespaceExport {
74    /// Creates an exported source/binding name pair.
75    pub fn new(source_name: impl Into<String>, exported_name: impl Into<String>) -> Self {
76        Self {
77            source_name: source_name.into(),
78            exported_name: exported_name.into(),
79        }
80    }
81
82    /// Returns the source binding name.
83    pub fn source_name(&self) -> &str {
84        &self.source_name
85    }
86
87    /// Returns the name exposed by the namespace.
88    pub fn exported_name(&self) -> &str {
89        &self.exported_name
90    }
91}
92
93impl ImportedName {
94    /// Creates an imported source/local name pair.
95    pub fn new(source_name: impl Into<String>, local_name: impl Into<String>) -> Self {
96        Self {
97            source_name: source_name.into(),
98            local_name: local_name.into(),
99        }
100    }
101
102    /// Returns the name exported by the source package.
103    pub fn source_name(&self) -> &str {
104        &self.source_name
105    }
106
107    /// Returns the name bound in the importing package.
108    pub fn local_name(&self) -> &str {
109        &self.local_name
110    }
111}
112
113/// One declaration from a namespace `import` or `importFrom` directive.
114#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum NamespaceImport {
116    /// Import all exports from `package`, except the listed names.
117    All {
118        package: String,
119        except: Vec<String>,
120    },
121    /// Import selected names from `package`, retaining local aliases.
122    From {
123        package: String,
124        names: Vec<ImportedName>,
125    },
126}
127
128impl NamespaceImport {
129    /// Returns the source package name.
130    pub fn package(&self) -> &str {
131        match self {
132            Self::All { package, .. } | Self::From { package, .. } => package,
133        }
134    }
135}
136
137/// Whether an S3 registration has an explicit function name.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub enum S3MethodName {
140    /// The registration has an `NA` function column.
141    Implicit,
142    /// The registration names a function explicitly.
143    Explicit(String),
144}
145
146/// One row from the supported four-column S3 registration matrix.
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct S3Registration {
149    generic: String,
150    class: String,
151    method: S3MethodName,
152    generic_package: Option<String>,
153}
154
155impl S3Registration {
156    pub fn generic(&self) -> &str {
157        &self.generic
158    }
159
160    pub fn class(&self) -> &str {
161        &self.class
162    }
163
164    pub fn method(&self) -> &S3MethodName {
165        &self.method
166    }
167
168    pub fn generic_package(&self) -> Option<&str> {
169        self.generic_package.as_deref()
170    }
171}
172
173/// Owned typed declarations from an installed package's `Meta/nsInfo.rds`.
174///
175/// The fields are independent: an invalid S3 matrix does not prevent valid
176/// declared exports from being read. The declarations are retained in input
177/// order and duplicates are not removed. They describe static namespace
178/// declarations only; they do not promise runtime exports, stored bindings,
179/// regex evaluation, re-exports, or `.onLoad` additions.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct NamespaceMetadata {
182    declared_exports: MetadataField<Vec<NamespaceExport>>,
183    export_patterns: MetadataField<Vec<String>>,
184    imports: MetadataField<Vec<NamespaceImport>>,
185    s3_registrations: MetadataField<Vec<S3Registration>>,
186    s3_generic_evidence: MetadataField<Vec<String>>,
187    export_classes: MetadataField<Vec<String>>,
188    export_methods: MetadataField<Vec<String>>,
189    export_class_patterns: MetadataField<Vec<String>>,
190}
191
192impl NamespaceMetadata {
193    /// Reads and validates `Meta/nsInfo.rds` below an installed package
194    /// directory.
195    pub fn read_installed(
196        package_dir: impl AsRef<std::path::Path>,
197    ) -> Result<Self, InstalledMetadataError> {
198        Self::read_installed_with_options(package_dir, &crate::file::ReadOptions::default())
199    }
200
201    /// Reads and validates `Meta/nsInfo.rds` with explicit file and decode
202    /// bounds.
203    pub fn read_installed_with_options(
204        package_dir: impl AsRef<std::path::Path>,
205        options: &crate::file::ReadOptions,
206    ) -> Result<Self, InstalledMetadataError> {
207        let (path, object) = super::read_installed_object(package_dir, "nsInfo.rds", options)?;
208        Self::from_object(&object).map_err(|source| InstalledMetadataError::View { path, source })
209    }
210
211    /// Parses an owned view of a named `Meta/nsInfo.rds` list.
212    ///
213    /// The root must be a named list. Unknown fields are ignored. Duplicate
214    /// known fields make only that field `Invalid`, allowing other fields to
215    /// remain useful to callers.
216    pub fn from_object(object: &RObject) -> Result<Self, ViewError> {
217        let items = expect_list(object, "NamespaceMetadata", None)?;
218        let names = named_values(object, "NamespaceMetadata", None)?;
219        if names.len() != items.len() {
220            return Err(unexpected_length(
221                "NamespaceMetadata",
222                None,
223                items.len().to_string(),
224                names.len(),
225            ));
226        }
227
228        let mut fields = Vec::with_capacity(items.len());
229        for (index, name) in names.iter().enumerate() {
230            let name = decode_required(name, &format!("NamespaceMetadata[{index}]"), None)?;
231            fields.push((name, &items[index]));
232        }
233
234        Ok(Self {
235            declared_exports: parse_known_field(&fields, "exports", parse_exports),
236            export_patterns: parse_known_field(&fields, "exportPatterns", parse_strings),
237            imports: parse_known_field(&fields, "imports", parse_imports),
238            s3_registrations: parse_known_field(&fields, "S3methods", parse_s3_registrations),
239            s3_generic_evidence: parse_known_field(&fields, "S3methods", parse_s3_generic_evidence),
240            export_classes: parse_known_field(&fields, "exportClasses", parse_strings),
241            export_methods: parse_known_field(&fields, "exportMethods", parse_strings),
242            export_class_patterns: parse_known_field(&fields, "exportClassPatterns", parse_strings),
243        })
244    }
245
246    pub fn declared_exports(&self) -> &MetadataField<Vec<NamespaceExport>> {
247        &self.declared_exports
248    }
249
250    pub fn export_patterns(&self) -> &MetadataField<Vec<String>> {
251        &self.export_patterns
252    }
253
254    pub fn imports(&self) -> &MetadataField<Vec<NamespaceImport>> {
255        &self.imports
256    }
257
258    pub fn s3_registrations(&self) -> &MetadataField<Vec<S3Registration>> {
259        &self.s3_registrations
260    }
261
262    pub fn s3_generic_evidence(&self) -> &MetadataField<Vec<String>> {
263        &self.s3_generic_evidence
264    }
265
266    pub fn export_classes(&self) -> &MetadataField<Vec<String>> {
267        &self.export_classes
268    }
269
270    pub fn export_methods(&self) -> &MetadataField<Vec<String>> {
271        &self.export_methods
272    }
273
274    pub fn export_class_patterns(&self) -> &MetadataField<Vec<String>> {
275        &self.export_class_patterns
276    }
277}
278
279impl TryFrom<&RObject> for NamespaceMetadata {
280    type Error = ViewError;
281
282    fn try_from(value: &RObject) -> Result<Self, Self::Error> {
283        Self::from_object(value)
284    }
285}
286
287enum ParseFailure {
288    Invalid(ViewError),
289    Unsupported(String),
290}
291
292fn parse_known_field<T, F>(fields: &[(String, &RObject)], name: &str, parser: F) -> MetadataField<T>
293where
294    F: FnOnce(&RObject, &str) -> Result<T, ParseFailure>,
295{
296    let mut matches = fields.iter().filter(|(field, _)| field == name);
297    let Some((_, object)) = matches.next() else {
298        return MetadataField::Missing;
299    };
300    if matches.next().is_some() {
301        return MetadataField::Invalid(super::duplicate(
302            &format!("NamespaceMetadata.{name}"),
303            Some(name.to_owned()),
304        ));
305    }
306    match parser(object, &format!("NamespaceMetadata.{name}")) {
307        Ok(value) => MetadataField::Present(value),
308        Err(ParseFailure::Invalid(error)) => MetadataField::Invalid(error),
309        Err(ParseFailure::Unsupported(description)) => {
310            MetadataField::UnsupportedSchema { description }
311        }
312    }
313}
314
315fn parse_exports(object: &RObject, path: &str) -> Result<Vec<NamespaceExport>, ParseFailure> {
316    let values = match object.value() {
317        RValue::Character(values) => values,
318        value => {
319            return Err(ParseFailure::Invalid(unexpected_type(
320                path,
321                None,
322                "character vector",
323                value.kind_name(),
324            )));
325        }
326    };
327    let aliases = match object.attributes().get("names") {
328        None => None,
329        Some(attribute) => match &attribute.value() {
330            RValue::Character(names) => Some(names),
331            value => {
332                return Err(ParseFailure::Invalid(unexpected_type(
333                    &format!("{path}.names"),
334                    None,
335                    "character vector",
336                    value.kind_name(),
337                )));
338            }
339        },
340    };
341    if let Some(aliases) = aliases
342        && aliases.len() != values.len()
343    {
344        return Err(ParseFailure::Invalid(unexpected_length(
345            &format!("{path}.names"),
346            None,
347            values.len().to_string(),
348            aliases.len(),
349        )));
350    }
351    values
352        .iter()
353        .enumerate()
354        .map(|(index, value)| {
355            let source_name = decode_required(value, &format!("{path}[{index}]"), None)
356                .map_err(ParseFailure::Invalid)?;
357            let exported_name = match aliases {
358                Some(aliases) => {
359                    let alias =
360                        decode_required(&aliases[index], &format!("{path}.names[{index}]"), None)
361                            .map_err(ParseFailure::Invalid)?;
362                    if alias.is_empty() {
363                        source_name.clone()
364                    } else {
365                        alias
366                    }
367                }
368                None => source_name.clone(),
369            };
370            Ok(NamespaceExport {
371                source_name,
372                exported_name,
373            })
374        })
375        .collect()
376}
377
378fn parse_strings(object: &RObject, path: &str) -> Result<Vec<String>, ParseFailure> {
379    let values = match object.value() {
380        RValue::Character(values) => values,
381        value => {
382            return Err(ParseFailure::Invalid(unexpected_type(
383                path,
384                None,
385                "character vector",
386                value.kind_name(),
387            )));
388        }
389    };
390    values
391        .iter()
392        .enumerate()
393        .map(|(index, value)| {
394            decode_required(value, &format!("{path}[{index}]"), None).map_err(ParseFailure::Invalid)
395        })
396        .collect()
397}
398
399fn parse_imports(object: &RObject, path: &str) -> Result<Vec<NamespaceImport>, ParseFailure> {
400    match object.value() {
401        RValue::Character(values) => values
402            .iter()
403            .enumerate()
404            .map(|(index, value)| {
405                let package = decode_required(value, &format!("{path}[{index}]"), None)
406                    .map_err(ParseFailure::Invalid)?;
407                Ok(NamespaceImport::All {
408                    package,
409                    except: Vec::new(),
410                })
411            })
412            .collect(),
413        RValue::List(values) => values
414            .iter()
415            .enumerate()
416            .map(|(index, value)| parse_import_entry(value, &format!("{path}[{index}]")))
417            .collect(),
418        value => Err(ParseFailure::Invalid(unexpected_type(
419            path,
420            None,
421            "character vector or list",
422            value.kind_name(),
423        ))),
424    }
425}
426
427fn parse_import_entry(object: &RObject, path: &str) -> Result<NamespaceImport, ParseFailure> {
428    if let RValue::Character(values) = object.value() {
429        if values.len() != 1 {
430            return Err(ParseFailure::Invalid(unexpected_length(
431                path,
432                None,
433                "1".to_owned(),
434                values.len(),
435            )));
436        }
437        return Ok(NamespaceImport::All {
438            package: decode_required(&values[0], path, None).map_err(ParseFailure::Invalid)?,
439            except: Vec::new(),
440        });
441    }
442
443    let RValue::List(values) = object.value() else {
444        return Err(ParseFailure::Unsupported(format!(
445            "{path} is not a supported import declaration"
446        )));
447    };
448    let names = match object.attributes().get("names") {
449        Some(attribute) => match &attribute.value() {
450            RValue::Character(names) => Some(names),
451            value => {
452                return Err(ParseFailure::Invalid(unexpected_type(
453                    &format!("{path}.names"),
454                    None,
455                    "character vector",
456                    value.kind_name(),
457                )));
458            }
459        },
460        None => None,
461    };
462    let Some(names) = names else {
463        // The only observed unnamed shape is list(package, selections).
464        if values.len() != 2 {
465            return Err(ParseFailure::Unsupported(format!(
466                "{path} does not contain a supported positional import declaration"
467            )));
468        }
469        let package = parse_package_scalar(&values[0], &format!("{path}[0]"))?;
470        let selections = parse_imported_names(&values[1], &format!("{path}[1]"))?;
471        return Ok(NamespaceImport::From {
472            package,
473            names: selections,
474        });
475    };
476
477    if names.len() != values.len() {
478        return Err(ParseFailure::Invalid(unexpected_length(
479            &format!("{path}.names"),
480            None,
481            values.len().to_string(),
482            names.len(),
483        )));
484    }
485
486    // A named entry must be interpreted entirely by its names. In particular,
487    // an unknown second name must not silently turn a two-element list into
488    // the positional package/selections shape. R writes the package element
489    // of `list("pkg", except = ...)` with an empty name; only that first
490    // position is accepted as an implicit package field.
491    let mut decoded_names = Vec::with_capacity(names.len());
492    let mut empty_name_indices = Vec::new();
493    for (index, name) in names.iter().enumerate() {
494        let decoded = decode_required(name, &format!("{path}.names[{index}]"), None)
495            .map_err(ParseFailure::Invalid)?;
496        if decoded.is_empty() {
497            empty_name_indices.push(index);
498            decoded_names.push(decoded);
499            continue;
500        }
501        if !matches!(
502            decoded.as_str(),
503            "package" | "except" | "selections" | "names"
504        ) {
505            return Err(ParseFailure::Unsupported(format!(
506                "{path} contains unsupported import field {decoded:?}"
507            )));
508        }
509        if decoded_names.iter().any(|existing| existing == &decoded) {
510            return Err(ParseFailure::Invalid(super::duplicate(
511                &format!("{path}.names"),
512                Some(decoded),
513            )));
514        }
515        decoded_names.push(decoded);
516    }
517
518    if empty_name_indices != [0] && !empty_name_indices.is_empty() {
519        return Err(ParseFailure::Unsupported(format!(
520            "{path} has an unnamed import field outside the first position"
521        )));
522    }
523    let named_package_index = decoded_names.iter().position(|name| name == "package");
524    if !empty_name_indices.is_empty() && named_package_index.is_some() {
525        return Err(ParseFailure::Invalid(super::duplicate(
526            &format!("{path}.names"),
527            Some("package".into()),
528        )));
529    }
530    let package_index = empty_name_indices.first().copied().or(named_package_index);
531    let Some(package_index) = package_index else {
532        return Err(ParseFailure::Unsupported(format!(
533            "{path} does not contain a package entry"
534        )));
535    };
536    let package =
537        parse_package_scalar(&values[package_index], &format!("{path}[{package_index}]"))?;
538
539    let except_index = decoded_names.iter().position(|name| name == "except");
540    let selection_indices: Vec<_> = decoded_names
541        .iter()
542        .enumerate()
543        .filter_map(|(index, name)| {
544            matches!(name.as_str(), "selections" | "names").then_some(index)
545        })
546        .collect();
547    if selection_indices.len() > 1 {
548        return Err(ParseFailure::Invalid(unexpected_type(
549            &format!("{path}.names"),
550            None,
551            "one import selection field",
552            "multiple import selection fields",
553        )));
554    }
555    if except_index.is_some() && !selection_indices.is_empty() {
556        return Err(ParseFailure::Invalid(unexpected_type(
557            &format!("{path}.names"),
558            None,
559            "either except or selections/names",
560            "both except and selections/names",
561        )));
562    }
563    if let Some(except_index) = except_index {
564        let except =
565            parse_string_vector(&values[except_index], &format!("{path}[{except_index}]"))?;
566        return Ok(NamespaceImport::All { package, except });
567    }
568
569    let Some(selection_index) = selection_indices.first().copied() else {
570        return Err(ParseFailure::Unsupported(format!(
571            "{path} does not contain import selections or except"
572        )));
573    };
574    let selections = parse_imported_names(
575        &values[selection_index],
576        &format!("{path}[{selection_index}]"),
577    )?;
578    Ok(NamespaceImport::From {
579        package,
580        names: selections,
581    })
582}
583
584fn parse_package_scalar(object: &RObject, path: &str) -> Result<String, ParseFailure> {
585    let RValue::Character(values) = object.value() else {
586        return Err(ParseFailure::Invalid(unexpected_type(
587            path,
588            None,
589            "character scalar",
590            object.value().kind_name(),
591        )));
592    };
593    if values.len() != 1 {
594        return Err(ParseFailure::Invalid(unexpected_length(
595            path,
596            None,
597            "1".to_owned(),
598            values.len(),
599        )));
600    }
601    decode_required(&values[0], path, None).map_err(ParseFailure::Invalid)
602}
603
604fn parse_string_vector(object: &RObject, path: &str) -> Result<Vec<String>, ParseFailure> {
605    parse_strings(object, path)
606}
607
608fn parse_imported_names(object: &RObject, path: &str) -> Result<Vec<ImportedName>, ParseFailure> {
609    let RValue::Character(values) = object.value() else {
610        return Err(ParseFailure::Invalid(unexpected_type(
611            path,
612            None,
613            "character vector",
614            object.value().kind_name(),
615        )));
616    };
617    let aliases = match object.attributes().get("names") {
618        None => None,
619        Some(attribute) => match &attribute.value() {
620            RValue::Character(values) => Some(values),
621            value => {
622                return Err(ParseFailure::Invalid(unexpected_type(
623                    &format!("{path}.names"),
624                    None,
625                    "character vector",
626                    value.kind_name(),
627                )));
628            }
629        },
630    };
631    if let Some(aliases) = aliases
632        && aliases.len() != values.len()
633    {
634        return Err(ParseFailure::Invalid(unexpected_length(
635            &format!("{path}.names"),
636            None,
637            values.len().to_string(),
638            aliases.len(),
639        )));
640    }
641    values
642        .iter()
643        .enumerate()
644        .map(|(index, value)| {
645            let source_name = decode_required(value, &format!("{path}[{index}]"), None)
646                .map_err(ParseFailure::Invalid)?;
647            let local_name = match aliases {
648                Some(aliases) => {
649                    let alias =
650                        decode_required(&aliases[index], &format!("{path}.names[{index}]"), None)
651                            .map_err(ParseFailure::Invalid)?;
652                    if alias.is_empty() {
653                        source_name.clone()
654                    } else {
655                        alias
656                    }
657                }
658                None => source_name.clone(),
659            };
660            Ok(ImportedName {
661                source_name,
662                local_name,
663            })
664        })
665        .collect()
666}
667
668fn parse_s3_registrations(
669    object: &RObject,
670    path: &str,
671) -> Result<Vec<S3Registration>, ParseFailure> {
672    let matrix = parse_character_matrix(object, path)?;
673    if matrix.ncol == 0 {
674        return Err(ParseFailure::Invalid(invalid_dimensions(
675            path,
676            "S3 registration matrix must have at least one column",
677        )));
678    }
679    if matrix.ncol != 4 {
680        return Err(ParseFailure::Unsupported(format!(
681            "S3 registration matrix has {} columns; only the four-column schema is supported",
682            matrix.ncol
683        )));
684    }
685    (0..matrix.nrow)
686        .map(|row| {
687            let generic = matrix.required(row, 0, path)?;
688            let class = matrix.required(row, 1, path)?;
689            let method = match matrix.optional(row, 2, path)? {
690                Some(value) => S3MethodName::Explicit(value),
691                None => S3MethodName::Implicit,
692            };
693            let generic_package = matrix.optional(row, 3, path)?;
694            Ok(S3Registration {
695                generic,
696                class,
697                method,
698                generic_package,
699            })
700        })
701        .collect()
702}
703
704fn parse_s3_generic_evidence(object: &RObject, path: &str) -> Result<Vec<String>, ParseFailure> {
705    let matrix = parse_character_matrix(object, path)?;
706    if matrix.ncol == 0 {
707        return Err(ParseFailure::Invalid(invalid_dimensions(
708            path,
709            "S3 metadata matrix must have at least one column",
710        )));
711    }
712    (0..matrix.nrow)
713        .map(|row| matrix.required(row, 0, path))
714        .collect()
715}
716
717struct CharacterMatrix<'a> {
718    values: &'a [RStr],
719    nrow: usize,
720    ncol: usize,
721}
722
723impl CharacterMatrix<'_> {
724    fn at(&self, row: usize, column: usize) -> &RStr {
725        &self.values[row + column * self.nrow]
726    }
727
728    fn required(&self, row: usize, column: usize, path: &str) -> Result<String, ParseFailure> {
729        decode_required(
730            self.at(row, column),
731            &format!("{path}[row={row},column={column}]"),
732            None,
733        )
734        .map_err(ParseFailure::Invalid)
735    }
736
737    fn optional(
738        &self,
739        row: usize,
740        column: usize,
741        path: &str,
742    ) -> Result<Option<String>, ParseFailure> {
743        decode_optional(
744            self.at(row, column),
745            &format!("{path}[row={row},column={column}]"),
746            None,
747        )
748        .map_err(ParseFailure::Invalid)
749    }
750}
751
752fn parse_character_matrix<'a>(
753    object: &'a RObject,
754    path: &str,
755) -> Result<CharacterMatrix<'a>, ParseFailure> {
756    let values = match object.value() {
757        RValue::Character(values) => values,
758        value => {
759            return Err(ParseFailure::Invalid(unexpected_type(
760                path,
761                None,
762                "character matrix",
763                value.kind_name(),
764            )));
765        }
766    };
767    let dimensions = object.attributes().get("dim").ok_or_else(|| {
768        ParseFailure::Invalid(super::missing(format!("{path}.attributes.dim"), None))
769    })?;
770    let RValue::Integer(dimensions) = dimensions.value() else {
771        return Err(ParseFailure::Invalid(unexpected_type(
772            &format!("{path}.attributes.dim"),
773            None,
774            "integer vector",
775            dimensions.value().kind_name(),
776        )));
777    };
778    if dimensions.len() != 2 {
779        return Err(ParseFailure::Invalid(unexpected_length(
780            &format!("{path}.attributes.dim"),
781            None,
782            "2".to_owned(),
783            dimensions.len(),
784        )));
785    }
786    let mut shape = [0usize; 2];
787    for (index, value) in dimensions.iter().enumerate() {
788        let Some(value) = value else {
789            return Err(ParseFailure::Invalid(invalid_dimensions(
790                &format!("{path}.attributes.dim"),
791                "dimensions must not contain NA",
792            )));
793        };
794        if *value < 0 {
795            return Err(ParseFailure::Invalid(invalid_dimensions(
796                &format!("{path}.attributes.dim"),
797                "dimensions must not be negative",
798            )));
799        }
800        shape[index] = *value as usize;
801    }
802    let expected = shape[0].checked_mul(shape[1]).ok_or_else(|| {
803        ParseFailure::Invalid(invalid_dimensions(
804            &format!("{path}.attributes.dim"),
805            "dimension product overflows",
806        ))
807    })?;
808    if expected != values.len() {
809        return Err(ParseFailure::Invalid(unexpected_length(
810            path,
811            None,
812            expected.to_string(),
813            values.len(),
814        )));
815    }
816    Ok(CharacterMatrix {
817        values,
818        nrow: shape[0],
819        ncol: shape[1],
820    })
821}
822
823impl fmt::Display for S3MethodName {
824    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
825        match self {
826            Self::Implicit => formatter.write_str("implicit"),
827            Self::Explicit(value) => formatter.write_str(value),
828        }
829    }
830}