|
58 | 58 | GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr, |
59 | 59 | SliceExpr, CastExpr, RevealTypeExpr, TypeApplication, Context, SymbolTable, |
60 | 60 | SymbolTableNode, BOUND_TVAR, UNBOUND_TVAR, ListComprehension, GeneratorExpr, |
61 | | - FuncExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr, |
| 61 | + FuncExpr, MDEF, FuncBase, Decorator, SetExpr, TypeVarExpr, NewTypeExpr, |
62 | 62 | StrExpr, BytesExpr, PrintStmt, ConditionalExpr, PromoteExpr, |
63 | 63 | ComparisonExpr, StarExpr, ARG_POS, ARG_NAMED, MroError, type_aliases, |
64 | 64 | YieldFromExpr, NamedTupleExpr, NonlocalDecl, |
@@ -769,6 +769,8 @@ def analyze_base_classes(self, defn: ClassDef) -> None: |
769 | 769 | defn.info.tuple_type = base |
770 | 770 | base_types.append(base.fallback) |
771 | 771 | elif isinstance(base, Instance): |
| 772 | + if base.type.is_newtype: |
| 773 | + self.fail("Cannot subclass NewType", defn) |
772 | 774 | base_types.append(base) |
773 | 775 | elif isinstance(base, AnyType): |
774 | 776 | defn.info.fallback_to_any = True |
@@ -1083,6 +1085,7 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: |
1083 | 1085 | for lvalue in s.lvalues: |
1084 | 1086 | self.store_declared_types(lvalue, s.type) |
1085 | 1087 | self.check_and_set_up_type_alias(s) |
| 1088 | + self.process_newtype_declaration(s) |
1086 | 1089 | self.process_typevar_declaration(s) |
1087 | 1090 | self.process_namedtuple_definition(s) |
1088 | 1091 |
|
@@ -1289,6 +1292,118 @@ def store_declared_types(self, lvalue: Node, typ: Type) -> None: |
1289 | 1292 | # This has been flagged elsewhere as an error, so just ignore here. |
1290 | 1293 | pass |
1291 | 1294 |
|
| 1295 | + def process_newtype_declaration(self, s: AssignmentStmt) -> None: |
| 1296 | + """Check if s declares a NewType; if yes, store it in symbol table.""" |
| 1297 | + # Extract and check all information from newtype declaration |
| 1298 | + name, call = self.analyze_newtype_declaration(s) |
| 1299 | + if name is None or call is None: |
| 1300 | + return |
| 1301 | + |
| 1302 | + old_type = self.check_newtype_args(name, call, s) |
| 1303 | + if old_type is None: |
| 1304 | + return |
| 1305 | + |
| 1306 | + # Create the corresponding class definition if the aliased type is subtypeable |
| 1307 | + if isinstance(old_type, TupleType): |
| 1308 | + newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type.fallback) |
| 1309 | + newtype_class_info.tuple_type = old_type |
| 1310 | + elif isinstance(old_type, Instance): |
| 1311 | + newtype_class_info = self.build_newtype_typeinfo(name, old_type, old_type) |
| 1312 | + else: |
| 1313 | + message = "Argument 2 to NewType(...) must be subclassable (got {})" |
| 1314 | + self.fail(message.format(old_type), s) |
| 1315 | + return |
| 1316 | + |
| 1317 | + # If so, add it to the symbol table. |
| 1318 | + node = self.lookup(name, s) |
| 1319 | + if node is None: |
| 1320 | + self.fail("Could not find {} in current namespace".format(name), s) |
| 1321 | + return |
| 1322 | + # TODO: why does NewType work in local scopes despite always being of kind GDEF? |
| 1323 | + node.kind = GDEF |
| 1324 | + node.node = newtype_class_info |
| 1325 | + call.analyzed = NewTypeExpr(newtype_class_info).set_line(call.line) |
| 1326 | + |
| 1327 | + def analyze_newtype_declaration(self, |
| 1328 | + s: AssignmentStmt) -> Tuple[Optional[str], Optional[CallExpr]]: |
| 1329 | + """Return the NewType call expression if `s` is a newtype declaration or None otherwise.""" |
| 1330 | + name, call = None, None |
| 1331 | + if (len(s.lvalues) == 1 |
| 1332 | + and isinstance(s.lvalues[0], NameExpr) |
| 1333 | + and isinstance(s.rvalue, CallExpr) |
| 1334 | + and isinstance(s.rvalue.callee, RefExpr) |
| 1335 | + and s.rvalue.callee.fullname == 'typing.NewType'): |
| 1336 | + lvalue = s.lvalues[0] |
| 1337 | + name = s.lvalues[0].name |
| 1338 | + if not lvalue.is_def: |
| 1339 | + if s.type: |
| 1340 | + self.fail("Cannot declare the type of a NewType declaration", s) |
| 1341 | + else: |
| 1342 | + self.fail("Cannot redefine '%s' as a NewType" % name, s) |
| 1343 | + |
| 1344 | + # This dummy NewTypeExpr marks the call as sufficiently analyzed; it will be |
| 1345 | + # overwritten later with a fully complete NewTypeExpr if there are no other |
| 1346 | + # errors with the NewType() call. |
| 1347 | + call = s.rvalue |
| 1348 | + call.analyzed = NewTypeExpr(None).set_line(call.line) |
| 1349 | + |
| 1350 | + return name, call |
| 1351 | + |
| 1352 | + def check_newtype_args(self, name: str, call: CallExpr, context: Context) -> Optional[Type]: |
| 1353 | + has_failed = False |
| 1354 | + args, arg_kinds = call.args, call.arg_kinds |
| 1355 | + if len(args) != 2 or arg_kinds[0] != ARG_POS or arg_kinds[1] != ARG_POS: |
| 1356 | + self.fail("NewType(...) expects exactly two positional arguments", context) |
| 1357 | + return None |
| 1358 | + |
| 1359 | + # Check first argument |
| 1360 | + if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)): |
| 1361 | + self.fail("Argument 1 to NewType(...) must be a string literal", context) |
| 1362 | + has_failed = True |
| 1363 | + elif cast(StrExpr, call.args[0]).value != name: |
| 1364 | + self.fail("Argument 1 to NewType(...) does not match variable name", context) |
| 1365 | + has_failed = True |
| 1366 | + |
| 1367 | + # Check second argument |
| 1368 | + try: |
| 1369 | + unanalyzed_type = expr_to_unanalyzed_type(call.args[1]) |
| 1370 | + except TypeTranslationError: |
| 1371 | + self.fail("Argument 2 to NewType(...) must be a valid type", context) |
| 1372 | + return None |
| 1373 | + old_type = self.anal_type(unanalyzed_type) |
| 1374 | + |
| 1375 | + if isinstance(old_type, Instance) and old_type.type.is_newtype: |
| 1376 | + self.fail("Argument 2 to NewType(...) cannot be another NewType", context) |
| 1377 | + has_failed = True |
| 1378 | + |
| 1379 | + return None if has_failed else old_type |
| 1380 | + |
| 1381 | + def build_newtype_typeinfo(self, name: str, old_type: Type, base_type: Instance) -> TypeInfo: |
| 1382 | + class_def = ClassDef(name, Block([])) |
| 1383 | + class_def.fullname = self.qualified_name(name) |
| 1384 | + |
| 1385 | + symbols = SymbolTable() |
| 1386 | + info = TypeInfo(symbols, class_def) |
| 1387 | + info.mro = [info] + base_type.type.mro |
| 1388 | + info.bases = [base_type] |
| 1389 | + info.is_newtype = True |
| 1390 | + |
| 1391 | + # Add __init__ method |
| 1392 | + args = [Argument(Var('cls'), NoneTyp(), None, ARG_POS), |
| 1393 | + self.make_argument('item', old_type)] |
| 1394 | + signature = CallableType( |
| 1395 | + arg_types=[cast(Type, None), old_type], |
| 1396 | + arg_kinds=[arg.kind for arg in args], |
| 1397 | + arg_names=['self', 'item'], |
| 1398 | + ret_type=old_type, |
| 1399 | + fallback=self.named_type('__builtins__.function'), |
| 1400 | + name=name) |
| 1401 | + init_func = FuncDef('__init__', args, Block([]), typ=signature) |
| 1402 | + init_func.info = info |
| 1403 | + symbols['__init__'] = SymbolTableNode(MDEF, init_func) |
| 1404 | + |
| 1405 | + return info |
| 1406 | + |
1292 | 1407 | def process_typevar_declaration(self, s: AssignmentStmt) -> None: |
1293 | 1408 | """Check if s declares a TypeVar; it yes, store it in symbol table.""" |
1294 | 1409 | call = self.get_typevar_declaration(s) |
|
0 commit comments