|
8 | 8 | import mypy.plugin # To avoid circular imports. |
9 | 9 | from mypy.checker import TypeChecker |
10 | 10 | from mypy.errorcodes import LITERAL_REQ |
| 11 | +from mypy.expandtype import expand_type |
11 | 12 | from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type |
12 | 13 | from mypy.messages import format_type_bare |
13 | 14 | from mypy.nodes import ( |
|
49 | 50 | deserialize_and_fixup_type, |
50 | 51 | ) |
51 | 52 | from mypy.server.trigger import make_wildcard_trigger |
52 | | -from mypy.typeops import make_simplified_union, map_type_from_supertype |
| 53 | +from mypy.typeops import get_type_vars, make_simplified_union, map_type_from_supertype |
53 | 54 | from mypy.types import ( |
54 | 55 | AnyType, |
55 | 56 | CallableType, |
|
61 | 62 | TupleType, |
62 | 63 | Type, |
63 | 64 | TypeOfAny, |
| 65 | + TypeVarId, |
64 | 66 | TypeVarType, |
65 | 67 | UnionType, |
66 | 68 | get_proper_type, |
|
85 | 87 | class Converter: |
86 | 88 | """Holds information about a `converter=` argument""" |
87 | 89 |
|
88 | | - def __init__(self, init_type: Type | None = None) -> None: |
| 90 | + def __init__(self, init_type: Type | None = None, ret_type: Type | None = None) -> None: |
89 | 91 | self.init_type = init_type |
| 92 | + self.ret_type = ret_type |
90 | 93 |
|
91 | 94 |
|
92 | 95 | class Attribute: |
@@ -115,11 +118,20 @@ def __init__( |
115 | 118 | def argument(self, ctx: mypy.plugin.ClassDefContext) -> Argument: |
116 | 119 | """Return this attribute as an argument to __init__.""" |
117 | 120 | assert self.init |
118 | | - |
119 | 121 | init_type: Type | None = None |
120 | 122 | if self.converter: |
121 | 123 | if self.converter.init_type: |
122 | 124 | init_type = self.converter.init_type |
| 125 | + if init_type and self.converter.ret_type: |
| 126 | + # The converter return type should be the same type as the attribute type. |
| 127 | + # Copy type vars from attr type to converter. |
| 128 | + converter_vars = get_type_vars(self.converter.ret_type) |
| 129 | + init_vars = get_type_vars(self.init_type) |
| 130 | + if converter_vars and len(converter_vars) == len(init_vars): |
| 131 | + variables = { |
| 132 | + binder.id: arg for binder, arg in zip(converter_vars, init_vars) |
| 133 | + } |
| 134 | + init_type = expand_type(init_type, variables) |
123 | 135 | else: |
124 | 136 | ctx.api.fail("Cannot determine __init__ type from converter", self.context) |
125 | 137 | init_type = AnyType(TypeOfAny.from_error) |
@@ -671,6 +683,8 @@ def _parse_converter( |
671 | 683 | converter_type = get_proper_type(converter_type) |
672 | 684 | if isinstance(converter_type, CallableType) and converter_type.arg_types: |
673 | 685 | converter_info.init_type = converter_type.arg_types[0] |
| 686 | + if not is_attr_converters_optional: |
| 687 | + converter_info.ret_type = converter_type.ret_type |
674 | 688 | elif isinstance(converter_type, Overloaded): |
675 | 689 | types: list[Type] = [] |
676 | 690 | for item in converter_type.items: |
|
0 commit comments