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 4a12b1ec4c35e69ba21bbb3f26aa4fc02ba3c166
Author: greg-dove <[email protected]>
AuthorDate: Thu May 14 13:46:37 2026 +1200

    Implement performance caching for name resolution and improve Workspace 
thread-safety.
    -Add result caching to IdentifierNode.resolve() when performance caching is 
enabled.
    -Refine ASScopeCache with null-safe dependency tracking and improved debug 
string representation for multiname lookups.
    -Enhance CompilerProject with granular scope cache invalidation and a 
single-scope optimization path.
    -Update ASScope.reconnectScopeNode() to trigger cache invalidation across 
all projects in the workspace.
    -Make Workspace.getProjects() public and add synchronization to project 
lifecycle methods to ensure thread-safe access during cache invalidation.
---
 .../internal/projects/CompilerProject.java         | 50 +++++++++++++++----
 .../royale/compiler/internal/scopes/ASScope.java   | 18 ++++++-
 .../compiler/internal/scopes/ASScopeCache.java     | 56 ++++++++++++----------
 .../compiler/internal/tree/as/IdentifierNode.java  | 25 ++++++++--
 .../compiler/internal/workspaces/Workspace.java    | 21 +++++---
 5 files changed, 126 insertions(+), 44 deletions(-)

diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java
index 397bb6bf3..eb0c24260 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java
@@ -238,12 +238,20 @@ public abstract class CompilerProject implements 
ICompilerProject
                 scopeRequests.add(unit.getFileScopeRequest());
         }
 
-        scopeCaches.invalidateAll();
-        initThreadLocalCaches();
+        invalidateAllScopeCaches();
         
         projectScope.addAllExternallyVisibleDefinitions(scopeRequests);
     }
 
+    /**
+     * Invalidates all scope caches for this project, including thread-local 
caches.
+     */
+    public void invalidateAllScopeCaches()
+    {
+        scopeCaches.invalidateAll();
+        initThreadLocalCaches();
+    }
+
     /**
      * Adds compilation units to the project and updates the public and private
      * definitions. Eventually this method should be removed, but it is
@@ -535,8 +543,7 @@ public abstract class CompilerProject implements 
ICompilerProject
                 compilationUnit.clean(null, cusToUpdate, true);
             }
 
-            scopeCaches.invalidateAll();
-            initThreadLocalCaches();
+            invalidateAllScopeCaches();
         }
         finally
         {
@@ -715,14 +722,41 @@ public abstract class CompilerProject implements 
ICompilerProject
         resetScopeCaches(relatedScopes);
     }
     
-    private void resetScopeCaches(Iterable<IASScope> scopes)
+    /**
+     * Resets all the {@link ASScopeCache}s associated with the specified
+     * {@link IASScope}s.
+     * 
+     * @param scopes {@link IASScope}s whose scope caches should be cleared
+     */
+    public void resetScopeCaches(Iterable<IASScope> scopes)
     {
         assert scopes != null;
-        for (IASScope scope : scopes)
+        boolean invalidated = false;
+        // Optimization: if it's a single scope, avoid overhead of iterator or 
complex checks
+        if (scopes instanceof Collection && ((Collection<?>)scopes).size() == 
1)
         {
-            scopeCaches.invalidate(scope);
+            IASScope scope = scopes.iterator().next();
+            if (scopeCaches.asMap().containsKey(scope))
+            {
+                scopeCaches.invalidate(scope);
+                invalidated = true;
+            }
+        }
+        else
+        {
+            for (IASScope scope : scopes)
+            {
+                if (scopeCaches.asMap().containsKey(scope))
+                {
+                    scopeCaches.invalidate(scope);
+                    invalidated = true;
+                }
+            }
+        }
+        if (invalidated)
+        {
+            initThreadLocalCaches();
         }
-        initThreadLocalCaches();
     }
 
     public void 
addGlobalUsedNamespacesToNamespaceSet(Set<INamespaceDefinition> nsSet)
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java
index 33bcc66ef..eb3d96e36 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java
@@ -37,6 +37,8 @@ import 
org.apache.royale.compiler.constants.IASLanguageConstants;
 import org.apache.royale.compiler.definitions.IClassDefinition;
 import org.apache.royale.compiler.definitions.IDefinition;
 import org.apache.royale.compiler.definitions.INamespaceDefinition;
+import 
org.apache.royale.compiler.definitions.INamespaceDefinition.IProtectedNamespaceDefinition;
+import 
org.apache.royale.compiler.definitions.INamespaceDefinition.IStaticProtectedNamespaceDefinition;
 import org.apache.royale.compiler.definitions.IQualifiers;
 import org.apache.royale.compiler.definitions.IScopedDefinition;
 import org.apache.royale.compiler.definitions.references.INamespaceReference;
@@ -286,12 +288,23 @@ public abstract class ASScope extends ASScopeBase
     public void reconnectScopeNode(IScopedNode node)
     {
         scopedNodeRef.reconnectNode(node);
+        IWorkspace w = getWorkspace();
+        if (w instanceof Workspace)
+        {
+            CompilerProject[] projects = ((Workspace)w).getProjects();
+            for (CompilerProject project : projects)
+            {
+                project.resetScopeCaches(Collections.singleton(this));
+            }
+        }
     }
 
     @Override
     public IScopedNode getScopeNode()
     {
         IWorkspace w = getWorkspace();
+        if (w == null)
+            return null;
         return (IScopedNode)scopedNodeRef.getNode(w, this);
     }
 
@@ -317,7 +330,7 @@ public abstract class ASScope extends ASScopeBase
      * For debugging only.
      */
     @Override
-    protected String toStringHeader()
+    public String toStringHeader()
     {
         StringBuilder sb = new StringBuilder();
 
@@ -1757,7 +1770,8 @@ public abstract class ASScope extends ASScopeBase
      */
     public IWorkspace getWorkspace()
     {
-        return getFileScope().getWorkspace();
+        ASFileScope fileScope = getFileScope();
+        return fileScope != null ? fileScope.getWorkspace() : null;
     }
 
     public String getContainingSourcePath(String qName, ICompilerProject 
project)
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java
index 59fa5aeba..4b0311761 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java
@@ -23,7 +23,6 @@ import org.apache.royale.compiler.common.DependencyType;
 import org.apache.royale.compiler.config.CompilerDiagnosticsConstants;
 import org.apache.royale.compiler.constants.IASLanguageConstants;
 import org.apache.royale.compiler.definitions.IDefinition;
-import org.apache.royale.compiler.definitions.IFunctionDefinition;
 import org.apache.royale.compiler.definitions.IInterfaceDefinition;
 import org.apache.royale.compiler.definitions.INamespaceDefinition;
 import org.apache.royale.compiler.definitions.ITypeDefinition;
@@ -125,12 +124,9 @@ public class ASScopeCache
 
     /**
      * Version of findProperty that uses a cache. Checks the cache first, and
-     * only queries the scope if the we don't have a cached result.
+     * only queries the scope if we don't have a cached result.
      * 
-     * @param scope The scope to perform the lookup in
      * @param name Name of the property to find
-     * @param cache ASDefinitionCache to use for the lookup - this is only used
-     * to get at the ICompilerProject
      * @param dt Which type of dependency to introduce when we do the lookup
      * @return The IDefinition for the property, or null if it wasn't found
      */
@@ -142,19 +138,24 @@ public class ASScopeCache
         if (result != null)
         {
             // We found a cached result - we're done
-               // after making sure it has a dependency
-               if (result instanceof ITypeDefinition)
-               {
-                       ICompilationUnit from = 
scope.getFileScope().getCompilationUnit();
-                   assert result.isInProject(project);
-                   
-                   String qname = result.getQualifiedName();
-                   ICompilationUnit to = 
((ASProjectScope)project.getScope()).getCompilationUnitForDefinition(result);
-                   if (to == null && !(qname.contentEquals("void") || 
qname.contentEquals("*")))
-                       System.err.println("No compilation unit for " + qname); 
-                   if (to != null)
-                       project.addDependency(from, to, dt, qname);
-               }
+            // after making sure it has a dependency
+            if (result instanceof ITypeDefinition)
+            {
+                ASFileScope fileScope = scope.getFileScope();
+                //if it is null, it might be partially detached or in the 
process of reconnection
+                if (fileScope != null)
+                {
+                    ICompilationUnit from = fileScope.getCompilationUnit();
+                    assert result.isInProject(project);
+
+                    String qname = result.getQualifiedName();
+                    ICompilationUnit to = 
((ASProjectScope)project.getScope()).getCompilationUnitForDefinition(result);
+                    if (to == null && !(qname.contentEquals("void") || 
qname.contentEquals("*")))
+                        System.err.println("No compilation unit for " + qname);
+                    if (to != null)
+                        project.addDependency(from, to, dt, qname);
+                }
+            }
             return result;
         }
 
@@ -180,7 +181,7 @@ public class ASScopeCache
                 assert def.isInProject(project);
                 break;
             default:
-               wasAmbiguous = true;
+                wasAmbiguous = true;
                 IDefinition d = 
AmbiguousDefinition.resolveAmbiguities(project, defs, favorTypes);
                 if (d != null)
                     def = d;
@@ -189,7 +190,9 @@ public class ASScopeCache
                     {
                         def = project.doubleCheckAmbiguousDefinition(scope, 
name, defs.get(0), defs.get(1));
                         if (def != null)
+                        {
                             return def;
+                        }
                     }
                     def = AmbiguousDefinition.get();
                 }
@@ -215,7 +218,6 @@ public class ASScopeCache
             }
         }
         return result;
-
     }
 
     private ConcurrentMap<String, IDefinition> getScopeChainMap()
@@ -266,12 +268,10 @@ public class ASScopeCache
 
     /**
      * Version of findPropertyQualified that uses a cache. Checks the cache
-     * first, and only queries the scope if the we don't have a cached result.
+     * first, and only queries the scope if we don't have a cached result.
      * 
-     * @param scope The scope to perform the lookup in
+     * @param qualifier The namespace qualifier
      * @param name Name of the property to find
-     * @param cache ASDefinitionCache to use for the lookup - this is only used
-     * to get at the ICompilerProject
      * @param dt Which type of dependency to introduce when we do the lookup
      * @return The IDefinition for the property, or null if it wasn't found
      */
@@ -360,7 +360,9 @@ public class ASScopeCache
         ConcurrentMap<IResolvedQualifiersReference, IDefinition> cache = 
getMultinameLookupMap();
         IDefinition result = cache.get(ref);
         if (result != null)
+        {
             return result;
+        }
 
         IDefinition def;
 
@@ -728,5 +730,11 @@ public class ASScopeCache
             }
             return false;
         }
+
+        @Override
+        public String toString()
+        {
+            return ns.toString() + "::" + name;
+        }
     }
 }
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java
index df434c321..9ae379395 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java
@@ -39,10 +39,13 @@ import org.apache.royale.abc.semantics.Nsset;
 import org.apache.royale.compiler.common.DependencyType;
 import org.apache.royale.compiler.config.Configuration;
 import org.apache.royale.compiler.constants.IASLanguageConstants;
+import org.apache.royale.compiler.definitions.IAccessorDefinition;
 import org.apache.royale.compiler.definitions.IClassDefinition;
 import org.apache.royale.compiler.definitions.IDefinition;
+import org.apache.royale.compiler.definitions.IFunctionDefinition;
 import org.apache.royale.compiler.definitions.INamespaceDefinition;
 import org.apache.royale.compiler.definitions.ITypeDefinition;
+import org.apache.royale.compiler.definitions.IVariableDefinition;
 import org.apache.royale.compiler.definitions.IQualifiers;
 import 
org.apache.royale.compiler.definitions.IVariableDefinition.VariableClassification;
 import org.apache.royale.compiler.definitions.references.INamespaceReference;
@@ -58,6 +61,8 @@ import 
org.apache.royale.compiler.internal.definitions.NamespaceDefinition;
 import org.apache.royale.compiler.internal.definitions.VariableDefinition;
 import org.apache.royale.compiler.internal.projects.RoyaleProject;
 import org.apache.royale.compiler.internal.scopes.ASScope;
+import org.apache.royale.compiler.internal.scopes.ASScopeBase;
+import org.apache.royale.compiler.internal.scopes.FunctionScope;
 import org.apache.royale.compiler.internal.semantics.PostProcessStep;
 import org.apache.royale.compiler.internal.semantics.SemanticUtils;
 import 
org.apache.royale.compiler.internal.tree.as.metadata.DefaultPropertyTagNode;
@@ -315,9 +320,17 @@ public class IdentifierNode extends ExpressionNodeBase 
implements IIdentifierNod
     @Override
     public IDefinition resolve(ICompilerProject project)
     {
-       if (DefinitionBase.getPerformanceCachingEnabled() && idDef != null)
-               return idDef;
-       
+        IDefinition currentIdDef = idDef;
+        if (DefinitionBase.getPerformanceCachingEnabled() && currentIdDef != 
null)
+        {
+            return currentIdDef;
+        }
+
+        return resolveInternal(project);
+    }
+
+    private IDefinition resolveInternal(ICompilerProject project)
+    {
         ASScope asScope = getASScope();
 
         if (asScope == null)
@@ -451,7 +464,11 @@ public class IdentifierNode extends ExpressionNodeBase 
implements IIdentifierNod
                        ((RoyaleProject)project).addToAPIReport(result);
         }
         
-        idDef = result;
+        if (DefinitionBase.getPerformanceCachingEnabled())
+        {
+            idDef = result;
+        }
+        
         return result;
     }
 
diff --git 
a/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java
 
b/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java
index fa6f4a7ef..283c80cd4 100644
--- 
a/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java
+++ 
b/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java
@@ -227,9 +227,12 @@ public final class Workspace implements IWorkspace
         return executorService;
     }
 
-    private CompilerProject[] getProjects()
+    public CompilerProject[] getProjects()
     {
-        return projects.keySet().toArray(new CompilerProject[0]);
+        synchronized (projects)
+        {
+            return projects.keySet().toArray(new CompilerProject[0]);
+        }
     }
     
     @Override
@@ -1024,7 +1027,10 @@ public final class Workspace implements IWorkspace
      */
     public void deleteProject(ICompilerProject compilerProject)
     {
-        projects.remove(compilerProject);
+        synchronized (projects)
+        {
+            projects.remove(compilerProject);
+        }
     }
 
     @Override
@@ -1237,8 +1243,11 @@ public final class Workspace implements IWorkspace
     
     public void addProject(CompilerProject project)
     {
-        // Need to give a non-null value, the class object for Object
-        // is a good a non-value as anything.
-        projects.put(project, Object.class);
+        synchronized (projects)
+        {
+            // Need to give a non-null value, the class object for Object
+            // is a good a non-value as anything.
+            projects.put(project, Object.class);
+        }
     }
 }

Reply via email to