Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion conformance/tests/annotations_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,4 @@ async def generator30() -> AsyncIterator[int]:
yield


assert_type(generator30, Callable[[], AsyncIterator[int]])
assert_type(generator30(), AsyncIterator[int])
Comment thread
AlexWaygood marked this conversation as resolved.
Outdated
13 changes: 11 additions & 2 deletions conformance/tests/generics_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ class OneDefault(Generic[T, DefaultBoolT]): ...

class AllTheDefaults(Generic[T1, T2, DefaultStrT, DefaultIntT, DefaultBoolT]): ...

def needs_allthedefaults_or_subclass(x: type[AllTheDefaults[Any, Any, str, int, bool]]) -> None: ...

# do not use `assert_type` here: some type checkers infer a more precise type
# than `type[]` for class objects
needs_allthedefaults_or_subclass(AllTheDefaults) # OK
Comment thread
AlexWaygood marked this conversation as resolved.
Outdated

assert_type(AllTheDefaults, type[AllTheDefaults[Any, Any, str, int, bool]])
Comment thread
AlexWaygood marked this conversation as resolved.
Outdated
assert_type(
AllTheDefaults[int, complex], type[AllTheDefaults[int, complex, str, int, bool]]
)
Expand Down Expand Up @@ -91,7 +95,12 @@ class Class_ParamSpec(Generic[DefaultP]): ...
class Class_TypeVarTuple(Generic[*DefaultTs]): ...


assert_type(Class_TypeVarTuple, type[Class_TypeVarTuple[*tuple[str, int]]])
def needs_classtypevartuple_or_subclass(x: type[Class_TypeVarTuple[Any, Any]]) -> None: ...

# do not use `assert_type` here: some type checkers infer a more precise type
# than `type[]` for class objects
needs_classtypevartuple_or_subclass(Class_TypeVarTuple) # OK

assert_type(Class_TypeVarTuple(), Class_TypeVarTuple[str, int])
assert_type(Class_TypeVarTuple[int, bool](), Class_TypeVarTuple[int, bool])

Expand Down
8 changes: 7 additions & 1 deletion conformance/tests/generics_defaults_referential.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ class Bar(Generic[Z1, ListDefaultT]): # OK
def __init__(self, x: Z1, y: ListDefaultT): ...


assert_type(Bar, type[Bar[Any, list[Any]]])
def requires_bar_or_subclass(x: type[Bar[Any, list[Any]]]) -> None: ...

# Don't use `assert_type(Bar, type[Bar[Any, list[Any]]])` here,
# since some type checkers infer a more precise type than `type[]` for
# class objects
requires_bar_or_subclass(Bar) # OK

assert_type(Bar[int], type[Bar[int, list[int]]])
assert_type(Bar[int](0, []), Bar[int, list[int]])
assert_type(Bar[int, list[str]](0, []), Bar[int, list[str]])
Expand Down
21 changes: 16 additions & 5 deletions conformance/tests/generics_scoping.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#scoping-rules-for-type-variables

from typing import TypeVar, Generic, Iterable, TypeAlias, assert_type
from typing import TypeVar, Generic, Iterable, TypeAlias, assert_type, Literal

# > A type variable used in a generic function could be inferred to represent
# > different types in the same code block.
Expand All @@ -11,8 +11,13 @@ def fun_1(x: T) -> T: # T here
def fun_2(x: T) -> T: # and here could be different
return x

assert_type(fun_1(1), int)
assert_type(fun_2('a'), str)
# One of these two should pass; either is acceptable:
assert_type(fun_1(1), int) # E[fun1-int]
assert_type(fun_1(1), Literal[1]) # E[fun1-int]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How does the conformance checker handle it if there's an error on both of these lines? It's the intent of the test that that should be considered non-conforming.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I believe a tagged error like this is still non-optional: an error must be emitted on at least one of these lines. This seems to be reflected by the fact that zuban is no longer considered conformant on this PR branch, if you look at the changes to the reported results -- it apparently doesn't emit an error on either of these lines. (That seems like a bug in zuban to me -- int and Literal[1] are not equivalent types, so fun_1(1) cannot be equivalent to both int and Literal[1].)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes, as per https://github.com/python/typing/tree/main/conformance#test-case-syntax:

# E[tag], where tag is an arbitrary string: must appear multiple times in a file with the same tag. Exactly one line with this tag must raise an error.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

cc @davidhalter - FYI this conformance change turned up what looks like a bug in zuban.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@rchen152 Thanks, it looks like something about the nesting causes issues. Will investigate.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I quickly fixed this (on master branch). Zuban inferred essentially an intersection type of int & Literal[1], which is relevant for some type inference details. Now it's still inferred this way, but the assert_type will only assert the type of Literal[1] and not int.

I don't think this was necessarily a bug in Zuban, but it's probably easier to work with the conformance tests if I implement it this way.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

int & Literal[1] is equivalent to Literal[1], and the spec says that assert_type verifies type equivalency.


# One of these two should pass; either is acceptable:
assert_type(fun_1("a"), str) # E[fun1-str]
assert_type(fun_1("a"), Literal["a"]) # E[fun1-str]
Comment thread
AlexWaygood marked this conversation as resolved.
Outdated

# > A type variable used in a method of a generic class that coincides
# > with one of the variables that parameterize this class is always bound
Expand All @@ -39,8 +44,14 @@ def method(self, x: T, y: S) -> S:
return y

x: Foo[int] = Foo()
assert_type(x.method(0, "abc"), str)
assert_type(x.method(0, b"abc"), bytes)

# Either of these is acceptable; one of the two should pass:
assert_type(x.method(0, "abc"), str) # E[method-str]
assert_type(x.method(0, "abc"), Literal["abc"]) # E[method-str]

# Either of these is acceptable; one of the two should pass:
assert_type(x.method(0, b"abc"), bytes) # E[method-bytes]
assert_type(x.method(0, b"abc"), Literal[b"abc"]) # E[method-bytes]

# > Unbound type variables should not appear in the bodies of generic functions,
# > or in the class bodies apart from method definitions.
Expand Down
29 changes: 15 additions & 14 deletions conformance/tests/generics_syntax_scoping.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# Specification: https://peps.python.org/pep-0695/#type-parameter-scopes

from typing import Callable, Mapping, Sequence, TypeVar, assert_type
from typing import Callable, Mapping, Literal, Sequence, TypeVar, assert_type

# > A compiler error or runtime exception is generated if the definition
# > of an earlier type parameter references a later type parameter even
Expand Down Expand Up @@ -102,23 +102,24 @@ def method3[T](self, x: T): # E
T = int(0)


class Outer2[T]:
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

the assertions in this test ran into issues because ty inferred T as having type Literal[""] where T = str("").

ty also rejected the assert_type(T, complex) call lower down after the T = 3j assignment, because we view complex in a type expression as immediately expanding to the union int | float | complex, and the second argument to assert_type takes a type expression -- but complex (the type inferred for T) is not equivalent to int | float | complex.

T = int(1)

assert_type(T, int)
def f(a: int, b: str, c: complex):
class Outer2[T]:
T = a

class Inner1:
T = str("")
assert_type(T, int)

assert_type(T, str)
class Inner1:
T = b

def inner_method(self):
assert_type(T, TypeVar)
assert_type(T, str)

def outer_method(self):
T = 3j
def inner_method(self):
assert_type(T, TypeVar)

assert_type(T, complex)
def outer_method(self):
T = c

def inner_func():
assert_type(T, complex)

def inner_func():
assert_type(T, complex)