mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	* Document `from __future__ import annotations` * Provide plumbing and tests for `from __future__ import annotations` * Implement unparsing the AST back to string form This is required for PEP 563 and as such only implements a part of the unparsing process that covers expressions.
		
			
				
	
	
		
			271 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
# Test various flavors of legal and illegal future statements
 | 
						|
 | 
						|
from functools import partial
 | 
						|
import unittest
 | 
						|
from test import support
 | 
						|
from textwrap import dedent
 | 
						|
import os
 | 
						|
import re
 | 
						|
 | 
						|
rx = re.compile(r'\((\S+).py, line (\d+)')
 | 
						|
 | 
						|
def get_error_location(msg):
 | 
						|
    mo = rx.search(str(msg))
 | 
						|
    return mo.group(1, 2)
 | 
						|
 | 
						|
class FutureTest(unittest.TestCase):
 | 
						|
 | 
						|
    def check_syntax_error(self, err, basename, lineno, offset=0):
 | 
						|
        self.assertIn('%s.py, line %d' % (basename, lineno), str(err))
 | 
						|
        self.assertEqual(os.path.basename(err.filename), basename + '.py')
 | 
						|
        self.assertEqual(err.lineno, lineno)
 | 
						|
        self.assertEqual(err.offset, offset)
 | 
						|
 | 
						|
    def test_future1(self):
 | 
						|
        with support.CleanImport('future_test1'):
 | 
						|
            from test import future_test1
 | 
						|
            self.assertEqual(future_test1.result, 6)
 | 
						|
 | 
						|
    def test_future2(self):
 | 
						|
        with support.CleanImport('future_test2'):
 | 
						|
            from test import future_test2
 | 
						|
            self.assertEqual(future_test2.result, 6)
 | 
						|
 | 
						|
    def test_future3(self):
 | 
						|
        with support.CleanImport('test_future3'):
 | 
						|
            from test import test_future3
 | 
						|
 | 
						|
    def test_badfuture3(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future3
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future3", 3)
 | 
						|
 | 
						|
    def test_badfuture4(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future4
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future4", 3)
 | 
						|
 | 
						|
    def test_badfuture5(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future5
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future5", 4)
 | 
						|
 | 
						|
    def test_badfuture6(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future6
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future6", 3)
 | 
						|
 | 
						|
    def test_badfuture7(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future7
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future7", 3, 53)
 | 
						|
 | 
						|
    def test_badfuture8(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future8
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future8", 3)
 | 
						|
 | 
						|
    def test_badfuture9(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future9
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future9", 3, 0)
 | 
						|
 | 
						|
    def test_badfuture10(self):
 | 
						|
        with self.assertRaises(SyntaxError) as cm:
 | 
						|
            from test import badsyntax_future10
 | 
						|
        self.check_syntax_error(cm.exception, "badsyntax_future10", 3, 0)
 | 
						|
 | 
						|
    def test_parserhack(self):
 | 
						|
        # test that the parser.c::future_hack function works as expected
 | 
						|
        # Note: although this test must pass, it's not testing the original
 | 
						|
        #       bug as of 2.6 since the with statement is not optional and
 | 
						|
        #       the parser hack disabled. If a new keyword is introduced in
 | 
						|
        #       2.6, change this to refer to the new future import.
 | 
						|
        try:
 | 
						|
            exec("from __future__ import print_function; print 0")
 | 
						|
        except SyntaxError:
 | 
						|
            pass
 | 
						|
        else:
 | 
						|
            self.fail("syntax error didn't occur")
 | 
						|
 | 
						|
        try:
 | 
						|
            exec("from __future__ import (print_function); print 0")
 | 
						|
        except SyntaxError:
 | 
						|
            pass
 | 
						|
        else:
 | 
						|
            self.fail("syntax error didn't occur")
 | 
						|
 | 
						|
    def test_multiple_features(self):
 | 
						|
        with support.CleanImport("test.test_future5"):
 | 
						|
            from test import test_future5
 | 
						|
 | 
						|
    def test_unicode_literals_exec(self):
 | 
						|
        scope = {}
 | 
						|
        exec("from __future__ import unicode_literals; x = ''", {}, scope)
 | 
						|
        self.assertIsInstance(scope["x"], str)
 | 
						|
 | 
						|
class AnnotationsFutureTestCase(unittest.TestCase):
 | 
						|
    template = dedent(
 | 
						|
        """
 | 
						|
        from __future__ import annotations
 | 
						|
        def f() -> {ann}:
 | 
						|
            ...
 | 
						|
        def g(arg: {ann}) -> None:
 | 
						|
            ...
 | 
						|
        var: {ann}
 | 
						|
        var2: {ann} = None
 | 
						|
        """
 | 
						|
    )
 | 
						|
 | 
						|
    def getActual(self, annotation):
 | 
						|
        scope = {}
 | 
						|
        exec(self.template.format(ann=annotation), {}, scope)
 | 
						|
        func_ret_ann = scope['f'].__annotations__['return']
 | 
						|
        func_arg_ann = scope['g'].__annotations__['arg']
 | 
						|
        var_ann1 = scope['__annotations__']['var']
 | 
						|
        var_ann2 = scope['__annotations__']['var2']
 | 
						|
        self.assertEqual(func_ret_ann, func_arg_ann)
 | 
						|
        self.assertEqual(func_ret_ann, var_ann1)
 | 
						|
        self.assertEqual(func_ret_ann, var_ann2)
 | 
						|
        return func_ret_ann
 | 
						|
 | 
						|
    def assertAnnotationEqual(
 | 
						|
        self, annotation, expected=None, drop_parens=False, is_tuple=False,
 | 
						|
    ):
 | 
						|
        actual = self.getActual(annotation)
 | 
						|
        if expected is None:
 | 
						|
            expected = annotation if not is_tuple else annotation[1:-1]
 | 
						|
        if drop_parens:
 | 
						|
            self.assertNotEqual(actual, expected)
 | 
						|
            actual = actual.replace("(", "").replace(")", "")
 | 
						|
 | 
						|
        self.assertEqual(actual, expected)
 | 
						|
 | 
						|
    def test_annotations(self):
 | 
						|
        eq = self.assertAnnotationEqual
 | 
						|
        eq('...')
 | 
						|
        eq("'some_string'")
 | 
						|
        eq("b'\\xa3'")
 | 
						|
        eq('Name')
 | 
						|
        eq('None')
 | 
						|
        eq('True')
 | 
						|
        eq('False')
 | 
						|
        eq('1')
 | 
						|
        eq('1.0')
 | 
						|
        eq('1j')
 | 
						|
        eq('True or False')
 | 
						|
        eq('True or False or None')
 | 
						|
        eq('True and False')
 | 
						|
        eq('True and False and None')
 | 
						|
        eq('(Name1 and Name2) or Name3')
 | 
						|
        eq('Name1 or (Name2 and Name3)')
 | 
						|
        eq('(Name1 and Name2) or (Name3 and Name4)')
 | 
						|
        eq('Name1 or (Name2 and Name3) or Name4')
 | 
						|
        eq('v1 << 2')
 | 
						|
        eq('1 >> v2')
 | 
						|
        eq(r'1 % finished')
 | 
						|
        eq('((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8)')
 | 
						|
        eq('not great')
 | 
						|
        eq('~great')
 | 
						|
        eq('+value')
 | 
						|
        eq('-1')
 | 
						|
        eq('(~int) and (not ((v1 ^ (123 + v2)) | True))')
 | 
						|
        eq('lambda arg: None')
 | 
						|
        eq('lambda a=True: a')
 | 
						|
        eq('lambda a, b, c=True: a')
 | 
						|
        eq("lambda a, b, c=True, *, d=(1 << v2), e='str': a")
 | 
						|
        eq("lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b")
 | 
						|
        eq('1 if True else 2')
 | 
						|
        eq('(str or None) if True else (str or bytes or None)')
 | 
						|
        eq('(str or None) if (1 if True else 2) else (str or bytes or None)')
 | 
						|
        eq("{'2.7': dead, '3.7': (long_live or die_hard)}")
 | 
						|
        eq("{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}}")
 | 
						|
        eq("{**a, **b, **c}")
 | 
						|
        eq("{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')}")
 | 
						|
        eq("({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None")
 | 
						|
        eq("()")
 | 
						|
        eq("(1,)")
 | 
						|
        eq("(1, 2)")
 | 
						|
        eq("(1, 2, 3)")
 | 
						|
        eq("[]")
 | 
						|
        eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]")
 | 
						|
        eq("{i for i in (1, 2, 3)}")
 | 
						|
        eq("{(i ** 2) for i in (1, 2, 3)}")
 | 
						|
        eq("{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
 | 
						|
        eq("{((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}")
 | 
						|
        eq("[i for i in (1, 2, 3)]")
 | 
						|
        eq("[(i ** 2) for i in (1, 2, 3)]")
 | 
						|
        eq("[(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
 | 
						|
        eq("[((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]")
 | 
						|
        eq(r"{i: 0 for i in (1, 2, 3)}")
 | 
						|
        eq("{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}")
 | 
						|
        eq("Python3 > Python2 > COBOL")
 | 
						|
        eq("Life is Life")
 | 
						|
        eq("call()")
 | 
						|
        eq("call(arg)")
 | 
						|
        eq("call(kwarg='hey')")
 | 
						|
        eq("call(arg, kwarg='hey')")
 | 
						|
        eq("call(arg, another, kwarg='hey', **kwargs)")
 | 
						|
        eq("lukasz.langa.pl")
 | 
						|
        eq("call.me(maybe)")
 | 
						|
        eq("1 .real")
 | 
						|
        eq("1.0 .real")
 | 
						|
        eq("....__class__")
 | 
						|
        eq("list[str]")
 | 
						|
        eq("dict[str, int]")
 | 
						|
        eq("tuple[str, ...]")
 | 
						|
        eq("tuple[str, int, float, dict[str, int]]")
 | 
						|
        eq("slice[0]")
 | 
						|
        eq("slice[0:1]")
 | 
						|
        eq("slice[0:1:2]")
 | 
						|
        eq("slice[:]")
 | 
						|
        eq("slice[:-1]")
 | 
						|
        eq("slice[1:]")
 | 
						|
        eq("slice[::-1]")
 | 
						|
        eq('(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)')
 | 
						|
        eq("f'f-string without formatted values is just a string'")
 | 
						|
        eq("f'{{NOT a formatted value}}'")
 | 
						|
        eq("f'some f-string with {a} {few():.2f} {formatted.values!r}'")
 | 
						|
        eq('''f"{f'{nested} inner'} outer"''')
 | 
						|
        eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
 | 
						|
 | 
						|
    def test_annotations_inexact(self):
 | 
						|
        """Source formatting is not always preserved
 | 
						|
 | 
						|
        This is due to reconstruction from AST.  We *need to* put the parens
 | 
						|
        in nested expressions because we don't know if the source code
 | 
						|
        had them in the first place or not.
 | 
						|
        """
 | 
						|
        eq = partial(self.assertAnnotationEqual, drop_parens=True)
 | 
						|
        eq('Name1 and Name2 or Name3')
 | 
						|
        eq('Name1 or Name2 and Name3')
 | 
						|
        eq('Name1 and Name2 or Name3 and Name4')
 | 
						|
        eq('Name1 or Name2 and Name3 or Name4')
 | 
						|
        eq('1 + v2 - v3 * 4 ^ v5 ** 6 / 7 // 8')
 | 
						|
        eq('~int and not v1 ^ 123 + v2 | True')
 | 
						|
        eq('str or None if True else str or bytes or None')
 | 
						|
        eq("{'2.7': dead, '3.7': long_live or die_hard}")
 | 
						|
        eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
 | 
						|
        eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
 | 
						|
        # Consequently, we always drop unnecessary parens if they were given in
 | 
						|
        # the outer scope:
 | 
						|
        some_name = self.getActual("(SomeName)")
 | 
						|
        self.assertEqual(some_name, 'SomeName')
 | 
						|
        # Interestingly, in the case of tuples (and generator expressions) the
 | 
						|
        # parens are *required* by the Python syntax in the annotation context.
 | 
						|
        # But there's no point storing that detail in __annotations__ so we're
 | 
						|
        # fine with the parens-less form.
 | 
						|
        eq = partial(self.assertAnnotationEqual, is_tuple=True)
 | 
						|
        eq("(Good, Bad, Ugly)")
 | 
						|
        eq("(i for i in (1, 2, 3))")
 | 
						|
        eq("((i ** 2) for i in (1, 2, 3))")
 | 
						|
        eq("((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
 | 
						|
        eq("(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))")
 | 
						|
        eq("(*starred)")
 | 
						|
        eq('(yield from outside_of_generator)')
 | 
						|
        eq('(yield)')
 | 
						|
        eq('(await some.complicated[0].call(with_args=(True or (1 is not 1))))')
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    unittest.main()
 |