diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 273dedf3be..98bff17d42 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,9 +50,15 @@ jobs: shadow: ./src/shadow - features: huge coverage: true + - features: huge + compiler: clang + extra: none + interface: dynamic + python3: stable-abi - features: huge compiler: gcc coverage: true + interface: dynamic extra: testgui uchar: true luaver: lua5.4 @@ -141,7 +147,16 @@ jobs: ;; huge) echo "TEST=scripttests test_libvterm" - echo "CONFOPT=--enable-perlinterp --enable-pythoninterp --enable-python3interp --enable-rubyinterp --enable-luainterp --enable-tclinterp" + if ${{ matrix.interface == 'dynamic' }}; then + if ${{ matrix.python3 == 'stable-abi' }}; then + PYTHON3_FLAGS="--with-python3-stable-abi=3.8" + else + PYTHON3_FLAGS="" + fi + echo "CONFOPT=--enable-perlinterp=dynamic --enable-pythoninterp=dynamic --enable-python3interp=dynamic --enable-rubyinterp=dynamic --enable-luainterp=dynamic --enable-tclinterp=dynamic ${PYTHON3_FLAGS}" + else + echo "CONFOPT=--enable-perlinterp --enable-pythoninterp --enable-python3interp --enable-rubyinterp --enable-luainterp --enable-tclinterp" + fi ;; esac @@ -369,8 +384,8 @@ jobs: fail-fast: false matrix: include: - - { features: HUGE, toolchain: msvc, VIMDLL: no, GUI: no, arch: x64 } - - { features: HUGE, toolchain: mingw, VIMDLL: yes, GUI: yes, arch: x86, coverage: yes } + - { features: HUGE, toolchain: msvc, VIMDLL: no, GUI: no, arch: x64, python3: stable } + - { features: HUGE, toolchain: mingw, VIMDLL: yes, GUI: yes, arch: x86, python3: stable, coverage: yes } - { features: HUGE, toolchain: msvc, VIMDLL: no, GUI: yes, arch: x86 } - { features: HUGE, toolchain: mingw, VIMDLL: yes, GUI: no, arch: x64, coverage: yes } - { features: NORMAL, toolchain: msvc, VIMDLL: yes, GUI: no, arch: x86 } @@ -501,6 +516,11 @@ jobs: ) else ( set GUI=${{ matrix.GUI }} ) + if "${{ matrix.python3 }}"=="stable" ( + set PYTHON3_STABLE=yes + ) else ( + set PYTHON3_STABLE=no + ) if "${{ matrix.features }}"=="HUGE" ( nmake -nologo -f Make_mvc.mak ^ FEATURES=${{ matrix.features }} ^ @@ -508,6 +528,7 @@ jobs: DYNAMIC_LUA=yes LUA=%LUA_DIR% ^ DYNAMIC_PYTHON=yes PYTHON=%PYTHON_DIR% ^ DYNAMIC_PYTHON3=yes PYTHON3=%PYTHON3_DIR% ^ + DYNAMIC_PYTHON3_STABLE_ABI=%PYTHON3_STABLE% ^ DYNAMIC_SODIUM=yes SODIUM=%SODIUM_DIR% ) else ( nmake -nologo -f Make_mvc.mak ^ @@ -525,6 +546,11 @@ jobs: else GUI=${{ matrix.GUI }} fi + if [ "${{ matrix.python3 }}" = "stable" ]; then + PYTHON3_STABLE=yes + else + PYTHON3_STABLE=no + fi if [ "${{ matrix.features }}" = "HUGE" ]; then mingw32-make -f Make_ming.mak -j2 \ FEATURES=${{ matrix.features }} \ @@ -532,6 +558,7 @@ jobs: DYNAMIC_LUA=yes LUA=${LUA_DIR_SLASH} \ DYNAMIC_PYTHON=yes PYTHON=${PYTHON_DIR} \ DYNAMIC_PYTHON3=yes PYTHON3=${PYTHON3_DIR} \ + DYNAMIC_PYTHON3_STABLE_ABI=${PYTHON3_STABLE} \ DYNAMIC_SODIUM=yes SODIUM=${SODIUM_DIR} \ STATIC_STDCPLUS=yes COVERAGE=${{ matrix.coverage }} else diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index e9bdad2edf..99bc9e2be7 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -10986,6 +10986,7 @@ python_dynamic Python 2.x interface is dynamically loaded. |has-python| python3 Python 3.x interface available. |has-python| python3_compiled Compiled with Python 3.x interface. |has-python| python3_dynamic Python 3.x interface is dynamically loaded. |has-python| +python3_stable Python 3.x interface is using Python Stable ABI. |has-python| pythonx Python 2.x and/or 3.x interface available. |python_x| qnx QNX version of Vim. quickfix Compiled with |quickfix| support. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 7e26605f84..4f9a3c99f5 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2424,6 +2424,25 @@ v:progpath Contains the command with which Vim was invoked, in a form ".exe" is not added to v:progpath. Read-only. + *v:python3_version* *python3-version-variable* +v:python3_version + Version of Python 3 that Vim was built against. When + Python is loaded dynamically (|python-dynamic|), this version + should exactly match the Python library up to the minor + version (e.g. 3.10.2 and 3.10.3 are compatible as the minor + version is "10", whereas 3.9.4 and 3.10.3 are not compatible). + When |python-stable-abi| is used, this will be the minimum Python + version that you can use instead. (e.g. if v:python3_version + indicates 3.9, you can use 3.9, 3.10, or anything above). + + This number is encoded as a hex number following Python ABI + versioning conventions. Do the following to have a + human-readable full version in hex: > + echo printf("%08X", v:python3_version) +< You can obtain only the minor version by doing: > + echo and(v:python3_version>>16,0xff) +< Read-only. + *v:register* *register-variable* v:register The name of the register in effect for the current normal mode command (regardless of whether that command actually used a diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt index c2a0094b63..3d3b92a662 100644 --- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -769,7 +769,19 @@ Unix ~ The 'pythondll' or 'pythonthreedll' option can be used to specify the Python shared library file instead of DYNAMIC_PYTHON_DLL or DYNAMIC_PYTHON3_DLL file what were specified at compile time. The version of the shared library must -match the Python 2.x or Python 3 version Vim was compiled with. +match the Python 2.x or Python 3 version (|v:python3_version|) Vim was +compiled with unless using |python3-stable-abi|. + + +Stable ABI and mixing Python versions ~ + *python-stable* *python-stable-abi* *python3-stable-abi* +If Vim was not compiled with Stable ABI (only available for Python 3), the +version of the Python shared library must match the version that Vim was +compiled with. Otherwise, mixing versions could result in unexpected crashes +and failures. With Stable ABI, this restriction is relaxed, and any Python 3 +library with version of at least |v:python3_version| will work. See +|has-python| for how to check if Stable ABI is supported, or see if version +output includes |+python3/dyn-stable|. ============================================================================== 10. Python 3 *python3* @@ -881,6 +893,18 @@ python support: > endif endif +When loading the library dynamically, Vim can be compiled to support Python 3 +Stable ABI (|python3-stable-abi|) which allows you to load a different version +of Python 3 library than the one Vim was compiled with. To check it: > + if has('python3_dynamic') + if has('python3_stable') + echo 'support Python 3 Stable ABI.' + else + echo 'does not support Python 3 Stable ABI.' + echo 'only use Python 3 version ' .. v:python3_version + endif + endif + This also tells you whether Python is dynamically loaded, which will fail if the runtime library cannot be found. diff --git a/runtime/doc/tags b/runtime/doc/tags index ce1918f26a..673c0db654 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1434,6 +1434,7 @@ $quote eval.txt /*$quote* +python/dyn various.txt /*+python\/dyn* +python3 various.txt /*+python3* +python3/dyn various.txt /*+python3\/dyn* ++python3/dyn-stable various.txt /*+python3\/dyn-stable* +quickfix various.txt /*+quickfix* +reltime various.txt /*+reltime* +rightleft various.txt /*+rightleft* @@ -9294,6 +9295,8 @@ python-path_hook if_pyth.txt /*python-path_hook* python-pyeval if_pyth.txt /*python-pyeval* python-range if_pyth.txt /*python-range* python-special-path if_pyth.txt /*python-special-path* +python-stable if_pyth.txt /*python-stable* +python-stable-abi if_pyth.txt /*python-stable-abi* python-strwidth if_pyth.txt /*python-strwidth* python-tabpage if_pyth.txt /*python-tabpage* python-tabpages if_pyth.txt /*python-tabpages* @@ -9306,6 +9309,8 @@ python.vim syntax.txt /*python.vim* python2-directory if_pyth.txt /*python2-directory* python3 if_pyth.txt /*python3* python3-directory if_pyth.txt /*python3-directory* +python3-stable-abi if_pyth.txt /*python3-stable-abi* +python3-version-variable eval.txt /*python3-version-variable* python_x if_pyth.txt /*python_x* python_x-special-comments if_pyth.txt /*python_x-special-comments* pythonx if_pyth.txt /*pythonx* @@ -10632,6 +10637,7 @@ v:prevcount eval.txt /*v:prevcount* v:profiling eval.txt /*v:profiling* v:progname eval.txt /*v:progname* v:progpath eval.txt /*v:progpath* +v:python3_version eval.txt /*v:python3_version* v:register eval.txt /*v:register* v:scrollstart eval.txt /*v:scrollstart* v:searchforward eval.txt /*v:searchforward* diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index a11e166c38..e478c8266e 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -450,6 +450,8 @@ m *+python* Python 2 interface |python| m *+python/dyn* Python 2 interface |python-dynamic| |/dyn| m *+python3* Python 3 interface |python| m *+python3/dyn* Python 3 interface |python-dynamic| |/dyn| +m *+python3/dyn-stable* + Python 3 interface |python-dynamic| |python-stable| |/dyn| N *+quickfix* |:make| and |quickfix| commands N *+reltime* |reltime()| function, 'hlsearch'/'incsearch' timeout, 'redrawtime' option diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak index 426125663c..e4fe0be8fb 100644 --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -412,6 +412,9 @@ PYTHON3INC=-I $(PYTHON3)/include else PYTHON3INC=-I $(PYTHON3)/win32inc endif + ifeq ($(DYNAMIC_PYTHON3_STABLE_ABI),yes) +PYTHON3INC += -DPy_LIMITED_API=0x3080000 + endif endif endif @@ -594,6 +597,9 @@ ifdef PYTHON3 CFLAGS += -DFEAT_PYTHON3 ifeq (yes, $(DYNAMIC_PYTHON3)) CFLAGS += -DDYNAMIC_PYTHON3 -DDYNAMIC_PYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\" + ifeq (yes, $(DYNAMIC_PYTHON3_STABLE_ABI)) +CFLAGS += -DDYNAMIC_PYTHON3_STABLE_ABI + endif else CFLAGS += -DPYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\" endif diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index f98d905042..e1d42fdcb9 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -950,7 +950,13 @@ PYTHON3_INC = /I "$(PYTHON3)\Include" /I "$(PYTHON3)\PC" ! if "$(DYNAMIC_PYTHON3)" == "yes" CFLAGS = $(CFLAGS) -DDYNAMIC_PYTHON3 \ -DDYNAMIC_PYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\" +! if "$(DYNAMIC_PYTHON3_STABLE_ABI)" == "yes" +CFLAGS = $(CFLAGS) -DDYNAMIC_PYTHON3_STABLE_ABI +PYTHON3_INC = $(PYTHON3_INC) -DPy_LIMITED_API=0x3080000 +PYTHON3_LIB = /nodefaultlib:python3.lib +! else PYTHON3_LIB = /nodefaultlib:python$(PYTHON3_VER).lib +! endif ! else CFLAGS = $(CFLAGS) -DPYTHON3_DLL=\"$(DYNAMIC_PYTHON3_DLL)\" PYTHON3_LIB = "$(PYTHON3)\libs\python$(PYTHON3_VER).lib" diff --git a/src/auto/configure b/src/auto/configure index b914aec84c..9fd6888191 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -680,6 +680,7 @@ PYTHON3_SRC PYTHON3_CFLAGS_EXTRA PYTHON3_CFLAGS PYTHON3_LIBS +vi_cv_var_python3_stable_abi vi_cv_path_python3 PYTHON_OBJ PYTHON_SRC @@ -811,6 +812,7 @@ with_python_command with_python_config_dir enable_python3interp with_python3_command +with_python3_stable_abi with_python3_config_dir enable_tclinterp with_tclsh @@ -1531,6 +1533,7 @@ Optional Packages: --with-python-command=NAME name of the Python 2 command (default: python2 or python) --with-python-config-dir=PATH Python's config directory (deprecated) --with-python3-command=NAME name of the Python 3 command (default: python3 or python) + --with-python3-stable-abi=VERSION stable ABI version to target (e.g. 3.8) --with-python3-config-dir=PATH Python's config directory (deprecated) --with-tclsh=PATH which tclsh to use (default: tclsh8.0) --with-ruby-command=RUBY name of the Ruby command (default: ruby) @@ -6753,6 +6756,34 @@ $as_echo_n "checking Python is 3.0 or better... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: yep" >&5 $as_echo "yep" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking --with-python3-stable-abi argument" >&5 +$as_echo_n "checking --with-python3-stable-abi argument... " >&6; } + + +# Check whether --with-python3-stable-abi was given. +if test "${with_python3_stable_abi+set}" = set; then : + withval=$with_python3_stable_abi; vi_cv_var_python3_stable_abi="$withval"; { $as_echo "$as_me:${as_lineno-$LINENO}: result: $vi_cv_var_python3_stable_abi" >&5 +$as_echo "$vi_cv_var_python3_stable_abi" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "X$vi_cv_var_python3_stable_abi" != "X"; then + if ${vi_cv_var_python3_stable_abi_hex+:} false; then : + $as_echo_n "(cached) " >&6 +else + + vi_cv_var_python3_stable_abi_hex=` + ${vi_cv_path_python3} -c \ + "major_minor='${vi_cv_var_python3_stable_abi}'.split('.'); print('0x{0:X}'.format( (int(major_minor.__getitem__(0))<<24) + (int(major_minor.__getitem__(1))<<16) ))"` +fi + + if test "X$vi_cv_var_python3_stable_abi_hex" == "X"; then + as_fn_error $? "can't parse Python 3 stable ABI version. It should be \".\"" "$LINENO" 5 + fi + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking Python's abiflags" >&5 $as_echo_n "checking Python's abiflags... " >&6; } if ${vi_cv_var_python3_abiflags+:} false; then : @@ -6897,9 +6928,12 @@ $as_echo "$vi_cv_dll_name_python3" >&6; } else PYTHON3_CFLAGS="-I${vi_cv_path_python3_pfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags} -I${vi_cv_path_python3_epfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags}" fi - if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then - PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'" - fi + if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then + PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'" + fi + if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then + PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPy_LIMITED_API=${vi_cv_var_python3_stable_abi_hex}" + fi PYTHON3_SRC="if_python3.c" PYTHON3_OBJ="objects/if_python3.o" @@ -7009,6 +7043,10 @@ if test "$python_ok" = yes && test "$python3_ok" = yes; then $as_echo "#define DYNAMIC_PYTHON3 1" >>confdefs.h + if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then + $as_echo "#define DYNAMIC_PYTHON3_STABLE_ABI 1" >>confdefs.h + + fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we can do without RTLD_GLOBAL for Python" >&5 $as_echo_n "checking whether we can do without RTLD_GLOBAL for Python... " >&6; } cflags_save=$CFLAGS @@ -7190,6 +7228,10 @@ rm -f core conftest.err conftest.$ac_objext \ elif test "$python3_ok" = yes && test "$enable_python3interp" = "dynamic"; then $as_echo "#define DYNAMIC_PYTHON3 1" >>confdefs.h + if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then + $as_echo "#define DYNAMIC_PYTHON3_STABLE_ABI 1" >>confdefs.h + + fi PYTHON3_SRC="if_python3.c" PYTHON3_OBJ="objects/if_python3.o" PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\"" diff --git a/src/config.h.in b/src/config.h.in index d0257ccabe..93972ca0cc 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -354,6 +354,9 @@ /* Define for linking via dlopen() or LoadLibrary() */ #undef DYNAMIC_PYTHON3 +/* Define if compiled against Python 3 stable ABI / limited API */ +#undef DYNAMIC_PYTHON3_STABLE_ABI + /* Define if dynamic python does not require RTLD_GLOBAL */ #undef PY_NO_RTLD_GLOBAL diff --git a/src/configure.ac b/src/configure.ac index a3c5f8da8c..cefd0c3b31 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -1503,6 +1503,23 @@ if test "$enable_python3interp" = "yes" -o "$enable_python3interp" = "dynamic"; then AC_MSG_RESULT(yep) + dnl -- get the stable ABI version if passed in + AC_MSG_CHECKING(--with-python3-stable-abi argument) + AC_SUBST(vi_cv_var_python3_stable_abi) + AC_ARG_WITH(python3-stable-abi, [ --with-python3-stable-abi=VERSION stable ABI version to target (e.g. 3.8)], + vi_cv_var_python3_stable_abi="$withval"; AC_MSG_RESULT($vi_cv_var_python3_stable_abi), + AC_MSG_RESULT(no)) + if test "X$vi_cv_var_python3_stable_abi" != "X"; then + AC_CACHE_VAL(vi_cv_var_python3_stable_abi_hex, + [ + vi_cv_var_python3_stable_abi_hex=` + ${vi_cv_path_python3} -c \ + "major_minor='${vi_cv_var_python3_stable_abi}'.split('.'); print('0x{0:X}'.format( (int(major_minor.__getitem__(0))<<24) + (int(major_minor.__getitem__(1))<<16) ))"` ]) + if test "X$vi_cv_var_python3_stable_abi_hex" == "X"; then + AC_MSG_ERROR([can't parse Python 3 stable ABI version. It should be "."]) + fi + fi + dnl -- get abiflags for python 3.2 or higher (PEP 3149) AC_CACHE_CHECK(Python's abiflags,vi_cv_var_python3_abiflags, [ @@ -1609,10 +1626,13 @@ eof else PYTHON3_CFLAGS="-I${vi_cv_path_python3_pfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags} -I${vi_cv_path_python3_epfx}/include/python${vi_cv_var_python3_version}${vi_cv_var_python3_abiflags}" fi - if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then - dnl Define PYTHON3_HOME if --with-python-config-dir was used - PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'" - fi + if test "X$have_python3_config_dir" = "X1" -a "$enable_python3interp" = "dynamic"; then + dnl Define PYTHON3_HOME if --with-python-config-dir was used + PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPYTHON3_HOME='L\"${vi_cv_path_python3_pfx}\"'" + fi + if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then + PYTHON3_CFLAGS="${PYTHON3_CFLAGS} -DPy_LIMITED_API=${vi_cv_var_python3_stable_abi_hex}" + fi PYTHON3_SRC="if_python3.c" PYTHON3_OBJ="objects/if_python3.o" @@ -1693,6 +1713,9 @@ dnl with dlopen(), dlsym(), dlclose() if test "$python_ok" = yes && test "$python3_ok" = yes; then AC_DEFINE(DYNAMIC_PYTHON) AC_DEFINE(DYNAMIC_PYTHON3) + if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then + AC_DEFINE(DYNAMIC_PYTHON3_STABLE_ABI) + fi AC_MSG_CHECKING(whether we can do without RTLD_GLOBAL for Python) cflags_save=$CFLAGS CFLAGS="$CFLAGS $PYTHON_CFLAGS" @@ -1816,6 +1839,9 @@ elif test "$python_ok" = yes; then fi elif test "$python3_ok" = yes && test "$enable_python3interp" = "dynamic"; then AC_DEFINE(DYNAMIC_PYTHON3) + if test "X$vi_cv_var_python3_stable_abi_hex" != "X"; then + AC_DEFINE(DYNAMIC_PYTHON3_STABLE_ABI) + fi PYTHON3_SRC="if_python3.c" PYTHON3_OBJ="objects/if_python3.o" PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\"" diff --git a/src/evalfunc.c b/src/evalfunc.c index 8561e94d22..3e020bcde0 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6165,6 +6165,13 @@ f_has(typval_T *argvars, typval_T *rettv) 1 #else 0 +#endif + }, + {"python3_stable", +#if defined(FEAT_PYTHON3) && defined(DYNAMIC_PYTHON3_STABLE_ABI) + 1 +#else + 0 #endif }, {"python3", diff --git a/src/evalvars.c b/src/evalvars.c index cb31966bd2..bd096dfd7b 100644 --- a/src/evalvars.c +++ b/src/evalvars.c @@ -157,6 +157,7 @@ static struct vimvar {VV_NAME("sizeoflong", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("sizeofpointer", VAR_NUMBER), NULL, VV_RO}, {VV_NAME("maxcol", VAR_NUMBER), NULL, VV_RO}, + {VV_NAME("python3_version", VAR_NUMBER), NULL, VV_RO}, }; // shorthand @@ -264,6 +265,10 @@ evalvars_init(void) set_vim_var_dict(VV_COLORNAMES, dict_alloc()); +#ifdef FEAT_PYTHON3 + set_vim_var_nr(VV_PYTHON3_VERSION, python3_version()); +#endif + // Default for v:register is not 0 but '"'. This is adjusted once the // clipboard has been setup by calling reset_reg_var(). set_reg_var(0); diff --git a/src/if_py_both.h b/src/if_py_both.h index 110de234fd..ff18098605 100644 --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -30,9 +30,285 @@ static const char *vim_special_path = "_vim_path_"; #define PyErr_FORMAT2(exc, str, arg1, arg2) PyErr_Format(exc, _(str), arg1,arg2) #define PyErr_VIM_FORMAT(str, arg) PyErr_FORMAT(VimError, str, arg) -#define Py_TYPE_NAME(obj) ((obj)->ob_type->tp_name == NULL \ +#ifdef USE_LIMITED_API +// Limited Python API. Need to call only exposed functions and remap macros. +// PyTypeObject is an opaque struct. + +typedef struct { + lenfunc sq_length; + binaryfunc sq_concat; + ssizeargfunc sq_repeat; + ssizeargfunc sq_item; + void *was_sq_slice; + ssizeobjargproc sq_ass_item; + void *was_sq_ass_slice; + objobjproc sq_contains; + + binaryfunc sq_inplace_concat; + ssizeargfunc sq_inplace_repeat; +} PySequenceMethods; + +typedef struct { + lenfunc mp_length; + binaryfunc mp_subscript; + objobjargproc mp_ass_subscript; +} PyMappingMethods; + +// This struct emulates the concrete _typeobject struct to allow the code to +// work the same way in both limited and full Python APIs. +struct typeobject_wrapper { + const char *tp_name; + Py_ssize_t tp_basicsize; + unsigned long tp_flags; + + // When adding new slots below, also need to make sure we add ADD_TP_SLOT + // call in AddHeapType for it. + + destructor tp_dealloc; + reprfunc tp_repr; + + PySequenceMethods *tp_as_sequence; + PyMappingMethods *tp_as_mapping; + + ternaryfunc tp_call; + getattrofunc tp_getattro; + setattrofunc tp_setattro; + + const char *tp_doc; + + traverseproc tp_traverse; + + inquiry tp_clear; + + getiterfunc tp_iter; + iternextfunc tp_iternext; + + struct PyMethodDef *tp_methods; + struct _typeobject *tp_base; + allocfunc tp_alloc; + newfunc tp_new; + freefunc tp_free; +}; + +# define DEFINE_PY_TYPE_OBJECT(type) \ + static struct typeobject_wrapper type; \ + static PyTypeObject* type##Ptr = NULL + +// PyObject_HEAD_INIT_TYPE and PyObject_FINISH_INIT_TYPE need to come in pairs +// We first initialize with NULL because the type is not allocated until +// init_types() is called later. It's in FINISH_INIT_TYPE where we fill the +// type in with the newly allocated type. +# define PyObject_HEAD_INIT_TYPE(type) PyObject_HEAD_INIT(NULL) +# define PyObject_FINISH_INIT_TYPE(obj, type) obj.ob_base.ob_type = type##Ptr + +# define Py_TYPE_GET_TP_ALLOC(type) ((allocfunc)PyType_GetSlot(type, Py_tp_alloc)) +# define Py_TYPE_GET_TP_METHODS(type) ((PyMethodDef *)PyType_GetSlot(type, Py_tp_methods)) + +// PyObject_NEW is not part of stable ABI, but PyObject_Malloc/Init are. +PyObject* Vim_PyObject_New(PyTypeObject *type, size_t objsize) +{ + PyObject *obj = (PyObject *)PyObject_Malloc(objsize); + if (obj == NULL) + return PyErr_NoMemory(); + return PyObject_Init(obj, type); +} +# undef PyObject_NEW +# define PyObject_NEW(type, typeobj) ((type *)Vim_PyObject_New(typeobj, sizeof(type))) + +// This is a somewhat convoluted because limited API doesn't expose an easy way +// to get the tp_name field, and so we have to manually reconstruct it as +// "__module__.__name__" (with __module__ omitted for builtins to emulate +// Python behavior). Also, some of the more convenient functions like +// PyUnicode_AsUTF8AndSize and PyType_GetQualName() are not available until +// late Python 3 versions, and won't be available if you set Py_LIMITED_API too +// low. +# define PyErr_FORMAT_TYPE(msg, obj) \ + do { \ + PyObject* qualname = PyObject_GetAttrString((PyObject*)(obj)->ob_type, "__qualname__"); \ + if (qualname == NULL) \ + { \ + PyErr_FORMAT(PyExc_TypeError, msg, "(NULL)"); \ + break; \ + } \ + PyObject* module = PyObject_GetAttrString((PyObject*)(obj)->ob_type, "__module__"); \ + PyObject* full; \ + if (module == NULL || PyUnicode_CompareWithASCIIString(module, "builtins") == 0 \ + || PyUnicode_CompareWithASCIIString(module, "__main__") == 0) \ + { \ + full = qualname; \ + Py_INCREF(full); \ + } \ + else \ + full = PyUnicode_FromFormat("%U.%U", module, qualname); \ + PyObject* full_bytes = PyUnicode_AsUTF8String(full); \ + const char* full_str = PyBytes_AsString(full_bytes); \ + full_str = full_str == NULL ? "(NULL)" : full_str; \ + PyErr_FORMAT(PyExc_TypeError, msg, full_str); \ + Py_DECREF(qualname); \ + Py_XDECREF(module); \ + Py_XDECREF(full); \ + Py_XDECREF(full_bytes); \ + } while(0) + +# define PyList_GET_ITEM(list, i) PyList_GetItem(list, i) +# define PyList_GET_SIZE(o) PyList_Size(o) +# define PyTuple_GET_ITEM(o, pos) PyTuple_GetItem(o, pos) +# define PyTuple_GET_SIZE(o) PyTuple_Size(o) + +// PyList_SET_ITEM and PyList_SetItem have slightly different behaviors. The +// former will leave the old item dangling, and the latter will decref on it. +// Since we only use this on new lists, this difference doesn't matter. +# define PyList_SET_ITEM(list, i, item) PyList_SetItem(list, i, item) + +# if Py_LIMITED_API < 0x03080000 +// PyIter_check only became part of stable ABI in 3.8, and there is no easy way +// to check for it in the API. We simply return false as a compromise. This +// does mean we should avoid compiling with stable ABI < 3.8. +# undef PyIter_Check +# define PyIter_Check(obj) (FALSE) +# endif + +PyTypeObject* AddHeapType(struct typeobject_wrapper* type_object) +{ + PyType_Spec type_spec; + type_spec.name = type_object->tp_name; + type_spec.basicsize = type_object->tp_basicsize; + type_spec.itemsize = 0; + type_spec.flags = type_object->tp_flags; + + // We just need to statically allocate a large enough buffer that can hold + // all slots. We need to leave a null-terminated slot at the end. + PyType_Slot slots[40] = { {0, NULL} }; + size_t slot_i = 0; + +# define ADD_TP_SLOT(slot_name) \ + if (slot_i >= 40) return NULL; /* this should never happen */ \ + if (type_object->slot_name != NULL) \ + { \ + slots[slot_i].slot = Py_##slot_name; \ + slots[slot_i].pfunc = (void*)type_object->slot_name; \ + ++slot_i; \ + } +# define ADD_TP_SUB_SLOT(sub_slot, slot_name) \ + if (slot_i >= 40) return NULL; /* this should never happen */ \ + if (type_object->sub_slot != NULL && type_object->sub_slot->slot_name != NULL) \ + { \ + slots[slot_i].slot = Py_##slot_name; \ + slots[slot_i].pfunc = (void*)type_object->sub_slot->slot_name; \ + ++slot_i; \ + } + + ADD_TP_SLOT(tp_dealloc) + ADD_TP_SLOT(tp_repr) + ADD_TP_SLOT(tp_call) + ADD_TP_SLOT(tp_getattro) + ADD_TP_SLOT(tp_setattro) + ADD_TP_SLOT(tp_doc) + ADD_TP_SLOT(tp_traverse) + ADD_TP_SLOT(tp_clear) + ADD_TP_SLOT(tp_iter) + ADD_TP_SLOT(tp_iternext) + ADD_TP_SLOT(tp_methods) + ADD_TP_SLOT(tp_base) + ADD_TP_SLOT(tp_alloc) + ADD_TP_SLOT(tp_new) + ADD_TP_SLOT(tp_free) + + ADD_TP_SUB_SLOT(tp_as_sequence, sq_length) + ADD_TP_SUB_SLOT(tp_as_sequence, sq_concat) + ADD_TP_SUB_SLOT(tp_as_sequence, sq_repeat) + ADD_TP_SUB_SLOT(tp_as_sequence, sq_item) + ADD_TP_SUB_SLOT(tp_as_sequence, sq_ass_item) + ADD_TP_SUB_SLOT(tp_as_sequence, sq_contains) + ADD_TP_SUB_SLOT(tp_as_sequence, sq_inplace_concat) + ADD_TP_SUB_SLOT(tp_as_sequence, sq_inplace_repeat) + + ADD_TP_SUB_SLOT(tp_as_mapping, mp_length) + ADD_TP_SUB_SLOT(tp_as_mapping, mp_subscript) + ADD_TP_SUB_SLOT(tp_as_mapping, mp_ass_subscript) +# undef ADD_TP_SLOT +# undef ADD_TP_SUB_SLOT + + type_spec.slots = slots; + + PyObject* newtype = PyType_FromSpec(&type_spec); + return (PyTypeObject*)newtype; +} + +// Add a heap type, since static types do not work in limited API +// Each PYTYPE_READY is paired with PYTYPE_CLEANUP. +// +// Note that we don't call Py_DECREF(type##Ptr) in clean up. The reason for +// that in 3.7, it's possible to de-allocate a heap type before all instances +// are cleared, leading to a crash, whereas in 3.8 the semantics were changed +// and instances hold strong references to types. Since these types are +// designed to be static, just keep them around to avoid having to write +// version-specific handling. Vim does not re-start the Python runtime so there +// will be no long-term leak. +# define PYTYPE_READY(type) \ + type##Ptr = AddHeapType(&(type)); \ + if (type##Ptr == NULL) \ + return -1; +# define PYTYPE_CLEANUP(type) \ + type##Ptr = NULL; + +// Limited API does not provide PyRun_* functions. Need to implement manually +// using PyCompile and PyEval. +PyObject* Vim_PyRun_String(const char *str, int start, PyObject *globals, PyObject *locals) +{ + // Just pass "" for filename for now. + PyObject* compiled = Py_CompileString(str, "", start); + if (compiled == NULL) + return NULL; + + PyObject* eval_result = PyEval_EvalCode(compiled, globals, locals); + Py_DECREF(compiled); + return eval_result; +} +int Vim_PyRun_SimpleString(const char *str) +{ + // This function emulates CPython's implementation. + PyObject* m = PyImport_AddModule("__main__"); + if (m == NULL) + return -1; + PyObject* d = PyModule_GetDict(m); + PyObject* output = Vim_PyRun_String(str, Py_file_input, d, d); + if (output == NULL) + { + PyErr_PrintEx(TRUE); + return -1; + } + Py_DECREF(output); + return 0; +} +#define PyRun_String Vim_PyRun_String +#define PyRun_SimpleString Vim_PyRun_SimpleString + +#else // !defined(USE_LIMITED_API) + +// Full Python API. Can make use of structs and macros directly. +# define DEFINE_PY_TYPE_OBJECT(type) \ + static PyTypeObject type; \ + static PyTypeObject* type##Ptr = &type +# define PyObject_HEAD_INIT_TYPE(type) PyObject_HEAD_INIT(&type) + +# define Py_TYPE_GET_TP_ALLOC(type) type->tp_alloc +# define Py_TYPE_GET_TP_METHODS(type) type->tp_methods + +# define Py_TYPE_NAME(obj) ((obj)->ob_type->tp_name == NULL \ ? "(NULL)" \ : (obj)->ob_type->tp_name) +# define PyErr_FORMAT_TYPE(msg, obj) \ + PyErr_FORMAT(PyExc_TypeError, msg, \ + Py_TYPE_NAME(obj)) + +// Add a static type +# define PYTYPE_READY(type) \ + if (PyType_Ready(type##Ptr)) \ + return -1; + +#endif + #define RAISE_NO_EMPTY_KEYS PyErr_SET_STRING(PyExc_ValueError, \ N_("empty keys are not allowed")) @@ -45,8 +321,7 @@ static const char *vim_special_path = "_vim_path_"; #define RAISE_KEY_ADD_FAIL(key) \ PyErr_VIM_FORMAT(N_("failed to add key '%s' to dictionary"), key) #define RAISE_INVALID_INDEX_TYPE(idx) \ - PyErr_FORMAT(PyExc_TypeError, N_("index must be int or slice, not %s"), \ - Py_TYPE_NAME(idx)); + PyErr_FORMAT_TYPE(N_("index must be int or slice, not %s"), idx); #define INVALID_BUFFER_VALUE ((buf_T *)(-1)) #define INVALID_WINDOW_VALUE ((win_T *)(-1)) @@ -144,13 +419,11 @@ StringToChars(PyObject *obj, PyObject **todecref) else { #if PY_MAJOR_VERSION < 3 - PyErr_FORMAT(PyExc_TypeError, - N_("expected str() or unicode() instance, but got %s"), - Py_TYPE_NAME(obj)); + PyErr_FORMAT_TYPE(N_("expected str() or unicode() instance, but got %s"), + obj); #else - PyErr_FORMAT(PyExc_TypeError, - N_("expected bytes() or str() instance, but got %s"), - Py_TYPE_NAME(obj)); + PyErr_FORMAT_TYPE(N_("expected bytes() or str() instance, but got %s"), + obj); #endif return NULL; } @@ -198,15 +471,15 @@ NumberToLong(PyObject *obj, long *result, int flags) else { #if PY_MAJOR_VERSION < 3 - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected int(), long() or something supporting " "coercing to long(), but got %s"), - Py_TYPE_NAME(obj)); + obj); #else - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected int() or something supporting coercing to int(), " "but got %s"), - Py_TYPE_NAME(obj)); + obj); #endif return -1; } @@ -278,7 +551,7 @@ ObjectDir(PyObject *self, char **attributes) return NULL; if (self) - for (method = self->ob_type->tp_methods ; method->ml_name != NULL ; ++method) + for (method = Py_TYPE_GET_TP_METHODS(self->ob_type) ; method->ml_name != NULL ; ++method) if (add_string(ret, (char *)method->ml_name)) { Py_DECREF(ret); @@ -308,7 +581,7 @@ ObjectDir(PyObject *self, char **attributes) // Function to write a line, points to either msg() or emsg(). typedef int (*writefn)(char *); -static PyTypeObject OutputType; +DEFINE_PY_TYPE_OBJECT(OutputType); typedef struct { @@ -514,14 +787,14 @@ static struct PyMethodDef OutputMethods[] = { static OutputObject Output = { - PyObject_HEAD_INIT(&OutputType) + PyObject_HEAD_INIT_TYPE(OutputType) 0, 0 }; static OutputObject Error = { - PyObject_HEAD_INIT(&OutputType) + PyObject_HEAD_INIT_TYPE(OutputType) 0, 1 }; @@ -552,7 +825,7 @@ typedef struct char *fullname; PyObject *result; } LoaderObject; -static PyTypeObject LoaderType; +DEFINE_PY_TYPE_OBJECT(LoaderType); static void LoaderDestructor(LoaderObject *self) @@ -1243,9 +1516,9 @@ call_load_module(char *name, int len, PyObject *find_module_result) if (!PyTuple_Check(find_module_result)) { - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected 3-tuple as imp.find_module() result, but got %s"), - Py_TYPE_NAME(find_module_result)); + find_module_result); return NULL; } if (PyTuple_GET_SIZE(find_module_result) != 3) @@ -1367,7 +1640,7 @@ FinderFindModule(PyObject *self, PyObject *args) return NULL; } - if (!(loader = PyObject_NEW(LoaderObject, &LoaderType))) + if (!(loader = PyObject_NEW(LoaderObject, LoaderTypePtr))) { vim_free(fullname); Py_DECREF(result); @@ -1424,7 +1697,7 @@ static struct PyMethodDef VimMethods[] = { * Generic iterator object */ -static PyTypeObject IterType; +DEFINE_PY_TYPE_OBJECT(IterType); typedef PyObject *(*nextfun)(void **); typedef void (*destructorfun)(void *); @@ -1451,7 +1724,7 @@ IterNew(void *start, destructorfun destruct, nextfun next, traversefun traverse, { IterObject *self; - self = PyObject_GC_New(IterObject, &IterType); + self = PyObject_GC_New(IterObject, IterTypePtr); self->cur = start; self->next = next; self->destruct = destruct; @@ -1556,7 +1829,7 @@ pyll_add(PyObject *self, pylinkedlist_T *ref, pylinkedlist_T **last) *last = ref; } -static PyTypeObject DictionaryType; +DEFINE_PY_TYPE_OBJECT(DictionaryType); typedef struct { @@ -1567,14 +1840,14 @@ typedef struct static PyObject *DictionaryUpdate(DictionaryObject *, PyObject *, PyObject *); -#define NEW_DICTIONARY(dict) DictionaryNew(&DictionaryType, dict) +#define NEW_DICTIONARY(dict) DictionaryNew(DictionaryTypePtr, dict) static PyObject * DictionaryNew(PyTypeObject *subtype, dict_T *dict) { DictionaryObject *self; - self = (DictionaryObject *) subtype->tp_alloc(subtype, 0); + self = (DictionaryObject *) Py_TYPE_GET_TP_ALLOC(subtype)(subtype, 0); if (self == NULL) return NULL; self->dict = dict; @@ -2238,7 +2511,7 @@ static struct PyMethodDef DictionaryMethods[] = { { NULL, NULL, 0, NULL} }; -static PyTypeObject ListType; +DEFINE_PY_TYPE_OBJECT(ListType); typedef struct { @@ -2247,7 +2520,7 @@ typedef struct pylinkedlist_T ref; } ListObject; -#define NEW_LIST(list) ListNew(&ListType, list) +#define NEW_LIST(list) ListNew(ListTypePtr, list) static PyObject * ListNew(PyTypeObject *subtype, list_T *list) @@ -2257,7 +2530,7 @@ ListNew(PyTypeObject *subtype, list_T *list) if (list == NULL) return NULL; - self = (ListObject *) subtype->tp_alloc(subtype, 0); + self = (ListObject *) Py_TYPE_GET_TP_ALLOC(subtype)(subtype, 0); if (self == NULL) return NULL; self->list = list; @@ -2937,10 +3210,10 @@ typedef struct int auto_rebind; } FunctionObject; -static PyTypeObject FunctionType; +DEFINE_PY_TYPE_OBJECT(FunctionType); #define NEW_FUNCTION(name, argc, argv, self, pt_auto) \ - FunctionNew(&FunctionType, (name), (argc), (argv), (self), (pt_auto)) + FunctionNew(FunctionTypePtr, (name), (argc), (argv), (self), (pt_auto)) static PyObject * FunctionNew(PyTypeObject *subtype, char_u *name, int argc, typval_T *argv, @@ -2948,7 +3221,7 @@ FunctionNew(PyTypeObject *subtype, char_u *name, int argc, typval_T *argv, { FunctionObject *self; - self = (FunctionObject *)subtype->tp_alloc(subtype, 0); + self = (FunctionObject *) Py_TYPE_GET_TP_ALLOC(subtype)(subtype, 0); if (self == NULL) return NULL; @@ -3311,7 +3584,7 @@ static struct PyMethodDef FunctionMethods[] = { * Options object */ -static PyTypeObject OptionsType; +DEFINE_PY_TYPE_OBJECT(OptionsType); typedef int (*checkfun)(void *); @@ -3335,7 +3608,7 @@ OptionsNew(int opt_type, void *from, checkfun Check, PyObject *fromObj) { OptionsObject *self; - self = PyObject_GC_New(OptionsObject, &OptionsType); + self = PyObject_GC_New(OptionsObject, OptionsTypePtr); if (self == NULL) return NULL; @@ -3692,7 +3965,7 @@ typedef struct static PyObject *WinListNew(TabPageObject *tabObject); -static PyTypeObject TabPageType; +DEFINE_PY_TYPE_OBJECT(TabPageType); static int CheckTabPage(TabPageObject *self) @@ -3718,7 +3991,7 @@ TabPageNew(tabpage_T *tab) } else { - self = PyObject_NEW(TabPageObject, &TabPageType); + self = PyObject_NEW(TabPageObject, TabPageTypePtr); if (self == NULL) return NULL; self->tab = tab; @@ -3810,7 +4083,7 @@ static struct PyMethodDef TabPageMethods[] = { * Window list object */ -static PyTypeObject TabListType; +DEFINE_PY_TYPE_OBJECT(TabListType); static PySequenceMethods TabListAsSeq; typedef struct @@ -3818,6 +4091,11 @@ typedef struct PyObject_HEAD } TabListObject; +static TabListObject TheTabPageList = +{ + PyObject_HEAD_INIT_TYPE(TabListType) +}; + static PyInt TabListLength(PyObject *self UNUSED) { @@ -3857,7 +4135,7 @@ typedef struct TabPageObject *tabObject; } WindowObject; -static PyTypeObject WindowType; +DEFINE_PY_TYPE_OBJECT(WindowType); static int CheckWindow(WindowObject *self) @@ -3899,7 +4177,7 @@ WindowNew(win_T *win, tabpage_T *tab) } else { - self = PyObject_GC_New(WindowObject, &WindowType); + self = PyObject_GC_New(WindowObject, WindowTypePtr); if (self == NULL) return NULL; self->win = win; @@ -4150,7 +4428,7 @@ static struct PyMethodDef WindowMethods[] = { * Window list object */ -static PyTypeObject WinListType; +DEFINE_PY_TYPE_OBJECT(WinListType); static PySequenceMethods WinListAsSeq; typedef struct @@ -4159,12 +4437,18 @@ typedef struct TabPageObject *tabObject; } WinListObject; +static WinListObject TheWindowList = +{ + PyObject_HEAD_INIT_TYPE(WinListType) + NULL +}; + static PyObject * WinListNew(TabPageObject *tabObject) { WinListObject *self; - self = PyObject_NEW(WinListObject, &WinListType); + self = PyObject_NEW(WinListObject, WinListTypePtr); self->tabObject = tabObject; Py_INCREF(tabObject); @@ -4259,13 +4543,13 @@ StringToLine(PyObject *obj) else { #if PY_MAJOR_VERSION < 3 - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected str() or unicode() instance, but got %s"), - Py_TYPE_NAME(obj)); + obj); #else - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected bytes() or str() instance, but got %s"), - Py_TYPE_NAME(obj)); + obj); #endif return NULL; } @@ -5028,7 +5312,7 @@ RBAppend( // Range object -static PyTypeObject RangeType; +DEFINE_PY_TYPE_OBJECT(RangeType); static PySequenceMethods RangeAsSeq; static PyMappingMethods RangeAsMapping; @@ -5045,7 +5329,7 @@ RangeNew(buf_T *buf, PyInt start, PyInt end) { BufferObject *bufr; RangeObject *self; - self = PyObject_GC_New(RangeObject, &RangeType); + self = PyObject_GC_New(RangeObject, RangeTypePtr); if (self == NULL) return NULL; @@ -5150,7 +5434,7 @@ static struct PyMethodDef RangeMethods[] = { { NULL, NULL, 0, NULL} }; -static PyTypeObject BufferType; +DEFINE_PY_TYPE_OBJECT(BufferType); static PySequenceMethods BufferAsSeq; static PyMappingMethods BufferAsMapping; @@ -5184,7 +5468,7 @@ BufferNew(buf_T *buf) } else { - self = PyObject_NEW(BufferObject, &BufferType); + self = PyObject_NEW(BufferObject, BufferTypePtr); if (self == NULL) return NULL; self->buf = buf; @@ -5410,13 +5694,18 @@ static struct PyMethodDef BufferMethods[] = { * Buffer list object - Implementation */ -static PyTypeObject BufMapType; +DEFINE_PY_TYPE_OBJECT(BufMapType); typedef struct { PyObject_HEAD } BufMapObject; +static BufMapObject TheBufferMap = +{ + PyObject_HEAD_INIT_TYPE(BufMapType) +}; + static PyInt BufMapLength(PyObject *self UNUSED) { @@ -5574,11 +5863,11 @@ CurrentSetattr(PyObject *self UNUSED, char *name, PyObject *valObject) { int count; - if (valObject->ob_type != &BufferType) + if (valObject->ob_type != BufferTypePtr) { - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected vim.Buffer object, but got %s"), - Py_TYPE_NAME(valObject)); + valObject); return -1; } @@ -5601,11 +5890,11 @@ CurrentSetattr(PyObject *self UNUSED, char *name, PyObject *valObject) { int count; - if (valObject->ob_type != &WindowType) + if (valObject->ob_type != WindowTypePtr) { - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected vim.Window object, but got %s"), - Py_TYPE_NAME(valObject)); + valObject); return -1; } @@ -5635,11 +5924,11 @@ CurrentSetattr(PyObject *self UNUSED, char *name, PyObject *valObject) } else if (strcmp(name, "tabpage") == 0) { - if (valObject->ob_type != &TabPageType) + if (valObject->ob_type != TabPageTypePtr) { - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("expected vim.TabPage object, but got %s"), - Py_TYPE_NAME(valObject)); + valObject); return -1; } @@ -6180,7 +6469,7 @@ ConvertFromPyMapping(PyObject *obj, typval_T *tv) if (!(lookup_dict = PyDict_New())) return -1; - if (PyType_IsSubtype(obj->ob_type, &DictionaryType)) + if (PyType_IsSubtype(obj->ob_type, DictionaryTypePtr)) { tv->v_type = VAR_DICT; tv->vval.v_dict = (((DictionaryObject *)(obj))->dict); @@ -6193,9 +6482,9 @@ ConvertFromPyMapping(PyObject *obj, typval_T *tv) ret = convert_dl(obj, tv, pymap_to_tv, lookup_dict); else { - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("unable to convert %s to a Vim dictionary"), - Py_TYPE_NAME(obj)); + obj); ret = -1; } Py_DECREF(lookup_dict); @@ -6211,7 +6500,7 @@ ConvertFromPySequence(PyObject *obj, typval_T *tv) if (!(lookup_dict = PyDict_New())) return -1; - if (PyType_IsSubtype(obj->ob_type, &ListType)) + if (PyType_IsSubtype(obj->ob_type, ListTypePtr)) { tv->v_type = VAR_LIST; tv->vval.v_list = (((ListObject *)(obj))->list); @@ -6222,9 +6511,9 @@ ConvertFromPySequence(PyObject *obj, typval_T *tv) ret = convert_dl(obj, tv, pyseq_to_tv, lookup_dict); else { - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("unable to convert %s to a Vim list"), - Py_TYPE_NAME(obj)); + obj); ret = -1; } Py_DECREF(lookup_dict); @@ -6247,19 +6536,19 @@ ConvertFromPyObject(PyObject *obj, typval_T *tv) static int _ConvertFromPyObject(PyObject *obj, typval_T *tv, PyObject *lookup_dict) { - if (PyType_IsSubtype(obj->ob_type, &DictionaryType)) + if (PyType_IsSubtype(obj->ob_type, DictionaryTypePtr)) { tv->v_type = VAR_DICT; tv->vval.v_dict = (((DictionaryObject *)(obj))->dict); ++tv->vval.v_dict->dv_refcount; } - else if (PyType_IsSubtype(obj->ob_type, &ListType)) + else if (PyType_IsSubtype(obj->ob_type, ListTypePtr)) { tv->v_type = VAR_LIST; tv->vval.v_list = (((ListObject *)(obj))->list); ++tv->vval.v_list->lv_refcount; } - else if (PyType_IsSubtype(obj->ob_type, &FunctionType)) + else if (PyType_IsSubtype(obj->ob_type, FunctionTypePtr)) { FunctionObject *func = (FunctionObject *) obj; if (func->self != NULL || func->argv != NULL) @@ -6365,9 +6654,9 @@ _ConvertFromPyObject(PyObject *obj, typval_T *tv, PyObject *lookup_dict) } else { - PyErr_FORMAT(PyExc_TypeError, + PyErr_FORMAT_TYPE( N_("unable to convert %s to a Vim structure"), - Py_TYPE_NAME(obj)); + obj); return -1; } return 0; @@ -6445,11 +6734,17 @@ ConvertToPyObject(typval_T *tv) return NULL; } +DEFINE_PY_TYPE_OBJECT(CurrentType); + typedef struct { PyObject_HEAD } CurrentObject; -static PyTypeObject CurrentType; + +static CurrentObject TheCurrent = +{ + PyObject_HEAD_INIT_TYPE(CurrentType) +}; static void init_structs(void) @@ -6466,7 +6761,11 @@ init_structs(void) OutputType.tp_alloc = call_PyType_GenericAlloc; OutputType.tp_new = call_PyType_GenericNew; OutputType.tp_free = call_PyObject_Free; +# ifndef USE_LIMITED_API + // The std printer type is only exposed in full API. It is not essential + // anyway and so in limited API we don't set it. OutputType.tp_base = &PyStdPrinter_Type; +# endif #else OutputType.tp_getattr = (getattrfunc)OutputGetattr; OutputType.tp_setattr = (setattrfunc)OutputSetattr; @@ -6487,7 +6786,7 @@ init_structs(void) CLEAR_FIELD(BufferType); BufferType.tp_name = "vim.buffer"; - BufferType.tp_basicsize = sizeof(BufferType); + BufferType.tp_basicsize = sizeof(BufferObject); BufferType.tp_dealloc = (destructor)BufferDestructor; BufferType.tp_repr = (reprfunc)BufferRepr; BufferType.tp_as_sequence = &BufferAsSeq; @@ -6550,11 +6849,11 @@ init_structs(void) BufMapType.tp_as_mapping = &BufMapAsMapping; BufMapType.tp_flags = Py_TPFLAGS_DEFAULT; BufMapType.tp_iter = BufMapIter; - BufferType.tp_doc = "vim buffer list"; + BufMapType.tp_doc = "vim buffer list"; CLEAR_FIELD(WinListType); WinListType.tp_name = "vim.windowlist"; - WinListType.tp_basicsize = sizeof(WinListType); + WinListType.tp_basicsize = sizeof(WinListObject); WinListType.tp_as_sequence = &WinListAsSeq; WinListType.tp_flags = Py_TPFLAGS_DEFAULT; WinListType.tp_doc = "vim window list"; @@ -6562,7 +6861,7 @@ init_structs(void) CLEAR_FIELD(TabListType); TabListType.tp_name = "vim.tabpagelist"; - TabListType.tp_basicsize = sizeof(TabListType); + TabListType.tp_basicsize = sizeof(TabListObject); TabListType.tp_as_sequence = &TabListAsSeq; TabListType.tp_flags = Py_TPFLAGS_DEFAULT; TabListType.tp_doc = "vim tab page list"; @@ -6690,10 +6989,6 @@ init_structs(void) #endif } -#define PYTYPE_READY(type) \ - if (PyType_Ready(&(type))) \ - return -1; - static int init_types(void) { @@ -6714,9 +7009,46 @@ init_types(void) #if PY_VERSION_HEX < 0x030700f0 PYTYPE_READY(LoaderType); #endif + +#ifdef USE_LIMITED_API + // We need to finish initializing all the static objects because the types + // are only just allocated on the heap now. + // Each PyObject_HEAD_INIT_TYPE should correspond to a + // PyObject_FINISH_INIT_TYPE below. + PyObject_FINISH_INIT_TYPE(Output, OutputType); + PyObject_FINISH_INIT_TYPE(Error, OutputType); + PyObject_FINISH_INIT_TYPE(TheBufferMap, BufMapType); + PyObject_FINISH_INIT_TYPE(TheWindowList, WinListType); + PyObject_FINISH_INIT_TYPE(TheCurrent, CurrentType); + PyObject_FINISH_INIT_TYPE(TheTabPageList, TabListType); +#endif return 0; } +#ifdef USE_LIMITED_API + static void +shutdown_types(void) +{ + PYTYPE_CLEANUP(IterType); + PYTYPE_CLEANUP(BufferType); + PYTYPE_CLEANUP(RangeType); + PYTYPE_CLEANUP(WindowType); + PYTYPE_CLEANUP(TabPageType); + PYTYPE_CLEANUP(BufMapType); + PYTYPE_CLEANUP(WinListType); + PYTYPE_CLEANUP(TabListType); + PYTYPE_CLEANUP(CurrentType); + PYTYPE_CLEANUP(DictionaryType); + PYTYPE_CLEANUP(ListType); + PYTYPE_CLEANUP(FunctionType); + PYTYPE_CLEANUP(OptionsType); + PYTYPE_CLEANUP(OutputType); +# if PY_VERSION_HEX < 0x030700f0 + PYTYPE_CLEANUP(LoaderType); +# endif +} +#endif + static int init_sys_path(void) { @@ -6789,27 +7121,6 @@ init_sys_path(void) return 0; } -static BufMapObject TheBufferMap = -{ - PyObject_HEAD_INIT(&BufMapType) -}; - -static WinListObject TheWindowList = -{ - PyObject_HEAD_INIT(&WinListType) - NULL -}; - -static CurrentObject TheCurrent = -{ - PyObject_HEAD_INIT(&CurrentType) -}; - -static TabListObject TheTabPageList = -{ - PyObject_HEAD_INIT(&TabListType) -}; - static struct numeric_constant { char *name; int val; @@ -6820,26 +7131,9 @@ static struct numeric_constant { {"VAR_DEF_SCOPE", VAR_DEF_SCOPE}, }; -static struct object_constant { +struct object_constant { char *name; PyObject *valObject; -} object_constants[] = { - {"buffers", (PyObject *)(void *)&TheBufferMap}, - {"windows", (PyObject *)(void *)&TheWindowList}, - {"tabpages", (PyObject *)(void *)&TheTabPageList}, - {"current", (PyObject *)(void *)&TheCurrent}, - - {"Buffer", (PyObject *)&BufferType}, - {"Range", (PyObject *)&RangeType}, - {"Window", (PyObject *)&WindowType}, - {"TabPage", (PyObject *)&TabPageType}, - {"Dictionary", (PyObject *)&DictionaryType}, - {"List", (PyObject *)&ListType}, - {"Function", (PyObject *)&FunctionType}, - {"Options", (PyObject *)&OptionsType}, -#if PY_VERSION_HEX < 0x030700f0 - {"_Loader", (PyObject *)&LoaderType}, -#endif }; #define ADD_OBJECT(m, name, obj) \ @@ -6872,6 +7166,25 @@ populate_module(PyObject *m) ADD_CHECKED_OBJECT(m, numeric_constants[i].name, PyInt_FromLong(numeric_constants[i].val)); + struct object_constant object_constants[] = { + {"buffers", (PyObject *)(void *)&TheBufferMap}, + {"windows", (PyObject *)(void *)&TheWindowList}, + {"tabpages", (PyObject *)(void *)&TheTabPageList}, + {"current", (PyObject *)(void *)&TheCurrent}, + + {"Buffer", (PyObject *)BufferTypePtr}, + {"Range", (PyObject *)RangeTypePtr}, + {"Window", (PyObject *)WindowTypePtr}, + {"TabPage", (PyObject *)TabPageTypePtr}, + {"Dictionary", (PyObject *)DictionaryTypePtr}, + {"List", (PyObject *)ListTypePtr}, + {"Function", (PyObject *)FunctionTypePtr}, + {"Options", (PyObject *)OptionsTypePtr}, +#if PY_VERSION_HEX < 0x030700f0 + {"_Loader", (PyObject *)LoaderTypePtr}, +#endif + }; + for (i = 0; i < (int)(sizeof(object_constants) / sizeof(struct object_constant)); ++i) diff --git a/src/if_python3.c b/src/if_python3.c index e4d4a90ed0..c6d1acc198 100644 --- a/src/if_python3.c +++ b/src/if_python3.c @@ -71,6 +71,10 @@ #define PyLong_Type (*py3_PyLong_Type) #define PyBool_Type (*py3_PyBool_Type) +#ifdef Py_LIMITED_API +# define USE_LIMITED_API // Using Python 3 limited ABI +#endif + #include #undef main // Defined in python.h - aargh @@ -203,7 +207,9 @@ static HINSTANCE hinstPy3 = 0; // Instance of python.dll # ifndef PyMapping_Keys # define PyMapping_Keys py3_PyMapping_Keys # endif -# if PY_VERSION_HEX >= 0x030a00b2 +# if (defined(USE_LIMITED_API) && Py_LIMITED_API >= 0x03080000) || \ + (!defined(USE_LIMITED_API) && PY_VERSION_HEX >= 0x03080000) +# undef PyIter_Check # define PyIter_Check py3_PyIter_Check # endif # define PyIter_Next py3_PyIter_Next @@ -212,10 +218,15 @@ static HINSTANCE hinstPy3 = 0; // Instance of python.dll # define PyObject_GetItem py3_PyObject_GetItem # define PyObject_IsTrue py3_PyObject_IsTrue # define PyModule_GetDict py3_PyModule_GetDict -#undef PyRun_SimpleString -# define PyRun_SimpleString py3_PyRun_SimpleString -#undef PyRun_String -# define PyRun_String py3_PyRun_String +# ifndef USE_LIMITED_API +# undef PyRun_SimpleString +# define PyRun_SimpleString py3_PyRun_SimpleString +# undef PyRun_String +# define PyRun_String py3_PyRun_String +# else +# define Py_CompileString py3_Py_CompileString +# define PyEval_EvalCode py3_PyEval_EvalCode +# endif # define PyObject_GetAttrString py3_PyObject_GetAttrString # define PyObject_HasAttrString py3_PyObject_HasAttrString # define PyObject_SetAttrString py3_PyObject_SetAttrString @@ -228,7 +239,7 @@ static HINSTANCE hinstPy3 = 0; // Instance of python.dll # define PySys_GetObject py3_PySys_GetObject # define PySys_SetArgv py3_PySys_SetArgv # define PyType_Ready py3_PyType_Ready -# if PY_VERSION_HEX >= 0x030900b0 +# if PY_VERSION_HEX >= 0x03040000 # define PyType_GetFlags py3_PyType_GetFlags # endif #undef Py_BuildValue @@ -244,14 +255,23 @@ static HINSTANCE hinstPy3 = 0; // Instance of python.dll # define PyModule_AddObject py3_PyModule_AddObject # define PyImport_AppendInittab py3_PyImport_AppendInittab # define PyImport_AddModule py3_PyImport_AddModule -# if PY_VERSION_HEX >= 0x030300f0 -# undef _PyUnicode_AsString -# define _PyUnicode_AsString py3_PyUnicode_AsUTF8 +# ifdef USE_LIMITED_API +# if Py_LIMITED_API >= 0x030a0000 +# define PyUnicode_AsUTF8AndSize py3_PyUnicode_AsUTF8AndSize +# endif # else -# define _PyUnicode_AsString py3__PyUnicode_AsString +# if PY_VERSION_HEX >= 0x030300f0 +# define PyUnicode_AsUTF8AndSize py3_PyUnicode_AsUTF8AndSize +# else +# define _PyUnicode_AsString py3__PyUnicode_AsString +# endif # endif +# undef PyUnicode_CompareWithASCIIString +# define PyUnicode_CompareWithASCIIString py3_PyUnicode_CompareWithASCIIString # undef PyUnicode_AsEncodedString # define PyUnicode_AsEncodedString py3_PyUnicode_AsEncodedString +# undef PyUnicode_AsUTF8String +# define PyUnicode_AsUTF8String py3_PyUnicode_AsUTF8String # undef PyBytes_AsString # define PyBytes_AsString py3_PyBytes_AsString # ifndef PyBytes_AsStringAndSize @@ -261,7 +281,7 @@ static HINSTANCE hinstPy3 = 0; // Instance of python.dll # define PyBytes_FromString py3_PyBytes_FromString # undef PyBytes_FromStringAndSize # define PyBytes_FromStringAndSize py3_PyBytes_FromStringAndSize -# if defined(Py_DEBUG) || PY_VERSION_HEX >= 0x030900b0 +# if defined(Py_DEBUG) || PY_VERSION_HEX >= 0x030900b0 || defined(USE_LIMITED_API) # define _Py_Dealloc py3__Py_Dealloc # endif # define PyFloat_FromDouble py3_PyFloat_FromDouble @@ -310,6 +330,10 @@ static HINSTANCE hinstPy3 = 0; // Instance of python.dll # define PyType_IsSubtype py3_PyType_IsSubtype # define PyCapsule_New py3_PyCapsule_New # define PyCapsule_GetPointer py3_PyCapsule_GetPointer +# ifdef USE_LIMITED_API +# define PyType_GetSlot py3_PyType_GetSlot +# define PyType_FromSpec py3_PyType_FromSpec +# endif # if defined(Py_DEBUG) && !defined(Py_DEBUG_NO_PYMALLOC) # undef PyObject_NEW @@ -358,8 +382,13 @@ static void (*py3_Py_Finalize)(void); static void (*py3_PyErr_SetString)(PyObject *, const char *); static void (*py3_PyErr_SetObject)(PyObject *, PyObject *); static int (*py3_PyErr_ExceptionMatches)(PyObject *); +# ifndef USE_LIMITED_API static int (*py3_PyRun_SimpleString)(char *); static PyObject* (*py3_PyRun_String)(char *, int, PyObject *, PyObject *); +# else +static PyObject* (*py3_Py_CompileString)(const char *, const char *, int); +static PyObject* (*py3_PyEval_EvalCode)(PyObject *co, PyObject *globals, PyObject *locals); +# endif static PyObject* (*py3_PyObject_GetAttrString)(PyObject *, const char *); static int (*py3_PyObject_HasAttrString)(PyObject *, const char *); static int (*py3_PyObject_SetAttrString)(PyObject *, const char *, PyObject *); @@ -379,7 +408,8 @@ static PyObject* (*py3_PyDict_GetItemString)(PyObject *, const char *); static int (*py3_PyDict_Next)(PyObject *, Py_ssize_t *, PyObject **, PyObject **); static PyObject* (*py3_PyLong_FromLong)(long); static PyObject* (*py3_PyDict_New)(void); -# if PY_VERSION_HEX >= 0x030a00b2 +# if (defined(USE_LIMITED_API) && Py_LIMITED_API >= 0x03080000) || \ + (!defined(USE_LIMITED_API) && PY_VERSION_HEX >= 0x03080000) static int (*py3_PyIter_Check)(PyObject *o); # endif static PyObject* (*py3_PyIter_Next)(PyObject *); @@ -388,7 +418,7 @@ static PyObject* (*py3_PyObject_Repr)(PyObject *); static PyObject* (*py3_PyObject_GetItem)(PyObject *, PyObject *); static int (*py3_PyObject_IsTrue)(PyObject *); static PyObject* (*py3_Py_BuildValue)(char *, ...); -# if PY_VERSION_HEX >= 0x030900b0 +# if PY_VERSION_HEX >= 0x03040000 static int (*py3_PyType_GetFlags)(PyTypeObject *o); # endif static int (*py3_PyType_Ready)(PyTypeObject *type); @@ -425,17 +455,25 @@ static PyObject* py3__Py_FalseStruct; static PyObject* py3__Py_TrueStruct; static int (*py3_PyModule_AddObject)(PyObject *m, const char *name, PyObject *o); static int (*py3_PyImport_AppendInittab)(const char *name, PyObject* (*initfunc)(void)); -# if PY_VERSION_HEX >= 0x030300f0 -static char* (*py3_PyUnicode_AsUTF8)(PyObject *unicode); +# ifdef USE_LIMITED_API +# if Py_LIMITED_API >= 0x030a0000 +static char* (*py3_PyUnicode_AsUTF8AndSize)(PyObject *unicode, Py_ssize_t *size); +# endif # else +# if PY_VERSION_HEX >= 0x030300f0 +static char* (*py3_PyUnicode_AsUTF8AndSize)(PyObject *unicode, Py_ssize_t *size); +# else static char* (*py3__PyUnicode_AsString)(PyObject *unicode); +# endif # endif +static int (*py3_PyUnicode_CompareWithASCIIString)(PyObject *unicode, const char* string); static PyObject* (*py3_PyUnicode_AsEncodedString)(PyObject *unicode, const char* encoding, const char* errors); +static PyObject* (*py3_PyUnicode_AsUTF8String)(PyObject *unicode); static char* (*py3_PyBytes_AsString)(PyObject *bytes); static int (*py3_PyBytes_AsStringAndSize)(PyObject *bytes, char **buffer, Py_ssize_t *length); static PyObject* (*py3_PyBytes_FromString)(char *str); static PyObject* (*py3_PyBytes_FromStringAndSize)(char *str, Py_ssize_t length); -# if defined(Py_DEBUG) || PY_VERSION_HEX >= 0x030900b0 +# if defined(Py_DEBUG) || PY_VERSION_HEX >= 0x030900b0 || defined(USE_LIMITED_API) static void (*py3__Py_Dealloc)(PyObject *obj); # endif # if PY_VERSION_HEX >= 0x030900b0 @@ -477,6 +515,10 @@ static PyObject*(*py3__PyObject_GC_New)(PyTypeObject *); static void(*py3_PyObject_GC_Del)(void *); static void(*py3_PyObject_GC_UnTrack)(void *); static int (*py3_PyType_IsSubtype)(PyTypeObject *, PyTypeObject *); +# ifdef USE_LIMITED_API +static void* (*py3_PyType_GetSlot)(PyTypeObject *, int); +static PyObject* (*py3_PyType_FromSpec)(PyType_Spec *); +# endif // Imported exception objects static PyObject *p3imp_PyExc_AttributeError; @@ -542,8 +584,13 @@ static struct {"PyErr_SetString", (PYTHON_PROC*)&py3_PyErr_SetString}, {"PyErr_SetObject", (PYTHON_PROC*)&py3_PyErr_SetObject}, {"PyErr_ExceptionMatches", (PYTHON_PROC*)&py3_PyErr_ExceptionMatches}, +# ifndef USE_LIMITED_API {"PyRun_SimpleString", (PYTHON_PROC*)&py3_PyRun_SimpleString}, {"PyRun_String", (PYTHON_PROC*)&py3_PyRun_String}, +# else + {"Py_CompileString", (PYTHON_PROC*)&py3_Py_CompileString}, + {"PyEval_EvalCode", (PYTHON_PROC*)&PyEval_EvalCode}, +# endif {"PyObject_GetAttrString", (PYTHON_PROC*)&py3_PyObject_GetAttrString}, {"PyObject_HasAttrString", (PYTHON_PROC*)&py3_PyObject_HasAttrString}, {"PyObject_SetAttrString", (PYTHON_PROC*)&py3_PyObject_SetAttrString}, @@ -563,7 +610,8 @@ static struct {"PyDict_Next", (PYTHON_PROC*)&py3_PyDict_Next}, {"PyMapping_Check", (PYTHON_PROC*)&py3_PyMapping_Check}, {"PyMapping_Keys", (PYTHON_PROC*)&py3_PyMapping_Keys}, -# if PY_VERSION_HEX >= 0x030a00b2 +# if (defined(USE_LIMITED_API) && Py_LIMITED_API >= 0x03080000) || \ + (!defined(USE_LIMITED_API) && PY_VERSION_HEX >= 0x03080000) {"PyIter_Check", (PYTHON_PROC*)&py3_PyIter_Check}, # endif {"PyIter_Next", (PYTHON_PROC*)&py3_PyIter_Next}, @@ -573,7 +621,7 @@ static struct {"PyObject_IsTrue", (PYTHON_PROC*)&py3_PyObject_IsTrue}, {"PyLong_FromLong", (PYTHON_PROC*)&py3_PyLong_FromLong}, {"PyDict_New", (PYTHON_PROC*)&py3_PyDict_New}, -# if PY_VERSION_HEX >= 0x030900b0 +# if PY_VERSION_HEX >= 0x03040000 {"PyType_GetFlags", (PYTHON_PROC*)&py3_PyType_GetFlags}, # endif {"PyType_Ready", (PYTHON_PROC*)&py3_PyType_Ready}, @@ -595,11 +643,19 @@ static struct {"PyObject_Init", (PYTHON_PROC*)&py3__PyObject_Init}, {"PyModule_AddObject", (PYTHON_PROC*)&py3_PyModule_AddObject}, {"PyImport_AppendInittab", (PYTHON_PROC*)&py3_PyImport_AppendInittab}, -# if PY_VERSION_HEX >= 0x030300f0 - {"PyUnicode_AsUTF8", (PYTHON_PROC*)&py3_PyUnicode_AsUTF8}, +# ifdef USE_LIMITED_API +# if Py_LIMITED_API >= 0x030a0000 + {"PyUnicode_AsUTF8AndSize", (PYTHON_PROC*)&py3_PyUnicode_AsUTF8AndSize}, +# endif # else +# if PY_VERSION_HEX >= 0x030300f0 + {"PyUnicode_AsUTF8AndSize", (PYTHON_PROC*)&py3_PyUnicode_AsUTF8AndSize}, +# else {"_PyUnicode_AsString", (PYTHON_PROC*)&py3__PyUnicode_AsString}, +# endif # endif + {"PyUnicode_CompareWithASCIIString", (PYTHON_PROC*)&py3_PyUnicode_CompareWithASCIIString}, + {"PyUnicode_AsUTF8String", (PYTHON_PROC*)&py3_PyUnicode_AsUTF8String}, # ifndef Py_UNICODE_USE_UCS_FUNCTIONS {"PyUnicode_FromFormat", (PYTHON_PROC*)&py3_PyUnicode_FromFormat}, # else @@ -613,7 +669,7 @@ static struct {"PyBytes_AsStringAndSize", (PYTHON_PROC*)&py3_PyBytes_AsStringAndSize}, {"PyBytes_FromString", (PYTHON_PROC*)&py3_PyBytes_FromString}, {"PyBytes_FromStringAndSize", (PYTHON_PROC*)&py3_PyBytes_FromStringAndSize}, -# if defined(Py_DEBUG) || PY_VERSION_HEX >= 0x030900b0 +# if defined(Py_DEBUG) || PY_VERSION_HEX >= 0x030900b0 || defined(USE_LIMITED_API) {"_Py_Dealloc", (PYTHON_PROC*)&py3__Py_Dealloc}, # endif # if PY_VERSION_HEX >= 0x030900b0 @@ -654,6 +710,12 @@ static struct {"PyType_IsSubtype", (PYTHON_PROC*)&py3_PyType_IsSubtype}, {"PyCapsule_New", (PYTHON_PROC*)&py3_PyCapsule_New}, {"PyCapsule_GetPointer", (PYTHON_PROC*)&py3_PyCapsule_GetPointer}, +# ifdef USE_LIMITED_API +# if PY_VERSION_HEX >= 0x03040000 + {"PyType_GetSlot", (PYTHON_PROC*)&py3_PyType_GetSlot}, +# endif + {"PyType_FromSpec", (PYTHON_PROC*)&py3_PyType_FromSpec}, +# endif {"", NULL}, }; @@ -922,7 +984,12 @@ static int py3initialised = 0; #define PYINITIALISED py3initialised static int python_end_called = FALSE; -#define DESTRUCTOR_FINISH(self) Py_TYPE(self)->tp_free((PyObject*)self) +#ifdef USE_LIMITED_API +# define DESTRUCTOR_FINISH(self) \ + ((freefunc)PyType_GetSlot(Py_TYPE(self), Py_tp_free))((PyObject*)self) +#else +# define DESTRUCTOR_FINISH(self) Py_TYPE(self)->tp_free((PyObject*)self) +#endif #define WIN_PYTHON_REF(win) win->w_python3_ref #define BUF_PYTHON_REF(buf) buf->b_python3_ref @@ -975,11 +1042,51 @@ static struct PyModuleDef vimmodule; */ #include "if_py_both.h" +#ifndef USE_LIMITED_API +# if PY_VERSION_HEX >= 0x030300f0 +# define PY_UNICODE_GET_UTF8_CHARS(obj) PyUnicode_AsUTF8AndSize(obj, NULL) +# else +# define PY_UNICODE_GET_UTF8_CHARS _PyUnicode_AsString +# endif +#else +# if Py_LIMITED_API >= 0x030A0000 +# define PY_UNICODE_GET_UTF8_CHARS(obj) PyUnicode_AsUTF8AndSize(obj, NULL) +# else +// Python limited API before 3.10 lack easy ways to query the raw UTF-8 chars. +// We need to first convert the string to bytes, and then extract the chars. +// This function is only used for attribute string comparisons, which have +// known short length. As such, just allocate a short static buffer to hold +// the characters instead of having to allocate/deallcoate it. +// +// An alternative would be to convert all attribute string comparisons to use +// PyUnicode_CompareWithASCIIString to skip having to extract the chars. +static char py3_unicode_utf8_chars[20]; +char* PY_UNICODE_GET_UTF8_CHARS(PyObject* str) +{ + py3_unicode_utf8_chars[0] = '\0'; + PyObject* bytes = PyUnicode_AsUTF8String(str); + if (bytes) + { + char *chars; + Py_ssize_t len; + if (PyBytes_AsStringAndSize(bytes, &chars, &len) != -1) + { + if (len < (Py_ssize_t)sizeof(py3_unicode_utf8_chars)) + // PyBytes_AsStringAndSize guarantees null-termination + memcpy(py3_unicode_utf8_chars, chars, len + 1); + } + Py_DECREF(bytes); + } + return py3_unicode_utf8_chars; +} +# endif +#endif + // NOTE: Must always be used at the start of a block, since it declares "name". #define GET_ATTR_STRING(name, nameobj) \ char *name = ""; \ if (PyUnicode_Check(nameobj)) \ - name = (char *)_PyUnicode_AsString(nameobj) + name = (char *)PY_UNICODE_GET_UTF8_CHARS(nameobj) #define PY3OBJ_DELETED(obj) (obj->ob_base.ob_refcnt<=0) @@ -1008,6 +1115,10 @@ python3_end(void) #endif if (Py_IsInitialized()) { +#ifdef USE_LIMITED_API + shutdown_types(); +#endif + // acquire lock before finalizing PyGILState_Ensure(); @@ -1956,4 +2067,14 @@ do_py3eval(char_u *str, typval_T *rettv) set_ref_in_python3(int copyID) { return set_ref_in_py(copyID); +} + + int +python3_version() +{ +#ifdef USE_LIMITED_API + return Py_LIMITED_API; +#else + return PY_VERSION_HEX; +#endif } diff --git a/src/proto/if_python3.pro b/src/proto/if_python3.pro index 4e71ad2a05..d2bcd8c5a0 100644 --- a/src/proto/if_python3.pro +++ b/src/proto/if_python3.pro @@ -10,4 +10,5 @@ void python3_window_free(win_T *win); void python3_tabpage_free(tabpage_T *tab); void do_py3eval(char_u *str, typval_T *rettv); int set_ref_in_python3(int copyID); +int python3_version(); /* vim: set ft=c : */ diff --git a/src/version.c b/src/version.c index 00bcb340f8..9a6f1622a6 100644 --- a/src/version.c +++ b/src/version.c @@ -474,7 +474,11 @@ static char *(features[]) = #endif #ifdef FEAT_PYTHON3 # ifdef DYNAMIC_PYTHON3 +# ifdef DYNAMIC_PYTHON3_STABLE_ABI + "+python3/dyn-stable", +# else "+python3/dyn", +# endif # else "+python3", # endif @@ -695,6 +699,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1776, /**/ 1775, /**/ diff --git a/src/vim.h b/src/vim.h index 6533a4536f..265fd738e2 100644 --- a/src/vim.h +++ b/src/vim.h @@ -2130,7 +2130,8 @@ typedef int sock_T; #define VV_SIZEOFLONG 103 #define VV_SIZEOFPOINTER 104 #define VV_MAXCOL 105 -#define VV_LEN 106 // number of v: vars +#define VV_PYTHON3_VERSION 106 +#define VV_LEN 107 // number of v: vars // used for v_number in VAR_BOOL and VAR_SPECIAL #define VVAL_FALSE 0L // VAR_BOOL