-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: verify that generics are unique and used
All defined generics in structs or enums must have a unique name and be used in some way inside the definition of the element. These new verifications in the compiler ensure the rules are upheld.
- Loading branch information
Showing
17 changed files
with
348 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
use std::{collections::HashMap, ops::Range}; | ||
|
||
use miette::Diagnostic; | ||
use stef_parser::{DataType, Enum, ExternalType, Fields, Generics, Span, Spanned, Struct}; | ||
use thiserror::Error; | ||
|
||
#[derive(Debug, Diagnostic, Error)] | ||
pub enum InvalidGenericType { | ||
#[error("duplicate generic type name found")] | ||
#[diagnostic(transparent)] | ||
Duplicate(#[from] DuplicateGenericName), | ||
#[error("unused generic type argument found")] | ||
#[diagnostic(transparent)] | ||
Unused(#[from] UnusedGeneric), | ||
} | ||
|
||
#[derive(Debug, Diagnostic, Error)] | ||
#[error("duplicate generic type name `{name}`")] | ||
#[diagnostic(help("the names of each generic type must be unique"))] | ||
pub struct DuplicateGenericName { | ||
pub name: String, | ||
#[label("first declared here")] | ||
pub first: Range<usize>, | ||
#[label("used here again")] | ||
pub second: Range<usize>, | ||
} | ||
|
||
#[derive(Debug, Diagnostic, Error)] | ||
#[error("unused generic type argument `{name}`")] | ||
#[diagnostic(help("each declared generic must be used in some way"))] | ||
pub struct UnusedGeneric { | ||
pub name: String, | ||
#[label("declared here")] | ||
pub definition: Range<usize>, | ||
} | ||
|
||
/// Ensure all generics in a struct are unqiue and used. | ||
pub fn validate_struct_generics(value: &Struct<'_>) -> Result<(), InvalidGenericType> { | ||
validate_duplicate_generics(&value.generics)?; | ||
|
||
let mut unvisited = value | ||
.generics | ||
.0 | ||
.iter() | ||
.map(|gen| (gen.get(), gen.span())) | ||
.collect::<HashMap<_, _>>(); | ||
|
||
validate_field_generics(&value.fields, &mut unvisited); | ||
|
||
unvisited.into_iter().next().map_or(Ok(()), |(name, span)| { | ||
Err(UnusedGeneric { | ||
name: name.to_owned(), | ||
definition: span.into(), | ||
} | ||
.into()) | ||
}) | ||
} | ||
|
||
/// Ensure all generics in an enum are unqiue and used. | ||
pub fn validate_enum_generics(value: &Enum<'_>) -> Result<(), InvalidGenericType> { | ||
validate_duplicate_generics(&value.generics)?; | ||
|
||
let mut unvisited = value | ||
.generics | ||
.0 | ||
.iter() | ||
.map(|gen| (gen.get(), gen.span())) | ||
.collect::<HashMap<_, _>>(); | ||
|
||
for variant in &value.variants { | ||
validate_field_generics(&variant.fields, &mut unvisited); | ||
} | ||
|
||
unvisited.into_iter().next().map_or(Ok(()), |(name, span)| { | ||
Err(UnusedGeneric { | ||
name: name.to_owned(), | ||
definition: span.into(), | ||
} | ||
.into()) | ||
}) | ||
} | ||
|
||
/// Ensure all generic type arguments are unique within a struct or enum. | ||
fn validate_duplicate_generics(value: &Generics<'_>) -> Result<(), DuplicateGenericName> { | ||
let mut visited = HashMap::with_capacity(value.0.len()); | ||
value | ||
.0 | ||
.iter() | ||
.find_map(|name| { | ||
visited | ||
.insert(name.get(), name.span()) | ||
.map(|first| DuplicateGenericName { | ||
name: name.get().to_owned(), | ||
first: first.into(), | ||
second: name.span().into(), | ||
}) | ||
}) | ||
.map_or(Ok(()), Err) | ||
} | ||
|
||
/// Iterate over all the fields and mark any generic types as used when disvored as type for a | ||
/// field. | ||
fn validate_field_generics(value: &Fields<'_>, unvisited: &mut HashMap<&str, Span>) { | ||
match &value { | ||
Fields::Named(named) => { | ||
for field in named { | ||
visit_externals(&field.ty, &mut |external| { | ||
if external.path.is_empty() && external.generics.is_empty() { | ||
unvisited.remove(external.name); | ||
} | ||
}); | ||
} | ||
} | ||
Fields::Unnamed(unnamed) => { | ||
for field in unnamed { | ||
visit_externals(&field.ty, &mut |external| { | ||
if external.path.is_empty() && external.generics.is_empty() { | ||
unvisited.remove(external.name); | ||
} | ||
}); | ||
} | ||
} | ||
Fields::Unit => {} | ||
} | ||
} | ||
|
||
/// Iterate recursively through the data type and invoke the closure on each discovered external | ||
/// type. | ||
fn visit_externals(value: &DataType<'_>, visit: &mut impl FnMut(&ExternalType<'_>)) { | ||
match value { | ||
DataType::Bool | ||
| DataType::U8 | ||
| DataType::U16 | ||
| DataType::U32 | ||
| DataType::U64 | ||
| DataType::U128 | ||
| DataType::I8 | ||
| DataType::I16 | ||
| DataType::I32 | ||
| DataType::I64 | ||
| DataType::I128 | ||
| DataType::F32 | ||
| DataType::F64 | ||
| DataType::String | ||
| DataType::StringRef | ||
| DataType::Bytes | ||
| DataType::BytesRef | ||
| DataType::NonZero(_) | ||
| DataType::BoxString | ||
| DataType::BoxBytes => {} | ||
DataType::Vec(ty) | ||
| DataType::HashSet(ty) | ||
| DataType::Option(ty) | ||
| DataType::Array(ty, _) => visit_externals(ty, visit), | ||
DataType::HashMap(kv) => { | ||
visit_externals(&kv.0, visit); | ||
visit_externals(&kv.1, visit); | ||
} | ||
DataType::Tuple(types) => { | ||
for ty in types { | ||
visit_externals(ty, visit); | ||
} | ||
} | ||
DataType::External(ty) => { | ||
visit(ty); | ||
|
||
for ty in &ty.generics { | ||
visit_externals(ty, visit); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,10 @@ | ||
|
||
/// Some comment | ||
struct Sample< | ||
A, | ||
B | ||
T | ||
> { | ||
|
||
a: u32 @1, | ||
b: bool @2, | ||
|
||
c: T @3, | ||
} |
Oops, something went wrong.