Skip to content
Open
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
1 change: 1 addition & 0 deletions Include/cpython/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ typedef struct {
#define FUTURE_BARRY_AS_BDFL "barry_as_FLUFL"
#define FUTURE_GENERATOR_STOP "generator_stop"
#define FUTURE_ANNOTATIONS "annotations"
#define FUTURE_BRACES "braces"
Comment thread
sobolevn marked this conversation as resolved.

#define PY_INVALID_STACK_EFFECT INT_MAX
PyAPI_FUNC(int) PyCompile_OpcodeStackEffect(int opcode, int oparg);
Expand Down
45 changes: 41 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,45 @@ 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"),
("brces", "braces"),
("brace", "braces"),
])
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,
)

def test_no_suggestion_on_missing_name(self):
self.assertSyntaxError(
"from __future__ import missing_name",
lineno=1,
message="future feature 'missing_name' is not defined",
offset=24,
)

def test_future_import_not_on_top(self):
Expand Down Expand Up @@ -137,7 +174,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 @@
Calculate suggestions during :exc:`SyntaxError` on typos when importing
names from :mod:`__future__`.
37 changes: 33 additions & 4 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 @@ -38,7 +39,7 @@ future_check_features(_PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
continue;
} else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
ff->ff_features |= CO_FUTURE_ANNOTATIONS;
} else if (strcmp(feature, "braces") == 0) {
} else if (strcmp(feature, FUTURE_BRACES) == 0) {
PyErr_SetString(PyExc_SyntaxError,
"not a chance");
PyErr_RangedSyntaxLocationObject(filename,
Expand All @@ -48,8 +49,36 @@ 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);
PyObject *suggestion;
PyObject *future_features = Py_BuildValue("[sssssssssss]",
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,
FUTURE_BRACES);
if (
future_features != NULL &&
(suggestion = _Py_CalculateSuggestions(future_features, name->name)) != 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