Removed the NoneType which has historically been used to track None and type[None] within pyright's logic. The new logic now models None like any other class and simply uses ClassType to track values of this type. This eliminates a bunch of special-case logic and edge-case bugs. (#6259)

This commit is contained in:
Eric Traut 2023-10-29 01:24:48 -07:00 committed by GitHub
parent 8c13584b84
commit 05fa7d06c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 107 additions and 196 deletions

View File

@ -57,6 +57,7 @@ export interface AnalyzerFileInfo {
isStubFile: boolean;
isTypingStubFile: boolean;
isTypingExtensionsStubFile: boolean;
isTypeshedStubFile: boolean;
isBuiltInStubFile: boolean;
isInPyTypedPackage: boolean;
ipythonMode: IPythonMode;

View File

@ -3786,17 +3786,15 @@ export class Checker extends ParseTreeWalker {
break;
case TypeCategory.Class:
// If it's a class, make sure that it has not been given explicit
// type arguments. This will result in a TypeError exception.
if (subtype.isTypeArgumentExplicit && !subtype.includeSubclasses) {
if (isNoneInstance(subtype)) {
isSupported = false;
} else if (subtype.isTypeArgumentExplicit && !subtype.includeSubclasses) {
// If it's a class, make sure that it has not been given explicit
// type arguments. This will result in a TypeError exception.
isSupported = false;
}
break;
case TypeCategory.None:
isSupported = TypeBase.isInstantiable(subtype);
break;
case TypeCategory.Function:
if (!TypeBase.isInstantiable(subtype) || subtype.isCallableWithTypeArgs) {
isSupported = false;

View File

@ -710,7 +710,6 @@ export class PackageTypeVerifier {
switch (type.category) {
case TypeCategory.Unbound:
case TypeCategory.Any:
case TypeCategory.None:
case TypeCategory.Never:
case TypeCategory.TypeVar:
break;
@ -1293,7 +1292,6 @@ export class PackageTypeVerifier {
switch (type.category) {
case TypeCategory.Unbound:
case TypeCategory.Any:
case TypeCategory.None:
case TypeCategory.Never:
case TypeCategory.TypeVar:
break;

View File

@ -173,6 +173,10 @@ export class SourceFile {
// special-case handling.
private readonly _isTypingExtensionsStubFile: boolean;
// True if the file is the "_typeshed.pyi" file, which needs special-
// case handling.
private readonly _isTypeshedStubFile: boolean;
// True if the file one of the other built-in stub files
// that require special-case handling: "collections.pyi",
// "dataclasses.pyi", "abc.pyi", "asyncio/coroutines.pyi".
@ -221,6 +225,8 @@ export class SourceFile {
this._isStubFile &&
(this._filePath.endsWith(normalizeSlashes('stdlib/typing.pyi')) || fileName === 'typing_extensions.pyi');
this._isTypingExtensionsStubFile = this._isStubFile && fileName === 'typing_extensions.pyi';
this._isTypeshedStubFile =
this._isStubFile && this._filePath.endsWith(normalizeSlashes('stdlib/_typeshed/__init__.pyi'));
this._isBuiltInStubFile = false;
if (this._isStubFile) {
@ -1217,6 +1223,7 @@ export class SourceFile {
isStubFile: this._isStubFile,
isTypingStubFile: this._isTypingStubFile,
isTypingExtensionsStubFile: this._isTypingExtensionsStubFile,
isTypeshedStubFile: this._isTypeshedStubFile,
isBuiltInStubFile: this._isBuiltInStubFile,
isInPyTypedPackage: this._isThirdPartyPyTypedPresent,
ipythonMode: this._ipythonMode,

View File

@ -79,9 +79,6 @@ export function createTracePrinter(roots: string[]): TracePrinter {
case TypeCategory.Never:
return `Never ${wrap(type.typeAliasInfo?.fullName)}`;
case TypeCategory.None:
return `None ${wrap(type.typeAliasInfo?.fullName)}`;
case TypeCategory.OverloadedFunction:
return `OverloadedFunction [${type.overloads.map((o) => wrap(printType(o), '"')).join(',')}]`;

View File

@ -284,7 +284,6 @@ import {
LiteralValue,
ModuleType,
NeverType,
NoneType,
OverloadedFunctionType,
SignatureWithOffsets,
TupleTypeArgument,
@ -914,8 +913,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
// that occurs when resolving tuple below.
getTypingType(node, 'Collection');
noneClassType = getTypeshedType(node, 'NoneType') || AnyType.create();
noneType = NoneType.createInstance();
noneClassType = getTypeshedType(node, 'NoneType') ?? UnknownType.create();
noneType = isInstantiableClass(noneClassType)
? ClassType.cloneAsInstance(noneClassType)
: UnknownType.create();
tupleClassType = getBuiltInType(node, 'tuple');
boolClassType = getBuiltInType(node, 'bool');
@ -1730,8 +1731,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
case TypeCategory.Unbound:
case TypeCategory.Unknown:
case TypeCategory.Any:
case TypeCategory.Never:
case TypeCategory.None: {
case TypeCategory.Never: {
return true;
}
@ -1828,8 +1828,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return findSubtype(type, (subtype) => canBeTruthy(subtype, recursionCount)) !== undefined;
}
case TypeCategory.Unbound:
case TypeCategory.None: {
case TypeCategory.Unbound: {
return false;
}
@ -1838,6 +1837,10 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
return true;
}
if (isNoneInstance(type)) {
return false;
}
// Check for Tuple[()] (an empty tuple).
if (isTupleClass(type)) {
if (type.tupleTypeArguments && type.tupleTypeArguments!.length === 0) {
@ -5210,23 +5213,6 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
}
function getTypeOfNoneBase(subtype: NoneType) {
if (noneClassType && isInstantiableClass(noneClassType)) {
if (TypeBase.isInstance(subtype)) {
return getTypeOfObjectMember(
node.memberName,
ClassType.cloneAsInstance(noneClassType),
memberName,
usage,
diag
);
} else {
return getTypeOfClassMember(node.memberName, noneClassType, memberName, usage, diag);
}
}
return undefined;
}
if (isParamSpec(baseType) && baseType.paramSpecAccess) {
baseType = makeTopLevelTypeVarsConcrete(baseType);
}
@ -5468,50 +5454,55 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
case TypeCategory.Union: {
type = mapSubtypes(baseType, (subtype) => {
if (isNoneInstance(subtype)) {
const typeResult = getTypeOfNoneBase(subtype);
if (isUnbound(subtype)) {
// Don't do anything if it's unbound. The error will already
// be reported elsewhere.
return undefined;
}
if (isNoneInstance(subtype) && noneType && isClassInstance(noneType)) {
const typeResult = getTypeOfObjectMember(node.memberName, noneType, memberName, usage, diag);
if (typeResult) {
type = addConditionToType(typeResult.type, getTypeCondition(baseType));
if (typeResult.isIncomplete) {
isIncomplete = true;
}
return type;
} else {
if (!isIncomplete) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportOptionalMemberAccess,
DiagnosticRule.reportOptionalMemberAccess,
Localizer.Diagnostic.noneUnknownMember().format({ name: memberName }),
node.memberName
);
}
return undefined;
}
} else if (isUnbound(subtype)) {
// Don't do anything if it's unbound. The error will already
// be reported elsewhere.
if (!isIncomplete) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(node).diagnosticRuleSet.reportOptionalMemberAccess,
DiagnosticRule.reportOptionalMemberAccess,
Localizer.Diagnostic.noneUnknownMember().format({ name: memberName }),
node.memberName
);
}
return undefined;
} else {
const typeResult = getTypeOfMemberAccessWithBaseType(
node,
{
type: subtype,
isIncomplete: baseTypeResult.isIncomplete,
},
usage,
EvaluatorFlags.None
);
if (typeResult.isIncomplete) {
isIncomplete = true;
}
if (typeResult?.memberAccessDeprecationInfo) {
memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo;
}
return typeResult.type;
}
const typeResult = getTypeOfMemberAccessWithBaseType(
node,
{
type: subtype,
isIncomplete: baseTypeResult.isIncomplete,
},
usage,
EvaluatorFlags.None
);
if (typeResult.isIncomplete) {
isIncomplete = true;
}
if (typeResult?.memberAccessDeprecationInfo) {
memberAccessDeprecationInfo = typeResult.memberAccessDeprecationInfo;
}
return typeResult.type;
});
break;
}
@ -5542,17 +5533,6 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
break;
}
case TypeCategory.None: {
const typeResult = getTypeOfNoneBase(baseType);
if (typeResult) {
type = addConditionToType(typeResult.type, getTypeCondition(baseType));
if (typeResult.isIncomplete) {
isIncomplete = true;
}
}
break;
}
default:
diag.addMessage(Localizer.DiagnosticAddendum.typeUnsupported().format({ type: printType(baseType) }));
break;
@ -9296,6 +9276,17 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
}
case TypeCategory.Class: {
if (isNoneInstance(expandedCallType)) {
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportOptionalCall,
DiagnosticRule.reportOptionalCall,
Localizer.Diagnostic.noneNotCallable(),
errorNode
);
return { argumentErrors: true };
}
if (TypeBase.isInstantiable(expandedCallType)) {
return validateCallForInstantiableClass(
errorNode,
@ -9319,34 +9310,6 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
);
}
case TypeCategory.None: {
if (TypeBase.isInstantiable(expandedCallType)) {
if (noneClassType && isInstantiableClass(noneClassType)) {
const callResult = validateCallForInstantiableClass(
errorNode,
argList,
noneClassType,
noneClassType,
skipUnknownArgCheck,
inferenceContext
);
return { ...callResult, returnType: NoneType.createInstance() };
}
return { returnType: NoneType.createInstance() };
}
addDiagnostic(
AnalyzerNodeInfo.getFileInfo(errorNode).diagnosticRuleSet.reportOptionalCall,
DiagnosticRule.reportOptionalCall,
Localizer.Diagnostic.noneNotCallable(),
errorNode
);
return { argumentErrors: true };
}
// TypeVars should have been expanded in most cases,
// but we still need to handle the case of Type[T] where
// T is a constrained type that contains a union. We also
@ -12771,10 +12734,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
let type: Type | undefined;
if (node.constType === KeywordType.None) {
type =
(flags & EvaluatorFlags.ExpectingInstantiableType) !== 0
? NoneType.createType()
: NoneType.createInstance();
type = (flags & EvaluatorFlags.ExpectingInstantiableType) !== 0 ? noneClassType : noneType;
} else if (
node.constType === KeywordType.True ||
node.constType === KeywordType.False ||
@ -14061,9 +14021,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
type = ClassType.cloneAsInstance(
ClassType.cloneForSpecialization(
builtInIteratorType,
isAsync
? [elementType, NoneType.createInstance()]
: [elementType, NoneType.createInstance(), NoneType.createInstance()],
isAsync ? [elementType, getNoneType()] : [elementType, getNoneType(), getNoneType()],
/* isTypeArgumentExplicit */ true
)
);
@ -14470,7 +14428,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
typeArg0Type = UnknownType.create();
}
const optionalType = combineTypes([typeArg0Type, NoneType.createType()]);
const optionalType = combineTypes([typeArg0Type, noneClassType ?? UnknownType.create()]);
if (isUnion(optionalType)) {
TypeBase.setSpecialForm(optionalType);
@ -14536,7 +14494,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
} else if (itemExpr.constType === KeywordType.False) {
type = cloneBuiltinClassWithLiteral(node, 'bool', false);
} else if (itemExpr.constType === KeywordType.None) {
type = NoneType.createType();
type = noneClassType ?? UnknownType.create();
}
} else if (
itemExpr.nodeType === ParseNodeType.UnaryOperation &&
@ -15816,7 +15774,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
scope?.type === ScopeType.Builtin ||
fileInfo.isTypingStubFile ||
fileInfo.isTypingExtensionsStubFile ||
fileInfo.isBuiltInStubFile
fileInfo.isBuiltInStubFile ||
fileInfo.isTypeshedStubFile
) {
classFlags |= ClassTypeFlags.BuiltInClass;

View File

@ -9,6 +9,7 @@
* negative ("else") narrowing cases.
*/
import { assert } from '../common/debug';
import {
ArgumentCategory,
AssignmentExpressionNode,
@ -49,7 +50,6 @@ import {
isTypeVar,
isUnpackedVariadicTypeVar,
maxTypeRecursionCount,
NoneType,
OverloadedFunctionType,
TupleTypeArgument,
Type,
@ -1192,9 +1192,9 @@ function narrowTypeForIsEllipsis(evaluator: TypeEvaluator, type: Type, isPositiv
// that accepts a single class, and a more complex form that accepts a tuple
// of classes (including arbitrarily-nested tuples). This method determines
// which form and returns a list of classes or undefined.
function getIsInstanceClassTypes(argType: Type): (ClassType | TypeVarType | NoneType | FunctionType)[] | undefined {
function getIsInstanceClassTypes(argType: Type): (ClassType | TypeVarType | FunctionType)[] | undefined {
let foundNonClassType = false;
const classTypeList: (ClassType | TypeVarType | NoneType | FunctionType)[] = [];
const classTypeList: (ClassType | TypeVarType | FunctionType)[] = [];
// Create a helper function that returns a list of class types or
// undefined if any of the types are not valid.
@ -1203,6 +1203,7 @@ function getIsInstanceClassTypes(argType: Type): (ClassType | TypeVarType | None
if (isInstantiableClass(subtype) || (isTypeVar(subtype) && TypeBase.isInstantiable(subtype))) {
classTypeList.push(subtype);
} else if (isNoneTypeClass(subtype)) {
assert(isInstantiableClass(subtype));
classTypeList.push(subtype);
} else if (
isFunction(subtype) &&
@ -1309,7 +1310,7 @@ export function isIsinstanceFilterSubclass(
function narrowTypeForIsInstance(
evaluator: TypeEvaluator,
type: Type,
classTypeList: (ClassType | TypeVarType | NoneType | FunctionType)[],
classTypeList: (ClassType | TypeVarType | FunctionType)[],
isInstanceCheck: boolean,
isPositiveTest: boolean,
allowIntersections: boolean,
@ -2484,12 +2485,15 @@ function narrowTypeForCallable(
return isPositiveTest ? subtype : undefined;
}
case TypeCategory.None:
case TypeCategory.Module: {
return isPositiveTest ? undefined : subtype;
}
case TypeCategory.Class: {
if (isNoneInstance(subtype)) {
return isPositiveTest ? undefined : subtype;
}
if (TypeBase.isInstantiable(subtype)) {
return isPositiveTest ? subtype : undefined;
}

View File

@ -713,12 +713,6 @@ function printTypeInternal(
return typeVarName;
}
case TypeCategory.None: {
return `${
TypeBase.isInstantiable(type) ? `${_printNestedInstantiable(type, 'None')}` : 'None'
}${getConditionalIndicator(type)}`;
}
case TypeCategory.Never: {
return type.isNoReturn ? 'NoReturn' : 'Never';
}
@ -852,6 +846,11 @@ function printObjectTypeForClassInternal(
(printTypeFlags & PrintTypeFlags.UseFullyQualifiedNames) !== 0 ? type.details.fullName : type.details.name;
}
// Special-case NoneType to convert it to None.
if (ClassType.isBuiltIn(type, 'NoneType')) {
objName = 'None';
}
// Use the fully-qualified name if the name isn't unique.
if (!uniqueNameMap.isUnique(objName)) {
objName = type.details.fullName;

View File

@ -43,7 +43,6 @@ import {
maxTypeRecursionCount,
ModuleType,
NeverType,
NoneType,
OverloadedFunctionType,
removeFromUnion,
SignatureWithOffsets,
@ -293,12 +292,12 @@ export function isOptionalType(type: Type): boolean {
return false;
}
export function isNoneInstance(type: Type): type is NoneType {
return type.category === TypeCategory.None && TypeBase.isInstance(type);
export function isNoneInstance(type: Type): boolean {
return isClassInstance(type) && ClassType.isBuiltIn(type, 'NoneType');
}
export function isNoneTypeClass(type: Type): type is NoneType {
return type.category === TypeCategory.None && TypeBase.isInstantiable(type);
export function isNoneTypeClass(type: Type): boolean {
return isInstantiableClass(type) && ClassType.isBuiltIn(type, 'NoneType');
}
// If the type is a union, remove an "None" type from the union,
@ -507,7 +506,6 @@ function compareTypes(a: Type, b: Type, recursionCount = 0): number {
case TypeCategory.Unbound:
case TypeCategory.Unknown:
case TypeCategory.Any:
case TypeCategory.None:
case TypeCategory.Never:
case TypeCategory.Union: {
return 0;
@ -594,6 +592,13 @@ function compareTypes(a: Type, b: Type, recursionCount = 0): number {
return 1;
}
// Always sort NoneType at the end.
if (ClassType.isBuiltIn(a, 'NoneType')) {
return 1;
} else if (ClassType.isBuiltIn(bClass, 'NoneType')) {
return -1;
}
// Sort non-generics before generics.
if (a.details.typeParameters.length > 0 || isTupleClass(a)) {
if (bClass.details.typeParameters.length === 0) {
@ -754,9 +759,6 @@ export function getFullNameOfType(type: Type): string | undefined {
case TypeCategory.Unknown:
return 'typing.Any';
case TypeCategory.None:
return 'builtins.None';
case TypeCategory.Class:
return type.details.fullName;
@ -787,7 +789,6 @@ export function addConditionToType(type: Type, condition: TypeCondition[] | unde
case TypeCategory.TypeVar:
return type;
case TypeCategory.None:
case TypeCategory.Function:
return TypeBase.cloneForCondition(type, TypeCondition.combine(type.condition, condition));
@ -816,7 +817,6 @@ export function getTypeCondition(type: Type): TypeCondition[] | undefined {
case TypeCategory.Union:
return undefined;
case TypeCategory.None:
case TypeCategory.Class:
case TypeCategory.Function:
return type.condition;
@ -2255,18 +2255,9 @@ export function convertToInstance(type: Type, includeSubclasses = true): Type {
}
}
// Handle NoneType as a special case.
if (TypeBase.isInstantiable(subtype) && ClassType.isBuiltIn(subtype, 'NoneType')) {
return NoneType.createInstance();
}
return ClassType.cloneAsInstance(subtype, includeSubclasses);
}
case TypeCategory.None: {
return NoneType.createInstance();
}
case TypeCategory.Function: {
if (TypeBase.isInstantiable(subtype)) {
return FunctionType.cloneAsInstance(subtype);
@ -2325,10 +2316,6 @@ export function convertToInstantiable(type: Type, includeSubclasses = true): Typ
return ClassType.cloneAsInstantiable(subtype, includeSubclasses);
}
case TypeCategory.None: {
return NoneType.createType();
}
case TypeCategory.Function: {
return FunctionType.cloneAsInstantiable(subtype);
}

View File

@ -206,7 +206,6 @@ export class TypeVarSignatureContext {
switch (type.category) {
case TypeCategory.Unknown:
case TypeCategory.Any:
case TypeCategory.None:
case TypeCategory.TypeVar: {
return 0.5;
}

View File

@ -15,7 +15,6 @@ import {
FunctionType,
ModuleType,
NeverType,
NoneType,
OverloadedFunctionType,
Type,
TypeCategory,
@ -68,10 +67,6 @@ export class TypeWalker {
this.visitUnknown(type);
break;
case TypeCategory.None:
this.visitNone(type);
break;
case TypeCategory.Never:
this.visitNever(type);
break;
@ -136,10 +131,6 @@ export class TypeWalker {
// Nothing to do.
}
visitNone(type: NoneType): void {
// Nothing to do.
}
visitNever(type: NeverType): void {
// Nothing to do.
}

View File

@ -930,7 +930,7 @@ export function assignTypedDictToTypedDict(
// so we need to make sure the dest entry is compatible with that.
const objType = evaluator.getObjectType();
if (objType && isClassInstance(objType)) {
if (isClassInstance(objType)) {
const subDiag = diag?.createAddendum();
if (
!evaluator.assignType(

View File

@ -24,9 +24,6 @@ export const enum TypeCategory {
// Type can be anything.
Any,
// Special "None" type defined in Python.
None,
// Used in type narrowing to indicate that all possible
// subtypes in a union have been eliminated, and execution
// should never get to this point.
@ -74,7 +71,6 @@ export type UnionableType =
| UnboundType
| UnknownType
| AnyType
| NoneType
| FunctionType
| OverloadedFunctionType
| ClassType
@ -2072,30 +2068,6 @@ export namespace OverloadedFunctionType {
}
}
export interface NoneType extends TypeBase {
category: TypeCategory.None;
}
export namespace NoneType {
const _noneInstance: NoneType = {
category: TypeCategory.None,
flags: TypeFlags.Instance,
};
const _noneType: NoneType = {
category: TypeCategory.None,
flags: TypeFlags.Instantiable,
};
export function createInstance() {
return _noneInstance;
}
export function createType() {
return _noneType;
}
}
export interface NeverType extends TypeBase {
category: TypeCategory.Never;
isNoReturn: boolean;

View File

@ -522,8 +522,6 @@ function getTypeCategoryString(typeCategory: TypeCategory, type: any) {
return 'Unknown';
case TypeCategory.Any:
return 'Any';
case TypeCategory.None:
return 'None';
case TypeCategory.Never:
return 'Never';
case TypeCategory.Function:

View File

@ -122,6 +122,7 @@ export function buildAnalyzerFileInfo(
isTypingStubFile: false,
isInPyTypedPackage: false,
isTypingExtensionsStubFile: false,
isTypeshedStubFile: false,
isBuiltInStubFile: false,
ipythonMode: IPythonMode.None,
accessedSymbolSet: new Set<number>(),