Skip to content

Conversation

@dhruvmanila
Copy link
Member

@dhruvmanila dhruvmanila commented Aug 29, 2025

Summary

This PR addresses an issue for a variadic argument when involved in argument type expansion of overload call evaluation.

The issue is that the expansion of the variadic argument could result in argument list of different arity. For example, in *args: tuple[int] | tuple[int, str], the expansion would lead to the variadic argument being unpacked into 1 and 2 element respectively. This means that the parameter matching that was performed initially isn't sufficient and each expanded argument list would need to redo the parameter matching again.

This is currently done by redoing the parameter matching directly, maintaining the state of argument forms (and the conflicting forms), and updating the Bindings values if it changes.

Closes: astral-sh/ty#735

Test Plan

Update existing mdtest.

@dhruvmanila dhruvmanila added the ty Multi-file analysis & type inference label Aug 29, 2025
@dhruvmanila dhruvmanila force-pushed the dhruv/retry-parameter-matching branch from eb554de to 29832ec Compare September 1, 2025 09:44
@github-actions
Copy link
Contributor

github-actions bot commented Sep 1, 2025

Diagnostic diff on typing conformance tests

No changes detected when running ty on typing conformance tests ✅

@github-actions
Copy link
Contributor

github-actions bot commented Sep 1, 2025

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

@dhruvmanila dhruvmanila force-pushed the dhruv/retry-parameter-matching branch 3 times, most recently from 52e6a51 to 648f6fa Compare September 2, 2025 13:47
@dhruvmanila dhruvmanila changed the title [ty] [WIP] Retry parameter matching for argument type expansion [ty] Retry parameter matching for argument type expansion Sep 2, 2025
@dhruvmanila dhruvmanila marked this pull request as ready for review September 2, 2025 16:13
@dhruvmanila
Copy link
Member Author

(This isn't completely ready but I'd appreciate some initial feedback on the approach.)

Comment on lines +209 to +218
// If the argument corresponding to this type is variadic, we need to
// update the tuple length because expanding could change the length.
// For example, in `tuple[int] | tuple[int, int]`, the length of the
// first type is 1, while the length of the second type is 2.
if let Some(expanded_type) = new_expanded_types[index] {
let length = expanded_type
.try_iterate(db)
.map(|tuple| tuple.len())
.unwrap_or(TupleLength::unknown());
new_arguments[index] = Argument::Variadic(length);
Copy link
Member

Choose a reason for hiding this comment

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

👍 Yep this looks like a good approach! It should pull out the more precise length of each union element during expansion

Comment on lines 1912 to 1915
#[derive(Default)]
struct ArgumentForms {
Copy link
Member

Choose a reason for hiding this comment

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

I like the cleanup from introducing this new type, too

Comment on lines 1401 to 1412
// The spec mentions that each expanded argument list should re-evaluate from step
// 2 which is the type checking step but we're re-evaluating from step 1. The tldr
// is that it allows ty to match the correct overload in case a variadic argument
// would expand into different number of arguments with each expansion. Refer to
// https://github.com/astral-sh/ty/issues/735 for more details.
for overload in &mut self.overloads {
// Clear the state of all overloads before re-evaluating from step 1
overload.reset();
overload.match_parameters(db, expanded_arguments, &mut argument_forms);
}
Copy link
Member

Choose a reason for hiding this comment

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

💯

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if we need to reflect this in our comments, but I think the spec is not wrong here -- the difference is that in the spec "step 1" means only "eliminate impossible overloads due to arity mismatch" whereas our step 1 is bigger than that, it also includes "match arguments to parameters", which in the spec is implicitly part of step 2, not part of step 1. This is why we need to "repeat step 1" and the spec does not. It may be clearer to say that we don't separate step 1 and 2 in the same way the spec does.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good point, I didn't realize this subtle difference, I'll update the comment to reflect this.

@dhruvmanila
Copy link
Member Author

openlibrary (https://github.com/internetarchive/openlibrary)
+ openlibrary/coverstore/archive.py:422:13: error[no-matching-overload] No overload of function `__new__` matches arguments
- Found 711 diagnostics
+ Found 712 diagnostics

I'm not exactly sure what's wrong here. I've created a playground snippet which breaks the union into it's elements which is what the type expansion would retry with (https://play.ty.dev/1990e010-195f-4800-9e31-b77894e37dd3). It's only the c variable which has a fixed length of 2 because the outermost type is a tuple containing two elements.

@dhruvmanila dhruvmanila requested a review from dcreager September 4, 2025 15:00
@dhruvmanila dhruvmanila force-pushed the dhruv/retry-parameter-matching branch from 63fe1a7 to 4184ce9 Compare September 8, 2025 06:24
@carljm
Copy link
Contributor

carljm commented Sep 8, 2025

@dcreager I'm assuming this is in your review queue unless you request review from me.

@carljm carljm removed their request for review September 8, 2025 21:10
@sharkdp sharkdp removed their request for review September 10, 2025 07:01
Copy link
Member

@dcreager dcreager left a comment

Choose a reason for hiding this comment

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

This is great, thank you for tackling this!

Comment on lines -2293 to -2313
let argument_types = match argument_types.as_ref() {
Tuple::Fixed(_) => {
Cow::Owned(argument_types.concat(self.db, &Tuple::homogeneous(Type::unknown())))
}
Tuple::Variable(_) => argument_types,
};

Copy link
Member

Choose a reason for hiding this comment

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

❤️ Love that we can get rid of this workaround!

@dhruvmanila dhruvmanila enabled auto-merge (squash) September 12, 2025 08:37
@dhruvmanila dhruvmanila merged commit bb9be26 into main Sep 12, 2025
37 checks passed
@dhruvmanila dhruvmanila deleted the dhruv/retry-parameter-matching branch September 12, 2025 08:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consider retrying arity check after argument type expansion

4 participants