Diagnostics should be worded in terms of the user’s source code, and thesource language, rather than GCC’s own implementation details.
A good diagnostic isactionable: it should assist the user intaking action.
Consider what an end user will want to do when encountering a diagnostic.
Given an error, an end user will think: “How do I fix this?”
Given a warning, an end user will think:
A good diagnostic provides pertinent information to allow the user toeasily answer the above questions.
A perfect compiler would issue a warning on every aspect of the user’ssource code that ought to be fixed, and issue no other warnings.Naturally, this ideal is impossible to achieve.
Warnings should have a goodsignal-to-noise ratio: we should have fewfalse positives (falsely issuing a warning when no warning iswarranted) and fewfalse negatives (failing to issue a warning whenoneis justified).
Note that a false positive can mean, in practice, a warning that theuser doesn’t agree with. Ideally a diagnostic should contain enoughinformation to allow the user to make an informed choice about whetherthey should care (and how to fix it), but a balance must be drawn againstoverloading the user with irrelevant data.
GCC is typically used in two different ways:
Keep both of these styles of usage in mind when implementing diagnostics.
Provide the user with details that allow them to identify what theproblem is. For example, the vaguely-worded message:
demo.c:1:1: warning: 'noinline' attribute ignored [-Wattributes] 1 | int foo __attribute__((noinline)); | ^~~
doesn’t tell the user why the attribute was ignored, or what kind ofentity the compiler thought the attribute was being applied to (thesource location for the diagnostic is also poor;seediscussion ofinput_location).A better message would be:
demo.c:1:24: warning: attribute 'noinline' on variable 'foo' was ignored [-Wattributes] 1 | int foo __attribute__((noinline)); | ~~~ ~~~~~~~~~~~~~~~^~~~~~~~~demo.c:1:24: note: attribute 'noinline' is only applicable to functions
which spells out the missing information (and fixes the locationinformation, as discussed below).
The above example uses a note to avoid a combinatorial explosion of possiblemessages.
It’s worth testing a new warning on many instances of real-world code,written by different people, and seeing what it complains about, andwhat it doesn’t complain about.
This may suggest heuristics that silence common false positives.
It may also suggest ways to improve the precision of the message.
Many diagnostics relate to a mismatch between two different places in theuser’s source code. Examples include:
In each case, the diagnostic should indicateboth pertinentlocations (so that the user can easily see the problem and how to fix it).
The standard way to do this is with a note (viainform). Forexample:
auto_diagnostic_group d; if (warning_at (loc, OPT_Wduplicated_cond, "duplicated %<if%> condition")) inform (EXPR_LOCATION (t), "previously used here");
which leads to:
demo.c: In function 'test':demo.c:5:17: warning: duplicated 'if' condition [-Wduplicated-cond] 5 | else if (flag > 3) | ~~~~~^~~demo.c:3:12: note: previously used here 3 | if (flag > 3) | ~~~~~^~~
Theinform call should be guarded by the return value from thewarning_at call so that the note isn’t emitted when the warningis suppressed.
For cases involving punctuation where the locations might be neareach other, they can be conditionally consolidated viagcc_rich_location::add_location_if_nearby:
auto_diagnostic_group d; gcc_rich_location richloc (primary_loc); bool added secondary = richloc.add_location_if_nearby (secondary_loc); error_at (&richloc, "main message"); if (!added secondary) inform (secondary_loc, "message for secondary");
This will emit either one diagnostic with two locations:
demo.c:42:10: error: main message (foo) ~ ^
or two diagnostics:
demo.c:42:4: error: main message foo) ^ demo.c:40:2: note: message for secondary ( ^
GCC’slocation_t type can support both ordinary locations,and locations relating to a macro expansion.
As of GCC 6, ordinary locations changed from supporting just apoint in the user’s source code to supporting three points: thecaret location, plus a start and a finish:
a = foo && bar; ~~~~^~~~~~ | | | | | finish | caret start
Tokens coming out of libcpp have locations of the formcaret == start,such as forfoo here:
a = foo && bar; ^~~ | | | finish caret == start
Compound expressions should be reported using the location of theexpression as a whole, rather than just of one token within it.
For example, in-Wformat, rather than underlining just the firsttoken of a bad argument:
printf("hello %i %s", (long)0, "world"); ~^ ~ %lithe whole of the expression should be underlined, so that the user caneasily identify what is being referred to:
printf("hello %i %s", (long)0, "world"); ~^ ~~~~~~~ %liAvoid using theinput_location global, and the diagnostic functionsthat implicitly use it—useerror_at andwarning_at ratherthanerror andwarning, and provide the most appropriatelocation_t value available at that phase of the compilation. It’spossible to supply secondarylocation_t values viarich_location.
For example, in the example of imprecise wording above, generating thediagnostic usingwarning:
// BAD: implicitly usesinput_location warning (OPT_Wattributes, "%qE attribute ignored", name);leads to:
// BAD: usesinput_locationdemo.c:1:1: warning: 'noinline' attribute ignored [-Wattributes] 1 | int foo __attribute__((noinline)); | ^~~which thus happened to use the location of theint token, ratherthan that of the attribute. Usingwarning_at with the location ofthe attribute, providing the location of the declaration in questionas a secondary location, and adding a note:
auto_diagnostic_group d; gcc_rich_location richloc (attrib_loc); richloc.add_range (decl_loc); if (warning_at (OPT_Wattributes, &richloc, "attribute %qE on variable %qE was ignored", name)) inform (attrib_loc, "attribute %qE is only applicable to functions");
would lead to:
// OK: use location of attribute, with a secondary locationdemo.c:1:24: warning: attribute 'noinline' on variable 'foo' was ignored [-Wattributes] 1 | int foo __attribute__((noinline)); | ~~~ ~~~~~~~~~~~~~~~^~~~~~~~~demo.c:1:24: note: attribute 'noinline' is only applicable to functions
See thediagnostics section of the GCC coding conventions.
In the C++ front end, when comparing two types in a message, use ‘%H’and ‘%I’ rather than ‘%T’, as this allows the diagnosticssubsystem to highlight differences between template-based types.For example, rather than using ‘%qT’:
// BAD: a pair of %qT used in C++ front end for type comparison error_at (loc, "could not convert %qE from %qT to %qT", expr, TREE_TYPE (expr), type);
which could lead to:
error: could not convert 'map<int, double>()' from 'map<int,double>' to 'map<int,int>'
using ‘%H’ and ‘%I’ (via ‘%qH’ and ‘%qI’):
// OK: compare types in C++ front end via %qH and %qI error_at (loc, "could not convert %qE from %qH to %qI", expr, TREE_TYPE (expr), type);
allows the above output to be simplified to:
error: could not convert 'map<int, double>()' from 'map<[...],double>' to 'map<[...],int>'
where thedouble andint are colorized to highlight them.
Useauto_diagnostic_group when issuing multiple relateddiagnostics (seen in various examples on this page). This informs thediagnostic subsystem that all diagnostics issued within the lifetimeof theauto_diagnostic_group are related. For example,-fdiagnostics-add-output=sarif will treat the first diagnosticemitted within the group as a top-level diagnostic, and all subsequentdiagnostics within the group as its children. Also, if a warning in thegroup is inhibited at nesting depth D, all subsequent notes at that depthor deeper will be inhibited as well, until an error or another warningis emitted, the depth decreases below D, or the group is popped.
Text should be quoted by either using the ‘q’ modifier in a directivesuch as ‘%qE’, or by enclosing the quoted text in a pair of ‘%<’and ‘%>’ directives, and never by using explicit quote characters.The directives handle the appropriate quote characters for each languageand apply the correct color or highlighting.
The following elements should be quoted in GCC diagnostics:
Other elements such as numbers that do not refer to numeric constants thatappear in the source code should not be quoted. For example, in the message:
argument %d of %qE must be a pointer type
since the argument number does not refer to a numerical constant in thesource code it should not be quoted.
As of GCC 15, the diagnostics subsystem has a concept of “highlightcolors”. These should be used to consistently colorize both the textwithin diagnostic messages and underlined ranges of quoted source whenhighlighting mismatches, for all messages with a logically-related groupof diagnostics.
Seediagnostic-highlight-colors.h for symbolic names for colorcodes, covering e.g.
highlight_colors::expected versushighlight_colors::actualhighlight_colors::lhs versushighlight_colors::rhsFor example, given:
error: invalid operands to binary + (have 'S' {aka 'struct s'} and 'T' {aka 'struct t'}) return callee_4a () + callee_4b (); ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~ | | | T {aka struct t} S {aka struct s}highlight_colors::lhs(which equates to the color namehighlight-a)highlight_colors::rhs(which equates to the color namehighlight-b)Doing so ought to make it easier for the user to understand what thediagnostic is telling them.
When issuing followupnote diagnostics, all diagnostics within thegroup should use a consistent scheme to highlight the mismatching elements,so that color contrasts the differences. For example, given:
warning: format ‘%i’ expects argument of type ‘int’, but argument 2 has type ‘const char *’ [-Wformat=] 279 | printf("hello " INT_FMT " world", msg); | ^~~~~~~~ ~~~ | | | const char *note: format string is defined here 278 | #define INT_FMT "%i" | ~^ | | | int | %s%i andint referring to the format string and theexpected type due to it should be colorized ashighlight-a bothin the diagnostics message and in the range quoted in therange.const char * in the diagnostic message and in the quotedrange should be colorized ashighlight-b.This can be implemented by using e.g.highlight_colors::actual andhighlight_colors::expected when adding ranges torich_location instances, and e.g. by using the%e formatcode forpretty_printer to use app_element *, and usingappropriate member functions of pp_element to add colorization.
See theterminology and markup section of the GCC coding conventions.
GCC’s diagnostic subsystem can emitfix-it hints: small suggestededits to the user’s source code.
They are printed by default underneath the code in question. Theycan also be viewed via-fdiagnostics-generate-patch and-fdiagnostics-parseable-fixits. With the latter, an IDEought to be able to offer to automatically apply the suggested fix.
Fix-it hints contain code fragments, and thus they should not be markedfor translation.
Fix-it hints can be added to a diagnostic by using arich_locationrather than alocation_t - the fix-it hints are added to therich_location using one of the variousadd_fixit memberfunctions ofrich_location. They are documented withrich_location inlibcpp/line-map.h.It’s easiest to use thegcc_rich_location subclass ofrich_location found ingcc-rich-location.h, as thisimplicitly supplies theline_table variable.
For example:
if (const char *suggestion = hint.suggestion ()) { gcc_rich_location richloc (location); richloc.add_fixit_replace (suggestion); error_at (&richloc, "%qE does not name a type; did you mean %qs?", id, suggestion); }which can lead to:
spellcheck-typenames.C:73:1: error: 'singed' does not name a type; did you mean 'signed'? 73 | singed char ch; | ^~~~~~ | signed
Non-trivial edits can be built up by adding multiple fix-it hints to onerich_location. It’s best to express the edits in terms of thelocations of individual tokens. Various handy functions for addingfix-it hints for idiomatic C and C++ can be seen ingcc-rich-location.h.
When implementing a fix-it hint, please verify that the suggested editleads to fixed, compilable code. (Unfortunately, this currently must bedone by hand using-fdiagnostics-generate-patch. It would begood to have an automated way of verifying that fix-it hints actually fixthe code).
For example, a “gotcha” here is to forget to add a space when adding amissing reserved word. Consider a C++ fix-it hint that addstypename in front of a template declaration. A naive way toimplement this might be:
gcc_rich_location richloc (loc);// BAD: insertion is missing a trailing spacerichloc.add_fixit_insert_before ("typename");error_at (&richloc, "need %<typename%> before %<%T::%E%> because " "%qT is a dependent scope", parser->scope, id, parser->scope);When applied to the code, this might lead to:
T::type x;
being “corrected” to:
typenameT::type x;
In this case, the correct thing to do is to add a trailing space aftertypename:
gcc_rich_location richloc (loc);// OK: note that here we have a trailing spacerichloc.add_fixit_insert_before ("typename ");error_at (&richloc, "need %<typename%> before %<%T::%E%> because " "%qT is a dependent scope", parser->scope, id, parser->scope);leading to this corrected code:
typename T::type x;
It’s best to express deletion suggestions in terms of deletion fix-ithints, rather than replacement fix-it hints. For example, consider this:
auto_diagnostic_group d; gcc_rich_location richloc (location_of (retval)); tree name = DECL_NAME (arg); richloc.add_fixit_replace (IDENTIFIER_POINTER (name)); warning_at (&richloc, OPT_Wredundant_move, "redundant move in return statement");
which is intended to e.g. replace astd::move with the underlyingvalue:
return std::move (retval); ~~~~~~~~~~^~~~~~~~ retval
where the change has been expressed as replacement, replacingwith the name of the declaration.This works for simple cases, but consider this case:
#ifdef SOME_CONFIG_FLAG# define CONFIGURY_GLOBAL global_a#else# define CONFIGURY_GLOBAL global_b#endifint fn (){ return std::move (CONFIGURY_GLOBAL /* some comment */);}The above implementation erroneously strips out the macro and thecomment in the fix-it hint:
return std::move (CONFIGURY_GLOBAL /* some comment */); ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ global_a
and thus this resulting code:
return global_a;
It’s better to do deletions in terms of deletions; deleting thestd::move ( and the trailing close-paren, leading tothis:
return std::move (CONFIGURY_GLOBAL /* some comment */); ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CONFIGURY_GLOBAL /* some comment */
and thus this result:
return CONFIGURY_GLOBAL /* some comment */;
Unfortunately, the pertinentlocation_t values are not alwaysavailable.
In the rare cases where you need to suggest more than one mutuallyexclusive solution to a problem, this can be done by emittingmultiple notes and callingrich_location::fixits_cannot_be_auto_applied on each note’srich_location. If this is called, then the fix-it hints intherich_location will be printed, but will not be added togenerated patches.