PY-81981 Sync pushed language level

GitOrigin-RevId: d60893c0631846bf411819c5d2687e411a0a578c
This commit is contained in:
Petr
2025-07-08 18:49:52 +02:00
committed by intellij-monorepo-bot
parent 35ce97e5ad
commit 1399a0c594
18 changed files with 180 additions and 68 deletions

View File

@@ -63,6 +63,8 @@
<projectOpenProcessor implementation="com.jetbrains.python.projectModel.uv.UvProjectOpenProcessor"/>
<externalSystemUnlinkedProjectAware implementation="com.jetbrains.python.projectModel.uv.UvUnlinkedProjectAware"/>
<postStartupActivity implementation="com.jetbrains.python.projectModel.uv.UvProjectAware$UvSyncStartupActivity"/>
<virtualFileCustomDataProvider implementation="com.jetbrains.python.psi.PyLangLevelVirtualFileCustomDataProvider"/>
</extensions>
<projectListeners>

View File

@@ -0,0 +1,38 @@
package com.jetbrains.python
import com.intellij.injected.editor.VirtualFileWindow
import com.intellij.notebook.editor.BackedVirtualFile
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.jetbrains.python.ast.PyAstFile
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
abstract class PyLanguageFacadeBase : PyLanguageFacade() {
final override fun getEffectiveLanguageLevel(psiElement: PsiElement): LanguageLevel {
if (psiElement is PsiDirectory) {
return getEffectiveLanguageLevel(psiElement.getProject(), psiElement.getVirtualFile())
}
val containingFile = psiElement.getContainingFile()
if (containingFile is PyAstFile) {
return containingFile.languageLevel
}
return LanguageLevel.getDefault()
}
final override fun getEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel {
var file = virtualFile
if (file is VirtualFileWindow) {
file = file.getDelegate()
}
file = BackedVirtualFile.getOriginFileIfBacked(file)
return doGetEffectiveLanguageLevel(project, file)
}
protected abstract fun doGetEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel
}

View File

@@ -18,6 +18,7 @@ package com.jetbrains.python.ast;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyLanguageFacadeKt;
import com.jetbrains.python.ast.controlFlow.AstScopeOwner;
import com.jetbrains.python.ast.docstring.DocStringUtilCore;
import com.jetbrains.python.ast.impl.PyPsiUtilsCore;
@@ -43,7 +44,9 @@ public interface PyAstFile extends PyAstElement, PsiFile, PyAstDocStringOwner, A
return stmts;
}
LanguageLevel getLanguageLevel();
default LanguageLevel getLanguageLevel() {
return PyLanguageFacadeKt.getEffectiveLanguageLevel(this);
}
/**
* Return true if the file contains a 'from __future__ import ...' statement with given feature.

View File

@@ -2,19 +2,18 @@ package com.jetbrains.python.psi;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.impl.PsiFileFactoryImpl;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyLanguageFacade;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.ast.*;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
@@ -103,7 +102,7 @@ public class PyAstElementGenerator {
final PsiFileFactory factory = PsiFileFactory.getInstance(myProject);
final String name = getDummyFileName();
final LightVirtualFile virtualFile = new LightVirtualFile(name, PythonFileType.INSTANCE, contents);
specifyFileLanguageLevel(virtualFile, langLevel);
PyLanguageFacade.getINSTANCE().setEffectiveLanguageLevel(virtualFile, langLevel);
final PsiFile psiFile = ((PsiFileFactoryImpl)factory).trySetupPsiForFile(virtualFile, PythonLanguage.getInstance(), physical, true);
assert psiFile != null;
return psiFile;
@@ -127,8 +126,6 @@ public class PyAstElementGenerator {
return (PyAstPassStatement)statementList.getStatements()[0];
}
protected void specifyFileLanguageLevel(@NotNull VirtualFile virtualFile, @Nullable LanguageLevel langLevel) { }
public @NotNull PyAstExpression createExpressionFromText(@NotNull LanguageLevel languageLevel, @NotNull String text)
throws IncorrectOperationException {
final PsiFile dummyFile = createDummyFile(languageLevel, text);

View File

@@ -2,14 +2,32 @@ package com.jetbrains.python
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.jetbrains.python.psi.LanguageLevel
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
abstract class PyLanguageFacade {
abstract fun forLanguage(psiElement: PsiElement): LanguageLevel
abstract fun getEffectiveLanguageLevel(psiElement: PsiElement): LanguageLevel
abstract fun getEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel
abstract fun setEffectiveLanguageLevel(virtualFile: VirtualFile, languageLevel: LanguageLevel?)
companion object {
@JvmStatic
val INSTANCE: PyLanguageFacade
get() = ApplicationManager.getApplication().service<PyLanguageFacade>()
get() = ApplicationManager.getApplication().service<PyLanguageFacade>()
}
}
@ApiStatus.Internal
fun getEffectiveLanguageLevel(file: PsiFile): LanguageLevel {
var curFile = file
while (curFile != curFile.getOriginalFile()) {
curFile = curFile.getOriginalFile()
}
val virtualFile = curFile.getVirtualFile() ?: curFile.getViewProvider().getVirtualFile()
return PyLanguageFacade.INSTANCE.getEffectiveLanguageLevel(curFile.project, virtualFile)
}

View File

@@ -212,7 +212,7 @@ public enum LanguageLevel {
}
public static @NotNull LanguageLevel forElement(@NotNull PsiElement element) {
return PyLanguageFacade.Companion.getINSTANCE().forLanguage(element);
return PyLanguageFacade.getINSTANCE().getEffectiveLanguageLevel(element);
}
public static @NotNull LanguageLevel getLatest() {

View File

@@ -1,22 +1,16 @@
package com.jetbrains.python
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiElement
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.python.psi.LanguageLevel
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher
class PyLanguageFacadeImpl : PyLanguageFacade() {
override fun forLanguage(psiElement: PsiElement): LanguageLevel {
if (psiElement is PsiDirectory) {
return PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(psiElement.getProject(), psiElement.getVirtualFile())
}
class PyLanguageFacadeImpl : PyLanguageFacadeBase() {
override fun doGetEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel {
return PythonLanguageLevelPusher.getEffectiveLanguageLevel(project, virtualFile)
}
val containingFile = psiElement.getContainingFile()
if (containingFile is PyFile) {
return containingFile.getLanguageLevel()
}
return LanguageLevel.getDefault()
override fun setEffectiveLanguageLevel(virtualFile: VirtualFile, languageLevel: LanguageLevel?) {
PythonLanguageLevelPusher.specifyFileLanguageLevel(virtualFile, languageLevel)
}
}

View File

@@ -36,11 +36,6 @@ public final class PyElementGeneratorImpl extends PyElementGenerator {
super(project);
}
@Override
protected void specifyFileLanguageLevel(@NotNull VirtualFile virtualFile, @Nullable LanguageLevel langLevel) {
PythonLanguageLevelPusher.specifyFileLanguageLevel(virtualFile, langLevel);
}
@Override
public ASTNode createNameIdentifier(String name, LanguageLevel languageLevel) {
final PsiFile dummyFile = createDummyFile(languageLevel, name);

View File

@@ -18,7 +18,6 @@ import com.intellij.psi.scope.DelegatingScopeProcessor;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
@@ -27,7 +26,6 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.ast.PyAstElementVisitor;
import com.jetbrains.python.ast.impl.PyUtilCore;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.psi.*;
@@ -247,11 +245,6 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
return null;
}
@Override
public LanguageLevel getLanguageLevel() {
return PythonLanguageLevelPusher.getLanguageLevelForFile(this);
}
@Override
public Icon getIcon(int flags) {
return PythonFileType.INSTANCE.getIcon();

View File

@@ -2,8 +2,6 @@
package com.jetbrains.python.psi.impl;
import com.google.common.collect.Maps;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.notebook.editor.BackedVirtualFile;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
@@ -31,9 +29,7 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.TreeNodeProcessingResult;
import com.intellij.util.indexing.IndexingBundle;
import com.intellij.util.messages.SimpleMessageBusConnection;
import com.jetbrains.python.PythonCodeStyleService;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonRuntimeService;
import com.jetbrains.python.*;
import com.jetbrains.python.codeInsight.typing.PyTypeShed;
import com.jetbrains.python.module.PyModuleService;
import com.jetbrains.python.psi.LanguageLevel;
@@ -159,6 +155,15 @@ public final class PythonLanguageLevelPusher implements FilePropertyPusher<Langu
if (fileOrDir.isDirectory() || isPythonFile(fileOrDir)) {
clearSdkPathCache(fileOrDir);
}
VirtualFilePyLanguageLevelListener publisher = project.getMessageBus().syncPublisher(VirtualFilePyLanguageLevelListener.TOPIC);
publisher.levelChanged(fileOrDir, level);
for (VirtualFile child : fileOrDir.getChildren()) {
if (!child.isDirectory() && isPythonFile(child)) {
publisher.levelChanged(child, level);
}
}
}
private static boolean isPythonFile(VirtualFile child) {
@@ -288,14 +293,7 @@ public final class PythonLanguageLevelPusher implements FilePropertyPusher<Langu
@ApiStatus.Experimental
public static @NotNull LanguageLevel getLanguageLevelForFile(@NotNull PsiFile file) {
while (file != file.getOriginalFile()) {
file = file.getOriginalFile();
}
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null) {
virtualFile = file.getViewProvider().getVirtualFile();
}
return getLanguageLevelForVirtualFile(file.getProject(), virtualFile);
return PyLanguageFacadeKt.getEffectiveLanguageLevel(file);
}
/**
@@ -304,11 +302,11 @@ public final class PythonLanguageLevelPusher implements FilePropertyPusher<Langu
* @see LanguageLevel#forElement
*/
public static @NotNull LanguageLevel getLanguageLevelForVirtualFile(@NotNull Project project, @NotNull VirtualFile virtualFile) {
if (virtualFile instanceof VirtualFileWindow) {
virtualFile = ((VirtualFileWindow)virtualFile).getDelegate();
}
virtualFile = BackedVirtualFile.getOriginFileIfBacked(virtualFile);
return PyLanguageFacade.getINSTANCE().getEffectiveLanguageLevel(project, virtualFile);
}
// Use `PyLanguageFacade.getEffectiveLanguageLevel(Project, VirtualFile)`
public static @NotNull LanguageLevel getEffectiveLanguageLevel(@NotNull Project project, @NotNull VirtualFile virtualFile) {
final LanguageLevel forced = LanguageLevel.FORCE_LANGUAGE_LEVEL;
if (ApplicationManager.getApplication().isUnitTestMode() && forced != null) return forced;

View File

@@ -0,0 +1,15 @@
package com.jetbrains.python.psi.impl
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.messages.Topic
import com.jetbrains.python.psi.LanguageLevel
interface VirtualFilePyLanguageLevelListener {
companion object {
@JvmField
@Topic.ProjectLevel
val TOPIC: Topic<VirtualFilePyLanguageLevelListener> = Topic(VirtualFilePyLanguageLevelListener::class.java)
}
fun levelChanged(virtualFile: VirtualFile, newLevel: LanguageLevel)
}

View File

@@ -28,6 +28,7 @@ jvm_library(
"//platform/code-style-impl:codeStyle-impl",
"@lib//:fastutil-min",
"@lib//:guava",
"@lib//:kotlinx-serialization-core",
],
runtime_deps = [":syntax_resources"]
)

View File

@@ -1,5 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all -opt-in=com.intellij.openapi.util.IntellijInternalApi" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="17" />
<stringArg name="apiVersion" arg="2.2" />
<stringArg name="languageVersion" arg="2.2" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>$KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar</args>
</arrayArg>
<arrayArg name="pluginOptions" />
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@@ -23,5 +45,6 @@
<orderEntry type="module" module-name="intellij.platform.codeStyle.impl" />
<orderEntry type="library" name="fastutil-min" level="project" />
<orderEntry type="library" name="Guava" level="project" />
<orderEntry type="library" name="kotlinx-serialization-core" level="project" />
</component>
</module>

View File

@@ -6,6 +6,7 @@ import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.FactoryMap;
import com.jetbrains.python.PyLanguageFacade;
import com.jetbrains.python.lexer.PythonHighlightingLexer;
import com.jetbrains.python.parsing.console.PyConsoleHighlightingLexer;
import com.jetbrains.python.psi.LanguageLevel;
@@ -48,8 +49,10 @@ public class PySyntaxHighlighterFactoryBase extends SyntaxHighlighterFactory {
return myMap.get(level);
}
protected LanguageLevel getLanguageLevel(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
return LanguageLevel.getDefault();
private static @NotNull LanguageLevel getLanguageLevel(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
return project != null && virtualFile != null ?
PyLanguageFacade.getINSTANCE().getEffectiveLanguageLevel(project, virtualFile) :
LanguageLevel.getDefault();
}
protected boolean useConsoleLexer(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {

View File

@@ -0,0 +1,6 @@
package com.jetbrains.python.psi
import kotlinx.serialization.Serializable
@Serializable
class LanguageLevelHolder(val languageLevel: LanguageLevel)

View File

@@ -8,20 +8,10 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.jetbrains.python.console.PydevConsoleRunnerUtil;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class PySyntaxHighlighterFactory extends PySyntaxHighlighterFactoryBase {
@Override
protected @NotNull LanguageLevel getLanguageLevel(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
return project != null && virtualFile != null ?
PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(project, virtualFile) :
LanguageLevel.getDefault();
}
@Override
protected boolean useConsoleLexer(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
if (virtualFile == null || project == null || virtualFile instanceof VirtualFileWindow) {

View File

@@ -0,0 +1,37 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi
import com.intellij.openapi.application.readAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileCustomDataProvider
import com.intellij.util.messages.impl.subscribeAsFlow
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher
import com.jetbrains.python.psi.impl.VirtualFilePyLanguageLevelListener
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlin.reflect.KType
import kotlin.reflect.typeOf
class PyLangLevelVirtualFileCustomDataProvider : VirtualFileCustomDataProvider<LanguageLevelHolder> {
override val id: String = "virtualFilePyLangLevel"
override val dataType: KType
get() = typeOf<LanguageLevelHolder>()
override fun getValues(project: Project, virtualFile: VirtualFile): Flow<LanguageLevelHolder> {
return project.messageBus.subscribeAsFlow(VirtualFilePyLanguageLevelListener.TOPIC) {
val level = readAction { PythonLanguageLevelPusher.getEffectiveLanguageLevel(project, virtualFile) }
trySend(level)
val vFile = virtualFile
object : VirtualFilePyLanguageLevelListener {
override fun levelChanged(virtualFile: VirtualFile, newLevel: LanguageLevel) {
if (vFile == virtualFile) {
trySend(newLevel)
}
}
}
}.map { LanguageLevelHolder(it) }
}
}

View File

@@ -4,9 +4,7 @@ package com.jetbrains.python.console
import com.intellij.lang.LanguageASTFactory
import com.intellij.openapi.application.PathManager
import com.intellij.testFramework.ParsingTestCase
import com.jetbrains.python.PythonDialectsTokenSetContributor
import com.jetbrains.python.PythonLanguage
import com.jetbrains.python.PythonTokenSetContributor
import com.jetbrains.python.*
import com.jetbrains.python.psi.impl.PythonASTFactory
/**
@@ -24,6 +22,7 @@ class IPythonConsoleParsingTest : ParsingTestCase(
override fun setUp() {
super.setUp()
application.registerService(PyLanguageFacade::class.java, PyLanguageFacadeImpl())
registerExtensionPoint(PythonDialectsTokenSetContributor.EP_NAME, PythonDialectsTokenSetContributor::class.java)
registerExtension(PythonDialectsTokenSetContributor.EP_NAME, PythonTokenSetContributor())
addExplicitExtension(LanguageASTFactory.INSTANCE, PythonLanguage.getInstance(), PythonASTFactory())