Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/metadata/scope_provider.py: 31%

620 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 07:09 +0000

1# Copyright (c) Meta Platforms, Inc. and affiliates. 

2# 

3# This source code is licensed under the MIT license found in the 

4# LICENSE file in the root directory of this source tree. 

5 

6 

7import abc 

8import builtins 

9from collections import defaultdict 

10from contextlib import contextmanager 

11from dataclasses import dataclass 

12from enum import auto, Enum 

13from typing import ( 

14 Collection, 

15 Dict, 

16 Iterator, 

17 List, 

18 Mapping, 

19 MutableMapping, 

20 Optional, 

21 Set, 

22 Tuple, 

23 Type, 

24 Union, 

25) 

26 

27import libcst as cst 

28from libcst import ensure_type 

29from libcst._add_slots import add_slots 

30from libcst.helpers import get_full_name_for_node 

31from libcst.metadata.base_provider import BatchableMetadataProvider 

32from libcst.metadata.expression_context_provider import ( 

33 ExpressionContext, 

34 ExpressionContextProvider, 

35) 

36 

37# Comprehensions are handled separately in _visit_comp_alike due to 

38# the complexity of the semantics 

39_ASSIGNMENT_LIKE_NODES = ( 

40 cst.AnnAssign, 

41 cst.AsName, 

42 cst.Assign, 

43 cst.AugAssign, 

44 cst.ClassDef, 

45 cst.CompFor, 

46 cst.FunctionDef, 

47 cst.Global, 

48 cst.Import, 

49 cst.ImportFrom, 

50 cst.NamedExpr, 

51 cst.Nonlocal, 

52 cst.Parameters, 

53 cst.WithItem, 

54) 

55 

56 

57@add_slots 

58@dataclass(frozen=False) 

59class Access: 

60 """ 

61 An Access records an access of an assignment. 

62 

63 .. note:: 

64 This scope analysis only analyzes access via a :class:`~libcst.Name` or a :class:`~libcst.Name` 

65 node embedded in other node like :class:`~libcst.Call` or :class:`~libcst.Attribute`. 

66 It doesn't support type annontation using :class:`~libcst.SimpleString` literal for forward 

67 references. E.g. in this example, the ``"Tree"`` isn't parsed as an access:: 

68 

69 class Tree: 

70 def __new__(cls) -> "Tree": 

71 ... 

72 """ 

73 

74 #: The node of the access. A name is an access when the expression context is 

75 #: :attr:`ExpressionContext.LOAD`. This is usually the name node representing the 

76 #: access, except for: 1) dotted imports, when it might be the attribute that 

77 #: represents the most specific part of the imported symbol; and 2) string 

78 #: annotations, when it is the entire string literal 

79 node: Union[cst.Name, cst.Attribute, cst.BaseString] 

80 

81 #: The scope of the access. Note that a access could be in a child scope of its 

82 #: assignment. 

83 scope: "Scope" 

84 

85 is_annotation: bool 

86 

87 is_type_hint: bool 

88 

89 __assignments: Set["BaseAssignment"] 

90 __index: int 

91 

92 def __init__( 

93 self, node: cst.Name, scope: "Scope", is_annotation: bool, is_type_hint: bool 

94 ) -> None: 

95 self.node = node 

96 self.scope = scope 

97 self.is_annotation = is_annotation 

98 self.is_type_hint = is_type_hint 

99 self.__assignments = set() 

100 self.__index = scope._assignment_count 

101 

102 def __hash__(self) -> int: 

103 return id(self) 

104 

105 @property 

106 def referents(self) -> Collection["BaseAssignment"]: 

107 """Return all assignments of the access.""" 

108 return self.__assignments 

109 

110 @property 

111 def _index(self) -> int: 

112 return self.__index 

113 

114 def record_assignment(self, assignment: "BaseAssignment") -> None: 

115 if assignment.scope != self.scope or assignment._index < self.__index: 

116 self.__assignments.add(assignment) 

117 

118 def record_assignments(self, name: str) -> None: 

119 assignments = self.scope[name] 

120 # filter out assignments that happened later than this access 

121 previous_assignments = { 

122 assignment 

123 for assignment in assignments 

124 if assignment.scope != self.scope or assignment._index < self.__index 

125 } 

126 if not previous_assignments and assignments and self.scope.parent != self.scope: 

127 previous_assignments = self.scope.parent[name] 

128 self.__assignments |= previous_assignments 

129 

130 

131class QualifiedNameSource(Enum): 

132 IMPORT = auto() 

133 BUILTIN = auto() 

134 LOCAL = auto() 

135 

136 

137@add_slots 

138@dataclass(frozen=True) 

139class QualifiedName: 

140 #: Qualified name, e.g. ``a.b.c`` or ``fn.<locals>.var``. 

141 name: str 

142 

143 #: Source of the name, either :attr:`QualifiedNameSource.IMPORT`, :attr:`QualifiedNameSource.BUILTIN` 

144 #: or :attr:`QualifiedNameSource.LOCAL`. 

145 source: QualifiedNameSource 

146 

147 

148class BaseAssignment(abc.ABC): 

149 """Abstract base class of :class:`Assignment` and :class:`BuitinAssignment`.""" 

150 

151 #: The name of assignment. 

152 name: str 

153 

154 #: The scope associates to assignment. 

155 scope: "Scope" 

156 __accesses: Set[Access] 

157 

158 def __init__(self, name: str, scope: "Scope") -> None: 

159 self.name = name 

160 self.scope = scope 

161 self.__accesses = set() 

162 

163 def record_access(self, access: Access) -> None: 

164 if access.scope != self.scope or self._index < access._index: 

165 self.__accesses.add(access) 

166 

167 def record_accesses(self, accesses: Set[Access]) -> None: 

168 later_accesses = { 

169 access 

170 for access in accesses 

171 if access.scope != self.scope or self._index < access._index 

172 } 

173 self.__accesses |= later_accesses 

174 earlier_accesses = accesses - later_accesses 

175 if earlier_accesses and self.scope.parent != self.scope: 

176 # Accesses "earlier" than the relevant assignment should be attached 

177 # to assignments of the same name in the parent 

178 for shadowed_assignment in self.scope.parent[self.name]: 

179 shadowed_assignment.record_accesses(earlier_accesses) 

180 

181 @property 

182 def references(self) -> Collection[Access]: 

183 """Return all accesses of the assignment.""" 

184 # we don't want to publicly expose the mutable version of this 

185 return self.__accesses 

186 

187 def __hash__(self) -> int: 

188 return id(self) 

189 

190 @property 

191 def _index(self) -> int: 

192 """Return an integer that represents the order of assignments in `scope`""" 

193 return -1 

194 

195 @abc.abstractmethod 

196 def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]: 

197 ... 

198 

199 

200class Assignment(BaseAssignment): 

201 """An assignment records the name, CSTNode and its accesses.""" 

202 

203 #: The node of assignment, it could be a :class:`~libcst.Import`, :class:`~libcst.ImportFrom`, 

204 #: :class:`~libcst.Name`, :class:`~libcst.FunctionDef`, or :class:`~libcst.ClassDef`. 

205 node: cst.CSTNode 

206 __index: int 

207 

208 def __init__( 

209 self, name: str, scope: "Scope", node: cst.CSTNode, index: int 

210 ) -> None: 

211 self.node = node 

212 self.__index = index 

213 super().__init__(name, scope) 

214 

215 @property 

216 def _index(self) -> int: 

217 return self.__index 

218 

219 def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]: 

220 return { 

221 QualifiedName( 

222 f"{self.scope._name_prefix}.{full_name}" 

223 if self.scope._name_prefix 

224 else full_name, 

225 QualifiedNameSource.LOCAL, 

226 ) 

227 } 

228 

229 

230# even though we don't override the constructor. 

231class BuiltinAssignment(BaseAssignment): 

232 """ 

233 A BuiltinAssignment represents an value provide by Python as a builtin, including 

234 `functions <https://docs.python.org/3/library/functions.html>`_, 

235 `constants <https://docs.python.org/3/library/constants.html>`_, and 

236 `types <https://docs.python.org/3/library/stdtypes.html>`_. 

237 """ 

238 

239 def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]: 

240 return {QualifiedName(f"builtins.{self.name}", QualifiedNameSource.BUILTIN)} 

241 

242 

243class ImportAssignment(Assignment): 

244 """An assignment records the import node and it's alias""" 

245 

246 as_name: cst.CSTNode 

247 

248 def __init__( 

249 self, 

250 name: str, 

251 scope: "Scope", 

252 node: cst.CSTNode, 

253 index: int, 

254 as_name: cst.CSTNode, 

255 ) -> None: 

256 super().__init__(name, scope, node, index) 

257 self.as_name = as_name 

258 

259 def get_module_name_for_import(self) -> str: 

260 module = "" 

261 if isinstance(self.node, cst.ImportFrom): 

262 module_attr = self.node.module 

263 relative = self.node.relative 

264 if module_attr: 

265 module = get_full_name_for_node(module_attr) or "" 

266 if relative: 

267 module = "." * len(relative) + module 

268 return module 

269 

270 def get_qualified_names_for(self, full_name: str) -> Set[QualifiedName]: 

271 module = self.get_module_name_for_import() 

272 results = set() 

273 assert isinstance(self.node, (cst.ImportFrom, cst.Import)) 

274 import_names = self.node.names 

275 if not isinstance(import_names, cst.ImportStar): 

276 for name in import_names: 

277 real_name = get_full_name_for_node(name.name) 

278 if not real_name: 

279 continue 

280 # real_name can contain `.` for dotted imports 

281 # for these we want to find the longest prefix that matches full_name 

282 parts = real_name.split(".") 

283 real_names = [".".join(parts[:i]) for i in range(len(parts), 0, -1)] 

284 for real_name in real_names: 

285 as_name = real_name 

286 if module and module.endswith("."): 

287 # from . import a 

288 # real_name should be ".a" 

289 real_name = f"{module}{real_name}" 

290 elif module: 

291 real_name = f"{module}.{real_name}" 

292 if name and name.asname: 

293 eval_alias = name.evaluated_alias 

294 if eval_alias is not None: 

295 as_name = eval_alias 

296 if full_name.startswith(as_name): 

297 remaining_name = full_name.split(as_name, 1)[1] 

298 if remaining_name and not remaining_name.startswith("."): 

299 continue 

300 remaining_name = remaining_name.lstrip(".") 

301 results.add( 

302 QualifiedName( 

303 f"{real_name}.{remaining_name}" 

304 if remaining_name 

305 else real_name, 

306 QualifiedNameSource.IMPORT, 

307 ) 

308 ) 

309 break 

310 return results 

311 

312 

313class Assignments: 

314 """A container to provide all assignments in a scope.""" 

315 

316 def __init__(self, assignments: Mapping[str, Collection[BaseAssignment]]) -> None: 

317 self._assignments = assignments 

318 

319 def __iter__(self) -> Iterator[BaseAssignment]: 

320 """Iterate through all assignments by ``for i in scope.assignments``.""" 

321 for assignments in self._assignments.values(): 

322 for assignment in assignments: 

323 yield assignment 

324 

325 def __getitem__(self, node: Union[str, cst.CSTNode]) -> Collection[BaseAssignment]: 

326 """Get assignments given a name str or :class:`~libcst.CSTNode` by ``scope.assignments[node]``""" 

327 name = _NameUtil.get_name_for(node) 

328 return set(self._assignments[name]) if name in self._assignments else set() 

329 

330 def __contains__(self, node: Union[str, cst.CSTNode]) -> bool: 

331 """Check if a name str or :class:`~libcst.CSTNode` has any assignment by ``node in scope.assignments``""" 

332 return len(self[node]) > 0 

333 

334 

335class Accesses: 

336 """A container to provide all accesses in a scope.""" 

337 

338 def __init__(self, accesses: Mapping[str, Collection[Access]]) -> None: 

339 self._accesses = accesses 

340 

341 def __iter__(self) -> Iterator[Access]: 

342 """Iterate through all accesses by ``for i in scope.accesses``.""" 

343 for accesses in self._accesses.values(): 

344 for access in accesses: 

345 yield access 

346 

347 def __getitem__(self, node: Union[str, cst.CSTNode]) -> Collection[Access]: 

348 """Get accesses given a name str or :class:`~libcst.CSTNode` by ``scope.accesses[node]``""" 

349 name = _NameUtil.get_name_for(node) 

350 return self._accesses[name] if name in self._accesses else set() 

351 

352 def __contains__(self, node: Union[str, cst.CSTNode]) -> bool: 

353 """Check if a name str or :class:`~libcst.CSTNode` has any access by ``node in scope.accesses``""" 

354 return len(self[node]) > 0 

355 

356 

357class _NameUtil: 

358 @staticmethod 

359 def get_name_for(node: Union[str, cst.CSTNode]) -> Optional[str]: 

360 """A helper function to retrieve simple name str from a CSTNode or str""" 

361 if isinstance(node, cst.Name): 

362 return node.value 

363 elif isinstance(node, str): 

364 return node 

365 elif isinstance(node, cst.Call): 

366 return _NameUtil.get_name_for(node.func) 

367 elif isinstance(node, cst.Subscript): 

368 return _NameUtil.get_name_for(node.value) 

369 elif isinstance(node, (cst.FunctionDef, cst.ClassDef)): 

370 return _NameUtil.get_name_for(node.name) 

371 return None 

372 

373 

374class Scope(abc.ABC): 

375 """ 

376 Base class of all scope classes. Scope object stores assignments from imports, 

377 variable assignments, function definition or class definition. 

378 A scope has a parent scope which represents the inheritance relationship. That means 

379 an assignment in parent scope is viewable to the child scope and the child scope may 

380 overwrites the assignment by using the same name. 

381 

382 Use ``name in scope`` to check whether a name is viewable in the scope. 

383 Use ``scope[name]`` to retrieve all viewable assignments in the scope. 

384 

385 .. note:: 

386 This scope analysis module only analyzes local variable names and it doesn't handle 

387 attribute names; for example, given ``a.b.c = 1``, local variable name ``a`` is recorded 

388 as an assignment instead of ``c`` or ``a.b.c``. To analyze the assignment/access of 

389 arbitrary object attributes, we leave the job to type inference metadata provider 

390 coming in the future. 

391 """ 

392 

393 #: Parent scope. Note the parent scope of a GlobalScope is itself. 

394 parent: "Scope" 

395 

396 #: Refers to the GlobalScope. 

397 globals: "GlobalScope" 

398 _assignments: MutableMapping[str, Set[BaseAssignment]] 

399 _assignment_count: int 

400 _accesses_by_name: MutableMapping[str, Set[Access]] 

401 _accesses_by_node: MutableMapping[cst.CSTNode, Set[Access]] 

402 _name_prefix: str 

403 

404 def __init__(self, parent: "Scope") -> None: 

405 super().__init__() 

406 self.parent = parent 

407 self.globals = parent.globals 

408 self._assignments = defaultdict(set) 

409 self._assignment_count = 0 

410 self._accesses_by_name = defaultdict(set) 

411 self._accesses_by_node = defaultdict(set) 

412 self._name_prefix = "" 

413 

414 def record_assignment(self, name: str, node: cst.CSTNode) -> None: 

415 target = self._find_assignment_target(name) 

416 target._assignments[name].add( 

417 Assignment( 

418 name=name, scope=target, node=node, index=target._assignment_count 

419 ) 

420 ) 

421 

422 def record_import_assignment( 

423 self, name: str, node: cst.CSTNode, as_name: cst.CSTNode 

424 ) -> None: 

425 target = self._find_assignment_target(name) 

426 target._assignments[name].add( 

427 ImportAssignment( 

428 name=name, 

429 scope=target, 

430 node=node, 

431 as_name=as_name, 

432 index=target._assignment_count, 

433 ) 

434 ) 

435 

436 def _find_assignment_target(self, name: str) -> "Scope": 

437 return self 

438 

439 def _find_assignment_target_parent(self, name: str) -> "Scope": 

440 return self 

441 

442 def record_access(self, name: str, access: Access) -> None: 

443 self._accesses_by_name[name].add(access) 

444 self._accesses_by_node[access.node].add(access) 

445 

446 def _getitem_from_self_or_parent(self, name: str) -> Set[BaseAssignment]: 

447 """Overridden by ClassScope to hide it's assignments from child scopes.""" 

448 return self[name] 

449 

450 def _contains_in_self_or_parent(self, name: str) -> bool: 

451 """Overridden by ClassScope to hide it's assignments from child scopes.""" 

452 return name in self 

453 

454 @abc.abstractmethod 

455 def __contains__(self, name: str) -> bool: 

456 """Check if the name str exist in current scope by ``name in scope``.""" 

457 ... 

458 

459 @abc.abstractmethod 

460 def __getitem__(self, name: str) -> Set[BaseAssignment]: 

461 """ 

462 Get assignments given a name str by ``scope[name]``. 

463 

464 .. note:: 

465 *Why does it return a list of assignments given a name instead of just one assignment?* 

466 

467 Many programming languages differentiate variable declaration and assignment. 

468 Further, those programming languages often disallow duplicate declarations within 

469 the same scope, and will often hoist the declaration (without its assignment) to 

470 the top of the scope. These design decisions make static analysis much easier, 

471 because it's possible to match a name against its single declaration for a given scope. 

472 

473 As an example, the following code would be valid in JavaScript:: 

474 

475 function fn() { 

476 console.log(value); // value is defined here, because the declaration is hoisted, but is currently 'undefined'. 

477 var value = 5; // A function-scoped declaration. 

478 } 

479 fn(); // prints 'undefined'. 

480 

481 In contrast, Python's declaration and assignment are identical and are not hoisted:: 

482 

483 if conditional_value: 

484 value = 5 

485 elif other_conditional_value: 

486 value = 10 

487 print(value) # possibly valid, depending on conditional execution 

488 

489 This code may throw a ``NameError`` if both conditional values are falsy. 

490 It also means that depending on the codepath taken, the original declaration 

491 could come from either ``value = ...`` assignment node. 

492 As a result, instead of returning a single declaration, 

493 we're forced to return a collection of all of the assignments we think could have 

494 defined a given name by the time a piece of code is executed. 

495 For the above example, value would resolve to a set of both assignments. 

496 """ 

497 ... 

498 

499 def __hash__(self) -> int: 

500 return id(self) 

501 

502 @abc.abstractmethod 

503 def record_global_overwrite(self, name: str) -> None: 

504 ... 

505 

506 @abc.abstractmethod 

507 def record_nonlocal_overwrite(self, name: str) -> None: 

508 ... 

509 

510 def get_qualified_names_for( 

511 self, node: Union[str, cst.CSTNode] 

512 ) -> Collection[QualifiedName]: 

513 """Get all :class:`~libcst.metadata.QualifiedName` in current scope given a 

514 :class:`~libcst.CSTNode`. 

515 The source of a qualified name can be either :attr:`QualifiedNameSource.IMPORT`, 

516 :attr:`QualifiedNameSource.BUILTIN` or :attr:`QualifiedNameSource.LOCAL`. 

517 Given the following example, ``c`` has qualified name ``a.b.c`` with source ``IMPORT``, 

518 ``f`` has qualified name ``Cls.f`` with source ``LOCAL``, ``a`` has qualified name 

519 ``Cls.f.<locals>.a``, ``i`` has qualified name ``Cls.f.<locals>.<comprehension>.i``, 

520 and the builtin ``int`` has qualified name ``builtins.int`` with source ``BUILTIN``:: 

521 

522 from a.b import c 

523 class Cls: 

524 def f(self) -> "c": 

525 c() 

526 a = int("1") 

527 [i for i in c()] 

528 

529 We extends `PEP-3155 <https://www.python.org/dev/peps/pep-3155/>`_ 

530 (defines ``__qualname__`` for class and function only; function namespace is followed 

531 by a ``<locals>``) to provide qualified name for all :class:`~libcst.CSTNode` 

532 recorded by :class:`~libcst.metadata.Assignment` and :class:`~libcst.metadata.Access`. 

533 The namespace of a comprehension (:class:`~libcst.ListComp`, :class:`~libcst.SetComp`, 

534 :class:`~libcst.DictComp`) is represented with ``<comprehension>``. 

535 

536 An imported name may be used for type annotation with :class:`~libcst.SimpleString` and 

537 currently resolving the qualified given :class:`~libcst.SimpleString` is not supported 

538 considering it could be a complex type annotation in the string which is hard to 

539 resolve, e.g. ``List[Union[int, str]]``. 

540 """ 

541 # if this node is an access we know the assignment and we can use that name 

542 node_accesses = ( 

543 self._accesses_by_node.get(node) if isinstance(node, cst.CSTNode) else None 

544 ) 

545 if node_accesses: 

546 return { 

547 qname 

548 for access in node_accesses 

549 for referent in access.referents 

550 for qname in referent.get_qualified_names_for(referent.name) 

551 } 

552 

553 full_name = get_full_name_for_node(node) 

554 if full_name is None: 

555 return set() 

556 

557 assignments = set() 

558 prefix = full_name 

559 while prefix: 

560 if prefix in self: 

561 assignments = self[prefix] 

562 break 

563 idx = prefix.rfind(".") 

564 prefix = None if idx == -1 else prefix[:idx] 

565 

566 if not isinstance(node, str): 

567 for assignment in assignments: 

568 if isinstance(assignment, Assignment) and _is_assignment( 

569 node, assignment.node 

570 ): 

571 return assignment.get_qualified_names_for(full_name) 

572 

573 results = set() 

574 for assignment in assignments: 

575 results |= assignment.get_qualified_names_for(full_name) 

576 return results 

577 

578 @property 

579 def assignments(self) -> Assignments: 

580 """Return an :class:`~libcst.metadata.Assignments` contains all assignmens in current scope.""" 

581 return Assignments(self._assignments) 

582 

583 @property 

584 def accesses(self) -> Accesses: 

585 """Return an :class:`~libcst.metadata.Accesses` contains all accesses in current scope.""" 

586 return Accesses(self._accesses_by_name) 

587 

588 

589class BuiltinScope(Scope): 

590 """ 

591 A BuiltinScope represents python builtin declarations. See https://docs.python.org/3/library/builtins.html 

592 """ 

593 

594 def __init__(self, globals: Scope) -> None: 

595 self.globals: Scope = globals # must be defined before Scope.__init__ is called 

596 super().__init__(parent=self) 

597 

598 def __contains__(self, name: str) -> bool: 

599 return hasattr(builtins, name) 

600 

601 def __getitem__(self, name: str) -> Set[BaseAssignment]: 

602 if name in self._assignments: 

603 return self._assignments[name] 

604 if hasattr(builtins, name): 

605 # note - we only see the builtin assignments during the deferred 

606 # access resolution. unfortunately that means we have to create the 

607 # assignment here, which can cause the set to mutate during iteration 

608 self._assignments[name].add(BuiltinAssignment(name, self)) 

609 return self._assignments[name] 

610 return set() 

611 

612 def record_global_overwrite(self, name: str) -> None: 

613 raise NotImplementedError("global overwrite in builtin scope are not allowed") 

614 

615 def record_nonlocal_overwrite(self, name: str) -> None: 

616 raise NotImplementedError("declarations in builtin scope are not allowed") 

617 

618 def _find_assignment_target(self, name: str) -> "Scope": 

619 raise NotImplementedError("assignments in builtin scope are not allowed") 

620 

621 

622class GlobalScope(Scope): 

623 """ 

624 A GlobalScope is the scope of module. All module level assignments are recorded in GlobalScope. 

625 """ 

626 

627 def __init__(self) -> None: 

628 super().__init__(parent=BuiltinScope(self)) 

629 

630 def __contains__(self, name: str) -> bool: 

631 if name in self._assignments: 

632 return len(self._assignments[name]) > 0 

633 return self.parent._contains_in_self_or_parent(name) 

634 

635 def __getitem__(self, name: str) -> Set[BaseAssignment]: 

636 if name in self._assignments: 

637 return self._assignments[name] 

638 else: 

639 return self.parent._getitem_from_self_or_parent(name) 

640 

641 def record_global_overwrite(self, name: str) -> None: 

642 pass 

643 

644 def record_nonlocal_overwrite(self, name: str) -> None: 

645 raise NotImplementedError("nonlocal declaration not allowed at module level") 

646 

647 

648class LocalScope(Scope, abc.ABC): 

649 _scope_overwrites: Dict[str, Scope] 

650 

651 #: Name of function. Used as qualified name. 

652 name: Optional[str] 

653 

654 #: The :class:`~libcst.CSTNode` node defines the current scope. 

655 node: cst.CSTNode 

656 

657 def __init__( 

658 self, parent: Scope, node: cst.CSTNode, name: Optional[str] = None 

659 ) -> None: 

660 super().__init__(parent) 

661 self.name = name 

662 self.node = node 

663 self._scope_overwrites = {} 

664 # pyre-fixme[4]: Attribute `_name_prefix` of class `LocalScope` has type `str` but no type is specified. 

665 self._name_prefix = self._make_name_prefix() 

666 

667 def record_global_overwrite(self, name: str) -> None: 

668 self._scope_overwrites[name] = self.globals 

669 

670 def record_nonlocal_overwrite(self, name: str) -> None: 

671 self._scope_overwrites[name] = self.parent 

672 

673 def _find_assignment_target(self, name: str) -> "Scope": 

674 if name in self._scope_overwrites: 

675 return self._scope_overwrites[name]._find_assignment_target_parent(name) 

676 else: 

677 return super()._find_assignment_target(name) 

678 

679 def __contains__(self, name: str) -> bool: 

680 if name in self._scope_overwrites: 

681 return name in self._scope_overwrites[name] 

682 if name in self._assignments: 

683 return len(self._assignments[name]) > 0 

684 return self.parent._contains_in_self_or_parent(name) 

685 

686 def __getitem__(self, name: str) -> Set[BaseAssignment]: 

687 if name in self._scope_overwrites: 

688 return self._scope_overwrites[name]._getitem_from_self_or_parent(name) 

689 if name in self._assignments: 

690 return self._assignments[name] 

691 else: 

692 return self.parent._getitem_from_self_or_parent(name) 

693 

694 def _make_name_prefix(self) -> str: 

695 # filter falsey strings out 

696 return ".".join(filter(None, [self.parent._name_prefix, self.name, "<locals>"])) 

697 

698 

699# even though we don't override the constructor. 

700class FunctionScope(LocalScope): 

701 """ 

702 When a function is defined, it creates a FunctionScope. 

703 """ 

704 

705 pass 

706 

707 

708# even though we don't override the constructor. 

709class ClassScope(LocalScope): 

710 """ 

711 When a class is defined, it creates a ClassScope. 

712 """ 

713 

714 def _find_assignment_target_parent(self, name: str) -> "Scope": 

715 """ 

716 Forward the assignment to parent. 

717 

718 def outer_fn(): 

719 v = ... # outer_fn's declaration 

720 class InnerCls: 

721 v = ... # shadows outer_fn's declaration 

722 def inner_fn(): 

723 nonlocal v 

724 v = ... # this should actually refer to outer_fn's declaration 

725 # and not to InnerCls's, because InnerCls's scope is 

726 # hidden from its children. 

727 

728 """ 

729 return self.parent._find_assignment_target_parent(name) 

730 

731 def _getitem_from_self_or_parent(self, name: str) -> Set[BaseAssignment]: 

732 """ 

733 Class variables are only accessible using ClassName.attribute, cls.attribute, or 

734 self.attribute in child scopes. They cannot be accessed with their bare names. 

735 """ 

736 return self.parent._getitem_from_self_or_parent(name) 

737 

738 def _contains_in_self_or_parent(self, name: str) -> bool: 

739 """ 

740 See :meth:`_getitem_from_self_or_parent` 

741 """ 

742 return self.parent._contains_in_self_or_parent(name) 

743 

744 def _make_name_prefix(self) -> str: 

745 # filter falsey strings out 

746 return ".".join(filter(None, [self.parent._name_prefix, self.name])) 

747 

748 

749# even though we don't override the constructor. 

750class ComprehensionScope(LocalScope): 

751 """ 

752 Comprehensions and generator expressions create their own scope. For example, in 

753 

754 [i for i in range(10)] 

755 

756 The variable ``i`` is only viewable within the ComprehensionScope. 

757 """ 

758 

759 # TODO: Assignment expressions (Python 3.8) will complicate ComprehensionScopes, 

760 # and will require us to handle such assignments as non-local. 

761 # https://www.python.org/dev/peps/pep-0572/#scope-of-the-target 

762 

763 def _make_name_prefix(self) -> str: 

764 # filter falsey strings out 

765 return ".".join(filter(None, [self.parent._name_prefix, "<comprehension>"])) 

766 

767 

768# Generates dotted names from an Attribute or Name node: 

769# Attribute(value=Name(value="a"), attr=Name(value="b")) -> ("a.b", "a") 

770# each string has the corresponding CSTNode attached to it 

771def _gen_dotted_names( 

772 node: Union[cst.Attribute, cst.Name] 

773) -> Iterator[Tuple[str, Union[cst.Attribute, cst.Name]]]: 

774 if isinstance(node, cst.Name): 

775 yield node.value, node 

776 else: 

777 value = node.value 

778 if isinstance(value, cst.Call): 

779 value = value.func 

780 if isinstance(value, (cst.Attribute, cst.Name)): 

781 name_values = _gen_dotted_names(value) 

782 try: 

783 next_name, next_node = next(name_values) 

784 except StopIteration: 

785 return 

786 else: 

787 yield next_name, next_node 

788 yield from name_values 

789 elif isinstance(value, (cst.Attribute, cst.Name)): 

790 name_values = _gen_dotted_names(value) 

791 try: 

792 next_name, next_node = next(name_values) 

793 except StopIteration: 

794 return 

795 else: 

796 yield f"{next_name}.{node.attr.value}", node 

797 yield next_name, next_node 

798 yield from name_values 

799 

800 

801def _is_assignment(node: cst.CSTNode, assignment_node: cst.CSTNode) -> bool: 

802 """ 

803 Returns true if ``node`` is part of the assignment at ``assignment_node``. 

804 

805 Normally this is just a simple identity check, except for imports where the 

806 assignment is attached to the entire import statement but we are interested in 

807 ``Name`` nodes inside the statement. 

808 """ 

809 if node is assignment_node: 

810 return True 

811 if isinstance(assignment_node, (cst.Import, cst.ImportFrom)): 

812 aliases = assignment_node.names 

813 if isinstance(aliases, cst.ImportStar): 

814 return False 

815 for alias in aliases: 

816 if alias.name is node: 

817 return True 

818 asname = alias.asname 

819 if asname is not None: 

820 if asname.name is node: 

821 return True 

822 return False 

823 

824 

825@dataclass(frozen=True) 

826class DeferredAccess: 

827 access: Access 

828 enclosing_attribute: Optional[cst.Attribute] 

829 enclosing_string_annotation: Optional[cst.BaseString] 

830 

831 

832class ScopeVisitor(cst.CSTVisitor): 

833 # since it's probably not useful. That can makes this visitor cleaner. 

834 def __init__(self, provider: "ScopeProvider") -> None: 

835 self.provider: ScopeProvider = provider 

836 self.scope: Scope = GlobalScope() 

837 self.__deferred_accesses: List[DeferredAccess] = [] 

838 self.__top_level_attribute_stack: List[Optional[cst.Attribute]] = [None] 

839 self.__in_annotation_stack: List[bool] = [False] 

840 self.__in_type_hint_stack: List[bool] = [False] 

841 self.__in_ignored_subscript: Set[cst.Subscript] = set() 

842 self.__last_string_annotation: Optional[cst.BaseString] = None 

843 self.__ignore_annotation: int = 0 

844 

845 @contextmanager 

846 def _new_scope( 

847 self, kind: Type[LocalScope], node: cst.CSTNode, name: Optional[str] = None 

848 ) -> Iterator[None]: 

849 parent_scope = self.scope 

850 self.scope = kind(parent_scope, node, name) 

851 try: 

852 yield 

853 finally: 

854 self.scope = parent_scope 

855 

856 @contextmanager 

857 def _switch_scope(self, scope: Scope) -> Iterator[None]: 

858 current_scope = self.scope 

859 self.scope = scope 

860 try: 

861 yield 

862 finally: 

863 self.scope = current_scope 

864 

865 def _visit_import_alike(self, node: Union[cst.Import, cst.ImportFrom]) -> bool: 

866 names = node.names 

867 if isinstance(names, cst.ImportStar): 

868 return False 

869 

870 # make sure node.names is Sequence[ImportAlias] 

871 for name in names: 

872 self.provider.set_metadata(name, self.scope) 

873 asname = name.asname 

874 if asname is not None: 

875 name_values = _gen_dotted_names(cst.ensure_type(asname.name, cst.Name)) 

876 import_node_asname = asname.name 

877 else: 

878 name_values = _gen_dotted_names(name.name) 

879 import_node_asname = name.name 

880 

881 for name_value, _ in name_values: 

882 self.scope.record_import_assignment( 

883 name_value, node, import_node_asname 

884 ) 

885 return False 

886 

887 def visit_Import(self, node: cst.Import) -> Optional[bool]: 

888 return self._visit_import_alike(node) 

889 

890 def visit_ImportFrom(self, node: cst.ImportFrom) -> Optional[bool]: 

891 return self._visit_import_alike(node) 

892 

893 def visit_Attribute(self, node: cst.Attribute) -> Optional[bool]: 

894 if self.__top_level_attribute_stack[-1] is None: 

895 self.__top_level_attribute_stack[-1] = node 

896 node.value.visit(self) # explicitly not visiting attr 

897 if self.__top_level_attribute_stack[-1] is node: 

898 self.__top_level_attribute_stack[-1] = None 

899 return False 

900 

901 def visit_Call(self, node: cst.Call) -> Optional[bool]: 

902 self.__top_level_attribute_stack.append(None) 

903 self.__in_type_hint_stack.append(False) 

904 qnames = {qn.name for qn in self.scope.get_qualified_names_for(node)} 

905 if "typing.NewType" in qnames or "typing.TypeVar" in qnames: 

906 node.func.visit(self) 

907 self.__in_type_hint_stack[-1] = True 

908 for arg in node.args[1:]: 

909 arg.visit(self) 

910 return False 

911 if "typing.cast" in qnames: 

912 node.func.visit(self) 

913 if len(node.args) > 0: 

914 self.__in_type_hint_stack.append(True) 

915 node.args[0].visit(self) 

916 self.__in_type_hint_stack.pop() 

917 for arg in node.args[1:]: 

918 arg.visit(self) 

919 return False 

920 return True 

921 

922 def leave_Call(self, original_node: cst.Call) -> None: 

923 self.__top_level_attribute_stack.pop() 

924 self.__in_type_hint_stack.pop() 

925 

926 def visit_Annotation(self, node: cst.Annotation) -> Optional[bool]: 

927 self.__in_annotation_stack.append(True) 

928 

929 def leave_Annotation(self, original_node: cst.Annotation) -> None: 

930 self.__in_annotation_stack.pop() 

931 

932 def visit_SimpleString(self, node: cst.SimpleString) -> Optional[bool]: 

933 self._handle_string_annotation(node) 

934 return False 

935 

936 def visit_ConcatenatedString(self, node: cst.ConcatenatedString) -> Optional[bool]: 

937 return not self._handle_string_annotation(node) 

938 

939 def _handle_string_annotation( 

940 self, node: Union[cst.SimpleString, cst.ConcatenatedString] 

941 ) -> bool: 

942 """Returns whether it successfully handled the string annotation""" 

943 if ( 

944 self.__in_type_hint_stack[-1] or self.__in_annotation_stack[-1] 

945 ) and not self.__in_ignored_subscript: 

946 value = node.evaluated_value 

947 if value: 

948 top_level_annotation = self.__last_string_annotation is None 

949 if top_level_annotation: 

950 self.__last_string_annotation = node 

951 try: 

952 mod = cst.parse_module(value) 

953 mod.visit(self) 

954 except cst.ParserSyntaxError: 

955 # swallow string annotation parsing errors 

956 # this is the same behavior as cPython 

957 pass 

958 if top_level_annotation: 

959 self.__last_string_annotation = None 

960 return True 

961 return False 

962 

963 def visit_Subscript(self, node: cst.Subscript) -> Optional[bool]: 

964 in_type_hint = False 

965 if isinstance(node.value, cst.Name): 

966 qnames = {qn.name for qn in self.scope.get_qualified_names_for(node.value)} 

967 if any(qn.startswith(("typing.", "typing_extensions.")) for qn in qnames): 

968 in_type_hint = True 

969 if "typing.Literal" in qnames or "typing_extensions.Literal" in qnames: 

970 self.__in_ignored_subscript.add(node) 

971 

972 self.__in_type_hint_stack.append(in_type_hint) 

973 return True 

974 

975 def leave_Subscript(self, original_node: cst.Subscript) -> None: 

976 self.__in_type_hint_stack.pop() 

977 self.__in_ignored_subscript.discard(original_node) 

978 

979 def visit_Name(self, node: cst.Name) -> Optional[bool]: 

980 # not all Name have ExpressionContext 

981 context = self.provider.get_metadata(ExpressionContextProvider, node, None) 

982 if context == ExpressionContext.STORE: 

983 self.scope.record_assignment(node.value, node) 

984 elif context in (ExpressionContext.LOAD, ExpressionContext.DEL, None): 

985 access = Access( 

986 node, 

987 self.scope, 

988 is_annotation=bool( 

989 self.__in_annotation_stack[-1] and not self.__ignore_annotation 

990 ), 

991 is_type_hint=bool(self.__in_type_hint_stack[-1]), 

992 ) 

993 self.__deferred_accesses.append( 

994 DeferredAccess( 

995 access=access, 

996 enclosing_attribute=self.__top_level_attribute_stack[-1], 

997 enclosing_string_annotation=self.__last_string_annotation, 

998 ) 

999 ) 

1000 

1001 def visit_FunctionDef(self, node: cst.FunctionDef) -> Optional[bool]: 

1002 self.scope.record_assignment(node.name.value, node) 

1003 self.provider.set_metadata(node.name, self.scope) 

1004 

1005 with self._new_scope(FunctionScope, node, get_full_name_for_node(node.name)): 

1006 node.params.visit(self) 

1007 node.body.visit(self) 

1008 

1009 for decorator in node.decorators: 

1010 decorator.visit(self) 

1011 returns = node.returns 

1012 if returns: 

1013 returns.visit(self) 

1014 

1015 return False 

1016 

1017 def visit_Lambda(self, node: cst.Lambda) -> Optional[bool]: 

1018 with self._new_scope(FunctionScope, node): 

1019 node.params.visit(self) 

1020 node.body.visit(self) 

1021 return False 

1022 

1023 def visit_Param(self, node: cst.Param) -> Optional[bool]: 

1024 self.scope.record_assignment(node.name.value, node) 

1025 self.provider.set_metadata(node.name, self.scope) 

1026 with self._switch_scope(self.scope.parent): 

1027 for field in [node.default, node.annotation]: 

1028 if field: 

1029 field.visit(self) 

1030 

1031 return False 

1032 

1033 def visit_Arg(self, node: cst.Arg) -> bool: 

1034 # The keyword of Arg is neither an Assignment nor an Access and we explicitly don't visit it. 

1035 value = node.value 

1036 if value: 

1037 value.visit(self) 

1038 return False 

1039 

1040 def visit_ClassDef(self, node: cst.ClassDef) -> Optional[bool]: 

1041 self.scope.record_assignment(node.name.value, node) 

1042 self.provider.set_metadata(node.name, self.scope) 

1043 for decorator in node.decorators: 

1044 decorator.visit(self) 

1045 for base in node.bases: 

1046 base.visit(self) 

1047 for keyword in node.keywords: 

1048 keyword.visit(self) 

1049 

1050 with self._new_scope(ClassScope, node, get_full_name_for_node(node.name)): 

1051 for statement in node.body.body: 

1052 statement.visit(self) 

1053 return False 

1054 

1055 def visit_ClassDef_bases(self, node: cst.ClassDef) -> None: 

1056 self.__ignore_annotation += 1 

1057 

1058 def leave_ClassDef_bases(self, node: cst.ClassDef) -> None: 

1059 self.__ignore_annotation -= 1 

1060 

1061 def visit_Global(self, node: cst.Global) -> Optional[bool]: 

1062 for name_item in node.names: 

1063 self.scope.record_global_overwrite(name_item.name.value) 

1064 return False 

1065 

1066 def visit_Nonlocal(self, node: cst.Nonlocal) -> Optional[bool]: 

1067 for name_item in node.names: 

1068 self.scope.record_nonlocal_overwrite(name_item.name.value) 

1069 return False 

1070 

1071 def visit_ListComp(self, node: cst.ListComp) -> Optional[bool]: 

1072 return self._visit_comp_alike(node) 

1073 

1074 def visit_SetComp(self, node: cst.SetComp) -> Optional[bool]: 

1075 return self._visit_comp_alike(node) 

1076 

1077 def visit_DictComp(self, node: cst.DictComp) -> Optional[bool]: 

1078 return self._visit_comp_alike(node) 

1079 

1080 def visit_GeneratorExp(self, node: cst.GeneratorExp) -> Optional[bool]: 

1081 return self._visit_comp_alike(node) 

1082 

1083 def _visit_comp_alike( 

1084 self, node: Union[cst.ListComp, cst.SetComp, cst.DictComp, cst.GeneratorExp] 

1085 ) -> bool: 

1086 """ 

1087 Cheat sheet: `[elt for target in iter if ifs]` 

1088 

1089 Terminology: 

1090 target: The variable or pattern we're storing each element of the iter in. 

1091 iter: The thing we're iterating over. 

1092 ifs: A list of conditions provided 

1093 elt: The value that will be computed and "yielded" each time the loop 

1094 iterates. For most comprehensions, this is just the `node.elt`, but 

1095 DictComp has `key` and `value`, which behave like `node.elt` would. 

1096 

1097 

1098 Nested Comprehension: ``[a for b in c for a in b]`` is a "nested" ListComp. 

1099 The outer iterator is in ``node.for_in`` and the inner iterator is in 

1100 ``node.for_in.inner_for_in``. 

1101 

1102 

1103 The first comprehension object's iter in generators is evaluated 

1104 outside of the ComprehensionScope. Every other comprehension's iter is 

1105 evaluated inside the ComprehensionScope. Even though that doesn't seem very sane, 

1106 but that appears to be how it works. 

1107 

1108 non_flat = [ [1,2,3], [4,5,6], [7,8] 

1109 flat = [y for x in non_flat for y in x] # this works fine 

1110 

1111 # This will give a "NameError: name 'x' is not defined": 

1112 flat = [y for x in x for y in x] 

1113 # x isn't defined, because the first iter is evaluted outside the scope. 

1114 

1115 # This will give an UnboundLocalError, indicating that the second 

1116 # comprehension's iter value is evaluated inside the scope as its elt. 

1117 # UnboundLocalError: local variable 'y' referenced before assignment 

1118 flat = [y for x in non_flat for y in y] 

1119 """ 

1120 for_in = node.for_in 

1121 for_in.iter.visit(self) 

1122 self.provider.set_metadata(for_in, self.scope) 

1123 with self._new_scope(ComprehensionScope, node): 

1124 for_in.target.visit(self) 

1125 # Things from here on can refer to the target. 

1126 self.scope._assignment_count += 1 

1127 for condition in for_in.ifs: 

1128 condition.visit(self) 

1129 inner_for_in = for_in.inner_for_in 

1130 if inner_for_in: 

1131 inner_for_in.visit(self) 

1132 if isinstance(node, cst.DictComp): 

1133 node.key.visit(self) 

1134 node.value.visit(self) 

1135 else: 

1136 node.elt.visit(self) 

1137 return False 

1138 

1139 def visit_For(self, node: cst.For) -> Optional[bool]: 

1140 node.target.visit(self) 

1141 self.scope._assignment_count += 1 

1142 for child in [node.iter, node.body, node.orelse, node.asynchronous]: 

1143 if child is not None: 

1144 child.visit(self) 

1145 return False 

1146 

1147 def infer_accesses(self) -> None: 

1148 # Aggregate access with the same name and batch add with set union as an optimization. 

1149 # In worst case, all accesses (m) and assignments (n) refer to the same name, 

1150 # the time complexity is O(m x n), this optimizes it as O(m + n). 

1151 scope_name_accesses = defaultdict(set) 

1152 for def_access in self.__deferred_accesses: 

1153 access, enclosing_attribute, enclosing_string_annotation = ( 

1154 def_access.access, 

1155 def_access.enclosing_attribute, 

1156 def_access.enclosing_string_annotation, 

1157 ) 

1158 name = ensure_type(access.node, cst.Name).value 

1159 if enclosing_attribute is not None: 

1160 # if _gen_dotted_names doesn't generate any values, fall back to 

1161 # the original name node above 

1162 for attr_name, node in _gen_dotted_names(enclosing_attribute): 

1163 if attr_name in access.scope: 

1164 access.node = node 

1165 name = attr_name 

1166 break 

1167 

1168 if enclosing_string_annotation is not None: 

1169 access.node = enclosing_string_annotation 

1170 

1171 scope_name_accesses[(access.scope, name)].add(access) 

1172 access.record_assignments(name) 

1173 access.scope.record_access(name, access) 

1174 

1175 for (scope, name), accesses in scope_name_accesses.items(): 

1176 for assignment in scope[name]: 

1177 assignment.record_accesses(accesses) 

1178 

1179 self.__deferred_accesses = [] 

1180 

1181 def on_leave(self, original_node: cst.CSTNode) -> None: 

1182 self.provider.set_metadata(original_node, self.scope) 

1183 if isinstance(original_node, _ASSIGNMENT_LIKE_NODES): 

1184 self.scope._assignment_count += 1 

1185 super().on_leave(original_node) 

1186 

1187 

1188class ScopeProvider(BatchableMetadataProvider[Optional[Scope]]): 

1189 """ 

1190 :class:`ScopeProvider` traverses the entire module and creates the scope inheritance 

1191 structure. It provides the scope of name assignment and accesses. It is useful for 

1192 more advanced static analysis. E.g. given a :class:`~libcst.FunctionDef` 

1193 node, we can check the type of its Scope to figure out whether it is a class method 

1194 (:class:`ClassScope`) or a regular function (:class:`GlobalScope`). 

1195 

1196 Scope metadata is available for most node types other than formatting information nodes 

1197 (whitespace, parentheses, etc.). 

1198 """ 

1199 

1200 METADATA_DEPENDENCIES = (ExpressionContextProvider,) 

1201 

1202 def visit_Module(self, node: cst.Module) -> Optional[bool]: 

1203 visitor = ScopeVisitor(self) 

1204 node.visit(visitor) 

1205 visitor.infer_accesses()