Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions Lib/test/test_future_stmt/test_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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 = """
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Suggest the closest valid feature name in the :exc:`SyntaxError` message
when an unknown name is imported from :mod:`__future__`.
36 changes: 33 additions & 3 deletions Python/future.c
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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);
Comment thread
sobolevn marked this conversation as resolved.
Py_DECREF(suggestion);
Comment thread
sobolevn marked this conversation as resolved.
}
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,
Expand Down
Loading