mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-78119 Introduce PyNeverType
PyNeverType is no longer child of PyUnionType, it was confusing. Fixed failing conformance tests GitOrigin-RevId: d05a425fa02e1647b82017a74b669fa3e1518354
This commit is contained in:
committed by
intellij-monorepo-bot
parent
99817d899d
commit
97cf7227da
@@ -0,0 +1,43 @@
|
||||
// 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.types
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.ProcessingContext
|
||||
import com.jetbrains.python.psi.AccessDirection
|
||||
import com.jetbrains.python.psi.PyExpression
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.resolve.RatedResolveResult
|
||||
|
||||
class PyNeverType private constructor(private val name: String): PyType {
|
||||
companion object {
|
||||
@JvmField val NEVER: PyNeverType = PyNeverType("Never")
|
||||
@JvmField val NO_RETURN: PyNeverType = PyNeverType("NoReturn")
|
||||
|
||||
@JvmStatic
|
||||
fun PyType?.toNoReturnIfNeeded(): PyType? = if (this === NEVER) NO_RETURN else this
|
||||
}
|
||||
|
||||
override fun getName(): String = name
|
||||
override fun isBuiltin(): Boolean = true
|
||||
override fun assertValid(message: String?) {}
|
||||
|
||||
override fun equals(other: Any?): Boolean = other is PyNeverType
|
||||
override fun hashCode(): Int = name.hashCode()
|
||||
|
||||
override fun resolveMember(
|
||||
name: String,
|
||||
location: PyExpression?,
|
||||
direction: AccessDirection,
|
||||
resolveContext: PyResolveContext,
|
||||
): List<RatedResolveResult> = emptyList()
|
||||
|
||||
override fun getCompletionVariants(
|
||||
completionPrefix: String?,
|
||||
location: PsiElement?,
|
||||
context: ProcessingContext?,
|
||||
): Array<Any> = emptyArray()
|
||||
|
||||
override fun <T> acceptTypeVisitor(visitor: PyTypeVisitor<T>): T {
|
||||
return visitor.visitPyNeverType(this)
|
||||
}
|
||||
}
|
||||
@@ -74,6 +74,10 @@ public abstract class PyTypeVisitor<T> {
|
||||
public T visitPyCallableParameterListType(@NotNull PyCallableParameterListType callableParameterListType) {
|
||||
return visitPyType(callableParameterListType);
|
||||
}
|
||||
|
||||
public T visitPyNeverType(@NotNull PyNeverType neverType) {
|
||||
return visitPyType(neverType);
|
||||
}
|
||||
|
||||
public T visitUnknownType() {
|
||||
return null;
|
||||
|
||||
+2
-2
@@ -2,10 +2,10 @@ package com.jetbrains.python.codeInsight.controlflow
|
||||
|
||||
import com.intellij.codeInsight.controlflow.ControlFlowBuilder
|
||||
import com.intellij.codeInsight.controlflow.impl.InstructionImpl
|
||||
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider
|
||||
import com.jetbrains.python.psi.PyCallExpression
|
||||
import com.jetbrains.python.psi.PyFunction
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext
|
||||
import com.jetbrains.python.psi.types.PyNeverType
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext
|
||||
|
||||
class CallInstruction(builder: ControlFlowBuilder, call: PyCallExpression) : InstructionImpl(builder, call) {
|
||||
@@ -18,7 +18,7 @@ class CallInstruction(builder: ControlFlowBuilder, call: PyCallExpression) : Ins
|
||||
if (callees.size == 1) {
|
||||
val pyFunction = callees.single()
|
||||
if (pyFunction is PyFunction) {
|
||||
return PyTypingTypeProvider.isNoReturn(pyFunction, context)
|
||||
return context.getReturnType(pyFunction) is PyNeverType
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
+3
-3
@@ -219,7 +219,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
return Ref.create(excludeFromUnion(unionType, suggested, context));
|
||||
}
|
||||
if (match(suggested, initial, context)) {
|
||||
return Ref.create(PyNeverType.INSTANCE);
|
||||
return Ref.create(PyNeverType.NEVER);
|
||||
}
|
||||
Ref<@Nullable PyType> diff = trySubtract(initial, suggested, context);
|
||||
return diff != null ? diff : Ref.create(initial);
|
||||
@@ -240,7 +240,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
}
|
||||
}
|
||||
if (members.isEmpty()) {
|
||||
return PyNeverType.INSTANCE;
|
||||
return PyNeverType.NEVER;
|
||||
}
|
||||
return PyUnionType.union(members);
|
||||
}
|
||||
@@ -261,7 +261,7 @@ public class PyTypeAssertionEvaluator extends PyRecursiveElementVisitor {
|
||||
List<PyLiteralType> enumMembers = PyStdlibTypeProvider.getEnumMembers(classType1.getPyClass(), context).toList();
|
||||
List<PyType> filteredEnumMembers = ContainerUtil.filter(enumMembers, m -> !PyTypeChecker.match(type2, m, context));
|
||||
if (filteredEnumMembers.isEmpty()) {
|
||||
return Ref.create(PyNeverType.INSTANCE);
|
||||
return Ref.create(PyNeverType.NEVER);
|
||||
}
|
||||
PyType type = enumMembers.size() == filteredEnumMembers.size() ? type1 : PyUnionType.union(filteredEnumMembers);
|
||||
return Ref.create(type);
|
||||
|
||||
+6
@@ -321,6 +321,12 @@ public final class PyTypeHintGenerationUtil {
|
||||
return Traversal.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Traversal visitPyNeverType(@NotNull PyNeverType neverType) {
|
||||
typingTypes.add(neverType.getName());
|
||||
return Traversal.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Traversal visitPyNamedTupleType(@NotNull PyNamedTupleType namedTupleType) {
|
||||
final PyQualifiedNameOwner element = namedTupleType.getDeclarationElement();
|
||||
|
||||
+5
-8
@@ -1270,12 +1270,6 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
|
||||
typeHintedWithName(owner, context, CLASS_VAR));
|
||||
}
|
||||
|
||||
public static boolean isNoReturn(@NotNull PyFunction function, @NotNull TypeEvalContext context) {
|
||||
return PyUtil.getParameterizedCachedValue(function, context, p ->
|
||||
typeHintedWithName(function, context, NO_RETURN, NO_RETURN_EXT, NEVER, NEVER_EXT));
|
||||
}
|
||||
|
||||
|
||||
private static boolean resolvesToQualifiedNames(@NotNull PyExpression expression, @NotNull TypeEvalContext context, String... names) {
|
||||
final var qualifiedNames = resolveToQualifiedNames(expression, context);
|
||||
return ContainerUtil.exists(names, qualifiedNames::contains);
|
||||
@@ -1465,8 +1459,11 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
|
||||
private static @Nullable PyType getNeverType(@NotNull PsiElement element) {
|
||||
var qName = getQualifiedName(element);
|
||||
if (qName == null) return null;
|
||||
if (List.of(NEVER, NEVER_EXT, NO_RETURN, NO_RETURN_EXT).contains(qName)) {
|
||||
return PyNeverType.INSTANCE;
|
||||
if (List.of(NEVER, NEVER_EXT).contains(qName)) {
|
||||
return PyNeverType.NEVER;
|
||||
}
|
||||
if (List.of(NO_RETURN, NO_RETURN_EXT).contains(qName)) {
|
||||
return PyNeverType.NO_RETURN;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -229,6 +229,11 @@ public abstract class PyTypeRenderer extends PyTypeVisitorExt<@NotNull HtmlChunk
|
||||
return result.toFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull HtmlChunk visitPyNeverType(@NotNull PyNeverType neverType) {
|
||||
return className(neverType.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HtmlChunk visitPyUnionType(@NotNull PyUnionType unionType) {
|
||||
// TODO Exclude "Unknown" once it's introduced, don't exclude explicit typing.Any
|
||||
|
||||
+3
-2
@@ -333,14 +333,15 @@ public class PyTypeCheckerInspection extends PyInspection {
|
||||
}
|
||||
}
|
||||
|
||||
if (PyUtil.isInitMethod(node) && !(returnsNone || PyTypingTypeProvider.isNoReturn(node, myTypeEvalContext))) {
|
||||
final PyType annotatedType = myTypeEvalContext.getReturnType(node);
|
||||
|
||||
if (PyUtil.isInitMethod(node) && !(returnsNone || annotatedType instanceof PyNeverType)) {
|
||||
registerProblem(annotation != null ? annotation.getValue() : node.getTypeComment(),
|
||||
PyPsiBundle.message("INSP.type.checker.init.should.return.none"));
|
||||
}
|
||||
|
||||
if (node.isGenerator()) {
|
||||
boolean shouldBeAsync = node.isAsync() && node.isAsyncAllowed();
|
||||
final PyType annotatedType = myTypeEvalContext.getReturnType(node);
|
||||
final var generatorDesc = GeneratorTypeDescriptor.create(annotatedType);
|
||||
if (generatorDesc != null && generatorDesc.isAsync() != shouldBeAsync) {
|
||||
final PyType inferredType = node.getInferredReturnType(myTypeEvalContext);
|
||||
|
||||
@@ -844,7 +844,7 @@ private fun PyClassType.resolveConstructors(callSite: PyCallSiteExpression?, res
|
||||
val callType = if (callSite != null) callableType.getCallType(context, callSite) else callableType.getReturnType(context)
|
||||
val expectedType = toInstance()
|
||||
PyTypeUtil.toStream(callType).anyMatch {
|
||||
it == null || !PyTypeChecker.match(expectedType, it, context)
|
||||
it == null || it is PyNeverType || !PyTypeChecker.match(expectedType, it, context)
|
||||
}
|
||||
}
|
||||
else false
|
||||
|
||||
@@ -125,7 +125,7 @@ public class PyCapturePatternImpl extends PyElementImpl implements PyCapturePatt
|
||||
|
||||
PyType mappingType = PyTypeUtil.convertToType(type, "typing.Mapping", pattern, context);
|
||||
if (mappingType == null) {
|
||||
return PyNeverType.INSTANCE;
|
||||
return PyNeverType.NEVER;
|
||||
}
|
||||
else if (mappingType instanceof PyCollectionType collectionType) {
|
||||
return collectionType.getElementTypes().get(1);
|
||||
|
||||
@@ -172,6 +172,7 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
|
||||
inferredType = returnType;
|
||||
}
|
||||
}
|
||||
inferredType = PyNeverType.toNoReturnIfNeeded(inferredType);
|
||||
return PyTypingTypeProvider.removeNarrowedTypeIfNeeded(PyTypingTypeProvider.toAsyncIfNeeded(this, inferredType));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.jetbrains.python.psi.types
|
||||
|
||||
object PyNeverType: PyUnionType(LinkedHashSet()) {
|
||||
override fun getName(): String = "Never"
|
||||
}
|
||||
@@ -113,7 +113,7 @@ public class PyTupleType extends PyClassTypeImpl implements PyCollectionType {
|
||||
return type;
|
||||
}
|
||||
});
|
||||
return PyUnionType.union(unpackedTypes);
|
||||
return PyUnionType.unionOrNever(unpackedTypes);
|
||||
}
|
||||
|
||||
public @NotNull PyUnpackedTupleType asUnpackedTupleType() {
|
||||
|
||||
@@ -113,14 +113,6 @@ public final class PyTypeChecker {
|
||||
}
|
||||
}
|
||||
|
||||
if (actual instanceof PyNeverType) {
|
||||
return Optional.of(true);
|
||||
}
|
||||
|
||||
if (expected instanceof PyNeverType) {
|
||||
return Optional.of(false);
|
||||
}
|
||||
|
||||
if (expected instanceof PyClassType) {
|
||||
Optional<Boolean> match = matchObject((PyClassType)expected, actual);
|
||||
if (match.isPresent()) {
|
||||
@@ -172,6 +164,14 @@ public final class PyTypeChecker {
|
||||
return Optional.of(match(callableParameterListType, actual, context));
|
||||
}
|
||||
|
||||
if (actual instanceof PyNeverType) {
|
||||
return Optional.of(true);
|
||||
}
|
||||
|
||||
if (expected instanceof PyNeverType) {
|
||||
return Optional.of(false);
|
||||
}
|
||||
|
||||
if (actual instanceof PyUnionType) {
|
||||
return Optional.of(match(expected, (PyUnionType)actual, context));
|
||||
}
|
||||
|
||||
@@ -86,23 +86,44 @@ public class PyUnionType implements PyType {
|
||||
return union(Arrays.asList(type1, type2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a union of the given types.
|
||||
* <p>
|
||||
* If the resulting union would be empty, returns {@code null} (representing Any type).
|
||||
* Consider using {@link #unionOrNever} instead, which falls back to {@link PyNeverType#NEVER}.
|
||||
*
|
||||
* @param members a collection of types to union
|
||||
* @return a PyType representing the union, or null if no valid members
|
||||
*/
|
||||
public static @Nullable PyType union(@NotNull Collection<@Nullable PyType> members) {
|
||||
if (members.size() < 2) {
|
||||
return ContainerUtil.getFirstItem(members);
|
||||
}
|
||||
else {
|
||||
final LinkedHashSet<PyType> newMembers = new LinkedHashSet<>();
|
||||
for (PyType member : members) {
|
||||
if (member instanceof PyUnionType) {
|
||||
newMembers.addAll(((PyUnionType)member).getMembers());
|
||||
}
|
||||
else {
|
||||
newMembers.add(member);
|
||||
}
|
||||
}
|
||||
return unionOrDefault(members, null);
|
||||
}
|
||||
|
||||
return newMembers.size() < 2 ? ContainerUtil.getFirstItem(newMembers) : new PyUnionType(newMembers);
|
||||
/**
|
||||
* Constructs a union of the given types, falling back to {@link PyNeverType#NEVER} instead of null (Any).
|
||||
*
|
||||
* @param members a collection of types to union
|
||||
* @return a PyType representing the union, or {@link PyNeverType#NEVER} if no valid members
|
||||
*/
|
||||
public static @Nullable PyType unionOrNever(@NotNull Collection<@Nullable PyType> members) {
|
||||
return unionOrDefault(members, PyNeverType.NEVER);
|
||||
}
|
||||
|
||||
private static @Nullable PyType unionOrDefault(@NotNull Collection<@Nullable PyType> members, @Nullable PyType defaultResult) {
|
||||
final LinkedHashSet<PyType> newMembers = new LinkedHashSet<>();
|
||||
for (PyType member : members) {
|
||||
if (member instanceof PyNeverType) {
|
||||
defaultResult = PyNeverType.NEVER;
|
||||
continue;
|
||||
}
|
||||
if (member instanceof PyUnionType) {
|
||||
newMembers.addAll(((PyUnionType)member).getMembers());
|
||||
}
|
||||
else {
|
||||
newMembers.add(member);
|
||||
}
|
||||
}
|
||||
return newMembers.size() < 2 ? ContainerUtil.getFirstItem(newMembers, defaultResult) : new PyUnionType(newMembers);
|
||||
}
|
||||
|
||||
public static @Nullable PyType createWeakType(@Nullable PyType type) {
|
||||
@@ -140,8 +161,8 @@ public class PyUnionType implements PyType {
|
||||
/**
|
||||
* Excludes all subtypes of type from the union
|
||||
*
|
||||
* @param type type to exclude. If type is a union all subtypes of union members will be excluded from the union
|
||||
* If type is null only null will be excluded from the union.
|
||||
* @param type type to exclude. If type is a union all subtypes of union members will be excluded from the union
|
||||
* If type is null only null will be excluded from the union.
|
||||
* @return union with excluded types
|
||||
*/
|
||||
public @Nullable PyType exclude(@Nullable PyType type, @NotNull TypeEvalContext context) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><div class="bottom"><icon src="AllIcons.Nodes.Package"/> <code><a href="psi_element://#module#typing">typing</a></code></div><div class="definition"><pre><span style="color:#000000;">Never</span><span style="">: </span><span style="color:#000000;"><a href="psi_element://#typename#typing._SpecialForm">_SpecialForm</a></span></pre></div></body></html>
|
||||
@@ -0,0 +1,3 @@
|
||||
from typing import Never
|
||||
|
||||
x: Nev<the_ref>er
|
||||
@@ -14,7 +14,6 @@ classes_override.py
|
||||
constructors_call_init.py
|
||||
constructors_call_new.py
|
||||
constructors_call_type.py
|
||||
constructors_call_metaclass.py
|
||||
constructors_callable.py
|
||||
dataclasses_hash.py
|
||||
dataclasses_inheritance.py
|
||||
@@ -64,5 +63,4 @@ specialtypes_promotions.py
|
||||
specialtypes_type.py
|
||||
tuples_type_form.py
|
||||
tuples_unpacked.py
|
||||
tuples_type_compat.py
|
||||
typeddicts_readonly_inheritance.py
|
||||
@@ -846,6 +846,11 @@ public class Py3QuickDocTest extends LightMarkedTestCase {
|
||||
public void testTypeAliasStatement() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
// PY-78119
|
||||
public void testNeverType() {
|
||||
checkHTMLOnly();
|
||||
}
|
||||
|
||||
// PY-23067
|
||||
public void testFunctoolsWraps() {
|
||||
|
||||
Reference in New Issue
Block a user