On 04/24/2018 06:27 PM, David Malcolm wrote:
> On Tue, 2018-04-24 at 16:45 +0200, Martin Liška wrote:
>> Hi.
>>
>> Some time ago, I investigated quite new feature of clang which
>> is support of --autocomplete argument. That can be run from bash
>> completion
>> script and one gets more precise completion hints:
>>
>> http://blog.llvm.org/2017/09/clang-bash-better-auto-completion-is.htm
>> l
>> https://www.youtube.com/watch?v=zLPwPdZBpSY
>>
>> I like the idea and I would describe how is that better to current
>> GCC completion
>> script sitting here:
>> https://github.com/scop/bash-completion/blob/master/completions/gcc
>>
>> 1) gcc -fsanitize=^ - no support for option enum values
>> 2) gcc -fno-sa^ - no support for negative options
>> 3) gcc --param=^ - no support for param names
>>
>> These are main limitations I see. I'm attaching working prototype,
>> which you
>> can test by installed GCC, setting it on $PATH and doing:
>> $ source gcc.sh
>>
>> Then bash completion is provided via the newly added option. Some
>> examples:
>>
>> 1)
>> $ gcc -fsanitize=
>> address                    bounds                     enum           
>>             integer-divide-by-zero     nonnull-
>> attribute          pointer-
>> compare            return                     shift-
>> base                 thread                     vla-bound
>> alignment                  bounds-strict              float-cast-
>> overflow        kernel-
>> address             null                       pointer-
>> overflow           returns-nonnull-attribute  shift-
>> exponent             undefined                  vptr
>> bool                       builtin                    float-divide-
>> by-zero       leak                       object-
>> size                pointer-
>> subtract           shift                      signed-integer-
>> overflow    unreachable                
>>
>> 2)
>> $ gcc -fno-ipa-
>> -fno-ipa-bit-cp         -fno-ipa-cp-alignment   -fno-ipa-
>> icf            -fno-ipa-icf-variables  -fno-ipa-profile        -fno-
>> ipa-pure-const     -fno-ipa-reference      -fno-ipa-struct-reorg   
>> -fno-ipa-cp             -fno-ipa-cp-clone       -fno-ipa-icf-
>> functions  -fno-ipa-matrix-reorg   -fno-ipa-pta            -fno-ipa-
>> ra             -fno-ipa-sra            -fno-ipa-vrp            
>>
>> 3)
>> $ gcc --param=lto-
>> lto-max-partition  lto-min-partition  lto-partitions    
>>
>> 4)
>> gcc --param lto-
>> lto-max-partition  lto-min-partition  lto-partitions     
>>
>> The patch is quite lean and if people like, I will prepare a proper
>> patch submission. I know about some limitations that can be then
>> handled incrementally.
>>
>> Thoughts?
>> Martin
> 
> Overall, looks good (albeit with various nits).  I like how you're
> reusing the m_option_suggestions machinery from the misspelled options
> code.  There are some awkward issues e.g. arch-specific completions,
> lang-specific completions, custom option-handling etc, but given that
> as-is this patch seems to be an improvement over the status quo, I'd
> prefer to tackle those later.

I'm sending second version of the patch. I did isolation of 
m_option_suggestions machinery
to a separate file. Mainly due to selftests that are linked with cc1.

> 
> The patch doesn't have tests.  There would need to be some way to
> achieve test coverage for the completion code (especially as we start
> to tackle the more interesting cases).  I wonder what the best way to
> do that is; perhaps a combination of selftest and DejaGnu?  (e.g. what
> about arch-specific completions? what about the interaction with bash?
> etc)

For now I come up with quite some selftests. Integration with bash&DejaGNU
would be challenging.

> 
> A few nits:
> * Do we want to hardcode that logging path in gcc.sh?

Sure, that needs to be purged. Crucial question here is where the gcc.sh script
should live. Note that clang has it in: ./tools/clang/utils/bash-autocomplete.sh
and:

head -n1 ./tools/clang/utils/bash-autocomplete.sh
# Please add "source /path/to/bash-autocomplete.sh" to your .bashrc to use this.

Which is not ideal. I would prefer to integrate the script into:
https://github.com/scop/bash-completion/blob/master/completions/gcc

Thoughts?

> 
> * Looks like m_option_suggestions isn't needed for handling the "-
> param" case, so maybe put the param-handling case before the "Lazily
> populate m_option_suggestions" code.
> 
> * You use "l" ("ell") as a variable name in two places, which I don't
> like, as IMHO it's too close to "1" (one) in some fonts.

Fixed both notes.
Thanks for fast review.

Martin

> 
> Thanks
> Dave
> 

>From 80d34e28ac6db6e63a0efe7d389bd9bfde40e434 Mon Sep 17 00:00:00 2001
From: marxin <mli...@suse.cz>
Date: Fri, 23 Feb 2018 12:28:43 +0100
Subject: [PATCH] Come up with new --completion option.

---
 gcc.sh                     |  51 ++++++
 gcc/Makefile.in            |   2 +-
 gcc/c-family/cppspec.c     |   1 +
 gcc/common.opt             |   4 +
 gcc/fortran/gfortranspec.c |   1 +
 gcc/gcc-main.c             |   1 +
 gcc/gcc.c                  | 127 ++------------
 gcc/gcc.h                  |   4 +-
 gcc/opt-proposer.c         | 420 +++++++++++++++++++++++++++++++++++++++++++++
 gcc/opt-proposer.h         |  84 +++++++++
 gcc/opts.c                 |   3 +
 gcc/selftest-run-tests.c   |   1 +
 gcc/selftest.c             |  33 ++++
 gcc/selftest.h             |  21 +++
 14 files changed, 638 insertions(+), 115 deletions(-)
 create mode 100644 gcc.sh
 create mode 100644 gcc/opt-proposer.c
 create mode 100644 gcc/opt-proposer.h

diff --git a/gcc.sh b/gcc.sh
new file mode 100644
index 00000000000..06b16b3152b
--- /dev/null
+++ b/gcc.sh
@@ -0,0 +1,51 @@
+# Please add "source /path/to/bash-autocomplete.sh" to your .bashrc to use this.
+
+log()
+{
+  echo $1 >> /tmp/bash-completion.log
+}
+
+_gcc()
+{
+    local cur prev prev2 words cword argument prefix
+    _init_completion || return
+    _expand || return
+
+    # extract also for situations like: -fsanitize=add
+    if [[ $cword > 2 ]]; then
+      prev2="${COMP_WORDS[$cword - 2]}"
+    fi
+
+    log "cur: '$cur', prev: '$prev': prev2: '$prev2' cword: '$cword'"
+
+    # sample: -fsan
+    if [[ "$cur" == -* ]]; then
+      argument=$cur
+    # sample: -fsanitize=
+    elif [[ "$cur" == "=" && $prev == -* ]]; then
+      argument=$prev$cur
+      prefix=$prev$cur
+    # sample: -fsanitize=add
+    elif [[ "$prev" == "=" && $prev2 == -* ]]; then
+      argument=$prev2$prev$cur
+      prefix=$prev2$prev
+    # sample: --param lto-
+    elif [[ "$prev" == "--param" ]]; then
+      argument="$prev $cur"
+      prefix="$prev "
+    fi
+
+    log  "argument: '$argument', prefix: '$prefix'"
+
+    if [[ "$argument" == "" ]]; then
+      _filedir
+    else
+      # In situation like '-fsanitize=add' $cur is equal to last token.
+      # Thus we need to strip the beginning of suggested option.
+      flags=$( gcc --completion="$argument" 2>/dev/null | sed "s/^$prefix//")
+      log "compgen: $flags"
+      [[ "${flags: -1}" == '=' ]] && compopt -o nospace 2> /dev/null
+      COMPREPLY=( $( compgen -W "$flags" -- "") )
+    fi
+}
+complete -F _gcc gcc
diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 20bee0494b1..26fa3dd17df 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1617,7 +1617,7 @@ OBJS-libcommon = diagnostic.o diagnostic-color.o diagnostic-show-locus.o \
 # compiler and containing target-dependent code.
 OBJS-libcommon-target = $(common_out_object_file) prefix.o params.o \
 	opts.o opts-common.o options.o vec.o hooks.o common/common-targhooks.o \
-	hash-table.o file-find.o spellcheck.o selftest.o
+	hash-table.o file-find.o spellcheck.o selftest.o opt-proposer.o
 
 # This lists all host objects for the front ends.
 ALL_HOST_FRONTEND_OBJS = $(foreach v,$(CONFIG_LANGUAGES),$($(v)_OBJS))
diff --git a/gcc/c-family/cppspec.c b/gcc/c-family/cppspec.c
index 1e0a8bcd294..794b3ced529 100644
--- a/gcc/c-family/cppspec.c
+++ b/gcc/c-family/cppspec.c
@@ -21,6 +21,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "system.h"
 #include "coretypes.h"
 #include "tm.h"
+#include "opt-proposer.h"
 #include "gcc.h"
 #include "opts.h"
 
diff --git a/gcc/common.opt b/gcc/common.opt
index d6ef85928f3..9b4ba28f287 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -254,6 +254,10 @@ Driver Alias(S)
 -compile
 Driver Alias(c)
 
+-completion=
+Common Driver Joined Undocumented
+--param Bash completion.
+
 -coverage
 Driver Alias(coverage)
 
diff --git a/gcc/fortran/gfortranspec.c b/gcc/fortran/gfortranspec.c
index fe1ec0447b3..b95c8810d0f 100644
--- a/gcc/fortran/gfortranspec.c
+++ b/gcc/fortran/gfortranspec.c
@@ -47,6 +47,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
+#include "opt-proposer.h"
 #include "gcc.h"
 #include "opts.h"
 
diff --git a/gcc/gcc-main.c b/gcc/gcc-main.c
index 9e6de743adc..3cfdfdc57fa 100644
--- a/gcc/gcc-main.c
+++ b/gcc/gcc-main.c
@@ -30,6 +30,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "obstack.h"
 #include "intl.h"
 #include "prefix.h"
+#include "opt-proposer.h"
 #include "gcc.h"
 
 /* Implement the top-level "main" within the driver in terms of
diff --git a/gcc/gcc.c b/gcc/gcc.c
index a716f708259..e9207bb9823 100644
--- a/gcc/gcc.c
+++ b/gcc/gcc.c
@@ -36,6 +36,7 @@ compilation is specified by a string called a "spec".  */
 #include "obstack.h"
 #include "intl.h"
 #include "prefix.h"
+#include "opt-proposer.h"
 #include "gcc.h"
 #include "diagnostic.h"
 #include "flags.h"
@@ -220,6 +221,8 @@ static int print_help_list;
 
 static int print_version;
 
+static const char *completion = NULL;
+
 /* Flag indicating whether we should ONLY print the command and
    arguments (like verbose_flag) without executing the command.
    Displayed arguments are quoted so that the generated command
@@ -3818,6 +3821,11 @@ driver_handle_option (struct gcc_options *opts,
       add_linker_option ("--version", strlen ("--version"));
       break;
 
+    case OPT__completion_:
+      validated = true;
+      completion = decoded->arg;
+      break;
+
     case OPT__help:
       print_help_list = 1;
 
@@ -7262,8 +7270,7 @@ compare_files (char *cmpfile[])
 
 driver::driver (bool can_finalize, bool debug) :
   explicit_link_files (NULL),
-  decoded_options (NULL),
-  m_option_suggestions (NULL)
+  decoded_options (NULL)
 {
   env.init (can_finalize, debug);
 }
@@ -7272,14 +7279,6 @@ driver::~driver ()
 {
   XDELETEVEC (explicit_link_files);
   XDELETEVEC (decoded_options);
-  if (m_option_suggestions)
-    {
-      int i;
-      char *str;
-      FOR_EACH_VEC_ELT (*m_option_suggestions, i, str)
-	free (str);
-      delete m_option_suggestions;
-    }
 }
 
 /* driver::main is implemented as a series of driver:: method calls.  */
@@ -7300,6 +7299,12 @@ driver::main (int argc, char **argv)
   maybe_putenv_OFFLOAD_TARGETS ();
   handle_unrecognized_options ();
 
+  if (completion)
+    {
+      m_option_proposer.suggest_completion (completion);
+      return 0;
+    }
+
   if (!maybe_print_and_exit ())
     return 0;
 
@@ -7768,106 +7773,6 @@ driver::maybe_putenv_OFFLOAD_TARGETS () const
   offload_targets = NULL;
 }
 
-/* Helper function for driver::suggest_option.  Populate
-   m_option_suggestions with candidate strings for misspelled options.
-   The strings will be freed by the driver's dtor.  */
-
-void
-driver::build_option_suggestions (void)
-{
-  gcc_assert (m_option_suggestions == NULL);
-  m_option_suggestions = new auto_vec <char *> ();
-
-  /* We build a vec of m_option_suggestions, using add_misspelling_candidates
-     to add copies of strings, without a leading dash.  */
-
-  for (unsigned int i = 0; i < cl_options_count; i++)
-    {
-      const struct cl_option *option = &cl_options[i];
-      const char *opt_text = option->opt_text;
-      switch (i)
-	{
-	default:
-	  if (option->var_type == CLVC_ENUM)
-	    {
-	      const struct cl_enum *e = &cl_enums[option->var_enum];
-	      for (unsigned j = 0; e->values[j].arg != NULL; j++)
-		{
-		  char *with_arg = concat (opt_text, e->values[j].arg, NULL);
-		  add_misspelling_candidates (m_option_suggestions, option,
-					      with_arg);
-		  free (with_arg);
-		}
-	    }
-	  else
-	    add_misspelling_candidates (m_option_suggestions, option,
-					opt_text);
-	  break;
-
-	case OPT_fsanitize_:
-	case OPT_fsanitize_recover_:
-	  /* -fsanitize= and -fsanitize-recover= can take
-	     a comma-separated list of arguments.  Given that combinations
-	     are supported, we can't add all potential candidates to the
-	     vec, but if we at least add them individually without commas,
-	     we should do a better job e.g. correcting
-	       "-sanitize=address"
-	     to
-	       "-fsanitize=address"
-	     rather than to "-Wframe-address" (PR driver/69265).  */
-	  {
-	    for (int j = 0; sanitizer_opts[j].name != NULL; ++j)
-	      {
-		struct cl_option optb;
-		/* -fsanitize=all is not valid, only -fno-sanitize=all.
-		   So don't register the positive misspelling candidates
-		   for it.  */
-		if (sanitizer_opts[j].flag == ~0U && i == OPT_fsanitize_)
-		  {
-		    optb = *option;
-		    optb.opt_text = opt_text = "-fno-sanitize=";
-		    optb.cl_reject_negative = true;
-		    option = &optb;
-		  }
-		/* Get one arg at a time e.g. "-fsanitize=address".  */
-		char *with_arg = concat (opt_text,
-					 sanitizer_opts[j].name,
-					 NULL);
-		/* Add with_arg and all of its variant spellings e.g.
-		   "-fno-sanitize=address" to candidates (albeit without
-		   leading dashes).  */
-		add_misspelling_candidates (m_option_suggestions, option,
-					    with_arg);
-		free (with_arg);
-	      }
-	  }
-	  break;
-	}
-    }
-}
-
-/* Helper function for driver::handle_unrecognized_options.
-
-   Given an unrecognized option BAD_OPT (without the leading dash),
-   locate the closest reasonable matching option (again, without the
-   leading dash), or NULL.
-
-   The returned string is owned by the driver instance.  */
-
-const char *
-driver::suggest_option (const char *bad_opt)
-{
-  /* Lazily populate m_option_suggestions.  */
-  if (!m_option_suggestions)
-    build_option_suggestions ();
-  gcc_assert (m_option_suggestions);
-
-  /* "m_option_suggestions" is now populated.  Use it.  */
-  return find_closest_string
-    (bad_opt,
-     (auto_vec <const char *> *) m_option_suggestions);
-}
-
 /* Reject switches that no pass was interested in.  */
 
 void
@@ -7876,7 +7781,7 @@ driver::handle_unrecognized_options ()
   for (size_t i = 0; (int) i < n_switches; i++)
     if (! switches[i].validated)
       {
-	const char *hint = suggest_option (switches[i].part1);
+	const char *hint = m_option_proposer.suggest_option (switches[i].part1);
 	if (hint)
 	  error ("unrecognized command line option %<-%s%>;"
 		 " did you mean %<-%s%>?",
diff --git a/gcc/gcc.h b/gcc/gcc.h
index ddbf42f78ea..a7606183393 100644
--- a/gcc/gcc.h
+++ b/gcc/gcc.h
@@ -45,8 +45,6 @@ class driver
   void putenv_COLLECT_GCC (const char *argv0) const;
   void maybe_putenv_COLLECT_LTO_WRAPPER () const;
   void maybe_putenv_OFFLOAD_TARGETS () const;
-  void build_option_suggestions (void);
-  const char *suggest_option (const char *bad_opt);
   void handle_unrecognized_options ();
   int maybe_print_and_exit () const;
   bool prepare_infiles ();
@@ -59,7 +57,7 @@ class driver
   char *explicit_link_files;
   struct cl_decoded_option *decoded_options;
   unsigned int decoded_options_count;
-  auto_vec <char *> *m_option_suggestions;
+  option_proposer m_option_proposer;
 };
 
 /* The mapping of a spec function name to the C function that
diff --git a/gcc/opt-proposer.c b/gcc/opt-proposer.c
new file mode 100644
index 00000000000..08379f0b631
--- /dev/null
+++ b/gcc/opt-proposer.c
@@ -0,0 +1,420 @@
+/* Provide option suggestion for --complete option and a misspelled
+   used by a user.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tm.h"
+#include "opts.h"
+#include "params.h"
+#include "spellcheck.h"
+#include "opt-proposer.h"
+#include "selftest.h"
+
+option_proposer::~option_proposer ()
+{
+  if (m_option_suggestions)
+    {
+      int i;
+      char *str;
+      FOR_EACH_VEC_ELT (*m_option_suggestions, i, str)
+       free (str);
+      delete m_option_suggestions;
+    }
+
+  release_completion_results ();
+}
+
+const char *
+option_proposer::suggest_option (const char *bad_opt)
+{
+  /* Lazily populate m_option_suggestions.  */
+  if (!m_option_suggestions)
+    build_option_suggestions ();
+  gcc_assert (m_option_suggestions);
+
+  /* "m_option_suggestions" is now populated.  Use it.  */
+  return find_closest_string
+    (bad_opt,
+     (auto_vec <const char *> *) m_option_suggestions);
+}
+
+void
+option_proposer::build_completions (const char *starting)
+{
+  release_completion_results ();
+
+  /* Bail out for an invalid input.  */
+  if (starting == NULL || starting[0] == '\0')
+    return;
+
+  if (starting[0] == '-')
+    starting++;
+
+  size_t length = strlen (starting);
+
+  /* Handle parameters.  */
+  const char *prefix = "-param";
+  if (length >= strlen (prefix) && strstr (starting, prefix) == starting)
+    {
+      /* We support both '-param-xyz=123' and '-param xyz=123' */
+      starting += strlen (prefix);
+      char separator = starting[0];
+      starting++;
+      if (separator == ' ' || separator == '=')
+	find_param_completions (separator, starting);
+    }
+  else
+    {
+      /* Lazily populate m_option_suggestions.  */
+      if (!m_option_suggestions)
+	build_option_suggestions ();
+      gcc_assert (m_option_suggestions);
+
+      for (unsigned i = 0; i < m_option_suggestions->length (); i++)
+	{
+	  char *candidate = (*m_option_suggestions)[i];
+	  if (strlen (candidate) >= length
+	      && strstr (candidate, starting) == candidate)
+	    m_completion_results.safe_push (concat ("-", candidate, NULL));
+	}
+    }
+}
+
+void
+option_proposer::suggest_completion (const char *starting)
+{
+  build_completions (starting);
+  print_completion_results ();
+  release_completion_results ();
+}
+
+auto_vec <char *> *
+option_proposer::get_completion_suggestions (const char *starting)
+{
+  build_completions (starting);
+  return &m_completion_results;
+}
+
+void
+option_proposer::build_option_suggestions (void)
+{
+  gcc_assert (m_option_suggestions == NULL);
+  m_option_suggestions = new auto_vec <char *> ();
+
+  /* We build a vec of m_option_suggestions, using add_misspelling_candidates
+     to add copies of strings, without a leading dash.  */
+
+  for (unsigned int i = 0; i < cl_options_count; i++)
+    {
+      const struct cl_option *option = &cl_options[i];
+      const char *opt_text = option->opt_text;
+      switch (i)
+	{
+	default:
+	  if (option->var_type == CLVC_ENUM)
+	    {
+	      const struct cl_enum *e = &cl_enums[option->var_enum];
+	      for (unsigned j = 0; e->values[j].arg != NULL; j++)
+		{
+		  char *with_arg = concat (opt_text, e->values[j].arg, NULL);
+		  add_misspelling_candidates (m_option_suggestions, option,
+					      with_arg);
+		  free (with_arg);
+		}
+	    }
+	  else
+	    add_misspelling_candidates (m_option_suggestions, option,
+					opt_text);
+	  break;
+
+	case OPT_fsanitize_:
+	case OPT_fsanitize_recover_:
+	  /* -fsanitize= and -fsanitize-recover= can take
+	     a comma-separated list of arguments.  Given that combinations
+	     are supported, we can't add all potential candidates to the
+	     vec, but if we at least add them individually without commas,
+	     we should do a better job e.g. correcting
+	       "-sanitize=address"
+	     to
+	       "-fsanitize=address"
+	     rather than to "-Wframe-address" (PR driver/69265).  */
+	  {
+	    for (int j = 0; sanitizer_opts[j].name != NULL; ++j)
+	      {
+		struct cl_option optb;
+		/* -fsanitize=all is not valid, only -fno-sanitize=all.
+		   So don't register the positive misspelling candidates
+		   for it.  */
+		if (sanitizer_opts[j].flag == ~0U && i == OPT_fsanitize_)
+		  {
+		    optb = *option;
+		    optb.opt_text = opt_text = "-fno-sanitize=";
+		    optb.cl_reject_negative = true;
+		    option = &optb;
+		  }
+		/* Get one arg at a time e.g. "-fsanitize=address".  */
+		char *with_arg = concat (opt_text,
+					 sanitizer_opts[j].name,
+					 NULL);
+		/* Add with_arg and all of its variant spellings e.g.
+		   "-fno-sanitize=address" to candidates (albeit without
+		   leading dashes).  */
+		add_misspelling_candidates (m_option_suggestions, option,
+					    with_arg);
+		free (with_arg);
+	      }
+	  }
+	  break;
+	}
+    }
+}
+
+void
+option_proposer::find_param_completions (const char separator,
+					 const char *starting)
+{
+  char separator_str[] {separator, '\0'};
+  size_t length = strlen (starting);
+  for (unsigned i = 0; i < get_num_compiler_params (); ++i)
+    {
+      const char *candidate = compiler_params[i].option;
+      if (strlen (candidate) >= length
+	  && strstr (candidate, starting) == candidate)
+	m_completion_results.safe_push (concat ("--param", separator_str,
+						candidate, NULL));
+    }
+}
+
+void
+option_proposer::print_completion_results ()
+{
+  for (unsigned i = 0; i < m_completion_results.length (); i++)
+    printf ("%s\n", m_completion_results[i]);
+}
+
+void
+option_proposer::release_completion_results ()
+{
+  for (unsigned i = 0; i < m_completion_results.length (); i++)
+    free (m_completion_results[i]);
+
+  m_completion_results.truncate (0);
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void
+test_valid_option (option_proposer &proposer, const char *option)
+{
+  auto_vec <char *> *suggestions = proposer.get_completion_suggestions (option);
+  ASSERT_GT (suggestions->length (), 0);
+
+  for (unsigned i = 0; i < suggestions->length (); i++)
+    ASSERT_STR_STARTSWITH ((*suggestions)[i], option);
+}
+
+/* Verify that valid options works correctly.  */
+
+static void
+test_completion_valid_options (option_proposer &proposer)
+{
+  const char *needles[]
+  {
+    "-fno-var-tracking-assignments-toggle",
+    "-fpredictive-commoning",
+    "--param=stack-clash-protection-guard-size",
+    "--param=max-predicted-iterations",
+    "-ftree-loop-distribute-patterns",
+    "-fno-var-tracking",
+    "-Walloc-zero",
+    "--param=ipa-cp-value-list-size",
+    "-Wsync-nand",
+    "-Wno-attributes",
+    "--param=tracer-dynamic-coverage-feedback",
+    "-Wno-format-contains-nul",
+    "-Wnamespaces",
+    "-fisolate-erroneous-paths-attribute",
+    "-Wno-underflow",
+    "-Wtarget-lifetime",
+    "--param=asan-globals",
+    "-Wno-empty-body",
+    "-Wno-odr",
+    "-Wformat-zero-length",
+    "-Wstringop-truncation",
+    "-fno-ipa-vrp",
+    "-fmath-errno",
+    "-Warray-temporaries",
+    "-Wno-unused-label",
+    "-Wreturn-local-addr",
+    "--param=sms-dfa-history",
+    "--param=asan-instrument-reads",
+    "-Wreturn-type",
+    "-Wc++17-compat",
+    "-Wno-effc++",
+    "--param=max-fields-for-field-sensitive",
+    "-fisolate-erroneous-paths-dereference",
+    "-fno-defer-pop",
+    "-Wcast-align=strict",
+    "-foptimize-strlen",
+    "-Wpacked-not-aligned",
+    "-funroll-loops",
+    "-fif-conversion2",
+    "-Wdesignated-init",
+    "--param=max-iterations-computation-cost",
+    "-Wmultiple-inheritance",
+    "-fno-sel-sched-reschedule-pipelined",
+    "-Wassign-intercept",
+    "-Wno-format-security",
+    "-fno-sched-stalled-insns",
+    "-fbtr-bb-exclusive",
+    "-fno-tree-tail-merge",
+    "-Wlong-long",
+    "-Wno-unused-but-set-parameter",
+    NULL
+  };
+
+  for (const char **ptr = needles; *ptr != NULL; ptr++)
+    test_valid_option (proposer, *ptr);
+}
+
+/* Verify that valid parameter works correctly.  */
+
+static void
+test_completion_valid_params (option_proposer &proposer)
+{
+  const char *needles[]
+  {
+    "--param=sched-state-edge-prob-cutoff",
+    "--param=iv-consider-all-candidates-bound",
+    "--param=align-threshold",
+    "--param=prefetch-min-insn-to-mem-ratio",
+    "--param=max-unrolled-insns",
+    "--param=max-early-inliner-iterations",
+    "--param=max-vartrack-reverse-op-size",
+    "--param=ipa-cp-loop-hint-bonus",
+    "--param=tracer-min-branch-ratio",
+    "--param=graphite-max-arrays-per-scop",
+    "--param=sink-frequency-threshold",
+    "--param=max-cse-path-length",
+    "--param=sra-max-scalarization-size-Osize",
+    "--param=prefetch-latency",
+    "--param=dse-max-object-size",
+    "--param=asan-globals",
+    "--param=max-vartrack-size",
+    "--param=case-values-threshold",
+    "--param=max-slsr-cand-scan",
+    "--param=min-insn-to-prefetch-ratio",
+    "--param=tracer-min-branch-probability",
+    "--param sink-frequency-threshold",
+    "--param max-cse-path-length",
+    "--param sra-max-scalarization-size-Osize",
+    "--param prefetch-latency",
+    "--param dse-max-object-size",
+    "--param asan-globals",
+    "--param max-vartrack-size",
+    NULL
+  };
+
+  for (const char **ptr = needles; *ptr != NULL; ptr++)
+    test_valid_option (proposer, *ptr);
+}
+
+/* Return true when EXPECTED is one of completions for OPTION string.  */
+
+static bool
+in_completion_p (option_proposer &proposer, const char *option,
+		 const char *expected)
+{
+  auto_vec <char *> *suggestions = proposer.get_completion_suggestions (option);
+
+  for (unsigned i = 0; i < suggestions->length (); i++)
+    {
+      char *r = (*suggestions)[i];
+      if (strcmp (r, expected) == 0)
+	return true;
+    }
+
+  return false;
+}
+
+static bool
+empty_completion_p (option_proposer &proposer, const char *option)
+{
+  auto_vec <char *> *suggestions = proposer.get_completion_suggestions (option);
+  return suggestions->is_empty ();
+}
+
+/* Verify partial completions.  */
+
+static void
+test_completion_partial_match (option_proposer &proposer)
+{
+  ASSERT_TRUE (in_completion_p (proposer, "-fsani", "-fsanitize=address"));
+  ASSERT_TRUE (in_completion_p (proposer, "-fsani",
+				"-fsanitize-address-use-after-scope"));
+  ASSERT_TRUE (in_completion_p (proposer, "-fipa-icf", "-fipa-icf-functions"));
+  ASSERT_TRUE (in_completion_p (proposer, "-fipa-icf", "-fipa-icf"));
+  ASSERT_TRUE (in_completion_p (proposer, "--param=",
+				"--param=max-vartrack-reverse-op-size"));
+  ASSERT_TRUE (in_completion_p (proposer, "--param ",
+				"--param max-vartrack-reverse-op-size"));
+
+  ASSERT_FALSE (in_completion_p (proposer, "-fipa-icf", "-fipa"));
+  ASSERT_FALSE (in_completion_p (proposer, "-fipa-icf-functions", "-fipa-icf"));
+}
+
+/* Verify that a garbage does not return a completion match.  */
+
+static void
+test_completion_garbage (option_proposer &proposer)
+{
+  ASSERT_TRUE (empty_completion_p (proposer, NULL));
+  ASSERT_TRUE (empty_completion_p (proposer, ""));
+  ASSERT_TRUE (empty_completion_p (proposer, "- "));
+  ASSERT_TRUE (empty_completion_p (proposer, "123456789"));
+  ASSERT_TRUE (empty_completion_p (proposer, "---------"));
+  ASSERT_TRUE (empty_completion_p (proposer, "#########"));
+  ASSERT_TRUE (empty_completion_p (proposer, "- - - - - -"));
+  ASSERT_TRUE (empty_completion_p (proposer, "-fsanitize=address2"));
+
+  ASSERT_FALSE (empty_completion_p (proposer, "-"));
+  ASSERT_FALSE (empty_completion_p (proposer, "-fipa"));
+  ASSERT_FALSE (empty_completion_p (proposer, "--par"));
+}
+
+/* Run all of the selftests within this file.  */
+
+void
+opt_proposer_c_tests ()
+{
+  option_proposer proposer;
+
+  test_completion_valid_options (proposer);
+  test_completion_valid_params (proposer);
+  test_completion_partial_match (proposer);
+  test_completion_garbage (proposer);
+}
+
+} // namespace selftest
+
+#endif /* #if CHECKING_P */
diff --git a/gcc/opt-proposer.h b/gcc/opt-proposer.h
new file mode 100644
index 00000000000..b673f02dc70
--- /dev/null
+++ b/gcc/opt-proposer.h
@@ -0,0 +1,84 @@
+/* Provide option suggestion for --complete option and a misspelled
+   used by a user.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_OPT_PROPOSER_H
+#define GCC_OPT_PROPOSER_H
+
+/* Option proposer is class used by driver in order to provide hints
+   for wrong options provided.  And it's used by --complete option that's
+   intended to be invoked by BASH in order to provide better option
+   completion support.  */
+
+class option_proposer
+{
+public:
+  /* Default constructor.  */
+  option_proposer (): m_option_suggestions (NULL), m_completion_results ()
+  {}
+
+  /* Default destructor.  */
+  ~option_proposer ();
+
+  /* Helper function for driver::handle_unrecognized_options.
+
+     Given an unrecognized option BAD_OPT (without the leading dash),
+     locate the closest reasonable matching option (again, without the
+     leading dash), or NULL.
+
+     The returned string is owned by the option_proposer instance.  */
+  const char *suggest_option (const char *bad_opt);
+
+  /* Print to stdout all options that start with STARTING.  */
+  void suggest_completion (const char *starting);
+
+  /* Return vector with completion suggestions that start with STARTING.
+
+     The returned strings are owned by the option_proposer instance.  */
+  auto_vec <char *> *get_completion_suggestions (const char *starting);
+
+private:
+  /* Helper function for option_proposer::suggest_option.  Populate
+     m_option_suggestions with candidate strings for misspelled options.
+     The strings will be freed by the option_proposer's dtor.  */
+  void build_option_suggestions ();
+
+  /* Build completions that start with STARTING and save them
+     into m_completion_results vector.  */
+  void build_completions (const char *starting);
+
+  /* Find parameter completions for --param format with SEPARATOR.
+     Again, save the completions into m_completion_results.  */
+  void find_param_completions (const char separator, const char *starting);
+
+  /* Print found completions in m_completion_results to stdout.  */
+  void print_completion_results ();
+
+  /* Free content of m_completion_results.  */
+  void release_completion_results ();
+
+private:
+  /* Cache with all suggestions.  */
+  auto_vec <char *> *m_option_suggestions;
+
+  /* Completion cache.  */
+  auto_vec <char *> m_completion_results;
+};
+
+#endif  /* GCC_OPT_PROPOSER_H */
diff --git a/gcc/opts.c b/gcc/opts.c
index 33efcc0d6e7..ed102c05c22 100644
--- a/gcc/opts.c
+++ b/gcc/opts.c
@@ -1982,6 +1982,9 @@ common_handle_option (struct gcc_options *opts,
       opts->x_exit_after_options = true;
       break;
 
+    case OPT__completion_:
+      break;
+
     case OPT_fsanitize_:
       opts->x_flag_sanitize
 	= parse_sanitizer_options (arg, loc, code,
diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c
index fe221ff8946..0b45c479192 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -70,6 +70,7 @@ selftest::run_tests ()
   fibonacci_heap_c_tests ();
   typed_splay_tree_c_tests ();
   unique_ptr_tests_cc_tests ();
+  opt_proposer_c_tests ();
 
   /* Mid-level data structures.  */
   input_c_tests ();
diff --git a/gcc/selftest.c b/gcc/selftest.c
index 5709110c291..4cff89d2909 100644
--- a/gcc/selftest.c
+++ b/gcc/selftest.c
@@ -118,6 +118,39 @@ assert_str_contains (const location &loc,
 	 desc_haystack, desc_needle, val_haystack, val_needle);
 }
 
+/* Implementation detail of ASSERT_STR_STARTSWITH.
+   Use strstr to determine if val_haystack starts with val_needle.
+   ::selftest::pass if it starts.
+   ::selftest::fail if it does not start.  */
+
+void
+assert_str_startswith (const location &loc,
+		       const char *desc_haystack,
+		       const char *desc_needle,
+		       const char *val_haystack,
+		       const char *val_needle)
+{
+  /* If val_haystack is NULL, fail with a custom error message.  */
+  if (val_haystack == NULL)
+    fail_formatted (loc, "ASSERT_STR_STARTSWITH (%s, %s) haystack=NULL",
+		    desc_haystack, desc_needle);
+
+  /* If val_needle is NULL, fail with a custom error message.  */
+  if (val_needle == NULL)
+    fail_formatted (loc,
+		    "ASSERT_STR_STARTSWITH (%s, %s) haystack=\"%s\" needle=NULL",
+		    desc_haystack, desc_needle, val_haystack);
+
+  const char *test = strstr (val_haystack, val_needle);
+  if (test == val_haystack)
+    pass (loc, "ASSERT_STR_STARTSWITH");
+  else
+    fail_formatted
+	(loc, "ASSERT_STR_STARTSWITH (%s, %s) haystack=\"%s\" needle=\"%s\"",
+	 desc_haystack, desc_needle, val_haystack, val_needle);
+}
+
+
 /* Constructor.  Generate a name for the file.  */
 
 named_temp_file::named_temp_file (const char *suffix)
diff --git a/gcc/selftest.h b/gcc/selftest.h
index e3117c6bfc4..b5d7eeef86e 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -78,6 +78,15 @@ extern void assert_str_contains (const location &loc,
 				 const char *val_haystack,
 				 const char *val_needle);
 
+/* Implementation detail of ASSERT_STR_STARTSWITH.  */
+
+extern void assert_str_startswith (const location &loc,
+				   const char *desc_expected,
+				   const char *desc_actual,
+				   const char *val_expected,
+				   const char *val_actual);
+
+
 /* A named temporary file for use in selftests.
    Usable for writing out files, and as the base class for
    temp_source_file.
@@ -216,6 +225,7 @@ extern void wide_int_cc_tests ();
 extern void predict_c_tests ();
 extern void simplify_rtx_c_tests ();
 extern void vec_perm_indices_c_tests ();
+extern void opt_proposer_c_tests ();
 
 extern int num_passes;
 
@@ -401,6 +411,17 @@ extern int num_passes;
 				   (HAYSTACK), (NEEDLE));		\
   SELFTEST_END_STMT
 
+/* Evaluate HAYSTACK and NEEDLE and use strstr to determine if HAYSTACK
+   starts with NEEDLE.
+   ::selftest::pass if starts.
+   ::selftest::fail if does not start.  */
+
+#define ASSERT_STR_STARTSWITH(HAYSTACK, NEEDLE)				    \
+  SELFTEST_BEGIN_STMT							    \
+  ::selftest::assert_str_startswith (SELFTEST_LOCATION, #HAYSTACK, #NEEDLE, \
+				     (HAYSTACK), (NEEDLE));		    \
+  SELFTEST_END_STMT
+
 /* Evaluate PRED1 (VAL1), calling ::selftest::pass if it is true,
    ::selftest::fail if it is false.  */
 
-- 
2.16.3

Reply via email to