@@ -361,72 +361,84 @@ def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]:
361361 # get some information as arguments, and they build an error message based
362362 # on them.
363363
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 :
365365 """Report a missing or non-accessible member.
366366
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.
370375 """
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 )):
373378 self .fail ('Member "{}" is not assignable' .format (member ), context )
374379 elif member == '__contains__' :
375380 self .fail ('Unsupported right operand type for in ({})' .format (
376- self .format (typ )), context )
381+ self .format (original_type )), context )
377382 elif member in op_methods .values ():
378383 # Access to a binary operator member (e.g. _add). This case does
379384 # not handle indexing operations.
380385 for op , method in op_methods .items ():
381386 if method == member :
382- self .unsupported_left_operand (op , typ , context )
387+ self .unsupported_left_operand (op , original_type , context )
383388 break
384389 elif member == '__neg__' :
385390 self .fail ('Unsupported operand type for unary - ({})' .format (
386- self .format (typ )), context )
391+ self .format (original_type )), context )
387392 elif member == '__pos__' :
388393 self .fail ('Unsupported operand type for unary + ({})' .format (
389- self .format (typ )), context )
394+ self .format (original_type )), context )
390395 elif member == '__invert__' :
391396 self .fail ('Unsupported operand type for ~ ({})' .format (
392- self .format (typ )), context )
397+ self .format (original_type )), context )
393398 elif member == '__getitem__' :
394399 # Indexed get.
395400 # 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 ():
397402 self .fail ('The type {} is not generic and not indexable' .format (
398- self .format (typ )), context )
403+ self .format (original_type )), context )
399404 else :
400405 self .fail ('Value of type {} is not indexable' .format (
401- self .format (typ )), context )
406+ self .format (original_type )), context )
402407 elif member == '__setitem__' :
403408 # Indexed set.
404409 self .fail ('Unsupported target for indexed assignment' , context )
405410 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' ):
407413 # "'function' not callable" is a confusing error message.
408414 # Explain that the problem is that the type of the function is not known.
409415 self .fail ('Cannot call function of unknown type' , context )
410416 else :
411- self .fail ('{} not callable' .format (self .format (typ )), context )
417+ self .fail ('{} not callable' .format (self .format (original_type )), context )
412418 else :
413419 # The non-special case: a missing ordinary attribute.
414420 if not self .disable_type_names :
415421 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 ())
418424 matches = [m for m in COMMON_MISTAKES .get (member , []) if m in alternatives ]
419425 matches .extend (best_matches (member , alternatives )[:3 ])
420426 if matches :
421427 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 )
423429 failed = True
424430 if not failed :
425- self .fail ('{} has no attribute "{}"' .format (self .format (typ ),
431+ self .fail ('{} has no attribute "{}"' .format (self .format (original_type ),
426432 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 )
430442 return AnyType ()
431443
432444 def unsupported_operand_types (self , op : str , left_type : Any ,
0 commit comments