@@ -361,72 +361,84 @@ def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]:
361
361
# get some information as arguments, and they build an error message based
362
362
# on them.
363
363
364
- def has_no_attr (self , typ : Type , member : str , context : Context ) -> Type :
364
+ def has_no_attr (self , original_type : Type , typ : Type , member : str , context : Context ) -> Type :
365
365
"""Report a missing or non-accessible member.
366
366
367
- The type argument is the base type. If member corresponds to
368
- an operator, use the corresponding operator name in the
369
- messages. Return type Any.
367
+ original_type is the top-level type on which the error occurred.
368
+ typ is the actual type that is missing the member. These can be
369
+ different, e.g., in a union, original_type will be the union and typ
370
+ will be the specific item in the union that does not have the member
371
+ attribute.
372
+
373
+ If member corresponds to an operator, use the corresponding operator
374
+ name in the messages. Return type Any.
370
375
"""
371
- if (isinstance (typ , Instance ) and
372
- typ .type .has_readable_member (member )):
376
+ if (isinstance (original_type , Instance ) and
377
+ original_type .type .has_readable_member (member )):
373
378
self .fail ('Member "{}" is not assignable' .format (member ), context )
374
379
elif member == '__contains__' :
375
380
self .fail ('Unsupported right operand type for in ({})' .format (
376
- self .format (typ )), context )
381
+ self .format (original_type )), context )
377
382
elif member in op_methods .values ():
378
383
# Access to a binary operator member (e.g. _add). This case does
379
384
# not handle indexing operations.
380
385
for op , method in op_methods .items ():
381
386
if method == member :
382
- self .unsupported_left_operand (op , typ , context )
387
+ self .unsupported_left_operand (op , original_type , context )
383
388
break
384
389
elif member == '__neg__' :
385
390
self .fail ('Unsupported operand type for unary - ({})' .format (
386
- self .format (typ )), context )
391
+ self .format (original_type )), context )
387
392
elif member == '__pos__' :
388
393
self .fail ('Unsupported operand type for unary + ({})' .format (
389
- self .format (typ )), context )
394
+ self .format (original_type )), context )
390
395
elif member == '__invert__' :
391
396
self .fail ('Unsupported operand type for ~ ({})' .format (
392
- self .format (typ )), context )
397
+ self .format (original_type )), context )
393
398
elif member == '__getitem__' :
394
399
# Indexed get.
395
400
# TODO: Fix this consistently in self.format
396
- if isinstance (typ , CallableType ) and typ .is_type_obj ():
401
+ if isinstance (original_type , CallableType ) and original_type .is_type_obj ():
397
402
self .fail ('The type {} is not generic and not indexable' .format (
398
- self .format (typ )), context )
403
+ self .format (original_type )), context )
399
404
else :
400
405
self .fail ('Value of type {} is not indexable' .format (
401
- self .format (typ )), context )
406
+ self .format (original_type )), context )
402
407
elif member == '__setitem__' :
403
408
# Indexed set.
404
409
self .fail ('Unsupported target for indexed assignment' , context )
405
410
elif member == '__call__' :
406
- if isinstance (typ , Instance ) and (typ .type .fullname () == 'builtins.function' ):
411
+ if isinstance (original_type , Instance ) and \
412
+ (original_type .type .fullname () == 'builtins.function' ):
407
413
# "'function' not callable" is a confusing error message.
408
414
# Explain that the problem is that the type of the function is not known.
409
415
self .fail ('Cannot call function of unknown type' , context )
410
416
else :
411
- self .fail ('{} not callable' .format (self .format (typ )), context )
417
+ self .fail ('{} not callable' .format (self .format (original_type )), context )
412
418
else :
413
419
# The non-special case: a missing ordinary attribute.
414
420
if not self .disable_type_names :
415
421
failed = False
416
- if isinstance (typ , Instance ) and typ .type .names :
417
- alternatives = set (typ .type .names .keys ())
422
+ if isinstance (original_type , Instance ) and original_type .type .names :
423
+ alternatives = set (original_type .type .names .keys ())
418
424
matches = [m for m in COMMON_MISTAKES .get (member , []) if m in alternatives ]
419
425
matches .extend (best_matches (member , alternatives )[:3 ])
420
426
if matches :
421
427
self .fail ('{} has no attribute "{}"; maybe {}?' .format (
422
- self .format (typ ), member , pretty_or (matches )), context )
428
+ self .format (original_type ), member , pretty_or (matches )), context )
423
429
failed = True
424
430
if not failed :
425
- self .fail ('{} has no attribute "{}"' .format (self .format (typ ),
431
+ self .fail ('{} has no attribute "{}"' .format (self .format (original_type ),
426
432
member ), context )
427
- else :
428
- self .fail ('Some element of union has no attribute "{}"' .format (
429
- member ), context )
433
+ elif isinstance (original_type , UnionType ):
434
+ # The checker passes "object" in lieu of "None" for attribute
435
+ # checks, so we manually convert it back.
436
+ typ_format = self .format (typ )
437
+ if typ_format == '"object"' and \
438
+ any (type (item ) == NoneTyp for item in original_type .items ):
439
+ typ_format = '"None"'
440
+ self .fail ('Item {} of {} has no attribute "{}"' .format (
441
+ typ_format , self .format (original_type ), member ), context )
430
442
return AnyType ()
431
443
432
444
def unsupported_operand_types (self , op : str , left_type : Any ,
0 commit comments