https://github.com/python/cpython/commit/dbac44740fe5b6402464ca272414f9b32f5999e2
commit: dbac44740fe5b6402464ca272414f9b32f5999e2
branch: main
author: sobolevn <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-09-24T16:29:48Z
summary:

gh-145868: Suggest names on typos in `__future__` imports (#154347)

* gh-145868: Suggest names on typos in `__future__` imports

* Address review

* Address review

* gh-145868: Suggest only valid future features

---------

Co-authored-by: Pablo Galindo Salgado <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-07-21-14-38-49.gh-issue-145868.7iGRvU.rst
M Lib/test/test_future_stmt/test_future.py
M Python/future.c

diff --git a/Lib/test/test_future_stmt/test_future.py 
b/Lib/test/test_future_stmt/test_future.py
index acd8d76dc90a29..5243d24a9ecf26 100644
--- a/Lib/test/test_future_stmt/test_future.py
+++ b/Lib/test/test_future_stmt/test_future.py
@@ -3,7 +3,7 @@
 import __future__
 import ast
 import unittest
-from test.support import force_not_colorized, import_helper
+from test.support import force_not_colorized, import_helper, subTests
 from test.support.script_helper import spawn_python, kill_python
 from textwrap import dedent
 import os
@@ -87,8 +87,44 @@ def test_unknown_future_flag(self):
             from __future__ import rested_snopes  # typo error here: nested => 
rested
         """
         self.assertSyntaxError(
-            code, lineno=2,
-            message='future feature rested_snopes is not defined', offset=24,
+            code,
+            lineno=2,
+            message=(
+                "future feature 'rested_snopes' is not defined. "
+                "Did you mean: 'nested_scopes'?"
+            ),
+            offset=24,
+        )
+
+    @subTests("typo, origin", [
+        ("nest_scopes", "nested_scopes"),
+        ("gneretors", "generators"),
+        ("divicion", "division"),
+        ("absolute_imports", "absolute_import"),
+        ("print_func", "print_function"),
+        ("unicode_literal", "unicode_literals"),
+        ("barry_as_bdfl", "barry_as_FLUFL"),
+        ("generatorstop", "generator_stop"),
+        ("anotations", "annotations"),
+    ])
+    def test_typos_in_future_imports(self, typo, origin):
+        self.assertSyntaxError(
+            f"from __future__ import {typo}",
+            lineno=1,
+            message=(
+                f"future feature '{typo}' is not defined. "
+                f"Did you mean: '{origin}'?"
+            ),
+            offset=24,
+        )
+
+    @subTests("name", ["missing_name", "brces", "brace"])
+    def test_no_suggestion_on_missing_name(self, name):
+        self.assertSyntaxError(
+            f"from __future__ import {name}",
+            lineno=1,
+            message=f"future feature '{name}' is not defined",
+            offset=24,
         )
 
     def test_future_import_not_on_top(self):
@@ -137,7 +173,7 @@ def test_future_import_star(self):
         code = """
             from __future__ import *
         """
-        self.assertSyntaxError(code, message='future feature * is not 
defined', offset=24)
+        self.assertSyntaxError(code, message="future feature '*' is not 
defined", offset=24)
 
     def test_future_import_braces(self):
         code = """
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-21-14-38-49.gh-issue-145868.7iGRvU.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-21-14-38-49.gh-issue-145868.7iGRvU.rst
new file mode 100644
index 00000000000000..c3aa54979f3d16
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-21-14-38-49.gh-issue-145868.7iGRvU.rst
@@ -0,0 +1,2 @@
+Suggest the closest valid feature name in the :exc:`SyntaxError` message
+when an unknown name is imported from :mod:`__future__`.
diff --git a/Python/future.c b/Python/future.c
index 79b6c0c503bace..41c4fa07916189 100644
--- a/Python/future.c
+++ b/Python/future.c
@@ -1,9 +1,10 @@
 #include "Python.h"
 #include "pycore_ast.h"           // _PyAST_GetDocString()
+#include "pycore_pyerrors.h"      // _Py_CalculateSuggestions()
 #include "pycore_symtable.h"      // _PyFutureFeatures
 #include "pycore_unicodeobject.h" // _PyUnicode_EqualToASCIIString()
 
-#define UNDEFINED_FUTURE_FEATURE "future feature %.100s is not defined"
+#define UNDEFINED_FUTURE_FEATURE "future feature '%.100s' is not defined"
 
 static int
 future_check_features(_PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
@@ -48,8 +49,37 @@ future_check_features(_PyFutureFeatures *ff, stmt_ty s, 
PyObject *filename)
                                              name->end_col_offset + 1);
             return 0;
         } else {
-            PyErr_Format(PyExc_SyntaxError,
-                         UNDEFINED_FUTURE_FEATURE, feature);
+            // Keep this list in sync with the feature checks above.
+            PyObject *future_features = Py_BuildValue("[ssssssssss]",
+                                                      FUTURE_NESTED_SCOPES,
+                                                      FUTURE_GENERATORS,
+                                                      FUTURE_DIVISION,
+                                                      FUTURE_ABSOLUTE_IMPORT,
+                                                      FUTURE_WITH_STATEMENT,
+                                                      FUTURE_PRINT_FUNCTION,
+                                                      FUTURE_UNICODE_LITERALS,
+                                                      FUTURE_BARRY_AS_BDFL,
+                                                      FUTURE_GENERATOR_STOP,
+                                                      FUTURE_ANNOTATIONS);
+            PyObject *suggestion = NULL;
+            if (future_features != NULL) {
+                suggestion = _Py_CalculateSuggestions(future_features,
+                                                      name->name);
+            }
+            if (suggestion != NULL) {
+                PyErr_Format(PyExc_SyntaxError,
+                             UNDEFINED_FUTURE_FEATURE ". Did you mean: %R?",
+                             feature, suggestion);
+                Py_DECREF(suggestion);
+            }
+            else {
+                // Do not fail on missing suggestion,
+                // just show the default message.
+                PyErr_Format(PyExc_SyntaxError,
+                             UNDEFINED_FUTURE_FEATURE,
+                             feature);
+            }
+            Py_XDECREF(future_features);
             PyErr_RangedSyntaxLocationObject(filename,
                                              name->lineno,
                                              name->col_offset + 1,

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to