468 lines
14 KiB
Python
468 lines
14 KiB
Python
|
# coding=utf-8
|
||
|
"""Tests for scope."""
|
||
|
# Copyright 2017 Google LLC
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
from __future__ import absolute_import
|
||
|
from __future__ import division
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import ast
|
||
|
import textwrap
|
||
|
import unittest
|
||
|
|
||
|
from pasta.base import ast_utils
|
||
|
from pasta.base import scope
|
||
|
from pasta.base import test_utils
|
||
|
|
||
|
|
||
|
class ScopeTest(test_utils.TestCase):
|
||
|
|
||
|
def test_top_level_imports(self):
|
||
|
self.maxDiff = None
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
import bbb, ccc.ddd
|
||
|
import aaa.bbb.ccc
|
||
|
from eee import fff
|
||
|
from ggg.hhh import iii, jjj
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_1_aaa = nodes[0].names[0]
|
||
|
node_2_bbb = nodes[1].names[0]
|
||
|
node_2_ccc_ddd = nodes[1].names[1]
|
||
|
node_3_aaa_bbb_ccc = nodes[2].names[0]
|
||
|
node_4_eee = nodes[3]
|
||
|
node_4_fff = nodes[3].names[0]
|
||
|
node_5_ggg_hhh = nodes[4]
|
||
|
node_5_iii = nodes[4].names[0]
|
||
|
node_5_jjj = nodes[4].names[1]
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(
|
||
|
s.names.keys(), {
|
||
|
'aaa', 'bbb', 'ccc', 'fff', 'iii', 'jjj'
|
||
|
})
|
||
|
self.assertItemsEqual(
|
||
|
s.external_references.keys(), {
|
||
|
'aaa', 'bbb', 'ccc', 'ccc.ddd', 'aaa.bbb', 'aaa.bbb.ccc', 'eee',
|
||
|
'eee.fff', 'ggg', 'ggg.hhh', 'ggg.hhh.iii', 'ggg.hhh.jjj'
|
||
|
})
|
||
|
self.assertItemsEqual(s.external_references['aaa'], [
|
||
|
scope.ExternalReference('aaa', node_1_aaa, s.names['aaa']),
|
||
|
scope.ExternalReference('aaa', node_3_aaa_bbb_ccc, s.names['aaa']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['bbb'], [
|
||
|
scope.ExternalReference('bbb', node_2_bbb, s.names['bbb']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['ccc'], [
|
||
|
scope.ExternalReference('ccc', node_2_ccc_ddd, s.names['ccc']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['ccc.ddd'], [
|
||
|
scope.ExternalReference('ccc.ddd', node_2_ccc_ddd,
|
||
|
s.names['ccc'].attrs['ddd']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['aaa.bbb'], [
|
||
|
scope.ExternalReference('aaa.bbb', node_3_aaa_bbb_ccc,
|
||
|
s.names['aaa'].attrs['bbb']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['aaa.bbb.ccc'], [
|
||
|
scope.ExternalReference('aaa.bbb.ccc', node_3_aaa_bbb_ccc,
|
||
|
s.names['aaa'].attrs['bbb'].attrs['ccc']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['eee'], [
|
||
|
scope.ExternalReference('eee', node_4_eee, None),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['eee.fff'], [
|
||
|
scope.ExternalReference('eee.fff', node_4_fff, s.names['fff']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['ggg'], [
|
||
|
scope.ExternalReference('ggg', node_5_ggg_hhh, None),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['ggg.hhh'], [
|
||
|
scope.ExternalReference('ggg.hhh', node_5_ggg_hhh, None),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['ggg.hhh.iii'], [
|
||
|
scope.ExternalReference('ggg.hhh.iii', node_5_iii, s.names['iii']),
|
||
|
])
|
||
|
self.assertItemsEqual(s.external_references['ggg.hhh.jjj'], [
|
||
|
scope.ExternalReference('ggg.hhh.jjj', node_5_jjj, s.names['jjj']),
|
||
|
])
|
||
|
|
||
|
self.assertIs(s.names['aaa'].definition, node_1_aaa)
|
||
|
self.assertIs(s.names['bbb'].definition, node_2_bbb)
|
||
|
self.assertIs(s.names['ccc'].definition, node_2_ccc_ddd)
|
||
|
self.assertIs(s.names['fff'].definition, node_4_fff)
|
||
|
self.assertIs(s.names['iii'].definition, node_5_iii)
|
||
|
self.assertIs(s.names['jjj'].definition, node_5_jjj)
|
||
|
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [node_3_aaa_bbb_ccc])
|
||
|
for ref in {'bbb', 'ccc', 'fff', 'iii', 'jjj'}:
|
||
|
self.assertEqual(s.names[ref].reads, [], 'Expected no reads for %s' % ref)
|
||
|
|
||
|
def test_if_nested_imports(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
if a:
|
||
|
import aaa
|
||
|
elif b:
|
||
|
import bbb
|
||
|
else:
|
||
|
import ccc
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_aaa, node_bbb, node_ccc = ast_utils.find_nodes_by_type(tree, ast.alias)
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'bbb', 'ccc', 'a', 'b'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa', 'bbb', 'ccc'})
|
||
|
|
||
|
self.assertEqual(s.names['aaa'].definition, node_aaa)
|
||
|
self.assertEqual(s.names['bbb'].definition, node_bbb)
|
||
|
self.assertEqual(s.names['ccc'].definition, node_ccc)
|
||
|
|
||
|
self.assertIsNone(s.names['a'].definition)
|
||
|
self.assertIsNone(s.names['b'].definition)
|
||
|
|
||
|
for ref in {'aaa', 'bbb', 'ccc'}:
|
||
|
self.assertEqual(s.names[ref].reads, [],
|
||
|
'Expected no reads for %s' % ref)
|
||
|
|
||
|
def test_try_nested_imports(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
try:
|
||
|
import aaa
|
||
|
except:
|
||
|
import bbb
|
||
|
finally:
|
||
|
import ccc
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_aaa, node_bbb, node_ccc = ast_utils.find_nodes_by_type(tree, ast.alias)
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'bbb', 'ccc'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa', 'bbb', 'ccc'})
|
||
|
|
||
|
self.assertEqual(s.names['aaa'].definition, node_aaa)
|
||
|
self.assertEqual(s.names['bbb'].definition, node_bbb)
|
||
|
self.assertEqual(s.names['ccc'].definition, node_ccc)
|
||
|
|
||
|
for ref in {'aaa', 'bbb', 'ccc'}:
|
||
|
self.assertEqual(s.names[ref].reads, [],
|
||
|
'Expected no reads for %s' % ref)
|
||
|
|
||
|
def test_functiondef_nested_imports(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
def foo(bar):
|
||
|
import aaa
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_aaa = ast_utils.find_nodes_by_type(tree, ast.alias)[0]
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
|
||
|
def test_classdef_nested_imports(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
class Foo():
|
||
|
import aaa
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_aaa = nodes[0].body[0].names[0]
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'Foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
|
||
|
def test_multilevel_import_reads(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa.bbb.ccc
|
||
|
aaa.bbb.ccc.foo()
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_ref = nodes[1].value.func.value
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.external_references.keys(),
|
||
|
{'aaa', 'aaa.bbb', 'aaa.bbb.ccc'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [node_ref.value.value])
|
||
|
self.assertItemsEqual(s.names['aaa'].attrs['bbb'].reads, [node_ref.value])
|
||
|
self.assertItemsEqual(s.names['aaa'].attrs['bbb'].attrs['ccc'].reads,
|
||
|
[node_ref])
|
||
|
|
||
|
def test_import_reads_in_functiondef(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
@aaa.x
|
||
|
def foo(bar):
|
||
|
return aaa
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
return_value = nodes[1].body[0].value
|
||
|
decorator = nodes[1].decorator_list[0].value
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [decorator, return_value])
|
||
|
|
||
|
def test_import_reads_in_classdef(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
@aaa.x
|
||
|
class Foo(aaa.Bar):
|
||
|
pass
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_aaa = nodes[0].names[0]
|
||
|
decorator = nodes[1].decorator_list[0].value
|
||
|
base = nodes[1].bases[0].value
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'Foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [decorator, base])
|
||
|
|
||
|
def test_import_masked_by_function_arg(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
def foo(aaa=aaa):
|
||
|
return aaa
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
argval = nodes[1].args.defaults[0]
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [argval])
|
||
|
|
||
|
def test_import_masked_by_assign(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
def foo():
|
||
|
aaa = 123
|
||
|
return aaa
|
||
|
aaa
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
node_aaa = nodes[2].value
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [node_aaa])
|
||
|
|
||
|
def test_import_in_decortator(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
@aaa.wrapper
|
||
|
def foo(aaa=1):
|
||
|
pass
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
decorator = nodes[1].decorator_list[0].value
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [decorator])
|
||
|
|
||
|
@test_utils.requires_features('type_annotations')
|
||
|
def test_import_in_return_type(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
def foo() -> aaa.Foo:
|
||
|
pass
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
func = nodes[1]
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads, [func.returns.value])
|
||
|
|
||
|
@test_utils.requires_features('type_annotations')
|
||
|
def test_import_in_argument_type(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
def foo(bar: aaa.Bar):
|
||
|
pass
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
func = nodes[1]
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'foo'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads,
|
||
|
[func.args.args[0].annotation.value])
|
||
|
|
||
|
def test_import_attribute_references(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa.bbb.ccc, ddd.eee
|
||
|
aaa.x()
|
||
|
aaa.bbb.y()
|
||
|
aaa.bbb.ccc.z()
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
nodes = tree.body
|
||
|
|
||
|
call1 = nodes[1].value.func.value
|
||
|
call2 = nodes[2].value.func.value
|
||
|
call3 = nodes[3].value.func.value
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'ddd'})
|
||
|
self.assertItemsEqual(s.external_references.keys(),
|
||
|
{'aaa', 'aaa.bbb', 'aaa.bbb.ccc', 'ddd', 'ddd.eee'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads,
|
||
|
[call1, call2.value, call3.value.value])
|
||
|
self.assertItemsEqual(s.names['aaa'].attrs['bbb'].reads,
|
||
|
[call2, call3.value])
|
||
|
self.assertItemsEqual(s.names['aaa'].attrs['bbb'].attrs['ccc'].reads,
|
||
|
[call3])
|
||
|
|
||
|
def test_lookup_scope(self):
|
||
|
src = textwrap.dedent("""\
|
||
|
import a
|
||
|
def b(c, d, e=1):
|
||
|
class F(d):
|
||
|
g = 1
|
||
|
return c
|
||
|
""")
|
||
|
t = ast.parse(src)
|
||
|
import_node, func_node = t.body
|
||
|
class_node, return_node = func_node.body
|
||
|
|
||
|
sc = scope.analyze(t)
|
||
|
import_node_scope = sc.lookup_scope(import_node)
|
||
|
self.assertIs(import_node_scope.node, t)
|
||
|
self.assertIs(import_node_scope, sc)
|
||
|
self.assertItemsEqual(import_node_scope.names, ['a', 'b'])
|
||
|
|
||
|
func_node_scope = sc.lookup_scope(func_node)
|
||
|
self.assertIs(func_node_scope.node, func_node)
|
||
|
self.assertIs(func_node_scope.parent_scope, sc)
|
||
|
self.assertItemsEqual(func_node_scope.names, ['c', 'd', 'e', 'F'])
|
||
|
|
||
|
class_node_scope = sc.lookup_scope(class_node)
|
||
|
self.assertIs(class_node_scope.node, class_node)
|
||
|
self.assertIs(class_node_scope.parent_scope, func_node_scope)
|
||
|
self.assertItemsEqual(class_node_scope.names, ['g'])
|
||
|
|
||
|
return_node_scope = sc.lookup_scope(return_node)
|
||
|
self.assertIs(return_node_scope.node, func_node)
|
||
|
self.assertIs(return_node_scope, func_node_scope)
|
||
|
self.assertItemsEqual(return_node_scope.names, ['c', 'd', 'e', 'F'])
|
||
|
|
||
|
self.assertIs(class_node_scope.lookup_scope(func_node),
|
||
|
func_node_scope)
|
||
|
|
||
|
self.assertIsNone(sc.lookup_scope(ast.Name(id='foo')))
|
||
|
|
||
|
def test_class_methods(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
import aaa
|
||
|
class C:
|
||
|
def aaa(self):
|
||
|
return aaa
|
||
|
|
||
|
def bbb(self):
|
||
|
return aaa
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
importstmt, classdef = tree.body
|
||
|
method_aaa, method_bbb = classdef.body
|
||
|
|
||
|
s = scope.analyze(tree)
|
||
|
|
||
|
self.assertItemsEqual(s.names.keys(), {'aaa', 'C'})
|
||
|
self.assertItemsEqual(s.external_references.keys(), {'aaa'})
|
||
|
self.assertItemsEqual(s.names['aaa'].reads,
|
||
|
[method_aaa.body[0].value, method_bbb.body[0].value])
|
||
|
# TODO: Test references to C.aaa, C.bbb once supported
|
||
|
|
||
|
def test_vararg_kwarg_references_in_function_body(self):
|
||
|
source = textwrap.dedent("""\
|
||
|
def aaa(bbb, *ccc, **ddd):
|
||
|
ccc
|
||
|
ddd
|
||
|
eee(ccc, ddd)
|
||
|
""")
|
||
|
tree = ast.parse(source)
|
||
|
funcdef, call = tree.body
|
||
|
ccc_expr, ddd_expr = funcdef.body
|
||
|
|
||
|
sc = scope.analyze(tree)
|
||
|
|
||
|
func_scope = sc.lookup_scope(funcdef)
|
||
|
self.assertIn('ccc', func_scope.names)
|
||
|
self.assertItemsEqual(func_scope.names['ccc'].reads, [ccc_expr.value])
|
||
|
self.assertIn('ddd', func_scope.names)
|
||
|
self.assertItemsEqual(func_scope.names['ddd'].reads, [ddd_expr.value])
|
||
|
|
||
|
|
||
|
def suite():
|
||
|
result = unittest.TestSuite()
|
||
|
result.addTests(unittest.makeSuite(ScopeTest))
|
||
|
return result
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
unittest.main()
|