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 {