Fixed bug in synthesized __new__ method for NamedTuple class that caused the constructor of subclasses of the NamedTuple to construct the base class.

This commit is contained in:
Eric Traut 2021-09-22 00:39:00 -07:00
parent 8057c63bc1
commit d07a64e733
6 changed files with 64 additions and 26 deletions

View File

@ -49,8 +49,10 @@ import {
import {
applySolvedTypeVars,
buildTypeVarMapFromSpecializedClass,
convertToInstance,
isLiteralType,
specializeTupleClass,
synthesizeTypeVarForSelfCls,
} from './typeUtils';
// Validates fields for compatibility with a dataclass and synthesizes
@ -64,6 +66,7 @@ export function synthesizeDataClassMethods(
) {
assert(ClassType.isDataClass(classType));
const classTypeVar = synthesizeTypeVarForSelfCls(classType, /* isClsParam */ true);
const newType = FunctionType.createInstance(
'__new__',
'',
@ -75,16 +78,16 @@ export function synthesizeDataClassMethods(
FunctionType.addParameter(newType, {
category: ParameterCategory.Simple,
name: 'cls',
type: classType,
type: classTypeVar,
hasDeclaredType: true,
});
FunctionType.addDefaultParameters(newType);
newType.details.declaredReturnType = ClassType.cloneAsInstance(classType);
newType.details.declaredReturnType = convertToInstance(classTypeVar);
const selfParam: FunctionParameter = {
category: ParameterCategory.Simple,
name: 'self',
type: ClassType.cloneAsInstance(classType),
type: synthesizeTypeVarForSelfCls(classType, /* isClsParam */ false),
hasDeclaredType: true,
};
FunctionType.addParameter(initType, selfParam);

View File

@ -42,6 +42,7 @@ import {
isOpenEndedTupleClass,
isTupleClass,
specializeTupleClass,
synthesizeTypeVarForSelfCls,
} from './typeUtils';
// Creates a new custom tuple factory class with named values.
@ -98,6 +99,7 @@ export function createNamedTupleType(
isInstantiableClass(namedTupleType) ? namedTupleType.details.effectiveMetaclass : UnknownType.create()
);
classType.details.baseClasses.push(namedTupleType);
classType.details.typeVarScopeId = evaluator.getScopeIdForNode(errorNode);
const classFields = classType.details.fields;
classFields.set(
@ -105,20 +107,21 @@ export function createNamedTupleType(
Symbol.createWithType(SymbolFlags.ClassMember | SymbolFlags.IgnoredForProtocolMatch, classType)
);
const classTypeVar = synthesizeTypeVarForSelfCls(classType, /* isClsParam */ true);
const constructorType = FunctionType.createInstance(
'__new__',
'',
'',
FunctionTypeFlags.ConstructorMethod | FunctionTypeFlags.SynthesizedMethod
);
constructorType.details.declaredReturnType = ClassType.cloneAsInstance(classType);
constructorType.details.declaredReturnType = convertToInstance(classTypeVar);
if (ParseTreeUtils.isAssignmentToDefaultsFollowingNamedTuple(errorNode)) {
constructorType.details.flags |= FunctionTypeFlags.DisableDefaultChecks;
}
FunctionType.addParameter(constructorType, {
category: ParameterCategory.Simple,
name: 'cls',
type: classType,
type: classTypeVar,
hasDeclaredType: true,
});
@ -127,7 +130,7 @@ export function createNamedTupleType(
const selfParameter: FunctionParameter = {
category: ParameterCategory.Simple,
name: 'self',
type: ClassType.cloneAsInstance(classType),
type: synthesizeTypeVarForSelfCls(classType, /* isClsParam */ false),
hasDeclaredType: true,
};

View File

@ -258,12 +258,12 @@ import {
removeTruthinessFromType,
requiresSpecialization,
requiresTypeArguments,
selfSpecializeClassType,
setTypeArgumentsRecursive,
specializeClassType,
specializeForBaseClass,
specializeTupleClass,
stripLiteralValue,
synthesizeTypeVarForSelfCls,
transformExpectedTypeForConstructor,
transformPossibleRecursiveTypeAlias,
} from './typeUtils';
@ -12569,7 +12569,6 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
const inferredParamType = inferFirstParamType(
functionType.details.flags,
containingClassType,
node
);
if (inferredParamType) {
functionType.details.parameters[0].type = inferredParamType;
@ -12680,26 +12679,12 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
function inferFirstParamType(
flags: FunctionTypeFlags,
containingClassType: ClassType,
functionNode: FunctionNode
): Type | undefined {
if ((flags & FunctionTypeFlags.StaticMethod) === 0) {
if (containingClassType) {
const hasClsParam = flags & (FunctionTypeFlags.ClassMethod | FunctionTypeFlags.ConstructorMethod);
const selfType = TypeVarType.createInstance(`__type_of_self_${containingClassType.details.name}`);
const scopeId = getScopeIdForNode(functionNode);
selfType.details.isSynthesized = true;
selfType.details.isSynthesizedSelfCls = true;
selfType.nameWithScope = TypeVarType.makeNameWithScope(selfType.details.name, scopeId);
selfType.scopeId = scopeId;
// The self/cls parameter is allowed to skip the abstract class test
// because the caller is possibly passing in a non-abstract subclass.
selfType.details.boundType = ClassType.cloneAsInstance(
selfSpecializeClassType(containingClassType, /* includeSubclasses */ true)
);
return hasClsParam ? convertToInstantiable(selfType) : selfType;
const hasClsParam =
(flags & (FunctionTypeFlags.ClassMethod | FunctionTypeFlags.ConstructorMethod)) !== 0;
return synthesizeTypeVarForSelfCls(containingClassType, hasClsParam);
}
}
@ -14324,7 +14309,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
const functionFlags = getFunctionFlagsFromDecorators(functionNode, /* isInClass */ true);
// If the first parameter doesn't have an explicit type annotation,
// provide a type if it's an instance, class or constructor method.
const inferredParamType = inferFirstParamType(functionFlags, classInfo.classType, functionNode);
const inferredParamType = inferFirstParamType(functionFlags, classInfo.classType);
writeTypeCache(node.name!, inferredParamType || UnknownType.create(), /* isIncomplete */ false);
return;
}

View File

@ -1303,6 +1303,23 @@ export function removeTruthinessFromType(type: Type): Type {
});
}
export function synthesizeTypeVarForSelfCls(classType: ClassType, isClsParam: boolean) {
const selfType = TypeVarType.createInstance(`__type_of_${isClsParam ? 'cls' : 'self'}_${classType.details.name}`);
const scopeId = getTypeVarScopeId(classType) ?? '';
selfType.details.isSynthesized = true;
selfType.details.isSynthesizedSelfCls = true;
selfType.nameWithScope = TypeVarType.makeNameWithScope(selfType.details.name, scopeId);
selfType.scopeId = scopeId;
// The self/cls parameter is allowed to skip the abstract class test
// because the caller is possibly passing in a non-abstract subclass.
selfType.details.boundType = ClassType.cloneAsInstance(
selfSpecializeClassType(classType, /* includeSubclasses */ true)
);
return isClsParam ? convertToInstantiable(selfType) : selfType;
}
// Returns the declared yield type if provided, or undefined otherwise.
export function getDeclaredGeneratorYieldType(functionType: FunctionType): Type | undefined {
const returnType = FunctionType.getSpecializedReturnType(functionType);

View File

@ -0,0 +1,24 @@
# This sample tests the case where a class derives from a named tuple.
# The synthesized __new__ method should be able to handle this.
from collections import namedtuple
from typing import Literal, NamedTuple
Class1 = namedtuple("Class1", "name")
class Class2(Class1):
some_class_member = 1
t1: Literal["Class2"] = reveal_type(Class2(name="a"))
Class3 = NamedTuple("Class3", [("name", str)])
class Class4(Class3):
some_class_member = 1
t2: Literal["Class4"] = reveal_type(Class4(name="a"))

View File

@ -980,6 +980,12 @@ test('NamedTuples3', () => {
TestUtils.validateResults(analysisResults, 2);
});
test('NamedTuples4', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['namedTuples4.py']);
TestUtils.validateResults(analysisResults, 0);
});
test('Slots1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['slots1.py']);