[flang] Warn about useless explicit typing of intrinsics

Fortran 2018 explicitly permits an ignored type declaration
for the result of a generic intrinsic function.  See the comment
added to Semantics/expression.cpp for an explanation of why this
is somewhat dangerous and worthy of a warning.

Differential Revision: https://reviews.llvm.org/D96879
This commit is contained in:
peter klausler 2021-02-17 10:24:14 -08:00
parent 8b624a3164
commit b82a8c3f23
8 changed files with 89 additions and 7 deletions

View File

@ -205,3 +205,12 @@ accepted if enabled by command-line options.
* We respect Fortran comments in macro actual arguments (like GNU, Intel, NAG;
unlike PGI and XLF) on the principle that macro calls should be treated
like function references. Fortran's line continuation methods also work.
## Standard features not silently accepted
* Fortran explicitly ignores type declaration statements when they
attempt to type the name of a generic intrinsic function (8.2 p3).
One can declare `CHARACTER::COS` and still get a real result
from `COS(3.14159)`, for example. f18 will complain when a
generic intrinsic function's inferred result type does not
match an explicit declaration. This message is a warning.

View File

@ -154,6 +154,7 @@ public:
// called by Fold() to rewrite in place
TypeAndShape &Rewrite(FoldingContext &);
std::string AsFortran() const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
private:

View File

@ -371,7 +371,7 @@ private:
std::optional<CalleeAndArguments> GetCalleeAndArguments(
const parser::ProcedureDesignator &, ActualArguments &&,
bool isSubroutine, bool mightBeStructureConstructor = false);
void CheckBadExplicitType(const SpecificCall &, const Symbol &);
void CheckForBadRecursion(parser::CharBlock, const semantics::Symbol &);
bool EnforceTypeConstraint(parser::CharBlock, const MaybeExpr &, TypeCategory,
bool defaultKind = false);

View File

@ -155,11 +155,9 @@ bool TypeAndShape::IsCompatibleWith(parser::ContextualMessages &messages,
bool isElemental, bool thisIsDeferredShape,
bool thatIsDeferredShape) const {
if (!type_.IsTkCompatibleWith(that.type_)) {
const auto &len{that.LEN()};
messages.Say(
"%1$s type '%2$s' is not compatible with %3$s type '%4$s'"_err_en_US,
thatIs, that.type_.AsFortran(len ? len->AsFortran() : ""), thisIs,
type_.AsFortran(LEN_ ? LEN_->AsFortran() : ""));
thatIs, that.AsFortran(), thisIs, AsFortran());
return false;
}
return isElemental ||
@ -235,6 +233,10 @@ void TypeAndShape::AcquireLEN(const semantics::Symbol &symbol) {
}
}
std::string TypeAndShape::AsFortran() const {
return type_.AsFortran(LEN_ ? LEN_->AsFortran() : "");
}
llvm::raw_ostream &TypeAndShape::Dump(llvm::raw_ostream &o) const {
o << type_.AsFortran(LEN_ ? LEN_->AsFortran() : "");
attrs_.Dump(o, EnumToString);

View File

@ -2044,6 +2044,7 @@ auto ExpressionAnalyzer::GetCalleeAndArguments(const parser::Name &name,
if (std::optional<SpecificCall> specificCall{context_.intrinsics().Probe(
CallCharacteristics{ultimate.name().ToString(), isSubroutine},
arguments, GetFoldingContext())}) {
CheckBadExplicitType(*specificCall, *symbol);
return CalleeAndArguments{
ProcedureDesignator{std::move(specificCall->specificIntrinsic)},
std::move(specificCall->arguments)};
@ -2081,6 +2082,39 @@ auto ExpressionAnalyzer::GetCalleeAndArguments(const parser::Name &name,
return std::nullopt;
}
// Fortran 2018 expressly states (8.2 p3) that any declared type for a
// generic intrinsic function "has no effect" on the result type of a
// call to that intrinsic. So one can declare "character*8 cos" and
// still get a real result from "cos(1.)". This is a dangerous feature,
// especially since implementations are free to extend their sets of
// intrinsics, and in doing so might clash with a name in a program.
// So we emit a warning in this situation, and perhaps it should be an
// error -- any correctly working program can silence the message by
// simply deleting the pointless type declaration.
void ExpressionAnalyzer::CheckBadExplicitType(
const SpecificCall &call, const Symbol &intrinsic) {
if (intrinsic.GetUltimate().GetType()) {
const auto &procedure{call.specificIntrinsic.characteristics.value()};
if (const auto &result{procedure.functionResult}) {
if (const auto *typeAndShape{result->GetTypeAndShape()}) {
if (auto declared{
typeAndShape->Characterize(intrinsic, GetFoldingContext())}) {
if (!declared->type().IsTkCompatibleWith(typeAndShape->type())) {
if (auto *msg{Say(
"The result type '%s' of the intrinsic function '%s' is not the explicit declared type '%s'"_en_US,
typeAndShape->AsFortran(), intrinsic.name(),
declared->AsFortran())}) {
msg->Attach(intrinsic.name(),
"Ignored declaration of intrinsic function '%s'"_en_US,
intrinsic.name());
}
}
}
}
}
}
}
void ExpressionAnalyzer::CheckForBadRecursion(
parser::CharBlock callSite, const semantics::Symbol &proc) {
if (const auto *scope{proc.scope()}) {

View File

@ -3488,6 +3488,15 @@ bool DeclarationVisitor::Pre(const parser::IntrinsicStmt &x) {
Say(symbol.name(),
"Symbol '%s' cannot have both EXTERNAL and INTRINSIC attributes"_err_en_US,
symbol.name());
} else if (symbol.GetType()) {
// These warnings are worded so that they should make sense in either
// order.
Say(symbol.name(),
"Explicit type declaration ignored for intrinsic function '%s'"_en_US,
symbol.name())
.Attach(name.source,
"INTRINSIC statement for explicitly-typed '%s'"_en_US,
name.source);
}
}
return false;
@ -5994,8 +6003,6 @@ void ResolveNamesVisitor::HandleProcedureName(
bool convertedToProcEntity{ConvertToProcEntity(*symbol)};
if (convertedToProcEntity && !symbol->attrs().test(Attr::EXTERNAL) &&
IsIntrinsic(symbol->name(), flag) && !IsDummy(*symbol)) {
// 8.2(3): ignore type from intrinsic in type-declaration-stmt
symbol->get<ProcEntityDetails>().set_interface(ProcInterface{});
AcquireIntrinsicProcedureFlags(*symbol);
}
if (!SetProcFlag(name, *symbol, flag)) {

View File

@ -0,0 +1,29 @@
! RUN: %f18 -fsyntax-only %s 2>&1 | FileCheck %s
type :: t
end type
integer :: acos
double precision :: cos
!CHECK: Explicit type declaration ignored for intrinsic function 'int'
complex :: int
character :: sin
logical :: asin
type(t) :: atan
!CHECK: INTRINSIC statement for explicitly-typed 'int'
intrinsic int
!CHECK: The result type 'REAL(4)' of the intrinsic function 'acos' is not the explicit declared type 'INTEGER(4)'
!CHECK: Ignored declaration of intrinsic function 'acos'
print *, acos(0.)
!CHECK: The result type 'REAL(4)' of the intrinsic function 'cos' is not the explicit declared type 'REAL(8)'
!CHECK: Ignored declaration of intrinsic function 'cos'
print *, cos(0.)
!CHECK: The result type 'REAL(4)' of the intrinsic function 'sin' is not the explicit declared type 'CHARACTER(KIND=1,LEN=1_8)'
!CHECK: Ignored declaration of intrinsic function 'sin'
print *, sin(0.)
!CHECK: The result type 'REAL(4)' of the intrinsic function 'asin' is not the explicit declared type 'LOGICAL(4)'
!CHECK: Ignored declaration of intrinsic function 'asin'
print *, asin(0.)
!CHECK: The result type 'REAL(4)' of the intrinsic function 'atan' is not the explicit declared type 't'
!CHECK: Ignored declaration of intrinsic function 'atan'
print *, atan(0.)
end

View File

@ -4,7 +4,7 @@
!DEF: /p1 MainProgram
program p1
!DEF: /p1/cos ELEMENTAL, INTRINSIC, PURE (Function) ProcEntity
!DEF: /p1/cos ELEMENTAL, INTRINSIC, PURE (Function) ProcEntity INTEGER(4)
integer cos
!DEF: /p1/y (Implicit) ObjectEntity REAL(4)
!REF: /p1/cos