Skip to content

Commit

Permalink
Fixed a bug that results in incorrect type evaluation behaviors when …
Browse files Browse the repository at this point in the history
…a class has a custom metaclass with a `__call__` method and a `__new__` or `__init__` method that provides a different bidirectional type inference context for parameters. This addresses microsoft#9067. (microsoft#9071)
  • Loading branch information
erictraut authored Sep 24, 2024
1 parent ef263af commit 399d57d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 10 deletions.
34 changes: 24 additions & 10 deletions packages/pyright-internal/src/analyzer/constructors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ export function validateConstructorArgs(
argList,
type,
skipUnknownArgCheck,
inferenceContext
inferenceContext,
/* useSpeculativeModeForArgs */ true
);

if (metaclassResult) {
Expand All @@ -143,6 +144,16 @@ export function validateConstructorArgs(
// overrides the normal `type.__call__` logic and don't perform the usual
// __new__ and __init__ validation.
if (metaclassResult.argumentErrors || shouldSkipNewAndInitEvaluation(evaluator, type, metaclassReturnType)) {
validateMetaclassCall(
evaluator,
errorNode,
argList,
type,
skipUnknownArgCheck,
inferenceContext,
/* useSpeculativeModeForArgs */ false
);

return metaclassResult;
}
}
Expand Down Expand Up @@ -561,22 +572,25 @@ function validateMetaclassCall(
argList: Arg[],
type: ClassType,
skipUnknownArgCheck: boolean | undefined,
inferenceContext: InferenceContext | undefined
inferenceContext: InferenceContext | undefined,
useSpeculativeModeForArgs: boolean
): CallResult | undefined {
const metaclassCallMethodInfo = getBoundCallMethod(evaluator, errorNode, type);

if (!metaclassCallMethodInfo) {
return undefined;
}

const callResult = evaluator.validateCallArgs(
errorNode,
argList,
metaclassCallMethodInfo,
/* constraints */ undefined,
skipUnknownArgCheck,
inferenceContext
);
const callResult = evaluator.useSpeculativeMode(useSpeculativeModeForArgs ? errorNode : undefined, () => {
return evaluator.validateCallArgs(
errorNode,
argList,
metaclassCallMethodInfo,
/* constraints */ undefined,
skipUnknownArgCheck,
inferenceContext
);
});

// If the return type is unannotated, don't use the inferred return type.
const callType = metaclassCallMethodInfo.type;
Expand Down
22 changes: 22 additions & 0 deletions packages/pyright-internal/src/tests/samples/constructor32.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This sample tests the case where a metaclass __call__ method is present
# and supplies a different bidirectional type inference context than
# the __new__ or __init__ methods.

from typing import TypedDict


class TD1(TypedDict):
x: int


class AMeta(type):
def __call__(cls, *args, **kwargs):
super().__call__(*args, **kwargs)


class A(metaclass=AMeta):
def __init__(self, params: TD1):
pass


A({"x": 42})
6 changes: 6 additions & 0 deletions packages/pyright-internal/src/tests/typeEvaluator6.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,12 @@ test('Constructor31', () => {
TestUtils.validateResults(analysisResults, 0);
});

test('Constructor32', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructor32.py']);

TestUtils.validateResults(analysisResults, 0);
});

test('ConstructorCallable1', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['constructorCallable1.py']);

Expand Down

0 comments on commit 399d57d

Please sign in to comment.