1I recently added a semantic check to the f18 compiler front end.  This document
2describes my thought process and the resulting implementation.
3
4For more information about the compiler, start with the
5[compiler overview](Overview.md).
6
7# Problem definition
8
9In the 2018 Fortran standard, section 11.1.7.4.3, paragraph 2, states that:
10
11```
12Except for the incrementation of the DO variable that occurs in step (3), the DO variable
13shall neither be redefined nor become undefined while the DO construct is active.
14```
15One of the ways that DO variables might be redefined is if they are passed to
16functions with dummy arguments whose `INTENT` is `INTENT(OUT)` or
17`INTENT(INOUT)`.  I implemented this semantic check.  Specifically, I changed
18the compiler to emit an error message if an active DO variable was passed to a
19dummy argument of a FUNCTION with INTENT(OUT).  Similarly, I had the compiler
20emit a warning if an active DO variable was passed to a dummy argument with
21INTENT(INOUT).  Previously, I had implemented similar checks for SUBROUTINE
22calls.
23
24# Creating a test
25
26My 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:
27
28```fortran
29  subroutine s()
30    Integer :: ivar, jvar
31
32    do ivar = 1, 10
33      jvar = intentOutFunc(ivar) ! Error since ivar is a DO variable
34    end do
35
36  contains
37    function intentOutFunc(dummyArg)
38      integer, intent(out) :: dummyArg
39      integer  :: intentOutFunc
40
41      dummyArg = 216
42    end function intentOutFunc
43  end subroutine s
44```
45
46I verified that other Fortran compilers produced an error message at the point
47of the call to `intentOutFunc()`:
48
49```fortran
50      jvar = intentOutFunc(ivar) ! Error since ivar is a DO variable
51```
52
53
54I also used this program to produce a parse tree for the program using the command:
55```bash
56  f18 -fdebug-dump-parse-tree -fparse-only testfun.f90
57```
58
59Here's the relevant fragment of the parse tree produced by the compiler:
60
61```
62| | ExecutionPartConstruct -> ExecutableConstruct -> DoConstruct
63| | | NonLabelDoStmt
64| | | | LoopControl -> LoopBounds
65| | | | | Scalar -> Name = 'ivar'
66| | | | | Scalar -> Expr = '1_4'
67| | | | | | LiteralConstant -> IntLiteralConstant = '1'
68| | | | | Scalar -> Expr = '10_4'
69| | | | | | LiteralConstant -> IntLiteralConstant = '10'
70| | | Block
71| | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> AssignmentStmt = 'jvar=intentoutfunc(ivar)'
72| | | | | Variable -> Designator -> DataRef -> Name = 'jvar'
73| | | | | Expr = 'intentoutfunc(ivar)'
74| | | | | | FunctionReference -> Call
75| | | | | | | ProcedureDesignator -> Name = 'intentoutfunc'
76| | | | | | | ActualArgSpec
77| | | | | | | | ActualArg -> Expr = 'ivar'
78| | | | | | | | | Designator -> DataRef -> Name = 'ivar'
79| | | EndDoStmt ->
80```
81
82Note that this fragment of the tree only shows four `parser::Expr` nodes,
83but the full parse tree also contained a fifth `parser::Expr` node for the
84constant 216 in the statement:
85
86```fortran
87      dummyArg = 216
88```
89# Analysis and implementation planning
90
91I then considered what I needed to do.  I needed to detect situations where an
92active DO variable was passed to a dummy argument with `INTENT(OUT)` or
93`INTENT(INOUT)`.  Once I detected such a situation, I needed to produce a
94message that highlighted the erroneous source code.
95
96## Deciding where to add the code to the compiler
97This new semantic check would depend on several types of information -- the
98parse tree, source code location information, symbols, and expressions.  Thus I
99needed to put my new code in a place in the compiler after the parse tree had
100been created, name resolution had already happened, and expression semantic
101checking had already taken place.
102
103Most semantic checks for statements are implemented by walking the parse tree
104and performing analysis on the nodes they visit.  My plan was to use this
105method.  The infrastructure for walking the parse tree for statement semantic
106checking is implemented in the files `lib/Semantics/semantics.cpp`.
107Here's a fragment of the declaration of the framework's parse tree visitor from
108`lib/Semantics/semantics.cpp`:
109
110```C++
111  // A parse tree visitor that calls Enter/Leave functions from each checker
112  // class C supplied as template parameters. Enter is called before the node's
113  // children are visited, Leave is called after. No two checkers may have the
114  // same Enter or Leave function. Each checker must be constructible from
115  // SemanticsContext and have BaseChecker as a virtual base class.
116  template<typename... C> class SemanticsVisitor : public virtual C... {
117  public:
118    using C::Enter...;
119    using C::Leave...;
120    using BaseChecker::Enter;
121    using BaseChecker::Leave;
122    SemanticsVisitor(SemanticsContext &context)
123      : C{context}..., context_{context} {}
124      ...
125
126```
127
128Since FUNCTION calls are a kind of expression, I was planning to base my
129implementation on the contents of `parser::Expr` nodes.  I would need to define
130either an `Enter()` or `Leave()` function whose parameter was a `parser::Expr`
131node.  Here's the declaration I put into `lib/Semantics/check-do.h`:
132
133```C++
134  void Leave(const parser::Expr &);
135```
136The `Enter()` functions get called at the time the node is first visited --
137that is, before its children.  The `Leave()` function gets called after the
138children are visited.  For my check the visitation order didn't matter, so I
139arbitrarily chose to implement the `Leave()` function to visit the parse tree
140node.
141
142Since my semantic check was focused on DO CONCURRENT statements, I added it to
143the file `lib/Semantics/check-do.cpp` where most of the semantic checking for
144DO statements already lived.
145
146## Taking advantage of prior work
147When implementing a similar check for SUBROUTINE calls, I created a utility
148functions in `lib/Semantics/semantics.cpp` to emit messages if
149a symbol corresponding to an active DO variable was being potentially modified:
150
151```C++
152  void WarnDoVarRedefine(const parser::CharBlock &location, const Symbol &var);
153  void CheckDoVarRedefine(const parser::CharBlock &location, const Symbol &var);
154```
155
156The first function is intended for dummy arguments of `INTENT(INOUT)` and
157the second for `INTENT(OUT)`.
158
159Thus I needed three pieces of
160information --
1611. the source location of the erroneous text,
1622. the `INTENT` of the associated dummy argument, and
1633. the relevant symbol passed as the actual argument.
164
165The first and third are needed since they're required to call the utility
166functions.  The second is needed to determine whether to call them.
167
168## Finding the source location
169The source code location information that I'd need for the error message must
170come from the parse tree.  I looked in the file
171`include/flang/Parser/parse-tree.h` and determined that a `struct Expr`
172contained source location information since it had the field `CharBlock
173source`.  Thus, if I visited a `parser::Expr` node, I could get the source
174location information for the associated expression.
175
176## Determining the `INTENT`
177I knew that I could find the `INTENT` of the dummy argument associated with the
178actual argument from the function called `dummyIntent()` in the class
179`evaluate::ActualArgument` in the file `include/flang/Evaluate/call.h`.  So
180if I could find an `evaluate::ActualArgument` in an expression, I could
181  determine the `INTENT` of the associated dummy argument.  I knew that it was
182  valid to call `dummyIntent()` because the data on which `dummyIntent()`
183  depends is established during semantic processing for expressions, and the
184  semantic processing for expressions happens before semantic checking for DO
185  constructs.
186
187In my prior work on checking the INTENT of arguments for SUBROUTINE calls,
188the parse tree held a node for the call (a `parser::CallStmt`) that contained
189an `evaluate::ProcedureRef` node.
190```C++
191  struct CallStmt {
192    WRAPPER_CLASS_BOILERPLATE(CallStmt, Call);
193    mutable std::unique_ptr<evaluate::ProcedureRef,
194        common::Deleter<evaluate::ProcedureRef>>
195        typedCall;  // filled by semantics
196  };
197```
198The `evaluate::ProcedureRef` contains a list of `evaluate::ActualArgument`
199nodes.  I could then find the INTENT of a dummy argument from the
200`evaluate::ActualArgument` node.
201
202For a FUNCTION call, though, there is no similar way to get from a parse tree
203node to an `evaluate::ProcedureRef` node.  But I knew that there was an
204existing framework used in DO construct semantic checking that traversed an
205`evaluate::Expr` node collecting `semantics::Symbol` nodes.  I guessed that I'd
206be able to use a similar framework to traverse an `evaluate::Expr`  node to
207find all of the `evaluate::ActualArgument` nodes.
208
209Note that the compiler has multiple types called `Expr`.  One is in the
210`parser` namespace.  `parser::Expr` is defined in the file
211`include/flang/Parser/parse-tree.h`.  It represents a parsed expression that
212maps directly to the source code and has fields that specify any operators in
213the expression, the operands, and the source position of the expression.
214
215Additionally, in the namespace `evaluate`, there are `evaluate::Expr<T>`
216template classes defined in the file `include/flang/Evaluate/expression.h`.
217These are parameterized over the various types of Fortran and constitute a
218suite of strongly-typed representations of valid Fortran expressions of type
219`T` that have been fully elaborated with conversion operations and subjected to
220constant folding.  After an expression has undergone semantic analysis, the
221field `typedExpr` in the `parser::Expr` node is filled in with a pointer that
222owns an instance of `evaluate::Expr<SomeType>`, the most general representation
223of an analyzed expression.
224
225All of the declarations associated with both FUNCTION and SUBROUTINE calls are
226in `include/flang/Evaluate/call.h`.  An `evaluate::FunctionRef` inherits from
227an `evaluate::ProcedureRef` which contains the list of
228`evaluate::ActualArgument` nodes.  But the relationship between an
229`evaluate::FunctionRef` node and its associated arguments is not relevant.  I
230only needed to find the `evaluate::ActualArgument` nodes in an expression.
231They hold all of the information I needed.
232
233So my plan was to start with the `parser::Expr` node and extract its
234associated `evaluate::Expr` field.  I would then traverse the
235`evaluate::Expr` tree collecting all of the `evaluate::ActualArgument`
236nodes.  I would look at each of these nodes to determine the `INTENT` of
237the associated dummy argument.
238
239This combination of the traversal framework and `dummyIntent()` would give
240me the `INTENT` of all of the dummy arguments in a FUNCTION call.  Thus, I
241would have the second piece of information I needed.
242
243## Determining if the actual argument is a variable
244I also guessed that I could determine if the `evaluate::ActualArgument`
245consisted of a variable.
246
247Once I had a symbol for the variable, I could call one of the functions:
248```C++
249  void WarnDoVarRedefine(const parser::CharBlock &, const Symbol &);
250  void CheckDoVarRedefine(const parser::CharBlock &, const Symbol &);
251```
252to emit the messages.
253
254If my plans worked out, this would give me the three pieces of information I
255needed -- the source location of the erroneous text, the `INTENT` of the dummy
256argument, and a symbol that I could use to determine whether the actual
257argument was an active DO variable.
258
259# Implementation
260
261## Adding a parse tree visitor
262I started my implementation by adding a visitor for `parser::Expr` nodes.
263Since this analysis is part of DO construct checking, I did this in
264`lib/Semantics/check-do.cpp`.  I added a print statement to the visitor to
265verify that my new code was actually getting executed.
266
267In `lib/Semantics/check-do.h`, I added the declaration for the visitor:
268
269```C++
270  void Leave(const parser::Expr &);
271```
272
273In `lib/Semantics/check-do.cpp`, I added an (almost empty) implementation:
274
275```C++
276  void DoChecker::Leave(const parser::Expr &) {
277    std::cout << "In Leave for parser::Expr\n";
278  }
279```
280
281I then built the compiler with these changes and ran it on my test program.
282This time, I made sure to invoke semantic checking.  Here's the command I used:
283```bash
284  f18 -fdebug-resolve-names -fdebug-dump-parse-tree -funparse-with-symbols testfun.f90
285```
286
287This produced the output:
288
289```
290  In Leave for parser::Expr
291  In Leave for parser::Expr
292  In Leave for parser::Expr
293  In Leave for parser::Expr
294  In Leave for parser::Expr
295```
296
297This made sense since the parse tree contained five `parser::Expr` nodes.
298So far, so good.  Note that a `parse::Expr` node has a field with the
299source position of the associated expression (`CharBlock source`).  So I
300now had one of the three pieces of information needed to detect and report
301errors.
302
303## Collecting the actual arguments
304To get the `INTENT` of the dummy arguments and the `semantics::Symbol` associated with the
305actual argument, I needed to find all of the actual arguments embedded in an
306expression that contained a FUNCTION call.  So my next step was to write the
307framework to walk the `evaluate::Expr` to gather all of the
308`evaluate::ActualArgument` nodes.  The code that I planned to model it on
309was the existing infrastructure that collected all of the `semantics::Symbol` nodes from an
310`evaluate::Expr`.  I found this implementation in
311`lib/Evaluate/tools.cpp`:
312
313```C++
314  struct CollectSymbolsHelper
315    : public SetTraverse<CollectSymbolsHelper, semantics::SymbolSet> {
316    using Base = SetTraverse<CollectSymbolsHelper, semantics::SymbolSet>;
317    CollectSymbolsHelper() : Base{*this} {}
318    using Base::operator();
319    semantics::SymbolSet operator()(const Symbol &symbol) const {
320      return {symbol};
321    }
322  };
323  template<typename A> semantics::SymbolSet CollectSymbols(const A &x) {
324    return CollectSymbolsHelper{}(x);
325  }
326```
327
328Note that the `CollectSymbols()` function returns a `semantics::Symbolset`,
329which is declared in `include/flang/Semantics/symbol.h`:
330
331```C++
332  using SymbolSet = std::set<SymbolRef>;
333```
334
335This infrastructure yields a collection based on `std::set<>`.  Using an
336`std::set<>` means that if the same object is inserted twice, the
337collection only gets one copy.  This was the behavior that I wanted.
338
339Here's a sample invocation of `CollectSymbols()` that I found:
340```C++
341    if (const auto *expr{GetExpr(parsedExpr)}) {
342      for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) {
343```
344
345I noted that a `SymbolSet` did not actually contain an
346`std::set<Symbol>`.  This wasn't surprising since we don't want to put the
347full `semantics::Symbol` objects into the set.  Ideally, we would be able to create an
348`std::set<Symbol &>` (a set of C++ references to symbols).  But C++ doesn't
349support sets that contain references.  This limitation is part of the rationale
350for the f18 implementation of type `common::Reference`, which is defined in
351  `include/flang/Common/reference.h`.
352
353`SymbolRef`, the specialization of the template `common::Reference` for
354`semantics::Symbol`, is declared in the file
355`include/flang/Semantics/symbol.h`:
356
357```C++
358  using SymbolRef = common::Reference<const Symbol>;
359```
360
361So to implement something that would collect `evaluate::ActualArgument`
362nodes from an `evaluate::Expr`, I first defined the required types
363`ActualArgumentRef` and `ActualArgumentSet`.  Since these are being
364used exclusively for DO construct semantic checking (currently), I put their
365definitions into `lib/Semantics/check-do.cpp`:
366
367
368```C++
369  namespace Fortran::evaluate {
370    using ActualArgumentRef = common::Reference<const ActualArgument>;
371  }
372
373
374  using ActualArgumentSet = std::set<evaluate::ActualArgumentRef>;
375```
376
377Since `ActualArgument` is in the namespace `evaluate`, I put the
378definition for `ActualArgumentRef` in that namespace, too.
379
380I then modeled the code to create an `ActualArgumentSet` after the code to
381collect a `SymbolSet` and put it into `lib/Semantics/check-do.cpp`:
382
383
384```C++
385  struct CollectActualArgumentsHelper
386    : public evaluate::SetTraverse<CollectActualArgumentsHelper,
387          ActualArgumentSet> {
388    using Base = SetTraverse<CollectActualArgumentsHelper, ActualArgumentSet>;
389    CollectActualArgumentsHelper() : Base{*this} {}
390    using Base::operator();
391    ActualArgumentSet operator()(const evaluate::ActualArgument &arg) const {
392      return ActualArgumentSet{arg};
393    }
394  };
395
396  template<typename A> ActualArgumentSet CollectActualArguments(const A &x) {
397    return CollectActualArgumentsHelper{}(x);
398  }
399
400  template ActualArgumentSet CollectActualArguments(const SomeExpr &);
401```
402
403Unfortunately, when I tried to build this code, I got an error message saying
404`std::set` requires the `<` operator to be defined for its contents.
405To fix this, I added a definition for `<`.  I didn't care how `<` was
406defined, so I just used the address of the object:
407
408```C++
409  inline bool operator<(ActualArgumentRef x, ActualArgumentRef y) {
410    return &*x < &*y;
411  }
412```
413
414I was surprised when this did not make the error message saying that I needed
415the `<` operator go away.  Eventually, I figured out that the definition of
416the `<` operator needed to be in the `evaluate` namespace.  Once I put
417it there, everything compiled successfully.  Here's the code that worked:
418
419```C++
420  namespace Fortran::evaluate {
421  using ActualArgumentRef = common::Reference<const ActualArgument>;
422
423  inline bool operator<(ActualArgumentRef x, ActualArgumentRef y) {
424    return &*x < &*y;
425  }
426  }
427```
428
429I then modified my visitor for the parser::Expr to invoke my new collection
430framework.  To verify that it was actually doing something, I printed out the
431number of `evaluate::ActualArgument` nodes that it collected.  Note the
432call to `GetExpr()` in the invocation of `CollectActualArguments()`.  I
433modeled this on similar code that collected a `SymbolSet` described above:
434
435```C++
436  void DoChecker::Leave(const parser::Expr &parsedExpr) {
437    std::cout << "In Leave for parser::Expr\n";
438    ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
439    std::cout << "Number of arguments: " << argSet.size() << "\n";
440  }
441```
442
443I compiled and tested this code on my little test program.  Here's the output that I got:
444```
445  In Leave for parser::Expr
446  Number of arguments: 0
447  In Leave for parser::Expr
448  Number of arguments: 0
449  In Leave for parser::Expr
450  Number of arguments: 0
451  In Leave for parser::Expr
452  Number of arguments: 1
453  In Leave for parser::Expr
454  Number of arguments: 0
455```
456
457So most of the `parser::Expr`nodes contained no actual arguments, but the
458fourth expression in the parse tree walk contained a single argument.  This may
459seem wrong since the third `parser::Expr` node in the file contains the
460`FunctionReference` node along with the arguments that we're gathering.
461But since the tree walk function is being called upon leaving a
462`parser::Expr` node, the function visits the `parser::Expr` node
463associated with the `parser::ActualArg` node before it visits the
464`parser::Expr` node associated with the `parser::FunctionReference`
465node.
466
467So far, so good.
468
469## Finding the `INTENT` of the dummy argument
470I now wanted to find the `INTENT` of the dummy argument associated with the
471arguments in the set.  As mentioned earlier, the type
472`evaluate::ActualArgument` has a member function called `dummyIntent()`
473that gives this value.  So I augmented my code to print out the `INTENT`:
474
475```C++
476  void DoChecker::Leave(const parser::Expr &parsedExpr) {
477    std::cout << "In Leave for parser::Expr\n";
478    ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
479    std::cout << "Number of arguments: " << argSet.size() << "\n";
480    for (const evaluate::ActualArgumentRef &argRef : argSet) {
481      common::Intent intent{argRef->dummyIntent()};
482      switch (intent) {
483        case common::Intent::In: std::cout << "INTENT(IN)\n"; break;
484        case common::Intent::Out: std::cout << "INTENT(OUT)\n"; break;
485        case common::Intent::InOut: std::cout << "INTENT(INOUT)\n"; break;
486        default: std::cout << "default INTENT\n";
487      }
488    }
489  }
490```
491
492I then rebuilt my compiler and ran it on my test case.  This produced the following output:
493
494```
495  In Leave for parser::Expr
496  Number of arguments: 0
497  In Leave for parser::Expr
498  Number of arguments: 0
499  In Leave for parser::Expr
500  Number of arguments: 0
501  In Leave for parser::Expr
502  Number of arguments: 1
503  INTENT(OUT)
504  In Leave for parser::Expr
505  Number of arguments: 0
506```
507
508I then modified my test case to convince myself that I was getting the correct
509`INTENT` for `IN`, `INOUT`, and default cases.
510
511So far, so good.
512
513## Finding the symbols for arguments that are variables
514The third and last piece of information I needed was to determine if a variable
515was being passed as an actual argument.  In such cases, I wanted to get the
516symbol table node (`semantics::Symbol`) for the variable.  My starting point was the
517`evaluate::ActualArgument` node.
518
519I was unsure of how to do this, so I browsed through existing code to look for
520how 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
521uses of `ActualArgument`.  One of the first hits I got was in `lib/Evaluate/call.cpp` in the definition of `ActualArgument::GetType()`:
522
523```C++
524std::optional<DynamicType> ActualArgument::GetType() const {
525  if (const Expr<SomeType> *expr{UnwrapExpr()}) {
526    return expr->GetType();
527  } else if (std::holds_alternative<AssumedType>(u_)) {
528    return DynamicType::AssumedType();
529  } else {
530    return std::nullopt;
531  }
532}
533```
534
535I noted the call to `UnwrapExpr()` that yielded a value of
536`Expr<SomeType>`.  So I guessed that I could use this member function to
537get an `evaluate::Expr<SomeType>` on which I could perform further analysis.
538
539I also knew that the header file `include/flang/Evaluate/tools.h` held many
540utility functions for dealing with `evaluate::Expr` objects.  I was hoping to
541find something that would determine if an `evaluate::Expr` was a variable.  So
542I searched for `IsVariable` and got a hit immediately.
543```C++
544  template<typename A> bool IsVariable(const A &x) {
545    if (auto known{IsVariableHelper{}(x)}) {
546      return *known;
547    } else {
548      return false;
549    }
550  }
551```
552
553But I actually needed more than just the knowledge that an `evaluate::Expr` was
554a variable.  I needed the `semantics::Symbol` associated with the variable.  So
555I searched in `include/flang/Evaluate/tools.h` for functions that returned a
556`semantics::Symbol`.  I found the following:
557
558```C++
559// If an expression is simply a whole symbol data designator,
560// extract and return that symbol, else null.
561template<typename A> const Symbol *UnwrapWholeSymbolDataRef(const A &x) {
562  if (auto dataRef{ExtractDataRef(x)}) {
563    if (const SymbolRef * p{std::get_if<SymbolRef>(&dataRef->u)}) {
564      return &p->get();
565    }
566  }
567  return nullptr;
568}
569```
570
571This was exactly what I wanted.  DO variables must be whole symbols.  So I
572could try to extract a whole `semantics::Symbol` from the `evaluate::Expr` in my
573`evaluate::ActualArgument`.  If this extraction resulted in a `semantics::Symbol`
574that wasn't a `nullptr`, I could then conclude if it was a variable that I
575could pass to existing functions that would determine if it was an active DO
576variable.
577
578I then modified the compiler to perform the analysis that I'd guessed would
579work:
580
581```C++
582  void DoChecker::Leave(const parser::Expr &parsedExpr) {
583    std::cout << "In Leave for parser::Expr\n";
584    ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
585    std::cout << "Number of arguments: " << argSet.size() << "\n";
586    for (const evaluate::ActualArgumentRef &argRef : argSet) {
587      if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) {
588        std::cout << "Got an unwrapped Expr\n";
589        if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
590          std::cout << "Found a whole variable: " << *var << "\n";
591        }
592      }
593      common::Intent intent{argRef->dummyIntent()};
594      switch (intent) {
595        case common::Intent::In: std::cout << "INTENT(IN)\n"; break;
596        case common::Intent::Out: std::cout << "INTENT(OUT)\n"; break;
597        case common::Intent::InOut: std::cout << "INTENT(INOUT)\n"; break;
598        default: std::cout << "default INTENT\n";
599      }
600    }
601  }
602```
603
604Note the line that prints out the symbol table entry for the variable:
605
606```C++
607          std::cout << "Found a whole variable: " << *var << "\n";
608```
609
610The compiler defines the "<<" operator for `semantics::Symbol`, which is handy
611for analyzing the compiler's behavior.
612
613Here's the result of running the modified compiler on my Fortran test case:
614
615```
616  In Leave for parser::Expr
617  Number of arguments: 0
618  In Leave for parser::Expr
619  Number of arguments: 0
620  In Leave for parser::Expr
621  Number of arguments: 0
622  In Leave for parser::Expr
623  Number of arguments: 1
624  Got an unwrapped Expr
625  Found a whole variable: ivar: ObjectEntity type: INTEGER(4)
626  INTENT(OUT)
627  In Leave for parser::Expr
628  Number of arguments: 0
629```
630
631Sweet.
632
633## Emitting the messages
634At this point, using the source location information from the original
635`parser::Expr`, I had enough information to plug into the exiting
636interfaces for emitting messages for active DO variables.  I modified the
637compiler code accordingly:
638
639
640```C++
641  void DoChecker::Leave(const parser::Expr &parsedExpr) {
642    std::cout << "In Leave for parser::Expr\n";
643    ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
644    std::cout << "Number of arguments: " << argSet.size() << "\n";
645    for (const evaluate::ActualArgumentRef &argRef : argSet) {
646      if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) {
647        std::cout << "Got an unwrapped Expr\n";
648        if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
649          std::cout << "Found a whole variable: " << *var << "\n";
650          common::Intent intent{argRef->dummyIntent()};
651          switch (intent) {
652            case common::Intent::In: std::cout << "INTENT(IN)\n"; break;
653            case common::Intent::Out:
654              std::cout << "INTENT(OUT)\n";
655              context_.CheckDoVarRedefine(parsedExpr.source, *var);
656              break;
657            case common::Intent::InOut:
658              std::cout << "INTENT(INOUT)\n";
659              context_.WarnDoVarRedefine(parsedExpr.source, *var);
660              break;
661            default: std::cout << "default INTENT\n";
662          }
663        }
664      }
665    }
666  }
667```
668
669I then ran this code on my test case, and miraculously, got the following
670output:
671
672```
673  In Leave for parser::Expr
674  Number of arguments: 0
675  In Leave for parser::Expr
676  Number of arguments: 0
677  In Leave for parser::Expr
678  Number of arguments: 0
679  In Leave for parser::Expr
680  Number of arguments: 1
681  Got an unwrapped Expr
682  Found a whole variable: ivar: ObjectEntity type: INTEGER(4)
683  INTENT(OUT)
684  In Leave for parser::Expr
685  Number of arguments: 0
686  testfun.f90:6:12: error: Cannot redefine DO variable 'ivar'
687        jvar = intentOutFunc(ivar)
688               ^^^^^^^^^^^^^^^^^^^
689  testfun.f90:5:6: Enclosing DO construct
690      do ivar = 1, 10
691         ^^^^
692```
693
694Even sweeter.
695
696# Improving the test case
697At this point, my implementation seemed to be working.  But I was concerned
698about the limitations of my test case.  So I augmented it to include arguments
699other than `INTENT(OUT)` and more complex expressions.  Luckily, my
700augmented test did not reveal any new problems.
701
702Here's the test I ended up with:
703
704```Fortran
705  subroutine s()
706
707    Integer :: ivar, jvar
708
709    ! This one is OK
710    do ivar = 1, 10
711      jvar = intentInFunc(ivar)
712    end do
713
714    ! Error for passing a DO variable to an INTENT(OUT) dummy
715    do ivar = 1, 10
716      jvar = intentOutFunc(ivar)
717    end do
718
719    ! Error for passing a DO variable to an INTENT(OUT) dummy, more complex
720    ! expression
721    do ivar = 1, 10
722      jvar = 83 + intentInFunc(intentOutFunc(ivar))
723    end do
724
725    ! Warning for passing a DO variable to an INTENT(INOUT) dummy
726    do ivar = 1, 10
727      jvar = intentInOutFunc(ivar)
728    end do
729
730  contains
731    function intentInFunc(dummyArg)
732      integer, intent(in) :: dummyArg
733      integer  :: intentInFunc
734
735      intentInFunc = 343
736    end function intentInFunc
737
738    function intentOutFunc(dummyArg)
739      integer, intent(out) :: dummyArg
740      integer  :: intentOutFunc
741
742      dummyArg = 216
743      intentOutFunc = 343
744    end function intentOutFunc
745
746    function intentInOutFunc(dummyArg)
747      integer, intent(inout) :: dummyArg
748      integer  :: intentInOutFunc
749
750      dummyArg = 216
751      intentInOutFunc = 343
752    end function intentInOutFunc
753
754  end subroutine s
755```
756
757# Submitting the pull request
758At this point, my implementation seemed functionally complete, so I stripped out all of the debug statements, ran `clang-format` on it and reviewed it
759to make sure that the names were clear.  Here's what I ended up with:
760
761```C++
762  void DoChecker::Leave(const parser::Expr &parsedExpr) {
763    ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
764    for (const evaluate::ActualArgumentRef &argRef : argSet) {
765      if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) {
766        if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
767          common::Intent intent{argRef->dummyIntent()};
768          switch (intent) {
769            case common::Intent::Out:
770              context_.CheckDoVarRedefine(parsedExpr.source, *var);
771              break;
772            case common::Intent::InOut:
773              context_.WarnDoVarRedefine(parsedExpr.source, *var);
774              break;
775            default:; // INTENT(IN) or default intent
776          }
777        }
778      }
779    }
780  }
781```
782
783I then created a pull request to get review comments.
784
785# Responding to pull request comments
786I got feedback suggesting that I use an `if` statement rather than a
787`case` statement.  Another comment reminded me that I should look at the
788code I'd previously writted to do a similar check for SUBROUTINE calls to see
789if there was an opportunity to share code.  This examination resulted in
790  converting my existing code to the following pair of functions:
791
792
793```C++
794  static void CheckIfArgIsDoVar(const evaluate::ActualArgument &arg,
795      const parser::CharBlock location, SemanticsContext &context) {
796    common::Intent intent{arg.dummyIntent()};
797    if (intent == common::Intent::Out || intent == common::Intent::InOut) {
798      if (const SomeExpr * argExpr{arg.UnwrapExpr()}) {
799        if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
800          if (intent == common::Intent::Out) {
801            context.CheckDoVarRedefine(location, *var);
802          } else {
803            context.WarnDoVarRedefine(location, *var);  // INTENT(INOUT)
804          }
805        }
806      }
807    }
808  }
809
810  void DoChecker::Leave(const parser::Expr &parsedExpr) {
811    if (const SomeExpr * expr{GetExpr(parsedExpr)}) {
812      ActualArgumentSet argSet{CollectActualArguments(*expr)};
813      for (const evaluate::ActualArgumentRef &argRef : argSet) {
814        CheckIfArgIsDoVar(*argRef, parsedExpr.source, context_);
815      }
816    }
817  }
818```
819
820The function `CheckIfArgIsDoVar()` was shared with the checks for DO
821variables being passed to SUBROUTINE calls.
822
823At this point, my pull request was approved, and I merged it and deleted the
824associated branch.
825