def testUnionTypeNe(self): u1 = pytd.UnionType((self.int, self.float)) u2 = pytd.UnionType((self.float, self.int, self.none_type)) self.assertNotEqual(u1, u2) self.assertNotEqual(u2, u1) self.assertEqual(u1.type_list, (self.int, self.float)) self.assertEqual(u2.type_list, (self.float, self.int, self.none_type))
def p_type_or(self, p): """type : type OR type""" # This rule depends on precedence specification if (isinstance(p[1], pytd.UnionType) and isinstance(p[3], pytd.NamedType)): p[0] = pytd.UnionType(p[1].type_list + (p[3], )) elif (isinstance(p[1], pytd.NamedType) and isinstance(p[3], pytd.UnionType)): # associative p[0] = pytd.UnionType((p[1], ) + p[3].type_list) else: p[0] = pytd.UnionType((p[1], p[3]))
def JoinTypes(types): """Combine a list of types into a union type, if needed. Leaves singular return values alone, or wraps a UnionType around them if there are multiple ones. Arguments: types: A list of types. This list might contain other UnionTypes. If so, they are flattened. Returns: A type that represents the union of the types passed in. Order is preserved. Raises: ValueError: If you pass a malformed (i.e., empty) list. """ if not types: raise ValueError("Can't join empty type list") queue = collections.deque(types) seen = set() new_types = [] while queue: t = queue.popleft() if isinstance(t, pytd.UnionType): queue.extendleft(reversed(t.type_list)) elif t not in seen: new_types.append(t) seen.add(t) if len(new_types) == 1: return new_types.pop() else: return pytd.UnionType( tuple(new_types)) # tuple() to make unions hashable
def testComplexCombinedType(self): """Test parsing a type with both union and intersection.""" data1 = r"def foo(a: Foo or Bar and Zot) -> object" data2 = r"def foo(a: Foo or (Bar and Zot)) -> object" result1 = self.Parse(data1) result2 = self.Parse(data2) f = pytd.Function( name="foo", signatures=(pytd.Signature( params=( pytd.Parameter( name="a", type=pytd.UnionType( type_list=( pytd.NamedType("Foo"), pytd.IntersectionType( type_list=( pytd.NamedType("Bar"), pytd.NamedType("Zot")))) ) ),), return_type=pytd.NamedType("object"), template=(), has_optional=False, exceptions=()),)) self.assertEqual(f, result1.Lookup("foo")) self.assertEqual(f, result2.Lookup("foo"))
def testOrder(self): # pytd types' primary sort key is the class name, second sort key is # the contents when interpreted as a (named)tuple. nodes = [ pytd.AnythingType(), pytd.GenericType(self.list, (self.int, )), pytd.NamedType("int"), pytd.NothingType(), pytd.UnionType(self.float), pytd.UnionType(self.int) ] for n1, n2 in zip(nodes[:-1], nodes[1:]): self.assertLess(n1, n2) self.assertLessEqual(n1, n2) self.assertGreater(n2, n1) self.assertGreaterEqual(n2, n1) for p in itertools.permutations(nodes): self.assertEquals(list(sorted(p)), nodes)
def testUnionNone(self): """Type checking of function with None union. """ self.assertEquals(3, union.IntOrNone(3)) self.assertEquals(None, union.IntOrNone(None)) with self.assertRaises(checker.CheckTypeAnnotationError) as context: union.IntOrNone("error") expected_param = checker.ParamTypeErrorMsg( "IntOrNone", "a", str, pytd.UnionType([int, type(None)])) expected_ret = checker.ReturnTypeErrorMsg( "IntOrNone", str, pytd.UnionType([int, type(None)])) [actual_param, actual_ret] = context.exception.args[0] self.assertEquals(expected_param, actual_param) self.assertEquals(expected_ret, actual_ret)
def setUp(self): self.bool = pytd.ClassType("bool") self.dict = pytd.ClassType("dict") self.float = pytd.ClassType("float") self.int = pytd.ClassType("int") self.list = pytd.ClassType("list") self.none_type = pytd.ClassType("NoneType") self.object = pytd.ClassType("object") self.str = pytd.ClassType("str") self.tuple = pytd.ClassType("tuple") self.intorfloat = pytd.UnionType((self.float, self.int)) self.intorstr = pytd.UnionType((self.int, self.str)) # Make get_pytd load _builtin_pytds self.builtin_pytds = parse.utils.GetBuiltins() for ty in (self.int, self.none_type, self.float, self.intorfloat, self.tuple, self.str, self.object, self.list, self.dict, self.bool): visitors.FillInClasses(ty, self.builtin_pytds)
def testUnionInReturnError(self): """Typechecking fct with union in return type (error). """ with self.assertRaises(checker.CheckTypeAnnotationError) as context: union.UnionReturnError() expected = checker.ReturnTypeErrorMsg("UnionReturnError", tuple, pytd.UnionType([int, list])) [actual] = context.exception.args[0] self.assertEquals(expected, actual)
def testUnionError(self): """Type checking of function with union args (error). """ with self.assertRaises(checker.CheckTypeAnnotationError) as context: union.IntOrFloat("1", 2) expected = checker.ParamTypeErrorMsg("IntOrFloat", "a", str, pytd.UnionType([int, float])) [actual] = context.exception.args[0] self.assertEquals(expected, actual)
def testListConcatMultiType(self): ty = self.InferDedent(""" def f(): x = [] x.append(1) x.append("str") return x + [1.3] + x f() """) self.assertHasOnlySignatures( ty.Lookup("f"), ((), pytd.GenericType(self.list, (pytd.UnionType( (self.int, self.float, self.str)), ))))
def ConvertToType(module, type_node): """Helper for converting a type node to a valid Python type. Args: module: The module to look up symbols/types type_node: A type node to convert into a python type Returns: A valid Python type. Note that None is considered a type in the declaration language, but a value in Python. So a string None is converted to a NoneType. We use the module object to look up potential type definitions defined inside that module. Raises: TypeError: if the type node passed is not supported/unknown """ # TODO: Convert this to a visitor. # clean up str if isinstance(type_node, pytd.NamedType): if type_node.name == "None": return types.NoneType elif type_node.name == "generator": return types.GeneratorType else: res = _EvalWithModuleContext(type_node.name, module) assert isinstance(res, type), (type_node.name, repr(res)) return res elif isinstance(type_node, pytd.UnionType): return pytd.UnionType([ConvertToType(module, t) for t in type_node.type_list]) elif isinstance(type_node, pytd.IntersectionType): return pytd.IntersectionType([ConvertToType(module, t) for t in type_node.type_list]) elif isinstance(type_node, pytd.GenericType): return pytd.GenericType(ConvertToType(module, type_node.base_type), type_node.parameters) elif isinstance(type_node, pytd.HomogeneousContainerType): return pytd.HomogeneousContainerType(ConvertToType(module, type_node.base_type), ConvertToType(module, type_node.element_type)) else: raise TypeError("Unknown type of type_node: {!r}".format(type_node))
def testSimpleArgNoneAble(self): """Type checking of function with none-able argument.""" # should work with no exceptions self.assertEquals(0, union.StrToInt(None)) self.assertEquals(10, union.StrToInt("10")) with self.assertRaises(checker.CheckTypeAnnotationError) as context: union.StrToInt(10) # can only pass str? so this should be an error expected = checker.ParamTypeErrorMsg("StrToInt", "s", int, pytd.UnionType([str, type(None)])) [actual] = context.exception.args[0] self.assertEquals(expected, actual)
def testJoinTypes(self): """Test that JoinTypes() does recursive flattening.""" n1, n2, n3, n4, n5, n6 = [pytd.NamedType("n%d" % i) for i in xrange(6)] # n1 or (n2 or (n3)) nested1 = pytd.UnionType( (n1, pytd.UnionType((n2, pytd.UnionType((n3, )))))) # ((n4) or n5) or n6 nested2 = pytd.UnionType((pytd.UnionType((pytd.UnionType( (n4, )), n5)), n6)) joined = optimize.JoinTypes([nested1, nested2]) self.assertEquals(joined.type_list, (n1, n2, n3, n4, n5, n6))
def testUnionWithClassTypes(self): """Type checking of function with union and class types. """ self.assertEquals(None, union.AppleOrBananaOrOrange(simple.Apple())) self.assertEquals(None, union.AppleOrBananaOrOrange(simple.Banana())) self.assertEquals(None, union.AppleOrBananaOrOrange(simple.Orange())) with self.assertRaises(checker.CheckTypeAnnotationError) as context: union.AppleOrBananaOrOrange(42) expected = checker.ParamTypeErrorMsg( "AppleOrBananaOrOrange", "f", int, pytd.UnionType([simple.Apple, simple.Banana, simple.Orange])) [actual] = context.exception.args[0] self.assertEquals(expected, actual)
def VisitUnionType(self, union): """Push unions down into containers. This collects similar container types in unions and merges them into single instances with the union type pushed down to the element_type level. Arguments: union: A pytd.Union instance. Might appear in a parameter, a return type, a constant type, etc. Returns: A simplified pytd.Union. """ if not any(isinstance(t, pytd.GenericType) for t in union.type_list): # Optimization: If we're not going to change anything, return original. return union union = utils.JoinTypes(union.type_list) # flatten if not isinstance(union, pytd.UnionType): union = pytd.UnionType((union, )) collect = {} for t in union.type_list: if isinstance(t, pytd.GenericType): if t.base_type in collect: collect[t.base_type] = tuple( utils.JoinTypes([p1, p2]) for p1, p2 in zip(collect[t.base_type], t.parameters)) else: collect[t.base_type] = t.parameters result = pytd.NothingType() done = set() for t in union.type_list: if isinstance(t, pytd.GenericType): if t.base_type in done: continue # already added add = t.Replace(parameters=collect[t.base_type]) done.add(t.base_type) else: add = t result = utils.JoinTypes([result, add]) return result
def JoinTypes(types): """Combine a list of types into a union type, if needed. Leaves singular return values alone, or wraps a UnionType around them if there are multiple ones, or if there are no elements in the list (or only NothingType) return NothingType. Arguments: types: A list of types. This list might contain other UnionTypes. If so, they are flattened. Returns: A type that represents the union of the types passed in. Order is preserved. """ queue = collections.deque(types) seen = set() new_types = [] while queue: t = queue.popleft() if isinstance(t, pytd.UnionType): queue.extendleft(reversed(t.type_list)) elif isinstance(t, pytd.NothingType): pass elif t not in seen: new_types.append(t) seen.add(t) if len(new_types) == 1: return new_types.pop() elif any(isinstance(t, pytd.AnythingType) for t in new_types): return pytd.AnythingType() elif new_types: return pytd.UnionType( tuple(new_types)) # tuple() to make unions hashable else: return pytd.NothingType()
def VisitUnionType(self, node): return pytd.UnionType(tuple(sorted(node.type_list)))
def p_type_or(self, p): """type : type OR type""" # This rule depends on precedence specification # UnionType flattens any contained UnionType's p[0] = pytd.UnionType((p[1], p[3]))