Skip to content

Commit

Permalink
Merge pull request #18354 from charris/backport-18184
Browse files Browse the repository at this point in the history
BUG: Fix f2py bugs when wrapping F90 subroutines.
  • Loading branch information
charris committed Feb 7, 2021
2 parents 730b5fe + 48ba4dd commit b66f57a
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 11 deletions.
2 changes: 1 addition & 1 deletion numpy/distutils/fcompiler/__init__.py
Expand Up @@ -976,7 +976,7 @@ def is_free_format(file):
with open(file, encoding='latin1') as f:
line = f.readline()
n = 10000 # the number of non-comment lines to scan for hints
if _has_f_header(line):
if _has_f_header(line) or _has_fix_header(line):
n = 0
elif _has_f90_header(line):
n = 0
Expand Down
5 changes: 5 additions & 0 deletions numpy/f2py/auxfuncs.py
Expand Up @@ -257,6 +257,7 @@ def ismodule(rout):
def isfunction(rout):
return 'block' in rout and 'function' == rout['block']


def isfunction_wrap(rout):
if isintent_c(rout):
return 0
Expand Down Expand Up @@ -284,6 +285,10 @@ def hasassumedshape(rout):
return False


def requiresf90wrapper(rout):
return ismoduleroutine(rout) or hasassumedshape(rout)


def isroutine(rout):
return isfunction(rout) or issubroutine(rout)

Expand Down
11 changes: 8 additions & 3 deletions numpy/f2py/crackfortran.py
Expand Up @@ -3109,7 +3109,7 @@ def crack2fortrangen(block, tab='\n', as_interface=False):
result = ' result (%s)' % block['result']
if block['result'] not in argsl:
argsl.append(block['result'])
body = crack2fortrangen(block['body'], tab + tabchar)
body = crack2fortrangen(block['body'], tab + tabchar, as_interface=as_interface)
vars = vars2fortran(
block, block['vars'], argsl, tab + tabchar, as_interface=as_interface)
mess = ''
Expand Down Expand Up @@ -3227,8 +3227,13 @@ def vars2fortran(block, vars, args, tab='', as_interface=False):
show(vars)
outmess('vars2fortran: No definition for argument "%s".\n' % a)
continue
if a == block['name'] and not block['block'] == 'function':
continue
if a == block['name']:
if block['block'] != 'function' or block.get('result'):
# 1) skip declaring a variable that name matches with
# subroutine name
# 2) skip declaring function when its type is
# declared via `result` construction
continue
if 'typespec' not in vars[a]:
if 'attrspec' in vars[a] and 'external' in vars[a]['attrspec']:
if a in args:
Expand Down
9 changes: 6 additions & 3 deletions numpy/f2py/func2subr.py
Expand Up @@ -130,7 +130,7 @@ def add(line, ret=ret):
l = l + ', ' + fortranname
if need_interface:
for line in rout['saved_interface'].split('\n'):
if line.lstrip().startswith('use '):
if line.lstrip().startswith('use ') and '__user__' not in line:
add(line)

args = args[1:]
Expand Down Expand Up @@ -222,7 +222,7 @@ def add(line, ret=ret):

if need_interface:
for line in rout['saved_interface'].split('\n'):
if line.lstrip().startswith('use '):
if line.lstrip().startswith('use ') and '__user__' not in line:
add(line)

dumped_args = []
Expand All @@ -247,7 +247,10 @@ def add(line, ret=ret):
pass
else:
add('interface')
add(rout['saved_interface'].lstrip())
for line in rout['saved_interface'].split('\n'):
if line.lstrip().startswith('use ') and '__user__' in line:
continue
add(line)
add('end interface')

sargs = ', '.join([a for a in args if a not in extra_args])
Expand Down
17 changes: 13 additions & 4 deletions numpy/f2py/rules.py
Expand Up @@ -76,7 +76,7 @@
issubroutine, issubroutine_wrap, isthreadsafe, isunsigned,
isunsigned_char, isunsigned_chararray, isunsigned_long_long,
isunsigned_long_longarray, isunsigned_short, isunsigned_shortarray,
l_and, l_not, l_or, outmess, replace, stripcomma,
l_and, l_not, l_or, outmess, replace, stripcomma, requiresf90wrapper
)

from . import capi_maps
Expand Down Expand Up @@ -1187,9 +1187,12 @@ def buildmodule(m, um):
nb1['args'] = a
nb_list.append(nb1)
for nb in nb_list:
# requiresf90wrapper must be called before buildapi as it
# rewrites assumed shape arrays as automatic arrays.
isf90 = requiresf90wrapper(nb)
api, wrap = buildapi(nb)
if wrap:
if ismoduleroutine(nb):
if isf90:
funcwrappers2.append(wrap)
else:
funcwrappers.append(wrap)
Expand Down Expand Up @@ -1291,7 +1294,10 @@ def buildmodule(m, um):
'C It contains Fortran 77 wrappers to fortran functions.\n')
lines = []
for l in ('\n\n'.join(funcwrappers) + '\n').split('\n'):
if l and l[0] == ' ':
if 0 <= l.find('!') < 66:
# don't split comment lines
lines.append(l + '\n')
elif l and l[0] == ' ':
while len(l) >= 66:
lines.append(l[:66] + '\n &')
l = l[66:]
Expand All @@ -1313,7 +1319,10 @@ def buildmodule(m, um):
'! It contains Fortran 90 wrappers to fortran functions.\n')
lines = []
for l in ('\n\n'.join(funcwrappers2) + '\n').split('\n'):
if len(l) > 72 and l[0] == ' ':
if 0 <= l.find('!') < 72:
# don't split comment lines
lines.append(l + '\n')
elif len(l) > 72 and l[0] == ' ':
lines.append(l[:72] + '&\n &')
l = l[72:]
while len(l) > 66:
Expand Down
25 changes: 25 additions & 0 deletions numpy/f2py/tests/test_callback.py
Expand Up @@ -211,3 +211,28 @@ class TestF77CallbackPythonTLS(TestF77Callback):
compiler-provided
"""
options = ["-DF2PY_USE_PYTHON_TLS"]


class TestF90Callback(util.F2PyTest):

suffix = '.f90'

code = textwrap.dedent(
"""
function gh17797(f, y) result(r)
external f
integer(8) :: r, f
integer(8), dimension(:) :: y
r = f(0)
r = r + sum(y)
end function gh17797
""")

def test_gh17797(self):

def incr(x):
return x + 123

y = np.array([1, 2, 3], dtype=np.int64)
r = self.module.gh17797(incr, y)
assert r == 123 + 1 + 2 + 3

0 comments on commit b66f57a

Please sign in to comment.