mirror of
https://github.com/llvm/llvm-project.git
synced 2025-05-01 04:06:07 +00:00

Implement the UNSIGNED extension type and operations under control of a language feature flag (-funsigned). This is nearly identical to the UNSIGNED feature that has been available in Sun Fortran for years, and now implemented in GNU Fortran for gfortran 15, and proposed for ISO standardization in J3/24-116.txt. See the new documentation for details; but in short, this is C's unsigned type, with guaranteed modular arithmetic for +, -, and *, and the related transformational intrinsic functions SUM & al.
480 lines
15 KiB
C++
480 lines
15 KiB
C++
//===-- lib/Semantics/scope.cpp -------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "flang/Semantics/scope.h"
|
|
#include "flang/Parser/characters.h"
|
|
#include "flang/Semantics/semantics.h"
|
|
#include "flang/Semantics/symbol.h"
|
|
#include "flang/Semantics/type.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
namespace Fortran::semantics {
|
|
|
|
Symbols<1024> Scope::allSymbols;
|
|
|
|
bool EquivalenceObject::operator==(const EquivalenceObject &that) const {
|
|
return symbol == that.symbol && subscripts == that.subscripts &&
|
|
substringStart == that.substringStart;
|
|
}
|
|
|
|
bool EquivalenceObject::operator<(const EquivalenceObject &that) const {
|
|
return &symbol < &that.symbol ||
|
|
(&symbol == &that.symbol &&
|
|
(subscripts < that.subscripts ||
|
|
(subscripts == that.subscripts &&
|
|
substringStart < that.substringStart)));
|
|
}
|
|
|
|
std::string EquivalenceObject::AsFortran() const {
|
|
std::string buf;
|
|
llvm::raw_string_ostream ss{buf};
|
|
ss << symbol.name().ToString();
|
|
if (!subscripts.empty()) {
|
|
char sep{'('};
|
|
for (auto subscript : subscripts) {
|
|
ss << sep << subscript;
|
|
sep = ',';
|
|
}
|
|
ss << ')';
|
|
}
|
|
if (substringStart) {
|
|
ss << '(' << *substringStart << ":)";
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
Scope &Scope::MakeScope(Kind kind, Symbol *symbol) {
|
|
return children_.emplace_back(*this, kind, symbol, context_);
|
|
}
|
|
|
|
template <typename T>
|
|
static std::vector<common::Reference<T>> GetSortedSymbols(
|
|
const std::map<SourceName, MutableSymbolRef> &symbols) {
|
|
std::vector<common::Reference<T>> result;
|
|
result.reserve(symbols.size());
|
|
for (auto &pair : symbols) {
|
|
result.push_back(*pair.second);
|
|
}
|
|
std::sort(result.begin(), result.end(), SymbolSourcePositionCompare{});
|
|
return result;
|
|
}
|
|
|
|
MutableSymbolVector Scope::GetSymbols() {
|
|
return GetSortedSymbols<Symbol>(symbols_);
|
|
}
|
|
SymbolVector Scope::GetSymbols() const {
|
|
return GetSortedSymbols<const Symbol>(symbols_);
|
|
}
|
|
|
|
Scope::iterator Scope::find(const SourceName &name) {
|
|
return symbols_.find(name);
|
|
}
|
|
Scope::size_type Scope::erase(const SourceName &name) {
|
|
auto it{symbols_.find(name)};
|
|
if (it != end()) {
|
|
symbols_.erase(it);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
Symbol *Scope::FindSymbol(const SourceName &name) const {
|
|
auto it{find(name)};
|
|
if (it != end()) {
|
|
return &*it->second;
|
|
} else if (IsSubmodule()) {
|
|
const Scope *parent{symbol_->get<ModuleDetails>().parent()};
|
|
return parent ? parent->FindSymbol(name) : nullptr;
|
|
} else if (CanImport(name)) {
|
|
return parent_->FindSymbol(name);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
Symbol *Scope::FindComponent(SourceName name) const {
|
|
CHECK(IsDerivedType());
|
|
auto found{find(name)};
|
|
if (found != end()) {
|
|
return &*found->second;
|
|
} else if (const Scope * parent{GetDerivedTypeParent()}) {
|
|
return parent->FindComponent(name);
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
bool Scope::Contains(const Scope &that) const {
|
|
for (const Scope *scope{&that};; scope = &scope->parent()) {
|
|
if (*scope == *this) {
|
|
return true;
|
|
}
|
|
if (scope->IsGlobal()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
Symbol *Scope::CopySymbol(const Symbol &symbol) {
|
|
auto pair{try_emplace(symbol.name(), symbol.attrs())};
|
|
if (!pair.second) {
|
|
return nullptr; // already exists
|
|
} else {
|
|
Symbol &result{*pair.first->second};
|
|
result.flags() = symbol.flags();
|
|
result.set_details(common::Clone(symbol.details()));
|
|
return &result;
|
|
}
|
|
}
|
|
|
|
void Scope::add_equivalenceSet(EquivalenceSet &&set) {
|
|
equivalenceSets_.emplace_back(std::move(set));
|
|
}
|
|
|
|
void Scope::add_crayPointer(const SourceName &name, Symbol &pointer) {
|
|
CHECK(pointer.test(Symbol::Flag::CrayPointer));
|
|
crayPointers_.emplace(name, pointer);
|
|
}
|
|
|
|
Symbol &Scope::MakeCommonBlock(const SourceName &name) {
|
|
const auto it{commonBlocks_.find(name)};
|
|
if (it != commonBlocks_.end()) {
|
|
return *it->second;
|
|
} else {
|
|
Symbol &symbol{MakeSymbol(name, Attrs{}, CommonBlockDetails{})};
|
|
commonBlocks_.emplace(name, symbol);
|
|
return symbol;
|
|
}
|
|
}
|
|
Symbol *Scope::FindCommonBlock(const SourceName &name) const {
|
|
const auto it{commonBlocks_.find(name)};
|
|
return it != commonBlocks_.end() ? &*it->second : nullptr;
|
|
}
|
|
|
|
Scope *Scope::FindSubmodule(const SourceName &name) const {
|
|
auto it{submodules_.find(name)};
|
|
if (it == submodules_.end()) {
|
|
return nullptr;
|
|
} else {
|
|
return &*it->second;
|
|
}
|
|
}
|
|
bool Scope::AddSubmodule(const SourceName &name, Scope &submodule) {
|
|
return submodules_.emplace(name, submodule).second;
|
|
}
|
|
|
|
const DeclTypeSpec *Scope::FindType(const DeclTypeSpec &type) const {
|
|
auto it{std::find(declTypeSpecs_.begin(), declTypeSpecs_.end(), type)};
|
|
return it != declTypeSpecs_.end() ? &*it : nullptr;
|
|
}
|
|
|
|
const DeclTypeSpec &Scope::MakeNumericType(
|
|
TypeCategory category, KindExpr &&kind) {
|
|
return MakeLengthlessType(NumericTypeSpec{category, std::move(kind)});
|
|
}
|
|
const DeclTypeSpec &Scope::MakeLogicalType(KindExpr &&kind) {
|
|
return MakeLengthlessType(LogicalTypeSpec{std::move(kind)});
|
|
}
|
|
const DeclTypeSpec &Scope::MakeTypeStarType() {
|
|
return MakeLengthlessType(DeclTypeSpec{DeclTypeSpec::TypeStar});
|
|
}
|
|
const DeclTypeSpec &Scope::MakeClassStarType() {
|
|
return MakeLengthlessType(DeclTypeSpec{DeclTypeSpec::ClassStar});
|
|
}
|
|
// Types that can't have length parameters can be reused without having to
|
|
// compare length expressions. They are stored in the global scope.
|
|
const DeclTypeSpec &Scope::MakeLengthlessType(DeclTypeSpec &&type) {
|
|
const auto *found{FindType(type)};
|
|
return found ? *found : declTypeSpecs_.emplace_back(std::move(type));
|
|
}
|
|
|
|
const DeclTypeSpec &Scope::MakeCharacterType(
|
|
ParamValue &&length, KindExpr &&kind) {
|
|
return declTypeSpecs_.emplace_back(
|
|
CharacterTypeSpec{std::move(length), std::move(kind)});
|
|
}
|
|
|
|
DeclTypeSpec &Scope::MakeDerivedType(
|
|
DeclTypeSpec::Category category, DerivedTypeSpec &&spec) {
|
|
return declTypeSpecs_.emplace_back(category, std::move(spec));
|
|
}
|
|
|
|
const DeclTypeSpec *Scope::GetType(const SomeExpr &expr) {
|
|
if (auto dyType{expr.GetType()}) {
|
|
if (dyType->IsAssumedType()) {
|
|
return &MakeTypeStarType();
|
|
} else if (dyType->IsUnlimitedPolymorphic()) {
|
|
return &MakeClassStarType();
|
|
} else {
|
|
switch (dyType->category()) {
|
|
case TypeCategory::Integer:
|
|
case TypeCategory::Unsigned:
|
|
case TypeCategory::Real:
|
|
case TypeCategory::Complex:
|
|
return &MakeNumericType(dyType->category(), KindExpr{dyType->kind()});
|
|
case TypeCategory::Character:
|
|
if (const ParamValue * lenParam{dyType->charLengthParamValue()}) {
|
|
return &MakeCharacterType(
|
|
ParamValue{*lenParam}, KindExpr{dyType->kind()});
|
|
} else {
|
|
auto lenExpr{dyType->GetCharLength()};
|
|
if (!lenExpr) {
|
|
lenExpr =
|
|
std::get<evaluate::Expr<evaluate::SomeCharacter>>(expr.u).LEN();
|
|
}
|
|
if (lenExpr) {
|
|
return &MakeCharacterType(
|
|
ParamValue{SomeIntExpr{std::move(*lenExpr)},
|
|
common::TypeParamAttr::Len},
|
|
KindExpr{dyType->kind()});
|
|
}
|
|
}
|
|
break;
|
|
case TypeCategory::Logical:
|
|
return &MakeLogicalType(KindExpr{dyType->kind()});
|
|
case TypeCategory::Derived:
|
|
return &MakeDerivedType(dyType->IsPolymorphic()
|
|
? DeclTypeSpec::ClassDerived
|
|
: DeclTypeSpec::TypeDerived,
|
|
DerivedTypeSpec{dyType->GetDerivedTypeSpec()});
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Scope::ImportKind Scope::GetImportKind() const {
|
|
if (importKind_) {
|
|
return *importKind_;
|
|
}
|
|
if (symbol_ && !symbol_->attrs().test(Attr::MODULE)) {
|
|
if (auto *details{symbol_->detailsIf<SubprogramDetails>()}) {
|
|
if (details->isInterface()) {
|
|
return ImportKind::None; // default for non-mod-proc interface body
|
|
}
|
|
}
|
|
}
|
|
return ImportKind::Default;
|
|
}
|
|
|
|
std::optional<parser::MessageFixedText> Scope::SetImportKind(ImportKind kind) {
|
|
if (!importKind_) {
|
|
importKind_ = kind;
|
|
return std::nullopt;
|
|
}
|
|
bool hasNone{kind == ImportKind::None || *importKind_ == ImportKind::None};
|
|
bool hasAll{kind == ImportKind::All || *importKind_ == ImportKind::All};
|
|
// Check C8100 and C898: constraints on multiple IMPORT statements
|
|
if (hasNone || hasAll) {
|
|
return hasNone
|
|
? "IMPORT,NONE must be the only IMPORT statement in a scope"_err_en_US
|
|
: "IMPORT,ALL must be the only IMPORT statement in a scope"_err_en_US;
|
|
} else if (kind != *importKind_ &&
|
|
(kind != ImportKind::Only && *importKind_ != ImportKind::Only)) {
|
|
return "Every IMPORT must have ONLY specifier if one of them does"_err_en_US;
|
|
} else {
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
void Scope::add_importName(const SourceName &name) {
|
|
importNames_.insert(name);
|
|
}
|
|
|
|
// true if name can be imported or host-associated from parent scope.
|
|
bool Scope::CanImport(const SourceName &name) const {
|
|
if (IsTopLevel() || parent_->IsTopLevel()) {
|
|
return false;
|
|
}
|
|
switch (GetImportKind()) {
|
|
SWITCH_COVERS_ALL_CASES
|
|
case ImportKind::None:
|
|
return false;
|
|
case ImportKind::All:
|
|
case ImportKind::Default:
|
|
return true;
|
|
case ImportKind::Only:
|
|
return importNames_.count(name) > 0;
|
|
}
|
|
}
|
|
|
|
void Scope::AddSourceRange(parser::CharBlock source) {
|
|
if (source.empty()) {
|
|
return;
|
|
}
|
|
const parser::AllCookedSources &allCookedSources{context_.allCookedSources()};
|
|
const parser::CookedSource *cooked{allCookedSources.Find(source)};
|
|
if (!cooked) {
|
|
CHECK(context_.IsTempName(source.ToString()));
|
|
return;
|
|
}
|
|
for (auto *scope{this}; !scope->IsTopLevel(); scope = &scope->parent()) {
|
|
CHECK(scope->sourceRange_.empty() == (scope->cookedSource_ == nullptr));
|
|
if (!scope->cookedSource_) {
|
|
context_.UpdateScopeIndex(*scope, source);
|
|
scope->cookedSource_ = cooked;
|
|
scope->sourceRange_ = source;
|
|
} else if (scope->cookedSource_ == cooked) {
|
|
auto combined{scope->sourceRange()};
|
|
combined.ExtendToCover(source);
|
|
context_.UpdateScopeIndex(*scope, combined);
|
|
scope->sourceRange_ = combined;
|
|
} else {
|
|
// There's a bug that will be hard to fix; crash informatively
|
|
const parser::AllSources &allSources{allCookedSources.allSources()};
|
|
const auto describe{[&](parser::CharBlock src) {
|
|
if (auto range{allCookedSources.GetProvenanceRange(src)}) {
|
|
std::size_t offset;
|
|
if (const parser::SourceFile *
|
|
file{allSources.GetSourceFile(range->start(), &offset)}) {
|
|
return "'"s + file->path() + "' at " + std::to_string(offset) +
|
|
" for " + std::to_string(range->size());
|
|
} else {
|
|
return "(GetSourceFile failed)"s;
|
|
}
|
|
} else {
|
|
return "(GetProvenanceRange failed)"s;
|
|
}
|
|
}};
|
|
std::string scopeDesc{describe(scope->sourceRange_)};
|
|
std::string newDesc{describe(source)};
|
|
common::die("AddSourceRange would have combined ranges from distinct "
|
|
"source files \"%s\" and \"%s\"",
|
|
scopeDesc.c_str(), newDesc.c_str());
|
|
}
|
|
// Note: If the "break;" here were unconditional (or, equivalently, if
|
|
// there were no loop at all) then the source ranges of parent scopes
|
|
// would not enclose the source ranges of their children. Timing
|
|
// shows that it's cheap to maintain this property, with the exceptions
|
|
// of top-level scopes and for (sub)modules and their descendant
|
|
// submodules.
|
|
if (scope->IsSubmodule()) {
|
|
break; // Submodules are child scopes but not contained ranges
|
|
}
|
|
}
|
|
}
|
|
|
|
llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const Scope &scope) {
|
|
os << Scope::EnumToString(scope.kind()) << " scope: ";
|
|
if (auto *symbol{scope.symbol()}) {
|
|
os << *symbol << ' ';
|
|
}
|
|
if (scope.derivedTypeSpec_) {
|
|
os << "instantiation of " << *scope.derivedTypeSpec_ << ' ';
|
|
}
|
|
os << scope.children_.size() << " children\n";
|
|
for (const auto &pair : scope.symbols_) {
|
|
const Symbol &symbol{*pair.second};
|
|
os << " " << symbol << '\n';
|
|
}
|
|
if (!scope.equivalenceSets_.empty()) {
|
|
os << " Equivalence Sets:\n";
|
|
for (const auto &set : scope.equivalenceSets_) {
|
|
os << " ";
|
|
for (const auto &object : set) {
|
|
os << ' ' << object.AsFortran();
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
for (const auto &pair : scope.commonBlocks_) {
|
|
const Symbol &symbol{*pair.second};
|
|
os << " " << symbol << '\n';
|
|
}
|
|
return os;
|
|
}
|
|
|
|
bool Scope::IsStmtFunction() const {
|
|
return symbol_ && symbol_->test(Symbol::Flag::StmtFunction);
|
|
}
|
|
|
|
template <common::TypeParamAttr... ParamAttr> struct IsTypeParamHelper {
|
|
static_assert(sizeof...(ParamAttr) == 0, "must have one or zero template");
|
|
static bool IsParam(const Symbol &symbol) {
|
|
return symbol.has<TypeParamDetails>();
|
|
}
|
|
};
|
|
|
|
template <common::TypeParamAttr ParamAttr> struct IsTypeParamHelper<ParamAttr> {
|
|
static bool IsParam(const Symbol &symbol) {
|
|
if (const auto *typeParam{symbol.detailsIf<TypeParamDetails>()}) {
|
|
return typeParam->attr() == ParamAttr;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
template <common::TypeParamAttr... ParamAttr>
|
|
static bool IsParameterizedDerivedTypeHelper(const Scope &scope) {
|
|
if (scope.IsDerivedType()) {
|
|
if (const Scope * parent{scope.GetDerivedTypeParent()}) {
|
|
if (IsParameterizedDerivedTypeHelper<ParamAttr...>(*parent)) {
|
|
return true;
|
|
}
|
|
}
|
|
for (const auto &nameAndSymbolPair : scope) {
|
|
if (IsTypeParamHelper<ParamAttr...>::IsParam(*nameAndSymbolPair.second)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Scope::IsParameterizedDerivedType() const {
|
|
return IsParameterizedDerivedTypeHelper<>(*this);
|
|
}
|
|
bool Scope::IsDerivedTypeWithLengthParameter() const {
|
|
return IsParameterizedDerivedTypeHelper<common::TypeParamAttr::Len>(*this);
|
|
}
|
|
bool Scope::IsDerivedTypeWithKindParameter() const {
|
|
return IsParameterizedDerivedTypeHelper<common::TypeParamAttr::Kind>(*this);
|
|
}
|
|
|
|
const DeclTypeSpec *Scope::FindInstantiatedDerivedType(
|
|
const DerivedTypeSpec &spec, DeclTypeSpec::Category category) const {
|
|
DeclTypeSpec type{category, spec};
|
|
if (const auto *result{FindType(type)}) {
|
|
return result;
|
|
} else if (IsGlobal()) {
|
|
return nullptr;
|
|
} else {
|
|
return parent().FindInstantiatedDerivedType(spec, category);
|
|
}
|
|
}
|
|
|
|
const Scope *Scope::GetDerivedTypeParent() const {
|
|
if (const Symbol * symbol{GetSymbol()}) {
|
|
if (const DerivedTypeSpec * parent{symbol->GetParentTypeSpec(this)}) {
|
|
return parent->scope();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const Scope &Scope::GetDerivedTypeBase() const {
|
|
const Scope *child{this};
|
|
for (const Scope *parent{GetDerivedTypeParent()}; parent != nullptr;
|
|
parent = child->GetDerivedTypeParent()) {
|
|
child = parent;
|
|
}
|
|
return *child;
|
|
}
|
|
|
|
void Scope::InstantiateDerivedTypes() {
|
|
for (DeclTypeSpec &type : declTypeSpecs_) {
|
|
if (type.category() == DeclTypeSpec::TypeDerived ||
|
|
type.category() == DeclTypeSpec::ClassDerived) {
|
|
type.derivedTypeSpec().Instantiate(*this);
|
|
}
|
|
}
|
|
}
|
|
} // namespace Fortran::semantics
|