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