patch 9.0.1909: Vim9: problem calling class method from other class

Problem:  Vim9: problem calling class method from other class
Solution: Fix this problem, fix readonly object access, update error
          messages.

Calling a class method from another method without the class name prefix
doesn't work properly.

A readonly object variable is modifiable outside the class using a
nested object assignment.

Remove the unused E1338 error message.

Update error messages.

closes: #13116

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
diff --git a/src/errors.h b/src/errors.h
index 1db7078..f1373a4 100644
--- a/src/errors.h
+++ b/src/errors.h
@@ -3418,8 +3418,7 @@
 #ifdef FEAT_EVAL
 EXTERN char e_class_member_str_not_found_in_class_str[]
 	INIT(= N_("E1337: Class member \"%s\" not found in class \"%s\""));
-EXTERN char e_interface_static_direct_access_str[]
-	INIT(= N_("E1338: Cannot directly access interface \"%s\" static member \"%s\""));
+// E1338 unused
 #endif
 #ifdef FEAT_PROP_POPUP
 EXTERN char e_cannot_add_textprop_with_text_after_using_textprop_with_negative_id[]
@@ -3506,11 +3505,11 @@
 	INIT(= N_("E1376: Object member \"%s\" accessible only using class \"%s\" object"));
 EXTERN char e_method_str_of_class_str_has_different_access[]
 	INIT(= N_("E1377: Access level of method \"%s\" is different in class \"%s\""));
-EXTERN char e_static_cannot_be_used_in_interface[]
-	INIT(= N_("E1378: Static cannot be used in an interface"));
-EXTERN char e_private_variable_str_in_interface[]
+EXTERN char e_static_member_not_supported_in_interface[]
+	INIT(= N_("E1378: Static member not supported in an interface"));
+EXTERN char e_private_variable_not_supported_in_interface[]
 	INIT(= N_("E1379: Private variable not supported in an interface"));
-EXTERN char e_private_method_str_in_interface[]
+EXTERN char e_private_method_not_supported_in_interface[]
 	INIT(= N_("E1380: Private method not supported in an interface"));
 EXTERN char e_interface_cannot_use_implements[]
 	INIT(= N_("E1381: Interface cannot use \"implements\""));
diff --git a/src/eval.c b/src/eval.c
index 5235469..0f952ee 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1180,14 +1180,6 @@
 	    return NULL;
 	lp->ll_tv = &v->di_tv;
     }
-    if (vim9script && writing && lp->ll_tv->v_type == VAR_CLASS
-	    && (lp->ll_tv->vval.v_class->class_flags & CLASS_INTERFACE) != 0)
-    {
-	if (!quiet)
-	    semsg(_(e_interface_static_direct_access_str),
-			    lp->ll_tv->vval.v_class->class_name, lp->ll_name);
-	return NULL;
-    }
 
     if (vim9script && (flags & GLV_NO_DECL) == 0)
     {
diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro
index 1448f19..d09f654 100644
--- a/src/proto/vim9class.pro
+++ b/src/proto/vim9class.pro
@@ -1,7 +1,7 @@
 /* vim9class.c */
 int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl, int is_static);
 void ex_class(exarg_T *eap);
-type_T *class_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx, ocmember_T **m);
+type_T *class_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx);
 void ex_enum(exarg_T *eap);
 void ex_type(exarg_T *eap);
 int class_object_index(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int verbose);
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 5e3b945..fa621b2 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -4,12 +4,14 @@
 import './vim9.vim' as v9
 
 def Test_class_basic()
+  # Class supported only in "vim9script"
   var lines =<< trim END
       class NotWorking
       endclass
   END
   v9.CheckSourceFailure(lines, 'E1316:')
 
+  # First character in a class name should be capitalized.
   lines =<< trim END
       vim9script
       class notWorking
@@ -17,6 +19,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1314:')
 
+  # Only alphanumeric characters are supported in a class name
   lines =<< trim END
       vim9script
       class Not@working
@@ -24,6 +27,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1315:')
 
+  # Unsupported keyword (instead of class)
   lines =<< trim END
       vim9script
       abstract noclass Something
@@ -31,6 +35,7 @@
   END
   v9.CheckSourceFailure(lines, 'E475:')
 
+  # Only the completed word "class" should be recognized
   lines =<< trim END
       vim9script
       abstract classy Something
@@ -38,6 +43,7 @@
   END
   v9.CheckSourceFailure(lines, 'E475:')
 
+  # The complete "endclass" should be specified.
   lines =<< trim END
       vim9script
       class Something
@@ -45,6 +51,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1065:')
 
+  # Additional words after "endclass"
   lines =<< trim END
       vim9script
       class Something
@@ -52,6 +59,7 @@
   END
   v9.CheckSourceFailure(lines, 'E488:')
 
+  # Additional commands after "endclass"
   lines =<< trim END
       vim9script
       class Something
@@ -59,6 +67,7 @@
   END
   v9.CheckSourceFailure(lines, 'E488:')
 
+  # Use "this" without any member variable name
   lines =<< trim END
       vim9script
       class Something
@@ -67,6 +76,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1317:')
 
+  # Use "this." without any member variable name
   lines =<< trim END
       vim9script
       class Something
@@ -75,6 +85,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1317:')
 
+  # Space between "this" and ".<variable>"
   lines =<< trim END
       vim9script
       class Something
@@ -83,6 +94,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1317:')
 
+  # Space between "this." and the member variable name
   lines =<< trim END
       vim9script
       class Something
@@ -91,6 +103,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1317:')
 
+  # Use "that" instead of "this"
   lines =<< trim END
       vim9script
       class Something
@@ -100,6 +113,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1318: Not a valid command in a class: that.count')
 
+  # Member variable without a type or initialization
   lines =<< trim END
       vim9script
       class Something
@@ -108,6 +122,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1022:')
 
+  # Use a non-existing member variable in new()
   lines =<< trim END
       vim9script
       class Something
@@ -117,8 +132,9 @@
       endclass
       var obj = Something.new()
   END
-  v9.CheckSourceFailure(lines, 'E1089:')
+  v9.CheckSourceFailure(lines, 'E1326: Member not found on object "Something": state')
 
+  # Space before ":" in a member variable declaration
   lines =<< trim END
       vim9script
       class Something
@@ -127,6 +143,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1059:')
 
+  # No space after ":" in a member variable declaration
   lines =<< trim END
       vim9script
       class Something
@@ -204,6 +221,8 @@
   END
   v9.CheckSourceFailure(lines, 'E1324: Using an object as a String')
 
+  # Test creating a class with member variables and methods, calling a object
+  # method.  Check for using type() and typename() with a class and an object.
   lines =<< trim END
       vim9script
 
@@ -270,6 +289,7 @@
   END
   v9.CheckSourceFailure(lines, 'E15:')
 
+  # Use a multi-line initialization for a member variable
   lines =<< trim END
   vim9script
   class A
@@ -347,6 +367,7 @@
 enddef
 
 def Test_object_not_set()
+  # Use an uninitialized object in script context
   var lines =<< trim END
       vim9script
 
@@ -360,6 +381,7 @@
   END
   v9.CheckSourceFailure(lines, 'E1360:')
 
+  # Use an uninitialized object from a def function
   lines =<< trim END
       vim9script
 
@@ -378,6 +400,8 @@
   END
   v9.CheckSourceFailure(lines, 'E1360:')
 
+  # Pass an uninitialized object variable to a "new" function and try to call an
+  # object method.
   lines =<< trim END
       vim9script
 
@@ -418,6 +442,7 @@
   v9.CheckSourceFailure(lines, 'E1363:')
 enddef
 
+" Null object assignment and comparison
 def Test_null_object_assign_compare()
   var lines =<< trim END
     vim9script
@@ -458,6 +483,7 @@
   v9.CheckSourceSuccess(lines)
 enddef
 
+" Test for object member initialization and disassembly
 def Test_class_member_initializer()
   var lines =<< trim END
       vim9script
@@ -517,32 +543,6 @@
   END
   v9.CheckSourceSuccess(lines)
 
-  lines =<< trim END
-      vim9script
-
-      class Inner
-        this.value: number = 0
-      endclass
-
-      class Outer
-        this.inner: Inner
-      endclass
-
-      def F(outer: Outer)
-        outer.inner.value = 1
-      enddef
-
-      def Test_assign_to_nested_typed_member()
-        var inner = Inner.new(0)
-        var outer = Outer.new(inner)
-        F(outer)
-        assert_equal(1, inner.value)
-      enddef
-
-      Test_assign_to_nested_typed_member()
-  END
-  v9.CheckSourceSuccess(lines)
-
   # Try modifying a private variable using an "any" object
   lines =<< trim END
     vim9script
@@ -588,7 +588,37 @@
   v9.CheckSourceFailure(lines, 'E1326: Member not found on object "Inner": someval')
 enddef
 
+" Nested assignment to a object variable which is of another class type
+def Test_assignment_nested_type()
+  var lines =<< trim END
+    vim9script
+
+    class Inner
+      public this.value: number = 0
+    endclass
+
+    class Outer
+      this.inner: Inner
+    endclass
+
+    def F(outer: Outer)
+      outer.inner.value = 1
+    enddef
+
+    def Test_assign_to_nested_typed_member()
+      var inner = Inner.new(0)
+      var outer = Outer.new(inner)
+      F(outer)
+      assert_equal(1, inner.value)
+    enddef
+
+    Test_assign_to_nested_typed_member()
+  END
+  v9.CheckSourceSuccess(lines)
+enddef
+
 def Test_assignment_with_operator()
+  # Use "+=" to assign to a object variable
   var lines =<< trim END
       vim9script
 
@@ -762,7 +792,6 @@
   v9.CheckSourceFailure(lines, "E1328: Constructor default value must be v:none:  = 'a'")
 enddef
 
-
 def Test_class_new_with_object_member()
   var lines =<< trim END
       vim9script
@@ -1677,7 +1706,7 @@
   call v9.CheckSourceSuccess(lines)
 endfunc
 
-def Test_class_function()
+def Test_class_method()
   var lines =<< trim END
       vim9script
       class Value
@@ -1713,6 +1742,29 @@
     endclass
   END
   v9.CheckSourceFailure(lines, 'E1318:')
+
+  # Test for calling a class method from another class method without the class
+  # name prefix.
+  lines =<< trim END
+    vim9script
+    class A
+      static myList: list<number> = [1]
+      static def Foo(n: number)
+        myList->add(n)
+      enddef
+      static def Bar()
+        Foo(2)
+      enddef
+      def Baz()
+        Foo(3)
+      enddef
+    endclass
+    A.Bar()
+    var a = A.new()
+    a.Baz()
+    assert_equal([1, 2, 3], A.myList)
+  END
+  v9.CheckSourceSuccess(lines)
 enddef
 
 def Test_class_defcompile()
@@ -2343,7 +2395,6 @@
   END
   v9.CheckSourceSuccess(lines)
 
-
   # No class that implements the interface.
   lines =<< trim END
       vim9script
@@ -2685,7 +2736,6 @@
   v9.CheckSourceSuccess(lines)
 enddef
 
-
 def Test_class_import()
   var lines =<< trim END
       vim9script
@@ -3651,7 +3701,7 @@
         return 1234
       enddef
       def Bar()
-        assert_equal(1234, A._Foo())
+        assert_equal(1234, _Foo())
       enddef
     endclass
     var a = A.new()
@@ -3659,7 +3709,8 @@
   END
   v9.CheckSourceSuccess(lines)
 
-  # Use a class private method from another class private method
+  # Use a class private method from another class private method without the
+  # class name prefix.
   lines =<< trim END
     vim9script
 
@@ -3668,10 +3719,10 @@
         return 1234
       enddef
       static def _Foo2()
-        assert_equal(1234, A._Foo1())
+        assert_equal(1234, _Foo1())
       enddef
       def Bar()
-        A._Foo2()
+        _Foo2()
       enddef
     endclass
     var a = A.new()
@@ -4063,7 +4114,7 @@
     enddef
     T()
   END
-  v9.CheckSourceFailure(lines, 'E1089: Unknown variable: _a')
+  v9.CheckSourceFailure(lines, 'E1326: Member not found on object "A": _a')
 
   # private static member variable
   lines =<< trim END
@@ -4091,7 +4142,7 @@
     enddef
     T()
   END
-  v9.CheckSourceFailure(lines, 'E1374: Class member "_val" accessible only inside class "A"')
+  v9.CheckSourceFailure(lines, 'E1375: Class member "_val" accessible only using class "A"')
 
   # private static class variable
   lines =<< trim END
@@ -4265,7 +4316,7 @@
     enddef
     T()
   END
-  v9.CheckSourceFailure(lines, 'E1374: Class member "svar2" accessible only inside class "A"')
+  v9.CheckSourceFailure(lines, 'E1375: Class member "svar2" accessible only using class "A"')
 enddef
 
 " Test for using a interface method using a child object
@@ -4711,7 +4762,7 @@
     enddef
     T()
   END
-  v9.CheckSourceFailure(lines, 'E1374: Class member "val" accessible only inside class "A"')
+  v9.CheckSourceFailure(lines, 'E1375: Class member "val" accessible only using class "A"')
 
   # Reading a class variable using an object at function level
   lines =<< trim END
@@ -4977,7 +5028,7 @@
       static num: number
     endinterface
   END
-  v9.CheckSourceFailure(lines, 'E1378: Static cannot be used in an interface')
+  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface')
 
   lines =<< trim END
     vim9script
@@ -4985,7 +5036,7 @@
       static _num: number
     endinterface
   END
-  v9.CheckSourceFailure(lines, 'E1378: Static cannot be used in an interface')
+  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface')
 
   lines =<< trim END
     vim9script
@@ -4993,7 +5044,7 @@
       public static num: number
     endinterface
   END
-  v9.CheckSourceFailure(lines, 'E1378: Static cannot be used in an interface')
+  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface')
 
   lines =<< trim END
     vim9script
@@ -5001,7 +5052,7 @@
       public static _num: number
     endinterface
   END
-  v9.CheckSourceFailure(lines, 'E1378: Static cannot be used in an interface')
+  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface')
 
   lines =<< trim END
     vim9script
@@ -5009,7 +5060,7 @@
       static def Foo(d: dict<any>): list<string>
     endinterface
   END
-  v9.CheckSourceFailure(lines, 'E1378: Static cannot be used in an interface')
+  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface')
 
   lines =<< trim END
     vim9script
@@ -5017,7 +5068,7 @@
       static def _Foo(d: dict<any>): list<string>
     endinterface
   END
-  v9.CheckSourceFailure(lines, 'E1378: Static cannot be used in an interface')
+  v9.CheckSourceFailure(lines, 'E1378: Static member not supported in an interface')
 
   lines =<< trim END
     vim9script
@@ -5402,4 +5453,35 @@
   v9.CheckSourceFailure(lines, 'E1382: Member "val": type mismatch, expected list<dict<string>> but got dict<number>')
 enddef
 
+" Test for assigning to a member variable in a nested class
+def Test_nested_object_assignment()
+  var lines =<< trim END
+    vim9script
+
+    class A
+        this.value: number
+    endclass
+
+    class B
+        this.a: A = A.new()
+    endclass
+
+    class C
+        this.b: B = B.new()
+    endclass
+
+    class D
+        this.c: C = C.new()
+    endclass
+
+    def T(da: D)
+        da.c.b.a.value = 10
+    enddef
+
+    var d = D.new()
+    T(d)
+  END
+  v9.CheckSourceFailure(lines, 'E46: Cannot change read-only variable "value"')
+enddef
+
 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/src/version.c b/src/version.c
index 18c12fa..7670cdd 100644
--- a/src/version.c
+++ b/src/version.c
@@ -700,6 +700,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1909,
+/**/
     1908,
 /**/
     1907,
diff --git a/src/vim9class.c b/src/vim9class.c
index 394589b..da862f2 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -1592,7 +1592,7 @@
 
 	    if (!is_class)
 	    {
-		emsg(_(e_static_cannot_be_used_in_interface));
+		emsg(_(e_static_member_not_supported_in_interface));
 		break;
 	    }
 	    has_static = TRUE;
@@ -1623,7 +1623,8 @@
 	    if (!is_class && *varname == '_')
 	    {
 		// private variables are not supported in an interface
-		semsg(_(e_private_variable_str_in_interface), varname);
+		semsg(_(e_private_variable_not_supported_in_interface),
+			varname);
 		break;
 	    }
 
@@ -1689,7 +1690,8 @@
 		if (!is_class && *name == '_')
 		{
 		    // private variables are not supported in an interface
-		    semsg(_(e_private_method_str_in_interface), name);
+		    semsg(_(e_private_method_not_supported_in_interface),
+			    name);
 		    func_clear_free(uf, FALSE);
 		    break;
 		}
@@ -2010,8 +2012,7 @@
     int		is_object,
     char_u	*name,
     char_u	*name_end,
-    int		*member_idx,
-    ocmember_T	**p_m)
+    int		*member_idx)
 {
     size_t	len = name_end - name;
     ocmember_T	*m;
@@ -2022,27 +2023,11 @@
 								member_idx);
     if (m == NULL)
     {
-	char_u *varname = vim_strnsave(name, len);
-	if (varname != NULL)
-	{
-	    if (is_object && class_member_idx(cl, name, len) >= 0)
-		// A class variable with this name is present
-		semsg(_(e_class_member_str_accessible_only_inside_class_str),
-			varname, cl->class_name);
-	    else if (!is_object && object_member_idx(cl, name, len) >= 0)
-		// An instance variable with this name is present
-		semsg(_(e_object_member_str_accessible_only_using_object_str),
-			varname, cl->class_name);
-	    else
-		semsg(_(e_unknown_variable_str), varname);
-	}
-	vim_free(varname);
+	member_not_found_msg(cl, is_object ? VAR_OBJECT : VAR_CLASS, name,
+									len);
 	return &t_any;
     }
 
-    if (p_m != NULL)
-	*p_m = m;
-
     return m->ocm_type;
 }
 
@@ -2250,12 +2235,6 @@
 	    semsg(_(e_cannot_access_private_member_str), m->ocm_name);
 	    return FAIL;
 	}
-	if ((cl->class_flags & CLASS_INTERFACE) != 0)
-	{
-	    semsg(_(e_interface_static_direct_access_str),
-		    cl->class_name, m->ocm_name);
-	    return FAIL;
-	}
 
 	typval_T *tv = &cl->class_members_tv[m_idx];
 	copy_tv(tv, rettv);
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 7de1b62..ab063f9 100644
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1579,6 +1579,47 @@
 }
 
 /*
+ * Returns TRUE if the class or object variable in "lhs" is modifiable.
+ * "var_start" points to the start of the variable name and "lhs->lhs_varlen"
+ * has the total length.  Note that the "lhs" can be nested an object reference
+ * (e.g.  a.b.c.d.var).
+ */
+    static int
+lhs_class_member_modifiable(lhs_T *lhs, char_u	*var_start, cctx_T *cctx)
+{
+    size_t	varlen = lhs->lhs_varlen;
+    class_T	*cl = lhs->lhs_type->tt_class;
+    int		is_object = lhs->lhs_type->tt_type == VAR_OBJECT;
+    char_u	*name = var_start + varlen + 1;
+    size_t	namelen = lhs->lhs_end - var_start - varlen - 1;
+    ocmember_T	*m;
+
+    m = member_lookup(cl, lhs->lhs_type->tt_type, name, namelen, NULL);
+    if (m == NULL)
+    {
+	member_not_found_msg(cl, lhs->lhs_type->tt_type, name, namelen);
+	return FALSE;
+    }
+
+    // If it is private member variable, then accessing it outside the
+    // class is not allowed.
+    // If it is a read only class variable, then it can be modified
+    // only inside the class where it is defined.
+    if ((m->ocm_access != VIM_ACCESS_ALL) &&
+	    ((is_object && !inside_class(cctx, cl))
+	     || (!is_object && cctx->ctx_ufunc->uf_class != cl)))
+    {
+	char *msg = (m->ocm_access == VIM_ACCESS_PRIVATE)
+				? e_cannot_access_private_member_str
+				: e_cannot_change_readonly_variable_str;
+	semsg(_(msg), m->ocm_name);
+	return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*
  * Figure out the LHS type and other properties for an assignment or one item
  * of ":unlet" with an index.
  * Returns OK or FAIL.
@@ -1691,9 +1732,9 @@
 	    {
 		if (is_decl)
 		{
-		    // if we come here with what looks like an assignment like .=
-		    // but which has been reject by assignment_len() from may_compile_assignment
-		    // give a better error message
+		    // if we come here with what looks like an assignment like
+		    // .= but which has been reject by assignment_len() from
+		    // may_compile_assignment give a better error message
 		    char_u *p = skipwhite(lhs->lhs_end);
 		    if (p[0] == '.' && p[1] == '=')
 			emsg(_(e_dot_equal_not_supported_with_script_version_two));
@@ -1959,36 +2000,17 @@
 	{
 	    // for an object or class member get the type of the member
 	    class_T	*cl = lhs->lhs_type->tt_class;
-	    ocmember_T	*m;
 	    int		is_object = lhs->lhs_type->tt_type == VAR_OBJECT;
 
+	    if (!lhs_class_member_modifiable(lhs, var_start, cctx))
+		return FAIL;
+
 	    lhs->lhs_member_type = class_member_type(cl,
 					is_object,
 					after + 1, lhs->lhs_end,
-					&lhs->lhs_member_idx, &m);
+					&lhs->lhs_member_idx);
 	    if (lhs->lhs_member_idx < 0)
 		return FAIL;
-	    if ((cl->class_flags & CLASS_INTERFACE) != 0
-					&& lhs->lhs_type->tt_type == VAR_CLASS)
-	    {
-		semsg(_(e_interface_static_direct_access_str),
-						cl->class_name, m->ocm_name);
-		return FAIL;
-	    }
-	    // If it is private member variable, then accessing it outside the
-	    // class is not allowed.
-	    // If it is a read only class variable, then it can be modified
-	    // only inside the class where it is defined.
-	    if ((m->ocm_access != VIM_ACCESS_ALL) &&
-		    ((is_object && !inside_class(cctx, cl))
-		     || (!is_object && cctx->ctx_ufunc->uf_class != cl)))
-	    {
-		char *msg = (m->ocm_access == VIM_ACCESS_PRIVATE)
-				? e_cannot_access_private_member_str
-				: e_cannot_change_readonly_variable_str;
-		semsg(_(msg), m->ocm_name);
-		return FAIL;
-	    }
 	}
 	else
 	{
@@ -2163,6 +2185,14 @@
 
 	lhs->lhs_type = cctx->ctx_type_stack.ga_len == 0 ? &t_void
 						  : get_type_on_stack(cctx, 0);
+
+	if (lhs->lhs_type->tt_type == VAR_OBJECT)
+	{
+	    // Check whether the object variable is modifiable
+	    if (!lhs_class_member_modifiable(lhs, var_start, cctx))
+		return FAIL;
+	}
+
 	// Now we can properly check the type.  The variable is indexed, thus
 	// we need the member type.  For a class or object we don't know the
 	// type yet, it depends on what member is used.
@@ -2198,8 +2228,7 @@
 
 	class_T	*cl = lhs->lhs_type->tt_class;
 	type_T	*type = class_member_type(cl, TRUE, dot + 1,
-					   lhs->lhs_end, &lhs->lhs_member_idx,
-					   NULL);
+					  lhs->lhs_end, &lhs->lhs_member_idx);
 	if (lhs->lhs_member_idx < 0)
 	    return FAIL;
 
diff --git a/src/vim9expr.c b/src/vim9expr.c
index a158f31..bcad79c 100644
--- a/src/vim9expr.c
+++ b/src/vim9expr.c
@@ -439,12 +439,6 @@
 	if (m != NULL)
 	{
 	    // Note: type->tt_type = VAR_CLASS
-	    if ((cl->class_flags & CLASS_INTERFACE) != 0)
-	    {
-		semsg(_(e_interface_static_direct_access_str),
-			cl->class_name, m->ocm_name);
-		return FAIL;
-	    }
 	    // A private class variable can be accessed only in the class where
 	    // it is defined.
 	    if (*name == '_' && cctx->ctx_ufunc->uf_class != cl)
@@ -1152,9 +1146,6 @@
 	    // the class where the function is defined.
 	    if (cctx->ctx_ufunc->uf_defclass == cl)
 	    {
-		// The generate_CALL() function expects the class type at the
-		// top of the stack.  So push the class type to the stack.
-		push_type_stack(cctx, &t_class);
 		res = generate_CALL(cctx, cl->class_class_functions[mi], NULL,
 							0, type, argcount);
 	    }
diff --git a/src/vim9instr.c b/src/vim9instr.c
index ccec0bc..b9bbfd0 100644
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1899,9 +1899,15 @@
     // drop the argument types
     cctx->ctx_type_stack.ga_len -= argcount;
 
-    // For an object or class method call, drop the object/class type
+    // For an object or class method call, drop the object/class type.
     if (ufunc->uf_class != NULL)
-	cctx->ctx_type_stack.ga_len--;
+    {
+	// When a class method is called without the class name prefix, then
+	// the type will not be in the stack.
+	type_T *stype = get_type_on_stack(cctx, 0);
+	if (stype->tt_type == VAR_CLASS || stype->tt_type == VAR_OBJECT)
+	    cctx->ctx_type_stack.ga_len--;
+    }
 
     // add return type
     return push_type_stack(cctx, ufunc->uf_ret_type);