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