1960: Replace AST visitors with macro r=viorina a=viorina

Fixes #1672.

Co-authored-by: Ekaterina Babshukova <ekaterina.babshukova@yandex.ru>
This commit is contained in:
bors[bot] 2019-10-05 14:54:25 +00:00 committed by GitHub
commit d3872964f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 349 additions and 429 deletions

View File

@ -51,13 +51,13 @@ fn test_split_import() {
fn split_import_works_with_trees() {
check_assist(
split_import,
"use algo:<|>:visitor::{Visitor, visit}",
"use algo::{<|>visitor::{Visitor, visit}}",
"use crate:<|>:db::{RootDatabase, FileSymbol}",
"use crate::{<|>db::{RootDatabase, FileSymbol}}",
)
}
#[test]
fn split_import_target() {
check_assist_target(split_import, "use algo::<|>visitor::{Visitor, visit}", "::");
check_assist_target(split_import, "use crate::<|>db::{RootDatabase, FileSymbol}", "::");
}
}

View File

@ -1,9 +1,6 @@
//! FIXME: write short doc here
use ra_syntax::{
algo::visit::{visitor_ctx, VisitorCtx},
ast, AstNode,
};
use ra_syntax::{ast, match_ast, AstNode};
use rustc_hash::FxHashMap;
use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions};
@ -19,10 +16,13 @@ pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext)
let mut params = FxHashMap::default();
for node in ctx.token.parent().ancestors() {
let _ = visitor_ctx(&mut params)
.visit::<ast::SourceFile, _>(process)
.visit::<ast::ItemList, _>(process)
.accept(&node);
match_ast! {
match node {
ast::SourceFile(it) => { process(it, &mut params) },
ast::ItemList(it) => { process(it, &mut params) },
_ => (),
}
}
}
params
.into_iter()

View File

@ -1,9 +1,8 @@
//! FIXME: write short doc here
use ra_syntax::{
algo::visit::{visitor, Visitor},
ast::{self, LoopBodyOwner},
AstNode,
match_ast, AstNode,
SyntaxKind::*,
SyntaxToken,
};
@ -84,12 +83,15 @@ fn is_in_loop_body(leaf: &SyntaxToken) -> bool {
if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR {
break;
}
let loop_body = visitor()
.visit::<ast::ForExpr, _>(|it| it.loop_body())
.visit::<ast::WhileExpr, _>(|it| it.loop_body())
.visit::<ast::LoopExpr, _>(|it| it.loop_body())
.accept(&node);
if let Some(Some(body)) = loop_body {
let loop_body = match_ast! {
match node {
ast::ForExpr(it) => { it.loop_body() },
ast::WhileExpr(it) => { it.loop_body() },
ast::LoopExpr(it) => { it.loop_body() },
_ => None,
}
};
if let Some(body) = loop_body {
if leaf.text_range().is_subrange(&body.syntax().text_range()) {
return true;
}

View File

@ -3,9 +3,8 @@
use hir::{AssocItem, FieldSource, HasSource, ModuleSource};
use ra_db::{FileId, SourceDatabase};
use ra_syntax::{
algo::visit::{visitor, Visitor},
ast::{self, DocCommentsOwner},
AstNode, AstPtr, SmolStr,
match_ast, AstNode, AstPtr, SmolStr,
SyntaxKind::{self, NAME},
SyntaxNode, TextRange,
};
@ -308,19 +307,22 @@ pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option
let parse = db.parse(symbol.file_id);
let node = symbol.ptr.to_node(parse.tree().syntax());
visitor()
.visit(|it: ast::FnDef| it.doc_comment_text())
.visit(|it: ast::StructDef| it.doc_comment_text())
.visit(|it: ast::EnumDef| it.doc_comment_text())
.visit(|it: ast::TraitDef| it.doc_comment_text())
.visit(|it: ast::Module| it.doc_comment_text())
.visit(|it: ast::TypeAliasDef| it.doc_comment_text())
.visit(|it: ast::ConstDef| it.doc_comment_text())
.visit(|it: ast::StaticDef| it.doc_comment_text())
.visit(|it: ast::RecordFieldDef| it.doc_comment_text())
.visit(|it: ast::EnumVariant| it.doc_comment_text())
.visit(|it: ast::MacroCall| it.doc_comment_text())
.accept(&node)?
match_ast! {
match node {
ast::FnDef(it) => { it.doc_comment_text() },
ast::StructDef(it) => { it.doc_comment_text() },
ast::EnumDef(it) => { it.doc_comment_text() },
ast::TraitDef(it) => { it.doc_comment_text() },
ast::Module(it) => { it.doc_comment_text() },
ast::TypeAliasDef(it) => { it.doc_comment_text() },
ast::ConstDef(it) => { it.doc_comment_text() },
ast::StaticDef(it) => { it.doc_comment_text() },
ast::RecordFieldDef(it) => { it.doc_comment_text() },
ast::EnumVariant(it) => { it.doc_comment_text() },
ast::MacroCall(it) => { it.doc_comment_text() },
_ => None,
}
}
}
/// Get a description of a symbol.
@ -330,16 +332,19 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) ->
let parse = db.parse(symbol.file_id);
let node = symbol.ptr.to_node(parse.tree().syntax());
visitor()
.visit(|node: ast::FnDef| node.short_label())
.visit(|node: ast::StructDef| node.short_label())
.visit(|node: ast::EnumDef| node.short_label())
.visit(|node: ast::TraitDef| node.short_label())
.visit(|node: ast::Module| node.short_label())
.visit(|node: ast::TypeAliasDef| node.short_label())
.visit(|node: ast::ConstDef| node.short_label())
.visit(|node: ast::StaticDef| node.short_label())
.visit(|node: ast::RecordFieldDef| node.short_label())
.visit(|node: ast::EnumVariant| node.short_label())
.accept(&node)?
match_ast! {
match node {
ast::FnDef(it) => { it.short_label() },
ast::StructDef(it) => { it.short_label() },
ast::EnumDef(it) => { it.short_label() },
ast::TraitDef(it) => { it.short_label() },
ast::Module(it) => { it.short_label() },
ast::TypeAliasDef(it) => { it.short_label() },
ast::ConstDef(it) => { it.short_label() },
ast::StaticDef(it) => { it.short_label() },
ast::RecordFieldDef(it) => { it.short_label() },
ast::EnumVariant(it) => { it.short_label() },
_ => None,
}
}
}

View File

@ -3,9 +3,8 @@
use crate::TextRange;
use ra_syntax::{
algo::visit::{visitor, Visitor},
ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner},
AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent,
match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent,
};
#[derive(Debug, Clone)]
@ -101,63 +100,66 @@ fn collapse_ws(node: &SyntaxNode, output: &mut String) {
})
}
visitor()
.visit(|fn_def: ast::FnDef| {
let mut detail = String::from("fn");
if let Some(type_param_list) = fn_def.type_param_list() {
collapse_ws(type_param_list.syntax(), &mut detail);
}
if let Some(param_list) = fn_def.param_list() {
collapse_ws(param_list.syntax(), &mut detail);
}
if let Some(ret_type) = fn_def.ret_type() {
detail.push_str(" ");
collapse_ws(ret_type.syntax(), &mut detail);
}
decl_with_detail(fn_def, Some(detail))
})
.visit(decl::<ast::StructDef>)
.visit(decl::<ast::EnumDef>)
.visit(decl::<ast::EnumVariant>)
.visit(decl::<ast::TraitDef>)
.visit(decl::<ast::Module>)
.visit(|td: ast::TypeAliasDef| {
let ty = td.type_ref();
decl_with_type_ref(td, ty)
})
.visit(decl_with_ascription::<ast::RecordFieldDef>)
.visit(decl_with_ascription::<ast::ConstDef>)
.visit(decl_with_ascription::<ast::StaticDef>)
.visit(|im: ast::ImplBlock| {
let target_type = im.target_type()?;
let target_trait = im.target_trait();
let label = match target_trait {
None => format!("impl {}", target_type.syntax().text()),
Some(t) => {
format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
match_ast! {
match node {
ast::FnDef(it) => {
let mut detail = String::from("fn");
if let Some(type_param_list) = it.type_param_list() {
collapse_ws(type_param_list.syntax(), &mut detail);
}
if let Some(param_list) = it.param_list() {
collapse_ws(param_list.syntax(), &mut detail);
}
if let Some(ret_type) = it.ret_type() {
detail.push_str(" ");
collapse_ws(ret_type.syntax(), &mut detail);
}
};
let node = StructureNode {
parent: None,
label,
navigation_range: target_type.syntax().text_range(),
node_range: im.syntax().text_range(),
kind: im.syntax().kind(),
detail: None,
deprecated: false,
};
Some(node)
})
.visit(|mc: ast::MacroCall| {
let first_token = mc.syntax().first_token().unwrap();
if first_token.text().as_str() != "macro_rules" {
return None;
}
decl(mc)
})
.accept(&node)?
decl_with_detail(it, Some(detail))
},
ast::StructDef(it) => { decl(it) },
ast::EnumDef(it) => { decl(it) },
ast::EnumVariant(it) => { decl(it) },
ast::TraitDef(it) => { decl(it) },
ast::Module(it) => { decl(it) },
ast::TypeAliasDef(it) => {
let ty = it.type_ref();
decl_with_type_ref(it, ty)
},
ast::RecordFieldDef(it) => { decl_with_ascription(it) },
ast::ConstDef(it) => { decl_with_ascription(it) },
ast::StaticDef(it) => { decl_with_ascription(it) },
ast::ImplBlock(it) => {
let target_type = it.target_type()?;
let target_trait = it.target_trait();
let label = match target_trait {
None => format!("impl {}", target_type.syntax().text()),
Some(t) => {
format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),)
}
};
let node = StructureNode {
parent: None,
label,
navigation_range: target_type.syntax().text_range(),
node_range: it.syntax().text_range(),
kind: it.syntax().kind(),
detail: None,
deprecated: false,
};
Some(node)
},
ast::MacroCall(it) => {
let first_token = it.syntax().first_token().unwrap();
if first_token.text().as_str() != "macro_rules" {
return None;
}
decl(it)
},
_ => None,
}
}
}
#[cfg(test)]

View File

@ -2,12 +2,9 @@
use ra_db::{FileId, SourceDatabase};
use ra_syntax::{
algo::{
find_node_at_offset,
visit::{visitor, Visitor},
},
algo::find_node_at_offset,
ast::{self, DocCommentsOwner},
AstNode, SyntaxNode,
match_ast, AstNode, SyntaxNode,
};
use crate::{
@ -114,91 +111,99 @@ pub(crate) fn name_definition(
}
fn named_target(file_id: FileId, node: &SyntaxNode) -> Option<NavigationTarget> {
visitor()
.visit(|node: ast::StructDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::EnumDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::EnumVariant| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::FnDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::TypeAliasDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::ConstDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::StaticDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::TraitDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::RecordFieldDef| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::Module| {
NavigationTarget::from_named(
file_id,
&node,
node.doc_comment_text(),
node.short_label(),
)
})
.visit(|node: ast::MacroCall| {
NavigationTarget::from_named(file_id, &node, node.doc_comment_text(), None)
})
.accept(node)
match_ast! {
match node {
ast::StructDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::EnumDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::EnumVariant(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::FnDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::TypeAliasDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::ConstDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::StaticDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::TraitDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::RecordFieldDef(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::Module(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
it.short_label(),
))
},
ast::MacroCall(it) => {
Some(NavigationTarget::from_named(
file_id,
&it,
it.doc_comment_text(),
None,
))
},
_ => None,
}
}
}
#[cfg(test)]

View File

@ -3,12 +3,9 @@
use hir::{Adt, HasSource, HirDisplay};
use ra_db::SourceDatabase;
use ra_syntax::{
algo::{
ancestors_at_offset, find_covering_element, find_node_at_offset,
visit::{visitor, Visitor},
},
algo::{ancestors_at_offset, find_covering_element, find_node_at_offset},
ast::{self, DocCommentsOwner},
AstNode,
match_ast, AstNode,
};
use crate::{
@ -178,37 +175,45 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeIn
}
} else if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), position.offset) {
if let Some(parent) = name.syntax().parent() {
let text = visitor()
.visit(|node: ast::StructDef| {
hover_text(node.doc_comment_text(), node.short_label())
})
.visit(|node: ast::EnumDef| hover_text(node.doc_comment_text(), node.short_label()))
.visit(|node: ast::EnumVariant| {
hover_text(node.doc_comment_text(), node.short_label())
})
.visit(|node: ast::FnDef| hover_text(node.doc_comment_text(), node.short_label()))
.visit(|node: ast::TypeAliasDef| {
hover_text(node.doc_comment_text(), node.short_label())
})
.visit(|node: ast::ConstDef| {
hover_text(node.doc_comment_text(), node.short_label())
})
.visit(|node: ast::StaticDef| {
hover_text(node.doc_comment_text(), node.short_label())
})
.visit(|node: ast::TraitDef| {
hover_text(node.doc_comment_text(), node.short_label())
})
.visit(|node: ast::RecordFieldDef| {
hover_text(node.doc_comment_text(), node.short_label())
})
.visit(|node: ast::Module| hover_text(node.doc_comment_text(), node.short_label()))
.visit(|node: ast::MacroCall| hover_text(node.doc_comment_text(), None))
.accept(&parent);
if let Some(text) = text {
res.extend(text);
}
let text = match_ast! {
match parent {
ast::StructDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::EnumDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::EnumVariant(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::FnDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::TypeAliasDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::ConstDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::StaticDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::TraitDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::RecordFieldDef(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::Module(it) => {
hover_text(it.doc_comment_text(), it.short_label())
},
ast::MacroCall(it) => {
hover_text(it.doc_comment_text(), None)
},
_ => None,
}
};
res.extend(text);
}
if !res.is_empty() && range.is_none() {

View File

@ -3,9 +3,8 @@
use crate::{db::RootDatabase, FileId};
use hir::{HirDisplay, SourceAnalyzer, Ty};
use ra_syntax::{
algo::visit::{visitor, Visitor},
ast::{self, AstNode, TypeAscriptionOwner},
SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
};
#[derive(Debug, PartialEq, Eq)]
@ -33,55 +32,58 @@ fn get_inlay_hints(
file_id: FileId,
node: &SyntaxNode,
) -> Option<Vec<InlayHint>> {
visitor()
.visit(|let_statement: ast::LetStmt| {
if let_statement.ascribed_type().is_some() {
return None;
}
let pat = let_statement.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, let_statement.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, false))
})
.visit(|closure_parameter: ast::LambdaExpr| {
let analyzer = SourceAnalyzer::new(db, file_id, closure_parameter.syntax(), None);
closure_parameter.param_list().map(|param_list| {
param_list
.params()
.filter(|closure_param| closure_param.ascribed_type().is_none())
.filter_map(|closure_param| closure_param.pat())
.map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false))
.flatten()
.collect()
})
})
.visit(|for_expression: ast::ForExpr| {
let pat = for_expression.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, for_expression.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, false))
})
.visit(|if_expr: ast::IfExpr| {
let pat = if_expr.condition()?.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, if_expr.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, true))
})
.visit(|while_expr: ast::WhileExpr| {
let pat = while_expr.condition()?.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, while_expr.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, true))
})
.visit(|match_arm_list: ast::MatchArmList| {
let analyzer = SourceAnalyzer::new(db, file_id, match_arm_list.syntax(), None);
Some(
match_arm_list
.arms()
.map(|match_arm| match_arm.pats())
.flatten()
.map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true))
.flatten()
.collect(),
)
})
.accept(&node)?
match_ast! {
match node {
ast::LetStmt(it) => {
if it.ascribed_type().is_some() {
return None;
}
let pat = it.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, false))
},
ast::LambdaExpr(it) => {
let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
it.param_list().map(|param_list| {
param_list
.params()
.filter(|closure_param| closure_param.ascribed_type().is_none())
.filter_map(|closure_param| closure_param.pat())
.map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false))
.flatten()
.collect()
})
},
ast::ForExpr(it) => {
let pat = it.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, false))
},
ast::IfExpr(it) => {
let pat = it.condition()?.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, true))
},
ast::WhileExpr(it) => {
let pat = it.condition()?.pat()?;
let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
Some(get_pat_type_hints(db, &analyzer, pat, true))
},
ast::MatchArmList(it) => {
let analyzer = SourceAnalyzer::new(db, file_id, it.syntax(), None);
Some(
it
.arms()
.map(|match_arm| match_arm.pats())
.flatten()
.map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true))
.flatten()
.collect(),
)
},
_ => None,
}
}
}
fn get_pat_type_hints(

View File

@ -32,9 +32,8 @@
SourceDatabase, SourceRootId,
};
use ra_syntax::{
algo::visit::{visitor, Visitor},
ast::{self, NameOwner},
AstNode, Parse, SmolStr, SourceFile,
match_ast, AstNode, Parse, SmolStr, SourceFile,
SyntaxKind::{self, *},
SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent,
};
@ -306,16 +305,19 @@ fn decl<N: NameOwner>(node: N) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> {
Some((name, ptr, name_range))
}
visitor()
.visit(decl::<ast::FnDef>)
.visit(decl::<ast::StructDef>)
.visit(decl::<ast::EnumDef>)
.visit(decl::<ast::TraitDef>)
.visit(decl::<ast::Module>)
.visit(decl::<ast::TypeAliasDef>)
.visit(decl::<ast::ConstDef>)
.visit(decl::<ast::StaticDef>)
.accept(node)?
match_ast! {
match node {
ast::FnDef(it) => { decl(it) },
ast::StructDef(it) => { decl(it) },
ast::EnumDef(it) => { decl(it) },
ast::TraitDef(it) => { decl(it) },
ast::Module(it) => { decl(it) },
ast::TypeAliasDef(it) => { decl(it) },
ast::ConstDef(it) => { decl(it) },
ast::StaticDef(it) => { decl(it) },
_ => None,
}
}
}
fn to_file_symbol(node: &SyntaxNode, file_id: FileId) -> Option<FileSymbol> {

View File

@ -1,7 +1,5 @@
//! FIXME: write short doc here
pub mod visit;
use std::ops::RangeInclusive;
use itertools::Itertools;

View File

@ -1,112 +0,0 @@
//! FIXME: write short doc here
use crate::{AstNode, SyntaxNode};
use std::marker::PhantomData;
pub fn visitor<'a, T>() -> impl Visitor<'a, Output = T> {
EmptyVisitor { ph: PhantomData }
}
pub fn visitor_ctx<'a, T, C>(ctx: C) -> impl VisitorCtx<'a, Output = T, Ctx = C> {
EmptyVisitorCtx { ph: PhantomData, ctx }
}
pub trait Visitor<'a>: Sized {
type Output;
fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output>;
fn visit<N, F>(self, f: F) -> Vis<Self, N, F>
where
N: AstNode + 'a,
F: FnOnce(N) -> Self::Output,
{
Vis { inner: self, f, ph: PhantomData }
}
}
pub trait VisitorCtx<'a>: Sized {
type Output;
type Ctx;
fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx>;
fn visit<N, F>(self, f: F) -> VisCtx<Self, N, F>
where
N: AstNode + 'a,
F: FnOnce(N, Self::Ctx) -> Self::Output,
{
VisCtx { inner: self, f, ph: PhantomData }
}
}
#[derive(Debug)]
struct EmptyVisitor<T> {
ph: PhantomData<fn() -> T>,
}
impl<'a, T> Visitor<'a> for EmptyVisitor<T> {
type Output = T;
fn accept(self, _node: &'a SyntaxNode) -> Option<T> {
None
}
}
#[derive(Debug)]
struct EmptyVisitorCtx<T, C> {
ctx: C,
ph: PhantomData<fn() -> T>,
}
impl<'a, T, C> VisitorCtx<'a> for EmptyVisitorCtx<T, C> {
type Output = T;
type Ctx = C;
fn accept(self, _node: &'a SyntaxNode) -> Result<T, C> {
Err(self.ctx)
}
}
#[derive(Debug)]
pub struct Vis<V, N, F> {
inner: V,
f: F,
ph: PhantomData<fn(N)>,
}
impl<'a, V, N, F> Visitor<'a> for Vis<V, N, F>
where
V: Visitor<'a>,
N: AstNode + 'a,
F: FnOnce(N) -> <V as Visitor<'a>>::Output,
{
type Output = <V as Visitor<'a>>::Output;
fn accept(self, node: &'a SyntaxNode) -> Option<Self::Output> {
let Vis { inner, f, .. } = self;
inner.accept(node).or_else(|| N::cast(node.clone()).map(f))
}
}
#[derive(Debug)]
pub struct VisCtx<V, N, F> {
inner: V,
f: F,
ph: PhantomData<fn(N)>,
}
impl<'a, V, N, F> VisitorCtx<'a> for VisCtx<V, N, F>
where
V: VisitorCtx<'a>,
N: AstNode + 'a,
F: FnOnce(N, <V as VisitorCtx<'a>>::Ctx) -> <V as VisitorCtx<'a>>::Output,
{
type Output = <V as VisitorCtx<'a>>::Output;
type Ctx = <V as VisitorCtx<'a>>::Ctx;
fn accept(self, node: &'a SyntaxNode) -> Result<Self::Output, Self::Ctx> {
let VisCtx { inner, f, .. } = self;
inner.accept(node).or_else(|ctx| match N::cast(node.clone()) {
None => Err(ctx),
Some(node) => Ok(f(node, ctx)),
})
}
}

View File

@ -160,6 +160,17 @@ pub fn parse(text: &str) -> Parse<SourceFile> {
}
}
#[macro_export]
macro_rules! match_ast {
(match $node:ident {
$( ast::$ast:ident($it:ident) => $res:block, )*
_ => $catch_all:expr,
}) => {{
$( if let Some($it) = ast::$ast::cast($node.clone()) $res else )*
{ $catch_all }
}};
}
/// This test does not assert anything and instead just shows off the crate's
/// API.
#[test]
@ -294,7 +305,7 @@ fn foo() {
// To recursively process the tree, there are three approaches:
// 1. explicitly call getter methods on AST nodes.
// 2. use descendants and `AstNode::cast`.
// 3. use descendants and the visitor.
// 3. use descendants and `match_ast!`.
//
// Here's how the first one looks like:
let exprs_cast: Vec<String> = file
@ -304,17 +315,17 @@ fn foo() {
.map(|expr| expr.syntax().text().to_string())
.collect();
// An alternative is to use a visitor. The visitor does not do traversal
// automatically (so it's more akin to a generic lambda) and is constructed
// from closures. This seems more flexible than a single generated visitor
// trait.
use algo::visit::{visitor, Visitor};
// An alternative is to use a macro.
let mut exprs_visit = Vec::new();
for node in file.syntax().descendants() {
if let Some(result) =
visitor().visit::<ast::Expr, _>(|expr| expr.syntax().text().to_string()).accept(&node)
{
exprs_visit.push(result);
match_ast! {
match node {
ast::Expr(it) => {
let res = it.syntax().text().to_string();
exprs_visit.push(res);
},
_ => (),
}
}
}
assert_eq!(exprs_cast, exprs_visit);

View File

@ -5,8 +5,7 @@
use rustc_lexer::unescape;
use crate::{
algo::visit::{visitor_ctx, VisitorCtx},
ast, AstNode, SyntaxError, SyntaxErrorKind,
ast, match_ast, AstNode, SyntaxError, SyntaxErrorKind,
SyntaxKind::{BYTE, BYTE_STRING, CHAR, INT_NUMBER, STRING},
SyntaxNode, SyntaxToken, TextUnit, T,
};
@ -97,12 +96,15 @@ fn from(err: rustc_lexer::unescape::EscapeError) -> Self {
pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
let mut errors = Vec::new();
for node in root.descendants() {
let _ = visitor_ctx(&mut errors)
.visit::<ast::Literal, _>(validate_literal)
.visit::<ast::BlockExpr, _>(block::validate_block_expr)
.visit::<ast::FieldExpr, _>(|it, errors| validate_numeric_name(it.name_ref(), errors))
.visit::<ast::RecordField, _>(|it, errors| validate_numeric_name(it.name_ref(), errors))
.accept(&node);
match_ast! {
match node {
ast::Literal(it) => { validate_literal(it, &mut errors) },
ast::BlockExpr(it) => { block::validate_block_expr(it, &mut errors) },
ast::FieldExpr(it) => { validate_numeric_name(it.name_ref(), &mut errors) },
ast::RecordField(it) => { validate_numeric_name(it.name_ref(), &mut errors) },
_ => (),
}
}
}
errors
}

View File

@ -79,9 +79,7 @@ Rust syntax tree structure and parser. See
- `grammar.ron` RON description of the grammar, which is used to
generate `syntax_kinds` and `ast` modules, using `cargo gen-syntax` command.
- `algo`: generic tree algorithms, including `walk` for O(1) stack
space tree traversal (this is cool) and `visit` for type-driven
visiting the nodes (this is double plus cool, if you understand how
`Visitor` works, you understand the design of syntax trees).
space tree traversal (this is cool).
Tests for ra_syntax are mostly data-driven: `test_data/parser` contains subdirectories with a bunch of `.rs`
(test vectors) and `.txt` files with corresponding syntax trees. During testing, we check

View File

@ -367,9 +367,9 @@ impl VariantData {
```rust
// before:
use algo:<|>:visitor::{Visitor, visit};
use crate:<|>:db::{RootDatabase, FileSymbol};
// after:
use algo::{<|>visitor::{Visitor, visit}};
use crate::{<|>db::{RootDatabase, FileSymbol}};
```
- Flip binary expression