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:
Aleksandr.Govenko
2025-06-03 15:07:07 +00:00
committed by intellij-monorepo-bot
parent 99817d899d
commit 97cf7227da
19 changed files with 129 additions and 49 deletions
@@ -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,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
@@ -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);
@@ -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();
@@ -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
@@ -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) {
+1
View File
@@ -0,0 +1 @@
<html><body><div class="bottom"><icon src="AllIcons.Nodes.Package"/>&nbsp;<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>
+3
View File
@@ -0,0 +1,3 @@
from typing import Never
x: Nev<the_ref>er
-2
View File
@@ -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() {