Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 .github/workflows/build-wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ jobs:
uses: pypa/cibuildwheel@v2.21.3
env:
CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }}*
CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
CIBW_ARCHS: ${{ matrix.buildplat[2] }}
CIBW_ENVIRONMENT_MACOS: MACOSX_DEPLOYMENT_TARGET=13.0
CIBW_ENVIRONMENT_PASS_LINUX: RUNNER_OS
Expand Down
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ list(APPEND tests
issue306_allocatable_realloc
issue307_logical_array
issue32
issue373_optional_constructor
issue41_abstract_classes
keep_single_interface
keyword_renaming_issue160
Expand Down
1 change: 1 addition & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ EXAMPLES = \
issue32 \
issue353_selected_kind \
issue357_name_conflict \
issue373_optional_constructor \
issue41_abstract_classes \
keep_single_interface \
keyword_renaming_issue160 \
Expand Down
33 changes: 33 additions & 0 deletions examples/issue373_optional_constructor/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#=======================================================================
# define the compiler names
#=======================================================================

include ../make.inc
PY_MOD = pywrapper
F90_SRC = main.f90
OBJ = $(F90_SRC:.f90=.o)
F90WRAP_SRC = $(addprefix f90wrap_,${F90_SRC})
WRAPFLAGS = -v
F2PY = f2py-f90wrap
SRC_AR = libsrc.a
.PHONY: all clean

all: test

clean:
rm -rf *.mod *.smod *.o ${SRC_AR} f90wrap*.f90 ${PY_MOD}.py _${PY_MOD}*.so __pycache__/ .f2py_f2cmap build ${PY_MOD}/

%.o: %.f90
${F90} ${F90FLAGS} -c $< -o $@

${F90WRAP_SRC}: ${OBJ}
${F90WRAP} -m ${PY_MOD} ${WRAPFLAGS} ${F90_SRC}

${SRC_AR}: ${OBJ}
ar rcs $@ ${OBJ}

f2py: ${F90WRAP_SRC} ${SRC_AR}
CFLAGS="${CFLAGS}" ${F2PY} -c -m _${PY_MOD} ${F2PYFLAGS} f90wrap_*.f90 -L. -lsrc

test: f2py
${PYTHON} tests.py
6 changes: 6 additions & 0 deletions examples/issue373_optional_constructor/Makefile.meson
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include ../make.meson.inc

NAME := pywrapper

test: build
$(PYTHON) tests.py
28 changes: 28 additions & 0 deletions examples/issue373_optional_constructor/main.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module widget_mod
implicit none
public

type :: widget_t
real :: x
real :: scale
end type widget_t

interface widget_t
module procedure widget_init
end interface widget_t

contains

function widget_init(x, scale) result(w)
real, intent(in) :: x
real, intent(in), optional :: scale
type(widget_t) :: w
w%x = x
if (present(scale)) then
w%scale = scale
else
w%scale = 1.0
end if
end function widget_init

end module widget_mod
19 changes: 19 additions & 0 deletions examples/issue373_optional_constructor/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import unittest

from pywrapper import widget_mod


class TestOptionalConstructor(unittest.TestCase):
def test_constructor_without_optional(self):
w = widget_mod.widget_t(3.0)
self.assertAlmostEqual(w.x, 3.0)
self.assertAlmostEqual(w.scale, 1.0)

def test_constructor_with_optional(self):
w = widget_mod.widget_t(2.0, scale=0.5)
self.assertAlmostEqual(w.x, 2.0)
self.assertAlmostEqual(w.scale, 0.5)


if __name__ == "__main__":
unittest.main()
10 changes: 8 additions & 2 deletions f90wrap/directc_cgen/arguments_scalar.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ def prepare_scalar_argument(gen: 'DirectCGenerator', arg: ft.Argument, intent: s
if optional:
gen.write(f"if (py_{arg.name} == Py_None) {{")
gen.indent()
gen.write(f"{arg.name}_val = 0;")
# Pass a NULL pointer so the Fortran side sees present(arg) == .false.
# instead of an argument whose value happens to be zero.
gen.write(f"{arg.name} = NULL;")
gen.dedent()
gen.write("} else {")
gen.indent()
Expand All @@ -121,7 +123,11 @@ def _prepare_character_none_case(
"""Handle None value for character arguments."""
gen.write(f"if (py_{arg.name} == Py_None) {{")
gen.indent()
if optional or intent != "in":
if optional:
# Absent optional argument: pass a NULL pointer so present(arg) == .false.
# The output path returns None for a NULL character buffer.
gen.write(f"{arg.name} = NULL;")
elif intent != "in":
gen.write(f"{arg.name}_len = {default_len};")
gen.write(f"if ({arg.name}_len <= 0) {{")
gen.indent()
Expand Down
8 changes: 7 additions & 1 deletion f90wrap/directc_cgen/procedures_return.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,13 @@ def _prepare_character_output(gen: DirectCGenerator, arg: ft.Argument) -> None:
if parsed:
# Check if buffer is from numpy array
gen.write(f"PyObject* py_{arg.name}_obj = NULL;")
gen.write(f"if ({arg.name}_is_array) {{")
# An absent optional argument has a NULL buffer: return None for it.
gen.write(f"if ({arg.name} == NULL) {{")
gen.indent()
gen.write("Py_INCREF(Py_None);")
gen.write(f"py_{arg.name}_obj = Py_None;")
gen.dedent()
gen.write(f"}} else if ({arg.name}_is_array) {{")
gen.indent()
gen.write("/* Numpy array was modified in place, no return object or free needed */")
gen.dedent()
Expand Down
10 changes: 4 additions & 6 deletions f90wrap/transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,12 +554,10 @@ def convert_derived_type_arguments(tree, init_lines, sizeof_fortran_t):
if 'constructor' in sub.attributes:
# Put the return value of the fortran constructors at first position in the arguments list
if 'fortranconstructor' in sub.attributes:
j = -1
for i, arg in enumerate(sub.arguments):
if 'optional' in arg.attributes:
j = i
break
assert(sub.arguments[j].name[:4]=='ret_')
j = next((i for i, arg in enumerate(sub.arguments)
if arg.name.startswith('ret_')), None)
assert j is not None, \
'no ret_ argument found in constructor %s' % sub.name
sub.arguments.insert(0, sub.arguments.pop(j))
sub.arguments[0].attributes = set_intent(sub.arguments[0].attributes, 'intent(out)')

Expand Down
Loading