Skip to content

Commit 6ca3d6f

Browse files
committed
added fuzzy matching for name not defined errors
1 parent 3cae656 commit 6ca3d6f

1 file changed

Lines changed: 36 additions & 0 deletions

File tree

mypy/semanal.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7475,6 +7475,12 @@ def name_not_defined(self, name: str, ctx: Context, namespace: str | None = None
74757475
self.record_incomplete_ref()
74767476
return
74777477
message = f'Name "{name}" is not defined'
7478+
# Collect all names in scope to suggest similar alternatives
7479+
alternatives = self._get_names_in_scope()
7480+
alternatives.discard(name)
7481+
matches = best_matches(name, alternatives, n=3)
7482+
if matches:
7483+
message += f"; did you mean {pretty_seq(matches, 'or')}?"
74787484
self.fail(message, ctx, code=codes.NAME_DEFINED)
74797485

74807486
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
74997505
).format(module=module, name=lowercased[fullname].rsplit(".", 1)[-1])
75007506
self.note(hint, ctx, code=codes.NAME_DEFINED)
75017507

7508+
def _get_names_in_scope(self) -> set[str]:
7509+
"""Collect all names visible in the current scope for fuzzy matching suggestions.
7510+
7511+
This includes:
7512+
- Local variables (from function scopes)
7513+
- Class attributes (if it's inside a class)
7514+
- Global/module-level names
7515+
- Builtins
7516+
"""
7517+
names: set[str] = set()
7518+
7519+
for table in self.locals:
7520+
if table is not None:
7521+
names.update(table.keys())
7522+
7523+
if self.type is not None:
7524+
names.update(self.type.names.keys())
7525+
7526+
names.update(self.globals.keys())
7527+
7528+
b = self.globals.get("__builtins__", None)
7529+
if b and isinstance(b.node, MypyFile):
7530+
# Only include public builtins (not _private ones)
7531+
for builtin_name in b.node.names.keys():
7532+
if not (len(builtin_name) > 1 and builtin_name[0] == "_" and builtin_name[1] != "_"):
7533+
names.add(builtin_name)
7534+
7535+
# Filter out internal/dunder names that aren't useful for suggestions and might introduce noise
7536+
return {n for n in names if not n.startswith("__") or n.endswith("__")}
7537+
75027538
def already_defined(
75037539
self, name: str, ctx: Context, original_ctx: SymbolTableNode | SymbolNode | None, noun: str
75047540
) -> None:

0 commit comments

Comments
 (0)