mirror of
				https://github.com/neovim/neovim.git
				synced 2025-11-04 01:34:25 +00:00 
			
		
		
		
	eval: Add id() function and make printf("%p") return something useful (#6095)
This commit is contained in:
		
				
					committed by
					
						
						Justin M. Keyes
					
				
			
			
				
	
			
			
			
						parent
						
							b1cf50c684
						
					
				
				
					commit
					abdbfd26bc
				
			@@ -2036,6 +2036,7 @@ insert({list}, {item} [, {idx}])
 | 
			
		||||
invert({expr})			Number  bitwise invert
 | 
			
		||||
isdirectory({directory})	Number	TRUE if {directory} is a directory
 | 
			
		||||
islocked({expr})		Number	TRUE if {expr} is locked
 | 
			
		||||
id({expr})			String	identifier of the container
 | 
			
		||||
items({dict})			List	key-value pairs in {dict}
 | 
			
		||||
jobclose({job}[, {stream}])	Number	Closes a job stream(s)
 | 
			
		||||
jobpid({job})			Number	Returns pid of a job.
 | 
			
		||||
@@ -4629,6 +4630,22 @@ islocked({expr})					*islocked()* *E786*
 | 
			
		||||
<		When {expr} is a variable that does not exist you get an error
 | 
			
		||||
		message.  Use |exists()| to check for existence.
 | 
			
		||||
 | 
			
		||||
id({expr})						*id()*
 | 
			
		||||
		Returns a |String| which is a unique identifier of the 
 | 
			
		||||
		container type (|List|, |Dict| and |Partial|). It is 
 | 
			
		||||
		guaranteed that for the mentioned types `id(v1) ==# id(v2)` 
 | 
			
		||||
		returns true iff `type(v1) == type(v2) && v1 is v2` (note: 
 | 
			
		||||
		|v:_null_list| and |v:_null_dict| have the same `id()` with 
 | 
			
		||||
		different types because they are internally represented as 
 | 
			
		||||
		a NULL pointers). Currently `id()` returns a hexadecimal 
 | 
			
		||||
		representanion of the pointers to the containers (i.e. like 
 | 
			
		||||
		`0x994a40`), same as `printf("%p", {expr})`, but it is advised 
 | 
			
		||||
		against counting on exact format of return value.
 | 
			
		||||
 | 
			
		||||
		It is not guaranteed that `id(no_longer_existing_container)` 
 | 
			
		||||
		will not be equal to some other `id()`: new containers may 
 | 
			
		||||
		reuse identifiers of the garbage-collected ones.
 | 
			
		||||
 | 
			
		||||
items({dict})						*items()*
 | 
			
		||||
		Return a |List| with all the key-value pairs of {dict}.  Each
 | 
			
		||||
		|List| item is a list with two items: the key of a {dict}
 | 
			
		||||
@@ -5500,6 +5517,7 @@ printf({fmt}, {expr1} ...)				*printf()*
 | 
			
		||||
		  %g	floating point number, as %f or %e depending on value
 | 
			
		||||
		  %G	floating point number, as %f or %E depending on value
 | 
			
		||||
		  %%	the % character itself
 | 
			
		||||
		  %p	representation of the pointer to the container
 | 
			
		||||
 | 
			
		||||
		Conversion specifications start with '%' and end with the
 | 
			
		||||
		conversion type.  All other characters are copied unchanged to
 | 
			
		||||
 
 | 
			
		||||
@@ -232,6 +232,12 @@ Additional differences:
 | 
			
		||||
  itself.
 | 
			
		||||
- ShaDa file keeps search direction (|v:searchforward|), viminfo does not.
 | 
			
		||||
 | 
			
		||||
|printf()| returns something meaningful when used with `%p` argument: in Vim 
 | 
			
		||||
it used to return useless address of the string (strings are copied to the 
 | 
			
		||||
newly allocated memory all over the place) and fail on types which cannot be 
 | 
			
		||||
coerced to strings. See |id()| for more details, currently it uses 
 | 
			
		||||
`printf("%p", {expr})` internally.
 | 
			
		||||
 | 
			
		||||
==============================================================================
 | 
			
		||||
5. Missing legacy features				 *nvim-features-missing*
 | 
			
		||||
				     *if_lua* *if_perl* *if_mzscheme* *if_tcl*
 | 
			
		||||
 
 | 
			
		||||
@@ -498,6 +498,14 @@ static PMap(uint64_t) *jobs = NULL;
 | 
			
		||||
static uint64_t last_timer_id = 0;
 | 
			
		||||
static PMap(uint64_t) *timers = NULL;
 | 
			
		||||
 | 
			
		||||
/// Dummy va_list for passing to vim_snprintf
 | 
			
		||||
///
 | 
			
		||||
/// Used because:
 | 
			
		||||
/// - passing a NULL pointer doesn't work when va_list isn't a pointer
 | 
			
		||||
/// - locally in the function results in a "used before set" warning
 | 
			
		||||
/// - using va_start() to initialize it gives "function with fixed args" error
 | 
			
		||||
static va_list dummy_ap;
 | 
			
		||||
 | 
			
		||||
static const char *const msgpack_type_names[] = {
 | 
			
		||||
  [kMPNil] = "nil",
 | 
			
		||||
  [kMPBoolean] = "boolean",
 | 
			
		||||
@@ -12122,6 +12130,16 @@ static void dict_list(typval_T *argvars, typval_T *rettv, int what)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// "id()" function
 | 
			
		||||
static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL
 | 
			
		||||
{
 | 
			
		||||
  const int len = vim_vsnprintf(NULL, 0, "%p", dummy_ap, argvars);
 | 
			
		||||
  rettv->v_type = VAR_STRING;
 | 
			
		||||
  rettv->vval.v_string = xmalloc(len + 1);
 | 
			
		||||
  vim_vsnprintf((char *)rettv->vval.v_string, len + 1, "%p", dummy_ap, argvars);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * "items(dict)" function
 | 
			
		||||
 */
 | 
			
		||||
@@ -13628,12 +13646,6 @@ static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
 | 
			
		||||
  rettv->vval.v_number = lnum;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* This dummy va_list is here because:
 | 
			
		||||
 * - passing a NULL pointer doesn't work when va_list isn't a pointer
 | 
			
		||||
 * - locally in the function results in a "used before set" warning
 | 
			
		||||
 * - using va_start() to initialize it gives "function with fixed args" error */
 | 
			
		||||
static va_list ap;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * "printf()" function
 | 
			
		||||
 */
 | 
			
		||||
@@ -13650,11 +13662,11 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
 | 
			
		||||
    /* Get the required length, allocate the buffer and do it for real. */
 | 
			
		||||
    did_emsg = FALSE;
 | 
			
		||||
    fmt = (char *)get_tv_string_buf(&argvars[0], buf);
 | 
			
		||||
    len = vim_vsnprintf(NULL, 0, fmt, ap, argvars + 1);
 | 
			
		||||
    len = vim_vsnprintf(NULL, 0, fmt, dummy_ap, argvars + 1);
 | 
			
		||||
    if (!did_emsg) {
 | 
			
		||||
      char *s = xmalloc(len + 1);
 | 
			
		||||
      rettv->vval.v_string = (char_u *)s;
 | 
			
		||||
      (void)vim_vsnprintf(s, len + 1, fmt, ap, argvars + 1);
 | 
			
		||||
      (void)vim_vsnprintf(s, len + 1, fmt, dummy_ap, argvars + 1);
 | 
			
		||||
    }
 | 
			
		||||
    did_emsg |= saved_did_emsg;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -168,6 +168,7 @@ return {
 | 
			
		||||
    invert={args=1},
 | 
			
		||||
    isdirectory={args=1},
 | 
			
		||||
    islocked={args=1},
 | 
			
		||||
    id={args=1},
 | 
			
		||||
    items={args=1},
 | 
			
		||||
    jobclose={args={1, 2}},
 | 
			
		||||
    jobpid={args=1},
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ typedef enum {
 | 
			
		||||
typedef struct {
 | 
			
		||||
  VarType v_type;  ///< Variable type.
 | 
			
		||||
  VarLockStatus v_lock;  ///< Variable lock status.
 | 
			
		||||
  union {
 | 
			
		||||
  union typval_vval_union {
 | 
			
		||||
    varnumber_T v_number;  ///< Number, for VAR_NUMBER.
 | 
			
		||||
    SpecialVarValue v_special;  ///< Special value, for VAR_SPECIAL.
 | 
			
		||||
    float_T v_float;  ///< Floating-point number, for VAR_FLOAT.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@
 | 
			
		||||
 | 
			
		||||
#include "nvim/vim.h"
 | 
			
		||||
#include "nvim/ascii.h"
 | 
			
		||||
#include "nvim/assert.h"
 | 
			
		||||
#include "nvim/message.h"
 | 
			
		||||
#include "nvim/charset.h"
 | 
			
		||||
#include "nvim/eval.h"
 | 
			
		||||
@@ -3043,6 +3044,38 @@ static char *tv_str(typval_T *tvs, int *idxp)
 | 
			
		||||
  return s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get pointer argument from the next entry in tvs
 | 
			
		||||
///
 | 
			
		||||
/// First entry is 1. Returns NULL for an error.
 | 
			
		||||
///
 | 
			
		||||
/// @param[in]  tvs  List of typval_T values.
 | 
			
		||||
/// @param[in,out]  idxp  Pointer to the index of the current value.
 | 
			
		||||
///
 | 
			
		||||
/// @return Pointer stored in typval_T or NULL.
 | 
			
		||||
static const void *tv_ptr(const typval_T *const tvs, int *const idxp)
 | 
			
		||||
  FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
 | 
			
		||||
{
 | 
			
		||||
#define OFF(attr) offsetof(union typval_vval_union, attr)
 | 
			
		||||
  STATIC_ASSERT(
 | 
			
		||||
      OFF(v_string) == OFF(v_list)
 | 
			
		||||
      && OFF(v_string) == OFF(v_dict)
 | 
			
		||||
      && OFF(v_string) == OFF(v_partial)
 | 
			
		||||
      && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list)
 | 
			
		||||
      && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict)
 | 
			
		||||
      && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial),
 | 
			
		||||
      "Strings, dictionaries, lists and partials are expected to be pointers, "
 | 
			
		||||
      "so that all three of them can be accessed via v_string");
 | 
			
		||||
#undef OFF
 | 
			
		||||
  const int idx = *idxp - 1;
 | 
			
		||||
  if (tvs[idx].v_type == VAR_UNKNOWN) {
 | 
			
		||||
    EMSG(_(e_printf));
 | 
			
		||||
    return NULL;
 | 
			
		||||
  } else {
 | 
			
		||||
    (*idxp)++;
 | 
			
		||||
    return tvs[idx].vval.v_string;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Get float argument from "idxp" entry in "tvs".  First entry is 1.
 | 
			
		||||
 */
 | 
			
		||||
@@ -3369,11 +3402,11 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap,
 | 
			
		||||
        size_t size_t_arg = 0;
 | 
			
		||||
 | 
			
		||||
        // only defined for p conversion
 | 
			
		||||
        void *ptr_arg = NULL;
 | 
			
		||||
        const void *ptr_arg = NULL;
 | 
			
		||||
 | 
			
		||||
        if (fmt_spec == 'p') {
 | 
			
		||||
          length_modifier = '\0';
 | 
			
		||||
          ptr_arg = tvs ? (void *)tv_str(tvs, &arg_idx) : va_arg(ap, void *);
 | 
			
		||||
          ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *);
 | 
			
		||||
          if (ptr_arg) {
 | 
			
		||||
            arg_sign = 1;
 | 
			
		||||
          }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
local helpers = require('test.functional.helpers')(after_each)
 | 
			
		||||
 | 
			
		||||
local clear = helpers.clear
 | 
			
		||||
local eq = helpers.eq
 | 
			
		||||
local eval = helpers.eval
 | 
			
		||||
local funcs = helpers.funcs
 | 
			
		||||
local meths = helpers.meths
 | 
			
		||||
local exc_exec = helpers.exc_exec
 | 
			
		||||
 | 
			
		||||
describe('printf()', function()
 | 
			
		||||
@@ -57,4 +60,33 @@ describe('printf()', function()
 | 
			
		||||
  it('errors out when %b modifier is used for a float', function()
 | 
			
		||||
    eq('Vim(call):E805: Using a Float as a Number', exc_exec('call printf("%b", 3.1415926535)'))
 | 
			
		||||
  end)
 | 
			
		||||
  it('works with %p correctly', function()
 | 
			
		||||
    local null_ret = nil
 | 
			
		||||
    local seen_rets = {}
 | 
			
		||||
    -- Collect all args in an array to avoid possible allocation of the same
 | 
			
		||||
    -- address after freeing unreferenced values.
 | 
			
		||||
    meths.set_var('__args', {})
 | 
			
		||||
    local function check_printf(expr, is_null)
 | 
			
		||||
      eq(0, exc_exec('call add(__args, ' .. expr .. ')'))
 | 
			
		||||
      eq(0, exc_exec('let __result = printf("%p", __args[-1])'))
 | 
			
		||||
      local id_ret = eval('id(__args[-1])')
 | 
			
		||||
      eq(id_ret, meths.get_var('__result'))
 | 
			
		||||
      if is_null then
 | 
			
		||||
        if null_ret then
 | 
			
		||||
          eq(null_ret, id_ret)
 | 
			
		||||
        else
 | 
			
		||||
          null_ret = id_ret
 | 
			
		||||
        end
 | 
			
		||||
      else
 | 
			
		||||
        eq(nil, seen_rets[id_ret])
 | 
			
		||||
        seen_rets[id_ret] = expr
 | 
			
		||||
      end
 | 
			
		||||
      meths.del_var('__result')
 | 
			
		||||
    end
 | 
			
		||||
    check_printf('v:_null_list', true)
 | 
			
		||||
    check_printf('v:_null_dict', true)
 | 
			
		||||
    check_printf('[]')
 | 
			
		||||
    check_printf('{}')
 | 
			
		||||
    check_printf('function("tr", ["a"])')
 | 
			
		||||
  end)
 | 
			
		||||
end)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user