Repository URL to install this package:
|
Version:
2022.2.8 ▾
|
pytype
/
matcher_test.py
|
|---|
"""Tests for matcher.py."""
import textwrap
from pytype import config
from pytype import context
from pytype import errors
from pytype import file_utils
from pytype import load_pytd
from pytype.abstract import abstract
from pytype.abstract import abstract_utils
from pytype.tests import test_base
import unittest
class MatcherTestBase(test_base.UnitTest):
def setUp(self):
super().setUp()
options = config.Options.create(python_version=self.python_version)
self.ctx = context.Context(errors.ErrorLog(), options,
load_pytd.Loader(options))
self.matcher = self.ctx.matcher(self.ctx.root_node)
def _match_var(self, left, right):
var = self.ctx.program.NewVariable()
var.AddBinding(left, [], self.ctx.root_node)
for view in abstract_utils.get_views([var], self.ctx.root_node):
yield self.matcher.match_var_against_type(var, right, {}, view)
def assertMatch(self, left, right):
for match in self._match_var(left, right):
self.assertEqual(match, {})
def assertNoMatch(self, left, right):
for match in self._match_var(left, right):
self.assertIsNone(match)
class MatcherTest(MatcherTestBase):
"""Test matcher.AbstractMatcher."""
def _make_class(self, name):
return abstract.InterpreterClass(name, [], {}, None, self.ctx)
def _parse_and_lookup(self, src, objname, filename=None):
if filename is None:
filename = str(hash(src))
with file_utils.Tempdir() as d:
d.create_file(filename + ".pyi", src)
self.ctx.options.tweak(pythonpath=[d.path]) # monkeypatch
ast = self.ctx.loader.import_name(filename)
return ast.Lookup(filename + "." + objname)
def _convert(self, x, name, as_instance=False):
pyval = self._parse_and_lookup(x, name)
if as_instance:
pyval = abstract_utils.AsInstance(pyval)
return self.ctx.convert.constant_to_value(pyval, {}, self.ctx.root_node)
def _convert_type(self, t, as_instance=False):
"""Convenience function for turning a string into an abstract value.
Note that this function cannot be called more than once per test with
the same arguments, since we hash the arguments to get a filename for
the temporary pyi.
Args:
t: The string representation of a type.
as_instance: Whether to convert as an instance.
Returns:
A BaseValue.
"""
src = textwrap.dedent(f"""
from typing import Any, Callable, Iterator, Tuple, Type, Union
from protocols import Sequence, SupportsLower
x = ... # type: {t}
""")
filename = str(hash((t, as_instance)))
x = self._parse_and_lookup(src, "x", filename).type
if as_instance:
x = abstract_utils.AsInstance(x)
return self.ctx.convert.constant_to_value(x, {}, self.ctx.root_node)
def test_basic(self):
self.assertMatch(abstract.Empty(self.ctx), abstract.Empty(self.ctx))
def test_type(self):
left = self._make_class("dummy")
type_parameters = {
abstract_utils.T: abstract.TypeParameter(abstract_utils.T, self.ctx)
}
other_type = abstract.ParameterizedClass(self.ctx.convert.type_type,
type_parameters, self.ctx)
for result in self._match_var(left, other_type):
instance_binding, = result[abstract_utils.T].bindings
self.assertEqual(instance_binding.data.cls, left)
def test_union(self):
left_option1 = self._make_class("o1")
left_option2 = self._make_class("o2")
left = abstract.Union([left_option1, left_option2], self.ctx)
self.assertMatch(left, self.ctx.convert.type_type)
def test_metaclass(self):
left = self._make_class("left")
meta1 = self._make_class("m1")
meta2 = self._make_class("m2")
left.set_class(
self.ctx.root_node,
self.ctx.program.NewVariable([meta1, meta2], [], self.ctx.root_node))
self.assertMatch(left, meta1)
self.assertMatch(left, meta2)
def test_empty_against_class(self):
var = self.ctx.program.NewVariable()
right = self._make_class("bar")
result = self.matcher.match_var_against_type(var, right, {}, {})
self.assertEqual(result, {})
def test_empty_var_against_empty(self):
var = self.ctx.program.NewVariable()
right = abstract.Empty(self.ctx)
result = self.matcher.match_var_against_type(var, right, {}, {})
self.assertEqual(result, {})
def test_empty_against_type_parameter(self):
var = self.ctx.program.NewVariable()
right = abstract.TypeParameter("T", self.ctx)
result = self.matcher.match_var_against_type(var, right, {}, {})
self.assertCountEqual(result.keys(), ["T"])
self.assertFalse(result["T"].bindings)
def test_empty_against_unsolvable(self):
var = self.ctx.program.NewVariable()
right = abstract.Empty(self.ctx)
result = self.matcher.match_var_against_type(var, right, {}, {})
self.assertEqual(result, {})
def test_class_against_type_union(self):
left = self._make_class("foo")
union = abstract.Union((left,), self.ctx)
right = abstract.ParameterizedClass(self.ctx.convert.type_type,
{abstract_utils.T: union}, self.ctx)
self.assertMatch(left, right)
def test_none_against_bool(self):
# See pep484.COMPAT_ITEMS.
left = self._convert_type("None", as_instance=True)
right = self._convert_type("bool")
self.assertMatch(left, right)
def test_homogeneous_tuple(self):
left = self._convert_type("Tuple[int, ...]", as_instance=True)
right1 = self._convert_type("Tuple[int, ...]")
right2 = self._convert_type("Tuple[str, ...]")
self.assertMatch(left, right1)
self.assertNoMatch(left, right2)
def test_heterogeneous_tuple(self):
left1 = self._convert_type("Tuple[Union[int, str]]", as_instance=True)
left2 = self._convert_type("Tuple[int, str]", as_instance=True)
left3 = self._convert_type("Tuple[str, int]", as_instance=True)
right = self._convert_type("Tuple[int, str]")
self.assertNoMatch(left1, right)
self.assertMatch(left2, right)
self.assertNoMatch(left3, right)
def test_heterogeneous_tuple_against_homogeneous_tuple(self):
left = self._convert_type("Tuple[bool, int]", as_instance=True)
right1 = self._convert_type("Tuple[bool, ...]")
right2 = self._convert_type("Tuple[int, ...]")
right3 = self._convert_type("tuple")
self.assertNoMatch(left, right1)
self.assertMatch(left, right2)
self.assertMatch(left, right3)
def test_homogeneous_tuple_against_heterogeneous_tuple(self):
left1 = self._convert_type("Tuple[bool, ...]", as_instance=True)
left2 = self._convert_type("Tuple[int, ...]", as_instance=True)
left3 = self._convert_type("tuple", as_instance=True)
right = self._convert_type("Tuple[bool, int]")
self.assertMatch(left1, right)
self.assertNoMatch(left2, right)
self.assertMatch(left3, right)
def test_tuple_type(self):
# homogeneous against homogeneous
left = self._convert_type("Type[Tuple[float, ...]]", as_instance=True)
right1 = self._convert_type("Type[Tuple[float, ...]]")
right2 = self._convert_type("Type[Tuple[str, ...]]")
self.assertMatch(left, right1)
self.assertNoMatch(left, right2)
# heterogeneous against heterogeneous
left1 = self._convert_type("Type[Tuple[Union[int, str]]]", as_instance=True)
left2 = self._convert_type("Type[Tuple[int, str]]", as_instance=True)
left3 = self._convert_type("Type[Tuple[str, int]]", as_instance=True)
right = self._convert_type("Type[Tuple[int, str]]")
self.assertNoMatch(left1, right)
self.assertMatch(left2, right)
self.assertNoMatch(left3, right)
# heterogeneous against homogeneous
left = self._convert_type("Type[Tuple[bool, int]]", as_instance=True)
right1 = self._convert_type("Type[Tuple[bool, ...]]")
right2 = self._convert_type("Type[Tuple[int, ...]]")
right3 = self._convert_type("Type[tuple]")
self.assertNoMatch(left, right1)
self.assertMatch(left, right2)
self.assertMatch(left, right3)
# homogeneous against heterogeneous
left1 = self._convert_type("Type[Tuple[bool, ...]]", as_instance=True)
left2 = self._convert_type("Type[Tuple[int, ...]]", as_instance=True)
left3 = self._convert_type("Type[tuple]", as_instance=True)
right = self._convert_type("Type[Tuple[bool, int]]")
self.assertMatch(left1, right)
self.assertNoMatch(left2, right)
self.assertMatch(left3, right)
def test_tuple_subclass(self):
left = self._convert("""
from typing import Tuple
class A(Tuple[bool, int]): ...""", "A", as_instance=True)
right1 = self._convert_type("Tuple[bool, int]")
right2 = self._convert_type("Tuple[int, bool]")
right3 = self._convert_type("Tuple[int, int]")
right4 = self._convert_type("Tuple[int]")
right5 = self._convert_type("tuple")
right6 = self._convert_type("Tuple[bool, ...]")
right7 = self._convert_type("Tuple[int, ...]")
self.assertMatch(left, right1)
self.assertNoMatch(left, right2)
self.assertMatch(left, right3)
self.assertNoMatch(left, right4)
self.assertMatch(left, right5)
self.assertNoMatch(left, right6)
self.assertMatch(left, right7)
def test_annotation_class(self):
left = abstract.AnnotationClass("Dict", self.ctx)
right = self.ctx.convert.object_type
self.assertMatch(left, right)
def test_empty_tuple_class(self):
var = self.ctx.program.NewVariable()
params = {
0: abstract.TypeParameter(abstract_utils.K, self.ctx),
1: abstract.TypeParameter(abstract_utils.V, self.ctx)
}
params[abstract_utils.T] = abstract.Union((params[0], params[1]), self.ctx)
right = abstract.TupleClass(self.ctx.convert.tuple_type, params, self.ctx)
match = self.matcher.match_var_against_type(var, right, {}, {})
self.assertSetEqual(set(match), {abstract_utils.K, abstract_utils.V})
def test_unsolvable_against_tuple_class(self):
left = self.ctx.convert.unsolvable
params = {
0: abstract.TypeParameter(abstract_utils.K, self.ctx),
1: abstract.TypeParameter(abstract_utils.V, self.ctx)
}
params[abstract_utils.T] = abstract.Union((params[0], params[1]), self.ctx)
right = abstract.TupleClass(self.ctx.convert.tuple_type, params, self.ctx)
for match in self._match_var(left, right):
self.assertSetEqual(set(match), {abstract_utils.K, abstract_utils.V})
self.assertEqual(match[abstract_utils.K].data,
[self.ctx.convert.unsolvable])
self.assertEqual(match[abstract_utils.V].data,
[self.ctx.convert.unsolvable])
def test_bool_against_float(self):
left = self.ctx.convert.true
right = self.ctx.convert.primitive_classes[float]
self.assertMatch(left, right)
def test_pytd_function_against_callable(self):
f = self._convert("def f(x: int) -> bool: ...", "f")
plain_callable = self._convert_type("Callable")
good_callable1 = self._convert_type("Callable[[bool], int]")
good_callable2 = self._convert_type("Callable[..., int]")
self.assertMatch(f, plain_callable)
self.assertMatch(f, good_callable1)
self.assertMatch(f, good_callable2)
def test_pytd_function_against_callable_bad_return(self):
f = self._convert("def f(x: int) -> bool: ...", "f")
callable_bad_ret = self._convert_type("Callable[[int], str]")
self.assertNoMatch(f, callable_bad_ret)
def test_pytd_function_against_callable_bad_arg_count(self):
f = self._convert("def f(x: int) -> bool: ...", "f")
callable_bad_count1 = self._convert_type("Callable[[], bool]")
callable_bad_count2 = self._convert_type("Callable[[int, str], bool]")
self.assertNoMatch(f, callable_bad_count1)
self.assertNoMatch(f, callable_bad_count2)
def test_pytd_function_against_callable_bad_arg_type(self):
f = self._convert("def f(x: bool) -> bool: ...", "f")
callable_bad_arg1 = self._convert_type("Callable[[int], bool]")
callable_bad_arg2 = self._convert_type("Callable[[str], bool]")
self.assertNoMatch(f, callable_bad_arg1)
self.assertNoMatch(f, callable_bad_arg2)
def test_bound_pytd_function_against_callable(self):
instance = self._convert("""
class A:
def f(self, x: int) -> bool: ...
""", "A", as_instance=True)
binding = instance.to_binding(self.ctx.root_node)
_, var = self.ctx.attribute_handler.get_attribute(self.ctx.root_node,
instance, "f", binding)
bound = var.data[0]
_, var = self.ctx.attribute_handler.get_attribute(self.ctx.root_node,
instance.cls, "f")
unbound = var.data[0]
callable_no_self = self._convert_type("Callable[[int]]")
callable_self = self._convert_type("Callable[[Any, int]]")
self.assertMatch(bound, callable_no_self)
self.assertNoMatch(unbound, callable_no_self)
self.assertNoMatch(bound, callable_self)
self.assertMatch(unbound, callable_self)
def test_native_function_against_callable(self):
# Matching a native function against a callable always succeeds, regardless
# of argument and return types.
f = abstract.NativeFunction("f", lambda x: x, self.ctx)
callable_type = self._convert_type("Callable[[int], int]")
self.assertMatch(f, callable_type)
def test_callable_instance(self):
left1 = self._convert_type("Callable[[int], bool]", as_instance=True)
left2 = self._convert_type("Callable", as_instance=True)
left3 = self._convert_type("Callable[..., int]", as_instance=True)
right1 = self._convert_type("Callable[[bool], int]")
right2 = self._convert_type("Callable[..., int]")
right3 = self._convert_type("Callable")
self.assertMatch(left1, right1)
self.assertMatch(left2, right1)
self.assertMatch(left3, right1)
self.assertMatch(left1, right2)
self.assertMatch(left2, right2)
self.assertMatch(left3, right2)
self.assertMatch(left1, right3)
self.assertMatch(left2, right3)
self.assertMatch(left3, right3)
def test_callable_instance_bad_return(self):
left1 = self._convert_type("Callable[[int], float]", as_instance=True)
left2 = self._convert_type("Callable[..., float]", as_instance=True)
right1 = self._convert_type("Callable[[bool], int]")
right2 = self._convert_type("Callable[..., int]")
self.assertNoMatch(left1, right1)
self.assertNoMatch(left2, right1)
self.assertNoMatch(left1, right2)
self.assertNoMatch(left2, right2)
def test_callable_instance_bad_arg_count(self):
left1 = self._convert_type("Callable[[], int]", as_instance=True)
left2 = self._convert_type("Callable[[str, str], int]", as_instance=True)
right = self._convert_type("Callable[[str], int]")
self.assertNoMatch(left1, right)
self.assertNoMatch(left2, right)
def test_callable_instance_bad_arg_type(self):
left1 = self._convert_type("Callable[[bool], Any]", as_instance=True)
left2 = self._convert_type("Callable[[str], Any]", as_instance=True)
right = self._convert_type("Callable[[int], Any]")
self.assertNoMatch(left1, right)
self.assertNoMatch(left2, right)
def test_type_against_callable(self):
left1 = self._convert_type("Type[int]", as_instance=True)
left2 = self._convert_type("Type[str]", as_instance=True)
right1 = self._convert_type("Callable[..., float]")
right2 = self._convert_type("Callable[[], float]")
self.assertMatch(left1, right1)
self.assertMatch(left1, right2)
self.assertNoMatch(left2, right1)
self.assertNoMatch(left2, right2)
def test_anystr_instance_against_anystr(self):
right = self.ctx.convert.name_to_value("typing.AnyStr")
dummy_instance = abstract.Instance(self.ctx.convert.tuple_type, self.ctx)
left = abstract.TypeParameterInstance(right, dummy_instance, self.ctx)
for result in self._match_var(left, right):
self.assertCountEqual(
[(name, var.data) for name, var in result.items()],
[("typing.AnyStr", [left])])
def test_protocol(self):
left1 = self._convert_type("str", as_instance=True)
left2 = self._convert("""
class A:
def lower(self) : ...
""", "A", as_instance=True)
left3 = self._convert_type("int", as_instance=True)
right = self._convert_type("SupportsLower")
self.assertMatch(left1, right)
self.assertMatch(left2, right)
self.assertNoMatch(left3, right)
def test_protocol_iterator(self):
left1 = self._convert_type("Iterator", as_instance=True)
left2 = self._convert("""
class A:
def __next__(self): ...
def __iter__(self): ...
""", "A", as_instance=True)
left3 = self._convert_type("int", as_instance=True)
right = self._convert_type("Iterator")
self.assertMatch(left1, right)
self.assertMatch(left2, right)
self.assertNoMatch(left3, right)
def test_protocol_sequence(self):
left1 = self._convert_type("list", as_instance=True)
left2 = self._convert("""
class A:
def __getitem__(self, i) : ...
def __len__(self): ...
""", "A", as_instance=True)
left3 = self._convert_type("int", as_instance=True)
right = self._convert_type("Sequence")
self.assertMatch(left1, right)
self.assertMatch(left2, right)
self.assertNoMatch(left3, right)
@unittest.skip("Needs to be fixed, tries to match protocol against A")
def test_parameterized_protocol(self):
left1 = self._convert("""
from typing import Iterator
class A:
def __iter__(self) -> Iterator[int] : ...
""", "A", as_instance=True)
left2 = self._convert_type("int", as_instance=True)
right = self._convert_type("Iterable[int]")
self.assertMatch(left1, right)
self.assertNoMatch(left2, right)
def test_no_return(self):
self.assertMatch(self.ctx.convert.no_return, self.ctx.convert.no_return)
def test_empty_against_no_return(self):
self.assertNoMatch(self.ctx.convert.empty, self.ctx.convert.no_return)
def test_no_return_against_class(self):
right = self._convert_type("int")
self.assertNoMatch(self.ctx.convert.no_return, right)
def test_empty_against_parameterized_iterable(self):
left = self.ctx.convert.empty
right = abstract.ParameterizedClass(
self.ctx.convert.list_type,
{abstract_utils.T: abstract.TypeParameter(abstract_utils.T, self.ctx)},
self.ctx)
for subst in self._match_var(left, right):
self.assertSetEqual(set(subst), {abstract_utils.T})
self.assertListEqual(subst[abstract_utils.T].data,
[self.ctx.convert.empty])
def test_list_against_mapping(self):
left = self._convert_type("list", as_instance=True)
right = self.ctx.convert.name_to_value("typing.Mapping")
self.assertNoMatch(left, right)
def test_list_against_parameterized_mapping(self):
left = self._convert_type("list", as_instance=True)
right = abstract.ParameterizedClass(
self.ctx.convert.name_to_value("typing.Mapping"), {
abstract_utils.K:
abstract.TypeParameter(abstract_utils.K, self.ctx),
abstract_utils.V:
abstract.TypeParameter(abstract_utils.V, self.ctx)
}, self.ctx)
self.assertNoMatch(left, right)
class TypeVarTest(MatcherTestBase):
"""Test matching TypeVar against various types."""
def test_match_from_mro(self):
# A TypeParameter never matches anything in match_from_mro, since its mro is
# empty. This test is mostly to make sure we don't crash.
self.assertIsNone(
self.matcher.match_from_mro(
abstract.TypeParameter("T", self.ctx), self.ctx.convert.int_type))
def test_compute_subst(self):
formal_args = [("x", self.ctx.convert.unsolvable)]
x_val = abstract.TypeParameter("T", self.ctx)
arg_dict = {"x": x_val.to_binding(self.ctx.root_node)}
view = {arg_dict["x"].variable: arg_dict["x"]}
subst, bad_param = self.matcher.compute_subst(formal_args, arg_dict, view)
self.assertEqual(subst, {})
self.assertIsNone(bad_param)
def test_compute_subst_no_match(self):
formal_args = [("x", self.ctx.convert.int_type)]
x_val = abstract.TypeParameter("T", self.ctx)
arg_dict = {"x": x_val.to_binding(self.ctx.root_node)}
view = {arg_dict["x"].variable: arg_dict["x"]}
subst, bad_param = self.matcher.compute_subst(formal_args, arg_dict, view)
self.assertIsNone(subst)
self.assertEqual(bad_param.name, "x")
def test_bad_matches(self):
self.assertFalse(
self.matcher.bad_matches(
abstract.TypeParameter("T",
self.ctx).to_variable(self.ctx.root_node),
self.ctx.convert.unsolvable))
def test_bad_matches_no_match(self):
self.assertTrue(
self.matcher.bad_matches(
abstract.TypeParameter("T",
self.ctx).to_variable(self.ctx.root_node),
self.ctx.convert.int_type))
def test_any(self):
self.assertMatch(
abstract.TypeParameter("T", self.ctx), self.ctx.convert.unsolvable)
def test_object(self):
self.assertMatch(
abstract.TypeParameter("T", self.ctx), self.ctx.convert.object_type)
def test_type(self):
self.assertMatch(
abstract.TypeParameter("T", self.ctx), self.ctx.convert.type_type)
def test_parameterized_type(self):
self.assertMatch(
abstract.TypeParameter("T", self.ctx),
abstract.ParameterizedClass(
self.ctx.convert.type_type,
{abstract_utils.T: self.ctx.convert.unsolvable}, self.ctx))
def test_parameterized_type_no_match(self):
self.assertNoMatch(
abstract.TypeParameter("T", self.ctx),
abstract.ParameterizedClass(
self.ctx.convert.type_type,
{abstract_utils.T: self.ctx.convert.int_type}, self.ctx))
def test_nested(self):
self.assertMatch(
abstract.ParameterizedClass(
self.ctx.convert.list_type,
{abstract_utils.T: abstract.TypeParameter("T", self.ctx)},
self.ctx), self.ctx.convert.type_type)
def test_nested_no_match(self):
self.assertNoMatch(
abstract.ParameterizedClass(
self.ctx.convert.list_type,
{abstract_utils.T: abstract.TypeParameter("T", self.ctx)},
self.ctx), self.ctx.convert.list_type)
def test_no_match(self):
self.assertNoMatch(
abstract.TypeParameter("T", self.ctx), self.ctx.convert.int_type)
if __name__ == "__main__":
unittest.main()