1010import textwrap
1111import types
1212import warnings
13- import codeop
14- import keyword
15- import tokenize
16- import io
1713import importlib .util
1814import pathlib
1915
@@ -1182,7 +1178,13 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
11821178 self .end_offset = exc_value .end_offset
11831179 self .msg = exc_value .msg
11841180 self ._is_syntax_error = True
1185- self ._exc_metadata = getattr (exc_value , "_metadata" , None )
1181+ metadata = getattr (exc_value , "_metadata" , None )
1182+ if isinstance (metadata , tuple ) and len (metadata ) == 6 :
1183+ (suggestion , self .text , lineno , self .offset ,
1184+ end_lineno , self .end_offset ) = metadata
1185+ self .lineno = str (lineno )
1186+ self .end_lineno = str (end_lineno )
1187+ self .msg = f"invalid syntax. Did you mean '{ suggestion } '?"
11861188 elif exc_type and issubclass (exc_type , ImportError ) and \
11871189 getattr (exc_value , "name_from" , None ) is not None :
11881190 wrong_name = getattr (exc_value , "name_from" , None )
@@ -1413,122 +1415,6 @@ def format_exception_only(self, *, show_group=False, _depth=0, **kwargs):
14131415 for ex in self .exceptions :
14141416 yield from ex .format_exception_only (show_group = show_group , _depth = _depth + 1 , colorize = colorize )
14151417
1416- def _find_keyword_typos (self ):
1417- assert self ._is_syntax_error
1418- try :
1419- import _suggestions
1420- except ImportError :
1421- _suggestions = None
1422-
1423- # Only try to find keyword typos if there is no custom message
1424- if self .msg != "invalid syntax" and "Perhaps you forgot a comma" not in self .msg :
1425- return
1426-
1427- if not self ._exc_metadata :
1428- return
1429-
1430- line , offset , source = self ._exc_metadata
1431- end_line = int (self .lineno ) if self .lineno is not None else 0
1432- lines = None
1433- from_filename = False
1434-
1435- if source is None :
1436- if self .filename :
1437- try :
1438- with open (self .filename ) as f :
1439- lines = f .read ().splitlines ()
1440- except Exception :
1441- line , end_line , offset = 0 ,1 ,0
1442- else :
1443- from_filename = True
1444- lines = lines if lines is not None else self .text .splitlines ()
1445- else :
1446- lines = source .splitlines ()
1447-
1448- error_code = lines [line - 1 if line > 0 else 0 :end_line ]
1449- error_code = textwrap .dedent ('\n ' .join (error_code ))
1450-
1451- # Do not continue if the source is too large
1452- if len (error_code ) > 1024 :
1453- return
1454-
1455- # If the original code doesn't raise SyntaxError, we can't validate
1456- # that a keyword replacement actually fixes anything
1457- try :
1458- codeop .compile_command (error_code , symbol = "exec" , flags = codeop .PyCF_ONLY_AST )
1459- except SyntaxError :
1460- pass # Good - the original code has a syntax error we might fix
1461- else :
1462- return # Original code compiles or is incomplete - can't validate fixes
1463-
1464- error_lines = error_code .splitlines ()
1465- tokens = tokenize .generate_tokens (io .StringIO (error_code ).readline )
1466- tokens_left_to_process = 10
1467- import difflib
1468- for token in tokens :
1469- start , end = token .start , token .end
1470- if token .type != tokenize .NAME :
1471- continue
1472- # Only consider NAME tokens on the same line as the error
1473- the_end = end_line if line == 0 else end_line + 1
1474- if from_filename and token .start [0 ]+ line != the_end :
1475- continue
1476- wrong_name = token .string
1477- if wrong_name in keyword .kwlist :
1478- continue
1479-
1480- # Limit the number of valid tokens to consider to not spend
1481- # to much time in this function
1482- tokens_left_to_process -= 1
1483- if tokens_left_to_process < 0 :
1484- break
1485- # Limit the number of possible matches to try
1486- max_matches = 3
1487- matches = []
1488-
1489- hint = _get_cross_language_keyword_hint (wrong_name )
1490- if hint :
1491- matches .append (hint )
1492- if _suggestions is not None :
1493- suggestion = _suggestions ._generate_suggestions (keyword .kwlist + keyword .softkwlist , wrong_name )
1494- if suggestion :
1495- matches .append (suggestion )
1496- matches .extend (
1497- difflib .get_close_matches (
1498- wrong_name ,
1499- keyword .kwlist + keyword .softkwlist ,
1500- n = max_matches ,
1501- cutoff = 0.5
1502- )
1503- )
1504- matches = matches [:max_matches ]
1505- for suggestion in matches :
1506- if not suggestion or suggestion == wrong_name :
1507- continue
1508- # Try to replace the token with the keyword
1509- the_lines = error_lines .copy ()
1510- the_line = the_lines [start [0 ] - 1 ][:]
1511- chars = list (the_line )
1512- chars [token .start [1 ]:token .end [1 ]] = suggestion
1513- the_lines [start [0 ] - 1 ] = '' .join (chars )
1514- code = '\n ' .join (the_lines )
1515-
1516- # Check if it works
1517- try :
1518- codeop .compile_command (code , symbol = "exec" , flags = codeop .PyCF_ONLY_AST )
1519- except SyntaxError :
1520- continue
1521-
1522- # Keep token.line but handle offsets correctly
1523- self .text = token .line
1524- self .offset = token .start [1 ] + 1
1525- self .end_offset = token .end [1 ] + 1
1526- self .lineno = start [0 ]
1527- self .end_lineno = end [0 ]
1528- self .msg = f"invalid syntax. Did you mean '{ suggestion } '?"
1529- return
1530-
1531-
15321418 def _format_syntax_error (self , stype , ** kwargs ):
15331419 """Format SyntaxError exceptions (internal helper)."""
15341420 # Show exactly where the problem was found.
@@ -1555,9 +1441,6 @@ def _format_syntax_error(self, stype, **kwargs):
15551441 # text = " foo\n"
15561442 # rtext = " foo"
15571443 # ltext = "foo"
1558- with suppress (Exception ):
1559- self ._find_keyword_typos ()
1560- text = self .text
15611444 rtext = text .rstrip ('\n ' )
15621445 ltext = rtext .lstrip (' \n \f ' )
15631446 spaces = len (rtext ) - len (ltext )
@@ -1798,17 +1681,6 @@ def print(self, *, file=None, chain=True, **kwargs):
17981681})
17991682
18001683
1801- # Cross-language keyword suggestions.
1802- _CROSS_LANGUAGE_KEYWORD_HINTS = frozendict ({
1803- # C/C++ equivalents
1804- 'switch' : 'match' ,
1805- 'delete' : 'del' ,
1806- # function define equivalents
1807- 'function' : 'def' ,
1808- 'func' : 'def' ,
1809- 'void' : 'def' ,
1810- })
1811-
18121684def _substitution_cost (ch_a , ch_b ):
18131685 if ch_a == ch_b :
18141686 return 0
@@ -1888,12 +1760,6 @@ def _get_cross_language_hint(obj, wrong_name):
18881760 return None
18891761
18901762
1891- def _get_cross_language_keyword_hint (wrong_name ):
1892- """Check if wrong_name is a common keyword from another language
1893- """
1894- return _CROSS_LANGUAGE_KEYWORD_HINTS .get (wrong_name )
1895-
1896-
18971763def _get_safe___dir__ (obj ):
18981764 # Use obj.__dir__() to avoid a TypeError when calling dir(obj).
18991765 # See gh-131001 and gh-139933.
0 commit comments