Skip to content

Comments

Add TypeForm (PEP 747) to the spec and conformance suite#2183

Open
JelleZijlstra wants to merge 7 commits intopython:mainfrom
JelleZijlstra:typeform
Open

Add TypeForm (PEP 747) to the spec and conformance suite#2183
JelleZijlstra wants to merge 7 commits intopython:mainfrom
JelleZijlstra:typeform

Conversation

@JelleZijlstra
Copy link
Member

No description provided.

@srittau srittau added topic: typing spec For improving the typing spec topic: conformance tests Issues with the conformance test suite labels Feb 20, 2026
Copy link
Member

@carljm carljm left a comment

Choose a reason for hiding this comment

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

Looks good to me - thank you!

Copy link
Contributor

@davidhalter davidhalter left a comment

Choose a reason for hiding this comment

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

Thanks for the quick addition, I think some cases where clarifications would be great (and that are also not really mentioned in the PEP):

def f(x: TypeForm[int | str]): ...

# Are these all fine or not? These are not normal statements and it's quite unusual to
# infer arguments in this way whereas things like `x: TypeAlias = int` are more typical.
# I personally don't care if this is inferred or not, just that it is part of the conformance tests.
f(int)
f(int | str)
f("int")

# There are obviously more complicated cases for type inference like this one:
def g(x: list[TypeForm[int]]): ...
g([int])
g(["int"])
g([TypeForm("int")])  # Obviously fine

This would obviously affect all type inference for all type checkers in quite a big way. Again, I'm just not sure what the PEP wants here, but you can probably clarify. Here I would probably also prefer that at least some of the above cases are added to the conformance tests, because otherwise we would have different behavior for inference in a feature that doesn't really need that. Type inference is already a big mess across type checkers.

v1_type_form: TypeForm[str | None] = str | None # OK

v2_actual: types.GenericAlias = list[int] # OK
v2_type_form: TypeForm = list[int] # OK
Copy link
Contributor

Choose a reason for hiding this comment

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

I would like this case to be clarified with an additional

assert_type(v2_type_form, TypeForm[Any])

since the PEP mentions that this case is not inferred, but Any (which seems reasonable). But I would like this to be consistent across type checkers.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure about that; some type checkers may infer a more precise type here and I don't think we need to disallow that. The PEP also doesn't say anything explicit about this case, where do you see that?

Copy link
Contributor

@davidhalter davidhalter Feb 24, 2026

Choose a reason for hiding this comment

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

@JelleZijlstra There is this sentence:

The type expression TypeForm, with no type argument provided, is equivalent to TypeForm[Any].

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 doesn't mean that v2_type_form should be inferred as TypeForm[Any], though.

For comparison, ty passes this test:

x: object = 1
assert_type(x, Literal[1])

Copy link
Member

Choose a reason for hiding this comment

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

Although we plan to change this in ty, so that annotated assignments act like an upcast. Which will mean that

x: object = 1
assert_type(x, object)

but still

x: object
x = 1
assert_type(x, Literal[1])

But I think if the goal here is just to clarify that TypeForm in a type expression means TypeForm[Any], it would be better to sidestep these issues altogether and write the test in this form:

def _(x: TypeForm):
    assert_type(x, TypeForm[Any])

@davidhalter
Copy link
Contributor

Another thing that I just remember is the case of

def takes_type_form(x: TypeForm[int | str]):
a = int | str
b = "int | str"
takes_type_form(a)
takes_type_form(b)

Here I would also like some guidance. Are type checkers expected to store more information than UnionType when inferring int | str.

@erictraut
Copy link
Collaborator

@davidhalter asked:

Are type checkers expected to store more information than UnionType when inferring int | str.

The typing spec says very little about type inference behaviors in general. Inference behaviors differ across type checkers. Your example therefore isn't covered by the spec (or the PEP).

If you change your example to the following, then it is covered by the PEP, and these are valid (no type error should be emitted).

x: TypeForm[int | str]
x = int | str
x = "int | str"

@JelleZijlstra
Copy link
Member Author

Thanks for the quick addition, I think some cases where clarifications would be great (and that are also not really mentioned in the PEP):

def f(x: TypeForm[int | str]): ...

# Are these all fine or not? These are not normal statements and it's quite unusual to
# infer arguments in this way whereas things like `x: TypeAlias = int` are more typical.
# I personally don't care if this is inferred or not, just that it is part of the conformance tests.
f(int)
f(int | str)
f("int")

I think it's important for these to be accepted, so TypeForm can be used to type functions like cast().

@davidhalter
Copy link
Contributor

The typing spec says very little about type inference behaviors in general. Inference behaviors differ across type checkers.

But the reason for this is that Pyright/Mypy inference predates the spec and therefore a lot of inference related things could not be part of the spec. However here we are talking about new behavior and while we don't have to specify everything, I would still prefer to add as much as possible to the spec.

@JelleZijlstra
Copy link
Member Author

But the reason for this is that Pyright/Mypy inference predates the spec and therefore a lot of inference related things could not be part of the spec. However here we are talking about new behavior and while we don't have to specify everything, I would still prefer to add as much as possible to the spec.

This PR is focused on implementing PEP 747, so I'd rather not add new rules that go beyond the accepted PEP. More precise inference rules are better kept for a separate change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: conformance tests Issues with the conformance test suite topic: typing spec For improving the typing spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants