thakis created this revision.
thakis added a reviewer: rnk.
Herald added a project: LLVM.

OptTable treats arguments starting with / that aren't a known option
as filenames. This means lld-link's and clang-cl's typo correction for
unknown flags didn't do spell checking for misspelled options that start
with /.

I first tried changing OptTable, but that got pretty messy, see PR41787
comments 2 and 3.

Instead, let lld-link's and clang's (including clang-cl's) "file not
found" diagnostic check if a non-existent file looks like it could be a
mis-spelled option, and if so add a "did you mean" suggestion to the
"file not found" diagnostic.

While here, make formatting of a few diagnostics a bit more
self-consistent.

Fixes PR41787.


https://reviews.llvm.org/D62276

Files:
  clang/include/clang/Basic/DiagnosticDriverKinds.td
  clang/include/clang/Driver/Driver.h
  clang/lib/Driver/Driver.cpp
  clang/test/Driver/unknown-arg.c
  lld/COFF/Driver.cpp
  lld/COFF/DriverUtils.cpp
  lld/test/COFF/color-diagnostics.test
  lld/test/COFF/could-not-open.test
  lld/test/COFF/driver.test
  lld/test/COFF/error-limit.test
  lld/test/COFF/nodefaultlib.test
  lld/test/COFF/responsefile.test

Index: lld/test/COFF/responsefile.test
===================================================================
--- lld/test/COFF/responsefile.test
+++ lld/test/COFF/responsefile.test
@@ -12,14 +12,14 @@
 # RUN: echo "blah\foo" > %t.rsp
 # RUN: not lld-link @%t.rsp 2>&1 | \
 # RUN:     FileCheck --check-prefix=DEFRSP %s
-DEFRSP: error: could not open blah\foo
+DEFRSP: error: could not open 'blah\foo'
 
 # RUN: echo "blah\foo" > %t.rsp
 # RUN: not lld-link --rsp-quoting=windows @%t.rsp 2>&1 | \
 # RUN:     FileCheck --check-prefix=WINRSP %s
-WINRSP: error: could not open blah\foo
+WINRSP: error: could not open 'blah\foo'
 
 # RUN: echo "blah\foo" > %t.rsp
 # RUN: not lld-link --rsp-quoting=posix @%t.rsp 2>&1 | \
 # RUN:     FileCheck --check-prefix=POSRSP %s
-POSRSP: error: could not open blahfoo
+POSRSP: error: could not open 'blahfoo'
Index: lld/test/COFF/nodefaultlib.test
===================================================================
--- lld/test/COFF/nodefaultlib.test
+++ lld/test/COFF/nodefaultlib.test
@@ -19,8 +19,8 @@
 # RUN:   /nodefaultlib:std64.lib >& %t.log || true
 # RUN: FileCheck -check-prefix=CHECK3 %s < %t.log
 
-CHECK1: error: could not open hello64.obj: {{[Nn]}}o such file or directory
-CHECK2: error: could not open hello64: {{[Nn]}}o such file or directory
+CHECK1: error: could not open 'hello64.obj': {{[Nn]}}o such file or directory
+CHECK2: error: could not open 'hello64': {{[Nn]}}o such file or directory
 CHECK3: error: undefined symbol: MessageBoxA
 CHECK3-NEXT: >>> referenced by {{.*}}hello64.obj:(main)
 
Index: lld/test/COFF/error-limit.test
===================================================================
--- lld/test/COFF/error-limit.test
+++ lld/test/COFF/error-limit.test
@@ -1,26 +1,26 @@
 RUN: not lld-link 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 \
 RUN:   21 22 2>&1 | FileCheck -check-prefix=DEFAULT %s
 
-DEFAULT:      could not open 01
-DEFAULT:      could not open 20
+DEFAULT:      could not open '01'
+DEFAULT:      could not open '20'
 DEFAULT-NEXT: too many errors emitted, stopping now (use /errorlimit:0 to see all errors)
-DEFAULT-NOT:  could not open 21
+DEFAULT-NOT:  could not open '21'
 
 RUN: not lld-link /errorlimit:5 01 02 03 04 05 06 07 08 09 10 2>&1 \
 RUN:   | FileCheck -check-prefix=LIMIT5 %s
 
-LIMIT5:      could not open 01
-LIMIT5:      could not open 05
+LIMIT5:      could not open '01'
+LIMIT5:      could not open '05'
 LIMIT5-NEXT: too many errors emitted, stopping now (use /errorlimit:0 to see all errors)
-LIMIT5-NOT:  could not open 06
+LIMIT5-NOT:  could not open '06'
 
 RUN: not lld-link /errorlimit:0 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 \
 RUN:   16 17 18 19 20 21 22 2>&1 | FileCheck -check-prefix=UNLIMITED %s
 
-UNLIMITED:     could not open 01
-UNLIMITED:     could not open 20
-UNLIMITED:     could not open 21
-UNLIMITED:     could not open 22
+UNLIMITED:     could not open '01'
+UNLIMITED:     could not open '20'
+UNLIMITED:     could not open '21'
+UNLIMITED:     could not open '22'
 UNLIMITED-NOT: too many errors emitted, stopping now (use /errorlimit:0 to see all errors)
 
 RUN: not lld-link /errorlimit:XYZ 01 02 03 04 05 06 07 08 09 10 11 12 13 14 \
Index: lld/test/COFF/driver.test
===================================================================
--- lld/test/COFF/driver.test
+++ lld/test/COFF/driver.test
@@ -1,6 +1,6 @@
 # RUN: not lld-link nosuchfile.obj >& %t.log
 # RUN: FileCheck -check-prefix=MISSING %s < %t.log
-MISSING: nosuchfile.obj: {{[Nn]}}o such file or directory
+MISSING: 'nosuchfile.obj': {{[Nn]}}o such file or directory
 
 # RUN: lld-link --version | FileCheck -check-prefix=VERSION %s
 VERSION: {{LLD [0-9]+\.[0-9]+}}
@@ -27,3 +27,13 @@
 # RUN: not lld-link -nodefaultlibs 2>&1 | FileCheck -check-prefix=SPELLNODEFAULTLIB %s
 SPELLNODEFAULTLIB: ignoring unknown argument '-nodefaultlibs', did you mean '-nodefaultlib'
 SPELLNODEFAULTLIB: no input files
+
+# RUN: not lld-link /nodefaultlibs 2>&1 | FileCheck -check-prefix=SPELLNODEFAULTLIB_SLASH %s
+SPELLNODEFAULTLIB_SLASH: could not open '/nodefaultlibs': {{.*}}; did you mean '/nodefaultlib'
+SPELLNODEFAULTLIB_SLASH-NOT: no input files
+
+# Getting flags as typo corrections for normal input files is a side effect
+# of how spell checking for /-style flags is implemented.
+# RUN: not lld-link force 2>&1 | FileCheck -check-prefix=SPELLFORCE %s
+SPELLFORCE: could not open 'force': {{.*}}; did you mean '/force'
+SPELLFORCE-NOT: no input files
Index: lld/test/COFF/could-not-open.test
===================================================================
--- lld/test/COFF/could-not-open.test
+++ lld/test/COFF/could-not-open.test
@@ -1,5 +1,5 @@
 RUN: not lld-link 01 2>&1 | FileCheck %s
 
-CHECK:     could not open 01
+CHECK:     could not open '01'
 CHECK-NOT: /machine is not specified
 CHECK-NOT: subsystem must be defined
Index: lld/test/COFF/color-diagnostics.test
===================================================================
--- lld/test/COFF/color-diagnostics.test
+++ lld/test/COFF/color-diagnostics.test
@@ -7,7 +7,7 @@
 # RUN:   | FileCheck -check-prefix=COLOR %s
 
 # COLOR: {{lld-link: .\[0;1;35mwarning: .\[0mignoring unknown argument '-xyz'}}
-# COLOR: {{lld-link: .\[0;1;31merror: .\[0mcould not open /nosuchfile}}
+# COLOR: {{lld-link: .\[0;1;31merror: .\[0mcould not open '/nosuchfile'}}
 
 # RUN: not lld-link /nosuchfile 2>&1 | FileCheck -check-prefix=NOCOLOR %s
 # RUN: not lld-link -color-diagnostics=never /nosuchfile 2>&1 \
@@ -15,4 +15,4 @@
 # RUN: not lld-link -color-diagnostics=always -no-color-diagnostics \
 # RUN:   /nosuchfile 2>&1 | FileCheck -check-prefix=NOCOLOR %s
 
-# NOCOLOR: lld-link: error: could not open /nosuchfile
+# NOCOLOR: lld-link: error: could not open '/nosuchfile'
Index: lld/COFF/DriverUtils.cpp
===================================================================
--- lld/COFF/DriverUtils.cpp
+++ lld/COFF/DriverUtils.cpp
@@ -833,7 +833,8 @@
 
   // Expand response files (arguments in the form of @<filename>)
   // and then parse the argument again.
-  SmallVector<const char *, 256> ExpandedArgv(Argv.data(), Argv.data() + Argv.size());
+  SmallVector<const char *, 256> ExpandedArgv(Argv.data(),
+                                              Argv.data() + Argv.size());
   cl::ExpandResponseFiles(Saver, getQuotingStyle(Args), ExpandedArgv);
   Args = Table.ParseArgs(makeArrayRef(ExpandedArgv).drop_front(), MissingIndex,
                          MissingCount);
Index: lld/COFF/Driver.cpp
===================================================================
--- lld/COFF/Driver.cpp
+++ lld/COFF/Driver.cpp
@@ -203,9 +203,20 @@
   std::string PathStr = Path;
   enqueueTask([=]() {
     auto MBOrErr = Future->get();
-    if (MBOrErr.second)
-      error("could not open " + PathStr + ": " + MBOrErr.second.message());
-    else
+    if (MBOrErr.second) {
+      std::string Error =
+          "could not open '" + PathStr + "': " + MBOrErr.second.message();
+      // Check if the filename is a typo for an option flag. OptTable thinks
+      // that all args that are not known options and that start with / are
+      // filenames, but e.g. `/nodefaultlibs` is more likely a typo for
+      // the option `/nodefaultlib` than a reference to a file in the root
+      // directory.
+      std::string Nearest;
+      if (COFFOptTable().findNearest(PathStr, Nearest) > 1)
+        error(Error);
+      else
+        error(Error + "; did you mean '" + Nearest + "'");
+    } else
       Driver->addBuffer(std::move(MBOrErr.first), WholeArchive);
   });
 }
Index: clang/test/Driver/unknown-arg.c
===================================================================
--- clang/test/Driver/unknown-arg.c
+++ clang/test/Driver/unknown-arg.c
@@ -1,23 +1,25 @@
 // RUN: not %clang %s -cake-is-lie -%0 -%d -HHHH -munknown-to-clang-option -print-stats -funknown-to-clang-option -ifoo -imultilib dir -### 2>&1 | \
-// RUN: FileCheck %s
+// RUN:     FileCheck %s
 // RUN: %clang %s -imultilib dir -### 2>&1 | \
-// RUN: FileCheck %s --check-prefix=MULTILIB
+// RUN:     FileCheck %s --check-prefix=MULTILIB
 // RUN: not %clang %s -stdlibs=foo -hell -version -### 2>&1 | \
-// RUN: FileCheck %s --check-prefix=DID-YOU-MEAN
+// RUN:     FileCheck %s --check-prefix=DID-YOU-MEAN
 // RUN: %clang_cl -cake-is-lie -%0 -%d -HHHH -munknown-to-clang-option -print-stats -funknown-to-clang-option -### -c -- %s 2>&1 | \
-// RUN: FileCheck %s --check-prefix=CL
+// RUN:     FileCheck %s --check-prefix=CL
 // RUN: %clang_cl -Brepo -### -- %s 2>&1 | \
-// RUN: FileCheck %s --check-prefix=CL-DID-YOU-MEAN
+// RUN:     FileCheck %s --check-prefix=CL-DID-YOU-MEAN
+// RUN: %clang_cl /Brepo -### -- %s 2>&1 | \
+// RUN:     FileCheck %s --check-prefix=CL-DID-YOU-MEAN-SLASH
 // RUN: not %clang_cl -cake-is-lie -%0 -%d -HHHH -munknown-to-clang-option -print-stats -funknown-to-clang-option -c -Werror=unknown-argument -### -- %s 2>&1 | \
-// RUN: FileCheck %s --check-prefix=CL-ERROR
+// RUN:     FileCheck %s --check-prefix=CL-ERROR
 // RUN: not %clang_cl -helo -Werror=unknown-argument -### -- %s 2>&1 | \
-// RUN: FileCheck %s --check-prefix=CL-ERROR-DID-YOU-MEAN
+// RUN:     FileCheck %s --check-prefix=CL-ERROR-DID-YOU-MEAN
 // RUN: %clang_cl -cake-is-lie -%0 -%d -HHHH -munknown-to-clang-option -print-stats -funknown-to-clang-option -c -Wno-unknown-argument -### -- %s 2>&1 | \
-// RUN: FileCheck %s --check-prefix=SILENT
+// RUN:     FileCheck %s --check-prefix=SILENT
 // RUN: not %clang -cc1as -hell --version 2>&1 | \
-// RUN: FileCheck %s --check-prefix=CC1AS-DID-YOU-MEAN
+// RUN:     FileCheck %s --check-prefix=CC1AS-DID-YOU-MEAN
 // RUN: not %clang -cc1asphalt -help 2>&1 | \
-// RUN: FileCheck %s --check-prefix=UNKNOWN-INTEGRATED
+// RUN:     FileCheck %s --check-prefix=UNKNOWN-INTEGRATED
 
 // CHECK: error: unknown argument: '-cake-is-lie'
 // CHECK: error: unknown argument: '-%0'
@@ -38,7 +40,8 @@
 // CL: warning: unknown argument ignored in clang-cl: '-munknown-to-clang-option'
 // CL: warning: unknown argument ignored in clang-cl: '-print-stats'
 // CL: warning: unknown argument ignored in clang-cl: '-funknown-to-clang-option'
-// CL-DID-YOU-MEAN: warning: unknown argument ignored in clang-cl '-Brepo' (did you mean '-Brepro'?)
+// CL-DID-YOU-MEAN: warning: unknown argument ignored in clang-cl '-Brepo', did you mean '-Brepro'?
+// CL-DID-YOU-MEAN-SLASH: error: no such file or directory: '/Brepo', did you mean '/Brepro'?
 // CL-ERROR: error: unknown argument ignored in clang-cl: '-cake-is-lie'
 // CL-ERROR: error: unknown argument ignored in clang-cl: '-%0'
 // CL-ERROR: error: unknown argument ignored in clang-cl: '-%d'
@@ -46,7 +49,7 @@
 // CL-ERROR: error: unknown argument ignored in clang-cl: '-munknown-to-clang-option'
 // CL-ERROR: error: unknown argument ignored in clang-cl: '-print-stats'
 // CL-ERROR: error: unknown argument ignored in clang-cl: '-funknown-to-clang-option'
-// CL-ERROR-DID-YOU-MEAN: error: unknown argument ignored in clang-cl '-helo' (did you mean '-help'?)
+// CL-ERROR-DID-YOU-MEAN: error: unknown argument ignored in clang-cl '-helo', did you mean '-help'?
 // SILENT-NOT: error:
 // SILENT-NOT: warning:
 // CC1AS-DID-YOU-MEAN: error: unknown argument '-hell', did you mean '-help'?
Index: clang/lib/Driver/Driver.cpp
===================================================================
--- clang/lib/Driver/Driver.cpp
+++ clang/lib/Driver/Driver.cpp
@@ -1975,11 +1975,9 @@
   }
 }
 
-/// Check that the file referenced by Value exists. If it doesn't,
-/// issue a diagnostic and return false.
-static bool DiagnoseInputExistence(const Driver &D, const DerivedArgList &Args,
-                                   StringRef Value, types::ID Ty) {
-  if (!D.getCheckInputsExist())
+bool Driver::DiagnoseInputExistence(const DerivedArgList &Args, StringRef Value,
+                                    types::ID Ty, bool TypoCorrect) const {
+  if (!getCheckInputsExist())
     return true;
 
   // stdin always exists.
@@ -1995,10 +1993,10 @@
     }
   }
 
-  if (D.getVFS().exists(Path))
+  if (getVFS().exists(Path))
     return true;
 
-  if (D.IsCLMode()) {
+  if (IsCLMode()) {
     if (!llvm::sys::path::is_absolute(Twine(Path)) &&
         llvm::sys::Process::FindInEnvPath("LIB", Value))
       return true;
@@ -2011,7 +2009,26 @@
     }
   }
 
-  D.Diag(clang::diag::err_drv_no_such_file) << Path;
+  if (TypoCorrect) {
+    // Check if the filename is a typo for an option flag. OptTable thinks
+    // that all args that are not known options and that start with / are
+    // filenames, but e.g. `/diagnostic:caret` is more likely a typo for
+    // the option `/diagnostics:caret` than a reference to a file in the root
+    // directory.
+    unsigned IncludedFlagsBitmask;
+    unsigned ExcludedFlagsBitmask;
+    std::tie(IncludedFlagsBitmask, ExcludedFlagsBitmask) =
+        getIncludeExcludeOptionFlagMasks(IsCLMode());
+    std::string Nearest;
+    if (getOpts().findNearest(Value, Nearest, IncludedFlagsBitmask,
+                                ExcludedFlagsBitmask) <= 1) {
+      Diag(clang::diag::err_drv_no_such_file_with_suggestion)
+          << Path << Nearest;
+      return false;
+    }
+  }
+
+  Diag(clang::diag::err_drv_no_such_file) << Path;
   return false;
 }
 
@@ -2128,19 +2145,21 @@
         }
       }
 
-      if (DiagnoseInputExistence(*this, Args, Value, Ty))
+      if (DiagnoseInputExistence(Args, Value, Ty, /*TypoCorrect=*/true))
         Inputs.push_back(std::make_pair(Ty, A));
 
     } else if (A->getOption().matches(options::OPT__SLASH_Tc)) {
       StringRef Value = A->getValue();
-      if (DiagnoseInputExistence(*this, Args, Value, types::TY_C)) {
+      if (DiagnoseInputExistence(Args, Value, types::TY_C,
+                                 /*TypoCorrect=*/false)) {
         Arg *InputArg = MakeInputArg(Args, *Opts, A->getValue());
         Inputs.push_back(std::make_pair(types::TY_C, InputArg));
       }
       A->claim();
     } else if (A->getOption().matches(options::OPT__SLASH_Tp)) {
       StringRef Value = A->getValue();
-      if (DiagnoseInputExistence(*this, Args, Value, types::TY_CXX)) {
+      if (DiagnoseInputExistence(Args, Value, types::TY_CXX,
+                                 /*TypoCorrect=*/false)) {
         Arg *InputArg = MakeInputArg(Args, *Opts, A->getValue());
         Inputs.push_back(std::make_pair(types::TY_CXX, InputArg));
       }
Index: clang/include/clang/Driver/Driver.h
===================================================================
--- clang/include/clang/Driver/Driver.h
+++ clang/include/clang/Driver/Driver.h
@@ -394,6 +394,12 @@
   void BuildUniversalActions(Compilation &C, const ToolChain &TC,
                              const InputList &BAInputs) const;
 
+  /// Check that the file referenced by Value exists. If it doesn't,
+  /// issue a diagnostic and return false.
+  bool DiagnoseInputExistence(const llvm::opt::DerivedArgList &Args,
+                              StringRef Value, types::ID Ty,
+                              bool TypoCorrect) const;
+
   /// BuildJobs - Bind actions to concrete tools and translate
   /// arguments to form the list of jobs to run.
   ///
Index: clang/include/clang/Basic/DiagnosticDriverKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -9,6 +9,8 @@
 let Component = "Driver" in {
 
 def err_drv_no_such_file : Error<"no such file or directory: '%0'">;
+def err_drv_no_such_file_with_suggestion : Error<
+  "no such file or directory: '%0', did you mean '%1'?">;
 def err_drv_unsupported_opt : Error<"unsupported option '%0'">;
 def err_drv_unsupported_opt_with_suggestion
   : Error<"unsupported option '%0', did you mean '%1'?">;
@@ -172,7 +174,7 @@
   "unknown argument ignored in clang-cl: '%0'">,
   InGroup<UnknownArgument>;
 def warn_drv_unknown_argument_clang_cl_with_suggestion : Warning<
-  "unknown argument ignored in clang-cl '%0' (did you mean '%1'?)">,
+  "unknown argument ignored in clang-cl '%0', did you mean '%1'?">,
   InGroup<UnknownArgument>;
 
 def warn_drv_ycyu_different_arg_clang_cl : Warning<
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to