We merged this and did the next step:

        8009-Rule-required-for-shadowed-variables #8917
                https://github.com/pharo-project/pharo/pull/8917

Thus the Code critique now correctly shows shadowed variables even for the case 
where the argument of a closure
shadows an other variables.

        • The rule of course can now be further improved to exactly tell the 
user what is shadowed              
        • We should add a rule that checks of instance or class vars shadow 
globals (which needs further refinement on the shadow model)
        • We should add a release test to make sure we have no shadowed vars in 
the system
                — both on the level of methods and on the level of Classes

but that is for the future


> On 29 Mar 2021, at 16:26, Marcus Denker <marcus.den...@inria.fr> wrote:
> 
> 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