Skip to content

Commit a9a94ef

Browse files
committed
Initial check-in of type specification conformance suite.
1 parent ec4903b commit a9a94ef

File tree

90 files changed

+3196
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+3196
-0
lines changed

conformance/.gitignore

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# Environments
30+
.env
31+
.venv
32+
33+
# Tools
34+
.mypy_cache
35+
.pyre_configuration
36+
.pyre
37+
.coverage
38+
htmlcov
39+
40+
# General
41+
.DS_Store
42+
43+
# Editor temp files
44+
.*.swp
45+
46+
# Workspace configurations
47+
.vscode

conformance/README.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Type System Conformance
2+
3+
## Motivation
4+
5+
[PEP 729](https://peps.python.org/pep-0729/) provides a structured and documented way to specify and evolve the Python type system. In support of this effort, an official [Python typing spec](https://github.com/python/typing/tree/main/docs/spec) has been drafted. This spec consolidates details from various historical typing-related PEPs. The spec will be modified over time to clarify unspecified and under-specified parts of the type system. It will also be extended to cover new features of the type system.
6+
7+
Accompanying the typing specification is this conformance test suite which validates the behavior of static type checkers against the specification.
8+
9+
## Structure & Name
10+
11+
This project contains test cases for behaviors defined in the Python typing spec. Tests are structured and grouped in accordance with the specification's chapter headings.
12+
13+
* [concepts](https://typing.readthedocs.io/en/latest/spec/concepts.html)
14+
* [annotations](https://typing.readthedocs.io/en/latest/spec/annotations.html)
15+
* [specialtypes](https://typing.readthedocs.io/en/latest/spec/special-types.html)
16+
* [generics](https://typing.readthedocs.io/en/latest/spec/generics.html)
17+
* [qualifiers](https://typing.readthedocs.io/en/latest/spec/qualifiers.html)
18+
* [classes](https://typing.readthedocs.io/en/latest/spec/class-compat.html)
19+
* [aliases](https://typing.readthedocs.io/en/latest/spec/aliases.html)
20+
* [literals](https://typing.readthedocs.io/en/latest/spec/literal.html)
21+
* [protocols](https://typing.readthedocs.io/en/latest/spec/protocol.html)
22+
* [callables](https://typing.readthedocs.io/en/latest/spec/callables.html)
23+
* [overloads](https://typing.readthedocs.io/en/latest/spec/overload.html)
24+
* [dataclasses](https://typing.readthedocs.io/en/latest/spec/dataclasses.html)
25+
* [typeddicts](https://typing.readthedocs.io/en/latest/spec/typeddict.html)
26+
* [narrowing](https://typing.readthedocs.io/en/latest/spec/narrowing.html)
27+
* [directives](https://typing.readthedocs.io/en/latest/spec/directives.html)
28+
* [distribution](https://typing.readthedocs.io/en/latest/spec/distributing.html)
29+
* [historical](https://typing.readthedocs.io/en/latest/spec/historical.html)
30+
31+
A test file is a ".py" file. The file name should start with one of the above names followed by a description of the test (with words separated by underscores). For example, `generics_paramspec_basic_usage.py` would contain the basic usage tests for `ParamSpec`. Each test file can contain multiple individual unit tests, but these tests should be related to each other. If the number of unit tests in a single test file exceeds ten, it may be desirable to split it into separate test files. This will help maintain a consistent level of granularity across tests.
32+
33+
## Notes About Tests
34+
35+
Tests are designed to run on all current and future static type checkers. They must therefore be type-checker agnostic and should not rely on functionality or behaviors that are specific to one type checker or another.
36+
37+
Test cases are meant to be human readable. They should include comments that help explain their purpose (what is being tested, whether an error should be generated, etc.). They should also contain links to the typing spec, discussions, and issue trackers.
38+
39+
The test suite focuses on static type checking not general Python semantics. Tests should therefore focus on static analysis behaviors, not runtime behaviors.
40+
41+
## Reporting Conformance Results
42+
43+
Different type checkers report errors in different ways (with different wording in error messages and different line numbers or character ranges for errors). This variation makes it difficult to fully automate test validation given that tests will want to check for both false positive and false negative type errors. Some level of manual inspection will therefore be needed to determine whether a type checker is fully conformant with all tests in any given test file. This "scoring" process is required only when the output of a test changes — e.g. when a new version of that type checker is released and the tests are rerun. We assume that the output of a type checker will be the same from one run to the next unless/until a new version is released that fixes or introduces a bug. In this case, the output will need to be manually inspected and the conformance results re-scored for those tests whose output has changed.
44+
45+
Conformance results are reported and summarized for each supported type checker. Initially, results will be reported for mypy and pyright. It is the goal and desire to add additional type checkers over time.
46+
47+
## Adding a New Test Case
48+
49+
To add a new test, create a new ".py" file in the `tests` directory. Its name must begin with one of the above test group names followed by an underscore. Write the contents of the test including a module docstring describing the purpose of the test. Next, run the tool. This will generate a new `.toml` file corresponding to the new test for each supported type checker. Manually review the output from each type checker and determine whether it conforms to the specification. If so, add `conformant = "Yes"` to the `.toml` file. If it does not fully comply, add `conformant = "Partial"` and a `notes` section detailing where it is not compliant. If the type checker doesn't support the feature in the test add `conformant = "Unsupported"`. Once the conformance status has been updated, re-run the test tool again to regenerate the summary report.
50+
51+
## Updating A Test Case
52+
53+
If a test is updated (augmented or fixed), the process is similar to when adding a new test. Run the tool to generate new results and manually examine the output of each supported type checker. Then update the conformance status accordingly. Once the conformance status has been updated, re-run the test tool again to regenerate the summary report.
54+
55+
## Updating a Type Checker
56+
57+
If a new version of a type checker is released, re-run the test tool with the new version. If the type checker output has changed for any test cases, the tool will supply the old and new outputs. Examine these to determine whether the conformance status has changed. Once the conformance status has been updated, re-run the test tool again to regenerate the summary report.
58+
59+
## Contributing
60+
61+
Contributions are welcome!

conformance/requirements.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
tomli
2+
tomlkit
3+
pyright
4+
mypy
5+
pyre-check
6+
pytype
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
conformant = "Yes"
2+
output = """
3+
annotations_typeexpr.py:77: error: Invalid type comment or annotation [valid-type]
4+
annotations_typeexpr.py:77: note: Suggestion: use eval[...] instead of eval(...)
5+
annotations_typeexpr.py:78: error: Bracketed expression "[...]" is not valid as a type [valid-type]
6+
annotations_typeexpr.py:79: error: Syntax error in type annotation [syntax]
7+
annotations_typeexpr.py:79: note: Suggestion: Use Tuple[T1, ..., Tn] instead of (T1, ..., Tn)
8+
annotations_typeexpr.py:80: error: Invalid type comment or annotation [valid-type]
9+
annotations_typeexpr.py:81: error: Invalid type comment or annotation [valid-type]
10+
annotations_typeexpr.py:82: error: Invalid type comment or annotation [valid-type]
11+
annotations_typeexpr.py:83: error: Invalid type comment or annotation [valid-type]
12+
annotations_typeexpr.py:84: error: Invalid type comment or annotation [valid-type]
13+
annotations_typeexpr.py:85: error: Variable "annotations_typeexpr.var1" is not valid as a type [valid-type]
14+
annotations_typeexpr.py:85: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases
15+
annotations_typeexpr.py:86: error: Invalid type: try using Literal[True] instead? [valid-type]
16+
annotations_typeexpr.py:87: error: Invalid type: try using Literal[1] instead? [valid-type]
17+
annotations_typeexpr.py:88: error: Invalid type: try using Literal[-1] instead? [valid-type]
18+
annotations_typeexpr.py:89: error: Invalid type comment or annotation [valid-type]
19+
annotations_typeexpr.py:90: error: Invalid type comment or annotation [valid-type]
20+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
conformant = "Partial"
2+
notes = """
3+
Does not infer the type of an unannotated `self` parameter to be type `Self`.
4+
Does not retain `Self` when calling method that returns `Self`.
5+
Does not infer the type of an unannotated `cls` parameter to be type `type[Self]`.
6+
Does not retain `Self` when accessing attribute through `type[Self]`.
7+
"""
8+
output = """
9+
generics_self_advanced.py:35: error: Expression is of type "ChildB", not "Self" [assert-type]
10+
generics_self_advanced.py:38: error: Expression is of type "ChildB", not "Self" [assert-type]
11+
generics_self_advanced.py:42: error: Expression is of type "type[ChildB]", not "type[Self]" [assert-type]
12+
generics_self_advanced.py:43: error: Expression is of type "list[ChildB]", not "list[Self]" [assert-type]
13+
generics_self_advanced.py:44: error: Expression is of type "ChildB", not "Self" [assert-type]
14+
generics_self_advanced.py:45: error: Expression is of type "ChildB", not "Self" [assert-type]
15+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
conformant = "Yes"
2+
output = """
3+
generics_self_attributes.py:26: error: Argument "next" to "OrdinalLinkedList" has incompatible type "LinkedList[int]"; expected "OrdinalLinkedList | None" [arg-type]
4+
generics_self_attributes.py:32: error: Incompatible types in assignment (expression has type "LinkedList[int]", variable has type "OrdinalLinkedList | None") [assignment]
5+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
conformant = "Partial"
2+
notes = """
3+
Does not properly handle constructor call through `cls` parameter.
4+
"""
5+
output = """
6+
generics_self_basic.py:19: error: Incompatible return value type (got "Shape", expected "Self") [return-value]
7+
generics_self_basic.py:27: error: Too many arguments for "Shape" [call-arg]
8+
generics_self_basic.py:32: error: Incompatible return value type (got "Shape", expected "Self") [return-value]
9+
generics_self_basic.py:64: error: Self type cannot have type arguments [misc]
10+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
conformant = "Yes"
2+
output = """
3+
generics_self_protocols.py:61: error: Argument 1 to "accepts_shape" has incompatible type "BadReturnType"; expected "ShapeProtocol" [arg-type]
4+
generics_self_protocols.py:61: note: Following member(s) of "BadReturnType" have conflicts:
5+
generics_self_protocols.py:61: note: Expected:
6+
generics_self_protocols.py:61: note: def set_scale(self, scale: float) -> BadReturnType
7+
generics_self_protocols.py:61: note: Got:
8+
generics_self_protocols.py:61: note: def set_scale(self, scale: float) -> int
9+
generics_self_protocols.py:64: error: Argument 1 to "accepts_shape" has incompatible type "ReturnDifferentClass"; expected "ShapeProtocol" [arg-type]
10+
generics_self_protocols.py:64: note: Following member(s) of "ReturnDifferentClass" have conflicts:
11+
generics_self_protocols.py:64: note: Expected:
12+
generics_self_protocols.py:64: note: def set_scale(self, scale: float) -> ReturnDifferentClass
13+
generics_self_protocols.py:64: note: Got:
14+
generics_self_protocols.py:64: note: def set_scale(self, scale: float) -> ReturnConcreteShape
15+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
conformant = "Yes"
2+
output = """
3+
generics_self_usage.py:73: error: Self type is only allowed in annotations within class definition [misc]
4+
generics_self_usage.py:76: error: Self type is only allowed in annotations within class definition [misc]
5+
generics_self_usage.py:82: error: Method cannot have explicit self annotation and Self type [misc]
6+
generics_self_usage.py:82: error: A function returning TypeVar should receive at least one argument containing the same TypeVar [type-var]
7+
generics_self_usage.py:82: note: Consider using the upper bound "Foo2" instead
8+
generics_self_usage.py:86: error: Incompatible return value type (got "Foo3", expected "Self") [return-value]
9+
generics_self_usage.py:101: error: Self type is only allowed in annotations within class definition [misc]
10+
generics_self_usage.py:103: error: Self type is only allowed in annotations within class definition [misc]
11+
generics_self_usage.py:106: error: Self type is only allowed in annotations within class definition [misc]
12+
generics_self_usage.py:106: error: Self type cannot be used in type alias target [misc]
13+
generics_self_usage.py:111: error: Static methods cannot use Self type [misc]
14+
generics_self_usage.py:111: error: A function returning TypeVar should receive at least one argument containing the same TypeVar [type-var]
15+
generics_self_usage.py:111: note: Consider using the upper bound "Base" instead
16+
generics_self_usage.py:116: error: Static methods cannot use Self type [misc]
17+
generics_self_usage.py:121: error: Self type cannot be used in a metaclass [misc]
18+
generics_self_usage.py:125: error: Self type cannot be used in a metaclass [misc]
19+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
conformant = "Partial"
2+
notes = """
3+
Does not narrow type of `x` with `x in Literal` type guard pattern.
4+
"""
5+
output = """
6+
literals_interactions.py:15: error: Tuple index out of range [misc]
7+
literals_interactions.py:16: error: Tuple index out of range [misc]
8+
literals_interactions.py:17: error: Tuple index out of range [misc]
9+
literals_interactions.py:18: error: Tuple index out of range [misc]
10+
literals_interactions.py:106: error: Argument 1 to "expects_bad_status" has incompatible type "str"; expected "Literal['MALFORMED', 'ABORTED']" [arg-type]
11+
literals_interactions.py:109: error: Argument 1 to "expects_pending_status" has incompatible type "str"; expected "Literal['PENDING']" [arg-type]
12+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
conformant = "Unsupported"
2+
notes = """
3+
Support for `LiteralString` has not been implemented in mypy.
4+
"""
5+
output = """
6+
literals_literalstring.py:36: error: Parameter 2 of Literal[...] is invalid [valid-type]
7+
literals_literalstring.py:37: error: Parameter 1 of Literal[...] is invalid [valid-type]
8+
literals_literalstring.py:43: error: Incompatible types in assignment (expression has type "Literal['two']", variable has type "Literal['']") [assignment]
9+
literals_literalstring.py:74: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment]
10+
literals_literalstring.py:75: error: Incompatible types in assignment (expression has type "bytes", variable has type "str") [assignment]
11+
literals_literalstring.py:142: error: Overloaded function signatures 1 and 2 overlap with incompatible return types [overload-overlap]
12+
literals_literalstring.py:142: error: Overloaded function signatures 1 and 3 overlap with incompatible return types [overload-overlap]
13+
literals_literalstring.py:152: error: Overloaded function signature 3 will never be matched: signature 2's parameter type(s) are the same or broader [misc]
14+
literals_literalstring.py:162: error: Expression is of type "bool", not "str" [assert-type]
15+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
conformant = "Partial"
2+
notes = """
3+
Does not reject tuple within Literal.
4+
"""
5+
output = """
6+
literals_parameterizations.py:40: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type]
7+
literals_parameterizations.py:41: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type]
8+
literals_parameterizations.py:42: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type]
9+
literals_parameterizations.py:43: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type]
10+
literals_parameterizations.py:44: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type]
11+
literals_parameterizations.py:46: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type]
12+
literals_parameterizations.py:47: error: Parameter 1 of Literal[...] is invalid [valid-type]
13+
literals_parameterizations.py:48: error: Parameter 1 of Literal[...] is invalid [valid-type]
14+
literals_parameterizations.py:49: error: Parameter 1 of Literal[...] is invalid [valid-type]
15+
literals_parameterizations.py:50: error: Parameter 1 of Literal[...] cannot be of type "float" [valid-type]
16+
literals_parameterizations.py:51: error: Parameter 1 of Literal[...] cannot be of type "Any" [valid-type]
17+
literals_parameterizations.py:52: error: Parameter 1 of Literal[...] is invalid [valid-type]
18+
literals_parameterizations.py:55: error: Invalid type: Literal[...] cannot contain arbitrary expressions [valid-type]
19+
literals_parameterizations.py:58: error: Literal[...] must have at least one parameter [valid-type]
20+
literals_parameterizations.py:59: error: Parameter 1 of Literal[...] is invalid [valid-type]
21+
literals_parameterizations.py:63: error: Incompatible types in assignment (expression has type "Literal[Color.RED]", variable has type "Literal['Color.RED']") [assignment]
22+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
conformant = "Yes"
2+
output = """
3+
literals_semantics.py:10: error: Incompatible types in assignment (expression has type "Literal[4]", variable has type "Literal[3]") [assignment]
4+
literals_semantics.py:24: error: Incompatible types in assignment (expression has type "Literal[0]", variable has type "Literal[False]") [assignment]
5+
literals_semantics.py:25: error: Incompatible types in assignment (expression has type "Literal[False]", variable has type "Literal[0]") [assignment]
6+
literals_semantics.py:33: error: Incompatible types in assignment (expression has type "int", variable has type "Literal[3, 4, 5]") [assignment]
7+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
conformant = "Yes"
2+
output = """
3+
narrowing_typeguard.py:102: error: TypeGuard functions must have a positional argument [valid-type]
4+
narrowing_typeguard.py:107: error: TypeGuard functions must have a positional argument [valid-type]
5+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
conformant = "Partial"
2+
notes = """
3+
Does not support keyword-argument form of alternative syntax (deprecated in 3.11).
4+
"""
5+
output = """
6+
typeddicts_alt_syntax.py:23: error: TypedDict() expects a dictionary literal as the second argument [misc]
7+
typeddicts_alt_syntax.py:27: error: Invalid TypedDict() field name [misc]
8+
typeddicts_alt_syntax.py:31: error: First argument "WrongName" to TypedDict() does not match variable name "BadTypedDict3" [name-match]
9+
typeddicts_alt_syntax.py:35: error: Too many arguments for TypedDict() [misc]
10+
typeddicts_alt_syntax.py:41: error: Unexpected arguments to TypedDict() [misc]
11+
typeddicts_alt_syntax.py:44: error: Extra keys ("name", "year") for TypedDict "Movie2" [typeddict-unknown-key]
12+
typeddicts_alt_syntax.py:45: error: Extra keys ("name", "year") for TypedDict "Movie2" [typeddict-unknown-key]
13+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
conformant = "Yes"
2+
output = """
3+
typeddicts_class_syntax.py:29: error: Invalid statement in TypedDict definition; expected "field_name: field_type" [misc]
4+
typeddicts_class_syntax.py:33: error: Invalid statement in TypedDict definition; expected "field_name: field_type" [misc]
5+
typeddicts_class_syntax.py:38: error: Invalid statement in TypedDict definition; expected "field_name: field_type" [misc]
6+
typeddicts_class_syntax.py:44: error: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict" [call-arg]
7+
typeddicts_class_syntax.py:49: error: Unexpected keyword argument "other" for "__init_subclass__" of "TypedDict" [call-arg]
8+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
conformant = "Yes"
2+
output = """
3+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
conformant = "Yes"
2+
output = """
3+
typeddicts_inheritance.py:44: error: All bases of a new TypedDict must be TypedDict types [misc]
4+
typeddicts_inheritance.py:55: error: Overwriting TypedDict field "x" while extending [misc]
5+
typeddicts_inheritance.py:65: error: Overwriting TypedDict field "x" while merging [misc]
6+
"""

0 commit comments

Comments
 (0)