Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4c4c701
Add new type object for union types
maggiemoss Jul 10, 2020
07b5c29
Add test.
maggiemoss Jul 10, 2020
32c207b
Add basic tp_* fields to union object
maggiemoss Jul 15, 2020
50b33c2
Squash this commit.
maggiemoss Jul 15, 2020
8901145
Squash this commit.
maggiemoss Jul 15, 2020
6450321
Add union_traverse method.
maggiemoss Jul 15, 2020
8a09a92
Handle Py_None in union.
maggiemoss Jul 16, 2020
e3c80e9
Clean up syntax with Py_None
maggiemoss Jul 16, 2020
165c323
Update getattro method.
maggiemoss Jul 16, 2020
7f0018e
Reduce code duplication when adding union types together.
maggiemoss Jul 17, 2020
cd584d5
Add __or__ method to _SpecialGenericAlias
maggiemoss Jul 17, 2020
ba031c1
Add support for NewType with unions.
maggiemoss Jul 17, 2020
e62de73
Remove old code from abstract.c
maggiemoss Jul 17, 2020
f6ab05b
Fix warning for incompatible pointer type.
maggiemoss Jul 17, 2020
9c20fbc
Add support for union use in subclasses.
maggiemoss Jul 19, 2020
9f48989
Fix code style inconsistencies.
maggiemoss Jul 22, 2020
a0cd651
Change is_not_union -> is_union
maggiemoss Jul 22, 2020
a5c6850
Implement dedup and flatten.
maggiemoss Jul 24, 2020
5255f4f
Add basic tp_repr to unionobject.
maggiemoss Jul 24, 2020
1732fad
Update is_unionable.
maggiemoss Jul 24, 2020
31653dc
Clean up whitespace.
maggiemoss Jul 25, 2020
2ffb050
Update test_types.
maggiemoss Jul 25, 2020
3fed944
Fix style and grammar issues.
maggiemoss Jul 27, 2020
a1c703b
Implement NE for union richcompare.
maggiemoss Jul 27, 2020
c4acc73
Remove is_generic_alias helper method.
maggiemoss Jul 27, 2020
6a8dfe9
Fix negative ref count.
maggiemoss Jul 27, 2020
af59751
Small code style fixes.
maggiemoss Jul 27, 2020
cae1a38
Add unionobject to pythoncore.vcxproj.filters
maggiemoss Jul 27, 2020
0cb47c1
Remove semicolon after PyObject_HEAD
maggiemoss Jul 27, 2020
265be78
Use _PyTuple_Resize instead of variable length array.
maggiemoss Jul 28, 2020
6fddcd6
Add error handling.
maggiemoss Jul 28, 2020
60f023a
Add additional tests for union_richcompare.
maggiemoss Jul 28, 2020
99ba1fd
📜🤖 Added by blurb_it.
blurb-it[bot] Jul 28, 2020
ad945fd
Update error check for PySet_New call.
maggiemoss Jul 30, 2020
0586c41
Allow Any and other _SpecialForm
maggiemoss Jul 30, 2020
e44a657
Update repr to handle NoneType properly.
maggiemoss Jul 30, 2020
ba3110c
Add Union to types.py
maggiemoss Jul 30, 2020
7bec987
Merge branch 'PEP605-implementation' of github.com:MaggieMoss/cpython…
maggiemoss Jul 30, 2020
e052c53
Remove commented out code.
maggiemoss Jul 31, 2020
15e0d29
Update test with nested unions.
maggiemoss Jul 31, 2020
2e31288
Add additional tests for optionals.
maggiemoss Jul 31, 2020
2a12a43
Add support for int | int == int
maggiemoss Jul 31, 2020
7116cd9
Add more error handling to unionobject
maggiemoss Jul 31, 2020
2c0befb
Handle NoneType vs. None
maggiemoss Aug 3, 2020
8d59e44
Don't allow generics in isinstance calls.
maggiemoss Aug 4, 2020
9f014e2
Add errors for union subclass.
maggiemoss Aug 5, 2020
30bb872
Implement code review suggestions.
maggiemoss Aug 12, 2020
6fe4956
Increase code sharing between is_typing_name and is_new_type
maggiemoss Aug 12, 2020
bae3d2a
Add tests, fix reference leaks.
maggiemoss Aug 12, 2020
17a7277
Update tp_flags for unionobject.
maggiemoss Aug 12, 2020
f47bce0
Return int.
maggiemoss Aug 13, 2020
0735352
Fix NoneType comparison side effect.
maggiemoss Aug 13, 2020
b2910f0
Merge branch 'PEP605-implementation' of github.com:MaggieMoss/cpython…
maggiemoss Aug 14, 2020
e9e5a5e
Remove GC flag from unionobject.
maggiemoss Aug 14, 2020
385a338
Implement code review suggestions
maggiemoss Aug 18, 2020
dddc598
Refactor and remove PyUnion_New
maggiemoss Aug 18, 2020
8c06c1d
General cleanup, fix reference leaks, add new test for possible crashes
pablogsal Sep 8, 2020
37699f1
Add newline
pablogsal Sep 8, 2020
47a4cb3
Make unionobject APIs private
pablogsal Sep 9, 2020
7f89abd
Add minor test for GenericAlias
pablogsal Sep 9, 2020
d0be56f
Remove ellipsis from repr.
maggiemoss Sep 9, 2020
59f06f7
Remove commented out test code.
maggiemoss Sep 9, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add test.
  • Loading branch information
maggiemoss committed Jul 10, 2020
commit 07b5c2934c63dead9641d83b749bdb4542bfd29b
16 changes: 16 additions & 0 deletions Lib/test/test_isinstance.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import unittest
import sys
import typing



Expand Down Expand Up @@ -208,6 +209,15 @@ def test_isinstance_abstract(self):
self.assertEqual(False, isinstance(AbstractChild(), Super))
self.assertEqual(False, isinstance(AbstractChild(), Child))

def test_isinstance_with_or_union(self):
self.assertEqual(True, isinstance(AbstractChild(), AbstractChild | int))
self.assertEqual(False, isinstance(None, str | int))
self.assertEqual(True, isinstance(3, str | int))
self.assertEqual(True, isinstance("", str | int))
self.assertEqual(True, isinstance([], typing.List | typing.Tuple))
self.assertEqual(True, isinstance(2, typing.List | int))
self.assertEqual(False, isinstance(2, typing.List | typing.Tuple))

def test_subclass_normal(self):
# normal classes
self.assertEqual(True, issubclass(Super, Super))
Expand All @@ -217,6 +227,8 @@ def test_subclass_normal(self):
self.assertEqual(True, issubclass(Child, Child))
self.assertEqual(True, issubclass(Child, Super))
self.assertEqual(False, issubclass(Child, AbstractSuper))
self.assertEqual(True, issubclass(typing.List, typing.List|typing.Tuple))
self.assertEqual(False, issubclass(int, typing.List|typing.Tuple))

def test_subclass_abstract(self):
# abstract classes
Expand Down Expand Up @@ -251,6 +263,10 @@ def test_isinstance_recursion_limit(self):
# blown
self.assertRaises(RecursionError, blowstack, isinstance, '', str)

# def test_subclass_with_union(self):
# self.assertEqual(True, issubclass(int, int | float | int))
# self.assertEqual(True, issubclass(str, str | Child | str))

def test_issubclass_refcount_handling(self):
# bpo-39382: abstract_issubclass() didn't hold item reference while
# peeking in the bases tuple, in the single inheritance case.
Expand Down
88 changes: 87 additions & 1 deletion Lib/test/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

from test.support import run_with_locale
import collections.abc
from collections import namedtuple
import inspect
import pickle
import locale
import sys
import types
import unittest.mock
import weakref
import typing

class TypesTests(unittest.TestCase):

Expand Down Expand Up @@ -598,7 +600,91 @@ def test_method_descriptor_types(self):
self.assertIsInstance(int.from_bytes, types.BuiltinMethodType)
self.assertIsInstance(int.__new__, types.BuiltinMethodType)


def test_or_types_operator(self):
self.assertEqual(int | str, typing.Union[int, str])
self.assertEqual(int | None, typing.Union[int, None])
self.assertEqual(None | int, typing.Union[int, None])
self.assertEqual(int | str | list, typing.Union[int, str, list])
self.assertEqual(int | (str | list), typing.Union[int, str, list])
self.assertEqual(typing.List | typing.Tuple, typing.Union[typing.List, typing.Tuple])
self.assertEqual(typing.List[int] | typing.Tuple[int], typing.Union[typing.List[int], typing.Tuple[int]])
self.assertEqual(typing.List[int] | None, typing.Union[typing.List[int], None])
self.assertEqual(None | typing.List[int], typing.Union[None, typing.List[int]])
self.assertEqual(
BaseException | \
bool | \
bytes | \
complex | \
float | \
int | \
list | \
map | \
set,
typing.Union[
BaseException,
bool,
bytes,
complex,
float,
int,
list,
map,
set,
])
with self.assertRaises(TypeError):
int | 3
with self.assertRaises(TypeError):
3 | int

def test_or_type_operator_with_TypeVar(self):
TV = typing.TypeVar('T')
assert TV | str == typing.Union[TV, str]
assert str | TV == typing.Union[str, TV]

def test_or_type_operator_with_forward(self):
T = typing.TypeVar('T')
ForwardAfter = T | 'Forward'
ForwardBefore = 'Forward' | T
def forward_after(x: ForwardAfter[int]) -> None: ...
def forward_before(x: ForwardBefore[int]) -> None: ...
assert typing.get_args(typing.get_type_hints(forward_after)['x']) == (int, Forward)
assert typing.get_args(typing.get_type_hints(forward_before)['x']) == (int, Forward)

def test_or_type_operator_with_Protocol(self):
class Proto(typing.Protocol):
def meth(self) -> int:
...
assert Proto | str == typing.Union[Proto, str]

def test_or_type_operator_with_Alias(self):
assert list | str == typing.Union[list, str]
assert typing.List | str == typing.Union[typing.List, str]

def test_or_type_operator_with_NamedTuple(self):
NT=namedtuple('A', ['B', 'C', 'D'])
assert NT | str == typing.Union[NT,str]

def test_or_type_operator_with_TypedDict(self):
class Point2D(typing.TypedDict):
x: int
y: int
label: str
assert Point2D | str == typing.Union[Point2D, str]

def test_or_type_operator_with_NewType(self):
UserId = typing.NewType('UserId', int)
assert UserId | str == typing.Union[UserId, str]

def test_or_type_operator_with_IO(self):
assert typing.IO | str == typing.Union[typing.IO, str]

class Forward: ...
# T = typing.TypeVar('T')
# ForwardAfter = T | 'Forward'
# ForwardBefore = 'Forward' | T
# def forward_after(x: ForwardAfter[int]) -> None: ...
# def forward_before(x: ForwardBefore[int]) -> None: ...
# class Forward: ...
class MappingProxyTests(unittest.TestCase):
mappingproxy = types.MappingProxyType

Expand Down
10 changes: 5 additions & 5 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@ def test_subclass_error(self):
issubclass(int, Union)
with self.assertRaises(TypeError):
issubclass(Union, int)
with self.assertRaises(TypeError):
issubclass(int, Union[int, str])
# with self.assertRaises(TypeError):
# issubclass(int, Union[int, str])
with self.assertRaises(TypeError):
issubclass(Union[int, str], int)

Expand Down Expand Up @@ -347,9 +347,9 @@ def test_empty(self):
with self.assertRaises(TypeError):
Union[()]

def test_union_instance_type_error(self):
with self.assertRaises(TypeError):
isinstance(42, Union[int, str])
# def test_union_instance_type_error(self):
# with self.assertRaises(TypeError):
# isinstance(42, Union[int, str])

def test_no_eval_union(self):
u = Union[int, str]
Expand Down
14 changes: 14 additions & 0 deletions Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,13 @@ def __init__(self, name, *constraints, bound=None,
if def_mod != 'typing':
self.__module__ = def_mod

def __or__(self,right):
return Union[self,right]
def __ror__(self,right):
return Union[self,right]
def __invert__(self):
return Union[self,None]

def __repr__(self):
if self.__covariant__:
prefix = '+'
Expand Down Expand Up @@ -685,6 +692,13 @@ def __eq__(self, other):
def __hash__(self):
return hash((self.__origin__, self.__args__))

def __or__(self,right):
return Union[self,right]
def __ror__(self,right):
return Union[self,right]
def __invert__(self):
return Union[self,None]

@_tp_cache
def __getitem__(self, params):
if self.__origin__ in (Generic, Protocol):
Expand Down
1 change: 1 addition & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ OBJECT_OBJS= \
Objects/typeobject.o \
Objects/unicodeobject.o \
Objects/unicodectype.o \
Objects/unionobject.o \
Objects/weakrefobject.o

##########################################################################
Expand Down
40 changes: 38 additions & 2 deletions Objects/abstract.c
Original file line number Diff line number Diff line change
Expand Up @@ -2469,6 +2469,40 @@ abstract_issubclass(PyObject *derived, PyObject *cls)
}
}

static PyObject*
union_to_tuple(PyObject* cls) {
//printf("union_to_tuple");
if (!strcmp(Py_TYPE(cls)->tp_name,"_GenericAlias")) {
PyObject* origin = PyObject_GetAttrString(cls, "__origin__");
//printf("origin = %p\n",origin);
if (origin == NULL) {
printf("origin == NULL, return cls");
return cls;
}
//printf("X origin = %s\n",Py_TYPE(cls)->tp_name);
if (PyObject_HasAttrString(origin, "_name")) {
PyObject* name = PyObject_GetAttrString(origin, "_name");
if (name==NULL) {
printf("_name = NULL\n");
Py_DECREF(origin);
return cls;
}
//printf("name = %s\n",Py_TYPE(name)->tp_name);
const char* data = (char*)PyUnicode_1BYTE_DATA(name);
//printf("DATA=%s\n",data);
if (data != NULL && !strcmp(data,"Union")) {
PyObject* new_cls = PyObject_GetAttrString(cls, "__args__");
if (new_cls != NULL) {
cls = new_cls;
}
}
Py_DECREF(name);
}
Py_DECREF(origin);
}
return cls;
}

static int
check_class(PyObject *cls, const char *error)
{
Expand Down Expand Up @@ -2511,7 +2545,7 @@ object_isinstance(PyObject *inst, PyObject *cls)
}
else {
if (!check_class(cls,
"isinstance() arg 2 must be a type or tuple of types"))
"isinstance() arg 2 must be a type, a tuple of types or union"))
return -1;
retval = _PyObject_LookupAttrId(inst, &PyId___class__, &icls);
if (icls != NULL) {
Expand All @@ -2537,7 +2571,7 @@ object_recursive_isinstance(PyThreadState *tstate, PyObject *inst, PyObject *cls
if (PyType_CheckExact(cls)) {
return object_isinstance(inst, cls);
}

cls = union_to_tuple(cls);
if (PyTuple_Check(cls)) {
/* Not a general sequence -- that opens up the road to
recursion and stack overflow. */
Expand Down Expand Up @@ -2626,6 +2660,8 @@ object_issubclass(PyThreadState *tstate, PyObject *derived, PyObject *cls)
return recursive_issubclass(derived, cls);
}

cls = union_to_tuple(cls);

if (PyTuple_Check(cls)) {

if (_Py_EnterRecursiveCall(tstate, " in __subclasscheck__")) {
Expand Down
43 changes: 42 additions & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3741,6 +3741,47 @@ type_is_gc(PyTypeObject *type)
return type->tp_flags & Py_TPFLAGS_HEAPTYPE;
}

static PyObject *
type_or(PyTypeObject* self, PyObject* param) {
PyObject* typing=PyImport_ImportModule("typing");
PyTypeObject* genericAlias = (PyTypeObject*)PyObject_GetAttrString(typing,"_GenericAlias");
PyTypeObject* typeVar = (PyTypeObject*)PyObject_GetAttrString(typing,"TypeVar");

// Check param is a PyType or GenericAlias
if ((param == NULL) ||
(
(param != Py_None) &&
! PyType_IsSubtype(genericAlias, Py_TYPE(param)) &&
! PyType_IsSubtype(typeVar, Py_TYPE(param)) &&
(PyObject_IsInstance(param, (PyObject *) &PyType_Type) != 1)
)
) {
PyErr_SetString(PyExc_TypeError, "'type' expected");
Py_DECREF(typeVar);
Py_DECREF(genericAlias);
Py_DECREF(typing);
return NULL;
}

// 1. Create a tuple with types
PyObject *tuple=PyTuple_Pack(2,self, param);
// 2. Create Union with tuple
PyObject* unionType = PyObject_GetAttrString(typing,"Union");
PyObject *newUnion=PyObject_GetItem(unionType, tuple);
// 3. Clean memory
Py_DECREF(typeVar);
Py_DECREF(genericAlias);
Py_DECREF(typing);
Py_DECREF(unionType);
Py_DECREF(tuple);
// 4. Return instance
return newUnion;
}

static PyNumberMethods type_as_number = {
.nb_or = (binaryfunc)type_or, // Add __or__ function
};

PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"type", /* tp_name */
Expand All @@ -3752,7 +3793,7 @@ PyTypeObject PyType_Type = {
0, /* tp_setattr */
0, /* tp_as_async */
(reprfunc)type_repr, /* tp_repr */
0, /* tp_as_number */
&type_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
Expand Down
1 change: 1 addition & 0 deletions PCbuild/pythoncore.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@
<ClCompile Include="..\Objects\typeobject.c" />
<ClCompile Include="..\Objects\unicodectype.c" />
<ClCompile Include="..\Objects\unicodeobject.c" />
<ClCompile Include="..\Objects\unionobject.c" />
<ClCompile Include="..\Objects\weakrefobject.c" />
<ClCompile Include="..\Parser\myreadline.c" />
<ClCompile Include="..\Parser\tokenizer.c" />
Expand Down