Repository: rust-lang/rust-analyzer
Pull request #17640 (2024-07-19 18:22:24)

Parse contextual dyn keyword properly in edition 2015
Turns out this is more important than I thought it would be given the metrics :)


Patch:
diff --git a/crates/hir-expand/src/builtin_fn_macro.rs b/crates/hir-expand/src/builtin_fn_macro.rs
index 7f25b7dc03..6272e1df7d 100644
--- a/crates/hir-expand/src/builtin_fn_macro.rs
+++ b/crates/hir-expand/src/builtin_fn_macro.rs
@@ -18,7 +18,7 @@ use crate::{
     name, quote,
     quote::dollar_crate,
     tt::{self, DelimSpan},
-    ExpandError, ExpandResult, HirFileIdExt, MacroCallId, MacroFileIdExt,
+    ExpandError, ExpandResult, HirFileIdExt, Lookup as _, MacroCallId,
 };
 
 macro_rules! register_builtin {
@@ -687,8 +687,8 @@ fn relative_file(
     path_str: &str,
     allow_recursion: bool,
 ) -> Result<EditionedFileId, ExpandError> {
-    let call_site =
-        call_id.as_macro_file().parent(db).original_file_respecting_includes(db).file_id();
+    let lookup = call_id.lookup(db);
+    let call_site = lookup.kind.file_id().original_file_respecting_includes(db).file_id();
     let path = AnchoredPath { anchor: call_site, path: path_str };
     let res = db
         .resolve_path(path)
@@ -697,7 +697,7 @@ fn relative_file(
     if res == call_site && !allow_recursion {
         Err(ExpandError::other(format!("recursive inclusion of `{path_str}`")))
     } else {
-        Ok(EditionedFileId::new(res, Edition::CURRENT_FIXME))
+        Ok(EditionedFileId::new(res, db.crate_graph()[lookup.krate].edition))
     }
 }
 
diff --git a/crates/mbe/src/to_parser_input.rs b/crates/mbe/src/to_parser_input.rs
index 66db525362..c35b28527a 100644
--- a/crates/mbe/src/to_parser_input.rs
+++ b/crates/mbe/src/to_parser_input.rs
@@ -65,9 +65,8 @@ pub(crate) fn to_parser_input<S: Copy + fmt::Debug>(
                         i if i.starts_with('\'') => res.push(LIFETIME_IDENT),
                         _ if ident.is_raw.yes() => res.push(IDENT),
                         "gen" if !edition.at_least_2024() => res.push(IDENT),
-                        "async" | "await" | "dyn" | "try" if !edition.at_least_2018() => {
-                            res.push(IDENT)
-                        }
+                        "dyn" if !edition.at_least_2018() => res.push_ident(DYN_KW),
+                        "async" | "await" | "try" if !edition.at_least_2018() => res.push(IDENT),
                         text => match SyntaxKind::from_keyword(text) {
                             Some(kind) => res.push(kind),
                             None => {
diff --git a/crates/parser/src/grammar/paths.rs b/crates/parser/src/grammar/paths.rs
index 01b8f9e918..86e19fbe5f 100644
--- a/crates/parser/src/grammar/paths.rs
+++ b/crates/parser/src/grammar/paths.rs
@@ -2,6 +2,8 @@ use super::*;
 
 pub(super) const PATH_FIRST: TokenSet =
     TokenSet::new(&[IDENT, T![self], T![super], T![crate], T![Self], T![:], T![<]]);
+pub(super) const WEAK_DYN_PATH_FIRST: TokenSet =
+    TokenSet::new(&[IDENT, T![self], T![super], T![crate], T![Self]]);
 
 pub(super) fn is_path_start(p: &Parser<'_>) -> bool {
     is_use_path_start(p) || p.at(T![<]) || p.at(T![Self])
diff --git a/crates/parser/src/grammar/types.rs b/crates/parser/src/grammar/types.rs
index f95425824a..9a1c6dfdf7 100644
--- a/crates/parser/src/grammar/types.rs
+++ b/crates/parser/src/grammar/types.rs
@@ -1,3 +1,5 @@
+use crate::grammar::paths::WEAK_DYN_PATH_FIRST;
+
 use super::*;
 
 pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(TokenSet::new(&[
@@ -49,6 +51,13 @@ fn type_with_bounds_cond(p: &mut Parser<'_>, allow_bounds: bool) {
         T![dyn] => dyn_trait_type(p),
         // Some path types are not allowed to have bounds (no plus)
         T![<] => path_type_bounds(p, allow_bounds),
+        T![ident]
+            if !p.edition().at_least_2018()
+                && p.at_contextual_kw(T![dyn])
+                && WEAK_DYN_PATH_FIRST.contains(p.nth(1)) =>
+        {
+            dyn_trait_type_weak(p)
+        }
         _ if paths::is_path_start(p) => path_or_macro_type_(p, allow_bounds),
         LIFETIME_IDENT if p.nth_at(1, T![+]) => bare_dyn_trait_type(p),
         _ => {
@@ -279,6 +288,18 @@ fn dyn_trait_type(p: &mut Parser<'_>) {
     m.complete(p, DYN_TRAIT_TYPE);
 }
 
+// test dyn_trait_type_weak 2015
+// type A = dyn Iterator<Item=Foo<'a>> + 'a;
+// type A = &dyn Iterator<Item=Foo<'a>> + 'a;
+// type A = dyn::Path;
+fn dyn_trait_type_weak(p: &mut Parser<'_>) {
+    assert!(p.at_contextual_kw(T![dyn]));
+    let m = p.start();
+    p.bump_remap(T![dyn]);
+    generic_params::bounds_without_colon(p);
+    m.complete(p, DYN_TRAIT_TYPE);
+}
+
 // test bare_dyn_types_with_leading_lifetime
 // type A = 'static + Trait;
 // type B = S<'static + Trait>;
diff --git a/crates/parser/src/parser.rs b/crates/parser/src/parser.rs
index 5b901f911d..7d3eb5de25 100644
--- a/crates/parser/src/parser.rs
+++ b/crates/parser/src/parser.rs
@@ -27,14 +27,14 @@ pub(crate) struct Parser<'t> {
     pos: usize,
     events: Vec<Event>,
     steps: Cell<u32>,
-    _edition: Edition,
+    edition: Edition,
 }
 
 static PARSER_STEP_LIMIT: Limit = Limit::new(15_000_000);
 
 impl<'t> Parser<'t> {
     pub(super) fn new(inp: &'t Input, edition: Edition) -> Parser<'t> {
-        Parser { inp, pos: 0, events: Vec::new(), steps: Cell::new(0), _edition: edition }
+        Parser { inp, pos: 0, events: Vec::new(), steps: Cell::new(0), edition }
     }
 
     pub(crate) fn finish(self) -> Vec<Event> {
@@ -277,6 +277,10 @@ impl<'t> Parser<'t> {
     fn push_event(&mut self, event: Event) {
         self.events.push(event);
     }
+
+    pub(crate) fn edition(&self) -> Edition {
+        self.edition
+    }
 }
 
 /// See [`Parser::start`].
diff --git a/crates/parser/src/shortcuts.rs b/crates/parser/src/shortcuts.rs
index 7f49cc087a..1cf81e79b0 100644
--- a/crates/parser/src/shortcuts.rs
+++ b/crates/parser/src/shortcuts.rs
@@ -12,7 +12,7 @@
 use std::mem;
 
 use crate::{
-    LexedStr, Step,
+    Edition, LexedStr, Step,
     SyntaxKind::{self, *},
 };
 
@@ -25,7 +25,7 @@ pub enum StrStep<'a> {
 }
 
 impl LexedStr<'_> {
-    pub fn to_input(&self) -> crate::Input {
+    pub fn to_input(&self, edition: Edition) -> crate::Input {
         let _p = tracing::info_span!("LexedStr::to_input").entered();
         let mut res = crate::Input::default();
         let mut was_joint = false;
@@ -35,8 +35,11 @@ impl LexedStr<'_> {
                 was_joint = false
             } else if kind == SyntaxKind::IDENT {
                 let token_text = self.text(i);
-                let contextual_kw =
-                    SyntaxKind::from_contextual_keyword(token_text).unwrap_or(SyntaxKind::IDENT);
+                let contextual_kw = if !edition.at_least_2018() && token_text == "dyn" {
+                    SyntaxKind::DYN_KW
+                } else {
+                    SyntaxKind::from_contextual_keyword(token_text).unwrap_or(SyntaxKind::IDENT)
+                };
                 res.push_ident(contextual_kw);
             } else {
                 if was_joint {
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 489bb42eec..4ddeb4ab1b 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -5,8 +5,8 @@ use project_model::{CargoConfig, RustLibSource};
 use rustc_hash::FxHashSet;
 
 use hir::{db::HirDatabase, Crate, HirFileIdExt, Module};
-use ide::{AnalysisHost, AssistResolveStrategy, DiagnosticsConfig, Severity};
-use ide_db::base_db::SourceDatabaseExt;
+use ide::{AnalysisHost, AssistResolveStrategy, Diagnostic, DiagnosticsConfig, Severity};
+use ide_db::{base_db::SourceDatabaseExt, LineIndexDatabase};
 use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
 
 use crate::cli::flags;
@@ -74,7 +74,11 @@ impl flags::Diagnostics {
                         found_error = true;
                     }
 
-                    println!("{diagnostic:?}");
+                    let Diagnostic { code, message, range, severity, .. } = diagnostic;
+                    let line_index = db.line_index(range.file_id);
+                    let start = line_index.line_col(range.range.start());
+                    let end = line_index.line_col(range.range.end());
+                    println!("{severity:?} {code:?} from {start:?} to {end:?}: {message}");
                 }
 
                 visited_files.insert(file_id);
diff --git a/crates/syntax/src/parsing.rs b/crates/syntax/src/parsing.rs
index e52daa42f1..2c7828c052 100644
--- a/crates/syntax/src/parsing.rs
+++ b/crates/syntax/src/parsing.rs
@@ -12,7 +12,7 @@ pub(crate) use crate::parsing::reparsing::incremental_reparse;
 pub(crate) fn parse_text(text: &str, edition: parser::Edition) -> (GreenNode, Vec<SyntaxError>) {
     let _p = tracing::info_span!("parse_text").entered();
     let lexed = parser::LexedStr::new(edition, text);
-    let parser_input = lexed.to_input();
+    let parser_input = lexed.to_input(edition);
     let parser_output = parser::TopEntryPoint::SourceFile.parse(&parser_input, edition);
     let (node, errors, _eof) = build_tree(lexed, parser_output);
     (node, errors)
@@ -25,7 +25,7 @@ pub(crate) fn parse_text_at(
 ) -> (GreenNode, Vec<SyntaxError>) {
     let _p = tracing::info_span!("parse_text_at").entered();
     let lexed = parser::LexedStr::new(edition, text);
-    let parser_input = lexed.to_input();
+    let parser_input = lexed.to_input(edition);
     let parser_output = entry.parse(&parser_input, edition);
     let (node, errors, _eof) = build_tree(lexed, parser_output);
     (node, errors)
diff --git a/crates/syntax/src/parsing/reparsing.rs b/crates/syntax/src/parsing/reparsing.rs
index a895d9e274..a5cc4e90df 100644
--- a/crates/syntax/src/parsing/reparsing.rs
+++ b/crates/syntax/src/parsing/reparsing.rs
@@ -92,7 +92,7 @@ fn reparse_block(
     let text = get_text_after_edit(node.clone().into(), edit);
 
     let lexed = parser::LexedStr::new(edition, text.as_str());
-    let parser_input = lexed.to_input();
+    let parser_input = lexed.to_input(edition);
     if !is_balanced(&lexed) {
         return None;
     }
diff --git a/xtask/src/metrics.rs b/xtask/src/metrics.rs
index 9a7785dd43..21001c28da 100644
--- a/xtask/src/metrics.rs
+++ b/xtask/src/metrics.rs
@@ -6,7 +6,7 @@ use std::{
     time::{Instant, SystemTime, UNIX_EPOCH},
 };
 
-use anyhow::{bail, format_err};
+use anyhow::format_err;
 use xshell::{cmd, Shell};
 
 use crate::flags::{self, MeasurementType};
@@ -193,7 +193,7 @@ impl Metrics {
 impl Host {
     fn new(sh: &Shell) -> anyhow::Result<Host> {
         if cfg!(not(target_os = "linux")) {
-            bail!("can only collect metrics on Linux ");
+            return Ok(Host { os: "unknown".into(), cpu: "unknown".into(), mem: "unknown".into() });
         }
 
         let os = read_field(sh, "/etc/os-release", "PRETTY_NAME=")?.trim_matches('"').to_owned();


Test patch:
diff --git a/crates/parser/src/tests.rs b/crates/parser/src/tests.rs
index d35d2d3b03..e7bccb6685 100644
--- a/crates/parser/src/tests.rs
+++ b/crates/parser/src/tests.rs
@@ -70,7 +70,7 @@ fn parse_err() {
 
 fn parse(entry: TopEntryPoint, text: &str, edition: Edition) -> (String, bool) {
     let lexed = LexedStr::new(edition, text);
-    let input = lexed.to_input();
+    let input = lexed.to_input(edition);
     let output = entry.parse(&input, edition);
 
     let mut buf = String::new();
diff --git a/crates/parser/src/tests/prefix_entries.rs b/crates/parser/src/tests/prefix_entries.rs
index 4d4ab345d9..e2268eed60 100644
--- a/crates/parser/src/tests/prefix_entries.rs
+++ b/crates/parser/src/tests/prefix_entries.rs
@@ -83,7 +83,7 @@ fn meta_item() {
 #[track_caller]
 fn check(entry: PrefixEntryPoint, input: &str, prefix: &str) {
     let lexed = LexedStr::new(Edition::CURRENT, input);
-    let input = lexed.to_input();
+    let input = lexed.to_input(Edition::CURRENT);
 
     let mut n_tokens = 0;
     for step in entry.parse(&input, Edition::CURRENT).iter() {
diff --git a/crates/parser/test_data/generated/runner.rs b/crates/parser/test_data/generated/runner.rs
index d0e6b3f6c9..a935fe4ee3 100644
--- a/crates/parser/test_data/generated/runner.rs
+++ b/crates/parser/test_data/generated/runner.rs
@@ -195,6 +195,13 @@ mod ok {
         run_and_expect_no_errors("test_data/parser/inline/ok/dyn_trait_type.rs");
     }
     #[test]
+    fn dyn_trait_type_weak() {
+        run_and_expect_no_errors_with_edition(
+            "test_data/parser/inline/ok/dyn_trait_type_weak.rs",
+            crate::Edition::Edition2015,
+        );
+    }
+    #[test]
     fn effect_blocks() { run_and_expect_no_errors("test_data/parser/inline/ok/effect_blocks.rs"); }
     #[test]
     fn exclusive_range_pat() {
diff --git a/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast b/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast
new file mode 100644
index 0000000000..00ca92402c
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rast
@@ -0,0 +1,113 @@
+SOURCE_FILE
+  TYPE_ALIAS
+    COMMENT "// 2015"
+    WHITESPACE "\n"
+    TYPE_KW "type"
+    WHITESPACE " "
+    NAME
+      IDENT "A"
+    WHITESPACE " "
+    EQ "="
+    WHITESPACE " "
+    DYN_TRAIT_TYPE
+      DYN_KW "dyn"
+      WHITESPACE " "
+      TYPE_BOUND_LIST
+        TYPE_BOUND
+          PATH_TYPE
+            PATH
+              PATH_SEGMENT
+                NAME_REF
+                  IDENT "Iterator"
+                GENERIC_ARG_LIST
+                  L_ANGLE "<"
+                  ASSOC_TYPE_ARG
+                    NAME_REF
+                      IDENT "Item"
+                    EQ "="
+                    PATH_TYPE
+                      PATH
+                        PATH_SEGMENT
+                          NAME_REF
+                            IDENT "Foo"
+                          GENERIC_ARG_LIST
+                            L_ANGLE "<"
+                            LIFETIME_ARG
+                              LIFETIME
+                                LIFETIME_IDENT "'a"
+                            R_ANGLE ">"
+                  R_ANGLE ">"
+        WHITESPACE " "
+        PLUS "+"
+        WHITESPACE " "
+        TYPE_BOUND
+          LIFETIME
+            LIFETIME_IDENT "'a"
+    SEMICOLON ";"
+  WHITESPACE "\n"
+  TYPE_ALIAS
+    TYPE_KW "type"
+    WHITESPACE " "
+    NAME
+      IDENT "A"
+    WHITESPACE " "
+    EQ "="
+    WHITESPACE " "
+    REF_TYPE
+      AMP "&"
+      DYN_TRAIT_TYPE
+        DYN_KW "dyn"
+        WHITESPACE " "
+        TYPE_BOUND_LIST
+          TYPE_BOUND
+            PATH_TYPE
+              PATH
+                PATH_SEGMENT
+                  NAME_REF
+                    IDENT "Iterator"
+                  GENERIC_ARG_LIST
+                    L_ANGLE "<"
+                    ASSOC_TYPE_ARG
+                      NAME_REF
+                        IDENT "Item"
+                      EQ "="
+                      PATH_TYPE
+                        PATH
+                          PATH_SEGMENT
+                            NAME_REF
+                              IDENT "Foo"
+                            GENERIC_ARG_LIST
+                              L_ANGLE "<"
+                              LIFETIME_ARG
+                                LIFETIME
+                                  LIFETIME_IDENT "'a"
+                              R_ANGLE ">"
+                    R_ANGLE ">"
+          WHITESPACE " "
+          PLUS "+"
+          WHITESPACE " "
+          TYPE_BOUND
+            LIFETIME
+              LIFETIME_IDENT "'a"
+    SEMICOLON ";"
+  WHITESPACE "\n"
+  TYPE_ALIAS
+    TYPE_KW "type"
+    WHITESPACE " "
+    NAME
+      IDENT "A"
+    WHITESPACE " "
+    EQ "="
+    WHITESPACE " "
+    PATH_TYPE
+      PATH
+        PATH
+          PATH_SEGMENT
+            NAME_REF
+              IDENT "dyn"
+        COLON2 "::"
+        PATH_SEGMENT
+          NAME_REF
+            IDENT "Path"
+    SEMICOLON ";"
+  WHITESPACE "\n"
diff --git a/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rs b/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rs
new file mode 100644
index 0000000000..c4941e65bf
--- /dev/null
+++ b/crates/parser/test_data/parser/inline/ok/dyn_trait_type_weak.rs
@@ -0,0 +1,4 @@
+// 2015
+type A = dyn Iterator<Item=Foo<'a>> + 'a;
+type A = &dyn Iterator<Item=Foo<'a>> + 'a;
+type A = dyn::Path;