diff --git a/Cargo.lock b/Cargo.lock index 4806204..88ae58c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bstr" @@ -579,9 +579,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d7c7ca818f8a53c300863d4f87566aac09943aef5b355bb83969dae75d87" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -591,9 +591,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465c6fc0621e4abc4187a2bda0937bfd4f722c2730b29562e19689ea796c9a4b" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -602,15 +602,15 @@ dependencies = [ [[package]] name = "regex-lite" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a6ebcd15653947e6140f59a9811a06ed061d18a5c35dfca2e2e4c5525696878" +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" [[package]] name = "regex-syntax" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56d84fdd47036b038fc80dd333d10b6aab10d5d31f4a366e20014def75328d33" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustc-demangle" @@ -620,9 +620,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.19" +version = "0.38.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ "bitflags", "errno", @@ -688,6 +688,7 @@ dependencies = [ "divan", "indoc", "mimalloc", + "stef-compiler", "stef-parser", ] diff --git a/crates/stef-compiler/src/lib.rs b/crates/stef-compiler/src/lib.rs index 4c6c23c..d8cdfdd 100644 --- a/crates/stef-compiler/src/lib.rs +++ b/crates/stef-compiler/src/lib.rs @@ -2,12 +2,17 @@ pub use ids::{DuplicateFieldId, DuplicateId, DuplicateVariantId}; use stef_parser::{Definition, Schema}; use thiserror::Error; +use self::names::{DuplicateFieldName, DuplicateName}; + mod ids; +mod names; #[derive(Debug, Error)] pub enum Error { #[error("duplicate ID found")] DuplicateId(#[from] DuplicateId), + #[error("duplicate name found")] + DuplicateName(#[from] DuplicateName), } impl From for Error { @@ -16,15 +21,31 @@ impl From for Error { } } +impl From for Error { + fn from(v: DuplicateFieldName) -> Self { + Self::DuplicateName(v.into()) + } +} + pub fn validate_schema(value: &Schema<'_>) -> Result<(), Error> { value.definitions.iter().try_for_each(validate_definition) } fn validate_definition(value: &Definition<'_>) -> Result<(), Error> { match value { - Definition::Module(m) => m.definitions.iter().try_for_each(validate_definition), - Definition::Struct(s) => ids::validate_struct_ids(s).map_err(Into::into), - Definition::Enum(e) => ids::validate_enum_ids(e).map_err(Into::into), - Definition::TypeAlias(_) | Definition::Const(_) | Definition::Import(_) => Ok(()), + Definition::Module(m) => { + m.definitions.iter().try_for_each(validate_definition)?; + } + Definition::Struct(s) => { + ids::validate_struct_ids(s)?; + names::validate_struct_names(s)?; + } + Definition::Enum(e) => { + ids::validate_enum_ids(e)?; + names::validate_enum_names(e)?; + } + Definition::TypeAlias(_) | Definition::Const(_) | Definition::Import(_) => {} } + + Ok(()) } diff --git a/crates/stef-compiler/src/names.rs b/crates/stef-compiler/src/names.rs new file mode 100644 index 0000000..56e0e1f --- /dev/null +++ b/crates/stef-compiler/src/names.rs @@ -0,0 +1,73 @@ +use std::collections::HashSet; + +use stef_parser::{Enum, Fields, Struct}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum DuplicateName { + #[error("duplicate name in an enum variant")] + EnumVariant(#[from] DuplicateVariantName), + #[error("duplicate name in a field")] + Field(#[from] DuplicateFieldName), +} + +#[derive(Debug, Error)] +#[error("duplicate variant name `{name}` in enum")] +pub struct DuplicateVariantName { + pub name: String, +} + +#[derive(Debug, Error)] +#[error("duplicate field name `{name}`")] +pub struct DuplicateFieldName { + pub name: String, +} + +/// Ensure all field names inside a struct are unique. +pub(crate) fn validate_struct_names(value: &Struct<'_>) -> Result<(), DuplicateFieldName> { + validate_field_names(&value.fields) +} + +/// Ensure all names inside an enum are unique, which means all variants have a unique name, plus +/// all potential fields in a variant are unique (within that variant). +pub(crate) fn validate_enum_names(value: &Enum<'_>) -> Result<(), DuplicateName> { + let mut visited = HashSet::with_capacity(value.variants.len()); + value + .variants + .iter() + .find_map(|variant| { + (!visited.insert(variant.name)) + .then(|| { + DuplicateVariantName { + name: variant.name.to_owned(), + } + .into() + }) + .or_else(|| { + validate_field_names(&variant.fields) + .err() + .map(DuplicateName::from) + }) + }) + .map_or(Ok(()), Err) +} + +/// Ensure all field names of a struct or enum are unique. +fn validate_field_names(value: &Fields) -> Result<(), DuplicateFieldName> { + match value { + Fields::Named(named) => { + let mut visited = HashSet::with_capacity(named.len()); + named + .iter() + .find_map(|field| { + (!visited.insert(field.name)).then(|| DuplicateFieldName { + name: field.name.to_owned(), + }) + }) + .map_or(Ok(()), Err)?; + } + Fields::Unnamed(_) | Fields::Unit => {} + } + + Ok(()) +}