From 6ca3d6fb3d1a6f5466f4dfbc819e0ffd8c27d040 Mon Sep 17 00:00:00 2001 From: KevinRK29 Date: Fri, 30 Jan 2026 01:06:15 -0500 Subject: [PATCH 1/3] added fuzzy matching for name not defined errors --- mypy/semanal.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index 20bcb2f4ac60a..e6bd2d835f69f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -7475,6 +7475,12 @@ def name_not_defined(self, name: str, ctx: Context, namespace: str | None = None self.record_incomplete_ref() return message = f'Name "{name}" is not defined' + # Collect all names in scope to suggest similar alternatives + alternatives = self._get_names_in_scope() + alternatives.discard(name) + matches = best_matches(name, alternatives, n=3) + if matches: + message += f"; did you mean {pretty_seq(matches, 'or')}?" self.fail(message, ctx, code=codes.NAME_DEFINED) if f"builtins.{name}" in SUGGESTED_TEST_FIXTURES: @@ -7499,6 +7505,36 @@ def name_not_defined(self, name: str, ctx: Context, namespace: str | None = None ).format(module=module, name=lowercased[fullname].rsplit(".", 1)[-1]) self.note(hint, ctx, code=codes.NAME_DEFINED) + def _get_names_in_scope(self) -> set[str]: + """Collect all names visible in the current scope for fuzzy matching suggestions. + + This includes: + - Local variables (from function scopes) + - Class attributes (if it's inside a class) + - Global/module-level names + - Builtins + """ + names: set[str] = set() + + for table in self.locals: + if table is not None: + names.update(table.keys()) + + if self.type is not None: + names.update(self.type.names.keys()) + + names.update(self.globals.keys()) + + b = self.globals.get("__builtins__", None) + if b and isinstance(b.node, MypyFile): + # Only include public builtins (not _private ones) + for builtin_name in b.node.names.keys(): + if not (len(builtin_name) > 1 and builtin_name[0] == "_" and builtin_name[1] != "_"): + names.add(builtin_name) + + # Filter out internal/dunder names that aren't useful for suggestions and might introduce noise + return {n for n in names if not n.startswith("__") or n.endswith("__")} + def already_defined( self, name: str, ctx: Context, original_ctx: SymbolTableNode | SymbolNode | None, noun: str ) -> None: From 115b8316b86e30afbf66c5f7d7700393f076caa9 Mon Sep 17 00:00:00 2001 From: KevinRK29 Date: Fri, 30 Jan 2026 01:06:20 -0500 Subject: [PATCH 2/3] added test to validate suggestions --- test-data/unit/check-errorcodes.test | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 58f48144b3e56..eca5a2e687c80 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -21,6 +21,25 @@ def f() -> None: [file m.py] [builtins fixtures/module.pyi] +[case testErrorCodeUndefinedNameSuggestion] +my_variable = 42 +my_constant = 100 + +x = my_variabel # E: Name "my_variabel" is not defined; did you mean "my_variable"? [name-defined] + +def calculate_sum(items: int) -> int: + return items + +calculate_summ(1) # E: Name "calculate_summ" is not defined; did you mean "calculate_sum"? [name-defined] + +class MyClass: + pass + +y = MyClas() # E: Name "MyClas" is not defined; did you mean "MyClass"? [name-defined] + +unknown_xyz # E: Name "unknown_xyz" is not defined [name-defined] +[builtins fixtures/module.pyi] + [case testErrorCodeUnclassifiedError] class A: def __init__(self) -> int: \ From ac380215449d999aeb2b677029f4c69070b2532a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 06:13:41 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/semanal.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index e6bd2d835f69f..0c4647eaf625c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -7529,7 +7529,9 @@ def _get_names_in_scope(self) -> set[str]: if b and isinstance(b.node, MypyFile): # Only include public builtins (not _private ones) for builtin_name in b.node.names.keys(): - if not (len(builtin_name) > 1 and builtin_name[0] == "_" and builtin_name[1] != "_"): + if not ( + len(builtin_name) > 1 and builtin_name[0] == "_" and builtin_name[1] != "_" + ): names.add(builtin_name) # Filter out internal/dunder names that aren't useful for suggestions and might introduce noise