Files
llvm/flang/lib/Semantics/assignment.cpp
Peter Klausler 2780c209e1 [flang] Emit error on impossible-to-implement construct (#160384)
An assignment to a whole polymorphic allocatable changes its dynamic
type to the type of the right-hand side expression. But when the
assignment is under control of a WHERE statement, or a FORALL / DO
CONCURRENT with a mask expression, there is no interpretation of the
assignment, as the type of a variable must be the same for all of its
elements.

There is no restriction in the standard against this usage, and no other
Fortran compiler complains about it. But it is not possible to implement
it in general, and the behavior produced by other compilers is not
reasonable, much less worthy of emulating. It's best to simply disallow
it with an error message.

Fixes https://github.com/llvm/llvm-project/issues/133669, or more
accurately, resolves it.
2025-09-30 10:34:41 -07:00

249 lines
8.5 KiB
C++

//===-- lib/Semantics/assignment.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 "assignment.h"
#include "definable.h"
#include "pointer-assignment.h"
#include "flang/Common/idioms.h"
#include "flang/Common/restorer.h"
#include "flang/Evaluate/characteristics.h"
#include "flang/Evaluate/expression.h"
#include "flang/Evaluate/fold.h"
#include "flang/Evaluate/tools.h"
#include "flang/Parser/message.h"
#include "flang/Parser/parse-tree-visitor.h"
#include "flang/Parser/parse-tree.h"
#include "flang/Semantics/expression.h"
#include "flang/Semantics/symbol.h"
#include "flang/Semantics/tools.h"
#include <optional>
#include <set>
#include <string>
#include <type_traits>
using namespace Fortran::parser::literals;
namespace Fortran::semantics {
class AssignmentContext {
public:
explicit AssignmentContext(SemanticsContext &context) : context_{context} {}
AssignmentContext(AssignmentContext &&) = default;
AssignmentContext(const AssignmentContext &) = delete;
bool operator==(const AssignmentContext &x) const { return this == &x; }
template <typename A> void PushWhereContext(const A &);
void PopWhereContext();
void Analyze(const parser::AssignmentStmt &);
void Analyze(const parser::PointerAssignmentStmt &);
SemanticsContext &context() { return context_; }
private:
bool CheckForPureContext(const SomeExpr &rhs, parser::CharBlock rhsSource);
void CheckShape(parser::CharBlock, const SomeExpr *);
template <typename... A>
parser::Message *Say(parser::CharBlock at, A &&...args) {
return &context_.Say(at, std::forward<A>(args)...);
}
evaluate::FoldingContext &foldingContext() {
return context_.foldingContext();
}
SemanticsContext &context_;
int whereDepth_{0}; // number of WHEREs currently nested in
// shape of masks in LHS of assignments in current WHERE:
std::vector<std::optional<std::int64_t>> whereExtents_;
};
void AssignmentContext::Analyze(const parser::AssignmentStmt &stmt) {
if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
const SomeExpr &lhs{assignment->lhs};
const SomeExpr &rhs{assignment->rhs};
auto lhsLoc{std::get<parser::Variable>(stmt.t).GetSource()};
const Scope &scope{context_.FindScope(lhsLoc)};
DefinabilityFlags flags{DefinabilityFlag::VectorSubscriptIsOk};
bool isDefinedAssignment{
std::holds_alternative<evaluate::ProcedureRef>(assignment->u)};
if (isDefinedAssignment) {
flags.set(DefinabilityFlag::AllowEventLockOrNotifyType);
} else if (const Symbol *
whole{evaluate::UnwrapWholeSymbolOrComponentDataRef(lhs)}) {
if (IsAllocatable(whole->GetUltimate())) {
flags.set(DefinabilityFlag::PotentialDeallocation);
if (IsPolymorphic(*whole) && whereDepth_ > 0) {
Say(lhsLoc,
"Assignment to whole polymorphic allocatable '%s' may not be nested in a WHERE statement or construct"_err_en_US,
whole->name());
}
}
}
if (auto whyNot{WhyNotDefinable(lhsLoc, scope, flags, lhs)}) {
if (whyNot->IsFatal()) {
if (auto *msg{Say(lhsLoc,
"Left-hand side of assignment is not definable"_err_en_US)}) {
msg->Attach(
std::move(whyNot->set_severity(parser::Severity::Because)));
}
} else {
context_.Say(std::move(*whyNot));
}
}
auto rhsLoc{std::get<parser::Expr>(stmt.t).source};
if (!isDefinedAssignment) {
CheckForPureContext(rhs, rhsLoc);
}
if (whereDepth_ > 0) {
CheckShape(lhsLoc, &lhs);
}
}
}
void AssignmentContext::Analyze(const parser::PointerAssignmentStmt &stmt) {
CHECK(whereDepth_ == 0);
if (const evaluate::Assignment * assignment{GetAssignment(stmt)}) {
parser::CharBlock at{context_.location().value()};
auto restorer{foldingContext().messages().SetLocation(at)};
CheckPointerAssignment(context_, *assignment, context_.FindScope(at));
}
}
static std::optional<std::string> GetPointerComponentDesignatorName(
const SomeExpr &expr) {
if (const auto *derived{
evaluate::GetDerivedTypeSpec(evaluate::DynamicType::From(expr))}) {
PotentialAndPointerComponentIterator potentials{*derived};
if (auto pointer{
std::find_if(potentials.begin(), potentials.end(), IsPointer)}) {
return pointer.BuildResultDesignatorName();
}
}
return std::nullopt;
}
// Checks C1594(5,6); false if check fails
bool CheckCopyabilityInPureScope(parser::ContextualMessages &messages,
const SomeExpr &expr, const Scope &scope) {
if (auto pointer{GetPointerComponentDesignatorName(expr)}) {
if (const Symbol * base{GetFirstSymbol(expr)}) {
const char *why{WhyBaseObjectIsSuspicious(base->GetUltimate(), scope)};
if (!why) {
if (auto coarray{evaluate::ExtractCoarrayRef(expr)}) {
base = &coarray->GetLastSymbol();
why = "coindexed";
}
}
if (why) {
evaluate::SayWithDeclaration(messages, *base,
"A pure subprogram may not copy the value of '%s' because it is %s"
" and has the POINTER potential subobject component '%s'"_err_en_US,
base->name(), why, *pointer);
return false;
}
}
}
return true;
}
bool AssignmentContext::CheckForPureContext(
const SomeExpr &rhs, parser::CharBlock rhsSource) {
const Scope &scope{context_.FindScope(rhsSource)};
if (FindPureProcedureContaining(scope)) {
parser::ContextualMessages messages{
context_.location().value(), &context_.messages()};
return CheckCopyabilityInPureScope(messages, rhs, scope);
} else {
return true;
}
}
// 10.2.3.1(2) The masks and LHS of assignments must be arrays of the same shape
void AssignmentContext::CheckShape(parser::CharBlock at, const SomeExpr *expr) {
if (auto shape{evaluate::GetShape(foldingContext(), expr)}) {
std::size_t size{shape->size()};
if (size == 0) {
Say(at, "The mask or variable must not be scalar"_err_en_US);
}
if (whereDepth_ == 0) {
whereExtents_.resize(size);
} else if (whereExtents_.size() != size) {
Say(at,
"Must have rank %zd to match prior mask or assignment of"
" WHERE construct"_err_en_US,
whereExtents_.size());
return;
}
for (std::size_t i{0}; i < size; ++i) {
if (std::optional<std::int64_t> extent{evaluate::ToInt64((*shape)[i])}) {
if (!whereExtents_[i]) {
whereExtents_[i] = *extent;
} else if (*whereExtents_[i] != *extent) {
Say(at,
"Dimension %d must have extent %jd to match prior mask or"
" assignment of WHERE construct"_err_en_US,
i + 1, *whereExtents_[i]);
}
}
}
}
}
template <typename A> void AssignmentContext::PushWhereContext(const A &x) {
const auto &expr{std::get<parser::LogicalExpr>(x.t)};
CheckShape(expr.thing.value().source, GetExpr(context_, expr));
++whereDepth_;
}
void AssignmentContext::PopWhereContext() {
--whereDepth_;
if (whereDepth_ == 0) {
whereExtents_.clear();
}
}
AssignmentChecker::~AssignmentChecker() {}
SemanticsContext &AssignmentChecker::context() {
return context_.value().context();
}
AssignmentChecker::AssignmentChecker(SemanticsContext &context)
: context_{new AssignmentContext{context}} {}
void AssignmentChecker::Enter(
const parser::OpenMPDeclareReductionConstruct &x) {
context().set_location(x.source);
}
void AssignmentChecker::Enter(const parser::AssignmentStmt &x) {
context_.value().Analyze(x);
}
void AssignmentChecker::Enter(const parser::PointerAssignmentStmt &x) {
context_.value().Analyze(x);
}
void AssignmentChecker::Enter(const parser::WhereStmt &x) {
context_.value().PushWhereContext(x);
}
void AssignmentChecker::Leave(const parser::WhereStmt &) {
context_.value().PopWhereContext();
}
void AssignmentChecker::Enter(const parser::WhereConstructStmt &x) {
context_.value().PushWhereContext(x);
}
void AssignmentChecker::Leave(const parser::EndWhereStmt &) {
context_.value().PopWhereContext();
}
void AssignmentChecker::Enter(const parser::MaskedElsewhereStmt &x) {
context_.value().PushWhereContext(x);
}
void AssignmentChecker::Leave(const parser::MaskedElsewhereStmt &) {
context_.value().PopWhereContext();
}
} // namespace Fortran::semantics
template class Fortran::common::Indirection<
Fortran::semantics::AssignmentContext>;