[lldb][Expression] Emit a 'Note' diagnostic that indicates the language used for expression evaluation (#161688)

Depends on:
* https://github.com/llvm/llvm-project/pull/162050

Since it's a 'Note' diagnostic it would only show up when expression
evaluation actually failed. This helps with expression evaluation
failure reports in mixed language environments where it's not quite
clear what language the expression ran as. It may also reduce confusion
around why the expression evaluator ran an expression in a language it
wasn't asked to run (a softer alternative to what I attempted in
https://github.com/llvm/llvm-project/pull/156648).

Here are some example outputs:
```
# Without target
(lldb) expr blah
note: Falling back to default language. Ran expression as 'Objective C++'.

# Stopped in target
(lldb) expr blah
note: Ran expression as 'C++14'.

(lldb) expr -l objc -- blah
note: Expression evaluation in pure Objective-C not supported. Ran expression as 'Objective C++'.

(lldb) expr -l c -- blah
note: Expression evaluation in pure C not supported. Ran expression as 'ISO C++'.

(lldb) expr -l c++14 -- blah
note: Ran expression as 'C++14'

(lldb) expr -l c++20 -- blah
note: Ran expression as 'C++20'

(lldb) expr -l objective-c++ -- blah
note: Ran expression as 'Objective C++'

(lldb) expr -l D -- blah
note: Expression evaluation in D not supported. Falling back to default language. Ran expression as 'Objective C++'.
```

I didn't put the diagnostic on the same line as the inline diagnostic
for now because of implementation convenience, but if reviewers deem
that a blocker I can take a stab at that again.

Also, other language plugins (namely Swift), won't immediately benefit
from this and will have to emit their own diagnistc. I played around
with having a virtual API on `UserExpression` or `ExpressionParser` that
will be called consistently, but by the time we're about to parse the
expression we are already several frames deep into the plugin. Before
(and at the beginning of) the generic `UserExpression::Parse` call we
don't have enough information to notify which language we're going to
parse in (at least for the C++ plugin).

rdar://160297649
rdar://159669244

GitOrigin-RevId: e3620fe0685c656915977d55f822a82090041965
diff --git a/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp b/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
index 3c49c91..6b121c9 100644
--- a/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
+++ b/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.cpp
@@ -74,6 +74,7 @@
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Disassembler.h"
 #include "lldb/Core/Module.h"
+#include "lldb/Expression/DiagnosticManager.h"
 #include "lldb/Expression/IRExecutionUnit.h"
 #include "lldb/Expression/IRInterpreter.h"
 #include "lldb/Host/File.h"
@@ -96,6 +97,7 @@
 #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h"
 #include "Plugins/Platform/MacOSX/PlatformDarwin.h"
 #include "lldb/Utility/XcodeSDK.h"
+#include "lldb/lldb-enumerations.h"
 
 #include <cctype>
 #include <memory>
@@ -527,7 +529,8 @@
 
 static void SetupLangOpts(CompilerInstance &compiler,
                           ExecutionContextScope &exe_scope,
-                          const Expression &expr) {
+                          const Expression &expr,
+                          DiagnosticManager &diagnostic_manager) {
   Log *log = GetLog(LLDBLog::Expressions);
 
   // If the expression is being evaluated in the context of an existing stack
@@ -547,6 +550,9 @@
                      : lldb::eLanguageTypeUnknown),
         lldb_private::Language::GetNameForLanguageType(language));
 
+  lldb::LanguageType language_for_note = language;
+  std::string language_fallback_reason;
+
   LangOptions &lang_opts = compiler.getLangOpts();
 
   switch (language) {
@@ -560,6 +566,10 @@
     // family language, because the expression parser uses features of C++ to
     // capture values.
     lang_opts.CPlusPlus = true;
+
+    language_for_note = lldb::eLanguageTypeC_plus_plus;
+    language_fallback_reason =
+        "Expression evaluation in pure C not supported. ";
     break;
   case lldb::eLanguageTypeObjC:
     lang_opts.ObjC = true;
@@ -567,6 +577,10 @@
     // to "ask for ObjC, get ObjC++" (see comment above).
     lang_opts.CPlusPlus = true;
 
+    language_for_note = lldb::eLanguageTypeObjC_plus_plus;
+    language_fallback_reason =
+        "Expression evaluation in pure Objective-C not supported. ";
+
     // Clang now sets as default C++14 as the default standard (with
     // GNU extensions), so we do the same here to avoid mismatches that
     // cause compiler error when evaluating expressions (e.g. nullptr not found
@@ -607,9 +621,27 @@
     lang_opts.CPlusPlus = true;
     lang_opts.CPlusPlus11 = true;
     compiler.getHeaderSearchOpts().UseLibcxx = true;
+
+    language_for_note = lldb::eLanguageTypeObjC_plus_plus;
+    if (language != language_for_note) {
+      if (language != lldb::eLanguageTypeUnknown)
+        language_fallback_reason = llvm::formatv(
+            "Expression evaluation in {0} not supported. ",
+            lldb_private::Language::GetDisplayNameForLanguageType(language));
+
+      language_fallback_reason +=
+          llvm::formatv("Falling back to default language. ");
+    }
     break;
   }
 
+  diagnostic_manager.AddDiagnostic(
+      llvm::formatv("{0}Ran expression as '{1}'.", language_fallback_reason,
+                    lldb_private::Language::GetDisplayNameForLanguageType(
+                        language_for_note))
+          .str(),
+      lldb::Severity::eSeverityInfo, DiagnosticOrigin::eDiagnosticOriginLLDB);
+
   lang_opts.Bool = true;
   lang_opts.WChar = true;
   lang_opts.Blocks = true;
@@ -687,8 +719,8 @@
 
 ClangExpressionParser::ClangExpressionParser(
     ExecutionContextScope *exe_scope, Expression &expr,
-    bool generate_debug_info, std::vector<std::string> include_directories,
-    std::string filename)
+    bool generate_debug_info, DiagnosticManager &diagnostic_manager,
+    std::vector<std::string> include_directories, std::string filename)
     : ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(),
       m_pp_callbacks(nullptr),
       m_include_directories(std::move(include_directories)),
@@ -754,7 +786,7 @@
   }
 
   // 4. Set language options.
-  SetupLangOpts(*m_compiler, *exe_scope, expr);
+  SetupLangOpts(*m_compiler, *exe_scope, expr, diagnostic_manager);
   auto *clang_expr = dyn_cast<ClangUserExpression>(&m_expr);
   if (clang_expr && clang_expr->DidImportCxxModules()) {
     LLDB_LOG(log, "Adding lang options for importing C++ modules");
diff --git a/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h b/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
index 93e0b00..734ad51 100644
--- a/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
+++ b/source/Plugins/ExpressionParser/Clang/ClangExpressionParser.h
@@ -65,6 +65,7 @@
   ///     diagnostics (i.e. errors, warnings or notes from Clang).
   ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
                         bool generate_debug_info,
+                        DiagnosticManager &diagnostic_manager,
                         std::vector<std::string> include_directories = {},
                         std::string filename = "<clang expression>");
 
diff --git a/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp b/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp
index e4a094f..d2db319 100644
--- a/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp
+++ b/source/Plugins/ExpressionParser/Clang/ClangFunctionCaller.cpp
@@ -189,8 +189,8 @@
   lldb::ProcessSP jit_process_sp(m_jit_process_wp.lock());
   if (jit_process_sp) {
     const bool generate_debug_info = true;
-    auto *clang_parser = new ClangExpressionParser(jit_process_sp.get(), *this,
-                                                   generate_debug_info);
+    auto *clang_parser = new ClangExpressionParser(
+        jit_process_sp.get(), *this, generate_debug_info, diagnostic_manager);
     num_errors = clang_parser->Parse(diagnostic_manager);
     m_parser.reset(clang_parser);
   } else {
diff --git a/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp b/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
index 6b743e2..e8d5ec3 100644
--- a/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
+++ b/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp
@@ -574,7 +574,7 @@
 
   m_parser = std::make_unique<ClangExpressionParser>(
       exe_ctx.GetBestExecutionContextScope(), *this, generate_debug_info,
-      m_include_directories, m_filename);
+      diagnostic_manager, m_include_directories, m_filename);
 
   unsigned num_errors = m_parser->Parse(diagnostic_manager);
 
@@ -818,7 +818,7 @@
   }
 
   ClangExpressionParser parser(exe_ctx.GetBestExecutionContextScope(), *this,
-                               false);
+                               false, diagnostic_manager);
 
   // We have to find the source code location where the user text is inside
   // the transformed expression code. When creating the transformed text, we
diff --git a/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp b/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp
index 1f44200..e698306 100644
--- a/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp
+++ b/source/Plugins/ExpressionParser/Clang/ClangUtilityFunction.cpp
@@ -120,7 +120,7 @@
 
   const bool generate_debug_info = true;
   ClangExpressionParser parser(exe_ctx.GetBestExecutionContextScope(), *this,
-                               generate_debug_info);
+                               generate_debug_info, diagnostic_manager);
 
   unsigned num_errors = parser.Parse(diagnostic_manager);
 
diff --git a/test/API/commands/expression/diagnostics/TestExprDiagnostics.py b/test/API/commands/expression/diagnostics/TestExprDiagnostics.py
index 0cc505a..ec208f2 100644
--- a/test/API/commands/expression/diagnostics/TestExprDiagnostics.py
+++ b/test/API/commands/expression/diagnostics/TestExprDiagnostics.py
@@ -215,8 +215,22 @@
 
             details = diags.GetValueForKey("details")
 
-            # Detail 1/2: undeclared 'a'
+            # Detail 1/3: note: requested expression language
             diag = details.GetItemAtIndex(0)
+            self.assertEqual(str(diag.GetValueForKey("severity")), "note")
+            self.assertEqual(
+                str(diag.GetValueForKey("message")), "Ran expression as 'C++11'."
+            )
+            self.assertEqual(
+                str(diag.GetValueForKey("rendered")), "Ran expression as 'C++11'."
+            )
+            self.assertEqual(str(diag.GetValueForKey("source_location")), "")
+            self.assertEqual(str(diag.GetValueForKey("file")), "")
+            self.assertFalse(diag.GetValueForKey("hidden").GetBooleanValue())
+            self.assertFalse(diag.GetValueForKey("in_user_input").GetBooleanValue())
+
+            # Detail 2/3: undeclared 'a'
+            diag = details.GetItemAtIndex(1)
 
             severity = diag.GetValueForKey("severity")
             message = diag.GetValueForKey("message")
@@ -234,8 +248,8 @@
             self.assertFalse(hidden.GetBooleanValue())
             self.assertTrue(in_user_input.GetBooleanValue())
 
-            # Detail 1/2: undeclared 'b'
-            diag = details.GetItemAtIndex(1)
+            # Detail 3/3: undeclared 'b'
+            diag = details.GetItemAtIndex(2)
             message = diag.GetValueForKey("message")
             self.assertIn("undeclared identifier 'b'", str(message))
 
diff --git a/test/API/python_api/interpreter/TestCommandInterpreterAPI.py b/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
index 1029bdc..01ed11a 100644
--- a/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
+++ b/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
@@ -1,4 +1,4 @@
-"""Test the SBCommandInterpreter APIs."""
+"""tESt the SBCommandInterpreter APIs."""
 
 import json
 import lldb
@@ -156,13 +156,15 @@
         self.assertEqual(transcript[0]["error"], "")
 
         # (lldb) an-unknown-command
-        self.assertEqual(transcript[1],
+        self.assertEqual(
+            transcript[1],
             {
                 "command": "an-unknown-command",
                 # Unresolved commands don't have "commandName"/"commandArguments"
                 "output": "",
                 "error": "error: 'an-unknown-command' is not a valid command.\n",
-            })
+            },
+        )
 
         # (lldb) br s -f main.c -l <line>
         self.assertEqual(transcript[2]["command"], "br s -f main.c -l %d" % self.line)
@@ -175,14 +177,17 @@
         self.assertEqual(transcript[2]["error"], "")
 
         # (lldb) p a
-        self.assertEqual(transcript[3],
+        self.assertEqual(
+            transcript[3],
             {
                 "command": "p a",
                 "commandName": "dwim-print",
                 "commandArguments": "-- a",
                 "output": "",
-                "error": "error: <user expression 0>:1:1: use of undeclared identifier 'a'\n    1 | a\n      | ^\n",
-            })
+                "error": "note: Falling back to default language. Ran expression as 'Objective C++'.\n"
+                "error: <user expression 0>:1:1: use of undeclared identifier 'a'\n    1 | a\n      | ^\n",
+            },
+        )
 
         # (lldb) statistics dump
         self.assertEqual(transcript[4]["command"], "statistics dump")
@@ -203,7 +208,10 @@
         self.assertTrue(ci, VALID_COMMAND_INTERPRETER)
 
         # The setting's default value should be "false"
-        self.runCmd("settings show interpreter.save-transcript", "interpreter.save-transcript (boolean) = false\n")
+        self.runCmd(
+            "settings show interpreter.save-transcript",
+            "interpreter.save-transcript (boolean) = false\n",
+        )
 
     def test_save_transcript_setting_off(self):
         ci = self.dbg.GetCommandInterpreter()
@@ -250,17 +258,37 @@
         structured_data_1 = ci.GetTranscript()
         self.assertTrue(structured_data_1.IsValid())
         self.assertEqual(structured_data_1.GetSize(), 1)
-        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
+        self.assertEqual(
+            structured_data_1.GetItemAtIndex(0)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "version",
+        )
 
         # Run some more commands and get the transcript as structured data again
         self.runCmd("help")
         structured_data_2 = ci.GetTranscript()
         self.assertTrue(structured_data_2.IsValid())
         self.assertEqual(structured_data_2.GetSize(), 2)
-        self.assertEqual(structured_data_2.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
-        self.assertEqual(structured_data_2.GetItemAtIndex(1).GetValueForKey("command").GetStringValue(100), "help")
+        self.assertEqual(
+            structured_data_2.GetItemAtIndex(0)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "version",
+        )
+        self.assertEqual(
+            structured_data_2.GetItemAtIndex(1)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "help",
+        )
 
         # Now, the first structured data should remain unchanged
         self.assertTrue(structured_data_1.IsValid())
         self.assertEqual(structured_data_1.GetSize(), 1)
-        self.assertEqual(structured_data_1.GetItemAtIndex(0).GetValueForKey("command").GetStringValue(100), "version")
+        self.assertEqual(
+            structured_data_1.GetItemAtIndex(0)
+            .GetValueForKey("command")
+            .GetStringValue(100),
+            "version",
+        )
diff --git a/test/Shell/Expr/TestExprLanguageNote.test b/test/Shell/Expr/TestExprLanguageNote.test
new file mode 100644
index 0000000..f3dc592
--- /dev/null
+++ b/test/Shell/Expr/TestExprLanguageNote.test
@@ -0,0 +1,87 @@
+# RUN: split-file %s %t
+# RUN: %clang_host -g %t/main.cpp -o %t.out
+#
+# RUN: %lldb -x -b -o "settings set interpreter.stop-command-source-on-error false" \
+# RUN:       -s %t/no-target.input 2>&1 | FileCheck %s --check-prefix=CHECK-NO-TARGET
+#
+# RUN: %lldb %t.out -x -b -o "settings set interpreter.stop-command-source-on-error false" \
+# RUN:       -s %t/with-target.input 2>&1 | FileCheck %s --check-prefix=CHECK-TARGET
+
+#--- main.cpp
+
+int main() {
+  int x = 10;
+  __builtin_debugtrap();
+}
+
+#--- with-target.input
+
+expr blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Falling back to default language. Ran expression as 'Objective C++'.
+
+run
+
+expr blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Ran expression as 'C++14'.
+
+expr -l objc -- blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Expression evaluation in pure Objective-C not supported. Ran expression as 'Objective C++'.
+
+expr -l c -- blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Expression evaluation in pure C not supported. Ran expression as 'ISO C++'.
+
+expr -l c++14 -- blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Ran expression as 'C++14'
+
+expr -l c++20 -- blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Ran expression as 'C++20'
+
+expr -l objective-c++ -- blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Ran expression as 'Objective C++'
+
+# D uses TypeSystemClang but running expressions in it isn't supported. Test that we warn about this.
+expr -l D -- blah
+
+# CHECK-TARGET: (lldb) expr
+# CHECK-TARGET: note: Expression evaluation in D not supported. Falling back to default language. Ran expression as 'Objective C++'.
+
+expr -l c++17 -- x = 5
+
+# CHECK-TARGET:     (lldb) expr
+# CHECK-TARGET-NOT: note:
+
+expr x = 5
+
+# CHECK-TARGET:     (lldb) expr
+# CHECK-TARGET-NOT: note:
+
+#--- no-target.input
+
+expr blah
+
+# CHECK-NO-TARGET:     (lldb) expr
+# CHECK-NO-TARGET:     note: Falling back to default language. Ran expression as 'Objective C++'.
+
+expr -l c++ -- 1 + 1
+
+# CHECK-NO-TARGET:     (lldb) expr
+# CHECK-NO-TARGET-NOT: note:
+
+expr 1 + 1
+
+# CHECK-NO-TARGET:     (lldb) expr
+# CHECK-NO-TARGET-NOT: note: