From 1b141ffbefe00af3dd238dab9b3893054a6383c2 Mon Sep 17 00:00:00 2001 From: Shantanu <12621235+hauntsaninja@users.noreply.github.com> Date: Mon, 15 Jan 2024 23:24:28 -0800 Subject: [PATCH] Add conformance test for generic types as base classes (#1590) --- .../results/mypy/generics_base_class.toml | 6 +++ .../results/pyre/generics_base_class.toml | 10 ++++ .../results/pyright/generics_base_class.toml | 9 ++++ .../results/pytype/generics_base_class.toml | 10 ++++ conformance/tests/generics_base_class.py | 46 +++++++++++++++++++ 5 files changed, 81 insertions(+) create mode 100644 conformance/results/mypy/generics_base_class.toml create mode 100644 conformance/results/pyre/generics_base_class.toml create mode 100644 conformance/results/pyright/generics_base_class.toml create mode 100644 conformance/results/pytype/generics_base_class.toml create mode 100644 conformance/tests/generics_base_class.py diff --git a/conformance/results/mypy/generics_base_class.toml b/conformance/results/mypy/generics_base_class.toml new file mode 100644 index 00000000..6a00b12c --- /dev/null +++ b/conformance/results/mypy/generics_base_class.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +output = """ +generics_base_class.py:22: error: Argument 1 to "takes_dict_incorrect" has incompatible type "SymbolTable"; expected "dict[str, list[object]]" [arg-type] +generics_base_class.py:37: error: "LinkedList" expects 1 type argument, but 2 given [type-arg] +generics_base_class.py:46: error: "MyDict" expects 1 type argument, but 2 given [type-arg] +""" diff --git a/conformance/results/pyre/generics_base_class.toml b/conformance/results/pyre/generics_base_class.toml new file mode 100644 index 00000000..377d2610 --- /dev/null +++ b/conformance/results/pyre/generics_base_class.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +notes = """ +Doesn't allow using generic in assert_type expression. +""" +output = """ +generics_base_class.py:22:25 Incompatible parameter type [6]: In call `takes_dict_incorrect`, for 1st positional argument, expected `Dict[str, List[object]]` but got `SymbolTable`. +generics_base_class.py:34:25 Undefined attribute [16]: `typing.Iterator` has no attribute `__getitem__`. +generics_base_class.py:37:21 Invalid type parameters [24]: Generic type `LinkedList` expects 1 type parameter, received 2. +generics_base_class.py:46:17 Invalid type parameters [24]: Generic type `MyDict` expects 1 type parameter, received 2. +""" diff --git a/conformance/results/pyright/generics_base_class.toml b/conformance/results/pyright/generics_base_class.toml new file mode 100644 index 00000000..c84c87d9 --- /dev/null +++ b/conformance/results/pyright/generics_base_class.toml @@ -0,0 +1,9 @@ +conformant = "Pass" +output = """ +generics_base_class.py:22:26 - error: Argument of type "SymbolTable" cannot be assigned to parameter "x" of type "dict[str, list[object]]" in function "takes_dict_incorrect" +  "SymbolTable" is incompatible with "dict[str, list[object]]" +    Type parameter "_VT@dict" is invariant, but "list[Node]" is not the same as "list[object]" +    Consider switching from "dict" to "Mapping" which is covariant in the value type (reportGeneralTypeIssues) +generics_base_class.py:37:38 - error: Too many type arguments provided for "LinkedList"; expected 1 but received 2 (reportGeneralTypeIssues) +generics_base_class.py:46:30 - error: Too many type arguments provided for "MyDict"; expected 1 but received 2 (reportGeneralTypeIssues) +""" diff --git a/conformance/results/pytype/generics_base_class.toml b/conformance/results/pytype/generics_base_class.toml new file mode 100644 index 00000000..8271cff2 --- /dev/null +++ b/conformance/results/pytype/generics_base_class.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +False negative on passing SymbolTable to dict[str, list[object]]. +""" +output = """ +File "generics_base_class.py", line 37, in : Invalid type annotation 'LinkedList[int, int]' [invalid-annotation] + LinkedList[T] expected 1 parameter, got 2 +File "generics_base_class.py", line 46, in : Invalid type annotation 'MyDict[int, int]' [invalid-annotation] + MyDict[T] expected 1 parameter, got 2 +""" diff --git a/conformance/tests/generics_base_class.py b/conformance/tests/generics_base_class.py new file mode 100644 index 00000000..9ef7b5de --- /dev/null +++ b/conformance/tests/generics_base_class.py @@ -0,0 +1,46 @@ +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#arbitrary-generic-types-as-base-classes + +from typing import TypeVar, Iterable, assert_type + +T = TypeVar("T") + +# > Generic[T] is only valid as a base class – it’s not a proper type. However, +# > user-defined generic types [...] and built-in generic types and ABCs such as +# > list[T] and Iterable[T] are valid both as types and as base classes. + +class Node: ... + +class SymbolTable(dict[str, list[Node]]): ... + +def takes_dict(x: dict): ... +def takes_dict_typed(x: dict[str, list[Node]]): ... +def takes_dict_incorrect(x: dict[str, list[object]]): ... + +def test_symbol_table(s: SymbolTable): + takes_dict(s) # OK + takes_dict_typed(s) # OK + takes_dict_incorrect(s) # Type error + +# > If a generic base class has a type variable as a type argument, this makes +# > the defined class generic. + +# Note that there is overlap in the spec and tests in generics_basic.py + +from collections.abc import Iterable, Container, Iterator + +class LinkedList(Iterable[T], Container[T]): ... + +def test_linked_list(l: LinkedList[int]): + assert_type(iter(l), Iterator[int]) + assert_type(l.__contains__(1), bool) + +linked_list_invalid: LinkedList[int, int] # Type error + +from collections.abc import Mapping + +class MyDict(Mapping[str, T]): ... + +def test_my_dict(d: MyDict[int]): + assert_type(d["a"], int) + +my_dict_invalid: MyDict[int, int] # Type error