This is an automated email from the ASF dual-hosted git repository.

greg-dove pushed a commit to branch local_var_resolution_issue
in repository https://gitbox.apache.org/repos/asf/royale-compiler.git

commit c55c317b8a90b6a706f17d5619a80249c6cb202b
Author: greg-dove <[email protected]>
AuthorDate: Thu May 14 14:20:00 2026 +1200

    Refine definition handling and add compiler diagnostic utilities.
    -Add synchronization to metatag management in DefinitionBase and improve 
robustness of file path comparisons.
    -Enhance FunctionScope to automatically trigger re-parsing of function 
bodies during property lookups if nodes have been reclaimed.
    -Improve robustness of ASScopeBase.toString() and 
RoyaleProject.doubleCheckAmbiguousDefinition() with null-safety checks.
    -Add CompilerDiagnosticsConstants.println() for filtered diagnostic logging.
    -Update ConfigProcessor visibility for testing purposes, and refine debug 
headers in ASFileScope and ASScopeBase.
    -Add NameResolutionAfterGCTest to verify resolution consistency.
---
 .../config/CompilerDiagnosticsConstants.java       |   7 ++
 .../internal/definitions/DefinitionBase.java       |  19 +++-
 .../internal/parsing/as/ConfigProcessor.java       |   2 +-
 .../compiler/internal/projects/RoyaleProject.java  |   2 +
 .../compiler/internal/scopes/ASFileScope.java      |   2 +-
 .../compiler/internal/scopes/ASScopeBase.java      |  10 +-
 .../compiler/internal/scopes/FunctionScope.java    |  49 +++++++++
 .../internal/scopes/NameResolutionAfterGCTest.java | 111 +++++++++++++++++++++
 8 files changed, 194 insertions(+), 8 deletions(-)

diff --git 
a/compiler-common/src/main/java/org/apache/royale/compiler/config/CompilerDiagnosticsConstants.java
 
b/compiler-common/src/main/java/org/apache/royale/compiler/config/CompilerDiagnosticsConstants.java
index 76eb4833d..68cfc0e30 100644
--- 
a/compiler-common/src/main/java/org/apache/royale/compiler/config/CompilerDiagnosticsConstants.java
+++ 
b/compiler-common/src/main/java/org/apache/royale/compiler/config/CompilerDiagnosticsConstants.java
@@ -42,4 +42,11 @@ public class CompilerDiagnosticsConstants
     public static final int COMPC_PHASES = 16384;
     public static final int GOOG_DEPS = 32768;
 
+    public static void println(int type, String message)
+    {
+        if ((diagnostics & type) == type)
+        {
+            System.out.println(message);
+        }
+    }
 }
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/definitions/DefinitionBase.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/definitions/DefinitionBase.java
index 60bca1baa..f756afd6c 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/definitions/DefinitionBase.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/definitions/DefinitionBase.java
@@ -970,7 +970,7 @@ public abstract class DefinitionBase implements 
IDocumentableDefinition, IDefini
         return metaTags;
     }
 
-    protected void addMetaTag(IMetaTag metaTag)
+    protected synchronized void addMetaTag(IMetaTag metaTag)
     {
         IMetaTag[] newMetaTags = new IMetaTag[metaTags.length + 1];
         System.arraycopy(metaTags, 0, newMetaTags, 0, metaTags.length);
@@ -978,7 +978,7 @@ public abstract class DefinitionBase implements 
IDocumentableDefinition, IDefini
         setMetaTags(newMetaTags);
     }
 
-    public void setMetaTags(IMetaTag[] newMetaTags)
+    public synchronized void setMetaTags(IMetaTag[] newMetaTags)
     {
         if (newMetaTags == null)
         {
@@ -1322,9 +1322,18 @@ public abstract class DefinitionBase implements 
IDocumentableDefinition, IDefini
         {
             return true; //we can't verify path because we might be a 
definition from a library
         }
-        else if (leftScope instanceof ASFileScope && rightScope instanceof 
ASFileScope)
+        if (leftScope instanceof ASFileScope && rightScope instanceof 
ASFileScope)
         {
-            if 
(((ASFileScope)leftScope).getContainingPath().compareTo(((ASFileScope)rightScope).getContainingPath())
 != 0)
+            String leftPath = ((ASFileScope)leftScope).getContainingPath();
+            String rightPath = ((ASFileScope)rightScope).getContainingPath();
+            if (leftPath != null && rightPath != null)
+            {
+                if (leftPath.compareTo(rightPath) != 0)
+                {
+                    return false;
+                }
+            }
+            else if (leftPath != rightPath)
             {
                 return false;
             }
@@ -1822,7 +1831,7 @@ public abstract class DefinitionBase implements 
IDocumentableDefinition, IDefini
         return false;
     }
 
-    protected void addFunctionTypeMeta(IFunctionTypeExpressionNode 
funcTypeExprNode, String paramName, ICompilerProject project)
+    protected synchronized void 
addFunctionTypeMeta(IFunctionTypeExpressionNode funcTypeExprNode, String 
paramName, ICompilerProject project)
     {
         int existingIndex = -1;
         for (int i = 0; i < metaTags.length; i++)
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ConfigProcessor.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ConfigProcessor.java
index f151ea7da..e170d9a62 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ConfigProcessor.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/parsing/as/ConfigProcessor.java
@@ -249,7 +249,7 @@ public class ConfigProcessor
 
     private final IWorkspace workspace;
 
-    ConfigProcessor(IWorkspace workspace, BaseASParser parser)
+    public ConfigProcessor(IWorkspace workspace, BaseASParser parser)
     {
         this.parser = parser;
         configNames = new HashSet<String>();
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProject.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProject.java
index 1f83fa685..8aa90785b 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProject.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/RoyaleProject.java
@@ -2209,6 +2209,8 @@ public class RoyaleProject extends ASProject implements 
IRoyaleProject, ICompile
     @Override
     public IDefinition doubleCheckAmbiguousDefinition(IASScope scope, String 
name, IDefinition def1, IDefinition def2)
     {
+        if (scope == null)
+            return null;
         IScopedDefinition scopeDef = 
((ASScope)scope).getContainingDefinition();
         String thisPackage = null;
         if (scopeDef != null) 
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASFileScope.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASFileScope.java
index c393502c4..b5df824c3 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASFileScope.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASFileScope.java
@@ -180,7 +180,7 @@ public class ASFileScope extends ASScope implements 
IFileScope
      * For debugging only.
      */
     @Override
-    protected String toStringHeader()
+    public String toStringHeader()
     {
         StringBuilder sb = new StringBuilder();
 
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeBase.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeBase.java
index 34fa1d65f..406cbacaa 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeBase.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeBase.java
@@ -475,6 +475,14 @@ public abstract class ASScopeBase implements IASScope
             // for a project scope this would actualize every 
DefinitionPromise.
             IDefinitionSet set = definitionStore.getDefinitionSetByName(name);
 
+            if (set == null)
+            {
+                indent(sb, level);
+                sb.append("  <ERROR: null definition set for ");
+                sb.append(name);
+                sb.append(">\n");
+                return;
+            }
             int n = set.getSize();
             for (int i = 0; i < n; i++)
             {
@@ -502,7 +510,7 @@ public abstract class ASScopeBase implements IASScope
      * For debugging only. Called by toString() to return the header that is
      * displayed at the beginning.
      */
-    protected String toStringHeader()
+    public String toStringHeader()
     {
         return getClass().getSimpleName();
     }
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/FunctionScope.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/FunctionScope.java
index b734781f3..0c671e7e1 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/FunctionScope.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/FunctionScope.java
@@ -20,9 +20,12 @@
 package org.apache.royale.compiler.internal.scopes;
 
 import java.util.Collection;
+import java.util.Set;
 
 import org.apache.royale.compiler.definitions.IDefinition;
+import org.apache.royale.compiler.definitions.INamespaceDefinition;
 import org.apache.royale.compiler.definitions.IParameterDefinition;
+import org.apache.royale.compiler.internal.projects.CompilerProject;
 import org.apache.royale.compiler.internal.tree.as.FunctionNode;
 import org.apache.royale.compiler.internal.tree.as.ScopedBlockNode;
 import org.apache.royale.compiler.tree.as.IASNode;
@@ -84,4 +87,50 @@ public class FunctionScope extends ASScope
         super.reconnectScopeNode(node);
     }
 
+    
+    @Override
+    public IScopedNode getScopeNode()
+    {
+        IScopedNode node = super.getScopeNode();
+        if (node instanceof ScopedBlockNode)
+        {
+            IASNode parentNode = node.getParent();
+            if (parentNode instanceof FunctionNode)
+            {
+                FunctionNode functionNode = (FunctionNode) parentNode;
+                if (functionNode.hasBeenParsed())
+                {
+                    reconnectScopeNode(node);
+                }
+            }
+        }
+        return node;
+    }
+
+    @Override
+    public void getAllLocalProperties(CompilerProject project, 
Collection<IDefinition> defs, Set<INamespaceDefinition> namespaceSet, 
INamespaceDefinition extraNamespace)
+    {
+        // If the function body hasn't been parsed yet (or needs re-parsing 
after GC),
+        // triggering getScopeNode() will ensure it is parsed and the scope is 
populated
+        // with local variables.
+        if (project.getWorkspace() != null && getFileScope() != null)
+        {
+            getScopeNode();
+        }
+        super.getAllLocalProperties(project, defs, namespaceSet, 
extraNamespace);
+    }
+
+    @Override
+    protected void getPropertyForScopeChain(CompilerProject project, 
Collection<IDefinition> defs, String baseName, NamespaceSetPredicate 
namespaceSet, boolean findAll)
+    {
+        // If the function body hasn't been parsed yet (or needs re-parsing 
after GC),
+        // triggering getScopeNode() will ensure it is parsed and the scope is 
populated
+        // with local variables.
+        if (project.getWorkspace() != null && getFileScope() != null)
+        {
+            getScopeNode();
+        }
+        super.getPropertyForScopeChain(project, defs, baseName, namespaceSet, 
findAll);
+    }
+
 }
diff --git 
a/compiler/src/test/java/org/apache/royale/compiler/internal/scopes/NameResolutionAfterGCTest.java
 
b/compiler/src/test/java/org/apache/royale/compiler/internal/scopes/NameResolutionAfterGCTest.java
new file mode 100644
index 000000000..e589ba2f5
--- /dev/null
+++ 
b/compiler/src/test/java/org/apache/royale/compiler/internal/scopes/NameResolutionAfterGCTest.java
@@ -0,0 +1,111 @@
+/*
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ */
+
+package org.apache.royale.compiler.internal.scopes;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.lang.reflect.Field;
+import java.util.Collections;
+import org.apache.royale.compiler.definitions.IDefinition;
+import org.apache.royale.compiler.internal.parsing.as.ASToken;
+import org.apache.royale.compiler.internal.parsing.as.ASTokenTypes;
+import org.apache.royale.compiler.internal.parsing.as.ConfigProcessor;
+import org.apache.royale.compiler.internal.tree.as.ASTestBase;
+import org.apache.royale.compiler.internal.tree.as.FunctionNode;
+import org.apache.royale.compiler.internal.tree.as.IdentifierNode;
+import org.apache.royale.compiler.internal.tree.as.NodeBase;
+import org.apache.royale.compiler.tree.as.IFunctionNode;
+import org.apache.royale.compiler.tree.as.IScopedNode;
+import org.junit.Test;
+
+public class NameResolutionAfterGCTest extends ASTestBase
+{
+    @Test
+    public void testNameResolutionAfterGC()
+    {
+        // 1. Create a function with a local variable
+        String code = "var a:int = 10; return a;";
+        IFunctionNode node = (IFunctionNode) getNode(code, 
IFunctionNode.class, WRAP_LEVEL_MEMBER);
+        FunctionScope scope = (FunctionScope) node.getScopedNode().getScope();
+        
+        // Verify we can find 'a' initially
+        IDefinition defA = scope.findProperty(project, "a", null, false);
+        assertNotNull("Initial resolution should be local", defA);
+        
+        // 2. Simulate GC by creating a new FunctionNode and reconnecting the 
scope.
+        // This simulates what happens when a FileNode is garbage collected 
and then re-parsed.
+        // We need a FunctionNode shell that hasn't been parsed yet.
+        FunctionNode newNode = new FunctionNode(null, new 
IdentifierNode("foo"));
+        // To make hasBeenParsed() return false, we set the function body info 
as deferred.
+        newNode.setFunctionBodyInfo(new ASToken(ASTokenTypes.TOKEN_BLOCK_OPEN, 
0, 0, 0, 0, "{"), 
+                                    new 
ASToken(ASTokenTypes.TOKEN_BLOCK_CLOSE, 0, 0, 0, 0, "}"), 
+                                    new 
ConfigProcessor(project.getWorkspace(), null), null);
+        
+        IScopedNode newScopedNode = newNode.getScopedNode();
+        // Since we are creating newNode manually, we must manually set its 
parent
+        ((NodeBase)newScopedNode).setParent(newNode);
+        
+        // This call to reconnectScopeNode should wipe local definitions 
because newNode.hasBeenParsed() is false.
+        scope.reconnectScopeNode(newScopedNode);
+        
+        // 3. Verify that the definitions were indeed wiped (explicit check).
+        // We use getAllLocalDefinitions() because it does NOT trigger the 
on-demand parsing fix
+        // in FunctionScope. It only inspects the current (wiped) state of the 
definition store.
+        boolean foundA = false;
+        for (IDefinition def : scope.getAllLocalDefinitions())
+        {
+            if ("a".equals(def.getBaseName()))
+            {
+                foundA = true;
+                break;
+            }
+        }
+        assertNull("Variable 'a' should be wiped from the raw scope store 
after reconnection", foundA ? "found a" : null);
+        
+        // 4. Test resolution after "GC" restoration.
+        // In a real scenario, getScopeNode() would trigger a re-parse that 
populates the node.
+        // Here we simulate the completion of a re-parse by clearing the 
deferred flag
+        // and manually adding the definition back to the scope.
+        // NOTE: In a real re-parse, the ASParser would create brand new 
definition instances.
+        // For this test, we re-use 'defA' to simplify the setup and focus on 
verifying 
+        // that the FunctionScope correctly triggers the restoration logic and 
allows 
+        // definitions to be re-added after they were wiped.
+        try {
+            Field field = 
FunctionNode.class.getDeclaredField("isBodyDeferred");
+            field.setAccessible(true);
+            field.set(newNode, false);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        
+        // We MUST re-use the SAME scope object but give it its "re-parsed" 
definitions.
+        scope.addDefinition(defA);
+        
+        // Before we call findProperty, we must clear the cache to ensure that 
the 
+        // name resolution logic actually calls getPropertyForScopeChain and 
triggers the fix.
+        project.resetScopeCaches(Collections.singleton(scope));
+        
+        // If the fix in FunctionScope is working, this call will trigger 
getScopeNode(),
+        // which ensures the function body is re-parsed (or in this case, uses 
our "re-parsed" node).
+        IDefinition defAfter = scope.findProperty(project, "a", null, false);
+        assertNotNull("Resolution after reconnection should successfully 
restore local definitions via on-demand parsing", defAfter);
+    }
+}

Reply via email to