mirror of
https://github.com/microsoft/pyright.git
synced 2024-09-11 07:55:56 +03:00
Fixed bug that results in inconsistent type narrowing on assignment based on whether the assignment occurs within the same statement that includes the (declared) type annotation for the variable and whether the type annotation is provided as a type comment. This addresses #7537. (#7638)
This commit is contained in:
parent
e65ac3854d
commit
d16990abc1
@ -2210,6 +2210,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
classType: memberInfo.classType,
|
||||
isIncomplete: !!memberInfo.isTypeIncomplete,
|
||||
isAsymmetricAccessor: memberInfo.isAsymmetricAccessor,
|
||||
narrowedTypeForSet: memberInfo.narrowedTypeForSet,
|
||||
memberAccessDeprecationInfo: memberInfo.memberAccessDeprecationInfo,
|
||||
typeErrors: memberInfo.isDescriptorError,
|
||||
};
|
||||
@ -3449,7 +3450,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
}
|
||||
|
||||
const resultToCache: TypeResult = {
|
||||
type,
|
||||
type: setTypeResult.narrowedTypeForSet ?? type,
|
||||
isIncomplete: isTypeIncomplete,
|
||||
memberAccessDeprecationInfo: setTypeResult.memberAccessDeprecationInfo,
|
||||
};
|
||||
@ -5195,6 +5196,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
let diag = new DiagnosticAddendum();
|
||||
const fileInfo = AnalyzerNodeInfo.getFileInfo(node);
|
||||
let type: Type | undefined;
|
||||
let narrowedTypeForSet: Type | undefined;
|
||||
let isIncomplete = !!baseTypeResult.isIncomplete;
|
||||
let isAsymmetricAccessor: boolean | undefined;
|
||||
const isRequired = false;
|
||||
@ -5324,6 +5326,14 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
/* skipSelfCondition */ true
|
||||
);
|
||||
|
||||
if (typeResult.narrowedTypeForSet) {
|
||||
narrowedTypeForSet = addConditionToType(
|
||||
typeResult.narrowedTypeForSet,
|
||||
getTypeCondition(baseType),
|
||||
/* skipSelfCondition */ true
|
||||
);
|
||||
}
|
||||
|
||||
if (typeResult.isIncomplete) {
|
||||
isIncomplete = true;
|
||||
}
|
||||
@ -5565,7 +5575,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
reportUseOfTypeCheckOnly(type, node.memberName);
|
||||
}
|
||||
|
||||
return { type, isIncomplete, isAsymmetricAccessor, isRequired, isNotRequired, memberAccessDeprecationInfo };
|
||||
return {
|
||||
type,
|
||||
isIncomplete,
|
||||
isAsymmetricAccessor,
|
||||
narrowedTypeForSet,
|
||||
isRequired,
|
||||
isNotRequired,
|
||||
memberAccessDeprecationInfo,
|
||||
};
|
||||
}
|
||||
|
||||
function getTypeOfClassMemberName(
|
||||
@ -5615,6 +5633,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
|
||||
let type: Type | undefined;
|
||||
let isTypeIncomplete = false;
|
||||
let narrowedTypeForSet: Type | undefined;
|
||||
|
||||
if (memberInfo.symbol.isInitVar()) {
|
||||
diag?.addMessage(LocAddendum.memberIsInitVar().format({ name: memberName }));
|
||||
@ -5830,6 +5849,15 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
});
|
||||
|
||||
if (!isDescriptorError && usage.method === 'set' && usage.setType) {
|
||||
if (errorNode && memberInfo.symbol.hasTypedDeclarations()) {
|
||||
// This is an assignment to a member with a declared type. Apply
|
||||
// narrowing logic based on the assigned type. Skip this for
|
||||
// descriptor-based accesses.
|
||||
narrowedTypeForSet = isDescriptorApplied
|
||||
? usage.setType.type
|
||||
: narrowTypeBasedOnAssignment(errorNode, type, usage.setType.type);
|
||||
}
|
||||
|
||||
// Verify that the assigned type is compatible.
|
||||
if (!assignType(type, usage.setType.type, diag?.createAddendum())) {
|
||||
if (!usage.setType.isIncomplete) {
|
||||
@ -5869,6 +5897,7 @@ export function createTypeEvaluator(importLookup: ImportLookup, evaluatorOptions
|
||||
isClassVar: memberInfo.isClassVar,
|
||||
classType: memberInfo.classType,
|
||||
isAsymmetricAccessor,
|
||||
narrowedTypeForSet,
|
||||
memberAccessDeprecationInfo,
|
||||
};
|
||||
}
|
||||
|
@ -205,6 +205,10 @@ export interface TypeResult<T extends Type = Type> {
|
||||
// corresponding __getattr__?
|
||||
isAsymmetricAccessor?: boolean;
|
||||
|
||||
// For member access operations that are 'set', this is the narrowed
|
||||
// type when considering the declared type of the member.
|
||||
narrowedTypeForSet?: Type | undefined;
|
||||
|
||||
// Is the type wrapped in a "Required", "NotRequired" or "ReadOnly" class?
|
||||
isRequired?: boolean;
|
||||
isNotRequired?: boolean;
|
||||
@ -412,6 +416,10 @@ export interface ClassMemberLookup {
|
||||
// to __get__ and __set__ types?
|
||||
isAsymmetricAccessor: boolean;
|
||||
|
||||
// For member access operations that are 'set', this is the narrowed
|
||||
// type when considering the declared type of the member.
|
||||
narrowedTypeForSet?: Type;
|
||||
|
||||
// Deprecation messages related to magic methods invoked via the member access.
|
||||
memberAccessDeprecationInfo?: MemberAccessDeprecationInfo;
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
# This sample tests the type checker's handling of
|
||||
# member assignments.
|
||||
|
||||
from not_present import NotPresentClass # type: ignore
|
||||
|
||||
class Foo:
|
||||
|
||||
class ClassA:
|
||||
def __init__(self):
|
||||
self.string_list: list[str] = []
|
||||
|
||||
@ -10,7 +12,7 @@ class Foo:
|
||||
return ""
|
||||
|
||||
|
||||
a = Foo()
|
||||
a = ClassA()
|
||||
|
||||
a.string_list = ["yep"]
|
||||
|
||||
@ -46,13 +48,25 @@ a.do_something = lambda: "hello"
|
||||
a.do_something = lambda x: 1
|
||||
|
||||
|
||||
Foo.do_something = patch2
|
||||
ClassA.do_something = patch2
|
||||
|
||||
# This should generate an error because of a param count mismatch
|
||||
Foo.do_something = patch1
|
||||
ClassA.do_something = patch1
|
||||
|
||||
|
||||
class Class1:
|
||||
class ClassB:
|
||||
# This should generate an error because assignment expressions
|
||||
# can't be used within a class.
|
||||
[(j := i) for i in range(5)]
|
||||
|
||||
|
||||
class ClassC:
|
||||
def __init__(self):
|
||||
self.x = NotPresentClass # type: int
|
||||
self.y: int
|
||||
self.y = NotPresentClass
|
||||
self.z: int = NotPresentClass
|
||||
|
||||
reveal_type(self.x, expected_text="Unknown | int")
|
||||
reveal_type(self.y, expected_text="Unknown | int")
|
||||
reveal_type(self.z, expected_text="Unknown | int")
|
||||
|
Loading…
Reference in New Issue
Block a user