Skip to content

Commit 13cd909

Browse files
committed
Support NewType in stubs.
This started as an attempt to support the stubs added to the pytype exclude list in python/typeshed#4785. Those stubs turned out to be a bit of a hairball, but supporting NewType seems useful anyway. Resolves #597. PiperOrigin-RevId: 345364292
1 parent 866f1a0 commit 13cd909

File tree

8 files changed

+70
-1
lines changed

8 files changed

+70
-1
lines changed

pytype/pyi/lexer.lex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ typedef pytype::parser::token t;
103103
"typing.NamedTuple" { return t::NAMEDTUPLE; }
104104
"namedtuple" { return t::COLL_NAMEDTUPLE; }
105105
"collections.namedtuple" { return t::COLL_NAMEDTUPLE; }
106+
"NewType" { return t::NEWTYPE; }
107+
"typing.NewType" { return t::NEWTYPE; }
106108
"TypedDict" { return t::TYPEDDICT; }
107109
"typing.TypedDict" { return t::TYPEDDICT; }
108110
"typing_extensions.TypedDict" { return t::TYPEDDICT; }

pytype/pyi/lexer_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ def test_typeddict(self):
302302
self.check(["TYPEDDICT"], "typing.TypedDict")
303303
self.check(["TYPEDDICT"], "typing_extensions.TypedDict")
304304

305+
def test_newtype(self):
306+
self.check(["NEWTYPE"], "NewType")
307+
self.check(["NEWTYPE"], "typing.NewType")
308+
305309

306310
if __name__ == "__main__":
307311
unittest.main()

pytype/pyi/parser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ enum CallSelector {
3434
kNewConstant,
3535
kNewFunction,
3636
kNewNamedTuple,
37+
kNewNewType,
3738
kNewTypedDict,
3839
kRegisterClassName,
3940
kAddTypeVar,

pytype/pyi/parser.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,27 @@ def new_named_tuple(self, base_name, fields):
10811081
self._generated_classes[base_name].append(nt_class)
10821082
return pytd.NamedType(nt_class.name)
10831083

1084+
def new_new_type(self, name, typ):
1085+
"""Returns a type for a NewType."""
1086+
name = _handle_string_literal(name)
1087+
args = [("self", pytd.AnythingType(), None), ("val", typ, None)]
1088+
ret = pytd.NamedType("NoneType")
1089+
methods = _merge_method_signatures(
1090+
[self.new_function((), False, "__init__", args, ret, ())])
1091+
cls_name = escape.pack_newtype_base_class(
1092+
name, len(self._generated_classes[name]))
1093+
cls = pytd.Class(name=cls_name,
1094+
metaclass=None,
1095+
parents=(typ,),
1096+
methods=tuple(methods),
1097+
constants=(),
1098+
decorators=(),
1099+
classes=(),
1100+
slots=None,
1101+
template=())
1102+
self._generated_classes[name].append(cls)
1103+
return pytd.NamedType(cls_name)
1104+
10841105
def new_typed_dict(self, name, items, total):
10851106
"""Returns a type for a TypedDict.
10861107

pytype/pyi/parser.yy

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ int pytypelex(pytype::parser::semantic_type* lvalp, pytype::location* llocp,
7070

7171
/* Reserved words. */
7272
%token ASYNC CLASS DEF ELSE ELIF IF OR AND PASS IMPORT FROM AS RAISE
73-
%token NOTHING NAMEDTUPLE COLL_NAMEDTUPLE TYPEDDICT TYPEVAR
73+
%token NOTHING NAMEDTUPLE COLL_NAMEDTUPLE NEWTYPE TYPEDDICT TYPEVAR
7474
/* Punctuation. */
7575
%token ARROW ELLIPSIS EQ NE LE GE
7676
/* Other. */
@@ -435,6 +435,9 @@ from_item
435435
| COLL_NAMEDTUPLE {
436436
$$ = PyString_FromString("namedtuple");
437437
}
438+
| NEWTYPE {
439+
$$ = PyString_FromString("NewType");
440+
}
438441
| TYPEDDICT {
439442
$$ = PyString_FromString("TypedDict");
440443
}
@@ -445,6 +448,7 @@ from_item
445448
$$ = PyString_FromString("*");
446449
}
447450
| NAME AS NAME { $$ = Py_BuildValue("(NN)", $1, $3); }
451+
| NEWTYPE AS NEWTYPE { $$ = PyString_FromString("NewType"); }
448452
;
449453

450454
alias_or_constant
@@ -504,6 +508,7 @@ funcdef
504508
funcname
505509
: NAME { $$ = $1; }
506510
| COLL_NAMEDTUPLE { $$ = PyString_FromString("namedtuple"); }
511+
| NEWTYPE { $$ = PyString_FromString("NewType"); }
507512
| TYPEDDICT { $$ = PyString_FromString("TypedDict"); }
508513
;
509514

@@ -652,6 +657,10 @@ type
652657
$$ = ctx->Call(kNewNamedTuple, "(NN)", $3, $5);
653658
CHECK($$, @$);
654659
}
660+
| NEWTYPE '(' STRING ',' type maybe_comma ')' {
661+
$$ = ctx->Call(kNewNewType, "(NN)", $3, $5);
662+
CHECK($$, @$);
663+
}
655664
| TYPEDDICT '(' STRING ',' typed_dict_fields maybe_typed_dict_kwarg ')' {
656665
$$ = ctx->Call(kNewTypedDict, "(NNN)", $3, $5, $6);
657666
CHECK($$, @$);

pytype/pyi/parser_ext.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ static const SelectorEntry<CallSelector> call_attributes[] = {
4545
{kNewConstant, "new_constant"},
4646
{kNewFunction, "new_function"},
4747
{kNewNamedTuple, "new_named_tuple"},
48+
{kNewNewType, "new_new_type"},
4849
{kNewTypedDict, "new_typed_dict"},
4950
{kRegisterClassName, "register_class_name"},
5051
{kAddTypeVar, "add_type_var"},
@@ -294,6 +295,7 @@ static void add_tokens_dict(PyObject* module) {
294295
add_token(tokens, "NOTHING", t::NOTHING);
295296
add_token(tokens, "NAMEDTUPLE", t::NAMEDTUPLE);
296297
add_token(tokens, "COLL_NAMEDTUPLE", t::COLL_NAMEDTUPLE);
298+
add_token(tokens, "NEWTYPE", t::NEWTYPE);
297299
add_token(tokens, "TYPEDDICT", t::TYPEDDICT);
298300
add_token(tokens, "TYPEVAR", t::TYPEVAR);
299301

pytype/pyi/parser_test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2579,5 +2579,30 @@ class Foo(TypedDict, metaclass=Meta): ...
25792579
""")
25802580

25812581

2582+
class NewTypeTest(_ParserTestBase):
2583+
2584+
def test_basic(self):
2585+
self.check("""
2586+
from typing import NewType
2587+
X = NewType('X', int)
2588+
""", """
2589+
X = newtype_X_0
2590+
2591+
class newtype_X_0(int):
2592+
def __init__(self, val: int) -> None: ...
2593+
""")
2594+
2595+
def test_fullname(self):
2596+
self.check("""
2597+
import typing
2598+
X = typing.NewType('X', int)
2599+
""", """
2600+
X = newtype_X_0
2601+
2602+
class newtype_X_0(int):
2603+
def __init__(self, val: int) -> None: ...
2604+
""")
2605+
2606+
25822607
if __name__ == "__main__":
25832608
unittest.main()

pytype/pytd/escape.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,8 @@ def pack_namedtuple_base_class(name: str, index: int) -> str:
6161
def unpack_namedtuple(name: str) -> str:
6262
"""Retrieve the original namedtuple class name."""
6363
return re.sub(r"\bnamedtuple[-_]([^-_]+)[-_\w]*", r"\1", name)
64+
65+
66+
def pack_newtype_base_class(name: str, index: int) -> str:
67+
"""Generate a name for a NewType proxy base class."""
68+
return "newtype_%s_%d" % (name, index)

0 commit comments

Comments
 (0)