|
1 | 1 | """Plugin that provides support for dataclasses.""" |
2 | 2 |
|
3 | 3 | from collections import OrderedDict |
4 | | -from typing import Dict, List, Set, Tuple, Optional, FrozenSet |
| 4 | +from typing import Dict, List, Set, Tuple, Optional, FrozenSet, Callable |
5 | 5 |
|
6 | 6 | from typing_extensions import Final |
7 | 7 |
|
| 8 | +from mypy.maptype import map_instance_to_supertype |
8 | 9 | from mypy.nodes import ( |
9 | 10 | ARG_OPT, ARG_POS, MDEF, Argument, AssignmentStmt, CallExpr, |
10 | 11 | Context, Expression, JsonDict, NameExpr, RefExpr, |
@@ -416,6 +417,22 @@ def asdict_callback(ctx: FunctionContext) -> Type: |
416 | 417 | return ctx.default_return_type |
417 | 418 |
|
418 | 419 |
|
| 420 | +def _transform_type_args_used_in_supertype(*, typ: Instance, supertype: TypeInfo, transform: Callable[[Instance], Type]) -> \ |
| 421 | + List[Type]: |
| 422 | + """For each type arg used in typ, call transform function if the arg is used in the given supertype.""" |
| 423 | + supertype_instance = map_instance_to_supertype(typ, supertype) |
| 424 | + supertype_args = set([arg for arg in supertype_instance.args if isinstance(arg, Instance)]) |
| 425 | + new_args = [] |
| 426 | + # Transform existing type args if they are part of the supertype's type args |
| 427 | + for arg in typ.args: |
| 428 | + if isinstance(arg, Instance) and arg in supertype_args: |
| 429 | + new_arg = transform(arg) |
| 430 | + else: |
| 431 | + new_arg = arg |
| 432 | + new_args.append(new_arg) |
| 433 | + return new_args |
| 434 | + |
| 435 | + |
419 | 436 | def _type_asdict(api: CheckerPluginInterface, context: Context, typ: Type) -> Type: |
420 | 437 | """Convert dataclasses into TypedDicts, recursively looking into built-in containers. |
421 | 438 |
|
@@ -447,15 +464,19 @@ def _type_asdict_inner(typ: Type, seen_dataclasses: FrozenSet[str]) -> Type: |
447 | 464 | fields[attr.name] = _type_asdict_inner(typ, seen_dataclasses) |
448 | 465 | return make_anonymous_typeddict(api, fields=fields, required_keys=set(fields.keys())) |
449 | 466 | elif info.has_base('builtins.list'): |
450 | | - # TODO: Support subclasses properly |
451 | | - assert len(typ.args) == 1 |
452 | | - arg = typ.args[0] |
453 | | - return Instance(typ.type, [_type_asdict_inner(arg, seen_dataclasses)]) |
| 467 | + new_args = _transform_type_args_used_in_supertype( |
| 468 | + typ=typ, |
| 469 | + supertype=api.named_generic_type('builtins.list', []).type, |
| 470 | + transform=lambda arg: _type_asdict_inner(arg, seen_dataclasses) |
| 471 | + ) |
| 472 | + return Instance(typ.type, new_args) |
454 | 473 | elif info.has_base('builtins.dict'): |
455 | | - # TODO: Support subclasses properly |
456 | | - assert len(typ.args) == 2 |
457 | | - return Instance(typ.type, [_type_asdict_inner(typ.args[0], seen_dataclasses), |
458 | | - _type_asdict_inner(typ.args[1], seen_dataclasses)]) |
| 474 | + new_args = _transform_type_args_used_in_supertype( |
| 475 | + typ=typ, |
| 476 | + supertype=api.named_generic_type('builtins.dict', []).type, |
| 477 | + transform=lambda arg: _type_asdict_inner(arg, seen_dataclasses) |
| 478 | + ) |
| 479 | + return Instance(typ.type, new_args) |
459 | 480 | elif isinstance(typ, TupleType): |
460 | 481 | # TODO: Support subclasses/namedtuples properly |
461 | 482 | return TupleType([_type_asdict_inner(item, seen_dataclasses) for item in typ.items], |
|
0 commit comments