Skip to content

Commit

Permalink
Merge pull request #690 from serge-sans-paille/feature/delayed-typing
Browse files Browse the repository at this point in the history
Delay typing until an error is found
  • Loading branch information
serge-sans-paille committed Jun 27, 2017
2 parents ae4d74e + a83429b commit 3ec043e
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 54 deletions.
2 changes: 1 addition & 1 deletion pythran/__init__.py
Expand Up @@ -11,7 +11,7 @@
Basic scenario is to turn a Python AST into C++ code:
>>> code = "def foo(x): return x * 2"
>>> cxx_generator = generate_cxx('my_module', code) # gets a BoostPythonModule
>>> cxx_generator, error_checker = generate_cxx('my_module', code)
>>> cxx = cxx_generator.generate()
To generate a native module, one need to add type information:
Expand Down
3 changes: 0 additions & 3 deletions pythran/analyses/constant_expressions.py
Expand Up @@ -6,9 +6,6 @@
from pythran.analyses.pure_expressions import PureExpressions
from pythran.intrinsic import FunctionIntr
from pythran.passmanager import NodeAnalysis
from pythran.tables import MODULES
from pythran.conversion import demangle
from pythran.utils import path_to_node

import gast as ast

Expand Down
1 change: 0 additions & 1 deletion pythran/analyses/dependencies.py
Expand Up @@ -3,7 +3,6 @@
"""

from pythran.passmanager import ModuleAnalysis
from pythran.tables import MODULES
from pythran.conversion import demangle

import gast as ast
Expand Down
2 changes: 0 additions & 2 deletions pythran/conversion.py
Expand Up @@ -2,10 +2,8 @@
from __future__ import absolute_import

import gast as ast
import itertools
import numpy
import sys
import types
import numbers


Expand Down
1 change: 0 additions & 1 deletion pythran/interval.py
@@ -1,7 +1,6 @@
""" Module with facilities to represent range values. """

from math import isinf
import gast as ast
import itertools

import numpy
Expand Down
1 change: 0 additions & 1 deletion pythran/optimizations/inline_builtins.py
Expand Up @@ -2,7 +2,6 @@

from pythran.analyses import Aliases
from pythran.analyses.pure_expressions import PureExpressions
from pythran.analyses.constant_expressions import ConstantExpressions
from pythran.passmanager import Transformation
from pythran.tables import MODULES
from pythran.intrinsic import FunctionIntr
Expand Down
3 changes: 1 addition & 2 deletions pythran/spec.py
Expand Up @@ -5,7 +5,6 @@

from pythran.types.conversion import pytype_to_pretty_type

import itertools
import re
import os.path
import ply.lex as lex
Expand Down Expand Up @@ -339,7 +338,7 @@ def spec_expander(args):
def specs_to_docstrings(specs, docstrings):
for function_name, signatures in specs.items():
sigdocs = []
for sigid, signature in enumerate(signatures):
for signature in signatures:
arguments_types = [pytype_to_pretty_type(t) for t in signature]
function_signatures = '{}({})'.format(
function_name,
Expand Down
50 changes: 19 additions & 31 deletions pythran/tests/test_typing.py
Expand Up @@ -4,7 +4,7 @@
import pythran
from textwrap import dedent

from pythran.typing import List, Dict
from pythran.typing import List, Dict, NDArray

class TestTyping(TestEnv):

Expand Down Expand Up @@ -295,7 +295,10 @@ def slice_assign (M):
4,
slice_assign=[int])


def verify_type_error(self, code):
with self.assertRaises(pythran.types.tog.PythranTypeError):
_, eh = pythran.generate_cxx("dumbo", dedent(code))
eh()

def test_type_inference0(self):
code = '''
Expand All @@ -313,102 +316,87 @@ def wc(content):
self.run_test(code, "cha-la head cha-la", wc=[str])

code_bis = code.replace("1", "'1'")


with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code_bis))
self.verify_type_error(code_bis)

code_ter = code.replace("0", "None")


with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code_ter))
self.verify_type_error(code_ter)

def test_type_inference1(self):
code = '''
def invalid_augassign(n):
s = n + "1"
s += 2
return s'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)


def test_type_inference2(self):
code = '''
def invalid_ifexp(n):
return 1 if n else "1"'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)


def test_type_inference3(self):
code = '''
def invalid_unary_op(n):
return -(n + 'n')'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)


def test_type_inference4(self):
code = '''
def invalid_list(n):
return [n, len(n)]'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)


def test_type_inference5(self):
code = '''
def invalid_set(n):
return {n, len(n)}'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)

def test_type_inference6(self):
code = '''
def invalid_dict_key(n):
return {n:1, len(n):2}'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)


def test_type_inference7(self):
code = '''
def invalid_dict_value(n):
return {1:n, 2:len(n)}'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)

def test_type_inference8(self):
code = '''
def invalid_multi_return(n):
for i in n:
return [n]
return {n}'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)

def test_type_inference9(self):
code = '''
def invalid_multi_yield(n):
for i in n:
yield [n]
yield n'''
with self.assertRaises(pythran.types.tog.PythranTypeError):
pythran.compile_pythrancode("dumbo", dedent(code))
self.verify_type_error(code)


def test_type_inference10(self):
code = '''
def valid_augassign(l):
l *= 0
return l[1,2]'''
pythran.compile_pythrancode("dumbo", dedent(code))
return l[1:2]'''
return self.run_test(code, np.array([0,1,2,3,4]), valid_augassign=[NDArray[int, :]])

def test_type_inference11(self):
code = '''
def valid_tuple_index(l):
return (1, 2, 3, 4)[l]'''
pythran.compile_pythrancode("dumbo", dedent(code))
return self.run_test(code, 0, valid_tuple_index=[int])

39 changes: 27 additions & 12 deletions pythran/toolchain.py
Expand Up @@ -112,7 +112,10 @@ def visit_Module(self, node):

def generate_cxx(module_name, code, specs=None, optimizations=None):
'''python + pythran spec -> c++ code
returns a PythonModule object
returns a PythonModule object and an error checker
the error checker can be used to print more detailed info on the origin of
a compile error (e.g. due to bad typing)
'''

Expand All @@ -127,9 +130,6 @@ def generate_cxx(module_name, code, specs=None, optimizations=None):
optimizations = [_parse_optimization(opt) for opt in optimizations]
refine(pm, ir, optimizations)

# type check
types = tog.typecheck(ir)

# back-end
content = pm.dump(Cxx, ir)

Expand All @@ -146,6 +146,10 @@ def __str__(self):
generate = __str__

mod = Generable(content)

def error_checker():
tog.typecheck(ir)

else:

# uniform typing
Expand All @@ -155,7 +159,11 @@ def __str__(self):

# verify the pythran export are compatible with the code
specs = expand_specs(specs)
check_specs(ir, specs, renamings, types)

def error_checker():
types = tog.typecheck(ir)
check_specs(ir, specs, renamings, types)

specs_to_docstrings(specs, docstrings)

if isinstance(code, bytes):
Expand Down Expand Up @@ -234,7 +242,7 @@ def __str__(self):
function_name,
arguments_types
)
return mod
return mod, error_checker


def compile_cxxfile(module_name, cxxfile, output_binary=None, **kwargs):
Expand Down Expand Up @@ -268,7 +276,8 @@ def compile_cxxfile(module_name, cxxfile, output_binary=None, **kwargs):
]
)
except SystemExit as e:
raise CompileError(e.args)
raise CompileError(str(e))


[target] = glob.glob(os.path.join(builddir, module_name + "*"))
if not output_binary:
Expand Down Expand Up @@ -318,7 +327,7 @@ def compile_pythrancode(module_name, pythrancode, specs=None,
specs = spec_parser(pythrancode)

# Generate C++, get a PythonModule object
module = generate_cxx(module_name, pythrancode, specs, opts)
module, error_checker = generate_cxx(module_name, pythrancode, specs, opts)

if cpponly:
# User wants only the C++ code
Expand All @@ -329,10 +338,16 @@ def compile_pythrancode(module_name, pythrancode, specs=None,
logger.info("Generated C++ source file: " + output_file)
else:
# Compile to binary
output_file = compile_cxxcode(module_name,
str(module),
output_binary=output_file,
**kwargs)
try:
output_file = compile_cxxcode(module_name,
str(module),
output_binary=output_file,
**kwargs)
except CompileError as ce:
logger.warn("Compilation error, trying hard to find its origin...")
error_checker()
logger.warn("Nop, I'm going to flood you with C++ errors!")
raise

return output_file

Expand Down

0 comments on commit 3ec043e

Please sign in to comment.