mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 16:06:39 +03:00
Fixed bug that resulted in a false negative when a TypedDict value was assigned to a protocol that included attributes that matched the TypedDict keys.
This commit is contained in:
parent
b0c32438e7
commit
047c4d0601
@ -532,6 +532,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
let boolClassType: Type | undefined;
|
||||
let strClassType: Type | undefined;
|
||||
let dictClassType: Type | undefined;
|
||||
let typedDictClassType: Type | undefined;
|
||||
let incompleteTypeCache: TypeCache | undefined;
|
||||
|
||||
const returnTypeInferenceContextStack: ReturnTypeInferenceContext[] = [];
|
||||
@ -745,6 +746,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
boolClassType = getBuiltInType(node, 'bool');
|
||||
strClassType = getBuiltInType(node, 'str');
|
||||
dictClassType = getBuiltInType(node, 'dict');
|
||||
typedDictClassType = getTypingType(node, '_TypedDict');
|
||||
}
|
||||
}
|
||||
|
||||
@ -17379,6 +17381,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
);
|
||||
const genericDestTypeVarMap = new TypeVarMap(getTypeVarScopeId(destType));
|
||||
|
||||
// If the source is a TypedDict, use the _TypedDict placeholder class
|
||||
// instead. We don't want to use the TypedDict members for protocol
|
||||
// comparison.
|
||||
if (ClassType.isTypedDictClass(srcType)) {
|
||||
if (typedDictClassType && isInstantiableClass(typedDictClassType)) {
|
||||
srcType = typedDictClassType;
|
||||
}
|
||||
}
|
||||
|
||||
let typesAreConsistent = true;
|
||||
const srcClassTypeVarMap = buildTypeVarMapFromSpecializedClass(srcType);
|
||||
|
||||
|
53
packages/pyright-internal/src/tests/samples/typedDict15.py
Normal file
53
packages/pyright-internal/src/tests/samples/typedDict15.py
Normal file
@ -0,0 +1,53 @@
|
||||
# This sample tests the type compatibility checks when the source
|
||||
# is a TypedDict and the dest is a protocol.
|
||||
|
||||
from typing import Protocol, TypeVar, TypedDict
|
||||
|
||||
|
||||
class HasName(Protocol):
|
||||
name: str
|
||||
|
||||
|
||||
class SupportsClear(Protocol):
|
||||
def clear(self) -> None:
|
||||
...
|
||||
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
class SupportsUpdate(Protocol):
|
||||
def update(self: _T, __m: _T) -> None:
|
||||
...
|
||||
|
||||
|
||||
class B(TypedDict):
|
||||
name: str
|
||||
|
||||
|
||||
def print_name(x: HasName):
|
||||
print(x.name)
|
||||
|
||||
|
||||
my_typed_dict: B = {"name": "my name"}
|
||||
|
||||
# This should generate an error. The "name"
|
||||
# attribute of a TypedDict can't be accessed
|
||||
# through a member access expression.
|
||||
print_name(my_typed_dict)
|
||||
|
||||
|
||||
def do_clear(x: SupportsClear):
|
||||
x.clear()
|
||||
|
||||
|
||||
# This should generate an error. Although a "dict"
|
||||
# class supports clear, a TypedDict does not.
|
||||
do_clear(my_typed_dict)
|
||||
|
||||
|
||||
def do_update(x: SupportsUpdate):
|
||||
x.update(x)
|
||||
|
||||
|
||||
do_update(my_typed_dict)
|
@ -932,3 +932,9 @@ test('TypedDict14', () => {
|
||||
|
||||
TestUtils.validateResults(analysisResults, 1);
|
||||
});
|
||||
|
||||
test('TypedDict15', () => {
|
||||
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['typedDict15.py']);
|
||||
|
||||
TestUtils.validateResults(analysisResults, 2);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user