changeset 28029:0ed38a4e028d v8.2.4539

patch 8.2.4539: when comparing special v:none and v:null are handled the same Commit: https://github.com/vim/vim/commit/53ba6ca5b27248680e368340707ad4b32a82591f Author: Bram Moolenaar <Bram@vim.org> Date: Thu Mar 10 19:23:28 2022 +0000 patch 8.2.4539: when comparing special v:none and v:null are handled the same Problem: When comparing special v:none and v:null are handled the same when compiling. Solution: Pass more information so that v:none can be handled differently at compile time. (issue #9923)
author Bram Moolenaar <Bram@vim.org>
date Thu, 10 Mar 2022 20:30:04 +0100
parents db86841e8419
children 7c6b90dd119e
files src/globals.h src/testdir/test_vim9_expr.vim src/version.c src/vim9compile.c src/vim9instr.c
diffstat 5 files changed, 71 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/src/globals.h
+++ b/src/globals.h
@@ -398,7 +398,8 @@ EXTERN type_T t_any INIT6(VAR_ANY, 0, 0,
 EXTERN type_T t_void INIT6(VAR_VOID, 0, 0, TTFLAG_STATIC, NULL, NULL);
 
 EXTERN type_T t_bool INIT6(VAR_BOOL, 0, 0, TTFLAG_STATIC, NULL, NULL);
-EXTERN type_T t_special INIT6(VAR_SPECIAL, 0, 0, TTFLAG_STATIC, NULL, NULL);
+EXTERN type_T t_null INIT6(VAR_SPECIAL, 0, 0, TTFLAG_STATIC, NULL, NULL);
+EXTERN type_T t_none INIT6(VAR_SPECIAL, 0, 0, TTFLAG_STATIC, NULL, NULL);
 EXTERN type_T t_number INIT6(VAR_NUMBER, 0, 0, TTFLAG_STATIC, NULL, NULL);
 EXTERN type_T t_number_bool INIT6(VAR_NUMBER, 0, 0, TTFLAG_STATIC|TTFLAG_BOOL_OK, NULL, NULL);
 EXTERN type_T t_float INIT6(VAR_FLOAT, 0, 0, TTFLAG_STATIC, NULL, NULL);
--- a/src/testdir/test_vim9_expr.vim
+++ b/src/testdir/test_vim9_expr.vim
@@ -828,8 +828,22 @@ def Test_expr4_compare_null()
   v9.CheckDefAndScriptFailure(['echo true != v:null'], 'E1072: Cannot compare bool with special')
   v9.CheckDefAndScriptFailure(['echo v:null != true'], 'E1072: Cannot compare special with bool')
   v9.CheckDefAndScriptFailure(['echo false == v:null'], 'E1072: Cannot compare bool with special')
-
-  v9.CheckDefExecAndScriptFailure(['echo [] == v:none'], ['E1072: Cannot compare list with special', 'E691: Can only compare List with List'])
+enddef
+
+def Test_expr4_compare_none()
+  var lines =<< trim END
+      assert_false('' == v:none)
+      assert_false('text' == v:none)
+      assert_true(v:none == v:none)
+      assert_false(v:none == '')
+      assert_false(v:none == 'text')
+      assert_true(v:none == v:none)
+  END
+  v9.CheckDefAndScriptSuccess(lines)
+
+  v9.CheckDefAndScriptFailure(['echo [] == v:none'], 'E1072: Cannot compare list with special')
+  v9.CheckDefAndScriptFailure(['echo 123 == v:none'], 'E1072: Cannot compare number with special')
+  v9.CheckDefAndScriptFailure(['echo 0z00 == v:none'], 'E1072: Cannot compare blob with special')
 enddef
 
 def Test_expr4_wrong_type()
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4539,
+/**/
     4538,
 /**/
     4537,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -1799,7 +1799,7 @@ compile_assign_unlet(
 		return FAIL;
 	    }
 	    type = get_type_on_stack(cctx, 0);
-	    if ((dest_type != VAR_BLOB && type != &t_special)
+	    if ((dest_type != VAR_BLOB && type->tt_type != VAR_SPECIAL)
 		    && need_type(type, &t_number,
 					    -1, 0, cctx, FALSE, FALSE) == FAIL)
 		return FAIL;
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -339,22 +339,30 @@ generate_two_op(cctx_T *cctx, char_u *op
 }
 
 /*
- * Get the instruction to use for comparing "type1" with "type2"
+ * Get the instruction to use for comparing two values with specified types.
+ * Either "tv1" and "tv2" are passed or "type1" and "type2".
  * Return ISN_DROP when failed.
  */
     static isntype_T
-get_compare_isn(exprtype_T exprtype, vartype_T type1, vartype_T type2)
+get_compare_isn(
+	exprtype_T exprtype,
+	typval_T    *tv1,
+	typval_T    *tv2,
+	type_T	    *type1,
+	type_T	    *type2)
 {
     isntype_T	isntype = ISN_DROP;
+    vartype_T	vartype1 = tv1 != NULL ? tv1->v_type : type1->tt_type;
+    vartype_T	vartype2 = tv2 != NULL ? tv2->v_type : type2->tt_type;
 
-    if (type1 == VAR_UNKNOWN)
-	type1 = VAR_ANY;
-    if (type2 == VAR_UNKNOWN)
-	type2 = VAR_ANY;
+    if (vartype1 == VAR_UNKNOWN)
+	vartype1 = VAR_ANY;
+    if (vartype2 == VAR_UNKNOWN)
+	vartype2 = VAR_ANY;
 
-    if (type1 == type2)
+    if (vartype1 == vartype2)
     {
-	switch (type1)
+	switch (vartype1)
 	{
 	    case VAR_BOOL: isntype = ISN_COMPAREBOOL; break;
 	    case VAR_SPECIAL: isntype = ISN_COMPARESPECIAL; break;
@@ -368,15 +376,28 @@ get_compare_isn(exprtype_T exprtype, var
 	    default: isntype = ISN_COMPAREANY; break;
 	}
     }
-    else if (type1 == VAR_ANY || type2 == VAR_ANY
-	    || ((type1 == VAR_NUMBER || type1 == VAR_FLOAT)
-			       && (type2 == VAR_NUMBER || type2 == VAR_FLOAT))
-	    || (type1 == VAR_FUNC && type2 == VAR_PARTIAL)
-	    || (type1 == VAR_PARTIAL && type2 == VAR_FUNC))
+    else if (vartype1 == VAR_ANY || vartype2 == VAR_ANY
+	    || ((vartype1 == VAR_NUMBER || vartype1 == VAR_FLOAT)
+			  && (vartype2 == VAR_NUMBER || vartype2 == VAR_FLOAT))
+	    || (vartype1 == VAR_FUNC && vartype2 == VAR_PARTIAL)
+	    || (vartype1 == VAR_PARTIAL && vartype2 == VAR_FUNC))
 	isntype = ISN_COMPAREANY;
-    else if (type1 == VAR_SPECIAL || type2 == VAR_SPECIAL)
+    else if (vartype1 == VAR_SPECIAL || vartype2 == VAR_SPECIAL)
     {
-	switch (type1 == VAR_SPECIAL ? type2 : type1)
+	if ((vartype1 == VAR_SPECIAL
+		&& (tv1 != NULL ? tv1->vval.v_number == VVAL_NONE
+							    : type1 == &t_none)
+		&& vartype2 != VAR_STRING)
+	    || (vartype2 == VAR_SPECIAL
+		&& (tv2 != NULL ? tv2->vval.v_number == VVAL_NONE
+							    : type2 == &t_none)
+		&& vartype1 != VAR_STRING))
+	{
+	    semsg(_(e_cannot_compare_str_with_str),
+			       vartype_name(vartype1), vartype_name(vartype2));
+	    return ISN_DROP;
+	}
+	switch (vartype1 == VAR_SPECIAL ? vartype2 : vartype1)
 	{
 	    case VAR_BLOB: break;
 	    case VAR_CHANNEL: break;
@@ -387,7 +408,7 @@ get_compare_isn(exprtype_T exprtype, var
 	    case VAR_PARTIAL: break;
 	    case VAR_STRING: break;
 	    default: semsg(_(e_cannot_compare_str_with_str),
-				   vartype_name(type1), vartype_name(type2));
+			       vartype_name(vartype1), vartype_name(vartype2));
 		     return ISN_DROP;
 	}
 	isntype = ISN_COMPARENULL;
@@ -400,20 +421,20 @@ get_compare_isn(exprtype_T exprtype, var
 	    || isntype == ISN_COMPAREFLOAT))
     {
 	semsg(_(e_cannot_use_str_with_str),
-		exprtype == EXPR_IS ? "is" : "isnot" , vartype_name(type1));
+		exprtype == EXPR_IS ? "is" : "isnot" , vartype_name(vartype1));
 	return ISN_DROP;
     }
     if (isntype == ISN_DROP
 	    || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL
-		    && (type1 == VAR_BOOL || type1 == VAR_SPECIAL
-		       || type2 == VAR_BOOL || type2 == VAR_SPECIAL)))
+		    && (vartype1 == VAR_BOOL || vartype1 == VAR_SPECIAL
+		       || vartype2 == VAR_BOOL || vartype2 == VAR_SPECIAL)))
 	    || ((exprtype != EXPR_EQUAL && exprtype != EXPR_NEQUAL
 			       && exprtype != EXPR_IS && exprtype != EXPR_ISNOT
-		    && (type1 == VAR_BLOB || type2 == VAR_BLOB
-			|| type1 == VAR_LIST || type2 == VAR_LIST))))
+		    && (vartype1 == VAR_BLOB || vartype2 == VAR_BLOB
+			|| vartype1 == VAR_LIST || vartype2 == VAR_LIST))))
     {
 	semsg(_(e_cannot_compare_str_with_str),
-		vartype_name(type1), vartype_name(type2));
+		vartype_name(vartype1), vartype_name(vartype2));
 	return ISN_DROP;
     }
     return isntype;
@@ -422,7 +443,7 @@ get_compare_isn(exprtype_T exprtype, var
     int
 check_compare_types(exprtype_T type, typval_T *tv1, typval_T *tv2)
 {
-    if (get_compare_isn(type, tv1->v_type, tv2->v_type) == ISN_DROP)
+    if (get_compare_isn(type, tv1, tv2, NULL, NULL) == ISN_DROP)
 	return FAIL;
     return OK;
 }
@@ -436,17 +457,14 @@ generate_COMPARE(cctx_T *cctx, exprtype_
     isntype_T	isntype;
     isn_T	*isn;
     garray_T	*stack = &cctx->ctx_type_stack;
-    vartype_T	type1;
-    vartype_T	type2;
 
     RETURN_OK_IF_SKIP(cctx);
 
     // Get the known type of the two items on the stack.  If they are matching
     // use a type-specific instruction. Otherwise fall back to runtime type
     // checking.
-    type1 = get_type_on_stack(cctx, 1)->tt_type;
-    type2 = get_type_on_stack(cctx, 0)->tt_type;
-    isntype = get_compare_isn(exprtype, type1, type2);
+    isntype = get_compare_isn(exprtype, NULL, NULL,
+		       get_type_on_stack(cctx, 1), get_type_on_stack(cctx, 0));
     if (isntype == ISN_DROP)
 	return FAIL;
 
@@ -664,7 +682,8 @@ generate_PUSHSPEC(cctx_T *cctx, varnumbe
     isn_T	*isn;
 
     RETURN_OK_IF_SKIP(cctx);
-    if ((isn = generate_instr_type(cctx, ISN_PUSHSPEC, &t_special)) == NULL)
+    if ((isn = generate_instr_type(cctx, ISN_PUSHSPEC,
+			     number == VVAL_NULL ? &t_null : &t_none)) == NULL)
 	return FAIL;
     isn->isn_arg.number = number;
 
@@ -1475,7 +1494,7 @@ generate_CALL(cctx_T *cctx, ufunc_T *ufu
 	    type_T *actual;
 
 	    actual = get_type_on_stack(cctx, argcount - i - 1);
-	    if (actual == &t_special
+	    if (actual->tt_type == VAR_SPECIAL
 			      && i >= regular_args - ufunc->uf_def_args.ga_len)
 	    {
 		// assume v:none used for default argument value
@@ -1606,7 +1625,7 @@ generate_PCALL(
 			expected = type->tt_args[
 					     type->tt_argcount - 1]->tt_member;
 		    else if (i >= type->tt_min_argcount
-						       && actual == &t_special)
+					     && actual->tt_type == VAR_SPECIAL)
 			expected = &t_any;
 		    else
 			expected = type->tt_args[i];