Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ serde = { version = "1.0.228", features = ["derive"] }
flate2 = "1.1"
serde_json = "1.0.149"
toml = "1.1.2"
metrics = "0.24.2"
metrics-util = "0.20.1"
tracing = { version = "0.1.44", default-features = false, features = ["attributes", "std"] }
thiserror = "2"
trybuild = "1.0.116"
Expand Down
2 changes: 2 additions & 0 deletions crates/whitaker_clones_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ thiserror = { workspace = true }
rustc_lexer = { workspace = true }
ra_ap_syntax = { workspace = true, optional = true }
sha2 = { workspace = true }
metrics = { workspace = true }
tracing = { workspace = true }
whitaker_sarif = { workspace = true }

[dev-dependencies]
camino = { workspace = true }
cap-std = { workspace = true }
insta = { workspace = true }
metrics-util = { workspace = true }
proptest = { workspace = true }
rstest = { workspace = true }
rstest-bdd = { workspace = true }
Expand Down
43 changes: 33 additions & 10 deletions crates/whitaker_clones_core/src/ast/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

use std::cell::Cell;
use std::ops::Range;
use std::time::Instant;

use ra_ap_syntax::{
AstNode, Edition, NodeOrToken, SourceFile, SyntaxKind, SyntaxNode, SyntaxToken, TextRange,
TextSize,
};
use tracing::{debug, error, warn};

use super::metrics::record_lower_span_metrics;
use super::{AstError, AstResult, ByteSpan, KindId, LeafClass, NormalizedNode, NormalizedTree};

pub use crate::hashing::PARSER_SCHEMA_VERSION;
Expand Down Expand Up @@ -44,8 +46,9 @@ fn trace_ast_error(
/// text. This defence-in-depth check is not redundant and must remain in place
/// even though it resembles double validation.
///
/// Latency metrics and feature-vector emission metrics are deferred to 7.3.2,
/// where scoring and SARIF emission consume those observations.
/// Latency and categorized outcome metrics emit at this lowering boundary.
/// Feature-vector emission metrics remain reserved for the 7.3.2 scoring and
/// SARIF Run 1 consumption boundary.
///
/// # Examples
///
Expand All @@ -60,16 +63,27 @@ fn trace_ast_error(
/// ```
#[tracing::instrument(skip(file_text), fields(start = span.start(), end = span.end()))]
pub fn lower_span(file_text: &str, span: ByteSpan) -> AstResult<NormalizedTree> {
let span = ByteSpan::new(file_text, span.start(), span.end()).map_err(|error| {
let started_at = Instant::now();
let (result, recovered) = lower_span_inner(file_text, span);
record_lower_span_metrics(&result, started_at.elapsed(), recovered);
result
}

fn lower_span_inner(file_text: &str, span: ByteSpan) -> (AstResult<NormalizedTree>, bool) {
let span = match ByteSpan::new(file_text, span.start(), span.end()).map_err(|error| {
trace_ast_error(
error,
"AST span lies outside the supplied source text",
"AST span validation failed",
)
})?;
}) {
Ok(span) => span,
Err(error) => return (Err(error), false),
};
let parse = SourceFile::parse(file_text, Edition::CURRENT);
let parse_errors = parse.errors();
if !parse_errors.is_empty() {
let recovered = !parse_errors.is_empty();
if recovered {
// This is the designated logging boundary for parser recovery in this
// adapter; the lowered AST domain remains parser-agnostic.
warn!(
Expand All @@ -81,13 +95,16 @@ pub fn lower_span(file_text: &str, span: ByteSpan) -> AstResult<NormalizedTree>
}
let root = parse.tree().syntax().clone();
let target_range = text_range(span);
let selected = select_covering_node(&root, span).map_err(|error| {
let selected = match select_covering_node(&root, span).map_err(|error| {
trace_ast_error(
error,
"no AST syntax node covers the requested span",
"AST covering-node selection failed",
)
})?;
}) {
Ok(selected) => selected,
Err(error) => return (Err(error), recovered),
};
debug!(
kind = ?selected.kind(),
span_width = u32::from(selected.text_range().len()),
Expand All @@ -97,7 +114,7 @@ pub fn lower_span(file_text: &str, span: ByteSpan) -> AstResult<NormalizedTree>
// Lowering doubles as parser-error detection: a single descent both builds
// the normalized subtree and rejects any `ERROR` node or token, so the
// selected span is never walked twice.
let lowered = LoweringLimits::new(span)
let lowered = match LoweringLimits::new(span)
.lower(&selected, 0)
.map_err(|error| {
if matches!(error, AstError::UnparsableSpan { .. }) {
Expand All @@ -108,10 +125,13 @@ pub fn lower_span(file_text: &str, span: ByteSpan) -> AstResult<NormalizedTree>
);
}
error
})?;
}) {
Ok(lowered) => lowered,
Err(error) => return (Err(error), recovered),
};

debug_assert!(selected.text_range().contains_range(target_range));
Ok(NormalizedTree::new(lowered, span))
(Ok(NormalizedTree::new(lowered, span)), recovered)
}

fn validate_covering_node_budget(span: ByteSpan, depth: usize, node_count: usize) -> AstResult<()> {
Expand Down Expand Up @@ -343,6 +363,9 @@ fn range_to_u32(range: TextRange) -> Range<u32> {
u32::from(range.start())..u32::from(range.end())
}

#[cfg(test)]
#[path = "lowering_metrics_tests.rs"]
mod metrics_tests;
#[cfg(test)]
#[path = "lowering_tests.rs"]
mod tests;
Loading
Loading