Skip to content

Commit

Permalink
Merge fddfce2 into 074cf4e
Browse files Browse the repository at this point in the history
  • Loading branch information
mristin committed Aug 24, 2018
2 parents 074cf4e + fddfce2 commit 229314a
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 207 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ venv3
*.egg-info
.tox
dist/
.coverage
htmlcov
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
language: python
python:
- "3.5"
# - "3.6"
#branches:
# only:
# - master
install:
- pip3 install -e .[dev]
- pip3 install coveralls
script:
- python3 precommit.py
- coveralls
6 changes: 1 addition & 5 deletions icontract/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
"""Decorate functions with contracts."""
import ast
import functools
import inspect
import reprlib
from typing import Callable, MutableMapping, Any, Optional, Set, List, Mapping # pylint: disable=unused-import
from typing import Callable, MutableMapping, Any, Optional, Set, List # pylint: disable=unused-import

import meta.decompiler

import icontract.recompute
import icontract.represent


Expand Down
6 changes: 5 additions & 1 deletion icontract/ast_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import tempfile
from typing import List, BinaryIO, cast # pylint: disable=unused-import

# We do not want to compute the coverage since visualizing AST graphs is used only for debugging.
# pragma: no cover


class Graph:
"""
Expand All @@ -15,6 +18,7 @@ class Graph:
"""

def __init__(self) -> None:
"""Initialize."""
self._nodes = [] # type: List[str]
self._edges = [] # type: List[str]
self._init = False
Expand All @@ -35,7 +39,7 @@ def visit(self, root: ast.AST) -> None:
while stack:
node = stack.pop()

label = ["<b>{}</b>".format(type(node).__name__)]
label = ["<b>{}<br/>{:x}</b>".format(type(node).__name__, id(node))]

for name, field in ast.iter_fields(node):
if isinstance(field, ast.AST):
Expand Down
53 changes: 40 additions & 13 deletions icontract/recompute.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import ast
import builtins
import functools
from typing import Any, Mapping, Dict, List, Optional # pylint: disable=unused-import
from typing import Any, Mapping, Dict, List, Optional, Union, Tuple, Set, Callable # pylint: disable=unused-import


class Visitor(ast.NodeVisitor):
Expand All @@ -24,25 +24,29 @@ def __init__(self, variable_lookup: List[Mapping[str, Any]]) -> None:
# value assigned to each visited node
self.recomputed_values = dict() # type: Dict[ast.AST, Any]

def visit_Num(self, node: ast.Num) -> Any:
def visit_Num(self, node: ast.Num) -> Union[int, float]:
"""Recompute the value as the number at the node."""
result = node.n

self.recomputed_values[node] = result
return result

def visit_Str(self, node: ast.Str) -> Any:
def visit_Str(self, node: ast.Str) -> str:
"""Recompute the value as the string at the node."""
result = node.s

self.recomputed_values[node] = result
return result

def visit_Bytes(self, node: ast.Bytes) -> Any:
def visit_Bytes(self, node: ast.Bytes) -> bytes:
"""Recompute the value as the bytes at the node."""
result = node.s

self.recomputed_values[node] = result
return node.s

def visit_List(self, node: ast.List) -> Any:
def visit_List(self, node: ast.List) -> List[Any]:
"""Visit the elements and assemble the results into a list."""
if isinstance(node.ctx, ast.Store):
raise NotImplementedError("Can not compute the value of a Store on a list")

Expand All @@ -51,7 +55,8 @@ def visit_List(self, node: ast.List) -> Any:
self.recomputed_values[node] = result
return result

def visit_Tuple(self, node: ast.Tuple) -> Any:
def visit_Tuple(self, node: ast.Tuple) -> Tuple[Any, ...]:
"""Visit the elements and assemble the results into a tuple."""
if isinstance(node.ctx, ast.Store):
raise NotImplementedError("Can not compute the value of a Store on a tuple")

Expand All @@ -60,13 +65,15 @@ def visit_Tuple(self, node: ast.Tuple) -> Any:
self.recomputed_values[node] = result
return result

def visit_Set(self, node: ast.Set) -> Any:
def visit_Set(self, node: ast.Set) -> Set[Any]:
"""Visit the elements and assemble the results into a set."""
result = set(self.visit(node=elt) for elt in node.elts)

self.recomputed_values[node] = result
return result

def visit_Dict(self, node: ast.Dict) -> Any:
def visit_Dict(self, node: ast.Dict) -> Dict[Any, Any]:
"""Visit keys and values and assemble a dictionary with the results."""
recomputed_dict = dict() # type: Dict[Any, Any]
for key, val in zip(node.keys, node.values):
recomputed_dict[self.visit(node=key)] = self.visit(node=val)
Expand All @@ -75,10 +82,12 @@ def visit_Dict(self, node: ast.Dict) -> Any:
return recomputed_dict

def visit_NameConstant(self, node: ast.NameConstant) -> Any:
"""Forward the node value as a result."""
self.recomputed_values[node] = node.value
return node.value

def visit_Name(self, node: ast.Name) -> Any:
"""Load the variable by looking it up in the variable look-up and in the built-ins."""
if not isinstance(node.ctx, ast.Load):
raise NotImplementedError("Can only compute a value of Load on a name {}, but got context: {}".format(
node.id, node.ctx))
Expand All @@ -99,12 +108,14 @@ def visit_Name(self, node: ast.Name) -> Any:
return result

def visit_Expr(self, node: ast.Expr) -> Any:
"""Visit the node's ``value``."""
result = self.visit(node=node.value)

self.recomputed_values[node] = result
return result

def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
"""Visit the node operand and apply the operation on the result."""
if isinstance(node.op, ast.UAdd):
result = +self.visit(node=node.operand)
elif isinstance(node.op, ast.USub):
Expand All @@ -120,6 +131,7 @@ def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
return result

def visit_BinOp(self, node: ast.BinOp) -> Any:
"""Recursively visit the left and right operand, respectively, and apply the operation on the results."""
# pylint: disable=too-many-branches
left = self.visit(node=node.left)
right = self.visit(node=node.right)
Expand Down Expand Up @@ -157,6 +169,7 @@ def visit_BinOp(self, node: ast.BinOp) -> Any:
return result

def visit_BoolOp(self, node: ast.BoolOp) -> Any:
"""Recursively visit the operands and apply the operation on them."""
values = [self.visit(value_node) for value_node in node.values]

if isinstance(node.op, ast.And):
Expand All @@ -170,8 +183,10 @@ def visit_BoolOp(self, node: ast.BoolOp) -> Any:
return result

def visit_Compare(self, node: ast.Compare) -> Any:
"""Recursively visit the comparators and apply the operations on them."""
# pylint: disable=too-many-branches
left = self.visit(node=node.left)

comparators = [self.visit(node=comparator) for comparator in node.comparators]

result = True
Expand Down Expand Up @@ -209,6 +224,7 @@ def visit_Compare(self, node: ast.Compare) -> Any:
return result

def visit_Call(self, node: ast.Call) -> Any:
"""Visit the function and the arguments and finally make the function call with them."""
func = self.visit(node=node.func)

args = [] # type: List[Any]
Expand All @@ -225,15 +241,18 @@ def visit_Call(self, node: ast.Call) -> Any:
for key, val in kw.items():
kwargs[key] = val

kwargs[keyword.arg] = self.visit(node=keyword.value)
else:
kwargs[keyword.arg] = self.visit(node=keyword.value)

result = func(*args, **kwargs)

self.recomputed_values[node] = result
return result

def visit_IfExp(self, node: ast.IfExp) -> Any:
"""Visit the ``test``, and depending on its outcome, the ``body`` or ``orelse``."""
test = self.visit(node=node.test)

if test:
result = self.visit(node=node.body)
else:
Expand All @@ -243,6 +262,7 @@ def visit_IfExp(self, node: ast.IfExp) -> Any:
return result

def visit_Attribute(self, node: ast.Attribute) -> Any:
"""Visit the node's ``value`` and get the attribute from the result."""
value = self.visit(node=node.value)
if not isinstance(node.ctx, ast.Load):
raise NotImplementedError(
Expand All @@ -254,12 +274,14 @@ def visit_Attribute(self, node: ast.Attribute) -> Any:
return result

def visit_Index(self, node: ast.Index) -> Any:
"""Visit the node's ``value``."""
result = self.visit(node=node.value)

self.recomputed_values[node] = result
return result

def visit_Slice(self, node: ast.Slice) -> Any:
def visit_Slice(self, node: ast.Slice) -> slice:
"""Visit ``lower``, ``upper`` and ``step`` and recompute the node as a ``slice``."""
lower = None # type: Optional[int]
if node.lower is not None:
lower = self.visit(node=node.lower)
Expand All @@ -272,18 +294,20 @@ def visit_Slice(self, node: ast.Slice) -> Any:
if node.step is not None:
step = self.visit(node=node.step)

result = slice(start=lower, stop=upper, step=step)
result = slice(lower, upper, step)

self.recomputed_values[node] = result
return result

def visit_ExtSlice(self, node: ast.ExtSlice) -> Any:
def visit_ExtSlice(self, node: ast.ExtSlice) -> Tuple[Any, ...]:
"""Visit each dimension of the advanced slicing and assemble the dimensions in a tuple."""
result = tuple(self.visit(node=dim) for dim in node.dims)

self.recomputed_values[node] = result
return result

def visit_Subscript(self, node: ast.Subscript) -> Any:
"""Visit the ``slice`` and a ``value`` and get the element."""
value = self.visit(node=node.value)
a_slice = self.visit(node=node.slice)

Expand All @@ -292,14 +316,17 @@ def visit_Subscript(self, node: ast.Subscript) -> Any:
self.recomputed_values[node] = result
return result

def visit_Lambda(self, node: ast.Lambda) -> Any:
def visit_Lambda(self, node: ast.Lambda) -> Callable:
"""Visit the lambda's body."""
result = self.visit(node.body)

self.recomputed_values[node] = result
return result

def visit_Return(self, node: ast.Return) -> Any: # pylint: disable=no-self-use
"""Raise an exception that this node is unexpected."""
raise AssertionError("Unexpected return node during the re-computation: {}".format(ast.dump(node)))

def generic_visit(self, node: ast.AST) -> None:
"""Raise an exception that this node has not been handled."""
raise NotImplementedError("Unhandled recomputation of the node: {} {}".format(type(node), node))
29 changes: 19 additions & 10 deletions icontract/represent.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,30 @@ def __init__(self, recomputed_values: Mapping[ast.AST, Any], variable_lookup: Li
self.reprs = dict() # type: MutableMapping[str, str]

def visit_Name(self, node: ast.Name) -> None:
value = self._recomputed_values[node]
"""
Resolve the name from the variable look-up and the built-ins.
Due to possible branching (e.g., If-expressions), some nodes might lack the recomputed values. These nodes
are ignored.
"""
if node in self._recomputed_values:
value = self._recomputed_values[node]

# Check if it is a non-built-in
is_builtin = True
for lookup in self._variable_lookup:
if node.id in lookup:
is_builtin = False
break
# Check if it is a non-built-in
is_builtin = True
for lookup in self._variable_lookup:
if node.id in lookup:
is_builtin = False
break

if not is_builtin and _representable(value=value):
text = str(meta.dump_python_source(node)).strip() # type: ignore
self.reprs[text] = value
if not is_builtin and _representable(value=value):
text = str(meta.dump_python_source(node)).strip() # type: ignore
self.reprs[text] = value

self.generic_visit(node=node)

def visit_Attribute(self, node: ast.Attribute) -> None:
"""Represent the attribute by dumping its source code."""
value = self._recomputed_values[node]

if _representable(value=value):
Expand All @@ -68,6 +76,7 @@ def visit_Attribute(self, node: ast.Attribute) -> None:
self.generic_visit(node=node)

def visit_Call(self, node: ast.Call) -> None:
"""Represent the call by dumping its source code."""
value = self._recomputed_values[node]

# pylint: disable=no-member
Expand Down
7 changes: 7 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[mypy]

[mypy-meta]
ignore_missing_imports = True

[mypy-meta.decompiler]
ignore_missing_imports = True
Loading

0 comments on commit 229314a

Please sign in to comment.