diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -2242,7 +2242,7 @@ ResetBy>; def fstrict_overflow : Flag<["-"], "fstrict-overflow">, Group; def fsyntax_only : Flag<["-"], "fsyntax-only">, - Flags<[NoXarchOption,CoreOption,CC1Option]>, Group; + Flags<[NoXarchOption,CoreOption,CC1Option,FC1Option]>, Group; def ftabstop_EQ : Joined<["-"], "ftabstop=">, Group; def ftemplate_depth_EQ : Joined<["-"], "ftemplate-depth=">, Group; def ftemplate_depth_ : Joined<["-"], "ftemplate-depth-">, Group; diff --git a/flang/include/flang/Frontend/CompilerInstance.h b/flang/include/flang/Frontend/CompilerInstance.h --- a/flang/include/flang/Frontend/CompilerInstance.h +++ b/flang/include/flang/Frontend/CompilerInstance.h @@ -12,6 +12,7 @@ #include "flang/Frontend/FrontendAction.h" #include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" +#include "flang/Semantics/semantics.h" #include "llvm/Support/raw_ostream.h" namespace Fortran::frontend { @@ -28,6 +29,12 @@ std::shared_ptr parsing_; + /// The stream for diagnostics from Semantics + llvm::raw_ostream *semaOutputStream_ = &llvm::errs(); + + /// The stream for diagnostics from Semantics if owned, otherwise nullptr. + std::unique_ptr ownedSemaOutputStream_; + /// The diagnostics engine instance. llvm::IntrusiveRefCntPtr diagnostics_; @@ -77,6 +84,11 @@ bool HasAllSources() const { return allSources_ != nullptr; } + parser::AllCookedSources &allCookedSources() { + assert(allCookedSources_ && "Compiler instance has no AllCookedSources!"); + return *allCookedSources_; + }; + /// } /// @name Parser Operations /// { @@ -84,6 +96,19 @@ /// Return parsing to be used by Actions. Fortran::parser::Parsing &parsing() const { return *parsing_; } + /// } + /// @name Semantic analysis + /// { + + /// Replace the current stream for verbose output. + void set_semaOutputStream(llvm::raw_ostream &Value); + + /// Replace the current stream for verbose output. + void set_semaOutputStream(std::unique_ptr Value); + + /// Get the current stream for verbose output. + llvm::raw_ostream &semaOutputStream() { return *semaOutputStream_; } + /// } /// @name High-Level Operations /// { diff --git a/flang/include/flang/Frontend/FrontendActions.h b/flang/include/flang/Frontend/FrontendActions.h --- a/flang/include/flang/Frontend/FrontendActions.h +++ b/flang/include/flang/Frontend/FrontendActions.h @@ -25,6 +25,10 @@ void ExecuteAction() override; }; +class ParseSyntaxOnlyAction : public FrontendAction { + void ExecuteAction() override; +}; + } // namespace Fortran::frontend #endif // LLVM_FLANG_FRONTEND_FRONTENDACTIONS_H diff --git a/flang/include/flang/Frontend/FrontendOptions.h b/flang/include/flang/Frontend/FrontendOptions.h --- a/flang/include/flang/Frontend/FrontendOptions.h +++ b/flang/include/flang/Frontend/FrontendOptions.h @@ -1,4 +1,4 @@ -//===- FrontendOptions.h ----------------------------------------*- C -*-===// +//===- FrontendOptions.h ----------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -24,7 +24,11 @@ /// -E mode. PrintPreprocessedInput, - /// TODO: RunPreprocessor, ParserSyntaxOnly, EmitLLVM, EmitLLVMOnly, + + /// -fsyntax-only + ParseSyntaxOnly, + + /// TODO: RunPreprocessor, EmitLLVM, EmitLLVMOnly, /// EmitCodeGenOnly, EmitAssembly, (...) }; @@ -34,6 +38,8 @@ return "InputOutputTest"; case PrintPreprocessedInput: return "PrintPreprocessedInput"; + case ParseSyntaxOnly: + return "ParseSyntaxOnly"; default: return ""; // TODO: diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt --- a/flang/lib/Frontend/CMakeLists.txt +++ b/flang/lib/Frontend/CMakeLists.txt @@ -13,6 +13,8 @@ LINK_LIBS FortranParser + FortranSemantics + FortranCommon clangBasic clangDriver diff --git a/flang/lib/Frontend/CompilerInstance.cpp b/flang/lib/Frontend/CompilerInstance.cpp --- a/flang/lib/Frontend/CompilerInstance.cpp +++ b/flang/lib/Frontend/CompilerInstance.cpp @@ -11,6 +11,7 @@ #include "flang/Frontend/TextDiagnosticPrinter.h" #include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" +#include "flang/Semantics/semantics.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" @@ -39,6 +40,17 @@ invocation_ = std::move(value); } +void CompilerInstance::set_semaOutputStream(raw_ostream &Value) { + ownedSemaOutputStream_.release(); + semaOutputStream_ = &Value; +} + +void CompilerInstance::set_semaOutputStream( + std::unique_ptr Value) { + ownedSemaOutputStream_.swap(Value); + semaOutputStream_ = ownedSemaOutputStream_.get(); +} + void CompilerInstance::AddOutputFile(OutputFile &&outFile) { outputFiles_.push_back(std::move(outFile)); } @@ -140,7 +152,7 @@ act.EndSourceFile(); } } - return true; + return !diagnostics().getClient()->getNumErrors(); } void CompilerInstance::CreateDiagnostics( diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -93,6 +93,9 @@ case clang::driver::options::OPT_E: opts.programAction_ = PrintPreprocessedInput; break; + case clang::driver::options::OPT_fsyntax_only: + opts.programAction_ = ParseSyntaxOnly; + break; // TODO: // case clang::driver::options::OPT_emit_obj: diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -7,10 +7,12 @@ //===----------------------------------------------------------------------===// #include "flang/Frontend/FrontendActions.h" +#include "flang/Common/default-kinds.h" #include "flang/Frontend/CompilerInstance.h" #include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" #include "flang/Parser/source.h" +#include "flang/Semantics/semantics.h" using namespace Fortran::frontend; @@ -68,3 +70,33 @@ return; } } + +void ParseSyntaxOnlyAction::ExecuteAction() { + CompilerInstance &ci = this->instance(); + + // TODO: These should be specifiable by users. For now just use the defaults. + common::LanguageFeatureControl features; + Fortran::common::IntrinsicTypeDefaultKinds defaultKinds; + + // Parse + ci.parsing().Parse(llvm::outs()); + auto &parseTree{*ci.parsing().parseTree()}; + + // Prepare semantics + Fortran::semantics::SemanticsContext semanticsContext{ + defaultKinds, features, ci.allCookedSources()}; + Fortran::semantics::Semantics semantics{ + semanticsContext, parseTree, ci.parsing().cooked().AsCharBlock()}; + + // Run semantic checks + semantics.Perform(); + + // Report the diagnostics from the semantic checks + semantics.EmitMessages(ci.semaOutputStream()); + + if (semantics.AnyFatalError()) { + unsigned DiagID = ci.diagnostics().getCustomDiagID( + clang::DiagnosticsEngine::Error, "semantic errors in %0"); + ci.diagnostics().Report(DiagID) << GetCurrentFileOrBufferName(); + } +} diff --git a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp --- a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -32,6 +32,9 @@ case PrintPreprocessedInput: return std::make_unique(); break; + case ParseSyntaxOnly: + return std::make_unique(); + break; default: break; // TODO: diff --git a/flang/test/Flang-Driver/syntax-only.f90 b/flang/test/Flang-Driver/syntax-only.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Flang-Driver/syntax-only.f90 @@ -0,0 +1,9 @@ +! RUN: not %flang-new -fc1 -fsyntax-only %s 2>&1 | FileCheck %s +! RUN: not %f18 -fparse-only %s 2>&1 | FileCheck %s + +! REQUIRES: new-flang-driver + +! CHECK: IF statement is not allowed in IF statement +! CHECK: semantic errors in {{.*}}syntax-only.f90 +IF (A > 0.0) IF (B < 0.0) A = LOG (A) +END diff --git a/flang/unittests/Frontend/PrintPreprocessedTest.cpp b/flang/unittests/Frontend/PrintPreprocessedTest.cpp --- a/flang/unittests/Frontend/PrintPreprocessedTest.cpp +++ b/flang/unittests/Frontend/PrintPreprocessedTest.cpp @@ -76,4 +76,60 @@ llvm::sys::fs::remove(inputFile); compInst.ClearOutputFiles(/*EraseFiles=*/true); } + +TEST(FrontendAction, ParseSyntaxOnly) { + std::string inputFile = "test-file.f"; + std::error_code ec; + + // 1. Create the input file for the file manager + // AllSources (which is used to manage files inside every compiler instance), + // works with paths. This means that it requires a physical file. Create one. + std::unique_ptr os{ + new llvm::raw_fd_ostream(inputFile, ec, llvm::sys::fs::OF_None)}; + if (ec) + FAIL() << "Fail to create the file need by the test"; + + // Populate the input file with the pre-defined input and flush it. + *(os) << "! if_stmt.f90:\n" + << "IF (A > 0.0) IF (B < 0.0) A = LOG (A)\n" + << "END"; + os.reset(); + + // Get the path of the input file + llvm::SmallString<64> cwd; + if (std::error_code ec = llvm::sys::fs::current_path(cwd)) + FAIL() << "Failed to obtain the current working directory"; + std::string testFilePath(cwd.c_str()); + testFilePath += "/" + inputFile; + + // 2. Prepare the compiler (CompilerInvocation + CompilerInstance) + CompilerInstance compInst; + compInst.CreateDiagnostics(); + auto invocation = std::make_shared(); + invocation->frontendOpts().programAction_ = ParseSyntaxOnly; + + compInst.set_invocation(std::move(invocation)); + compInst.frontendOpts().inputs_.push_back( + FrontendInputFile(testFilePath, Language::Fortran)); + + // 3. Set-up the output stream for the semantic diagnostics. + llvm::SmallVector outputDiagBuffer; + std::unique_ptr outputStream( + new llvm::raw_svector_ostream(outputDiagBuffer)); + compInst.set_semaOutputStream(std::move(outputStream)); + + // 4. Execute the ParseSyntaxOnly action + bool success = ExecuteCompilerInvocation(&compInst); + + // 5. Validate the expected output + EXPECT_FALSE(success); + EXPECT_TRUE(!outputDiagBuffer.empty()); + EXPECT_TRUE( + llvm::StringRef(outputDiagBuffer.data()) + .startswith( + ":2:14: error: IF statement is not allowed in IF statement\n")); + + // 6. Clear the input files. + llvm::sys::fs::remove(inputFile); +} } // namespace