From d3a3be4cd75ef4f89d1641c340daf24faf0a6aab Mon Sep 17 00:00:00 2001 From: Morgan Bartholomew Date: Thu, 5 Mar 2026 17:59:02 +1000 Subject: [PATCH] PY-78235 `PyAnyType`: option to enable (cherry picked from commit 9774a8d25a879a47ffe2200cc8a68eeca138274e) GitOrigin-RevId: 6d04596d71db1589225f7263615aafdeeebcc425 --- .../jetbrains/python/psi/types/PyAnyType.kt | 21 +++++-- .../resources/intellij.python.psi.impl.xml | 2 + .../typing/PyTypingTypeProvider.kt | 7 ++- .../python/documentation/PyTypeRenderer.java | 7 ++- .../jetbrains/python/psi/types/PyTypeUtil.kt | 35 +++++++++++ .../com/jetbrains/python/Py3TypeTest.java | 2 +- .../com/jetbrains/python/PyTypingTest.java | 59 ++++++++++++++----- 7 files changed, 106 insertions(+), 27 deletions(-) diff --git a/python/python-psi-api/src/com/jetbrains/python/psi/types/PyAnyType.kt b/python/python-psi-api/src/com/jetbrains/python/psi/types/PyAnyType.kt index a8b244c0f0c6..1511dafc46e8 100644 --- a/python/python-psi-api/src/com/jetbrains/python/psi/types/PyAnyType.kt +++ b/python/python-psi-api/src/com/jetbrains/python/psi/types/PyAnyType.kt @@ -1,6 +1,7 @@ // Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.psi.types +import com.intellij.openapi.util.registry.Registry import com.intellij.psi.PsiElement import com.intellij.util.ProcessingContext import com.jetbrains.python.PyNames @@ -16,7 +17,11 @@ import org.jetbrains.annotations.ApiStatus * currently unused */ @ApiStatus.Experimental -class PyAnyType private constructor(override val name: String) : PyType { +sealed class PyAnyType private constructor(override val name: String) : PyType { + + object Any : PyAnyType(PyNames.ANY_TYPE) + object Unknown : PyAnyType(PyNames.UNKNOWN_TYPE) + override fun resolveMember( name: String, location: PyExpression?, @@ -35,12 +40,16 @@ class PyAnyType private constructor(override val name: String) : PyType { override fun assertValid(message: String?) { } - override fun acceptTypeVisitor(visitor: PyTypeVisitor): T? = - if (this === Any) visitor.visitAnyType() - else visitor.visitUnknownType() + override fun acceptTypeVisitor(visitor: PyTypeVisitor): T? = when (this) { + is Any -> visitor.visitAnyType() + is Unknown -> visitor.visitUnknownType() + } companion object { - val Any: PyAnyType = PyAnyType(PyNames.ANY_TYPE) - val Unknown: PyAnyType = PyAnyType(PyNames.UNKNOWN_TYPE) + @JvmStatic + val isEnabled: Boolean get() = Registry.`is`("python.type.any") + + val any: Any? get() = if (isEnabled) Any else null + val unknown: Unknown? get() = if (isEnabled) Unknown else null } } diff --git a/python/python-psi-impl/resources/intellij.python.psi.impl.xml b/python/python-psi-impl/resources/intellij.python.psi.impl.xml index 7eb984ba336f..7e1edd81703c 100644 --- a/python/python-psi-impl/resources/intellij.python.psi.impl.xml +++ b/python/python-psi-impl/resources/intellij.python.psi.impl.xml @@ -570,6 +570,8 @@ description="Enable soft-key caching for TypeEvalContext"/> + diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.kt index 86dbb6247dee..9e390161a89b 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.kt @@ -90,6 +90,7 @@ import com.jetbrains.python.psi.resolve.PyResolveContext import com.jetbrains.python.psi.resolve.PyResolveUtil import com.jetbrains.python.psi.resolve.RatedResolveResult import com.jetbrains.python.psi.stubs.PyModuleNameIndex +import com.jetbrains.python.psi.types.PyAnyType import com.jetbrains.python.psi.types.PyCallableParameterImpl import com.jetbrains.python.psi.types.PyCallableParameterListType import com.jetbrains.python.psi.types.PyCallableParameterListTypeImpl @@ -1393,7 +1394,7 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext() { if (typeEngineType != null) { return typeEngineType } - return null + return PyAnyType.unknown?.let { Ref(it) } } finally { if (resolved is PyClass) { @@ -1625,10 +1626,10 @@ class PyTypingTypeProvider : PyTypeProviderWithCustomContext() { private fun getAnyType(element: PsiElement, context: Context): Ref? { if (ANY == getQualifiedName(element)) { - return Ref() + return Ref(PyAnyType.any) } if (context.typeRepresentationMode && ANY == element.text) { - return Ref() + return Ref(PyAnyType.any) } return null } diff --git a/python/python-psi-impl/src/com/jetbrains/python/documentation/PyTypeRenderer.java b/python/python-psi-impl/src/com/jetbrains/python/documentation/PyTypeRenderer.java index 563a56cd8ec2..700c1f954e1f 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/documentation/PyTypeRenderer.java +++ b/python/python-psi-impl/src/com/jetbrains/python/documentation/PyTypeRenderer.java @@ -19,6 +19,7 @@ import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyQualifiedNameOwner; import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.psi.types.PyAnyType; import com.jetbrains.python.psi.types.PyCallableParameter; import com.jetbrains.python.psi.types.PyCallableParameterListType; import com.jetbrains.python.psi.types.PyCallableType; @@ -469,12 +470,14 @@ public abstract class PyTypeRenderer extends PyTypeVisitorExt<@NotNull HtmlChunk @Override public HtmlChunk visitAnyType() { - return HtmlChunk.raw(isRenderingFqn() ? PyTypingTypeProvider.ANY : PyNames.ANY_TYPE); //NON-NLS + return HtmlChunk.raw(isRenderingFqn() ? PyTypingTypeProvider.ANY : PyNames.ANY_TYPE); } @Override public HtmlChunk visitUnknownType() { - // TODO: show "Unknown" instead of "typing.Any" when we convert to it + if (PyAnyType.isEnabled()) { + return HtmlChunk.raw(PyNames.UNKNOWN_TYPE); + } return visitAnyType(); } diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt index 1c253f742183..578bbde22816 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/types/PyTypeUtil.kt @@ -32,6 +32,8 @@ import org.jetbrains.annotations.UnmodifiableView import java.util.Collections import java.util.stream.Collector import java.util.stream.Collectors +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract /** * Tools and wrappers around [PyType] inheritors @@ -326,3 +328,36 @@ object PyTypeUtil { return Collections.unmodifiableSet(result) } } + +@OptIn(ExperimentalContracts::class) +val PyType?.isAnyOrUnknown: Boolean + get() { + contract { + returns(true) implies (this@isAnyOrUnknown is PyAnyType?) + returns(false) implies (this@isAnyOrUnknown is PyType) + } + + return if (PyAnyType.isEnabled) this is PyAnyType else this == null + } + +@OptIn(ExperimentalContracts::class) +val PyType?.isAny: Boolean + get() { + contract { + returns(true) implies (this@isAny is PyAnyType.Any?) + returns(false) implies (this@isAny is PyType) + } + + return if (PyAnyType.isEnabled) this is PyAnyType.Any else this == null + } + +@OptIn(ExperimentalContracts::class) +val PyType?.isUnknown: Boolean + get() { + contract { + returns(true) implies (this@isUnknown is PyAnyType.Unknown?) + returns(false) implies (this@isUnknown is PyType) + } + + return if (PyAnyType.isEnabled) this is PyAnyType.Unknown else this == null +} diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index 7b0ef342015b..161a3648e8dc 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python; import com.intellij.idea.TestFor; diff --git a/python/testSrc/com/jetbrains/python/PyTypingTest.java b/python/testSrc/com/jetbrains/python/PyTypingTest.java index 1ca3c51bf672..b9b6981e495a 100644 --- a/python/testSrc/com/jetbrains/python/PyTypingTest.java +++ b/python/testSrc/com/jetbrains/python/PyTypingTest.java @@ -1,23 +1,11 @@ -/* - * Copyright 2000-2018 JetBrains s.r.o. - * - * Licensed 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 - * - * http://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. - */ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python; +import com.intellij.idea.TestFor; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.registry.Registry; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.psi.util.PsiTreeUtil; @@ -6940,6 +6928,35 @@ public class PyTypingTest extends PyTestCase { """); } + public void testSimpleUnknown() { + withNewAnyTypeEnabled(() -> { + doTest("Unknown", "expr = asdf"); + }); + } + + public void testPlainAny() { + withNewAnyTypeEnabled(() -> { + doTest("Any", """ + from typing import Any + + expr: Any + """); + }); + } + + @TestFor(issues = "PY-84430") + public void testQuotedAny() { + fixme("quoted Any", AssertionError.class, "Failed in code analysis context expected:<[Any]> but was:<[Literal[0]]>", () -> + doTest("Any", """ + from typing import Any + + any: "Any" = 1 + + expr = any.imag + """) + ); + } + private void doTestNoInjectedText(@NotNull String text) { myFixture.configureByText(PythonFileType.INSTANCE, text); final InjectedLanguageManager languageManager = InjectedLanguageManager.getInstance(myFixture.getProject()); @@ -6990,4 +7007,16 @@ public class PyTypingTest extends PyTestCase { final TypeEvalContext userInitiated = TypeEvalContext.userInitiated(expr.getProject(), expr.getContainingFile()).withTracing(); assertType("Failed in user initiated context", expectedType, expr, userInitiated); } + + private static void withNewAnyTypeEnabled(@NotNull Runnable test) { + var key = Registry.get("python.type.any"); + var previousValue = key.asBoolean(); + try { + key.setValue(true); + test.run(); + } + finally { + key.setValue(previousValue); + } + } }