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; 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; def warn_drv_ycyu_different_arg_clang_cl : Warning< 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/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/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: 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: lld/COFF/DriverUtils.cpp =================================================================== --- lld/COFF/DriverUtils.cpp +++ lld/COFF/DriverUtils.cpp @@ -833,7 +833,8 @@ // Expand response files (arguments in the form of @) // and then parse the argument again. - SmallVector ExpandedArgv(Argv.data(), Argv.data() + Argv.size()); + SmallVector ExpandedArgv(Argv.data(), + Argv.data() + Argv.size()); cl::ExpandResponseFiles(Saver, getQuotingStyle(Args), ExpandedArgv); Args = Table.ParseArgs(makeArrayRef(ExpandedArgv).drop_front(), MissingIndex, MissingCount); 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/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/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/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/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/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'