Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace direct panics with Errors #432

Merged
merged 1 commit into from Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/CI.yml
Expand Up @@ -31,6 +31,8 @@ jobs:
--allow clippy::unknown_clippy_lints \
--allow clippy::unnecessary_cast \
--allow clippy::block_in_if_condition_stmt
# Prevent regressions of https://github.com/trishume/syntect/issues/98
cargo clippy --all-features --lib -- --deny clippy::panic
- name: Run cargo check
run: |
cargo check --all-features --all-targets
Expand Down
4 changes: 2 additions & 2 deletions examples/synhtml-css-classes.rs
Expand Up @@ -106,15 +106,15 @@ int main() {
let css_dark_file = File::create(Path::new("theme-dark.css"))?;
let mut css_dark_writer = BufWriter::new(&css_dark_file);

let css_dark = css_for_theme_with_class_style(dark_theme, ClassStyle::Spaced);
let css_dark = css_for_theme_with_class_style(dark_theme, ClassStyle::Spaced).unwrap();
writeln!(css_dark_writer, "{}", css_dark)?;

// create light color scheme css
let light_theme = &ts.themes["Solarized (light)"];
let css_light_file = File::create(Path::new("theme-light.css"))?;
let mut css_light_writer = BufWriter::new(&css_light_file);

let css_light = css_for_theme_with_class_style(light_theme, ClassStyle::Spaced);
let css_light = css_for_theme_with_class_style(light_theme, ClassStyle::Spaced).unwrap();
writeln!(css_light_writer, "{}", css_light)?;

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion examples/synstats.rs
Expand Up @@ -85,7 +85,7 @@ fn count_line(ops: &[(usize, ScopeStackOp)], line: &str, stack: &mut ScopeStack,
let mut line_has_doc_comment = false;
let mut line_has_code = false;
for (s, op) in ScopeRegionIterator::new(ops, line) {
stack.apply(op);
stack.apply(op).unwrap();
if s.is_empty() { // in this case we don't care about blank tokens
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion examples/syntest.rs
Expand Up @@ -283,7 +283,7 @@ fn test_file(
}
let mut col: usize = 0;
for (s, op) in ScopeRegionIterator::new(&ops, &line) {
stack.apply(op);
stack.apply(op).unwrap();
if s.is_empty() {
// in this case we don't care about blank tokens
continue;
Expand Down
4 changes: 2 additions & 2 deletions src/easy.rs
Expand Up @@ -298,7 +298,7 @@ mod tests {
let mut stack = ScopeStack::new();
let mut token_count = 0;
for (s, op) in ScopeRegionIterator::new(&ops, line) {
stack.apply(op);
stack.apply(op).expect("#[cfg(test)]");
if s.is_empty() { // in this case we don't care about blank tokens
continue;
}
Expand Down Expand Up @@ -326,7 +326,7 @@ mod tests {

let mut iterated_ops: Vec<&ScopeStackOp> = Vec::new();
for (_, op) in ScopeRegionIterator::new(&ops, line) {
stack.apply(op);
stack.apply(op).expect("#[cfg(test)]");
iterated_ops.push(op);
println!("{:?}", op);
}
Expand Down
2 changes: 1 addition & 1 deletion src/highlighting/highlighter.rs
Expand Up @@ -181,7 +181,7 @@ impl<'a, 'b> Iterator for RangedHighlightIterator<'a, 'b> {
m_caches.pop();
}
}
});
}).ok()?;
}
self.pos = end;
self.index += 1;
Expand Down
24 changes: 12 additions & 12 deletions src/html.rs
Expand Up @@ -94,7 +94,7 @@ impl<'a> ClassedHTMLGenerator<'a> {
parsed_line.as_slice(),
self.style,
&mut self.scope_stack,
);
)?;
self.open_spans += delta;
self.html.push_str(formatted_line.as_str());

Expand Down Expand Up @@ -128,11 +128,11 @@ impl<'a> ClassedHTMLGenerator<'a> {

#[deprecated(since="4.2.0", note="Please use `css_for_theme_with_class_style` instead.")]
pub fn css_for_theme(theme: &Theme) -> String {
css_for_theme_with_class_style(theme, ClassStyle::Spaced)
css_for_theme_with_class_style(theme, ClassStyle::Spaced).expect("Please use `css_for_theme_with_class_style` instead.")
}

/// Create a complete CSS for a given theme. Can be used inline, or written to a CSS file.
pub fn css_for_theme_with_class_style(theme: &Theme, style: ClassStyle) -> String {
pub fn css_for_theme_with_class_style(theme: &Theme, style: ClassStyle) -> Result<String, Error> {
let mut css = String::new();

css.push_str("/*\n");
Expand Down Expand Up @@ -201,7 +201,7 @@ pub fn css_for_theme_with_class_style(theme: &Theme, style: ClassStyle) -> Strin
css.push_str("}\n");
}

css
Ok(css)
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
Expand Down Expand Up @@ -337,7 +337,7 @@ pub fn line_tokens_to_classed_spans(
ops: &[(usize, ScopeStackOp)],
style: ClassStyle,
stack: &mut ScopeStack,
) -> (String, isize) {
) -> Result<(String, isize), Error> {
let mut s = String::with_capacity(line.len() + ops.len() * 8); // a guess
let mut cur_index = 0;
let mut span_delta = 0;
Expand All @@ -349,7 +349,7 @@ pub fn line_tokens_to_classed_spans(
for &(i, ref op) in ops {
if i > cur_index {
span_empty = false;
write!(s, "{}", Escape(&line[cur_index..i])).unwrap();
write!(s, "{}", Escape(&line[cur_index..i]))?;
cur_index = i
}
stack.apply_with_hook(op, |basic_op, _| match basic_op {
Expand All @@ -370,10 +370,10 @@ pub fn line_tokens_to_classed_spans(
span_delta -= 1;
span_empty = false;
}
});
})?;
}
write!(s, "{}", Escape(&line[cur_index..line.len()])).unwrap();
(s, span_delta)
write!(s, "{}", Escape(&line[cur_index..line.len()]))?;
Ok((s, span_delta))
}

/// Preserved for compatibility, always use `line_tokens_to_classed_spans`
Expand All @@ -385,15 +385,15 @@ pub fn tokens_to_classed_spans(
ops: &[(usize, ScopeStackOp)],
style: ClassStyle,
) -> (String, isize) {
line_tokens_to_classed_spans(line, ops, style, &mut ScopeStack::new())
line_tokens_to_classed_spans(line, ops, style, &mut ScopeStack::new()).expect("Use `line_tokens_to_classed_spans` instead, this can panic and highlight incorrectly")
}

#[deprecated(since="3.1.0", note="Use `line_tokens_to_classed_spans` instead to avoid incorrect highlighting and panics")]
pub fn tokens_to_classed_html(line: &str,
ops: &[(usize, ScopeStackOp)],
style: ClassStyle)
-> String {
line_tokens_to_classed_spans(line, ops, style, &mut ScopeStack::new()).0
line_tokens_to_classed_spans(line, ops, style, &mut ScopeStack::new()).expect("Use `line_tokens_to_classed_spans` instead to avoid incorrect highlighting and panics").0
}

/// Determines how background color attributes are generated
Expand Down Expand Up @@ -543,7 +543,7 @@ mod tests {
// use util::debug_print_ops;
// debug_print_ops(line, &ops);

let (html, _) = line_tokens_to_classed_spans(line, &ops[..], ClassStyle::Spaced, &mut stack);
let (html, _) = line_tokens_to_classed_spans(line, &ops[..], ClassStyle::Spaced, &mut stack).expect("#[cfg(test)]");
println!("{}", html);
assert_eq!(html, include_str!("../testdata/test2.html").trim_end());

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Expand Up @@ -58,6 +58,9 @@ pub enum Error {
#[cfg(feature = "parsing")]
#[error("Parsing error: {0}")]
ParsingError(#[from] crate::parsing::ParsingError),
/// Scope error
#[error("Scope error: {0}")]
ScopeError(#[from] crate::parsing::ScopeError),
/// Formatting error
#[error("Formatting error: {0}")]
Fmt(#[from] std::fmt::Error),
Expand Down
43 changes: 26 additions & 17 deletions src/parsing/parser.rs
Expand Up @@ -25,10 +25,16 @@ use crate::parsing::syntax_definition::ContextId;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ParsingError {
#[error("Somehow main context was popped from the stack")]
MissingMainContext,
/// A context is missing. Usually caused by a syntax referencing a another
/// syntax that is not known to syntect. See e.g. <https://github.com/trishume/syntect/issues/421>
#[error("Missing context with ID '{0:?}'")]
MissingContext(ContextId),
#[error("Bad index to match_at: {0}")]
BadMatchIndex(usize),
#[error("Tried to use a ContextReference that has not bee resolved yet: {0:?}")]
UnresolvedContextReference(ContextReference),
}

/// Keeps the current parser state (the internal syntax interpreter stack) between lines of parsing.
Expand Down Expand Up @@ -210,8 +216,9 @@ impl ParseState {
line: &str,
syntax_set: &SyntaxSet,
) -> Result<Vec<(usize, ScopeStackOp)>, ParsingError> {
assert!(!self.stack.is_empty(),
"Somehow main context was popped from the stack");
if self.stack.is_empty() {
return Err(ParsingError::MissingMainContext)
}
let mut match_start = 0;
let mut res = Vec::new();

Expand Down Expand Up @@ -300,7 +307,7 @@ impl ParseState {
// check the next "pop" for loops. Otherwise leave the state,
// e.g. non-consuming "set" could also result in a loop.
let context = reg_match.context;
let match_pattern = context.match_at(reg_match.pat_index);
let match_pattern = context.match_at(reg_match.pat_index)?;
if let MatchOperation::Push(_) = match_pattern.operation {
*non_consuming_push_at = (match_end, self.stack.len() + 1);
}
Expand Down Expand Up @@ -362,7 +369,7 @@ impl ParseState {

for (from_with_proto, ctx, captures) in context_chain {
for (pat_context, pat_index) in context_iter(syntax_set, syntax_set.get_context(ctx)?) {
let match_pat = pat_context.match_at(pat_index);
let match_pat = pat_context.match_at(pat_index)?;

if let Some(match_region) = self.search(
line, start, match_pat, captures, search_cache, regions
Expand Down Expand Up @@ -475,10 +482,10 @@ impl ParseState {
) -> Result<bool, ParsingError> {
let (match_start, match_end) = reg_match.regions.pos(0).unwrap();
let context = reg_match.context;
let pat = context.match_at(reg_match.pat_index);
let pat = context.match_at(reg_match.pat_index)?;
// println!("running pattern {:?} on '{}' at {}, operation {:?}", pat.regex_str, line, match_start, pat.operation);

self.push_meta_ops(true, match_start, level_context, &pat.operation, syntax_set, ops);
self.push_meta_ops(true, match_start, level_context, &pat.operation, syntax_set, ops)?;
for s in &pat.scope {
// println!("pushing {:?} at {}", s, match_start);
ops.push((match_start, ScopeStackOp::Push(*s)));
Expand Down Expand Up @@ -511,7 +518,7 @@ impl ParseState {
// println!("popping at {}", match_end);
ops.push((match_end, ScopeStackOp::Pop(pat.scope.len())));
}
self.push_meta_ops(false, match_end, &*level_context, &pat.operation, syntax_set, ops);
self.push_meta_ops(false, match_end, &*level_context, &pat.operation, syntax_set, ops)?;

self.perform_op(line, &reg_match.regions, pat, syntax_set)
}
Expand All @@ -524,7 +531,7 @@ impl ParseState {
match_op: &MatchOperation,
syntax_set: &'a SyntaxSet,
ops: &mut Vec<(usize, ScopeStackOp)>,
) {
) -> Result<(), ParsingError>{
// println!("metas ops for {:?}, initial: {}",
// match_op,
// initial);
Expand All @@ -542,7 +549,7 @@ impl ParseState {

// cleared scopes are restored after the scopes from match pattern that invoked the pop are applied
if !initial && cur_context.clear_scopes != None {
ops.push((index, ScopeStackOp::Restore));
ops.push((index, ScopeStackOp::Restore))
}
},
// for some reason the ST3 behaviour of set is convoluted and is inconsistent with the docs and other ops
Expand All @@ -560,7 +567,7 @@ impl ParseState {
}
// add each context's meta scope
for r in context_refs.iter() {
let ctx = r.resolve(syntax_set);
let ctx = r.resolve(syntax_set)?;

if !is_set {
if let Some(clear_amount) = ctx.clear_scopes {
Expand All @@ -574,14 +581,14 @@ impl ParseState {
}
} else {
let repush = (is_set && (!cur_context.meta_scope.is_empty() || !cur_context.meta_content_scope.is_empty())) || context_refs.iter().any(|r| {
let ctx = r.resolve(syntax_set);
let ctx = r.resolve(syntax_set).unwrap();

!ctx.meta_content_scope.is_empty() || (ctx.clear_scopes.is_some() && is_set)
});
if repush {
// remove previously pushed meta scopes, so that meta content scopes will be applied in the correct order
let mut num_to_pop : usize = context_refs.iter().map(|r| {
let ctx = r.resolve(syntax_set);
let ctx = r.resolve(syntax_set).unwrap();
ctx.meta_scope.len()
}).sum();

Expand All @@ -597,7 +604,7 @@ impl ParseState {

// now we push meta scope and meta context scope for each context pushed
for r in context_refs {
let ctx = r.resolve(syntax_set);
let ctx = r.resolve(syntax_set)?;

// for some reason, contrary to my reading of the docs, set does this after the token
if is_set {
Expand All @@ -618,6 +625,8 @@ impl ParseState {
},
MatchOperation::None => (),
}

Ok(())
}

/// Returns true if the stack was changed
Expand Down Expand Up @@ -657,10 +666,10 @@ impl ParseState {
// referred to as the "target" of the push by sublimehq - see
// https://forum.sublimetext.com/t/dev-build-3111/19240/17 for more info
if let Some(ref p) = pat.with_prototype {
proto_ids.push(p.id());
proto_ids.push(p.id()?);
}
}
let context_id = r.id();
let context_id = r.id()?;
let context = syntax_set.get_context(&context_id)?;
let captures = {
let mut uses_backrefs = context.uses_backrefs;
Expand Down Expand Up @@ -770,7 +779,7 @@ mod tests {

let mut stack = ScopeStack::new();
for &(_, ref op) in ops.iter() {
stack.apply(op);
stack.apply(op).expect("#[cfg(test)]");
}
assert_eq!(stack, test_stack);
}
Expand Down Expand Up @@ -1827,7 +1836,7 @@ contexts:
let mut states = Vec::new();
let mut stack = ScopeStack::new();
for &(_, ref op) in ops.iter() {
stack.apply(op);
stack.apply(op).expect("#[cfg(test)]");
let scopes: Vec<String> = stack.as_slice().iter().map(|s| format!("{:?}", s)).collect();
let stack_str = scopes.join(", ");
states.push(stack_str);
Expand Down
17 changes: 14 additions & 3 deletions src/parsing/scope.rs
Expand Up @@ -11,6 +11,14 @@ use std::mem;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::de::{Error, Visitor};

/// Scope related errors
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ScopeError {
#[error("Tried to restore cleared scopes, but none were cleared")]
NoClearedScopesToRestore,
}

/// Multiplier on the power of 2 for MatchPower. This is only useful if you compute your own
/// [`MatchPower`] scores
///
Expand Down Expand Up @@ -219,6 +227,7 @@ impl Scope {
/// I can't think of any reason you'd find this useful. It is used internally for turning a
/// scope back into a string.
pub fn atom_at(self, index: usize) -> u16 {
#[allow(clippy::panic)] // The below panic is too much of an edge-case for it to be worth propagating
let shifted = if index < 4 {
self.a >> ((3 - index) * 16)
} else if index < 8 {
Expand Down Expand Up @@ -398,7 +407,7 @@ impl ScopeStack {
/// Modifies this stack according to the operation given
///
/// Use this to create a stack from a `Vec` of changes given by the parser.
pub fn apply(&mut self, op: &ScopeStackOp) {
pub fn apply(&mut self, op: &ScopeStackOp) -> Result<(), ScopeError> {
self.apply_with_hook(op, |_,_|{})
}

Expand All @@ -410,7 +419,7 @@ impl ScopeStack {
/// [`apply`]: #method.apply
/// [`BasicScopeStackOp`]: enum.BasicScopeStackOp.html
#[inline]
pub fn apply_with_hook<F>(&mut self, op: &ScopeStackOp, mut hook: F)
pub fn apply_with_hook<F>(&mut self, op: &ScopeStackOp, mut hook: F) -> Result<(), ScopeError>
where F: FnMut(BasicScopeStackOp, &[Scope])
{
match *op {
Expand Down Expand Up @@ -451,11 +460,13 @@ impl ScopeStack {
hook(BasicScopeStackOp::Push(*s), self.as_slice());
}
}
None => panic!("tried to restore cleared scopes, but none were cleared"),
None => return Err(ScopeError::NoClearedScopesToRestore),
}
}
ScopeStackOp::Noop => (),
}

Ok(())
}

/// Prints out each scope in the stack separated by spaces
Expand Down