12
12
TupleExpr , ListExpr , NameExpr , CallExpr , RefExpr , FuncDef ,
13
13
is_class_var , TempNode , Decorator , MemberExpr , Expression ,
14
14
SymbolTableNode , MDEF , JsonDict , OverloadedFuncDef , ARG_NAMED_OPT , ARG_NAMED ,
15
- TypeVarExpr , PlaceholderNode
15
+ TypeVarExpr , PlaceholderNode , LambdaExpr
16
16
)
17
17
from mypy .plugin import SemanticAnalyzerPluginInterface
18
18
from mypy .plugins .common import (
@@ -60,19 +60,16 @@ class Converter:
60
60
"""Holds information about a `converter=` argument"""
61
61
62
62
def __init__ (self ,
63
- type : Optional [Type ] = None ,
64
- is_attr_converters_optional : bool = False ,
65
- is_invalid_converter : bool = False ) -> None :
66
- self .type = type
67
- self .is_attr_converters_optional = is_attr_converters_optional
68
- self .is_invalid_converter = is_invalid_converter
63
+ init_type : Optional [Type ] = None ,
64
+ ) -> None :
65
+ self .init_type = init_type
69
66
70
67
71
68
class Attribute :
72
69
"""The value of an attr.ib() call."""
73
70
74
71
def __init__ (self , name : str , info : TypeInfo ,
75
- has_default : bool , init : bool , kw_only : bool , converter : Converter ,
72
+ has_default : bool , init : bool , kw_only : bool , converter : Optional [ Converter ] ,
76
73
context : Context ,
77
74
init_type : Optional [Type ]) -> None :
78
75
self .name = name
@@ -88,54 +85,35 @@ def argument(self, ctx: 'mypy.plugin.ClassDefContext') -> Argument:
88
85
"""Return this attribute as an argument to __init__."""
89
86
assert self .init
90
87
91
- init_type = self .init_type or self .info [self .name ].type
92
-
93
- if self .converter .type and not self .converter .is_invalid_converter :
94
- # When a converter is set the init_type is overridden by the first argument
95
- # of the converter method.
96
- converter_type = self .converter .type
97
- init_type = None
98
- converter_type = get_proper_type (converter_type )
99
- if isinstance (converter_type , CallableType ) and converter_type .arg_types :
100
- init_type = converter_type .arg_types [0 ]
101
- elif isinstance (converter_type , Overloaded ):
102
- types : List [Type ] = []
103
- for item in converter_type .items :
104
- # Walk the overloads looking for methods that can accept one argument.
105
- num_arg_types = len (item .arg_types )
106
- if not num_arg_types :
107
- continue
108
- if num_arg_types > 1 and any (kind == ARG_POS for kind in item .arg_kinds [1 :]):
109
- continue
110
- types .append (item .arg_types [0 ])
111
- # Make a union of all the valid types.
112
- if types :
113
- init_type = make_simplified_union (types )
114
-
115
- if self .converter .is_attr_converters_optional and init_type :
116
- # If the converter was attr.converter.optional(type) then add None to
117
- # the allowed init_type.
118
- init_type = UnionType .make_union ([init_type , NoneType ()])
119
-
120
- if not init_type :
88
+ init_type : Optional [Type ] = None
89
+ if self .converter :
90
+ if self .converter .init_type :
91
+ init_type = self .converter .init_type
92
+ else :
121
93
ctx .api .fail ("Cannot determine __init__ type from converter" , self .context )
122
94
init_type = AnyType (TypeOfAny .from_error )
123
- elif self .converter .is_invalid_converter :
124
- # This means we had a converter but it's not of a type we can infer.
125
- init_type = AnyType (TypeOfAny .from_error )
95
+ else : # There is no converter, the init type is the normal type.
96
+ init_type = self .init_type or self .info [self .name ].type
126
97
98
+ unannotated = False
127
99
if init_type is None :
128
- if ctx .api .options .disallow_untyped_defs :
129
- # This is a compromise. If you don't have a type here then the
130
- # __init__ will be untyped. But since the __init__ is added it's
131
- # pointing at the decorator. So instead we also show the error in the
132
- # assignment, which is where you would fix the issue.
133
- node = self .info [self .name ].node
134
- assert node is not None
135
- ctx .api .msg .need_annotation_for_var (node , self .context )
136
-
100
+ unannotated = True
137
101
# Convert type not set to Any.
138
102
init_type = AnyType (TypeOfAny .unannotated )
103
+ else :
104
+ proper_type = get_proper_type (init_type )
105
+ if isinstance (proper_type , AnyType ):
106
+ if proper_type .type_of_any == TypeOfAny .unannotated :
107
+ unannotated = True
108
+
109
+ if unannotated and ctx .api .options .disallow_untyped_defs :
110
+ # This is a compromise. If you don't have a type here then the
111
+ # __init__ will be untyped. But since the __init__ is added it's
112
+ # pointing at the decorator. So instead we also show the error in the
113
+ # assignment, which is where you would fix the issue.
114
+ node = self .info [self .name ].node
115
+ assert node is not None
116
+ ctx .api .msg .need_annotation_for_var (node , self .context )
139
117
140
118
if self .kw_only :
141
119
arg_kind = ARG_NAMED_OPT if self .has_default else ARG_NAMED
@@ -154,9 +132,9 @@ def serialize(self) -> JsonDict:
154
132
'has_default' : self .has_default ,
155
133
'init' : self .init ,
156
134
'kw_only' : self .kw_only ,
157
- 'converter_type ' : self .converter . type . serialize () if self . converter . type else None ,
158
- 'converter_is_attr_converters_optional ' : self .converter .is_attr_converters_optional ,
159
- 'converter_is_invalid_converter' : self .converter . is_invalid_converter ,
135
+ 'has_converter ' : self .converter is not None ,
136
+ 'converter_init_type ' : self .converter .init_type . serialize ()
137
+ if self .converter and self . converter . init_type else None ,
160
138
'context_line' : self .context .line ,
161
139
'context_column' : self .context .column ,
162
140
'init_type' : self .init_type .serialize () if self .init_type else None ,
@@ -169,17 +147,16 @@ def deserialize(cls, info: TypeInfo,
169
147
"""Return the Attribute that was serialized."""
170
148
raw_init_type = data ['init_type' ]
171
149
init_type = deserialize_and_fixup_type (raw_init_type , api ) if raw_init_type else None
150
+ raw_converter_init_type = data ['converter_init_type' ]
151
+ converter_init_type = (deserialize_and_fixup_type (raw_converter_init_type , api )
152
+ if raw_converter_init_type else None )
172
153
173
- converter_type = None
174
- if data ['converter_type' ]:
175
- converter_type = deserialize_and_fixup_type (data ['converter_type' ], api )
176
154
return Attribute (data ['name' ],
177
155
info ,
178
156
data ['has_default' ],
179
157
data ['init' ],
180
158
data ['kw_only' ],
181
- Converter (converter_type , data ['converter_is_attr_converters_optional' ],
182
- data ['converter_is_invalid_converter' ]),
159
+ Converter (converter_init_type ) if data ['has_converter' ] else None ,
183
160
Context (line = data ['context_line' ], column = data ['context_column' ]),
184
161
init_type )
185
162
@@ -542,7 +519,7 @@ def _attribute_from_auto_attrib(ctx: 'mypy.plugin.ClassDefContext',
542
519
has_rhs = not isinstance (rvalue , TempNode )
543
520
sym = ctx .cls .info .names .get (name )
544
521
init_type = sym .type if sym else None
545
- return Attribute (name , ctx .cls .info , has_rhs , True , kw_only , Converter () , stmt , init_type )
522
+ return Attribute (name , ctx .cls .info , has_rhs , True , kw_only , None , stmt , init_type )
546
523
547
524
548
525
def _attribute_from_attrib_maker (ctx : 'mypy.plugin.ClassDefContext' ,
@@ -613,40 +590,76 @@ def _attribute_from_attrib_maker(ctx: 'mypy.plugin.ClassDefContext',
613
590
614
591
615
592
def _parse_converter (ctx : 'mypy.plugin.ClassDefContext' ,
616
- converter : Optional [Expression ]) -> Converter :
593
+ converter_expr : Optional [Expression ]) -> Optional [ Converter ] :
617
594
"""Return the Converter object from an Expression."""
618
595
# TODO: Support complex converters, e.g. lambdas, calls, etc.
619
- if converter :
620
- if isinstance (converter , RefExpr ) and converter .node :
621
- if (isinstance (converter .node , FuncDef )
622
- and converter .node .type
623
- and isinstance (converter .node .type , FunctionLike )):
624
- return Converter (converter .node .type )
625
- elif (isinstance (converter .node , OverloadedFuncDef )
626
- and is_valid_overloaded_converter (converter .node )):
627
- return Converter (converter .node .type )
628
- elif isinstance (converter .node , TypeInfo ):
629
- from mypy .checkmember import type_object_type # To avoid import cycle.
630
- return Converter (type_object_type (converter .node , ctx .api .named_type ))
631
-
632
- if (isinstance (converter , CallExpr )
633
- and isinstance (converter .callee , RefExpr )
634
- and converter .callee .fullname in attr_optional_converters
635
- and converter .args
636
- and converter .args [0 ]):
637
- # Special handling for attr.converters.optional(type)
638
- # We extract the type and add make the init_args Optional in Attribute.argument
639
- argument = _parse_converter (ctx , converter .args [0 ])
640
- argument .is_attr_converters_optional = True
641
- return argument
642
-
596
+ if not converter_expr :
597
+ return None
598
+ converter_info = Converter ()
599
+ if (isinstance (converter_expr , CallExpr )
600
+ and isinstance (converter_expr .callee , RefExpr )
601
+ and converter_expr .callee .fullname in attr_optional_converters
602
+ and converter_expr .args
603
+ and converter_expr .args [0 ]):
604
+ # Special handling for attr.converters.optional(type)
605
+ # We extract the type and add make the init_args Optional in Attribute.argument
606
+ converter_expr = converter_expr .args [0 ]
607
+ is_attr_converters_optional = True
608
+ else :
609
+ is_attr_converters_optional = False
610
+
611
+ converter_type : Optional [Type ] = None
612
+ if isinstance (converter_expr , RefExpr ) and converter_expr .node :
613
+ if isinstance (converter_expr .node , FuncDef ):
614
+ if converter_expr .node .type and isinstance (converter_expr .node .type , FunctionLike ):
615
+ converter_type = converter_expr .node .type
616
+ else : # The converter is an unannotated function.
617
+ converter_info .init_type = AnyType (TypeOfAny .unannotated )
618
+ return converter_info
619
+ elif (isinstance (converter_expr .node , OverloadedFuncDef )
620
+ and is_valid_overloaded_converter (converter_expr .node )):
621
+ converter_type = converter_expr .node .type
622
+ elif isinstance (converter_expr .node , TypeInfo ):
623
+ from mypy .checkmember import type_object_type # To avoid import cycle.
624
+ converter_type = type_object_type (converter_expr .node , ctx .api .named_type )
625
+ if isinstance (converter_expr , LambdaExpr ):
626
+ # TODO: should we send a fail if converter_expr.min_args > 1?
627
+ converter_info .init_type = AnyType (TypeOfAny .unannotated )
628
+ return converter_info
629
+
630
+ if not converter_type :
643
631
# Signal that we have an unsupported converter.
644
632
ctx .api .fail (
645
- "Unsupported converter, only named functions and types are currently supported" ,
646
- converter
633
+ "Unsupported converter, only named functions, types and lambdas are currently "
634
+ "supported" ,
635
+ converter_expr
647
636
)
648
- return Converter (None , is_invalid_converter = True )
649
- return Converter (None )
637
+ converter_info .init_type = AnyType (TypeOfAny .from_error )
638
+ return converter_info
639
+
640
+ converter_type = get_proper_type (converter_type )
641
+ if isinstance (converter_type , CallableType ) and converter_type .arg_types :
642
+ converter_info .init_type = converter_type .arg_types [0 ]
643
+ elif isinstance (converter_type , Overloaded ):
644
+ types : List [Type ] = []
645
+ for item in converter_type .items :
646
+ # Walk the overloads looking for methods that can accept one argument.
647
+ num_arg_types = len (item .arg_types )
648
+ if not num_arg_types :
649
+ continue
650
+ if num_arg_types > 1 and any (kind == ARG_POS for kind in item .arg_kinds [1 :]):
651
+ continue
652
+ types .append (item .arg_types [0 ])
653
+ # Make a union of all the valid types.
654
+ if types :
655
+ converter_info .init_type = make_simplified_union (types )
656
+
657
+ if is_attr_converters_optional and converter_info .init_type :
658
+ # If the converter was attr.converter.optional(type) then add None to
659
+ # the allowed init_type.
660
+ converter_info .init_type = UnionType .make_union ([converter_info .init_type , NoneType ()])
661
+
662
+ return converter_info
650
663
651
664
652
665
def is_valid_overloaded_converter (defn : OverloadedFuncDef ) -> bool :
0 commit comments