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