[flang] Add support for submodules

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@2e4424dbc8
Reviewed-on: https://github.com/flang-compiler/f18/pull/160
Tree-same-pre-rewrite: false
This commit is contained in:
Tim Keith 2018-08-02 16:21:27 -07:00
parent 4a20cc8478
commit 96b187efdf
17 changed files with 393 additions and 143 deletions

View 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,58 +393,91 @@ 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,
parser::MessageFixedText fixedText, const std::string &arg) {
return parser::Message{

View 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

View 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);
}

View 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

View 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();

View 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_;

View 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;

View 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};
};

View 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})

View file

@ -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

View file

@ -0,0 +1,8 @@
submodule(m) s1
integer s1_x
end
!Expect: m-s1.mod
!submodule(m) s1
!integer::s1_x
!end

View file

@ -0,0 +1,8 @@
submodule(m:s1) s2
integer s2_x
end
!Expect: m-s2.mod
!submodule(m:s1) s2
!integer::s2_x
!end

View file

@ -0,0 +1,8 @@
submodule(m:s2) s3
integer s3_x
end
!Expect: m-s3.mod
!submodule(m:s2) s3
!integer::s3_x
!end

View file

@ -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

View file

@ -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

View file

@ -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

View 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()) {