From f5a81ec4683613bd62624811733345d627f2127b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sat, 30 Jan 2021 18:19:21 +0300 Subject: Upgrade rowan Notably, new rowan comes with support for mutable syntax trees. --- crates/hir/src/semantics.rs | 30 ++++-- crates/hir_expand/src/lib.rs | 5 +- crates/ide/src/call_hierarchy.rs | 11 +-- crates/ide/src/doc_links.rs | 2 +- crates/ide/src/extend_selection.rs | 13 ++- crates/ide/src/goto_definition.rs | 4 +- crates/ide/src/goto_type_definition.rs | 2 +- crates/ide/src/hover.rs | 4 +- crates/ide/src/join_lines.rs | 44 +++++---- crates/ide/src/matching_brace.rs | 2 +- crates/ide/src/references.rs | 7 +- crates/ide/src/runnables.rs | 3 +- crates/ide/src/syntax_highlighting.rs | 19 ++-- crates/ide/src/syntax_highlighting/format.rs | 2 +- crates/ide/src/syntax_tree.rs | 2 +- crates/ide/src/typing.rs | 2 +- crates/ide_assists/src/handlers/add_turbo_fish.rs | 2 +- .../ide_assists/src/handlers/change_visibility.rs | 2 +- .../ide_assists/src/handlers/expand_glob_import.rs | 2 +- .../ide_assists/src/handlers/extract_function.rs | 20 ++-- .../ide_assists/src/handlers/flip_trait_bound.rs | 2 +- crates/ide_assists/src/handlers/invert_if.rs | 2 +- crates/ide_assists/src/handlers/move_bounds.rs | 62 +++++------- crates/ide_assists/src/handlers/split_import.rs | 2 +- crates/ide_assists/src/handlers/unwrap_block.rs | 2 +- crates/ide_completion/src/completions/fn_param.rs | 2 +- .../ide_completion/src/completions/trait_impl.rs | 7 +- crates/ide_completion/src/context.rs | 15 +-- crates/ide_completion/src/patterns.rs | 16 +--- crates/ide_db/src/call_info.rs | 2 +- crates/ide_ssr/src/resolving.rs | 2 +- crates/syntax/Cargo.toml | 2 +- crates/syntax/src/algo.rs | 35 +++---- crates/syntax/src/ast.rs | 7 ++ crates/syntax/src/ast/edit_in_place.rs | 105 +++++++++++++++++++++ crates/syntax/src/ast/make.rs | 14 ++- crates/syntax/src/ast/node_ext.rs | 4 +- crates/syntax/src/lib.rs | 1 + crates/syntax/src/parsing/reparsing.rs | 6 +- crates/syntax/src/ted.rs | 78 +++++++++++++++ 40 files changed, 373 insertions(+), 171 deletions(-) create mode 100644 crates/syntax/src/ast/edit_in_place.rs create mode 100644 crates/syntax/src/ted.rs (limited to 'crates') diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 519339c0c..03c9371b5 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -143,6 +143,12 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.diagnostics_display_range(diagnostics) } + pub fn token_ancestors_with_macros( + &self, + token: SyntaxToken, + ) -> impl Iterator + '_ { + token.parent().into_iter().flat_map(move |it| self.ancestors_with_macros(it)) + } pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator + '_ { self.imp.ancestors_with_macros(node) } @@ -270,8 +276,8 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.scope(node) } - pub fn scope_at_offset(&self, node: &SyntaxNode, offset: TextSize) -> SemanticsScope<'db> { - self.imp.scope_at_offset(node, offset) + pub fn scope_at_offset(&self, token: &SyntaxToken, offset: TextSize) -> SemanticsScope<'db> { + self.imp.scope_at_offset(&token.parent().unwrap(), offset) } pub fn scope_for_def(&self, def: Trait) -> SemanticsScope<'db> { @@ -341,7 +347,10 @@ impl<'db> SemanticsImpl<'db> { fn descend_into_macros(&self, token: SyntaxToken) -> SyntaxToken { let _p = profile::span("descend_into_macros"); - let parent = token.parent(); + let parent = match token.parent() { + Some(it) => it, + None => return token, + }; let sa = self.analyze(&parent); let token = successors(Some(InFile::new(sa.file_id, token)), |token| { @@ -360,7 +369,9 @@ impl<'db> SemanticsImpl<'db> { .as_ref()? .map_token_down(token.as_ref())?; - self.cache(find_root(&token.value.parent()), token.file_id); + if let Some(parent) = token.value.parent() { + self.cache(find_root(&parent), token.file_id); + } Some(token) }) @@ -378,7 +389,7 @@ impl<'db> SemanticsImpl<'db> { // Handle macro token cases node.token_at_offset(offset) .map(|token| self.descend_into_macros(token)) - .map(|it| self.ancestors_with_macros(it.parent())) + .map(|it| self.token_ancestors_with_macros(it)) .flatten() } @@ -394,6 +405,13 @@ impl<'db> SemanticsImpl<'db> { src.with_value(&node).original_file_range(self.db.upcast()) } + fn token_ancestors_with_macros( + &self, + token: SyntaxToken, + ) -> impl Iterator + '_ { + token.parent().into_iter().flat_map(move |parent| self.ancestors_with_macros(parent)) + } + fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator + '_ { let node = self.find_file(node); node.ancestors_with_macros(self.db.upcast()).map(|it| it.value) @@ -405,7 +423,7 @@ impl<'db> SemanticsImpl<'db> { offset: TextSize, ) -> impl Iterator + '_ { node.token_at_offset(offset) - .map(|token| self.ancestors_with_macros(token.parent())) + .map(|token| self.token_ancestors_with_macros(token)) .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len()) } diff --git a/crates/hir_expand/src/lib.rs b/crates/hir_expand/src/lib.rs index e388ddacc..eee430af1 100644 --- a/crates/hir_expand/src/lib.rs +++ b/crates/hir_expand/src/lib.rs @@ -510,7 +510,10 @@ impl InFile { self, db: &dyn db::AstDatabase, ) -> impl Iterator> + '_ { - self.map(|it| it.parent()).ancestors_with_macros(db) + self.value + .parent() + .into_iter() + .flat_map(move |parent| InFile::new(self.file_id, parent).ancestors_with_macros(db)) } } diff --git a/crates/ide/src/call_hierarchy.rs b/crates/ide/src/call_hierarchy.rs index b848945d7..96021f677 100644 --- a/crates/ide/src/call_hierarchy.rs +++ b/crates/ide/src/call_hierarchy.rs @@ -53,10 +53,8 @@ pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Optio for (r_range, _) in references { let token = file.token_at_offset(r_range.start()).next()?; let token = sema.descend_into_macros(token); - let syntax = token.parent(); - // This target is the containing function - if let Some(nav) = syntax.ancestors().find_map(|node| { + if let Some(nav) = token.ancestors().find_map(|node| { let fn_ = ast::Fn::cast(node)?; let def = sema.to_def(&fn_)?; def.try_to_nav(sema.db) @@ -77,12 +75,13 @@ pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Optio let file = file.syntax(); let token = file.token_at_offset(position.offset).next()?; let token = sema.descend_into_macros(token); - let syntax = token.parent(); let mut calls = CallLocations::default(); - syntax - .descendants() + token + .parent() + .into_iter() + .flat_map(|it| it.descendants()) .filter_map(|node| FnCallNode::with_node_exact(&node)) .filter_map(|call_node| { let name_ref = call_node.name_ref()?; diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 7bdd3cca3..461e11060 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs @@ -279,7 +279,7 @@ pub(crate) fn external_docs( let token = pick_best(file.token_at_offset(position.offset))?; let token = sema.descend_into_macros(token); - let node = token.parent(); + let node = token.parent()?; let definition = match_ast! { match node { ast::NameRef(name_ref) => NameRefClass::classify(&sema, &name_ref).map(|d| d.referenced(sema.db)), diff --git a/crates/ide/src/extend_selection.rs b/crates/ide/src/extend_selection.rs index b540d04fe..e187243cb 100644 --- a/crates/ide/src/extend_selection.rs +++ b/crates/ide/src/extend_selection.rs @@ -88,7 +88,7 @@ fn try_extend_selection( return Some(range); } } - token.parent() + token.parent()? } NodeOrToken::Node(node) => node, }; @@ -142,7 +142,8 @@ fn extend_tokens_from_range( let extended = { let fst_expanded = sema.descend_into_macros(first_token.clone()); let lst_expanded = sema.descend_into_macros(last_token.clone()); - let mut lca = algo::least_common_ancestor(&fst_expanded.parent(), &lst_expanded.parent())?; + let mut lca = + algo::least_common_ancestor(&fst_expanded.parent()?, &lst_expanded.parent()?)?; lca = shallowest_node(&lca); if lca.first_token() == Some(fst_expanded) && lca.last_token() == Some(lst_expanded) { lca = lca.parent()?; @@ -151,9 +152,13 @@ fn extend_tokens_from_range( }; // Compute parent node range - let validate = |token: &SyntaxToken| { + let validate = |token: &SyntaxToken| -> bool { let expanded = sema.descend_into_macros(token.clone()); - algo::least_common_ancestor(&extended, &expanded.parent()).as_ref() == Some(&extended) + let parent = match expanded.parent() { + Some(it) => it, + None => return false, + }; + algo::least_common_ancestor(&extended, &parent).as_ref() == Some(&extended) }; // Find the first and last text range under expanded parent diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index e8f31e4b1..6986477a5 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -30,7 +30,7 @@ pub(crate) fn goto_definition( let file = sema.parse(position.file_id).syntax().clone(); let original_token = pick_best(file.token_at_offset(position.offset))?; let token = sema.descend_into_macros(original_token.clone()); - let parent = token.parent(); + let parent = token.parent()?; if let Some(comment) = ast::Comment::cast(token) { let nav = def_for_doc_comment(&sema, position, &comment)?.try_to_nav(db)?; return Some(RangeInfo::new(original_token.text_range(), vec![nav])); @@ -63,7 +63,7 @@ fn def_for_doc_comment( position: FilePosition, doc_comment: &ast::Comment, ) -> Option { - let parent = doc_comment.syntax().parent(); + let parent = doc_comment.syntax().parent()?; let (link, ns) = extract_positioned_link_from_comment(position, doc_comment)?; let def = doc_owner_to_def(sema, parent)?; diff --git a/crates/ide/src/goto_type_definition.rs b/crates/ide/src/goto_type_definition.rs index 369a59820..2d38cb112 100644 --- a/crates/ide/src/goto_type_definition.rs +++ b/crates/ide/src/goto_type_definition.rs @@ -22,7 +22,7 @@ pub(crate) fn goto_type_definition( let token: SyntaxToken = pick_best(file.syntax().token_at_offset(position.offset))?; let token: SyntaxToken = sema.descend_into_macros(token); - let (ty, node) = sema.ancestors_with_macros(token.parent()).find_map(|node| { + let (ty, node) = sema.token_ancestors_with_macros(token).find_map(|node| { let ty = match_ast! { match node { ast::Expr(it) => sema.type_of_expr(&it)?, diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index ea45086ce..6215df6bd 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -92,7 +92,7 @@ pub(crate) fn hover( let mut res = HoverResult::default(); - let node = token.parent(); + let node = token.parent()?; let definition = match_ast! { match node { // we don't use NameClass::referenced_or_defined here as we do not want to resolve @@ -438,7 +438,7 @@ fn hover_for_keyword( if !token.kind().is_keyword() { return None; } - let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()).krate()); + let famous_defs = FamousDefs(&sema, sema.scope(&token.parent()?).krate()); // std exposes {}_keyword modules with docstrings on the root to document keywords let keyword_mod = format!("{}_keyword", token.text()); let doc_owner = find_std_module(&famous_defs, &keyword_mod)?; diff --git a/crates/ide/src/join_lines.rs b/crates/ide/src/join_lines.rs index d571ed559..4b25135cd 100644 --- a/crates/ide/src/join_lines.rs +++ b/crates/ide/src/join_lines.rs @@ -32,29 +32,35 @@ pub(crate) fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { range }; - let node = match file.syntax().covering_element(range) { - NodeOrToken::Node(node) => node, - NodeOrToken::Token(token) => token.parent(), - }; let mut edit = TextEdit::builder(); - for token in node.descendants_with_tokens().filter_map(|it| it.into_token()) { - let range = match range.intersect(token.text_range()) { - Some(range) => range, - None => continue, - } - token.text_range().start(); - let text = token.text(); - for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { - let pos: TextSize = (pos as u32).into(); - let offset = token.text_range().start() + range.start() + pos; - if !edit.invalidates_offset(offset) { - remove_newline(&mut edit, &token, offset); + match file.syntax().covering_element(range) { + NodeOrToken::Node(node) => { + for token in node.descendants_with_tokens().filter_map(|it| it.into_token()) { + remove_newlines(&mut edit, &token, range) } } - } - + NodeOrToken::Token(token) => remove_newlines(&mut edit, &token, range), + }; edit.finish() } +fn remove_newlines(edit: &mut TextEditBuilder, token: &SyntaxToken, range: TextRange) { + let intersection = match range.intersect(token.text_range()) { + Some(range) => range, + None => return, + }; + + let range = intersection - token.text_range().start(); + let text = token.text(); + for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { + let pos: TextSize = (pos as u32).into(); + let offset = token.text_range().start() + range.start() + pos; + if !edit.invalidates_offset(offset) { + remove_newline(edit, &token, offset); + } + } +} + fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextSize) { if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { let mut string_open_quote = false; @@ -148,7 +154,7 @@ fn has_comma_after(node: &SyntaxNode) -> bool { } fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { - let block_expr = ast::BlockExpr::cast(token.parent())?; + let block_expr = ast::BlockExpr::cast(token.parent()?)?; if !block_expr.is_standalone() { return None; } @@ -170,7 +176,7 @@ fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Op } fn join_single_use_tree(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { - let use_tree_list = ast::UseTreeList::cast(token.parent())?; + let use_tree_list = ast::UseTreeList::cast(token.parent()?)?; let (tree,) = use_tree_list.use_trees().collect_tuple()?; edit.replace(use_tree_list.syntax().text_range(), tree.syntax().text().to_string()); Some(()) diff --git a/crates/ide/src/matching_brace.rs b/crates/ide/src/matching_brace.rs index 000c412d9..4241a6dac 100644 --- a/crates/ide/src/matching_brace.rs +++ b/crates/ide/src/matching_brace.rs @@ -25,7 +25,7 @@ pub(crate) fn matching_brace(file: &SourceFile, offset: TextSize) -> Option Optio fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> Option { let token = syntax.token_at_offset(position.offset).right_biased()?; + let token_parent = token.parent()?; let kind = token.kind(); if kind == T![;] { - ast::Struct::cast(token.parent()) + ast::Struct::cast(token_parent) .filter(|struct_| struct_.field_list().is_none()) .and_then(|struct_| struct_.name()) } else if kind == T!['{'] { match_ast! { - match (token.parent()) { + match token_parent { ast::RecordFieldList(rfl) => match_ast! { match (rfl.syntax().parent()?) { ast::Variant(it) => it.name(), @@ -169,7 +170,7 @@ fn get_name_of_item_declaration(syntax: &SyntaxNode, position: FilePosition) -> } } } else if kind == T!['('] { - let tfl = ast::TupleFieldList::cast(token.parent())?; + let tfl = ast::TupleFieldList::cast(token_parent)?; match_ast! { match (tfl.syntax().parent()?) { ast::Variant(it) => it.name(), diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 0c7a8fbf8..397e2126b 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -167,8 +167,7 @@ fn find_related_tests( let functions = refs.iter().filter_map(|(range, _)| { let token = file.token_at_offset(range.start()).next()?; let token = sema.descend_into_macros(token); - let syntax = token.parent(); - syntax.ancestors().find_map(ast::Fn::cast) + token.ancestors().find_map(ast::Fn::cast) }); for fn_def in functions { diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 9bed329d8..870146d24 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -64,7 +64,7 @@ pub(crate) fn highlight( Some(range) => { let node = match source_file.syntax().covering_element(range) { NodeOrToken::Node(it) => it, - NodeOrToken::Token(it) => it.parent(), + NodeOrToken::Token(it) => it.parent().unwrap(), }; (node, range) } @@ -167,16 +167,19 @@ fn traverse( let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT { // Inside a macro -- expand it first let token = match element.clone().into_token() { - Some(it) if it.parent().kind() == TOKEN_TREE => it, + Some(it) if it.parent().map_or(false, |it| it.kind() == TOKEN_TREE) => it, _ => continue, }; let token = sema.descend_into_macros(token.clone()); - let parent = token.parent(); - - // We only care Name and Name_ref - match (token.kind(), parent.kind()) { - (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), - _ => token.into(), + match token.parent() { + Some(parent) => { + // We only care Name and Name_ref + match (token.kind(), parent.kind()) { + (IDENT, NAME) | (IDENT, NAME_REF) => parent.into(), + _ => token.into(), + } + } + None => token.into(), } } else { element.clone() diff --git a/crates/ide/src/syntax_highlighting/format.rs b/crates/ide/src/syntax_highlighting/format.rs index 8c67a0863..e503abc93 100644 --- a/crates/ide/src/syntax_highlighting/format.rs +++ b/crates/ide/src/syntax_highlighting/format.rs @@ -28,7 +28,7 @@ pub(super) fn highlight_format_string( } fn is_format_string(string: &ast::String) -> Option<()> { - let parent = string.syntax().parent(); + let parent = string.syntax().parent()?; let name = parent.parent().and_then(ast::MacroCall::cast)?.path()?.segment()?.name_ref()?; if !matches!(name.text(), "format_args" | "format_args_nl") { diff --git a/crates/ide/src/syntax_tree.rs b/crates/ide/src/syntax_tree.rs index f979ba434..8979de528 100644 --- a/crates/ide/src/syntax_tree.rs +++ b/crates/ide/src/syntax_tree.rs @@ -27,7 +27,7 @@ pub(crate) fn syntax_tree( if let Some(tree) = syntax_tree_for_string(&token, text_range) { return tree; } - token.parent() + token.parent().unwrap() } }; diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index a718faf63..e10b7d98e 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -108,7 +108,7 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option { }; let current_indent_len = TextSize::of(current_indent); - let parent = whitespace.syntax().parent(); + let parent = whitespace.syntax().parent()?; // Make sure dot is a part of call chain if !matches!(parent.kind(), FIELD_EXPR | METHOD_CALL_EXPR) { return None; diff --git a/crates/ide_assists/src/handlers/add_turbo_fish.rs b/crates/ide_assists/src/handlers/add_turbo_fish.rs index ee879c151..436767895 100644 --- a/crates/ide_assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide_assists/src/handlers/add_turbo_fish.rs @@ -38,7 +38,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<( cov_mark::hit!(add_turbo_fish_one_fish_is_enough); return None; } - let name_ref = ast::NameRef::cast(ident.parent())?; + let name_ref = ast::NameRef::cast(ident.parent()?)?; let def = match NameRefClass::classify(&ctx.sema, &name_ref)? { NameRefClass::Definition(def) => def, NameRefClass::ExternCrate(_) | NameRefClass::FieldShorthand { .. } => return None, diff --git a/crates/ide_assists/src/handlers/change_visibility.rs b/crates/ide_assists/src/handlers/change_visibility.rs index ec99a5505..d7e39b2ae 100644 --- a/crates/ide_assists/src/handlers/change_visibility.rs +++ b/crates/ide_assists/src/handlers/change_visibility.rs @@ -41,7 +41,7 @@ fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { }); let (offset, target) = if let Some(keyword) = item_keyword { - let parent = keyword.parent(); + let parent = keyword.parent()?; let def_kws = vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT]; // Parent is not a definition, can't add visibility if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { diff --git a/crates/ide_assists/src/handlers/expand_glob_import.rs b/crates/ide_assists/src/handlers/expand_glob_import.rs index 5fe617ba4..5b540df5c 100644 --- a/crates/ide_assists/src/handlers/expand_glob_import.rs +++ b/crates/ide_assists/src/handlers/expand_glob_import.rs @@ -48,7 +48,7 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti _ => return None, }; - let current_scope = ctx.sema.scope(&star.parent()); + let current_scope = ctx.sema.scope(&star.parent()?); let current_module = current_scope.module()?; let refs_in_target = find_refs_in_mod(ctx, target_module, Some(current_module))?; diff --git a/crates/ide_assists/src/handlers/extract_function.rs b/crates/ide_assists/src/handlers/extract_function.rs index dd4501709..5fdc8bf38 100644 --- a/crates/ide_assists/src/handlers/extract_function.rs +++ b/crates/ide_assists/src/handlers/extract_function.rs @@ -16,7 +16,6 @@ use syntax::{ edit::{AstNodeEdit, IndentLevel}, AstNode, }, - SyntaxElement, SyntaxKind::{self, BLOCK_EXPR, BREAK_EXPR, COMMENT, PATH_EXPR, RETURN_EXPR}, SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T, }; @@ -62,7 +61,10 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option return None; } - let node = element_to_node(node); + let node = match node { + syntax::NodeOrToken::Node(n) => n, + syntax::NodeOrToken::Token(t) => t.parent()?, + }; let body = extraction_target(&node, ctx.frange.range)?; @@ -560,14 +562,6 @@ impl HasTokenAtOffset for FunctionBody { } } -/// node or token's parent -fn element_to_node(node: SyntaxElement) -> SyntaxNode { - match node { - syntax::NodeOrToken::Node(n) => n, - syntax::NodeOrToken::Token(t) => t.parent(), - } -} - /// Try to guess what user wants to extract /// /// We have basically have two cases: @@ -1246,7 +1240,7 @@ fn make_body( }) } FlowHandler::If { .. } => { - let lit_false = ast::Literal::cast(make::tokens::literal("false").parent()).unwrap(); + let lit_false = make::expr_literal("false"); with_tail_expr(block, lit_false.into()) } FlowHandler::IfOption { .. } => { @@ -1420,9 +1414,7 @@ fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) -> S fn make_rewritten_flow(handler: &FlowHandler, arg_expr: Option) -> Option { let value = match handler { FlowHandler::None | FlowHandler::Try { .. } => return None, - FlowHandler::If { .. } => { - ast::Literal::cast(make::tokens::literal("true").parent()).unwrap().into() - } + FlowHandler::If { .. } => make::expr_literal("true").into(), FlowHandler::IfOption { .. } => { let expr = arg_expr.unwrap_or_else(|| make::expr_tuple(Vec::new())); let args = make::arg_list(iter::once(expr)); diff --git a/crates/ide_assists/src/handlers/flip_trait_bound.rs b/crates/ide_assists/src/handlers/flip_trait_bound.rs index d419d263e..a868aa43d 100644 --- a/crates/ide_assists/src/handlers/flip_trait_bound.rs +++ b/crates/ide_assists/src/handlers/flip_trait_bound.rs @@ -23,7 +23,7 @@ pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option let plus = ctx.find_token_syntax_at_offset(T![+])?; // Make sure we're in a `TypeBoundList` - if ast::TypeBoundList::cast(plus.parent()).is_none() { + if ast::TypeBoundList::cast(plus.parent()?).is_none() { return None; } diff --git a/crates/ide_assists/src/handlers/invert_if.rs b/crates/ide_assists/src/handlers/invert_if.rs index b131dc205..53612ec3f 100644 --- a/crates/ide_assists/src/handlers/invert_if.rs +++ b/crates/ide_assists/src/handlers/invert_if.rs @@ -30,7 +30,7 @@ use crate::{ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let if_keyword = ctx.find_token_syntax_at_offset(T![if])?; - let expr = ast::IfExpr::cast(if_keyword.parent())?; + let expr = ast::IfExpr::cast(if_keyword.parent()?)?; let if_range = if_keyword.text_range(); let cursor_in_range = if_range.contains_range(ctx.frange.range); if !cursor_in_range { diff --git a/crates/ide_assists/src/handlers/move_bounds.rs b/crates/ide_assists/src/handlers/move_bounds.rs index cf260c6f8..48efa67ed 100644 --- a/crates/ide_assists/src/handlers/move_bounds.rs +++ b/crates/ide_assists/src/handlers/move_bounds.rs @@ -1,8 +1,6 @@ use syntax::{ - ast::{self, edit::AstNodeEdit, make, AstNode, NameOwner, TypeBoundsOwner}, + ast::{self, edit_in_place::GenericParamsOwnerEdit, make, AstNode, NameOwner, TypeBoundsOwner}, match_ast, - SyntaxKind::*, - T, }; use crate::{AssistContext, AssistId, AssistKind, Assists}; @@ -23,7 +21,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // } // ``` pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let type_param_list = ctx.find_node_at_offset::()?; + let type_param_list = ctx.find_node_at_offset::()?.clone_for_update(); let mut type_params = type_param_list.type_params(); if type_params.all(|p| p.type_bound_list().is_none()) { @@ -31,23 +29,7 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext } let parent = type_param_list.syntax().parent()?; - if parent.children_with_tokens().any(|it| it.kind() == WHERE_CLAUSE) { - return None; - } - - let anchor = match_ast! { - match parent { - ast::Fn(it) => it.body()?.syntax().clone().into(), - ast::Trait(it) => it.assoc_item_list()?.syntax().clone().into(), - ast::Impl(it) => it.assoc_item_list()?.syntax().clone().into(), - ast::Enum(it) => it.variant_list()?.syntax().clone().into(), - ast::Struct(it) => { - it.syntax().children_with_tokens() - .find(|it| it.kind() == RECORD_FIELD_LIST || it.kind() == T![;])? - }, - _ => return None - } - }; + let original_parent_range = parent.text_range(); let target = type_param_list.syntax().text_range(); acc.add( @@ -55,29 +37,27 @@ pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext "Move to where clause", target, |edit| { - let new_params = type_param_list - .type_params() - .filter(|it| it.type_bound_list().is_some()) - .map(|type_param| { - let without_bounds = type_param.remove_bounds(); - (type_param, without_bounds) - }); - - let new_type_param_list = type_param_list.replace_descendants(new_params); - edit.replace_ast(type_param_list.clone(), new_type_param_list); - - let where_clause = { - let predicates = type_param_list.type_params().filter_map(build_predicate); - make::where_clause(predicates) + let where_clause: ast::WhereClause = match_ast! { + match parent { + ast::Fn(it) => it.get_or_create_where_clause(), + // ast::Trait(it) => it.get_or_create_where_clause(), + ast::Impl(it) => it.get_or_create_where_clause(), + // ast::Enum(it) => it.get_or_create_where_clause(), + ast::Struct(it) => it.get_or_create_where_clause(), + _ => return, + } }; - let to_insert = match anchor.prev_sibling_or_token() { - Some(ref elem) if elem.kind() == WHITESPACE => { - format!("{} ", where_clause.syntax()) + for type_param in type_param_list.type_params() { + if let Some(tbl) = type_param.type_bound_list() { + if let Some(predicate) = build_predicate(type_param.clone()) { + where_clause.add_predicate(predicate.clone_for_update()) + } + tbl.remove() } - _ => format!(" {}", where_clause.syntax()), - }; - edit.insert(anchor.text_range().start(), to_insert); + } + + edit.replace(original_parent_range, parent.to_string()) }, ) } diff --git a/crates/ide_assists/src/handlers/split_import.rs b/crates/ide_assists/src/handlers/split_import.rs index 9319a4267..446f30544 100644 --- a/crates/ide_assists/src/handlers/split_import.rs +++ b/crates/ide_assists/src/handlers/split_import.rs @@ -17,7 +17,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // ``` pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let colon_colon = ctx.find_token_syntax_at_offset(T![::])?; - let path = ast::Path::cast(colon_colon.parent())?.qualifier()?; + let path = ast::Path::cast(colon_colon.parent()?)?.qualifier()?; let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?; let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?; diff --git a/crates/ide_assists/src/handlers/unwrap_block.rs b/crates/ide_assists/src/handlers/unwrap_block.rs index ed6f6177d..440639322 100644 --- a/crates/ide_assists/src/handlers/unwrap_block.rs +++ b/crates/ide_assists/src/handlers/unwrap_block.rs @@ -30,7 +30,7 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> let assist_label = "Unwrap block"; let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?; - let mut block = ast::BlockExpr::cast(l_curly_token.parent())?; + let mut block = ast::BlockExpr::cast(l_curly_token.parent()?)?; let target = block.syntax().text_range(); let mut parent = block.syntax().parent()?; if ast::MatchArm::can_cast(parent.kind()) { diff --git a/crates/ide_completion/src/completions/fn_param.rs b/crates/ide_completion/src/completions/fn_param.rs index 0243dce56..0ea558489 100644 --- a/crates/ide_completion/src/completions/fn_param.rs +++ b/crates/ide_completion/src/completions/fn_param.rs @@ -33,7 +33,7 @@ pub(crate) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) }); }; - for node in ctx.token.parent().ancestors() { + for node in ctx.token.ancestors() { match_ast! { match node { ast::SourceFile(it) => it.items().filter_map(|item| match item { diff --git a/crates/ide_completion/src/completions/trait_impl.rs b/crates/ide_completion/src/completions/trait_impl.rs index 5a7361f8e..a26fe7c6c 100644 --- a/crates/ide_completion/src/completions/trait_impl.rs +++ b/crates/ide_completion/src/completions/trait_impl.rs @@ -82,13 +82,14 @@ pub(crate) fn complete_trait_impl(acc: &mut Completions, ctx: &CompletionContext fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, SyntaxNode, Impl)> { let mut token = ctx.token.clone(); - // For keywork without name like `impl .. { fn $0 }`, the current position is inside + // For keyword without name like `impl .. { fn $0 }`, the current position is inside // the whitespace token, which is outside `FN` syntax node. // We need to follow the previous token in this case. if token.kind() == SyntaxKind::WHITESPACE { token = token.prev_token()?; } + let parent_kind = token.parent().map_or(SyntaxKind::EOF, |it| it.kind()); let impl_item_offset = match token.kind() { // `impl .. { const $0 }` // ERROR 0 @@ -102,14 +103,14 @@ fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, Synt // FN/TYPE_ALIAS/CONST 1 // NAME 0 // IDENT <- * - SyntaxKind::IDENT if token.parent().kind() == SyntaxKind::NAME => 1, + SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME => 1, // `impl .. { foo$0 }` // MACRO_CALL 3 // PATH 2 // PATH_SEGMENT 1 // NAME_REF 0 // IDENT <- * - SyntaxKind::IDENT if token.parent().kind() == SyntaxKind::NAME_REF => 3, + SyntaxKind::IDENT if parent_kind == SyntaxKind::NAME_REF => 3, _ => return None, }; diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs index e6cc6329c..8cbbdb477 100644 --- a/crates/ide_completion/src/context.rs +++ b/crates/ide_completion/src/context.rs @@ -120,7 +120,7 @@ impl<'a> CompletionContext<'a> { let original_token = original_file.syntax().token_at_offset(position.offset).left_biased()?; let token = sema.descend_into_macros(original_token.clone()); - let scope = sema.scope_at_offset(&token.parent(), position.offset); + let scope = sema.scope_at_offset(&token, position.offset); let mut locals = vec![]; scope.process_all_names(&mut |name, scope| { if let ScopeDef::Local(local) = scope { @@ -281,7 +281,7 @@ impl<'a> CompletionContext<'a> { fn fill_impl_def(&mut self) { self.impl_def = self .sema - .ancestors_with_macros(self.token.parent()) + .token_ancestors_with_macros(self.token.clone()) .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) .find_map(ast::Impl::cast); } @@ -293,7 +293,10 @@ impl<'a> CompletionContext<'a> { offset: TextSize, ) { let expected = { - let mut node = self.token.parent(); + let mut node = match self.token.parent() { + Some(it) => it, + None => return, + }; loop { let ret = match_ast! { match node { @@ -474,17 +477,17 @@ impl<'a> CompletionContext<'a> { } self.use_item_syntax = - self.sema.ancestors_with_macros(self.token.parent()).find_map(ast::Use::cast); + self.sema.token_ancestors_with_macros(self.token.clone()).find_map(ast::Use::cast); self.function_syntax = self .sema - .ancestors_with_macros(self.token.parent()) + .token_ancestors_with_macros(self.token.clone()) .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) .find_map(ast::Fn::cast); self.record_field_syntax = self .sema - .ancestors_with_macros(self.token.parent()) + .token_ancestors_with_macros(self.token.clone()) .take_while(|it| { it.kind() != SOURCE_FILE && it.kind() != MODULE && it.kind() != CALL_EXPR }) diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs index f3ce91dd1..cf5ef07b7 100644 --- a/crates/ide_completion/src/patterns.rs +++ b/crates/ide_completion/src/patterns.rs @@ -184,11 +184,7 @@ fn test_has_impl_as_prev_sibling() { } pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { - let leaf = match element { - NodeOrToken::Node(node) => node, - NodeOrToken::Token(token) => token.parent(), - }; - for node in leaf.ancestors() { + for node in element.ancestors() { if node.kind() == FN || node.kind() == CLOSURE_EXPR { break; } @@ -201,7 +197,7 @@ pub(crate) fn is_in_loop_body(element: SyntaxElement) -> bool { } }; if let Some(body) = loop_body { - if body.syntax().text_range().contains_range(leaf.text_range()) { + if body.syntax().text_range().contains_range(element.text_range()) { return true; } } @@ -235,12 +231,8 @@ fn previous_sibling_or_ancestor_sibling(element: SyntaxElement) -> Option node, - NodeOrToken::Token(token) => token.parent(), - }; - let range = node.text_range(); - let top_node = node.ancestors().take_while(|it| it.text_range() == range).last()?; + let range = element.text_range(); + let top_node = element.ancestors().take_while(|it| it.text_range() == range).last()?; let prev_sibling_node = top_node.ancestors().find(|it| { non_trivia_sibling(NodeOrToken::Node(it.to_owned()), Direction::Prev).is_some() })?; diff --git a/crates/ide_db/src/call_info.rs b/crates/ide_db/src/call_info.rs index d8878aa91..d4016973c 100644 --- a/crates/ide_db/src/call_info.rs +++ b/crates/ide_db/src/call_info.rs @@ -109,7 +109,7 @@ fn call_info_impl( token: SyntaxToken, ) -> Option<(hir::Callable, Option)> { // Find the calling expression and it's NameRef - let calling_node = FnCallNode::with_node(&token.parent())?; + let calling_node = FnCallNode::with_node(&token.parent()?)?; let callable = match &calling_node { FnCallNode::CallExpr(call) => sema.type_of_expr(&call.expr()?)?.as_callable(sema.db)?, diff --git a/crates/ide_ssr/src/resolving.rs b/crates/ide_ssr/src/resolving.rs index af94c7bb1..dc7835473 100644 --- a/crates/ide_ssr/src/resolving.rs +++ b/crates/ide_ssr/src/resolving.rs @@ -195,7 +195,7 @@ impl<'db> ResolutionScope<'db> { .syntax() .token_at_offset(resolve_context.offset) .left_biased() - .map(|token| token.parent()) + .and_then(|token| token.parent()) .unwrap_or_else(|| file.syntax().clone()); let node = pick_node_for_resolution(node); let scope = sema.scope(&node); diff --git a/crates/syntax/Cargo.toml b/crates/syntax/Cargo.toml index c0fd894b0..74cafaa8d 100644 --- a/crates/syntax/Cargo.toml +++ b/crates/syntax/Cargo.toml @@ -13,7 +13,7 @@ doctest = false [dependencies] cov-mark = { version = "1.1", features = ["thread-local"] } itertools = "0.10.0" -rowan = "0.12.2" +rowan = "0.13.0-pre.2" rustc_lexer = { version = "710.0.0", package = "rustc-ap-rustc_lexer" } rustc-hash = "1.1.0" arrayvec = "0.5.1" diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs index b13252eec..82ebf9037 100644 --- a/crates/syntax/src/algo.rs +++ b/crates/syntax/src/algo.rs @@ -4,7 +4,6 @@ use std::{ fmt, hash::BuildHasherDefault, ops::{self, RangeInclusive}, - ptr, }; use indexmap::IndexMap; @@ -27,7 +26,7 @@ pub fn ancestors_at_offset( offset: TextSize, ) -> impl Iterator { node.token_at_offset(offset) - .map(|token| token.parent().ancestors()) + .map(|token| token.ancestors()) .kmerge_by(|node1, node2| node1.text_range().len() < node2.text_range().len()) } @@ -171,7 +170,7 @@ pub fn diff(from: &SyntaxNode, to: &SyntaxNode) -> TreeDiff { && lhs.text_range().len() == rhs.text_range().len() && match (&lhs, &rhs) { (NodeOrToken::Node(lhs), NodeOrToken::Node(rhs)) => { - ptr::eq(lhs.green(), rhs.green()) || lhs.text() == rhs.text() + lhs == rhs || lhs.text() == rhs.text() } (NodeOrToken::Token(lhs), NodeOrToken::Token(rhs)) => lhs.text() == rhs.text(), _ => false, @@ -280,9 +279,10 @@ fn _insert_children( to_green_element(element) }); - let mut old_children = parent.green().children().map(|it| match it { - NodeOrToken::Token(it) => NodeOrToken::Token(it.clone()), - NodeOrToken::Node(it) => NodeOrToken::Node(it.clone()), + let parent_green = parent.green(); + let mut old_children = parent_green.children().map(|it| match it { + NodeOrToken::Token(it) => NodeOrToken::Token(it.to_owned()), + NodeOrToken::Node(it) => NodeOrToken::Node(it.to_owned()), }); let new_children = match &position { @@ -319,9 +319,10 @@ fn _replace_children( ) -> SyntaxNode { let start = position_of_child(parent, to_delete.start().clone()); let end = position_of_child(parent, to_delete.end().clone()); - let mut old_children = parent.green().children().map(|it| match it { - NodeOrToken::Token(it) => NodeOrToken::Token(it.clone()), - NodeOrToken::Node(it) => NodeOrToken::Node(it.clone()), + let parent_green = parent.green(); + let mut old_children = parent_green.children().map(|it| match it { + NodeOrToken::Token(it) => NodeOrToken::Token(it.to_owned()), + NodeOrToken::Node(it) => NodeOrToken::Node(it.to_owned()), }); let before = old_children.by_ref().take(start).collect::>(); @@ -487,9 +488,9 @@ impl<'a> SyntaxRewriter<'a> { /// Returns `None` when there are no replacements. pub fn rewrite_root(&self) -> Option { let _p = profile::span("rewrite_root"); - fn element_to_node_or_parent(element: &SyntaxElement) -> SyntaxNode { + fn element_to_node_or_parent(element: &SyntaxElement) -> Option { match element { - SyntaxElement::Node(it) => it.clone(), + SyntaxElement::Node(it) => Some(it.clone()), SyntaxElement::Token(it) => it.parent(), } } @@ -497,9 +498,9 @@ impl<'a> SyntaxRewriter<'a> { assert!(self.f.is_none()); self.replacements .keys() - .map(element_to_node_or_parent) - .chain(self.insertions.keys().map(|pos| match pos { - InsertPos::FirstChildOf(it) => it.clone(), + .filter_map(element_to_node_or_parent) + .chain(self.insertions.keys().filter_map(|pos| match pos { + InsertPos::FirstChildOf(it) => Some(it.clone()), InsertPos::After(it) => element_to_node_or_parent(it), })) // If we only have one replacement/insertion, we must return its parent node, since `rewrite` does @@ -552,7 +553,7 @@ impl<'a> SyntaxRewriter<'a> { }; } else { match element { - NodeOrToken::Token(it) => acc.push(NodeOrToken::Token(it.green().clone())), + NodeOrToken::Token(it) => acc.push(NodeOrToken::Token(it.green().to_owned())), NodeOrToken::Node(it) => { acc.push(NodeOrToken::Node(self.rewrite_children(it))); } @@ -567,7 +568,7 @@ impl<'a> SyntaxRewriter<'a> { fn element_to_green(element: SyntaxElement) -> NodeOrToken { match element { NodeOrToken::Node(it) => NodeOrToken::Node(it.green().to_owned()), - NodeOrToken::Token(it) => NodeOrToken::Token(it.green().clone()), + NodeOrToken::Token(it) => NodeOrToken::Token(it.green().to_owned()), } } @@ -625,7 +626,7 @@ fn position_of_child(parent: &SyntaxNode, child: SyntaxElement) -> usize { fn to_green_element(element: SyntaxElement) -> NodeOrToken { match element { NodeOrToken::Node(it) => it.green().to_owned().into(), - NodeOrToken::Token(it) => it.green().clone().into(), + NodeOrToken::Token(it) => it.green().to_owned().into(), } } diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs index b3a24d39d..19261686c 100644 --- a/crates/syntax/src/ast.rs +++ b/crates/syntax/src/ast.rs @@ -6,6 +6,7 @@ mod token_ext; mod node_ext; mod expr_ext; pub mod edit; +pub mod edit_in_place; pub mod make; use std::marker::PhantomData; @@ -40,6 +41,12 @@ pub trait AstNode { Self: Sized; fn syntax(&self) -> &SyntaxNode; + fn clone_for_update(&self) -> Self + where + Self: Sized, + { + Self::cast(self.syntax().clone_for_update()).unwrap() + } } /// Like `AstNode`, but wraps tokens rather than interior nodes. diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs new file mode 100644 index 000000000..06cde591d --- /dev/null +++ b/crates/syntax/src/ast/edit_in_place.rs @@ -0,0 +1,105 @@ +//! Structural editing for ast. + +use std::iter::empty; + +use ast::{edit::AstNodeEdit, make, GenericParamsOwner, WhereClause}; +use parser::T; + +use crate::{ + ast, + ted::{self, Position}, + AstNode, Direction, SyntaxKind, +}; + +use super::NameOwner; + +pub trait GenericParamsOwnerEdit: ast::GenericParamsOwner + AstNodeEdit { + fn get_or_create_where_clause(&self) -> ast::WhereClause; +} + +impl GenericParamsOwnerEdit for ast::Fn { + fn get_or_create_where_clause(&self) -> WhereClause { + if self.where_clause().is_none() { + let position = if let Some(ty) = self.ret_type() { + Position::after(ty.syntax().clone()) + } else if let Some(param_list) = self.param_list() { + Position::after(param_list.syntax().clone()) + } else { + Position::last_child_of(self.syntax().clone()) + }; + create_where_clause(position) + } + self.where_clause().unwrap() + } +} + +impl GenericParamsOwnerEdit for ast::Impl { + fn get_or_create_where_clause(&self) -> WhereClause { + if self.where_clause().is_none() { + let position = if let Some(ty) = self.self_ty() { + Position::after(ty.syntax().clone()) + } else { + Position::last_child_of(self.syntax().clone()) + }; + create_where_clause(position) + } + self.where_clause().unwrap() + } +} +impl GenericParamsOwnerEdit for ast::Struct { + fn get_or_create_where_clause(&self) -> WhereClause { + if self.where_clause().is_none() { + let tfl = self.field_list().and_then(|fl| match fl { + ast::FieldList::RecordFieldList(_) => None, + ast::FieldList::TupleFieldList(it) => Some(it), + }); + let position = if let Some(tfl) = tfl { + Position::after(tfl.syntax().clone()) + } else if let Some(gpl) = self.generic_param_list() { + Position::after(gpl.syntax().clone()) + } else if let Some(name) = self.name() { + Position::after(name.syntax().clone()) + } else { + Position::last_child_of(self.syntax().clone()) + }; + create_where_clause(position) + } + self.where_clause().unwrap() + } +} + +fn create_where_clause(position: Position) { + let elements = vec![ + make::tokens::single_space().into(), + make::where_clause(empty()).clone_for_update().syntax().clone().into(), + ]; + ted::insert_all(position, elements); +} + +impl ast::WhereClause { + pub fn add_predicate(&self, predicate: ast::WherePred) { + if let Some(pred) = self.predicates().last() { + if !pred.syntax().siblings_with_tokens(Direction::Next).any(|it| it.kind() == T![,]) { + ted::append_child(self.syntax().clone(), make::token(T![,])); + } + } + if self.syntax().children_with_tokens().last().map(|it| it.kind()) + != Some(SyntaxKind::WHITESPACE) + { + ted::append_child(self.syntax().clone(), make::tokens::single_space()); + } + ted::append_child(self.syntax().clone(), predicate.syntax().clone()) + } +} + +impl ast::TypeBoundList { + pub fn remove(&self) { + if let Some(colon) = + self.syntax().siblings_with_tokens(Direction::Prev).find(|it| it.kind() == T![:]) + { + ted::remove_all(colon..=self.syntax().clone().into()) + } else { + ted::remove(self.syntax().clone()) + } + } +} diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 05a6b0b25..810c8d4c8 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -174,6 +174,11 @@ pub fn block_expr( pub fn expr_unit() -> ast::Expr { expr_from_text("()") } +pub fn expr_literal(text: &str) -> ast::Literal { + assert_eq!(text.trim(), text); + ast_from_text(&format!("fn f() {{ let _ = {}; }}", text)) +} + pub fn expr_empty_block() -> ast::Expr { expr_from_text("{}") } @@ -390,6 +395,7 @@ pub fn token(kind: SyntaxKind) -> SyntaxToken { tokens::SOURCE_FILE .tree() .syntax() + .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == kind) @@ -544,6 +550,7 @@ pub mod tokens { SOURCE_FILE .tree() .syntax() + .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == WHITESPACE && it.text() == " ") @@ -569,13 +576,16 @@ pub mod tokens { } pub fn single_newline() -> SyntaxToken { - SOURCE_FILE + let res = SOURCE_FILE .tree() .syntax() + .clone_for_update() .descendants_with_tokens() .filter_map(|it| it.into_token()) .find(|it| it.kind() == WHITESPACE && it.text() == "\n") - .unwrap() + .unwrap(); + res.detach(); + res } pub fn blank_line() -> SyntaxToken { diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index 52ac97c84..0b0d39a75 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs @@ -34,7 +34,9 @@ impl ast::NameRef { } fn text_of_first_token(node: &SyntaxNode) -> &str { - node.green().children().next().and_then(|it| it.into_token()).unwrap().text() + let t = + node.green().children().next().and_then(|it| it.into_token()).unwrap().text().to_string(); + Box::leak(Box::new(t)) } pub enum Macro { diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs index 09e212e8c..2a5c61171 100644 --- a/crates/syntax/src/lib.rs +++ b/crates/syntax/src/lib.rs @@ -38,6 +38,7 @@ pub mod ast; #[doc(hidden)] pub mod fuzz; pub mod utils; +pub mod ted; use std::{marker::PhantomData, sync::Arc}; diff --git a/crates/syntax/src/parsing/reparsing.rs b/crates/syntax/src/parsing/reparsing.rs index 3d637bf91..4ad50ab72 100644 --- a/crates/syntax/src/parsing/reparsing.rs +++ b/crates/syntax/src/parsing/reparsing.rs @@ -124,11 +124,7 @@ fn is_contextual_kw(text: &str) -> bool { fn find_reparsable_node(node: &SyntaxNode, range: TextRange) -> Option<(SyntaxNode, Reparser)> { let node = node.covering_element(range); - let mut ancestors = match node { - NodeOrToken::Token(it) => it.parent().ancestors(), - NodeOrToken::Node(it) => it.ancestors(), - }; - ancestors.find_map(|node| { + node.ancestors().find_map(|node| { let first_child = node.first_child_or_token().map(|it| it.kind()); let parent = node.parent().map(|it| it.kind()); Reparser::for_node(node.kind(), first_child, parent).map(|r| (node, r)) diff --git a/crates/syntax/src/ted.rs b/crates/syntax/src/ted.rs new file mode 100644 index 000000000..8d6175ed9 --- /dev/null +++ b/crates/syntax/src/ted.rs @@ -0,0 +1,78 @@ +//! Primitive tree editor, ed for trees +#![allow(unused)] +use std::ops::RangeInclusive; + +use crate::{SyntaxElement, SyntaxNode}; + +#[derive(Debug)] +pub struct Position { + repr: PositionRepr, +} + +#[derive(Debug)] +enum PositionRepr { + FirstChild(SyntaxNode), + After(SyntaxElement), +} + +impl Position { + pub fn after(elem: impl Into) -> Position { + let repr = PositionRepr::After(elem.into()); + Position { repr } + } + pub fn before(elem: impl Into) -> Position { + let elem = elem.into(); + let repr = match elem.prev_sibling_or_token() { + Some(it) => PositionRepr::After(it), + None => PositionRepr::FirstChild(elem.parent().unwrap()), + }; + Position { repr } + } + pub fn first_child_of(node: impl Into) -> Position { + let repr = PositionRepr::FirstChild(node.into()); + Position { repr } + } + pub fn last_child_of(node: impl Into) -> Position { + let node = node.into(); + let repr = match node.last_child_or_token() { + Some(it) => PositionRepr::After(it), + None => PositionRepr::FirstChild(node), + }; + Position { repr } + } +} + +pub fn insert(position: Position, elem: impl Into) { + insert_all(position, vec![elem.into()]) +} +pub fn insert_all(position: Position, elements: Vec) { + let (parent, index) = match position.repr { + PositionRepr::FirstChild(parent) => (parent, 0), + PositionRepr::After(child) => (child.parent().unwrap(), child.index() + 1), + }; + parent.splice_children(index..index, elements); +} + +pub fn remove(elem: impl Into) { + let elem = elem.into(); + remove_all(elem.clone()..=elem) +} +pub fn remove_all(range: RangeInclusive) { + replace_all(range, Vec::new()) +} + +pub fn replace(old: impl Into, new: impl Into) { + let old = old.into(); + replace_all(old.clone()..=old, vec![new.into()]) +} +pub fn replace_all(range: RangeInclusive, new: Vec) { + let start = range.start().index(); + let end = range.end().index(); + let parent = range.start().parent().unwrap(); + parent.splice_children(start..end + 1, new) +} + +pub fn append_child(node: impl Into, child: impl Into) { + let position = Position::last_child_of(node); + insert(position, child) +} -- cgit v1.2.3