Skip to content

Commit

Permalink
feat: ensure uniqueness of all module declarations
Browse files Browse the repository at this point in the history
Verify that all declared elements within a module (including the schema
root which is a module itself) have a unique name so they can't collide.
  • Loading branch information
dnaka91 committed Oct 27, 2023
1 parent b43e779 commit d09e182
Show file tree
Hide file tree
Showing 34 changed files with 360 additions and 62 deletions.
4 changes: 2 additions & 2 deletions crates/stef-build/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub fn compile_struct(
fields,
}: &Struct<'_>,
) -> TokenStream {
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let (generics, generics_where) = compile_generics(generics);
let field_vars = compile_field_vars(fields);
let field_matches = compile_field_matches(fields);
Expand Down Expand Up @@ -55,7 +55,7 @@ pub fn compile_enum(
variants,
}: &Enum<'_>,
) -> TokenStream {
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let (generics, generics_where) = compile_generics(generics);
let variants = variants.iter().map(compile_variant);

Expand Down
12 changes: 6 additions & 6 deletions crates/stef-build/src/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fn compile_module(
}: &Module<'_>,
) -> TokenStream {
let comment = compile_comment(comment);
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let definitions = definitions.iter().map(compile_definition);

quote! {
Expand All @@ -83,7 +83,7 @@ fn compile_struct(
}: &Struct<'_>,
) -> TokenStream {
let comment = compile_comment(comment);
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let generics = compile_generics(generics);
let fields = compile_fields(fields, true);

Expand All @@ -105,7 +105,7 @@ fn compile_enum(
}: &Enum<'_>,
) -> TokenStream {
let comment = compile_comment(comment);
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let generics = compile_generics(generics);
let variants = variants.iter().map(compile_variant);

Expand Down Expand Up @@ -165,7 +165,7 @@ fn compile_const(
}: &Const<'_>,
) -> TokenStream {
let comment = compile_comment(comment);
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let ty = compile_const_data_type(ty);
let value = compile_literal(value);

Expand All @@ -185,8 +185,8 @@ fn compile_import(Import { segments, element }: &Import<'_>) -> TokenStream {
quote! {#segment}
}
});
let element = element.map(|element| {
let element = Ident::new(element, Span::call_site());
let element = element.as_ref().map(|element| {
let element = Ident::new(element.get(), Span::call_site());
quote! { ::#element}
});

Expand Down
4 changes: 2 additions & 2 deletions crates/stef-build/src/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub fn compile_struct(
fields,
}: &Struct<'_>,
) -> TokenStream {
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let (generics, generics_where) = compile_generics(generics);
let fields = compile_struct_fields(fields);

Expand Down Expand Up @@ -94,7 +94,7 @@ pub fn compile_enum(
variants,
}: &Enum<'_>,
) -> TokenStream {
let name = Ident::new(name, Span::call_site());
let name = Ident::new(name.get(), Span::call_site());
let (generics, generics_where) = compile_generics(generics);
let variants = variants.iter().map(compile_variant);

Expand Down
2 changes: 2 additions & 0 deletions crates/stef-compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ impl From<DuplicateFieldName> for Error {
}

pub fn validate_schema(value: &Schema<'_>) -> Result<(), Error> {
names::validate_names_in_module(&value.definitions)?;
value.definitions.iter().try_for_each(validate_definition)
}

fn validate_definition(value: &Definition<'_>) -> Result<(), Error> {
match value {
Definition::Module(m) => {
names::validate_names_in_module(&m.definitions)?;
m.definitions.iter().try_for_each(validate_definition)?;
}
Definition::Struct(s) => {
Expand Down
50 changes: 49 additions & 1 deletion crates/stef-compiler/src/names.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashMap, ops::Range};

use miette::Diagnostic;
use stef_parser::{Enum, Fields, Spanned, Struct};
use stef_parser::{Definition, Enum, Fields, Import, Spanned, Struct};
use thiserror::Error;

#[derive(Debug, Diagnostic, Error)]
Expand All @@ -12,6 +12,9 @@ pub enum DuplicateName {
#[error("duplicate name in a field")]
#[diagnostic(transparent)]
Field(#[from] DuplicateFieldName),
#[error("duplicate name in the scope of a module")]
#[diagnostic(transparent)]
InModule(#[from] DuplicateNameInModule),
}

#[derive(Debug, Diagnostic, Error)]
Expand All @@ -36,6 +39,19 @@ pub struct DuplicateFieldName {
pub second: Range<usize>,
}

#[derive(Debug, Diagnostic, Error)]
#[error("duplicate definition name `{name}`")]
#[diagnostic(help(
"the names of each definition must be unique and not collide with other declarations"
))]
pub struct DuplicateNameInModule {
pub name: String,
#[label("first declared here")]
pub first: Range<usize>,
#[label("used here again")]
pub second: Range<usize>,
}

/// Ensure all field names inside a struct are unique.
pub(crate) fn validate_struct_names(value: &Struct<'_>) -> Result<(), DuplicateFieldName> {
validate_field_names(&value.fields)
Expand Down Expand Up @@ -91,3 +107,35 @@ fn validate_field_names(value: &Fields<'_>) -> Result<(), DuplicateFieldName> {

Ok(())
}

pub(crate) fn validate_names_in_module(value: &[Definition<'_>]) -> Result<(), DuplicateName> {
let mut visited = HashMap::with_capacity(value.len());
value
.iter()
.find_map(|definition| {
let name = match definition {
Definition::Module(m) => &m.name,
Definition::Struct(s) => &s.name,
Definition::Enum(e) => &e.name,
Definition::TypeAlias(_) => {
eprintln!("TODO: implement name check for type aliases");
return None;
}
Definition::Const(c) => &c.name,
Definition::Import(Import {
element: Some(name),
..
}) => name,
Definition::Import(_) => return None,
};
visited.insert(name.get(), name.span()).map(|first| {
DuplicateNameInModule {
name: name.get().to_owned(),
first: first.into(),
second: name.span().into(),
}
.into()
})
})
.map_or(Ok(()), Err)
}
10 changes: 5 additions & 5 deletions crates/stef-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ pub struct Module<'a> {
/// Optional module-level comment.
pub comment: Comment<'a>,
/// Unique name of the module, within the current scope.
pub name: &'a str,
pub name: Name<'a>,
/// List of definitions that are scoped within this module.
pub definitions: Vec<Definition<'a>>,
}
Expand Down Expand Up @@ -264,7 +264,7 @@ pub struct Struct<'a> {
/// Optional attributes to customize the behavior.
pub attributes: Attributes<'a>,
/// Unique name for this struct (within its scope).
pub name: &'a str,
pub name: Name<'a>,
/// Potential generics.
pub generics: Generics<'a>,
/// Fields of the struct, if any.
Expand Down Expand Up @@ -318,7 +318,7 @@ pub struct Enum<'a> {
/// Optional attributes to customize the behavior.
pub attributes: Attributes<'a>,
/// Unique name for this enum, within its current scope.
pub name: &'a str,
pub name: Name<'a>,
/// Potential generics.
pub generics: Generics<'a>,
/// List of possible variants that the enum can represent.
Expand Down Expand Up @@ -895,7 +895,7 @@ pub struct Const<'a> {
/// Optional element-level comment.
pub comment: Comment<'a>,
/// Unique identifier of this constant.
pub name: &'a str,
pub name: Name<'a>,
/// Type of the value.
pub ty: DataType<'a>,
/// Literal value that this declaration represents.
Expand Down Expand Up @@ -952,7 +952,7 @@ pub struct Import<'a> {
pub segments: Vec<&'a str>,
/// Optional final element that allows to fully import the type, making it look as it would be
/// defined in the current schema.
pub element: Option<&'a str>,
pub element: Option<Name<'a>>,
}

impl<'a> Print for Import<'a> {
Expand Down
9 changes: 7 additions & 2 deletions crates/stef-parser/src/parser/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use winnow::{
};

use super::{literals, types, Input, ParserExt, Result};
use crate::{highlight, location, Comment, Const};
use crate::{highlight, location, Comment, Const, Name};

/// Encountered an invalid `const` declaration.
#[derive(Debug, ParserError)]
Expand Down Expand Up @@ -105,13 +105,18 @@ pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result<Const<'i>, ParseError>
})
}

pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> {
pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<Name<'i>, Cause> {
(
one_of('A'..='Z'),
take_while(0.., ('A'..='Z', '0'..='9', '_')),
)
.recognize()
.with_span()
.parse_next(input)
.map(|(value, span)| Name {
value,
span: span.into(),
})
.map_err(|e| {
e.map(|()| Cause::InvalidName {
at: input.location(),
Expand Down
9 changes: 7 additions & 2 deletions crates/stef-parser/src/parser/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use winnow::{
};

use super::{comments, fields, generics, ids, ws, Input, ParserExt, Result};
use crate::{highlight, Attributes, Comment, Enum, Variant};
use crate::{highlight, Attributes, Comment, Enum, Name, Variant};

/// Encountered an invalid `enum` declaration.
#[derive(Debug, ParserError)]
Expand Down Expand Up @@ -109,10 +109,15 @@ pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result<Enum<'i>, ParseError> {
})
}

pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> {
pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<Name<'i>, Cause> {
(one_of('A'..='Z'), alphanumeric0)
.recognize()
.with_span()
.parse_next(input)
.map(|(value, span)| Name {
value,
span: span.into(),
})
.map_err(|e| {
e.map(|()| Cause::InvalidName {
at: input.location(),
Expand Down
9 changes: 7 additions & 2 deletions crates/stef-parser/src/parser/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use winnow::{
};

use super::{parse_definition, ws, Input, ParserExt, Result};
use crate::{error::ParseDefinitionError, highlight, location, Comment, Module};
use crate::{error::ParseDefinitionError, highlight, location, Comment, Module, Name};

/// Encountered an invalid `mod` declaration.
#[derive(Debug, ParserError)]
Expand Down Expand Up @@ -89,13 +89,18 @@ pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result<Module<'i>, ParseError>
})
}

fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> {
fn parse_name<'i>(input: &mut Input<'i>) -> Result<Name<'i>, Cause> {
(
one_of('a'..='z'),
take_while(0.., ('a'..='z', '0'..='9', '_')),
)
.recognize()
.with_span()
.parse_next(input)
.map(|(value, span)| Name {
value,
span: span.into(),
})
.map_err(|e| {
e.map(|()| Cause::InvalidName {
at: input.location(),
Expand Down
9 changes: 7 additions & 2 deletions crates/stef-parser/src/parser/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use winnow::{
};

use super::{fields, generics, Input, ParserExt, Result};
use crate::{highlight, location, Attributes, Comment, Struct};
use crate::{highlight, location, Attributes, Comment, Name, Struct};

/// Encountered an invalid `struct` declaration.
#[derive(Debug, ParserError)]
Expand Down Expand Up @@ -89,10 +89,15 @@ pub(super) fn parse<'i>(input: &mut Input<'i>) -> Result<Struct<'i>, ParseError>
})
}

pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<&'i str, Cause> {
pub(super) fn parse_name<'i>(input: &mut Input<'i>) -> Result<Name<'i>, Cause> {
(one_of('A'..='Z'), alphanumeric0)
.recognize()
.with_span()
.parse_next(input)
.map(|(value, span)| Name {
value,
span: span.into(),
})
.map_err(|e| {
e.map(|()| Cause::InvalidName {
at: input.location(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ Schema {
},
],
),
name: "Sample",
name: Name {
value: "Sample",
span: Span {
start: 39,
end: 45,
},
},
generics: Generics(
[],
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ Schema {
},
],
),
name: "Sample",
name: Name {
value: "Sample",
span: Span {
start: 35,
end: 41,
},
},
generics: Generics(
[],
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ Schema {
},
],
),
name: "Sample",
name: Name {
value: "Sample",
span: Span {
start: 21,
end: 27,
},
},
generics: Generics(
[],
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ Schema {
},
],
),
name: "Sample",
name: Name {
value: "Sample",
span: Span {
start: 111,
end: 117,
},
},
generics: Generics(
[],
),
Expand Down
Loading

0 comments on commit d09e182

Please sign in to comment.