patch 9.1.0411: too long functions in eval.c

Commit: 
https://github.com/vim/vim/commit/4ceb4dc825854032eed423ec1fc372317d3420bf
Author: Yegappan Lakshmanan <yegap...@yahoo.com>
Date:   Sun May 12 09:24:35 2024 +0200

    patch 9.1.0411: too long functions in eval.c
    
    Problem:  too long functions in eval.c
    Solution: refactor functions (Yegappan Lakshmanan)
    
    closes: #14755
    
    Signed-off-by: Yegappan Lakshmanan <yegap...@yahoo.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/src/eval.c b/src/eval.c
index b547143fe..93dc09192 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -250,93 +250,146 @@ eval_expr_get_funccal(typval_T *expr, typval_T *rettv)
 }
 
 /*
- * Evaluate an expression, which can be a function, partial or string.
+ * Evaluate a partial.
  * Pass arguments "argv[argc]".
- * If "want_func" is TRUE treat a string as a function name, not an expression.
  * "fc_arg" is from eval_expr_get_funccal() or NULL;
  * Return the result in "rettv" and OK or FAIL.
  */
-    int
-eval_expr_typval(
-       typval_T    *expr,
-       int         want_func,
-       typval_T    *argv,
-       int         argc,
-       funccall_T  *fc_arg,
-       typval_T    *rettv)
+    static int
+eval_expr_partial(
+    typval_T   *expr,
+    typval_T   *argv,
+    int                argc,
+    funccall_T *fc_arg,
+    typval_T   *rettv)
 {
-    char_u     *s;
-    char_u     buf[NUMBUFLEN];
-    funcexe_T  funcexe;
+    partial_T  *partial = expr->vval.v_partial;
 
-    if (expr->v_type == VAR_PARTIAL)
+    if (partial == NULL)
+       return FAIL;
+
+    if (partial->pt_func != NULL
+                       && partial->pt_func->uf_def_status != UF_NOT_COMPILED)
     {
-       partial_T   *partial = expr->vval.v_partial;
+       funccall_T      *fc = fc_arg != NULL ? fc_arg
+                               : create_funccal(partial->pt_func, rettv);
+       int             r;
 
-       if (partial == NULL)
+       if (fc == NULL)
            return FAIL;
 
-       if (partial->pt_func != NULL
-                         && partial->pt_func->uf_def_status != UF_NOT_COMPILED)
-       {
-           funccall_T  *fc = fc_arg != NULL ? fc_arg
-                                    : create_funccal(partial->pt_func, rettv);
-           int         r;
-
-           if (fc == NULL)
-               return FAIL;
-
-           // Shortcut to call a compiled function with minimal overhead.
-           r = call_def_function(partial->pt_func, argc, argv,
-                                   DEF_USE_PT_ARGV, partial, NULL, fc, rettv);
-           if (fc_arg == NULL)
-               remove_funccal();
-           if (r == FAIL)
-               return FAIL;
-       }
-       else
-       {
-           s = partial_name(partial);
-           if (s == NULL || *s == NUL)
-               return FAIL;
-           CLEAR_FIELD(funcexe);
-           funcexe.fe_evaluate = TRUE;
-           funcexe.fe_partial = partial;
-           if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL)
-               return FAIL;
-       }
-    }
-    else if (expr->v_type == VAR_INSTR)
-    {
-       return exe_typval_instr(expr, rettv);
+       // Shortcut to call a compiled function with minimal overhead.
+       r = call_def_function(partial->pt_func, argc, argv, DEF_USE_PT_ARGV,
+                                               partial, NULL, fc, rettv);
+       if (fc_arg == NULL)
+           remove_funccal();
+       if (r == FAIL)
+           return FAIL;
     }
-    else if (expr->v_type == VAR_FUNC || want_func)
+    else
     {
-       s = expr->v_type == VAR_FUNC
-               ? expr->vval.v_string
-               : tv_get_string_buf_chk_strict(expr, buf, in_vim9script());
+       char_u          *s = partial_name(partial);
+       funcexe_T       funcexe;
+
        if (s == NULL || *s == NUL)
            return FAIL;
+
        CLEAR_FIELD(funcexe);
        funcexe.fe_evaluate = TRUE;
+       funcexe.fe_partial = partial;
        if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL)
            return FAIL;
     }
+
+    return OK;
+}
+
+/*
+ * Evaluate an expression which is a function.
+ * Pass arguments "argv[argc]".
+ * Return the result in "rettv" and OK or FAIL.
+ */
+    static int
+eval_expr_func(
+    typval_T   *expr,
+    typval_T   *argv,
+    int                argc,
+    typval_T   *rettv)
+{
+    funcexe_T  funcexe;
+    char_u     buf[NUMBUFLEN];
+    char_u     *s;
+
+    if (expr->v_type == VAR_FUNC)
+       s = expr->vval.v_string;
     else
-    {
        s = tv_get_string_buf_chk_strict(expr, buf, in_vim9script());
-       if (s == NULL)
-           return FAIL;
-       s = skipwhite(s);
-       if (eval1_emsg(&s, rettv, NULL) == FAIL)
-           return FAIL;
-       if (*skipwhite(s) != NUL)  // check for trailing chars after expr
-       {
-           clear_tv(rettv);
-           semsg(_(e_invalid_expression_str), s);
-           return FAIL;
-       }
+    if (s == NULL || *s == NUL)
+       return FAIL;
+
+    CLEAR_FIELD(funcexe);
+    funcexe.fe_evaluate = TRUE;
+    if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL)
+       return FAIL;
+
+    return OK;
+}
+
+/*
+ * Evaluate an expression, which is a string.
+ * Return the result in "rettv" and OK or FAIL.
+ */
+    static int
+eval_expr_string(
+    typval_T   *expr,
+    typval_T   *rettv)
+{
+    char_u     *s;
+    char_u     buf[NUMBUFLEN];
+
+    s = tv_get_string_buf_chk_strict(expr, buf, in_vim9script());
+    if (s == NULL)
+       return FAIL;
+
+    s = skipwhite(s);
+    if (eval1_emsg(&s, rettv, NULL) == FAIL)
+       return FAIL;
+
+    if (*skipwhite(s) != NUL)  // check for trailing chars after expr
+    {
+       clear_tv(rettv);
+       semsg(_(e_invalid_expression_str), s);
+       return FAIL;
     }
+
+    return OK;
+}
+
+/*
+ * Evaluate an expression, which can be a function, partial or string.
+ * Pass arguments "argv[argc]".
+ * If "want_func" is TRUE treat a string as a function name, not an expression.
+ * "fc_arg" is from eval_expr_get_funccal() or NULL;
+ * Return the result in "rettv" and OK or FAIL.
+ */
+    int
+eval_expr_typval(
+       typval_T    *expr,
+       int         want_func,
+       typval_T    *argv,
+       int         argc,
+       funccall_T  *fc_arg,
+       typval_T    *rettv)
+{
+    if (expr->v_type == VAR_PARTIAL)
+       return eval_expr_partial(expr, argv, argc, fc_arg, rettv);
+    else if (expr->v_type == VAR_INSTR)
+       return exe_typval_instr(expr, rettv);
+    else if (expr->v_type == VAR_FUNC || want_func)
+       return eval_expr_func(expr, argv, argc, rettv);
+    else
+       return eval_expr_string(expr, rettv);
+
     return OK;
 }
 
@@ -1117,41 +1170,40 @@ get_lval_check_access(
     ch_log(NULL, "LKVAR: get_lval_check_access(), cl_exec %p, cl %p, %c",
                                                (void*)cl_exec, (void*)cl, *p);
 #endif
-    if (cl_exec == NULL || cl_exec != cl)
+    if (cl_exec != NULL && cl_exec == cl)
+       return OK;
+
+    char *msg = NULL;
+    switch (om->ocm_access)
     {
-       char *msg = NULL;
-       switch (om->ocm_access)
-       {
-           case VIM_ACCESS_PRIVATE:
-               msg = e_cannot_access_protected_variable_str;
+       case VIM_ACCESS_PRIVATE:
+           msg = e_cannot_access_protected_variable_str;
+           break;
+       case VIM_ACCESS_READ:
+           // If [idx] or .key following, read only OK.
+           if (*p == '[' || *p == '.')
                break;
-           case VIM_ACCESS_READ:
-               // If [idx] or .key following, read only OK.
-               if (*p == '[' || *p == '.')
-                   break;
-               if ((flags & GLV_READ_ONLY) == 0)
+           if ((flags & GLV_READ_ONLY) == 0)
+           {
+               if (IS_ENUM(cl))
                {
-                   if (IS_ENUM(cl))
-                   {
-                       if (om->ocm_type->tt_type == VAR_OBJECT)
-                           semsg(_(e_enumvalue_str_cannot_be_modified),
-                                   cl->class_name, om->ocm_name);
-                       else
-                           msg = e_variable_is_not_writable_str;
-                   }
+                   if (om->ocm_type->tt_type == VAR_OBJECT)
+                       semsg(_(e_enumvalue_str_cannot_be_modified),
+                               cl->class_name, om->ocm_name);
                    else
                        msg = e_variable_is_not_writable_str;
                }
-               break;
-           case VIM_ACCESS_ALL:
-               break;
-       }
-       if (msg != NULL)
-       {
-           emsg_var_cl_define(msg, om->ocm_name, 0, cl);
-           return FAIL;
-       }
-
+               else
+                   msg = e_variable_is_not_writable_str;
+           }
+           break;
+       case VIM_ACCESS_ALL:
+           break;
+    }
+    if (msg != NULL)
+    {
+       emsg_var_cl_define(msg, om->ocm_name, 0, cl);
+       return FAIL;
     }
     return OK;
 }
@@ -1230,11 +1282,15 @@ failed:
     return rc == OK ? p : NULL;
 }
 
+typedef enum {
+    GLV_FAIL,
+    GLV_OK,
+    GLV_STOP
+} glv_status_T;
+
 /*
- * Get an lval: variable, Dict item or List item that can be assigned a value
- * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]",
- * "name.key", "name.key[expr]" etc.
- * Indexing only works if "name" is an existing List or Dictionary.
+ * Get an Dict lval variable that can be assigned a value to: "name",
+ * "name[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc.
  * "name" points to the start of the name.
  * If "rettv" is not NULL it points to the value to be assigned.
  * "unlet" is TRUE for ":unlet": slightly different behavior when something is
@@ -1245,115 +1301,487 @@ failed:
  *  GLV_READ_ONLY:   will not change the variable
  *  GLV_NO_AUTOLOAD: do not use script autoloading
  *
- * Returns a pointer to just after the name, including indexes.
- * When an evaluation error occurs "lp->ll_name" is NULL;
- * Returns NULL for a parsing error.  Still need to free items in "lp"!
+ * The Dict is returned in 'lp'.  Returns GLV_OK on success and GLV_FAIL on
+ * failure.  Returns GLV_STOP to stop processing the characters following
+ * 'key_end'.
  */
-    char_u *
-get_lval(
+    static int
+get_lval_dict_item(
     char_u     *name,
-    typval_T   *rettv,
     lval_T     *lp,
+    char_u     *key,
+    int                len,
+    char_u     **key_end,
+    typval_T   *var1,
+    int                flags,
     int                unlet,
-    int                skip,
-    int                flags,      // GLV_ values
-    int                fne_flags)  // flags for find_name_end()
+    typval_T   *rettv)
 {
-    char_u     *p;
-    char_u     *expr_start, *expr_end;
-    int                cc;
-    dictitem_T *v = NULL;
-    typval_T   var1;
-    typval_T   var2;
-    int                empty1 = FALSE;
-    char_u     *key = NULL;
-    int                len;
-    hashtab_T  *ht = NULL;
     int                quiet = flags & GLV_QUIET;
-    int                writing = 0;
-    int                vim9script = in_vim9script();
-    class_T    *cl_exec = NULL;    // class that is executing, or NULL.
-
-#ifdef LOG_LOCKVAR
-    if (lval_root == NULL)
-       ch_log(NULL, "LKVAR: get_lval(): name: %s, lval_root (nil)", name);
-    else
-       ch_log(NULL, "LKVAR: get_lval(): name: %s, lr_tv %p lr_is_arg %d",
-                       name, (void*)lval_root->lr_tv, lval_root->lr_is_arg);
-    char buf[80];
-    ch_log(NULL, "LKVAR:    ...: GLV flags: %s",
-                   flags_tostring(flags, glv_flag_strings, buf, sizeof(buf)));
-#endif
+    char_u     *p = *key_end;
 
-    // Clear everything in "lp".
-    CLEAR_POINTER(lp);
+    if (len == -1)
+    {
+       // "[key]": get key from "var1"
+       key = tv_get_string_chk(var1);  // is number or string
+       if (key == NULL)
+       {
+           clear_tv(var1);
+           return GLV_FAIL;
+       }
+    }
+    lp->ll_list = NULL;
+    lp->ll_object = NULL;
+    lp->ll_class = NULL;
 
-    if (skip || (flags & GLV_COMPILING))
+    // a NULL dict is equivalent with an empty dict
+    if (lp->ll_tv->vval.v_dict == NULL)
     {
-       // When skipping or compiling just find the end of the name.
-       lp->ll_name = name;
-       lp->ll_name_end = find_name_end(name, NULL, NULL,
-                                                     FNE_INCL_BR | fne_flags);
-       return lp->ll_name_end;
+       lp->ll_tv->vval.v_dict = dict_alloc();
+       if (lp->ll_tv->vval.v_dict == NULL)
+       {
+           clear_tv(var1);
+           return GLV_FAIL;
+       }
+       ++lp->ll_tv->vval.v_dict->dv_refcount;
     }
+    lp->ll_dict = lp->ll_tv->vval.v_dict;
 
-    // Cannot use "s:var" at the Vim9 script level.  "s: type" is OK.
-    if (vim9script && at_script_level()
-                 && name[0] == 's' && name[1] == ':' && !VIM_ISWHITE(name[2]))
+    lp->ll_di = dict_find(lp->ll_dict, key, len);
+
+    // When assigning to a scope dictionary check that a function and
+    // variable name is valid (only variable name unless it is l: or
+    // g: dictionary). Disallow overwriting a builtin function.
+    if (rettv != NULL && lp->ll_dict->dv_scope != 0)
     {
-       semsg(_(e_cannot_use_s_colon_in_vim9_script_str), name);
-       return NULL;
+       int prevval;
+
+       if (len != -1)
+       {
+           prevval = key[len];
+           key[len] = NUL;
+       }
+       else
+           prevval = 0; // avoid compiler warning
+       int wrong = (lp->ll_dict->dv_scope == VAR_DEF_SCOPE
+               && (rettv->v_type == VAR_FUNC
+                   || rettv->v_type == VAR_PARTIAL)
+               && var_wrong_func_name(key, lp->ll_di == NULL))
+           || !valid_varname(key, -1, TRUE);
+       if (len != -1)
+           key[len] = prevval;
+       if (wrong)
+       {
+           clear_tv(var1);
+           return GLV_FAIL;
+       }
     }
 
-    // Find the end of the name.
-    p = find_name_end(name, &expr_start, &expr_end, fne_flags);
-    lp->ll_name_end = p;
-    if (expr_start != NULL)
+    if (lp->ll_valtype != NULL)
+       // use the type of the member
+       lp->ll_valtype = lp->ll_valtype->tt_member;
+
+    if (lp->ll_di == NULL)
     {
-       // Don't expand the name when we already know there is an error.
-       if (unlet && !VIM_ISWHITE(*p) && !ends_excmd(*p)
-                                                   && *p != '[' && *p != '.')
+       // Can't add "v:" or "a:" variable.
+       if (lp->ll_dict == get_vimvar_dict()
+               || &lp->ll_dict->dv_hashtab == get_funccal_args_ht())
        {
-           semsg(_(e_trailing_characters_str), p);
-           return NULL;
+           semsg(_(e_illegal_variable_name_str), name);
+           clear_tv(var1);
+           return GLV_FAIL;
        }
 
-       lp->ll_exp_name = make_expanded_name(name, expr_start, expr_end, p);
-       if (lp->ll_exp_name == NULL)
+       // Key does not exist in dict: may need to add it.
+       if (*p == '[' || *p == '.' || unlet)
        {
-           // Report an invalid expression in braces, unless the
-           // expression evaluation has been cancelled due to an
-           // aborting error, an interrupt, or an exception.
-           if (!aborting() && !quiet)
-           {
-               emsg_severe = TRUE;
-               semsg(_(e_invalid_argument_str), name);
-               return NULL;
-           }
+           if (!quiet)
+               semsg(_(e_key_not_present_in_dictionary_str), key);
+           clear_tv(var1);
+           return GLV_FAIL;
        }
-       lp->ll_name = lp->ll_exp_name;
+       if (len == -1)
+           lp->ll_newkey = vim_strsave(key);
+       else
+           lp->ll_newkey = vim_strnsave(key, len);
+       clear_tv(var1);
+       if (lp->ll_newkey == NULL)
+           p = NULL;
+
+       *key_end = p;
+       return GLV_STOP;
     }
-    else
+    // existing variable, need to check if it can be changed
+    else if ((flags & GLV_READ_ONLY) == 0
+           && (var_check_ro(lp->ll_di->di_flags, name, FALSE)
+               || var_check_lock(lp->ll_di->di_flags, name, FALSE)))
     {
-       lp->ll_name = name;
+       clear_tv(var1);
+       return GLV_FAIL;
+    }
 
-       if (vim9script)
-       {
-           // "a: type" is declaring variable "a" with a type, not "a:".
-           // However, "g:[key]" is indexing a dictionary.
-           if (p == name + 2 && p[-1] == ':' && *p != '[')
-           {
-               --p;
-               lp->ll_name_end = p;
-           }
-           if (*skipwhite(p) == ':')
-           {
-               char_u      *tp = skipwhite(p + 1);
+    clear_tv(var1);
+    lp->ll_tv = &lp->ll_di->di_tv;
 
-               if (is_scoped_variable(name))
-               {
-                   semsg(_(e_cannot_use_type_with_this_variable_str), name);
-                   return NULL;
+    return GLV_OK;
+}
+
+/*
+ * Get an blob lval variable that can be assigned a value to: "name",
+ * "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc.
+ *
+ * 'var1' specifies the starting blob index and 'var2' specifies the ending
+ * blob index.  If the first index is not specified in a range, then 'empty1'
+ * is TRUE.  If 'quiet' is TRUE, then error messages are not displayed for
+ * invalid indexes.
+ *
+ * The blob is returned in 'lp'.  Returns OK on success and FAIL on failure.
+ */
+    static int
+get_lval_blob(
+    lval_T     *lp,
+    typval_T   *var1,
+    typval_T   *var2,
+    int                empty1,
+    int                quiet)
+{
+    long       bloblen = blob_len(lp->ll_tv->vval.v_blob);
+
+    // Get the number and item for the only or first index of the List.
+    if (empty1)
+       lp->ll_n1 = 0;
+    else
+       // is number or string
+       lp->ll_n1 = (long)tv_get_number(var1);
+    clear_tv(var1);
+
+    if (check_blob_index(bloblen, lp->ll_n1, quiet) == FAIL)
+    {
+       clear_tv(var2);
+       return FAIL;
+    }
+    if (lp->ll_range && !lp->ll_empty2)
+    {
+       lp->ll_n2 = (long)tv_get_number(var2);
+       clear_tv(var2);
+       if (check_blob_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL)
+           return FAIL;
+    }
+
+    if (!lp->ll_range)
+       // Indexing a single byte in a blob.  So the rhs type is a
+       // number.
+       lp->ll_valtype = &t_number;
+
+    lp->ll_blob = lp->ll_tv->vval.v_blob;
+    lp->ll_tv = NULL;
+
+    return OK;
+}
+
+/*
+ * Get a List lval variable that can be assigned a value to: "name",
+ * "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc.
+ *
+ * 'var1' specifies the starting List index and 'var2' specifies the ending
+ * List index.  If the first index is not specified in a range, then 'empty1'
+ * is TRUE.  If 'quiet' is TRUE, then error messages are not displayed for
+ * invalid indexes.
+ *
+ * The List is returned in 'lp'.  Returns OK on success and FAIL on failure.
+ */
+    static int
+get_lval_list(
+    lval_T     *lp,
+    typval_T   *var1,
+    typval_T   *var2,
+    int                empty1,
+    int                flags,
+    int                quiet)
+{
+    /*
+     * Get the number and item for the only or first index of the List.
+     */
+    if (empty1)
+       lp->ll_n1 = 0;
+    else
+       // is number or string
+       lp->ll_n1 = (long)tv_get_number(var1);
+    clear_tv(var1);
+
+    lp->ll_dict = NULL;
+    lp->ll_object = NULL;
+    lp->ll_class = NULL;
+    lp->ll_list = lp->ll_tv->vval.v_list;
+    lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1,
+                               (flags & GLV_ASSIGN_WITH_OP) == 0, quiet);
+    if (lp->ll_li == NULL)
+    {
+       clear_tv(var2);
+       return FAIL;
+    }
+
+    if (lp->ll_valtype != NULL && !lp->ll_range)
+       // use the type of the member
+       lp->ll_valtype = lp->ll_valtype->tt_member;
+
+    /*
+     * May need to find the item or absolute index for the second
+     * index of a range.
+     * When no index given: "lp->ll_empty2" is TRUE.
+     * Otherwise "lp->ll_n2" is set to the second index.
+     */
+    if (lp->ll_range && !lp->ll_empty2)
+    {
+       lp->ll_n2 = (long)tv_get_number(var2);
+       // is number or string
+       clear_tv(var2);
+       if (check_range_index_two(lp->ll_list,
+                       &lp->ll_n1, lp->ll_li, &lp->ll_n2, quiet) == FAIL)
+           return FAIL;
+    }
+
+    lp->ll_tv = &lp->ll_li->li_tv;
+
+    return OK;
+}
+
+/*
+ * Get a Class or Object lval variable that can be assigned a value to:
+ * "name", "name.key", "name.key[expr]" etc.
+ *
+ * 'cl_exec' is the class that is executing, or NULL. 'v_type' is VAR_CLASS or
+ * VAR_OBJECT.  'key' points to the member variable name and 'key_end' points
+ * to the character after 'key'.  If 'quiet' is TRUE, then error messages
+ * are not displayed for invalid indexes.
+ *
+ * The Class or Object is returned in 'lp'.  Returns OK on success and FAIL on
+ * failure.
+ */
+    static int
+get_lval_class_or_obj(
+    class_T    *cl_exec,
+    vartype_T  v_type,
+    lval_T     *lp,
+    char_u     *key,
+    char_u     *key_end,
+    int                flags,
+    int                quiet)
+{
+    lp->ll_dict = NULL;
+    lp->ll_list = NULL;
+
+    class_T *cl;
+    if (v_type == VAR_OBJECT)
+    {
+       if (lp->ll_tv->vval.v_object == NULL)
+       {
+           if (!quiet)
+               emsg(_(e_using_null_object));
+           return FAIL;
+       }
+       cl = lp->ll_tv->vval.v_object->obj_class;
+       lp->ll_object = lp->ll_tv->vval.v_object;
+    }
+    else
+    {
+       cl = lp->ll_tv->vval.v_class;
+       lp->ll_object = NULL;
+    }
+    lp->ll_class = cl;
+
+    // TODO: what if class is NULL?
+    if (cl != NULL)
+    {
+       lp->ll_valtype = NULL;
+
+       if (flags & GLV_PREFER_FUNC)
+       {
+           // First look for a function with this name.
+           // round 1: class functions (skipped for an object)
+           // round 2: object methods
+           for (int round = v_type == VAR_OBJECT ? 2 : 1;
+                   round <= 2; ++round)
+           {
+               int     m_idx;
+               ufunc_T *fp;
+
+               fp = method_lookup(cl,
+                       round == 1 ? VAR_CLASS : VAR_OBJECT,
+                       key, key_end - key, &m_idx);
+               lp->ll_oi = m_idx;
+               if (fp != NULL)
+               {
+                   lp->ll_ufunc = fp;
+                   lp->ll_valtype = fp->uf_func_type;
+                   break;
+               }
+           }
+       }
+
+       if (lp->ll_valtype == NULL)
+       {
+           int         m_idx;
+           ocmember_T  *om
+               = member_lookup(cl, v_type, key, key_end - key, &m_idx);
+           lp->ll_oi = m_idx;
+           if (om != NULL)
+           {
+               if (get_lval_check_access(cl_exec, cl, om,
+                           key_end, flags) == FAIL)
+                   return FAIL;
+
+               // When lhs is used to modify the variable, check it is
+               // not a read-only variable.
+               if ((flags & GLV_READ_ONLY) == 0
+                       && (*key_end != '.' && *key_end != '[')
+                       && oc_var_check_ro(cl, om))
+                   return FAIL;
+
+               lp->ll_valtype = om->ocm_type;
+
+               if (v_type == VAR_OBJECT)
+                   lp->ll_tv = ((typval_T *)(
+                               lp->ll_tv->vval.v_object + 1)) + m_idx;
+               else
+                   lp->ll_tv = &cl->class_members_tv[m_idx];
+           }
+       }
+
+       if (lp->ll_valtype == NULL)
+       {
+           member_not_found_msg(cl, v_type, key, key_end - key);
+           return FAIL;
+       }
+    }
+
+    return OK;
+}
+
+/*
+ * Get an lval: variable, Dict item or List item that can be assigned a value
+ * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]",
+ * "name.key", "name.key[expr]" etc.
+ * Indexing only works if "name" is an existing List or Dictionary.
+ * "name" points to the start of the name.
+ * If "rettv" is not NULL it points to the value to be assigned.
+ * "unlet" is TRUE for ":unlet": slightly different behavior when something is
+ * wrong; must end in space or cmd separator.
+ *
+ * flags:
+ *  GLV_QUIET:       do not give error messages
+ *  GLV_READ_ONLY:   will not change the variable
+ *  GLV_NO_AUTOLOAD: do not use script autoloading
+ *
+ * Returns a pointer to just after the name, including indexes.
+ * When an evaluation error occurs "lp->ll_name" is NULL;
+ * Returns NULL for a parsing error.  Still need to free items in "lp"!
+ */
+    char_u *
+get_lval(
+    char_u     *name,
+    typval_T   *rettv,
+    lval_T     *lp,
+    int                unlet,
+    int                skip,
+    int                flags,      // GLV_ values
+    int                fne_flags)  // flags for find_name_end()
+{
+    char_u     *p;
+    char_u     *expr_start, *expr_end;
+    int                cc;
+    dictitem_T *v = NULL;
+    typval_T   var1;
+    typval_T   var2;
+    int                empty1 = FALSE;
+    char_u     *key = NULL;
+    int                len;
+    hashtab_T  *ht = NULL;
+    int                quiet = flags & GLV_QUIET;
+    int                writing = 0;
+    int                vim9script = in_vim9script();
+    class_T    *cl_exec = NULL;    // class that is executing, or NULL.
+
+#ifdef LOG_LOCKVAR
+    if (lval_root == NULL)
+       ch_log(NULL, "LKVAR: get_lval(): name: %s, lval_root (nil)", name);
+    else
+       ch_log(NULL, "LKVAR: get_lval(): name: %s, lr_tv %p lr_is_arg %d",
+                       name, (void*)lval_root->lr_tv, lval_root->lr_is_arg);
+    char buf[80];
+    ch_log(NULL, "LKVAR:    ...: GLV flags: %s",
+                   flags_tostring(flags, glv_flag_strings, buf, sizeof(buf)));
+#endif
+
+    // Clear everything in "lp".
+    CLEAR_POINTER(lp);
+
+    if (skip || (flags & GLV_COMPILING))
+    {
+       // When skipping or compiling just find the end of the name.
+       lp->ll_name = name;
+       lp->ll_name_end = find_name_end(name, NULL, NULL,
+                                                     FNE_INCL_BR | fne_flags);
+       return lp->ll_name_end;
+    }
+
+    // Cannot use "s:var" at the Vim9 script level.  "s: type" is OK.
+    if (vim9script && at_script_level()
+                 && name[0] == 's' && name[1] == ':' && !VIM_ISWHITE(name[2]))
+    {
+       semsg(_(e_cannot_use_s_colon_in_vim9_script_str), name);
+       return NULL;
+    }
+
+    // Find the end of the name.
+    p = find_name_end(name, &expr_start, &expr_end, fne_flags);
+    lp->ll_name_end = p;
+    if (expr_start != NULL)
+    {
+       // Don't expand the name when we already know there is an error.
+       if (unlet && !VIM_ISWHITE(*p) && !ends_excmd(*p)
+                                                   && *p != '[' && *p != '.')
+       {
+           semsg(_(e_trailing_characters_str), p);
+           return NULL;
+       }
+
+       lp->ll_exp_name = make_expanded_name(name, expr_start, expr_end, p);
+       if (lp->ll_exp_name == NULL)
+       {
+           // Report an invalid expression in braces, unless the
+           // expression evaluation has been cancelled due to an
+           // aborting error, an interrupt, or an exception.
+           if (!aborting() && !quiet)
+           {
+               emsg_severe = TRUE;
+               semsg(_(e_invalid_argument_str), name);
+               return NULL;
+           }
+       }
+       lp->ll_name = lp->ll_exp_name;
+    }
+    else
+    {
+       lp->ll_name = name;
+
+       if (vim9script)
+       {
+           // "a: type" is declaring variable "a" with a type, not "a:".
+           // However, "g:[key]" is indexing a dictionary.
+           if (p == name + 2 && p[-1] == ':' && *p != '[')
+           {
+               --p;
+               lp->ll_name_end = p;
+           }
+           if (*skipwhite(p) == ':')
+           {
+               char_u      *tp = skipwhite(p + 1);
+
+               if (is_scoped_variable(name))
+               {
+                   semsg(_(e_cannot_use_type_with_this_variable_str), name);
+                   return NULL;
                }
                if (VIM_ISWHITE(*p))
                {
@@ -1614,279 +2042,32 @@ get_lval(
 
        if (v_type == VAR_DICT)
        {
-           if (len == -1)
-           {
-               // "[key]": get key from "var1"
-               key = tv_get_string_chk(&var1); // is number or string
-               if (key == NULL)
-               {
-                   clear_tv(&var1);
-                   return NULL;
-               }
-           }
-           lp->ll_list = NULL;
-           lp->ll_object = NULL;
-           lp->ll_class = NULL;
-
-           // a NULL dict is equivalent with an empty dict
-           if (lp->ll_tv->vval.v_dict == NULL)
-           {
-               lp->ll_tv->vval.v_dict = dict_alloc();
-               if (lp->ll_tv->vval.v_dict == NULL)
-               {
-                   clear_tv(&var1);
-                   return NULL;
-               }
-               ++lp->ll_tv->vval.v_dict->dv_refcount;
-           }
-           lp->ll_dict = lp->ll_tv->vval.v_dict;
-
-           lp->ll_di = dict_find(lp->ll_dict, key, len);
-
-           // When assigning to a scope dictionary check that a function and
-           // variable name is valid (only variable name unless it is l: or
-           // g: dictionary). Disallow overwriting a builtin function.
-           if (rettv != NULL && lp->ll_dict->dv_scope != 0)
-           {
-               int prevval;
-
-               if (len != -1)
-               {
-                   prevval = key[len];
-                   key[len] = NUL;
-               }
-               else
-                   prevval = 0; // avoid compiler warning
-               int wrong = (lp->ll_dict->dv_scope == VAR_DEF_SCOPE
-                              && (rettv->v_type == VAR_FUNC
-                                           || rettv->v_type == VAR_PARTIAL)
-                              && var_wrong_func_name(key, lp->ll_di == NULL))
-                       || !valid_varname(key, -1, TRUE);
-               if (len != -1)
-                   key[len] = prevval;
-               if (wrong)
-               {
-                   clear_tv(&var1);
-                   return NULL;
-               }
-           }
-
-           if (lp->ll_valtype != NULL)
-               // use the type of the member
-               lp->ll_valtype = lp->ll_valtype->tt_member;
-
-           if (lp->ll_di == NULL)
-           {
-               // Can't add "v:" or "a:" variable.
-               if (lp->ll_dict == get_vimvar_dict()
-                        || &lp->ll_dict->dv_hashtab == get_funccal_args_ht())
-               {
-                   semsg(_(e_illegal_variable_name_str), name);
-                   clear_tv(&var1);
-                   return NULL;
-               }
+           glv_status_T glv_status;
 
-               // Key does not exist in dict: may need to add it.
-               if (*p == '[' || *p == '.' || unlet)
-               {
-                   if (!quiet)
-                       semsg(_(e_key_not_present_in_dictionary_str), key);
-                   clear_tv(&var1);
-                   return NULL;
-               }
-               if (len == -1)
-                   lp->ll_newkey = vim_strsave(key);
-               else
-                   lp->ll_newkey = vim_strnsave(key, len);
-               clear_tv(&var1);
-               if (lp->ll_newkey == NULL)
-                   p = NULL;
-               break;
-           }
-           // existing variable, need to check if it can be changed
-           else if ((flags & GLV_READ_ONLY) == 0
-                       && (var_check_ro(lp->ll_di->di_flags, name, FALSE)
-                         || var_check_lock(lp->ll_di->di_flags, name, FALSE)))
-           {
-               clear_tv(&var1);
+           glv_status = get_lval_dict_item(name, lp, key, len, &p, &var1,
+                                                       flags, unlet, rettv);
+           if (glv_status == GLV_FAIL)
                return NULL;
-           }
-
-           clear_tv(&var1);
-           lp->ll_tv = &lp->ll_di->di_tv;
+           if (glv_status == GLV_STOP)
+               break;
        }
        else if (v_type == VAR_BLOB)
        {
-           long bloblen = blob_len(lp->ll_tv->vval.v_blob);
-
-           /*
-            * Get the number and item for the only or first index of the List.
-            */
-           if (empty1)
-               lp->ll_n1 = 0;
-           else
-               // is number or string
-               lp->ll_n1 = (long)tv_get_number(&var1);
-           clear_tv(&var1);
-
-           if (check_blob_index(bloblen, lp->ll_n1, quiet) == FAIL)
-           {
-               clear_tv(&var2);
+           if (get_lval_blob(lp, &var1, &var2, empty1, quiet) == FAIL)
                return NULL;
-           }
-           if (lp->ll_range && !lp->ll_empty2)
-           {
-               lp->ll_n2 = (long)tv_get_number(&var2);
-               clear_tv(&var2);
-               if (check_blob_range(bloblen, lp->ll_n1, lp->ll_n2, quiet)
-                                                                      == FAIL)
-                   return NULL;
-           }
-
-           if (!lp->ll_range)
-               // Indexing a single byte in a blob.  So the rhs type is a
-               // number.
-               lp->ll_valtype = &t_number;
 
-           lp->ll_blob = lp->ll_tv->vval.v_blob;
-           lp->ll_tv = NULL;
            break;
        }
        else if (v_type == VAR_LIST)
        {
-           /*
-            * Get the number and item for the only or first index of the List.
-            */
-           if (empty1)
-               lp->ll_n1 = 0;
-           else
-               // is number or string
-               lp->ll_n1 = (long)tv_get_number(&var1);
-           clear_tv(&var1);
-
-           lp->ll_dict = NULL;
-           lp->ll_object = NULL;
-           lp->ll_class = NULL;
-           lp->ll_list = lp->ll_tv->vval.v_list;
-           lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1,
-                                    (flags & GLV_ASSIGN_WITH_OP) == 0, quiet);
-           if (lp->ll_li == NULL)
-           {
-               clear_tv(&var2);
+           if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL)
                return NULL;
-           }
-
-           if (lp->ll_valtype != NULL && !lp->ll_range)
-               // use the type of the member
-               lp->ll_valtype = lp->ll_valtype->tt_member;
-
-           /*
-            * May need to find the item or absolute index for the second
-            * index of a range.
-            * When no index given: "lp->ll_empty2" is TRUE.
-            * Otherwise "lp->ll_n2" is set to the second index.
-            */
-           if (lp->ll_range && !lp->ll_empty2)
-           {
-               lp->ll_n2 = (long)tv_get_number(&var2);
-                                                   // is number or string
-               clear_tv(&var2);
-               if (check_range_index_two(lp->ll_list,
-                                           &lp->ll_n1, lp->ll_li,
-                                           &lp->ll_n2, quiet) == FAIL)
-                   return NULL;
-           }
-
-           lp->ll_tv = &lp->ll_li->li_tv;
        }
        else  // v_type == VAR_CLASS || v_type == VAR_OBJECT
        {
-           lp->ll_dict = NULL;
-           lp->ll_list = NULL;
-
-           class_T *cl;
-           if (v_type == VAR_OBJECT)
-           {
-               if (lp->ll_tv->vval.v_object == NULL)
-               {
-                   if (!quiet)
-                       emsg(_(e_using_null_object));
-                   return NULL;
-               }
-               cl = lp->ll_tv->vval.v_object->obj_class;
-               lp->ll_object = lp->ll_tv->vval.v_object;
-           }
-           else
-           {
-               cl = lp->ll_tv->vval.v_class;
-               lp->ll_object = NULL;
-           }
-           lp->ll_class = cl;
-
-           // TODO: what if class is NULL?
-           if (cl != NULL)
-           {
-               lp->ll_valtype = NULL;
-
-               if (flags & GLV_PREFER_FUNC)
-               {
-                   // First look for a function with this name.
-                   // round 1: class functions (skipped for an object)
-                   // round 2: object methods
-                   for (int round = v_type == VAR_OBJECT ? 2 : 1;
-                                                       round <= 2; ++round)
-                   {
-                       int     m_idx;
-                       ufunc_T *fp;
-
-                       fp = method_lookup(cl,
-                               round == 1 ? VAR_CLASS : VAR_OBJECT,
-                               key, p - key, &m_idx);
-                       lp->ll_oi = m_idx;
-                       if (fp != NULL)
-                       {
-                           lp->ll_ufunc = fp;
-                           lp->ll_valtype = fp->uf_func_type;
-                           break;
-                       }
-                   }
-               }
-
-               if (lp->ll_valtype == NULL)
-               {
-                   int         m_idx;
-                   ocmember_T  *om
-                           = member_lookup(cl, v_type, key, p - key, &m_idx);
-                   lp->ll_oi = m_idx;
-                   if (om != NULL)
-                   {
-                       if (get_lval_check_access(cl_exec, cl, om,
-                                                         p, flags) == FAIL)
-                           return NULL;
-
-                       // When lhs is used to modify the variable, check it is
-                       // not a read-only variable.
-                       if ((flags & GLV_READ_ONLY) == 0
-                               && (*p != '.' && *p != '[')
-                               && oc_var_check_ro(cl, om))
-                           return NULL;
-
-                       lp->ll_valtype = om->ocm_type;
-
-                       if (v_type == VAR_OBJECT)
-                           lp->ll_tv = ((typval_T *)(
-                                       lp->ll_tv->vval.v_object + 1)) + m_idx;
-                       else
-                           lp->ll_tv = &cl->class_members_tv[m_idx];
-                   }
-               }
-
-               if (lp->ll_valtype == NULL)
-               {
-                   member_not_found_msg(cl, v_type, key, p - key);
-                   return NULL;
-               }
-           }
+           if (get_lval_class_or_obj(cl_exec, v_type, lp, key, p, flags,
+                                                       quiet) == FAIL)
+               return FAIL;
        }
     }
 
@@ -2090,159 +2271,240 @@ set_var_lval(
 }
 
 /*
- * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2"
- * and "tv1 .= tv2"
+ * Handle "blob1 += blob2".
  * Returns OK or FAIL.
  */
-    int
-tv_op(typval_T *tv1, typval_T *tv2, char_u *op)
+    static int
+tv_op_blob(typval_T *tv1, typval_T *tv2, char_u *op)
+{
+    if (*op != '+' || tv2->v_type != VAR_BLOB)
+       return FAIL;
+
+    // Blob += Blob
+    if (tv1->vval.v_blob == NULL || tv2->vval.v_blob == NULL)
+       return OK;
+
+    blob_T     *b1 = tv1->vval.v_blob;
+    blob_T     *b2 = tv2->vval.v_blob;
+    int                len = blob_len(b2);
+
+    for (int i = 0; i < len; i++)
+       ga_append(&b1->bv_ga, blob_get(b2, i));
+
+    return OK;
+}
+
+/*
+ * Handle "list1 += list2".
+ * Returns OK or FAIL.
+ */
+    static int
+tv_op_list(typval_T *tv1, typval_T *tv2, char_u *op)
+{
+    if (*op != '+' || tv2->v_type != VAR_LIST)
+       return FAIL;
+
+    // List += List
+    if (tv2->vval.v_list == NULL)
+       return OK;
+
+    if (tv1->vval.v_list == NULL)
+    {
+       tv1->vval.v_list = tv2->vval.v_list;
+       ++tv1->vval.v_list->lv_refcount;
+    }
+    else
+       list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL);
+
+    return OK;
+}
+
+/*
+ * Handle number operations:
+ *     nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr
+ *
+ * Returns OK or FAIL.
+ */
+    static int
+tv_op_number(typval_T *tv1, typval_T *tv2, char_u *op)
 {
     varnumber_T        n;
+    int                failed = FALSE;
+
+    n = tv_get_number(tv1);
+    if (tv2->v_type == VAR_FLOAT)
+    {
+       float_T f = n;
+
+       if (*op == '%')
+           return FAIL;
+       switch (*op)
+       {
+           case '+': f += tv2->vval.v_float; break;
+           case '-': f -= tv2->vval.v_float; break;
+           case '*': f *= tv2->vval.v_float; break;
+           case '/': f /= tv2->vval.v_float; break;
+       }
+       clear_tv(tv1);
+       tv1->v_type = VAR_FLOAT;
+       tv1->vval.v_float = f;
+    }
+    else
+    {
+       switch (*op)
+       {
+           case '+': n += tv_get_number(tv2); break;
+           case '-': n -= tv_get_number(tv2); break;
+           case '*': n *= tv_get_number(tv2); break;
+           case '/': n = num_divide(n, tv_get_number(tv2), &failed); break;
+           case '%': n = num_modulus(n, tv_get_number(tv2), &failed); break;
+       }
+       clear_tv(tv1);
+       tv1->v_type = VAR_NUMBER;
+       tv1->vval.v_number = n;
+    }
+
+    return failed ? FAIL : OK;
+}
+
+/*
+ * Handle "str1 .= str2"
+ * Returns OK or FAIL.
+ */
+    static int
+tv_op_string(typval_T *tv1, typval_T *tv2, char_u *op UNUSED)
+{
     char_u     numbuf[NUMBUFLEN];
     char_u     *s;
-    int                failed = FALSE;
 
-    // Can't do anything with a Funcref or Dict or Type on the right.
+    if (tv2->v_type == VAR_FLOAT)
+       return FAIL;
+
+    // str .= str
+    s = tv_get_string(tv1);
+    s = concat_str(s, tv_get_string_buf(tv2, numbuf));
+    clear_tv(tv1);
+    tv1->v_type = VAR_STRING;
+    tv1->vval.v_string = s;
+
+    return OK;
+}
+
+/*
+ * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2"
+ * and "tv1 .= tv2"
+ * Returns OK or FAIL.
+ */
+    static int
+tv_op_nr_or_string(typval_T *tv1, typval_T *tv2, char_u *op)
+{
+    if (tv2->v_type == VAR_LIST)
+       return FAIL;
+
+    if (vim_strchr((char_u *)"+-*/%", *op) != NULL)
+       return tv_op_number(tv1, tv2, op);
+
+    return tv_op_string(tv1, tv2, op);
+}
+
+/*
+ * Handle "f1 += f2", "f1 -= f2", "f1 *= f2", "f1 /= f2".
+ * Returns OK or FAIL.
+ */
+    static int
+tv_op_float(typval_T *tv1, typval_T *tv2, char_u *op)
+{
+    float_T f;
+
+    if (*op == '%' || *op == '.'
+           || (tv2->v_type != VAR_FLOAT
+               && tv2->v_type != VAR_NUMBER
+               && tv2->v_type != VAR_STRING))
+       return FAIL;
+
+    if (tv2->v_type == VAR_FLOAT)
+       f = tv2->vval.v_float;
+    else
+       f = tv_get_number(tv2);
+    switch (*op)
+    {
+       case '+': tv1->vval.v_float += f; break;
+       case '-': tv1->vval.v_float -= f; break;
+       case '*': tv1->vval.v_float *= f; break;
+       case '/': tv1->vval.v_float /= f; break;
+    }
+
+    return OK;
+}
+
+/*
+ * Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2"
+ * and "tv1 .= tv2"
+ * Returns OK or FAIL.
+ */
+    int
+tv_op(typval_T *tv1, typval_T *tv2, char_u *op)
+{
+    // Can't do anything with a Funcref or Dict on the right.
     // v:true and friends only work with "..=".
-    if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT
-                   && tv2->v_type != VAR_CLASS && tv2->v_type != VAR_TYPEALIAS
-                   && ((tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL)
-                                                               || *op == '.'))
-    {
-       switch (tv1->v_type)
-       {
-           case VAR_UNKNOWN:
-           case VAR_ANY:
-           case VAR_VOID:
-           case VAR_DICT:
-           case VAR_FUNC:
-           case VAR_PARTIAL:
-           case VAR_BOOL:
-           case VAR_SPECIAL:
-           case VAR_JOB:
-           case VAR_CHANNEL:
-           case VAR_INSTR:
-           case VAR_OBJECT:
-               break;
-           case VAR_CLASS:
-           case VAR_TYPEALIAS:
-               check_typval_is_value(tv1);
-               return FAIL;
+    if (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_DICT
+               || ((tv2->v_type == VAR_BOOL || tv2->v_type == VAR_SPECIAL)
+                                                       && *op != '.'))
+    {
+       semsg(_(e_wrong_variable_type_for_str_equal), op);
+       return FAIL;
+    }
 
-           case VAR_BLOB:
-               if (*op != '+' || tv2->v_type != VAR_BLOB)
-                   break;
-               // BLOB += BLOB
-               if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL)
-               {
-                   blob_T  *b1 = tv1->vval.v_blob;
-                   blob_T  *b2 = tv2->vval.v_blob;
-                   int i, len = blob_len(b2);
-                   for (i = 0; i < len; i++)
-                       ga_append(&b1->bv_ga, blob_get(b2, i));
-               }
-               return OK;
+    if (tv2->v_type == VAR_CLASS || tv2->v_type == VAR_TYPEALIAS)
+    {
+       check_typval_is_value(tv2);
+       return FAIL;
+    }
 
-           case VAR_LIST:
-               if (*op != '+' || tv2->v_type != VAR_LIST)
-                   break;
-               // List += List
-               if (tv2->vval.v_list != NULL)
-               {
-                   if (tv1->vval.v_list == NULL)
-                   {
-                       tv1->vval.v_list = tv2->vval.v_list;
-                       ++tv1->vval.v_list->lv_refcount;
-                   }
-                   else
-                       list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL);
-               }
-               return OK;
+    int retval = FAIL;
+    switch (tv1->v_type)
+    {
+       case VAR_UNKNOWN:
+       case VAR_ANY:
+       case VAR_VOID:
+       case VAR_DICT:
+       case VAR_FUNC:
+       case VAR_PARTIAL:
+       case VAR_BOOL:
+       case VAR_SPECIAL:
+       case VAR_JOB:
+       case VAR_CHANNEL:
+       case VAR_INSTR:
+       case VAR_OBJECT:
+           break;
 
-           case VAR_NUMBER:
-           case VAR_STRING:
-               if (tv2->v_type == VAR_LIST)
-                   break;
-               if (vim_strchr((char_u *)"+-*/%", *op) != NULL)
-               {
-                   // nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr
-                   n = tv_get_number(tv1);
-                   if (tv2->v_type == VAR_FLOAT)
-                   {
-                       float_T f = n;
+       case VAR_CLASS:
+       case VAR_TYPEALIAS:
+           check_typval_is_value(tv1);
+           return FAIL;
 
-                       if (*op == '%')
-                           break;
-                       switch (*op)
-                       {
-                           case '+': f += tv2->vval.v_float; break;
-                           case '-': f -= tv2->vval.v_float; break;
-                           case '*': f *= tv2->vval.v_float; break;
-                           case '/': f /= tv2->vval.v_float; break;
-                       }
-                       clear_tv(tv1);
-                       tv1->v_type = VAR_FLOAT;
-                       tv1->vval.v_float = f;
-                   }
-                   else
-                   {
-                       switch (*op)
-                       {
-                           case '+': n += tv_get_number(tv2); break;
-                           case '-': n -= tv_get_number(tv2); break;
-                           case '*': n *= tv_get_number(tv2); break;
-                           case '/': n = num_divide(n, tv_get_number(tv2),
-                                                              &failed); break;
-                           case '%': n = num_modulus(n, tv_get_number(tv2),
-                                                              &failed); break;
-                       }
-                       clear_tv(tv1);
-                       tv1->v_type = VAR_NUMBER;
-                       tv1->vval.v_number = n;
-                   }
-               }
-               else
-               {
-                   if (tv2->v_type == VAR_FLOAT)
-                       break;
-
-                   // str .= str
-                   s = tv_get_string(tv1);
-                   s = concat_str(s, tv_get_string_buf(tv2, numbuf));
-                   clear_tv(tv1);
-                   tv1->v_type = VAR_STRING;
-                   tv1->vval.v_string = s;
-               }
-               return failed ? FAIL : OK;
+       case VAR_BLOB:
+           retval = tv_op_blob(tv1, tv2, op);
+           break;
 
-           case VAR_FLOAT:
-               {
-                   float_T f;
-
-                   if (*op == '%' || *op == '.'
-                                  || (tv2->v_type != VAR_FLOAT
-                                   && tv2->v_type != VAR_NUMBER
-                                   && tv2->v_type != VAR_STRING))
-                       break;
-                   if (tv2->v_type == VAR_FLOAT)
-                       f = tv2->vval.v_float;
-                   else
-                       f = tv_get_number(tv2);
-                   switch (*op)
-                   {
-                       case '+': tv1->vval.v_float += f; break;
-                       case '-': tv1->vval.v_float -= f; break;
-                       case '*': tv1->vval.v_float *= f; break;
-                       case '/': tv1->vval.v_float /= f; break;
-                   }
-               }
-               return OK;
-       }
+       case VAR_LIST:
+           retval = tv_op_list(tv1, tv2, op);
+           break;
+
+       case VAR_NUMBER:
+       case VAR_STRING:
+           retval = tv_op_nr_or_string(tv1, tv2, op);
+           break;
+
+       case VAR_FLOAT:
+           retval = tv_op_float(tv1, tv2, op);
+           break;
     }
 
-    if (check_typval_is_value(tv2) == OK)
+    if (retval != OK)
        semsg(_(e_wrong_variable_type_for_str_equal), op);
-    return FAIL;
+
+    return retval;
 }
 
 /*
diff --git a/src/version.c b/src/version.c
index 2829873f4..f4c792d01 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    411,
 /**/
     410,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1s63el-00EAGg-3R%40256bit.org.

Raspunde prin e-mail lui