Skip to content

Commit

Permalink
[mypyc] Add mypyc run tests for singledispatch (#10589)
Browse files Browse the repository at this point in the history
Add some run tests to run-singledispatch.test for situations that are
likely to be common or are likely to be edge cases that we don't handle
correctly.

Change the tests that were previously marked as skipped to xfails so
that the tests still get run even if they aren't passing.
  • Loading branch information
pranavrajpal authored Jun 14, 2021
1 parent 6ab0efc commit 9d92fba
Show file tree
Hide file tree
Showing 2 changed files with 388 additions and 0 deletions.
387 changes: 387 additions & 0 deletions mypyc/test-data/run-singledispatch.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@
# Test cases related to the functools.singledispatch decorator
# Most of these tests are marked as xfails because mypyc doesn't support singledispatch yet
# (These tests will be re-enabled when mypyc supports singledispatch)

[case testSpecializedImplementationUsed-xfail]
from functools import singledispatch

@singledispatch
def fun(arg) -> bool:
return False

@fun.register
def fun_specialized(arg: str) -> bool:
return True

def test_specialize() -> None:
assert fun('a')
assert not fun(3)

[case testSubclassesOfExpectedTypeUseSpecialized-xfail]
from functools import singledispatch
class A: pass
class B(A): pass

@singledispatch
def fun(arg) -> bool:
return False

@fun.register
def fun_specialized(arg: A) -> bool:
return True

def test_specialize() -> None:
assert fun(B())
assert fun(A())

[case testSuperclassImplementationNotUsedWhenSubclassHasImplementation-xfail]
from functools import singledispatch
class A: pass
class B(A): pass

@singledispatch
def fun(arg) -> bool:
# shouldn't be using this
assert False

@fun.register
def fun_specialized(arg: A) -> bool:
return False

@fun.register
def fun_specialized2(arg: B) -> bool:
return True

def test_specialize() -> None:
assert fun(B())
assert not fun(A())

[case testMultipleUnderscoreFunctionsIsntError-xfail]
from functools import singledispatch

@singledispatch
def fun(arg) -> str:
return 'default'

@fun.register
def _(arg: str) -> str:
return 'str'

@fun.register
def _(arg: int) -> str:
return 'int'

def test_singledispatch() -> None:
assert fun(0) == 'int'
assert fun('a') == 'str'
assert fun({'a': 'b'}) == 'default'

[case testCanRegisterCompiledClasses-xfail]
from functools import singledispatch
class A: pass

@singledispatch
def fun(arg) -> bool:
return False
@fun.register
def fun_specialized(arg: A) -> bool:
return True

def test_singledispatch() -> None:
assert fun(A())
assert not fun(1)

[case testTypeUsedAsArgumentToRegister]
from functools import singledispatch

@singledispatch
def fun(arg) -> bool:
return False

@fun.register(int)
def fun_specialized(arg) -> bool:
return True

def test_singledispatch() -> None:
assert fun(1)
assert not fun('a')

[case testUseRegisterAsAFunction]
from functools import singledispatch

@singledispatch
def fun(arg) -> bool:
return False

def fun_specialized_impl(arg) -> bool:
return True

fun.register(int, fun_specialized_impl)

def test_singledispatch() -> None:
assert fun(0)
assert not fun('a')

[case testRegisterDoesntChangeFunction-xfail]
from functools import singledispatch

@singledispatch
def fun(arg) -> bool:
return False

@fun.register
def fun_specialized(arg: int) -> bool:
return True

def test_singledispatch() -> None:
assert fun_specialized('a')

[case testTypeAnnotationsDisagreeWithRegisterArgument-xfail]
from functools import singledispatch

@singledispatch
def fun(arg) -> bool:
return False

@fun.register(int)
def fun_specialized(arg: str) -> bool:
return True

def test_singledispatch() -> None:
assert fun(3) # type: ignore
assert not fun('a')

[case testNoneIsntATypeWhenUsedAsArgumentToRegister-xfail]
from functools import singledispatch

@singledispatch
def fun(arg) -> bool:
return False

try:
@fun.register(None)
def fun_specialized(arg) -> bool:
return True
except TypeError:
pass

[case testRegisteringTheSameFunctionSeveralTimes]
from functools import singledispatch

@singledispatch
def fun(arg) -> bool:
return False

@fun.register(int)
@fun.register(str)
def fun_specialized(arg) -> bool:
return True

def test_singledispatch() -> None:
assert fun(0)
assert fun('a')
assert not fun([1, 2])

[case testTypeIsAnABC-xfail]
from functools import singledispatch
from collections.abc import Mapping

@singledispatch
def fun(arg) -> bool:
return False

@fun.register
def fun_specialized(arg: Mapping) -> bool:
return True

def test_singledispatch() -> None:
assert not fun(1)
assert fun({'a': 'b'})

[case testArgumentDoesntMatchTypeOfAnySpecializedImplementationsOrDefaultImplementation-xfail]
from functools import singledispatch
class A: pass
class B(A): pass

@singledispatch
def fun(arg: A) -> bool:
return False

@fun.register
def fun_specialized(arg: B) -> bool:
return True

def test_singledispatch() -> None:
assert fun(B())
assert fun(A())
assert not fun([1, 2])


[case testSingleDispatchMethod-xfail]
from functools import singledispatchmethod
class A:
@singledispatchmethod
def fun(self, arg) -> str:
return 'default'

@fun.register
def fun_int(self, arg: int) -> str:
return 'int'

@fun.register
def fun_str(self, arg: str) -> str:
return 'str'

def test_singledispatchmethod() -> None:
x = A()
assert x.fun(5) == 'int'
assert x.fun('a') == 'str'
assert x.fun([1, 2]) == 'default'

[case testSingleDispatchMethodWithOtherDecorator-xfail]
from functools import singledispatchmethod
class A:
@singledispatchmethod
@staticmethod
def fun(arg) -> str:
return 'default'

@fun.register
@staticmethod
def fun_int(arg: int) -> str:
return 'int'

@fun.register
@staticmethod
def fun_str(arg: str) -> str:
return 'str'

def test_singledispatchmethod() -> None:
x = A()
assert x.fun(5) == 'int'
assert x.fun('a') == 'str'
assert x.fun([1, 2]) == 'default'

[case testSingledispatchTreeSumAndEqual-xfail]
from functools import singledispatch

class Tree:
pass
class Leaf(Tree):
pass
class Node(Tree):
def __init__(self, value: int, left: Tree, right: Tree) -> None:
self.value = value
self.left = left
self.right = right

@singledispatch
def calc_sum(x: Tree) -> int:
raise TypeError('invalid type for x')

@calc_sum.register
def _(x: Leaf) -> int:
return 0

@calc_sum.register
def _(x: Node) -> int:
return x.value + calc_sum(x.left) + calc_sum(x.right)

@singledispatch
def equal(to_compare: Tree, known: Tree) -> bool:
raise TypeError('invalid type for x')

@equal.register
def _(to_compare: Leaf, known: Tree) -> bool:
return isinstance(known, Leaf)

@equal.register
def _(to_compare: Node, known: Tree) -> bool:
if isinstance(known, Node):
if to_compare.value != known.value:
return False
else:
return equal(to_compare.left, known.left) and equal(to_compare.right, known.right)
return False

def build(n: int) -> Tree:
if n == 0:
return Leaf()
return Node(n, build(n - 1), build(n - 1))

def test_sum_and_equal():
tree = build(5)
tree2 = build(5)
tree2.right.right.right.value = 10
assert calc_sum(tree) == 57
assert calc_sum(tree2) == 62
assert equal(tree, tree)
assert not equal(tree, tree2)
tree3 = build(4)
assert not equal(tree, tree3)

[case testSimulateMypySingledispatch-xfail]
from functools import singledispatch
from mypy_extensions import trait
from typing import Iterator, Union, TypeVar, Any, List, Type
# based on use of singledispatch in stubtest.py
class Error:
def __init__(self, msg: str) -> None:
self.msg = msg

@trait
class Node: pass

class MypyFile(Node): pass
class TypeInfo(Node): pass


class SymbolNode(Node): pass
class Expression(Node): pass
class TypeVarLikeExpr(SymbolNode, Expression): pass
class TypeVarExpr(TypeVarLikeExpr): pass

class Missing: pass
MISSING = Missing()

T = TypeVar("T")

MaybeMissing = Union[T, Missing]

@singledispatch
def verify(stub: Node, a: MaybeMissing[Any], b: List[str]) -> Iterator[Error]:
yield Error('unknown node type')

@verify.register(MypyFile)
def verify_mypyfile(stub: MypyFile, a: MaybeMissing[int], b: List[str]) -> Iterator[Error]:
if isinstance(a, Missing):
yield Error("shouldn't be missing")
return
if not isinstance(a, int):
# this check should be unnecessary because of the type signature and the previous check,
# but stubtest.py has this check
yield Error("should be an int")
return
yield from verify(TypeInfo(), str, ['abc', 'def'])

@verify.register(TypeInfo)
def verify_typeinfo(stub: TypeInfo, a: MaybeMissing[Type[Any]], b: List[str]) -> Iterator[Error]:
yield Error('in TypeInfo')
yield Error('hello')

@verify.register(TypeVarExpr)
def verify_typevarexpr(stub: TypeVarExpr, a: MaybeMissing[Any], b: List[str]) -> Iterator[Error]:
if False:
yield None

def verify_list(stub, a, b) -> List[str]:
"""Helper function that converts iterator of errors to list of messages"""
return list(err.msg for err in verify(stub, a, b))

def test_verify() -> None:
assert verify_list(Node(), 'a', ['a', 'b']) == ['unknown node type']
assert verify_list(MypyFile(), 'a', ['a', 'b']) == ['should be an int']
assert verify_list(MypyFile(), MISSING, ['a', 'b']) == ["shouldn't be missing"]
assert verify_list(MypyFile(), 5, ['a', 'b']) == ['in TypeInfo', 'hello']
assert verify_list(TypeInfo(), str, ['a', 'b']) == ['in TypeInfo', 'hello']
assert verify_list(TypeVarExpr(), 'a', ['x', 'y']) == ['x', 'y']
1 change: 1 addition & 0 deletions mypyc/test/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
'run-bench.test',
'run-mypy-sim.test',
'run-dunders.test',
'run-singledispatch.test'
]
if sys.version_info >= (3, 7):
files.append('run-python37.test')
Expand Down

0 comments on commit 9d92fba

Please sign in to comment.