crate::ix!();
impl RehydrateFromSignature for ast::Fn {
#[instrument(level = "trace", skip(signature_source))]
fn rehydrate_from_signature(signature_source: &str) -> Option<Self> {
parse_exact_one_top_level_item_of_type(signature_source)
}
}
impl RehydrateFromSignature for ast::Struct {
#[instrument(level = "trace", skip(signature_source))]
fn rehydrate_from_signature(signature_source: &str) -> Option<Self> {
parse_exact_one_top_level_item_of_type(signature_source)
}
}
impl RehydrateFromSignature for ast::Enum {
#[instrument(level = "trace", skip(signature_source))]
fn rehydrate_from_signature(signature_source: &str) -> Option<Self> {
parse_exact_one_top_level_item_of_type(signature_source)
}
}
impl RehydrateFromSignature for ast::Trait {
#[instrument(level = "trace", skip(signature_source))]
fn rehydrate_from_signature(signature_source: &str) -> Option<Self> {
parse_exact_one_top_level_item_of_type(signature_source)
}
}
impl RehydrateFromSignature for ast::TypeAlias {
#[instrument(level = "trace", skip(signature_source))]
fn rehydrate_from_signature(signature_source: &str) -> Option<Self> {
parse_exact_one_top_level_item_of_type(signature_source)
}
}
impl RehydrateFromSignature for ast::MacroRules {
#[instrument(level = "trace", skip(signature_source))]
fn rehydrate_from_signature(signature_source: &str) -> Option<Self> {
parse_exact_one_top_level_item_of_type(signature_source)
}
}
impl RehydrateFromSignature for ast::MacroCall {
#[instrument(level = "trace", skip(signature_source))]
fn rehydrate_from_signature(signature_source: &str) -> Option<Self> {
parse_exact_one_top_level_item_of_type(signature_source)
}
}
#[instrument(level = "trace", skip(signature_source))]
fn parse_exact_one_top_level_item_of_type<T: AstNode>(signature_source: &str) -> Option<T> {
trace!("Parsing snippet:\n{}", signature_source);
let parse = SourceFile::parse(signature_source, Edition::Edition2021);
let sf = parse.tree();
let parse_errors = parse.errors();
if !parse_errors.is_empty() {
warn!("Parse errors found in snippet => not rehydrating. Errors: {:?}", parse_errors);
return None;
}
let items: Vec<ast::Item> = sf.items().collect();
if items.len() != 1 {
debug!(
"Expected exactly 1 top-level item, found {} => not rehydrating.",
items.len()
);
return None;
}
let only_item = &items[0];
if let Some(desired_node) = T::cast(only_item.syntax().clone()) {
let item_range: TextRange = only_item.syntax().text_range();
let file_range: TextRange = sf.syntax().text_range();
if item_range.end() < file_range.end() {
let file_text = sf.syntax().text();
let trailing_slice = file_text.slice(item_range.end()..file_range.end());
let trailing_str = trailing_slice.to_string();
let cleaned = remove_comments_and_whitespace(&trailing_str);
if !cleaned.is_empty() {
debug!("Found extra tokens after the item: {:?}", cleaned);
return None;
}
}
trace!("Snippet rehydrated successfully => returning T");
Some(desired_node)
} else {
debug!("Top-level item is not the correct type => cast failed.");
None
}
}
fn remove_comments_and_whitespace(input: &str) -> String {
let mut no_block = input.replace("/*", "\u{1}").replace("*/", "\u{2}");
while let Some(start) = no_block.find('\u{1}') {
if let Some(end) = no_block[start..].find('\u{2}') {
let absolute_end = start + end + 1; no_block.replace_range(start..absolute_end, "");
} else {
no_block.replace_range(start..no_block.len(), "");
}
}
no_block = no_block.replace('\u{1}', "").replace('\u{2}', "");
let mut cleaned = String::new();
for line in no_block.lines() {
let trimmed = line.trim_start();
if trimmed.starts_with("//") {
continue;
}
let l = line.trim();
if !l.is_empty() {
cleaned.push_str(l);
cleaned.push('\n');
}
}
cleaned.trim().to_string()
}