about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-10-03 13:35:47 +0000
committerGitHub <noreply@github.com>2021-10-03 13:35:47 +0000
commit10eaa5a0afc750ad4c009bc2f3276d3413c32b5f (patch)
treedcf78cf89685e62fe9ecfe5363fa8ee13674b462
parente28aa1928b039720bea15c437b6d9885007ad69e (diff)
parenta31bc668f50b7ad6f67028c80a8805457cc7ebdf (diff)
downloadrust-10eaa5a0afc750ad4c009bc2f3276d3413c32b5f.tar.gz
rust-10eaa5a0afc750ad4c009bc2f3276d3413c32b5f.zip
Merge #10441
10441: feat: Hide type inlay hints for constructors r=Veykril a=Veykril

Fixes https://github.com/rust-analyzer/rust-analyzer/issues/3022
bors r+

Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
-rw-r--r--crates/ide/src/inlay_hints.rs133
-rw-r--r--crates/test_utils/src/minicore.rs11
2 files changed, 136 insertions, 8 deletions
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs
index 401feffff42..3dcb510ab05 100644
--- a/crates/ide/src/inlay_hints.rs
+++ b/crates/ide/src/inlay_hints.rs
@@ -2,6 +2,7 @@ use either::Either;
 use hir::{known, Callable, HasVisibility, HirDisplay, Semantics, TypeInfo};
 use ide_db::RootDatabase;
 use ide_db::{base_db::FileRange, helpers::FamousDefs};
+use itertools::Itertools;
 use stdx::to_lower_snake_case;
 use syntax::{
     ast::{self, AstNode, HasArgList, HasName},
@@ -198,28 +199,95 @@ fn get_bind_pat_hints(
 
     let descended = sema.descend_node_into_attributes(pat.clone()).pop();
     let desc_pat = descended.as_ref().unwrap_or(pat);
-    let krate = sema.scope(desc_pat.syntax()).module().map(|it| it.krate());
-    let famous_defs = FamousDefs(sema, krate);
-
     let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
 
     if should_not_display_type_hint(sema, &pat, &ty) {
         return None;
     }
 
+    let krate = sema.scope(desc_pat.syntax()).module().map(|it| it.krate());
+    let famous_defs = FamousDefs(sema, krate);
+    let label = hint_iterator(sema, &famous_defs, config, &ty);
+
+    let label = match label {
+        Some(label) => label,
+        None => {
+            let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
+            if is_named_constructor(sema, pat, &ty_name).is_some() {
+                return None;
+            }
+            ty_name.into()
+        }
+    };
+
     acc.push(InlayHint {
         range: match pat.name() {
             Some(name) => name.syntax().text_range(),
             None => pat.syntax().text_range(),
         },
         kind: InlayKind::TypeHint,
-        label: hint_iterator(sema, &famous_defs, config, &ty)
-            .unwrap_or_else(|| ty.display_truncated(sema.db, config.max_length).to_string().into()),
+        label,
     });
 
     Some(())
 }
 
+fn is_named_constructor(
+    sema: &Semantics<RootDatabase>,
+    pat: &ast::IdentPat,
+    ty_name: &str,
+) -> Option<()> {
+    let let_node = pat.syntax().parent()?;
+    let expr = match_ast! {
+        match let_node {
+            ast::LetStmt(it) => it.initializer(),
+            ast::Condition(it) => it.expr(),
+            _ => None,
+        }
+    }?;
+
+    let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
+    // unwrap postfix expressions
+    let expr = match expr {
+        ast::Expr::TryExpr(it) => it.expr(),
+        ast::Expr::AwaitExpr(it) => it.expr(),
+        expr => Some(expr),
+    }?;
+    let expr = match expr {
+        ast::Expr::CallExpr(call) => match call.expr()? {
+            ast::Expr::PathExpr(p) => p,
+            _ => return None,
+        },
+        _ => return None,
+    };
+    let path = expr.path()?;
+
+    // Check for tuple-struct or tuple-variant in which case we can check the last segment
+    let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
+    let callable_kind = callable.map(|it| it.kind());
+    if let Some(hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_)) =
+        callable_kind
+    {
+        if let Some(ctor) = path.segment() {
+            return (&ctor.to_string() == ty_name).then(|| ());
+        }
+    }
+
+    // otherwise use the qualifying segment as the constructor name
+    let qual_seg = path.qualifier()?.segment()?;
+    let ctor_name = match qual_seg.kind()? {
+        ast::PathSegmentKind::Name(name_ref) => {
+            match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
+                Some(generics) => format!("{}<{}>", name_ref, generics.format(", ")),
+                None => name_ref.to_string(),
+            }
+        }
+        ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
+        _ => return None,
+    };
+    (&ctor_name == ty_name).then(|| ())
+}
+
 /// Checks if the type is an Iterator from std::iter and replaces its hint with an `impl Iterator<Item = Ty>`.
 fn hint_iterator(
     sema: &Semantics<RootDatabase>,
@@ -470,10 +538,12 @@ mod tests {
         max_length: None,
     };
 
+    #[track_caller]
     fn check(ra_fixture: &str) {
         check_with_config(TEST_CONFIG, ra_fixture);
     }
 
+    #[track_caller]
     fn check_params(ra_fixture: &str) {
         check_with_config(
             InlayHintsConfig {
@@ -486,6 +556,7 @@ mod tests {
         );
     }
 
+    #[track_caller]
     fn check_types(ra_fixture: &str) {
         check_with_config(
             InlayHintsConfig {
@@ -498,6 +569,7 @@ mod tests {
         );
     }
 
+    #[track_caller]
     fn check_chains(ra_fixture: &str) {
         check_with_config(
             InlayHintsConfig {
@@ -510,6 +582,7 @@ mod tests {
         );
     }
 
+    #[track_caller]
     fn check_with_config(config: InlayHintsConfig, ra_fixture: &str) {
         let (analysis, file_id) = fixture::file(&ra_fixture);
         let expected = extract_annotations(&*analysis.file_text(file_id).unwrap());
@@ -519,6 +592,7 @@ mod tests {
         assert_eq!(expected, actual, "\nExpected:\n{:#?}\n\nActual:\n{:#?}", expected, actual);
     }
 
+    #[track_caller]
     fn check_expect(config: InlayHintsConfig, ra_fixture: &str, expect: Expect) {
         let (analysis, file_id) = fixture::file(&ra_fixture);
         let inlay_hints = analysis.inlay_hints(&config, file_id).unwrap();
@@ -1191,11 +1265,12 @@ trait Display {}
 trait Sync {}
 
 fn main() {
-    let _v = Vec::<Box<&(dyn Display + Sync)>>::new();
+    // The block expression wrapping disables the constructor hint hiding logic
+    let _v = { Vec::<Box<&(dyn Display + Sync)>>::new() };
       //^^ Vec<Box<&(dyn Display + Sync)>>
-    let _v = Vec::<Box<*const (dyn Display + Sync)>>::new();
+    let _v = { Vec::<Box<*const (dyn Display + Sync)>>::new() };
       //^^ Vec<Box<*const (dyn Display + Sync)>>
-    let _v = Vec::<Box<dyn Display + Sync>>::new();
+    let _v = { Vec::<Box<dyn Display + Sync>>::new() };
       //^^ Vec<Box<dyn Display + Sync>>
 }
 "#,
@@ -1235,6 +1310,48 @@ fn main() {
     }
 
     #[test]
+    fn skip_constructor_type_hints() {
+        check_types(
+            r#"
+//- minicore: try
+use core::ops::ControlFlow;
+
+struct Struct;
+struct TupleStruct();
+
+impl Struct {
+    fn new() -> Self {
+        Struct
+    }
+    fn try_new() -> ControlFlow<(), Self> {
+        ControlFlow::Continue(Struct)
+    }
+}
+
+struct Generic<T>(T);
+impl Generic<i32> {
+    fn new() -> Self {
+        Generic(0)
+    }
+}
+
+fn main() {
+    let strukt = Struct::new();
+    let tuple_struct = TupleStruct();
+    let generic0 = Generic::new();
+     // ^^^^^^^^ Generic<i32>
+    let generic1 = Generic::<i32>::new();
+    let generic2 = <Generic<i32>>::new();
+}
+
+fn fallible() -> ControlFlow<()> {
+    let strukt = Struct::try_new()?;
+}
+"#,
+        );
+    }
+
+    #[test]
     fn closures() {
         check(
             r#"
diff --git a/crates/test_utils/src/minicore.rs b/crates/test_utils/src/minicore.rs
index 5e17047a407..045b4898e50 100644
--- a/crates/test_utils/src/minicore.rs
+++ b/crates/test_utils/src/minicore.rs
@@ -300,6 +300,17 @@ pub mod ops {
             #[lang = "branch"]
             fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
         }
+
+        impl<B, C> Try for ControlFlow<B, C> {
+            type Output = C;
+            type Residual = ControlFlow<B, convert::Infallible>;
+            fn from_output(output: Self::Output) -> Self {}
+            fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {}
+        }
+
+        impl<B, C> FromResidual for ControlFlow<B, C> {
+            fn from_residual(residual: ControlFlow<B, convert::Infallible>) -> Self {}
+        }
     }
     pub use self::try_::{ControlFlow, FromResidual, Try};
     // endregion:try