simplify Span struct and remove salsa tracking (#205)
Some checks are pending
lint / pre-commit (push) Waiting to run
lint / rustfmt (push) Waiting to run
lint / clippy (push) Waiting to run
lint / cargo-check (push) Waiting to run
release / build (push) Waiting to run
release / test (push) Waiting to run
release / release (push) Blocked by required conditions
test / generate-matrix (push) Waiting to run
test / Python , Django () (push) Blocked by required conditions
test / tests (push) Blocked by required conditions
zizmor 🌈 / zizmor latest via PyPI (push) Waiting to run

This commit is contained in:
Josh Thomas 2025-09-08 21:56:40 -05:00 committed by GitHub
parent 333e939c3f
commit 03f18d2211
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 134 deletions

View file

@ -57,8 +57,8 @@ impl Default for LineOffsets {
#[derive(Clone, Debug, PartialEq, Eq, salsa::Update)] #[derive(Clone, Debug, PartialEq, Eq, salsa::Update)]
pub enum Node<'db> { pub enum Node<'db> {
Tag(TagNode<'db>), Tag(TagNode<'db>),
Comment(CommentNode<'db>), Comment(CommentNode),
Text(TextNode<'db>), Text(TextNode),
Variable(VariableNode<'db>), Variable(VariableNode<'db>),
} }
@ -66,7 +66,7 @@ pub enum Node<'db> {
pub struct TagNode<'db> { pub struct TagNode<'db> {
pub name: TagName<'db>, pub name: TagName<'db>,
pub bits: Vec<String>, pub bits: Vec<String>,
pub span: Span<'db>, pub span: Span,
} }
#[salsa::interned(debug)] #[salsa::interned(debug)]
@ -75,22 +75,22 @@ pub struct TagName<'db> {
} }
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)] #[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
pub struct CommentNode<'db> { pub struct CommentNode {
pub content: String, pub content: String,
pub span: Span<'db>, pub span: Span,
} }
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)] #[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
pub struct TextNode<'db> { pub struct TextNode {
pub content: String, pub content: String,
pub span: Span<'db>, pub span: Span,
} }
#[derive(Debug, Clone, PartialEq, Eq, salsa::Update)] #[derive(Debug, Clone, PartialEq, Eq, salsa::Update)]
pub struct VariableNode<'db> { pub struct VariableNode<'db> {
pub var: VariableName<'db>, pub var: VariableName<'db>,
pub filters: Vec<FilterName<'db>>, pub filters: Vec<FilterName<'db>>,
pub span: Span<'db>, pub span: Span,
} }
#[salsa::interned(debug)] #[salsa::interned(debug)]
@ -103,29 +103,29 @@ pub struct FilterName<'db> {
pub text: String, pub text: String,
} }
#[salsa::tracked(debug)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
pub struct Span<'db> { pub struct Span {
#[tracked]
pub start: u32, pub start: u32,
#[tracked]
pub length: u32, pub length: u32,
} }
impl<'db> Span<'db> { impl Span {
pub fn from_token(db: &'db dyn crate::db::Db, token: &Token) -> Self { #[must_use]
let start = token.start().unwrap_or(0); pub fn new(start: u32, length: u32) -> Self {
let length = u32::try_from(token.lexeme().len()).unwrap_or(0); Self { start, length }
Span::new(db, start, length)
} }
#[must_use] #[must_use]
pub fn to_lsp_range( pub fn from_token(token: &Token) -> Self {
&self, let start = token.start().unwrap_or(0);
db: &'db dyn crate::db::Db, let length = u32::try_from(token.lexeme().len()).unwrap_or(0);
line_offsets: &LineOffsets, Self { start, length }
) -> tower_lsp_server::lsp_types::Range { }
let start_pos = self.start(db) as usize;
let end_pos = (self.start(db) + self.length(db)) as usize; #[must_use]
pub fn to_lsp_range(&self, line_offsets: &LineOffsets) -> tower_lsp_server::lsp_types::Range {
let start_pos = self.start as usize;
let end_pos = (self.start + self.length) as usize;
let (start_line, start_char) = line_offsets.position_to_line_col(start_pos); let (start_line, start_char) = line_offsets.position_to_line_col(start_pos);
let (end_line, end_char) = line_offsets.position_to_line_col(end_pos); let (end_line, end_char) = line_offsets.position_to_line_col(end_pos);
@ -151,58 +151,35 @@ pub enum AstError {
InvalidTagStructure { InvalidTagStructure {
tag: String, tag: String,
reason: String, reason: String,
span_start: u32, span: Span,
span_length: u32,
}, },
#[error("Unbalanced structure: '{opening_tag}' missing closing '{expected_closing}'")] #[error("Unbalanced structure: '{opening_tag}' missing closing '{expected_closing}'")]
UnbalancedStructure { UnbalancedStructure {
opening_tag: String, opening_tag: String,
expected_closing: String, expected_closing: String,
opening_span_start: u32, opening_span: Span,
opening_span_length: u32, closing_span: Option<Span>,
closing_span_start: Option<u32>,
closing_span_length: Option<u32>,
}, },
#[error("Invalid {node_type} node: {reason}")] #[error("Invalid {node_type} node: {reason}")]
InvalidNode { InvalidNode {
node_type: String, node_type: String,
reason: String, reason: String,
span_start: u32, span: Span,
span_length: u32,
}, },
#[error("Unclosed tag: {tag}")] #[error("Unclosed tag: {tag}")]
UnclosedTag { UnclosedTag { tag: String, span: Span },
tag: String,
span_start: u32,
span_length: u32,
},
#[error("Orphaned tag '{tag}' - {context}")] #[error("Orphaned tag '{tag}' - {context}")]
OrphanedTag { OrphanedTag {
tag: String, tag: String,
context: String, context: String,
span_start: u32, span: Span,
span_length: u32,
}, },
#[error("endblock '{name}' does not match any open block")] #[error("endblock '{name}' does not match any open block")]
UnmatchedBlockName { UnmatchedBlockName { name: String, span: Span },
name: String,
span_start: u32,
span_length: u32,
},
#[error("Tag '{tag}' requires at least {min} argument{}", if *.min == 1 { "" } else { "s" })] #[error("Tag '{tag}' requires at least {min} argument{}", if *.min == 1 { "" } else { "s" })]
MissingRequiredArguments { MissingRequiredArguments { tag: String, min: usize, span: Span },
tag: String,
min: usize,
span_start: u32,
span_length: u32,
},
#[error("Tag '{tag}' accepts at most {max} argument{}", if *.max == 1 { "" } else { "s" })] #[error("Tag '{tag}' accepts at most {max} argument{}", if *.max == 1 { "" } else { "s" })]
TooManyArguments { TooManyArguments { tag: String, max: usize, span: Span },
tag: String,
max: usize,
span_start: u32,
span_length: u32,
},
} }
impl AstError { impl AstError {
@ -210,46 +187,16 @@ impl AstError {
#[must_use] #[must_use]
pub fn span(&self) -> Option<(u32, u32)> { pub fn span(&self) -> Option<(u32, u32)> {
match self { match self {
AstError::UnbalancedStructure { AstError::UnbalancedStructure { opening_span, .. } => {
opening_span_start, Some((opening_span.start, opening_span.length))
opening_span_length,
..
} => Some((*opening_span_start, *opening_span_length)),
AstError::InvalidTagStructure {
span_start,
span_length,
..
} }
| AstError::InvalidNode { AstError::InvalidTagStructure { span, .. }
span_start, | AstError::InvalidNode { span, .. }
span_length, | AstError::UnclosedTag { span, .. }
.. | AstError::OrphanedTag { span, .. }
} | AstError::UnmatchedBlockName { span, .. }
| AstError::UnclosedTag { | AstError::MissingRequiredArguments { span, .. }
span_start, | AstError::TooManyArguments { span, .. } => Some((span.start, span.length)),
span_length,
..
}
| AstError::OrphanedTag {
span_start,
span_length,
..
}
| AstError::UnmatchedBlockName {
span_start,
span_length,
..
}
| AstError::MissingRequiredArguments {
span_start,
span_length,
..
}
| AstError::TooManyArguments {
span_start,
span_length,
..
} => Some((*span_start, *span_length)),
AstError::EmptyAst => None, AstError::EmptyAst => None,
} }
} }

View file

@ -163,8 +163,8 @@ fn accumulate_error(db: &dyn Db, error: &TemplateError, line_offsets: &LineOffse
let range = error let range = error
.span() .span()
.map(|(start, length)| { .map(|(start, length)| {
let span = crate::ast::Span::new(db, start, length); let span = crate::ast::Span::new(start, length);
span.to_lsp_range(db, line_offsets) span.to_lsp_range(line_offsets)
}) })
.unwrap_or_default(); .unwrap_or_default();

View file

@ -101,7 +101,7 @@ impl<'db> Parser<'db> {
Ok(Node::Comment(CommentNode { Ok(Node::Comment(CommentNode {
content: token.content(), content: token.content(),
span: Span::from_token(self.db, &token), span: Span::from_token(&token),
})) }))
} }
@ -116,7 +116,7 @@ impl<'db> Parser<'db> {
let name_str = args.first().ok_or(ParserError::EmptyTag)?.clone(); let name_str = args.first().ok_or(ParserError::EmptyTag)?.clone();
let name = TagName::new(self.db, name_str); // Intern the tag name let name = TagName::new(self.db, name_str); // Intern the tag name
let bits = args.into_iter().skip(1).collect(); let bits = args.into_iter().skip(1).collect();
let span = Span::from_token(self.db, &token); let span = Span::from_token(&token);
Ok(Node::Tag(TagNode { name, bits, span })) Ok(Node::Tag(TagNode { name, bits, span }))
} }
@ -137,7 +137,7 @@ impl<'db> Parser<'db> {
.skip(1) .skip(1)
.map(|s| FilterName::new(self.db, s.trim().to_string())) // Intern filter names .map(|s| FilterName::new(self.db, s.trim().to_string())) // Intern filter names
.collect(); .collect();
let span = Span::from_token(self.db, &token); let span = Span::from_token(&token);
Ok(Node::Variable(VariableNode { var, filters, span })) Ok(Node::Variable(VariableNode { var, filters, span }))
} }
@ -175,7 +175,7 @@ impl<'db> Parser<'db> {
let offset = u32::try_from(text.find(content.as_str()).unwrap_or(0)) let offset = u32::try_from(text.find(content.as_str()).unwrap_or(0))
.expect("Offset should fit in u32"); .expect("Offset should fit in u32");
let length = u32::try_from(content.len()).expect("Content length should fit in u32"); let length = u32::try_from(content.len()).expect("Content length should fit in u32");
let span = Span::new(self.db, start + offset, length); let span = Span::new(start + offset, length);
Ok(Node::Text(TextNode { content, span })) Ok(Node::Text(TextNode { content, span }))
} }
@ -404,20 +404,20 @@ mod tests {
Node::Tag(TagNode { name, bits, span }) => TestNode::Tag { Node::Tag(TagNode { name, bits, span }) => TestNode::Tag {
name: name.text(db).to_string(), name: name.text(db).to_string(),
bits: bits.clone(), bits: bits.clone(),
span: (span.start(db), span.length(db)), span: (span.start, span.length),
}, },
Node::Comment(CommentNode { content, span }) => TestNode::Comment { Node::Comment(CommentNode { content, span }) => TestNode::Comment {
content: content.clone(), content: content.clone(),
span: (span.start(db), span.length(db)), span: (span.start, span.length),
}, },
Node::Text(TextNode { content, span }) => TestNode::Text { Node::Text(TextNode { content, span }) => TestNode::Text {
content: content.clone(), content: content.clone(),
span: (span.start(db), span.length(db)), span: (span.start, span.length),
}, },
Node::Variable(VariableNode { var, filters, span }) => TestNode::Variable { Node::Variable(VariableNode { var, filters, span }) => TestNode::Variable {
var: var.text(db).to_string(), var: var.text(db).to_string(),
filters: filters.iter().map(|f| f.text(db).to_string()).collect(), filters: filters.iter().map(|f| f.text(db).to_string()).collect(),
span: (span.start(db), span.length(db)), span: (span.start, span.length),
}, },
} }
} }

View file

@ -92,8 +92,7 @@ impl<'db> TagValidator<'db> {
while let Some(tag) = self.stack.pop() { while let Some(tag) = self.stack.pop() {
self.errors.push(AstError::UnclosedTag { self.errors.push(AstError::UnclosedTag {
tag: tag.name.text(self.db), tag: tag.name.text(self.db),
span_start: tag.span.start(self.db), span: tag.span,
span_length: tag.span.length(self.db),
}); });
} }
@ -104,7 +103,7 @@ impl<'db> TagValidator<'db> {
&mut self, &mut self,
name: &str, name: &str,
bits: &[String], bits: &[String],
span: Span<'db>, span: Span,
arg_spec: Option<&ArgSpec>, arg_spec: Option<&ArgSpec>,
) { ) {
let Some(arg_spec) = arg_spec else { let Some(arg_spec) = arg_spec else {
@ -116,8 +115,7 @@ impl<'db> TagValidator<'db> {
self.errors.push(AstError::MissingRequiredArguments { self.errors.push(AstError::MissingRequiredArguments {
tag: name.to_string(), tag: name.to_string(),
min, min,
span_start: span.start(self.db), span,
span_length: span.length(self.db),
}); });
} }
} }
@ -127,14 +125,13 @@ impl<'db> TagValidator<'db> {
self.errors.push(AstError::TooManyArguments { self.errors.push(AstError::TooManyArguments {
tag: name.to_string(), tag: name.to_string(),
max, max,
span_start: span.start(self.db), span,
span_length: span.length(self.db),
}); });
} }
} }
} }
fn handle_intermediate(&mut self, name: &str, span: Span<'db>) { fn handle_intermediate(&mut self, name: &str, span: Span) {
// Check if this intermediate tag has the required parent // Check if this intermediate tag has the required parent
let parent_tags = self.db.tag_specs().get_parent_tags_for_intermediate(name); let parent_tags = self.db.tag_specs().get_parent_tags_for_intermediate(name);
if parent_tags.is_empty() { if parent_tags.is_empty() {
@ -159,13 +156,12 @@ impl<'db> TagValidator<'db> {
self.errors.push(AstError::OrphanedTag { self.errors.push(AstError::OrphanedTag {
tag: name.to_string(), tag: name.to_string(),
context, context,
span_start: span.start(self.db), span,
span_length: span.length(self.db),
}); });
} }
} }
fn handle_closer(&mut self, name: TagName<'db>, bits: &[String], span: Span<'db>) { fn handle_closer(&mut self, name: TagName<'db>, bits: &[String], span: Span) {
let name_str = name.text(self.db); let name_str = name.text(self.db);
if self.stack.is_empty() { if self.stack.is_empty() {
@ -173,10 +169,8 @@ impl<'db> TagValidator<'db> {
self.errors.push(AstError::UnbalancedStructure { self.errors.push(AstError::UnbalancedStructure {
opening_tag: name_str.to_string(), opening_tag: name_str.to_string(),
expected_closing: String::new(), expected_closing: String::new(),
opening_span_start: span.start(self.db), opening_span: span,
opening_span_length: span.length(self.db), closing_span: None,
closing_span_start: None,
closing_span_length: None,
}); });
return; return;
} }
@ -188,10 +182,8 @@ impl<'db> TagValidator<'db> {
self.errors.push(AstError::UnbalancedStructure { self.errors.push(AstError::UnbalancedStructure {
opening_tag: name_str.to_string(), opening_tag: name_str.to_string(),
expected_closing: String::new(), expected_closing: String::new(),
opening_span_start: span.start(self.db), opening_span: span,
opening_span_length: span.length(self.db), closing_span: None,
closing_span_start: None,
closing_span_length: None,
}); });
return; return;
}; };
@ -234,8 +226,7 @@ impl<'db> TagValidator<'db> {
// Report the mismatch // Report the mismatch
self.errors.push(AstError::UnmatchedBlockName { self.errors.push(AstError::UnmatchedBlockName {
name: bits[0].clone(), name: bits[0].clone(),
span_start: span.start(self.db), span,
span_length: span.length(self.db),
}); });
// Find the nearest block to close (and report it as unclosed) // Find the nearest block to close (and report it as unclosed)
@ -249,8 +240,7 @@ impl<'db> TagValidator<'db> {
// Report that we're closing the wrong block // Report that we're closing the wrong block
self.errors.push(AstError::UnclosedTag { self.errors.push(AstError::UnclosedTag {
tag: nearest_block.name.text(self.db), tag: nearest_block.name.text(self.db),
span_start: nearest_block.span.start(self.db), span: nearest_block.span,
span_length: nearest_block.span.length(self.db),
}); });
// Pop everything after as unclosed // Pop everything after as unclosed
@ -264,10 +254,8 @@ impl<'db> TagValidator<'db> {
self.errors.push(AstError::UnbalancedStructure { self.errors.push(AstError::UnbalancedStructure {
opening_tag: opener_name, opening_tag: opener_name,
expected_closing: name_str.to_string(), expected_closing: name_str.to_string(),
opening_span_start: span.start(self.db), opening_span: span,
opening_span_length: span.length(self.db), closing_span: None,
closing_span_start: None,
closing_span_length: None,
}); });
} }
} }
@ -277,8 +265,7 @@ impl<'db> TagValidator<'db> {
if let Some(unclosed) = self.stack.pop() { if let Some(unclosed) = self.stack.pop() {
self.errors.push(AstError::UnclosedTag { self.errors.push(AstError::UnclosedTag {
tag: unclosed.name.text(self.db), tag: unclosed.name.text(self.db),
span_start: unclosed.span.start(self.db), span: unclosed.span,
span_length: unclosed.span.length(self.db),
}); });
} }
} }