mirror of
https://github.com/vim/vim.git
synced 2025-09-25 03:54:15 -04:00
patch 9.0.2170: Vim9: no support for const/final class/objects vars
Problem: Vim9: no support for const/final class/objects vars Solution: Support final and const object and class variables closes: #13655 Signed-off-by: Yegappan Lakshmanan <yegappan@yahoo.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
d8bf87c9fb
commit
e5437c5427
@@ -4505,7 +4505,10 @@ E1401 vim9class.txt /*E1401*
|
||||
E1402 vim9class.txt /*E1402*
|
||||
E1403 vim9class.txt /*E1403*
|
||||
E1407 vim9class.txt /*E1407*
|
||||
E1408 vim9class.txt /*E1408*
|
||||
E1409 vim9class.txt /*E1409*
|
||||
E141 message.txt /*E141*
|
||||
E1410 vim9class.txt /*E1410*
|
||||
E142 message.txt /*E142*
|
||||
E143 autocmd.txt /*E143*
|
||||
E144 various.txt /*E144*
|
||||
@@ -9096,6 +9099,8 @@ o_V motion.txt /*o_V*
|
||||
o_object-select motion.txt /*o_object-select*
|
||||
o_v motion.txt /*o_v*
|
||||
object vim9class.txt /*object*
|
||||
object-const-variable vim9class.txt /*object-const-variable*
|
||||
object-final-variable vim9class.txt /*object-final-variable*
|
||||
object-motions motion.txt /*object-motions*
|
||||
object-select motion.txt /*object-select*
|
||||
objects index.txt /*objects*
|
||||
|
@@ -1,4 +1,4 @@
|
||||
*todo.txt* For Vim version 9.0. Last change: 2023 Jun 08
|
||||
*todo.txt* For Vim version 9.0. Last change: 2023 Dec 14
|
||||
|
||||
|
||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||
@@ -122,7 +122,6 @@ Upcoming larger works:
|
||||
|
||||
Further Vim9 improvements:
|
||||
- Classes and Interfaces. See |vim9-classes|
|
||||
- "final" object members - can only be set in the constructor.
|
||||
- Cannot use class type of itself in the method (Issue #12369)
|
||||
- Getting member of variable with "any" type should be handled at runtime.
|
||||
Remove temporary solution from #12096 / patch 9.0.1375.
|
||||
|
@@ -364,6 +364,78 @@ super class. Depending on the class where the member is used the
|
||||
corresponding class member will be used. The type of the class member in a
|
||||
child class can be different from that in the super class.
|
||||
|
||||
*object-final-variable* *E1409*
|
||||
The |:final| keyword can be used to make a class or object variable a
|
||||
constant. Examples: >
|
||||
|
||||
class A
|
||||
final v1 = [1, 2] # final object variable
|
||||
public final v2 = {x: 1} # final object variable
|
||||
static final v3 = 'abc' # final class variable
|
||||
public static final v4 = 0z10 # final class variable
|
||||
endclass
|
||||
<
|
||||
A final variable can be changed only from a constructor function. Example: >
|
||||
|
||||
class A
|
||||
final v1: list<number>
|
||||
def new()
|
||||
this.v1 = [1, 2]
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
echo a.v1
|
||||
<
|
||||
Note that the value of a final variable can be changed. Example: >
|
||||
|
||||
class A
|
||||
public final v1 = [1, 2]
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.v1[0] = 6 # OK
|
||||
a.v1->add(3) # OK
|
||||
a.v1 = [3, 4] # Error
|
||||
<
|
||||
*E1408*
|
||||
Final variables are not supported in an interface. A class or object method
|
||||
cannot be final.
|
||||
|
||||
*object-const-variable*
|
||||
The |:const| keyword can be used to make a class or object variable and the
|
||||
value a constant. Examples: >
|
||||
|
||||
class A
|
||||
const v1 = [1, 2] # const object variable
|
||||
public const v2 = {x: 1} # const object variable
|
||||
static const v3 = 'abc' # const class variable
|
||||
public static const v4 = 0z10 # const class variable
|
||||
endclass
|
||||
<
|
||||
A const variable can be changed only from a constructor function. Example: >
|
||||
|
||||
class A
|
||||
const v1: list<number>
|
||||
def new()
|
||||
this.v1 = [1, 2]
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
echo a.v1
|
||||
<
|
||||
A const variable and its value cannot be changed. Example: >
|
||||
|
||||
class A
|
||||
public const v1 = [1, 2]
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.v1[0] = 6 # Error
|
||||
a.v1->add(3) # Error
|
||||
a.v1 = [3, 4] # Error
|
||||
<
|
||||
*E1410*
|
||||
Const variables are not supported in an interface. A class or object method
|
||||
cannot be a const.
|
||||
|
||||
==============================================================================
|
||||
|
||||
4. Using an abstract class *Vim9-abstract-class*
|
||||
@@ -982,8 +1054,6 @@ function declaration syntax for class/object variables and methods. Vim9 also
|
||||
reuses the general function declaration syntax for methods. So, for the sake
|
||||
of consistency, we require "var" in these declarations.
|
||||
|
||||
This also allows for a natural use of "final" and "const" in the future.
|
||||
|
||||
|
||||
Using "ClassName.new()" to construct an object ~
|
||||
|
||||
|
16
src/errors.h
16
src/errors.h
@@ -3406,8 +3406,8 @@ EXTERN char e_invalid_class_variable_declaration_str[]
|
||||
INIT(= N_("E1329: Invalid class variable declaration: %s"));
|
||||
EXTERN char e_invalid_type_for_object_variable_str[]
|
||||
INIT(= N_("E1330: Invalid type for object variable: %s"));
|
||||
EXTERN char e_public_must_be_followed_by_var_or_static[]
|
||||
INIT(= N_("E1331: Public must be followed by \"var\" or \"static\""));
|
||||
EXTERN char e_public_must_be_followed_by_var_static_final_or_const[]
|
||||
INIT(= N_("E1331: Public must be followed by \"var\" or \"static\" or \"final\" or \"const\""));
|
||||
EXTERN char e_public_variable_name_cannot_start_with_underscore_str[]
|
||||
INIT(= N_("E1332: Public variable name cannot start with underscore: %s"));
|
||||
EXTERN char e_cannot_access_protected_variable_str[]
|
||||
@@ -3488,8 +3488,8 @@ EXTERN char e_cannot_access_protected_method_str[]
|
||||
INIT(= N_("E1366: Cannot access protected method: %s"));
|
||||
EXTERN char e_variable_str_of_interface_str_has_different_access[]
|
||||
INIT(= N_("E1367: Access level of variable \"%s\" of interface \"%s\" is different"));
|
||||
EXTERN char e_static_must_be_followed_by_var_or_def[]
|
||||
INIT(= N_("E1368: Static must be followed by \"var\" or \"def\""));
|
||||
EXTERN char e_static_must_be_followed_by_var_def_final_or_const[]
|
||||
INIT(= N_("E1368: Static must be followed by \"var\" or \"def\" or \"final\" or \"const\""));
|
||||
EXTERN char e_duplicate_variable_str[]
|
||||
INIT(= N_("E1369: Duplicate variable: %s"));
|
||||
EXTERN char e_cannot_define_new_method_as_static[]
|
||||
@@ -3568,8 +3568,14 @@ EXTERN char e_using_class_as_var_val[]
|
||||
INIT(= N_("E1406: Cannot use a Class as a variable or value"));
|
||||
EXTERN char e_using_typealias_as_var_val[]
|
||||
INIT(= N_("E1407: Cannot use a Typealias as a variable or value"));
|
||||
EXTERN char e_final_variable_not_supported_in_interface[]
|
||||
INIT(= N_("E1408: Final variable not supported in an interface"));
|
||||
EXTERN char e_cannot_change_readonly_variable_str_in_class_str[]
|
||||
INIT(= N_("E1409: Cannot change read-only variable \"%s\" in class \"%s\""));
|
||||
EXTERN char e_const_variable_not_supported_in_interface[]
|
||||
INIT(= N_("E1410: Const variable not supported in an interface"));
|
||||
#endif
|
||||
// E1408 - E1499 unused (reserved for Vim9 class support)
|
||||
// E1411 - E1499 unused (reserved for Vim9 class support)
|
||||
EXTERN char e_cannot_mix_positional_and_non_positional_str[]
|
||||
INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s"));
|
||||
EXTERN char e_fmt_arg_nr_unused_str[]
|
||||
|
@@ -1778,6 +1778,13 @@ get_lval(
|
||||
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)
|
||||
|
@@ -18,6 +18,8 @@ ocmember_T *member_lookup(class_T *cl, vartype_T v_type, char_u *name, size_t na
|
||||
void emsg_var_cl_define(char *msg, char_u *name, size_t len, class_T *cl);
|
||||
ufunc_T *method_lookup(class_T *cl, vartype_T v_type, char_u *name, size_t namelen, int *idx);
|
||||
int inside_class(cctx_T *cctx_arg, class_T *cl);
|
||||
int oc_var_check_ro(class_T *cl, ocmember_T *m);
|
||||
void obj_lock_const_vars(object_T *obj);
|
||||
void copy_object(typval_T *from, typval_T *to);
|
||||
void copy_class(typval_T *from, typval_T *to);
|
||||
void class_unref(class_T *cl);
|
||||
|
@@ -1523,14 +1523,18 @@ typedef enum {
|
||||
VIM_ACCESS_ALL // read/write everywhere
|
||||
} omacc_T;
|
||||
|
||||
#define OCMFLAG_HAS_TYPE 0x01 // type specified explicitly
|
||||
#define OCMFLAG_FINAL 0x02 // "final" object/class member
|
||||
#define OCMFLAG_CONST 0x04 // "const" object/class member
|
||||
|
||||
/*
|
||||
* Entry for an object or class member variable.
|
||||
*/
|
||||
typedef struct {
|
||||
char_u *ocm_name; // allocated
|
||||
omacc_T ocm_access;
|
||||
int ocm_has_type; // type specified explicitly
|
||||
type_T *ocm_type;
|
||||
int ocm_flags;
|
||||
char_u *ocm_init; // allocated
|
||||
} ocmember_T;
|
||||
|
||||
|
@@ -9051,4 +9051,612 @@ def Test_compile_many_def_functions_in_funcref_instr()
|
||||
assert_equal(0, v:shell_error)
|
||||
enddef
|
||||
|
||||
" Test for 'final' class and object variables
|
||||
def Test_final_class_object_variable()
|
||||
# Test for changing a final object variable from an object function
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
final foo: string = "abc"
|
||||
def Foo()
|
||||
this.foo = "def"
|
||||
enddef
|
||||
endclass
|
||||
defcompile A.Foo
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "foo" in class "A"', 1)
|
||||
|
||||
# Test for changing a final object variable from the 'new' function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
final s1: string
|
||||
final s2: string
|
||||
def new(this.s1)
|
||||
this.s2 = 'def'
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new('abc')
|
||||
assert_equal('abc', a.s1)
|
||||
assert_equal('def', a.s2)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for a final class variable
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
static final s1: string = "abc"
|
||||
endclass
|
||||
assert_equal('abc', A.s1)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for changing a final class variable from a class function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
static final s1: string = "abc"
|
||||
static def Foo()
|
||||
s1 = "def"
|
||||
enddef
|
||||
endclass
|
||||
A.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
|
||||
|
||||
# Test for changing a public final class variable at script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public static final s1: string = "abc"
|
||||
endclass
|
||||
assert_equal('abc', A.s1)
|
||||
A.s1 = 'def'
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 6)
|
||||
|
||||
# Test for changing a public final class variable from a class function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public static final s1: string = "abc"
|
||||
static def Foo()
|
||||
s1 = "def"
|
||||
enddef
|
||||
endclass
|
||||
A.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
|
||||
|
||||
# Test for changing a public final class variable from a function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public static final s1: string = "abc"
|
||||
endclass
|
||||
def Foo()
|
||||
A.s1 = 'def'
|
||||
enddef
|
||||
defcompile
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
|
||||
|
||||
# Test for using a final variable of composite type
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number>
|
||||
def new()
|
||||
this.l = [1, 2]
|
||||
enddef
|
||||
def Foo()
|
||||
this.l[0] = 3
|
||||
this.l->add(4)
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
assert_equal([1, 2], a.l)
|
||||
a.Foo()
|
||||
assert_equal([3, 2, 4], a.l)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for changing a final variable of composite type from another object
|
||||
# function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number> = [1, 2]
|
||||
def Foo()
|
||||
this.l = [3, 4]
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
|
||||
|
||||
# Test for modifying a final variable of composite type at script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number> = [1, 2]
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.l[0] = 3
|
||||
a.l->add(4)
|
||||
assert_equal([3, 2, 4], a.l)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for modifying a final variable of composite type from a function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number> = [1, 2]
|
||||
endclass
|
||||
def Foo()
|
||||
var a = A.new()
|
||||
a.l[0] = 3
|
||||
a.l->add(4)
|
||||
assert_equal([3, 2, 4], a.l)
|
||||
enddef
|
||||
Foo()
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for modifying a final variable of composite type from another object
|
||||
# function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number> = [1, 2]
|
||||
def Foo()
|
||||
this.l[0] = 3
|
||||
this.l->add(4)
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
assert_equal([3, 2, 4], a.l)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for assigning a new value to a final variable of composite type at
|
||||
# script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number> = [1, 2]
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.l = [3, 4]
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 6)
|
||||
|
||||
# Test for assigning a new value to a final variable of composite type from
|
||||
# another object function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number> = [1, 2]
|
||||
def Foo()
|
||||
this.l = [3, 4]
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
|
||||
|
||||
# Test for assigning a new value to a final variable of composite type from
|
||||
# another function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public final l: list<number> = [1, 2]
|
||||
endclass
|
||||
def Foo()
|
||||
var a = A.new()
|
||||
a.l = [3, 4]
|
||||
enddef
|
||||
Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 2)
|
||||
|
||||
# Error case: Use 'final' with just a variable name
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
final foo
|
||||
endclass
|
||||
var a = A.new()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: Use 'final' followed by 'public'
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
final public foo: number
|
||||
endclass
|
||||
var a = A.new()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: Use 'final' followed by 'static'
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
final static foo: number
|
||||
endclass
|
||||
var a = A.new()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: 'final' cannot be used in an interface
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
interface A
|
||||
final foo: number = 10
|
||||
endinterface
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1408: Final variable not supported in an interface', 3)
|
||||
|
||||
# Error case: 'final' not supported for an object method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
final def Foo()
|
||||
enddef
|
||||
endclass
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: 'final' not supported for a class method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
static final def Foo()
|
||||
enddef
|
||||
endclass
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
enddef
|
||||
|
||||
" Test for 'const' class and object variables
|
||||
def Test_const_class_object_variable()
|
||||
# Test for changing a const object variable from an object function
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
const foo: string = "abc"
|
||||
def Foo()
|
||||
this.foo = "def"
|
||||
enddef
|
||||
endclass
|
||||
defcompile A.Foo
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "foo" in class "A"', 1)
|
||||
|
||||
# Test for changing a const object variable from the 'new' function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
const s1: string
|
||||
const s2: string
|
||||
def new(this.s1)
|
||||
this.s2 = 'def'
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new('abc')
|
||||
assert_equal('abc', a.s1)
|
||||
assert_equal('def', a.s2)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for changing a const object variable from an object method called from
|
||||
# the 'new' function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
const s1: string = 'abc'
|
||||
def new()
|
||||
this.ChangeStr()
|
||||
enddef
|
||||
def ChangeStr()
|
||||
this.s1 = 'def'
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
|
||||
|
||||
# Test for a const class variable
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
static const s1: string = "abc"
|
||||
endclass
|
||||
assert_equal('abc', A.s1)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Test for changing a const class variable from a class function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
static const s1: string = "abc"
|
||||
static def Foo()
|
||||
s1 = "def"
|
||||
enddef
|
||||
endclass
|
||||
A.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
|
||||
|
||||
# Test for changing a public const class variable at script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public static const s1: string = "abc"
|
||||
endclass
|
||||
assert_equal('abc', A.s1)
|
||||
A.s1 = 'def'
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 6)
|
||||
|
||||
# Test for changing a public const class variable from a class function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public static const s1: string = "abc"
|
||||
static def Foo()
|
||||
s1 = "def"
|
||||
enddef
|
||||
endclass
|
||||
A.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
|
||||
|
||||
# Test for changing a public const class variable from a function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public static const s1: string = "abc"
|
||||
endclass
|
||||
def Foo()
|
||||
A.s1 = 'def'
|
||||
enddef
|
||||
defcompile
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "s1" in class "A"', 1)
|
||||
|
||||
# Test for changing a const List item from an object function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number>
|
||||
def new()
|
||||
this.l = [1, 2]
|
||||
enddef
|
||||
def Foo()
|
||||
this.l[0] = 3
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
assert_equal([1, 2], a.l)
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 1)
|
||||
|
||||
# Test for adding a value to a const List from an object function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number>
|
||||
def new()
|
||||
this.l = [1, 2]
|
||||
enddef
|
||||
def Foo()
|
||||
this.l->add(3)
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 1)
|
||||
|
||||
# Test for reassigning a const List from an object function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
def Foo()
|
||||
this.l = [3, 4]
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
|
||||
|
||||
# Test for changing a const List item at script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.l[0] = 3
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E741: Value is locked:', 6)
|
||||
|
||||
# Test for adding a value to a const List item at script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.l->add(4)
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E741: Value is locked:', 6)
|
||||
|
||||
# Test for changing a const List item from a function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
endclass
|
||||
def Foo()
|
||||
var a = A.new()
|
||||
a.l[0] = 3
|
||||
enddef
|
||||
Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 2)
|
||||
|
||||
# Test for adding a value to a const List item from a function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
endclass
|
||||
def Foo()
|
||||
var a = A.new()
|
||||
a.l->add(4)
|
||||
enddef
|
||||
Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 2)
|
||||
|
||||
# Test for changing a const List item from an object method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
def Foo()
|
||||
this.l[0] = 3
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1119: Cannot change locked list item', 1)
|
||||
|
||||
# Test for adding a value to a const List item from an object method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
def Foo()
|
||||
this.l->add(4)
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E741: Value is locked: add() argument', 1)
|
||||
|
||||
# Test for reassigning a const List object variable at script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.l = [3, 4]
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 6)
|
||||
|
||||
# Test for reassigning a const List object variable from an object method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
def Foo()
|
||||
this.l = [3, 4]
|
||||
enddef
|
||||
endclass
|
||||
var a = A.new()
|
||||
a.Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 1)
|
||||
|
||||
# Test for reassigning a const List object variable from another function
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
public const l: list<number> = [1, 2]
|
||||
endclass
|
||||
def Foo()
|
||||
var a = A.new()
|
||||
a.l = [3, 4]
|
||||
enddef
|
||||
Foo()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1409: Cannot change read-only variable "l" in class "A"', 2)
|
||||
|
||||
# Error case: Use 'const' with just a variable name
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
const foo
|
||||
endclass
|
||||
var a = A.new()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: Use 'const' followed by 'public'
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
const public foo: number
|
||||
endclass
|
||||
var a = A.new()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: Use 'const' followed by 'static'
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
const static foo: number
|
||||
endclass
|
||||
var a = A.new()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: 'const' cannot be used in an interface
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
interface A
|
||||
const foo: number = 10
|
||||
endinterface
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1410: Const variable not supported in an interface', 3)
|
||||
|
||||
# Error case: 'const' not supported for an object method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
const def Foo()
|
||||
enddef
|
||||
endclass
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
|
||||
# Error case: 'const' not supported for a class method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class A
|
||||
static const def Foo()
|
||||
enddef
|
||||
endclass
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1022: Type or initialization required', 3)
|
||||
enddef
|
||||
|
||||
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
|
||||
|
@@ -704,6 +704,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
2170,
|
||||
/**/
|
||||
2169,
|
||||
/**/
|
||||
|
114
src/vim9class.c
114
src/vim9class.c
@@ -152,6 +152,19 @@ parse_member(
|
||||
return OK;
|
||||
}
|
||||
|
||||
typedef struct oc_newmember_S oc_newmember_T;
|
||||
struct oc_newmember_S
|
||||
{
|
||||
garray_T *gap;
|
||||
char_u *varname;
|
||||
char_u *varname_end;
|
||||
int has_public;
|
||||
int has_final;
|
||||
int has_type;
|
||||
type_T *type;
|
||||
char_u *init_expr;
|
||||
};
|
||||
|
||||
/*
|
||||
* Add a member to an object or a class.
|
||||
* Returns OK when successful, "init_expr" will be consumed then.
|
||||
@@ -163,6 +176,8 @@ add_member(
|
||||
char_u *varname,
|
||||
char_u *varname_end,
|
||||
int has_public,
|
||||
int has_final,
|
||||
int has_const,
|
||||
int has_type,
|
||||
type_T *type,
|
||||
char_u *init_expr)
|
||||
@@ -173,7 +188,12 @@ add_member(
|
||||
m->ocm_name = vim_strnsave(varname, varname_end - varname);
|
||||
m->ocm_access = has_public ? VIM_ACCESS_ALL
|
||||
: *varname == '_' ? VIM_ACCESS_PRIVATE : VIM_ACCESS_READ;
|
||||
m->ocm_has_type = has_type;
|
||||
if (has_final)
|
||||
m->ocm_flags |= OCMFLAG_FINAL;
|
||||
if (has_const)
|
||||
m->ocm_flags |= OCMFLAG_CONST;
|
||||
if (has_type)
|
||||
m->ocm_flags |= OCMFLAG_HAS_TYPE;
|
||||
m->ocm_type = type;
|
||||
if (init_expr != NULL)
|
||||
m->ocm_init = init_expr;
|
||||
@@ -1132,7 +1152,7 @@ add_class_members(class_T *cl, exarg_T *eap, garray_T *type_list_gap)
|
||||
if (etv != NULL)
|
||||
{
|
||||
if (m->ocm_type->tt_type == VAR_ANY
|
||||
&& !m->ocm_has_type
|
||||
&& !(m->ocm_flags & OCMFLAG_HAS_TYPE)
|
||||
&& etv->v_type != VAR_SPECIAL)
|
||||
// If the member variable type is not yet set, then use
|
||||
// the initialization expression type.
|
||||
@@ -1149,6 +1169,8 @@ add_class_members(class_T *cl, exarg_T *eap, garray_T *type_list_gap)
|
||||
tv->v_type = m->ocm_type->tt_type;
|
||||
tv->vval.v_string = NULL;
|
||||
}
|
||||
if (m->ocm_flags & OCMFLAG_CONST)
|
||||
item_lock(tv, DICT_MAXNEST, TRUE, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1558,9 +1580,10 @@ early_ret:
|
||||
has_public = TRUE;
|
||||
p = skipwhite(line + 6);
|
||||
|
||||
if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "static", 6) != 0)
|
||||
if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "static", 6) != 0
|
||||
&& STRNCMP(p, "final", 5) != 0 && STRNCMP(p, "const", 5) != 0)
|
||||
{
|
||||
emsg(_(e_public_must_be_followed_by_var_or_static));
|
||||
emsg(_(e_public_must_be_followed_by_var_static_final_or_const));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1616,22 +1639,60 @@ early_ret:
|
||||
has_static = TRUE;
|
||||
p = skipwhite(ps + 6);
|
||||
|
||||
if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "def", 3) != 0)
|
||||
if (STRNCMP(p, "var", 3) != 0 && STRNCMP(p, "def", 3) != 0
|
||||
&& STRNCMP(p, "final", 5) != 0 && STRNCMP(p, "const", 5) != 0)
|
||||
{
|
||||
emsg(_(e_static_must_be_followed_by_var_or_def));
|
||||
emsg(_(e_static_must_be_followed_by_var_def_final_or_const));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int has_final = FALSE;
|
||||
int has_var = FALSE;
|
||||
int has_const = FALSE;
|
||||
if (checkforcmd(&p, "var", 3))
|
||||
has_var = TRUE;
|
||||
else if (checkforcmd(&p, "final", 5))
|
||||
{
|
||||
if (!is_class)
|
||||
{
|
||||
emsg(_(e_final_variable_not_supported_in_interface));
|
||||
break;
|
||||
}
|
||||
has_final = TRUE;
|
||||
}
|
||||
else if (checkforcmd(&p, "const", 5))
|
||||
{
|
||||
if (!is_class)
|
||||
{
|
||||
emsg(_(e_const_variable_not_supported_in_interface));
|
||||
break;
|
||||
}
|
||||
has_const = TRUE;
|
||||
}
|
||||
p = skipwhite(p);
|
||||
|
||||
// object members (public, read access, private):
|
||||
// "var _varname"
|
||||
// "var varname"
|
||||
// "public var varname"
|
||||
// "final _varname"
|
||||
// "final varname"
|
||||
// "public final varname"
|
||||
// "const _varname"
|
||||
// "const varname"
|
||||
// "public const varname"
|
||||
// class members (public, read access, private):
|
||||
// "static var _varname"
|
||||
// "static var varname"
|
||||
// "public static var varname"
|
||||
if (checkforcmd(&p, "var", 3))
|
||||
// "static final _varname"
|
||||
// "static final varname"
|
||||
// "public static final varname"
|
||||
// "static const _varname"
|
||||
// "static const varname"
|
||||
// "public static const varname"
|
||||
if (has_var || has_final || has_const)
|
||||
{
|
||||
char_u *varname = p;
|
||||
char_u *varname_end = NULL;
|
||||
@@ -1671,8 +1732,9 @@ early_ret:
|
||||
vim_free(init_expr);
|
||||
break;
|
||||
}
|
||||
if (add_member(has_static ? &classmembers : &objmembers, varname, varname_end,
|
||||
has_public, has_type, type, init_expr) == FAIL)
|
||||
if (add_member(has_static ? &classmembers : &objmembers, varname,
|
||||
varname_end, has_public, has_final, has_const,
|
||||
has_type, type, init_expr) == FAIL)
|
||||
{
|
||||
vim_free(init_expr);
|
||||
break;
|
||||
@@ -2779,6 +2841,40 @@ inside_class(cctx_T *cctx_arg, class_T *cl)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return TRUE if object/class variable "m" is read-only.
|
||||
* Also give an error message.
|
||||
*/
|
||||
int
|
||||
oc_var_check_ro(class_T *cl, ocmember_T *m)
|
||||
{
|
||||
if (m->ocm_flags & (OCMFLAG_FINAL | OCMFLAG_CONST))
|
||||
{
|
||||
semsg(_(e_cannot_change_readonly_variable_str_in_class_str),
|
||||
m->ocm_name, cl->class_name);
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock all the constant object variables. Called after creating and
|
||||
* initializing a new object.
|
||||
*/
|
||||
void
|
||||
obj_lock_const_vars(object_T *obj)
|
||||
{
|
||||
for (int i = 0; i < obj->obj_class->class_obj_member_count; i++)
|
||||
{
|
||||
ocmember_T *ocm = &obj->obj_class->class_obj_members[i];
|
||||
if (ocm->ocm_flags & OCMFLAG_CONST)
|
||||
{
|
||||
typval_T *mtv = ((typval_T *)(obj + 1)) + i;
|
||||
item_lock(mtv, DICT_MAXNEST, TRUE, TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a copy of an object.
|
||||
*/
|
||||
|
@@ -1770,6 +1770,12 @@ compile_lhs(
|
||||
lhs->lhs_name);
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
ocmember_T *m =
|
||||
&defcl->class_class_members[lhs->lhs_classmember_idx];
|
||||
if (oc_var_check_ro(defcl, m))
|
||||
return FAIL;
|
||||
|
||||
lhs->lhs_dest = dest_class_member;
|
||||
lhs->lhs_class = cctx->ctx_ufunc->uf_class;
|
||||
lhs->lhs_type =
|
||||
@@ -2040,6 +2046,10 @@ compile_lhs(
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
if (!IS_CONSTRUCTOR_METHOD(cctx->ctx_ufunc)
|
||||
&& oc_var_check_ro(cl, m))
|
||||
return FAIL;
|
||||
|
||||
lhs->lhs_member_type = m->ocm_type;
|
||||
}
|
||||
else
|
||||
@@ -3356,7 +3366,7 @@ compile_def_function(
|
||||
|
||||
type_T *type = get_type_on_stack(&cctx, 0);
|
||||
if (m->ocm_type->tt_type == VAR_ANY
|
||||
&& !m->ocm_has_type
|
||||
&& !(m->ocm_flags & OCMFLAG_HAS_TYPE)
|
||||
&& type->tt_type != VAR_SPECIAL)
|
||||
{
|
||||
// If the member variable type is not yet set, then use
|
||||
|
@@ -4455,7 +4455,11 @@ exec_instructions(ectx_T *ectx)
|
||||
else
|
||||
{
|
||||
*tv = *STACK_TV_VAR(0);
|
||||
++tv->vval.v_object->obj_refcount;
|
||||
object_T *obj = tv->vval.v_object;
|
||||
++obj->obj_refcount;
|
||||
|
||||
// Lock all the constant object variables
|
||||
obj_lock_const_vars(obj);
|
||||
}
|
||||
// FALLTHROUGH
|
||||
|
||||
|
Reference in New Issue
Block a user