Skip to content

Commit

Permalink
Changed behavior of two-argument form of super when it is used outs…
Browse files Browse the repository at this point in the history
…ide of an attribute access expression. It now employs a heuristic whereby it evaluates to the next base class of the bound type. This addresses #6660. (#6662)
  • Loading branch information
erictraut authored Dec 6, 2023
1 parent c93d9eb commit 5cd59ad
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 23 deletions.
99 changes: 76 additions & 23 deletions packages/pyright-internal/src/analyzer/typeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ import {
convertToInstance,
convertToInstantiable,
convertTypeToParamSpecValue,
derivesFromAnyOrUnknown,
derivesFromClassRecursive,
derivesFromStdlibClass,
doForEachSubtype,
Expand Down Expand Up @@ -8300,6 +8301,8 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
Localizer.Diagnostic.superCallSecondArg().format({ type: printType(targetClassType) }),
node.arguments[1].valueExpression
);

return { type: UnknownType.create() };
}
} else if (enclosingClassType) {
bindToType = ClassType.cloneAsInstance(enclosingClassType);
Expand Down Expand Up @@ -8380,38 +8383,88 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
const lookupResults = bindToType
? lookUpClassMember(bindToType, memberName, MemberAccessFlags.Default, effectiveTargetClass)
: undefined;

let resultType: Type;
if (lookupResults && isInstantiableClass(lookupResults.classType)) {
return {
type: resultIsInstance
? ClassType.cloneAsInstance(lookupResults.classType)
: lookupResults.classType,
bindToSelfType: bindToType
? TypeBase.cloneForCondition(
synthesizeTypeVarForSelfCls(bindToType, /* isClsParam */ false),
bindToType.condition
)
: undefined,
};
resultType = lookupResults.classType;
} else if (
effectiveTargetClass &&
!isAnyOrUnknown(effectiveTargetClass) &&
!derivesFromAnyOrUnknown(effectiveTargetClass) &&
objectType &&
isClassInstance(objectType)
) {
resultType = ClassType.cloneAsInstantiable(objectType);
} else {
resultType = UnknownType.create();
}

return {
type: resultIsInstance ? convertToInstance(resultType) : resultType,
bindToSelfType: bindToType
? TypeBase.cloneForCondition(
synthesizeTypeVarForSelfCls(bindToType, /* isClsParam */ false),
bindToType.condition
)
: undefined,
};
}

// If the lookup failed, try to return the first base class. An error
// will be reported by the member lookup logic at a later time.
// Handle the super() call when used outside of a member access expression.
if (isInstantiableClass(targetClassType)) {
// If the class derives from one or more unknown classes,
// return unknown here to prevent spurious errors.
if (targetClassType.details.mro.some((mroBase) => isAnyOrUnknown(mroBase))) {
return { type: UnknownType.create() };
}
// We don't know which member is going to be accessed, so we cannot
// deterministically determine the correct type in this case. We'll
// use a heuristic that produces the "correct" (desired) behavior in
// most cases. If there's a bindToType and the targetClassType is one
// of the base classes of the bindToType, we'll return the next base
// class.
if (bindToType) {
let nextBaseClassType: Type | undefined;

if (ClassType.isSameGenericClass(bindToType, targetClassType)) {
if (bindToType.details.baseClasses.length > 0) {
nextBaseClassType = bindToType.details.baseClasses[0];
}
} else {
const baseClassIndex = bindToType.details.baseClasses.findIndex(
(baseClass) =>
isClass(baseClass) && ClassType.isSameGenericClass(baseClass, targetClassType as ClassType)
);

if (baseClassIndex >= 0 && baseClassIndex < bindToType.details.baseClasses.length - 1) {
nextBaseClassType = bindToType.details.baseClasses[baseClassIndex + 1];
}
}

if (nextBaseClassType) {
if (isInstantiableClass(nextBaseClassType)) {
nextBaseClassType = specializeForBaseClass(bindToType, nextBaseClassType);
}
return { type: resultIsInstance ? convertToInstance(nextBaseClassType) : nextBaseClassType };
}

const baseClasses = targetClassType.details.baseClasses;
if (baseClasses.length > 0) {
const baseClassType = baseClasses[0];
if (isInstantiableClass(baseClassType)) {
// There's not much we can say about the type. Simply return object or type.
if (objectType && isClassInstance(objectType) && typeClassType && isInstantiableClass(typeClassType)) {
return {
type: resultIsInstance ? ClassType.cloneAsInstance(baseClassType) : baseClassType,
type: resultIsInstance ? objectType : convertToInstance(typeClassType),
};
}
} else {
// If the class derives from one or more unknown classes,
// return unknown here to prevent spurious errors.
if (targetClassType.details.mro.some((mroBase) => isAnyOrUnknown(mroBase))) {
return { type: UnknownType.create() };
}

const baseClasses = targetClassType.details.baseClasses;
if (baseClasses.length > 0) {
const baseClassType = baseClasses[0];
if (isInstantiableClass(baseClassType)) {
return {
type: resultIsInstance ? ClassType.cloneAsInstance(baseClassType) : baseClassType,
};
}
}
}
}

Expand Down
38 changes: 38 additions & 0 deletions packages/pyright-internal/src/tests/samples/super7.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# This sample tests the use of super() with two arguments where the second
# argument is an instance.

from typing import Generic, TypeVar


T = TypeVar("T")


class BaseClass:
def my_method(self, value: int) -> int:
Expand Down Expand Up @@ -50,3 +55,36 @@ def staticmethod_super_extra_arg(value: int) -> int:

# This should generate an error.
return super(__class__, self).my_method(self, value)


class A(Generic[T]):
...


class B(Generic[T]):
...


class C(A[int], B[T]):
pass


c = C[str]()
super_obj_c = super(C, c)
reveal_type(super_obj_c, expected_text="A[int]")

super_obj_a = super(A, c)
reveal_type(super_obj_a, expected_text="B[str]")

super_obj_b = super(B, c)
reveal_type(super_obj_b, expected_text="object")


super_cls_c = super(C, C)
reveal_type(super_cls_c, expected_text="A[int]")

super_cls_a = super(A, C)
reveal_type(super_cls_a, expected_text="B[Unknown]")

super_cls_b = super(B, C)
reveal_type(super_cls_b, expected_text="object")

0 comments on commit 5cd59ad

Please sign in to comment.