diff --git a/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties b/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties
index e39be4743567..0b359f9857ed 100644
--- a/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties
+++ b/java/java-frontback-psi-api/resources/messages/JavaPsiBundle.properties
@@ -123,6 +123,7 @@ feature.foreign.functions=Foreign Function & Memory API
feature.virtual.threads=Virtual Threads
feature.statements.before.super=Statements before super()
feature.module.import.declarations=Module Import Declarations
+feature.package.import.shadow.module.import=Import-on-demand over module import
else.without.if='else' without 'if'
enum.constant.context=Enum constant ''{0}'' in ''{1}''
diff --git a/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java b/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java
index 70d812faee35..58262f7afa77 100644
--- a/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java
+++ b/java/java-frontback-psi-api/src/com/intellij/pom/java/JavaFeature.java
@@ -117,7 +117,9 @@ public enum JavaFeature {
IMPLICIT_IMPORT_IN_IMPLICIT_CLASSES(LanguageLevel.JDK_23_PREVIEW, "feature.implicit.import.in.implicit.classes"),
PRIMITIVE_TYPES_IN_PATTERNS(LanguageLevel.JDK_23_PREVIEW, "feature.primitive.types.in.patterns"),
+ //see together with PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS
MODULE_IMPORT_DECLARATIONS(LanguageLevel.JDK_23_PREVIEW, "feature.module.import.declarations"),
+ PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS(LanguageLevel.JDK_24_PREVIEW, "feature.package.import.shadow.module.import"),
;
private final @NotNull LanguageLevel myLevel;
diff --git a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java
index d56ab54cc214..93f218f3172d 100644
--- a/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java
+++ b/java/java-psi-impl/src/com/intellij/psi/impl/source/resolve/ClassResolverProcessor.java
@@ -5,6 +5,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiAnonymousClassImpl;
import com.intellij.psi.infos.CandidateInfo;
@@ -14,9 +15,11 @@ import com.intellij.psi.scope.JavaScopeProcessorEvent;
import com.intellij.psi.scope.NameHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import java.util.List;
@@ -171,9 +174,25 @@ public class ClassResolverProcessor implements PsiScopeProcessor, NameHint, Elem
return Domination.DOMINATES;
}
+ // on-demand wins over module import
+ if (PsiUtil.isAvailable(JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS, myPlace)) {
+ boolean myIsModule = isImportedByModule(myCurrentFileContext);
+ boolean otherIsModule = isImportedByModule(info.getCurrentFileResolveScope());
+ if (myIsModule && otherOnDemand && !otherIsModule) {
+ return Domination.DOMINATED_BY;
+ }
+ if (myOnDemand && !myIsModule && otherIsModule) {
+ return Domination.DOMINATES;
+ }
+ }
+
return Domination.EQUAL;
}
+ private static boolean isImportedByModule(@Nullable PsiElement context) {
+ return context instanceof PsiImportModuleStatement;
+ }
+
private boolean isAccessible(PsiClass otherClass) {
if (otherClass.hasModifierProperty(PsiModifier.PRIVATE)) {
final PsiClass containingClass = otherClass.getContainingClass();
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ConflictImplicitModuleImportAndExplicitImport.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ConflictImplicitModuleImportAndExplicitImport.java
new file mode 100644
index 000000000000..fb957b7b0ba0
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ConflictImplicitModuleImportAndExplicitImport.java
@@ -0,0 +1,5 @@
+import module java.sql;
+
+public static void main(String[] args) {
+ Date date = new Date();
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackages.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackages.java
new file mode 100644
index 000000000000..3eacc82d58b5
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackages.java
@@ -0,0 +1,6 @@
+import a.b.*;
+
+List a;
+
+void main() {
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackagesPackagesOverModule.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackagesPackagesOverModule.java
new file mode 100644
index 000000000000..a40f401bd2fd
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithPackagesPackagesOverModule.java
@@ -0,0 +1,6 @@
+import a.b.*;
+
+List a;
+
+void main() {
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSamePackage.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSamePackage.java
new file mode 100644
index 000000000000..92d376ac01a0
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSamePackage.java
@@ -0,0 +1,4 @@
+List a;
+
+void main() {
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSingleImport.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSingleImport.java
new file mode 100644
index 000000000000..9fd6742c12cd
--- /dev/null
+++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/implicitClass/ImplicitWithSingleImport.java
@@ -0,0 +1,6 @@
+import a.b.List;
+
+List a;
+
+void main() {
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/importModule/resolve/AmbiguousModuleImport.java b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImport.java
new file mode 100644
index 000000000000..4d3290c81398
--- /dev/null
+++ b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImport.java
@@ -0,0 +1,5 @@
+import module my.source.moduleB;
+import module my.source.moduleA;
+class AmbiguousModuleImport {
+ Imported module;
+}
diff --git a/java/java-tests/testData/importModule/resolve/AmbiguousModuleImportWithPackageImport.java b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImportWithPackageImport.java
new file mode 100644
index 000000000000..43b8e74ec983
--- /dev/null
+++ b/java/java-tests/testData/importModule/resolve/AmbiguousModuleImportWithPackageImport.java
@@ -0,0 +1,7 @@
+import module my.source.moduleB;
+
+import my.source.moduleA.*;
+
+class AmbiguousModuleImportWithPackageImport {
+ Imported module;
+}
diff --git a/java/java-tests/testData/importModule/resolve/ModuleImportWithDefaultPackageImport.java b/java/java-tests/testData/importModule/resolve/ModuleImportWithDefaultPackageImport.java
new file mode 100644
index 000000000000..e8fa9c5d6a18
--- /dev/null
+++ b/java/java-tests/testData/importModule/resolve/ModuleImportWithDefaultPackageImport.java
@@ -0,0 +1,5 @@
+import module my.source.moduleB;
+
+class ModuleImportWithDefaultPackageImport {
+ String a;
+}
diff --git a/java/java-tests/testData/importModule/resolve/ModuleImportWithPackageImport.java b/java/java-tests/testData/importModule/resolve/ModuleImportWithPackageImport.java
new file mode 100644
index 000000000000..280b4c2a6407
--- /dev/null
+++ b/java/java-tests/testData/importModule/resolve/ModuleImportWithPackageImport.java
@@ -0,0 +1,7 @@
+import module my.source.moduleB;
+
+import my.source.moduleA.*;
+
+class ModuleImportWithPackageImport {
+ Imported module;
+}
diff --git a/java/java-tests/testData/importModule/resolve/ModuleImportWithSingleImport.java b/java/java-tests/testData/importModule/resolve/ModuleImportWithSingleImport.java
new file mode 100644
index 000000000000..3c1cec17608d
--- /dev/null
+++ b/java/java-tests/testData/importModule/resolve/ModuleImportWithSingleImport.java
@@ -0,0 +1,7 @@
+import module my.source.moduleB;
+
+import my.source.moduleA.Imported;
+
+class ModuleImportWithSingleImport {
+ Imported module;
+}
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt
index 638cefc04112..00057ae46f71 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt
+++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ImplicitClassHighlightingTest.kt
@@ -6,9 +6,11 @@ import com.intellij.pom.java.JavaFeature
import com.intellij.pom.java.LanguageLevel
import com.intellij.psi.*
import com.intellij.psi.util.PsiUtil
+import com.intellij.psi.util.parentOfType
import com.intellij.testFramework.IdeaTestUtil
import com.intellij.testFramework.UsefulTestCase
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
+import org.jetbrains.plugins.groovy.intentions.style.inference.resolve
class ImplicitClassHighlightingTest : LightJavaCodeInsightFixtureTestCase() {
override fun getProjectDescriptor() = JAVA_21
@@ -105,6 +107,63 @@ class ImplicitClassHighlightingTest : LightJavaCodeInsightFixtureTestCase() {
})
}
+ fun testImplicitWithPackages() {
+ IdeaTestUtil.withLevel(module, JavaFeature.IMPLICIT_IMPORT_IN_IMPLICIT_CLASSES.minimumLevel, Runnable {
+ myFixture.addClass("""
+ package a.b;
+
+ public final class List {
+ }
+ """.trimIndent())
+ val psiFile = myFixture.configureByFile(getTestName(false) + ".java")
+ myFixture.checkHighlighting()
+ })
+ }
+
+ fun testImplicitWithPackagesPackagesOverModule() {
+ IdeaTestUtil.withLevel(module, JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.minimumLevel, Runnable {
+ myFixture.addClass("""
+ package a.b;
+
+ public final class List {
+ }
+ """.trimIndent())
+ val psiFile = myFixture.configureByFile(getTestName(false) + ".java")
+ myFixture.checkHighlighting()
+ val element = psiFile.findElementAt(myFixture.caretOffset)
+ assertEquals("a.b.List", element?.parentOfType()?.type.resolve()?.qualifiedName)
+ })
+ }
+
+
+ fun testImplicitWithSingleImport() {
+ IdeaTestUtil.withLevel(module, JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.minimumLevel, Runnable {
+ myFixture.addClass("""
+ package a.b;
+
+ public final class List {
+ }
+ """.trimIndent())
+ val psiFile = myFixture.configureByFile(getTestName(false) + ".java")
+ myFixture.checkHighlighting()
+ val element = psiFile.findElementAt(myFixture.caretOffset)
+ assertEquals("a.b.List", element?.parentOfType()?.type.resolve()?.qualifiedName)
+ })
+ }
+
+ fun testImplicitWithSamePackage() {
+ IdeaTestUtil.withLevel(module, JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.minimumLevel, Runnable {
+ myFixture.addClass("""
+ public final class List {
+ }
+ """.trimIndent())
+ val psiFile = myFixture.configureByFile(getTestName(false) + ".java")
+ myFixture.checkHighlighting()
+ val element = psiFile.findElementAt(myFixture.caretOffset)
+ assertEquals("List", element?.parentOfType()?.type.resolve()?.qualifiedName)
+ })
+ }
+
private fun doTest() {
myFixture.configureByFile(getTestName(false) + ".java")
myFixture.checkHighlighting()
diff --git a/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java b/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java
index 1e3c74d5e64f..0ffb518043ca 100644
--- a/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/psi/resolve/ResolveModuleImportTest.java
@@ -9,9 +9,11 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.roots.DependencyScope;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
+import com.intellij.testFramework.IdeaTestUtil;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -298,6 +300,86 @@ public class ResolveModuleImportTest extends LightJava9ModulesCodeInsightFixture
assertEquals("my.source.moduleC.SourceTestC", psiClass.getQualifiedName());
}
+ public void testAmbiguousModuleImport() {
+ IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{
+ prepareAmbiguousModuleTests();
+ myFixture.configureByFile(getTestName(false) + ".java");
+ myFixture.checkHighlighting();
+ });
+ }
+
+ public void testModuleImportWithPackageImport() {
+ IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{
+ prepareAmbiguousModuleTests();
+ myFixture.configureByFile(getTestName(false) + ".java");
+ myFixture.checkHighlighting();
+ PsiClass psiClass = getPsiClass();
+ assertNotNull(psiClass);
+ assertEquals("my.source.moduleA.Imported", psiClass.getQualifiedName());
+ });
+ }
+
+ public void testModuleImportWithDefaultPackageImport() {
+ IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{
+ addCode("module-info.java", """
+ module my.source.moduleB {
+ exports my.source.moduleB;
+ }
+ """, M2);
+ addCode("my/source/moduleB/String.java", """
+ package my.source.moduleB;
+ public class String {}
+ """, M2);
+ myFixture.configureByFile(getTestName(false) + ".java");
+ myFixture.checkHighlighting();
+ PsiClass psiClass = getPsiClass();
+ assertNotNull(psiClass);
+ assertEquals("java.lang.String", psiClass.getQualifiedName());
+ });
+ }
+
+ public void testAmbiguousModuleImportWithPackageImport() {
+ IdeaTestUtil.withLevel(getModule(), JavaFeature.MODULE_IMPORT_DECLARATIONS.getMinimumLevel(), ()->{
+ prepareAmbiguousModuleTests();
+ myFixture.configureByFile(getTestName(false) + ".java");
+ myFixture.checkHighlighting();
+ });
+ }
+
+ public void testModuleImportWithSingleImport() {
+ IdeaTestUtil.withLevel(getModule(), JavaFeature.PACKAGE_IMPORTS_SHADOW_MODULE_IMPORTS.getMinimumLevel(), ()->{
+ prepareAmbiguousModuleTests();
+ myFixture.configureByFile(getTestName(false) + ".java");
+ myFixture.checkHighlighting();
+ PsiClass psiClass = getPsiClass();
+ assertNotNull(psiClass);
+ assertEquals("my.source.moduleA.Imported", psiClass.getQualifiedName());
+ });
+ }
+
+ private void prepareAmbiguousModuleTests() {
+ addCode("module-info.java", """
+ module my.source.moduleB {
+ exports my.source.moduleB;
+ }
+ """, M2);
+ addCode("my/source/moduleB/Imported.java", """
+ package my.source.moduleB;
+ public class Imported {}
+ """, M2);
+ addCode("module-info.java", """
+ module my.source.moduleA {
+ exports my.source.moduleA;
+ }
+ """, M4);
+ addCode("my/source/moduleA/Imported.java", """
+ package my.source.moduleA;
+ public class Imported {}
+ """, M4);
+ addCode("Test.java", """
+ """);
+ }
+
@Nullable
private PsiClass getPsiClass() {
PsiField field = PsiTreeUtil.getParentOfType(myFixture.getFile().findElementAt(myFixture.getCaretOffset()), PsiField.class);