Skip to content

Commit d6ab3e0

Browse files
Merge pull request #262 from 1Password/darrell/use-span-for-line-column-nums-errors
Use Span to capture line and column numbers for parsing rejections.
2 parents c798580 + 0d0dccd commit d6ab3e0

15 files changed

Lines changed: 349 additions & 191 deletions

File tree

Cargo.lock

Lines changed: 23 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ inherits = "release"
3535

3636
[workspace.dependencies]
3737
log = "0.4"
38-
flexi_logger = "0.30"
38+
flexi_logger = "0.31"

cli/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ python = []
1717

1818
[dependencies]
1919
clap = { version = "4.5", features = [
20-
"cargo",
21-
"derive",
22-
"unicode",
23-
"wrap_help",
20+
"cargo",
21+
"derive",
22+
"unicode",
23+
"wrap_help",
2424
] }
2525
ignore = "0.4"
2626
once_cell = "1"
2727
serde = { version = "1", features = ["derive"] }
28-
toml = "0.8"
28+
toml = "0.9"
2929
typeshare-core = { path = "../core", version = "=1.13.3" }
3030
log.workspace = true
3131
flexi_logger.workspace = true

cli/src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ fn generate_types(config_file: Option<&Path>, options: &Args) -> anyhow::Result<
138138

139139
check_parse_errors(&parsed_data)?;
140140

141-
info!("typeshare started writing generated types");
141+
info!(
142+
"typeshare started writing {} generated types",
143+
parsed_data.len()
144+
);
142145

143146
write_generated(destination, lang.as_mut(), parsed_data, import_candidates)?;
144147

cli/src/parse.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Source file parsing.
2+
use anyhow::anyhow;
23
use anyhow::Context;
34
use crossbeam::channel::bounded;
45
use ignore::{DirEntry, WalkBuilder, WalkState};
@@ -8,8 +9,9 @@ use std::{
89
};
910
use typeshare_core::{
1011
context::{ParseContext, ParseFileContext},
12+
error::ParseErrorWithSpan,
1113
language::{CrateName, CrateTypes, SupportedLanguage, SINGLE_FILE_CRATE_NAME},
12-
parser::{ParseError, ParsedData},
14+
parser::ParsedData,
1315
RenameExt,
1416
};
1517

@@ -80,23 +82,48 @@ pub fn all_types(file_mappings: &mut BTreeMap<CrateName, ParsedData>) -> CrateTy
8082
)
8183
}
8284

85+
#[derive(Debug)]
86+
enum ParseDirError {
87+
IO(String),
88+
ParseError(ParseErrorWithSpan),
89+
}
90+
91+
impl From<ParseErrorWithSpan> for ParseDirError {
92+
fn from(value: ParseErrorWithSpan) -> Self {
93+
Self::ParseError(value)
94+
}
95+
}
96+
97+
impl std::error::Error for ParseDirError {}
98+
99+
impl std::fmt::Display for ParseDirError {
100+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101+
match &self {
102+
ParseDirError::IO(s) => f.write_str(s),
103+
ParseDirError::ParseError(parse_error_with_span) => {
104+
write!(f, "{parse_error_with_span}")
105+
}
106+
}
107+
}
108+
}
109+
83110
fn parse_dir_entry(
84111
parse_context: &ParseContext,
85112
language_type: SupportedLanguage,
86113
dir_entry: &DirEntry,
87-
) -> Result<Option<ParsedData>, ParseError> {
114+
) -> Result<Option<ParsedData>, ParseDirError> {
88115
if dir_entry.path().is_dir() {
89116
return Ok(None);
90117
}
91118

92119
let Some(parse_file_context) =
93120
parse_file_context(parse_context.multi_file, language_type, dir_entry)
94-
.map_err(|err| ParseError::IOError(err.to_string()))?
121+
.map_err(|err| ParseDirError::IO(err.to_string()))?
95122
else {
96123
return Ok(None);
97124
};
98125

99-
typeshare_core::parser::parse(parse_context, parse_file_context)
126+
typeshare_core::parser::parse(parse_context, parse_file_context).map_err(Into::into)
100127
}
101128

102129
/// Use parallel builder to walk all source directories concurrently.
@@ -126,7 +153,7 @@ pub fn parallel_parse(
126153
Box::new(move |result| {
127154
let result = result.context("Failed traversing").and_then(|dir_entry| {
128155
parse_dir_entry(parse_context, language_type, &dir_entry)
129-
.with_context(|| format!("Parsing failed: {:?}", dir_entry.path()))
156+
.map_err(|err| anyhow!("Parsing failed: {:?}, {err}", dir_entry.path()))
130157
});
131158
match result {
132159
Ok(Some(parsed_data)) => {

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description = "The code generator used by Typeshare's command line tool"
77
repository = "https://github.com/1Password/typeshare"
88

99
[dependencies]
10-
proc-macro2 = "1"
10+
proc-macro2 = { version = "1", features = ["span-locations"] }
1111
quote = "1"
1212
syn = { version = "2", features = ["full", "visit"] }
1313
thiserror = "2"

core/src/error.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
//! Error types for parsing.
2+
use itertools::Itertools as _;
3+
use proc_macro2::Span;
4+
use thiserror::Error;
5+
6+
#[derive(Debug)]
7+
/// Wrapper for a parse error which includes a span.
8+
pub struct ParseErrorWithSpan {
9+
/// Parse error
10+
error: ParseError,
11+
/// Span
12+
span: Span,
13+
}
14+
15+
impl std::error::Error for ParseErrorWithSpan {}
16+
17+
impl std::fmt::Display for ParseErrorWithSpan {
18+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19+
write!(
20+
f,
21+
"{}, on line {} and column {}",
22+
self.error,
23+
self.span.start().line,
24+
self.span.start().column
25+
)
26+
}
27+
}
28+
29+
/// Errors that can occur while parsing Rust source input.
30+
#[derive(Debug, Error)]
31+
#[allow(missing_docs)]
32+
pub enum ParseError {
33+
#[error("{0}")]
34+
SynError(#[from] syn::Error),
35+
#[error("Failed to parse a Rust type: {0}")]
36+
RustTypeParseError(#[from] RustTypeParseError),
37+
#[error("Unsupported language encountered: {0}")]
38+
UnsupportedLanguage(String),
39+
#[error("Unsupported type encountered: {0}")]
40+
UnsupportedType(String),
41+
#[error("Tuple structs with more than one field are currently unsupported")]
42+
ComplexTupleStruct,
43+
#[error("Multiple unnamed associated types are not currently supported")]
44+
MultipleUnnamedAssociatedTypes,
45+
#[error("The serde tag attribute is not supported for non-algebraic enums: {enum_ident}")]
46+
SerdeTagNotAllowed { enum_ident: String },
47+
#[error("The serde content attribute is not supported for non-algebraic enums: {enum_ident}")]
48+
SerdeContentNotAllowed { enum_ident: String },
49+
#[error("Serde tag attribute needs to be specified for algebraic enum {enum_ident}. e.g. #[serde(tag = \"type\", content = \"content\")]")]
50+
SerdeTagRequired { enum_ident: String },
51+
#[error("Serde content attribute needs to be specified for algebraic enum {enum_ident}. e.g. #[serde(tag = \"type\", content = \"content\")]")]
52+
SerdeContentRequired { enum_ident: String },
53+
#[error("The expression assigned to this constant variable is not a numeric literal")]
54+
RustConstExprInvalid,
55+
#[error("You cannot use typeshare on a constant that is not a numeric literal")]
56+
RustConstTypeInvalid,
57+
#[error("The serde flatten attribute is not currently supported")]
58+
SerdeFlattenNotAllowed,
59+
#[error("IO error: {0}")]
60+
IOError(String),
61+
}
62+
63+
/// Parse error types that can capture a span and convert
64+
/// into the top level [ParseErrorWithSpan] type.
65+
pub trait WithSpan {
66+
/// Convert [Self] into a [`ParserErrorWithSpan`].
67+
fn with_span(self, span: Span) -> ParseErrorWithSpan;
68+
}
69+
70+
impl WithSpan for RustTypeParseError {
71+
fn with_span(self, span: Span) -> ParseErrorWithSpan {
72+
ParseError::RustTypeParseError(self).with_span(span)
73+
}
74+
}
75+
76+
impl WithSpan for ParseError {
77+
fn with_span(self, span: Span) -> ParseErrorWithSpan {
78+
ParseErrorWithSpan { error: self, span }
79+
}
80+
}
81+
82+
#[derive(Debug, Error)]
83+
/// Errors during file generation.
84+
pub enum GenerationError {
85+
/// The post generation step failed.
86+
#[error("Post generation failed: {0}")]
87+
PostGeneration(String),
88+
}
89+
90+
#[derive(Debug, Error)]
91+
#[allow(missing_docs)]
92+
pub enum RustTypeParseError {
93+
#[error("Unsupported type: \"{}\"", .0.iter().join(","))]
94+
UnsupportedType(Vec<String>),
95+
#[error("Unexpected token when parsing type: `{0}`. This is an internal error, please ping a typeshare developer to resolve this problem.")]
96+
UnexpectedToken(String),
97+
#[error("Tuples are not allowed in typeshare types")]
98+
UnexpectedParameterizedTuple,
99+
#[error("Could not parse numeric literal")]
100+
NumericLiteral(syn::parse::Error),
101+
}

core/src/language/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use crate::{
2-
parser::{ParseError, ParsedData},
2+
error::{GenerationError, ParseError},
3+
parser::ParsedData,
34
rust_types::{
45
Id, RustConst, RustEnum, RustEnumVariant, RustItem, RustStruct, RustType, RustTypeAlias,
56
RustTypeFormatError, SpecialRustType,
67
},
78
topsort::topsort,
89
visitors::ImportedType,
9-
GenerationError,
1010
};
1111
use itertools::Itertools;
1212
use log::warn;

core/src/language/swift.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
use crate::{
2+
error::GenerationError,
23
language::{Language, SupportedLanguage},
34
parser::{remove_dash_from_identifier, DecoratorKind, ParsedData},
45
rename::RenameExt,
56
rust_types::{
67
DecoratorMap, RustConst, RustEnum, RustEnumVariant, RustStruct, RustTypeAlias,
78
RustTypeFormatError, SpecialRustType,
89
},
9-
GenerationError,
1010
};
1111
use itertools::{Either, Itertools};
1212
use joinery::JoinableIterator;

core/src/lib.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
//! The core library for typeshare.
22
//! Contains the parser and language converters.
3-
use thiserror::Error;
4-
53
pub mod context;
4+
pub mod error;
65
/// Implementations for each language converter
76
pub mod language;
87
/// Parsing Rust code into a format the `language` modules can understand
@@ -16,20 +15,3 @@ mod topsort;
1615
mod visitors;
1716

1817
pub use rename::RenameExt;
19-
20-
#[derive(Debug, Error)]
21-
#[allow(missing_docs)]
22-
pub enum ProcessInputError {
23-
#[error("a parsing error occurred: {0}")]
24-
ParseError(#[from] parser::ParseError),
25-
#[error("a type generation error occurred: {0}")]
26-
IoError(#[from] std::io::Error),
27-
}
28-
29-
#[derive(Debug, Error)]
30-
/// Errors during file generation.
31-
pub enum GenerationError {
32-
/// The post generation step failed.
33-
#[error("Post generation failed: {0}")]
34-
PostGeneration(String),
35-
}

0 commit comments

Comments
 (0)