diff mbox

[8] C++: hints for missing std:: headers

Message ID 1493063594-51495-1-git-send-email-dmalcolm@redhat.com
State New
Headers show

Commit Message

David Malcolm April 24, 2017, 7:53 p.m. UTC
If the user forgets to include an STL header, then an attempt
to use a class in an explicitly scoped "std::" currently leads to
this error:

test.cc:3:8: error: 'string' is not a member of 'std'
   std::string s ("hello world");
        ^~~~~~

This patch attempts to make this error a bit more user-friendly
by hinting at which header file is missing, for a subset of
STL types/headers (somewhat arbitrarily chosen by me when browsing
cppreference.com).

This turns the above into:

test.cc:3:8: error: 'string' is not a member of 'std'
   std::string s ("hello world");
        ^~~~~~
test.cc:3:8: note: 'std::string' is defined in header '<string>'; did you forget to '#include <string>'?

...and ultimately, once fix-it hints can contain newlines, we can
also provide a fix-it hint, making it easy for an IDE to insert
the missing #include when the user clicks on the error.

Successfully bootstrapped&regrtested on x86_64-pc-linux-gnu.

OK for trunk?

gcc/cp/ChangeLog:
	* name-lookup.c (get_std_name_hint): New function.
	(maybe_suggest_missing_header): New function.
	(suggest_alternative_in_explicit_scope): Call
	maybe_suggest_missing_header.

gcc/testsuite/ChangeLog:
	* g++.dg/lookup/missing-std-include.C: New test file.
---
 gcc/cp/name-lookup.c                              | 109 ++++++++++++++++++++++
 gcc/testsuite/g++.dg/lookup/missing-std-include.C |  29 ++++++
 2 files changed, 138 insertions(+)
 create mode 100644 gcc/testsuite/g++.dg/lookup/missing-std-include.C

Comments

Nathan Sidwell April 25, 2017, 11:39 a.m. UTC | #1
On 04/24/2017 03:53 PM, David Malcolm wrote:
> If the user forgets to include an STL header, then an attempt
> to use a class in an explicitly scoped "std::" currently leads to
> this error:
> 

> +  if (scope == NULL_TREE)
> +    return;
> +  if (TREE_CODE (scope) != NAMESPACE_DECL)
> +    return;
> +  /* We only offer suggestions for the "std" namespace.  */
> +  if (scope != std_node)
> +    return;

You might also consider if the current scope has a using directive 
naming ::std?  That could be a separate patch, if you care.

Ok for trunk.

nathan
diff mbox

Patch

diff --git a/gcc/cp/name-lookup.c b/gcc/cp/name-lookup.c
index eda6db2..0c5df93 100644
--- a/gcc/cp/name-lookup.c
+++ b/gcc/cp/name-lookup.c
@@ -4537,6 +4537,113 @@  suggest_alternatives_for (location_t location, tree name,
   candidates.release ();
 }
 
+/* Subroutine of maybe_suggest_missing_header for handling unrecognized names
+   for some of the most common names within "std::".
+   Given non-NULL NAME, a name for lookup within "std::", return the header
+   name defining it within the C++ Standard Library (without '<' and '>'),
+   or NULL.  */
+
+static const char *
+get_std_name_hint (const char *name)
+{
+  struct std_name_hint
+  {
+    const char *name;
+    const char *header;
+  };
+  static const std_name_hint hints[] = {
+    /* <array>.  */
+    {"array", "array"}, // C++11
+    /* <deque>.  */
+    {"deque", "deque"},
+    /* <forward_list>.  */
+    {"forward_list", "forward_list"},  // C++11
+    /* <fstream>.  */
+    {"basic_filebuf", "fstream"},
+    {"basic_ifstream", "fstream"},
+    {"basic_ofstream", "fstream"},
+    {"basic_fstream", "fstream"},
+    /* <iostream>.  */
+    {"cin", "iostream"},
+    {"cout", "iostream"},
+    {"cerr", "iostream"},
+    {"clog", "iostream"},
+    {"wcin", "iostream"},
+    {"wcout", "iostream"},
+    {"wclog", "iostream"},
+    /* <list>.  */
+    {"list", "list"},
+    /* <map>.  */
+    {"map", "map"},
+    {"multimap", "map"},
+    /* <queue>.  */
+    {"queue", "queue"},
+    {"priority_queue", "queue"},
+    /* <ostream>.  */
+    {"ostream", "ostream"},
+    {"wostream", "ostream"},
+    {"ends", "ostream"},
+    {"flush", "ostream"},
+    {"endl", "ostream"},
+    /* <set>.  */
+    {"set", "set"},
+    {"multiset", "set"},
+    /* <sstream>.  */
+    {"basic_stringbuf", "sstream"},
+    {"basic_istringstream", "sstream"},
+    {"basic_ostringstream", "sstream"},
+    {"basic_stringstream", "sstream"},
+    /* <stack>.  */
+    {"stack", "stack"},
+    /* <string>.  */
+    {"string", "string"},
+    {"wstring", "string"},
+    {"u16string", "string"},
+    {"u32string", "string"},
+    /* <unordered_map>.  */
+    {"unordered_map", "unordered_map"}, // C++11
+    {"unordered_multimap", "unordered_map"}, // C++11
+    /* <unordered_set>.  */
+    {"unordered_set", "unordered_set"}, // C++11
+    {"unordered_multiset", "unordered_set"}, // C++11
+    /* <vector>.  */
+    {"vector", "vector"},
+  };
+  const size_t num_hints = sizeof (hints) / sizeof (hints[0]);
+  for (size_t i = 0; i < num_hints; i++)
+    {
+      if (0 == strcmp (name, hints[i].name))
+	return hints[i].header;
+    }
+  return NULL;
+}
+
+/* Subroutine of suggest_alternative_in_explicit_scope, for use when we have no
+   suggestions to offer.
+   If SCOPE is the "std" namespace, then suggest pertinent header
+   files for NAME.  */
+
+static void
+maybe_suggest_missing_header (location_t location, tree name, tree scope)
+{
+  if (scope == NULL_TREE)
+    return;
+  if (TREE_CODE (scope) != NAMESPACE_DECL)
+    return;
+  /* We only offer suggestions for the "std" namespace.  */
+  if (scope != std_node)
+    return;
+  gcc_assert (TREE_CODE (name) == IDENTIFIER_NODE);
+
+  const char *name_str = IDENTIFIER_POINTER (name);
+  const char *header_hint = get_std_name_hint (name_str);
+  if (header_hint)
+    inform (location,
+	    "%<std::%s%> is defined in header %<<%s>%>;"
+	    " did you forget to %<#include <%s>%>?",
+	    name_str, header_hint, header_hint);
+}
+
 /* Look for alternatives for NAME, an IDENTIFIER_NODE for which name
    lookup failed within the explicitly provided SCOPE.  Suggest the
    the best meaningful candidates (if any) as a fix-it hint.
@@ -4564,6 +4671,8 @@  suggest_alternative_in_explicit_scope (location_t location, tree name,
 			  fuzzy_name);
       return true;
     }
+  else
+    maybe_suggest_missing_header (location, name, scope);
 
   return false;
 }
diff --git a/gcc/testsuite/g++.dg/lookup/missing-std-include.C b/gcc/testsuite/g++.dg/lookup/missing-std-include.C
new file mode 100644
index 0000000..82f994f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/lookup/missing-std-include.C
@@ -0,0 +1,29 @@ 
+void test (void)
+{
+  std::string s ("hello world"); // { dg-error ".string. is not a member of .std." }
+  // { dg-message ".std::string. is defined in header .<string>.; did you forget to .#include <string>.?" "" { target *-*-* } .-1 }
+
+  std::wstring ws ("hello world"); // { dg-error ".wstring. is not a member of .std." }
+  // { dg-message ".std::wstring. is defined in header .<string>.; did you forget to .#include <string>.?" "" { target *-*-* } .-1 }
+
+  std::cout << 10; // { dg-error ".cout. is not a member of .std." }
+  // { dg-message ".std::cout. is defined in header .<iostream>.; did you forget to .#include <iostream>.?" "" { target *-*-* } .-1 }
+
+  int i;
+  std::cin >> i; // { dg-error ".cin. is not a member of .std." }
+  // { dg-message ".std::cin. is defined in header .<iostream>.; did you forget to .#include <iostream>.?" "" { target *-*-* } .-1 }
+
+  std::array a; // { dg-error ".array. is not a member of .std." }
+  // { dg-message ".std::array. is defined in header .<array>.; did you forget to .#include <array>.?" "" { target *-*-* } .-1 }
+
+  std::deque a; // { dg-error ".deque. is not a member of .std." }
+  // { dg-message ".std::deque. is defined in header .<deque>.; did you forget to .#include <deque>.?" "" { target *-*-* } .-1 }
+
+  std::vector<int> v; // { dg-error ".vector. is not a member of .std." }
+  // { dg-message ".std::vector. is defined in header .<vector>.; did you forget to .#include <vector>.?" "" { target *-*-* } .-1 }
+  // { dg-error "expected primary-expression before .int." "" { target *-*-* } .-2 }
+
+  std::list<int> lst;  // { dg-error ".list. is not a member of .std." }
+  // { dg-message ".std::list. is defined in header .<list>.; did you forget to .#include <list>.?" "" { target *-*-* } .-1 }
+  // { dg-error "expected primary-expression before .int." "" { target *-*-* } .-2 }
+}