Skip to content

Conversation

@dcreager
Copy link
Member

This PR is changes how reveal_type determines what type to reveal, in a way that should be a no-op to most callers.

Previously, we would reveal the type of the first parameter, after all of the call binding machinery had done its work. This includes inferring the specialization of a generic function, and then applying that specialization to all parameter and argument types, which is relevant since the typeshed definition of reveal_type is generic:

def reveal_type(obj: _T, /) -> _T: ...

Normally this does not matter, since we infer _T = [arg type] and apply that to the parameter type, yielding [arg type]. But applying that specialization also simplifies the argument type, which makes reveal_type less useful as a debugging aid when we want to see the actual, raw, unsimplified argument type.

With this patch, we now grab the original unmodified argument type and reveal that instead.

In addition to making the debugging aid example work, this also makes our reveal_type implementation more robust to custom typeshed definitions, such as

def reveal_type(obj: Any) -> Any: ...

(That custom definition is probably not what anyone would want, since you wouldn't be able to depend on the return type being equivalent to the argument type, but still)

@dcreager dcreager added internal An internal refactor or improvement ty Multi-file analysis & type inference labels Jul 17, 2025
Comment on lines +1053 to +1056
let revealed_type = overload
.arguments_for_parameter(call_arguments, 0)
.fold(UnionBuilder::new(db), |builder, (_, ty)| builder.add(ty))
.build();
Copy link
Member Author

Choose a reason for hiding this comment

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

This is the meat of the change; the rest is just refactoring to enable this + cleanup.

We need the union builder because technically multiple arguments could be matched to the first parameter, though that would require a custom typeshed definition of reveal_type that uses a variadic parameter:

def reveal_type(*args) -> Any: ...

@github-actions
Copy link
Contributor

mypy_primer results

No ecosystem changes detected ✅
No memory usage changes detected ✅

Copy link
Contributor

@sharkdp sharkdp left a comment

Choose a reason for hiding this comment

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

Thank you very much!

I was thinking about how to write a test for this. One could probably create an unsimplified int | Literal[1] union type via UnionType::new(db, […]) (which we would ideally make private if we could, but for this test, it would be helpfully available).

But then how do we feed that type into reveal_type? We could expose some unsafe functionality via ty_extensions maybe, but that seems like a very bad idea.

All that is to say: I'm fine with there not being a test. It will help everyone who's working on the {Union/Intersection}Builder in the future.

@dcreager
Copy link
Member Author

I can try writing a Rust test instead of an mdtest

@sharkdp
Copy link
Contributor

sharkdp commented Jul 17, 2025

I can try writing a Rust test instead of an mdtest

I was thinking about Rust tests as well. But I assume it's not trivial to invoke the whole call inference machinery without having an AST. And if you construct an AST by parsing some example, then it's unclear how to inject that "wrong" type? Maybe you figure something out, it's certainly not impossible. But I'm not sure if it's worth spending a lot of time on. That's what I wanted to say originally.

@dcreager dcreager merged commit 4aee039 into main Jul 17, 2025
39 checks passed
@dcreager dcreager deleted the dcreager/reveal-argument branch July 17, 2025 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal An internal refactor or improvement ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants