From 3159de3e9fcd8b7bf8747711fe240a36d620075f Mon Sep 17 00:00:00 2001
From: William S Fulton
Date: Wed, 2 Mar 2022 19:33:03 +0000
Subject: [PATCH] Add support for Python variable annotations as a feature.
Both function annotations and variable annotations are turned on using the
"python:annotations" feature. Example:
%feature("python:annotations", "c");
struct V {
float val;
};
The generated code contains a variable annotation containing the C float type:
class V(object):
val: "float" = property(_example.V_val_get, _example.V_val_set)
...
Python 3.5 and earlier do not support variable annotations, so variable
annotations can be turned off with a "python:annotations:novar" feature flag.
Example turning on function annotations but not variable annotations globally:
%feature("python:annotations", "c");
%feature("python:annotations:novar");
or via the command line:
-features python:annotations=c,python:annotations:novar
Closes #1951
---
CHANGES.current | 33 ++++++++
Doc/Manual/Contents.html | 2 +-
Doc/Manual/Python.html | 76 ++++++++++++++++---
Examples/test-suite/python/Makefile.in | 1 +
.../python_annotations_variable_c_runme.py | 24 ++++++
.../python_annotations_variable_c.i | 44 +++++++++++
Source/Modules/python.cxx | 26 ++++---
7 files changed, 182 insertions(+), 24 deletions(-)
create mode 100644 Examples/test-suite/python/python_annotations_variable_c_runme.py
create mode 100644 Examples/test-suite/python_annotations_variable_c.i
diff --git a/CHANGES.current b/CHANGES.current
index ae12d1a2c..a715b7e5d 100644
--- a/CHANGES.current
+++ b/CHANGES.current
@@ -7,6 +7,37 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/
Version 4.1.0 (in progress)
===========================
+2022-03-02: geographika, wsfulton
+ [Python] #1951 Add Python variable annotations support.
+
+ Both function annotations and variable annotations are turned on using the
+ "python:annotations" feature. Example:
+
+ %feature("python:annotations", "c");
+
+ struct V {
+ float val;
+ };
+
+ The generated code contains a variable annotation containing the C float type:
+
+ class V(object):
+ val: "float" = property(_example.V_val_get, _example.V_val_set)
+ ...
+
+ Python 3.5 and earlier do not support variable annotations, so variable
+ annotations can be turned off with a "python:annotations:novar" feature flag.
+ Example turning on function annotations but not variable annotations globally:
+
+ %feature("python:annotations", "c");
+ %feature("python:annotations:novar");
+
+ or via the command line:
+
+ -features python:annotations=c,python:annotations:novar
+
+ *** POTENTIAL INCOMPATIBILITY ***
+
2022-03-02: olly
#891 Give error for typemap argument without a value. Previously
SWIG segfaulted.
@@ -23,6 +54,8 @@ Version 4.1.0 (in progress)
-features python:annotations=c
+ Also see entry dated 2022-03-02, regarding variable annotations.
+
*** POTENTIAL INCOMPATIBILITY ***
2022-02-26: wsfulton
diff --git a/Doc/Manual/Contents.html b/Doc/Manual/Contents.html
index 56049ba6c..07cd1e723 100644
--- a/Doc/Manual/Contents.html
+++ b/Doc/Manual/Contents.html
@@ -1478,7 +1478,7 @@
Python 3 Support
Python 3 Support
-- Function annotations
+
- Python function annotations and variable annotations
@@ -2536,7 +2536,7 @@ assert(issubclass(B.Derived, A.Base))
-Python function annotations are not supported.
+
Python annotations are not supported.
@@ -3998,7 +3998,7 @@ Also included in the table for comparison is using the -builtin option
Although the -fastproxy option results in faster code over the default, the generated proxy code is not as user-friendly
-as docstring/doxygen comments, Python function annotations and functions with default values are not visible in the generated Python proxy class.
+as docstring/doxygen comments, Python annotations and functions with default values are not visible in the generated Python proxy class.
The -olddefs option can rectify this.
@@ -6762,13 +6762,15 @@ The following are Python 3 new features that are currently supported by
SWIG.
-
+
Python 3 supports function annotations as defined in
PEP 3107.
-Note that there is currently no annotations support for the -builtin nor
+Python 3.6 and later additionally support variable annotations as defined in
+PEP 526.
+Note that currently there is no annotations support in SWIG for the -builtin nor
the -fastproxy option.
Annotations are added via the python:annotations
%feature directives.
@@ -6779,7 +6781,7 @@ SWIG currently supports one type of function annotation.
-The %feature("python:annotations", "c") directive generates function annotations
+The %feature("python:annotations", "c") directive generates annotations
containing C/C++ types. For example:
@@ -6789,16 +6791,16 @@ int *global_ints(int &ri);
-The generated code then contains function annotations containing the C types:
+The generated code then contains function annotations containing the C++ types:
def global_ints(ri: "int &") -> "int *":
- return _python_annotations_c.global_ints(ri)
+ return _example.global_ints(ri)
-There are some limitations with annotations support, for example, overloaded functions use
+There are some limitations with function annotations support, for example, overloaded functions use
*args or **kwargs when keyword arguments are enabled.
The parameter names and types are then not shown. For example, with input:
@@ -6815,13 +6817,65 @@ Only the return type is annotated.
def global_overloaded(*args) -> "int *":
- return _python_annotations_c.global_overloaded(*args)
+ return _example.global_overloaded(*args)
+
+Below is an example demonstrating variable annotations.
+
+
+
+%feature("python:annotations", "c");
+
+struct V {
+ float val;
+};
+
+
+
+The generated code contains a variable annotation containing the C float type:
+
+
+
+class V(object):
+ val: "float" = property(_example.V_val_get, _example.V_val_set)
+ ...
+
+
+
+Variable annotations are only supported from Python 3.6. If you need to support earlier versions of Python, you'll need to turn variable annotations off via the python:annotations:novar feature flag.
+It is quite easy to support function annotations but turn off variable annotations. The next example shows how to do this for all variables.
+
+
+
+%feature("python:annotations", "c"); // Turn on function annotations and variable annotations globally
+%feature("python:annotations:novar"); // Turn off variable annotations globally
+
+struct V {
+ float val;
+ void vv(float *v) const;
+};
+
+
+
+The resulting code will work with versions older than Python 3.6 as the variable annotations are turned off:
+
+
+
+class V(object):
+ val = property(_example.V_val_get, _example.V_val_set)
+
+ def vv(self, v: "float *") -> "void":
+ return _example.V_vv(self, v)
+ ...
+
+
+
Compatibility Note: SWIG-4.1.0 changed the way that function annotations are generated.
-Prior versions required the -py3 option which enabled function annotation support
+Prior versions required the -py3 option to generate function annotation support
containing C/C++ types instead of supporting %feature("python:annotations", "c").
+Variable annotations were also added in SWIG-4.1.0.
diff --git a/Examples/test-suite/python/Makefile.in b/Examples/test-suite/python/Makefile.in
index 57f72d512..80116be13 100644
--- a/Examples/test-suite/python/Makefile.in
+++ b/Examples/test-suite/python/Makefile.in
@@ -49,6 +49,7 @@ CPP_TEST_CASES += \
primitive_types \
python_abstractbase \
python_annotations_c \
+ python_annotations_variable_c \
python_append \
python_builtin \
python_destructor_exception \
diff --git a/Examples/test-suite/python/python_annotations_variable_c_runme.py b/Examples/test-suite/python/python_annotations_variable_c_runme.py
new file mode 100644
index 000000000..153852d05
--- /dev/null
+++ b/Examples/test-suite/python/python_annotations_variable_c_runme.py
@@ -0,0 +1,24 @@
+import sys
+
+# Variable annotations for properties is only supported in python-3.6 and later (PEP 526)
+if sys.version_info[0:2] >= (3, 6):
+ from python_annotations_variable_c import *
+
+ # No SWIG __annotations__ support with -builtin or -fastproxy
+ annotations_supported = not(is_python_builtin() or is_python_fastproxy())
+
+ if annotations_supported:
+ ts = TemplateShort()
+ anno = ts.__annotations__
+ if anno != {'member_variable': 'int'}:
+ raise RuntimeError("annotations mismatch: {}".format(anno))
+
+ ts = StructWithVar()
+ anno = ts.__annotations__
+ if anno != {'member_variable': 'int'}:
+ raise RuntimeError("annotations mismatch: {}".format(anno))
+
+ ts = StructWithVarNotAnnotated()
+ if getattr(ts, "__annotations__", None) != None:
+ anno = ts.__annotations__
+ raise RuntimeError("annotations mismatch: {}".format(anno))
diff --git a/Examples/test-suite/python_annotations_variable_c.i b/Examples/test-suite/python_annotations_variable_c.i
new file mode 100644
index 000000000..876bbee75
--- /dev/null
+++ b/Examples/test-suite/python_annotations_variable_c.i
@@ -0,0 +1,44 @@
+%module python_annotations_variable_c
+
+// Tests Python variable annotations, containing C/C++ types, in C++ member variables wrappers.
+// Member variables are wrapped using Python properties.
+// This is in a separate test to python_annotations_c.i (which tests function annotations) so that runtime testing
+// of variable annotations can be done which requires Python 3.6 and later. A syntax error occurs in earlier
+// versions of Python when importing code containing variable annotations.
+
+%feature("python:annotations", "c");
+%feature("python:annotations:novar") member_variable_not_annotated;
+
+
+%inline %{
+namespace Space {
+template
+struct Template {
+ int member_variable;
+ int member_variable_not_annotated;
+};
+struct StructWithVar{
+ int member_variable;
+};
+struct StructWithVarNotAnnotated {
+ int member_variable_not_annotated;
+};
+short global_variable;
+}
+%}
+%template(TemplateShort) Space::Template;
+
+%inline %{
+#ifdef SWIGPYTHON_BUILTIN
+int is_python_builtin() { return 1; }
+#else
+int is_python_builtin() { return 0; }
+#endif
+
+#if defined SWIGPYTHON_FASTPROXY
+int is_python_fastproxy() { return 1; }
+#else
+int is_python_fastproxy() { return 0; }
+#endif
+%}
+
diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx
index 179375fda..bfa198283 100644
--- a/Source/Modules/python.cxx
+++ b/Source/Modules/python.cxx
@@ -2395,20 +2395,20 @@ public:
}
/* ------------------------------------------------------------
- * returnPropertyAnnotation()
+ * variableAnnotation()
*
- * Helper function for constructing a property annotation
- * return a empty string for Python 2.x
+ * Helper function for constructing a variable annotation
* ------------------------------------------------------------ */
- String *returnPropertyAnnotation(Node *n) {
- String *ret = 0;
- ret = Getattr(n, "type");
- if (ret) {
- ret = SwigType_str(ret, 0);
- }
- return (ret && py3) ? NewStringf(": \"%s\"", ret)
- : NewString("");
+ String *variableAnnotation(Node *n) {
+ String *type = Getattr(n, "type");
+ if (type)
+ type = SwigType_str(type, 0);
+ bool anno = Equal(Getattr(n, "feature:python:annotations"), "c") ? true : false;
+ anno = GetFlag(n, "feature:python:annotations:novar") ? false : anno;
+ String *annotation = (type && anno) ? NewStringf(": \"%s\"", type) : NewString("");
+ Delete(type);
+ return annotation;
}
/* ------------------------------------------------------------
@@ -5059,12 +5059,14 @@ public:
String *setname = Swig_name_set(NSPACE_TODO, mname);
String *getname = Swig_name_get(NSPACE_TODO, mname);
int assignable = is_assignable(n);
- Printv(f_shadow, tab4, symname, returnPropertyAnnotation(n), " = property(", module, ".", getname, NIL);
+ String *variable_annotation = variableAnnotation(n);
+ Printv(f_shadow, tab4, symname, variable_annotation, " = property(", module, ".", getname, NIL);
if (assignable)
Printv(f_shadow, ", ", module, ".", setname, NIL);
if (have_docstring(n))
Printv(f_shadow, ", doc=", docstring(n, AUTODOC_VAR, tab4), NIL);
Printv(f_shadow, ")\n", NIL);
+ Delete(variable_annotation);
Delete(mname);
Delete(setname);
Delete(getname);