[flang] CheckConformance: tristate-ify result

To ensure that errors are emitted by CheckConformance and
its callers in all situations, it's necessary for the returned result
of that function to distinguish between three possible
outcomes: the arrays are known to conform at compilation time,
the arrays are known to not conform (and a message has been
produced), and an indeterminate result in which is not possible
to determine conformance.  So convert CheckConformance's
result into an optional<bool>, and convert its confusing
Boolean flag arguments into a bit-set of named flags too.

Differential Revision: https://reviews.llvm.org/D103654
This commit is contained in:
peter klausler 2021-06-03 15:48:16 -07:00
parent ffde966cd9
commit dfecbcae0d
9 changed files with 60 additions and 38 deletions

View File

@ -144,8 +144,8 @@ public:
int Rank() const { return GetRank(shape_); }
bool IsCompatibleWith(parser::ContextualMessages &, const TypeAndShape &that,
const char *thisIs = "pointer", const char *thatIs = "target",
bool isElemental = false, bool thisIsDeferredShape = false,
bool thatIsDeferredShape = false) const;
bool isElemental = false,
enum CheckConformanceFlags::Flags = CheckConformanceFlags::None) const;
std::optional<Expr<SubscriptInteger>> MeasureElementSizeInBytes(
FoldingContext &, bool align) const;
std::optional<Expr<SubscriptInteger>> MeasureSizeInBytes(

View File

@ -239,12 +239,30 @@ std::optional<ConstantSubscripts> GetConstantExtents(
}
// Compilation-time shape conformance checking, when corresponding extents
// are known.
bool CheckConformance(parser::ContextualMessages &, const Shape &left,
const Shape &right, const char *leftIs = "left operand",
const char *rightIs = "right operand", bool leftScalarExpandable = true,
bool rightScalarExpandable = true, bool leftIsDeferredShape = false,
bool rightIsDeferredShape = false);
// are or should be known. The result is an optional Boolean:
// - nullopt: no error found or reported, but conformance cannot
// be guaranteed during compilation; this result is possible only
// when one or both arrays are allowed to have deferred shape
// - true: no error found or reported, arrays conform
// - false: errors found and reported
// Use "CheckConformance(...).value_or()" to specify a default result
// when you don't care whether messages have been emitted.
struct CheckConformanceFlags {
enum Flags {
None = 0,
LeftScalarExpandable = 1,
RightScalarExpandable = 2,
LeftIsDeferredShape = 4,
RightIsDeferredShape = 8,
EitherScalarExpandable = LeftScalarExpandable | RightScalarExpandable,
BothDeferredShape = LeftIsDeferredShape | RightIsDeferredShape,
RightIsExpandableDeferred = RightScalarExpandable | RightIsDeferredShape,
};
};
std::optional<bool> CheckConformance(parser::ContextualMessages &,
const Shape &left, const Shape &right,
CheckConformanceFlags::Flags flags = CheckConformanceFlags::None,
const char *leftIs = "left operand", const char *rightIs = "right operand");
// Increments one-based subscripts in element order (first varies fastest)
// and returns true when they remain in range; resets them all to one and

View File

@ -149,8 +149,7 @@ std::optional<TypeAndShape> TypeAndShape::Characterize(
bool TypeAndShape::IsCompatibleWith(parser::ContextualMessages &messages,
const TypeAndShape &that, const char *thisIs, const char *thatIs,
bool isElemental, bool thisIsDeferredShape,
bool thatIsDeferredShape) const {
bool isElemental, enum CheckConformanceFlags::Flags flags) const {
if (!type_.IsTkCompatibleWith(that.type_)) {
messages.Say(
"%1$s type '%2$s' is not compatible with %3$s type '%4$s'"_err_en_US,
@ -158,9 +157,8 @@ bool TypeAndShape::IsCompatibleWith(parser::ContextualMessages &messages,
return false;
}
return isElemental ||
CheckConformance(messages, shape_, that.shape_, thisIs, thatIs, false,
false /* no scalar expansion */, thisIsDeferredShape,
thatIsDeferredShape);
CheckConformance(messages, shape_, that.shape_, flags, thisIs, thatIs)
.value_or(true /*fail only when nonconformance is known now*/);
}
std::optional<Expr<SubscriptInteger>> TypeAndShape::MeasureElementSizeInBytes(

View File

@ -390,8 +390,9 @@ std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol,
.Expand(std::move(folded));
} else if (auto resultShape{GetShape(context, folded)}) {
if (CheckConformance(context.messages(), symTS->shape(),
*resultShape, "initialized object",
"initialization expression", false, false)) {
*resultShape, CheckConformanceFlags::None,
"initialized object", "initialization expression")
.value_or(false /*fail if not known now to conform*/)) {
// make a constant array with adjusted lower bounds
return ArrayConstantBoundChanger{
std::move(*AsConstantExtents(

View File

@ -1030,8 +1030,9 @@ auto ApplyElementwise(FoldingContext &context,
if (rightExpr.Rank() > 0) {
if (std::optional<Shape> rightShape{GetShape(context, rightExpr)}) {
if (auto right{AsFlatArrayConstructor(rightExpr)}) {
if (CheckConformance(
context.messages(), *leftShape, *rightShape)) {
if (CheckConformance(context.messages(), *leftShape, *rightShape,
CheckConformanceFlags::EitherScalarExpandable)
.value_or(false /*fail if not known now to conform*/)) {
return MapOperation(context, std::move(f), *leftShape,
std::move(*left), std::move(*right));
} else {

View File

@ -759,18 +759,16 @@ auto GetShapeHelper::operator()(const ProcedureRef &call) const -> Result {
return std::nullopt;
}
// Check conformance of the passed shapes. Only return true if we can verify
// that they conform
bool CheckConformance(parser::ContextualMessages &messages, const Shape &left,
const Shape &right, const char *leftIs, const char *rightIs,
bool leftScalarExpandable, bool rightScalarExpandable,
bool leftIsDeferredShape, bool rightIsDeferredShape) {
// Check conformance of the passed shapes.
std::optional<bool> CheckConformance(parser::ContextualMessages &messages,
const Shape &left, const Shape &right, CheckConformanceFlags::Flags flags,
const char *leftIs, const char *rightIs) {
int n{GetRank(left)};
if (n == 0 && leftScalarExpandable) {
if (n == 0 && (flags & CheckConformanceFlags::LeftScalarExpandable)) {
return true;
}
int rn{GetRank(right)};
if (rn == 0 && rightScalarExpandable) {
if (rn == 0 && (flags & CheckConformanceFlags::RightScalarExpandable)) {
return true;
}
if (n != rn) {
@ -787,11 +785,11 @@ bool CheckConformance(parser::ContextualMessages &messages, const Shape &left,
j + 1, leftIs, *leftDim, rightIs, *rightDim);
return false;
}
} else if (!rightIsDeferredShape) {
return false;
} else if (!(flags & CheckConformanceFlags::RightIsDeferredShape)) {
return std::nullopt;
}
} else if (!leftIsDeferredShape) {
return false;
} else if (!(flags & CheckConformanceFlags::LeftIsDeferredShape)) {
return std::nullopt;
}
}
return true;

View File

@ -160,7 +160,8 @@ static void CheckExplicitDataArg(const characteristics::DummyDataObject &dummy,
// Let CheckConformance accept scalars; storage association
// cases are checked here below.
CheckConformance(messages, dummy.type.shape(), actualType.shape(),
"dummy argument", "actual argument", true, true);
evaluate::CheckConformanceFlags::EitherScalarExpandable,
"dummy argument", "actual argument");
}
} else {
const auto &len{actualType.LEN()};

View File

@ -1655,17 +1655,21 @@ MaybeExpr ExpressionAnalyzer::Analyze(
"Rank-%d array value is not compatible with scalar component '%s'"_err_en_US,
GetRank(*valueShape), symbol->name()),
*symbol);
} else if (CheckConformance(messages, *componentShape,
*valueShape, "component", "value", false,
true /* can expand scalar value */)) {
if (GetRank(*componentShape) > 0 && GetRank(*valueShape) == 0 &&
} else {
auto checked{
CheckConformance(messages, *componentShape, *valueShape,
CheckConformanceFlags::RightIsExpandableDeferred,
"component", "value")};
if (checked && *checked && GetRank(*componentShape) > 0 &&
GetRank(*valueShape) == 0 &&
!IsExpandableScalar(*converted)) {
AttachDeclaration(
Say(expr.source,
"Scalar value cannot be expanded to shape of array component '%s'"_err_en_US,
symbol->name()),
*symbol);
} else {
}
if (checked.value_or(true)) {
result.Add(*symbol, std::move(*converted));
}
}
@ -3146,8 +3150,9 @@ bool ArgumentAnalyzer::CheckConformance() const {
auto rhShape{GetShape(foldingContext, *rhs)};
if (lhShape && rhShape) {
return evaluate::CheckConformance(foldingContext.messages(), *lhShape,
*rhShape, "left operand", "right operand", true,
true /* scalar expansion is allowed */);
*rhShape, CheckConformanceFlags::EitherScalarExpandable,
"left operand", "right operand")
.value_or(false /*fail when conformance is not known now*/);
}
}
}

View File

@ -171,7 +171,7 @@ bool PointerAssignmentChecker::Check(const evaluate::FunctionRef<T> &f) {
CHECK(frTypeAndShape);
if (!lhsType_->IsCompatibleWith(context_.messages(), *frTypeAndShape,
"pointer", "function result", false /*elemental*/,
true /*left: deferred shape*/, true /*right: deferred shape*/)) {
evaluate::CheckConformanceFlags::BothDeferredShape)) {
msg = "%s is associated with the result of a reference to function '%s'"
" whose pointer result has an incompatible type or shape"_err_en_US;
}