Merge pull request #21752 from ChayimFriedman2/question-mark-conversion

feat: When going to def on `?` on `Result` that goes through `From`, go to the `From` impl
This commit is contained in:
Lukas Wirth 2026-03-05 08:57:32 +00:00 committed by GitHub
commit e8ee51ab9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 96 additions and 2 deletions

View File

@ -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 })
}

View File

@ -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(

View File

@ -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(())
}
"#,
);
}
}