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