Skip to content

Commit 45e4d59

Browse files
committed
[flang] Warn about inexact real literal implicit widening pitfall
When a REAL or COMPLEX literal appears without an explicit kind suffix or a kind-determining exponent letter, and the conversion of that literal from decimal to binary is inexact, emit a warning if that constant is later implicitly widened to a more precise kind, since it will have a different value than was probably intended. Values that convert exactly from decimal to default real, e.g. 1.0 and 0.125, do not elicit this warning. There are many contexts in which Fortran implicitly converts constants. This patch covers name constant values, variable and component initialization, constants in expressions, structure constructor components, and array constructors. For example, "real(8) :: tenth = 0.1" is a common Fortran bug that's hard to find, and is one that often trips up even experienced Fortran programmers. Unlike C and C++, the literal constant 0.1 is *not* double precision by default, and it does not have the same value as 0.1d0 or 0.1_8 do when it is converted from decimal to real(4) and then to real(8).
1 parent 130ddbb commit 45e4d59

30 files changed

+505
-290
lines changed

flang/include/flang/Common/enum-set.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,8 @@ template <typename ENUM, std::size_t BITS> class EnumSet {
175175
constexpr bool empty() const { return none(); }
176176
void clear() { reset(); }
177177
void insert(enumerationType x) { set(x); }
178-
void insert(enumerationType &&x) { set(x); }
179-
void emplace(enumerationType &&x) { set(x); }
178+
void emplace(enumerationType x) { set(x); }
180179
void erase(enumerationType x) { reset(x); }
181-
void erase(enumerationType &&x) { reset(x); }
182180

183181
constexpr std::optional<enumerationType> LeastElement() const {
184182
if (empty()) {

flang/include/flang/Evaluate/check-expression.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ bool IsInitialProcedureTarget(const Symbol &);
6464
bool IsInitialProcedureTarget(const ProcedureDesignator &);
6565
bool IsInitialProcedureTarget(const Expr<SomeType> &);
6666

67+
// Emit warnings about default REAL literal constants in contexts that
68+
// will be converted to a higher precision REAL kind than the default.
69+
void CheckRealWidening(
70+
const Expr<SomeType> &, const DynamicType &toType, FoldingContext &);
71+
void CheckRealWidening(const Expr<SomeType> &,
72+
const std::optional<DynamicType> &, FoldingContext &);
73+
6774
// Validate the value of a named constant, the static initial
6875
// value of a non-pointer non-allocatable non-dummy variable, or the
6976
// default initializer of a component of a derived type (or instantiation

flang/include/flang/Evaluate/constant.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,19 @@ class ConstantBase : public ConstantBounds {
128128
bool empty() const { return values_.empty(); }
129129
std::size_t size() const { return values_.size(); }
130130
const std::vector<Element> &values() const { return values_; }
131-
constexpr Result result() const { return result_; }
131+
Result &result() { return result_; }
132+
const Result &result() const { return result_; }
132133

133134
constexpr DynamicType GetType() const { return result_.GetType(); }
134135
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
136+
std::string AsFortran() const;
135137

136138
protected:
137139
std::vector<Element> Reshape(const ConstantSubscripts &) const;
138140
std::size_t CopyFrom(const ConstantBase &source, std::size_t count,
139141
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);
140142

141-
Result result_;
143+
Result result_; // usually empty except for Real & Complex
142144
std::vector<Element> values_;
143145
};
144146

@@ -209,6 +211,7 @@ class Constant<Type<TypeCategory::Character, KIND>> : public ConstantBounds {
209211

210212
Constant Reshape(ConstantSubscripts &&) const;
211213
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
214+
std::string AsFortran() const;
212215
DynamicType GetType() const { return {KIND, length_}; }
213216
std::size_t CopyFrom(const Constant &source, std::size_t count,
214217
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);

flang/include/flang/Evaluate/real.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ template <typename WORD, int PREC> class Real {
442442
// or parenthesized constant expression that produces this value.
443443
llvm::raw_ostream &AsFortran(
444444
llvm::raw_ostream &, int kind, bool minimal = false) const;
445+
std::string AsFortran(int kind, bool minimal = false) const;
445446

446447
private:
447448
using Significand = Integer<significandBits>; // no implicit bit

flang/include/flang/Evaluate/type.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,26 @@ class Type<TypeCategory::Unsigned, KIND>
274274
using Scalar = value::Integer<8 * KIND>;
275275
};
276276

277+
// Records when a default REAL literal constant is inexactly converted to binary
278+
// (e.g., 0.1 but not 0.125) to enable a usage warning if the expression in
279+
// which it appears undergoes an implicit widening conversion.
280+
class TrackInexactLiteralConversion {
281+
public:
282+
constexpr bool isFromInexactLiteralConversion() const {
283+
return isFromInexactLiteralConversion_;
284+
}
285+
void set_isFromInexactLiteralConversion(bool yes = true) {
286+
isFromInexactLiteralConversion_ = yes;
287+
}
288+
289+
private:
290+
bool isFromInexactLiteralConversion_{false};
291+
};
292+
277293
template <int KIND>
278294
class Type<TypeCategory::Real, KIND>
279-
: public TypeBase<TypeCategory::Real, KIND> {
295+
: public TypeBase<TypeCategory::Real, KIND>,
296+
public TrackInexactLiteralConversion {
280297
public:
281298
static constexpr int precision{common::PrecisionOfRealKind(KIND)};
282299
static constexpr int bits{common::BitsForBinaryPrecision(precision)};
@@ -289,7 +306,8 @@ class Type<TypeCategory::Real, KIND>
289306
// The KIND type parameter on COMPLEX is the kind of each of its components.
290307
template <int KIND>
291308
class Type<TypeCategory::Complex, KIND>
292-
: public TypeBase<TypeCategory::Complex, KIND> {
309+
: public TypeBase<TypeCategory::Complex, KIND>,
310+
public TrackInexactLiteralConversion {
293311
public:
294312
using Part = Type<TypeCategory::Real, KIND>;
295313
using Scalar = value::Complex<typename Part::Scalar>;

flang/include/flang/Support/Fortran-features.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ ENUM_CLASS(UsageWarning, Portability, PointerToUndefinable,
7878
MismatchingDummyProcedure, SubscriptedEmptyArray, UnsignedLiteralTruncation,
7979
CompatibleDeclarationsFromDistinctModules,
8080
NullActualForDefaultIntentAllocatable, UseAssociationIntoSameNameSubprogram,
81-
HostAssociatedIntentOutInSpecExpr, NonVolatilePointerToVolatile)
81+
HostAssociatedIntentOutInSpecExpr, NonVolatilePointerToVolatile,
82+
RealConstantWidening)
8283

8384
using LanguageFeatures = EnumSet<LanguageFeature, LanguageFeature_enumSize>;
8485
using UsageWarnings = EnumSet<UsageWarning, UsageWarning_enumSize>;

flang/lib/Evaluate/check-expression.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,88 @@ bool IsInitialProcedureTarget(const Expr<SomeType> &expr) {
405405
}
406406
}
407407

408+
class SuspiciousRealLiteralFinder
409+
: public AnyTraverse<SuspiciousRealLiteralFinder> {
410+
public:
411+
using Base = AnyTraverse<SuspiciousRealLiteralFinder>;
412+
SuspiciousRealLiteralFinder(int kind, FoldingContext &c)
413+
: Base{*this}, kind_{kind}, context_{c} {}
414+
using Base::operator();
415+
template <int KIND>
416+
bool operator()(const Constant<Type<TypeCategory::Real, KIND>> &x) const {
417+
if (kind_ > KIND && x.result().isFromInexactLiteralConversion()) {
418+
context_.messages().Say(common::UsageWarning::RealConstantWidening,
419+
"Default real literal in REAL(%d) context might need a kind suffix, as its rounded value %s is inexact"_warn_en_US,
420+
kind_, x.AsFortran());
421+
return true;
422+
} else {
423+
return false;
424+
}
425+
}
426+
template <int KIND>
427+
bool operator()(const Constant<Type<TypeCategory::Complex, KIND>> &x) const {
428+
if (kind_ > KIND && x.result().isFromInexactLiteralConversion()) {
429+
context_.messages().Say(common::UsageWarning::RealConstantWidening,
430+
"Default real literal in COMPLEX(%d) context might need a kind suffix, as its rounded value %s is inexact"_warn_en_US,
431+
kind_, x.AsFortran());
432+
return true;
433+
} else {
434+
return false;
435+
}
436+
}
437+
template <TypeCategory TOCAT, int TOKIND, TypeCategory FROMCAT>
438+
bool operator()(const Convert<Type<TOCAT, TOKIND>, FROMCAT> &x) const {
439+
if constexpr ((TOCAT == TypeCategory::Real ||
440+
TOCAT == TypeCategory::Complex) &&
441+
(FROMCAT == TypeCategory::Real || FROMCAT == TypeCategory::Complex)) {
442+
auto fromType{x.left().GetType()};
443+
if (!fromType || fromType->kind() < TOKIND) {
444+
return false;
445+
}
446+
}
447+
return (*this)(x.left());
448+
}
449+
450+
private:
451+
int kind_;
452+
FoldingContext &context_;
453+
};
454+
455+
void CheckRealWidening(const Expr<SomeType> &expr, const DynamicType &toType,
456+
FoldingContext &context) {
457+
if (toType.category() == TypeCategory::Real ||
458+
toType.category() == TypeCategory::Complex) {
459+
if (auto fromType{expr.GetType()}) {
460+
if ((fromType->category() == TypeCategory::Real ||
461+
fromType->category() == TypeCategory::Complex) &&
462+
toType.kind() > fromType->kind()) {
463+
SuspiciousRealLiteralFinder{toType.kind(), context}(expr);
464+
}
465+
}
466+
}
467+
}
468+
469+
void CheckRealWidening(const Expr<SomeType> &expr,
470+
const std::optional<DynamicType> &toType, FoldingContext &context) {
471+
if (toType) {
472+
CheckRealWidening(expr, *toType, context);
473+
}
474+
}
475+
476+
class InexactLiteralConversionFlagClearer
477+
: public AnyTraverse<InexactLiteralConversionFlagClearer> {
478+
public:
479+
using Base = AnyTraverse<InexactLiteralConversionFlagClearer>;
480+
InexactLiteralConversionFlagClearer() : Base(*this) {}
481+
using Base::operator();
482+
template <int KIND>
483+
bool operator()(const Constant<Type<TypeCategory::Real, KIND>> &x) const {
484+
auto &mut{const_cast<Type<TypeCategory::Real, KIND> &>(x.result())};
485+
mut.set_isFromInexactLiteralConversion(false);
486+
return false;
487+
}
488+
};
489+
408490
// Converts, folds, and then checks type, rank, and shape of an
409491
// initialization expression for a named constant, a non-pointer
410492
// variable static initialization, a component default initializer,
@@ -416,6 +498,7 @@ std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol,
416498
if (auto symTS{
417499
characteristics::TypeAndShape::Characterize(symbol, context)}) {
418500
auto xType{x.GetType()};
501+
CheckRealWidening(x, symTS->type(), context);
419502
auto converted{ConvertToType(symTS->type(), Expr<SomeType>{x})};
420503
if (!converted &&
421504
symbol.owner().context().IsEnabled(
@@ -433,6 +516,7 @@ std::optional<Expr<SomeType>> NonPointerInitializationExpr(const Symbol &symbol,
433516
if (converted) {
434517
auto folded{Fold(context, std::move(*converted))};
435518
if (IsActuallyConstant(folded)) {
519+
InexactLiteralConversionFlagClearer{}(folded);
436520
int symRank{symTS->Rank()};
437521
if (IsImpliedShape(symbol)) {
438522
if (folded.Rank() == symRank) {

flang/lib/Evaluate/fold-complex.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,21 @@ Expr<Type<TypeCategory::Complex, KIND>> FoldOperation(
8383
if (auto array{ApplyElementwise(context, x)}) {
8484
return *array;
8585
}
86-
using Result = Type<TypeCategory::Complex, KIND>;
86+
using ComplexType = Type<TypeCategory::Complex, KIND>;
8787
if (auto folded{OperandsAreConstants(x)}) {
88-
return Expr<Result>{
89-
Constant<Result>{Scalar<Result>{folded->first, folded->second}}};
88+
using RealType = typename ComplexType::Part;
89+
Constant<ComplexType> result{
90+
Scalar<ComplexType>{folded->first, folded->second}};
91+
if (const auto *re{UnwrapConstantValue<RealType>(x.left())};
92+
re && re->result().isFromInexactLiteralConversion()) {
93+
result.result().set_isFromInexactLiteralConversion();
94+
} else if (const auto *im{UnwrapConstantValue<RealType>(x.right())};
95+
im && im->result().isFromInexactLiteralConversion()) {
96+
result.result().set_isFromInexactLiteralConversion();
97+
}
98+
return Expr<ComplexType>{std::move(result)};
9099
}
91-
return Expr<Result>{std::move(x)};
100+
return Expr<ComplexType>{std::move(x)};
92101
}
93102

94103
#ifdef _MSC_VER // disable bogus warning about missing definitions

flang/lib/Evaluate/fold-implementation.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,8 +1321,8 @@ template <typename T> class ArrayConstructorFolder {
13211321
*charLength_, std::move(elements_), ConstantSubscripts{n}}};
13221322
}
13231323
} else {
1324-
return Expr<T>{
1325-
Constant<T>{std::move(elements_), ConstantSubscripts{n}}};
1324+
return Expr<T>{Constant<T>{
1325+
std::move(elements_), ConstantSubscripts{n}, resultInfo_}};
13261326
}
13271327
}
13281328
return Expr<T>{std::move(array)};
@@ -1343,6 +1343,11 @@ template <typename T> class ArrayConstructorFolder {
13431343
if (!knownCharLength_) {
13441344
charLength_ = std::max(c->LEN(), charLength_.value_or(-1));
13451345
}
1346+
} else if constexpr (T::category == TypeCategory::Real ||
1347+
T::category == TypeCategory::Complex) {
1348+
if (c->result().isFromInexactLiteralConversion()) {
1349+
resultInfo_.set_isFromInexactLiteralConversion();
1350+
}
13461351
}
13471352
return true;
13481353
} else {
@@ -1395,6 +1400,7 @@ template <typename T> class ArrayConstructorFolder {
13951400
std::vector<Scalar<T>> elements_;
13961401
std::optional<ConstantSubscript> charLength_;
13971402
bool knownCharLength_{false};
1403+
typename Constant<T>::Result resultInfo_;
13981404
};
13991405

14001406
template <typename T>

flang/lib/Evaluate/formatting.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ llvm::raw_ostream &ConstantBase<RESULT, VALUE>::AsFortran(
9898
return o;
9999
}
100100

101+
template <typename RESULT, typename VALUE>
102+
std::string ConstantBase<RESULT, VALUE>::AsFortran() const {
103+
std::string result;
104+
llvm::raw_string_ostream sstream(result);
105+
AsFortran(sstream);
106+
return result;
107+
}
108+
101109
template <int KIND>
102110
llvm::raw_ostream &Constant<Type<TypeCategory::Character, KIND>>::AsFortran(
103111
llvm::raw_ostream &o) const {
@@ -126,6 +134,14 @@ llvm::raw_ostream &Constant<Type<TypeCategory::Character, KIND>>::AsFortran(
126134
return o;
127135
}
128136

137+
template <int KIND>
138+
std::string Constant<Type<TypeCategory::Character, KIND>>::AsFortran() const {
139+
std::string result;
140+
llvm::raw_string_ostream sstream(result);
141+
AsFortran(sstream);
142+
return result;
143+
}
144+
129145
llvm::raw_ostream &EmitVar(llvm::raw_ostream &o, const Symbol &symbol,
130146
std::optional<parser::CharBlock> name = std::nullopt) {
131147
const auto &renamings{symbol.owner().context().moduleFileOutputRenamings()};

0 commit comments

Comments
 (0)