[flang] Add support for submodules
authorTim Keith <tkeith@nvidia.com>
Thu, 2 Aug 2018 23:21:27 +0000 (16:21 -0700)
committerTim Keith <tkeith@nvidia.com>
Thu, 2 Aug 2018 23:21:27 +0000 (16:21 -0700)
Symbols for submodules have `ModuleDetails` with `isSubmodule` set.
Scopes for submodules have `Module` kind and have a parent scope that
is also `Module` kind.

Scopes for modules now contain a mapping of submodule name to scope
so that we can find them without having to search the scope tree or
re-read their `.mod` file.

The module file for submodule `s` with ancestor module `m` is named `m-s.mod`.
The tree structure of scopes means module file writing is now recursive.
Similarly, reading the module file for a submodule may require reading
the module files of its parent and ancestor. `ResolveNames` now requires
the parent scope to be passed in -- it is not always the global scope.

`test_modfiles.sh` now handles an argument that is a filename glob so
that the test can involve multiple files. This allows `modfile09` to
test reading of `.mod` files for modules and submodules.

Original-commit: flang-compiler/f18@2e4424dbc8cc5803d561274dbf70005afe7e65dc
Reviewed-on: https://github.com/flang-compiler/f18/pull/160
Tree-same-pre-rewrite: false

17 files changed:
flang/lib/semantics/mod-file.cc
flang/lib/semantics/mod-file.h
flang/lib/semantics/resolve-names.cc
flang/lib/semantics/resolve-names.h
flang/lib/semantics/scope.cc
flang/lib/semantics/scope.h
flang/lib/semantics/symbol.cc
flang/lib/semantics/symbol.h
flang/test/semantics/CMakeLists.txt
flang/test/semantics/modfile09-a.f90 [new file with mode: 0644]
flang/test/semantics/modfile09-b.f90 [new file with mode: 0644]
flang/test/semantics/modfile09-c.f90 [new file with mode: 0644]
flang/test/semantics/modfile09-d.f90 [new file with mode: 0644]
flang/test/semantics/resolve26.f90 [new file with mode: 0644]
flang/test/semantics/resolve27.f90 [new file with mode: 0644]
flang/test/semantics/test_modfile.sh
flang/tools/f18/f18.cc

index 3bde249..d3766d2 100644 (file)
@@ -34,14 +34,16 @@ using namespace parser::literals;
 static constexpr auto extension{".mod"};
 // The initial characters of a file that identify it as a .mod file.
 static constexpr auto magic{"!mod$"};
-// Construct the path to a module file.
-static std::string ModFilePath(const std::string &, const std::string &);
+
 // Helpers for creating error messages.
 static parser::Message Error(
     const SourceName &, parser::MessageFixedText, const std::string &);
 static parser::Message Error(const SourceName &, parser::MessageFixedText,
     const std::string &, const std::string &);
 
+static const SourceName *GetSubmoduleParent(const parser::Program &);
+static std::string ModFilePath(
+    const std::string &, const SourceName &, const std::string &);
 static void PutEntity(std::ostream &, const Symbol &);
 static void PutObjectEntity(std::ostream &, const Symbol &);
 static void PutProcEntity(std::ostream &, const Symbol &);
@@ -54,41 +56,57 @@ static std::ostream &PutLower(std::ostream &, const std::string &);
 static std::string CheckSum(const std::string &);
 
 bool ModFileWriter::WriteAll() {
-  for (const auto &scope : Scope::globalScope.children()) {
-    if (scope.kind() == Scope::Kind::Module) {
-      auto &symbol{*scope.symbol()};  // symbol must be present for module
-      if (!symbol.test(Symbol::Flag::ModFile)) {
-        WriteOne(symbol);
-      }
-    }
-  }
+  WriteChildren(Scope::globalScope);
   return errors_.empty();
 }
 
-bool ModFileWriter::WriteOne(const Symbol &modSymbol) {
-  CHECK(modSymbol.has<ModuleDetails>());
-  auto name{parser::ToLowerCaseLetters(modSymbol.name().ToString())};
-  std::string path{ModFilePath(dir_, name)};
-  std::ofstream os{path};
-  PutSymbols(*modSymbol.scope());
-  std::string all{GetAsString(name)};
-  auto header{GetHeader(all)};
-  os << header << all;
-  os.close();
-  if (!os) {
-    errors_.emplace_back(
-        "Error writing %s: %s"_err_en_US, path.c_str(), std::strerror(errno));
-    return false;
+void ModFileWriter::WriteChildren(const Scope &scope) {
+  for (const auto &child : scope.children()) {
+    WriteOne(child);
   }
-  return true;
+}
+
+void ModFileWriter::WriteOne(const Scope &scope) {
+  auto *symbol{scope.symbol()};
+  if (scope.kind() != Scope::Kind::Module) {
+    return;
+  }
+  if (!symbol->test(Symbol::Flag::ModFile)) {
+    auto *ancestor{symbol->get<ModuleDetails>().ancestor()};
+    auto ancestorName{ancestor ? ancestor->name().ToString() : ""s};
+    auto path{ModFilePath(dir_, symbol->name(), ancestorName)};
+    std::ofstream os{path};
+    PutSymbols(scope);
+    std::string all{GetAsString(*symbol)};
+    auto header{GetHeader(all)};
+    os << header << all;
+    os.close();
+    if (!os) {
+      errors_.emplace_back(
+          "Error writing %s: %s"_err_en_US, path.c_str(), std::strerror(errno));
+      return;
+    }
+  }
+  WriteChildren(scope);  // write out submodules
 }
 
 // Return the entire body of the module file
 // and clear saved uses, decls, and contains.
-std::string ModFileWriter::GetAsString(const std::string &name) {
+std::string ModFileWriter::GetAsString(const Symbol &symbol) {
   std::stringstream all;
-  all << "module " << name << '\n';
-  all << uses_.str();
+  auto &details{symbol.get<ModuleDetails>()};
+  if (!details.isSubmodule()) {
+    PutLower(all << "module ", symbol);
+  } else {
+    auto *parent{details.parent()->symbol()};
+    auto *ancestor{details.ancestor()->symbol()};
+    PutLower(all << "submodule(", *ancestor);
+    if (parent != ancestor) {
+      PutLower(all << ':', *parent);
+    }
+    PutLower(all << ") ", symbol);
+  }
+  all << '\n' << uses_.str();
   uses_.str(""s);
   all << useExtraAttrs_.str();
   useExtraAttrs_.str(""s);
@@ -348,10 +366,22 @@ std::string CheckSum(const std::string &str) {
   return result;
 }
 
-bool ModFileReader::Read(const SourceName &modName) {
-  auto path{FindModFile(modName)};
+Scope *ModFileReader::Read(const SourceName &name, Scope *ancestor) {
+  std::string ancestorName;  // empty for module
+  if (ancestor) {
+    if (auto *scope{ancestor->FindSubmodule(name)}) {
+      return scope;
+    }
+    ancestorName = ancestor->name().ToString();
+  } else {
+    auto it{Scope::globalScope.find(name)};
+    if (it != Scope::globalScope.end()) {
+      return it->second->scope();
+    }
+  }
+  auto path{FindModFile(name, ancestorName)};
   if (!path.has_value()) {
-    return false;
+    return nullptr;
   }
   // TODO: Construct parsing with an AllSources reference to share provenance
   parser::Parsing parsing;
@@ -363,56 +393,89 @@ bool ModFileReader::Read(const SourceName &modName) {
   if (!parsing.messages().empty() || !parsing.consumedWholeFile() ||
       !parseTree.has_value()) {
     errors_.push_back(
-        Error(modName, "Module file for '%s' is corrupt: %s"_err_en_US,
-            modName.ToString(), *path));
-    return false;
+        Error(name, "Module file for '%s' is corrupt: %s"_err_en_US,
+            name.ToString(), *path));
+    return nullptr;
   }
-  ResolveNames(*parseTree, parsing.cooked(), directories_);
-
-  const auto &it{Scope::globalScope.find(modName)};
-  if (it == Scope::globalScope.end()) {
-    return false;
+  Scope *parentScope;
+  if (!ancestor) {
+    // module: goes into global scope
+    parentScope = &Scope::globalScope;
+  } else {
+    // submodule: goes into parent module/submodule
+    auto *parent{GetSubmoduleParent(*parseTree)};
+    parentScope = parent ? Read(*parent, ancestor) : ancestor;
+  }
+  ResolveNames(*parentScope, *parseTree, parsing.cooked(), directories_);
+  const auto &it{parentScope->find(name)};
+  if (it == parentScope->end()) {
+    return nullptr;
   }
   auto &modSymbol{*it->second};
   // TODO: Preserve the CookedSource rather than acquiring its string.
   modSymbol.scope()->set_chars(std::string{parsing.cooked().AcquireData()});
   modSymbol.set(Symbol::Flag::ModFile);
-  return true;
+  return modSymbol.scope();
 }
 
-// Look for the .mod file for this module in the search directories.
-// Add to errors_ if not found.
 std::optional<std::string> ModFileReader::FindModFile(
-    const SourceName &modName) {
-  auto error{Error(modName, "Cannot find module file for '%s'"_err_en_US,
-      modName.ToString())};
+    const SourceName &name, const std::string &ancestor) {
+  std::vector<parser::Message> errors;
   for (auto &dir : directories_) {
-    std::string path{ModFilePath(dir, modName.ToString())};
+    std::string path{ModFilePath(dir, name, ancestor)};
     std::ifstream ifstream{path};
     if (!ifstream.good()) {
-      error.Attach(Error(
-          modName, "%s: %s"_en_US, path, std::string{std::strerror(errno)}));
+      errors.push_back(
+          Error(name, "%s: %s"_en_US, path, std::string{std::strerror(errno)}));
     } else {
       std::string line;
       ifstream >> line;
       if (std::equal(line.begin(), line.end(), std::string{magic}.begin())) {
-        // TODO: verify reset of header line: version, checksum, etc.
-        return path;  // success
+        // TODO: verify rest of header line: version, checksum, etc.
+        return path;
       }
-      error.Attach(Error(modName, "%s: Not a valid module file"_en_US, path));
+      errors.push_back(Error(name, "%s: Not a valid module file"_en_US, path));
     }
   }
+  auto error{Error(name,
+      ancestor.empty()
+          ? "Cannot find module file for '%s'"_err_en_US
+          : "Cannot find module file for submodule '%s' of module '%s'"_err_en_US,
+      name.ToString(), ancestor)};
+  for (auto &e : errors) {
+    error.Attach(e);
+  }
   errors_.push_back(error);
   return std::nullopt;
 }
 
-static std::string ModFilePath(
-    const std::string &dir, const std::string &modName) {
-  if (dir == "."s) {
-    return modName + extension;
+// program was read from a .mod file for a submodule; return the name of the
+// submodule's parent submodule, nullptr if none.
+static const SourceName *GetSubmoduleParent(const parser::Program &program) {
+  CHECK(program.v.size() == 1);
+  auto &unit{program.v.front()};
+  auto &submod{std::get<common::Indirection<parser::Submodule>>(unit.u)};
+  auto &stmt{std::get<parser::Statement<parser::SubmoduleStmt>>(submod->t)};
+  auto &parentId{std::get<parser::ParentIdentifier>(stmt.statement.t)};
+  if (auto &parent{std::get<std::optional<parser::Name>>(parentId.t)}) {
+    return &parent->source;
   } else {
-    return dir + '/' + modName + extension;
+    return nullptr;
+  }
+}
+
+// Construct the path to a module file. ancestorName not empty means submodule.
+static std::string ModFilePath(const std::string &dir, const SourceName &name,
+    const std::string &ancestorName) {
+  std::stringstream path;
+  if (dir != "."s) {
+    path << dir << '/';
+  }
+  if (!ancestorName.empty()) {
+    PutLower(path, ancestorName) << '-';
   }
+  PutLower(path, name.ToString()) << extension;
+  return path.str();
 }
 
 static parser::Message Error(const SourceName &location,
index ec24958..a75d248 100644 (file)
@@ -49,8 +49,6 @@ public:
 
   // Write out all .mod files; if error return false.
   bool WriteAll();
-  // Write out .mod file for one module; if error return false.
-  bool WriteOne(const Symbol &);
 
 private:
   using symbolSet = std::set<const Symbol *>;
@@ -66,7 +64,9 @@ private:
   // Any errors encountered during writing:
   std::vector<parser::MessageFormattedText> errors_;
 
-  std::string GetAsString(const std::string &);
+  void WriteChildren(const Scope &);
+  void WriteOne(const Scope &);
+  std::string GetAsString(const Symbol &);
   std::string GetHeader(const std::string &);
   void PutSymbols(const Scope &);
   symbolVector SortSymbols(const symbolSet);
@@ -84,18 +84,19 @@ public:
   // directories specifies where to search for module files
   ModFileReader(const std::vector<std::string> &directories)
     : directories_{directories} {}
-
-  // Find and read the module file for modName.
-  // Return true on success; otherwise errors() reports the problems.
-  bool Read(const SourceName &modName);
+  // Find and read the module file for a module or submodule.
+  // If ancestor is specified, look for a submodule of that module.
+  // Return the Scope for that module/submodule or nullptr on error.
+  Scope *Read(const SourceName &, Scope *ancestor = nullptr);
+  // Errors that occurred when Read returns nullptr.
   std::vector<parser::Message> &errors() { return errors_; }
 
 private:
   std::vector<std::string> directories_;
   std::vector<parser::Message> errors_;
 
-  std::optional<std::string> FindModFile(const SourceName &);
-  bool Prescan(const SourceName &, const std::string &);
+  std::optional<std::string> FindModFile(
+      const SourceName &, const std::string &);
 };
 
 }  // namespace Fortran::semantics
index c43a1fb..24bc364 100644 (file)
@@ -292,13 +292,14 @@ private:
 // Manage a stack of Scopes
 class ScopeHandler : public virtual ImplicitRulesVisitor {
 public:
-  ScopeHandler() { PushScope(Scope::globalScope); }
+  void set_rootScope(Scope &scope) { PushScope(scope); }
   Scope &CurrScope() { return *scopes_.top(); }
   // Return the enclosing scope not corresponding to a derived type:
   Scope &CurrNonTypeScope();
 
   // Create a new scope and push it on the scope stack.
   Scope &PushScope(Scope::Kind kind, Symbol *symbol);
+  void PushScope(Scope &scope);
   void PopScope();
 
   Symbol *FindSymbol(const SourceName &name);
@@ -376,14 +377,14 @@ protected:
 private:
   // Stack of containing scopes; memory referenced is owned by parent scopes
   std::stack<Scope *, std::list<Scope *>> scopes_;
-
-  void PushScope(Scope &scope);
 };
 
 class ModuleVisitor : public virtual ScopeHandler {
 public:
   bool Pre(const parser::Module &);
   void Post(const parser::Module &);
+  bool Pre(const parser::Submodule &);
+  void Post(const parser::Submodule &);
   bool Pre(const parser::AccessStmt &);
   bool Pre(const parser::Only &);
   bool Pre(const parser::Rename::Names &);
@@ -406,13 +407,15 @@ private:
 
   void SetAccess(const parser::Name &, Attr);
   void ApplyDefaultAccess();
-  const Scope *FindModule(const SourceName &);
   void AddUse(const parser::Rename::Names &);
   void AddUse(const parser::Name &);
   // Record a use from useModuleScope_ of useName as localName. location is
   // where it occurred (either the module or the rename) for error reporting.
   void AddUse(const SourceName &location, const SourceName &localName,
       const SourceName &useName);
+  Symbol &BeginModule(const SourceName &, bool isSubmodule,
+      const std::optional<parser::ModuleSubprogramPart> &);
+  Scope *FindModule(const SourceName &, Scope * = nullptr);
 };
 
 class InterfaceVisitor : public virtual ScopeHandler {
@@ -1268,30 +1271,6 @@ void ModuleVisitor::Post(const parser::UseStmt &x) {
   useModuleScope_ = nullptr;
 }
 
-// Find the module with this name and return its scope.
-// May have to read a .mod file to find it.
-// Return nullptr on error, after reporting it.
-const Scope *ModuleVisitor::FindModule(const SourceName &name) {
-  auto it{Scope::globalScope.find(name)};
-  if (it == Scope::globalScope.end()) {
-    ModFileReader reader{searchDirectories_};
-    if (!reader.Read(name)) {
-      for (auto &error : reader.errors()) {
-        Say(std::move(error));
-      }
-      return nullptr;
-    }
-    it = Scope::globalScope.find(name);
-    CHECK(it != Scope::globalScope.end());  // else would have reported error
-  }
-  const auto *details{it->second->detailsIf<ModuleDetails>()};
-  if (!details) {
-    Say(name, "'%s' is not a module"_err_en_US);
-    return nullptr;
-  }
-  return details->scope();
-}
-
 void ModuleVisitor::AddUse(const parser::Rename::Names &names) {
   const SourceName &useName{std::get<0>(names.t).source};
   const SourceName &localName{std::get<1>(names.t).source};
@@ -1336,22 +1315,43 @@ void ModuleVisitor::AddUse(const SourceName &location,
   }
 }
 
+bool ModuleVisitor::Pre(const parser::Submodule &x) {
+  auto &stmt{std::get<parser::Statement<parser::SubmoduleStmt>>(x.t)};
+  auto &name{std::get<parser::Name>(stmt.statement.t).source};
+  auto &subpPart{std::get<std::optional<parser::ModuleSubprogramPart>>(x.t)};
+  auto &parentId{std::get<parser::ParentIdentifier>(stmt.statement.t)};
+  auto &ancestorName{std::get<parser::Name>(parentId.t).source};
+  auto &parentName{std::get<std::optional<parser::Name>>(parentId.t)};
+  Scope *ancestor{FindModule(ancestorName)};
+  if (!ancestor) {
+    return false;
+  }
+  Scope *parentScope{
+      parentName ? FindModule(parentName->source, ancestor) : ancestor};
+  if (!parentScope) {
+    return false;
+  }
+  PushScope(*parentScope);  // submodule is hosted in parent
+  auto &symbol{BeginModule(name, true, subpPart)};
+  if (ancestor->AddSubmodule(name, &CurrScope())) {
+    Say(name, "Module '%s' already has a submodule named '%s'"_err_en_US,
+        ancestorName, name);
+  }
+  MakeSymbol(name, symbol.get<ModuleDetails>());
+  return true;
+}
+void ModuleVisitor::Post(const parser::Submodule &) {
+  PopScope();  // submodule's scope
+  PopScope();  // parent's scope
+}
+
 bool ModuleVisitor::Pre(const parser::Module &x) {
   // Make a symbol and push a scope for this module
   const auto &name{
-      std::get<parser::Statement<parser::ModuleStmt>>(x.t).statement.v};
-  auto &symbol{MakeSymbol(name, ModuleDetails{})};
-  ModuleDetails &details{symbol.get<ModuleDetails>()};
-  Scope &modScope{PushScope(Scope::Kind::Module, &symbol)};
-  details.set_scope(&modScope);
-  MakeSymbol(name, ModuleDetails{details});
-  // collect module subprogram names
-  if (const auto &subpPart{
-          std::get<std::optional<parser::ModuleSubprogramPart>>(x.t)}) {
-    subpNamesOnly_ = SubprogramKind::Module;
-    parser::Walk(*subpPart, *static_cast<ResolveNamesVisitor *>(this));
-    subpNamesOnly_ = std::nullopt;
-  }
+      std::get<parser::Statement<parser::ModuleStmt>>(x.t).statement.v.source};
+  auto &subpPart{std::get<std::optional<parser::ModuleSubprogramPart>>(x.t)};
+  auto &symbol{BeginModule(name, false, subpPart)};
+  MakeSymbol(name, symbol.details());
   return true;
 }
 
@@ -1361,6 +1361,40 @@ void ModuleVisitor::Post(const parser::Module &) {
   prevAccessStmt_ = nullptr;
 }
 
+Symbol &ModuleVisitor::BeginModule(const SourceName &name, bool isSubmodule,
+    const std::optional<parser::ModuleSubprogramPart> &subpPart) {
+  auto &symbol{MakeSymbol(name, ModuleDetails{isSubmodule})};
+  auto &details{symbol.get<ModuleDetails>()};
+  auto &modScope{PushScope(Scope::Kind::Module, &symbol)};
+  details.set_scope(&modScope);
+  if (subpPart) {
+    subpNamesOnly_ = SubprogramKind::Module;
+    parser::Walk(*subpPart, *static_cast<ResolveNamesVisitor *>(this));
+    subpNamesOnly_ = std::nullopt;
+  }
+  return symbol;
+}
+
+// Find a module or submodule by name and return its scope.
+// If ancestor is present, look for a submodule of that ancestor module.
+// May have to read a .mod file to find it.
+// If an error occurs, report it and return nullptr.
+Scope *ModuleVisitor::FindModule(const SourceName &name, Scope *ancestor) {
+  ModFileReader reader{searchDirectories_};
+  auto *scope{reader.Read(name, ancestor)};
+  if (!scope) {
+    for (auto &error : reader.errors()) {
+      Say(std::move(error));
+    }
+    return nullptr;
+  }
+  if (scope->kind() != Scope::Kind::Module) {
+    Say(name, "'%s' is not a module"_err_en_US);
+    return nullptr;
+  }
+  return scope;
+}
+
 void ModuleVisitor::ApplyDefaultAccess() {
   for (auto &pair : CurrScope()) {
     Symbol &symbol = *pair.second;
@@ -2443,10 +2477,11 @@ void ResolveNamesVisitor::Post(const parser::Program &) {
   CHECK(!GetDeclTypeSpec());
 }
 
-void ResolveNames(parser::Program &program,
+void ResolveNames(Scope &rootScope, parser::Program &program,
     const parser::CookedSource &cookedSource,
     const std::vector<std::string> &searchDirectories) {
   ResolveNamesVisitor visitor;
+  visitor.set_rootScope(rootScope);
   for (auto &dir : searchDirectories) {
     visitor.add_searchDirectory(dir);
   }
index 8a786a8..f071360 100644 (file)
@@ -25,8 +25,10 @@ class CookedSource;
 
 namespace Fortran::semantics {
 
-void ResolveNames(parser::Program &, const parser::CookedSource &,
-    const std::vector<std::string> &);
+class Scope;
+
+void ResolveNames(Scope &rootScope, parser::Program &,
+    const parser::CookedSource &, const std::vector<std::string> &);
 void DumpSymbols(std::ostream &);
 
 }  // namespace Fortran::semantics
index 63e6d47..d36d028 100644 (file)
@@ -48,6 +48,18 @@ Scope::size_type Scope::erase(const SourceName &name) {
     return 0;
   }
 }
+Scope *Scope::FindSubmodule(const SourceName &name) const {
+  auto it{submodules_.find(name)};
+  if (it == submodules_.end()) {
+    return nullptr;
+  } else {
+    return it->second;
+  }
+}
+Scope *Scope::AddSubmodule(const SourceName &name, Scope *submodule) {
+  auto pair{submodules_.emplace(name, submodule)};
+  return !pair.second ? pair.first->second : nullptr;
+}
 DerivedTypeSpec &Scope::MakeDerivedTypeSpec(const SourceName &name) {
   derivedTypeSpecs_.emplace_back(name);
   return derivedTypeSpecs_.back();
index cdeac43..b9a64be 100644 (file)
@@ -91,7 +91,7 @@ public:
   std::pair<iterator, bool> try_emplace(
       const SourceName &name, Attrs attrs, D &&details) {
     Symbol &symbol{MakeSymbol(name, attrs, std::move(details))};
-    return symbols_.insert(std::make_pair(name, &symbol));
+    return symbols_.emplace(name, &symbol);
   }
 
   /// Make a Symbol but don't add it to the scope.
@@ -103,13 +103,16 @@ public:
   std::list<Scope> &children() { return children_; }
   const std::list<Scope> &children() const { return children_; }
 
+  // For Module scope, maintain a mapping of all submodule scopes with this
+  // module as its ancestor module.
+  Scope *FindSubmodule(const SourceName &) const;
+  Scope *AddSubmodule(const SourceName &, Scope *);
+
   DerivedTypeSpec &MakeDerivedTypeSpec(const SourceName &);
 
   // For modules read from module files, this is the stream of characters
   // that are referenced by SourceName objects.
-  void set_chars(std::string &&chars) {
-    chars_ = std::move(chars);
-  }
+  void set_chars(std::string &&chars) { chars_ = std::move(chars); }
 
 private:
   Scope &parent_;
@@ -117,6 +120,7 @@ private:
   Symbol *const symbol_;
   std::list<Scope> children_;
   mapType symbols_;
+  std::map<SourceName, Scope *> submodules_;
   std::list<DerivedTypeSpec> derivedTypeSpecs_;
   std::string chars_;
 
index 1fd8cbf..970fb1d 100644 (file)
@@ -23,6 +23,28 @@ std::ostream &operator<<(std::ostream &os, const parser::CharBlock &name) {
   return os << name.ToString();
 }
 
+const Scope *ModuleDetails::parent() const {
+  return isSubmodule_ ? &scope_->parent() : nullptr;
+}
+const Scope *ModuleDetails::ancestor() const {
+  if (!isSubmodule_) {
+    return nullptr;
+  }
+  for (auto *scope{scope_};;) {
+    auto *parent{&scope->parent()};
+    if (parent->kind() != Scope::Kind::Module) {
+      return scope;
+    }
+    scope = parent;
+  }
+}
+void ModuleDetails::set_scope(const Scope *scope) {
+  CHECK(!scope_);
+  bool scopeIsSubmodule{scope->parent().kind() == Scope::Kind::Module};
+  CHECK(isSubmodule_ == scopeIsSubmodule);
+  scope_ = scope;
+}
+
 void EntityDetails::set_type(const DeclTypeSpec &type) {
   CHECK(!type_);
   type_ = type;
@@ -261,7 +283,17 @@ std::ostream &operator<<(std::ostream &os, const Details &details) {
       common::visitors{
           [&](const UnknownDetails &x) {},
           [&](const MainProgramDetails &x) {},
-          [&](const ModuleDetails &x) {},
+          [&](const ModuleDetails &x) {
+            if (x.isSubmodule()) {
+              auto &ancestor{x.ancestor()->name()};
+              auto &parent{x.parent()->name()};
+              os << " (" << ancestor.ToString();
+              if (parent != ancestor) {
+                os << ':' << parent.ToString();
+              }
+              os << ")";
+            }
+          },
           [&](const SubprogramDetails &x) {
             os << " (";
             int n = 0;
index 5c4fb9c..cfefe24 100644 (file)
@@ -29,15 +29,18 @@ namespace Fortran::semantics {
 class Scope;
 class Symbol;
 
+// A module or submodule.
 class ModuleDetails {
 public:
+  ModuleDetails(bool isSubmodule = false) : isSubmodule_{isSubmodule} {}
+  bool isSubmodule() const { return isSubmodule_; }
   const Scope *scope() const { return scope_; }
-  void set_scope(const Scope *scope) {
-    CHECK(!scope_);
-    scope_ = scope;
-  }
+  const Scope *ancestor() const;  // for submodule; nullptr for module
+  const Scope *parent() const;  // for submodule; nullptr for module
+  void set_scope(const Scope *);
 
 private:
+  bool isSubmodule_;
   const Scope *scope_{nullptr};
 };
 
index a1805e3..6e7fa31 100644 (file)
@@ -49,6 +49,8 @@ set(ERROR_TESTS
   resolve23.f90
   resolve24.f90
   resolve25.f90
+  resolve26.f90
+  resolve27.f90
 )
 
 # These test files have expected symbols in the source
@@ -66,6 +68,7 @@ set(MODFILE_TESTS
   modfile06.f90
   modfile07.f90
   modfile08.f90
+  modfile09-*.f90
 )
 
 foreach(test ${ERROR_TESTS})
diff --git a/flang/test/semantics/modfile09-a.f90 b/flang/test/semantics/modfile09-a.f90
new file mode 100644 (file)
index 0000000..1baceec
--- /dev/null
@@ -0,0 +1,16 @@
+module m
+  integer :: m1_x
+  interface
+    module subroutine s()
+    end subroutine
+  end interface
+end
+
+!Expect: m.mod
+!module m
+!integer::m1_x
+!interface
+!module subroutine s()
+!end
+!end interface
+!end
diff --git a/flang/test/semantics/modfile09-b.f90 b/flang/test/semantics/modfile09-b.f90
new file mode 100644 (file)
index 0000000..6fc6703
--- /dev/null
@@ -0,0 +1,8 @@
+submodule(m) s1
+  integer s1_x
+end
+
+!Expect: m-s1.mod
+!submodule(m) s1
+!integer::s1_x
+!end
diff --git a/flang/test/semantics/modfile09-c.f90 b/flang/test/semantics/modfile09-c.f90
new file mode 100644 (file)
index 0000000..d6670e4
--- /dev/null
@@ -0,0 +1,8 @@
+submodule(m:s1) s2
+  integer s2_x
+end
+
+!Expect: m-s2.mod
+!submodule(m:s1) s2
+!integer::s2_x
+!end
diff --git a/flang/test/semantics/modfile09-d.f90 b/flang/test/semantics/modfile09-d.f90
new file mode 100644 (file)
index 0000000..00550b5
--- /dev/null
@@ -0,0 +1,8 @@
+submodule(m:s2) s3
+  integer s3_x
+end
+
+!Expect: m-s3.mod
+!submodule(m:s2) s3
+!integer::s3_x
+!end
diff --git a/flang/test/semantics/resolve26.f90 b/flang/test/semantics/resolve26.f90
new file mode 100644 (file)
index 0000000..a02962f
--- /dev/null
@@ -0,0 +1,24 @@
+module m1
+  interface
+    module subroutine s()
+    end subroutine
+  end interface
+end
+
+module m2
+  interface
+    module subroutine s()
+    end subroutine
+  end interface
+end
+
+submodule(m1) s1
+end
+
+!ERROR: Cannot find module file for submodule 's1' of module 'm2'
+submodule(m2:s1) s2
+end
+
+!ERROR: Cannot find module file for 'm3'
+submodule(m3:s1) s3
+end
diff --git a/flang/test/semantics/resolve27.f90 b/flang/test/semantics/resolve27.f90
new file mode 100644 (file)
index 0000000..3f04c1a
--- /dev/null
@@ -0,0 +1,21 @@
+module m
+  interface
+    module subroutine s()
+    end subroutine
+  end interface
+end
+
+submodule(m) s1
+end
+
+submodule(m) s2
+end
+
+submodule(m:s1) s3
+  integer x
+end
+
+!ERROR: Module 'm' already has a submodule named 's3'
+submodule(m:s2) s3
+  integer y
+end
index f5e1c20..954cf2f 100755 (executable)
@@ -19,47 +19,55 @@ set -e
 PATH=/usr/bin:/bin
 srcdir=$(dirname $0)
 CMD="${F18:-../../../tools/f18/f18} -fdebug-resolve-names -fparse-only"
-
 if [[ $# != 1 ]]; then
   echo "Usage: $0 <fortran-source>"
   exit 1
 fi
 src=$srcdir/$1
-[[ ! -f $src ]] && echo "File not found: $src" && exit 1
 
 temp=temp-$1
 rm -rf $temp
 mkdir $temp
 [[ $KEEP ]] || trap "rm -rf $temp" EXIT
-
-( cd $temp && $CMD $src )
-
 actual=$temp/actual.mod
 expect=$temp/expect.mod
 actual_files=$temp/actual_files
+prev_files=$temp/prev_files
 diffs=$temp/diffs
 
-( cd $temp && ls -1 *.mod ) > $actual_files
-expected_files=$(sed -n 's/^!Expect: \(.*\)/\1/p' $src)
-extra_files=$(echo "$expected_files" | comm -23 $actual_files -)
-if [[ ! -z "$extra_files" ]]; then
-  echo "Unexpected .mod files produced:" $extra_files
-  echo FAIL
-  exit 1
-fi
-for mod in $expected_files; do
-  if [[ ! -f $temp/$mod ]]; then
-    echo "Compilation did not produce expected mod file: $mod"
-    echo FAIL
-    exit 1
-  fi
-  sed '/^!mod\$/d' $temp/$mod > $actual
-  sed '1,/^!Expect: '"$mod"'/d' $src | sed -e '/^$/,$d' -e 's/^! *//' > $expect
-  if ! diff -U999999 $actual $expect > $diffs; then
-    echo "Module file $mod differs from expected:"
-    sed '1,2d' $diffs
+set $src
+
+touch $actual
+for src in "$@"; do
+  [[ ! -f $src ]] && echo "File not found: $src" && exit 1
+  (
+    cd $temp
+    ls -1 *.mod > prev_files
+    $CMD $src
+    ls -1 *.mod | comm -13 prev_files -
+  ) > $actual_files
+  expected_files=$(sed -n 's/^!Expect: \(.*\)/\1/p' $src)
+  extra_files=$(echo "$expected_files" | comm -23 $actual_files -)
+  if [[ ! -z "$extra_files" ]]; then
+    echo "Unexpected .mod files produced:" $extra_files
     echo FAIL
     exit 1
   fi
+  for mod in $expected_files; do
+    if [[ ! -f $temp/$mod ]]; then
+      echo "Compilation did not produce expected mod file: $mod"
+      echo FAIL
+      exit 1
+    fi
+    sed '/^!mod\$/d' $temp/$mod > $actual
+    sed '1,/^!Expect: '"$mod"'/d' $src | sed -e '/^$/,$d' -e 's/^! *//' > $expect
+    if ! diff -U999999 $actual $expect > $diffs; then
+      echo "Module file $mod differs from expected:"
+      sed '1,2d' $diffs
+      echo FAIL
+      exit 1
+    fi
+  done
+  rm -f $actual $expect
 done
 echo PASS
index 0d1c3fe..340b625 100644 (file)
@@ -25,6 +25,7 @@
 #include "../../lib/semantics/dump-parse-tree.h"
 #include "../../lib/semantics/mod-file.h"
 #include "../../lib/semantics/resolve-names.h"
+#include "../../lib/semantics/scope.h"
 #include "../../lib/semantics/unparse-with-symbols.h"
 #include <cerrno>
 #include <cstdio>
@@ -212,7 +213,8 @@ std::string CompileFortran(
     if (driver.moduleDirectory != "."s) {
       directories.insert(directories.begin(), driver.moduleDirectory);
     }
-    Fortran::semantics::ResolveNames(parseTree, parsing.cooked(), directories);
+    Fortran::semantics::ResolveNames(Fortran::semantics::Scope::globalScope,
+        parseTree, parsing.cooked(), directories);
     Fortran::semantics::ModFileWriter writer;
     writer.set_directory(driver.moduleDirectory);
     if (!writer.WriteAll()) {