| <!--===- 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 |
| |
| --> |
| # How to implement a Sematic Check in Flang |
| |
| ```eval_rst |
| .. contents:: |
| :local: |
| ``` |
| |
| 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. |