diff options
Diffstat (limited to 'flang/docs/ImplementingASemanticCheck.md')
-rw-r--r-- | flang/docs/ImplementingASemanticCheck.md | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/flang/docs/ImplementingASemanticCheck.md b/flang/docs/ImplementingASemanticCheck.md new file mode 100644 index 000000000000..3bb16915cb88 --- /dev/null +++ b/flang/docs/ImplementingASemanticCheck.md @@ -0,0 +1,832 @@ +<!--===- docs/ImplementingASemanticCheck.md + + Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. + See https://llvm.org/LICENSE.txt for license information. + SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +--> +# Introduction +I recently added a semantic check to the f18 compiler front end. This document +describes my thought process and the resulting implementation. + +For more information about the compiler, start with the +[compiler overview](Overview.md). + +# Problem definition + +In the 2018 Fortran standard, section 11.1.7.4.3, paragraph 2, states that: + +``` +Except for the incrementation of the DO variable that occurs in step (3), the DO variable +shall neither be redefined nor become undefined while the DO construct is active. +``` +One of the ways that DO variables might be redefined is if they are passed to +functions with dummy arguments whose `INTENT` is `INTENT(OUT)` or +`INTENT(INOUT)`. I implemented this semantic check. Specifically, I changed +the compiler to emit an error message if an active DO variable was passed to a +dummy argument of a FUNCTION with INTENT(OUT). Similarly, I had the compiler +emit a warning if an active DO variable was passed to a dummy argument with +INTENT(INOUT). Previously, I had implemented similar checks for SUBROUTINE +calls. + +# Creating a test + +My first step was to create a test case to cause the problem. I called it testfun.f90 and used it to check the behavior of other Fortran compilers. Here's the initial version: + +```fortran + subroutine s() + Integer :: ivar, jvar + + do ivar = 1, 10 + jvar = intentOutFunc(ivar) ! Error since ivar is a DO variable + end do + + contains + function intentOutFunc(dummyArg) + integer, intent(out) :: dummyArg + integer :: intentOutFunc + + dummyArg = 216 + end function intentOutFunc + end subroutine s +``` + +I verified that other Fortran compilers produced an error message at the point +of the call to `intentOutFunc()`: + +```fortran + jvar = intentOutFunc(ivar) ! Error since ivar is a DO variable +``` + + +I also used this program to produce a parse tree for the program using the command: +```bash + f18 -fdebug-dump-parse-tree -fparse-only testfun.f90 +``` + +Here's the relevant fragment of the parse tree produced by the compiler: + +``` +| | ExecutionPartConstruct -> ExecutableConstruct -> DoConstruct +| | | NonLabelDoStmt +| | | | LoopControl -> LoopBounds +| | | | | Scalar -> Name = 'ivar' +| | | | | Scalar -> Expr = '1_4' +| | | | | | LiteralConstant -> IntLiteralConstant = '1' +| | | | | Scalar -> Expr = '10_4' +| | | | | | LiteralConstant -> IntLiteralConstant = '10' +| | | Block +| | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> AssignmentStmt = 'jvar=intentoutfunc(ivar)' +| | | | | Variable -> Designator -> DataRef -> Name = 'jvar' +| | | | | Expr = 'intentoutfunc(ivar)' +| | | | | | FunctionReference -> Call +| | | | | | | ProcedureDesignator -> Name = 'intentoutfunc' +| | | | | | | ActualArgSpec +| | | | | | | | ActualArg -> Expr = 'ivar' +| | | | | | | | | Designator -> DataRef -> Name = 'ivar' +| | | EndDoStmt -> +``` + +Note that this fragment of the tree only shows four `parser::Expr` nodes, +but the full parse tree also contained a fifth `parser::Expr` node for the +constant 216 in the statement: + +```fortran + dummyArg = 216 +``` +# Analysis and implementation planning + +I then considered what I needed to do. I needed to detect situations where an +active DO variable was passed to a dummy argument with `INTENT(OUT)` or +`INTENT(INOUT)`. Once I detected such a situation, I needed to produce a +message that highlighted the erroneous source code. + +## Deciding where to add the code to the compiler +This new semantic check would depend on several types of information -- the +parse tree, source code location information, symbols, and expressions. Thus I +needed to put my new code in a place in the compiler after the parse tree had +been created, name resolution had already happened, and expression semantic +checking had already taken place. + +Most semantic checks for statements are implemented by walking the parse tree +and performing analysis on the nodes they visit. My plan was to use this +method. The infrastructure for walking the parse tree for statement semantic +checking is implemented in the files `lib/Semantics/semantics.cpp`. +Here's a fragment of the declaration of the framework's parse tree visitor from +`lib/Semantics/semantics.cpp`: + +```C++ + // A parse tree visitor that calls Enter/Leave functions from each checker + // class C supplied as template parameters. Enter is called before the node's + // children are visited, Leave is called after. No two checkers may have the + // same Enter or Leave function. Each checker must be constructible from + // SemanticsContext and have BaseChecker as a virtual base class. + template<typename... C> class SemanticsVisitor : public virtual C... { + public: + using C::Enter...; + using C::Leave...; + using BaseChecker::Enter; + using BaseChecker::Leave; + SemanticsVisitor(SemanticsContext &context) + : C{context}..., context_{context} {} + ... + +``` + +Since FUNCTION calls are a kind of expression, I was planning to base my +implementation on the contents of `parser::Expr` nodes. I would need to define +either an `Enter()` or `Leave()` function whose parameter was a `parser::Expr` +node. Here's the declaration I put into `lib/Semantics/check-do.h`: + +```C++ + void Leave(const parser::Expr &); +``` +The `Enter()` functions get called at the time the node is first visited -- +that is, before its children. The `Leave()` function gets called after the +children are visited. For my check the visitation order didn't matter, so I +arbitrarily chose to implement the `Leave()` function to visit the parse tree +node. + +Since my semantic check was focused on DO CONCURRENT statements, I added it to +the file `lib/Semantics/check-do.cpp` where most of the semantic checking for +DO statements already lived. + +## Taking advantage of prior work +When implementing a similar check for SUBROUTINE calls, I created a utility +functions in `lib/Semantics/semantics.cpp` to emit messages if +a symbol corresponding to an active DO variable was being potentially modified: + +```C++ + void WarnDoVarRedefine(const parser::CharBlock &location, const Symbol &var); + void CheckDoVarRedefine(const parser::CharBlock &location, const Symbol &var); +``` + +The first function is intended for dummy arguments of `INTENT(INOUT)` and +the second for `INTENT(OUT)`. + +Thus I needed three pieces of +information -- +1. the source location of the erroneous text, +2. the `INTENT` of the associated dummy argument, and +3. the relevant symbol passed as the actual argument. + +The first and third are needed since they're required to call the utility +functions. The second is needed to determine whether to call them. + +## Finding the source location +The source code location information that I'd need for the error message must +come from the parse tree. I looked in the file +`include/flang/Parser/parse-tree.h` and determined that a `struct Expr` +contained source location information since it had the field `CharBlock +source`. Thus, if I visited a `parser::Expr` node, I could get the source +location information for the associated expression. + +## Determining the `INTENT` +I knew that I could find the `INTENT` of the dummy argument associated with the +actual argument from the function called `dummyIntent()` in the class +`evaluate::ActualArgument` in the file `include/flang/Evaluate/call.h`. So +if I could find an `evaluate::ActualArgument` in an expression, I could + determine the `INTENT` of the associated dummy argument. I knew that it was + valid to call `dummyIntent()` because the data on which `dummyIntent()` + depends is established during semantic processing for expressions, and the + semantic processing for expressions happens before semantic checking for DO + constructs. + +In my prior work on checking the INTENT of arguments for SUBROUTINE calls, +the parse tree held a node for the call (a `parser::CallStmt`) that contained +an `evaluate::ProcedureRef` node. +```C++ + struct CallStmt { + WRAPPER_CLASS_BOILERPLATE(CallStmt, Call); + mutable std::unique_ptr<evaluate::ProcedureRef, + common::Deleter<evaluate::ProcedureRef>> + typedCall; // filled by semantics + }; +``` +The `evaluate::ProcedureRef` contains a list of `evaluate::ActualArgument` +nodes. I could then find the INTENT of a dummy argument from the +`evaluate::ActualArgument` node. + +For a FUNCTION call, though, there is no similar way to get from a parse tree +node to an `evaluate::ProcedureRef` node. But I knew that there was an +existing framework used in DO construct semantic checking that traversed an +`evaluate::Expr` node collecting `semantics::Symbol` nodes. I guessed that I'd +be able to use a similar framework to traverse an `evaluate::Expr` node to +find all of the `evaluate::ActualArgument` nodes. + +Note that the compiler has multiple types called `Expr`. One is in the +`parser` namespace. `parser::Expr` is defined in the file +`include/flang/Parser/parse-tree.h`. It represents a parsed expression that +maps directly to the source code and has fields that specify any operators in +the expression, the operands, and the source position of the expression. + +Additionally, in the namespace `evaluate`, there are `evaluate::Expr<T>` +template classes defined in the file `include/flang/Evaluate/expression.h`. +These are parameterized over the various types of Fortran and constitute a +suite of strongly-typed representations of valid Fortran expressions of type +`T` that have been fully elaborated with conversion operations and subjected to +constant folding. After an expression has undergone semantic analysis, the +field `typedExpr` in the `parser::Expr` node is filled in with a pointer that +owns an instance of `evaluate::Expr<SomeType>`, the most general representation +of an analyzed expression. + +All of the declarations associated with both FUNCTION and SUBROUTINE calls are +in `include/flang/Evaluate/call.h`. An `evaluate::FunctionRef` inherits from +an `evaluate::ProcedureRef` which contains the list of +`evaluate::ActualArgument` nodes. But the relationship between an +`evaluate::FunctionRef` node and its associated arguments is not relevant. I +only needed to find the `evaluate::ActualArgument` nodes in an expression. +They hold all of the information I needed. + +So my plan was to start with the `parser::Expr` node and extract its +associated `evaluate::Expr` field. I would then traverse the +`evaluate::Expr` tree collecting all of the `evaluate::ActualArgument` +nodes. I would look at each of these nodes to determine the `INTENT` of +the associated dummy argument. + +This combination of the traversal framework and `dummyIntent()` would give +me the `INTENT` of all of the dummy arguments in a FUNCTION call. Thus, I +would have the second piece of information I needed. + +## Determining if the actual argument is a variable +I also guessed that I could determine if the `evaluate::ActualArgument` +consisted of a variable. + +Once I had a symbol for the variable, I could call one of the functions: +```C++ + void WarnDoVarRedefine(const parser::CharBlock &, const Symbol &); + void CheckDoVarRedefine(const parser::CharBlock &, const Symbol &); +``` +to emit the messages. + +If my plans worked out, this would give me the three pieces of information I +needed -- the source location of the erroneous text, the `INTENT` of the dummy +argument, and a symbol that I could use to determine whether the actual +argument was an active DO variable. + +# Implementation + +## Adding a parse tree visitor +I started my implementation by adding a visitor for `parser::Expr` nodes. +Since this analysis is part of DO construct checking, I did this in +`lib/Semantics/check-do.cpp`. I added a print statement to the visitor to +verify that my new code was actually getting executed. + +In `lib/Semantics/check-do.h`, I added the declaration for the visitor: + +```C++ + void Leave(const parser::Expr &); +``` + +In `lib/Semantics/check-do.cpp`, I added an (almost empty) implementation: + +```C++ + void DoChecker::Leave(const parser::Expr &) { + std::cout << "In Leave for parser::Expr\n"; + } +``` + +I then built the compiler with these changes and ran it on my test program. +This time, I made sure to invoke semantic checking. Here's the command I used: +```bash + f18 -fdebug-resolve-names -fdebug-dump-parse-tree -funparse-with-symbols testfun.f90 +``` + +This produced the output: + +``` + In Leave for parser::Expr + In Leave for parser::Expr + In Leave for parser::Expr + In Leave for parser::Expr + In Leave for parser::Expr +``` + +This made sense since the parse tree contained five `parser::Expr` nodes. +So far, so good. Note that a `parse::Expr` node has a field with the +source position of the associated expression (`CharBlock source`). So I +now had one of the three pieces of information needed to detect and report +errors. + +## Collecting the actual arguments +To get the `INTENT` of the dummy arguments and the `semantics::Symbol` associated with the +actual argument, I needed to find all of the actual arguments embedded in an +expression that contained a FUNCTION call. So my next step was to write the +framework to walk the `evaluate::Expr` to gather all of the +`evaluate::ActualArgument` nodes. The code that I planned to model it on +was the existing infrastructure that collected all of the `semantics::Symbol` nodes from an +`evaluate::Expr`. I found this implementation in +`lib/Evaluate/tools.cpp`: + +```C++ + struct CollectSymbolsHelper + : public SetTraverse<CollectSymbolsHelper, semantics::SymbolSet> { + using Base = SetTraverse<CollectSymbolsHelper, semantics::SymbolSet>; + CollectSymbolsHelper() : Base{*this} {} + using Base::operator(); + semantics::SymbolSet operator()(const Symbol &symbol) const { + return {symbol}; + } + }; + template<typename A> semantics::SymbolSet CollectSymbols(const A &x) { + return CollectSymbolsHelper{}(x); + } +``` + +Note that the `CollectSymbols()` function returns a `semantics::Symbolset`, +which is declared in `include/flang/Semantics/symbol.h`: + +```C++ + using SymbolSet = std::set<SymbolRef>; +``` + +This infrastructure yields a collection based on `std::set<>`. Using an +`std::set<>` means that if the same object is inserted twice, the +collection only gets one copy. This was the behavior that I wanted. + +Here's a sample invocation of `CollectSymbols()` that I found: +```C++ + if (const auto *expr{GetExpr(parsedExpr)}) { + for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) { +``` + +I noted that a `SymbolSet` did not actually contain an +`std::set<Symbol>`. This wasn't surprising since we don't want to put the +full `semantics::Symbol` objects into the set. Ideally, we would be able to create an +`std::set<Symbol &>` (a set of C++ references to symbols). But C++ doesn't +support sets that contain references. This limitation is part of the rationale +for the f18 implementation of type `common::Reference`, which is defined in + `include/flang/Common/reference.h`. + +`SymbolRef`, the specialization of the template `common::Reference` for +`semantics::Symbol`, is declared in the file +`include/flang/Semantics/symbol.h`: + +```C++ + using SymbolRef = common::Reference<const Symbol>; +``` + +So to implement something that would collect `evaluate::ActualArgument` +nodes from an `evaluate::Expr`, I first defined the required types +`ActualArgumentRef` and `ActualArgumentSet`. Since these are being +used exclusively for DO construct semantic checking (currently), I put their +definitions into `lib/Semantics/check-do.cpp`: + + +```C++ + namespace Fortran::evaluate { + using ActualArgumentRef = common::Reference<const ActualArgument>; + } + + + using ActualArgumentSet = std::set<evaluate::ActualArgumentRef>; +``` + +Since `ActualArgument` is in the namespace `evaluate`, I put the +definition for `ActualArgumentRef` in that namespace, too. + +I then modeled the code to create an `ActualArgumentSet` after the code to +collect a `SymbolSet` and put it into `lib/Semantics/check-do.cpp`: + + +```C++ + struct CollectActualArgumentsHelper + : public evaluate::SetTraverse<CollectActualArgumentsHelper, + ActualArgumentSet> { + using Base = SetTraverse<CollectActualArgumentsHelper, ActualArgumentSet>; + CollectActualArgumentsHelper() : Base{*this} {} + using Base::operator(); + ActualArgumentSet operator()(const evaluate::ActualArgument &arg) const { + return ActualArgumentSet{arg}; + } + }; + + template<typename A> ActualArgumentSet CollectActualArguments(const A &x) { + return CollectActualArgumentsHelper{}(x); + } + + template ActualArgumentSet CollectActualArguments(const SomeExpr &); +``` + +Unfortunately, when I tried to build this code, I got an error message saying +`std::set` requires the `<` operator to be defined for its contents. +To fix this, I added a definition for `<`. I didn't care how `<` was +defined, so I just used the address of the object: + +```C++ + inline bool operator<(ActualArgumentRef x, ActualArgumentRef y) { + return &*x < &*y; + } +``` + +I was surprised when this did not make the error message saying that I needed +the `<` operator go away. Eventually, I figured out that the definition of +the `<` operator needed to be in the `evaluate` namespace. Once I put +it there, everything compiled successfully. Here's the code that worked: + +```C++ + namespace Fortran::evaluate { + using ActualArgumentRef = common::Reference<const ActualArgument>; + + inline bool operator<(ActualArgumentRef x, ActualArgumentRef y) { + return &*x < &*y; + } + } +``` + +I then modified my visitor for the parser::Expr to invoke my new collection +framework. To verify that it was actually doing something, I printed out the +number of `evaluate::ActualArgument` nodes that it collected. Note the +call to `GetExpr()` in the invocation of `CollectActualArguments()`. I +modeled this on similar code that collected a `SymbolSet` described above: + +```C++ + void DoChecker::Leave(const parser::Expr &parsedExpr) { + std::cout << "In Leave for parser::Expr\n"; + ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))}; + std::cout << "Number of arguments: " << argSet.size() << "\n"; + } +``` + +I compiled and tested this code on my little test program. Here's the output that I got: +``` + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 1 + In Leave for parser::Expr + Number of arguments: 0 +``` + +So most of the `parser::Expr`nodes contained no actual arguments, but the +fourth expression in the parse tree walk contained a single argument. This may +seem wrong since the third `parser::Expr` node in the file contains the +`FunctionReference` node along with the arguments that we're gathering. +But since the tree walk function is being called upon leaving a +`parser::Expr` node, the function visits the `parser::Expr` node +associated with the `parser::ActualArg` node before it visits the +`parser::Expr` node associated with the `parser::FunctionReference` +node. + +So far, so good. + +## Finding the `INTENT` of the dummy argument +I now wanted to find the `INTENT` of the dummy argument associated with the +arguments in the set. As mentioned earlier, the type +`evaluate::ActualArgument` has a member function called `dummyIntent()` +that gives this value. So I augmented my code to print out the `INTENT`: + +```C++ + void DoChecker::Leave(const parser::Expr &parsedExpr) { + std::cout << "In Leave for parser::Expr\n"; + ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))}; + std::cout << "Number of arguments: " << argSet.size() << "\n"; + for (const evaluate::ActualArgumentRef &argRef : argSet) { + common::Intent intent{argRef->dummyIntent()}; + switch (intent) { + case common::Intent::In: std::cout << "INTENT(IN)\n"; break; + case common::Intent::Out: std::cout << "INTENT(OUT)\n"; break; + case common::Intent::InOut: std::cout << "INTENT(INOUT)\n"; break; + default: std::cout << "default INTENT\n"; + } + } + } +``` + +I then rebuilt my compiler and ran it on my test case. This produced the following output: + +``` + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 1 + INTENT(OUT) + In Leave for parser::Expr + Number of arguments: 0 +``` + +I then modified my test case to convince myself that I was getting the correct +`INTENT` for `IN`, `INOUT`, and default cases. + +So far, so good. + +## Finding the symbols for arguments that are variables +The third and last piece of information I needed was to determine if a variable +was being passed as an actual argument. In such cases, I wanted to get the +symbol table node (`semantics::Symbol`) for the variable. My starting point was the +`evaluate::ActualArgument` node. + +I was unsure of how to do this, so I browsed through existing code to look for +how it treated `evaluate::ActualArgument` objects. Since most of the code that deals with the `evaluate` namespace is in the lib/Evaluate directory, I looked there. I ran `grep` on all of the `.cpp` files looking for +uses of `ActualArgument`. One of the first hits I got was in `lib/Evaluate/call.cpp` in the definition of `ActualArgument::GetType()`: + +```C++ +std::optional<DynamicType> ActualArgument::GetType() const { + if (const Expr<SomeType> *expr{UnwrapExpr()}) { + return expr->GetType(); + } else if (std::holds_alternative<AssumedType>(u_)) { + return DynamicType::AssumedType(); + } else { + return std::nullopt; + } +} +``` + +I noted the call to `UnwrapExpr()` that yielded a value of +`Expr<SomeType>`. So I guessed that I could use this member function to +get an `evaluate::Expr<SomeType>` on which I could perform further analysis. + +I also knew that the header file `include/flang/Evaluate/tools.h` held many +utility functions for dealing with `evaluate::Expr` objects. I was hoping to +find something that would determine if an `evaluate::Expr` was a variable. So +I searched for `IsVariable` and got a hit immediately. +```C++ + template<typename A> bool IsVariable(const A &x) { + if (auto known{IsVariableHelper{}(x)}) { + return *known; + } else { + return false; + } + } +``` + +But I actually needed more than just the knowledge that an `evaluate::Expr` was +a variable. I needed the `semantics::Symbol` associated with the variable. So +I searched in `include/flang/Evaluate/tools.h` for functions that returned a +`semantics::Symbol`. I found the following: + +```C++ +// If an expression is simply a whole symbol data designator, +// extract and return that symbol, else null. +template<typename A> const Symbol *UnwrapWholeSymbolDataRef(const A &x) { + if (auto dataRef{ExtractDataRef(x)}) { + if (const SymbolRef * p{std::get_if<SymbolRef>(&dataRef->u)}) { + return &p->get(); + } + } + return nullptr; +} +``` + +This was exactly what I wanted. DO variables must be whole symbols. So I +could try to extract a whole `semantics::Symbol` from the `evaluate::Expr` in my +`evaluate::ActualArgument`. If this extraction resulted in a `semantics::Symbol` +that wasn't a `nullptr`, I could then conclude if it was a variable that I +could pass to existing functions that would determine if it was an active DO +variable. + +I then modified the compiler to perform the analysis that I'd guessed would +work: + +```C++ + void DoChecker::Leave(const parser::Expr &parsedExpr) { + std::cout << "In Leave for parser::Expr\n"; + ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))}; + std::cout << "Number of arguments: " << argSet.size() << "\n"; + for (const evaluate::ActualArgumentRef &argRef : argSet) { + if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) { + std::cout << "Got an unwrapped Expr\n"; + if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) { + std::cout << "Found a whole variable: " << *var << "\n"; + } + } + common::Intent intent{argRef->dummyIntent()}; + switch (intent) { + case common::Intent::In: std::cout << "INTENT(IN)\n"; break; + case common::Intent::Out: std::cout << "INTENT(OUT)\n"; break; + case common::Intent::InOut: std::cout << "INTENT(INOUT)\n"; break; + default: std::cout << "default INTENT\n"; + } + } + } +``` + +Note the line that prints out the symbol table entry for the variable: + +```C++ + std::cout << "Found a whole variable: " << *var << "\n"; +``` + +The compiler defines the "<<" operator for `semantics::Symbol`, which is handy +for analyzing the compiler's behavior. + +Here's the result of running the modified compiler on my Fortran test case: + +``` + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 1 + Got an unwrapped Expr + Found a whole variable: ivar: ObjectEntity type: INTEGER(4) + INTENT(OUT) + In Leave for parser::Expr + Number of arguments: 0 +``` + +Sweet. + +## Emitting the messages +At this point, using the source location information from the original +`parser::Expr`, I had enough information to plug into the exiting +interfaces for emitting messages for active DO variables. I modified the +compiler code accordingly: + + +```C++ + void DoChecker::Leave(const parser::Expr &parsedExpr) { + std::cout << "In Leave for parser::Expr\n"; + ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))}; + std::cout << "Number of arguments: " << argSet.size() << "\n"; + for (const evaluate::ActualArgumentRef &argRef : argSet) { + if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) { + std::cout << "Got an unwrapped Expr\n"; + if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) { + std::cout << "Found a whole variable: " << *var << "\n"; + common::Intent intent{argRef->dummyIntent()}; + switch (intent) { + case common::Intent::In: std::cout << "INTENT(IN)\n"; break; + case common::Intent::Out: + std::cout << "INTENT(OUT)\n"; + context_.CheckDoVarRedefine(parsedExpr.source, *var); + break; + case common::Intent::InOut: + std::cout << "INTENT(INOUT)\n"; + context_.WarnDoVarRedefine(parsedExpr.source, *var); + break; + default: std::cout << "default INTENT\n"; + } + } + } + } + } +``` + +I then ran this code on my test case, and miraculously, got the following +output: + +``` + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 0 + In Leave for parser::Expr + Number of arguments: 1 + Got an unwrapped Expr + Found a whole variable: ivar: ObjectEntity type: INTEGER(4) + INTENT(OUT) + In Leave for parser::Expr + Number of arguments: 0 + testfun.f90:6:12: error: Cannot redefine DO variable 'ivar' + jvar = intentOutFunc(ivar) + ^^^^^^^^^^^^^^^^^^^ + testfun.f90:5:6: Enclosing DO construct + do ivar = 1, 10 + ^^^^ +``` + +Even sweeter. + +# Improving the test case +At this point, my implementation seemed to be working. But I was concerned +about the limitations of my test case. So I augmented it to include arguments +other than `INTENT(OUT)` and more complex expressions. Luckily, my +augmented test did not reveal any new problems. + +Here's the test I ended up with: + +```Fortran + subroutine s() + + Integer :: ivar, jvar + + ! This one is OK + do ivar = 1, 10 + jvar = intentInFunc(ivar) + end do + + ! Error for passing a DO variable to an INTENT(OUT) dummy + do ivar = 1, 10 + jvar = intentOutFunc(ivar) + end do + + ! Error for passing a DO variable to an INTENT(OUT) dummy, more complex + ! expression + do ivar = 1, 10 + jvar = 83 + intentInFunc(intentOutFunc(ivar)) + end do + + ! Warning for passing a DO variable to an INTENT(INOUT) dummy + do ivar = 1, 10 + jvar = intentInOutFunc(ivar) + end do + + contains + function intentInFunc(dummyArg) + integer, intent(in) :: dummyArg + integer :: intentInFunc + + intentInFunc = 343 + end function intentInFunc + + function intentOutFunc(dummyArg) + integer, intent(out) :: dummyArg + integer :: intentOutFunc + + dummyArg = 216 + intentOutFunc = 343 + end function intentOutFunc + + function intentInOutFunc(dummyArg) + integer, intent(inout) :: dummyArg + integer :: intentInOutFunc + + dummyArg = 216 + intentInOutFunc = 343 + end function intentInOutFunc + + end subroutine s +``` + +# Submitting the pull request +At this point, my implementation seemed functionally complete, so I stripped out all of the debug statements, ran `clang-format` on it and reviewed it +to make sure that the names were clear. Here's what I ended up with: + +```C++ + void DoChecker::Leave(const parser::Expr &parsedExpr) { + ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))}; + for (const evaluate::ActualArgumentRef &argRef : argSet) { + if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) { + if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) { + common::Intent intent{argRef->dummyIntent()}; + switch (intent) { + case common::Intent::Out: + context_.CheckDoVarRedefine(parsedExpr.source, *var); + break; + case common::Intent::InOut: + context_.WarnDoVarRedefine(parsedExpr.source, *var); + break; + default:; // INTENT(IN) or default intent + } + } + } + } + } +``` + +I then created a pull request to get review comments. + +# Responding to pull request comments +I got feedback suggesting that I use an `if` statement rather than a +`case` statement. Another comment reminded me that I should look at the +code I'd previously writted to do a similar check for SUBROUTINE calls to see +if there was an opportunity to share code. This examination resulted in + converting my existing code to the following pair of functions: + + +```C++ + static void CheckIfArgIsDoVar(const evaluate::ActualArgument &arg, + const parser::CharBlock location, SemanticsContext &context) { + common::Intent intent{arg.dummyIntent()}; + if (intent == common::Intent::Out || intent == common::Intent::InOut) { + if (const SomeExpr * argExpr{arg.UnwrapExpr()}) { + if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) { + if (intent == common::Intent::Out) { + context.CheckDoVarRedefine(location, *var); + } else { + context.WarnDoVarRedefine(location, *var); // INTENT(INOUT) + } + } + } + } + } + + void DoChecker::Leave(const parser::Expr &parsedExpr) { + if (const SomeExpr * expr{GetExpr(parsedExpr)}) { + ActualArgumentSet argSet{CollectActualArguments(*expr)}; + for (const evaluate::ActualArgumentRef &argRef : argSet) { + CheckIfArgIsDoVar(*argRef, parsedExpr.source, context_); + } + } + } +``` + +The function `CheckIfArgIsDoVar()` was shared with the checks for DO +variables being passed to SUBROUTINE calls. + +At this point, my pull request was approved, and I merged it and deleted the +associated branch. |