Skip to content

Comments

[ty] Resolve overloads for hovers#21417

Merged
Gankra merged 3 commits intomainfrom
gankra/overloads
Nov 20, 2025
Merged

[ty] Resolve overloads for hovers#21417
Gankra merged 3 commits intomainfrom
gankra/overloads

Conversation

@Gankra
Copy link
Contributor

@Gankra Gankra commented Nov 13, 2025

This is a very conservative minimal implementation of applying overloads to resolve a callable-type-being-called down to a single function signature on hover. If we ever encounter a situation where the answer doesn't simplify down to a single function call, we bail out to preserve prettier printing of non-raw-Signatures.

The resulting Signatures are still a bit bare, I'm going to try to improve that in a followup to improve our Signature printing in general.

Fixes astral-sh/ty#73

@Gankra Gankra added server Related to the LSP server ty Multi-file analysis & type inference labels Nov 13, 2025
@Gankra
Copy link
Contributor Author

Gankra commented Nov 13, 2025

Most of these snapshot changes are bad and the consequence of this logic firing too eagerly. I need to cleanup that part, but I wanted to get the Types and Semantics part up sooner than later for correction.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 13, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 13, 2025

mypy_primer results

No ecosystem changes detected ✅

No memory usage changes detected ✅

@Gankra Gankra marked this pull request as ready for review November 13, 2025 20:33
Comment on lines +1018 to +1023
binding.matching_overloads().map(|(_, overload)| {
overload
.signature
.display_with(db, DisplaySettings::default().multiline())
.to_string()
})
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 spent an unconscionable amount of time trying to have this return a Type or something more useful than a String but the types on Binding are haunted by the overloads when you try to render them (meaning printing them still returns the full overload set!).

Stringifying the raw Signature is the only thing that seems to be safe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(An early version returned a full CallSignatureDetails but this usecase has no use of that so I dropped it as a waste of effort. Also this version that just returns a String is easier to iterate on (I kept trying to Not have a String and had to refactor like 4 calls and 2 types over and over as I experimented between Type vs Vec<Type> vs CallSignatureDetails vs...))

Comment on lines -96 to 100

pub struct DisplayHover<'a> {
db: &'a dyn Db,
hover: &'a Hover<'a>,
pub struct DisplayHover<'db, 'a> {
db: &'db dyn Db,
hover: &'a Hover<'db>,
kind: MarkupKind,
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is debris from versions where this was being asked to store CallSignatureDetails, which is actually invariant over 'db. Leaving it like this just simplifies any potential future work.

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

I'll do a full review on Monday but I've a question why we limit overload matching to hover only

// TODO: Render the symbol's signature instead of just its type.
let mut contents = Vec::new();
if let Some(ty) = ty {
if let Some(signature) = goto_target.call_type_simplified_by_overloads(&model) {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason we only apply overload simplification for hover mode? For example, TypeScript jumps to the matching overload if you use "go to definition," which matches my expectation. https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABFApgZygCgIYC5FggC2ARigE4CU+hpFA3AFCiSwLLpZ4HFnkA0iEvgzkYYAObVEo8RKYto8JKgw4avCoJIB+EVDGTp2MAE9EAb0aMAvtcaqsARgAMggESP3le0A

I'm surprised that Pylance doesn't seem to do that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm strictly speaking I don't think goto-definition should jump to an overload unless we can't find the actual implementation (in which case it's actually goto-declaration). goto-declaration should ideally jump to the overload though.

let func_type = call_expr.func.inferred_type(model);

// Use into_callable to handle all the complex type conversions
let callable_type = func_type.try_upcast_to_callable(db)?;
Copy link
Member

Choose a reason for hiding this comment

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

Is my understanding correct that this adds another "todo" call-site to astral-sh/ty#1086?

Copy link
Contributor

@carljm carljm Nov 17, 2025

Choose a reason for hiding this comment

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

I'm not sure if we'll be able to do astral-sh/ty#1086; commented on that issue.

Comment on lines +1017 to +1024
.flat_map(|binding| {
binding.matching_overloads().map(|(_, overload)| {
overload
.signature
.display_with(db, DisplaySettings::default().multiline())
.to_string()
})
})
Copy link
Member

@dhruvmanila dhruvmanila Nov 17, 2025

Choose a reason for hiding this comment

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

I think I remember now facing a similar problem regarding using the signature_type on Binding struct that we saw during our call last week. For context, the signature_type would represent the FunctionLiteral which means the display would contain all the overloads and this is the main reason we need to use the display of Signature to display the matching overload which leads to requiring #21438.

I faced this issue while implementing #18452 and the way I resolved it is by capturing the required information (FunctionType, BoundMethodType, etc. and the matching overload index) in MatchingOverloadLiteral and implementing a .get method to return the matching OverloadLiteral.

I think we could possibly use a similar approach to get the actual OverloadLiteral that needs to be displayed as it does implement the Display trait. You might find it useful to go through this part of the code:

// TODO: This should probably be adapted to handle more
// types of callables[1]. At present, it just handles
// standard function and method calls.
//
// [1]: https://github.com/astral-sh/ty/issues/274#issuecomment-2881856028
let function_type_and_kind = match self.signature_type {
Type::FunctionLiteral(function) => Some((FunctionKind::Function, function)),
Type::BoundMethod(bound_method) => Some((
FunctionKind::BoundMethod,
bound_method.function(context.db()),
)),
Type::KnownBoundMethod(KnownBoundMethodType::FunctionTypeDunderGet(
function,
)) => Some((FunctionKind::MethodWrapper, function)),
_ => None,
};
// If there is a single matching overload, the diagnostics should be reported
// directly for that overload.
if let Some(matching_overload_index) = self.matching_overload_index {
let callable_description =
CallableDescription::new(context.db(), self.signature_type);
let matching_overload =
function_type_and_kind.map(|(kind, function)| MatchingOverloadLiteral {
index: matching_overload_index,
kind,
function,
});
self.overloads[matching_overload_index].report_diagnostics(
context,
node,
self.signature_type,
callable_description.as_ref(),
union_diag,
matching_overload.as_ref(),
);
return;
}

Another approach could be to expand the DisplaySettings to include the matching_overload_index: MatchingOverloadIndex which can then be used directly to filter the overloads at the display level. The None variant would display all the overloads.

@MichaReiser
Copy link
Member

@Gankra what's the status of this PR?

@Gankra
Copy link
Contributor Author

Gankra commented Nov 20, 2025

I think this PR could be landed as-is as an incremental strict improvement. I've been locally experimenting with alternative approaches that would e.g. empower goto-declaration as well, but I keep running into issues around the fact that randomly places in the code (like type printing) don't want to talk about A Specific Overload (connected to the fact that definitions-hanging-off-types is a fundamental hack).

@MichaReiser
Copy link
Member

I'm fine with handling go-to separately. But we might want to address @dhruvmanila's feedback before landing?

@Gankra
Copy link
Contributor Author

Gankra commented Nov 20, 2025

Yeah dhruv's suggestions are what I was experimenting with locally. Hover doesn't really benefit from it (it really just needs a String, and I'm computing that String fine), but goto-decl would benefit.

Copy link
Member

@MichaReiser MichaReiser left a comment

Choose a reason for hiding this comment

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

Alright. I'm fine with this, given that you're already working on the follow ups. If you don't end up implementing all of them, would you mind opening follow up issues in that case.

@Gankra
Copy link
Contributor Author

Gankra commented Nov 20, 2025

Or well, I suppose dhruv's suggestions would ideally obsolete the more hacky stuff in #21438

But yeah it's all kinda the same work.

@Gankra Gankra merged commit 6e84f4f into main Nov 20, 2025
41 checks passed
@Gankra Gankra deleted the gankra/overloads branch November 20, 2025 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

server Related to the LSP server ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update hover implementation for overloads to account for the matched signature

4 participants