Commit 90c52acc authored by Stefan Behnel's avatar Stefan Behnel

Merge branch 'master' of git+ssh://github.com/cython/cython

parents 3d5523b5 b585cf51
...@@ -59,6 +59,16 @@ Other changes ...@@ -59,6 +59,16 @@ Other changes
------------- -------------
0.23.5 (2015-xx-yy)
===================
Bugs fixed
----------
* Fix prange() to behave identically to range(). The end condition was
miscalculated when the range was not exactly divisible by the step.
0.23.4 (2015-10-10) 0.23.4 (2015-10-10)
=================== ===================
......
...@@ -398,7 +398,7 @@ def fully_qualified_name(filename): ...@@ -398,7 +398,7 @@ def fully_qualified_name(filename):
@cached_function @cached_function
def parse_dependencies(source_filename): def parse_dependencies(source_filename):
# Actual parsing is way to slow, so we use regular expressions. # Actual parsing is way too slow, so we use regular expressions.
# The only catch is that we must strip comments and string # The only catch is that we must strip comments and string
# literals ahead of time. # literals ahead of time.
fh = Utils.open_source_file(source_filename, error_handling='ignore') fh = Utils.open_source_file(source_filename, error_handling='ignore')
...@@ -435,6 +435,8 @@ class DependencyTree(object): ...@@ -435,6 +435,8 @@ class DependencyTree(object):
self._transitive_cache = {} self._transitive_cache = {}
def parse_dependencies(self, source_filename): def parse_dependencies(self, source_filename):
if path_exists(source_filename):
source_filename = os.path.normpath(source_filename)
return parse_dependencies(source_filename) return parse_dependencies(source_filename)
@cached_method @cached_method
......
...@@ -9625,8 +9625,7 @@ class TypecastNode(ExprNode): ...@@ -9625,8 +9625,7 @@ class TypecastNode(ExprNode):
return CoerceIntToBytesNode(self.operand, env) return CoerceIntToBytesNode(self.operand, env)
elif self.operand.type.can_coerce_to_pyobject(env): elif self.operand.type.can_coerce_to_pyobject(env):
self.result_ctype = py_object_type self.result_ctype = py_object_type
base_type = self.base_type.analyse(env) self.operand = self.operand.coerce_to(self.type, env)
self.operand = self.operand.coerce_to(base_type, env)
else: else:
if self.operand.type.is_ptr: if self.operand.type.is_ptr:
if not (self.operand.type.base_type.is_void or self.operand.type.base_type.is_struct): if not (self.operand.type.base_type.is_void or self.operand.type.base_type.is_struct):
......
...@@ -8186,8 +8186,8 @@ class ParallelStatNode(StatNode, ParallelNode): ...@@ -8186,8 +8186,8 @@ class ParallelStatNode(StatNode, ParallelNode):
code.set_all_labels(self.old_loop_labels + (self.old_return_label, code.set_all_labels(self.old_loop_labels + (self.old_return_label,
self.old_error_label)) self.old_error_label))
def end_parallel_control_flow_block(self, code, def end_parallel_control_flow_block(
break_=False, continue_=False): self, code, break_=False, continue_=False, return_=False):
""" """
This ends the parallel control flow block and based on how the parallel This ends the parallel control flow block and based on how the parallel
section was exited, takes the corresponding action. The break_ and section was exited, takes the corresponding action. The break_ and
...@@ -8244,8 +8244,9 @@ class ParallelStatNode(StatNode, ParallelNode): ...@@ -8244,8 +8244,9 @@ class ParallelStatNode(StatNode, ParallelNode):
code.put(" case 2: ") code.put(" case 2: ")
code.put_goto(code.break_label) code.put_goto(code.break_label)
code.put(" case 3: ") if return_:
code.put_goto(code.return_label) code.put(" case 3: ")
code.put_goto(code.return_label)
if self.error_label_used: if self.error_label_used:
code.globalstate.use_utility_code(restore_exception_utility_code) code.globalstate.use_utility_code(restore_exception_utility_code)
...@@ -8323,10 +8324,12 @@ class ParallelWithBlockNode(ParallelStatNode): ...@@ -8323,10 +8324,12 @@ class ParallelWithBlockNode(ParallelStatNode):
continue_ = code.label_used(code.continue_label) continue_ = code.label_used(code.continue_label)
break_ = code.label_used(code.break_label) break_ = code.label_used(code.break_label)
return_ = code.label_used(code.return_label)
self.restore_labels(code) self.restore_labels(code)
self.end_parallel_control_flow_block(code, break_=break_, self.end_parallel_control_flow_block(code, break_=break_,
continue_=continue_) continue_=continue_,
return_=return_)
self.release_closure_privates(code) self.release_closure_privates(code)
...@@ -8528,6 +8531,7 @@ class ParallelRangeNode(ParallelStatNode): ...@@ -8528,6 +8531,7 @@ class ParallelRangeNode(ParallelStatNode):
# the start, stop , step, temps and target cnames # the start, stop , step, temps and target cnames
fmt_dict = { fmt_dict = {
'target': target_index_cname, 'target': target_index_cname,
'target_type': self.target.type.empty_declaration_code()
} }
# Setup start, stop and step, allocating temps if needed # Setup start, stop and step, allocating temps if needed
...@@ -8556,7 +8560,7 @@ class ParallelRangeNode(ParallelStatNode): ...@@ -8556,7 +8560,7 @@ class ParallelRangeNode(ParallelStatNode):
self.control_flow_var_code_point = code.insertion_point() self.control_flow_var_code_point = code.insertion_point()
# Note: nsteps is private in an outer scope if present # Note: nsteps is private in an outer scope if present
code.putln("%(nsteps)s = (%(stop)s - %(start)s) / %(step)s;" % fmt_dict) code.putln("%(nsteps)s = (%(stop)s - %(start)s + %(step)s - %(step)s/abs(%(step)s)) / %(step)s;" % fmt_dict)
# The target iteration variable might not be initialized, do it only if # The target iteration variable might not be initialized, do it only if
# we are executing at least 1 iteration, otherwise we should leave the # we are executing at least 1 iteration, otherwise we should leave the
...@@ -8670,7 +8674,7 @@ class ParallelRangeNode(ParallelStatNode): ...@@ -8670,7 +8674,7 @@ class ParallelRangeNode(ParallelStatNode):
# at least it doesn't spoil indentation # at least it doesn't spoil indentation
code.begin_block() code.begin_block()
code.putln("%(target)s = %(start)s + %(step)s * %(i)s;" % fmt_dict) code.putln("%(target)s = (%(target_type)s)(%(start)s + %(step)s * %(i)s);" % fmt_dict)
self.initialize_privates_to_nan(code, exclude=self.target.entry) self.initialize_privates_to_nan(code, exclude=self.target.entry)
if self.is_parallel: if self.is_parallel:
......
...@@ -2703,11 +2703,13 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2703,11 +2703,13 @@ class TransformBuiltinMethods(EnvTransform):
node.function.pos, operand1=node.args[0], operand2=node.args[1]) node.function.pos, operand1=node.args[0], operand2=node.args[1])
elif function == u'cast': elif function == u'cast':
if len(node.args) != 2: if len(node.args) != 2:
error(node.function.pos, u"cast() takes exactly two arguments") error(node.function.pos,
u"cast() takes exactly two arguments and an optional typecheck keyword")
else: else:
type = node.args[0].analyse_as_type(self.current_env()) type = node.args[0].analyse_as_type(self.current_env())
if type: if type:
node = ExprNodes.TypecastNode(node.function.pos, type=type, operand=node.args[1]) node = ExprNodes.TypecastNode(
node.function.pos, type=type, operand=node.args[1], typecheck=False)
else: else:
error(node.args[0].pos, "Not a type") error(node.args[0].pos, "Not a type")
elif function == u'sizeof': elif function == u'sizeof':
...@@ -2753,6 +2755,26 @@ class TransformBuiltinMethods(EnvTransform): ...@@ -2753,6 +2755,26 @@ class TransformBuiltinMethods(EnvTransform):
return self._inject_super(node, func_name) return self._inject_super(node, func_name)
return node return node
def visit_GeneralCallNode(self, node):
function = node.function.as_cython_attribute()
if function:
args = node.positional_args.args
kwargs = node.keyword_args.compile_time_value(None)
if function == u'cast':
if (len(args) != 2 or len(kwargs) > 1 or
(len(kwargs) == 1 and 'typecheck' not in kwargs)):
error(node.function.pos,
u"cast() takes exactly two arguments and an optional typecheck keyword")
else:
type = args[0].analyse_as_type(self.current_env())
if type:
typecheck = kwargs.get('typecheck', False)
node = ExprNodes.TypecastNode(
node.function.pos, type=type, operand=args[1], typecheck=typecheck)
else:
error(args[0].pos, "Not a type")
return node
class ReplaceFusedTypeChecks(VisitorTransform): class ReplaceFusedTypeChecks(VisitorTransform):
""" """
......
...@@ -148,7 +148,9 @@ def cmod(a, b): ...@@ -148,7 +148,9 @@ def cmod(a, b):
# Emulated language constructs # Emulated language constructs
def cast(type, *args): def cast(type, *args, **kwargs):
kwargs.pop('typecheck', None)
assert not kwargs
if hasattr(type, '__call__'): if hasattr(type, '__call__'):
return type(*args) return type(*args)
else: else:
......
...@@ -287,6 +287,15 @@ Further Cython functions and declarations ...@@ -287,6 +287,15 @@ Further Cython functions and declarations
T = cython.typedef(cython.p_int) # ctypedef int* T T = cython.typedef(cython.p_int) # ctypedef int* T
* ``cast`` will (unsafely) reinterpret an expression type. ``cython.cast(T, t)``
is equivalent to ``<T>t``. The first attribute must be a type, the second is
the expression to cast. Specifying the optional keyword argument
``typecheck=True`` has the semantics of ``<T?>t``.
::
t1 = cython.cast(T, t)
t2 = cython.cast(T, t, typecheck=True)
.. _magic_attributes_pxd: .. _magic_attributes_pxd:
......
...@@ -49,6 +49,26 @@ def test_cast(x): ...@@ -49,6 +49,26 @@ def test_cast(x):
n = cython.cast(cython.int, x) n = cython.cast(cython.int, x)
return n return n
@cython.locals(as_list=list)
def test_cast_object(x, typecheck):
"""
>>> test_cast_object([1, 2, 3], True)
[1, 2, 3]
>>> test_cast_object([1, 2, 3], False)
[1, 2, 3]
>>> test_cast_object((1, 2, 3), True)
Traceback (most recent call last):
...
TypeError: Expected list, got tuple
>>> test_cast_object((1, 2, 3), False)
(1, 2, 3)
"""
if typecheck:
as_list = cython.cast(list, x, typecheck=True)
else:
as_list = cython.cast(list, x, typecheck=False)
return as_list
@cython.locals(x=cython.int, y=cython.p_int) @cython.locals(x=cython.int, y=cython.p_int)
def test_address(x): def test_address(x):
""" """
......
...@@ -46,6 +46,34 @@ def test_descending_prange(): ...@@ -46,6 +46,34 @@ def test_descending_prange():
return sum return sum
def test_prange_matches_range(int start, int stop, int step):
"""
>>> test_prange_matches_range(0, 8, 3)
>>> test_prange_matches_range(0, 9, 3)
>>> test_prange_matches_range(0, 10, 3)
>>> test_prange_matches_range(0, 10, -3)
>>> test_prange_matches_range(0, -10, -3)
>>> test_prange_matches_range(1, -10, -3)
>>> test_prange_matches_range(2, -10, -3)
>>> test_prange_matches_range(3, -10, -3)
"""
cdef int i, range_last, prange_last
prange_set = set()
for i in prange(start, stop, step, nogil=True, num_threads=3):
prange_last = i
with gil:
prange_set.add(i)
range_set = set(range(start, stop, step))
assert range_set == prange_set, "missing: %s extra %s" % (sorted(range_set-prange_set), sorted(prange_set - range_set))
for ii in range(start, stop, step):
range_last = ii
if range_set:
assert prange_last == i
assert range_last == prange_last
def test_propagation(): def test_propagation():
""" """
>>> test_propagation() >>> test_propagation()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment