Skip to content

Commit 355a4bd

Browse files
bors[bot]matklad
andauthored
Merge #11134
11134: internal: tighten up parser API r=matklad a=matklad It's tempting to expose things like `Expr::parse`, but they'll necessary have somewhat ad-hoc semantics. Instead, we narrow down the parser's interface strictly to what's needed for MBE. For everything else (eg, parsing imports), the proper way is enclose the input string into some context, parse the whole as a file, and then verify that the input was parsed as intended. bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
2 parents 4d3ad04 + ea96c37 commit 355a4bd

File tree

81 files changed

+331
-651
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+331
-651
lines changed

crates/hir/src/attrs.rs

+13-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use hir_def::{
1111
};
1212
use hir_expand::{hygiene::Hygiene, MacroDefId};
1313
use hir_ty::db::HirDatabase;
14-
use syntax::ast;
14+
use syntax::{ast, AstNode};
1515

1616
use crate::{
1717
Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam,
@@ -147,8 +147,18 @@ fn resolve_doc_path(
147147
// FIXME
148148
AttrDefId::MacroDefId(_) => return None,
149149
};
150-
let path = ast::Path::parse(link).ok()?;
151-
let modpath = ModPath::from_src(db.upcast(), path, &Hygiene::new_unhygienic())?;
150+
151+
let modpath = {
152+
let ast_path = ast::SourceFile::parse(&format!("type T = {};", link))
153+
.syntax_node()
154+
.descendants()
155+
.find_map(ast::Path::cast)?;
156+
if ast_path.to_string() != link {
157+
return None;
158+
}
159+
ModPath::from_src(db.upcast(), ast_path, &Hygiene::new_unhygienic())?
160+
};
161+
152162
let resolved = resolver.resolve_module_path_in_items(db.upcast(), &modpath);
153163
let resolved = if resolved == PerNs::none() {
154164
resolver.resolve_module_path_in_trait_assoc_items(db.upcast(), &modpath)?

crates/hir_def/src/attr.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,7 @@ impl Attr {
714714
hygiene: &Hygiene,
715715
id: AttrId,
716716
) -> Option<Attr> {
717-
let (parse, _) =
718-
mbe::token_tree_to_syntax_node(tt, mbe::ParserEntryPoint::MetaItem).ok()?;
717+
let (parse, _) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MetaItem).ok()?;
719718
let ast = ast::Meta::cast(parse.syntax_node())?;
720719

721720
Self::from_src(db, ast, hygiene, id)

crates/hir_expand/src/builtin_derive_macro.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ struct BasicAdtInfo {
7272
}
7373

7474
fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, mbe::ExpandError> {
75-
let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, mbe::ParserEntryPoint::Items)?; // FragmentKind::Items doesn't parse attrs?
75+
let (parsed, token_map) = mbe::token_tree_to_syntax_node(tt, mbe::TopEntryPoint::MacroItems)?; // FragmentKind::Items doesn't parse attrs?
7676
let macro_items = ast::MacroItems::cast(parsed.syntax_node()).ok_or_else(|| {
7777
debug!("derive node didn't parse");
7878
mbe::ExpandError::UnexpectedToken

crates/hir_expand/src/db.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,11 @@ fn token_tree_to_syntax_node(
497497
expand_to: ExpandTo,
498498
) -> Result<(Parse<SyntaxNode>, mbe::TokenMap), ExpandError> {
499499
let entry_point = match expand_to {
500-
ExpandTo::Statements => mbe::ParserEntryPoint::Statements,
501-
ExpandTo::Items => mbe::ParserEntryPoint::Items,
502-
ExpandTo::Pattern => mbe::ParserEntryPoint::Pattern,
503-
ExpandTo::Type => mbe::ParserEntryPoint::Type,
504-
ExpandTo::Expr => mbe::ParserEntryPoint::Expr,
500+
ExpandTo::Statements => mbe::TopEntryPoint::MacroStmts,
501+
ExpandTo::Items => mbe::TopEntryPoint::MacroItems,
502+
ExpandTo::Pattern => mbe::TopEntryPoint::Pattern,
503+
ExpandTo::Type => mbe::TopEntryPoint::Type,
504+
ExpandTo::Expr => mbe::TopEntryPoint::Expr,
505505
};
506506
mbe::token_tree_to_syntax_node(tt, entry_point)
507507
}

crates/hir_expand/src/eager.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ pub fn expand_eager_macro(
131131
let arg_file_id = arg_id;
132132

133133
let parsed_args = diagnostic_sink
134-
.result(mbe::token_tree_to_syntax_node(&parsed_args, mbe::ParserEntryPoint::Expr))?
134+
.result(mbe::token_tree_to_syntax_node(&parsed_args, mbe::TopEntryPoint::Expr))?
135135
.0;
136136
let result = eager_macro_recur(
137137
db,

crates/ide_assists/src/handlers/remove_dbg.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
3636
let input_expressions = input_expressions
3737
.into_iter()
3838
.filter_map(|(is_sep, group)| (!is_sep).then(|| group))
39-
.map(|mut tokens| ast::Expr::parse(&tokens.join("")))
40-
.collect::<Result<Vec<ast::Expr>, _>>()
41-
.ok()?;
39+
.map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
40+
.collect::<Option<Vec<ast::Expr>>>()?;
4241

4342
let parent = macro_call.syntax().parent()?;
4443
let (range, text) = match &*input_expressions {

crates/ide_completion/src/completions/attribute.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ fn parse_comma_sep_expr(input: ast::TokenTree) -> Option<Vec<ast::Expr>> {
309309
input_expressions
310310
.into_iter()
311311
.filter_map(|(is_sep, group)| (!is_sep).then(|| group))
312-
.filter_map(|mut tokens| ast::Expr::parse(&tokens.join("")).ok())
312+
.filter_map(|mut tokens| syntax::hacks::parse_expr_from_str(&tokens.join("")))
313313
.collect::<Vec<ast::Expr>>(),
314314
)
315315
}

crates/ide_completion/src/snippet.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -212,15 +212,14 @@ fn validate_snippet(
212212
) -> Option<(Box<[GreenNode]>, String, Option<Box<str>>)> {
213213
let mut imports = Vec::with_capacity(requires.len());
214214
for path in requires.iter() {
215-
let path = ast::Path::parse(path).ok()?;
216-
let valid_use_path = path.segments().all(|seg| {
217-
matches!(seg.kind(), Some(ast::PathSegmentKind::Name(_)))
218-
|| seg.generic_arg_list().is_none()
219-
});
220-
if !valid_use_path {
215+
let use_path = ast::SourceFile::parse(&format!("use {};", path))
216+
.syntax_node()
217+
.descendants()
218+
.find_map(ast::Path::cast)?;
219+
if use_path.syntax().text() != path.as_str() {
221220
return None;
222221
}
223-
let green = path.syntax().green().into_owned();
222+
let green = use_path.syntax().green().into_owned();
224223
imports.push(green);
225224
}
226225
let snippet = snippet.iter().join("\n");

crates/ide_db/src/helpers.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ pub fn get_path_at_cursor_in_tt(cursor: &ast::Ident) -> Option<ast::Path> {
6767
.filter_map(SyntaxElement::into_token)
6868
.take_while(|tok| tok != cursor);
6969

70-
ast::Path::parse(&path_tokens.chain(iter::once(cursor.clone())).join("")).ok()
70+
syntax::hacks::parse_expr_from_str(&path_tokens.chain(iter::once(cursor.clone())).join(""))
71+
.and_then(|expr| match expr {
72+
ast::Expr::PathExpr(it) => it.path(),
73+
_ => None,
74+
})
7175
}
7276

7377
/// Parses and resolves the path at the cursor position in the given attribute, if it is a derive.
@@ -323,7 +327,12 @@ pub fn parse_tt_as_comma_sep_paths(input: ast::TokenTree) -> Option<Vec<ast::Pat
323327
let paths = input_expressions
324328
.into_iter()
325329
.filter_map(|(is_sep, group)| (!is_sep).then(|| group))
326-
.filter_map(|mut tokens| ast::Path::parse(&tokens.join("")).ok())
330+
.filter_map(|mut tokens| {
331+
syntax::hacks::parse_expr_from_str(&tokens.join("")).and_then(|expr| match expr {
332+
ast::Expr::PathExpr(it) => it.path(),
333+
_ => None,
334+
})
335+
})
327336
.collect();
328337
Some(paths)
329338
}

crates/ide_ssr/src/fragments.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//! When specifying SSR rule, you generally want to map one *kind* of thing to
2+
//! the same kind of thing: path to path, expression to expression, type to
3+
//! type.
4+
//!
5+
//! The problem is, while this *kind* is generally obvious to the human, the ide
6+
//! needs to determine it somehow. We do this in a stupid way -- by pasting SSR
7+
//! rule into different contexts and checking what works.
8+
9+
use syntax::{ast, AstNode, SyntaxNode};
10+
11+
pub(crate) fn ty(s: &str) -> Result<SyntaxNode, ()> {
12+
fragment::<ast::Type>("type T = {};", s)
13+
}
14+
15+
pub(crate) fn item(s: &str) -> Result<SyntaxNode, ()> {
16+
fragment::<ast::Item>("{}", s)
17+
}
18+
19+
pub(crate) fn pat(s: &str) -> Result<SyntaxNode, ()> {
20+
fragment::<ast::Pat>("const _: () = {let {} = ();};", s)
21+
}
22+
23+
pub(crate) fn expr(s: &str) -> Result<SyntaxNode, ()> {
24+
fragment::<ast::Expr>("const _: () = {};", s)
25+
}
26+
27+
pub(crate) fn stmt(s: &str) -> Result<SyntaxNode, ()> {
28+
let template = "const _: () = { {}; };";
29+
let input = template.replace("{}", s);
30+
let parse = syntax::SourceFile::parse(&input);
31+
if !parse.errors().is_empty() {
32+
return Err(());
33+
}
34+
let mut node =
35+
parse.tree().syntax().descendants().skip(2).find_map(ast::Stmt::cast).ok_or(())?;
36+
if !s.ends_with(';') && node.to_string().ends_with(';') {
37+
node = node.clone_for_update();
38+
node.syntax().last_token().map(|it| it.detach());
39+
}
40+
if node.to_string() != s {
41+
return Err(());
42+
}
43+
Ok(node.syntax().clone_subtree())
44+
}
45+
46+
fn fragment<T: AstNode>(template: &str, s: &str) -> Result<SyntaxNode, ()> {
47+
let s = s.trim();
48+
let input = template.replace("{}", s);
49+
let parse = syntax::SourceFile::parse(&input);
50+
if !parse.errors().is_empty() {
51+
return Err(());
52+
}
53+
let node = parse.tree().syntax().descendants().find_map(T::cast).ok_or(())?;
54+
if node.syntax().text() != s {
55+
return Err(());
56+
}
57+
Ok(node.syntax().clone_subtree())
58+
}

crates/ide_ssr/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ mod from_comment;
7171
mod matching;
7272
mod nester;
7373
mod parsing;
74+
mod fragments;
7475
mod replacing;
7576
mod resolving;
7677
mod search;

crates/ide_ssr/src/parsing.rs

+18-19
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
//! placeholders, which start with `$`. For replacement templates, this is the final form. For
55
//! search patterns, we go further and parse the pattern as each kind of thing that we can match.
66
//! e.g. expressions, type references etc.
7-
8-
use crate::errors::bail;
9-
use crate::{SsrError, SsrPattern, SsrRule};
107
use rustc_hash::{FxHashMap, FxHashSet};
118
use std::{fmt::Display, str::FromStr};
12-
use syntax::{ast, AstNode, SmolStr, SyntaxKind, SyntaxNode, T};
9+
use syntax::{SmolStr, SyntaxKind, SyntaxNode, T};
10+
11+
use crate::errors::bail;
12+
use crate::{fragments, SsrError, SsrPattern, SsrRule};
1313

1414
#[derive(Debug)]
1515
pub(crate) struct ParsedRule {
@@ -73,17 +73,16 @@ impl ParsedRule {
7373
rules: Vec::new(),
7474
};
7575

76-
let raw_template_stmt = raw_template.map(ast::Stmt::parse);
77-
if let raw_template_expr @ Some(Ok(_)) = raw_template.map(ast::Expr::parse) {
78-
builder.try_add(ast::Expr::parse(&raw_pattern), raw_template_expr);
76+
let raw_template_stmt = raw_template.map(fragments::stmt);
77+
if let raw_template_expr @ Some(Ok(_)) = raw_template.map(fragments::expr) {
78+
builder.try_add(fragments::expr(&raw_pattern), raw_template_expr);
7979
} else {
80-
builder.try_add(ast::Expr::parse(&raw_pattern), raw_template_stmt.clone());
80+
builder.try_add(fragments::expr(&raw_pattern), raw_template_stmt.clone());
8181
}
82-
builder.try_add(ast::Type::parse(&raw_pattern), raw_template.map(ast::Type::parse));
83-
builder.try_add(ast::Item::parse(&raw_pattern), raw_template.map(ast::Item::parse));
84-
builder.try_add(ast::Path::parse(&raw_pattern), raw_template.map(ast::Path::parse));
85-
builder.try_add(ast::Pat::parse(&raw_pattern), raw_template.map(ast::Pat::parse));
86-
builder.try_add(ast::Stmt::parse(&raw_pattern), raw_template_stmt);
82+
builder.try_add(fragments::ty(&raw_pattern), raw_template.map(fragments::ty));
83+
builder.try_add(fragments::item(&raw_pattern), raw_template.map(fragments::item));
84+
builder.try_add(fragments::pat(&raw_pattern), raw_template.map(fragments::pat));
85+
builder.try_add(fragments::stmt(&raw_pattern), raw_template_stmt);
8786
builder.build()
8887
}
8988
}
@@ -94,20 +93,20 @@ struct RuleBuilder {
9493
}
9594

9695
impl RuleBuilder {
97-
fn try_add<T: AstNode, T2: AstNode>(
96+
fn try_add(
9897
&mut self,
99-
pattern: Result<T, ()>,
100-
template: Option<Result<T2, ()>>,
98+
pattern: Result<SyntaxNode, ()>,
99+
template: Option<Result<SyntaxNode, ()>>,
101100
) {
102101
match (pattern, template) {
103102
(Ok(pattern), Some(Ok(template))) => self.rules.push(ParsedRule {
104103
placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
105-
pattern: pattern.syntax().clone(),
106-
template: Some(template.syntax().clone()),
104+
pattern,
105+
template: Some(template),
107106
}),
108107
(Ok(pattern), None) => self.rules.push(ParsedRule {
109108
placeholders_by_stand_in: self.placeholders_by_stand_in.clone(),
110-
pattern: pattern.syntax().clone(),
109+
pattern,
111110
template: None,
112111
}),
113112
_ => {}

crates/ide_ssr/src/replacing.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Code for applying replacement templates for matches that have previously been found.
22
3+
use crate::fragments;
34
use crate::{resolving::ResolvedRule, Match, SsrMatches};
45
use itertools::Itertools;
56
use rustc_hash::{FxHashMap, FxHashSet};
@@ -225,12 +226,13 @@ fn token_is_method_call_receiver(token: &SyntaxToken) -> bool {
225226

226227
fn parse_as_kind(code: &str, kind: SyntaxKind) -> Option<SyntaxNode> {
227228
if ast::Expr::can_cast(kind) {
228-
if let Ok(expr) = ast::Expr::parse(code) {
229-
return Some(expr.syntax().clone());
229+
if let Ok(expr) = fragments::expr(code) {
230+
return Some(expr);
230231
}
231-
} else if ast::Item::can_cast(kind) {
232-
if let Ok(item) = ast::Item::parse(code) {
233-
return Some(item.syntax().clone());
232+
}
233+
if ast::Item::can_cast(kind) {
234+
if let Ok(item) = fragments::item(code) {
235+
return Some(item);
234236
}
235237
}
236238
None

crates/ide_ssr/src/tests.rs

+22
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,15 @@ fn ssr_struct_lit() {
331331
)
332332
}
333333

334+
#[test]
335+
fn ssr_struct_def() {
336+
assert_ssr_transform(
337+
"struct Foo { $f: $t } ==>> struct Foo($t);",
338+
r#"struct Foo { field: i32 }"#,
339+
expect![[r#"struct Foo(i32);"#]],
340+
)
341+
}
342+
334343
#[test]
335344
fn ignores_whitespace() {
336345
assert_matches("1+2", "fn f() -> i32 {1 + 2}", &["1 + 2"]);
@@ -792,6 +801,19 @@ fn replace_type() {
792801
"struct Result<T, E> {} struct Option<T> {} fn f1() -> Option<Vec<Error>> {foo()}"
793802
]],
794803
);
804+
assert_ssr_transform(
805+
"dyn Trait<$a> ==>> DynTrait<$a>",
806+
r#"
807+
trait Trait<T> {}
808+
struct DynTrait<T> {}
809+
fn f1() -> dyn Trait<Vec<Error>> {foo()}
810+
"#,
811+
expect![[r#"
812+
trait Trait<T> {}
813+
struct DynTrait<T> {}
814+
fn f1() -> DynTrait<Vec<Error>> {foo()}
815+
"#]],
816+
);
795817
}
796818

797819
#[test]

0 commit comments

Comments
 (0)