[flang-commits] [flang] d7a1351 - [flang] Enforce accessibility requirement on type-bound generic operators, &c.
Peter Klausler via flang-commits
flang-commits at lists.llvm.org
Sat Dec 3 10:13:10 PST 2022
Author: Peter Klausler
Date: 2022-12-03T10:12:58-08:00
New Revision: d7a1351bb8d74af7fb5ed4b9df3125b193f2984b
URL: https://github.com/llvm/llvm-project/commit/d7a1351bb8d74af7fb5ed4b9df3125b193f2984b
DIFF: https://github.com/llvm/llvm-project/commit/d7a1351bb8d74af7fb5ed4b9df3125b193f2984b.diff
LOG: [flang] Enforce accessibility requirement on type-bound generic operators, &c.
Type-bound generics like operator(+) and assignment(=) need to not be
PRIVATE if they are used outside the module in which they are declared.
Differential Revision: https://reviews.llvm.org/D139123
Added:
Modified:
flang/include/flang/Semantics/scope.h
flang/include/flang/Semantics/tools.h
flang/lib/Semantics/expression.cpp
flang/lib/Semantics/resolve-names.cpp
flang/lib/Semantics/tools.cpp
flang/test/Semantics/resolve34.f90
flang/test/Semantics/resolve63.f90
flang/test/Semantics/resolve64.f90
Removed:
################################################################################
diff --git a/flang/include/flang/Semantics/scope.h b/flang/include/flang/Semantics/scope.h
index d0fb37962dfb..0c5beb6492c6 100644
--- a/flang/include/flang/Semantics/scope.h
+++ b/flang/include/flang/Semantics/scope.h
@@ -117,6 +117,7 @@ class Scope {
const Scope *GetDerivedTypeParent() const;
const Scope &GetDerivedTypeBase() const;
inline std::optional<SourceName> GetName() const;
+ // Returns true if this scope contains, or is, another scope.
bool Contains(const Scope &) const;
/// Make a scope nested in this one
Scope &MakeScope(Kind kind, Symbol *symbol = nullptr);
diff --git a/flang/include/flang/Semantics/tools.h b/flang/include/flang/Semantics/tools.h
index 88cb720c3d62..da5c93663101 100644
--- a/flang/include/flang/Semantics/tools.h
+++ b/flang/include/flang/Semantics/tools.h
@@ -85,8 +85,13 @@ bool IsIntrinsicConcat(
bool IsGenericDefinedOp(const Symbol &);
bool IsDefinedOperator(SourceName);
std::string MakeOpName(SourceName);
+
+// Returns true if maybeAncestor exists and is a proper ancestor of a
+// descendent scope (or symbol owner). Will be false, unlike Scope::Contains(),
+// if maybeAncestor *is* the descendent.
bool DoesScopeContain(const Scope *maybeAncestor, const Scope &maybeDescendent);
bool DoesScopeContain(const Scope *, const Symbol &);
+
bool IsUseAssociated(const Symbol &, const Scope &);
bool IsHostAssociated(const Symbol &, const Scope &);
bool IsHostAssociatedIntoSubprogram(const Symbol &, const Scope &);
@@ -182,8 +187,9 @@ bool HasCoarray(const parser::Expr &);
bool IsAssumedType(const Symbol &);
bool IsPolymorphic(const Symbol &);
bool IsPolymorphicAllocatable(const Symbol &);
-// Return an error if component symbol is not accessible from scope (7.5.4.8(2))
-std::optional<parser::MessageFormattedText> CheckAccessibleComponent(
+
+// Return an error if a symbol is not accessible from a scope
+std::optional<parser::MessageFormattedText> CheckAccessibleSymbol(
const semantics::Scope &, const Symbol &);
// Analysis of image control statements
diff --git a/flang/lib/Semantics/expression.cpp b/flang/lib/Semantics/expression.cpp
index cd0c717c7cf1..16e23df10fed 100644
--- a/flang/lib/Semantics/expression.cpp
+++ b/flang/lib/Semantics/expression.cpp
@@ -155,8 +155,10 @@ class ArgumentAnalyzer {
// Find and return a user-defined operator or report an error.
// The provided message is used if there is no such operator.
- MaybeExpr TryDefinedOp(const char *, parser::MessageFixedText,
- const Symbol **definedOpSymbolPtr = nullptr, bool isUserOp = false);
+ // If a definedOpSymbolPtr is provided, the caller must check
+ // for its accessibility.
+ MaybeExpr TryDefinedOp(
+ const char *, parser::MessageFixedText, bool isUserOp = false);
template <typename E>
MaybeExpr TryDefinedOp(E opr, parser::MessageFixedText msg) {
return TryDefinedOp(
@@ -175,7 +177,7 @@ class ArgumentAnalyzer {
MaybeExpr AnalyzeExprOrWholeAssumedSizeArray(const parser::Expr &);
bool AreConformable() const;
const Symbol *FindBoundOp(parser::CharBlock, int passIndex,
- const Symbol *&definedOp, bool isSubroutine);
+ const Symbol *&generic, bool isSubroutine);
void AddAssignmentConversion(
const DynamicType &lhsType, const DynamicType &rhsType);
bool OkLogicalIntegerAssignment(TypeCategory lhs, TypeCategory rhs);
@@ -1778,10 +1780,9 @@ MaybeExpr ExpressionAnalyzer::Analyze(
}
}
if (symbol) {
- if (const auto *currScope{context_.globalScope().FindScope(source)}) {
- if (auto msg{CheckAccessibleComponent(*currScope, *symbol)}) {
- Say(source, *msg);
- }
+ const semantics::Scope &innermost{context_.FindScope(expr.source)};
+ if (auto msg{CheckAccessibleSymbol(innermost, *symbol)}) {
+ Say(expr.source, std::move(*msg));
}
if (checkConflicts) {
auto componentIter{
@@ -1809,7 +1810,6 @@ MaybeExpr ExpressionAnalyzer::Analyze(
}
unavailable.insert(symbol->name());
if (value) {
- const auto &innermost{context_.FindScope(expr.source)};
if (symbol->has<semantics::ProcEntityDetails>()) {
CHECK(IsPointer(*symbol));
} else if (symbol->has<semantics::ObjectEntityDetails>()) {
@@ -2869,7 +2869,7 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::DefinedUnary &x) {
ArgumentAnalyzer analyzer{*this, name.source};
analyzer.Analyze(std::get<1>(x.t));
return analyzer.TryDefinedOp(name.source.ToString().c_str(),
- "No operator %s defined for %s"_err_en_US, nullptr, true);
+ "No operator %s defined for %s"_err_en_US, true);
}
// Binary (dyadic) operations
@@ -3053,7 +3053,7 @@ MaybeExpr ExpressionAnalyzer::Analyze(const parser::Expr::DefinedBinary &x) {
analyzer.Analyze(std::get<1>(x.t));
analyzer.Analyze(std::get<2>(x.t));
return analyzer.TryDefinedOp(name.source.ToString().c_str(),
- "No operator %s defined for %s and %s"_err_en_US, nullptr, true);
+ "No operator %s defined for %s and %s"_err_en_US, true);
}
// Returns true if a parsed function reference should be converted
@@ -3635,63 +3635,100 @@ bool ArgumentAnalyzer::CheckForNullPointer(const char *where) {
return true;
}
-MaybeExpr ArgumentAnalyzer::TryDefinedOp(const char *opr,
- parser::MessageFixedText error, const Symbol **definedOpSymbolPtr,
- bool isUserOp) {
+MaybeExpr ArgumentAnalyzer::TryDefinedOp(
+ const char *opr, parser::MessageFixedText error, bool isUserOp) {
if (AnyUntypedOrMissingOperand()) {
context_.Say(error, ToUpperCase(opr), TypeAsFortran(0), TypeAsFortran(1));
return std::nullopt;
}
- const Symbol *localDefinedOpSymbolPtr{nullptr};
- if (!definedOpSymbolPtr) {
- definedOpSymbolPtr = &localDefinedOpSymbolPtr;
- }
+ MaybeExpr result;
+ bool anyPossibilities{false};
+ std::optional<parser::MessageFormattedText> inaccessible;
+ std::vector<const Symbol *> hit;
+ std::string oprNameString{
+ isUserOp ? std::string{opr} : "operator("s + opr + ')'};
+ parser::CharBlock oprName{oprNameString};
{
auto restorer{context_.GetContextualMessages().DiscardMessages()};
- std::string oprNameString{
- isUserOp ? std::string{opr} : "operator("s + opr + ')'};
- parser::CharBlock oprName{oprNameString};
const auto &scope{context_.context().FindScope(source_)};
if (Symbol *symbol{scope.FindSymbol(oprName)}) {
- *definedOpSymbolPtr = symbol;
+ anyPossibilities = true;
parser::Name name{symbol->name(), symbol};
- if (auto result{context_.AnalyzeDefinedOp(name, GetActuals())}) {
- return result;
+ result = context_.AnalyzeDefinedOp(name, GetActuals());
+ if (result) {
+ inaccessible = CheckAccessibleSymbol(scope, *symbol);
+ if (inaccessible) {
+ result.reset();
+ } else {
+ hit.push_back(symbol);
+ }
}
}
for (std::size_t passIndex{0}; passIndex < actuals_.size(); ++passIndex) {
- if (const Symbol *
- symbol{FindBoundOp(oprName, passIndex, *definedOpSymbolPtr, false)}) {
- if (MaybeExpr result{TryBoundOp(*symbol, passIndex)}) {
- return result;
+ const Symbol *generic{nullptr};
+ if (const Symbol *binding{
+ FindBoundOp(oprName, passIndex, generic, false)}) {
+ anyPossibilities = true;
+ if (MaybeExpr thisResult{TryBoundOp(*binding, passIndex)}) {
+ if (auto thisInaccessible{
+ CheckAccessibleSymbol(scope, DEREF(generic))}) {
+ inaccessible = thisInaccessible;
+ } else {
+ result = std::move(thisResult);
+ hit.push_back(binding);
+ }
}
}
}
}
- if (*definedOpSymbolPtr) {
- SayNoMatch(ToUpperCase((*definedOpSymbolPtr)->name().ToString()));
- } else if (actuals_.size() == 1 || AreConformable()) {
- if (CheckForNullPointer()) {
- context_.Say(error, ToUpperCase(opr), TypeAsFortran(0), TypeAsFortran(1));
+ if (result) {
+ if (hit.size() > 1) {
+ if (auto *msg{context_.Say(
+ "%zd matching accessible generic interfaces for %s were found"_err_en_US,
+ hit.size(), ToUpperCase(opr))}) {
+ for (const Symbol *symbol : hit) {
+ AttachDeclaration(*msg, *symbol);
+ }
+ }
}
- } else {
+ } else if (inaccessible) {
+ context_.Say(source_, std::move(*inaccessible));
+ } else if (anyPossibilities) {
+ SayNoMatch(ToUpperCase(oprNameString), false);
+ } else if (actuals_.size() == 2 && !AreConformable()) {
context_.Say(
"Operands of %s are not conformable; have rank %d and rank %d"_err_en_US,
ToUpperCase(opr), actuals_[0]->Rank(), actuals_[1]->Rank());
+ } else if (CheckForNullPointer()) {
+ context_.Say(error, ToUpperCase(opr), TypeAsFortran(0), TypeAsFortran(1));
}
- return std::nullopt;
+ return result;
}
MaybeExpr ArgumentAnalyzer::TryDefinedOp(
std::vector<const char *> oprs, parser::MessageFixedText error) {
- const Symbol *definedOpSymbolPtr{nullptr};
- for (std::size_t i{1}; i < oprs.size(); ++i) {
+ if (oprs.size() == 1) {
+ return TryDefinedOp(oprs[0], error);
+ }
+ MaybeExpr result;
+ std::vector<const char *> hit;
+ {
auto restorer{context_.GetContextualMessages().DiscardMessages()};
- if (auto result{TryDefinedOp(oprs[i], error, &definedOpSymbolPtr)}) {
- return result;
+ for (std::size_t i{0}; i < oprs.size(); ++i) {
+ if (MaybeExpr thisResult{TryDefinedOp(oprs[i], error)}) {
+ result = std::move(thisResult);
+ hit.push_back(oprs[i]);
+ }
}
}
- return TryDefinedOp(oprs[0], error, &definedOpSymbolPtr);
+ if (hit.empty()) { // for the error
+ result = TryDefinedOp(oprs[0], error);
+ } else if (hit.size() > 1) {
+ context_.Say(
+ "Matching accessible definitions were found with %zd variant spellings of the generic operator ('%s', '%s')"_err_en_US,
+ hit.size(), ToUpperCase(hit[0]), ToUpperCase(hit[1]));
+ }
+ return result;
}
MaybeExpr ArgumentAnalyzer::TryBoundOp(const Symbol &symbol, int passIndex) {
@@ -3768,31 +3805,34 @@ bool ArgumentAnalyzer::OkLogicalIntegerAssignment(
}
std::optional<ProcedureRef> ArgumentAnalyzer::GetDefinedAssignmentProc() {
- auto restorer{context_.GetContextualMessages().DiscardMessages()};
+ const Symbol *proc{nullptr};
+ int passedObjectIndex{-1};
std::string oprNameString{"assignment(=)"};
parser::CharBlock oprName{oprNameString};
- const Symbol *proc{nullptr};
const auto &scope{context_.context().FindScope(source_)};
- if (const Symbol *symbol{scope.FindSymbol(oprName)}) {
- ExpressionAnalyzer::AdjustActuals noAdjustment;
- auto pair{context_.ResolveGeneric(*symbol, actuals_, noAdjustment, true)};
- if (pair.first) {
- proc = pair.first;
- } else {
- context_.EmitGenericResolutionError(*symbol, pair.second, true);
- }
- }
- int passedObjectIndex{-1};
- const Symbol *definedOpSymbol{nullptr};
- for (std::size_t i{0}; i < actuals_.size(); ++i) {
- if (const Symbol *
- specific{FindBoundOp(oprName, i, definedOpSymbol, true)}) {
- if (const Symbol *
- resolution{GetBindingResolution(GetType(i), *specific)}) {
- proc = resolution;
+ // If multiple resolutions were possible, they will have been already
+ // diagnosed.
+ {
+ auto restorer{context_.GetContextualMessages().DiscardMessages()};
+ if (const Symbol *symbol{scope.FindSymbol(oprName)}) {
+ ExpressionAnalyzer::AdjustActuals noAdjustment;
+ auto pair{context_.ResolveGeneric(*symbol, actuals_, noAdjustment, true)};
+ if (pair.first) {
+ proc = pair.first;
} else {
- proc = specific;
- passedObjectIndex = i;
+ context_.EmitGenericResolutionError(*symbol, pair.second, true);
+ }
+ }
+ for (std::size_t i{0}; i < actuals_.size(); ++i) {
+ const Symbol *generic{nullptr};
+ if (const Symbol *specific{FindBoundOp(oprName, i, generic, true)}) {
+ if (const Symbol *resolution{
+ GetBindingResolution(GetType(i), *specific)}) {
+ proc = resolution;
+ } else {
+ proc = specific;
+ passedObjectIndex = i;
+ }
}
}
}
@@ -3871,24 +3911,23 @@ bool ArgumentAnalyzer::AreConformable() const {
// Look for a type-bound operator in the type of arg number passIndex.
const Symbol *ArgumentAnalyzer::FindBoundOp(parser::CharBlock oprName,
- int passIndex, const Symbol *&definedOp, bool isSubroutine) {
+ int passIndex, const Symbol *&generic, bool isSubroutine) {
const auto *type{GetDerivedTypeSpec(GetType(passIndex))};
if (!type || !type->scope()) {
return nullptr;
}
- const Symbol *symbol{type->scope()->FindComponent(oprName)};
- if (!symbol) {
+ generic = type->scope()->FindComponent(oprName);
+ if (!generic) {
return nullptr;
}
- definedOp = symbol;
ExpressionAnalyzer::AdjustActuals adjustment{
[&](const Symbol &proc, ActualArguments &) {
return passIndex == GetPassIndex(proc);
}};
auto pair{
- context_.ResolveGeneric(*symbol, actuals_, adjustment, isSubroutine)};
+ context_.ResolveGeneric(*generic, actuals_, adjustment, isSubroutine)};
if (!pair.first) {
- context_.EmitGenericResolutionError(*symbol, pair.second, isSubroutine);
+ context_.EmitGenericResolutionError(*generic, pair.second, isSubroutine);
}
return pair.first;
}
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 980f1b188ce0..983f5b465355 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -5989,7 +5989,7 @@ bool DeclarationVisitor::OkToAddComponent(
} else if (extends) {
msg = "Type cannot be extended as it has a component named"
" '%s'"_err_en_US;
- } else if (CheckAccessibleComponent(currScope(), *prev)) {
+ } else if (CheckAccessibleSymbol(currScope(), *prev)) {
// inaccessible component -- redeclaration is ok
msg = "Component '%s' is inaccessibly declared in or as a "
"parent of this derived type"_warn_en_US;
@@ -6864,8 +6864,7 @@ const parser::Name *DeclarationVisitor::FindComponent(
derived->Instantiate(currScope()); // in case of forward referenced type
if (const Scope * scope{derived->scope()}) {
if (Resolve(component, scope->FindComponent(component.source))) {
- if (auto msg{
- CheckAccessibleComponent(currScope(), *component.symbol)}) {
+ if (auto msg{CheckAccessibleSymbol(currScope(), *component.symbol)}) {
context().Say(component.source, *msg);
}
return &component;
diff --git a/flang/lib/Semantics/tools.cpp b/flang/lib/Semantics/tools.cpp
index 5ac5f9de54bb..20c87ae759bd 100644
--- a/flang/lib/Semantics/tools.cpp
+++ b/flang/lib/Semantics/tools.cpp
@@ -991,9 +991,8 @@ bool IsPolymorphicAllocatable(const Symbol &symbol) {
return IsAllocatable(symbol) && IsPolymorphic(symbol);
}
-std::optional<parser::MessageFormattedText> CheckAccessibleComponent(
+std::optional<parser::MessageFormattedText> CheckAccessibleSymbol(
const Scope &scope, const Symbol &symbol) {
- CHECK(symbol.owner().IsDerivedType()); // symbol must be a component
if (symbol.attrs().test(Attr::PRIVATE)) {
if (FindModuleFileContaining(scope)) {
// Don't enforce component accessibility checks in module files;
@@ -1003,7 +1002,7 @@ std::optional<parser::MessageFormattedText> CheckAccessibleComponent(
moduleScope{FindModuleContaining(symbol.owner())}) {
if (!moduleScope->Contains(scope)) {
return parser::MessageFormattedText{
- "PRIVATE component '%s' is only accessible within module '%s'"_err_en_US,
+ "PRIVATE name '%s' is only accessible within module '%s'"_err_en_US,
symbol.name(), moduleScope->GetName().value()};
}
}
diff --git a/flang/test/Semantics/resolve34.f90 b/flang/test/Semantics/resolve34.f90
index e0c61689d7d0..4ddb8fd0b0eb 100644
--- a/flang/test/Semantics/resolve34.f90
+++ b/flang/test/Semantics/resolve34.f90
@@ -91,9 +91,9 @@ subroutine s7
type(t2) :: x
integer :: j
j = x%i2
- !ERROR: PRIVATE component 'i3' is only accessible within module 'm7'
+ !ERROR: PRIVATE name 'i3' is only accessible within module 'm7'
j = x%i3
- !ERROR: PRIVATE component 't1' is only accessible within module 'm7'
+ !ERROR: PRIVATE name 't1' is only accessible within module 'm7'
j = x%t1%i1
end
@@ -117,11 +117,11 @@ subroutine s1
subroutine s8
use m8
type(t) :: x
- !ERROR: PRIVATE component 'i2' is only accessible within module 'm8'
+ !ERROR: PRIVATE name 'i2' is only accessible within module 'm8'
x = t(2, 5)
- !ERROR: PRIVATE component 'i2' is only accessible within module 'm8'
+ !ERROR: PRIVATE name 'i2' is only accessible within module 'm8'
x = t(i1=2, i2=5)
- !ERROR: PRIVATE component 'i2' is only accessible within module 'm8'
+ !ERROR: PRIVATE name 'i2' is only accessible within module 'm8'
a = [y%i2]
end
@@ -143,3 +143,24 @@ module subroutine s
x = t(i1=2, i2=5) !OK
end
end
+
+module m10
+ type t
+ integer n
+ contains
+ procedure :: f
+ generic, private :: operator(+) => f
+ end type
+ contains
+ type(t) function f(x,y)
+ class(t), intent(in) :: x, y
+ f = t(x%n + y%n)
+ end function
+end module
+subroutine s10
+ use m10
+ type(t) x
+ x = t(1)
+ !ERROR: PRIVATE name 'operator(+)' is only accessible within module 'm10'
+ x = x + x
+end subroutine
diff --git a/flang/test/Semantics/resolve63.f90 b/flang/test/Semantics/resolve63.f90
index af6c2e4d94d1..a3154d82153a 100644
--- a/flang/test/Semantics/resolve63.f90
+++ b/flang/test/Semantics/resolve63.f90
@@ -58,15 +58,15 @@ subroutine test_relational()
l = z'fe' == r !OK
l = cVar == z'fe' !OK
l = z'fe' == cVar !OK
- !ERROR: No intrinsic or user-defined OPERATOR(==) matches operand types CHARACTER(KIND=1) and INTEGER(4)
+ !ERROR: Operands of .EQ. must have comparable types; have CHARACTER(KIND=1) and INTEGER(4)
l = charVar == z'fe'
- !ERROR: No intrinsic or user-defined OPERATOR(==) matches operand types INTEGER(4) and CHARACTER(KIND=1)
+ !ERROR: Operands of .EQ. must have comparable types; have INTEGER(4) and CHARACTER(KIND=1)
l = z'fe' == charVar
- !ERROR: No intrinsic or user-defined OPERATOR(==) matches operand types LOGICAL(4) and INTEGER(4)
- l = l == z'fe' !OK
- !ERROR: No intrinsic or user-defined OPERATOR(==) matches operand types INTEGER(4) and LOGICAL(4)
- l = z'fe' == l !OK
- !ERROR: No intrinsic or user-defined OPERATOR(==) matches operand types TYPE(t) and REAL(4)
+ !ERROR: Operands of .EQ. must have comparable types; have LOGICAL(4) and INTEGER(4)
+ l = l == z'fe'
+ !ERROR: Operands of .EQ. must have comparable types; have INTEGER(4) and LOGICAL(4)
+ l = z'fe' == l
+ !ERROR: Operands of .EQ. must have comparable types; have TYPE(t) and REAL(4)
l = x == r
lVar = z'a' == b'1010' !OK
@@ -265,9 +265,9 @@ subroutine test(x, y, z)
i = x + y
i = x + i
i = y + i
- !ERROR: No intrinsic or user-defined OPERATOR(+) matches operand types CLASS(t2) and CLASS(t1)
+ !ERROR: Operands of + must be numeric; have CLASS(t2) and CLASS(t1)
i = y + x
- !ERROR: No intrinsic or user-defined OPERATOR(+) matches operand types INTEGER(4) and CLASS(t1)
+ !ERROR: Operands of + must be numeric; have INTEGER(4) and CLASS(t1)
i = i + x
end
end
@@ -307,9 +307,9 @@ subroutine test
j = null() - null(mold=x1)
j = null(mold=x1) - null()
j = null() - null()
- !ERROR: No intrinsic or user-defined OPERATOR(/) matches operand types untyped and TYPE(t1)
+ !ERROR: A NULL() pointer is not allowed as an operand here
j = null() / null(mold=x1)
- !ERROR: No intrinsic or user-defined OPERATOR(/) matches operand types TYPE(t1) and untyped
+ !ERROR: A NULL() pointer is not allowed as an operand here
j = null(mold=x1) / null()
!ERROR: A NULL() pointer is not allowed as an operand here
j = null() / null()
diff --git a/flang/test/Semantics/resolve64.f90 b/flang/test/Semantics/resolve64.f90
index 5a6bdc158a13..f49766d25dab 100644
--- a/flang/test/Semantics/resolve64.f90
+++ b/flang/test/Semantics/resolve64.f90
@@ -37,9 +37,9 @@ logical function f2(x, y)
subroutine s1(x, y, z)
logical :: x
complex :: y, z
- !ERROR: No intrinsic or user-defined OPERATOR(.A.) matches operand types COMPLEX(4) and COMPLEX(4)
+ !ERROR: Operands of .AND. must be LOGICAL; have COMPLEX(4) and COMPLEX(4)
x = y .and. z
- !ERROR: No intrinsic or user-defined OPERATOR(.A.) matches operand types COMPLEX(4) and COMPLEX(4)
+ !ERROR: Operands of .AND. must be LOGICAL; have COMPLEX(4) and COMPLEX(4)
x = y .a. z
end
end
More information about the flang-commits
mailing list