From c380ace56af634c8e878a5c60b2b0ab2e0e1dd72 Mon Sep 17 00:00:00 2001 From: Dominik Nakamura Date: Thu, 2 Nov 2023 12:35:45 +0900 Subject: [PATCH] feat: improve error reporting for cross-schema errors Type resolution that goes across a single file now shows code snippets for both the use side and declaration side to better pinpoint the problem. --- crates/stef-benches/benches/compiler.rs | 4 +- crates/stef-benches/benches/parser.rs | 10 +- crates/stef-build/src/definition.rs | 2 +- crates/stef-build/src/lib.rs | 7 +- crates/stef-build/tests/compiler.rs | 16 +- crates/stef-cli/src/main.rs | 18 +- crates/stef-compiler/src/lib.rs | 125 ++++++++++---- crates/stef-compiler/src/resolve.rs | 155 ++++++++++++++---- crates/stef-compiler/tests/compiler.rs | 36 ++-- ...ror_resolve@remote_gens_mismatch.stef.snap | 13 +- ...ror_resolve@remote_kind_mismatch.stef.snap | 13 ++ crates/stef-parser/src/error.rs | 64 +++++++- crates/stef-parser/src/lib.rs | 40 ++++- crates/stef-parser/src/parser.rs | 14 +- crates/stef-parser/tests/parser.rs | 13 +- .../parser__error@alias_name.stef.snap | 4 +- .../parser__error@const_literal.stef.snap | 4 +- ...parser__error@const_literal_bool.stef.snap | 4 +- ...arser__error@const_literal_float.stef.snap | 4 +- .../parser__error@const_name.stef.snap | 4 +- .../parser__error@enum_name.stef.snap | 4 +- .../parser__error@field_name.stef.snap | 6 +- .../parser__error@mod_name.stef.snap | 4 +- .../parser__error@struct_name.stef.snap | 4 +- .../parser__parse@alias_basic.stef.snap | 4 + .../parser__parse@attribute_multi.stef.snap | 4 + .../parser__parse@attribute_single.stef.snap | 4 + .../parser__parse@attribute_unit.stef.snap | 4 + .../parser__parse@attributes.stef.snap | 4 + .../parser__parse@attributes_min_ws.stef.snap | 4 + .../parser__parse@const_basic.stef.snap | 4 + .../parser__parse@const_string.stef.snap | 4 + .../parser__parse@enum_basic.stef.snap | 4 + .../parser__parse@enum_generics.stef.snap | 4 + .../parser__parse@enum_many_ws.stef.snap | 4 + .../parser__parse@enum_min_ws.stef.snap | 4 + .../parser__parse@import_basic.stef.snap | 4 + .../parser__parse@module_basic.stef.snap | 4 + .../parser__parse@schema_basic.stef.snap | 4 + .../parser__parse@struct_basic.stef.snap | 4 + .../parser__parse@struct_generics.stef.snap | 4 + .../parser__parse@struct_many_ws.stef.snap | 4 + .../parser__parse@struct_min_ws.stef.snap | 4 + .../parser__parse@struct_tuple.stef.snap | 4 + .../parser__parse@types_basic.stef.snap | 4 + .../parser__parse@types_generic.stef.snap | 4 + .../parser__parse@types_nested.stef.snap | 4 + .../parser__parse@types_non_zero.stef.snap | 4 + .../parser__parse@types_ref.stef.snap | 4 + 49 files changed, 524 insertions(+), 144 deletions(-) diff --git a/crates/stef-benches/benches/compiler.rs b/crates/stef-benches/benches/compiler.rs index 772c5e0..e212395 100644 --- a/crates/stef-benches/benches/compiler.rs +++ b/crates/stef-benches/benches/compiler.rs @@ -10,7 +10,7 @@ fn main() { #[divan::bench(consts = [1, 10, 100, 1000])] fn validate_large_schema(bencher: Bencher) { let schema = stef_benches::generate_schema(N); - let schema = stef_parser::Schema::parse(&schema).unwrap(); + let schema = stef_parser::Schema::parse(&schema, None).unwrap(); stef_compiler::validate_schema(&schema).unwrap(); bencher.bench(|| stef_compiler::validate_schema(black_box(&schema))) @@ -19,7 +19,7 @@ fn validate_large_schema(bencher: Bencher) { #[divan::bench(consts = [1, 10, 100, 1000])] fn resolve_large_schema(bencher: Bencher) { let schema = stef_benches::generate_schema(N); - let schema = stef_parser::Schema::parse(&schema).unwrap(); + let schema = stef_parser::Schema::parse(&schema, None).unwrap(); stef_compiler::validate_schema(&schema).unwrap(); let list = &[("bench", black_box(&schema))]; diff --git a/crates/stef-benches/benches/parser.rs b/crates/stef-benches/benches/parser.rs index 732a96d..a89a329 100644 --- a/crates/stef-benches/benches/parser.rs +++ b/crates/stef-benches/benches/parser.rs @@ -41,23 +41,23 @@ fn basic(bencher: Bencher) { type SampleTyped = SampleStruct; "#}; - stef_parser::Schema::parse(input).unwrap(); + stef_parser::Schema::parse(input, None).unwrap(); - bencher.bench(|| stef_parser::Schema::parse(black_box(input))); + bencher.bench(|| stef_parser::Schema::parse(black_box(input), None)); } #[divan::bench(consts = [1, 10, 100, 1000])] fn large_schema(bencher: Bencher) { let schema = stef_benches::generate_schema(N); - stef_parser::Schema::parse(&schema).unwrap(); + stef_parser::Schema::parse(&schema, None).unwrap(); - bencher.bench(|| stef_parser::Schema::parse(black_box(&schema))) + bencher.bench(|| stef_parser::Schema::parse(black_box(&schema), None)) } #[divan::bench(consts = [1, 10, 100, 1000])] fn print(bencher: Bencher) { let schema = stef_benches::generate_schema(N); - let schema = stef_parser::Schema::parse(&schema).unwrap(); + let schema = stef_parser::Schema::parse(&schema, None).unwrap(); bencher.bench(|| black_box(&schema).to_string()) } diff --git a/crates/stef-build/src/definition.rs b/crates/stef-build/src/definition.rs index 390c218..0640765 100644 --- a/crates/stef-build/src/definition.rs +++ b/crates/stef-build/src/definition.rs @@ -7,7 +7,7 @@ use stef_parser::{ use super::{decode, encode}; -pub fn compile_schema(Schema { definitions }: &Schema<'_>) -> TokenStream { +pub fn compile_schema(Schema { definitions, .. }: &Schema<'_>) -> TokenStream { let definitions = definitions.iter().map(compile_definition); quote! { diff --git a/crates/stef-build/src/lib.rs b/crates/stef-build/src/lib.rs index 9345936..ad81f1e 100644 --- a/crates/stef-build/src/lib.rs +++ b/crates/stef-build/src/lib.rs @@ -88,14 +88,13 @@ pub fn compile(schemas: &[impl AsRef], _includes: &[impl AsRef]) -> R for (path, input) in &inputs { let stem = path.file_stem().unwrap().to_str().unwrap(); - let schema = Schema::parse(input).map_err(|e| Error::Parse { - report: e.with_source_code(NamedSource::new(path.display().to_string(), input.clone())), + let schema = Schema::parse(input, Some(path)).map_err(|e| Error::Parse { + report: Report::new(e), file: path.clone(), })?; stef_compiler::validate_schema(&schema).map_err(|e| Error::Compile { - report: Report::new(e) - .with_source_code(NamedSource::new(path.display().to_string(), input.clone())), + report: Report::new(e), file: path.clone(), })?; diff --git a/crates/stef-build/tests/compiler.rs b/crates/stef-build/tests/compiler.rs index 81b563d..c868bf6 100644 --- a/crates/stef-build/tests/compiler.rs +++ b/crates/stef-build/tests/compiler.rs @@ -1,13 +1,23 @@ -use std::fs; +use std::{ + fs, + path::{Path, PathBuf}, +}; use insta::{assert_snapshot, glob, with_settings}; use stef_parser::Schema; +fn strip_path(path: &Path) -> PathBuf { + path.strip_prefix(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/inputs")) + .or_else(|_| path.strip_prefix(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/inputs_extra"))) + .unwrap() + .to_owned() +} + #[test] fn compile_schema() { glob!("inputs/*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let value = Schema::parse(input.as_str()).unwrap(); + let value = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap(); let value = stef_build::compile_schema(&value); let value = prettyplease::unparse(&syn::parse2(value.clone()).unwrap()); @@ -24,7 +34,7 @@ fn compile_schema() { fn compile_schema_extra() { glob!("inputs_extra/*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let value = Schema::parse(input.as_str()).unwrap(); + let value = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap(); let value = stef_build::compile_schema(&value); let value = prettyplease::unparse(&syn::parse2(value.clone()).unwrap()); diff --git a/crates/stef-cli/src/main.rs b/crates/stef-cli/src/main.rs index 0fe3f87..ab7d278 100644 --- a/crates/stef-cli/src/main.rs +++ b/crates/stef-cli/src/main.rs @@ -51,7 +51,8 @@ fn check(patterns: Vec) -> Result<()> { .into_diagnostic() .wrap_err_with(|| format!("Failed reading {entry:?}"))?; - if let Err(e) = Schema::parse(&buf).wrap_err("Failed parsing schema file") { + if let Err(e) = Schema::parse(&buf, Some(&entry)).wrap_err("Failed parsing schema file") + { eprintln!("{e:?}"); } } @@ -68,13 +69,14 @@ fn format(patterns: Vec) -> Result<()> { { let entry = entry.into_diagnostic().wrap_err("Failed reading entry")?; let buf = fs::read_to_string(&entry).into_diagnostic()?; - let schema = match Schema::parse(&buf).wrap_err("Failed parsing schema file") { - Ok(schema) => schema, - Err(e) => { - eprintln!("{e:?}"); - continue; - } - }; + let schema = + match Schema::parse(&buf, Some(&entry)).wrap_err("Failed parsing schema file") { + Ok(schema) => schema, + Err(e) => { + eprintln!("{e:?}"); + continue; + } + }; let formatted = schema.to_string(); diff --git a/crates/stef-compiler/src/lib.rs b/crates/stef-compiler/src/lib.rs index b5d8c1c..364d42d 100644 --- a/crates/stef-compiler/src/lib.rs +++ b/crates/stef-compiler/src/lib.rs @@ -3,15 +3,20 @@ #![warn(clippy::pedantic)] #![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)] +use std::{ + error::Error, + fmt::{self, Display}, +}; + pub use ids::{DuplicateFieldId, DuplicateId, DuplicateVariantId}; -use miette::Diagnostic; +use miette::{Diagnostic, NamedSource}; use stef_parser::{Definition, Schema}; use thiserror::Error; use self::{ generics::InvalidGenericType, names::{DuplicateFieldName, DuplicateName}, - resolve::{ResolveImport, ResolveLocal, ResolveRemote}, + resolve::ResolveError, }; mod generics; @@ -21,7 +26,7 @@ mod names; mod resolve; #[derive(Debug, Diagnostic, Error)] -pub enum Error { +pub enum ValidationError { #[error("duplicate ID found")] #[diagnostic(transparent)] DuplicateId(#[from] DuplicateId), @@ -31,47 +36,26 @@ pub enum Error { #[error("invalid generic type found")] #[diagnostic(transparent)] InvalidGeneric(#[from] InvalidGenericType), - #[error("type resolution failed")] - #[diagnostic(transparent)] - Resolve(#[from] resolve::ResolveError), } -impl From for Error { +impl From for ValidationError { fn from(v: DuplicateFieldId) -> Self { Self::DuplicateId(v.into()) } } -impl From for Error { +impl From for ValidationError { fn from(v: DuplicateFieldName) -> Self { Self::DuplicateName(v.into()) } } -impl From for Error { - fn from(v: ResolveLocal) -> Self { - Self::Resolve(v.into()) - } -} - -impl From for Error { - fn from(v: ResolveImport) -> Self { - Self::Resolve(v.into()) - } -} - -impl From for Error { - fn from(v: ResolveRemote) -> Self { - Self::Resolve(v.into()) - } -} - -pub fn validate_schema(value: &Schema<'_>) -> Result<(), Error> { +pub fn validate_schema(value: &Schema<'_>) -> Result<(), ValidationError> { names::validate_names_in_module(&value.definitions)?; value.definitions.iter().try_for_each(validate_definition) } -fn validate_definition(value: &Definition<'_>) -> Result<(), Error> { +fn validate_definition(value: &Definition<'_>) -> Result<(), ValidationError> { match value { Definition::Module(m) => { names::validate_names_in_module(&m.definitions)?; @@ -93,20 +77,95 @@ fn validate_definition(value: &Definition<'_>) -> Result<(), Error> { Ok(()) } -pub fn resolve_schemas(values: &[(&str, &Schema<'_>)]) -> Result<(), Error> { +#[derive(Debug)] +pub struct ResolutionError { + source_code: NamedSource, + cause: resolve::ResolveError, +} + +impl Error for ResolutionError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.cause) + } +} + +impl Display for ResolutionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("type resolution failed") + } +} + +impl Diagnostic for ResolutionError { + fn code<'a>(&'a self) -> Option> { + self.cause.code() + } + + fn severity(&self) -> Option { + self.cause.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.cause.help() + } + + fn url<'a>(&'a self) -> Option> { + self.cause.url() + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(&self.source_code) + } + + fn labels(&self) -> Option + '_>> { + self.cause.labels() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.cause.related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.cause.diagnostic_source() + } +} + +pub fn resolve_schemas(values: &[(&str, &Schema<'_>)]) -> Result<(), ResolutionError> { let modules = values .iter() - .map(|(name, value)| (*name, resolve::resolve_types(name, value))) + .map(|(name, schema)| (*name, resolve::resolve_types(name, schema))) .collect::>(); - for (_, module) in &modules { + for (schema, module) in modules + .iter() + .enumerate() + .map(|(i, (_, module))| (values[i].1, module)) + { let mut missing = Vec::new(); resolve::resolve_module_types(module, &mut missing); - let imports = resolve::resolve_module_imports(module, &modules)?; + let imports = + resolve::resolve_module_imports(module, &modules).map_err(|e| ResolutionError { + source_code: NamedSource::new( + schema + .path + .as_ref() + .map_or_else(|| "".to_owned(), |p| p.display().to_string()), + schema.source.to_owned(), + ), + cause: ResolveError::Import(e), + })?; for ty in missing { - resolve::resolve_type_remotely(ty, &imports)?; + resolve::resolve_type_remotely(ty, &imports).map_err(|e| ResolutionError { + source_code: NamedSource::new( + schema + .path + .as_ref() + .map_or_else(|| "".to_owned(), |p| p.display().to_string()), + schema.source.to_owned(), + ), + cause: e, + })?; } } diff --git a/crates/stef-compiler/src/resolve.rs b/crates/stef-compiler/src/resolve.rs index 44c4ef8..b760b08 100644 --- a/crates/stef-compiler/src/resolve.rs +++ b/crates/stef-compiler/src/resolve.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, ops::Range}; -use miette::Diagnostic; +use miette::{Diagnostic, NamedSource}; use stef_parser::{ DataType, Definition, ExternalType, Fields, Generics, Import, Name, Schema, Spanned, }; @@ -18,7 +18,13 @@ pub enum ResolveError { Import(#[from] ResolveImport), #[error("failed resolving type in remote modules")] #[diagnostic(transparent)] - Remote(#[from] ResolveRemote), + Remote(#[from] Box), +} + +impl From for ResolveError { + fn from(value: ResolveRemote) -> Self { + Self::Remote(value.into()) + } } #[derive(Debug, Diagnostic, Error)] @@ -93,6 +99,7 @@ pub struct InvalidKind { pub(crate) struct Module<'a> { /// Name of this module. pub name: &'a str, + schema: &'a Schema<'a>, /// Full path from the root (the schema) till here. path: String, path2: Vec<&'a str>, @@ -119,7 +126,11 @@ enum TypeKind { pub(crate) enum ResolvedImport<'a> { Module(&'a Module<'a>), - Type { name: &'a Name<'a>, generics: usize }, + Type { + schema: &'a Schema<'a>, + name: &'a Name<'a>, + generics: usize, + }, } impl<'a> Module<'a> { @@ -217,6 +228,7 @@ impl<'a> Module<'a> { .into()), TypeKind::Struct { generics } | TypeKind::Enum { generics } => { Ok(ResolvedImport::Type { + schema: self.schema, name: &definition.name, generics, }) @@ -259,20 +271,52 @@ impl<'a> Module<'a> { if generics != ty.generics.len() => { Err(RemoteGenericsCount { - definition: generics, - usage: ty.generics.len(), + amount: ty.generics.len(), used: ty.name.span().into(), + declaration: [RemoteGenericsCountDeclaration { + amount: generics, + source_code: NamedSource::new( + self.schema.path.as_ref().map_or_else( + || "".to_owned(), + |p| p.display().to_string(), + ), + self.schema.source.to_owned(), + ), + used: definition.name.span().into(), + }], } .into()) } TypeKind::Alias => Err(RemoteInvalidKind { kind: "type alias", used: ty.name.span().into(), + declaration: [RemoteInvalidKindDeclaration { + kind: "type alias", + source_code: NamedSource::new( + self.schema + .path + .as_ref() + .map_or_else(|| "".to_owned(), |p| p.display().to_string()), + self.schema.source.to_owned(), + ), + used: definition.name.span().into(), + }], } .into()), TypeKind::Const => Err(RemoteInvalidKind { kind: "constant", used: ty.name.span().into(), + declaration: [RemoteInvalidKindDeclaration { + kind: "constant", + source_code: NamedSource::new( + self.schema + .path + .as_ref() + .map_or_else(|| "".to_owned(), |p| p.display().to_string()), + self.schema.source.to_owned(), + ), + used: definition.name.span().into(), + }], } .into()), _ => Ok(()), @@ -354,8 +398,8 @@ pub(crate) fn resolve_module_types<'a>( } } -pub(crate) fn resolve_types<'a>(name: &'a str, value: &'a Schema<'_>) -> Module<'a> { - visit_module_tree(name, &[], &value.definitions) +pub(crate) fn resolve_types<'a>(name: &'a str, schema: &'a Schema<'a>) -> Module<'a> { + visit_module_tree(name, schema, &[], &schema.definitions) } /// Build up modules from the given one all the way down to all submodules. @@ -364,6 +408,7 @@ pub(crate) fn resolve_types<'a>(name: &'a str, value: &'a Schema<'_>) -> Module< /// 2nd step to ensure all used types are actually available and correct. fn visit_module_tree<'a>( name: &'a str, + schema: &'a Schema<'a>, path: &[&'a str], defs: &'a [Definition<'_>], ) -> Module<'a> { @@ -376,6 +421,7 @@ fn visit_module_tree<'a>( let mut module = Module { name, + schema, path: path.join("::"), path2: path, imports: Vec::new(), @@ -389,7 +435,7 @@ fn visit_module_tree<'a>( Definition::Module(m) => { module.modules.insert( m.name.get(), - visit_module_tree(m.name.get(), &module.path2, &m.definitions), + visit_module_tree(m.name.get(), schema, &module.path2, &m.definitions), ); } Definition::Struct(s) => module.types.push(Type { @@ -509,12 +555,9 @@ pub(crate) fn resolve_module_imports<'a>( #[derive(Debug, Diagnostic, Error)] pub enum ResolveRemote { - #[error("missing import for type {}", highlight::value(ty))] - MissingImport { - ty: String, - #[label("used here")] - used: Range, - }, + #[error(transparent)] + #[diagnostic(transparent)] + MissingImport(#[from] MissingImport), #[error(transparent)] #[diagnostic(transparent)] MissingModule(#[from] MissingModule), @@ -529,18 +572,39 @@ pub enum ResolveRemote { InvalidKind(#[from] RemoteInvalidKind), } +#[derive(Debug, Diagnostic, Error)] +#[error("missing import for type {}", highlight::value(ty))] +pub struct MissingImport { + ty: String, + #[label("used here")] + used: Range, +} + #[derive(Debug, Diagnostic, Error)] #[error( - "the definition has {} generics but the use side has {}", - highlight::value(definition), - highlight::value(usage) + "the use side has {} generic(s), mismatching with the declaration", + highlight::value(amount) )] #[diagnostic(help("the amount of generics must always match"))] pub struct RemoteGenericsCount { - pub definition: usize, - pub usage: usize, + pub amount: usize, #[label("used here")] - pub used: Range, + used: Range, + #[related] + declaration: [RemoteGenericsCountDeclaration; 1], +} + +#[derive(Debug, Diagnostic, Error)] +#[error( + "the declaration has {} generic(s), mismatching with the use side", + highlight::value(amount) +)] +pub struct RemoteGenericsCountDeclaration { + pub amount: usize, + #[source_code] + source_code: NamedSource, + #[label("declared here")] + used: Range, } #[derive(Debug, Diagnostic, Error)] @@ -552,13 +616,28 @@ pub struct RemoteGenericsCount { pub struct RemoteInvalidKind { pub kind: &'static str, #[label("used here")] - pub used: Range, + used: Range, + #[related] + pub declaration: [RemoteInvalidKindDeclaration; 1], +} + +#[derive(Debug, Diagnostic, Error)] +#[error( + "the definition is a {}, which can't be referenced", + highlight::sample(kind) +)] +pub struct RemoteInvalidKindDeclaration { + pub kind: &'static str, + #[source_code] + source_code: NamedSource, + #[label("declared here")] + used: Range, } pub(crate) fn resolve_type_remotely( ty: LocallyMissingType<'_>, imports: &[ResolvedImport<'_>], -) -> Result<(), super::Error> { +) -> Result<(), ResolveError> { if imports.is_empty() { return Err(ty.error.into()); } else if let Some(name) = ty.external.path.first() { @@ -570,7 +649,7 @@ pub(crate) fn resolve_type_remotely( match module { Some(module) => module.resolve_remote(ty.external)?, None => { - return Err(ResolveRemote::MissingImport { + return Err(ResolveRemote::MissingImport(MissingImport { ty: format!( "{}{}", ty.external @@ -584,35 +663,47 @@ pub(crate) fn resolve_type_remotely( ty.external.name ), used: ty.external.name.span().into(), - } + }) .into()); } } } else { let found = imports.iter().find_map(|import| match import { ResolvedImport::Module(_) => None, - ResolvedImport::Type { name, generics } => { - (name.get() == ty.external.name.get()).then_some((name, *generics)) - } + ResolvedImport::Type { + schema, + name, + generics, + } => (name.get() == ty.external.name.get()).then_some((schema, name, *generics)), }); - if let Some((_name, generics)) = found { + if let Some((schema, name, generics)) = found { if generics == ty.external.generics.len() { return Ok(()); } return Err(ResolveRemote::GenericsCount(RemoteGenericsCount { - definition: generics, - usage: ty.external.generics.len(), + amount: ty.external.generics.len(), used: ty.external.name.span().into(), + declaration: [RemoteGenericsCountDeclaration { + amount: generics, + source_code: NamedSource::new( + schema + .path + .as_ref() + .map_or_else(|| "".to_owned(), |p| p.display().to_string()), + schema.source.to_owned(), + ), + used: name.span().into(), + }], }) .into()); } - return Err(ResolveRemote::MissingImport { + return Err(ResolveRemote::MissingImport(MissingImport { ty: ty.external.name.get().to_owned(), used: ty.external.name.span().into(), - } + }) .into()); } diff --git a/crates/stef-compiler/tests/compiler.rs b/crates/stef-compiler/tests/compiler.rs index 4baa69a..64aff97 100644 --- a/crates/stef-compiler/tests/compiler.rs +++ b/crates/stef-compiler/tests/compiler.rs @@ -1,7 +1,7 @@ use std::{ fmt::{self, Display}, fs, - path::Path, + path::{Path, PathBuf}, sync::OnceLock, }; @@ -36,11 +36,17 @@ impl<'a> Display for Wrapper<'a> { } } +fn strip_path(path: &Path) -> PathBuf { + path.strip_prefix(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/inputs")) + .unwrap() + .to_owned() +} + #[test] fn validate_schema() { glob!("inputs/validate/*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let schema = Schema::parse(input.as_str()).unwrap(); + let schema = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap(); let result = stef_compiler::validate_schema(&schema).unwrap_err(); let report = Report::new(result).with_source_code(NamedSource::new( path.file_name().unwrap().to_string_lossy(), @@ -60,7 +66,7 @@ fn validate_schema() { fn resolve_schema_local() { glob!("inputs/resolve/local_*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let schema = Schema::parse(input.as_str()).unwrap(); + let schema = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap(); let result = stef_compiler::resolve_schemas(&[("test", &schema)]).unwrap_err(); let report = Report::new(result).with_source_code(NamedSource::new( path.file_name().unwrap().to_string_lossy(), @@ -78,15 +84,15 @@ fn resolve_schema_local() { #[test] fn resolve_schema_import() { - let input = fs::read_to_string( - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/inputs/resolve/datetime.stef"), - ) - .unwrap(); - let datetime = Schema::parse(input.as_str()).unwrap(); + let input = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/inputs/resolve/datetime.stef" + )); + let datetime = Schema::parse(input, Some(Path::new("resolve/datetime.stef"))).unwrap(); glob!("inputs/resolve/import_*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let schema = Schema::parse(input.as_str()).unwrap(); + let schema = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap(); let result = stef_compiler::resolve_schemas(&[("test", &schema), ("datetime", &datetime)]) .unwrap_err(); let report = Report::new(result).with_source_code(NamedSource::new( @@ -105,15 +111,15 @@ fn resolve_schema_import() { #[test] fn resolve_schema_remote() { - let input = fs::read_to_string( - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/inputs/resolve/datetime.stef"), - ) - .unwrap(); - let datetime = Schema::parse(input.as_str()).unwrap(); + let input = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/inputs/resolve/datetime.stef" + )); + let datetime = Schema::parse(input, Some(Path::new("resolve/datetime.stef"))).unwrap(); glob!("inputs/resolve/remote_*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let schema = Schema::parse(input.as_str()).unwrap(); + let schema = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap(); let result = stef_compiler::resolve_schemas(&[("test", &schema), ("datetime", &datetime)]) .unwrap_err(); let report = Report::new(result).with_source_code(NamedSource::new( diff --git a/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_gens_mismatch.stef.snap b/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_gens_mismatch.stef.snap index 1cd49df..2305308 100644 --- a/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_gens_mismatch.stef.snap +++ b/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_gens_mismatch.stef.snap @@ -5,7 +5,7 @@ input_file: crates/stef-compiler/tests/inputs/resolve/remote_gens_mismatch.stef --- × type resolution failed ├─▶ failed resolving type in remote modules - ╰─▶ the definition has ❬Y❭0❬Y❭ generics but the use side has ❬Y❭1❬Y❭ + ╰─▶ the use side has ❬Y❭1❬Y❭ generic(s), mismatching with the declaration ╭─[remote_gens_mismatch.stef:1:1] 1 │ use datetime::timing::Timestamp; 2 │ @@ -17,3 +17,14 @@ input_file: crates/stef-compiler/tests/inputs/resolve/remote_gens_mismatch.stef ╰──── help: the amount of generics must always match +Error: × the declaration has ❬Y❭0❬Y❭ generic(s), mismatching with the use side + ╭─[resolve/datetime.stef:7:1] + 7 │ mod timing { + 8 │ const UNIX_EPOCH: u64 = 0; + 9 │ + 10 │ struct Timestamp(u64 @1) + · ────┬──── + · ╰── declared here + 11 │ } + ╰──── + diff --git a/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_kind_mismatch.stef.snap b/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_kind_mismatch.stef.snap index 6d8580d..5cc1ae9 100644 --- a/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_kind_mismatch.stef.snap +++ b/crates/stef-compiler/tests/snapshots/compiler__error_resolve@remote_kind_mismatch.stef.snap @@ -17,3 +17,16 @@ input_file: crates/stef-compiler/tests/inputs/resolve/remote_kind_mismatch.stef ╰──── help: only struct and enum definitions can be used +Error: × the definition is a ❬B❭constant❬B❭, which can't be referenced + ╭─[resolve/datetime.stef:5:1] + 5 │ } + 6 │ + 7 │ mod timing { + 8 │ const UNIX_EPOCH: u64 = 0; + · ─────┬──── + · ╰── declared here + 9 │ + 10 │ struct Timestamp(u64 @1) + 11 │ } + ╰──── + diff --git a/crates/stef-parser/src/error.rs b/crates/stef-parser/src/error.rs index 51d1d59..ef7151e 100644 --- a/crates/stef-parser/src/error.rs +++ b/crates/stef-parser/src/error.rs @@ -11,7 +11,7 @@ use std::{ fmt::{self, Display}, }; -use miette::Diagnostic; +use miette::{Diagnostic, NamedSource}; use winnow::error::ErrorKind; pub use crate::parser::{ @@ -24,14 +24,66 @@ pub use crate::parser::{ }; /// Reason why a `STEF` schema definition was invalid. +#[derive(Debug)] +pub struct ParseSchemaError { + pub(crate) source_code: NamedSource, + pub cause: ParseSchemaCause, +} + +impl Error for ParseSchemaError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.cause.source() + } +} + +impl Display for ParseSchemaError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.cause.fmt(f) + } +} + +impl Diagnostic for ParseSchemaError { + fn code<'a>(&'a self) -> Option> { + self.cause.code() + } + + fn severity(&self) -> Option { + self.cause.severity() + } + + fn help<'a>(&'a self) -> Option> { + self.cause.help() + } + + fn url<'a>(&'a self) -> Option> { + self.cause.url() + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(&self.source_code) + } + + fn labels(&self) -> Option + '_>> { + self.cause.labels() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + self.cause.related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + self.cause.diagnostic_source() + } +} + #[derive(Debug, Diagnostic)] -pub enum ParseSchemaError { +pub enum ParseSchemaCause { Parser(ErrorKind), #[diagnostic(transparent)] Definition(ParseDefinitionError), } -impl Error for ParseSchemaError { +impl Error for ParseSchemaCause { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::Parser(kind) => kind.source(), @@ -40,7 +92,7 @@ impl Error for ParseSchemaError { } } -impl Display for ParseSchemaError { +impl Display for ParseSchemaCause { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Parser(kind) => kind.fmt(f), @@ -49,13 +101,13 @@ impl Display for ParseSchemaError { } } -impl From for ParseSchemaError { +impl From for ParseSchemaCause { fn from(value: ParseDefinitionError) -> Self { Self::Definition(value) } } -impl winnow::error::ParserError for ParseSchemaError { +impl winnow::error::ParserError for ParseSchemaCause { fn from_error_kind(_: &I, kind: winnow::error::ErrorKind) -> Self { Self::Parser(kind) } diff --git a/crates/stef-parser/src/lib.rs b/crates/stef-parser/src/lib.rs index 71ce768..6918f96 100644 --- a/crates/stef-parser/src/lib.rs +++ b/crates/stef-parser/src/lib.rs @@ -8,7 +8,7 @@ //! Parse a basic `STEF` schema and print it back out. //! //! ``` -//! let schema = stef_parser::Schema::parse("struct Sample(u32 @1)").unwrap(); +//! let schema = stef_parser::Schema::parse("struct Sample(u32 @1)", None).unwrap(); //! //! // Pretty print the schema itself //! println!("{schema}"); @@ -23,12 +23,15 @@ use std::{ fmt::{self, Display}, ops::Range, + path::{Path, PathBuf}, }; pub use miette::{Diagnostic, LabeledSpan}; -use miette::{IntoDiagnostic, Report, Result}; +use miette::{IntoDiagnostic, NamedSource, Result}; use winnow::Parser; +use self::error::ParseSchemaError; + pub mod error; mod ext; mod highlight; @@ -88,8 +91,8 @@ pub trait Spanned { /// /// Fails if the schema is not proper. The returned error will try to describe the problem as /// precise as possible. -pub fn from_str(schema: &str) -> Result> { - Schema::parse(schema) +pub fn from_str<'a>(schema: &'a str, path: Option<&Path>) -> Result> { + Schema::parse(schema, path).into_diagnostic() } /// Shorthand for calling [`Schema::parse`], but converts the byte slice to valid [`&str`] first. @@ -98,14 +101,21 @@ pub fn from_str(schema: &str) -> Result> { /// /// Fails if the schema is not proper. The returned error will try to describe the problem as /// precise as possible. Or, in case the given bytes are not valid UTF-8. -pub fn from_slice(schema: &[u8]) -> Result> { +pub fn from_slice<'a>(schema: &'a [u8], path: Option<&Path>) -> Result> { let s = std::str::from_utf8(schema).into_diagnostic()?; - Schema::parse(s) + Schema::parse(s, path).into_diagnostic() } /// Uppermost element, describing a single _`STEF` Schema_ file. #[derive(Debug, PartialEq)] pub struct Schema<'a> { + /// Physical location of the file that contains the schema source code. + /// + /// Might be missing if the schema originated from an inline string or otherwise omitted during + /// the parsing process. + pub path: Option, + /// Original source code form which this schema was parsed. + pub source: &'a str, /// List of all the definitions that make up the schema. pub definitions: Vec>, } @@ -113,14 +123,28 @@ pub struct Schema<'a> { impl<'a> Schema<'a> { /// Try to parse the given schema. /// + /// The optional path is not necessary, but can help to improve the error messages that are + /// printed out on the terminal in case of an invalid schema. Effectively that means messages + /// will include direct links to the files. + /// /// # Errors /// /// Fails if the schema is not proper. The returned error will try to describe the problem as /// precise as possible. - pub fn parse(input: &'a str) -> Result { + pub fn parse(input: &'a str, path: Option<&Path>) -> Result { parser::parse_schema .parse(winnow::Located::new(input)) - .map_err(|e| Report::new(e.into_inner()).with_source_code(input.to_owned())) + .map(|mut schema| { + schema.path = path.map(ToOwned::to_owned); + schema + }) + .map_err(|e| ParseSchemaError { + source_code: NamedSource::new( + path.map_or_else(|| "".to_owned(), |p| p.display().to_string()), + input.to_owned(), + ), + cause: e.into_inner(), + }) } } diff --git a/crates/stef-parser/src/parser.rs b/crates/stef-parser/src/parser.rs index a186914..43977cb 100644 --- a/crates/stef-parser/src/parser.rs +++ b/crates/stef-parser/src/parser.rs @@ -25,7 +25,7 @@ pub use self::{ types::{Cause as ParseTypeCause, ParseError as ParseTypeError}, }; use crate::{ - error::{ParseDefinitionError, ParseSchemaError}, + error::{ParseDefinitionError, ParseSchemaCause}, ext::ParserExt, Definition, Schema, }; @@ -43,9 +43,11 @@ mod structs; mod types; type Input<'i> = winnow::Located<&'i str>; -type Result = winnow::PResult; +type Result = winnow::PResult; + +pub(crate) fn parse_schema<'i>(input: &mut Input<'i>) -> Result, ParseSchemaCause> { + let source = *input.as_ref(); -pub(crate) fn parse_schema<'i>(input: &mut Input<'i>) -> Result, ParseSchemaError> { trace( "schema", terminated( @@ -54,7 +56,11 @@ pub(crate) fn parse_schema<'i>(input: &mut Input<'i>) -> Result, Pars ), ) .parse_next(input) - .map(|definitions| Schema { definitions }) + .map(|definitions| Schema { + path: None, + source, + definitions, + }) } fn parse_definition<'i>(input: &mut Input<'i>) -> Result, ParseDefinitionError> { diff --git a/crates/stef-parser/tests/parser.rs b/crates/stef-parser/tests/parser.rs index 2645b63..59119e7 100644 --- a/crates/stef-parser/tests/parser.rs +++ b/crates/stef-parser/tests/parser.rs @@ -1,17 +1,24 @@ use std::{ fmt::{self, Display}, fs, + path::{Path, PathBuf}, }; use insta::{assert_snapshot, glob, with_settings}; use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler}; use stef_parser::Schema; +fn strip_path(path: &Path) -> PathBuf { + path.strip_prefix(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/inputs")) + .unwrap() + .to_owned() +} + #[test] fn parse_schema() { glob!("inputs/*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let value = Schema::parse(input.as_str()).unwrap(); + let value = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap(); with_settings!({ description => input.trim(), @@ -42,13 +49,13 @@ fn parse_invalid_schema() { glob!("inputs/invalid/*.stef", |path| { let input = fs::read_to_string(path).unwrap(); - let value = Schema::parse(input.as_str()).unwrap_err(); + let value = Schema::parse(input.as_str(), Some(&strip_path(path))).unwrap_err(); with_settings!({ description => input.trim(), omit_expression => true, }, { - assert_snapshot!("error", Wrapper(&handler, &*value).to_string()); + assert_snapshot!("error", Wrapper(&handler, &value).to_string()); }); }); } diff --git a/crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap index eed27b0..f23ebb5 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@alias_name.stef.snap @@ -7,7 +7,7 @@ stef::parse::alias_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru × Failed to parse type alias declaration ╰─▶ Invalid alias name - ╭──── + ╭─[invalid/alias_name.stef:1:1] 1 │ type sImple = Simple; · ▲ · ╰── In this declaration @@ -17,7 +17,7 @@ stef::parse::alias_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru Error: stef::parse::alias_def::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseAliasCause.html#variant.InvalidName) × Invalid alias name - ╭──── + ╭─[invalid/alias_name.stef:1:1] 1 │ type sImple = Simple; · ▲ · ╰── Problematic character diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap index a34e249..535e9cf 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@const_literal.stef.snap @@ -7,7 +7,7 @@ stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru × Failed to parse const declaration ╰─▶ Unexpected character - ╭──── + ╭─[invalid/const_literal.stef:1:1] 1 │ const VALUE: u32 = 1z; · ───────────┬────────── · ╰── In this declaration @@ -17,7 +17,7 @@ stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru Error: stef::parse::const_def::char (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseConstCause.html#variant.UnexpectedChar) × Unexpected character - ╭──── + ╭─[invalid/const_literal.stef:1:1] 1 │ const VALUE: u32 = 1z; · ▲ · ╰── Problematic character diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap index 15e60e5..fb64c04 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@const_literal_bool.stef.snap @@ -8,7 +8,7 @@ stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru × Failed to parse const declaration ├─▶ Failed to parse literal value ╰─▶ error Tag - ╭──── + ╭─[invalid/const_literal_bool.stef:1:1] 1 │ const VALUE: bool = truze; · ─────────────┬──────────── · ╰── In this declaration @@ -19,7 +19,7 @@ Error: stef::parse::literal (https://docs.rs/stef-parser/0.1.0/stef_parser/error × Failed to parse literal value ╰─▶ error Tag - ╭──── + ╭─[invalid/const_literal_bool.stef:1:1] 1 │ const VALUE: bool = truze; · ▲ · ╰── In this declaration diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap index 31fa16d..d5b85c2 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@const_literal_float.stef.snap @@ -7,7 +7,7 @@ stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru × Failed to parse const declaration ╰─▶ Unexpected character - ╭──── + ╭─[invalid/const_literal_float.stef:1:1] 1 │ const VALUE: f64 = 1.0z; · ────────────┬─────────── · ╰── In this declaration @@ -17,7 +17,7 @@ stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru Error: stef::parse::const_def::char (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseConstCause.html#variant.UnexpectedChar) × Unexpected character - ╭──── + ╭─[invalid/const_literal_float.stef:1:1] 1 │ const VALUE: f64 = 1.0z; · ▲ · ╰── Problematic character diff --git a/crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap index 5fa2636..6558910 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@const_name.stef.snap @@ -7,7 +7,7 @@ stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru × Failed to parse const declaration ╰─▶ Invalid const name - ╭──── + ╭─[invalid/const_name.stef:1:1] 1 │ const vALUE: u32 = 1; · ──────────┬────────── · ╰── In this declaration @@ -17,7 +17,7 @@ stef::parse::const_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru Error: stef::parse::const_def::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseConstCause.html#variant.InvalidName) × Invalid const name - ╭──── + ╭─[invalid/const_name.stef:1:1] 1 │ const vALUE: u32 = 1; · ▲ · ╰── Problematic character diff --git a/crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap index 0d4ae4c..3d9a2c8 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@enum_name.stef.snap @@ -7,7 +7,7 @@ stef::parse::enum_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struc × Failed to parse enum declaration ╰─▶ Invalid enum name - ╭─[1:1] + ╭─[invalid/enum_name.stef:1:1] 1 │ enum sample { · ▲ · ╰── In this declaration @@ -18,7 +18,7 @@ stef::parse::enum_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struc Error: stef::parse::enum_def::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseEnumCause.html#variant.InvalidName) × Invalid enum name - ╭─[1:1] + ╭─[invalid/enum_name.stef:1:1] 1 │ enum sample { · ▲ · ╰── Problematic character diff --git a/crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap index 5030302..bd2e565 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@field_name.stef.snap @@ -8,7 +8,7 @@ stef::parse::struct_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/str × Failed to parse struct declaration ├─▶ Failed to parse fields declaration ╰─▶ Invalid field name - ╭─[1:1] + ╭─[invalid/field_name.stef:1:1] 1 │ ╭─▶ struct Sample { 2 │ │ 1value: u32 @1, 3 │ ├─▶ } @@ -20,7 +20,7 @@ Error: stef::parse::id (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru × Failed to parse fields declaration ╰─▶ Invalid field name - ╭─[1:1] + ╭─[invalid/field_name.stef:1:1] 1 │ ╭─▶ struct Sample { 2 │ ├─▶ 1value: u32 @1, · ╰──── In this declaration @@ -32,7 +32,7 @@ Error: stef::parse::id (https://docs.rs/stef-parser/0.1.0/stef_parser/error/stru Error: stef::parse::fields::named::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseFieldsCause.html#variant.InvalidName) × Invalid field name - ╭─[1:1] + ╭─[invalid/field_name.stef:1:1] 1 │ struct Sample { 2 │ 1value: u32 @1, · ▲ diff --git a/crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap index e943907..87c36a9 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@mod_name.stef.snap @@ -7,7 +7,7 @@ stef::parse::mod_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct × Failed to parse id declaration ╰─▶ Invalid module name - ╭──── + ╭─[invalid/mod_name.stef:1:1] 1 │ mod Sample {} · ──────┬────── · ╰── In this declaration @@ -17,7 +17,7 @@ stef::parse::mod_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/struct Error: stef::parse::module::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseModuleCause.html#variant.InvalidName) × Invalid module name - ╭──── + ╭─[invalid/mod_name.stef:1:1] 1 │ mod Sample {} · ▲ · ╰── Problematic character diff --git a/crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap b/crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap index 2a3c20a..51324af 100644 --- a/crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__error@struct_name.stef.snap @@ -7,7 +7,7 @@ stef::parse::struct_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/str × Failed to parse struct declaration ╰─▶ Invalid struct name - ╭─[1:1] + ╭─[invalid/struct_name.stef:1:1] 1 │ ╭─▶ struct sample { 2 │ │ value: u32 @1, 3 │ ├─▶ } @@ -18,7 +18,7 @@ stef::parse::struct_def (https://docs.rs/stef-parser/0.1.0/stef_parser/error/str Error: stef::parse::struct_def::invalid_name (https://docs.rs/stef-parser/0.1.0/stef_parser/error/enum.ParseStructCause.html#variant.InvalidName) × Invalid struct name - ╭─[1:1] + ╭─[invalid/struct_name.stef:1:1] 1 │ struct sample { · ▲ · ╰── Problematic character diff --git a/crates/stef-parser/tests/snapshots/parser__parse@alias_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@alias_basic.stef.snap index d3d07ce..c2ab65f 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@alias_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@alias_basic.stef.snap @@ -4,6 +4,10 @@ description: "/// Sample type alias.\ntype Sample = u32;" input_file: crates/stef-parser/tests/inputs/alias_basic.stef --- Schema { + path: Some( + "alias_basic.stef", + ), + source: "/// Sample type alias.\ntype Sample = u32;\n", definitions: [ TypeAlias( TypeAlias { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attribute_multi.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attribute_multi.stef.snap index 041e170..5f7d090 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@attribute_multi.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@attribute_multi.stef.snap @@ -4,6 +4,10 @@ description: "#[validate(min = 1, max = 100)]\nstruct Sample" input_file: crates/stef-parser/tests/inputs/attribute_multi.stef --- Schema { + path: Some( + "attribute_multi.stef", + ), + source: "#[validate(min = 1, max = 100)]\nstruct Sample\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attribute_single.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attribute_single.stef.snap index 538bba3..2bde824 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@attribute_single.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@attribute_single.stef.snap @@ -4,6 +4,10 @@ description: "#[deprecated = \"don't use\"]\nstruct Sample" input_file: crates/stef-parser/tests/inputs/attribute_single.stef --- Schema { + path: Some( + "attribute_single.stef", + ), + source: "#[deprecated = \"don't use\"]\nstruct Sample\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attribute_unit.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attribute_unit.stef.snap index 34bf153..5bf03e6 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@attribute_unit.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@attribute_unit.stef.snap @@ -4,6 +4,10 @@ description: "#[deprecated]\nstruct Sample" input_file: crates/stef-parser/tests/inputs/attribute_unit.stef --- Schema { + path: Some( + "attribute_unit.stef", + ), + source: "#[deprecated]\nstruct Sample\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap index 4e0eded..53aaf0b 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@attributes.stef.snap @@ -4,6 +4,10 @@ description: "#[deprecated = \"don't use\", compress]\n#[validate(\n in_range input_file: crates/stef-parser/tests/inputs/attributes.stef --- Schema { + path: Some( + "attributes.stef", + ), + source: "#[deprecated = \"don't use\", compress]\n#[validate(\n in_range(min = 100, max = 200),\n non_empty,\n)]\nstruct Sample\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@attributes_min_ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@attributes_min_ws.stef.snap index d50fab3..0f2bd41 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@attributes_min_ws.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@attributes_min_ws.stef.snap @@ -4,6 +4,10 @@ description: "#[deprecated=\"don't use\",compress]\n#[validate(in_range(min=100, input_file: crates/stef-parser/tests/inputs/attributes_min_ws.stef --- Schema { + path: Some( + "attributes_min_ws.stef", + ), + source: "#[deprecated=\"don't use\",compress]\n#[validate(in_range(min=100,max=200),non_empty)]\nstruct Sample\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@const_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@const_basic.stef.snap index 6b4fc60..8c26162 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@const_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@const_basic.stef.snap @@ -4,6 +4,10 @@ description: "const BOOL_TRUE: bool = true;\nconst BOOL_FALSE: bool = false;\nco input_file: crates/stef-parser/tests/inputs/const_basic.stef --- Schema { + path: Some( + "const_basic.stef", + ), + source: "const BOOL_TRUE: bool = true;\nconst BOOL_FALSE: bool = false;\nconst INT: u32 = 100;\nconst FLOAT: f64 = 5.5;\nconst STRING: string = \"value\";\nconst BYTES: bytes = [1, 2, 3];\n", definitions: [ Const( Const { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@const_string.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@const_string.stef.snap index 9c9172a..8a42ab9 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@const_string.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@const_string.stef.snap @@ -4,6 +4,10 @@ description: "const SIMPLE: string = \"value\";\n\nconst NEWLINE_ESCAPE: string input_file: crates/stef-parser/tests/inputs/const_string.stef --- Schema { + path: Some( + "const_string.stef", + ), + source: "const SIMPLE: string = \"value\";\n\nconst NEWLINE_ESCAPE: string = \"one \\\n two \\\n three\\\n\";\n\nconst ESCAPES: string = \"escape basics \\r\\n \\t \\b \\f \\\\ \\\"\\\n hello\\\" \\n\\\n unicode \\u{2764} \\\n emoji ❤ \\\n\";\n\nconst MULTILINE: string = \"a\n b\n c\n\";\n", definitions: [ Const( Const { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum_basic.stef.snap index 9cdb82f..d6e5d16 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@enum_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum_basic.stef.snap @@ -4,6 +4,10 @@ description: "/// Sample enum.\nenum Sample {\n One @1,\n /// Second varia input_file: crates/stef-parser/tests/inputs/enum_basic.stef --- Schema { + path: Some( + "enum_basic.stef", + ), + source: "/// Sample enum.\nenum Sample {\n One @1,\n /// Second variant\n Two(u32 @1, u64 @2) @2,\n Three {\n field1: u32 @1,\n /// Second field of third variant\n field2: bool @2,\n } @3,\n}\n", definitions: [ Enum( Enum { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum_generics.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum_generics.stef.snap index 181de33..a6a22bb 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@enum_generics.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum_generics.stef.snap @@ -4,6 +4,10 @@ description: "/// Enum with generics.\nenum Sample {\n One @1,\n input_file: crates/stef-parser/tests/inputs/enum_generics.stef --- Schema { + path: Some( + "enum_generics.stef", + ), + source: "/// Enum with generics.\nenum Sample {\n One @1,\n Two(A @1, B @2) @2,\n Three {\n field1: C @1,\n field2: D @2,\n } @3,\n}\n", definitions: [ Enum( Enum { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum_many_ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum_many_ws.stef.snap index d8664f2..8f82438 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@enum_many_ws.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum_many_ws.stef.snap @@ -4,6 +4,10 @@ description: "/// Sample enum.\n enum Sample {\n\n One @1,\n\n input_file: crates/stef-parser/tests/inputs/enum_many_ws.stef --- Schema { + path: Some( + "enum_many_ws.stef", + ), + source: "\n /// Sample enum.\n enum Sample {\n\n One @1,\n\n Two ( u32 @1, u64 @2) @2,\n\n Three {\n\n field1: u32 @1,\n\n field2: bool @2,\n\n } @3,\n\n }\n", definitions: [ Enum( Enum { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@enum_min_ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@enum_min_ws.stef.snap index 5fb90e2..c79633a 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@enum_min_ws.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@enum_min_ws.stef.snap @@ -4,6 +4,10 @@ description: "enum Sample{One@1,Two(u32@1,u64@2,T@3)@2,Three{field1:u32@1,fie input_file: crates/stef-parser/tests/inputs/enum_min_ws.stef --- Schema { + path: Some( + "enum_min_ws.stef", + ), + source: "enum Sample{One@1,Two(u32@1,u64@2,T@3)@2,Three{field1:u32@1,field2:bool@2,field3:T@3}@3}\n", definitions: [ Enum( Enum { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@import_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@import_basic.stef.snap index 3f7722d..c52e146 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@import_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@import_basic.stef.snap @@ -4,6 +4,10 @@ description: "use other::schema::Sample;\nuse second::submodule;" input_file: crates/stef-parser/tests/inputs/import_basic.stef --- Schema { + path: Some( + "import_basic.stef", + ), + source: "use other::schema::Sample;\nuse second::submodule;\n", definitions: [ Import( Import { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@module_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@module_basic.stef.snap index 5d61a65..3ab0f94 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@module_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@module_basic.stef.snap @@ -4,6 +4,10 @@ description: "mod a {\n /// Inner module\n mod b {\n enum Sample {\ input_file: crates/stef-parser/tests/inputs/module_basic.stef --- Schema { + path: Some( + "module_basic.stef", + ), + source: "mod a {\n /// Inner module\n mod b {\n enum Sample {\n One @1,\n }\n }\n\n struct Sample {\n value: u32 @1,\n }\n}\n", definitions: [ Module( Module { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@schema_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@schema_basic.stef.snap index cb355b8..a697aae 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@schema_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@schema_basic.stef.snap @@ -4,6 +4,10 @@ description: "/// Basic struct.\nstruct SampleStruct {\n a: u32 @1,\n b: b input_file: crates/stef-parser/tests/inputs/schema_basic.stef --- Schema { + path: Some( + "schema_basic.stef", + ), + source: "/// Basic struct.\nstruct SampleStruct {\n a: u32 @1,\n b: bool @2,\n}\n\n/// Sample enum.\nenum SampleEnum {\n One @1,\n Two(u32 @1, u64 @2) @2,\n Three {\n field1: u32 @1,\n field2: bool @2,\n } @3,\n}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct_basic.stef.snap index e277ac6..f629c9b 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@struct_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct_basic.stef.snap @@ -4,6 +4,10 @@ description: "/// Basic struct.\nstruct Sample {\n a: u32 @1,\n /// Second input_file: crates/stef-parser/tests/inputs/struct_basic.stef --- Schema { + path: Some( + "struct_basic.stef", + ), + source: "/// Basic struct.\nstruct Sample {\n a: u32 @1,\n /// Second field\n b: bool @2,\n}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct_generics.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct_generics.stef.snap index 846c0bd..927067c 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@struct_generics.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct_generics.stef.snap @@ -4,6 +4,10 @@ description: "/// Generic key-value pair.\nstruct KeyValue {\n key: K @ input_file: crates/stef-parser/tests/inputs/struct_generics.stef --- Schema { + path: Some( + "struct_generics.stef", + ), + source: "/// Generic key-value pair.\nstruct KeyValue {\n key: K @1,\n value: V @2,\n}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct_many_ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct_many_ws.stef.snap index 139f2ba..bd4dc1d 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@struct_many_ws.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct_many_ws.stef.snap @@ -4,6 +4,10 @@ description: "/// Some comment\n struct Sample<\n T\n input_file: crates/stef-parser/tests/inputs/struct_many_ws.stef --- Schema { + path: Some( + "struct_many_ws.stef", + ), + source: "\n /// Some comment\n struct Sample<\n T\n > {\n\n a: u32 @1,\n b: bool @2,\n c: T @3,\n }\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct_min_ws.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct_min_ws.stef.snap index 59c078e..33ffdba 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@struct_min_ws.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct_min_ws.stef.snap @@ -4,6 +4,10 @@ description: "struct Sample{a:u32@1,b:bool@2,c:T@3}" input_file: crates/stef-parser/tests/inputs/struct_min_ws.stef --- Schema { + path: Some( + "struct_min_ws.stef", + ), + source: "struct Sample{a:u32@1,b:bool@2,c:T@3}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@struct_tuple.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@struct_tuple.stef.snap index 1cff906..0dd4b4b 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@struct_tuple.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@struct_tuple.stef.snap @@ -4,6 +4,10 @@ description: "/// Basic struct.\nstruct Sample(u32 @1, bool @2)" input_file: crates/stef-parser/tests/inputs/struct_tuple.stef --- Schema { + path: Some( + "struct_tuple.stef", + ), + source: "/// Basic struct.\nstruct Sample(u32 @1, bool @2)\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types_basic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types_basic.stef.snap index f7c14dc..22e6702 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@types_basic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@types_basic.stef.snap @@ -4,6 +4,10 @@ description: "struct Sample {\n f01: bool @1,\n f02: u8 @2,\n f03: u16 input_file: crates/stef-parser/tests/inputs/types_basic.stef --- Schema { + path: Some( + "types_basic.stef", + ), + source: "struct Sample {\n f01: bool @1,\n f02: u8 @2,\n f03: u16 @3,\n f04: u32 @4,\n f05: u64 @5,\n f06: u128 @6,\n f07: i8 @7,\n f08: i16 @8,\n f09: i32 @9,\n f10: i64 @10,\n f11: i128 @11,\n f12: f32 @12,\n f13: f64 @13,\n f14: string @14,\n f15: &string @15,\n f16: bytes @16,\n f17: &bytes @17,\n f18: box @18,\n f19: box @19,\n f20: (u32, u32, u32) @20,\n f21: [u32; 12] @21,\n}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types_generic.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types_generic.stef.snap index 35aa2ee..bcccba4 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@types_generic.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@types_generic.stef.snap @@ -4,6 +4,10 @@ description: "struct Sample {\n f1: vec @1,\n f2: hash_map @1,\n f2: hash_map @2,\n f3: hash_set @3,\n f4: option @4,\n f5: non_zero @5,\n}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types_nested.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types_nested.stef.snap index b281049..c8a5c92 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@types_nested.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@types_nested.stef.snap @@ -4,6 +4,10 @@ description: "struct Sample {\n value: vec>>>> @1,\n}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types_non_zero.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types_non_zero.stef.snap index 3d44cc9..dfc25e2 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@types_non_zero.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@types_non_zero.stef.snap @@ -4,6 +4,10 @@ description: "struct Sample {\n f01: non_zero @1,\n f02: non_zero input_file: crates/stef-parser/tests/inputs/types_non_zero.stef --- Schema { + path: Some( + "types_non_zero.stef", + ), + source: "struct Sample {\n f01: non_zero @1,\n f02: non_zero @2,\n f03: non_zero @3,\n f04: non_zero @4,\n f05: non_zero @5,\n f06: non_zero @6,\n f07: non_zero @7,\n f08: non_zero @8,\n f09: non_zero @9,\n f10: non_zero @10,\n f11: non_zero @11,\n f12: non_zero @12,\n f13: non_zero> @13,\n f14: non_zero> @14,\n f15: non_zero> @15,\n}\n", definitions: [ Struct( Struct { diff --git a/crates/stef-parser/tests/snapshots/parser__parse@types_ref.stef.snap b/crates/stef-parser/tests/snapshots/parser__parse@types_ref.stef.snap index 32ae7d2..be1c020 100644 --- a/crates/stef-parser/tests/snapshots/parser__parse@types_ref.stef.snap +++ b/crates/stef-parser/tests/snapshots/parser__parse@types_ref.stef.snap @@ -4,6 +4,10 @@ description: "struct Sample {\n basic: Test123 @1,\n with_generics: KeyVal input_file: crates/stef-parser/tests/inputs/types_ref.stef --- Schema { + path: Some( + "types_ref.stef", + ), + source: "struct Sample {\n basic: Test123 @1,\n with_generics: KeyValue @2,\n}\n\nenum Test123 {\n Value @1,\n}\n\nstruct KeyValue {\n key: K @1,\n value: V @2,\n}\n", definitions: [ Struct( Struct {