Repository: rust-analyzer/rust-analyzer
Pull request #5693 (2020-08-08 18:59:19)

Fix no inlay hints / unresolved tokens until manual edit to refresh
Fixes https://github.com/rust-analyzer/rust-analyzer/issues/5349 

Now we return ContentModified during the workspace loading. This signifies the language
client to retry the operation (i.e. the client will
continue polling the server while it returns ContentModified).
I believe that there might be cases of overly big projects where the backoff
logic we have setup in `sendRequestWithRetry` (which we use for inlay hints)
might bail too early (currently the largest retry standby time is 10 seconds).
However, I've tried on one of my project with 500+ dependencies and it is still enough.

Here are the examples before/after the change (the gifs are quite lengthy because they show testing rather large cargo workspace).

<details>
<summary>Before</summary>

Here you can see that the client receives empty array of inlay hints and does nothing more.
Same applies to semantic tokens. The client receives unresolved tokens and does nothing more.
The user needs to do a manual edit to refresh the editor.

![prev-demo](https://user-images.githubusercontent.com/36276403/89717721-e4471280-d9c1-11ea-89ce-7dc3e83d9768.gif)

</details>

<details>
<summary>After</summary>

Here the server returns ContentModified, so the client periodically retries the requests and eventually receives the wellformed response.

![new-demo](https://user-images.githubusercontent.com/36276403/89717725-eb6e2080-d9c1-11ea-84c9-796bb2b22cec.gif)

</details>


Patch:
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index 3d12039a6d..28f32a0a4d 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -440,12 +440,6 @@ impl<'a> InferenceContext<'a> {
                 let ty = self.insert_type_vars(ty.subst(&substs));
                 forbid_unresolved_segments((ty, Some(strukt.into())), unresolved)
             }
-            TypeNs::AdtId(AdtId::UnionId(u)) => {
-                let substs = Ty::substs_from_path(&ctx, path, u.into(), true);
-                let ty = self.db.ty(u.into());
-                let ty = self.insert_type_vars(ty.subst(&substs));
-                forbid_unresolved_segments((ty, Some(u.into())), unresolved)
-            }
             TypeNs::EnumVariantId(var) => {
                 let substs = Ty::substs_from_path(&ctx, path, var.into(), true);
                 let ty = self.db.ty(var.parent.into());
@@ -496,7 +490,10 @@ impl<'a> InferenceContext<'a> {
                 // FIXME potentially resolve assoc type
                 (Ty::Unknown, None)
             }
-            TypeNs::AdtId(AdtId::EnumId(_)) | TypeNs::BuiltinType(_) | TypeNs::TraitId(_) => {
+            TypeNs::AdtId(AdtId::EnumId(_))
+            | TypeNs::AdtId(AdtId::UnionId(_))
+            | TypeNs::BuiltinType(_)
+            | TypeNs::TraitId(_) => {
                 // FIXME diagnostic
                 (Ty::Unknown, None)
             }
diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs
index 7638f167b5..1eacc6f95e 100644
--- a/crates/ra_hir_ty/src/lower.rs
+++ b/crates/ra_hir_ty/src/lower.rs
@@ -518,7 +518,6 @@ impl Ty {
         let (segment, generic_def) = match resolved {
             ValueTyDefId::FunctionId(it) => (last, Some(it.into())),
             ValueTyDefId::StructId(it) => (last, Some(it.into())),
-            ValueTyDefId::UnionId(it) => (last, Some(it.into())),
             ValueTyDefId::ConstId(it) => (last, Some(it.into())),
             ValueTyDefId::StaticId(_) => (last, None),
             ValueTyDefId::EnumVariantId(var) => {
@@ -1149,12 +1148,11 @@ impl_from!(BuiltinType, AdtId(StructId, EnumId, UnionId), TypeAliasId for TyDefI
 pub enum ValueTyDefId {
     FunctionId(FunctionId),
     StructId(StructId),
-    UnionId(UnionId),
     EnumVariantId(EnumVariantId),
     ConstId(ConstId),
     StaticId(StaticId),
 }
-impl_from!(FunctionId, StructId, UnionId, EnumVariantId, ConstId, StaticId for ValueTyDefId);
+impl_from!(FunctionId, StructId, EnumVariantId, ConstId, StaticId for ValueTyDefId);
 
 /// Build the declared type of an item. This depends on the namespace; e.g. for
 /// `struct Foo(usize)`, we have two types: The type of the struct itself, and
@@ -1181,7 +1179,6 @@ pub(crate) fn value_ty_query(db: &dyn HirDatabase, def: ValueTyDefId) -> Binders
     match def {
         ValueTyDefId::FunctionId(it) => type_for_fn(db, it),
         ValueTyDefId::StructId(it) => type_for_struct_constructor(db, it),
-        ValueTyDefId::UnionId(it) => type_for_adt(db, it.into()),
         ValueTyDefId::EnumVariantId(it) => type_for_enum_variant_constructor(db, it),
         ValueTyDefId::ConstId(it) => type_for_const(db, it),
         ValueTyDefId::StaticId(it) => type_for_static(db, it),
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index 385e3e64e3..aa48cb412f 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -508,37 +508,6 @@ fn main() { }
         );
     }
 
-    #[test]
-    fn hover_shows_fn_doc() {
-        check(
-            r#"
-/// # Example
-/// ```
-/// # use std::path::Path;
-/// #
-/// foo(Path::new("hello, world!"))
-/// ```
-pub fn foo<|>(_: &Path) {}
-
-fn main() { }
-"#,
-            expect![[r#"
-                *foo*
-                ```rust
-                pub fn foo(_: &Path)
-                ```
-                ___
-
-                # Example
-                ```
-                # use std::path::Path;
-                #
-                foo(Path::new("hello, world!"))
-                ```
-            "#]],
-        );
-    }
-
     #[test]
     fn hover_shows_struct_field_info() {
         // Hovering over the field when instantiating
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index f71b804fe9..89efe71dab 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -4,7 +4,7 @@ mod injection;
 #[cfg(test)]
 mod tests;
 
-use hir::{Name, Semantics, VariantDef};
+use hir::{Name, Semantics};
 use ra_ide_db::{
     defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
     RootDatabase,
@@ -455,18 +455,6 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
     Some(TextRange::new(range_start, range_end))
 }
 
-fn is_possibly_unsafe(name_ref: &ast::NameRef) -> bool {
-    name_ref
-        .syntax()
-        .parent()
-        .and_then(|parent| {
-            ast::FieldExpr::cast(parent.clone())
-                .map(|_| true)
-                .or_else(|| ast::RecordPatField::cast(parent).map(|_| true))
-        })
-        .unwrap_or(false)
-}
-
 fn highlight_element(
     sema: &Semantics<RootDatabase>,
     bindings_shadow_count: &mut FxHashMap<Name, u32>,
@@ -496,19 +484,10 @@ fn highlight_element(
 
             match name_kind {
                 Some(NameClass::Definition(def)) => {
-                    highlight_name(db, def, false) | HighlightModifier::Definition
-                }
-                Some(NameClass::ConstReference(def)) => highlight_name(db, def, false),
-                Some(NameClass::FieldShorthand { field, .. }) => {
-                    let mut h = HighlightTag::Field.into();
-                    if let Definition::Field(field) = field {
-                        if let VariantDef::Union(_) = field.parent_def(db) {
-                            h |= HighlightModifier::Unsafe;
-                        }
-                    }
-
-                    h
+                    highlight_name(db, def) | HighlightModifier::Definition
                 }
+                Some(NameClass::ConstReference(def)) => highlight_name(db, def),
+                Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(),
                 None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
             }
         }
@@ -519,7 +498,6 @@ fn highlight_element(
         }
         NAME_REF => {
             let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
-            let possibly_unsafe = is_possibly_unsafe(&name_ref);
             match classify_name_ref(sema, &name_ref) {
                 Some(name_kind) => match name_kind {
                     NameRefClass::Definition(def) => {
@@ -530,13 +508,11 @@ fn highlight_element(
                                 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
                             }
                         };
-                        highlight_name(db, def, possibly_unsafe)
+                        highlight_name(db, def)
                     }
                     NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
                 },
-                None if syntactic_name_ref_highlighting => {
-                    highlight_name_ref_by_syntax(name_ref, sema)
-                }
+                None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref),
                 None => HighlightTag::UnresolvedReference.into(),
             }
         }
@@ -676,19 +652,10 @@ fn is_child_of_impl(element: &SyntaxElement) -> bool {
     }
 }
 
-fn highlight_name(db: &RootDatabase, def: Definition, possibly_unsafe: bool) -> Highlight {
+fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
     match def {
         Definition::Macro(_) => HighlightTag::Macro,
-        Definition::Field(field) => {
-            let mut h = HighlightTag::Field.into();
-            if possibly_unsafe {
-                if let VariantDef::Union(_) = field.parent_def(db) {
-                    h |= HighlightModifier::Unsafe;
-                }
-            }
-
-            return h;
-        }
+        Definition::Field(_) => HighlightTag::Field,
         Definition::ModuleDef(def) => match def {
             hir::ModuleDef::Module(_) => HighlightTag::Module,
             hir::ModuleDef::Function(func) => {
@@ -758,7 +725,7 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
     tag.into()
 }
 
-fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
+fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
     let default = HighlightTag::UnresolvedReference;
 
     let parent = match name.syntax().parent() {
@@ -768,20 +735,7 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
 
     let tag = match parent.kind() {
         METHOD_CALL_EXPR => HighlightTag::Function,
-        FIELD_EXPR => {
-            let h = HighlightTag::Field;
-            let is_union = ast::FieldExpr::cast(parent)
-                .and_then(|field_expr| {
-                    let field = sema.resolve_field(&field_expr)?;
-                    Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
-                        true
-                    } else {
-                        false
-                    })
-                })
-                .unwrap_or(false);
-            return if is_union { h | HighlightModifier::Unsafe } else { h.into() };
-        }
+        FIELD_EXPR => HighlightTag::Field,
         PATH_SEGMENT => {
             let path = match parent.parent().and_then(ast::Path::cast) {
                 Some(it) => it,
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 895af1dd78..067259e246 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -892,7 +892,7 @@ pub(crate) fn handle_code_lens(
             }
 
             let action = runnable.action();
-            let range = to_proto::range(&line_index, runnable.nav.full_range);
+            let range = to_proto::range(&line_index, runnable.nav.focus_or_full_range());
             let r = to_proto::runnable(&snap, file_id, runnable)?;
             if snap.config.lens.run {
                 let lens = CodeLens {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 438e965e08..eb7c96933c 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -47,7 +47,7 @@ pub fn main_loop(config: Config, connection: Connection) -> Result<()> {
         SetThreadPriority(thread, thread_priority_above_normal);
     }
 
-    GlobalState::new(connection.sender, config).run(connection.receiver)
+    GlobalState::new(connection.sender.clone(), config).run(connection.receiver)
 }
 
 enum Event {
@@ -337,6 +337,16 @@ impl GlobalState {
     fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> {
         self.register_request(&req, request_received);
 
+        if self.status == Status::Loading && req.method != "shutdown" {
+            self.respond(lsp_server::Response::new_err(
+                req.id,
+                // FIXME: i32 should impl From<ErrorCode> (from() guarantees lossless conversion)
+                lsp_server::ErrorCode::ContentModified as i32,
+                "Rust Analyzer is still loading...".to_owned(),
+            ));
+            return Ok(());
+        }
+
         RequestDispatcher { req: Some(req), global_state: self }
             .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.fetch_workspaces()))?
             .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))?
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 27460db78c..5eba1f1555 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -755,8 +755,7 @@ pub(crate) fn runnable(
 }
 
 pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent {
-    let value = crate::markdown::format_docs(markup.as_str());
-    lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value }
+    lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value: markup.into() }
 }
 
 #[cfg(test)]
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index 970fedb378..49d2d1c6fb 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -64,7 +64,8 @@ export async function sendRequestWithRetry<TParam, TRet>(
     param: TParam,
     token?: vscode.CancellationToken,
 ): Promise<TRet> {
-    for (const delay of [2, 4, 6, 8, 10, null]) {
+    // The sequence is `10 * (2 ** (2 * n))` where n is 1, 2, 3...
+    for (const delay of [40, 160, 640, 2560, 10240, null]) {
         try {
             return await (token
                 ? client.sendRequest(reqType, param, token)
@@ -84,8 +85,7 @@ export async function sendRequestWithRetry<TParam, TRet>(
                 log.warn("LSP request failed", { method: reqType.method, param, error });
                 throw error;
             }
-
-            await sleep(10 * (1 << delay));
+            await sleep(delay);
         }
     }
     throw 'unreachable';


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs
index 5a7cf9455b..3fd7d5cd4f 100644
--- a/crates/ra_hir_ty/src/tests/simple.rs
+++ b/crates/ra_hir_ty/src/tests/simple.rs
@@ -334,44 +334,16 @@ fn infer_union() {
             bar: f32,
         }
 
-        fn test() {
-            let u = MyUnion { foo: 0 };
-            unsafe { baz(u); }
-            let u = MyUnion { bar: 0.0 };
-            unsafe { baz(u); }
-        }
-
         unsafe fn baz(u: MyUnion) {
             let inner = u.foo;
-            let inner = u.bar;
         }
         "#,
         expect![[r#"
-            57..172 '{     ...); } }': ()
-            67..68 'u': MyUnion
-            71..89 'MyUnio...o: 0 }': MyUnion
-            86..87 '0': u32
-            95..113 'unsafe...(u); }': ()
-            102..113 '{ baz(u); }': ()
-            104..107 'baz': fn baz(MyUnion)
-            104..110 'baz(u)': ()
-            108..109 'u': MyUnion
-            122..123 'u': MyUnion
-            126..146 'MyUnio... 0.0 }': MyUnion
-            141..144 '0.0': f32
-            152..170 'unsafe...(u); }': ()
-            159..170 '{ baz(u); }': ()
-            161..164 'baz': fn baz(MyUnion)
-            161..167 'baz(u)': ()
-            165..166 'u': MyUnion
-            188..189 'u': MyUnion
-            200..249 '{     ...bar; }': ()
-            210..215 'inner': u32
-            218..219 'u': MyUnion
-            218..223 'u.foo': u32
-            233..238 'inner': f32
-            241..242 'u': MyUnion
-            241..246 'u.bar': f32
+            61..62 'u': MyUnion
+            73..99 '{     ...foo; }': ()
+            83..88 'inner': u32
+            91..92 'u': MyUnion
+            91..96 'u.foo': u32
         "#]],
     );
 }
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index 730efff0de..b9b3580223 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -275,11 +275,6 @@ fn test_unsafe_highlighting() {
         r#"
 unsafe fn unsafe_fn() {}
 
-union Union {
-    a: u32,
-    b: f32,
-}
-
 struct HasUnsafeFn;
 
 impl HasUnsafeFn {
@@ -294,14 +289,8 @@ static mut global_mut: TypeForStaticMut = TypeForStaticMut { a: 0 };
 
 fn main() {
     let x = &5 as *const usize;
-    let u = Union { b: 0 };
     unsafe {
         unsafe_fn();
-        let b = u.b;
-        match u {
-            Union { b: 0 } => (),
-            Union { a } => (),
-        }
         HasUnsafeFn.unsafe_method();
         let y = *(x);
         let z = -x;
diff --git a/crates/ra_ide/test_data/highlight_unsafe.html b/crates/ra_ide/test_data/highlight_unsafe.html
index 79409fe816..87d8c15568 100644
--- a/crates/ra_ide/test_data/highlight_unsafe.html
+++ b/crates/ra_ide/test_data/highlight_unsafe.html
@@ -37,11 +37,6 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 </style>
 <pre><code><span class="keyword unsafe">unsafe</span> <span class="keyword">fn</span> <span class="function declaration unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span><span class="punctuation">}</span>
 
-<span class="keyword">union</span> <span class="union declaration">Union</span> <span class="punctuation">{</span>
-    <span class="field declaration">a</span><span class="punctuation">:</span> <span class="builtin_type">u32</span><span class="punctuation">,</span>
-    <span class="field declaration">b</span><span class="punctuation">:</span> <span class="builtin_type">f32</span><span class="punctuation">,</span>
-<span class="punctuation">}</span>
-
 <span class="keyword">struct</span> <span class="struct declaration">HasUnsafeFn</span><span class="punctuation">;</span>
 
 <span class="keyword">impl</span> <span class="struct">HasUnsafeFn</span> <span class="punctuation">{</span>
@@ -56,14 +51,8 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
 
 <span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
     <span class="keyword">let</span> <span class="variable declaration">x</span> <span class="operator">=</span> <span class="operator">&</span><span class="numeric_literal">5</span> <span class="keyword">as</span> <span class="keyword">*</span><span class="keyword">const</span> <span class="builtin_type">usize</span><span class="punctuation">;</span>
-    <span class="keyword">let</span> <span class="variable declaration">u</span> <span class="operator">=</span> <span class="union">Union</span> <span class="punctuation">{</span> <span class="field">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span><span class="punctuation">;</span>
     <span class="keyword unsafe">unsafe</span> <span class="punctuation">{</span>
         <span class="function unsafe">unsafe_fn</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
-        <span class="keyword">let</span> <span class="variable declaration">b</span> <span class="operator">=</span> <span class="variable">u</span><span class="punctuation">.</span><span class="field unsafe">b</span><span class="punctuation">;</span>
-        <span class="keyword control">match</span> <span class="variable">u</span> <span class="punctuation">{</span>
-            <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">b</span><span class="punctuation">:</span> <span class="numeric_literal">0</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span>
-            <span class="union">Union</span> <span class="punctuation">{</span> <span class="field unsafe">a</span> <span class="punctuation">}</span> <span class="operator">=&gt;</span> <span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">,</span>
-        <span class="punctuation">}</span>
         <span class="struct">HasUnsafeFn</span><span class="punctuation">.</span><span class="function unsafe">unsafe_method</span><span class="punctuation">(</span><span class="punctuation">)</span><span class="punctuation">;</span>
         <span class="keyword">let</span> <span class="variable declaration">y</span> <span class="operator">=</span> <span class="operator unsafe">*</span><span class="punctuation">(</span><span class="variable">x</span><span class="punctuation">)</span><span class="punctuation">;</span>
         <span class="keyword">let</span> <span class="variable declaration">z</span> <span class="operator">=</span> <span class="numeric_literal">-</span><span class="variable">x</span><span class="punctuation">;</span>