The issue tracker entry "Rule required for shadowed variables" 
[#8009](https://github.com/pharo-project/pharo/issues/8009)

It reads:

___


In the CI output log for PRs on Pharo 9 issues I see the following warnings on 
Roassal classes:

SystemNotification: RSCanvas>>numberOfEdges(edges is shadowed)
SystemNotification: RSCanvas>>numberOfEdges(edges is shadowed)
SystemNotification: RSCanvas>>numberOfNodes(nodes is shadowed)
SystemNotification: RSCanvas>>numberOfNodes(nodes is shadowed)
SystemNotification: RSComposite>>numberOfEdges(edges is shadowed)
SystemNotification: RSComposite>>numberOfEdges(edges is shadowed)
SystemNotification: RSComposite>>numberOfNodes(nodes is shadowed)
SystemNotification: RSComposite>>numberOfNodes(nodes is shadowed)
SystemNotification: RSShape>>printOn:(model is shadowed)
SystemNotification: RSShape>>printOn:(model is shadowed)

which means we have local variables (or block variable) in a method with the 
same name as an existing ivar in the class.

These cases should be cleaned in Rossal3 for the Pharo 9 image - for that I 
opened https://github.com/ObjectProfile/Roassal3/issues/321 on Roassal3 issue 
tracker.

But it would be good if we could have a Lint rule that shows that problem so 
one can avoid it:

**Side note:** Yes - there already is a rule called 
ReTempVarOverridesInstVarRuleTest - but this is only for temps
                  and does not cover variables in blocks like in

```Smalltalk
numberOfEdges
        "Return the number of edges contained in the container"
        ^ self privateEdges
                ifNil: [ 0 ]
                ifNotNil: [ :edges | edges size ]
```
when "edges" is already an instance variable

___

So how is ReTempVarOverridesInstVarRuleTest implemented?

```Smalltalk
check: aMethod forCritiquesDo: aCriticBlock
        | ivarNames problemTemps|
        ivarNames := aMethod methodClass instVarNames.
        ivarNames ifEmpty: [ ^ self ].
        
        problemTemps := ((aMethod ast arguments, aMethod ast temporaries)
                select: [ :node |ivarNames includes: node name]).
        problemTemps do: [ :node |
                aCriticBlock cull: (self critiqueFor: aMethod about: node) ] 
```

So, this test is done purely on the results of the compilation, it just checks 
if temps are overriding the instance vars.
It just does not care about blocks, nor class variables or globals.

We could now, for this issue, recurse into the compiled block closures, but 
that would make the code quite complex.

To see a better solution, let's check who actually prints out the log message 
in the build. Just searching for a substring will
lead us to OCShadowVariableWarning>>#variable:shadows:

```Smalltalk 
variable: varNode shadows: semVar
        compilationContext interactive
                ifTrue: [ 
                        OCSemanticError new
                                node: node;
                                compilationContext: compilationContext;
                                messageText: self stringMessage;
                                signal ]
                ifFalse: [ self showWarningOnTranscript ].
                
```

This is an exception raised by the AST visitor that does name analysis, 
OCASTSemanticAnalyzer:

```Smalltalk
variable: variableNode shadows: semVar
        compilationContext optionSkipSemanticWarnings ifTrue: [ ^semVar ].
        ^ OCShadowVariableWarning new
                node: variableNode;
                shadowedVar: semVar;
                compilationContext: compilationContext;
                signal
```

This method is called whever we lookup a Variable name to create a Variable 
object describing it:

```Smalltalk
declareVariableNode: aVariableNode as: anOCTempVariable
        | name var |
        name := aVariableNode name.
        var := scope lookupVarForDeclaration: name.
        var ifNotNil: [ 
                "Another variable with same name is visible from current scope.
                Warn about the name clash and if proceed add new temp to shadow 
existing var" 
                self variable: aVariableNode shadows: var ].
        var := scope addTemp: anOCTempVariable.
        aVariableNode binding: var.
        ^ var
```

This means: in non-interactive mode, we just log the error and compile while 
allowing shadowing. In interactive mode
we forbid it (we raise a OCSemanticError exception that will be displayed in 
the code editor).

What could we now do to improve the model of shadowing variables? The best 
thing would be if we could add the fact that
 a variable shadows another to the semantic information. That is, it should be 
modeled as part of the variable.

We do not need much: just add this information as a property would be perfectly 
enough. This way we can add a test method
(that check if the property exists). On the level of the Code Critique rule it 
will be very simple: just get all variables
that are defined (args, temps, args of blocks) and check if they are shadowing. 
That's easy!

So what is needed to implement it?

We first need to make sure we hand over one more parameter to 
#variable:shadows: and call it later: right now, in 
interactive mode, we never create a Variable at all. There is no problem to 
delay that and tag the variable correctly:

```Smalltalk
variable: aVariable shadows: shadowedVariable inNode: variableNode
        aVariable shadowing: shadowedVariable.
        compilationContext optionSkipSemanticWarnings ifTrue: [ ^aVariable ].
        ^ OCShadowVariableWarning new
                node: variableNode;
                shadowedVar: aVariable;
                compilationContext: compilationContext;
                signal
```

with:

```Smalltalk
declareVariableNode: aVariableNode as: anOCTempVariable
        | name var shadowed |
        name := aVariableNode name. 
        var := scope lookupVarForDeclaration: name.
        var ifNotNil: [ 
                "Another variable with same name is visible from current scope"
                shadowed := var.
                ].
        var := scope addTemp: anOCTempVariable.
        aVariableNode binding: var.
        shadowed ifNotNil: [self variable: var shadows: shadowed inNode: 
aVariableNode].
        ^ var
```


To be able to tag Variables, we implement


```Smalltalk
shadowing: anotherVariable
        self propertyAt: #shadows put: anotherVariable
```

and 

```Smalltalk
"testing"
isShadowing
        ^self hasProperty: #shadows
```

We do this on Variables, which means that we can later introduce Variable 
shadow tagging even for instance variables and Class Variables

How do we now test this?

One idea is to rewrite ReTempVarOverridesInstVarRule>>#check:forCritiquesDo: to 
use #isShadowing. ReTempVarOverridesInstVarRuleTest
is green, we have something that is reado to commit.

Let's try

```Smalltalk
check: aMethod forCritiquesDo: aCriticBlock

        | problemTemps |
        problemTemps := aMethod temporaryVariables select: [ :var | 
                                var isShadowing ].
        problemTemps do: [ :var | 
                aCriticBlock cull:
                        (self critiqueFor: aMethod about: var definingNode) ]
```

And: Success! This is now a first PR to be done [see 
here](https://github.com/pharo-project/pharo/pull/8909). 

This does not yet provide the Rule and the Release Test for shadowing vars in 
general, but it is a very nice first step that we can build upon next. 

Reply via email to