@@ -7529,6 +7529,18 @@ def name_not_defined(self, name: str, ctx: Context, namespace: str | None = None
75297529 self .record_incomplete_ref ()
75307530 return
75317531 message = f'Name "{ name } " is not defined'
7532+ if (
7533+ not self .msg .prefer_simple_messages ()
7534+ and "." not in name
7535+ and not (name .startswith ("__" ) and name .endswith ("__" ))
7536+ and f"builtins.{ name } " not in SUGGESTED_TEST_FIXTURES
7537+ and ctx .line not in self .errors .ignored_lines .get (self .errors .file , {})
7538+ ):
7539+ alternatives = self ._get_names_in_scope ()
7540+ alternatives .discard (name )
7541+ matches = best_matches (name , alternatives , n = 3 )
7542+ if matches :
7543+ message += f"; did you mean { pretty_seq (matches , 'or' )} ?"
75327544 self .fail (message , ctx , code = codes .NAME_DEFINED )
75337545
75347546 if f"builtins.{ name } " in SUGGESTED_TEST_FIXTURES :
@@ -7553,6 +7565,39 @@ def name_not_defined(self, name: str, ctx: Context, namespace: str | None = None
75537565 ).format (module = module , name = lowercased [fullname ].rsplit ("." , 1 )[- 1 ])
75547566 self .note (hint , ctx , code = codes .NAME_DEFINED )
75557567
7568+ def _get_names_in_scope (self ) -> set [str ]:
7569+ """Collect all names visible in the current scope for fuzzy matching suggestions.
7570+
7571+ This includes:
7572+ - Local variables (from function scopes)
7573+ - Class attributes (only when directly in class body, not in methods)
7574+ - Global/module-level names
7575+ - Builtins
7576+ """
7577+ names : set [str ] = set ()
7578+
7579+ for table in self .locals :
7580+ if table is not None :
7581+ names .update (table .keys ())
7582+
7583+ if self .is_class_scope ():
7584+ assert self .type is not None
7585+ names .update (self .type .names .keys ())
7586+
7587+ names .update (self .globals .keys ())
7588+
7589+ b = self .globals .get ("__builtins__" , None )
7590+ if b :
7591+ assert isinstance (b .node , MypyFile )
7592+ for builtin_name in b .node .names .keys ():
7593+ if not (
7594+ len (builtin_name ) > 1 and builtin_name [0 ] == "_" and builtin_name [1 ] != "_"
7595+ ):
7596+ names .add (builtin_name )
7597+
7598+ # Filter out internal/dunder names that aren't useful as suggestions
7599+ return {n for n in names if not n .startswith ("__" )}
7600+
75567601 def already_defined (
75577602 self , name : str , ctx : Context , original_ctx : SymbolTableNode | SymbolNode | None , noun : str
75587603 ) -> None :
0 commit comments