]> git.lizzy.rs Git - rust.git/commitdiff
Merge #8221
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>
Sat, 27 Mar 2021 22:43:34 +0000 (22:43 +0000)
committerGitHub <noreply@github.com>
Sat, 27 Mar 2021 22:43:34 +0000 (22:43 +0000)
8221: Prefer adding `mod` declaration to lib.rs over file.rs in UnlinkedFile fix r=Veykril a=Veykril

When there is a `lib.rs` and `main.rs` in one crate, one usually wants the `lib.rs` file to declare the modules.
bors r+

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
21 files changed:
crates/hir_def/src/body/lower.rs
crates/hir_def/src/expr.rs
crates/hir_def/src/item_tree.rs
crates/hir_def/src/item_tree/lower.rs
crates/hir_expand/src/db.rs
crates/hir_ty/src/infer/expr.rs
crates/hir_ty/src/tests/macros.rs
crates/ide_assists/src/handlers/generate_impl.rs
crates/ide_assists/src/utils.rs
crates/ide_db/src/call_info/tests.rs
crates/ide_db/src/helpers/insert_use.rs
crates/ide_db/src/label.rs
crates/ide_db/src/search.rs
crates/ide_db/src/source_change.rs
crates/ide_db/src/traits.rs
crates/ide_db/src/traits/tests.rs
crates/ide_db/src/ty_filter.rs
crates/syntax/src/ast/node_ext.rs
crates/syntax/src/lib.rs
crates/syntax/src/token_text.rs [new file with mode: 0644]
docs/user/manual.adoc

index 19f5065d1381776ae01670b67b85c919f39af8b4..229e81dd43a6fb9c5f93b1c094ee471b9e7e3d74 100644 (file)
@@ -74,6 +74,7 @@ pub(super) fn lower(
             _c: Count::new(),
         },
         expander,
+        statements_in_scope: Vec::new(),
     }
     .collect(params, body)
 }
@@ -83,6 +84,7 @@ struct ExprCollector<'a> {
     expander: Expander,
     body: Body,
     source_map: BodySourceMap,
+    statements_in_scope: Vec<Statement>,
 }
 
 impl ExprCollector<'_> {
@@ -533,15 +535,13 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
                 ids[0]
             }
             ast::Expr::MacroStmts(e) => {
-                // FIXME:  these statements should be held by some hir containter
-                for stmt in e.statements() {
-                    self.collect_stmt(stmt);
-                }
-                if let Some(expr) = e.expr() {
-                    self.collect_expr(expr)
-                } else {
-                    self.alloc_expr(Expr::Missing, syntax_ptr)
-                }
+                e.statements().for_each(|s| self.collect_stmt(s));
+                let tail = e
+                    .expr()
+                    .map(|e| self.collect_expr(e))
+                    .unwrap_or_else(|| self.alloc_expr(Expr::Missing, syntax_ptr.clone()));
+
+                self.alloc_expr(Expr::MacroStmts { tail }, syntax_ptr)
             }
         })
     }
@@ -618,58 +618,54 @@ fn collect_expr_opt(&mut self, expr: Option<ast::Expr>) -> ExprId {
         }
     }
 
-    fn collect_stmt(&mut self, s: ast::Stmt) -> Option<Vec<Statement>> {
-        let stmt = match s {
+    fn collect_stmt(&mut self, s: ast::Stmt) {
+        match s {
             ast::Stmt::LetStmt(stmt) => {
-                self.check_cfg(&stmt)?;
-
+                if self.check_cfg(&stmt).is_none() {
+                    return;
+                }
                 let pat = self.collect_pat_opt(stmt.pat());
                 let type_ref = stmt.ty().map(|it| TypeRef::from_ast(&self.ctx(), it));
                 let initializer = stmt.initializer().map(|e| self.collect_expr(e));
-                vec![Statement::Let { pat, type_ref, initializer }]
+                self.statements_in_scope.push(Statement::Let { pat, type_ref, initializer });
             }
             ast::Stmt::ExprStmt(stmt) => {
-                self.check_cfg(&stmt)?;
+                if self.check_cfg(&stmt).is_none() {
+                    return;
+                }
 
                 // Note that macro could be expended to multiple statements
                 if let Some(ast::Expr::MacroCall(m)) = stmt.expr() {
                     let syntax_ptr = AstPtr::new(&stmt.expr().unwrap());
-                    let mut stmts = vec![];
 
                     self.collect_macro_call(m, syntax_ptr.clone(), false, |this, expansion| {
                         match expansion {
                             Some(expansion) => {
                                 let statements: ast::MacroStmts = expansion;
 
-                                statements.statements().for_each(|stmt| {
-                                    if let Some(mut r) = this.collect_stmt(stmt) {
-                                        stmts.append(&mut r);
-                                    }
-                                });
+                                statements.statements().for_each(|stmt| this.collect_stmt(stmt));
                                 if let Some(expr) = statements.expr() {
-                                    stmts.push(Statement::Expr(this.collect_expr(expr)));
+                                    let expr = this.collect_expr(expr);
+                                    this.statements_in_scope.push(Statement::Expr(expr));
                                 }
                             }
                             None => {
-                                stmts.push(Statement::Expr(
-                                    this.alloc_expr(Expr::Missing, syntax_ptr.clone()),
-                                ));
+                                let expr = this.alloc_expr(Expr::Missing, syntax_ptr.clone());
+                                this.statements_in_scope.push(Statement::Expr(expr));
                             }
                         }
                     });
-                    stmts
                 } else {
-                    vec![Statement::Expr(self.collect_expr_opt(stmt.expr()))]
+                    let expr = self.collect_expr_opt(stmt.expr());
+                    self.statements_in_scope.push(Statement::Expr(expr));
                 }
             }
             ast::Stmt::Item(item) => {
-                self.check_cfg(&item)?;
-
-                return None;
+                if self.check_cfg(&item).is_none() {
+                    return;
+                }
             }
-        };
-
-        Some(stmt)
+        }
     }
 
     fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
@@ -685,10 +681,12 @@ fn collect_block(&mut self, block: ast::BlockExpr) -> ExprId {
         let module = if has_def_map { def_map.root() } else { self.expander.module };
         let prev_def_map = mem::replace(&mut self.expander.def_map, def_map);
         let prev_local_module = mem::replace(&mut self.expander.module, module);
+        let prev_statements = std::mem::take(&mut self.statements_in_scope);
+
+        block.statements().for_each(|s| self.collect_stmt(s));
 
-        let statements =
-            block.statements().filter_map(|s| self.collect_stmt(s)).flatten().collect();
         let tail = block.tail_expr().map(|e| self.collect_expr(e));
+        let statements = std::mem::replace(&mut self.statements_in_scope, prev_statements);
         let syntax_node_ptr = AstPtr::new(&block.into());
         let expr_id = self.alloc_expr(
             Expr::Block { id: block_id, statements, tail, label: None },
index 24be9377395ec21e17bd7521218c5133896bfb31..6c7376fad3abafc5c87e8fae81d946f3eba5c1e7 100644 (file)
@@ -171,6 +171,9 @@ pub enum Expr {
     Unsafe {
         body: ExprId,
     },
+    MacroStmts {
+        tail: ExprId,
+    },
     Array(Array),
     Literal(Literal),
 }
@@ -357,6 +360,7 @@ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
                     f(*repeat)
                 }
             },
+            Expr::MacroStmts { tail } => f(*tail),
             Expr::Literal(_) => {}
         }
     }
index ae2475b4e46abfce0fbf79449c61c3ebf07c9a9e..ca0048b1637f30ff4a44b1d75bfb473faa6febd8 100644 (file)
@@ -110,15 +110,6 @@ pub(crate) fn file_item_tree_query(db: &dyn DefDatabase, file_id: HirFileId) ->
                     // still need to collect inner items.
                     ctx.lower_inner_items(e.syntax())
                 },
-                ast::ExprStmt(stmt) => {
-                    // Macros can expand to stmt. We return an empty item tree in this case, but
-                    // still need to collect inner items.
-                    ctx.lower_inner_items(stmt.syntax())
-                },
-                ast::Item(item) => {
-                    // Macros can expand to stmt and other item, and we add it as top level item
-                    ctx.lower_single_item(item)
-                },
                 _ => {
                     panic!("cannot create item tree from {:?} {}", syntax, syntax);
                 },
index d3fe1ce1eb2f222bda385c9488669dca9caeb933..3f558edd81622e33d2feb6e8ada2b99dc5254740 100644 (file)
@@ -87,14 +87,6 @@ pub(super) fn lower_macro_stmts(mut self, stmts: ast::MacroStmts) -> ItemTree {
         self.tree
     }
 
-    pub(super) fn lower_single_item(mut self, item: ast::Item) -> ItemTree {
-        self.tree.top_level = self
-            .lower_mod_item(&item, false)
-            .map(|item| item.0)
-            .unwrap_or_else(|| Default::default());
-        self.tree
-    }
-
     pub(super) fn lower_inner_items(mut self, within: &SyntaxNode) -> ItemTree {
         self.collect_inner_items(within);
         self.tree
index fc73e435bfbfdcbcf8d00ca927e0b1c057c53f11..d672f67238a2a0bb985988957145721227909cba 100644 (file)
@@ -5,7 +5,13 @@
 use base_db::{salsa, SourceDatabase};
 use mbe::{ExpandError, ExpandResult, MacroRules};
 use parser::FragmentKind;
-use syntax::{algo::diff, ast::NameOwner, AstNode, GreenNode, Parse, SyntaxKind::*, SyntaxNode};
+use syntax::{
+    algo::diff,
+    ast::{MacroStmts, NameOwner},
+    AstNode, GreenNode, Parse,
+    SyntaxKind::*,
+    SyntaxNode,
+};
 
 use crate::{
     ast_id_map::AstIdMap, hygiene::HygieneFrame, BuiltinDeriveExpander, BuiltinFnLikeExpander,
@@ -340,13 +346,19 @@ fn parse_macro_with_arg(
         None => return ExpandResult { value: None, err: result.err },
     };
 
-    log::debug!("expanded = {}", tt.as_debug_string());
-
     let fragment_kind = to_fragment_kind(db, macro_call_id);
 
+    log::debug!("expanded = {}", tt.as_debug_string());
+    log::debug!("kind = {:?}", fragment_kind);
+
     let (parse, rev_token_map) = match mbe::token_tree_to_syntax_node(&tt, fragment_kind) {
         Ok(it) => it,
         Err(err) => {
+            log::debug!(
+                "failed to parse expanstion to {:?} = {}",
+                fragment_kind,
+                tt.as_debug_string()
+            );
             return ExpandResult::only_err(err);
         }
     };
@@ -362,15 +374,34 @@ fn parse_macro_with_arg(
                     return ExpandResult::only_err(err);
                 }
             };
-
-            if !diff(&node, &call_node.value).is_empty() {
-                ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: Some(err) }
-            } else {
+            if is_self_replicating(&node, &call_node.value) {
                 return ExpandResult::only_err(err);
+            } else {
+                ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: Some(err) }
+            }
+        }
+        None => {
+            log::debug!("parse = {:?}", parse.syntax_node().kind());
+            ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: None }
+        }
+    }
+}
+
+fn is_self_replicating(from: &SyntaxNode, to: &SyntaxNode) -> bool {
+    if diff(from, to).is_empty() {
+        return true;
+    }
+    if let Some(stmts) = MacroStmts::cast(from.clone()) {
+        if stmts.statements().any(|stmt| diff(stmt.syntax(), to).is_empty()) {
+            return true;
+        }
+        if let Some(expr) = stmts.expr() {
+            if diff(expr.syntax(), to).is_empty() {
+                return true;
             }
         }
-        None => ExpandResult { value: Some((parse, Arc::new(rev_token_map))), err: None },
     }
+    false
 }
 
 fn hygiene_frame(db: &dyn AstDatabase, file_id: HirFileId) -> Arc<HygieneFrame> {
@@ -390,21 +421,15 @@ fn to_fragment_kind(db: &dyn AstDatabase, id: MacroCallId) -> FragmentKind {
 
     let parent = match syn.parent() {
         Some(it) => it,
-        None => {
-            // FIXME:
-            // If it is root, which means the parent HirFile
-            // MacroKindFile must be non-items
-            // return expr now.
-            return FragmentKind::Expr;
-        }
+        None => return FragmentKind::Statements,
     };
 
     match parent.kind() {
         MACRO_ITEMS | SOURCE_FILE => FragmentKind::Items,
-        MACRO_STMTS => FragmentKind::Statement,
+        MACRO_STMTS => FragmentKind::Statements,
         ITEM_LIST => FragmentKind::Items,
         LET_STMT => {
-            // FIXME: Handle Pattern
+            // FIXME: Handle LHS Pattern
             FragmentKind::Expr
         }
         EXPR_STMT => FragmentKind::Statements,
index 3f3187ea275d4fd3c2c90bbb89563bfbcc95d976..e6ede05ca144289878dab272188da978f635a74a 100644 (file)
@@ -767,6 +767,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
                     None => self.table.new_float_var(),
                 },
             },
+            Expr::MacroStmts { tail } => self.infer_expr(*tail, expected),
         };
         // use a new type variable if we got unknown here
         let ty = self.insert_type_vars_shallow(ty);
index 7eda518663aedbad797237d0b200c5c7785dce2b..01935ec99431542826b1edd8f7e308cff53a89d7 100644 (file)
@@ -226,11 +226,48 @@ fn foo() {
         "#,
         expect![[r#"
             !0..8 'leta=();': ()
+            !0..8 'leta=();': ()
+            !3..4 'a': ()
+            !5..7 '()': ()
             57..84 '{     ...); } }': ()
         "#]],
     );
 }
 
+#[test]
+fn recurisve_macro_expanded_in_stmts() {
+    check_infer(
+        r#"
+        macro_rules! ng {
+            ([$($tts:tt)*]) => {
+                $($tts)*;
+            };
+            ([$($tts:tt)*] $head:tt $($rest:tt)*) => {
+                ng! {
+                    [$($tts)* $head] $($rest)*
+                }
+            };
+        }
+        fn foo() {
+            ng!([] let a = 3);
+            let b = a;
+        }
+        "#,
+        expect![[r#"
+            !0..7 'leta=3;': {unknown}
+            !0..7 'leta=3;': {unknown}
+            !0..13 'ng!{[leta=3]}': {unknown}
+            !0..13 'ng!{[leta=]3}': {unknown}
+            !0..13 'ng!{[leta]=3}': {unknown}
+            !3..4 'a': i32
+            !5..6 '3': i32
+            196..237 '{     ...= a; }': ()
+            229..230 'b': i32
+            233..234 'a': i32
+        "#]],
+    );
+}
+
 #[test]
 fn recursive_inner_item_macro_rules() {
     check_infer(
@@ -246,7 +283,8 @@ fn foo() {
         "#,
         expect![[r#"
             !0..1 '1': i32
-            !0..7 'mac!($)': {unknown}
+            !0..26 'macro_...>{1};}': {unknown}
+            !0..26 'macro_...>{1};}': {unknown}
             107..143 '{     ...!(); }': ()
             129..130 'a': i32
         "#]],
index a8e3c4fc24fd3f42ac14d5ba6af75f88c4be133d..fd2e250bc390f3e3d44191b24f9b0e895229132f 100644 (file)
@@ -69,6 +69,17 @@ fn test_add_impl() {
             "struct Foo<'a, T: Foo<'a>> {$0}",
             "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n    $0\n}",
         );
+        check_assist(
+            generate_impl,
+            r#"
+            struct MyOwnArray<T, const S: usize> {}$0"#,
+            r#"
+            struct MyOwnArray<T, const S: usize> {}
+
+            impl<T, const S: usize> MyOwnArray<T, S> {
+                $0
+            }"#,
+        );
         check_assist(
             generate_impl,
             r#"
@@ -114,11 +125,11 @@ impl<T> Defaulted<T> {
         check_assist(
             generate_impl,
             r#"
-            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}$0"#,
+            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}$0"#,
             r#"
-            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}
+            struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}
 
-            impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b> Defaulted<'a, 'b, T> {
+            impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> {
                 $0
             }"#,
         );
index 9de9e4dbd3d753bad5f992298d1753312476a447..5f630ec75a1bcfcaef6a3b9c4bf40e032a8d2279 100644 (file)
@@ -434,7 +434,8 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str
             }
             buf
         });
-        let generics = lifetimes.chain(type_params).format(", ");
+        let const_params = generic_params.const_params().map(|t| t.syntax().to_string());
+        let generics = lifetimes.chain(type_params).chain(const_params).format(", ");
         format_to!(buf, "<{}>", generics);
     }
     buf.push(' ');
@@ -452,7 +453,11 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str
             .type_params()
             .filter_map(|it| it.name())
             .map(|it| SmolStr::from(it.text()));
-        format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
+        let const_params = generic_params
+            .const_params()
+            .filter_map(|it| it.name())
+            .map(|it| SmolStr::from(it.text()));
+        format_to!(buf, "<{}>", lifetime_params.chain(type_params).chain(const_params).format(", "))
     }
 
     match adt.where_clause() {
index 75ab3eb6ea7936f55df7191bcb69bbb0ab6cff75..281a081a38777d99bc3eeedeaad5c9df882c3862 100644 (file)
@@ -1,8 +1,9 @@
-use crate::RootDatabase;
 use base_db::{fixture::ChangeFixture, FilePosition};
 use expect_test::{expect, Expect};
 use test_utils::RangeOrOffset;
 
+use crate::RootDatabase;
+
 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
     let change_fixture = ChangeFixture::parse(ra_fixture);
index e681ced801e383262285652f5803d44bbbd8c51b..be3a22725ab1d6d0bce229f189e2bb695fafebfc 100644 (file)
@@ -1,7 +1,6 @@
 //! Handle syntactic aspects of inserting a new `use`.
 use std::{cmp::Ordering, iter::successors};
 
-use crate::RootDatabase;
 use hir::Semantics;
 use itertools::{EitherOrBoth, Itertools};
 use syntax::{
@@ -14,6 +13,8 @@
     AstToken, InsertPosition, NodeOrToken, SyntaxElement, SyntaxNode, SyntaxToken,
 };
 
+use crate::RootDatabase;
+
 pub use hir::PrefixKind;
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
index c0e89e72f5827c07e8d22e0112780b7e01296288..1f1e715c97f34db7b67ab5a743ff4c19e59eaef9 100644 (file)
@@ -1,4 +1,4 @@
-//! See `Label`
+//! See [`Label`]
 use std::fmt;
 
 /// A type to specify UI label, like an entry in the list of assists. Enforces
index 3634b2b2677fa2c6cdc594e3f577c31b4fbc25b3..b55e3851ebbb3845a2853d4294bd094a6f8346ea 100644 (file)
@@ -12,9 +12,8 @@
 use rustc_hash::FxHashMap;
 use syntax::{ast, match_ast, AstNode, TextRange, TextSize};
 
-use crate::defs::NameClass;
 use crate::{
-    defs::{Definition, NameRefClass},
+    defs::{Definition, NameClass, NameRefClass},
     RootDatabase,
 };
 
index b36455d494480282fe59fc1b0c518bf263a9b1bd..846530f784558d8250b1d3fabc9f8241e2f613f8 100644 (file)
@@ -37,6 +37,8 @@ pub fn from_text_edit(file_id: FileId, edit: TextEdit) -> Self {
         }
     }
 
+    /// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing
+    /// edits for a file if some already exist.
     pub fn insert_source_edit(&mut self, file_id: FileId, edit: TextEdit) {
         match self.source_file_edits.entry(file_id) {
             Entry::Occupied(mut entry) => {
index 78a43f587ab0e5cf4730212b09f86f019d13f053..66ae81c73eacfd6c6f2f61b9f0e78e8f51508344 100644 (file)
@@ -61,7 +61,7 @@ pub fn get_missing_assoc_items(
     resolve_target_trait(sema, impl_def).map_or(vec![], |target_trait| {
         target_trait
             .items(sema.db)
-            .iter()
+            .into_iter()
             .filter(|i| match i {
                 hir::AssocItem::Function(f) => {
                     !impl_fns_consts.contains(&f.name(sema.db).to_string())
@@ -72,7 +72,6 @@ pub fn get_missing_assoc_items(
                     .map(|n| !impl_fns_consts.contains(&n.to_string()))
                     .unwrap_or_default(),
             })
-            .cloned()
             .collect()
     })
 }
index 84bb255059e6baf4e5df87dd9c7cb205eaa06cfa..2a5482024dc0015ac204092395be617f652d50bc 100644 (file)
@@ -1,10 +1,11 @@
-use crate::RootDatabase;
 use base_db::{fixture::ChangeFixture, FilePosition};
 use expect_test::{expect, Expect};
 use hir::Semantics;
 use syntax::ast::{self, AstNode};
 use test_utils::RangeOrOffset;
 
+use crate::RootDatabase;
+
 /// Creates analysis from a multi-file fixture, returns positions marked with $0.
 pub(crate) fn position(ra_fixture: &str) -> (RootDatabase, FilePosition) {
     let change_fixture = ChangeFixture::parse(ra_fixture);
index f8406851bd5405e9945ccf02d2010d3e9dbb7118..988ecd0607d4b0e0af710b451a06d868da5d1997 100644 (file)
@@ -2,11 +2,13 @@
 //! Use case for structures in this module is, for example, situation when you need to process
 //! only certain `Enum`s.
 
-use crate::RootDatabase;
-use hir::{Adt, Semantics, Type};
 use std::iter;
+
+use hir::{Adt, Semantics, Type};
 use syntax::ast::{self, make};
 
+use crate::RootDatabase;
+
 /// Enum types that implement `std::ops::Try` trait.
 #[derive(Clone, Copy)]
 pub enum TryEnum {
index 6d7db5fb26ca8cd7fe033fa662b28780ee90ac2f..2772d7364442d033a91510fdd5e6cc7c74a2dc13 100644 (file)
@@ -8,23 +8,23 @@
 
 use crate::{
     ast::{self, support, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode},
-    SmolStr, SyntaxElement, SyntaxToken, T,
+    SmolStr, SyntaxElement, SyntaxToken, TokenText, T,
 };
 
 impl ast::Lifetime {
-    pub fn text(&self) -> SmolStr {
+    pub fn text(&self) -> TokenText {
         text_of_first_token(self.syntax())
     }
 }
 
 impl ast::Name {
-    pub fn text(&self) -> SmolStr {
+    pub fn text(&self) -> TokenText {
         text_of_first_token(self.syntax())
     }
 }
 
 impl ast::NameRef {
-    pub fn text(&self) -> SmolStr {
+    pub fn text(&self) -> TokenText {
         text_of_first_token(self.syntax())
     }
 
@@ -33,8 +33,11 @@ pub fn as_tuple_field(&self) -> Option<usize> {
     }
 }
 
-fn text_of_first_token(node: &SyntaxNode) -> SmolStr {
-    node.green().children().next().and_then(|it| it.into_token()).unwrap().text().into()
+fn text_of_first_token(node: &SyntaxNode) -> TokenText {
+    let first_token =
+        node.green().children().next().and_then(|it| it.into_token()).unwrap().to_owned();
+
+    TokenText(first_token)
 }
 
 pub enum Macro {
@@ -376,7 +379,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 }
 
 impl NameOrNameRef {
-    pub fn text(&self) -> SmolStr {
+    pub fn text(&self) -> TokenText {
         match self {
             NameOrNameRef::Name(name) => name.text(),
             NameOrNameRef::NameRef(name_ref) => name_ref.text(),
index 2a5c61171b3a57c8071035ee1f6e7a1eebb80564..90de6bef6f6f5559bcba5432342759ebffc8a3db 100644 (file)
@@ -29,6 +29,7 @@ macro_rules! eprintln {
 mod parsing;
 mod validation;
 mod ptr;
+mod token_text;
 #[cfg(test)]
 mod tests;
 
@@ -55,6 +56,7 @@ macro_rules! eprintln {
         SyntaxElement, SyntaxElementChildren, SyntaxNode, SyntaxNodeChildren, SyntaxToken,
         SyntaxTreeBuilder,
     },
+    token_text::TokenText,
 };
 pub use parser::{SyntaxKind, T};
 pub use rowan::{
diff --git a/crates/syntax/src/token_text.rs b/crates/syntax/src/token_text.rs
new file mode 100644 (file)
index 0000000..d2ed0a1
--- /dev/null
@@ -0,0 +1,77 @@
+//! Yet another version of owned string, backed by a syntax tree token.
+
+use std::{cmp::Ordering, fmt, ops};
+
+pub struct TokenText(pub(crate) rowan::GreenToken);
+
+impl TokenText {
+    pub fn as_str(&self) -> &str {
+        self.0.text()
+    }
+}
+
+impl ops::Deref for TokenText {
+    type Target = str;
+
+    fn deref(&self) -> &str {
+        self.as_str()
+    }
+}
+impl AsRef<str> for TokenText {
+    fn as_ref(&self) -> &str {
+        self.as_str()
+    }
+}
+
+impl From<TokenText> for String {
+    fn from(token_text: TokenText) -> Self {
+        token_text.as_str().into()
+    }
+}
+
+impl PartialEq<&'_ str> for TokenText {
+    fn eq(&self, other: &&str) -> bool {
+        self.as_str() == *other
+    }
+}
+impl PartialEq<TokenText> for &'_ str {
+    fn eq(&self, other: &TokenText) -> bool {
+        other == self
+    }
+}
+impl PartialEq<String> for TokenText {
+    fn eq(&self, other: &String) -> bool {
+        self.as_str() == other.as_str()
+    }
+}
+impl PartialEq<TokenText> for String {
+    fn eq(&self, other: &TokenText) -> bool {
+        other == self
+    }
+}
+impl PartialEq for TokenText {
+    fn eq(&self, other: &TokenText) -> bool {
+        self.as_str() == other.as_str()
+    }
+}
+impl Eq for TokenText {}
+impl Ord for TokenText {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.as_str().cmp(other.as_str())
+    }
+}
+impl PartialOrd for TokenText {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+impl fmt::Display for TokenText {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Display::fmt(self.as_str(), f)
+    }
+}
+impl fmt::Debug for TokenText {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        fmt::Debug::fmt(self.as_str(), f)
+    }
+}
index 36a86e78f0294e27413162290cccda43f841be21..b1beeb8838503535959395b46e63b61e25585ab6 100644 (file)
@@ -178,6 +178,15 @@ $ cargo xtask install --server
 If your editor can't find the binary even though the binary is on your `$PATH`, the likely explanation is that it doesn't see the same `$PATH` as the shell, see https://github.com/rust-analyzer/rust-analyzer/issues/1811[this issue].
 On Unix, running the editor from a shell or changing the `.desktop` file to set the environment should help.
 
+==== `rustup`
+
+`rust-analyzer` is available in `rustup`, but only in the nightly toolchain:
+
+[source,bash]
+---
+$ rustup +nightly component add rust-analyzer-preview
+---
+
 ==== Arch Linux
 
 The `rust-analyzer` binary can be installed from the repos or AUR (Arch User Repository):