1import{_ as n,c as s,e as a,o as t}from"./app-CjUkjRdW.js";const o="/assets/ident-scopes-DCN6fCAo.png",c="/assets/global-scope-resolution-BmJstCxW.png",p="/assets/block-scope-resolution-DnYSqQLx.png",l={};function i(r,e){return t(),s("div",null,e[0]||(e[0]=[a('<h1 id="scope-resolution" tabindex="-1"><a class="header-anchor" href="#scope-resolution"><span>Scope Resolution</span></a></h1><p>The current implementation of the <code>resolve</code> method in <code>IdentReference</code> is very naive. It only looks for the first <code>IdentDecl</code> element in the file, and returns it if the name matches the reference. This won't work in a real-world scenario where there could be multiple matching declarations in the file at different scopes. For example, consider the following code snippet (we haven't implemented <code>block</code> statements yet, but let's assume we have):</p><p><img src="'+o+`" alt="Identifier Scopes" width="400"></p><p>There are three declarations in this file:</p><ul><li>Line 2: <code>msg</code> declaration in a <code>block</code> scope. This scope is inaccessible to the <code>echo</code> statement.</li><li>Line 4: <code>msg</code> declaration in the file scope before the reference. This is the correct declaration that the reference should resolve to.</li><li>Line 7: <code>msg</code> declaration in the file scope after the reference. This scope is also inaccessible to the <code>echo</code> statement.</li></ul><p>We need to change our implementation of the <code>resolve</code> method to handle those scoping rules.</p><h2 id="tree-walk-up" tabindex="-1"><a class="header-anchor" href="#tree-walk-up"><span>Tree Walk-Up</span></a></h2><p>The IntelliJ platform provides a mechanism for scope-based resolution. It's a bit complex, but it allows us to define custom scopes and resolve references within those scopes. It works as follows:</p><ul><li>When <code>resolve</code> is called on a reference, we perform a <em>tree walk-up</em> to find the <em>scope</em> in which the reference is resolved. The <code>PsiTreeUtil.treeWalkUp</code> utility method can be used for this purpose. This method takes an instance of <code>PsiScopeProcessor</code> interface that is responsible for deciding whether a particular element is the target of the reference.</li><li>The <code>treeWalkUp</code> method iterates over the ancestors of the reference element, and calls a <code>processDeclarations</code> method on each of them, passing the <code>PsiScopeProcessor</code> instance.</li><li>For each of the ancestors, their respective <code>processDeclarations</code> method is called. This method is responsible for finding the declarations in the scope of that ancestor.</li><li>For each declaration found, the <code>execute</code> method of the <code>PsiScopeProcessor</code> is called. This method is responsible for deciding whether the declaration is the target of the reference. This is typically done by comparing the name of the declaration against the name of the reference. If a match is found, the <code>execute</code> method returns <code>false</code> to stop the iteration, and the declaration is returned as the result of the <code>resolve</code>. If a match is not found, the <code>execute</code> method returns <code>true</code> to continue the iteration.</li></ul><p>To better understand the tree walk-up mechanism, let's take a look at the implementation of the <code>treeWalkUp</code> method from the <code>PsiTreeUtil</code> class:</p><div class="language-java line-numbers-mode" data-highlighter="prismjs" data-ext="java" data-title="java"><pre><code><span class="line"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">treeWalkUp</span><span class="token punctuation">(</span><span class="token annotation punctuation">@NotNull</span> <span class="token class-name">PsiScopeProcessor</span> processor<span class="token punctuation">,</span></span> 2<span class="line"> <span class="token annotation punctuation">@NotNull</span> <span class="token class-name">PsiElement</span> entrance<span class="token punctuation">,</span></span> 3<span class="line">
3 <span class="token annotation punctuation">@Nullable</span> <span class="token class-name">PsiElement</span> maxScope<span class="token punctuation">,</span></span> 4<span class="line"> <span class="token annotation punctuation">@NotNull</span> <span class="token class-name">ResolveState</span> state<span class="token punctuation">)</span> <span class="token punctuation">{</span></span> 5<span class="line"> <span class="token class-name">PsiElement</span> prevParent <span class="token operator">=</span> entrance<span class="token punctuation">;</span></span> 6<span class="line"> <span class="token class-name">PsiElement</span> scope <span class="token operator">=</span> entrance<span class="token punctuation">;</span></span> 7<span class="line"></span> 8<span class="line"> <span class="token keyword">while</span> <span class="token punctuation">(</span>scope <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span></span> 9<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>scope<span class="token punctuation">.</span><span class="token function">processDeclarations</span><span class="token punctuation">(</span>processor<span class="token punctuation">,</span> state<span class="token punctuation">,</span> prevParent<span class="token punctuation">,</span> entrance<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span></span> 10<span class="line"></span> 11<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>scope <span class="token operator">==</span> maxScope<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span></span> 12<span class="line"> prevParent <span class="token operator">=</span> scope<span class="token punctuation">;</span></span> 13<span class="line"> scope <span class="token operator">=</span> prevParent<span class="token punctuation">.</span><span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></span> 14<span class="line"> <span class="token punctuation">}</span></span> 15<span class="line"></span> 16<span class="line"> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span></span> 17<span class="line"> <span class="token punctuation">}</span></span> 18<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>In a nutshell, the <code>treeWalkUp</code> method iterates over the ancestors of the <code>entrance</code> element, starting with the <code>entrance</code> element itself, passing the <code>processor</code> instance to the <code>processDeclarations</code> method of each ancestor. The iteration stops in one of two cases:</p><ol><li>if <code>processDeclarations</code> returns <code>false</code>, indicating that the target element has been found, or</li><li>if the <code>maxScope</code> is reached (in our case, that would be the top-level <code>NimFile</code> element).</li></ol><p>On every iteration, the <code>treeWalkUp</code> method moves up one level to the next "context" of the current element. The <code>getContext</code> method is defined in the <code>PsiElement</code> interface, and by default returns the parent of the element (it delegates to <code>getParent</code>). However, we're free to override this method in our custom PSI elements to return the parent "scope" of the element, which is not necessarily the parent element.</p><p>In the example above, if we were to use the default implementation of <code>getContext</code>, the <code>treeWalkUp</code> method would iterate over the following ancestors, in order: <code>Command</code> -> <code>Stmt</code> -> <code>StmtList</code> -> <code>NimFile</code>. However, when doing scope-based resolution, we can skip over intermediate ancestors that don't define their own scope, and only consider the ones that do. In this particular example, there's only one scope that we need to consider: the <code>NimFile</code> node. When we implement other scope-defining elements like <code>block</code>, <code>proc</code>, <code>for</code> loops, etc., we'll consider them as well.</p><p>Let's go ahead and implement the <code>getContext</code> method for the <code>IdentRef</code> element, which should return the closest ancestor that defines a scope.</p><div class="language-kotlin line-numbers-mode" data-highlighter="prismjs" data-ext="kt" data-title="kt"><pre><code><span class="line"><span class="token comment">// src/main/kotlin/khaledh/nimjet/psi/impl/IdentRefMixin.kt</span></span> 19<span class="line"><span class="token operator">..</span><span class="token punctuation">.</span></span> 20<span class="line"></span> 21<span class="line"><span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token function">IdentRefMixin</span><span class="token punctuation">(</span>node<span class="token operator">:</span> ASTNode<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">ASTWrapperPsiElement</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">,</span> IdentRef <span class="token punctuation">{</span></span> 22<span class="line"></span> 23<span class="line"> <span class="token keyword">companion</span> <span class="token keyword">object</span> <span class="token punctuation">{</span></span> 24<span class="line">
24 <span class="token keyword">private</span> <span class="token keyword">val</span> SCOPE_ELEMENT_TYPES <span class="token operator">=</span> <span class="token function">arrayOf</span><span class="token punctuation">(</span>NimFile<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">.</span>java<span class="token punctuation">)</span></span> 25<span class="line"> <span class="token punctuation">}</span></span> 26<span class="line"></span> 27<span class="line"> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> PsiElement<span class="token operator">?</span> <span class="token punctuation">{</span></span> 28<span class="line"> <span class="token keyword">return</span> PsiTreeUtil<span class="token punctuation">.</span><span class="token function">getParentOfType</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token operator">*</span>SCOPE_ELEMENT_TYPES<span class="token punctuation">)</span></span> 29<span class="line"> <span class="token punctuation">}</span></span> 30<span class="line"> </span> 31<span class="line"> <span class="token operator">..</span><span class="token punctuation">.</span></span> 32<span class="line"></span> 33<span class="line"><span class="token punctuation">}</span></span> 34<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Fortunately, the <code>PsiTreeUtil</code> class provides a utility method called <code>getParentOfType</code> that we can use to find the closest ancestor of one of the specified types. Since we're going to add more scopes later, we define a <code>SCOPE_ELEMENT_TYPES</code> array that contains the classes of the scope-defining elements. So far, we only have the <code>NimFile</code> class in the array.</p><h2 id="scope-processor" tabindex="-1"><a class="header-anchor" href="#scope-processor"><span>Scope Processor</span></a></h2><p>We now know how to find the ancestor scopes of a reference. The next step is to try to find the target declaration within each of those scopes, until either the target is found or we run out of scopes.</p><p>Let's use the example above to illustrate how this works.</p><ul><li>We create an implementation of <code>PsiScopeProcessor</code>, let's call it <code>IdentScopeProcessor</code>. Its <code>execute</code> method will compare the name of the declaration against the name of the reference, and return <code>false</code> if they match (which stops the iteration), and <code>true</code> otherwise.</li><li>When <code>resolve</code> is called on <code>IdentReference</code>, the <code>treeWalkUp</code> method is called, passing an instance of <code>IdentScopeProcessor</code>.</li><li>The <code>treeWalkUp</code> method iterates over the ancestor scopes of the <code>IdentRef</code> node, starting with the reference element itself. In this particular case, the only<br> ancestor scope is the <code>NimFile</code> (as returned by the <code>getContext</code> method of the <code>IdentRef</code> node).</li><li>The ancestor's <code>processDeclarations</code> method is called with the <code>IdentScopeProcessor</code> instance. In our case, the <code>NimFile</code> node will have a <code>processDeclarations</code> method that will look for declarations under its scope.</li><li>For each found <code>IdentDecl</code> node, the <code>execute</code> method of the <code>IdentScopeProcessor</code> instance is called. If the name of the declaration matches the name of the reference, the <code>execute</code> method will return <code>false</code>, which stops the iteration and returns the declaration as the result of the <code>resolve</code> method.</li></ul><p>With this in mind, let's implement the <code>IdentScopeProcessor</code> class:</p><div class="language-kotlin line-numbers-mode" data-highlighter="prismjs" data-ext="kt" data-title="kt"><pre><code><span class="line"><span class="token comment">// src/main/kotlin/khaledh/nimjet/psi/IdentScopeProcessor.kt</span></span> 35<span class="line"><span class="token operator">..</span><span class="token punctuation">.</span></span> 36<span class="line"></span> 37<span class="line"><span class="token keyword">class</span> <span class="token function">IdentScopeProcessor</span><span class="token punctuation">(</span><span class="token keyword">private</span> <span class="token keyword">val</span> name<span class="token operator">:</span> String<span class="token punctuation">)</span> <span class="token operator">:</span>
37 PsiScopeProcessor <span class="token punctuation">{</span></span> 38<span class="line"> <span class="token keyword">var</span> result<span class="token operator">:</span> PsiElement<span class="token operator">?</span> <span class="token operator">=</span> <span class="token keyword">null</span></span> 39<span class="line"></span> 40<span class="line"> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">execute</span><span class="token punctuation">(</span>element<span class="token operator">:</span> PsiElement<span class="token punctuation">,</span> state<span class="token operator">:</span> ResolveState<span class="token punctuation">)</span><span class="token operator">:</span> Boolean <span class="token punctuation">{</span></span> 41<span class="line"> <span class="token keyword">if</span> <span class="token punctuation">(</span>element <span class="token keyword">is</span> IdentDecl <span class="token operator">&&</span> element<span class="token punctuation">.</span>name <span class="token operator">==</span> name<span class="token punctuation">)</span> <span class="token punctuation">{</span></span> 42<span class="line"> result <span class="token operator">=</span> element</span> 43<span class="line"> <span class="token keyword">return</span> <span class="token boolean">false</span></span> 44<span class="line"> <span class="token punctuation">}</span></span> 45<span class="line"> <span class="token keyword">return</span> <span class="token boolean">true</span></span> 46<span class="line"> <span class="token punctuation">}</span></span> 47<span class="line"></span> 48<span class="line"><span class="token punctuation">}</span></span> 49<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>How it works:</p><ul><li>The <code>name</code> of the target declaration to be found is passed to the constructor.</li><li>The <code>result</code> field holds the target declaration if found.</li><li>The <code>execute</code> method is called for each declaration in the scope. If the name of the declaration matches the name of the reference, we set the <code>result</code> field to the declaration and return <code>false</code> to stop the iteration. Otherwise, we return <code>true</code> to continue to the next declaration (if any).</li></ul><p>Now let's modify the <code>resolve</code> method in the <code>IdentReference</code> class to use this scope processor.</p><div class="language-kotlin line-numbers-mode" data-highlighter="prismjs" data-ext="kt" data-title="kt"><pre><code><span class="line"><span class="token comment">// src/main/kotlin/khaledh/nimjet/psi/IdentReference.kt</span></span> 50<span class="line"><span class="token operator">..</span><span class="token punctuation">.</span> </span> 51<span class="line"></span> 52<span class="line"><span class="token keyword">class</span> <span class="token function">IdentReference</span><span class="token punctuation">(</span>element<span class="token operator">:</span> IdentRef<span class="token punctuation">,</span> textRange<span class="token operator">:</span> TextRange<span class="token punctuation">)</span></span> 53<span class="line"> <span class="token operator">:</span> PsiReferenceBase<span class="token operator"><</span>IdentRef<span class="token operator">></span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> textRange<span class="token punctuation">)</span> <span class="token punctuation">{</span></span> 54<span class="line"></span> 55<span class="line"> <span class="token keyword">
55override</span> <span class="token keyword">fun</span> <span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> PsiElement<span class="token operator">?</span> <span class="token punctuation">{</span></span> 56<span class="line"> <span class="token keyword">val</span> processor <span class="token operator">=</span> <span class="token function">IdentScopeProcessor</span><span class="token punctuation">(</span>element<span class="token punctuation">.</span>text<span class="token punctuation">)</span></span> 57<span class="line"> PsiTreeUtil<span class="token punctuation">.</span><span class="token function">treeWalkUp</span><span class="token punctuation">(</span></span> 58<span class="line"> processor<span class="token punctuation">,</span> <span class="token comment">// scope processor</span></span> 59<span class="line"> element<span class="token punctuation">,</span> <span class="token comment">// entrance element</span></span> 60<span class="line"> element<span class="token punctuation">.</span>containingFile<span class="token punctuation">,</span> <span class="token comment">// max scope</span></span> 61<span class="line"> ResolveState<span class="token punctuation">.</span><span class="token function">initial</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// initial state</span></span> 62<span class="line"> <span class="token punctuation">)</span></span> 63<span class="line"> <span class="token keyword">return</span> processor<span class="token punctuation">.</span>result</span> 64<span class="line"> <span class="token punctuation">}</span></span> 65<span class="line"></span> 66<span class="line"><span class="token punctuation">}</span></span> 67<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>The new implementation should be straightforward to understand. We create an instance of <code>IdentScopeProcessor</code> with the name of the reference, and pass it to the <code>treeWalkUp</code> method. We also pass the reference element as the entrance element (i.e. the starting point of the tree walk-up), and the containing <code>NimFile</code> as the maximum scope. The <code>ResolveState.initial()</code> method creates a <code>state</code> object that can be used to store additional information during the resolution process (we're not making use of it in our case). Finally, we return the <code>result</code> field of the <code>IdentScopeProcessor</code>, whether it's <code>null</code> (if the target declaration wasn't found) or the found declaration.</p><h2 id="processing-declarations" tabindex="-1"><a class="header-anchor" href="#processing-declarations"><span>Processing Declarations</span></a></h2><p>The last missing piece is to implement the <code>processDeclarations</code> method in the <code>NimFile</code> class. It turns out that processing declarations is the same under any scope, so we'll implement it once in a separate class and reuse it in all scope-defining elements.</p><p>Let's add a <code>NimScope</code> singleton object that contains the <code>processDeclarations</code> method.</p><div class="language-kotlin line-numbers-mode" data-highlighter="prismjs" data-ext="kt" data-title="kt"><pre><code><span class="line"><span class="token keyword">object</span> NimScope <span class="token punctuation">{</span></span> 68<span class="line"> <span class="token keyword">val</span> SCOPE_ELEMENT_TYPES <span class="token operator">=</span> <span class="token function">hashSetOf</span><span class="token punctuation">(</span>NimFile<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">)</span></span> 69<span class="line"></span> 70<span class="line"> <span class="token keyword">fun</span> <span class="token function">processDeclarations</span><span class="token punctuation">(</span></span> 71<span class="line"> scopeElement<span class="token operator">:</span> PsiElement<span class="token punctuation">,</span></span> 72<span class="line">
72 processor<span class="token operator">:</span> PsiScopeProcessor<span class="token punctuation">,</span></span> 73<span class="line"> state<span class="token operator">:</span> ResolveState<span class="token punctuation">,</span></span> 74<span class="line"> lastParent<span class="token operator">:</span> PsiElement<span class="token operator">?</span><span class="token punctuation">,</span></span> 75<span class="line"> place<span class="token operator">:</span> PsiElement</span> 76<span class="line"> <span class="token punctuation">)</span><span class="token operator">:</span> Boolean <span class="token punctuation">{</span></span> 77<span class="line"> <span class="token comment">// Get all descendants, stopping at new scopes or subtrees that we have already processed</span></span> 78<span class="line"> <span class="token keyword">var</span> decls <span class="token operator">=</span> scopeElement<span class="token punctuation">.</span><span class="token function">descendants</span><span class="token punctuation">(</span></span> 79<span class="line"> canGoInside <span class="token operator">=</span> <span class="token punctuation">{</span></span> 80<span class="line"> it <span class="token operator">===</span> scopeElement <span class="token operator">||</span></span> 81<span class="line"> it <span class="token operator">!==</span> lastParent <span class="token operator">&&</span></span> 82<span class="line"> it<span class="token operator">::</span><span class="token keyword">class</span> <span class="token operator">!</span><span class="token keyword">in</span> SCOPE_ELEMENT_TYPES</span> 83<span class="line"> <span class="token punctuation">}</span></span> 84<span class="line"> <span class="token punctuation">)</span></span> 85<span class="line"> <span class="token comment">// Keep only declaration elements</span></span> 86<span class="line"> decls <span class="token operator">=</span> decls<span class="token punctuation">.</span>filterIsInstance<span class="token operator"><</span>IdentDecl<span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 87<span class="line"> <span class="token comment">// Keep the ones that are declared before the reference element</span></span> 88<span class="line"> decls <span class="token operator">=</span> decls<span class="token punctuation">.</span><span class="token function">filter</span> <span class="token punctuation">{</span> it<span class="token punctuation">.</span>startOffset <span class="token operator"><</span> place<span class="token punctuation">.</span>startOffset <span class="token punctuation">}</span></span> 89<span class="line"></span> 90<span class="line"> <span class="token comment">// Now process each declaration</span></span> 91<span class="line"> <span class="token keyword">return</span> decls<span class="token punctuation">.</span><span class="token function">all</span> <span class="token punctuation">{</span> processor<span class="token punctuation">.</span><span class="token function">execute</span><span class="token punctuation">(</span>it<span class="token punctuation">,</span> state<span class="token punctuation">)</span> <span class="token punctuation">}</span></span> 92<span class="line"> <span class="token punctuation">}</span></span> 93<span class="line"></span> 94<span class="line"><span class="token punctuation">}</span></span> 95<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>We're leveraging the <code>descendants</code> extension function to get all the descendants of the scope element. This function takes a <code>canGoInside</code> lambda that determines whether the function should descend into the children of the current element (the element being visited). There are three cases where we continue descending:</p><ul><li>If the current element is the scope element itself (since <code>descendents</code> includes starting element),</li><li>If the current element is not the last parent of a subtree that we already processed,</li><li>If the current element is not a scope-defining element.</li></ul><p>The last condition is important because we don'
95t want to descend into new scopes, which would be inaccessible to the reference (like the <code>block</code> scope in the example).</p><p>We then perform two filtering operations on the descendants:</p><ul><li>We keep only the <code>IdentDecl</code> elements,</li><li>We keep only the declarations that come before the reference element.</li></ul><p>Finally, we use the <code>all</code> function on those declarations to process each of them. If any of the declarations returns <code>false</code> from the <code>execute</code> method, the <code>all</code> function will return early and stop the iteration. Otherwise, it will return <code>true</code> to indicate that no matching declaration was found.</p><p>The last thing we need to do is call this method from the <code>processDeclarations</code> method of the <code>NimFile</code> class.</p><div class="language-kotlin line-numbers-mode" data-highlighter="prismjs" data-ext="kt" data-title="kt"><pre><code><span class="line"><span class="token keyword">class</span> <span class="token function">NimFile</span><span class="token punctuation">(</span>viewProvider<span class="token operator">:</span> FileViewProvider<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">PsiFileBase</span><span class="token punctuation">(</span>viewProvider<span class="token punctuation">,</span> NimLanguage<span class="token punctuation">)</span> <span class="token punctuation">{</span></span> 96<span class="line"> <span class="token operator">..</span><span class="token punctuation">.</span></span> 97<span class="line"></span> 98<span class="line"> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">processDeclarations</span><span class="token punctuation">(</span></span> 99<span class="line"> processor<span class="token operator">:</span> PsiScopeProcessor<span class="token punctuation">,</span></span> 100<span class="line"> state<span class="token operator">:</span> ResolveState<span class="token punctuation">,</span></span> 101<span class="line"> lastParent<span class="token operator">:</span> PsiElement<span class="token operator">?</span><span class="token punctuation">,</span></span> 102<span class="line"> place<span class="token operator">:</span> PsiElement</span> 103<span class="line"> <span class="token punctuation">)</span><span class="token operator">:</span> Boolean <span class="token operator">=</span></span> 104<span class="line"> NimScope<span class="token punctuation">.</span><span class="token function">processDeclarations</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> processor<span class="token punctuation">,</span> state<span class="token punctuation">,</span> lastParent<span class="token punctuation">,</span> place<span class="token punctuation">)</span></span> 105<span class="line"></span> 106<span class="line"><span class="token punctuation">}</span></span> 107<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Before we forget, we also need to update the <code>IdentRefMixin</code> class to use the same <code>SCOPE_ELEMENT_TYPES</code> array from the <code>NimScope</code> object, instead of defining it locally (which could easily diverge). Since the logic for finding the scope element is the same for all scope-defining elements, we can actually put that logic in the <code>NimScope</code> object itself.</p><div class="language-kotlin line-numbers-mode" data-highlighter="prismjs" data-ext="kt" data-title="kt"><pre><code><span class="line"><span class="token operator">..</span><span class="token punctuation">.</span></span> 108<span class="line"></span> 109<span class="line"><span class="token keyword">object</span> NimScope <span class="token punctuation">{</span></span> 110<span class="line"> <span class="token keyword">val</span> SCOPE_ELEMENT_TYPES <span class="token operator">=</span> <span class="token function">hashSetOf</span><span class="token punctuation">(</span>NimFile<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">,</span> BlockStmt<span class="token operator">::</span><span class="token keyword">class</span><span class="token punctuation">)</span></span> 111<span class="line"> <span class="token operator">..</span><span class="token punctuation">.</span></span> 112<span class="line"></span> 113<span class="line"> <span class="token keyword">fun</span> <span class="token function">parentScope</span><span class="token punctuation">(</span>element<span class="token operator">:</span> PsiElement<span class="token punctuation">)</span><span class="token operator">:</span> PsiElement<span class="token operator">?</span> <span class="token punctuation">{</span></span> 114<span class="line">
114 <span class="token keyword">val</span> scopeElementTypes <span class="token operator">=</span> SCOPE_ELEMENT_TYPES<span class="token punctuation">.</span><span class="token function">map</span> <span class="token punctuation">{</span> it<span class="token punctuation">.</span>java <span class="token punctuation">}</span><span class="token punctuation">.</span><span class="token function">toTypedArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> 115<span class="line"> <span class="token keyword">return</span> PsiTreeUtil<span class="token punctuation">.</span><span class="token function">getParentOfType</span><span class="token punctuation">(</span>element<span class="token punctuation">,</span> <span class="token operator">*</span>scopeElementTypes<span class="token punctuation">)</span></span> 116<span class="line"> <span class="token punctuation">}</span></span> 117<span class="line"><span class="token punctuation">}</span></span> 118<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>Now we can use the <code>parentScope</code> method in the <code>IdentRefMixin</code> class to find the scope element.</p><div class="language-kotlin line-numbers-mode" data-highlighter="prismjs" data-ext="kt" data-title="kt"><pre><code><span class="line"><span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token function">IdentRefMixin</span><span class="token punctuation">(</span>node<span class="token operator">:</span> ASTNode<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token function">ASTWrapperPsiElement</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">,</span> IdentRef <span class="token punctuation">{</span></span> 119<span class="line"></span> 120<span class="line"> <span class="token keyword">override</span> <span class="token keyword">fun</span> <span class="token function">getContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> PsiElement<span class="token operator">?</span> <span class="token operator">=</span> NimScope<span class="token punctuation">.</span><span class="token function">parentScope</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span></span> 121<span class="line"></span> 122<span class="line"> <span class="token operator">..</span><span class="token punctuation">.</span></span> 123<span class="line"><span class="token punctuation">}</span></span> 124<span class="line"></span></code></pre><div class="line-numbers" aria-hidden="true" style="counter-reset:line-number 0;"><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div><div class="line-number"></div></div></div><p>This should take care of the scope resolution for the <code>IdentReference</code> class.</p><h2 id="testing-scope-resolution" tabindex="-1"><a class="header-anchor" href="#testing-scope-resolution"><span>Testing Scope Resolution</span></a></h2><p>Let's test it out. I modified the BNF grammar temporarily to include the <code>block</code> statement so that I can test the scope resolution (I used a semicolon to terminate the block, since we don't have indentation-based parsing yet).</p><p><img src="`+c+'" alt="Global Scope Resolution" width="400"></p><p>Notice that the highlighted <code>msg</code> reference in the global scope now resolves to the correct declaration right above it, and not the one in the block scope, even though the block scope appears first in the file. Now, let's look at the block scope.</p><p><img src="'+p+'" alt="Block Scope Resolution" width="400"></p><p>Great! The highlighted <code>msg</code> reference in the block scope resolves to the correct declaration inside the block, and the other reference in the global scope is not highlighted. Also, if we move the declaration after the reference, the reference doesn't resolve to it, which is the correct behavior.</p><p>We've reached an important milestone. Resolving references according to scoping rules is a semantic feature that is crucial for any language plugin. The groundwork we've laid here will enable additional features such as find usages, rename refactoring, code completion, and others.</p>',53)]))}const u=n(l,[["render",i],["__file","09-scopes.html.vue"]]),k=JSON.parse(`{"path":"/nimjet/09-scopes.html","title":"Scope Resolution","lang":"en-US","frontmatter":{},"headers":[{"level":2,"title":"Tree Walk-Up","slug":"tree-walk-up","link":"#tree-walk-up","children":[]},{"level":2,"title":"Scope Processor","slug":"scope-processor","link":"#scope-processor","children":[]},{"level":2,"title":"Processing Declarations","slug":"processing-declarations","link":"#processing-declarations","children":[]},{"level":2,"title":"Testing Scope Resolution","slug":"testing-scope-resolution","link":"#testing-scope-resolution","children":[]}
124],"git":{"updatedTime":1727231650000},"filePathRelative":"nimjet/09-scopes.md","excerpt":"\\n<p>The current implementation of the <code>resolve</code> method in <code>IdentReference</code> is very naive. It\\nonly looks for the first <code>IdentDecl</code> element in the file, and returns it if the name\\nmatches the reference. This won't work in a real-world scenario where there could be\\nmultiple matching declarations in the file at different scopes. For example, consider the\\nfollowing code snippet (we haven't implemented <code>block</code> statements yet, but let's assume we\\nhave):</p>"}`);export{u as comp,k as data};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.