Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
82d4a17
Add annotations
edreamleo Jan 2, 2023
001d5fd
More annotations
edreamleo Jan 2, 2023
a4a5f51
Still more annotations
edreamleo Jan 2, 2023
6cc44ef
Merge branch 'master' into study
edreamleo Jan 4, 2023
a61d525
tweaks for study
edreamleo Jan 5, 2023
8987f7f
Merge branch 'master' into study
edreamleo Jan 5, 2023
15bf2b9
Blacken
edreamleo Jan 5, 2023
2a312b9
Improve docstrings. Add annotations
edreamleo Jan 6, 2023
1c06fc7
Add traces to saveit decorator
edreamleo Jan 6, 2023
e7a38b9
Improve traces
edreamleo Jan 6, 2023
ea9c675
Blacken. Suppress pylint warning about pdb.
edreamleo Jan 6, 2023
d7cae09
Add print statement
edreamleo Jan 6, 2023
fb5dec3
rename func to class_name
edreamleo Jan 6, 2023
eea3ff0
Tweaks
edreamleo Jan 6, 2023
9223629
Improve docstring and traces
edreamleo Jan 6, 2023
15c55e8
Improve docstring. Separate the trace and non-tracing versions of _wr…
edreamleo Jan 6, 2023
353898d
Tweak docstring
edreamleo Jan 6, 2023
7c35a70
Another go at the docstring
edreamleo Jan 6, 2023
0011f3d
traces
edreamleo Jan 27, 2023
d284930
Merge branch 'master' into study
edreamleo Jan 27, 2023
d24dc8b
Improve headlines
edreamleo Jan 27, 2023
981b798
Add the g.trace_ctors pattern
edreamleo Jan 28, 2023
348d839
Add more traces
edreamleo Jan 28, 2023
b817122
Blacken several files. Let's hope rope's test actions succeed this time
edreamleo Jan 28, 2023
2136994
Add rope.base.leoGlobals so checkin tasks have a chance of passing
edreamleo Jan 29, 2023
b689299
Annotate
edreamleo Jan 29, 2023
5180cc5
Add comment re injecting g.trace_ctors
edreamleo Jan 30, 2023
09bb07f
Use pprint in objToString, as in Leo, without the aliases
edreamleo Jan 30, 2023
198c670
Use rope.base.utils.debugutils instead of rope.base.leoGlobals
edreamleo Jan 30, 2023
00cdfff
Remove unused kwarg
edreamleo Jan 30, 2023
b254d69
Improve and simplify tracing of ctors
edreamleo Jan 31, 2023
e1daee0
Removed unused import
edreamleo Jan 31, 2023
70f9f4c
Tweak g.objToString
edreamleo Jan 31, 2023
f466b0e
More tweaks to objToString
edreamleo Jan 31, 2023
1b4bcc5
Improve objToString
edreamleo Jan 31, 2023
d4dccd7
Revise, following Leo revisions
edreamleo Jan 31, 2023
945d817
Tweaks, per Leo PR
edreamleo Feb 1, 2023
aa20327
Fix docstring. It's not the same as in Leo
edreamleo Feb 1, 2023
bebdb50
Blacken all files after upgrading black.
edreamleo Feb 1, 2023
1a4fca7
Rename debugutils.py to debug_utils.py
edreamleo Feb 1, 2023
fd69bfa
Add trace lists
edreamleo Feb 1, 2023
264f972
Use pep8 names in debug_utils
edreamleo Feb 1, 2023
c2d0533
Rename debug_utils to tracing_utils
edreamleo Feb 2, 2023
91034ad
Complete the renaming
edreamleo Feb 2, 2023
2c1d6a2
Revert to @saveit
edreamleo Feb 2, 2023
288168b
Remove unimportant ### comments
edreamleo Feb 2, 2023
2304362
Add annotations for do-nothing methods!
edreamleo Feb 2, 2023
fe91ab8
Fix recent annotations
edreamleo Feb 2, 2023
8d7bb77
Eliminate ### and faux generality
edreamleo Feb 2, 2023
dca8e55
Add trace to RopeNodeVisitor.visit
edreamleo Feb 2, 2023
8593547
Improve trace. Fix g.trace
edreamleo Feb 2, 2023
6df2dcf
Improve tracing of ast.RopeNodeVisitor.visit
edreamleo Feb 2, 2023
34b2af8
Improve docstrings
edreamleo Feb 3, 2023
5dc69a1
Fix mypy complaints
edreamleo Feb 3, 2023
5123659
Merge master into study
edreamleo Feb 6, 2023
70d4285
Remove (again) two pyflakes complaints
edreamleo Feb 6, 2023
74424fb
Add doc files
edreamleo Feb 6, 2023
6a3f491
blacken
edreamleo Feb 6, 2023
46f3c95
Simplify traces with g.format
edreamleo Feb 8, 2023
98e3e1f
Blacken
edreamleo Feb 8, 2023
a89df6b
Add format_ctor and use it
edreamleo Feb 8, 2023
57315e3
Improve traces
edreamleo Feb 8, 2023
4d33fd5
Add g.align and use it
edreamleo Feb 8, 2023
8d71ccd
format_ctor takes an explit class name. Previous code gave unexpected…
edreamleo Feb 8, 2023
7f6b993
Improve tracing
edreamleo Feb 9, 2023
934361a
Add/improve traces
edreamleo Feb 9, 2023
4dff16a
Improve traces, again
edreamleo Feb 9, 2023
c1ea12b
Revise again
edreamleo Feb 9, 2023
cafc168
More traces
edreamleo Feb 9, 2023
d44638e
Zero in on important traces/methods
edreamleo Feb 9, 2023
c89c58e
Add/suppress annotations
edreamleo Feb 10, 2023
3f7ae15
Fix/suppress all remaining mypy complaints
edreamleo Feb 10, 2023
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 docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Welcome to rope's documentation!
library
configuration
contributing
theory
release-process
dev/issues

Expand Down
57 changes: 57 additions & 0 deletions docs/theory.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
.. rst3: filename: docs/theory

.. _`python's ast module`: https://docs.python.org/3/library/ast.html

=====================
How Rope infers types
=====================

This is the Theory of Operation for Rope's type inference,
the most complex part of Rope.

Only Rope's core devs need to understand this material.

Some familiarity with `Python's ast module`_ is recommended, but not essential.

.. contents:: Table of Contents

Why Rope must infer types
-------------------------

.. To do.

Overview of Rope's code base
----------------------------

- Startup code.
- Type inference code.
- Utility code.
- Refactoring code.
- Code completion.

Overview of type inference
--------------------------


First principles:
- Traversers do most of the work.
- Local inference is easy; global inference is hard.

Learning what to ignore is important.
- @saveit is part of Rope's startup code. It has no direct part in type inference.

What *not* to ignore:
- @prevent_recursion prevents endless inference loops.

What is an inference?
+++++++++++++++++++++

Further study
-------------

Deep study of a few unit tests is recommended.

`ObjectInferTest.test_simple_type_inferencing` is a good place to start.

.. --- Insert traces here ---

24 changes: 21 additions & 3 deletions rope/base/arguments.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
from __future__ import annotations
from typing import Any, Union, TYPE_CHECKING
import rope.base.evaluate
from rope.base import ast

if TYPE_CHECKING:
from rope.base.pyobjects import PyFunction
from rope.base.pyobjectsdef import PyFunction as DefinedPyFunction
from rope.base.pyscopes import Scope

PyFunc = Union[PyFunction, DefinedPyFunction]
else:
PyFunc = Any
Scope = Any
Node = ast.AST


class Arguments:
"""A class for evaluating parameters passed to a function
Expand Down Expand Up @@ -41,10 +54,15 @@ def _evaluate(self, ast_node):
return rope.base.evaluate.eval_node(self.scope, ast_node)


def create_arguments(primary, pyfunction, call_node, scope):
# Call(expr func, expr* args, keyword* keywords)


def create_arguments(
primary, pyfunction: PyFunc, call_node: ast.Call, scope: Scope
) -> Arguments:
"""A factory for creating `Arguments`"""
args = list(call_node.args)
args.extend(call_node.keywords)
args.extend(call_node.keywords) # type:ignore
called = call_node.func
# XXX: Handle constructors
if _is_method_call(primary, pyfunction) and isinstance(called, ast.Attribute):
Expand Down Expand Up @@ -94,7 +112,7 @@ def get_instance_pyname(self):
return self.pyname


def _is_method_call(primary, pyfunction):
def _is_method_call(primary: Any, pyfunction: PyFunc) -> bool:
if primary is None:
return False
pyobject = primary.get_object()
Expand Down
11 changes: 10 additions & 1 deletion rope/base/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

from rope.base import fscommands

from rope.base.utils import tracing_utils as g

assert g

try:
from ast import _const_node_type_names
from ast import _const_node_type_names # type:ignore
except ImportError:
# backported from stdlib `ast`
assert sys.version_info < (3, 8)
Expand Down Expand Up @@ -58,10 +62,15 @@ def call_for_nodes(node, callback):


class RopeNodeVisitor(ast.NodeVisitor):
# This is the only visit method in Rope.
def visit(self, node):
"""Modified from ast.NodeVisitor to match rope's existing Visitor implementation"""
method = "_" + node.__class__.__name__
visitor = getattr(self, method, self.generic_visit)
if 0: # trace
module, name = visitor.__module__, visitor.__name__
if name != "generic_visit":
print(g.format("visit", module, name))
return visitor(node)


Expand Down
6 changes: 6 additions & 0 deletions rope/base/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@
import rope.base.evaluate
from rope.base import arguments, ast, pynames, pyobjects, utils

from rope.base.utils import tracing_utils as g

assert g


class BuiltinModule(pyobjects.AbstractModule):
def __init__(self, name, pycore=None, initial={}):
super().__init__()
self.name = name
self.pycore = pycore
self.initial = initial
if 0: # trace
print(g.format_ctor("builtins.BuiltinModule", __file__), name)

parent = None

Expand Down
4 changes: 2 additions & 2 deletions rope/base/change.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import rope.base.fscommands
from rope.base import exceptions, taskhandle, utils
from rope.base.fscommands import FileContent
from rope.base.fscommands import FileContent # type:ignore


class Change:
Expand Down Expand Up @@ -334,7 +334,7 @@ def _get_fscommands(self, resource):
def write_file(self, resource, contents: Union[str, FileContent]):
data: FileContent
if not isinstance(contents, bytes):
data = rope.base.fscommands.unicode_to_file_data(
data = rope.base.fscommands.unicode_to_file_data( # type:ignore
contents,
newlines=resource.newlines,
)
Expand Down
8 changes: 6 additions & 2 deletions rope/base/evaluate.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations
from operator import itemgetter
from typing import Optional, Tuple
from typing import Optional, Tuple, TYPE_CHECKING

import rope.base.builtins
import rope.base.pynames
Expand All @@ -14,6 +15,9 @@
worder,
)

if TYPE_CHECKING:
from rope.base.pyscopes import Scope

BadIdentifierError = exceptions.BadIdentifierError


Expand Down Expand Up @@ -158,7 +162,7 @@ def _find_module(self, module_name):


class StatementEvaluator(ast.RopeNodeVisitor):
def __init__(self, scope):
def __init__(self, scope: Scope):
self.scope = scope
self.result = None
self.old_result = None
Expand Down
1 change: 1 addition & 0 deletions rope/base/fscommands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# type:ignore
"""Project file system commands.

This modules implements file system operations used by rope. Different
Expand Down
4 changes: 2 additions & 2 deletions rope/base/libutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def analyze_modules(project, task_handle=taskhandle.DEFAULT_TASK_HANDLE):


def get_string_module(project, code, resource=None, force_errors=False):
"""Returns a `PyObject` object for the given code
"""Instantiates a `PyModule` object for the given code.

If `force_errors` is `True`, `exceptions.ModuleSyntaxError` is
raised if module has syntax errors. This overrides
Expand All @@ -94,7 +94,7 @@ def get_string_module(project, code, resource=None, force_errors=False):


def get_string_scope(project, code, resource=None):
"""Returns a `Scope` object for the given code"""
"""Instantiates a `Scope` object for the given code"""
return get_string_module(project, code, resource).get_scope()


Expand Down
5 changes: 3 additions & 2 deletions rope/base/nameanalyze.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import List
from rope.base import ast


Expand All @@ -19,8 +20,8 @@ def get_name_levels(node):


class _NodeNameCollector(ast.RopeNodeVisitor):
def __init__(self, levels=None):
self.names = []
def __init__(self, levels: List[int] = None):
self.names: List[str] = []
self.levels = levels
self.index = 0

Expand Down
1 change: 1 addition & 0 deletions rope/base/oi/doa.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# type:ignore
import base64
import contextlib
import hashlib
Expand Down
2 changes: 1 addition & 1 deletion rope/base/oi/runmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def __rope_start_everything():
import sys

try:
import cPickle as pickle
import cPickle as pickle # type:ignore
except ImportError:
import pickle
import base64
Expand Down
16 changes: 14 additions & 2 deletions rope/base/oi/soa.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
# type:ignore
from __future__ import annotations
from typing import Any, Union, TYPE_CHECKING

import rope.base.ast
import rope.base.oi.soi
import rope.base.pynames
from rope.base import arguments, evaluate, nameanalyze, pyobjects

if TYPE_CHECKING:
from rope.base.pyobjects import PyFunction
from rope.base.pyobjectsdef import PyFunction as DefinedPyFunction

PyFunc = Union[PyFunction, DefinedPyFunction]
else:
PyFunc = Any


def analyze_module(pycore, pymodule, should_analyze, search_subscopes, followed_calls):
"""Analyze `pymodule` for static object inference
Expand All @@ -25,7 +37,7 @@ def _analyze_node(pycore, pydefined, should_analyze, search_subscopes, followed_
return_true = lambda pydefined: True
return_false = lambda pydefined: False

def _follow(pyfunction):
def _follow(pyfunction: PyFunc) -> None:
_analyze_node(
pycore, pyfunction, return_true, return_false, new_followed_calls
)
Expand Down Expand Up @@ -70,7 +82,7 @@ def _Call(self, node):
return
self._call(pyfunction, args)

def _args_with_self(self, primary, self_pyname, pyfunction, node):
def _args_with_self(self, primary, self_pyname, pyfunction: PyFunc, node: None):
base_args = arguments.create_arguments(primary, pyfunction, node, self.scope)
return arguments.MixedArguments(self_pyname, base_args, self.scope)

Expand Down
19 changes: 16 additions & 3 deletions rope/base/oi/soi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,28 @@
package.

"""
from __future__ import annotations
from typing import Any, Union, TYPE_CHECKING
import rope.base.builtins # Use full qualification for clarity.
from rope.base import arguments, evaluate, pynames, pyobjects, utils
from rope.base.oi.type_hinting.factory import get_type_hinting_factory

if TYPE_CHECKING:
import ast
from rope.base.pyobjects import PyFunction
from rope.base.pyobjectsdef import PyFunction as DefinedPyFunction

Node = ast.AST
PyFunc = Union[PyFunction, DefinedPyFunction]
else:
Node = Any
PyFunc = Any

_ignore_inferred = utils.ignore_exception(pyobjects.IsBeingInferredError)


@_ignore_inferred
def infer_returned_object(pyfunction, args):
def infer_returned_object(pyfunction: PyFunc, args):
"""Infer the `PyObject` this `PyFunction` returns after calling"""
object_info = pyfunction.pycore.object_info
result = object_info.get_exact_returned(pyfunction, args)
Expand All @@ -36,7 +49,7 @@ def infer_returned_object(pyfunction, args):


@_ignore_inferred
def infer_parameter_objects(pyfunction):
def infer_parameter_objects(pyfunction: PyFunc):
"""Infer the `PyObject` of parameters of this `PyFunction`"""
object_info = pyfunction.pycore.object_info
result = object_info.get_parameter_objects(pyfunction)
Expand Down Expand Up @@ -85,7 +98,7 @@ def infer_assigned_object(pyname):
return result


def get_passed_objects(pyfunction, parameter_index):
def get_passed_objects(pyfunction: PyFunc, parameter_index: int) -> Any:
object_info = pyfunction.pycore.object_info
result = object_info.get_passed_objects(pyfunction, parameter_index)
if not result:
Expand Down
1 change: 1 addition & 0 deletions rope/base/oi/type_hinting/evaluate.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# type:ignore
# Based on super lightweight Simple Top-Down Parser from http://effbot.org/zone/simple-top-down-parsing.htm
# and https://bitbucket.org/emacsway/sqlbuilder/src/default/sqlbuilder/smartsql/contrib/evaluate.py
import re
Expand Down
1 change: 1 addition & 0 deletions rope/base/oi/type_hinting/providers/numpydocstrings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# type: ignore
"""
Some code extracted (or based on code) from:
https://github.com/davidhalter/jedi/blob/b489019f5bd5750051122b94cc767df47751ecb7/jedi/evaluate/docstrings.py
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re

from rope.base.oi.type_hinting import utils
# from rope.base.oi.type_hinting import utils
from rope.base.oi.type_hinting.providers import interfaces


Expand Down
8 changes: 5 additions & 3 deletions rope/base/oi/type_hinting/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# type:ignore
import logging
from typing import Optional, Union
from typing import Optional, Tuple

import rope.base.utils as base_utils
from rope.base import evaluate
Expand Down Expand Up @@ -72,8 +73,9 @@ def get_mro(pyclass):
return class_list


def resolve_type(type_name, pyobject):
# type: (str, Union[PyDefinedObject, PyObject]) -> Optional[PyDefinedObject, PyObject]
def resolve_type(
type_name: str, pyobject: PyObject
) -> Optional[Tuple[PyDefinedObject, PyObject]]:
"""
Find proper type object from its name.
"""
Expand Down
1 change: 1 addition & 0 deletions rope/base/prefs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# type: ignore
"""Rope preferences."""
from dataclasses import asdict, dataclass
from textwrap import dedent
Expand Down
Loading