patch 9.1.0850: Vim9: cannot access nested object inside objects
Problem: Vim9: cannot access nested object inside objects
(lifepillar, 91khr, mawkish)
Solution: Add support for accessing an object member using a "any"
variable type (Yegappan Lakshmanan)
fixes: #11822
fixes: #12024
fixes: #12196
fixes: #12198
closes: #16029
Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/src/vim9execute.c b/src/vim9execute.c
index f523e27..da03d5e 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -2268,15 +2268,23 @@
ocmember_T *m = object_member_lookup(cl, member, 0, &m_idx);
if (m != NULL)
{
- if (*member == '_')
+ // Get the current function
+ ufunc_T *ufunc = (((dfunc_T *)def_functions.ga_data)
+ + ectx->ec_dfunc_idx)->df_ufunc;
+
+ // Check whether the member variable is writeable
+ if ((m->ocm_access != VIM_ACCESS_ALL) &&
+ (ufunc->uf_class == NULL ||
+ !class_instance_of(ufunc->uf_class, cl)))
{
- emsg_var_cl_define(
- e_cannot_access_protected_variable_str,
- m->ocm_name, 0, cl);
+ char *msg = (m->ocm_access == VIM_ACCESS_PRIVATE)
+ ? e_cannot_access_protected_variable_str
+ : e_variable_is_not_writable_str;
+ emsg_var_cl_define(msg, m->ocm_name, 0, cl);
status = FAIL;
}
-
- lidx = m_idx;
+ else
+ lidx = m_idx;
}
else
{
@@ -3120,6 +3128,73 @@
}
/*
+ * Accessing the member of an object stored in a variable of type "any".
+ * Returns OK if the member variable is present.
+ * Returns FAIL if the variable is not found.
+ */
+ static int
+any_var_get_obj_member(class_T *current_class, isn_T *iptr, typval_T *tv)
+{
+ object_T *obj = tv->vval.v_object;
+ typval_T mtv;
+
+ if (obj == NULL)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_using_null_object));
+ return FAIL;
+ }
+
+ // get_member_tv() needs the object information in the typval argument.
+ // So set the object information.
+ copy_tv(tv, &mtv);
+
+ // 'name' can either be a object variable or a object method
+ int namelen = STRLEN(iptr->isn_arg.string);
+ int save_did_emsg = did_emsg;
+
+ if (get_member_tv(obj->obj_class, TRUE, iptr->isn_arg.string, namelen,
+ current_class, &mtv) == OK)
+ {
+ copy_tv(&mtv, tv);
+ clear_tv(&mtv);
+ return OK;
+ }
+
+ if (did_emsg != save_did_emsg)
+ return FAIL;
+
+ // could be a member function
+ ufunc_T *obj_method;
+ int obj_method_idx;
+
+ obj_method = method_lookup(obj->obj_class, VAR_OBJECT,
+ iptr->isn_arg.string, namelen,
+ &obj_method_idx);
+ if (obj_method == NULL)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ semsg(_(e_variable_not_found_on_object_str_str), iptr->isn_arg.string,
+ obj->obj_class->class_name);
+ return FAIL;
+ }
+
+ // Protected methods are not accessible outside the class
+ if (*obj_method->uf_name == '_'
+ && !class_instance_of(current_class, obj->obj_class))
+ {
+ semsg(_(e_cannot_access_protected_method_str), obj_method->uf_name);
+ return FAIL;
+ }
+
+ // Create a partial for the member function
+ if (obj_method_to_partial_tv(obj, obj_method, tv) == FAIL)
+ return FAIL;
+
+ return OK;
+}
+
+/*
* Execute instructions in execution context "ectx".
* Return OK or FAIL;
*/
@@ -5482,6 +5557,7 @@
}
break;
+ // dict member with string key (dict['member'])
case ISN_MEMBER:
{
dict_T *dict;
@@ -5526,35 +5602,51 @@
}
break;
- // dict member with string key
+ // dict member with string key (dict.member)
+ // or can be an object
case ISN_STRINGMEMBER:
{
dict_T *dict;
dictitem_T *di;
tv = STACK_TV_BOT(-1);
- if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL)
- {
- SOURCING_LNUM = iptr->isn_lnum;
- emsg(_(e_dictionary_required));
- goto on_error;
- }
- dict = tv->vval.v_dict;
- if ((di = dict_find(dict, iptr->isn_arg.string, -1))
- == NULL)
+ if (tv->v_type == VAR_OBJECT)
{
- SOURCING_LNUM = iptr->isn_lnum;
- semsg(_(e_key_not_present_in_dictionary_str),
- iptr->isn_arg.string);
- goto on_error;
- }
- // Put the dict used on the dict stack, it might be used by
- // a dict function later.
- if (dict_stack_save(tv) == FAIL)
- goto on_fatal_error;
+ if (dict_stack_save(tv) == FAIL)
+ goto on_fatal_error;
- copy_tv(&di->di_tv, tv);
+ ufunc_T *ufunc = (((dfunc_T *)def_functions.ga_data)
+ + ectx->ec_dfunc_idx)->df_ufunc;
+ // Class object (not a Dict)
+ if (any_var_get_obj_member(ufunc->uf_class, iptr, tv) == FAIL)
+ goto on_error;
+ }
+ else
+ {
+ if (tv->v_type != VAR_DICT || tv->vval.v_dict == NULL)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ emsg(_(e_dictionary_required));
+ goto on_error;
+ }
+ dict = tv->vval.v_dict;
+
+ if ((di = dict_find(dict, iptr->isn_arg.string, -1))
+ == NULL)
+ {
+ SOURCING_LNUM = iptr->isn_lnum;
+ semsg(_(e_key_not_present_in_dictionary_str),
+ iptr->isn_arg.string);
+ goto on_error;
+ }
+ // Put the dict used on the dict stack, it might be
+ // used by a dict function later.
+ if (dict_stack_save(tv) == FAIL)
+ goto on_fatal_error;
+
+ copy_tv(&di->di_tv, tv);
+ }
}
break;