Skip to content

Conversation

@mizvekov
Copy link
Contributor

@mizvekov mizvekov commented Apr 8, 2025

This changes the expression diagnostic printer to always add quotes, and removes hardcoded quotes from the diagnostic format strings.

In some cases, a placeholder could be filled by either an expression or as a string. In order to quote this consistently, a new modifier was implemented, which can be used to quote strings.

One diagnostic kind was relying on unquoted expressions in order to generate code suggestions. This diagnostic is converted to use fixit hints instead.

@mizvekov mizvekov self-assigned this Apr 8, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules HLSL HLSL Language Support clang:openmp OpenMP related changes to Clang labels Apr 8, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 8, 2025

@llvm/pr-subscribers-clang-modules
@llvm/pr-subscribers-hlsl

@llvm/pr-subscribers-clang

Author: Matheus Izvekov (mizvekov)

Changes

This changes the expression diagnostic printer to always add quotes, and removes hardcoded quotes from the diagnostic format strings.

In some cases, a placeholder could be filled by either an expression or as a string. In order to quote this consistently, a new modifier was implemented, which can be used to quote strings.

One diagnostic kind was relying on unquoted expressions in order to generate code suggestions. This diagnostic is converted to use fixit hints instead.


Patch is 89.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/134769.diff

51 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/include/clang/Basic/DiagnosticASTKinds.td (+2-2)
  • (modified) clang/include/clang/Basic/DiagnosticParseKinds.td (+1-1)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+38-32)
  • (modified) clang/lib/AST/ASTDiagnostic.cpp (-2)
  • (modified) clang/lib/Basic/Diagnostic.cpp (+17-12)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+4-1)
  • (modified) clang/test/AST/ByteCode/literals.cpp (+1-1)
  • (modified) clang/test/CodeGenCXX/sections.cpp (+3-3)
  • (added) clang/test/FixIt/fixit-bool.cpp (+7)
  • (modified) clang/test/Modules/odr_hash.cpp (+2-2)
  • (modified) clang/test/OpenMP/declare_variant_messages.c (+6-6)
  • (modified) clang/test/OpenMP/declare_variant_messages.cpp (+9-9)
  • (modified) clang/test/OpenMP/distribute_parallel_for_simd_linear_messages.cpp (+2-2)
  • (modified) clang/test/OpenMP/distribute_simd_linear_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/for_linear_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/for_simd_linear_messages.cpp (+3-3)
  • (modified) clang/test/OpenMP/for_simd_misc_messages.c (+1-1)
  • (modified) clang/test/OpenMP/masked_taskloop_simd_linear_messages.cpp (+3-3)
  • (modified) clang/test/OpenMP/master_taskloop_simd_linear_messages.cpp (+3-3)
  • (modified) clang/test/OpenMP/parallel_for_linear_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/parallel_for_simd_linear_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/parallel_for_simd_misc_messages.c (+1-1)
  • (modified) clang/test/OpenMP/parallel_masked_taskloop_simd_linear_messages.cpp (+4-4)
  • (modified) clang/test/OpenMP/parallel_master_taskloop_simd_linear_messages.cpp (+3-3)
  • (modified) clang/test/OpenMP/simd_linear_messages.cpp (+3-3)
  • (modified) clang/test/OpenMP/simd_misc_messages.c (+1-1)
  • (modified) clang/test/OpenMP/target_parallel_for_linear_messages.cpp (+2-2)
  • (modified) clang/test/OpenMP/target_parallel_for_simd_linear_messages.cpp (+2-2)
  • (modified) clang/test/OpenMP/target_simd_linear_messages.cpp (+2-2)
  • (modified) clang/test/OpenMP/target_teams_distribute_parallel_for_simd_linear_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/target_teams_distribute_simd_linear_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/task_affinity_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/task_depend_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/taskloop_simd_linear_messages.cpp (+3-3)
  • (modified) clang/test/OpenMP/teams_distribute_parallel_for_simd_linear_messages.cpp (+1-1)
  • (modified) clang/test/OpenMP/teams_distribute_simd_linear_messages.cpp (+1-1)
  • (modified) clang/test/Sema/code_align.c (+2-2)
  • (modified) clang/test/Sema/warn-int-in-bool-context.c (+12-12)
  • (modified) clang/test/Sema/warn-lifetime-analysis-nocfg.cpp (+13-13)
  • (modified) clang/test/SemaCXX/attr-lifetime-capture-by.cpp (+5-5)
  • (modified) clang/test/SemaCXX/attr-lifetimebound.cpp (+7-7)
  • (modified) clang/test/SemaCXX/cxx2c-pack-indexing.cpp (+7-7)
  • (modified) clang/test/SemaCXX/warn-dangling-local.cpp (+3-3)
  • (modified) clang/test/SemaHLSL/BuiltIns/asuint-errors.hlsl (+4-4)
  • (modified) clang/test/SemaHLSL/BuiltIns/select-errors.hlsl (+1-1)
  • (modified) clang/test/SemaHLSL/BuiltIns/splitdouble-errors.hlsl (+12-12)
  • (modified) clang/test/SemaHLSL/Language/OutputParameters.hlsl (+4-4)
  • (modified) clang/test/SemaHLSL/parameter_modifiers.hlsl (+2-2)
  • (modified) clang/test/SemaOpenCL/to_addr_builtin.cl (+3-3)
  • (modified) clang/utils/TableGen/ClangDiagnosticsEmitter.cpp (+5)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e671183522565..2bf74d52da6c6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -283,6 +283,7 @@ Improvements to Clang's diagnostics
 - Clang now respects the current language mode when printing expressions in
   diagnostics. This fixes a bunch of `bool` being printed as `_Bool`, and also
   a bunch of HLSL types being printed as their C++ equivalents.
+- Clang now consistently quotes expressions in diagnostics.
 - When printing types for diagnostics, clang now doesn't suppress the scopes of
   template arguments contained within nested names.
 - The ``-Wshift-bool`` warning has been added to warn about shifting a boolean. (#GH28334)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 8d69a2f2cf4a3..f73963752bb67 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -143,8 +143,8 @@ def note_constexpr_null_subobject : Note<
   "access array element of|perform pointer arithmetic on|"
   "access real component of|"
   "access imaginary component of}0 null pointer">;
-def note_constexpr_null_callee : Note<
-  "'%0' evaluates to a null function pointer">;
+def note_constexpr_null_callee
+    : Note<"%0 evaluates to a null function pointer">;
 def note_constexpr_function_param_value_unknown : Note<
   "function parameter %0 with unknown value cannot be used in a constant "
   "expression">;
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index f46e7fed28794..7a3cac528a363 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1639,7 +1639,7 @@ def note_omp_ctx_compatible_set_and_selector_for_property
            "'match(%2={%1(%0)})'">;
 def warn_omp_ctx_incompatible_score_for_property
     : Warning<"the context selector '%0' in the context set '%1' cannot have a "
-              "score ('%2'); score ignored">,
+              "score (%quoted2); score ignored">,
       InGroup<OpenMPClauses>;
 def warn_omp_more_one_device_type_clause
     : Warning<"more than one 'device_type' clause is specified">,
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 1ad09aba60935..5b35dc353b968 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -167,9 +167,10 @@ def err_expr_not_string_literal : Error<"expression is not a string literal">;
 def ext_predef_outside_function : Warning<
   "predefined identifier is only valid inside function">,
   InGroup<DiagGroup<"predefined-identifier-outside-function">>;
-def ext_init_from_predefined : ExtWarn<
-  "initializing an array from a '%0' predefined identifier is a Microsoft extension">,
-  InGroup<MicrosoftInitFromPredefined>;
+def ext_init_from_predefined
+    : ExtWarn<"initializing an array from a %0 predefined identifier is a "
+              "Microsoft extension">,
+      InGroup<MicrosoftInitFromPredefined>;
 def ext_string_literal_from_predefined : ExtWarn<
   "expansion of predefined identifier '%0' to a string literal is a Microsoft extension">,
   InGroup<MicrosoftStringLiteralFromPredefined>;
@@ -3019,14 +3020,14 @@ def note_substituted_constraint_expr_is_ill_formed : Note<
   "because substituted constraint expression is ill-formed%0">;
 def note_constraint_references_error
     : Note<"constraint depends on a previously diagnosed expression">;
-def note_atomic_constraint_evaluated_to_false : Note<
-  "%select{and|because}0 '%1' evaluated to false">;
-def note_concept_specialization_constraint_evaluated_to_false : Note<
-  "%select{and|because}0 '%1' evaluated to false">;
+def note_atomic_constraint_evaluated_to_false
+    : Note<"%select{and|because}0 %1 evaluated to false">;
+def note_concept_specialization_constraint_evaluated_to_false
+    : Note<"%select{and|because}0 %1 evaluated to false">;
 def note_single_arg_concept_specialization_constraint_evaluated_to_false : Note<
   "%select{and|because}0 %1 does not satisfy %2">;
-def note_atomic_constraint_evaluated_to_false_elaborated : Note<
-  "%select{and|because}0 '%1' (%2 %3 %4) evaluated to false">;
+def note_atomic_constraint_evaluated_to_false_elaborated
+    : Note<"%select{and|because}0 %1 (%2 %3 %4) evaluated to false">;
 def note_is_deducible_constraint_evaluated_to_false : Note<
   "cannot deduce template arguments for %0 from %1">;
 def err_constrained_virtual_method : Error<
@@ -3046,14 +3047,14 @@ def note_expr_requirement_expr_substitution_error : Note<
   "%select{and|because}0 '%1' would be invalid: %2">;
 def note_expr_requirement_expr_unknown_substitution_error : Note<
   "%select{and|because}0 '%1' would be invalid">;
-def note_expr_requirement_noexcept_not_met : Note<
-  "%select{and|because}0 '%1' may throw an exception">;
+def note_expr_requirement_noexcept_not_met
+    : Note<"%select{and|because}0 %1 may throw an exception">;
 def note_expr_requirement_type_requirement_substitution_error : Note<
   "%select{and|because}0 '%1' would be invalid: %2">;
 def note_expr_requirement_type_requirement_unknown_substitution_error : Note<
   "%select{and|because}0 '%1' would be invalid">;
-def note_expr_requirement_constraints_not_satisfied : Note<
-  "%select{and|because}0 type constraint '%1' was not satisfied:">;
+def note_expr_requirement_constraints_not_satisfied
+    : Note<"%select{and|because}0 type constraint %1 was not satisfied:">;
 def note_expr_requirement_constraints_not_satisfied_simple : Note<
   "%select{and|because}0 %1 does not satisfy %2:">;
 def note_type_requirement_substitution_error : Note<
@@ -4352,9 +4353,9 @@ def note_reference_is_return_value : Note<"%0 returns a reference">;
 
 def note_pointer_declared_here : Note<
   "pointer %0 declared here">;
-def warn_division_sizeof_ptr : Warning<
-  "'%0' will return the size of the pointer, not the array itself">,
-  InGroup<DiagGroup<"sizeof-pointer-div">>;
+def warn_division_sizeof_ptr
+    : Warning<"%0 will return the size of the pointer, not the array itself">,
+      InGroup<DiagGroup<"sizeof-pointer-div">>;
 def warn_division_sizeof_array : Warning<
   "expression does not compute the number of elements in this array; element "
   "type is %0, not %1">,
@@ -5577,8 +5578,9 @@ def note_function_member_spec_matched : Note<
 def err_template_recursion_depth_exceeded : Error<
   "recursive template instantiation exceeded maximum depth of %0">,
   DefaultFatal, NoSFINAE;
-def err_constraint_depends_on_self : Error<
-  "satisfaction of constraint '%0' depends on itself">, NoSFINAE;
+def err_constraint_depends_on_self
+    : Error<"satisfaction of constraint %0 depends on itself">,
+      NoSFINAE;
 def note_template_recursion_depth : Note<
   "use -ftemplate-depth=N to increase recursive template instantiation depth">;
 
@@ -7094,8 +7096,8 @@ def warn_precedence_bitwise_rel : Warning<
   InGroup<Parentheses>;
 def note_precedence_bitwise_first : Note<
   "place parentheses around the %0 expression to evaluate it first">;
-def note_precedence_silence : Note<
-  "place parentheses around the '%0' expression to silence this warning">;
+def note_precedence_silence : Note<"place parentheses around the %quoted0 "
+                                   "expression to silence this warning">;
 
 def warn_precedence_conditional : Warning<
   "operator '?:' has lower precedence than '%0'; '%0' will be evaluated first">,
@@ -7113,9 +7115,11 @@ def warn_consecutive_comparison : Warning<
 def warn_enum_constant_in_bool_context : Warning<
   "converting the enum constant to a boolean">,
   InGroup<IntInBoolContext>, DefaultIgnore;
-def warn_left_shift_in_bool_context : Warning<
-  "converting the result of '<<' to a boolean; did you mean '(%0) != 0'?">,
-  InGroup<IntInBoolContext>, DefaultIgnore;
+def warn_left_shift_in_bool_context
+    : Warning<"converting the result of '<<' to a boolean; did you mean to "
+              "compare with '0'?">,
+      InGroup<IntInBoolContext>,
+      DefaultIgnore;
 def warn_logical_instead_of_bitwise : Warning<
   "use of logical '%0' with constant operand">,
   InGroup<DiagGroup<"constant-logical-operand">>;
@@ -10308,9 +10312,11 @@ def warn_dangling_pointer_assignment : Warning<
    "object backing %select{|the pointer }0%1 "
    "will be destroyed at the end of the full-expression">,
    InGroup<DanglingAssignment>;
-def warn_dangling_reference_captured : Warning<
-   "object whose reference is captured by '%0' will be destroyed at the end of "
-   "the full-expression">, InGroup<DanglingCapture>;
+def warn_dangling_reference_captured
+    : Warning<"object whose reference is captured by %0 will be destroyed at "
+              "the end of "
+              "the full-expression">,
+      InGroup<DanglingCapture>;
 def warn_dangling_reference_captured_by_unknown : Warning<
    "object whose reference is captured will be destroyed at the end of "
    "the full-expression">, InGroup<DanglingCapture>;
@@ -13009,15 +13015,15 @@ def note_acc_atomic_too_many_stmts
 def note_acc_atomic_expected_binop : Note<"expected binary operation on right "
                                           "hand side of assignment operator">;
 def note_acc_atomic_mismatch_operand
-    : Note<"left hand side of assignment operation('%0') must match one side "
-           "of the sub-operation on the right hand side('%1' and '%2')">;
+    : Note<"left hand side of assignment operation(%0) must match one side "
+           "of the sub-operation on the right hand side(%1 and %2)">;
 def note_acc_atomic_mismatch_compound_operand
     : Note<"variable %select{|in unary expression|on right hand side of "
            "assignment|on left hand side of assignment|on left hand side of "
-           "compound assignment|on left hand side of assignment}2('%3') must "
+           "compound assignment|on left hand side of assignment}2(%3) must "
            "match variable used %select{|in unary expression|on right hand "
            "side of assignment|<not possible>|on left hand side of compound "
-           "assignment|on left hand side of assignment}0('%1') from the first "
+           "assignment|on left hand side of assignment}0(%1) from the first "
            "statement">;
 def err_acc_declare_required_clauses
     : Error<"no valid clauses specified in OpenACC 'declare' directive">;
@@ -13037,9 +13043,9 @@ def err_acc_multiple_references
     : Error<"variable referenced in '%0' clause of OpenACC 'declare' directive "
             "was already referenced">;
 def err_acc_routine_not_func
-    : Error<"OpenACC routine name '%0' does not name a function">;
+    : Error<"OpenACC routine name %0 does not name a function">;
 def err_acc_routine_overload_set
-    : Error<"OpenACC routine name '%0' names a set of overloads">;
+    : Error<"OpenACC routine name %0 names a set of overloads">;
 def err_acc_magic_static_in_routine
     : Error<"function static variables are not permitted in functions to which "
             "an OpenACC 'routine' directive applies">;
diff --git a/clang/lib/AST/ASTDiagnostic.cpp b/clang/lib/AST/ASTDiagnostic.cpp
index ccfef9c7ae361..0c3b30bd264e0 100644
--- a/clang/lib/AST/ASTDiagnostic.cpp
+++ b/clang/lib/AST/ASTDiagnostic.cpp
@@ -512,8 +512,6 @@ void clang::FormatASTNodeDiagnosticArgument(
       const Expr *E = reinterpret_cast<Expr *>(Val);
       assert(E && "Received null Expr!");
       E->printPretty(OS, /*Helper=*/nullptr, Context.getPrintingPolicy());
-      // FIXME: Include quotes when printing expressions.
-      NeedQuotes = false;
       break;
     }
   }
diff --git a/clang/lib/Basic/Diagnostic.cpp b/clang/lib/Basic/Diagnostic.cpp
index 4b4a85aaccf8b..538c1d18a8ac1 100644
--- a/clang/lib/Basic/Diagnostic.cpp
+++ b/clang/lib/Basic/Diagnostic.cpp
@@ -1144,20 +1144,25 @@ FormatDiagnostic(const char *DiagStr, const char *DiagEnd,
 
     switch (Kind) {
     // ---- STRINGS ----
-    case DiagnosticsEngine::ak_std_string: {
-      const std::string &S = getArgStdStr(ArgNo);
-      assert(ModifierLen == 0 && "No modifiers for strings yet");
-      EscapeStringForDiagnostic(S, OutStr);
-      break;
-    }
+    case DiagnosticsEngine::ak_std_string:
     case DiagnosticsEngine::ak_c_string: {
-      const char *S = getArgCStr(ArgNo);
-      assert(ModifierLen == 0 && "No modifiers for strings yet");
-
-      // Don't crash if get passed a null pointer by accident.
-      if (!S)
-        S = "(null)";
+      StringRef S = [&]() -> StringRef {
+        if (Kind == DiagnosticsEngine::ak_std_string)
+          return getArgStdStr(ArgNo);
+        const char *SZ = getArgCStr(ArgNo);
+        // Don't crash if get passed a null pointer by accident.
+        return SZ ? SZ : "(null)";
+      }();
+      bool Quoted = false;
+      if (ModifierIs(Modifier, ModifierLen, "quoted")) {
+        Quoted = true;
+        OutStr.push_back('\'');
+      } else {
+        assert(ModifierLen == 0 && "unknown modifier for string");
+      }
       EscapeStringForDiagnostic(S, OutStr);
+      if (Quoted)
+        OutStr.push_back('\'');
       break;
     }
     // ---- INTEGERS ----
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index bffd0dd461d3d..204c8d94573e7 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -11683,7 +11683,10 @@ static void DiagnoseIntInBoolContext(Sema &S, Expr *E) {
         S.Diag(ExprLoc, diag::warn_left_shift_always)
             << (Result.Val.getInt() != 0);
       else if (E->getType()->isSignedIntegerType())
-        S.Diag(ExprLoc, diag::warn_left_shift_in_bool_context) << E;
+        S.Diag(ExprLoc, diag::warn_left_shift_in_bool_context)
+            << FixItHint::CreateInsertion(E->getBeginLoc(), "(")
+            << FixItHint::CreateInsertion(S.getLocForEndOfToken(E->getEndLoc()),
+                                          ") != 0");
     }
   }
 
diff --git a/clang/test/AST/ByteCode/literals.cpp b/clang/test/AST/ByteCode/literals.cpp
index 68d400bc31dd7..6b33c5cc22367 100644
--- a/clang/test/AST/ByteCode/literals.cpp
+++ b/clang/test/AST/ByteCode/literals.cpp
@@ -920,7 +920,7 @@ namespace CompoundLiterals {
   // null pointer suggests we're doing something odd during constant expression
   // evaluation: I think it's still taking 'x' as being null from the call to
   // f3() rather than tracking the assignment happening in the VLA.
-  constexpr int f3(int *x, int (*y)[*(x=(int[]){1,2,3})]) { // both-warning {{object backing the pointer x will be destroyed at the end of the full-expression}}
+  constexpr int f3(int *x, int (*y)[*(x=(int[]){1,2,3})]) { // both-warning {{object backing the pointer 'x' will be destroyed at the end of the full-expression}}
     return x[0]; // both-note {{read of dereferenced null pointer is not allowed in a constant expression}}
   }
   constexpr int h = f3(0,0); // both-error {{constexpr variable 'h' must be initialized by a constant expression}} \
diff --git a/clang/test/CodeGenCXX/sections.cpp b/clang/test/CodeGenCXX/sections.cpp
index c7fe4e45ea05a..88efd7692b1e0 100644
--- a/clang/test/CodeGenCXX/sections.cpp
+++ b/clang/test/CodeGenCXX/sections.cpp
@@ -18,19 +18,19 @@ int D = 1;
 #pragma data_seg(".data")
 int a = 1;
 extern const Mutable mutable_custom_section;
-const Mutable mutable_custom_section; // expected-warning {{`#pragma const_seg` for section ".my_const" will not apply to 'mutable_custom_section' due to the presence of a mutable field}}
+const Mutable mutable_custom_section; // expected-warning {{`#pragma const_seg` for section '".my_const"' will not apply to 'mutable_custom_section' due to the presence of a mutable field}}
 extern const Normal normal_custom_section;
 const Normal normal_custom_section;
 struct NonTrivialDtor {
   ~NonTrivialDtor();
 };
 extern const NonTrivialDtor non_trivial_dtor_custom_section;
-const NonTrivialDtor non_trivial_dtor_custom_section; // expected-warning {{`#pragma const_seg` for section ".my_const" will not apply to 'non_trivial_dtor_custom_section' due to the presence of a non-trivial destructor}}
+const NonTrivialDtor non_trivial_dtor_custom_section; // expected-warning {{`#pragma const_seg` for section '".my_const"' will not apply to 'non_trivial_dtor_custom_section' due to the presence of a non-trivial destructor}}
 struct NonTrivialCtor {
   NonTrivialCtor();
 };
 extern const NonTrivialCtor non_trivial_ctor_custom_section;
-const NonTrivialCtor non_trivial_ctor_custom_section; // expected-warning {{`#pragma const_seg` for section ".my_const" will not apply to 'non_trivial_ctor_custom_section' due to the presence of a non-trivial constructor}}
+const NonTrivialCtor non_trivial_ctor_custom_section; // expected-warning {{`#pragma const_seg` for section '".my_const"' will not apply to 'non_trivial_ctor_custom_section' due to the presence of a non-trivial constructor}}
 #pragma data_seg(push, label, ".data2")
 extern const int b;
 const int b = 1;
diff --git a/clang/test/FixIt/fixit-bool.cpp b/clang/test/FixIt/fixit-bool.cpp
new file mode 100644
index 0000000000000..ddc8f7f7cdd22
--- /dev/null
+++ b/clang/test/FixIt/fixit-bool.cpp
@@ -0,0 +1,7 @@
+// RUN: %clang_cc1 -fdiagnostics-parseable-fixits -std=c++26 -Wint-in-bool-context %s 2>&1 | FileCheck %s
+
+int x;
+bool t1 = x << x;
+// CHECK-LABEL: 4:13: warning: converting the result of '<<' to a boolean
+// CHECK: fix-it:"{{.*}}":{4:11-4:11}:"("
+// CHECK-NEXT: fix-it:"{{.*}}":{4:17-4:17}:") != 0"
diff --git a/clang/test/Modules/odr_hash.cpp b/clang/test/Modules/odr_hash.cpp
index b0f5b904f2b39..da24b1fe8729a 100644
--- a/clang/test/Modules/odr_hash.cpp
+++ b/clang/test/Modules/odr_hash.cpp
@@ -3380,8 +3380,8 @@ struct S17 {
 };
 #else
 S17 s17;
-// [email protected]:* {{'FunctionTemplate::S17' has different definitions in different modules; first difference is definition in module 'SecondModule' found function template 'foo' with 1st template parameter with default argument 1 + 1}}
-// [email protected]:* {{but in 'FirstModule' found function template 'foo' with 1st template parameter with default argument 2}}
+// [email protected]:* {{'FunctionTemplate::S17' has different definitions in different modules; first difference is definition in module 'SecondModule' found function template 'foo' with 1st template parameter with default argument '1 + 1'}}
+// [email protected]:* {{but in 'FirstModule' found function template 'foo' with 1st template parameter with default argument '2'}}
 #endif
 
 #if defined(FIRST)
diff --git a/clang/test/OpenMP/declare_variant_messages.c b/clang/test/OpenMP/declare_variant_messages.c
index 14637d8200671..32e365cc415bd 100644
--- a/clang/test/OpenMP/declare_variant_messages.c
+++ b/clang/test/OpenMP/declare_variant_messages.c
@@ -34,7 +34,7 @@ int foo(void);
 #pragma omp declare variant(foo) match(implementation={vendor(score ibm)}) // expected-error {{expected '(' after 'score'}} expected-warning {{expected '':'' after the score expression; '':'' assumed}}
 #pragma omp declare variant(foo) match(implementation={vendor(score( ibm)}) // expected-error {{use of undeclared identifier 'ibm'}} expected-error {{expected ')'}} expected-warning {{expected '':'' after the score expression; '':'' assumed}} expected-warning {{expected identifier or string literal describing a context property; property skipped}} expected-note {{context property options are: 'amd' 'arm' 'bsc' 'cray' 'fujitsu' 'gnu' 'ibm' 'intel' 'llvm' 'nec' 'nvidia' 'pgi' 'ti' 'unknown'}} expected-note {{to match this '('}}
 #pragma omp declare variant(foo) match(implementation={vendor(score(2 ibm)}) // expected-error {{expected ')'}} expected-error {{expected ')'}} expected-warning {{expected '':'' after the score expression; '':'' assumed}} expected-warning {{expected identifier or string literal describing a context property; property skipped}} expected-note {{to match this '('}} expected-note {{context property options are: 'amd' 'arm' 'bsc' 'cray' 'fujitsu' 'gnu' 'ibm' 'intel' 'llvm' 'nec' 'nvidia' 'pgi' 'ti' 'unknown'}} expected-note {{to match this '('}}
-#pragma omp declare variant(foo) match(implementation={vendor(score(foo()) ibm)}) // expected-warning {{expected '':'' after the score expression; '':'' assumed}} expected-warning {{score expressions in the OpenMP context se...
[truncated]

Copy link
Contributor

@zyn0217 zyn0217 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working productively.

Comment on lines +1149 to +1162
StringRef S = [&]() -> StringRef {
if (Kind == DiagnosticsEngine::ak_std_string)
return getArgStdStr(ArgNo);
const char *SZ = getArgCStr(ArgNo);
// Don't crash if get passed a null pointer by accident.
return SZ ? SZ : "(null)";
}();
bool Quoted = false;
if (ModifierIs(Modifier, ModifierLen, "quoted")) {
Quoted = true;
OutStr.push_back('\'');
} else {
assert(ModifierLen == 0 && "unknown modifier for string");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding quotes in the DiagnosticEngine, can we teach our tablegen to generate quotes for %quote specifier?

Of course I wouldn't insist if that would end up too much churn to the implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be somewhat novel for us to do string modification of the diagnostic text in tablegen, I think setting it here is sensible. But @AaronBallman probably can take a look here and give an opinion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the modifiers are specified in tablegen so it can validate its input, but the semantics are currently all implemented outside.

Ideally we could always quote the strings here, same as everything else, but the problem is that currently there are a bunch of places which are assembling the diagnostic prose itself from strings, which is something that should not happen, as it makes the job of localization much more difficult.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are a bunch of places which are assembling the diagnostic prose itself from strings

Like concept diagnostics?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that's one example.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to assert we don't end up with double quotes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not impossible, but we suffer from the problem generally, and is one reason we should be pointing to things, not printing them.

Comment on lines +11686 to +11691
S.Diag(ExprLoc, diag::warn_left_shift_in_bool_context)
<< FixItHint::CreateInsertion(E->getBeginLoc(), "(")
<< FixItHint::CreateInsertion(S.getLocForEndOfToken(E->getEndLoc()),
") != 0");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is that related?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explained in the commit message, but this would look incorrect with quotes as it was pasting code together.

Comment on lines +1149 to +1162
StringRef S = [&]() -> StringRef {
if (Kind == DiagnosticsEngine::ak_std_string)
return getArgStdStr(ArgNo);
const char *SZ = getArgCStr(ArgNo);
// Don't crash if get passed a null pointer by accident.
return SZ ? SZ : "(null)";
}();
bool Quoted = false;
if (ModifierIs(Modifier, ModifierLen, "quoted")) {
Quoted = true;
OutStr.push_back('\'');
} else {
assert(ModifierLen == 0 && "unknown modifier for string");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to assert we don't end up with double quotes?

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please be sure to update

Formatting a Diagnostic Argument
as well so the new %quoted specifier is explicitly documented.

@mizvekov mizvekov force-pushed the users/mizvekov/diag-expr-quoted branch from 270ce96 to 36a48fa Compare April 8, 2025 16:55
Comment on lines +146 to +147
def note_constexpr_null_callee
: Note<"%0 evaluates to a null function pointer">;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity: do you have a formatter running for td files? I hardly remembered clang-format would handle it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, clang-format gained support for these files a while back.

int a = 1;
extern const Mutable mutable_custom_section;
const Mutable mutable_custom_section; // expected-warning {{`#pragma const_seg` for section ".my_const" will not apply to 'mutable_custom_section' due to the presence of a mutable field}}
const Mutable mutable_custom_section; // expected-warning {{`#pragma const_seg` for section '".my_const"' will not apply to 'mutable_custom_section' due to the presence of a mutable field}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks suspicious. Is '" what we want?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is printing the string expression, instead of resolving it to the actual section name.

I think that this was bad to start with, and the fact this now gets quoted doesn't make that much worse.

I was trying to avoid fixing yet another pre-existing diagnostic issue and making this patch more complicated than it strictly needs to be.

Copy link
Contributor

@zyn0217 zyn0217 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@mizvekov
Copy link
Contributor Author

I plan to merge this tomorrow if there are no further comments.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woops! Thought I'd already approved this.

@mizvekov mizvekov force-pushed the users/mizvekov/diag-expr-quoted branch 2 times, most recently from 7a77796 to 7fe930d Compare April 15, 2025 05:37
This changes the expression diagnostic printer to always add quotes,
and removes hardcoded quotes from the diagnostic format strings.

In some cases, a placeholder could be filled by either an expression
or as a string. In order to quote this consistently, a new modifier
was added, which can be used to quote strings.

One diagnostic was relying on unquoted expressions in order to
generate code suggestions. This diagnostic is converted to use fixit
hints instead.
@mizvekov mizvekov force-pushed the users/mizvekov/diag-expr-quoted branch from 7fe930d to e2c4004 Compare April 15, 2025 05:58
@mizvekov mizvekov merged commit fceb9ce into main Apr 15, 2025
10 of 12 checks passed
@mizvekov mizvekov deleted the users/mizvekov/diag-expr-quoted branch April 15, 2025 07:18
@llvm-ci
Copy link
Collaborator

llvm-ci commented Apr 15, 2025

LLVM Buildbot has detected a new failure on builder lldb-aarch64-ubuntu running on linaro-lldb-aarch64-ubuntu while building clang at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/59/builds/16037

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteAttach.py (1199 of 2123)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteCompletion.py (1200 of 2123)
UNSUPPORTED: lldb-api :: tools/lldb-server/TestGdbRemoteFork.py (1201 of 2123)
UNSUPPORTED: lldb-api :: tools/lldb-server/TestGdbRemoteForkNonStop.py (1202 of 2123)
UNSUPPORTED: lldb-api :: tools/lldb-server/TestGdbRemoteForkResume.py (1203 of 2123)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteExitCode.py (1204 of 2123)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteHostInfo.py (1205 of 2123)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteModuleInfo.py (1206 of 2123)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteAuxvSupport.py (1207 of 2123)
UNRESOLVED: lldb-api :: tools/lldb-dap/variables/TestDAP_variables.py (1208 of 2123)
******************** TEST 'lldb-api :: tools/lldb-dap/variables/TestDAP_variables.py' FAILED ********************
Script:
--
/usr/bin/python3.10 /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./lib --env LLVM_INCLUDE_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/include --env LLVM_TOOLS_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin --arch aarch64 --build-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex --lldb-module-cache-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/lldb --compiler /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/clang --dsymutil /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin --lldb-obj-root /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/tools/lldb --lldb-libs-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./lib /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/tools/lldb-dap/variables -p TestDAP_variables.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision fceb9cecdf6264eb773ee826b72a51a9ec68ec74)
  clang revision fceb9cecdf6264eb773ee826b72a51a9ec68ec74
  llvm revision fceb9cecdf6264eb773ee826b72a51a9ec68ec74
Skipping the following test categories: ['libc++', 'dsym', 'gmodules', 'debugserver', 'objc']

--
Command Output (stderr):
--
UNSUPPORTED: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_darwin_dwarf_missing_obj (TestDAP_variables.TestDAP_variables) (requires one of macosx, darwin, ios, tvos, watchos, bridgeos, iphonesimulator, watchsimulator, appletvsimulator) 
UNSUPPORTED: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_darwin_dwarf_missing_obj_with_symbol_ondemand_enabled (TestDAP_variables.TestDAP_variables) (requires one of macosx, darwin, ios, tvos, watchos, bridgeos, iphonesimulator, watchsimulator, appletvsimulator) 
========= DEBUG ADAPTER PROTOCOL LOGS =========
1744702573.702286243 --> (stdin/stdout) {"command":"initialize","type":"request","arguments":{"adapterID":"lldb-native","clientID":"vscode","columnsStartAt1":true,"linesStartAt1":true,"locale":"en-us","pathFormat":"path","supportsRunInTerminalRequest":true,"supportsVariablePaging":true,"supportsVariableType":true,"supportsStartDebuggingRequest":true,"supportsProgressReporting":true,"$__lldb_sourceInitFile":false},"seq":1}
1744702573.704278946 <-- (stdin/stdout) {"body":{"$__lldb_version":"lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision fceb9cecdf6264eb773ee826b72a51a9ec68ec74)\n  clang revision fceb9cecdf6264eb773ee826b72a51a9ec68ec74\n  llvm revision fceb9cecdf6264eb773ee826b72a51a9ec68ec74","completionTriggerCharacters":["."," ","\t"],"exceptionBreakpointFilters":[{"default":false,"filter":"cpp_catch","label":"C++ Catch"},{"default":false,"filter":"cpp_throw","label":"C++ Throw"},{"default":false,"filter":"objc_catch","label":"Objective-C Catch"},{"default":false,"filter":"objc_throw","label":"Objective-C Throw"}],"supportTerminateDebuggee":true,"supportsBreakpointLocationsRequest":true,"supportsCancelRequest":true,"supportsCompletionsRequest":true,"supportsConditionalBreakpoints":true,"supportsConfigurationDoneRequest":true,"supportsDataBreakpoints":true,"supportsDelayedStackTraceLoading":true,"supportsDisassembleRequest":true,"supportsEvaluateForHovers":true,"supportsExceptionInfoRequest":true,"supportsExceptionOptions":true,"supportsFunctionBreakpoints":true,"supportsHitConditionalBreakpoints":true,"supportsInstructionBreakpoints":true,"supportsLogPoints":true,"supportsModulesRequest":true,"supportsReadMemoryRequest":true,"supportsRestartRequest":true,"supportsSetVariable":true,"supportsStepInTargetsRequest":true,"supportsSteppingGranularity":true,"supportsValueFormattingOptions":true},"command":"initialize","request_seq":1,"seq":0,"success":true,"type":"response"}
1744702573.704492807 --> (stdin/stdout) {"command":"launch","type":"request","arguments":{"program":"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/tools/lldb-dap/variables/TestDAP_variables.test_indexedVariables/a.out","initCommands":["settings clear -all","settings set symbols.enable-external-lookup false","settings set target.inherit-tcc true","settings set target.disable-aslr false","settings set target.detach-on-error false","settings set target.auto-apply-fixits false","settings set plugin.process.gdb-remote.packet-timeout 60","settings set symbols.clang-modules-cache-path \"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api\"","settings set use-color false","settings set show-statusline false"],"disableASLR":false,"enableAutoVariableSummaries":false,"enableSyntheticChildDebugging":false,"displayExtendedBacktrace":false,"commandEscapePrefix":null},"seq":2}
1744702573.704720497 <-- (stdin/stdout) {"body":{"category":"console","output":"Running initCommands:\n"},"event":"output","seq":0,"type":"event"}
1744702573.704746246 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings clear -all\n"},"event":"output","seq":0,"type":"event"}
1744702573.704756975 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set symbols.enable-external-lookup false\n"},"event":"output","seq":0,"type":"event"}
1744702573.704766035 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.inherit-tcc true\n"},"event":"output","seq":0,"type":"event"}
1744702573.704774618 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.disable-aslr false\n"},"event":"output","seq":0,"type":"event"}
1744702573.704782963 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.detach-on-error false\n"},"event":"output","seq":0,"type":"event"}
1744702573.704790831 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.auto-apply-fixits false\n"},"event":"output","seq":0,"type":"event"}
1744702573.704798937 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set plugin.process.gdb-remote.packet-timeout 60\n"},"event":"output","seq":0,"type":"event"}
1744702573.704818964 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set symbols.clang-modules-cache-path \"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api\"\n"},"event":"output","seq":0,"type":"event"}
1744702573.704827309 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set use-color false\n"},"event":"output","seq":0,"type":"event"}
1744702573.704837799 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set show-statusline false\n"},"event":"output","seq":0,"type":"event"}
1744702573.780465126 <-- (stdin/stdout) {"command":"launch","request_seq":2,"seq":0,"success":true,"type":"response"}
1744702573.780519485 <-- (stdin/stdout) {"body":{"isLocalProcess":true,"name":"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/tools/lldb-dap/variables/TestDAP_variables.test_indexedVariables/a.out","startMethod":"launch","systemProcessId":2652545},"event":"process","seq":0,"type":"event"}
1744702573.780528784 <-- (stdin/stdout) {"event":"initialized","seq":0,"type":"event"}
1744702573.780831575 --> (stdin/stdout) {"command":"setBreakpoints","type":"request","arguments":{"source":{"name":"main.cpp","path":"main.cpp"},"sourceModified":false,"lines":[40],"breakpoints":[{"line":40}]},"seq":3}
1744702573.782320976 <-- (stdin/stdout) {"body":{"breakpoints":[{"column":1,"id":1,"instructionReference":"0xAAAADD1F0C54","line":41,"source":{"name":"main.cpp","path":"main.cpp"},"verified":true}]},"command":"setBreakpoints","request_seq":3,"seq":0,"success":true,"type":"response"}

@hnrklssn
Copy link
Member

@mizvekov
We got some downstream failures from this. I was wondering about the case when the expression sometimes is a character literal, but not always. Previously we would get diagnostics like this:

lorem 0 ipsum
lorem 'X' ipsum

while with this change we get:

lorem '0' ipsum
lorem ''X'' ipsum

Is this something we want? The '0' can now be confused for the character literal of the digit 0 instead of the null character code, and the double single quotes can be confused for double quotes (although this should be less of a problem in any monospace font, but it still looks weird).

I'm less interested in changing anyone's stance on this: I'm quite happy with this change overall. I'm more interested in establishing a clear style guide for these cases, so that we can be consistent with the upstream style.

@mizvekov
Copy link
Contributor Author

Yeah, this is something we want to fix.

This problem already existed for the cases we already quoted expressions when printing them.
Now it exists consistently everywhere.

The opposite problem of an expression mixing in with the prose of the diagnostic disappeared though.

I think this is still the direction we want to move on to.

It seems easier to fix the double quote problem, than the reverse case of making the expression stand out in the prose without them.

One solution I have been thinking, is to recognize certain kinds of expressions which would print weirdly because they already have their own delimiters, and printing them with a special case.

For example we could say, instead of ''x'', we say "the character literal 'x'".

We could do similarly for strings as well.

I think this looks better than trying to escape them.

@hnrklssn
Copy link
Member

Right, yeah. Thanks for working on this! We have quite a few diagnostics for bounds safety to help guide users, so good diagnostics are important to us.

hnrklssn added a commit to swiftlang/llvm-project that referenced this pull request Apr 23, 2025
In fceb9ce (llvm#134769) upstream changed the diagnostic engine to always wrap expressions in single quotes. We already had manual wrapping for most of those, so the manual single quotes needed to be removed. In other placed the test cases needed to be updated to reflect the new output.

rdar://149359009
@damyanp damyanp moved this to Closed in HLSL Support Apr 25, 2025
@damyanp damyanp removed this from HLSL Support Jun 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang:openmp OpenMP related changes to Clang clang Clang issues not falling into any other category HLSL HLSL Language Support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants