Skip to content

[flang] Improve procedure interface compatibility checking for dummy … #72704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion flang/include/flang/Common/Fortran-features.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable,
ShortCharacterActual, ExprPassedToVolatile, ImplicitInterfaceActual,
PolymorphicTransferArg, PointerComponentTransferArg, TransferSizePresence,
F202XAllocatableBreakingChange, DimMustBePresent, CommonBlockPadding,
LogicalVsCBool, BindCCharLength)
LogicalVsCBool, BindCCharLength, ProcDummyArgShapes)

using LanguageFeatures = EnumSet<LanguageFeature, LanguageFeature_enumSize>;
using UsageWarnings = EnumSet<UsageWarning, UsageWarning_enumSize>;
Expand Down
14 changes: 8 additions & 6 deletions flang/include/flang/Evaluate/characteristics.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ bool DistinguishableOpOrAssign(const common::LanguageFeatureControl &,
// Shapes of function results and dummy arguments have to have
// the same rank, the same deferred dimensions, and the same
// values for explicit dimensions when constant.
bool ShapesAreCompatible(const Shape &, const Shape &);
bool ShapesAreCompatible(
const Shape &, const Shape &, bool *possibleWarning = nullptr);

class TypeAndShape {
public:
Expand Down Expand Up @@ -222,8 +223,8 @@ struct DummyDataObject {
bool operator!=(const DummyDataObject &that) const {
return !(*this == that);
}
bool IsCompatibleWith(
const DummyDataObject &, std::string *whyNot = nullptr) const;
bool IsCompatibleWith(const DummyDataObject &, std::string *whyNot = nullptr,
std::optional<std::string> *warning = nullptr) const;
static std::optional<DummyDataObject> Characterize(
const semantics::Symbol &, FoldingContext &);
bool CanBePassedViaImplicitInterface() const;
Expand Down Expand Up @@ -283,8 +284,8 @@ struct DummyArgument {
void SetIntent(common::Intent);
bool CanBePassedViaImplicitInterface() const;
bool IsTypelessIntrinsicDummy() const;
bool IsCompatibleWith(
const DummyArgument &, std::string *whyNot = nullptr) const;
bool IsCompatibleWith(const DummyArgument &, std::string *whyNot = nullptr,
std::optional<std::string> *warning = nullptr) const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;

// name and pass are not characteristics and so do not participate in
Expand Down Expand Up @@ -379,7 +380,8 @@ struct Procedure {
bool CanBeCalledViaImplicitInterface() const;
bool CanOverride(const Procedure &, std::optional<int> passIndex) const;
bool IsCompatibleWith(const Procedure &, std::string *whyNot = nullptr,
const SpecificIntrinsic * = nullptr) const;
const SpecificIntrinsic * = nullptr,
std::optional<std::string> *warning = nullptr) const;

llvm::raw_ostream &Dump(llvm::raw_ostream &) const;

Expand Down
13 changes: 11 additions & 2 deletions flang/include/flang/Evaluate/tools.h
Original file line number Diff line number Diff line change
Expand Up @@ -1082,11 +1082,12 @@ bool IsExpandableScalar(const Expr<T> &expr, FoldingContext &context,

// Common handling for procedure pointer compatibility of left- and right-hand
// sides. Returns nullopt if they're compatible. Otherwise, it returns a
// message that needs to be augmented by the names of the left and right sides
// message that needs to be augmented by the names of the left and right sides.
std::optional<parser::MessageFixedText> CheckProcCompatibility(bool isCall,
const std::optional<characteristics::Procedure> &lhsProcedure,
const characteristics::Procedure *rhsProcedure,
const SpecificIntrinsic *specificIntrinsic, std::string &whyNotCompatible);
const SpecificIntrinsic *specificIntrinsic, std::string &whyNotCompatible,
std::optional<std::string> &warning);

// Scalar constant expansion
class ScalarConstantExpander {
Expand Down Expand Up @@ -1178,6 +1179,12 @@ class ArrayConstantBoundChanger {
ConstantSubscripts &&lbounds_;
};

// Predicate: should two expressions be considered identical for the purposes
// of determining whether two procedure interfaces are compatible, modulo
// naming of corresponding dummy arguments?
std::optional<bool> AreEquivalentInInterface(
const Expr<SubscriptInteger> &, const Expr<SubscriptInteger> &);

} // namespace Fortran::evaluate

namespace Fortran::semantics {
Expand Down Expand Up @@ -1254,6 +1261,8 @@ bool AreTkCompatibleTypes(const DeclTypeSpec *x, const DeclTypeSpec *y);

common::IgnoreTKRSet GetIgnoreTKR(const Symbol &);

std::optional<int> GetDummyArgumentNumber(const Symbol *);

} // namespace Fortran::semantics

#endif // FORTRAN_EVALUATE_TOOLS_H_
60 changes: 27 additions & 33 deletions flang/lib/Evaluate/characteristics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,23 @@ static void CopyAttrs(const semantics::Symbol &src, A &dst,
// Shapes of function results and dummy arguments have to have
// the same rank, the same deferred dimensions, and the same
// values for explicit dimensions when constant.
bool ShapesAreCompatible(const Shape &x, const Shape &y) {
bool ShapesAreCompatible(
const Shape &x, const Shape &y, bool *possibleWarning) {
if (x.size() != y.size()) {
return false;
}
auto yIter{y.begin()};
for (const auto &xDim : x) {
const auto &yDim{*yIter++};
if (xDim) {
if (!yDim || ToInt64(*xDim) != ToInt64(*yDim)) {
return false;
if (xDim && yDim) {
if (auto equiv{AreEquivalentInInterface(*xDim, *yDim)}) {
if (!*equiv) {
return false;
}
} else if (possibleWarning) {
*possibleWarning = true;
}
} else if (yDim) {
} else if (xDim || yDim) {
return false;
}
}
Expand Down Expand Up @@ -270,35 +275,19 @@ llvm::raw_ostream &TypeAndShape::Dump(llvm::raw_ostream &o) const {
bool DummyDataObject::operator==(const DummyDataObject &that) const {
return type == that.type && attrs == that.attrs && intent == that.intent &&
coshape == that.coshape && cudaDataAttr == that.cudaDataAttr;
;
}

static bool AreCompatibleDummyDataObjectShapes(const Shape &x, const Shape &y) {
int n{GetRank(x)};
if (n != GetRank(y)) {
return false;
}
auto xIter{x.begin()};
auto yIter{y.begin()};
for (; n-- > 0; ++xIter, ++yIter) {
if (auto xVal{ToInt64(*xIter)}) {
if (auto yVal{ToInt64(*yIter)}) {
if (*xVal != *yVal) {
return false;
}
}
}
}
return true;
}

bool DummyDataObject::IsCompatibleWith(
const DummyDataObject &actual, std::string *whyNot) const {
if (!AreCompatibleDummyDataObjectShapes(type.shape(), actual.type.shape())) {
bool DummyDataObject::IsCompatibleWith(const DummyDataObject &actual,
std::string *whyNot, std::optional<std::string> *warning) const {
bool possibleWarning{false};
if (!ShapesAreCompatible(
type.shape(), actual.type.shape(), &possibleWarning)) {
if (whyNot) {
*whyNot = "incompatible dummy data object shapes";
}
return false;
} else if (warning && possibleWarning) {
*warning = "distinct dummy data object shapes";
}
// Treat deduced dummy character type as if it were assumed-length character
// to avoid useless "implicit interfaces have distinct type" warnings from
Expand Down Expand Up @@ -748,11 +737,11 @@ bool DummyArgument::operator==(const DummyArgument &that) const {
return u == that.u; // name and passed-object usage are not characteristics
}

bool DummyArgument::IsCompatibleWith(
const DummyArgument &actual, std::string *whyNot) const {
bool DummyArgument::IsCompatibleWith(const DummyArgument &actual,
std::string *whyNot, std::optional<std::string> *warning) const {
if (const auto *ifaceData{std::get_if<DummyDataObject>(&u)}) {
if (const auto *actualData{std::get_if<DummyDataObject>(&actual.u)}) {
return ifaceData->IsCompatibleWith(*actualData, whyNot);
return ifaceData->IsCompatibleWith(*actualData, whyNot, warning);
}
if (whyNot) {
*whyNot = "one dummy argument is an object, the other is not";
Expand Down Expand Up @@ -1181,7 +1170,8 @@ bool Procedure::operator==(const Procedure &that) const {
}

bool Procedure::IsCompatibleWith(const Procedure &actual, std::string *whyNot,
const SpecificIntrinsic *specificIntrinsic) const {
const SpecificIntrinsic *specificIntrinsic,
std::optional<std::string> *warning) const {
// 15.5.2.9(1): if dummy is not pure, actual need not be.
// Ditto with elemental.
Attrs actualAttrs{actual.attrs};
Expand Down Expand Up @@ -1226,13 +1216,17 @@ bool Procedure::IsCompatibleWith(const Procedure &actual, std::string *whyNot,
// subroutine s1(base); subroutine s2(extended)
// procedure(s1), pointer :: p
// p => s2 ! an error, s2 is more restricted, can't handle "base"
std::optional<std::string> gotWarning;
if (!actual.dummyArguments[j].IsCompatibleWith(
dummyArguments[j], whyNot)) {
dummyArguments[j], whyNot, warning ? &gotWarning : nullptr)) {
if (whyNot) {
*whyNot = "incompatible dummy argument #"s + std::to_string(j + 1) +
": "s + *whyNot;
}
return false;
} else if (warning && !*warning && gotWarning) {
*warning = "possibly incompatible dummy argument #"s +
std::to_string(j + 1) + ": "s + std::move(*gotWarning);
}
}
return true;
Expand Down
103 changes: 100 additions & 3 deletions flang/lib/Evaluate/tools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,8 @@ std::optional<std::string> FindImpureCall(
std::optional<parser::MessageFixedText> CheckProcCompatibility(bool isCall,
const std::optional<characteristics::Procedure> &lhsProcedure,
const characteristics::Procedure *rhsProcedure,
const SpecificIntrinsic *specificIntrinsic, std::string &whyNotCompatible) {
const SpecificIntrinsic *specificIntrinsic, std::string &whyNotCompatible,
std::optional<std::string> &warning) {
std::optional<parser::MessageFixedText> msg;
if (!lhsProcedure) {
msg = "In assignment to object %s, the target '%s' is a procedure"
Expand All @@ -1096,8 +1097,8 @@ std::optional<parser::MessageFixedText> CheckProcCompatibility(bool isCall,
*rhsProcedure->functionResult, &whyNotCompatible)) {
msg =
"Function %s associated with incompatible function designator '%s': %s"_err_en_US;
} else if (lhsProcedure->IsCompatibleWith(
*rhsProcedure, &whyNotCompatible, specificIntrinsic)) {
} else if (lhsProcedure->IsCompatibleWith(*rhsProcedure, &whyNotCompatible,
specificIntrinsic, &warning)) {
// OK
} else if (isCall) {
msg = "Procedure %s associated with result of reference to function '%s'"
Expand Down Expand Up @@ -1275,6 +1276,83 @@ std::optional<Expr<SomeType>> HollerithToBOZ(FoldingContext &context,
}
}

// Extracts a whole symbol being used as a bound of a dummy argument,
// possibly wrapped with parentheses or MAX(0, ...).
template <int KIND>
static const Symbol *GetBoundSymbol(
const Expr<Type<TypeCategory::Integer, KIND>> &expr) {
using T = Type<TypeCategory::Integer, KIND>;
return common::visit(
common::visitors{
[](const Extremum<T> &max) -> const Symbol * {
if (max.ordering == Ordering::Greater) {
if (auto zero{ToInt64(max.left())}; zero && *zero == 0) {
return GetBoundSymbol(max.right());
}
}
return nullptr;
},
[](const Parentheses<T> &x) { return GetBoundSymbol(x.left()); },
[](const Designator<T> &x) -> const Symbol * {
if (const auto *ref{std::get_if<SymbolRef>(&x.u)}) {
return &**ref;
}
return nullptr;
},
[](const Convert<T, TypeCategory::Integer> &x) {
return common::visit(
[](const auto &y) -> const Symbol * {
using yType = std::decay_t<decltype(y)>;
using yResult = typename yType::Result;
if constexpr (yResult::kind <= KIND) {
return GetBoundSymbol(y);
} else {
return nullptr;
}
},
x.left().u);
},
[](const auto &) -> const Symbol * { return nullptr; },
},
expr.u);
}

std::optional<bool> AreEquivalentInInterface(
const Expr<SubscriptInteger> &x, const Expr<SubscriptInteger> &y) {
auto xVal{ToInt64(x)};
auto yVal{ToInt64(y)};
if (xVal && yVal) {
return *xVal == *yVal;
} else if (xVal || yVal) {
return false;
}
const Symbol *xSym{GetBoundSymbol(x)};
const Symbol *ySym{GetBoundSymbol(y)};
if (xSym && ySym) {
if (&xSym->GetUltimate() == &ySym->GetUltimate()) {
return true; // USE/host associated same symbol
}
auto xNum{semantics::GetDummyArgumentNumber(xSym)};
auto yNum{semantics::GetDummyArgumentNumber(ySym)};
if (xNum && yNum) {
if (*xNum == *yNum) {
auto xType{DynamicType::From(*xSym)};
auto yType{DynamicType::From(*ySym)};
return xType && yType && xType->IsEquivalentTo(*yType);
}
}
return false;
} else if (xSym || ySym) {
return false;
}
// Neither expression is an integer constant or a whole symbol.
if (x == y) {
return true;
} else {
return std::nullopt; // not sure
}
}

} // namespace Fortran::evaluate

namespace Fortran::semantics {
Expand Down Expand Up @@ -1788,4 +1866,23 @@ common::IgnoreTKRSet GetIgnoreTKR(const Symbol &symbol) {
return result;
}

std::optional<int> GetDummyArgumentNumber(const Symbol *symbol) {
if (symbol) {
if (IsDummy(*symbol)) {
if (const Symbol * subpSym{symbol->owner().symbol()}) {
if (const auto *subp{subpSym->detailsIf<SubprogramDetails>()}) {
int j{0};
for (const Symbol *dummy : subp->dummyArgs()) {
if (dummy == symbol) {
return j;
}
++j;
}
}
}
}
}
return std::nullopt;
}

} // namespace Fortran::semantics
24 changes: 20 additions & 4 deletions flang/lib/Semantics/check-call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,9 @@ static void CheckProcedureArg(evaluate::ActualArgument &arg,
}
if (interface.HasExplicitInterface()) {
std::string whyNot;
if (!interface.IsCompatibleWith(argInterface, &whyNot)) {
std::optional<std::string> warning;
if (!interface.IsCompatibleWith(argInterface, &whyNot,
/*specificIntrinsic=*/nullptr, &warning)) {
// 15.5.2.9(1): Explicit interfaces must match
if (argInterface.HasExplicitInterface()) {
messages.Say(
Expand All @@ -985,6 +987,11 @@ static void CheckProcedureArg(evaluate::ActualArgument &arg,
"Actual procedure argument has an implicit interface which is not known to be compatible with %s which has an explicit interface"_warn_en_US,
dummyName);
}
} else if (warning &&
context.ShouldWarn(common::UsageWarning::ProcDummyArgShapes)) {
messages.Say(
"Actual procedure argument has possible interface incompatibility with %s: %s"_warn_en_US,
dummyName, std::move(*warning));
}
} else { // 15.5.2.9(2,3)
if (interface.IsSubroutine() && argInterface.IsFunction()) {
Expand Down Expand Up @@ -1348,16 +1355,25 @@ static void CheckAssociated(evaluate::ActualArguments &arguments,
*targetExpr, foldingContext)}) {
bool isCall{!!UnwrapProcedureRef(*targetExpr)};
std::string whyNot;
std::optional<std::string> warning;
const auto *targetProcDesignator{
evaluate::UnwrapExpr<evaluate::ProcedureDesignator>(
*targetExpr)};
const evaluate::SpecificIntrinsic *specificIntrinsic{
targetProcDesignator
? targetProcDesignator->GetSpecificIntrinsic()
: nullptr};
if (std::optional<parser::MessageFixedText> msg{
CheckProcCompatibility(isCall, pointerProc,
&*targetProc, specificIntrinsic, whyNot)}) {
std::optional<parser::MessageFixedText> msg{
CheckProcCompatibility(isCall, pointerProc, &*targetProc,
specificIntrinsic, whyNot, warning)};
if (!msg && warning &&
semanticsContext.ShouldWarn(
common::UsageWarning::ProcDummyArgShapes)) {
msg =
"Procedures '%s' and '%s' may not be completely compatible: %s"_warn_en_US;
whyNot = std::move(*warning);
}
if (msg) {
msg->set_severity(parser::Severity::Warning);
messages.Say(std::move(*msg),
"pointer '" + pointerExpr->AsFortran() + "'",
Expand Down
Loading