patch 8.2.3395: Vim9: expression breakpoint not checked in :def function

Problem:    Vim9: expression breakpoint not checked in :def function.
Solution:   Always compile a function for debugging if there is an expression
            breakpoint. (closes #8803)
diff --git a/src/debugger.c b/src/debugger.c
index 1b01998..1e28d66 100644
--- a/src/debugger.c
+++ b/src/debugger.c
@@ -518,6 +518,7 @@
 #define BREAKP(idx)		(((struct debuggy *)dbg_breakp.ga_data)[idx])
 #define DEBUGGY(gap, idx)	(((struct debuggy *)gap->ga_data)[idx])
 static int last_breakp = 0;	// nr of last defined breakpoint
+static int has_expr_breakpoint = FALSE;
 
 #ifdef FEAT_PROFILE
 // Profiling uses file and func names similar to breakpoints.
@@ -691,6 +692,8 @@
 	    // DBG_EXPR
 	    DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp;
 	    ++debug_tick;
+	    if (gap == &dbg_breakp)
+		has_expr_breakpoint = TRUE;
 	}
     }
 }
@@ -707,6 +710,29 @@
 	debug_greedy = FALSE;
 }
 
+    static void
+update_has_expr_breakpoint()
+{
+    int i;
+
+    has_expr_breakpoint = FALSE;
+    for (i = 0; i < dbg_breakp.ga_len; ++i)
+	if (BREAKP(i).dbg_type == DBG_EXPR)
+	{
+	    has_expr_breakpoint = TRUE;
+	    break;
+	}
+}
+
+/*
+ * Return TRUE if there is any expression breakpoint.
+ */
+    int
+debug_has_expr_breakpoint()
+{
+    return has_expr_breakpoint;
+}
+
 /*
  * ":breakdel" and ":profdel".
  */
@@ -799,6 +825,8 @@
 	// If all breakpoints were removed clear the array.
 	if (gap->ga_len == 0)
 	    ga_clear(gap);
+	if (gap == &dbg_breakp)
+	    update_has_expr_breakpoint();
     }
 }
 
diff --git a/src/proto/debugger.pro b/src/proto/debugger.pro
index e018622..d80ecf5 100644
--- a/src/proto/debugger.pro
+++ b/src/proto/debugger.pro
@@ -6,6 +6,7 @@
 int dbg_check_skipped(exarg_T *eap);
 void ex_breakadd(exarg_T *eap);
 void ex_debuggreedy(exarg_T *eap);
+int debug_has_expr_breakpoint(void);
 void ex_breakdel(exarg_T *eap);
 void ex_breaklist(exarg_T *eap);
 linenr_T dbg_find_breakpoint(int file, char_u *fname, linenr_T after);
diff --git a/src/proto/vim9execute.pro b/src/proto/vim9execute.pro
index 8b4b6ea..ef22af7 100644
--- a/src/proto/vim9execute.pro
+++ b/src/proto/vim9execute.pro
@@ -5,6 +5,7 @@
 char_u *string_slice(char_u *str, varnumber_T first, varnumber_T last, int exclusive);
 int fill_partial_and_closure(partial_T *pt, ufunc_T *ufunc, ectx_T *ectx);
 typval_T *lookup_debug_var(char_u *name);
+int may_break_in_function(ufunc_T *ufunc);
 int exe_typval_instr(typval_T *tv, typval_T *rettv);
 char_u *exe_substitute_instr(void);
 int call_def_function(ufunc_T *ufunc, int argc_arg, typval_T *argv, partial_T *partial, typval_T *rettv);
diff --git a/src/testdir/test_debugger.vim b/src/testdir/test_debugger.vim
index 2937d54..85ab6ea 100644
--- a/src/testdir/test_debugger.vim
+++ b/src/testdir/test_debugger.vim
@@ -932,6 +932,27 @@
   call delete('Xtest2.vim')
 endfunc
 
+func Test_DefFunction_expr()
+  CheckCWD
+  let file3 =<< trim END
+      vim9script
+      g:someVar = "foo"
+      def g:ChangeVar()
+        g:someVar = "bar"
+        echo "changed"
+      enddef
+      defcompile
+  END
+  call writefile(file3, 'Xtest3.vim')
+  let buf = RunVimInTerminal('-S Xtest3.vim', {})
+
+  call RunDbgCmd(buf, ':breakadd expr g:someVar')
+  call RunDbgCmd(buf, ':call g:ChangeVar()', ['Oldval = "''foo''"', 'Newval = "''bar''"', 'function ChangeVar', 'line 2: echo "changed"'])
+
+  call StopVimInTerminal(buf)
+  call delete('Xtest3.vim')
+endfunc
+
 func Test_debug_def_and_legacy_function()
   CheckCWD
   let file =<< trim END
diff --git a/src/version.c b/src/version.c
index 8811ee1..435affe 100644
--- a/src/version.c
+++ b/src/version.c
@@ -756,6 +756,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3395,
+/**/
     3394,
 /**/
     3393,
diff --git a/src/vim.h b/src/vim.h
index b9105b5..cb769e2 100644
--- a/src/vim.h
+++ b/src/vim.h
@@ -1808,9 +1808,16 @@
 
 // Keep in sync with INSTRUCTIONS().
 #ifdef FEAT_PROFILE
-# define COMPILE_TYPE(ufunc) (debug_break_level > 0 || ufunc->uf_has_breakpoint ? CT_DEBUG : do_profiling == PROF_YES && (ufunc)->uf_profiling ? CT_PROFILE : CT_NONE)
+# define COMPILE_TYPE(ufunc) (debug_break_level > 0 \
+	|| may_break_in_function(ufunc) \
+		? CT_DEBUG \
+		: do_profiling == PROF_YES && (ufunc)->uf_profiling \
+			? CT_PROFILE : CT_NONE)
 #else
-# define COMPILE_TYPE(ufunc) debug_break_level > 0 || ufunc->uf_has_breakpoint ? CT_DEBUG : CT_NONE
+# define COMPILE_TYPE(ufunc) debug_break_level > 0 \
+	|| may_break_in_function(ufunc) \
+		? CT_DEBUG \
+		: CT_NONE
 #endif
 
 /*
diff --git a/src/vim9.h b/src/vim9.h
index 67c9a71..0fc1ab5 100644
--- a/src/vim9.h
+++ b/src/vim9.h
@@ -513,14 +513,14 @@
 // Keep in sync with COMPILE_TYPE()
 #ifdef FEAT_PROFILE
 # define INSTRUCTIONS(dfunc) \
-	(debug_break_level > 0 || dfunc->df_ufunc->uf_has_breakpoint \
+	(debug_break_level > 0 || may_break_in_function(dfunc->df_ufunc) \
 	    ? (dfunc)->df_instr_debug \
 	    : ((do_profiling == PROF_YES && (dfunc->df_ufunc)->uf_profiling) \
 		? (dfunc)->df_instr_prof \
 		: (dfunc)->df_instr))
 #else
 # define INSTRUCTIONS(dfunc) \
-	(debug_break_level > 0 || dfunc->df_ufunc->uf_has_breakpoint \
+	(debug_break_level > 0 || may_break_in_function(dfunc->df_ufunc) \
 		? (dfunc)->df_instr_debug \
 		: (dfunc)->df_instr)
 #endif
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 4562a08..fdca977 100644
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -1483,6 +1483,16 @@
     return NULL;
 }
 
+/*
+ * Return TRUE if there might be a breakpoint in "ufunc", which is when a
+ * breakpoint was set in that function or when there is any expression.
+ */
+    int
+may_break_in_function(ufunc_T *ufunc)
+{
+    return ufunc->uf_has_breakpoint || debug_has_expr_breakpoint();
+}
+
     static void
 handle_debug(isn_T *iptr, ectx_T *ectx)
 {
@@ -1498,7 +1508,7 @@
     {
 	linenr_T breakpoint;
 
-	if (!ufunc->uf_has_breakpoint)
+	if (!may_break_in_function(ufunc))
 	    return;
 
 	// check for the next breakpoint if needed