Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add the ability to jump from into to from definitions #18934

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

1hakusai1
Copy link
Contributor

@1hakusai1 1hakusai1 commented Jan 14, 2025

close #18316
close #15315
(maybe related to #4558)

This PR add the ability to jump from into to from definitions.

  • from into to from
  • from try_into to try_from
  • from parse to from_str

Test

I can't write unit tests for TryFrom and FromStr because minicore doesn't contain them.

use std::str::FromStr;

struct SourceType;

struct TargetType;

impl Into<i64> for SourceType {
    // E
    fn into(self) -> i64 {
        todo!()
    }
}

impl TryInto<String> for SourceType {
    type Error = String;

    // F
    fn try_into(self) -> Result<String, Self::Error> {
        todo!()
    }
}

impl From<SourceType> for TargetType {
    // A
    fn from(value: SourceType) -> Self {
        todo!()
    }
}

impl FromStr for TargetType {
    type Err = String;

    // D
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        todo!()
    }
}

impl From<i64> for TargetType {
    // B
    fn from(value: i64) -> Self {
        todo!()
    }
}

impl TryFrom<String> for TargetType {
    type Error = String;

    // C
    fn try_from(value: String) -> Result<Self, Self::Error> {
        todo!()
    }
}

fn main() {
    // into to from
    let source = SourceType;
    let target: TargetType = source.into(); // goto A
    let target: TargetType = 12.into(); // goto B

    // try_into to try_from
    let maybe_target: Result<TargetType, _> = String::from("aaa").try_into(); // goto C

    // parse to from_str
    let maybe_target: Result<TargetType, String> = "aaaaa".parse(); // goto D

    // regular cases
    let source = SourceType;
    let integer: i64 = source.into(); // goto E
    let source = SourceType;
    let string: Result<String, _> = source.try_into(); // goto F
}

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 14, 2025
@1hakusai1 1hakusai1 changed the title Add the ability to jump from into to from definitions feat: Add the ability to jump from into to from definitions Jan 14, 2025
// FIXME: This condition does not work for complicated cases such as
// receiver_type: Vec<i64>
// arg.ty(): T: IntoIterator<Item = i64>
args.first().is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty()))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this works in many cases but does not cover all cases.

If someone knows how to check type compatibilities, please comment.
Maybe this feature is required to cover all cases.

@ChayimFriedman2
Copy link
Contributor

I can't write unit tests for TryFrom and FromStr because minicore doesn't contain them.

Feel free to add them to minicore (behind flags).

// - return_type is B (type of b)
// We will find the definition of B::from(a: A).
let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?;
let receiver_type = sema.type_of_expr(&method_call.receiver()?)?.original();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we actually need the adjusted type for the receiver.

@@ -125,6 +130,62 @@ pub(crate) fn goto_definition(
Some(RangeInfo::new(original_token.text_range(), navs))
}

// If the token is into(), try_into(), parse(), search the definition of From, TryFrom, FromStr.
fn find_from_definition(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name is rather confusing / non-descriptive so I'd rather have a longer one like (especially given this handles more than just From impls

Suggested change
fn find_from_definition(
fn find_definition_for_known_blanket_dual_impls(

fn find_from_definition(
file_id: FileId,
original_token: &SyntaxToken,
sema: &Semantics<'_, RootDatabase>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, semantics is usually the first argument to a function

Comment on lines 146 to 165
let method_call = ast::MethodCallExpr::cast(original_token.parent()?.parent()?)?;
let receiver_type = sema.type_of_expr(&method_call.receiver()?)?.adjusted();
let return_type = sema.type_of_expr(&method_call.clone().into())?.original();

let (search_method, search_trait, return_type) = match method_call.name_ref()?.text().as_str() {
"into" => ("from", FamousDefs(sema, krate).core_convert_From()?, return_type),
// If the method is try_into() or parse(), return_type is Result<T, Error>.
// Get T from type arguments of Result<T, Error>.
"try_into" => (
"try_from",
FamousDefs(sema, krate).core_convert_TryFrom()?,
return_type.type_arguments().next()?,
),
"parse" => (
"from_str",
FamousDefs(sema, krate).core_str_FromStr()?,
return_type.type_arguments().next()?,
),
_ => return None,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use Semantics::resolve_method_call_as_callable here, that gives us a bunch of the things here already like receiver param/type, function id and return type.

Comment on lines +167 to +183
let from_impls = Impl::all_for_type(db, return_type)
.into_iter()
.filter(|impl_| impl_.trait_(db).is_some_and(|trait_| trait_ == search_trait));
let from_methods = from_impls.flat_map(|impl_| impl_.items(db)).filter_map(|item| match item {
AssocItem::Function(function) if function.name(db).as_str() == search_method => {
Some(function)
}
_ => None,
});
let target_method = from_methods.into_iter().find(|method| {
let args = method.assoc_fn_params(db);

// FIXME: This condition does not work for complicated cases such as
// receiver_type: Vec<i64>
// arg.ty(): T: IntoIterator<Item = i64>
args.first().is_some_and(|arg| receiver_type.could_coerce_to(db, arg.ty()))
})?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be going through the (currently unexposed) SourceAnalyzer::resolve_impl_method_or_trait_def function

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RFC: goto-definition between From::from and Into::into Go to definition (but skip generic intermediaries)
4 participants