Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/hir/src/lib.rs7
-rw-r--r--crates/hir/src/semantics.rs22
-rw-r--r--crates/ide/src/goto_definition.rs69
3 files changed, 96 insertions, 2 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 43cdf80e0d..11d79e2d7b 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -6158,6 +6158,13 @@ impl<'db> Type<'db> {
Some(adt.into())
}
+ /// Holes in the args can come from lifetime/const params.
+ pub fn as_adt_with_args(&self) -> Option<(Adt, Vec<Option<Type<'db>>>)> {
+ let (adt, args) = self.ty.as_adt()?;
+ let args = args.iter().map(|arg| Some(self.derived(arg.ty()?))).collect();
+ Some((adt.into(), args))
+ }
+
pub fn as_builtin(&self) -> Option<BuiltinType> {
self.ty.as_builtin().map(|inner| BuiltinType { inner })
}
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 4bc757da44..1cf3b98160 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -1819,6 +1819,28 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(try_expr.syntax())?.resolve_try_expr(self.db, try_expr)
}
+ /// The type that the associated `try` block, closure or function expects.
+ pub fn try_expr_returned_type(&self, try_expr: &ast::TryExpr) -> Option<Type<'db>> {
+ self.ancestors_with_macros(try_expr.syntax().clone()).find_map(|parent| {
+ if let Some(try_block) = ast::BlockExpr::cast(parent.clone())
+ && try_block.try_block_modifier().is_some()
+ {
+ Some(self.type_of_expr(&try_block.into())?.original)
+ } else if let Some(closure) = ast::ClosureExpr::cast(parent.clone()) {
+ Some(
+ self.type_of_expr(&closure.into())?
+ .original
+ .as_callable(self.db)?
+ .return_type(),
+ )
+ } else if let Some(function) = ast::Fn::cast(parent) {
+ Some(self.to_def(&function)?.ret_type(self.db))
+ } else {
+ None
+ }
+ })
+ }
+
// This does not resolve the method call to the correct trait impl!
// We should probably fix that.
pub fn resolve_method_call_as_callable(
diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs
index 3c3ac9d3bb..3890bcad7f 100644
--- a/crates/ide/src/goto_definition.rs
+++ b/crates/ide/src/goto_definition.rs
@@ -95,6 +95,13 @@ pub(crate) fn goto_definition(
continue;
}
+ let parent = token.value.parent()?;
+
+ if let Some(question_mark_conversion) = goto_question_mark_conversions(sema, &parent) {
+ navs.extend(def_to_nav(sema, question_mark_conversion.into()));
+ continue;
+ }
+
if let Some(token) = ast::String::cast(token.value.clone())
&& let Some(original_token) = ast::String::cast(original_token.clone())
&& let Some((analysis, fixture_analysis)) =
@@ -113,8 +120,6 @@ pub(crate) fn goto_definition(
});
}
- let parent = token.value.parent()?;
-
let token_file_id = token.file_id;
if let Some(token) = ast::String::cast(token.value.clone())
&& let Some(x) =
@@ -149,6 +154,45 @@ pub(crate) fn goto_definition(
Some(RangeInfo::new(original_token.text_range(), navs))
}
+/// When the `?` operator is used on `Result`, go to the `From` impl if it exists as this provides more value.
+fn goto_question_mark_conversions(
+ sema: &Semantics<'_, RootDatabase>,
+ node: &SyntaxNode,
+) -> Option<hir::Function> {
+ let node = ast::TryExpr::cast(node.clone())?;
+ let try_expr_ty = sema.type_of_expr(&node.expr()?)?.adjusted();
+
+ let fd = FamousDefs(sema, try_expr_ty.krate(sema.db));
+ let result_enum = fd.core_result_Result()?.into();
+
+ let (try_expr_ty_adt, try_expr_ty_args) = try_expr_ty.as_adt_with_args()?;
+ if try_expr_ty_adt != result_enum {
+ // FIXME: Support `Poll<Result>`.
+ return None;
+ }
+ let original_err_ty = try_expr_ty_args.get(1)?.clone()?;
+
+ let returned_ty = sema.try_expr_returned_type(&node)?;
+ let (returned_adt, returned_ty_args) = returned_ty.as_adt_with_args()?;
+ if returned_adt != result_enum {
+ return None;
+ }
+ let returned_err_ty = returned_ty_args.get(1)?.clone()?;
+
+ if returned_err_ty.could_unify_with_deeply(sema.db, &original_err_ty) {
+ return None;
+ }
+
+ let from_trait = fd.core_convert_From()?;
+ let from_fn = from_trait.function(sema.db, sym::from)?;
+ sema.resolve_trait_impl_method(
+ returned_err_ty.clone(),
+ from_trait,
+ from_fn,
+ [returned_err_ty, original_err_ty],
+ )
+}
+
// If the token is into(), try_into(), search the definition of From, TryFrom.
fn find_definition_for_known_blanket_dual_impls(
sema: &Semantics<'_, RootDatabase>,
@@ -4034,4 +4078,25 @@ where
"#,
)
}
+
+ #[test]
+ fn question_mark_on_result_goes_to_conversion() {
+ check(
+ r#"
+//- minicore: try, result, from
+
+struct Foo;
+struct Bar;
+impl From<Foo> for Bar {
+ fn from(_: Foo) -> Bar { Bar }
+ // ^^^^
+}
+
+fn foo() -> Result<(), Bar> {
+ Err(Foo)?$0;
+ Ok(())
+}
+ "#,
+ );
+ }
}