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

jdaugherty pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/grails-intellij-plugin.git


The following commit(s) were added to refs/heads/main by this push:
     new a9d13dc  Fix expensive method calls from being invoked
a9d13dc is described below

commit a9d13dc98914c3462376d1eaf9f5bc465852e1a8
Author: James Daugherty <[email protected]>
AuthorDate: Thu Jul 30 11:54:15 2026 -0400

    Fix expensive method calls from being invoked
---
 ...ilsWebApplicationContextMembersContributor.java | 57 +++++++++++--------
 .../plugin/spring/InjectedSpringBeanProvider.java  | 19 ++++---
 .../intellij/plugin/spring/SpringModelAccess.java  | 52 ++++++++++++++++++
 .../spring/GrailsSpringIntegrationTest.java        | 64 ++++++++++++++++++++++
 4 files changed, 162 insertions(+), 30 deletions(-)

diff --git 
a/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/GrailsWebApplicationContextMembersContributor.java
 
b/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/GrailsWebApplicationContextMembersContributor.java
index bd2dc3c..a172d43 100644
--- 
a/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/GrailsWebApplicationContextMembersContributor.java
+++ 
b/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/GrailsWebApplicationContextMembersContributor.java
@@ -36,6 +36,9 @@ import 
org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrLightVariable;
 import org.jetbrains.plugins.groovy.lang.resolve.NonCodeMembersContributor;
 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
 
+import java.util.ArrayList;
+import java.util.List;
+
 final class GrailsWebApplicationContextMembersContributor extends 
NonCodeMembersContributor {
   @Override
   public String getParentClassName() {
@@ -54,33 +57,43 @@ final class GrailsWebApplicationContextMembersContributor 
extends NonCodeMembers
     if (structure == null) return;
 
     Module module = structure.getModule();
-
-    final SpringModel model = 
SpringManager.getInstance(module.getProject()).getCombinedModel(module);
-
     PsiManager manager = structure.getManager();
-
     String nameHint = ResolveUtil.getNameHint(processor);
 
-    if (nameHint == null) {
-      for (SpringBeanPointer<?> pointer : model.getAllCommonBeans()) {
-        if (pointer.isValid()) {
-          PsiType type = 
TypesUtil.getLeastUpperBound(pointer.getEffectiveBeanTypes().toArray(PsiType.EMPTY_ARRAY),
 manager);
-          PsiElement psiElement = pointer.getPsiElement();
-          if (psiElement != null) {
-            if (!processor.execute(new GrLightVariable(manager, 
pointer.getName(), type, psiElement), state)) return;
-          }
-        }
-      }
+    // The beans are collected up front so that the whole Spring model lookup 
stays inside the single
+    // SpringModelAccess scope, and the processor runs outside it.
+    for (GrLightVariable bean : SpringModelAccess.compute(() -> 
collectBeans(module, manager, nameHint))) {
+      if (!processor.execute(bean, state)) return;
     }
-    else {
+  }
+
+  private static List<GrLightVariable> collectBeans(@NotNull Module module, 
@NotNull PsiManager manager, @Nullable String nameHint) {
+    final SpringModel model = 
SpringManager.getInstance(module.getProject()).getCombinedModel(module);
+
+    if (nameHint != null) {
       SpringBeanPointer<?>  bean = SpringModelSearchers.findBean(model, 
nameHint);
-      if (bean != null && bean.isValid()) {
-        PsiType type = 
TypesUtil.getLeastUpperBound(bean.getEffectiveBeanTypes().toArray(PsiType.EMPTY_ARRAY),
 manager);
-        PsiElement psiElement = bean.getPsiElement();
-        if (psiElement != null) {
-          if (!processor.execute(new GrLightVariable(manager, nameHint, type, 
psiElement), state)) return;
-        }
-      }
+      // The bean may have been found by an alias, so the name asked for wins 
over the bean's own name.
+      GrLightVariable variable = bean == null ? null : createVariable(manager, 
nameHint, bean);
+      return variable == null ? List.of() : List.of(variable);
+    }
+
+    List<GrLightVariable> beans = new ArrayList<>();
+    for (SpringBeanPointer<?> pointer : model.getAllCommonBeans()) {
+      GrLightVariable variable = createVariable(manager, pointer.getName(), 
pointer);
+      if (variable != null) beans.add(variable);
     }
+    return beans;
+  }
+
+  private static @Nullable GrLightVariable createVariable(@NotNull PsiManager 
manager,
+                                                          String name,
+                                                          @NotNull 
SpringBeanPointer<?> pointer) {
+    if (!pointer.isValid()) return null;
+
+    PsiElement psiElement = pointer.getPsiElement();
+    if (psiElement == null) return null;
+
+    PsiType type = 
TypesUtil.getLeastUpperBound(pointer.getEffectiveBeanTypes().toArray(PsiType.EMPTY_ARRAY),
 manager);
+    return new GrLightVariable(manager, name, type, psiElement);
   }
 }
diff --git 
a/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/InjectedSpringBeanProvider.java
 
b/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/InjectedSpringBeanProvider.java
index 4f676cf..62959fd 100644
--- 
a/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/InjectedSpringBeanProvider.java
+++ 
b/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/InjectedSpringBeanProvider.java
@@ -53,16 +53,18 @@ public final class InjectedSpringBeanProvider extends 
GrVariableEnhancer {
     PsiClass aClass = ((GrField)variable).getContainingClass();
     if (aClass == null || !isSupportInjection(aClass)) return null;
 
-    final CommonSpringModel model = 
SpringModelUtils.getInstance().getSpringModel(aClass);
+    return SpringModelAccess.compute(() -> {
+      final CommonSpringModel model = 
SpringModelUtils.getInstance().getSpringModel(aClass);
 
-    final SpringBeanPointer<?>  springBean = 
SpringModelSearchers.findBean(model, variable.getName());
-    if (springBean == null) return null;
+      final SpringBeanPointer<?>  springBean = 
SpringModelSearchers.findBean(model, variable.getName());
+      if (springBean == null) return null;
 
-    if (declaredType != null) {
-      if (!beanCanBeAssignedTo(springBean, declaredType)) return null;
-    }
+      if (declaredType != null) {
+        if (!beanCanBeAssignedTo(springBean, declaredType)) return null;
+      }
 
-    return springBean;
+      return springBean;
+    });
   }
 
   private static boolean beanCanBeAssignedTo(SpringBeanPointer<?> springBean, 
PsiType variableType) {
@@ -85,7 +87,8 @@ public final class InjectedSpringBeanProvider extends 
GrVariableEnhancer {
     SpringBeanPointer<?>  bean = getInjectedBean(variable);
 
     if (bean != null) {
-      return 
TypesUtil.getLeastUpperBound(bean.getEffectiveBeanTypes().toArray(PsiType.EMPTY_ARRAY),
 variable.getManager());
+      return SpringModelAccess.compute(
+        () -> 
TypesUtil.getLeastUpperBound(bean.getEffectiveBeanTypes().toArray(PsiType.EMPTY_ARRAY),
 variable.getManager()));
     }
 
     return null;
diff --git 
a/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/SpringModelAccess.java
 
b/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/SpringModelAccess.java
new file mode 100644
index 0000000..7837404
--- /dev/null
+++ 
b/plugin/src/main/java/org/apache/grails/intellij/plugin/spring/SpringModelAccess.java
@@ -0,0 +1,52 @@
+/*
+ * 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
+ *
+ *   https://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.grails.intellij.plugin.spring;
+
+import com.intellij.openapi.application.AccessToken;
+import 
com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.function.Supplier;
+
+final class SpringModelAccess {
+
+  private SpringModelAccess() {
+  }
+
+  /**
+   * Runs a Spring model lookup with the platform's "the expensive method 
should not be called during the references
+   * contributing" assertion suppressed.
+   * <p>
+   * Grails resolves injected beans and {@code applicationContext} members 
through the Spring model, so Groovy
+   * reference resolution depends on it. Reference contributors resolve Groovy 
code themselves — IntelliLang, for
+   * instance, resolves the enclosing call to find out whether a literal 
carries an injected language — which drags
+   * the Spring model into a phase where {@code SpringManager} and {@code 
SpringModelUtils} assert against it.
+   * <p>
+   * Skipping the lookup instead is not an option: the results feed {@code 
CachedValue}s that outlive the pass, so a
+   * degraded type computed here would be served to every later caller.
+   *
+   * @see <a 
href="https://github.com/apache/grails-intellij-plugin/issues/16";>issue 16</a>
+   */
+  static <T> T compute(@NotNull Supplier<T> lookup) {
+    try (AccessToken ignored = 
ReferenceProvidersRegistry.suppressAssertNotContributingReferences()) {
+      return lookup.get();
+    }
+  }
+}
diff --git 
a/plugin/src/test/java/org/apache/grails/intellij/plugin/reference/spring/GrailsSpringIntegrationTest.java
 
b/plugin/src/test/java/org/apache/grails/intellij/plugin/reference/spring/GrailsSpringIntegrationTest.java
index a7a850a..b066358 100644
--- 
a/plugin/src/test/java/org/apache/grails/intellij/plugin/reference/spring/GrailsSpringIntegrationTest.java
+++ 
b/plugin/src/test/java/org/apache/grails/intellij/plugin/reference/spring/GrailsSpringIntegrationTest.java
@@ -21,6 +21,7 @@ import com.intellij.openapi.vfs.JarFileSystem;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
+import com.intellij.psi.util.PsiTreeUtil;
 import com.intellij.spring.SpringApiIcons;
 import com.intellij.spring.facet.SpringFacet;
 import com.intellij.spring.model.utils.SpringCommonUtils;
@@ -30,6 +31,7 @@ import org.apache.grails.intellij.plugin.fileType.GspFileType;
 import org.jetbrains.plugins.groovy.GroovyLanguage;
 import org.apache.grails.intellij.lib.testFramework.GrailsTestUtil;
 import org.apache.grails.intellij.lib.testFramework.HddGrailsTestCase;
+import 
org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
 
 import java.util.List;
 
@@ -159,6 +161,68 @@ public class GrailsSpringIntegrationTest extends 
HddGrailsTestCase {
     HddGrailsTestCase.checkResolve(testFile, "yyy");
   }
 
+  /**
+   * Contributing references to the literal makes IntelliLang resolve the 
enclosing call to look for an injected
+   * language, which in turn asks for the type of the {@code myService} 
argument. That reaches
+   * {@link 
org.apache.grails.intellij.plugin.spring.InjectedSpringBeanProvider}, where the 
platform forbids expensive
+   * computations. See <a 
href="https://github.com/apache/grails-intellij-plugin/issues/16";>issue 16</a>.
+   */
+  public void testInjectedBeanTypeDuringReferenceContribution() {
+    addLanguageAnnotatedClass();
+    myFixture.addFileToProject("grails-app/services/MyService.groovy", "class 
MyService { def xxx() {} }");
+    PsiFile controller = addController("""
+                                         import static example.Queries.execute
+                                         class CccController {
+                                           def myService
+                                           def index() {
+                                             execute('select 1', myService)
+                                             myService.xxx()
+                                           }
+                                         }
+                                         """);
+
+    contributeReferencesToFirstLiteral(controller);
+
+    // The bean type must still be inferred: suppressing the assertion may not 
degrade to an unknown type,
+    // because whatever is computed here is cached for the rest of the session.
+    HddGrailsTestCase.checkResolve(controller);
+  }
+
+  /** The same as {@link #testInjectedBeanTypeDuringReferenceContribution}, 
reaching the Spring model through the
+   *  {@code applicationContext} member contributor instead of the field type 
enhancer. */
+  public void testApplicationContextBeanDuringReferenceContribution() {
+    addLanguageAnnotatedClass();
+    PsiFile controller = addController("""
+                                         import static example.Queries.execute
+                                         class CccController {
+                                           
org.springframework.context.ApplicationContext ctx
+                                           def index() {
+                                             execute('select 1', 
ctx.pluginManager)
+                                           }
+                                         }
+                                         """);
+
+    contributeReferencesToFirstLiteral(controller);
+
+    HddGrailsTestCase.checkResolve(controller);
+  }
+
+  private void addLanguageAnnotatedClass() {
+    myFixture.addClass("""
+                         package example;
+                         import org.intellij.lang.annotations.Language;
+                         public class Queries {
+                           public static void execute(@Language("SQL") String 
query, Object parameter) {}
+                         }
+                         """);
+  }
+
+  private static void contributeReferencesToFirstLiteral(PsiFile file) {
+    GrLiteral literal = PsiTreeUtil.findChildOfType(file, GrLiteral.class);
+    assertNotNull(literal);
+    literal.getReferences();
+  }
+
   public void testSpringInjectionAnnotator1() {
     PsiFile controller = 
myFixture.addFileToProject("grails-app/controllers/CccController.groovy", """
       class CccController {

Reply via email to